Mercurial > hg4j
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 } |