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@703: package org.tmatesoft.hg.internal.diff;
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@703: import org.tmatesoft.hg.internal.IntMap;
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@677: private int[] lineNumbers;
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@677: li.init(i+1, lineNumbers[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@677: lineRevisions = new int [knownLines.length];
tikhomirov@676: Arrays.fill(lineRevisions, NO_REVISION);
tikhomirov@677: lineNumbers = new int[knownLines.length];
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@677: line(lnInFinal, ln, 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@677: private void line(int lineNumber, int firstAppearance, int changesetRevIndex) {
tikhomirov@676: lineRevisions[lineNumber] = changesetRevIndex;
tikhomirov@677: lineNumbers[lineNumber] = firstAppearance;
tikhomirov@557: }
tikhomirov@555: }