# HG changeset patch # User Artem Tikhomirov # Date 1332956077 -7200 # Node ID 063b0663495a4483ddd94aeec337b880d7792c3b # Parent 48f993aa2f41f769e055c46ce4404e0f50d7a4de HgManifest#getFileRevisions refactored into #walkFileRevisions to match pattern throught rest of the library diff -r 48f993aa2f41 -r 063b0663495a cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 28 18:39:29 2012 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Wed Mar 28 19:34:37 2012 +0200 @@ -94,7 +94,8 @@ public static void main(String[] args) throws Exception { Main m = new Main(args); - m.checkSubProgress(); + m.checkWalkFileRevisions(); +// m.checkSubProgress(); // m.checkFileFlags(); // m.buildFileLog(); // m.testConsoleLog(); @@ -118,6 +119,12 @@ // m.bunchOfTests(); } + // hg4j repo + public void checkWalkFileRevisions() throws Exception { + // hg --debug manifest --rev 150 | grep cmdline/org/tmatesoft/hg/console/Main.java + hgRepo.getManifest().walkFileRevisions(Path.create("cmdline/org/tmatesoft/hg/console/Main.java"), new ManifestDump(), 100, 150, 200, 210, 300); + } + // no repo private void checkSubProgress() { ProgressSupport ps = new ProgressSupport() { diff -r 48f993aa2f41 -r 063b0663495a src/org/tmatesoft/hg/repo/HgChangelog.java --- a/src/org/tmatesoft/hg/repo/HgChangelog.java Wed Mar 28 18:39:29 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgChangelog.java Wed Mar 28 19:34:37 2012 +0200 @@ -47,7 +47,7 @@ * @author Artem Tikhomirov * @author TMate Software Ltd. */ -public class HgChangelog extends Revlog { +public final class HgChangelog extends Revlog { /* package-local */HgChangelog(HgRepository hgRepo, RevlogStream content) { super(hgRepo, content); diff -r 48f993aa2f41 -r 063b0663495a src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Mar 28 18:39:29 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Mar 28 19:34:37 2012 +0200 @@ -58,7 +58,7 @@ * @author Artem Tikhomirov * @author TMate Software Ltd. */ -public class HgDataFile extends Revlog { +public final class HgDataFile extends Revlog { // absolute from repo root? // slashes, unix-style? @@ -77,12 +77,15 @@ } // exists is not the best name possible. now it means no file with such name was ever known to the repo. - // it might be confused with files existed before but lately removed. + // it might be confused with files existed before but lately removed. TODO HgFileNode.exists makes more sense. + // or HgDataFile.known() public boolean exists() { return content != null; // XXX need better impl } - // human-readable (i.e. "COPYING", not "store/data/_c_o_p_y_i_n_g.i") + /** + * Human-readable file name, i.e. "COPYING", not "store/data/_c_o_p_y_i_n_g.i" + */ public Path getPath() { return path; // hgRepo.backresolve(this) -> name? In this case, what about hashed long names? } diff -r 48f993aa2f41 -r 063b0663495a src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Wed Mar 28 18:39:29 2012 +0200 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Wed Mar 28 19:34:37 2012 +0200 @@ -23,8 +23,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; import org.tmatesoft.hg.core.HgChangesetFileSneaker; import org.tmatesoft.hg.core.Nodeid; @@ -32,7 +30,6 @@ import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.DigestHelper; import org.tmatesoft.hg.internal.EncodingHelper; -import org.tmatesoft.hg.internal.Experimental; import org.tmatesoft.hg.internal.IntMap; import org.tmatesoft.hg.internal.IterateControlMediator; import org.tmatesoft.hg.internal.Lifecycle; @@ -50,7 +47,7 @@ * @author Artem Tikhomirov * @author TMate Software Ltd. */ -public class HgManifest extends Revlog { +public final class HgManifest extends Revlog { private RevisionMapper revisionMap; private EncodingHelper encodingHelper; @@ -243,8 +240,10 @@ /** * Extracts file revision as it was known at the time of given changeset. - * For more thorough details about file at specific changeset, use {@link HgChangesetFileSneaker}. + *

