# HG changeset patch # User Artem Tikhomirov # Date 1374955574 -7200 # Node ID 5050ee565bd12e3fc574fc497517f10fa38f1bec # Parent 1499139a600a20f2c36bf9b84a3efa9f0d29e621 Issue 44: Renames/copies other than for the very first revision of a file are not recognized diff -r 1499139a600a -r 5050ee565bd1 src/org/tmatesoft/hg/core/HgFileRevision.java --- a/src/org/tmatesoft/hg/core/HgFileRevision.java Sat Jul 27 20:15:37 2013 +0200 +++ b/src/org/tmatesoft/hg/core/HgFileRevision.java Sat Jul 27 22:06:14 2013 +0200 @@ -168,14 +168,12 @@ } private void checkCopy() throws HgRuntimeException { - HgDataFile fn = repo.getFileNode(path); - if (fn.isCopy()) { - if (fn.getRevision(0).equals(revision)) { - // this HgFileRevision represents first revision of the copy - isCopy = Boolean.TRUE; - origin = fn.getCopySourceName(); - return; - } + HgDataFile df = repo.getFileNode(path); + int revIdx = df.getRevisionIndex(revision); + if (df.isCopy(revIdx)) { + isCopy = Boolean.TRUE; + origin = df.getCopySource(revIdx).getPath(); + return; } isCopy = Boolean.FALSE; } diff -r 1499139a600a -r 5050ee565bd1 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Sat Jul 27 20:15:37 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Sat Jul 27 22:06:14 2013 +0200 @@ -28,6 +28,7 @@ import java.util.Arrays; import org.tmatesoft.hg.core.HgChangesetFileSneaker; +import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.FileUtils; @@ -371,22 +372,19 @@ } /** - * Tells whether this file originates from another repository file - * @return true if this file is a copy of another from the repository + * Tells whether first revision of this file originates from another repository file. + * This method is shorthand for {@link #isCopy(int) isCopy(0)} and it's advised to use {@link #isCopy(int)} instead. + * + * @return true if first revision of this file is a copy of some other from the repository * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ public boolean isCopy() throws HgRuntimeException { - if (metadata == null || !metadata.checked(0)) { - checkAndRecordMetadata(0); - } - if (!metadata.known(0)) { - return false; - } - return metadata.find(0, "copy") != null; + return isCopy(0); } /** - * Get name of the file this one was copied from. + * Get name of the file first revision of this one was copied from. + * Note, it's better to use {@link #getCopySource(int)} instead. * * @return name of the file origin * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception @@ -400,7 +398,7 @@ } /** - * + * Use {@link #getCopySource(int)} instead * @return revision this file was copied from * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception */ @@ -410,6 +408,53 @@ } throw new UnsupportedOperationException(); } + + /** + * Tell if specified file revision was created by copying or renaming another file + * + * @param fileRevisionIndex index of file revision to check + * @return true if this revision originates (as a result of copy/rename) from another file + * @throws HgRuntimeException subclass thereof to indicate issues with the library. Runtime exception + * @since 1.2 + */ + public boolean isCopy(int fileRevisionIndex) throws HgRuntimeException { + if (fileRevisionIndex == TIP) { + fileRevisionIndex = getLastRevision(); + } + if (wrongRevisionIndex(fileRevisionIndex) || fileRevisionIndex == BAD_REVISION || fileRevisionIndex == WORKING_COPY || fileRevisionIndex == NO_REVISION) { + throw new HgInvalidRevisionException(fileRevisionIndex); + } + + if (metadata == null || !metadata.checked(fileRevisionIndex)) { + checkAndRecordMetadata(fileRevisionIndex); + } + if (!metadata.known(fileRevisionIndex)) { + return false; + } + return metadata.find(fileRevisionIndex, "copy") != null; + } + + /** + * Find out which file and which revision of that file given revision originates from + * + * @param fileRevisionIndex file revision index of interest, it's assumed {@link #isCopy(int)} for the same revision is true + * @return origin revision descriptor + * @throws HgRuntimeException + * @throws UnsupportedOperationException if specified revision is not a {@link #isCopy(int) copy} revision + * @since 1.2 + */ + public HgFileRevision getCopySource(int fileRevisionIndex) throws HgRuntimeException { + if (fileRevisionIndex == TIP) { + fileRevisionIndex = getLastRevision(); + } + if (!isCopy(fileRevisionIndex)) { + throw new UnsupportedOperationException(); + } + Path.Source ps = getRepo().getSessionContext().getPathFactory(); + Path origin = ps.path(metadata.find(fileRevisionIndex, "copy")); + Nodeid originRev = Nodeid.fromAscii(metadata.find(fileRevisionIndex, "copyrev")); // XXX reuse/cache Nodeid + return new HgFileRevision(getRepo(), originRev, null, origin); + } /** * Get file flags recorded in the manifest diff -r 1499139a600a -r 5050ee565bd1 src/org/tmatesoft/hg/repo/HgStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgStatusCollector.java Sat Jul 27 20:15:37 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgStatusCollector.java Sat Jul 27 22:06:14 2013 +0200 @@ -26,6 +26,7 @@ import java.util.Map; import java.util.TreeSet; +import org.tmatesoft.hg.core.HgFileRevision; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.IntMap; import org.tmatesoft.hg.internal.ManifestRevision; @@ -316,7 +317,7 @@ } else { try { Path copyTarget = r2fname; - Path copyOrigin = detectCopies ? getOriginIfCopy(repo, copyTarget, r1Files, rev1) : null; + Path copyOrigin = detectCopies ? getOriginIfCopy(repo, copyTarget, r2.nodeid(copyTarget), r1Files, rev1) : null; if (copyOrigin != null) { inspector.copied(getPathPool().mangle(copyOrigin) /*pipe through pool, just in case*/, copyTarget); } else { @@ -361,29 +362,50 @@ return rv; } - /*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Collection originals, int originalChangelogRevision) throws HgRuntimeException { + /*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Nodeid fnameRev, Collection originals, int originalChangesetIndex) throws HgRuntimeException { HgDataFile df = hgRepo.getFileNode(fname); if (!df.exists()) { String msg = String.format("Didn't find file '%s' in the repo. Perhaps, bad storage name conversion?", fname); - throw new HgInvalidFileException(msg, null).setFileName(fname).setRevisionIndex(originalChangelogRevision); + throw new HgInvalidFileException(msg, null).setFileName(fname).setRevisionIndex(originalChangesetIndex); } - while (df.isCopy()) { - Path original = df.getCopySourceName(); - if (originals.contains(original)) { - df = hgRepo.getFileNode(original); - int changelogRevision = df.getChangesetRevisionIndex(0); - if (changelogRevision <= originalChangelogRevision) { + assert fnameRev != null; + assert !Nodeid.NULL.equals(fnameRev); + int fileRevIndex = fnameRev == null ? 0 : df.getRevisionIndex(fnameRev); + Path lastOriginFound = null; + while(fileRevIndex >=0) { + if (!df.isCopy(fileRevIndex)) { + fileRevIndex--; + continue; + } + int csetRevIndex = df.getChangesetRevisionIndex(fileRevIndex); + if (csetRevIndex <= originalChangesetIndex) { + // we've walked past originalChangelogRevIndex and no chances we'll find origin + // if we get here, it means fname's origin is not from the base revision + return null; + } + HgFileRevision origin = df.getCopySource(fileRevIndex); + // prepare for the next step, df(copyFromFileRev) would point to copy origin and its revision + df = hgRepo.getFileNode(origin.getPath()); + int copyFromFileRevIndex = df.getRevisionIndex(origin.getRevision()); + if (originals.contains(origin.getPath())) { + int copyFromCsetIndex = df.getChangesetRevisionIndex(copyFromFileRevIndex); + if (copyFromCsetIndex <= originalChangesetIndex) { // copy/rename source was known prior to rev1 // (both r1Files.contains is true and original was created earlier than rev1) // without r1Files.contains changelogRevision <= rev1 won't suffice as the file // might get removed somewhere in between (changelogRevision < R < rev1) - return original; + return origin.getPath(); } - break; // copy/rename done later - } - df = hgRepo.getFileNode(original); // try more steps away + // copy/rename happened in [copyFromCsetIndex..target], let's see if + // origin wasn't renamed once more in [originalChangesetIndex..copyFromCsetIndex] + lastOriginFound = origin.getPath(); + // FALL-THROUGH + } + // try more steps away + // copyFromFileRev or one of its predecessors might be copies as well + fileRevIndex = copyFromFileRevIndex; // df is already origin file } - return null; + return lastOriginFound; } // XXX for r1..r2 status, only modified, added, removed (and perhaps, clean) make sense diff -r 1499139a600a -r 5050ee565bd1 src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java --- a/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Sat Jul 27 20:15:37 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java Sat Jul 27 22:06:14 2013 +0200 @@ -420,10 +420,24 @@ // removed: nothing to report, if (ds.checkNormal(fname) != null || ds.checkMerged(fname) != null) { try { - Path origin = HgStatusCollector.getOriginIfCopy(repo, fname, baseRevNames, baseRevision); - if (origin != null) { - inspector.copied(getPathPool().mangle(origin), fname); - return; + // FIXME refactor, done numerous time e.g. in TestStatus#testStatusCommand with base = 3 + ArrayList parents = new ArrayList(2); + parents.add(ds.parents().first()); + parents.add(ds.parents().second()); + parents.remove(Nodeid.NULL); + // try either parent if file came through one of them, or both + for (Nodeid parent : parents) { + int csetIndex = repo.getChangelog().getRevisionIndex(parent); + Nodeid fileRev = repo.getManifest().getFileRevision(csetIndex, fname); + if (fileRev == null) { + continue; + } + // see if file revision known in this parent got copied from one of baseRevNames + Path origin = HgStatusCollector.getOriginIfCopy(repo, fname, fileRev, baseRevNames, baseRevision); + if (origin != null) { + inspector.copied(getPathPool().mangle(origin), fname); + return; + } } // fall-through, report as added } catch (HgInvalidFileException ex) {