# HG changeset patch # User Artem Tikhomirov # Date 1313167737 -7200 # Node ID 3fbfce107f942081b0fd835ccb0342124e90fac2 # Parent f052f40839ec4da449c76edba2ee7a15de9bfa47 Issue 8: Means to find out information about given file at specific changeset. Inner ManifestRevisionInspector got promoted to ManifestRevision diff -r f052f40839ec -r 3fbfce107f94 cmdline/org/tmatesoft/hg/console/Main.java --- 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 { diff -r f052f40839ec -r 3fbfce107f94 src/org/tmatesoft/hg/core/HgCatCommand.java --- 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 this 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) { diff -r f052f40839ec -r 3fbfce107f94 src/org/tmatesoft/hg/core/HgFileInformer.java --- /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 true 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 getFileRevision().getPath() + */ + public Path filename() { + assertCheckRan(); + return fileRevision.getPath(); + } + + /** + * Revision of the checked file + * + * @return handy shortcut for getFileRevision().getRevision() + */ + public Nodeid revision() { + assertCheckRan(); + return fileRevision.getRevision(); + } + + private void assertCheckRan() { + if (!checked) { + throw new HgBadStateException("Shall invoke #check(Path) first"); + } + } +} diff -r f052f40839ec -r 3fbfce107f94 src/org/tmatesoft/hg/internal/ManifestRevision.java --- /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 idsMap; + private final TreeMap flagsMap; + private final Pool idsPool; + private final Pool 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 nodeidPool, Pool filenamePool) { + idsPool = nodeidPool; + namesPool = filenamePool; + idsMap = new TreeMap(); + flagsMap = new TreeMap(); + } + + public Collection 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> 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 diff -r f052f40839ec -r 3fbfce107f94 src/org/tmatesoft/hg/repo/HgMergeState.java --- 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 nodeidPool = new Pool(); Pool fnamePool = new Pool(); - 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); diff -r f052f40839ec -r 3fbfce107f94 src/org/tmatesoft/hg/repo/HgStatusCollector.java --- 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 cache; // sparse array, in fact + private final SortedMap 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 cacheNodes; private final Pool 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(); + cache = new TreeMap(); cacheNodes = new Pool(); cacheFilenames = new Pool(); - 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 idsMap; - private final TreeMap flagsMap; - private final Pool idsPool; - private final Pool namesPool; - - // optional pools for effective management of nodeids and filenames (they are likely - // to be duplicated among different manifest revisions - public ManifestRevisionInspector(Pool nodeidPool, Pool filenamePool) { - idsPool = nodeidPool; - namesPool = filenamePool; - idsMap = new TreeMap(); - flagsMap = new TreeMap(); - } - - public Collection 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> 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; - } - } } diff -r f052f40839ec -r 3fbfce107f94 src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java --- 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 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(collect.files()); @@ -251,7 +251,7 @@ } // XXX refactor checkLocalStatus methods in more OO way - private void checkLocalStatusAgainstBaseRevision(Set baseRevNames, ManifestRevisionInspector collect, int baseRevision, Path fname, File f, HgStatusInspector inspector) { + private void checkLocalStatusAgainstBaseRevision(Set 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());