changeset 218:047b1dec7a04

Issue 7: Correctly handle manifest and changelog with different number of (or non-matching) revisions
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 17 May 2011 03:42:33 +0200
parents e39cf474ef94
children d63583b47bfa
files src/org/tmatesoft/hg/repo/HgManifest.java src/org/tmatesoft/hg/repo/HgStatusCollector.java src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java src/org/tmatesoft/hg/repo/Revlog.java
diffstat 4 files changed, 101 insertions(+), 8 deletions(-) [+]
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/repo/HgManifest.java	Tue May 17 03:40:52 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgManifest.java	Tue May 17 03:42:33 2011 +0200
@@ -16,11 +16,16 @@
  */
 package org.tmatesoft.hg.repo;
 
+import static org.tmatesoft.hg.repo.HgRepository.TIP;
+
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
 
 import org.tmatesoft.hg.core.HgBadStateException;
 import org.tmatesoft.hg.core.Nodeid;
 import org.tmatesoft.hg.internal.DataAccess;
+import org.tmatesoft.hg.internal.Lifecycle;
 import org.tmatesoft.hg.internal.Pool;
 import org.tmatesoft.hg.internal.RevlogStream;
 
@@ -31,18 +36,38 @@
  * @author TMate Software Ltd.
  */
 public class HgManifest extends Revlog {
+	private RevisionMapper revisionMap;
 
 	/*package-local*/ HgManifest(HgRepository hgRepo, RevlogStream content) {
 		super(hgRepo, content);
 	}
 
+	/**
+	 * 
+	 * @param start changelog (not manifest!) revision to begin with
+	 * @param end changelog (not manifest!) revision to stop, inclusive.
+	 * @param inspector can't be <code>null</code>
+	 */
 	public void walk(int start, int end, final Inspector inspector) {
 		if (inspector == null) {
 			throw new IllegalArgumentException();
 		}
-		content.iterate(start, end, true, new ManifestParser(inspector));
+		int start0 = fromChangelog(start);
+		int end0 = fromChangelog(end);
+		content.iterate(start0, end0, true, new ManifestParser(inspector));
 	}
-
+	
+	/*package-local*/ int fromChangelog(int revisionNumber) {
+		if (HgInternals.wrongLocalRevision(revisionNumber)) {
+			throw new IllegalArgumentException(String.valueOf(revisionNumber));
+		}
+		if (revisionMap == null) {
+			revisionMap = new RevisionMapper(getRepo());
+			content.iterate(0, TIP, false, revisionMap);
+		}
+		return revisionMap.at(revisionNumber);
+	}
+	
 	public interface Inspector {
 		boolean begin(int revision, Nodeid nid);
 		boolean next(Nodeid nid, String fname, String flags);
@@ -103,4 +128,68 @@
 			}
 		}
 	}
+	
+	private static class RevisionMapper implements RevlogStream.Inspector, Lifecycle {
+		
+		private final int changelogRevisions;
+		private int[] changelog2manifest;
+		private final HgRepository repo;
+
+		public RevisionMapper(HgRepository hgRepo) {
+			repo = hgRepo;
+			changelogRevisions = repo.getChangelog().getRevisionCount();
+		}
+
+		public int at(int revisionNumber) {
+			if (changelog2manifest != null) {
+				return changelog2manifest[revisionNumber];
+			}
+			return revisionNumber;
+		}
+
+		public void next(int revisionNumber, int actualLen, int baseRevision, int linkRevision, int parent1Revision, int parent2Revision, byte[] nodeid, DataAccess data) {
+			if (changelog2manifest != null) {
+				changelog2manifest[linkRevision] = revisionNumber;
+			} else {
+				if (revisionNumber != linkRevision) {
+					changelog2manifest = new int[changelogRevisions];
+					Arrays.fill(changelog2manifest, -1);
+					for (int i = 0; i < revisionNumber; changelog2manifest[i] = i, i++)
+						;
+					changelog2manifest[linkRevision] = revisionNumber;
+				}
+			}
+		}
+		
+		public void start(int count, Callback callback, Object token) {
+			if (count != changelogRevisions) {
+				assert count < changelogRevisions; // no idea what to do if manifest has more revisions than changelog
+				// the way how manifest may contain more revisions than changelog, as I can imagine, is a result of  
+				// some kind of an import tool (e.g. from SVN or CVS), that creates manifest and changelog independently.
+				// Note, it's pure guess, I didn't see such repository yet (although the way manifest revisions
+				// in cpython repo are numbered makes me think aforementioned way) 
+				changelog2manifest = new int[changelogRevisions];
+				Arrays.fill(changelog2manifest, -1);
+			}
+		}
+
+		public void finish(Object token) {
+			if (changelog2manifest == null) {
+				return;
+			}
+			// I assume there'd be not too many revisions we don't know manifest of
+			ArrayList<Integer> undefinedChangelogRevision = new ArrayList<Integer>();
+			for (int i = 0; i < changelog2manifest.length; i++) {
+				if (changelog2manifest[i] == -1) {
+					undefinedChangelogRevision.add(i);
+				}
+			}
+			for (int u : undefinedChangelogRevision) {
+				Nodeid manifest = repo.getChangelog().range(u, u).get(0).manifest();
+				// FIXME calculate those missing effectively (e.g. cache and sort nodeids to spead lookup
+				// right away in the #next (may refactor ParentWalker's sequential and sorted into dedicated helper and reuse here)
+				changelog2manifest[u] = repo.getManifest().getLocalRevision(manifest);
+			}
+		}
+	}
 }
--- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Tue May 17 03:40:52 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java	Tue May 17 03:42:33 2011 +0200
@@ -175,7 +175,7 @@
 		if (inspector instanceof Record) {
 			((Record) inspector).init(rev1, rev2, this);
 		}
-		final int lastManifestRevision = repo.getManifest().getLastRevision();
+		final int lastManifestRevision = repo.getChangelog().getLastRevision();
 		if (rev1 == TIP) {
 			rev1 = lastManifestRevision;
 		}
--- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Tue May 17 03:40:52 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java	Tue May 17 03:42:33 2011 +0200
@@ -18,6 +18,7 @@
 
 import static java.lang.Math.max;
 import static java.lang.Math.min;
+import static org.tmatesoft.hg.repo.HgRepository.*;
 import static org.tmatesoft.hg.repo.HgRepository.BAD_REVISION;
 import static org.tmatesoft.hg.repo.HgRepository.TIP;
 
@@ -61,8 +62,8 @@
 	}
 
 	HgWorkingCopyStatusCollector(HgRepository hgRepo, FileIterator hgRepoWalker) {
-		this.repo = hgRepo;
-		this.repoWalker = hgRepoWalker;
+		repo = hgRepo;
+		repoWalker = hgRepoWalker;
 	}
 	
 	/**
@@ -98,14 +99,17 @@
 
 	// may be invoked few times
 	public void walk(int baseRevision, HgStatusInspector inspector) {
+		if (HgInternals.wrongLocalRevision(baseRevision) || baseRevision == BAD_REVISION || baseRevision == WORKING_COPY) {
+			throw new IllegalArgumentException(String.valueOf(baseRevision));
+		}
 		final HgIgnore hgIgnore = repo.getIgnore();
 		TreeSet<String> knownEntries = getDirstate().all();
 		final boolean isTipBase;
 		if (baseRevision == TIP) {
-			baseRevision = repo.getManifest().getRevisionCount() - 1;
+			baseRevision = repo.getChangelog().getLastRevision();
 			isTipBase = true;
 		} else {
-			isTipBase = baseRevision == repo.getManifest().getRevisionCount() - 1;
+			isTipBase = baseRevision == repo.getChangelog().getLastRevision();
 		}
 		HgStatusCollector.ManifestRevisionInspector collect = null;
 		Set<String> baseRevFiles = Collections.emptySet();
--- a/src/org/tmatesoft/hg/repo/Revlog.java	Tue May 17 03:40:52 2011 +0200
+++ b/src/org/tmatesoft/hg/repo/Revlog.java	Tue May 17 03:42:33 2011 +0200
@@ -98,7 +98,7 @@
 	// Till now, i follow approach that NULL nodeid is never part of revlog
 	public final boolean isKnown(Nodeid nodeid) {
 		final int rn = content.findLocalRevisionNumber(nodeid);
-		if (Integer.MIN_VALUE == rn) {
+		if (BAD_REVISION == rn) {
 			return false;
 		}
 		if (rn < 0 || rn >= content.revisionCount()) {