tikhomirov@546: /* tikhomirov@546: * Copyright (c) 2013 TMate Software Ltd tikhomirov@546: * tikhomirov@546: * This program is free software; you can redistribute it and/or modify tikhomirov@546: * it under the terms of the GNU General Public License as published by tikhomirov@546: * the Free Software Foundation; version 2 of the License. tikhomirov@546: * tikhomirov@546: * This program is distributed in the hope that it will be useful, tikhomirov@546: * but WITHOUT ANY WARRANTY; without even the implied warranty of tikhomirov@546: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the tikhomirov@546: * GNU General Public License for more details. tikhomirov@546: * tikhomirov@546: * For information on how to redistribute this software under tikhomirov@546: * the terms of a license other than GNU General Public License tikhomirov@546: * contact TMate Software at support@hg4j.com tikhomirov@546: */ tikhomirov@548: package org.tmatesoft.hg.internal; tikhomirov@546: tikhomirov@546: tikhomirov@676: import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION; tikhomirov@676: tikhomirov@676: import java.util.Arrays; tikhomirov@676: tikhomirov@676: import org.tmatesoft.hg.core.HgAnnotateCommand; tikhomirov@629: import org.tmatesoft.hg.core.HgBlameInspector; tikhomirov@676: import org.tmatesoft.hg.core.HgIterateDirection; tikhomirov@629: import org.tmatesoft.hg.core.HgBlameInspector.RevisionDescriptor; tikhomirov@676: import org.tmatesoft.hg.core.HgCallbackTargetException; tikhomirov@557: import org.tmatesoft.hg.repo.HgInvalidStateException; tikhomirov@676: import org.tmatesoft.hg.util.CancelSupport; tikhomirov@676: import org.tmatesoft.hg.util.CancelledException; tikhomirov@676: import org.tmatesoft.hg.util.ProgressSupport; tikhomirov@546: tikhomirov@546: /** tikhomirov@676: * Produce output like 'hg annotate' does. tikhomirov@676: * Expects revisions to come in order from child to parent. tikhomirov@676: * Unlike {@link ForwardAnnotateInspector}, can be easily modified to report lines as soon as its origin is detected. tikhomirov@676: * tikhomirov@676: * (+) Handles annotate of partial history, at any moment lines with ({@link #knownLines} == false indicate lines tikhomirov@676: * that were added prior to any revision already visited. tikhomirov@546: * tikhomirov@546: * @author Artem Tikhomirov tikhomirov@546: * @author TMate Software Ltd. tikhomirov@546: */ tikhomirov@676: public class ReverseAnnotateInspector implements HgBlameInspector, RevisionDescriptor.Recipient { tikhomirov@555: tikhomirov@557: // keeps of equal blocks, origin to target, from some previous step tikhomirov@674: private RangePairSeq activeEquals; tikhomirov@555: // equal blocks of the current iteration, to be recalculated before next step tikhomirov@555: // to track line number (current target to ultimate target) mapping tikhomirov@674: private RangePairSeq intermediateEquals = new RangePairSeq(); tikhomirov@555: tikhomirov@555: private boolean[] knownLines; tikhomirov@557: private RevisionDescriptor revisionDescriptor; tikhomirov@557: private BlockData lineContent; tikhomirov@557: tikhomirov@674: private IntMap mergedRanges = new IntMap(10); tikhomirov@674: private IntMap equalRanges = new IntMap(10); tikhomirov@557: private boolean activeEqualsComesFromMerge = false; tikhomirov@555: tikhomirov@676: private int[] lineRevisions; tikhomirov@676: tikhomirov@676: /** tikhomirov@676: * @return desired order of iteration for diff tikhomirov@676: */ tikhomirov@676: public HgIterateDirection iterateDirection() { tikhomirov@676: return HgIterateDirection.NewToOld; tikhomirov@676: } tikhomirov@676: tikhomirov@676: public void report(int annotateRevIndex, HgAnnotateCommand.Inspector insp, ProgressSupport progress, CancelSupport cancel) throws HgCallbackTargetException, CancelledException { tikhomirov@676: LineImpl li = new LineImpl(); tikhomirov@676: progress.start(lineRevisions.length); tikhomirov@676: for (int i = 0; i < lineRevisions.length; i++) { tikhomirov@676: byte[] c = lineContent.elementAt(i).asArray(); tikhomirov@676: li.init(i+1, lineRevisions[i], c); tikhomirov@676: insp.next(li); tikhomirov@676: progress.worked(1); tikhomirov@676: cancel.checkCancelled(); tikhomirov@676: } tikhomirov@676: progress.done(); tikhomirov@555: } tikhomirov@555: tikhomirov@555: public void start(RevisionDescriptor rd) { tikhomirov@557: revisionDescriptor = rd; tikhomirov@555: if (knownLines == null) { tikhomirov@557: lineContent = rd.target(); tikhomirov@557: knownLines = new boolean[lineContent.elementCount()]; tikhomirov@676: lineRevisions = new int [lineContent.elementCount()]; tikhomirov@676: Arrays.fill(lineRevisions, NO_REVISION); tikhomirov@674: activeEquals = new RangePairSeq(); tikhomirov@557: activeEquals.add(0, 0, knownLines.length); tikhomirov@557: equalRanges.put(rd.targetChangesetIndex(), activeEquals); tikhomirov@557: } else { tikhomirov@557: activeEquals = equalRanges.get(rd.targetChangesetIndex()); tikhomirov@557: if (activeEquals == null) { tikhomirov@557: // we didn't see this target revision as origin yet tikhomirov@557: // the only way this may happen is that this revision was a merge parent tikhomirov@557: activeEquals = mergedRanges.get(rd.targetChangesetIndex()); tikhomirov@557: activeEqualsComesFromMerge = true; tikhomirov@557: if (activeEquals == null) { tikhomirov@557: throw new HgInvalidStateException(String.format("Can't find previously visited revision %d (while in %d->%1$d diff)", rd.targetChangesetIndex(), rd.originChangesetIndex())); tikhomirov@557: } tikhomirov@557: } tikhomirov@555: } tikhomirov@555: } tikhomirov@555: tikhomirov@557: public void done(RevisionDescriptor rd) { tikhomirov@557: // update line numbers of the intermediate target to point to ultimate target's line numbers tikhomirov@674: RangePairSeq v = intermediateEquals.intersect(activeEquals); tikhomirov@557: if (activeEqualsComesFromMerge) { tikhomirov@557: mergedRanges.put(rd.originChangesetIndex(), v); tikhomirov@557: } else { tikhomirov@557: equalRanges.put(rd.originChangesetIndex(), v); tikhomirov@557: } tikhomirov@560: if (rd.isMerge() && !mergedRanges.containsKey(rd.mergeChangesetIndex())) { tikhomirov@560: // seen merge, but no lines were merged from p2. tikhomirov@560: // Add empty range to avoid uncertainty when a parent of p2 pops in tikhomirov@674: mergedRanges.put(rd.mergeChangesetIndex(), new RangePairSeq()); tikhomirov@560: } tikhomirov@557: intermediateEquals.clear(); tikhomirov@557: activeEquals = null; tikhomirov@557: activeEqualsComesFromMerge = false; tikhomirov@557: revisionDescriptor = null; tikhomirov@557: } tikhomirov@555: tikhomirov@557: public void same(EqualBlock block) { tikhomirov@557: intermediateEquals.add(block.originStart(), block.targetStart(), block.length()); tikhomirov@557: } tikhomirov@557: tikhomirov@557: public void added(AddBlock block) { tikhomirov@674: RangePairSeq rs = null; tikhomirov@557: if (revisionDescriptor.isMerge() && block.originChangesetIndex() == revisionDescriptor.mergeChangesetIndex()) { tikhomirov@557: rs = mergedRanges.get(revisionDescriptor.mergeChangesetIndex()); tikhomirov@557: if (rs == null) { tikhomirov@674: mergedRanges.put(revisionDescriptor.mergeChangesetIndex(), rs = new RangePairSeq()); tikhomirov@557: } tikhomirov@557: } tikhomirov@557: if (activeEquals.size() == 0) { tikhomirov@557: return; tikhomirov@557: } tikhomirov@557: for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) { tikhomirov@557: int lnInFinal = activeEquals.mapLineIndex(ln); tikhomirov@557: if (lnInFinal != -1/* && !knownLines[lnInFinal]*/) { tikhomirov@557: if (rs != null) { tikhomirov@557: rs.add(block.insertedAt() + i, lnInFinal, 1); tikhomirov@557: } else { tikhomirov@676: line(lnInFinal, block.targetChangesetIndex()); tikhomirov@557: } tikhomirov@557: knownLines[lnInFinal] = true; tikhomirov@557: } tikhomirov@557: } tikhomirov@557: } tikhomirov@557: tikhomirov@557: public void changed(ChangeBlock block) { tikhomirov@557: added(block); tikhomirov@557: } tikhomirov@557: tikhomirov@557: public void deleted(DeleteBlock block) { tikhomirov@557: } tikhomirov@557: tikhomirov@676: private void line(int lineNumber, int changesetRevIndex) { tikhomirov@676: lineRevisions[lineNumber] = changesetRevIndex; tikhomirov@557: } tikhomirov@555: }