comparison src/org/tmatesoft/hg/internal/AnnotateFacility.java @ 549:83afa680555d

Annotate merge revision (combined diff against two parents without looking further)
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Tue, 19 Feb 2013 21:17:39 +0100
parents ab21ac7dd833
children 4ea0351ca878
comparison
equal deleted inserted replaced
548:ab21ac7dd833 549:83afa680555d
21 21
22 import org.tmatesoft.hg.core.Nodeid; 22 import org.tmatesoft.hg.core.Nodeid;
23 import org.tmatesoft.hg.internal.PatchGenerator.LineSequence; 23 import org.tmatesoft.hg.internal.PatchGenerator.LineSequence;
24 import org.tmatesoft.hg.repo.HgDataFile; 24 import org.tmatesoft.hg.repo.HgDataFile;
25 import org.tmatesoft.hg.repo.HgInvalidStateException; 25 import org.tmatesoft.hg.repo.HgInvalidStateException;
26 import org.tmatesoft.hg.repo.HgRepository;
27 import org.tmatesoft.hg.util.CancelledException; 26 import org.tmatesoft.hg.util.CancelledException;
28 27
29 /** 28 /**
30 * 29 *
31 * @author Artem Tikhomirov 30 * @author Artem Tikhomirov
32 * @author TMate Software Ltd. 31 * @author TMate Software Ltd.
33 */ 32 */
34 @Experimental(reason="work in progress") 33 @Experimental(reason="work in progress")
35 public class AnnotateFacility { 34 public class AnnotateFacility {
35
36 /**
37 * mimic 'hg diff -r csetRevIndex1 -r csetRevIndex2'
38 */
39 public void diff(HgDataFile df, int csetRevIndex1, int csetRevIndex2, BlockInspector insp) {
40 int fileRevIndex1 = fileRevIndex(df, csetRevIndex1);
41 int fileRevIndex2 = fileRevIndex(df, csetRevIndex2);
42 LineSequence c1 = lines(df, fileRevIndex1);
43 LineSequence c2 = lines(df, fileRevIndex2);
44 PatchGenerator<LineSequence> pg = new PatchGenerator<LineSequence>();
45 pg.init(c1, c2);
46 pg.findMatchingBlocks(new BlameBlockInspector(insp, csetRevIndex1, csetRevIndex2));
47 }
36 48
37 /** 49 /**
38 * Annotate file revision, line by line. 50 * Annotate file revision, line by line.
39 */ 51 */
40 public void annotate(HgDataFile df, int changesetRevisionIndex, LineInspector insp) { 52 public void annotate(HgDataFile df, int changesetRevisionIndex, LineInspector insp) {
41 if (!df.exists()) { 53 if (!df.exists()) {
42 return; 54 return;
43 } 55 }
44 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(changesetRevisionIndex, df.getPath()); 56 int fileRevIndex = fileRevIndex(df, changesetRevisionIndex);
45 int fileRevIndex = df.getRevisionIndex(fileRev);
46 int[] fileRevParents = new int[2]; 57 int[] fileRevParents = new int[2];
47 FileAnnotation fa = new FileAnnotation(insp); 58 FileAnnotation fa = new FileAnnotation(insp);
48 do { 59 do {
49 // also covers changesetRevisionIndex == TIP, #implAnnotateChange doesn't tolerate constants 60 // also covers changesetRevisionIndex == TIP, #implAnnotateChange doesn't tolerate constants
50 changesetRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex); 61 changesetRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex);
57 /** 68 /**
58 * Annotates changes of the file against its parent(s) 69 * Annotates changes of the file against its parent(s)
59 */ 70 */
60 public void annotateChange(HgDataFile df, int changesetRevisionIndex, BlockInspector insp) { 71 public void annotateChange(HgDataFile df, int changesetRevisionIndex, BlockInspector insp) {
61 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f 72 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f
62 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(changesetRevisionIndex, df.getPath()); 73 int fileRevIndex = fileRevIndex(df, changesetRevisionIndex);
63 int fileRevIndex = df.getRevisionIndex(fileRev);
64 int[] fileRevParents = new int[2]; 74 int[] fileRevParents = new int[2];
65 df.parents(fileRevIndex, fileRevParents, null, null); 75 df.parents(fileRevIndex, fileRevParents, null, null);
66 if (changesetRevisionIndex == TIP) { 76 if (changesetRevisionIndex == TIP) {
67 changesetRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex); 77 changesetRevisionIndex = df.getChangesetRevisionIndex(fileRevIndex);
68 } 78 }
69 implAnnotateChange(df, changesetRevisionIndex, fileRevIndex, fileRevParents, insp); 79 implAnnotateChange(df, changesetRevisionIndex, fileRevIndex, fileRevParents, insp);
70 } 80 }
71 81
72 private void implAnnotateChange(HgDataFile df, int csetRevIndex, int fileRevIndex, int[] fileParentRevs, BlockInspector insp) { 82 private void implAnnotateChange(HgDataFile df, int csetRevIndex, int fileRevIndex, int[] fileParentRevs, BlockInspector insp) {
83 final LineSequence fileRevLines = lines(df, fileRevIndex);
84 if (fileParentRevs[0] != NO_REVISION && fileParentRevs[1] != NO_REVISION) {
85 LineSequence p1Lines = lines(df, fileParentRevs[0]);
86 LineSequence p2Lines = lines(df, fileParentRevs[1]);
87 int p1ClogIndex = df.getChangesetRevisionIndex(fileParentRevs[0]);
88 int p2ClogIndex = df.getChangesetRevisionIndex(fileParentRevs[1]);
89 PatchGenerator<LineSequence> pg = new PatchGenerator<LineSequence>();
90 pg.init(p2Lines, fileRevLines);
91 EqualBlocksCollector p2MergeCommon = new EqualBlocksCollector();
92 pg.findMatchingBlocks(p2MergeCommon);
93 //
94 pg.init(p1Lines);
95 BlameBlockInspector bbi = new BlameBlockInspector(insp, p1ClogIndex, csetRevIndex);
96 bbi.setMergeParent2(p2MergeCommon, p2ClogIndex);
97 pg.findMatchingBlocks(bbi);
98 } else if (fileParentRevs[0] == fileParentRevs[1]) {
99 // may be equal iff both are unset
100 assert fileParentRevs[0] == NO_REVISION;
101 // everything added
102 BlameBlockInspector bbi = new BlameBlockInspector(insp, NO_REVISION, csetRevIndex);
103 bbi.begin(LineSequence.newlines(new byte[0]), fileRevLines);
104 bbi.match(0, fileRevLines.chunkCount()-1, 0);
105 bbi.end();
106 } else {
107 int soleParent = fileParentRevs[0] == NO_REVISION ? fileParentRevs[1] : fileParentRevs[0];
108 assert soleParent != NO_REVISION;
109 LineSequence parentLines = lines(df, soleParent);
110
111 int parentChangesetRevIndex = df.getChangesetRevisionIndex(soleParent);
112 PatchGenerator<LineSequence> pg = new PatchGenerator<LineSequence>();
113 pg.init(parentLines, fileRevLines);
114 pg.findMatchingBlocks(new BlameBlockInspector(insp, parentChangesetRevIndex, csetRevIndex));
115 }
116 }
117
118 private static int fileRevIndex(HgDataFile df, int csetRevIndex) {
119 Nodeid fileRev = df.getRepo().getManifest().getFileRevision(csetRevIndex, df.getPath());
120 return df.getRevisionIndex(fileRev);
121 }
122
123 private static LineSequence lines(HgDataFile df, int fileRevIndex) {
73 try { 124 try {
74 if (fileParentRevs[0] != NO_REVISION && fileParentRevs[1] != NO_REVISION) { 125 ByteArrayChannel c;
75 // merge 126 df.content(fileRevIndex, c = new ByteArrayChannel());
76 } else if (fileParentRevs[0] == fileParentRevs[1]) { 127 return LineSequence.newlines(c.toArray());
77 // may be equal iff both are unset
78 assert fileParentRevs[0] == NO_REVISION;
79 // everything added
80 ByteArrayChannel c;
81 df.content(fileRevIndex, c = new ByteArrayChannel());
82 BlameBlockInspector bbi = new BlameBlockInspector(insp, NO_REVISION, csetRevIndex);
83 LineSequence cls = LineSequence.newlines(c.toArray());
84 bbi.begin(LineSequence.newlines(new byte[0]), cls);
85 bbi.match(0, cls.chunkCount()-1, 0);
86 bbi.end();
87 } else {
88 int soleParent = fileParentRevs[0] == NO_REVISION ? fileParentRevs[1] : fileParentRevs[0];
89 assert soleParent != NO_REVISION;
90 ByteArrayChannel c1, c2;
91 df.content(soleParent, c1 = new ByteArrayChannel());
92 df.content(fileRevIndex, c2 = new ByteArrayChannel());
93 int parentChangesetRevIndex = df.getChangesetRevisionIndex(soleParent);
94 PatchGenerator<LineSequence> pg = new PatchGenerator<LineSequence>();
95 pg.init(LineSequence.newlines(c1.toArray()), LineSequence.newlines(c2.toArray()));
96 pg.findMatchingBlocks(new BlameBlockInspector(insp, parentChangesetRevIndex, csetRevIndex));
97 }
98 } catch (CancelledException ex) { 128 } catch (CancelledException ex) {
99 // TODO likely it was bad idea to throw cancelled exception from content() 129 // TODO likely it was bad idea to throw cancelled exception from content()
100 // deprecate and provide alternative? 130 // deprecate and provide alternative?
101 HgInvalidStateException ise = new HgInvalidStateException("ByteArrayChannel never throws CancelledException"); 131 HgInvalidStateException ise = new HgInvalidStateException("ByteArrayChannel never throws CancelledException");
102 ise.initCause(ex); 132 ise.initCause(ex);
164 194
165 195
166 196
167 static class BlameBlockInspector extends PatchGenerator.DeltaInspector<LineSequence> { 197 static class BlameBlockInspector extends PatchGenerator.DeltaInspector<LineSequence> {
168 private final BlockInspector insp; 198 private final BlockInspector insp;
169 private final int csetP1; 199 private final int csetOrigin;
170 private final int csetTarget; 200 private final int csetTarget;
171 201 private EqualBlocksCollector p2MergeCommon;
172 public BlameBlockInspector(BlockInspector inspector, int parentCset1, int targetCset) { 202 private int csetMergeParent;
203 private IntVector mergeRanges;
204
205 public BlameBlockInspector(BlockInspector inspector, int originCset, int targetCset) {
173 assert inspector != null; 206 assert inspector != null;
174 insp = inspector; 207 insp = inspector;
175 csetP1 = parentCset1; 208 csetOrigin = originCset;
176 csetTarget = targetCset; 209 csetTarget = targetCset;
210 }
211
212 public void setMergeParent2(EqualBlocksCollector p2Merge, int parentCset2) {
213 p2MergeCommon = p2Merge;
214 csetMergeParent = parentCset2;
215 mergeRanges = new IntVector(3*10, 3*10);
177 } 216 }
178 217
179 @Override 218 @Override
180 public void begin(LineSequence s1, LineSequence s2) { 219 public void begin(LineSequence s1, LineSequence s2) {
181 super.begin(s1, s2); 220 super.begin(s1, s2);
192 } 231 }
193 } 232 }
194 233
195 @Override 234 @Override
196 protected void changed(int s1From, int s1To, int s2From, int s2To) { 235 protected void changed(int s1From, int s1To, int s2From, int s2To) {
197 BlockImpl2 block = new BlockImpl2(seq1, seq2, s1From, s1To-s1From, s2From, s2To - s2From, s1From, s2From); 236 if (p2MergeCommon != null) {
198 block.setOriginAndTarget(csetP1, csetTarget); 237 mergeRanges.clear();
199 insp.changed(block); 238 p2MergeCommon.combineAndMarkRangesWithTarget(s2From, s2To - s2From, csetOrigin, csetMergeParent, mergeRanges);
239
240 /*
241 * Usecases:
242 * 3 lines changed to 10 lines. range of 10 lines breaks down to 2 from p2, 3 from p1, and 5 from p2.
243 * We report: 2 lines changed to 2(p2), then 1 line changed with 3(p1) and 5 lines added from p2.
244 *
245 * 10 lines changed to 3 lines, range of 3 lines breaks down to 2 line from p1 and 1 line from p2.
246 * We report: 2 lines changed to 2(p1) and 8 lines changed to 1(p2)
247 */
248 int s1TotalLines = s1To - s1From, s1ConsumedLines = 0, s1Start = s1From;
249
250 for (int i = 0; i < mergeRanges.size(); i += 3) {
251 final int rangeOrigin = mergeRanges.get(i);
252 final int rangeStart = mergeRanges.get(i+1);
253 final int rangeLen = mergeRanges.get(i+2);
254 final boolean lastRange = i+3 >= mergeRanges.size();
255 final int s1LinesLeft = s1TotalLines - s1ConsumedLines;
256 // how many lines we may reported as changed (don't use more than in range unless it's the very last range)
257 final int s1LinesToBorrow = lastRange ? s1LinesLeft : Math.min(s1LinesLeft, rangeLen);
258 if (s1LinesToBorrow > 0) {
259 BlockImpl2 block = new BlockImpl2(seq1, seq2, s1Start, s1LinesToBorrow, rangeStart, rangeLen, s1Start, rangeStart);
260 block.setOriginAndTarget(rangeOrigin, csetTarget);
261 insp.changed(block);
262 s1ConsumedLines += s1LinesToBorrow;
263 s1Start += s1LinesToBorrow;
264 } else {
265 BlockImpl2 block = getAddBlock(rangeStart, rangeLen, s1Start);
266 block.setOriginAndTarget(rangeOrigin, csetTarget);
267 insp.added(block);
268 }
269 }
270 if (s1ConsumedLines != s1TotalLines) {
271 throw new HgInvalidStateException(String.format("Expected to process %d lines, but actually was %d", s1TotalLines, s1ConsumedLines));
272 }
273 } else {
274 BlockImpl2 block = new BlockImpl2(seq1, seq2, s1From, s1To-s1From, s2From, s2To - s2From, s1From, s2From);
275 block.setOriginAndTarget(csetOrigin, csetTarget);
276 insp.changed(block);
277 }
200 } 278 }
201 279
202 @Override 280 @Override
203 protected void added(int s1InsertPoint, int s2From, int s2To) { 281 protected void added(int s1InsertPoint, int s2From, int s2To) {
204 BlockImpl2 block = new BlockImpl2(null, seq2, -1, -1, s2From, s2To - s2From, s1InsertPoint, -1); 282 if (p2MergeCommon != null) {
205 block.setOriginAndTarget(csetP1, csetTarget); 283 mergeRanges.clear();
206 insp.added(block); 284 p2MergeCommon.combineAndMarkRangesWithTarget(s2From, s2To - s2From, csetOrigin, csetMergeParent, mergeRanges);
285 int insPoint = s1InsertPoint; // track changes to insertion point
286 for (int i = 0; i < mergeRanges.size(); i += 3) {
287 int rangeOrigin = mergeRanges.get(i);
288 int rangeStart = mergeRanges.get(i+1);
289 int rangeLen = mergeRanges.get(i+2);
290 BlockImpl2 block = getAddBlock(rangeStart, rangeLen, insPoint);
291 block.setOriginAndTarget(rangeOrigin, csetTarget);
292 insp.added(block);
293 // indicate insPoint moved down number of lines we just reported
294 insPoint += rangeLen;
295 }
296 } else {
297 BlockImpl2 block = getAddBlock(s2From, s2To - s2From, s1InsertPoint);
298 block.setOriginAndTarget(csetOrigin, csetTarget);
299 insp.added(block);
300 }
207 } 301 }
208 302
209 @Override 303 @Override
210 protected void deleted(int s2DeletePoint, int s1From, int s1To) { 304 protected void deleted(int s2DeletePoint, int s1From, int s1To) {
211 BlockImpl2 block = new BlockImpl2(seq1, null, s1From, s1To - s1From, -1, -1, -1, s2DeletePoint); 305 BlockImpl2 block = new BlockImpl2(seq1, null, s1From, s1To - s1From, -1, -1, -1, s2DeletePoint);
212 block.setOriginAndTarget(csetP1, csetTarget); 306 block.setOriginAndTarget(csetOrigin, csetTarget);
213 insp.deleted(block); 307 insp.deleted(block);
214 } 308 }
215 309
216 @Override 310 @Override
217 protected void unchanged(int s1From, int s2From, int length) { 311 protected void unchanged(int s1From, int s2From, int length) {
218 BlockImpl1 block = new BlockImpl1(s1From, s2From, length); 312 BlockImpl1 block = new BlockImpl1(s1From, s2From, length);
219 block.setOriginAndTarget(csetP1, csetTarget); 313 block.setOriginAndTarget(csetOrigin, csetTarget);
220 insp.same(block); 314 insp.same(block);
315 }
316
317 private BlockImpl2 getAddBlock(int start, int len, int insPoint) {
318 return new BlockImpl2(null, seq2, -1, -1, start, len, insPoint, -1);
221 } 319 }
222 } 320 }
223 321
224 static class BlockImpl implements Block { 322 static class BlockImpl implements Block {
225 323
342 return String.format("@@ -%d,%d +%d,0 @@", firstRemovedLine(), totalRemovedLines(), removedAt()); 440 return String.format("@@ -%d,%d +%d,0 @@", firstRemovedLine(), totalRemovedLines(), removedAt());
343 } 441 }
344 return String.format("@@ -%d,%d +%d,%d @@", firstRemovedLine(), totalRemovedLines(), firstAddedLine(), totalAddedLines()); 442 return String.format("@@ -%d,%d +%d,%d @@", firstRemovedLine(), totalRemovedLines(), firstAddedLine(), totalAddedLines());
345 } 443 }
346 } 444 }
445
446 static class EqualBlocksCollector implements PatchGenerator.MatchInspector<LineSequence> {
447 private final IntVector matches = new IntVector(10*3, 2*3);
448
449 public void begin(LineSequence s1, LineSequence s2) {
450 }
451
452 public void match(int startSeq1, int startSeq2, int matchLength) {
453 matches.add(startSeq1);
454 matches.add(startSeq2);
455 matches.add(matchLength);
456 }
457
458 public void end() {
459 }
460
461 // true when specified line in origin is equal to a line in target
462 public boolean includesOriginLine(int ln) {
463 return includes(ln, 0);
464 }
465
466 // true when specified line in target is equal to a line in origin
467 public boolean includesTargetLine(int ln) {
468 return includes(ln, 1);
469 }
470
471 public void intersectWithTarget(int start, int length, IntVector result) {
472 int s = start;
473 for (int l = start, x = start + length; l < x; l++) {
474 if (!includesTargetLine(l)) {
475 if (l - s > 0) {
476 result.add(s);
477 result.add(l - s);
478 }
479 s = l+1;
480 }
481 }
482 if (s < start+length) {
483 result.add(s);
484 result.add((start + length) - s);
485 }
486 }
487
488 /*
489 * intersects [start..start+length) with ranges of target lines, and based on the intersection
490 * breaks initial range into smaller ranges and records them into result, with marker to indicate
491 * whether the range is from initial range (markerSource) or is a result of the intersection with target
492 * (markerTarget)
493 */
494 public void combineAndMarkRangesWithTarget(int start, int length, int markerSource, int markerTarget, IntVector result) {
495 int sourceStart = start, targetStart = start, sourceEnd = start + length;
496 for (int l = sourceStart; l < sourceEnd; l++) {
497 if (includesTargetLine(l)) {
498 // l is from target
499 if (sourceStart < l) {
500 // few lines from source range were not in the target, report them
501 result.add(markerSource);
502 result.add(sourceStart);
503 result.add(l - sourceStart);
504 }
505 // indicate the earliest line from source range to use
506 sourceStart = l + 1;
507 } else {
508 // l is not in target
509 if (targetStart < l) {
510 // report lines from target range
511 result.add(markerTarget);
512 result.add(targetStart);
513 result.add(l - targetStart);
514 }
515 // next line *may* be from target
516 targetStart = l + 1;
517 }
518 }
519 // if source range end with line from target, sourceStart would be == sourceEnd, and we need to add range with markerTarget
520 // if source range doesn't end with target line, targetStart == sourceEnd, while sourceStart < sourceEnd
521 if (sourceStart < sourceEnd) {
522 assert targetStart == sourceEnd;
523 // something left from the source range
524 result.add(markerSource);
525 result.add(sourceStart);
526 result.add(sourceEnd - sourceStart);
527 } else if (targetStart < sourceEnd) {
528 assert sourceStart == sourceEnd;
529 result.add(markerTarget);
530 result.add(targetStart);
531 result.add(sourceEnd - targetStart);
532 }
533 }
534
535 private boolean includes(int ln, int o) {
536 for (int i = 2; i < matches.size(); o += 3, i+=3) {
537 int rangeStart = matches.get(o);
538 if (rangeStart > ln) {
539 return false;
540 }
541 int rangeLen = matches.get(i);
542 if (rangeStart + rangeLen > ln) {
543 return true;
544 }
545 }
546 return false;
547 }
548 }
549
550 public static void main(String[] args) {
551 EqualBlocksCollector bc = new EqualBlocksCollector();
552 bc.match(-1, 5, 3);
553 bc.match(-1, 10, 2);
554 bc.match(-1, 15, 3);
555 bc.match(-1, 20, 3);
556 assert !bc.includesTargetLine(4);
557 assert bc.includesTargetLine(7);
558 assert !bc.includesTargetLine(8);
559 assert bc.includesTargetLine(10);
560 assert !bc.includesTargetLine(12);
561 IntVector r = new IntVector();
562 bc.intersectWithTarget(7, 10, r);
563 for (int i = 0; i < r.size(); i+=2) {
564 System.out.printf("[%d..%d) ", r.get(i), r.get(i) + r.get(i+1));
565 }
566 System.out.println();
567 r.clear();
568 bc.combineAndMarkRangesWithTarget(0, 16, 508, 514, r);
569 for (int i = 0; i < r.size(); i+=3) {
570 System.out.printf("%d:[%d..%d) ", r.get(i), r.get(i+1), r.get(i+1) + r.get(i+2));
571 }
572 }
347 } 573 }