# HG changeset patch # User Artem Tikhomirov # Date 1374163431 -7200 # Node ID 3219cfadda49438bd06aba7bc5818460243881ac # Parent a20121a2bba690d369037908d690ad6284009d0e Switch to alternative annotate producer (walks from parents to children). Refactor FileAnnotation to match updated annotate approach diff -r a20121a2bba6 -r 3219cfadda49 src/org/tmatesoft/hg/core/HgAnnotateCommand.java --- a/src/org/tmatesoft/hg/core/HgAnnotateCommand.java Thu Jul 18 18:02:36 2013 +0200 +++ b/src/org/tmatesoft/hg/core/HgAnnotateCommand.java Thu Jul 18 18:03:51 2013 +0200 @@ -16,16 +16,9 @@ */ package org.tmatesoft.hg.core; -import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; - -import java.util.Arrays; - -import org.tmatesoft.hg.core.HgBlameInspector.BlockData; import org.tmatesoft.hg.internal.Callback; import org.tmatesoft.hg.internal.CsetParamKeeper; -import org.tmatesoft.hg.internal.FileAnnotation; -import org.tmatesoft.hg.internal.FileAnnotation.LineDescriptor; -import org.tmatesoft.hg.internal.FileAnnotation.LineInspector; +import org.tmatesoft.hg.internal.ForwardAnnotateInspector; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; @@ -88,6 +81,7 @@ } // TODO [post-1.1] set encoding and provide String line content from LineInfo + // TODO FWIW: diff algorithms: http://bramcohen.livejournal.com/73318.html /** * Annotate selected file @@ -107,32 +101,26 @@ final ProgressSupport progress = getProgressSupport(inspector); final CancelSupport cancellation = getCancelSupport(inspector, true); cancellation.checkCancelled(); - progress.start(2); + progress.start(200); try { HgDataFile df = repo.getFileNode(file); if (!df.exists()) { return; } final int changesetStart = followRename ? 0 : df.getChangesetRevisionIndex(0); - Collector c = new Collector(cancellation); - FileAnnotation fa = new FileAnnotation(c); - HgDiffCommand cmd = new HgDiffCommand(repo); - cmd.file(df).order(HgIterateDirection.NewToOld); - cmd.range(changesetStart, annotateRevision.get()); - cmd.executeAnnotate(fa); - progress.worked(1); - c.throwIfCancelled(); + final int annotateRevIndex = annotateRevision.get(); + HgDiffCommand cmd = new HgDiffCommand(repo).file(df); + cmd.range(changesetStart, annotateRevIndex); + cmd.set(cancellation); + cmd.set(new ProgressSupport.Sub(progress, 100)); + // +// ReverseAnnotateInspector ai = new ReverseAnnotateInspector(); + ForwardAnnotateInspector ai = new ForwardAnnotateInspector(); + cmd.order(ai.iterateDirection()); + // + cmd.executeAnnotate(ai); cancellation.checkCancelled(); - ProgressSupport.Sub subProgress = new ProgressSupport.Sub(progress, 1); - subProgress.start(c.lineRevisions.length); - LineImpl li = new LineImpl(); - for (int i = 0; i < c.lineRevisions.length; i++) { - li.init(i+1, c.lineRevisions[i], c.line(i)); - inspector.next(li); - subProgress.worked(1); - cancellation.checkCancelled(); - } - subProgress.done(); + ai.report(annotateRevIndex, inspector, new ProgressSupport.Sub(progress, 100), cancellation); } catch (HgRuntimeException ex) { throw new HgLibraryFailureException(ex); } @@ -159,70 +147,4 @@ int getChangesetIndex(); byte[] getContent(); } - - // TODO [post-1.1] there's no need in FileAnnotation.LineInspector, merge it here - // ok for 1.1 as this LineInspector is internal class - private static class Collector implements LineInspector { - private int[] lineRevisions; - private byte[][] lines; - private final CancelSupport cancelSupport; - private CancelledException cancelEx; - - Collector(CancelSupport cancellation) { - cancelSupport = cancellation; - } - - public void line(int lineNumber, int changesetRevIndex, BlockData lineContent, LineDescriptor ld) { - if (cancelEx != null) { - return; - } - if (lineRevisions == null) { - lineRevisions = new int [ld.totalLines()]; - Arrays.fill(lineRevisions, NO_REVISION); - lines = new byte[ld.totalLines()][]; - } - lineRevisions[lineNumber] = changesetRevIndex; - lines[lineNumber] = lineContent.asArray(); - try { - cancelSupport.checkCancelled(); - } catch (CancelledException ex) { - cancelEx = ex; - } - } - - public byte[] line(int i) { - return lines[i]; - } - - public void throwIfCancelled() throws CancelledException { - if (cancelEx != null) { - throw cancelEx; - } - } - } - - - private static class LineImpl implements LineInfo { - private int ln; - private int rev; - private byte[] content; - - void init(int line, int csetRev, byte[] cnt) { - ln = line; - rev = csetRev; - content = cnt; - } - - public int getLineNumber() { - return ln; - } - - public int getChangesetIndex() { - return rev; - } - - public byte[] getContent() { - return content; - } - } } diff -r a20121a2bba6 -r 3219cfadda49 src/org/tmatesoft/hg/internal/FileAnnotation.java --- a/src/org/tmatesoft/hg/internal/FileAnnotation.java Thu Jul 18 18:02:36 2013 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,148 +0,0 @@ -/* - * 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 org.tmatesoft.hg.core.HgBlameInspector; -import org.tmatesoft.hg.core.HgBlameInspector.RevisionDescriptor; -import org.tmatesoft.hg.repo.HgInvalidStateException; - -/** - * Produce output like 'hg annotate' does - * - * @author Artem Tikhomirov - * @author TMate Software Ltd. - */ -public class FileAnnotation implements HgBlameInspector, 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(); - } - - // keeps of equal blocks, origin to target, from some previous step - private RangePairSeq activeEquals; - // equal blocks of the current iteration, to be recalculated before next step - // to track line number (current target to ultimate target) mapping - private RangePairSeq intermediateEquals = new RangePairSeq(); - - private boolean[] knownLines; - private final LineInspector delegate; - private RevisionDescriptor revisionDescriptor; - private BlockData lineContent; - - private IntMap mergedRanges = new IntMap(10); - private IntMap equalRanges = new IntMap(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 RangePairSeq(); - 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 - RangePairSeq v = intermediateEquals.intersect(activeEquals); - if (activeEqualsComesFromMerge) { - mergedRanges.put(rd.originChangesetIndex(), v); - } else { - equalRanges.put(rd.originChangesetIndex(), v); - } - if (rd.isMerge() && !mergedRanges.containsKey(rd.mergeChangesetIndex())) { - // seen merge, but no lines were merged from p2. - // Add empty range to avoid uncertainty when a parent of p2 pops in - mergedRanges.put(rd.mergeChangesetIndex(), new RangePairSeq()); - } - 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) { - RangePairSeq rs = null; - if (revisionDescriptor.isMerge() && block.originChangesetIndex() == revisionDescriptor.mergeChangesetIndex()) { - rs = mergedRanges.get(revisionDescriptor.mergeChangesetIndex()); - if (rs == null) { - mergedRanges.put(revisionDescriptor.mergeChangesetIndex(), rs = new RangePairSeq()); - } - } - 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; - } - } -} \ No newline at end of file diff -r a20121a2bba6 -r 3219cfadda49 src/org/tmatesoft/hg/internal/ForwardAnnotateInspector.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/ForwardAnnotateInspector.java Thu Jul 18 18:03:51 2013 +0200 @@ -0,0 +1,163 @@ +/* + * 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 org.tmatesoft.hg.core.HgAnnotateCommand.Inspector; +import org.tmatesoft.hg.core.HgAnnotateCommand.LineInfo; +import org.tmatesoft.hg.core.HgBlameInspector; +import org.tmatesoft.hg.core.HgCallbackTargetException; +import org.tmatesoft.hg.core.HgDiffCommand; +import org.tmatesoft.hg.core.HgException; +import org.tmatesoft.hg.core.HgIterateDirection; +import org.tmatesoft.hg.repo.HgLookup; +import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.util.CancelSupport; +import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.ProgressSupport; + +/** + * Annotate file history iterating from parents to children + * + * At the moment, doesn't handle start from any revision but 0 + * + * (+) May report annotate for any revision in the visited range. + * + * @see ReverseAnnotateInspector + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class ForwardAnnotateInspector implements HgBlameInspector, HgBlameInspector.RevisionDescriptor.Recipient { + final IntMap all = new IntMap(100); + // revision->map(lineNumber->lineContent) + private final IntMap> lineContent = new IntMap>(100); + private IntSliceSeq current; + private RevisionDescriptor revDescriptor; + + /** + * @return desired order of iteration for diff + */ + public HgIterateDirection iterateDirection() { + return HgIterateDirection.OldToNew; + } + + public void report(int revision, Inspector insp, ProgressSupport progress, CancelSupport cancel) throws HgCallbackTargetException, CancelledException { + int totalLines = 0; + for (IntTuple t : all.get(revision)) { + totalLines += t.at(0); + } + progress.start(totalLines); + LineImpl li = new LineImpl(); + int line = 1; + for (IntTuple t : all.get(revision)) { + IntMap revLines = lineContent.get(t.at(1)); + for (int i = 0, x = t.at(0); i < x; i++) { + final int lineInRev = t.at(2) + i; + final byte[] lc = revLines.get(lineInRev); + li.init(line++, t.at(1), lc); + insp.next(li); + progress.worked(1); + cancel.checkCancelled(); + } + } + progress.done(); + } + + public void start(RevisionDescriptor rd) throws HgCallbackTargetException { + all.put(rd.targetChangesetIndex(), current = new IntSliceSeq(3)); + revDescriptor = rd; + } + + public void done(RevisionDescriptor rd) throws HgCallbackTargetException { + revDescriptor = null; + } + + public void same(EqualBlock block) throws HgCallbackTargetException { + copyBlock(block.originChangesetIndex(), block.originStart(), block.length()); + } + + public void added(AddBlock block) throws HgCallbackTargetException { + if (revDescriptor.isMerge() && block.originChangesetIndex() == revDescriptor.mergeChangesetIndex()) { + copyBlock(block.originChangesetIndex(), block.insertedAt(), block.totalAddedLines()); + return; + } + BlockData addedLines = block.addedLines(); + IntMap revLines = lineContent.get(block.targetChangesetIndex()); + if (revLines == null) { + lineContent.put(block.targetChangesetIndex(), revLines = new IntMap(block.totalAddedLines())); + } + for (int i = 0; i < block.totalAddedLines(); i++) { + revLines.put(block.firstAddedLine() + i, addedLines.elementAt(i).asArray()); + } + current.add(block.totalAddedLines(), block.targetChangesetIndex(), block.firstAddedLine()); + } + + public void changed(ChangeBlock block) throws HgCallbackTargetException { + added(block); + } + + public void deleted(DeleteBlock block) throws HgCallbackTargetException { + } + + private void copyBlock(int originChangesetIndex, int originStart, int length) { + IntSliceSeq origin = all.get(originChangesetIndex); + assert origin != null; // shall visit parents before came to this child + int originPos = 0; + int targetBlockLen = length; + for (IntTuple t : origin) { + int originBlockLen = t.at(0); + int originBlockEnd = originPos + originBlockLen; + if (originBlockEnd > originStart) { + int originBlockOverlap = Math.min(originBlockLen, originBlockEnd - originStart); + assert originBlockOverlap > 0; + originBlockOverlap = Math.min(originBlockOverlap, targetBlockLen); + int originBlockLine = t.at(2); + if (originPos < originStart) { + originBlockLine += originBlockLen-originBlockOverlap; + } + // copy fragment of original block; + current.add(originBlockOverlap, t.at(1), originBlockLine); + targetBlockLen -= originBlockOverlap; + if (targetBlockLen == 0) { + break; + } + } + originPos += originBlockLen; + } + } + + + public static void main(String[] args) throws HgCallbackTargetException, CancelledException, HgException { + HgRepository repo = new HgLookup().detect("/home/artem/hg/junit-test-repos/test-annotate/"); + HgDiffCommand cmd = new HgDiffCommand(repo); + cmd.file(repo.getFileNode("file1")).order(HgIterateDirection.OldToNew); + cmd.range(0, 8); + final ForwardAnnotateInspector c2 = new ForwardAnnotateInspector(); + cmd.executeAnnotate(c2); + for (IntTuple t : c2.all.get(8)) { + System.out.printf("Block %d lines from revision %d (starts with line %d in the origin)\n", t.at(0), t.at(1), t.at(2)); + } + for (IntTuple t : c2.all.get(8)) { + System.out.printf("Block %d lines from revision %d (starts with line %d in the origin)\n", t.at(0), t.at(1), 1+t.at(2)); + } + c2.report(8, new Inspector() { + + public void next(LineInfo lineInfo) throws HgCallbackTargetException { + System.out.printf("%3d:%3d: %s", lineInfo.getChangesetIndex(), lineInfo.getLineNumber(), new String(lineInfo.getContent())); + } + }, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null)); + } +} \ No newline at end of file diff -r a20121a2bba6 -r 3219cfadda49 src/org/tmatesoft/hg/internal/LineImpl.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/LineImpl.java Thu Jul 18 18:03:51 2013 +0200 @@ -0,0 +1,47 @@ +/* + * 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 org.tmatesoft.hg.core.HgAnnotateCommand.LineInfo; + +/** + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +final class LineImpl implements LineInfo { + private int ln; + private int rev; + private byte[] content; + + void init(int line, int csetRev, byte[] cnt) { + ln = line; + rev = csetRev; + content = cnt; + } + + public int getLineNumber() { + return ln; + } + + public int getChangesetIndex() { + return rev; + } + + public byte[] getContent() { + return content; + } +} \ No newline at end of file diff -r a20121a2bba6 -r 3219cfadda49 src/org/tmatesoft/hg/internal/ReverseAnnotateInspector.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/ReverseAnnotateInspector.java Thu Jul 18 18:03:51 2013 +0200 @@ -0,0 +1,164 @@ +/* + * 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 static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; + +import java.util.Arrays; + +import org.tmatesoft.hg.core.HgAnnotateCommand; +import org.tmatesoft.hg.core.HgBlameInspector; +import org.tmatesoft.hg.core.HgIterateDirection; +import org.tmatesoft.hg.core.HgBlameInspector.RevisionDescriptor; +import org.tmatesoft.hg.core.HgCallbackTargetException; +import org.tmatesoft.hg.repo.HgInvalidStateException; +import org.tmatesoft.hg.util.CancelSupport; +import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.ProgressSupport; + +/** + * Produce output like 'hg annotate' does. + * Expects revisions to come in order from child to parent. + * Unlike {@link ForwardAnnotateInspector}, can be easily modified to report lines as soon as its origin is detected. + * + * (+) Handles annotate of partial history, at any moment lines with ({@link #knownLines} == false indicate lines + * that were added prior to any revision already visited. + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class ReverseAnnotateInspector implements HgBlameInspector, RevisionDescriptor.Recipient { + + // keeps of equal blocks, origin to target, from some previous step + private RangePairSeq activeEquals; + // equal blocks of the current iteration, to be recalculated before next step + // to track line number (current target to ultimate target) mapping + private RangePairSeq intermediateEquals = new RangePairSeq(); + + private boolean[] knownLines; + private RevisionDescriptor revisionDescriptor; + private BlockData lineContent; + + private IntMap mergedRanges = new IntMap(10); + private IntMap equalRanges = new IntMap(10); + private boolean activeEqualsComesFromMerge = false; + + private int[] lineRevisions; + + /** + * @return desired order of iteration for diff + */ + public HgIterateDirection iterateDirection() { + return HgIterateDirection.NewToOld; + } + + public void report(int annotateRevIndex, HgAnnotateCommand.Inspector insp, ProgressSupport progress, CancelSupport cancel) throws HgCallbackTargetException, CancelledException { + LineImpl li = new LineImpl(); + progress.start(lineRevisions.length); + for (int i = 0; i < lineRevisions.length; i++) { + byte[] c = lineContent.elementAt(i).asArray(); + li.init(i+1, lineRevisions[i], c); + insp.next(li); + progress.worked(1); + cancel.checkCancelled(); + } + progress.done(); + } + + public void start(RevisionDescriptor rd) { + revisionDescriptor = rd; + if (knownLines == null) { + lineContent = rd.target(); + knownLines = new boolean[lineContent.elementCount()]; + lineRevisions = new int [lineContent.elementCount()]; + Arrays.fill(lineRevisions, NO_REVISION); + activeEquals = new RangePairSeq(); + 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 + RangePairSeq v = intermediateEquals.intersect(activeEquals); + if (activeEqualsComesFromMerge) { + mergedRanges.put(rd.originChangesetIndex(), v); + } else { + equalRanges.put(rd.originChangesetIndex(), v); + } + if (rd.isMerge() && !mergedRanges.containsKey(rd.mergeChangesetIndex())) { + // seen merge, but no lines were merged from p2. + // Add empty range to avoid uncertainty when a parent of p2 pops in + mergedRanges.put(rd.mergeChangesetIndex(), new RangePairSeq()); + } + 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) { + RangePairSeq rs = null; + if (revisionDescriptor.isMerge() && block.originChangesetIndex() == revisionDescriptor.mergeChangesetIndex()) { + rs = mergedRanges.get(revisionDescriptor.mergeChangesetIndex()); + if (rs == null) { + mergedRanges.put(revisionDescriptor.mergeChangesetIndex(), rs = new RangePairSeq()); + } + } + 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 { + line(lnInFinal, block.targetChangesetIndex()); + } + knownLines[lnInFinal] = true; + } + } + } + + public void changed(ChangeBlock block) { + added(block); + } + + public void deleted(DeleteBlock block) { + } + + private void line(int lineNumber, int changesetRevIndex) { + lineRevisions[lineNumber] = changesetRevIndex; + } +} \ No newline at end of file diff -r a20121a2bba6 -r 3219cfadda49 test/org/tmatesoft/hg/test/TestBlame.java --- a/test/org/tmatesoft/hg/test/TestBlame.java Thu Jul 18 18:02:36 2013 +0200 +++ b/test/org/tmatesoft/hg/test/TestBlame.java Thu Jul 18 18:03:51 2013 +0200 @@ -20,7 +20,6 @@ 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; @@ -47,15 +46,17 @@ import org.tmatesoft.hg.core.HgDiffCommand; import org.tmatesoft.hg.core.HgRepoFacade; import org.tmatesoft.hg.core.Nodeid; -import org.tmatesoft.hg.internal.FileAnnotation; -import org.tmatesoft.hg.internal.FileAnnotation.LineDescriptor; -import org.tmatesoft.hg.internal.FileAnnotation.LineInspector; +import org.tmatesoft.hg.internal.ForwardAnnotateInspector; import org.tmatesoft.hg.internal.IntVector; +import org.tmatesoft.hg.internal.ReverseAnnotateInspector; import org.tmatesoft.hg.repo.HgChangelog; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgLookup; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.util.CancelSupport; +import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Path; +import org.tmatesoft.hg.util.ProgressSupport; /** * @@ -103,10 +104,11 @@ /*, TIP */}; for (int cs : toTest) { ar.run(cs, false); - FileAnnotateInspector fa = new FileAnnotateInspector(); diffCmd.range(0, cs); - diffCmd.executeAnnotate(new FileAnnotation(fa)); - doAnnotateLineCheck(cs, ar.getLines(), Arrays.asList(fa.lineRevisions), Arrays.asList(fa.lines)); + final ReverseAnnotateInspector insp = new ReverseAnnotateInspector(); + diffCmd.executeAnnotate(insp); + AnnotateInspector fa = new AnnotateInspector().fill(cs, insp); + doAnnotateLineCheck(cs, ar.getLines(), fa.changesets, fa.lines); } } @@ -119,10 +121,11 @@ final HgDiffCommand diffCmd = new HgDiffCommand(repo).file(df).order(NewToOld); for (int cs : new int[] { 4, 6 /*, 8 see below*/, TIP}) { ar.run(cs, false); - FileAnnotateInspector fa = new FileAnnotateInspector(); diffCmd.range(0, cs); - diffCmd.executeAnnotate(new FileAnnotation(fa)); - doAnnotateLineCheck(cs, ar.getLines(), Arrays.asList(fa.lineRevisions), Arrays.asList(fa.lines)); + final ReverseAnnotateInspector insp = new ReverseAnnotateInspector(); + diffCmd.executeAnnotate(insp); + AnnotateInspector fa = new AnnotateInspector().fill(cs, insp); + doAnnotateLineCheck(cs, ar.getLines(), fa.changesets, fa.lines); } /*`hg annotate -r 8` and HgBlameFacility give different result * for "r0, line 5" line, which was deleted in rev2 and restored back in @@ -131,7 +134,7 @@ * However `hg annotate -r 4` shows rev4 for the line, too. The aforementioned rev0 for * the merge rev8 results from the iteration order and is implementation specific * (i.e. one can't tell which one is right). Mercurial walks from parents to children, - * and traces equal lines, wile HgBlameFacility walks from child to parents and records + * and traces equal lines, while HgBlameFacility walks from child to parents and records * changes (additions). Seems it processes branch with rev3 and rev6 first * (printout in context.py, annotate and annotate.pair reveals that), and the line 0_5 * comes as unchanged through this branch, and later processing rev2 and rev4 doesn't @@ -259,6 +262,8 @@ ar.run(changeset, false); doAnnotateLineCheck(changeset, ar.getLines(), ai.changesets, ai.lines); } + + // FIXME add originLineNumber to HgAnnotateCommand#LineInfo, pass it from FileAnnotate, test private void doAnnotateLineCheck(int cs, String[] hgAnnotateLines, List cmdChangesets, List cmdLines) { assertTrue("[sanity]", hgAnnotateLines.length > 0); @@ -367,11 +372,13 @@ } errorCollector.verify(); */ - FileAnnotateInspector fa = new FileAnnotateInspector(); - diffCmd.range(0, 8).order(NewToOld); - diffCmd.executeAnnotate(new FileAnnotation(fa)); - for (int i = 0; i < fa.lineRevisions.length; i++) { - System.out.printf("%d: %s", fa.lineRevisions[i], fa.line(i) == null ? "null\n" : fa.line(i)); + ForwardAnnotateInspector insp = new ForwardAnnotateInspector(); + diffCmd.range(0, 8).order(insp.iterateDirection()); + diffCmd.executeAnnotate(insp); + AnnotateInspector fa = new AnnotateInspector().fill(8, insp); + for (int i = 0; i < fa.changesets.size(); i++) { + final String line = fa.lines.get(i); + System.out.printf("%d: %s", fa.changesets.get(i), line == null ? "null\n" : line); } } @@ -476,28 +483,6 @@ } } - private static class FileAnnotateInspector implements LineInspector { - private Integer[] lineRevisions; - private String[] lines; - - FileAnnotateInspector() { - } - - public void line(int lineNumber, int changesetRevIndex, HgBlameInspector.BlockData lineContent, LineDescriptor ld) { - if (lineRevisions == null) { - lineRevisions = new Integer[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]; - } - } - @SuppressWarnings("unused") private static class LineDumpInspector implements HgBlameInspector { @@ -544,7 +529,16 @@ private int lineNumber = 1; public final ArrayList lines = new ArrayList(); public final ArrayList changesets = new ArrayList(); - + + AnnotateInspector fill(int rev, ReverseAnnotateInspector ai) throws HgCallbackTargetException, CancelledException { + ai.report(rev, this, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null)); + return this; + } + AnnotateInspector fill(int rev, ForwardAnnotateInspector ai) throws HgCallbackTargetException, CancelledException { + ai.report(rev, this, ProgressSupport.Factory.get(null), CancelSupport.Factory.get(null)); + return this; + } + public void next(LineInfo lineInfo) throws HgCallbackTargetException { Assert.assertEquals(lineInfo.getLineNumber(), lineNumber); lineNumber++;