For more thorough details about file at specific changeset, use {@link HgChangesetFileSneaker}. + *

To visit few changesets for the same file, use {@link #walkFileRevisions(Path, Inspector, int...)} * + * @see #walkFileRevisions(Path, Inspector, int...) * @see HgChangesetFileSneaker * @param changelogRevisionIndex local changeset index * @param file path to file in question @@ -257,21 +256,40 @@ // both file revision and changeset revision index. But there's no easy way to go from changesetRevisionIndex to // file revision (the task this method solves), exept for HgFileInformer // I feel methods dealing with changeset indexes shall be more exposed in HgChangelog and HgManifest API. - return getFileRevisions(file, changelogRevisionIndex).get(changelogRevisionIndex); + // TODO need tests + int manifestRevIndex = fromChangelog(changelogRevisionIndex); + if (manifestRevIndex == BAD_REVISION) { + return null; + } + IntMap resMap = new IntMap(3); + FileLookupInspector parser = new FileLookupInspector(encodingHelper, file, resMap, null); + parser.walk(manifestRevIndex, content); + return resMap.get(changelogRevisionIndex); } - - // XXX package-local or better API - @Experimental(reason="Map as return value isn't that good") - public Map getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException { - // FIXME in fact, walk(Inspectr, path, int[]) might be better alternative than get() - // TODO need tests - int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); - IntMap resMap = new IntMap(changelogRevisionIndexes.length); - content.iterate(manifestRevisionIndexes, true, new FileLookupInspector(encodingHelper, file, resMap, null)); - // IntMap to HashMap, - HashMap rv = new HashMap(); - resMap.fill(rv); - return rv; + + /** + * Visit file revisions as they were recorded at the time of given changesets. Same file revision may be reported as many times as + * there are changesets that refer to that revision. Both {@link Inspector#begin(int, Nodeid, int)} and {@link Inspector#end(int)} + * with appropriate values are invoked around {@link Inspector#next(Nodeid, Path, Flags)} call for the supplied file + * + *

NOTE, this method doesn't respect return values from callback (i.e. to stop iteration), as it's lookup of a single file + * and canceling it seems superfluous. However, this may change in future and it's recommended to return true from + * all {@link Inspector} methods. + * + * @see #getFileRevision(int, Path) + * @param file path of interest + * @param inspector callback to receive details about selected file + * @param changelogRevisionIndexes changeset indexes to visit + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception + */ + public void walkFileRevisions(Path file, Inspector inspector, int... changelogRevisionIndexes) throws HgRuntimeException { + if (file == null || inspector == null || changelogRevisionIndexes == null) { + throw new IllegalArgumentException(); + } + // TODO [post-1.0] need tests. There's Main#checkWalkFileRevisions that may be a starting point + int[] manifestRevIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); + FileLookupInspector parser = new FileLookupInspector(encodingHelper, file, inspector); + parser.walk(manifestRevIndexes, content); } /** @@ -286,7 +304,8 @@ public Flags getFileFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { int manifestRevIdx = fromChangelog(changesetRevIndex); IntMap resMap = new IntMap(2); - content.iterate(manifestRevIdx, manifestRevIdx, true, new FileLookupInspector(encodingHelper, file, null, resMap)); + FileLookupInspector parser = new FileLookupInspector(encodingHelper, file, null, resMap); + parser.walk(manifestRevIdx, content); return resMap.get(changesetRevIndex); } @@ -611,17 +630,39 @@ */ private static class FileLookupInspector implements RevlogStream.Inspector { + private final Path filename; private final byte[] filenameAsBytes; private final IntMap csetIndex2FileRev; private final IntMap csetIndex2Flags; + private final Inspector delegate; public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, IntMap csetIndex2FileRevMap, IntMap csetIndex2FlagsMap) { assert fileToLookUp != null; // need at least one map for the inspector to make any sense assert csetIndex2FileRevMap != null || csetIndex2FlagsMap != null; + filename = fileToLookUp; + filenameAsBytes = eh.toManifest(fileToLookUp.toString()); + delegate = null; csetIndex2FileRev = csetIndex2FileRevMap; csetIndex2Flags = csetIndex2FlagsMap; + } + + public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, Inspector delegateInspector) { + assert fileToLookUp != null; + assert delegateInspector != null; + filename = fileToLookUp; filenameAsBytes = eh.toManifest(fileToLookUp.toString()); + delegate = delegateInspector; + csetIndex2FileRev = null; + csetIndex2Flags = null; + } + + void walk(int manifestRevIndex, RevlogStream content) { + content.iterate(manifestRevIndex, manifestRevIndex, true, this); + } + + void walk(int[] manifestRevIndexes, RevlogStream content) { + content.iterate(manifestRevIndexes, true, this); } public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) { @@ -635,24 +676,40 @@ byte[] byteArray = bos.toByteArray(); bos.reset(); if (Arrays.equals(filenameAsBytes, byteArray)) { - if (csetIndex2FileRev != null) { + Nodeid fileRev = null; + Flags flags = null; + if (csetIndex2FileRev != null || delegate != null) { byte[] nid = new byte[40]; data.readBytes(nid, 0, 40); - csetIndex2FileRev.put(linkRevision, Nodeid.fromAscii(nid, 0, 40)); + fileRev = Nodeid.fromAscii(nid, 0, 40); } else { data.skip(40); } - if (csetIndex2Flags != null) { + if (csetIndex2Flags != null || delegate != null) { while (!data.isEmpty() && (b = data.readByte()) != '\n') { bos.write(b); } - Flags flags; if (bos.size() == 0) { flags = Flags.RegularFile; } else { flags = Flags.parse(bos.toByteArray(), 0, bos.size()); } - csetIndex2Flags.put(linkRevision, flags); + + } + if (delegate != null) { + assert flags != null; + assert fileRev != null; + delegate.begin(revisionNumber, Nodeid.fromBinary(nodeid, 0), linkRevision); + delegate.next(fileRev, filename, flags); + delegate.end(revisionNumber); + + } else { + if (csetIndex2FileRev != null) { + csetIndex2FileRev.put(linkRevision, fileRev); + } + if (csetIndex2Flags != null) { + csetIndex2Flags.put(linkRevision, flags); + } } break; } else { diff -r 48f993aa2f41 -r 063b0663495a test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java --- a/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java Wed Mar 28 18:39:29 2012 +0200 +++ b/test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java Wed Mar 28 19:34:37 2012 +0200 @@ -338,7 +338,27 @@ HgDataFile fileNode = repository.getFileNode(targetPath); final long start2 = System.nanoTime(); final int lastRev = fileNode.getLastRevision(); - final Map fileRevisionAtTagRevision = repository.getManifest().getFileRevisions(targetPath, tagLocalRevs); + final Map fileRevisionAtTagRevision = new HashMap(); + HgManifest.Inspector collectFileRevAtCset = new HgManifest.Inspector() { + + private int csetRevIndex; + + public boolean next(Nodeid nid, Path fname, Flags flags) { + fileRevisionAtTagRevision.put(csetRevIndex, nid); + return true; + } + + public boolean end(int manifestRevision) { + return true; + } + + public boolean begin(int mainfestRevision, Nodeid nid, int changelogRevision) { + csetRevIndex = changelogRevision; + return true; + } + }; + repository.getManifest().walkFileRevisions(targetPath, collectFileRevAtCset,tagLocalRevs); + final long start2a = System.nanoTime(); fileNode.walk(0, lastRev, new HgDataFile.RevisionInspector() {