comparison src/org/tmatesoft/hg/internal/diff/ReverseAnnotateInspector.java @ 703:7839ff0bfd78

Refactor: move diff/blame related code to a separate package
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 14 Aug 2013 14:51:51 +0200
parents src/org/tmatesoft/hg/internal/ReverseAnnotateInspector.java@1c49c0cee540
children 497e697636fc
comparison
equal deleted inserted replaced
702:992fa84e7885 703:7839ff0bfd78
1 /*
2 * Copyright (c) 2013 TMate Software Ltd
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * For information on how to redistribute this software under
14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com
16 */
17 package org.tmatesoft.hg.internal.diff;
18
19
20 import static org.tmatesoft.hg.repo.HgRepository.NO_REVISION;
21
22 import java.util.Arrays;
23
24 import org.tmatesoft.hg.core.HgAnnotateCommand;
25 import org.tmatesoft.hg.core.HgBlameInspector;
26 import org.tmatesoft.hg.core.HgIterateDirection;
27 import org.tmatesoft.hg.core.HgBlameInspector.RevisionDescriptor;
28 import org.tmatesoft.hg.core.HgCallbackTargetException;
29 import org.tmatesoft.hg.internal.IntMap;
30 import org.tmatesoft.hg.repo.HgInvalidStateException;
31 import org.tmatesoft.hg.util.CancelSupport;
32 import org.tmatesoft.hg.util.CancelledException;
33 import org.tmatesoft.hg.util.ProgressSupport;
34
35 /**
36 * Produce output like 'hg annotate' does.
37 * Expects revisions to come in order from child to parent.
38 * Unlike {@link ForwardAnnotateInspector}, can be easily modified to report lines as soon as its origin is detected.
39 *
40 * (+) Handles annotate of partial history, at any moment lines with ({@link #knownLines} == <code>false</code> indicate lines
41 * that were added prior to any revision already visited.
42 *
43 * @author Artem Tikhomirov
44 * @author TMate Software Ltd.
45 */
46 public class ReverseAnnotateInspector implements HgBlameInspector, RevisionDescriptor.Recipient {
47
48 // keeps <startSeq1, startSeq2, len> of equal blocks, origin to target, from some previous step
49 private RangePairSeq activeEquals;
50 // equal blocks of the current iteration, to be recalculated before next step
51 // to track line number (current target to ultimate target) mapping
52 private RangePairSeq intermediateEquals = new RangePairSeq();
53
54 private boolean[] knownLines;
55 private RevisionDescriptor revisionDescriptor;
56 private BlockData lineContent;
57
58 private IntMap<RangePairSeq> mergedRanges = new IntMap<RangePairSeq>(10);
59 private IntMap<RangePairSeq> equalRanges = new IntMap<RangePairSeq>(10);
60 private boolean activeEqualsComesFromMerge = false;
61
62 private int[] lineRevisions;
63 private int[] lineNumbers;
64
65 /**
66 * @return desired order of iteration for diff
67 */
68 public HgIterateDirection iterateDirection() {
69 return HgIterateDirection.NewToOld;
70 }
71
72 public void report(int annotateRevIndex, HgAnnotateCommand.Inspector insp, ProgressSupport progress, CancelSupport cancel) throws HgCallbackTargetException, CancelledException {
73 LineImpl li = new LineImpl();
74 progress.start(lineRevisions.length);
75 for (int i = 0; i < lineRevisions.length; i++) {
76 byte[] c = lineContent.elementAt(i).asArray();
77 li.init(i+1, lineNumbers[i] + 1, lineRevisions[i], c);
78 insp.next(li);
79 progress.worked(1);
80 cancel.checkCancelled();
81 }
82 progress.done();
83 }
84
85 public void start(RevisionDescriptor rd) {
86 revisionDescriptor = rd;
87 if (knownLines == null) {
88 lineContent = rd.target();
89 knownLines = new boolean[lineContent.elementCount()];
90 lineRevisions = new int [knownLines.length];
91 Arrays.fill(lineRevisions, NO_REVISION);
92 lineNumbers = new int[knownLines.length];
93 activeEquals = new RangePairSeq();
94 activeEquals.add(0, 0, knownLines.length);
95 equalRanges.put(rd.targetChangesetIndex(), activeEquals);
96 } else {
97 activeEquals = equalRanges.get(rd.targetChangesetIndex());
98 if (activeEquals == null) {
99 // we didn't see this target revision as origin yet
100 // the only way this may happen is that this revision was a merge parent
101 activeEquals = mergedRanges.get(rd.targetChangesetIndex());
102 activeEqualsComesFromMerge = true;
103 if (activeEquals == null) {
104 throw new HgInvalidStateException(String.format("Can't find previously visited revision %d (while in %d->%1$d diff)", rd.targetChangesetIndex(), rd.originChangesetIndex()));
105 }
106 }
107 }
108 }
109
110 public void done(RevisionDescriptor rd) {
111 // update line numbers of the intermediate target to point to ultimate target's line numbers
112 RangePairSeq v = intermediateEquals.intersect(activeEquals);
113 if (activeEqualsComesFromMerge) {
114 mergedRanges.put(rd.originChangesetIndex(), v);
115 } else {
116 equalRanges.put(rd.originChangesetIndex(), v);
117 }
118 if (rd.isMerge() && !mergedRanges.containsKey(rd.mergeChangesetIndex())) {
119 // seen merge, but no lines were merged from p2.
120 // Add empty range to avoid uncertainty when a parent of p2 pops in
121 mergedRanges.put(rd.mergeChangesetIndex(), new RangePairSeq());
122 }
123 intermediateEquals.clear();
124 activeEquals = null;
125 activeEqualsComesFromMerge = false;
126 revisionDescriptor = null;
127 }
128
129 public void same(EqualBlock block) {
130 intermediateEquals.add(block.originStart(), block.targetStart(), block.length());
131 }
132
133 public void added(AddBlock block) {
134 RangePairSeq rs = null;
135 if (revisionDescriptor.isMerge() && block.originChangesetIndex() == revisionDescriptor.mergeChangesetIndex()) {
136 rs = mergedRanges.get(revisionDescriptor.mergeChangesetIndex());
137 if (rs == null) {
138 mergedRanges.put(revisionDescriptor.mergeChangesetIndex(), rs = new RangePairSeq());
139 }
140 }
141 if (activeEquals.size() == 0) {
142 return;
143 }
144 for (int i = 0, ln = block.firstAddedLine(), x = block.totalAddedLines(); i < x; i++, ln++) {
145 int lnInFinal = activeEquals.mapLineIndex(ln);
146 if (lnInFinal != -1/* && !knownLines[lnInFinal]*/) {
147 if (rs != null) {
148 rs.add(block.insertedAt() + i, lnInFinal, 1);
149 } else {
150 line(lnInFinal, ln, block.targetChangesetIndex());
151 }
152 knownLines[lnInFinal] = true;
153 }
154 }
155 }
156
157 public void changed(ChangeBlock block) {
158 added(block);
159 }
160
161 public void deleted(DeleteBlock block) {
162 }
163
164 private void line(int lineNumber, int firstAppearance, int changesetRevIndex) {
165 lineRevisions[lineNumber] = changesetRevIndex;
166 lineNumbers[lineNumber] = firstAppearance;
167 }
168 }