Mercurial > jhg
comparison src/org/tmatesoft/hg/internal/AnnotateFacility.java @ 555:e623aa2ca526
Annotate: RevisionDescriptor provides extra knowledge about inspected/annotated revision
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Fri, 22 Feb 2013 19:03:25 +0100 |
parents | a5fd757d1b5d |
children |
comparison
equal
deleted
inserted
replaced
554:a5fd757d1b5d | 555:e623aa2ca526 |
---|---|
23 import java.util.LinkedList; | 23 import java.util.LinkedList; |
24 import java.util.ListIterator; | 24 import java.util.ListIterator; |
25 | 25 |
26 import org.tmatesoft.hg.core.HgIterateDirection; | 26 import org.tmatesoft.hg.core.HgIterateDirection; |
27 import org.tmatesoft.hg.core.Nodeid; | 27 import org.tmatesoft.hg.core.Nodeid; |
28 import org.tmatesoft.hg.internal.AnnotateFacility.BlockData; | 28 import org.tmatesoft.hg.internal.AnnotateFacility.RevisionDescriptor.Recipient; |
29 import org.tmatesoft.hg.internal.DiffHelper.LineSequence; | 29 import org.tmatesoft.hg.internal.DiffHelper.LineSequence; |
30 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain; | 30 import org.tmatesoft.hg.internal.DiffHelper.LineSequence.ByteChain; |
31 import org.tmatesoft.hg.repo.HgDataFile; | 31 import org.tmatesoft.hg.repo.HgDataFile; |
32 import org.tmatesoft.hg.repo.HgInvalidStateException; | 32 import org.tmatesoft.hg.repo.HgInvalidStateException; |
33 import org.tmatesoft.hg.repo.HgRepository; | |
34 import org.tmatesoft.hg.util.Adaptable; | |
33 import org.tmatesoft.hg.util.CancelledException; | 35 import org.tmatesoft.hg.util.CancelledException; |
34 import org.tmatesoft.hg.util.Pair; | 36 import org.tmatesoft.hg.util.Pair; |
35 | 37 |
36 /** | 38 /** |
39 * Facility with diff/annotate functionality. | |
37 * | 40 * |
38 * @author Artem Tikhomirov | 41 * @author Artem Tikhomirov |
39 * @author TMate Software Ltd. | 42 * @author TMate Software Ltd. |
40 */ | 43 */ |
41 @Experimental(reason="work in progress") | 44 @Experimental(reason="work in progress") |
53 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); | 56 DiffHelper<LineSequence> pg = new DiffHelper<LineSequence>(); |
54 pg.init(c1, c2); | 57 pg.init(c1, c2); |
55 pg.findMatchingBlocks(new BlameBlockInspector(insp, clogRevIndex1, clogRevIndex2)); | 58 pg.findMatchingBlocks(new BlameBlockInspector(insp, clogRevIndex1, clogRevIndex2)); |
56 } | 59 } |
57 | 60 |
61 /** | |
62 * Walk file history up to revision at given changeset and report changes for each revision | |
63 */ | |
58 public void annotate(HgDataFile df, int changelogRevisionIndex, BlockInspector insp, HgIterateDirection iterateOrder) { | 64 public void annotate(HgDataFile df, int changelogRevisionIndex, BlockInspector insp, HgIterateDirection iterateOrder) { |
59 if (!df.exists()) { | 65 if (!df.exists()) { |
60 return; | 66 return; |
61 } | 67 } |
62 // Note, changelogRevisionIndex may be TIP, while #implAnnotateChange doesn't tolerate constants | 68 // Note, changelogRevisionIndex may be TIP, while #implAnnotateChange doesn't tolerate constants |
105 implAnnotateChange(fileInfoCache, clogRevIndex, fri, fileRevParents, insp); | 111 implAnnotateChange(fileInfoCache, clogRevIndex, fri, fileRevParents, insp); |
106 } | 112 } |
107 } | 113 } |
108 | 114 |
109 /** | 115 /** |
110 * Annotate file revision, line by line. | 116 * Annotates changes of the file against its parent(s). |
117 * Unlike {@link #annotate(HgDataFile, int, BlockInspector, HgIterateDirection)}, doesn't | |
118 * walk file history, looks at the specified revision only. Handles both parents (if merge revision). | |
111 */ | 119 */ |
112 public void annotate(HgDataFile df, int changelogRevisionIndex, LineInspector insp) { | 120 public void annotateSingleRevision(HgDataFile df, int changelogRevisionIndex, BlockInspector insp) { |
113 if (!df.exists()) { | |
114 return; | |
115 } | |
116 FileAnnotation fa = new FileAnnotation(insp); | |
117 annotate(df, changelogRevisionIndex, fa, HgIterateDirection.NewToOld); | |
118 } | |
119 | |
120 /** | |
121 * Annotates changes of the file against its parent(s) | |
122 */ | |
123 public void annotateChange(HgDataFile df, int changelogRevisionIndex, BlockInspector insp) { | |
124 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f | 121 // TODO detect if file is text/binary (e.g. looking for chars < ' ' and not \t\r\n\f |
125 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); | 122 int fileRevIndex = fileRevIndex(df, changelogRevisionIndex); |
126 int[] fileRevParents = new int[2]; | 123 int[] fileRevParents = new int[2]; |
127 df.parents(fileRevIndex, fileRevParents, null, null); | 124 df.parents(fileRevIndex, fileRevParents, null, null); |
128 if (changelogRevisionIndex == TIP) { | 125 if (changelogRevisionIndex == TIP) { |
231 } | 228 } |
232 return rv; | 229 return rv; |
233 } | 230 } |
234 } | 231 } |
235 | 232 |
233 /** | |
234 * Client's sink for revision differences. | |
235 * | |
236 * When implemented, clients shall not expect new {@link Block blocks} instances in each call. | |
237 * | |
238 * In case more information about annotated revision is needed, inspector instances may supply | |
239 * {@link RevisionDescriptor.Recipient} through {@link Adaptable}. | |
240 */ | |
236 @Callback | 241 @Callback |
237 public interface BlockInspector { | 242 public interface BlockInspector { |
238 void same(EqualBlock block); | 243 void same(EqualBlock block); |
239 void added(AddBlock block); | 244 void added(AddBlock block); |
240 void changed(ChangeBlock block); | 245 void changed(ChangeBlock block); |
241 void deleted(DeleteBlock block); | 246 void deleted(DeleteBlock block); |
242 } | |
243 | |
244 @Callback | |
245 public interface BlockInspectorEx extends BlockInspector { // XXX better name | |
246 // XXX perhaps, shall pass object instead of separate values for future extension? | |
247 void start(BlockData originContent, BlockData targetContent); | |
248 void done(); | |
249 } | 247 } |
250 | 248 |
251 /** | 249 /** |
252 * Represents content of a block, either as a sequence of bytes or a | 250 * Represents content of a block, either as a sequence of bytes or a |
253 * sequence of smaller blocks (lines), if appropriate (according to usage context). | 251 * sequence of smaller blocks (lines), if appropriate (according to usage context). |
274 BlockData elementAt(int index); | 272 BlockData elementAt(int index); |
275 int elementCount(); | 273 int elementCount(); |
276 byte[] asArray(); | 274 byte[] asArray(); |
277 } | 275 } |
278 | 276 |
277 /** | |
278 * {@link BlockInspector} may optionally request extra information about revisions | |
279 * being inspected, denoting itself as a {@link RevisionDescriptor.Recipient}. This class | |
280 * provides complete information about file revision under annotation now. | |
281 */ | |
282 public interface RevisionDescriptor { | |
283 /** | |
284 * @return complete source of the diff origin, never <code>null</code> | |
285 */ | |
286 BlockData origin(); | |
287 /** | |
288 * @return complete source of the diff target, never <code>null</code> | |
289 */ | |
290 BlockData target(); | |
291 /** | |
292 * @return changeset revision index of original file, or {@link HgRepository#NO_REVISION} if it's the very first revision | |
293 */ | |
294 int originChangesetIndex(); | |
295 /** | |
296 * @return changeset revision index of the target file | |
297 */ | |
298 int targetChangesetIndex(); | |
299 /** | |
300 * @return <code>true</code> if this revision is merge | |
301 */ | |
302 boolean isMerge(); | |
303 /** | |
304 * @return changeset revision index of the second, merged parent | |
305 */ | |
306 int mergeChangesetIndex(); | |
307 /** | |
308 * @return revision index of the change in file's revlog | |
309 */ | |
310 int fileRevisionIndex(); | |
311 | |
312 /** | |
313 * Implement to indicate interest in {@link RevisionDescriptor}. | |
314 * | |
315 * Note, instance of {@link RevisionDescriptor} is the same for | |
316 * {@link #start(RevisionDescriptor)} and {@link #done(RevisionDescriptor)} | |
317 * methods, and not necessarily a new one (i.e. <code>==</code>) for the next | |
318 * revision announced. | |
319 */ | |
320 @Callback | |
321 public interface Recipient { | |
322 /** | |
323 * Comes prior to any change {@link Block blocks} | |
324 */ | |
325 void start(RevisionDescriptor revisionDescription); | |
326 /** | |
327 * Comes after all change {@link Block blocks} were dispatched | |
328 */ | |
329 void done(RevisionDescriptor revisionDescription); | |
330 } | |
331 } | |
332 | |
279 public interface Block { | 333 public interface Block { |
280 int originChangesetIndex(); | 334 int originChangesetIndex(); |
281 int targetChangesetIndex(); | 335 int targetChangesetIndex(); |
282 // boolean isMergeRevision(); | |
283 // int fileRevisionIndex(); | |
284 // int originFileRevisionIndex(); | |
285 // String[] lines(); | |
286 // byte[] data(); | |
287 } | 336 } |
288 | 337 |
289 public interface EqualBlock extends Block { | 338 public interface EqualBlock extends Block { |
290 int originStart(); | 339 int originStart(); |
291 int targetStart(); | 340 int targetStart(); |
306 BlockData removedLines(); | 355 BlockData removedLines(); |
307 } | 356 } |
308 public interface ChangeBlock extends AddBlock, DeleteBlock { | 357 public interface ChangeBlock extends AddBlock, DeleteBlock { |
309 } | 358 } |
310 | 359 |
311 @Callback | 360 private static class BlameBlockInspector extends DiffHelper.DeltaInspector<LineSequence> { |
312 public interface LineInspector { | |
313 /** | |
314 * Not necessarily invoked sequentially by line numbers | |
315 */ | |
316 void line(int lineNumber, int changesetRevIndex, LineDescriptor ld); | |
317 } | |
318 | |
319 public interface LineDescriptor { | |
320 int totalLines(); | |
321 } | |
322 | |
323 | |
324 | |
325 static class BlameBlockInspector extends DiffHelper.DeltaInspector<LineSequence> { | |
326 private final BlockInspector insp; | 361 private final BlockInspector insp; |
327 private final int csetOrigin; | 362 private final int csetOrigin; |
328 private final int csetTarget; | 363 private final int csetTarget; |
329 private EqualBlocksCollector p2MergeCommon; | 364 private EqualBlocksCollector p2MergeCommon; |
330 private int csetMergeParent; | 365 private int csetMergeParent; |
331 private IntVector mergeRanges; | 366 private IntVector mergeRanges; |
332 private ContentBlock originContent, targetContent; | 367 private final AnnotateRev annotatedRevision; |
333 | 368 |
334 public BlameBlockInspector(BlockInspector inspector, int originCset, int targetCset) { | 369 public BlameBlockInspector(BlockInspector inspector, int originCset, int targetCset) { |
335 assert inspector != null; | 370 assert inspector != null; |
336 insp = inspector; | 371 insp = inspector; |
372 annotatedRevision = new AnnotateRev(); | |
337 csetOrigin = originCset; | 373 csetOrigin = originCset; |
338 csetTarget = targetCset; | 374 csetTarget = targetCset; |
339 } | 375 } |
340 | 376 |
341 public void setMergeParent2(EqualBlocksCollector p2Merge, int parentCset2) { | 377 public void setMergeParent2(EqualBlocksCollector p2Merge, int parentCset2) { |
345 } | 381 } |
346 | 382 |
347 @Override | 383 @Override |
348 public void begin(LineSequence s1, LineSequence s2) { | 384 public void begin(LineSequence s1, LineSequence s2) { |
349 super.begin(s1, s2); | 385 super.begin(s1, s2); |
350 originContent = new ContentBlock(s1); | 386 ContentBlock originContent = new ContentBlock(s1); |
351 targetContent = new ContentBlock(s2); | 387 ContentBlock targetContent = new ContentBlock(s2); |
352 if (insp instanceof BlockInspectorEx) { | 388 annotatedRevision.set(originContent, targetContent); |
353 ((BlockInspectorEx) insp).start(originContent, targetContent); | 389 annotatedRevision.set(csetOrigin, csetTarget, p2MergeCommon != null ? csetMergeParent : NO_REVISION); |
390 Recipient curious = Adaptable.Factory.getAdapter(insp, Recipient.class, null); | |
391 if (curious != null) { | |
392 curious.start(annotatedRevision); | |
354 } | 393 } |
355 } | 394 } |
356 | 395 |
357 @Override | 396 @Override |
358 public void end() { | 397 public void end() { |
359 super.end(); | 398 super.end(); |
360 if(insp instanceof BlockInspectorEx) { | 399 Recipient curious = Adaptable.Factory.getAdapter(insp, Recipient.class, null); |
361 ((BlockInspectorEx) insp).done(); | 400 if (curious != null) { |
362 } | 401 curious.done(annotatedRevision); |
402 } | |
403 p2MergeCommon = null; | |
363 } | 404 } |
364 | 405 |
365 @Override | 406 @Override |
366 protected void changed(int s1From, int s1To, int s2From, int s2To) { | 407 protected void changed(int s1From, int s1To, int s2From, int s2To) { |
367 if (p2MergeCommon != null) { | 408 if (p2MergeCommon != null) { |
385 final boolean lastRange = i+3 >= mergeRanges.size(); | 426 final boolean lastRange = i+3 >= mergeRanges.size(); |
386 final int s1LinesLeft = s1TotalLines - s1ConsumedLines; | 427 final int s1LinesLeft = s1TotalLines - s1ConsumedLines; |
387 // how many lines we may reported as changed (don't use more than in range unless it's the very last range) | 428 // how many lines we may reported as changed (don't use more than in range unless it's the very last range) |
388 final int s1LinesToBorrow = lastRange ? s1LinesLeft : Math.min(s1LinesLeft, rangeLen); | 429 final int s1LinesToBorrow = lastRange ? s1LinesLeft : Math.min(s1LinesLeft, rangeLen); |
389 if (s1LinesToBorrow > 0) { | 430 if (s1LinesToBorrow > 0) { |
390 ChangeBlockImpl block = new ChangeBlockImpl(originContent, targetContent, s1Start, s1LinesToBorrow, rangeStart, rangeLen, s1Start, rangeStart); | 431 ChangeBlockImpl block = getChangeBlock(s1Start, s1LinesToBorrow, rangeStart, rangeLen); |
391 block.setOriginAndTarget(rangeOrigin, csetTarget); | 432 block.setOriginAndTarget(rangeOrigin, csetTarget); |
392 insp.changed(block); | 433 insp.changed(block); |
393 s1ConsumedLines += s1LinesToBorrow; | 434 s1ConsumedLines += s1LinesToBorrow; |
394 s1Start += s1LinesToBorrow; | 435 s1Start += s1LinesToBorrow; |
395 } else { | 436 } else { |
400 } | 441 } |
401 if (s1ConsumedLines != s1TotalLines) { | 442 if (s1ConsumedLines != s1TotalLines) { |
402 throw new HgInvalidStateException(String.format("Expected to process %d lines, but actually was %d", s1TotalLines, s1ConsumedLines)); | 443 throw new HgInvalidStateException(String.format("Expected to process %d lines, but actually was %d", s1TotalLines, s1ConsumedLines)); |
403 } | 444 } |
404 } else { | 445 } else { |
405 ChangeBlockImpl block = new ChangeBlockImpl(originContent, targetContent, s1From, s1To-s1From, s2From, s2To - s2From, s1From, s2From); | 446 ChangeBlockImpl block = getChangeBlock(s1From, s1To-s1From, s2From, s2To - s2From); |
406 block.setOriginAndTarget(csetOrigin, csetTarget); | 447 block.setOriginAndTarget(csetOrigin, csetTarget); |
407 insp.changed(block); | 448 insp.changed(block); |
408 } | 449 } |
409 } | 450 } |
410 | 451 |
431 } | 472 } |
432 } | 473 } |
433 | 474 |
434 @Override | 475 @Override |
435 protected void deleted(int s2DeletePoint, int s1From, int s1To) { | 476 protected void deleted(int s2DeletePoint, int s1From, int s1To) { |
436 ChangeBlockImpl block = new ChangeBlockImpl(originContent, null, s1From, s1To - s1From, -1, -1, -1, s2DeletePoint); | 477 ChangeBlockImpl block = new ChangeBlockImpl(annotatedRevision.origin, null, s1From, s1To - s1From, -1, -1, -1, s2DeletePoint); |
437 block.setOriginAndTarget(csetOrigin, csetTarget); | 478 block.setOriginAndTarget(csetOrigin, csetTarget); |
438 insp.deleted(block); | 479 insp.deleted(block); |
439 } | 480 } |
440 | 481 |
441 @Override | 482 @Override |
442 protected void unchanged(int s1From, int s2From, int length) { | 483 protected void unchanged(int s1From, int s2From, int length) { |
443 EqualBlockImpl block = new EqualBlockImpl(s1From, s2From, length, targetContent); | 484 EqualBlockImpl block = new EqualBlockImpl(s1From, s2From, length, annotatedRevision.target); |
444 block.setOriginAndTarget(csetOrigin, csetTarget); | 485 block.setOriginAndTarget(csetOrigin, csetTarget); |
445 insp.same(block); | 486 insp.same(block); |
446 } | 487 } |
447 | 488 |
448 private ChangeBlockImpl getAddBlock(int start, int len, int insPoint) { | 489 private ChangeBlockImpl getAddBlock(int start, int len, int insPoint) { |
449 return new ChangeBlockImpl(null, targetContent, -1, -1, start, len, insPoint, -1); | 490 return new ChangeBlockImpl(null, annotatedRevision.target, -1, -1, start, len, insPoint, -1); |
450 } | 491 } |
451 } | 492 |
452 | 493 private ChangeBlockImpl getChangeBlock(int start1, int end1, int start2, int end2) { |
453 static class BlockImpl implements Block { | 494 return new ChangeBlockImpl(annotatedRevision.origin, annotatedRevision.target, start1, end1-start1, start2, end2-start2, start1, start2); |
495 } | |
496 } | |
497 | |
498 private static class BlockImpl implements Block { | |
454 | 499 |
455 private int originCset; | 500 private int originCset; |
456 private int targetCset; | 501 private int targetCset; |
457 | 502 |
458 void setOriginAndTarget(int originChangesetIndex, int targetChangesetIndex) { | 503 void setOriginAndTarget(int originChangesetIndex, int targetChangesetIndex) { |
470 public int targetChangesetIndex() { | 515 public int targetChangesetIndex() { |
471 return targetCset; | 516 return targetCset; |
472 } | 517 } |
473 } | 518 } |
474 | 519 |
475 static class EqualBlockImpl extends BlockImpl implements EqualBlock { | 520 private static class EqualBlockImpl extends BlockImpl implements EqualBlock { |
476 private final int start1, start2; | 521 private final int start1, start2; |
477 private final int length; | 522 private final int length; |
478 private final ContentBlock fullContent; | 523 private final ContentBlock fullContent; |
479 private FilterBlock myContent; | 524 private FilterBlock myContent; |
480 | 525 |
508 public String toString() { | 553 public String toString() { |
509 return String.format("@@ [%d..%d) == [%d..%d) @@", start1, start1+length, start2, start2+length); | 554 return String.format("@@ [%d..%d) == [%d..%d) @@", start1, start1+length, start2, start2+length); |
510 } | 555 } |
511 } | 556 } |
512 | 557 |
513 static class ChangeBlockImpl extends BlockImpl implements ChangeBlock { | 558 private static class ChangeBlockImpl extends BlockImpl implements ChangeBlock { |
514 | 559 |
515 private final ContentBlock oldContent; | 560 private final ContentBlock oldContent; |
516 private final ContentBlock newContent; | 561 private final ContentBlock newContent; |
517 private final int s1Start; | 562 private final int s1Start; |
518 private final int s1Len; | 563 private final int s1Len; |
756 } | 801 } |
757 return false; | 802 return false; |
758 } | 803 } |
759 } | 804 } |
760 | 805 |
806 private static class AnnotateRev implements RevisionDescriptor { | |
807 public ContentBlock origin, target; | |
808 public int originCset, targetCset, mergeCset, fileRevIndex; | |
809 | |
810 public void set(ContentBlock o, ContentBlock t) { | |
811 origin = o; | |
812 target = t; | |
813 } | |
814 public void set(int o, int t, int m) { | |
815 originCset = o; | |
816 targetCset = t; | |
817 mergeCset = m; | |
818 } | |
819 | |
820 public BlockData origin() { | |
821 return origin; | |
822 } | |
823 | |
824 public BlockData target() { | |
825 return target; | |
826 } | |
827 | |
828 public int originChangesetIndex() { | |
829 return originCset; | |
830 } | |
831 | |
832 public int targetChangesetIndex() { | |
833 return targetCset; | |
834 } | |
835 | |
836 public boolean isMerge() { | |
837 return mergeCset != NO_REVISION; | |
838 } | |
839 | |
840 public int mergeChangesetIndex() { | |
841 return mergeCset; | |
842 } | |
843 | |
844 public int fileRevisionIndex() { | |
845 return fileRevIndex; | |
846 } | |
847 } | |
848 | |
761 public static void main(String[] args) { | 849 public static void main(String[] args) { |
762 EqualBlocksCollector bc = new EqualBlocksCollector(); | 850 EqualBlocksCollector bc = new EqualBlocksCollector(); |
763 bc.match(-1, 5, 3); | 851 bc.match(-1, 5, 3); |
764 bc.match(-1, 10, 2); | 852 bc.match(-1, 10, 2); |
765 bc.match(-1, 15, 3); | 853 bc.match(-1, 15, 3); |