# HG changeset patch # User Artem Tikhomirov # Date 1305596553 -7200 # Node ID 047b1dec7a04cddcf991fad1b87ef19083e7ea95 # Parent e39cf474ef940ceeed67e1c2eb59cddc6f6a1842 Issue 7: Correctly handle manifest and changelog with different number of (or non-matching) revisions diff -r e39cf474ef94 -r 047b1dec7a04 src/org/tmatesoft/hg/repo/HgManifest.java --- 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 null + */ 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 undefinedChangelogRevision = new ArrayList(); + 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); + } + } + } } diff -r e39cf474ef94 -r 047b1dec7a04 src/org/tmatesoft/hg/repo/HgStatusCollector.java --- 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; } diff -r e39cf474ef94 -r 047b1dec7a04 src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java --- 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 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 baseRevFiles = Collections.emptySet(); diff -r e39cf474ef94 -r 047b1dec7a04 src/org/tmatesoft/hg/repo/Revlog.java --- 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()) {