# HG changeset patch # User Artem Tikhomirov # Date 1332443646 -3600 # Node ID ccd7d25e5aea2aa06ba67e6ae91cd2a8d9dd443a # Parent d30083c80d5210c9d418694f423feb08f40e3ad3 New and better name for HgFileInformer - HgChangesetFileSneaker. Explain (comments) ties between HgManifest, HgDataFile, HgChangesetFileSneaker and reasons for method placement diff -r d30083c80d52 -r ccd7d25e5aea cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Thu Mar 22 19:11:33 2012 +0100 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Thu Mar 22 20:14:06 2012 +0100 @@ -32,10 +32,9 @@ import org.tmatesoft.hg.core.HgCallbackTargetException; import org.tmatesoft.hg.core.HgCatCommand; import org.tmatesoft.hg.core.HgChangeset; +import org.tmatesoft.hg.core.HgChangesetFileSneaker; import org.tmatesoft.hg.core.HgChangesetTreeHandler; -import org.tmatesoft.hg.core.HgDataStreamException; import org.tmatesoft.hg.core.HgException; -import org.tmatesoft.hg.core.HgFileInformer; import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.HgManifestCommand; @@ -136,7 +135,7 @@ StringBuilder sb = new StringBuilder(); HashSet test = new HashSet(entry.childRevisions()); for (HgChangeset cc : entry.children()) { - sb.append(cc.getRevision()); + sb.append(cc.getRevisionIndex()); sb.append(':'); sb.append(cc.getNodeid().shortNotation()); sb.append(", "); @@ -145,14 +144,14 @@ final boolean isJoin = !parents.first().isNull() && !parents.second().isNull(); final boolean isFork = entry.children().size() > 1; final HgChangeset cset = entry.changeset(); - System.out.printf("%d:%s - %s\n", cset.getRevision(), cset.getNodeid().shortNotation(), cset.getComment()); + System.out.printf("%d:%s - %s\n", cset.getRevisionIndex(), cset.getNodeid().shortNotation(), cset.getComment()); if (!isJoin && !isFork && !entry.children().isEmpty()) { System.out.printf("\t=> %s\n", sb); } if (isJoin) { HgChangeset p1 = entry.parents().first(); HgChangeset p2 = entry.parents().second(); - System.out.printf("\tjoin <= (%d:%s, %d:%s)", p1.getRevision(), p1.getNodeid().shortNotation(), p2.getRevision(), p2.getNodeid().shortNotation()); + System.out.printf("\tjoin <= (%d:%s, %d:%s)", p1.getRevisionIndex(), p1.getNodeid().shortNotation(), p2.getRevisionIndex(), p2.getNodeid().shortNotation()); if (isFork) { System.out.print(", "); } @@ -353,7 +352,7 @@ final ByteArrayChannel sink = new ByteArrayChannel(); cmd.execute(sink); System.out.println(sink.toArray().length); - HgFileInformer i = new HgFileInformer(hgRepo); + HgChangesetFileSneaker i = new HgChangesetFileSneaker(hgRepo); boolean result = i.changeset(cset).checkExists(file); Assert.assertFalse(result); Assert.assertFalse(i.exists()); diff -r d30083c80d52 -r ccd7d25e5aea src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java Thu Mar 22 20:14:06 2012 +0100 @@ -0,0 +1,219 @@ +/* + * Copyright (c) 2011-2012 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.HgManifest; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.util.Path; +import org.tmatesoft.hg.util.Status; + +/** + * 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. + * + *

Sample: + *


+ *   HgChangesetFileSneaker i = new HgChangesetFileSneaker(hgRepo).changeset(Nodeid.fromString("<40 digits>")).followRenames(true);
+ *   if (i.check(file)) {
+ *   	HgCatCommand catCmd = new HgCatCommand(hgRepo).revision(i.getFileRevision());
+ *   	catCmd.execute(...);
+ *   	...
+ *   }
+ * 
+ * + * TODO may add #manifest(Nodeid) to select manifest according to its revision (not only changeset revision as it's now) + * + *

