diff src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java @ 690:b286222158be

Fix file.isCopy() use for status and cat commands
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 01 Aug 2013 21:45:47 +0200
parents 3ca4ae7bdd38
children 72fc7774b87e
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java	Sat Jul 27 22:06:14 2013 +0200
+++ b/src/org/tmatesoft/hg/core/HgChangesetFileSneaker.java	Thu Aug 01 21:45:47 2013 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011-2012 TMate Software Ltd
+ * Copyright (c) 2011-2013 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
@@ -16,14 +16,18 @@
  */
 package org.tmatesoft.hg.core;
 
+import java.util.ArrayDeque;
+
 import org.tmatesoft.hg.internal.ManifestRevision;
 import org.tmatesoft.hg.repo.HgDataFile;
 import org.tmatesoft.hg.repo.HgInvalidStateException;
 import org.tmatesoft.hg.repo.HgManifest;
+import org.tmatesoft.hg.repo.HgManifest.Flags;
 import org.tmatesoft.hg.repo.HgRepository;
 import org.tmatesoft.hg.repo.HgRuntimeException;
+import org.tmatesoft.hg.util.Outcome;
+import org.tmatesoft.hg.util.Pair;
 import org.tmatesoft.hg.util.Path;
-import org.tmatesoft.hg.util.Outcome;
 
 /**
  * Primary purpose is to provide information about file revisions at specific changeset. Multiple {@link #check(Path)} calls 
@@ -131,7 +135,6 @@
 			return checkResult;
 		}
 		Nodeid toExtract = null;
-		HgManifest.Flags extractRevFlags = null;
 		String phaseMsg = "Extract manifest revision failed";
 		try {
 			if (cachedManifest == null) {
@@ -141,27 +144,59 @@
 				// 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);
+				int csetIndex = repo.getChangelog().getRevisionIndex(cset);
+				int ccFileRevIndex = dataFile.getLastRevision(); // copy candidate
+				int csetFileEnds = dataFile.getChangesetRevisionIndex(ccFileRevIndex);
+				if (csetIndex > csetFileEnds) {
+					return new Outcome(Outcome.Kind.Success, String.format("%s: last known changeset for the file %s is %d. Follow renames is possible towards older changesets only", phaseMsg, file, csetFileEnds));
 				}
+				// XXX code is similar to that in HgStatusCollector#getOriginIfCopy. Why it's different in lastOrigin processing then?
+				// traceback stack keeps record of all files with isCopy(fileRev) == true we've tried to follow, so that we can try earlier file
+				// revisions in case followed fileRev didn't succeed
+				ArrayDeque<Pair<HgDataFile, Integer>> traceback = new ArrayDeque<Pair<HgDataFile, Integer>>();
+				do {
+					int ccCsetIndex = dataFile.getChangesetRevisionIndex(ccFileRevIndex);
+					if (ccCsetIndex <= csetIndex) {
+						// present dataFile is our (distant) origin
+						toExtract = dataFile.getRevision(ccFileRevIndex);
+						renamed = true;
+						break;
+					}
+					if (!dataFile.isCopy(ccFileRevIndex)) {
+						// nothing left to return to when traceback.isEmpty()
+						while (ccFileRevIndex == 0 && !traceback.isEmpty()) {
+							Pair<HgDataFile, Integer> lastTurnPoint = traceback.pop();
+							dataFile = lastTurnPoint.first();
+							ccFileRevIndex = lastTurnPoint.second(); // generally ccFileRevIndex != 0 here, but doesn't hurt to check, hence while
+							// fall through to shift down from the file revision we've already looked at
+						}
+						ccFileRevIndex--;
+						continue;
+					}
+					if (ccFileRevIndex > 0) {
+						// there's no reason to memorize turn point if it's the very first revision
+						// of the file and we won't be able to try any other earlier revision
+						traceback.push(new Pair<HgDataFile, Integer>(dataFile, ccFileRevIndex));
+					}
+					HgFileRevision origin = dataFile.getCopySource(ccFileRevIndex);
+					dataFile = repo.getFileNode(origin.getPath());
+					ccFileRevIndex = dataFile.getRevisionIndex(origin.getRevision());
+				} while (ccFileRevIndex >= 0);
+				// didn't get to csetIndex, no ancestor in file rename history found.
 			}
 		} catch (HgRuntimeException ex) {
 			checkResult = new Outcome(Outcome.Kind.Failure, phaseMsg, ex);
 			return checkResult;
 		}
 		if (toExtract != null) {
+			Flags extractRevFlags = cachedManifest.flags(dataFile.getPath());
 			fileRevision = new HgFileRevision(repo, toExtract, extractRevFlags, dataFile.getPath());
 			checkResult = new Outcome(Outcome.Kind.Success, String.format("File %s, revision %s found at changeset %s", dataFile.getPath(), toExtract.shortNotation(), cset.shortNotation()));
 			return checkResult;
 		} 
-		checkResult = new Outcome(Outcome.Kind.Success, String.format("File %s nor its origins were known at repository %s revision", file, cset.shortNotation()));
+		checkResult = new Outcome(Outcome.Kind.Success, String.format("File %s nor its origins were known at revision %s", file, cset.shortNotation()));
 		return checkResult;
 	}