diff src/org/tmatesoft/hg/repo/HgManifest.java @ 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 e2115da4cf6a
children 8de327242aa0
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);
+			}
+		}
+	}
 }