Mercurial > hg4j
changeset 557:b9e5ac26dd83
Annotate: Line annotation needs true line position from merged blocks; test-annotate repo updated to show elements from both parents in the merged revision
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Sun, 24 Feb 2013 00:11:40 +0100 |
parents | e55f17a7a195 |
children | 154718ae23ed |
files | src/org/tmatesoft/hg/core/HgCallbackTargetException.java src/org/tmatesoft/hg/internal/FileAnnotation.java src/org/tmatesoft/hg/repo/HgBlameFacility.java test-data/test-repos.jar test/org/tmatesoft/hg/test/TestBlame.java |
diffstat | 5 files changed, 303 insertions(+), 153 deletions(-) [+] |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Fri Feb 22 20:21:24 2013 +0100 +++ b/src/org/tmatesoft/hg/core/HgCallbackTargetException.java Sun Feb 24 00:11:40 2013 +0100 @@ -32,8 +32,8 @@ * <p>It's intentionally not a subclass of {@link HgException} to avoid get mixed with library own errors and be processed separately. * * <p>Top-level API handlers ({@link HgStatusHandler}, {@link HgManifestHandler}, {@link HgChangesetHandler}, etc) allow to throw - * HgCallbackTargetException from their methods. Exceptions throws this way are not handled in corresponding commands, except for - * revision or file name specification, unless already set. The, these exceptions go straight to the command caller. + * HgCallbackTargetException from their methods. Exceptions thrown this way are not handled in corresponding commands, except for + * revision or file name specification, unless already set. Then, these exceptions go straight to the command caller. * * @author Artem Tikhomirov * @author TMate Software Ltd.
--- a/src/org/tmatesoft/hg/internal/FileAnnotation.java Fri Feb 22 20:21:24 2013 +0100 +++ b/src/org/tmatesoft/hg/internal/FileAnnotation.java Sun Feb 24 00:11:40 2013 +0100 @@ -16,12 +16,18 @@ */ package org.tmatesoft.hg.internal; -import java.util.LinkedList; +import java.util.Formatter; import org.tmatesoft.hg.core.HgIterateDirection; import org.tmatesoft.hg.repo.HgBlameFacility; +import org.tmatesoft.hg.repo.HgInvalidStateException; +import org.tmatesoft.hg.repo.HgBlameFacility.AddBlock; +import org.tmatesoft.hg.repo.HgBlameFacility.BlockData; +import org.tmatesoft.hg.repo.HgBlameFacility.ChangeBlock; +import org.tmatesoft.hg.repo.HgBlameFacility.DeleteBlock; +import org.tmatesoft.hg.repo.HgBlameFacility.EqualBlock; +import org.tmatesoft.hg.repo.HgBlameFacility.RevisionDescriptor; import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgBlameFacility.*; /** * Produce output like 'hg annotate' does @@ -37,7 +43,7 @@ /** * Not necessarily invoked sequentially by line numbers */ - void line(int lineNumber, int changesetRevIndex, LineDescriptor ld); + void line(int lineNumber, int changesetRevIndex, BlockData lineContent, LineDescriptor ld); } public interface LineDescriptor { @@ -56,60 +62,168 @@ af.annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld); } - // blocks deleted in the target, as reported at the previous step - private LinkedList<DeleteBlock> deleted = new LinkedList<DeleteBlock>(); - // blocks deleted in the origin, to become deletions in target at the next step - private LinkedList<DeleteBlock> newDeleted = new LinkedList<DeleteBlock>(); - // keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from previous step - // XXX smth like IntSliceVector to access triples (or slices of any size, in fact) - // with easy indexing, e.g. #get(sliceIndex, indexWithinSlice) - // and vect.get(7,2) instead of vect.get(7*SIZEOF_SLICE+2) - private IntVector identical = new IntVector(20 * 3, 2 * 3); + // keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from some previous step + private RangeSeq activeEquals; // equal blocks of the current iteration, to be recalculated before next step // to track line number (current target to ultimate target) mapping - private IntVector newIdentical = new IntVector(20 * 3, 2 * 3); + private RangeSeq intermediateEquals = new RangeSeq(); private boolean[] knownLines; private final LineInspector delegate; + private RevisionDescriptor revisionDescriptor; + private BlockData lineContent; + + private IntMap<RangeSeq> mergedRanges = new IntMap<RangeSeq>(10); + private IntMap<RangeSeq> equalRanges = new IntMap<RangeSeq>(10); + private boolean activeEqualsComesFromMerge = false; public FileAnnotation(LineInspector lineInspector) { delegate = lineInspector; } public void start(RevisionDescriptor rd) { + revisionDescriptor = rd; if (knownLines == null) { - knownLines = new boolean[rd.target().elementCount()]; + lineContent = rd.target(); + knownLines = new boolean[lineContent.elementCount()]; + activeEquals = new RangeSeq(); + activeEquals.add(0, 0, knownLines.length); + equalRanges.put(rd.targetChangesetIndex(), activeEquals); + } else { + activeEquals = equalRanges.get(rd.targetChangesetIndex()); + if (activeEquals == null) { + // we didn't see this target revision as origin yet + // the only way this may happen is that this revision was a merge parent + activeEquals = mergedRanges.get(rd.targetChangesetIndex()); + activeEqualsComesFromMerge = true; + if (activeEquals == null) { + throw new HgInvalidStateException(String.format("Can't find previously visited revision %d (while in %d->%1$d diff)", rd.targetChangesetIndex(), rd.originChangesetIndex())); + } + } } } - // private static void ppp(IntVector v) { - // for (int i = 0; i < v.size(); i+= 3) { - // int len = v.get(i+2); - // System.out.printf("[%d..%d) == [%d..%d); ", v.get(i), v.get(i) + len, v.get(i+1), v.get(i+1) + len); - // } - // System.out.println(); - // } + public void done(RevisionDescriptor rd) { + // update line numbers of the intermediate target to point to ultimate target's line numbers + RangeSeq v = intermediateEquals.intersect(activeEquals); + if (activeEqualsComesFromMerge) { + mergedRanges.put(rd.originChangesetIndex(), v); + } else { + equalRanges.put(rd.originChangesetIndex(), v); + } + intermediateEquals.clear(); + activeEquals = null; + activeEqualsComesFromMerge = false; + revisionDescriptor = null; + } - public void done(RevisionDescriptor rd) { - if (identical.size() > 0) { - // update line numbers of the intermediate target to point to ultimate target's line numbers - IntVector v = new IntVector(identical.size(), 2 * 3); - for (int i = 0; i < newIdentical.size(); i += 3) { - int originLine = newIdentical.get(i); - int targetLine = newIdentical.get(i + 1); - int length = newIdentical.get(i + 2); + public void same(EqualBlock block) { + intermediateEquals.add(block.originStart(), block.targetStart(), block.length()); + } + + public void added(AddBlock block) { + RangeSeq rs = null; + if (revisionDescriptor.isMerge() && block.originChangesetIndex() == revisionDescriptor.mergeChangesetIndex()) { + rs = mergedRanges.get(revisionDescriptor.mergeChangesetIndex()); + if (rs == null) { + mergedRanges.put(revisionDescriptor.mergeChangesetIndex(), rs = new RangeSeq()); + } + } + if (activeEquals.size() == 0) { + return; + } + for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) { + int lnInFinal = activeEquals.mapLineIndex(ln); + if (lnInFinal != -1/* && !knownLines[lnInFinal]*/) { + if (rs != null) { + rs.add(block.insertedAt() + i, lnInFinal, 1); + } else { + delegate.line(lnInFinal, block.targetChangesetIndex(), lineContent.elementAt(lnInFinal), new LineDescriptorImpl()); + } + knownLines[lnInFinal] = true; + } + } + } + + public void changed(ChangeBlock block) { + added(block); + } + + public void deleted(DeleteBlock block) { + } + + private final class LineDescriptorImpl implements LineDescriptor { + LineDescriptorImpl() { + } + + public int totalLines() { + return FileAnnotation.this.knownLines.length; + } + } + + private static class RangeSeq { + // XXX smth like IntSliceVector to access triples (or slices of any size, in fact) + // with easy indexing, e.g. #get(sliceIndex, indexWithinSlice) + // and vect.get(7,2) instead of vect.get(7*SIZEOF_SLICE+2) + private final IntVector ranges = new IntVector(3*10, 3*5); + private int count; + + public void add(int start1, int start2, int length) { + if (count > 0) { + int lastIndex = 3 * (count-1); + int lastS1 = ranges.get(lastIndex); + int lastS2 = ranges.get(lastIndex + 1); + int lastLen = ranges.get(lastIndex + 2); + if (start1 == lastS1 + lastLen && start2 == lastS2 + lastLen) { + // new range continues the previous one - just increase the length + ranges.set(lastIndex + 2, lastLen + length); + return; + } + } + ranges.add(start1, start2, length); + count++; + } + + public void clear() { + ranges.clear(); + count = 0; + } + + public int size() { + return count; + } + + public int mapLineIndex(int ln) { + for (int i = 0; i < ranges.size(); i += 3) { + int s1 = ranges.get(i); + if (s1 > ln) { + return -1; + } + int l = ranges.get(i+2); + if (s1 + l > ln) { + int s2 = ranges.get(i + 1); + return s2 + (ln - s1); + } + } + return -1; + } + + public RangeSeq intersect(RangeSeq target) { + RangeSeq v = new RangeSeq(); + for (int i = 0; i < ranges.size(); i += 3) { + int originLine = ranges.get(i); + int targetLine = ranges.get(i + 1); + int length = ranges.get(i + 2); int startTargetLine = -1, startOriginLine = -1, c = 0; for (int j = 0; j < length; j++) { - int lnInFinal = mapLineIndex(targetLine + j); + int lnInFinal = target.mapLineIndex(targetLine + j); if (lnInFinal == -1 || (startTargetLine != -1 && lnInFinal != startTargetLine + c)) { // the line is not among "same" in ultimate origin // or belongs to another/next "same" chunk if (startOriginLine == -1) { continue; } - v.add(startOriginLine); - v.add(startTargetLine); - v.add(c); + v.add(startOriginLine, startTargetLine, c); c = 0; startOriginLine = startTargetLine = -1; // fall-through to check if it's not complete miss but a next chunk @@ -120,6 +234,7 @@ startTargetLine = lnInFinal; c = 1; } else { + // lnInFinal != startTargetLine + s is covered above assert lnInFinal == startTargetLine + c; c++; } @@ -127,94 +242,28 @@ } if (startOriginLine != -1) { assert c > 0; - v.add(startOriginLine); - v.add(startTargetLine); - v.add(c); + v.add(startOriginLine, startTargetLine, c); } } - newIdentical.clear(); - identical = v; - } else { - IntVector li = newIdentical; - newIdentical = identical; - identical = li; - } - LinkedList<DeleteBlock> ld = newDeleted; - deleted.clear(); - newDeleted = deleted; - deleted = ld; - } - - public void same(EqualBlock block) { - newIdentical.add(block.originStart()); - newIdentical.add(block.targetStart()); - newIdentical.add(block.length()); - } - - public void added(AddBlock block) { - for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) { - int lnInFinal = mapLineIndex(ln); - if (lnInFinal != -1 && !knownLines[lnInFinal]) { - delegate.line(lnInFinal, block.targetChangesetIndex(), new LineDescriptorImpl()); - knownLines[lnInFinal] = true; - } + return v; } - } - - public void changed(ChangeBlock block) { - deleted(block); - added(block); - } - - public void deleted(DeleteBlock block) { - newDeleted.add(block); - } - - // line - index in the target - private boolean isDeleted(int line) { - for (DeleteBlock b : deleted) { - if (b.firstRemovedLine() > line) { - break; + + @SuppressWarnings("unused") + public CharSequence dump() { + StringBuilder sb = new StringBuilder(); + Formatter f = new Formatter(sb); + for (int i = 0; i < ranges.size(); i += 3) { + int s1 = ranges.get(i); + int s2 = ranges.get(i + 1); + int len = ranges.get(i + 2); + f.format("[%d..%d) == [%d..%d); ", s1, s1 + len, s2, s2 + len); } - // line >= b.firstRemovedLine - if (b.firstRemovedLine() + b.totalRemovedLines() > line) { - return true; - } + return sb; } - return false; - } - - // map target lines to the lines of the revision being annotated (the one that came first) - private int mapLineIndex(int ln) { - if (isDeleted(ln)) { - return -1; - } - if (identical.isEmpty()) { - return ln; - } - for (int i = 0; i < identical.size(); i += 3) { - final int originStart = identical.get(i); - if (originStart > ln) { - // assert false; - return -1; - } - // ln >= b.originStart - final int length = identical.get(i + 2); - if (originStart + length > ln) { - int targetStart = identical.get(i + 1); - return targetStart + (ln - originStart); - } - } - // assert false; - return -1; - } - - private final class LineDescriptorImpl implements LineDescriptor { - LineDescriptorImpl() { - } - - public int totalLines() { - return FileAnnotation.this.knownLines.length; + + @Override + public String toString() { + return String.format("RangeSeq[%d]:%s", count, dump()); } } } \ No newline at end of file
--- a/src/org/tmatesoft/hg/repo/HgBlameFacility.java Fri Feb 22 20:21:24 2013 +0100 +++ b/src/org/tmatesoft/hg/repo/HgBlameFacility.java Sun Feb 24 00:11:40 2013 +0100 @@ -442,12 +442,15 @@ p2MergeCommon.combineAndMarkRangesWithTarget(s2From, s2To - s2From, csetOrigin, csetMergeParent, mergeRanges); /* - * Usecases: + * Usecases, how it USED TO BE initially: * 3 lines changed to 10 lines. range of 10 lines breaks down to 2 from p2, 3 from p1, and 5 from p2. * We report: 2 lines changed to 2(p2), then 1 line changed with 3(p1) and 5 lines added from p2. * * 10 lines changed to 3 lines, range of 3 lines breaks down to 2 line from p1 and 1 line from p2. - * We report: 2 lines changed to 2(p1) and 8 lines changed to 1(p2) + * We report: 2 lines changed to 2(p1) and 8 lines changed to 1(p2) + * + * NOW, lines from p2 are always reported as pure add (since we need their insertion point to be in p2, not in p1) + * and we try to consume p1 changes as soon as we see first p1's range */ int s1TotalLines = s1To - s1From, s1ConsumedLines = 0, s1Start = s1From; @@ -457,22 +460,30 @@ final int rangeLen = mergeRanges.get(i+2); final boolean lastRange = i+3 >= mergeRanges.size(); final int s1LinesLeft = s1TotalLines - s1ConsumedLines; - // how many lines we may reported as changed (don't use more than in range unless it's the very last range) + // how many lines we may report as changed (don't use more than in range unless it's the very last range) final int s1LinesToBorrow = lastRange ? s1LinesLeft : Math.min(s1LinesLeft, rangeLen); - if (s1LinesToBorrow > 0) { + if (rangeOrigin != csetMergeParent && s1LinesToBorrow > 0) { ChangeBlockImpl block = getChangeBlock(s1Start, s1LinesToBorrow, rangeStart, rangeLen); block.setOriginAndTarget(rangeOrigin, csetTarget); insp.changed(block); s1ConsumedLines += s1LinesToBorrow; s1Start += s1LinesToBorrow; } else { - ChangeBlockImpl block = getAddBlock(rangeStart, rangeLen, s1Start); + int blockInsPoint = rangeOrigin != csetMergeParent ? s1Start : p2MergeCommon.reverseMapLine(rangeStart); + ChangeBlockImpl block = getAddBlock(rangeStart, rangeLen, blockInsPoint); block.setOriginAndTarget(rangeOrigin, csetTarget); insp.added(block); } } if (s1ConsumedLines != s1TotalLines) { - throw new HgInvalidStateException(String.format("Expected to process %d lines, but actually was %d", s1TotalLines, s1ConsumedLines)); + assert s1ConsumedLines < s1TotalLines : String.format("Expected to process %d lines, but actually was %d", s1TotalLines, s1ConsumedLines); + // either there were no ranges from p1, whole s2From..s2To range came from p2, shall report as deleted + // or the ranges found were not enough to consume whole s2From..s2To + // The "deletion point" is shifted to the end of last csetOrigin->csetTarget change + int s2DeletePoint = s2From + s1ConsumedLines; + ChangeBlockImpl block = new ChangeBlockImpl(annotatedRevision.origin, null, s1Start, s1To - s1Start, -1, -1, -1, s2DeletePoint); + block.setOriginAndTarget(csetOrigin, csetTarget); + insp.deleted(block); } } else { ChangeBlockImpl block = getChangeBlock(s1From, s1To - s1From, s2From, s2To - s2From); @@ -730,6 +741,7 @@ static class EqualBlocksCollector implements DiffHelper.MatchInspector<LineSequence> { + // FIXME replace with RangeSeq private final IntVector matches = new IntVector(10*3, 2*3); public void begin(LineSequence s1, LineSequence s2) { @@ -771,6 +783,25 @@ } } + /** + * find out line index in origin that matches specifid target line + */ + public int reverseMapLine(int targetLine) { + for (int i = 0; i < matches.size(); i +=3) { + int os = matches.get(i); + int ts = matches.get(i + 1); + int l = matches.get(i + 2); + if (ts > targetLine) { + return -1; + } + if (ts + l > targetLine) { + return os + (targetLine - ts); + } + } + return -1; + } + + /* * intersects [start..start+length) with ranges of target lines, and based on the intersection * breaks initial range into smaller ranges and records them into result, with marker to indicate @@ -877,6 +908,13 @@ public int fileRevisionIndex() { return fileRevIndex; } + @Override + public String toString() { + if (isMerge()) { + return String.format("[%d,%d->%d]", originCset, mergeCset, targetCset); + } + return String.format("[%d->%d]", originCset, targetCset); + } } public static void main(String[] args) {
--- a/test/org/tmatesoft/hg/test/TestBlame.java Fri Feb 22 20:21:24 2013 +0100 +++ b/test/org/tmatesoft/hg/test/TestBlame.java Sun Feb 24 00:11:40 2013 +0100 @@ -22,10 +22,13 @@ import static org.tmatesoft.hg.repo.HgRepository.TIP; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.PrintStream; import java.util.Arrays; import java.util.LinkedHashSet; import java.util.LinkedList; +import java.util.ListIterator; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.junit.Assert; @@ -37,15 +40,15 @@ import org.tmatesoft.hg.internal.FileAnnotation.LineInspector; import org.tmatesoft.hg.internal.IntVector; import org.tmatesoft.hg.repo.HgBlameFacility; -import org.tmatesoft.hg.repo.HgDataFile; -import org.tmatesoft.hg.repo.HgLookup; -import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgBlameFacility.AddBlock; import org.tmatesoft.hg.repo.HgBlameFacility.Block; import org.tmatesoft.hg.repo.HgBlameFacility.BlockData; import org.tmatesoft.hg.repo.HgBlameFacility.ChangeBlock; import org.tmatesoft.hg.repo.HgBlameFacility.DeleteBlock; import org.tmatesoft.hg.repo.HgBlameFacility.EqualBlock; +import org.tmatesoft.hg.repo.HgDataFile; +import org.tmatesoft.hg.repo.HgLookup; +import org.tmatesoft.hg.repo.HgRepository; /** * @@ -56,6 +59,7 @@ @Rule public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); + private ExecHelper eh; @Test @@ -76,29 +80,46 @@ } @Test - public void testFileAnnotate() throws Exception { + public void testFileLineAnnotate1() throws Exception { HgRepository repo = new HgLookup().detectFromWorkingDir(); final String fname = "src/org/tmatesoft/hg/internal/PatchGenerator.java"; HgDataFile df = repo.getFileNode(fname); OutputParser.Stub op = new OutputParser.Stub(); - ExecHelper eh = new ExecHelper(op, null); + eh = new ExecHelper(op, null); for (int startChangeset : new int[] { 539, 541 /*, TIP */}) { - FileAnnotateInspector fa = new FileAnnotateInspector(); - FileAnnotation.annotate(df, startChangeset, fa); - + doLineAnnotateTest(df, startChangeset, op); + } + } + + private void doLineAnnotateTest(HgDataFile df, int cs, OutputParser.Stub op) throws InterruptedException, IOException { + FileAnnotateInspector fa = new FileAnnotateInspector(); + FileAnnotation.annotate(df, cs, fa); - op.reset(); - eh.run("hg", "annotate", "-r", startChangeset == TIP ? "tip" : String.valueOf(startChangeset), fname); - - String[] hgAnnotateLines = splitLines(op.result()); - assertTrue("[sanity]", hgAnnotateLines.length > 0); - assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, fa.lineRevisions.length); + op.reset(); + eh.run("hg", "annotate", "-r", cs == TIP ? "tip" : String.valueOf(cs), df.getPath().toString()); + + String[] hgAnnotateLines = splitLines(op.result()); + assertTrue("[sanity]", hgAnnotateLines.length > 0); + assertEquals("Number of lines reported by native annotate and our impl", hgAnnotateLines.length, fa.lineRevisions.length); + + for (int i = 0; i < fa.lineRevisions.length; i++) { + int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':'))); + errorCollector.assertEquals(String.format("Revision mismatch for line %d", i+1), hgAnnotateRevIndex, fa.lineRevisions[i]); + String hgAnnotateLine = hgAnnotateLines[i].substring(hgAnnotateLines[i].indexOf(':') + 1); + String apiLine = fa.line(i).trim(); + errorCollector.assertEquals(hgAnnotateLine.trim(), apiLine); + } + } - for (int i = 0; i < fa.lineRevisions.length; i++) { - int hgAnnotateRevIndex = Integer.parseInt(hgAnnotateLines[i].substring(0, hgAnnotateLines[i].indexOf(':'))); - errorCollector.assertEquals(String.format("Revision mismatch for line %d", i+1), hgAnnotateRevIndex, fa.lineRevisions[i]); - } + @Test + public void testFileLineAnnotate2() throws Exception { + HgRepository repo = Configuration.get().find("test-annotate"); + HgDataFile df = repo.getFileNode("file1"); + OutputParser.Stub op = new OutputParser.Stub(); + eh = new ExecHelper(op, repo.getWorkingDir()); + for (int cs : new int[] { 4, 6, TIP/*, 8 FIXME find out how come hg annotate doesn't see re-added line in rev4*/}) { + doLineAnnotateTest(df, cs, op); } } @@ -112,6 +133,41 @@ af.annotate(df, TIP, dump, HgIterateDirection.OldToNew); LinkedList<String> apiResult = new LinkedList<String>(Arrays.asList(splitLines(bos.toString()))); + /* + * FIXME this is an ugly hack to deal with the way `hg diff -c <mergeRev>` describes the change + * and our merge handling approach. For merged revision m, and lines changed both in p1 and p2 + * we report lines from p2 as pure additions, regardless of intersecting p1 changes (which + * are reported as deletions, if no sufficient changed lines in m found) + * So, here we try to combine deletion that follows a change (based on identical insertionPoint) + * into a single change + * To fix, need to find better approach to find out reference info (i.e. `hg diff -c` is flawed in this case, + * as it uses first parent only). + */ + Pattern fix = Pattern.compile("@@ -(\\d+),(\\d+) \\+(\\d+),(\\d+) @@"); + int v1, v2, v3, v4; + v1 = v2 = v3 = v4 = -1; + for (ListIterator<String> it = apiResult.listIterator(); it.hasNext();) { + String n = it.next(); + Matcher m = fix.matcher(n); + if (m.find()) { + int d1 = Integer.parseInt(m.group(1)); + int d2 = Integer.parseInt(m.group(2)); + int d3 = Integer.parseInt(m.group(3)); + int d4 = Integer.parseInt(m.group(4)); + if (v1 == d1 && d4 == 0) { + it.previous(); // shift to current element + it.previous(); // to real previous + it.remove(); + it.next(); + it.set(String.format("@@ -%d,%d +%d,%d @@", v1, v2+d2, v3, v4)); + } + v1 = d1; + v2 = d2; + v3 = d3; + v4 = d4; + } + } + LineGrepOutputParser gp = new LineGrepOutputParser("^@@.+"); ExecHelper eh = new ExecHelper(gp, repo.getWorkingDir()); for (int cs : dump.getReportedTargetRevisions()) { @@ -199,18 +255,18 @@ HgRepository repo = new HgLookup().detect("/home/artem/hg/junit-test-repos/test-annotate/"); HgDataFile df = repo.getFileNode("file1"); HgBlameFacility af = new HgBlameFacility(); - DiffOutInspector dump = new DiffOutInspector(System.out); - dump.needRevisions(true); - af.annotate(df, TIP, dump, HgIterateDirection.OldToNew); - System.out.println(); - af.annotate(df, TIP, new LineDumpInspector(true), HgIterateDirection.NewToOld); - System.out.println(); - af.annotate(df, TIP, new LineDumpInspector(false), HgIterateDirection.NewToOld); - System.out.println(); +// DiffOutInspector dump = new DiffOutInspector(System.out); +// dump.needRevisions(true); +// af.annotate(df, TIP, dump, HgIterateDirection.OldToNew); +// System.out.println(); +// af.annotate(df, TIP, new LineDumpInspector(true), HgIterateDirection.NewToOld); +// System.out.println(); +// af.annotate(df, TIP, new LineDumpInspector(false), HgIterateDirection.NewToOld); +// System.out.println(); FileAnnotateInspector fa = new FileAnnotateInspector(); - FileAnnotation.annotate(df, TIP, fa); + FileAnnotation.annotate(df, TIP, fa); //4,6,TIP for (int i = 0; i < fa.lineRevisions.length; i++) { - System.out.printf("%d: LINE %d\n", fa.lineRevisions[i], i+1); + System.out.printf("%d: %s", fa.lineRevisions[i], fa.line(i) == null ? "null\n" : fa.line(i)); } } @@ -312,16 +368,23 @@ private static class FileAnnotateInspector implements LineInspector { private int[] lineRevisions; + private String[] lines; FileAnnotateInspector() { } - public void line(int lineNumber, int changesetRevIndex, LineDescriptor ld) { + public void line(int lineNumber, int changesetRevIndex, BlockData lineContent, LineDescriptor ld) { if (lineRevisions == null) { lineRevisions = new int [ld.totalLines()]; Arrays.fill(lineRevisions, NO_REVISION); + lines = new String[ld.totalLines()]; } lineRevisions[lineNumber] = changesetRevIndex; + lines[lineNumber] = new String(lineContent.asArray()); + } + + public String line(int i) { + return lines[i]; } }