Mercurial > hg4j
view 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 source
/* * Copyright (c) 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 * the Free Software Foundation; version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * For information on how to redistribute this software under * the terms of a license other than GNU General Public License * contact TMate Software at support@hg4j.com */ package org.tmatesoft.hg.internal; 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; /** * Produce output like 'hg annotate' does * * @author Artem Tikhomirov * @author TMate Software Ltd. */ public class FileAnnotation implements HgBlameFacility.BlockInspector, RevisionDescriptor.Recipient { @Experimental(reason="The line-by-line inspector likely to become part of core/command API") @Callback public interface LineInspector { /** * Not necessarily invoked sequentially by line numbers */ void line(int lineNumber, int changesetRevIndex, BlockData lineContent, LineDescriptor ld); } public interface LineDescriptor { int totalLines(); } /** * Annotate file revision, line by line. */ public static void annotate(HgDataFile df, int changelogRevisionIndex, LineInspector insp) { if (!df.exists()) { return; } FileAnnotation fa = new FileAnnotation(insp); HgBlameFacility af = new HgBlameFacility(); af.annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld); } // 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 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) { 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())); } } } } 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 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 = 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, startTargetLine, c); c = 0; startOriginLine = startTargetLine = -1; // fall-through to check if it's not complete miss but a next chunk } if (lnInFinal != -1) { if (startOriginLine == -1) { startOriginLine = originLine + j; startTargetLine = lnInFinal; c = 1; } else { // lnInFinal != startTargetLine + s is covered above assert lnInFinal == startTargetLine + c; c++; } } } if (startOriginLine != -1) { assert c > 0; v.add(startOriginLine, startTargetLine, c); } } return v; } @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); } return sb; } @Override public String toString() { return String.format("RangeSeq[%d]:%s", count, dump()); } } }