Mercurial > hg4j
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); |
