changeset 689:5050ee565bd1

Issue 44: Renames/copies other than for the very first revision of a file are not recognized
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sat, 27 Jul 2013 22:06:14 +0200
parents 1499139a600a
children b286222158be
files src/org/tmatesoft/hg/core/HgFileRevision.java src/org/tmatesoft/hg/repo/HgDataFile.java src/org/tmatesoft/hg/repo/HgStatusCollector.java src/org/tmatesoft/hg/repo/HgWorkingCopyStatusCollector.java
diffstat 4 files changed, 116 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- 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;
 	}
--- 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 <code>true</code> 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 <code>true</code> 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. <em>Runtime exception</em>
 	 */
 	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. <em>Runtime exception</em>
@@ -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. <em>Runtime exception</em>
 	 */
@@ -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 <code>true</code> if this revision originates (as a result of copy/rename) from another file
+	 * @throws HgRuntimeException subclass thereof to indicate issues with the library. <em>Runtime exception</em>
+	 * @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 <code>true</code> 
+	 * @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
--- 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<Path> originals, int originalChangelogRevision) throws HgRuntimeException {
+	/*package-local*/static Path getOriginIfCopy(HgRepository hgRepo, Path fname, Nodeid fnameRev, Collection<Path> 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
--- 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<Nodeid> parents = new ArrayList<Nodeid>(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) {