# HG changeset patch # User Artem Tikhomirov # Date 1306984419 -7200 # Node ID b7347daa50e33f93c836e7d0eafe851015c5f4d9 # Parent 1792b37650f22a878f18e9f286de3b49f1571d61 Allow to cat a file with changeset revision diff -r 1792b37650f2 -r b7347daa50e3 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Wed Jun 01 05:44:25 2011 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Thu Jun 02 05:13:39 2011 +0200 @@ -24,6 +24,7 @@ import java.util.Map; import org.tmatesoft.hg.core.HgLogCommand.FileRevision; +import org.tmatesoft.hg.core.HgCatCommand; import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.HgManifestCommand; import org.tmatesoft.hg.core.Nodeid; @@ -64,7 +65,8 @@ public static void main(String[] args) throws Exception { Main m = new Main(args); - m.testMergeState(); + m.testCatAtCsetRevision(); +// m.testMergeState(); // m.testFileStatus(); // m.dumpBranches(); // m.inflaterLengthException(); @@ -77,6 +79,16 @@ // m.bunchOfTests(); } + // TODO as test in TestCat + private void testCatAtCsetRevision() throws Exception { + HgCatCommand cmd = new HgCatCommand(hgRepo); + cmd.file(Path.create("src/org/tmatesoft/hg/internal/RevlogStream.java")); + cmd.changeset(Nodeid.fromAscii("08db726a0fb7914ac9d27ba26dc8bbf6385a0554")); + final ByteArrayChannel sink = new ByteArrayChannel(); + cmd.execute(sink); + System.out.println(sink.toArray().length); + } + private void testMergeState() throws Exception { final HgMergeState mergeState = hgRepo.getMergeState(); mergeState.refresh(); diff -r 1792b37650f2 -r b7347daa50e3 src/org/tmatesoft/hg/core/HgCatCommand.java --- a/src/org/tmatesoft/hg/core/HgCatCommand.java Wed Jun 01 05:44:25 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgCatCommand.java Thu Jun 02 05:13:39 2011 +0200 @@ -41,6 +41,7 @@ private Path file; private int localRevision = TIP; private Nodeid revision; + private Nodeid cset; public HgCatCommand(HgRepository hgRepo) { repo = hgRepo; @@ -61,7 +62,11 @@ } /** + * Select specific local revision of the file to cat. Note, revision numbering is of particular file, not that of + * repository (i.e. revision 0 means initial content of the file, irrespective of changeset revision at the time of commit) + * * Invocation of this method clears revision set with {@link #revision(Nodeid)} or {@link #revision(int)} earlier. + * * XXX rev can't be WORKING_COPY (if allowed, need to implement in #execute()) * @param rev local revision number, non-negative, or one of predefined constants. Note, use of {@link HgRepository#BAD_REVISION}, * although possible, makes little sense (command would fail if executed). @@ -73,13 +78,16 @@ } localRevision = rev; revision = null; + cset = null; return this; } /** - * Select revision to read. Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier. + * Select revision to read. Note, this revision is file revision (i.e. the one from manifest), not the changeset revision. + * + * Invocation of this method clears revision set with {@link #revision(int)} or {@link #revision(Nodeid)} earlier. * - * @param nodeid - unique revision identifier, Note, use of null or {@link Nodeid#NULL} is senseless + * @param nodeid - unique file revision identifier, Note, use of null or {@link Nodeid#NULL} is senseless * @return this for convenience */ public HgCatCommand revision(Nodeid nodeid) { @@ -88,6 +96,23 @@ } revision = nodeid; localRevision = BAD_REVISION; + cset = null; + return this; + } + + /** + * Select whatever revision of the file that was actual at the time of the specified changeset. Unlike {@link #revision(int)} or {@link #revision(Nodeid)}, this method + * operates in terms of repository global revisions (aka changesets). + * + * Invocation of this method clears local file revisions selection. + * + * @param nodeid changeset revision + * @return this for convenience + */ + public HgCatCommand changeset(Nodeid nodeid) { + localRevision = BAD_REVISION; + revision = null; + cset = nodeid; return this; } @@ -99,8 +124,8 @@ * @throws IllegalArgumentException when command arguments are incomplete or wrong */ public void execute(ByteChannel sink) throws HgDataStreamException, IOException, CancelledException { - if (localRevision == BAD_REVISION && revision == null) { - throw new IllegalArgumentException("Either local file revision number or nodeid shall be specified"); + if (localRevision == BAD_REVISION && revision == null && cset == null) { + throw new IllegalArgumentException("File revision, corresponing local number, or a changset nodeid shall be specified"); } if (file == null) { throw new IllegalArgumentException("Name of the file is missing"); @@ -113,7 +138,25 @@ throw new HgDataStreamException(file, new FileNotFoundException(file.toString())); } int revToExtract; - if (revision != null) { + if (cset != null) { + int csetRev = repo.getChangelog().getLocalRevision(cset); + Nodeid toExtract = null; + do { + toExtract = repo.getManifest().getFileRevision(csetRev, file); + if (toExtract == null) { + if (dataFile.isCopy()) { + file = dataFile.getCopySourceName(); + dataFile = repo.getFileNode(file); + } else { + break; + } + } + } while (toExtract == null); + if (toExtract == null) { + throw new HgBadStateException(String.format("File %s not its origins were not known at repository %s revision", file, cset.shortNotation())); + } + revToExtract = dataFile.getLocalRevision(toExtract); + } else if (revision != null) { revToExtract = dataFile.getLocalRevision(revision); } else { revToExtract = localRevision; diff -r 1792b37650f2 -r b7347daa50e3 src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Wed Jun 01 05:44:25 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Thu Jun 02 05:13:39 2011 +0200 @@ -18,6 +18,7 @@ import static org.tmatesoft.hg.repo.HgRepository.TIP; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -25,9 +26,11 @@ import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; +import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.Lifecycle; import org.tmatesoft.hg.internal.Pool; import org.tmatesoft.hg.internal.RevlogStream; +import org.tmatesoft.hg.util.Path; /** @@ -57,6 +60,7 @@ content.iterate(start0, end0, true, new ManifestParser(inspector)); } + // manifest revision number that corresponds to the given changeset /*package-local*/ int fromChangelog(int revisionNumber) { if (HgInternals.wrongLocalRevision(revisionNumber)) { throw new IllegalArgumentException(String.valueOf(revisionNumber)); @@ -68,6 +72,48 @@ return revisionMap.at(revisionNumber); } + /** + * Extracts file revision as it was known at the time of given changeset. + * + * @param revisionNumber local changeset index + * @param file path to file in question + * @return file revision or null if manifest at specified revision doesn't list such file + */ + @Experimental(reason="Perhaps, HgDataFile shall own this method") + public Nodeid getFileRevision(int revisionNumber, final Path file) { + int rev = fromChangelog(revisionNumber); + final Nodeid[] rv = new Nodeid[] { null }; + content.iterate(rev, rev, true, new RevlogStream.Inspector() { + + public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + try { + byte b; + while (!data.isEmpty() && (b = data.readByte()) != '\n') { + if (b != 0) { + bos.write(b); + } else { + String fname = new String(bos.toByteArray()); + bos.reset(); + if (file.toString().equals(fname)) { + byte[] nid = new byte[40]; + data.readBytes(nid, 0, 40); + rv[0] = Nodeid.fromAscii(nid, 0, 40); + break; + } + // else skip to the end of line + while (!data.isEmpty() && (b = data.readByte()) != '\n') + ; + } + } + } catch (IOException ex) { + throw new HgBadStateException(ex); + } + } + }); + return rv[0]; + } + public interface Inspector { boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision); boolean next(Nodeid nid, String fname, String flags);