changeset 426:063b0663495a

HgManifest#getFileRevisions refactored into #walkFileRevisions to match pattern throught rest of the library
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 28 Mar 2012 19:34:37 +0200
parents 48f993aa2f41
children 31a89587eb04
files cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/repo/HgChangelog.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgManifest.java test/org/tmatesoft/hg/test/MapTagsToFileRevisions.java
diffstat 5 files changed, 118 insertions(+), 31 deletions(-) [+]
line wrap: on
line diff
--- 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() {
--- 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);
--- 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?
 	}
--- 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}.
+	 * <p>For more thorough details about file at specific changeset, use {@link HgChangesetFileSneaker}.
+	 * <p>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<Nodeid> resMap = new IntMap<Nodeid>(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<Integer, Nodeid> 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<Nodeid> resMap = new IntMap<Nodeid>(changelogRevisionIndexes.length);
-		content.iterate(manifestRevisionIndexes, true, new FileLookupInspector(encodingHelper, file, resMap, null));
-		// IntMap to HashMap, 
-		HashMap<Integer,Nodeid> rv = new HashMap<Integer, Nodeid>();
-		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
+	 * 
+	 * <p>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 <code>true</code> 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. <em>Runtime exception</em>
+	 */
+	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<Flags> resMap = new IntMap<Flags>(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<Nodeid> csetIndex2FileRev;
 		private final IntMap<Flags> csetIndex2Flags;
+		private final Inspector delegate;
 
 		public FileLookupInspector(EncodingHelper eh, Path fileToLookUp, IntMap<Nodeid> csetIndex2FileRevMap, IntMap<Flags> 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 {
--- 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<Integer, Nodeid> fileRevisionAtTagRevision = repository.getManifest().getFileRevisions(targetPath, tagLocalRevs);
+		final Map<Integer, Nodeid> fileRevisionAtTagRevision = new HashMap<Integer, Nodeid>();
+		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() {