Mercurial > hg4j
diff src/org/tmatesoft/hg/internal/FileAnnotation.java @ 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 |
line wrap: on
line diff
--- 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