changeset 248:3fbfce107f94

Issue 8: Means to find out information about given file at specific changeset. Inner ManifestRevisionInspector got promoted to ManifestRevision
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 12 Aug 2011 18:48:57 +0200
parents f052f40839ec
children 4c3b9f679412
files cmdline/org/tmatesoft/hg/console/Main.java src/org/tmatesoft/hg/core/HgCatCommand.java src/org/tmatesoft/hg/core/HgFileInformer.java src/org/tmatesoft/hg/internal/ManifestRevision.java src/org/tmatesoft/hg/repo/HgMergeState.java src/org/tmatesoft/hg/repo/HgStatusCollector.java src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java
diffstat 7 files changed, 303 insertions(+), 78 deletions(-) [+]
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Main.java	Fri Aug 12 17:17:37 2011 +0200
+++ b/cmdline/org/tmatesoft/hg/console/Main.java	Fri Aug 12 18:48:57 2011 +0200
@@ -23,9 +23,11 @@
 import java.util.List;
 import java.util.Map;
 
+import org.junit.Assert;
 import org.tmatesoft.hg.core.HgLogCommand;
 import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
 import org.tmatesoft.hg.core.HgCatCommand;
+import org.tmatesoft.hg.core.HgFileInformer;
 import org.tmatesoft.hg.core.HgFileRevision;
 import org.tmatesoft.hg.core.HgManifestCommand;
 import org.tmatesoft.hg.core.Nodeid;
@@ -77,10 +79,10 @@
 //		m.testReadWorkingCopy();
 //		m.testParents();
 //		m.testEffectiveFileLog();
-//		m.testCatAtCsetRevision();
+		m.testCatAtCsetRevision();
 //		m.testMergeState();
 //		m.testFileStatus();
-		m.dumpBranches();
+//		m.dumpBranches();
 //		m.inflaterLengthException();
 //		m.dumpIgnored();
 //		m.dumpDirstate();
@@ -206,11 +208,25 @@
 	// 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 Path file = Path.create("src/org/tmatesoft/hg/internal/RevlogStream.java");
+		cmd.file(file);
+		final Nodeid cset = Nodeid.fromAscii("08db726a0fb7914ac9d27ba26dc8bbf6385a0554");
+		cmd.changeset(cset);
 		final ByteArrayChannel sink = new ByteArrayChannel();
 		cmd.execute(sink);
 		System.out.println(sink.toArray().length);
+		HgFileInformer i = new HgFileInformer(hgRepo);
+		boolean result = i.changeset(cset).check(file);
+		Assert.assertFalse(result);
+		Assert.assertFalse(i.exists());
+		result = i.followRenames(true).check(file);
+		Assert.assertTrue(result);
+		Assert.assertTrue(i.exists());
+		HgCatCommand cmd2 = new HgCatCommand(hgRepo).revision(i.getFileRevision());
+		final ByteArrayChannel sink2 = new ByteArrayChannel();
+		cmd2.execute(sink2);
+		System.out.println(sink2.toArray().length);
+		Assert.assertEquals(sink.toArray().length, sink2.toArray().length);
 	}
 	
 	private void testMergeState() throws Exception {
--- a/src/org/tmatesoft/hg/core/HgCatCommand.java	Fri Aug 12 17:17:37 2011 +0200
+++ b/src/org/tmatesoft/hg/core/HgCatCommand.java	Fri Aug 12 18:48:57 2011 +0200
@@ -98,6 +98,16 @@
 		cset = null;
 		return this;
 	}