Unlike {@link HgManifest#getFileRevision(int, Path)}, this class is useful when few files from the same changeset have to be inspected + * + * @see HgManifest#getFileRevision(int, Path) + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class HgChangesetFileSneaker {// TODO mark final once HgFileInformer gone + + private final HgRepository repo; + private boolean followRenames; + private Nodeid cset; + private ManifestRevision cachedManifest; + private HgFileRevision fileRevision; + private boolean renamed; + private Status checkResult; + + public HgChangesetFileSneaker(HgRepository hgRepo) { + repo = hgRepo; + } + + /** + * Select specific changelog revision + * + * @param nid changeset identifier + * @return this for convenience + */ + public HgChangesetFileSneaker changeset(Nodeid nid) { + if (nid == null || nid.isNull()) { + throw new IllegalArgumentException(); + } + cset = nid; + cachedManifest = null; + fileRevision = null; + return this; + } + + /** + * Whether to check file origins, default is false (look up only the name supplied) + * + * @param follow true to check copy/rename origin of the file if it is a copy. + * @return this for convenience + */ + public HgChangesetFileSneaker followRenames(boolean follow) { + followRenames = follow; + fileRevision = null; + return this; + } + + /** + * Shortcut to perform {@link #check(Path)} and {@link #exists()}. Result of the check may be accessed via {@link #getCheckStatus()}. + * + * @param file name of the file in question + * @return true if file is known at the selected changeset. + * @throws IllegalArgumentException if {@link #changeset(Nodeid)} not specified or file argument is bad. + * @throws HgInvalidControlFileException if access to revlog index/data entry failed + */ + public boolean checkExists(Path file) throws HgInvalidControlFileException { + check(file); + if (!checkResult.isOk() && checkResult.getException() instanceof HgInvalidControlFileException) { + throw (HgInvalidControlFileException) checkResult.getException(); + } + return checkResult.isOk() && exists(); + } + + /** + * Find file (or its origin, if {@link #followRenames(boolean)} was set to true) among files known at specified {@link #changeset(Nodeid)}. + * + * @param file name of the file in question + * @return status object that describes outcome, {@link Status#isOk() Ok} status indicates successful completion of the operation, but doesn't imply + * file existence, use {@link #exists()} for that purpose. Message of the status may provide further hints on what exactly had happened. + * @throws IllegalArgumentException if {@link #changeset(Nodeid)} not specified or file argument is bad. + */ + public Status check(Path file) { + fileRevision = null; + checkResult = null; + renamed = false; + if (cset == null || file == null || file.isDirectory()) { + throw new IllegalArgumentException(); + } + HgDataFile dataFile = repo.getFileNode(file); + if (!dataFile.exists()) { + checkResult = new Status(Status.Kind.OK, String.format("File named %s is not known in the repository", file)); + return checkResult; + } + Nodeid toExtract = null; + HgManifest.Flags extractRevFlags = null; + String phaseMsg = "Extract manifest revision failed"; + try { + if (cachedManifest == null) { + int csetRev = repo.getChangelog().getRevisionIndex(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.getRevisionIndex() above ensures we've got version that exists. + } + toExtract = cachedManifest.nodeid(file); + extractRevFlags = cachedManifest.flags(file); + phaseMsg = "Follow copy/rename failed"; + if (toExtract == null && followRenames) { + while (toExtract == null && dataFile.isCopy()) { + renamed = true; + file = dataFile.getCopySourceName(); + dataFile = repo.getFileNode(file); + toExtract = cachedManifest.nodeid(file); + extractRevFlags = cachedManifest.flags(file); + } + } + } catch (HgException ex) { + checkResult = new Status(Status.Kind.ERROR, phaseMsg, ex); + return checkResult; + } + if (toExtract != null) { + fileRevision = new HgFileRevision(repo, toExtract, extractRevFlags, dataFile.getPath()); + checkResult = new Status(Status.Kind.OK, String.format("File %s, revision %s found at changeset %s", dataFile.getPath(), toExtract.shortNotation(), cset.shortNotation())); + return checkResult; + } + checkResult = new Status(Status.Kind.OK, String.format("File %s nor its origins were known at repository %s revision", file, cset.shortNotation())); + return checkResult; + } + + /** + * Re-get latest check status object + */ + public Status getCheckStatus() { + assertCheckRan(); + return checkResult; + } + + /** + * @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 (checkResult == null) { + throw new HgBadStateException("Shall invoke #check(Path) first"); + } + } + +} diff -r d30083c80d52 -r ccd7d25e5aea src/org/tmatesoft/hg/core/HgFileInformer.java --- a/src/org/tmatesoft/hg/core/HgFileInformer.java Thu Mar 22 19:11:33 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgFileInformer.java Thu Mar 22 20:14:06 2012 +0100 @@ -16,201 +16,18 @@ */ package org.tmatesoft.hg.core; -import org.tmatesoft.hg.internal.ManifestRevision; -import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgManifest; import org.tmatesoft.hg.repo.HgRepository; -import org.tmatesoft.hg.util.Path; -import org.tmatesoft.hg.util.Status; + /** - * 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. - * - *

Sample: - *


- *   HgFileInformer i = new HgFileInformer(hgRepo).changeset(Nodeid.fromString("<40 digits>")).followRenames(true);
- *   if (i.check(file)) {
- *   	HgCatCommand catCmd = new HgCatCommand(hgRepo).revision(i.getFileRevision());
- *   	catCmd.execute(...);
- *   	...
- *   }
- * 
- * - * FIXME need better name. It's more about manifest of specific changeset, rather than informing (about) files - * TODO may add #manifest(Nodeid) to select manifest according to its revision (not only changeset revision as it's now) - * + * @deprecated Use {@link HgChangesetFileSneaker} directly * @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 renamed; - private Status checkResult; - +@Deprecated +public class HgFileInformer extends HgChangesetFileSneaker { + public HgFileInformer(HgRepository hgRepo) { - repo = hgRepo; - } - - /** - * Select specific changelog revision - * - * @param nid changeset identifier - * @return this for convenience - */ - public HgFileInformer changeset(Nodeid nid) { - if (nid == null || nid.isNull()) { - throw new IllegalArgumentException(); - } - cset = nid; - cachedManifest = null; - fileRevision = null; - return this; - } - - /** - * Whether to check file origins, default is false (look up only the name supplied) - * - * @param follow true to check copy/rename origin of the file if it is a copy. - * @return this for convenience - */ - public HgFileInformer followRenames(boolean follow) { - followRenames = follow; - fileRevision = null; - return this; - } - - /** - * Shortcut to perform {@link #check(Path)} and {@link #exists()}. Result of the check may be accessed via {@link #getCheckStatus()}. - * - * @param file name of the file in question - * @return true if file is known at the selected changeset. - * @throws IllegalArgumentException if {@link #changeset(Nodeid)} not specified or file argument is bad. - * @throws HgInvalidControlFileException if access to revlog index/data entry failed - */ - public boolean checkExists(Path file) throws HgInvalidControlFileException { - check(file); - if (!checkResult.isOk() && checkResult.getException() instanceof HgInvalidControlFileException) { - throw (HgInvalidControlFileException) checkResult.getException(); - } - return checkResult.isOk() && exists(); - } - - /** - * Find file (or its origin, if {@link #followRenames(boolean)} was set to true) among files known at specified {@link #changeset(Nodeid)}. - * - * @param file name of the file in question - * @return status object that describes outcome, {@link Status#isOk() Ok} status indicates successful completion of the operation, but doesn't imply - * file existence, use {@link #exists()} for that purpose. Message of the status may provide further hints on what exactly had happened. - * @throws IllegalArgumentException if {@link #changeset(Nodeid)} not specified or file argument is bad. - */ - public Status check(Path file) { - fileRevision = null; - checkResult = null; - renamed = false; - if (cset == null || file == null || file.isDirectory()) { - throw new IllegalArgumentException(); - } - HgDataFile dataFile = repo.getFileNode(file); - if (!dataFile.exists()) { - checkResult = new Status(Status.Kind.OK, String.format("File named %s is not known in the repository", file)); - return checkResult; - } - Nodeid toExtract = null; - HgManifest.Flags extractRevFlags = null; - String phaseMsg = "Extract manifest revision failed"; - try { - if (cachedManifest == null) { - int csetRev = repo.getChangelog().getRevisionIndex(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.getRevisionIndex() above ensures we've got version that exists. - } - toExtract = cachedManifest.nodeid(file); - extractRevFlags = cachedManifest.flags(file); - phaseMsg = "Follow copy/rename failed"; - if (toExtract == null && followRenames) { - while (toExtract == null && dataFile.isCopy()) { - renamed = true; - file = dataFile.getCopySourceName(); - dataFile = repo.getFileNode(file); - toExtract = cachedManifest.nodeid(file); - extractRevFlags = cachedManifest.flags(file); - } - } - } catch (HgException ex) { - checkResult = new Status(Status.Kind.ERROR, phaseMsg, ex); - return checkResult; - } - if (toExtract != null) { - fileRevision = new HgFileRevision(repo, toExtract, extractRevFlags, dataFile.getPath()); - checkResult = new Status(Status.Kind.OK, String.format("File %s, revision %s found at changeset %s", dataFile.getPath(), toExtract.shortNotation(), cset.shortNotation())); - return checkResult; - } - checkResult = new Status(Status.Kind.OK, String.format("File %s nor its origins were known at repository %s revision", file, cset.shortNotation())); - return checkResult; - } - - /** - * Re-get latest check status object - */ - public Status getCheckStatus() { - assertCheckRan(); - return checkResult; - } - - /** - * @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 (checkResult == null) { - throw new HgBadStateException("Shall invoke #check(Path) first"); - } + super(hgRepo); } } diff -r d30083c80d52 -r ccd7d25e5aea src/org/tmatesoft/hg/internal/IntMap.java --- a/src/org/tmatesoft/hg/internal/IntMap.java Thu Mar 22 19:11:33 2012 +0100 +++ b/src/org/tmatesoft/hg/internal/IntMap.java Thu Mar 22 20:14:06 2012 +0100 @@ -21,8 +21,6 @@ import java.util.Map.Entry; import java.util.NoSuchElementException; -import org.tmatesoft.hg.core.Nodeid; - /** * Map implementation that uses plain int keys and performs with log n effectiveness. diff -r d30083c80d52 -r ccd7d25e5aea src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Thu Mar 22 19:11:33 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Thu Mar 22 20:14:06 2012 +0100 @@ -448,6 +448,15 @@ history(0, getLastRevision(), inspector); } + /** + * + * @param start local revision index + * @param end local revision index + * @param inspector + * FIXME EXCEPTIONS + * @throws HgInvalidRevisionException + * @throws HgInvalidControlFileException + */ public void history(int start, int end, HgChangelog.Inspector inspector) throws HgInvalidRevisionException, HgInvalidControlFileException { if (!exists()) { throw new IllegalStateException("Can't get history of invalid repository file node"); @@ -556,24 +565,18 @@ } throw new UnsupportedOperationException(); } -/* FIXME - public Nodeid getRevisionAtChangeset(int changesetRevision) { - } - - public HgManifest.Flags getFlagsAtChangeset(int changesetRevisionIndex) { - } -*/ - + /** - * + * Get file flags recorded in the manifest * @param fileRevisionIndex - revision local index, non-negative, or {@link HgRepository#TIP}. - * FIXME EXCEPTIONS + * @see HgManifest#getFileFlags(int, Path) + * FIXME EXCEPTIONS * @throws HgInvalidControlFileException * @throws HgInvalidRevisionException */ public HgManifest.Flags getFlags(int fileRevisionIndex) throws HgInvalidControlFileException, HgInvalidRevisionException { int changesetRevIndex = getChangesetRevisionIndex(fileRevisionIndex); - return getRepo().getManifest().extractFlags(changesetRevIndex, getPath()); + return getRepo().getManifest().getFileFlags(changesetRevIndex, getPath()); } @Override diff -r d30083c80d52 -r ccd7d25e5aea src/org/tmatesoft/hg/repo/HgManifest.java --- a/src/org/tmatesoft/hg/repo/HgManifest.java Thu Mar 22 19:11:33 2012 +0100 +++ b/src/org/tmatesoft/hg/repo/HgManifest.java Thu Mar 22 20:14:06 2012 +0100 @@ -28,6 +28,7 @@ import java.util.Map; import org.tmatesoft.hg.core.HgBadStateException; +import org.tmatesoft.hg.core.HgChangesetFileSneaker; import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.HgInvalidControlFileException; import org.tmatesoft.hg.core.HgInvalidRevisionException; @@ -243,21 +244,28 @@ /** * Extracts file revision as it was known at the time of given changeset. + * For more thorough details about file at specific changeset, use {@link HgChangesetFileSneaker}. * + * @see HgChangesetFileSneaker * @param changelogRevisionIndex local changeset index * @param file path to file in question * @return file revision or null if manifest at specified revision doesn't list such file * @throws HgInvalidRevisionException if method argument specifies non-existent revision index * @throws HgInvalidControlFileException if access to revlog index/data entry failed */ - @Experimental(reason="Perhaps, HgDataFile shall own this method, or get a delegate?") public Nodeid getFileRevision(int changelogRevisionIndex, final Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { + // there's no need for HgDataFile to own this method, or get a delegate + // as most of HgDataFile API is using file revision indexes, and there's easy step from file revision index to + // both file revision and changeset revision index. But there's no easy way to go from changesetRevisionIndex to + // file revision (the task this method solves), exept for HgFileInformer + // I feel methods dealing with changeset indexes shall be more exposed in HgChangelog and HgManifest API. return getFileRevisions(file, changelogRevisionIndex).get(changelogRevisionIndex); } - - // XXX package-local, IntMap, and HgDataFile getFileRevisionAt(int... localChangelogRevisions) - @Experimental(reason="@see #getFileRevision") + + // XXX package-local or better API + @Experimental(reason="Map as return value isn't that good") public Map getFileRevisions(final Path file, int... changelogRevisionIndexes) throws HgInvalidRevisionException, HgInvalidControlFileException { + // FIXME in fact, walk(Inspectr, path, int[]) might be better alternative than get() // TODO need tests int[] manifestRevisionIndexes = toManifestRevisionIndexes(changelogRevisionIndexes, null); IntMap resMap = new IntMap(changelogRevisionIndexes.length); @@ -269,16 +277,17 @@ } /** - * {@link HgDataFile#getFlags(int)} is public API - * + * Extract file {@link Flags flags} as they were recorded in appropriate manifest version. + * + * @see HgDataFile#getFlags(int) * @param changesetRevIndex changeset revision index * @param file path to look up - * @return one of predefined enum values, or null if file was not known in the specified revision + * @return one of predefined enum values, or null if file was not known in the specified revision * FIXME EXCEPTIONS * @throws HgInvalidControlFileException * @throws HgInvalidRevisionException */ - /*package-local*/ Flags extractFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { + public Flags getFileFlags(int changesetRevIndex, Path file) throws HgInvalidRevisionException, HgInvalidControlFileException { int manifestRevIdx = fromChangelog(changesetRevIndex); IntMap resMap = new IntMap(2); content.iterate(manifestRevIdx, manifestRevIdx, true, new FileLookupInspector(encodingHelper, file, null, resMap));