# HG changeset patch # User Artem Tikhomirov # Date 1365789021 -7200 # Node ID e49f9d9513fa2e92c9e92b07f7d6ba168777ee46 # Parent becd2a1310a2a97f5270d651b27bb42197070684 Partial blame when start/end revisions are in the middle of a single filename history diff -r becd2a1310a2 -r e49f9d9513fa src/org/tmatesoft/hg/internal/IntVector.java --- a/src/org/tmatesoft/hg/internal/IntVector.java Fri Apr 12 18:30:55 2013 +0200 +++ b/src/org/tmatesoft/hg/internal/IntVector.java Fri Apr 12 19:50:21 2013 +0200 @@ -84,6 +84,13 @@ count = 0; } + public void trimTo(int newSize) { + if (newSize < 0 || newSize > count) { + throw new IllegalArgumentException(String.format("Can't trim vector of size %d to %d", count, newSize)); + } + count = newSize; + } + public void trimToSize() { data = toArray(true); } diff -r becd2a1310a2 -r e49f9d9513fa src/org/tmatesoft/hg/repo/HgBlameFacility.java --- a/src/org/tmatesoft/hg/repo/HgBlameFacility.java Fri Apr 12 18:30:55 2013 +0200 +++ b/src/org/tmatesoft/hg/repo/HgBlameFacility.java Fri Apr 12 19:50:21 2013 +0200 @@ -79,7 +79,7 @@ if (wrongRevisionIndex(changelogRevIndexStart) || wrongRevisionIndex(changelogRevIndexEnd)) { throw new IllegalArgumentException(); } - // Note, changelogRevisionIndex may be TIP, while the code below doesn't tolerate constants + // Note, changelogRevIndexEnd may be TIP, while the code below doesn't tolerate constants // int lastRevision = df.getRepo().getChangelog().getLastRevision(); if (changelogRevIndexEnd == TIP) { @@ -101,8 +101,18 @@ fileCompleteHistory.addFirst(fileHistory); // to get the list in old-to-new order nextChunk = fileHistory; bh.useFileUpTo(currentFile, fileLastClogRevIndex); - if (currentFile.isCopy()) { - // TODO SessionContext.getPathFactory() and replace all Path.create + if (fileHistory.changeset(0) > changelogRevIndexStart && currentFile.isCopy()) { + // fileHistory.changeset(0) is the earliest revision we know about so far, + // once we get to revisions earlier than the requested start, stop digging. + // The reason there's NO == (i.e. not >=) because: + // (easy): once it's equal, we've reached our intended start + // (hard): if changelogRevIndexStart happens to be exact start of one of renames in the + // chain of renames (test-annotate2 repository, file1->file1a->file1b, i.e. points + // to the very start of file1a or file1 history), presence of == would get us to the next + // chunk and hence changed parents of present chunk's first element. Our annotate alg + // relies on parents only (i.e. knows nothing about 'last iteration element') to find out + // what to compare, and hence won't report all lines of 'last iteration element' (which is the + // first revision of the renamed file) as "added in this revision", leaving gaps in annotate HgRepository repo = currentFile.getRepo(); Nodeid originLastRev = currentFile.getCopySourceRevision(); currentFile = repo.getFileNode(currentFile.getCopySourceName()); @@ -110,9 +120,10 @@ // XXX perhaps, shall fail with meaningful exception if new file doesn't exist (.i/.d not found for whatever reason) // or source revision is missing? } else { + fileHistory.chopAtChangeset(changelogRevIndexStart); currentFile = null; // stop iterating } - } while (currentFile != null && fileLastClogRevIndex >= changelogRevIndexStart); + } while (currentFile != null && fileLastClogRevIndex > changelogRevIndexStart); // fileCompleteHistory is in (origin, intermediate target, ultimate target) order int[] fileClogParentRevs = new int[2]; @@ -406,7 +417,30 @@ target.originFileRev = fileRevsToVisit.get(0); // files to visit are new to old target.originChangelogRev = changeset(target.originFileRev); } - + + /** + * Mark revision closest(ceil) to specified as the very first one (no parents) + */ + public void chopAtChangeset(int firstChangelogRevOfInterest) { + if (firstChangelogRevOfInterest == 0) { + return; // nothing to do + } + int i = 0, x = fileRevsToVisit.size(), fileRev = BAD_REVISION; + // fileRevsToVisit is new to old, greater numbers to smaller + while (i < x && changeset(fileRev = fileRevsToVisit.get(i)) >= firstChangelogRevOfInterest) { + i++; + } + assert fileRev != BAD_REVISION; // there's at least 1 revision in fileRevsToVisit + if (i == x && changeset(fileRev) != firstChangelogRevOfInterest) { + assert false : "Requested changeset shall belong to the chunk"; + return; + } + fileRevsToVisit.trimTo(i); // no need to iterate more + // pretend fileRev got no parents + fileParentRevs.set(fileRev * 2, NO_REVISION); + fileParentRevs.set(fileRev, NO_REVISION); + } + public int[] fileRevisions(HgIterateDirection iterateOrder) { // fileRevsToVisit is { r10, r7, r6, r5, r0 }, new to old int[] rv = fileRevsToVisit.toArray(); diff -r becd2a1310a2 -r e49f9d9513fa test/org/tmatesoft/hg/test/TestBlame.java --- a/test/org/tmatesoft/hg/test/TestBlame.java Fri Apr 12 18:30:55 2013 +0200 +++ b/test/org/tmatesoft/hg/test/TestBlame.java Fri Apr 12 19:50:21 2013 +0200 @@ -18,11 +18,15 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import static org.tmatesoft.hg.core.HgIterateDirection.NewToOld; +import static org.tmatesoft.hg.core.HgIterateDirection.OldToNew; import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; import static org.tmatesoft.hg.repo.HgRepository.TIP; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.IOException; +import java.io.OutputStream; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; @@ -186,6 +190,39 @@ errorCollector.assertTrue(String.format("Annotate API reported excessive diff: %s ", apiResult.toString()), apiResult.isEmpty()); } + + @Test + public void testPartialHistoryFollow() throws Exception { + HgRepository repo = Configuration.get().find("test-annotate2"); + HgDataFile df = repo.getFileNode("file1b.txt"); + // rev3: file1 -> file1a, rev7: file1a -> file1b, tip: rev10 + HgBlameFacility bf = new HgBlameFacility(df); + DiffOutInspector insp = new DiffOutInspector(new PrintStream(new OutputStream() { + @Override + public void write(int b) throws IOException { + // NULL OutputStream + } + })); + // rev6 changes rev4, rev4 changes rev3. Plus, anything changed + // earlier than rev2 shall be reported as new from change3 + int[] change_2_8_new2old = new int[] {4, 6, 3, 4, -1, 3}; + int[] change_2_8_old2new = new int[] {-1, 3, 3, 4, 4, 6 }; + bf.annotate(2, 8, insp, NewToOld); + Assert.assertArrayEquals(change_2_8_new2old, insp.getReportedRevisionPairs()); + insp.reset(); + bf.annotate(2, 8, insp, OldToNew); + Assert.assertArrayEquals(change_2_8_old2new, insp.getReportedRevisionPairs()); + // same as 2 to 8, with addition of rev9 changes rev7 (rev6 to rev7 didn't change content, only name) + int[] change_3_9_new2old = new int[] {7, 9, 4, 6, 3, 4, -1, 3 }; + int[] change_3_9_old2new = new int[] {-1, 3, 3, 4, 4, 6, 7, 9 }; + insp.reset(); + bf.annotate(3, 9, insp, NewToOld); + Assert.assertArrayEquals(change_3_9_new2old, insp.getReportedRevisionPairs()); + insp.reset(); + bf.annotate(3, 9, insp, OldToNew); + Assert.assertArrayEquals(change_3_9_old2new, insp.getReportedRevisionPairs()); + } + @Test public void testAnnotateCmdFollowNoFollow() throws Exception { HgRepoFacade hgRepoFacade = new HgRepoFacade(); @@ -369,6 +406,14 @@ return x; } + int[] getReportedRevisionPairs() { + return reportedRevisionPairs.toArray(); + } + + void reset() { + reportedRevisionPairs.clear(); + } + public void same(EqualBlock block) { // nothing }