+
+	/**
+	 * Parameterize the command from file revision object.
+	 * 
+	 * @param fileRev file revision to cat 
+	 * @return <code>this</code> for convenience
+	 */
+	public HgCatCommand revision(HgFileRevision fileRev) {
+		return file(fileRev.getPath()).revision(fileRev.getRevision());
+	}
 	
 	/**
 	 * 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 
@@ -152,7 +162,7 @@
 				}
 			} 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()));
+				throw new HgBadStateException(String.format("File %s nor its origins were not known at repository %s revision", file, cset.shortNotation()));
 			}
 			revToExtract = dataFile.getLocalRevision(toExtract);
 		} else if (revision != null) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/core/HgFileInformer.java	Fri Aug 12 18:48:57 2011 +0200
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.core;
+
+import org.tmatesoft.hg.internal.ManifestRevision;
+import org.tmatesoft.hg.repo.HgDataFile;
+import org.tmatesoft.hg.repo.HgRepository;
+import org.tmatesoft.hg.util.Path;
+
+/**
+ * Primary purpose is to provide information about file revisions at specific changeset. Multiple {@link #check(Path)} calls 
+ * are possible once {@link #changeset(Nodeid)} (and optionally, {@link #followRenames(boolean)}) were set.
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public class HgFileInformer {
+
+	private final HgRepository repo;
+	private boolean followRenames;
+	private Nodeid cset;
+	private ManifestRevision cachedManifest;
+	private HgFileRevision fileRevision;
+	private boolean checked, renamed; 
+
+	public HgFileInformer(HgRepository hgRepo) {
+		repo = hgRepo;
+	}
+
+	public HgFileInformer changeset(Nodeid nid) {
+		if (nid == null || Nodeid.NULL.equals(nid)) {
+			throw new IllegalArgumentException(); 
+		}
+		cset = nid;
+		cachedManifest = null;
+		fileRevision = null;
+		return this;
+	}
+	
+	public HgFileInformer followRenames(boolean follow) {
+		followRenames = follow;
+		fileRevision = null;
+		return this;
+	}
+
+	public boolean check(Path file) { // XXX IStatus instead of boolean?
+		fileRevision = null;
+		checked = false;
+		renamed = false;
+		if (cset == null || file == null || file.isDirectory()) {
+			throw new IllegalArgumentException();
+		}
+		HgDataFile dataFile = repo.getFileNode(file);
+		if (!dataFile.exists()) {
+			return false;
+		}
+		if (cachedManifest == null) {
+			int csetRev = repo.getChangelog().getLocalRevision(cset);
+			cachedManifest = new ManifestRevision(null, null); // XXX how about context and cached manifest revisions
+			repo.getManifest().walk(csetRev, csetRev, cachedManifest);
+			// cachedManifest shall be meaningful - changelog.getLocalRevision above ensures we've got version that exists.
+		}
+		Nodeid toExtract = cachedManifest.nodeid(file.toString());
+		try {
+			if (toExtract == null && followRenames) {
+				while (toExtract == null && dataFile.isCopy()) {
+					renamed = true;
+					file = dataFile.getCopySourceName();
+					dataFile = repo.getFileNode(file);
+					toExtract = cachedManifest.nodeid(file.toString());
+				}
+			}
+		} catch (HgDataStreamException ex) {
+			ex.printStackTrace(); // XXX log(INFO) 
+			// ignore now, however if there's IStatus retval, might report error with reasonable explanation.
+			// Perhaps, may add a String reason() method with such info?
+		}
+		checked = true;
+		if (toExtract != null) {
+			fileRevision = new HgFileRevision(repo, toExtract, dataFile.getPath());
+			return true;
+		} // else String.format("File %s nor its origins were not known at repository %s revision", file, cset.shortNotation())
+		return false;
+	}
+	
+	/**
+	 * @return result of the last {@link #check(Path)} call.
+	 */
+	public boolean exists() {
+		assertCheckRan();
+		return fileRevision != null;
+	}
+	
+	/**
+	 * @return <code>true</code> if checked file was known by another name at the time of specified changeset.
+	 */
+	public boolean hasAnotherName() {
+		assertCheckRan();
+		return renamed;
+	}
+
+	/**
+	 * @return holder for file revision information
+	 */
+	public HgFileRevision getFileRevision() {
+		assertCheckRan();
+		return fileRevision;
+	}
+
+	/**
+	 * Name of the checked file as it was known at the time of the specified changeset.
+	 * 
+	 * @return handy shortcut for <code>getFileRevision().getPath()</code>
+	 */
+	public Path filename() {
+		assertCheckRan();
+		return fileRevision.getPath();
+	}
+	
+	/**
+	 * Revision of the checked file
+	 * 
+	 * @return handy shortcut for <code>getFileRevision().getRevision()</code>
+	 */
+	public Nodeid revision() {
+		assertCheckRan();
+		return fileRevision.getRevision();
+	}
+
+	private void assertCheckRan() {
+		if (!checked) {
+			throw new HgBadStateException("Shall invoke #check(Path) first");
+		}
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/org/tmatesoft/hg/internal/ManifestRevision.java	Fri Aug 12 18:48:57 2011 +0200
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2011 TMate Software Ltd
+ *  
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * For information on how to redistribute this software under
+ * the terms of a license other than GNU General Public License
+ * contact TMate Software at support@hg4j.com
+ */
+package org.tmatesoft.hg.internal;
+
+import java.util.Collection;
+import java.util.TreeMap;
+
+import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.repo.HgManifest;
+
+/**
+ * Specific revision of the manifest. 
+ * Note, suited to keep single revision only ({@link #changeset()}).
+ *
+ * @author Artem Tikhomirov
+ * @author TMate Software Ltd.
+ */
+public final class ManifestRevision implements HgManifest.Inspector {
+	private final TreeMap<String, Nodeid> idsMap;
+	private final TreeMap<String, String> flagsMap;
+	private final Pool<Nodeid> idsPool;
+	private final Pool<String> namesPool;
+	private Nodeid changeset;
+	private int changelogRev; 
+
+	// optional pools for effective management of nodeids and filenames (they are likely
+	// to be duplicated among different manifest revisions
+	public ManifestRevision(Pool<Nodeid> nodeidPool, Pool<String> filenamePool) {
+		idsPool = nodeidPool;
+		namesPool = filenamePool;
+		idsMap = new TreeMap<String, Nodeid>();
+		flagsMap = new TreeMap<String, String>();
+	}
+	
+	public Collection<String> files() {
+		return idsMap.keySet();
+	}
+
+	public Nodeid nodeid(String fname) {
+		return idsMap.get(fname);
+	}
+
+	public String flags(String fname) {
+		return flagsMap.get(fname);
+	}
+
+	/**
+	 * @return identifier of the changeset this manifest revision corresponds to.
+	 */
+	public Nodeid changeset() {
+		return changeset;
+	}
+	
+	public int changesetLocalRev() {
+		return changelogRev;
+	}
+	
+	//
+
+	public boolean next(Nodeid nid, String fname, String flags) {
+		if (namesPool != null) {
+			fname = namesPool.unify(fname);
+		}
+		if (idsPool != null) {
+			nid = idsPool.unify(nid);
+		}
+		idsMap.put(fname, nid);
+		if (flags != null) {
+			// TreeMap$Entry takes 32 bytes. No reason to keep null for such price
+			// Perhaps, Map<String, Pair<Nodeid, String>> might be better solution
+			flagsMap.put(fname, flags);
+		}
+		return true;
+	}
+
+	public boolean end(int revision) {
+		// in fact, this class cares about single revision
+		return false; 
+	}
+
+	public boolean begin(int revision, Nodeid nid, int changelogRevision) {
+		if (changeset != null) {
+			idsMap.clear();
+			flagsMap.clear();
+		}
+		changeset = nid;
+		changelogRev = changelogRevision;
+		return true;
+	}
+}
\ No newline at end of file
--- a/src/org/tmatesoft/hg/repo/HgMergeState.java	Fri Aug 12 17:17:37 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgMergeState.java	Fri Aug 12 18:48:57 2011 +0200
@@ -29,8 +29,8 @@
 import org.tmatesoft.hg.core.HgFileRevision;
 import org.tmatesoft.hg.core.HgLogCommand.FileRevision;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.internal.Pool;
-import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathPool;
 import org.tmatesoft.hg.util.PathRewrite;
@@ -102,8 +102,8 @@
 		PathPool pathPool = new PathPool(new PathRewrite.Empty());
 		Pool<Nodeid> nodeidPool = new Pool<Nodeid>();
 		Pool<String> fnamePool = new Pool<String>();
-		final ManifestRevisionInspector m1 = new ManifestRevisionInspector(nodeidPool, fnamePool);
-		final ManifestRevisionInspector m2 = new ManifestRevisionInspector(nodeidPool, fnamePool);
+		final ManifestRevision m1 = new ManifestRevision(nodeidPool, fnamePool);
+		final ManifestRevision m2 = new ManifestRevision(nodeidPool, fnamePool);
 		final int rp1 = repo.getChangelog().getLocalRevision(wcp1);
 		final int rp2 = repo.getChangelog().getLocalRevision(wcp2);
 		repo.getManifest().walk(rp1, rp1, m1);
--- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Fri Aug 12 17:17:37 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Fri Aug 12 18:48:57 2011 +0200
@@ -31,6 +31,7 @@
 
 import org.tmatesoft.hg.core.HgDataStreamException;
 import org.tmatesoft.hg.core.Nodeid;
+import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.internal.Pool;
 import org.tmatesoft.hg.util.Path;
 import org.tmatesoft.hg.util.PathPool;
@@ -46,7 +47,7 @@
 public class HgStatusCollector {
 
 	private final HgRepository repo;
-	private final SortedMap<Integer, ManifestRevisionInspector> cache; // sparse array, in fact
+	private final SortedMap<Integer, ManifestRevision> cache; // sparse array, in fact
 	// with cpython repository, ~70 000 changes, complete Log (direct out, no reverse) output 
 	// no cache limit, no nodeids and fname caching - OOME on changeset 1035
 	// no cache limit, but with cached nodeids and filenames - 1730+
@@ -55,17 +56,17 @@
 	private PathPool pathPool;
 	private final Pool<Nodeid> cacheNodes;
 	private final Pool<String> cacheFilenames; // XXX in fact, need to think if use of PathPool directly instead is better solution
-	private final ManifestRevisionInspector emptyFakeState;
+	private final ManifestRevision emptyFakeState;
 	private Path.Matcher scope = new Path.Matcher.Any();
 	
 
 	public HgStatusCollector(HgRepository hgRepo) {
 		this.repo = hgRepo;
-		cache = new TreeMap<Integer, ManifestRevisionInspector>();
+		cache = new TreeMap<Integer, ManifestRevision>();
 		cacheNodes = new Pool<Nodeid>();
 		cacheFilenames = new Pool<String>();
 
-		emptyFakeState = new ManifestRevisionInspector(null, null);
+		emptyFakeState = new ManifestRevision(null, null);
 		emptyFakeState.begin(-1, null, -1);
 		emptyFakeState.end(-1);
 	}
@@ -74,8 +75,8 @@
 		return repo;
 	}
 	
-	private ManifestRevisionInspector get(int rev) {
-		ManifestRevisionInspector i = cache.get(rev);
+	private ManifestRevision get(int rev) {
+		ManifestRevision i = cache.get(rev);
 		if (i == null) {
 			if (rev == -1) {
 				return emptyFakeState;
@@ -84,7 +85,7 @@
 				// assume usually we go from oldest to newest, hence remove oldest as most likely to be no longer necessary
 				cache.remove(cache.firstKey());
 			}
-			i = new ManifestRevisionInspector(cacheNodes, cacheFilenames);
+			i = new ManifestRevision(cacheNodes, cacheFilenames);
 			cache.put(rev, i);
 			repo.getManifest().walk(rev, rev, i);
 		}
@@ -101,7 +102,7 @@
 			cache.remove(cache.firstKey());
 		}
 		repo.getManifest().walk(minRev, maxRev, new HgManifest.Inspector() {
-			private ManifestRevisionInspector delegate;
+			private ManifestRevision delegate;
 			private boolean cacheHit; // range may include revisions we already know about, do not re-create them
 
 			public boolean begin(int manifestRevision, Nodeid nid, int changelogRevision) {
@@ -109,7 +110,7 @@
 				if (cache.containsKey(changelogRevision)) { // don't need to check emptyFakeState hit as revision never -1 here
 					cacheHit = true;
 				} else {
-					cache.put(changelogRevision, delegate = new ManifestRevisionInspector(cacheNodes, cacheFilenames));
+					cache.put(changelogRevision, delegate = new ManifestRevision(cacheNodes, cacheFilenames));
 					// cache may grow bigger than max size here, but it's ok as present simplistic cache clearing mechanism may
 					// otherwise remove entries we just added
 					delegate.begin(manifestRevision, nid, changelogRevision);
@@ -136,7 +137,7 @@
 		});
 	}
 	
-	/*package-local*/ ManifestRevisionInspector raw(int rev) {
+	/*package-local*/ ManifestRevision raw(int rev) {
 		return get(rev);
 	}
 	/*package-local*/ PathPool getPathPool() {
@@ -193,7 +194,7 @@
 		}
 		// in fact, rev1 and rev2 are often next (or close) to each other,
 		// thus, we can optimize Manifest reads here (manifest.walk(rev1, rev2))
-		ManifestRevisionInspector r1, r2 ;
+		ManifestRevision r1, r2 ;
 		boolean need1 = !cached(rev1), need2 = !cached(rev2);
 		if (need1 || need2) {
 			int minRev, maxRev;
@@ -422,60 +423,5 @@
 			return l;
 		}
 	}
-	
-	/*package-local*/ static final class ManifestRevisionInspector implements HgManifest.Inspector {
-		private final TreeMap<String, Nodeid> idsMap;
-		private final TreeMap<String, String> flagsMap;
-		private final Pool<Nodeid> idsPool;
-		private final Pool<String> namesPool; 
-
-		// optional pools for effective management of nodeids and filenames (they are likely
-		// to be duplicated among different manifest revisions
-		public ManifestRevisionInspector(Pool<Nodeid> nodeidPool, Pool<String> filenamePool) {
-			idsPool = nodeidPool;
-			namesPool = filenamePool;
-			idsMap = new TreeMap<String, Nodeid>();
-			flagsMap = new TreeMap<String, String>();
-		}
-		
-		public Collection<String> files() {
-			return idsMap.keySet();
-		}
-
-		public Nodeid nodeid(String fname) {
-			return idsMap.get(fname);
-		}
-
-		public String flags(String fname) {
-			return flagsMap.get(fname);
-		}
-
-		//
-
-		public boolean next(Nodeid nid, String fname, String flags) {
-			if (namesPool != null) {
-				fname = namesPool.unify(fname);
-			}
-			if (idsPool != null) {
-				nid = idsPool.unify(nid);
-			}
-			idsMap.put(fname, nid);
-			if (flags != null) {
-				// TreeMap$Entry takes 32 bytes. No reason to keep null for such price
-				// Perhaps, Map<String, Pair<Nodeid, String>> might be better solution
-				flagsMap.put(fname, flags);
-			}
-			return true;
-		}
-
-		public boolean end(int revision) {
-			// in fact, this class cares about single revision
-			return false; 
-		}
-
-		public boolean begin(int revision, Nodeid nid, int changelogRevision) {
-			return true;
-		}
-	}
 
 }
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Fri Aug 12 17:17:37 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Fri Aug 12 18:48:57 2011 +0200
@@ -37,8 +37,8 @@
 import org.tmatesoft.hg.internal.ByteArrayChannel;
 import org.tmatesoft.hg.internal.Experimental;
 import org.tmatesoft.hg.internal.FilterByteChannel;
+import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.internal.PathScope;
-import org.tmatesoft.hg.repo.HgStatusCollector.ManifestRevisionInspector;
 import org.tmatesoft.hg.util.ByteChannel;
 import org.tmatesoft.hg.util.CancelledException;
 import org.tmatesoft.hg.util.FileIterator;
@@ -115,13 +115,13 @@
 		} else {
 			isTipBase = baseRevision == repo.getChangelog().getLastRevision();
 		}
-		HgStatusCollector.ManifestRevisionInspector collect = null;
+		ManifestRevision collect = null;
 		Set<String> baseRevFiles = Collections.emptySet(); // files from base revision not affected by status calculation 
 		if (!isTipBase) {
 			if (baseRevisionCollector != null) {
 				collect = baseRevisionCollector.raw(baseRevision);
 			} else {
-				collect = new HgStatusCollector.ManifestRevisionInspector(null, null);
+				collect = new ManifestRevision(null, null);
 				repo.getManifest().walk(baseRevision, baseRevision, collect);
 			}
 			baseRevFiles = new TreeSet<String>(collect.files());
@@ -251,7 +251,7 @@
 	}
 	
 	// XXX refactor checkLocalStatus methods in more OO way
-	private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, ManifestRevisionInspector collect, int baseRevision, Path fname, File f, HgStatusInspector inspector) {
+	private void checkLocalStatusAgainstBaseRevision(Set<String> baseRevNames, ManifestRevision collect, int baseRevision, Path fname, File f, HgStatusInspector inspector) {
 		// fname is in the dirstate, either Normal, Added, Removed or Merged
 		Nodeid nid1 = collect.nodeid(fname.toString());
 		String flags = collect.flags(fname.toString());