Mercurial > jhg
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 515:e6c8b9b654b2
Provide access to HgDataFile being iterated into HgChangesetTreeHandler.TreeElement to give context for renamed files
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Mon, 17 Dec 2012 20:51:12 +0100 |
| parents | 5dcb4581c8ef |
| children | 0ae5768081aa |
comparison
equal
deleted
inserted
replaced
| 514:5dcb4581c8ef | 515:e6c8b9b654b2 |
|---|---|
| 295 progressHelper.done(); | 295 progressHelper.done(); |
| 296 } | 296 } |
| 297 } | 297 } |
| 298 | 298 |
| 299 /** | 299 /** |
| 300 * Tree-wise iteration of a file history, with handy access to parent-child relations between changesets. | 300 * Tree-wise iteration of a file history, with handy access to parent-child relations between changesets. |
| 301 * When file history is being followed, handler may additionally implement {@link HgFileRenameHandlerMixin} | |
| 302 * to get notified about switching between history chunks that belong to different names. | |
| 301 * | 303 * |
| 302 * @param handler callback to process changesets. | 304 * @param handler callback to process changesets. |
| 305 * @see HgFileRenameHandlerMixin | |
| 303 * @throws HgCallbackTargetException propagated exception from the handler | 306 * @throws HgCallbackTargetException propagated exception from the handler |
| 304 * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state | 307 * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state |
| 305 * @throws CancelledException if execution of the command was cancelled | 308 * @throws CancelledException if execution of the command was cancelled |
| 306 * @throws IllegalArgumentException if command is not satisfied with its arguments | 309 * @throws IllegalArgumentException if command is not satisfied with its arguments |
| 307 * @throws ConcurrentModificationException if this log command instance is already running | 310 * @throws ConcurrentModificationException if this log command instance is already running |
| 308 */ | 311 */ |
| 309 public void execute(HgChangesetTreeHandler handler) throws HgCallbackTargetException, HgException, CancelledException { | 312 public void execute(final HgChangesetTreeHandler handler) throws HgCallbackTargetException, HgException, CancelledException { |
| 310 if (handler == null) { | 313 if (handler == null) { |
| 311 throw new IllegalArgumentException(); | 314 throw new IllegalArgumentException(); |
| 312 } | 315 } |
| 313 if (csetTransform != null) { | 316 if (csetTransform != null) { |
| 314 throw new ConcurrentModificationException(); | 317 throw new ConcurrentModificationException(); |
| 324 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followRenames); | 327 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followRenames); |
| 325 // we iterate separate histories of each filename, need to connect | 328 // we iterate separate histories of each filename, need to connect |
| 326 // last node of historyA with first node of historyB (A renamed to B case) | 329 // last node of historyA with first node of historyB (A renamed to B case) |
| 327 // to make overall history smooth. | 330 // to make overall history smooth. |
| 328 HistoryNode lastFromPrevIteration = null; | 331 HistoryNode lastFromPrevIteration = null; |
| 329 HgFileRevision copiedFrom = null, copiedTo = null; | 332 |
| 330 boolean shallReportRenameAfter1Step = false; | 333 class HandlerDispatcher { |
| 331 | 334 private final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ |
| 332 final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ | 335 private ElementImpl ei = null; |
| 333 ElementImpl ei = null; | 336 private ProgressSupport progress; |
| 337 private HgDataFile currentFileNode; | |
| 338 | |
| 339 public void prepare(ProgressSupport parentProgress, int historyNodeCount, TreeBuildInspector treeBuildInspector) { | |
| 340 if (ei == null) { | |
| 341 // when follow is true, changeHistory.size() of the first revision might be quite short | |
| 342 // (e.g. bad fname recognized soon), hence ensure at least cache size at once | |
| 343 ei = new ElementImpl(Math.max(CACHE_CSET_IN_ADVANCE_THRESHOLD, historyNodeCount)); | |
| 344 } | |
| 345 if (historyNodeCount < CACHE_CSET_IN_ADVANCE_THRESHOLD ) { | |
| 346 int[] commitRevisions = treeBuildInspector.getCommitRevisions(); | |
| 347 // read bunch of changesets at once and cache 'em | |
| 348 ei.initTransform(); | |
| 349 repo.getChangelog().range(ei, commitRevisions); | |
| 350 parentProgress.worked(1); | |
| 351 progress = new ProgressSupport.Sub(parentProgress, 2); | |
| 352 } else { | |
| 353 progress = new ProgressSupport.Sub(parentProgress, 3); | |
| 354 } | |
| 355 progress.start(historyNodeCount); | |
| 356 } | |
| 357 public void once(HistoryNode n) throws HgCallbackTargetException, CancelledException { | |
| 358 handler.treeElement(ei.init(n, currentFileNode)); | |
| 359 progress.worked(1); | |
| 360 cancelHelper.checkCancelled(); | |
| 361 } | |
| 362 | |
| 363 public void switchTo(HgDataFile df) { | |
| 364 // from now on, use df in TreeElement | |
| 365 currentFileNode = df; | |
| 366 } | |
| 367 }; | |
| 368 final HandlerDispatcher dispatcher = new HandlerDispatcher(); | |
| 334 | 369 |
| 335 // renamed files in the queue are placed with respect to #iterateDirection | 370 // renamed files in the queue are placed with respect to #iterateDirection |
| 336 // i.e. if we iterate from new to old, recent filenames come first | 371 // i.e. if we iterate from new to old, recent filenames come first |
| 337 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); | 372 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); |
| 338 progressHelper.start(4 * fileRenamesQueue.size()); | 373 progressHelper.start(4 * fileRenamesQueue.size()); |
| 342 cancelHelper.checkCancelled(); | 377 cancelHelper.checkCancelled(); |
| 343 final List<HistoryNode> changeHistory = treeBuildInspector.go(renameInfo.first(), renameInfo.second()); | 378 final List<HistoryNode> changeHistory = treeBuildInspector.go(renameInfo.first(), renameInfo.second()); |
| 344 assert changeHistory.size() > 0; | 379 assert changeHistory.size() > 0; |
| 345 progressHelper.worked(1); | 380 progressHelper.worked(1); |
| 346 cancelHelper.checkCancelled(); | 381 cancelHelper.checkCancelled(); |
| 347 final ProgressSupport ph2; | 382 dispatcher.prepare(progressHelper, changeHistory.size(), treeBuildInspector); |
| 348 if (ei == null) { | |
| 349 // when follow is true, changeHistory.size() of the first revision might be quite short | |
| 350 // (e.g. bad fname recognized soon), hence ensure at least cache size at once | |
| 351 ei = new ElementImpl(Math.max(CACHE_CSET_IN_ADVANCE_THRESHOLD, changeHistory.size())); | |
| 352 } | |
| 353 if (changeHistory.size() < CACHE_CSET_IN_ADVANCE_THRESHOLD ) { | |
| 354 int[] commitRevisions = treeBuildInspector.getCommitRevisions(); | |
| 355 assert changeHistory.size() == commitRevisions.length; | |
| 356 // read bunch of changesets at once and cache 'em | |
| 357 ei.initTransform(); | |
| 358 repo.getChangelog().range(ei, commitRevisions); | |
| 359 progressHelper.worked(1); | |
| 360 ph2 = new ProgressSupport.Sub(progressHelper, 2); | |
| 361 } else { | |
| 362 ph2 = new ProgressSupport.Sub(progressHelper, 3); | |
| 363 } | |
| 364 ph2.start(changeHistory.size()); | |
| 365 if (lastFromPrevIteration != null) { | 383 if (lastFromPrevIteration != null) { |
| 366 if (iterateDirection == IterateDirection.FromOldToNew) { | 384 if (iterateDirection == IterateDirection.FromOldToNew) { |
| 367 // forward, from old to new: | 385 // forward, from old to new: |
| 368 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) | 386 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) |
| 369 // then A(n).bind(B(0)) | 387 // then A(n).bind(B(0)) |
| 370 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); // B(0) | 388 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); // B(0) |
| 371 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); // lastFromPrevIteration is A(n) | 389 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); // lastFromPrevIteration is A(n) |
| 372 changeHistory.add(0, lastFromPrevIteration); | 390 dispatcher.once(lastFromPrevIteration); |
| 373 if (renameHandler != null) { // shall report renames | 391 if (renameHandler != null) { // shall report renames |
| 374 assert namesIndex > 0; | 392 assert namesIndex > 0; |
| 375 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // A | 393 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // A |
| 376 copiedFrom = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, null); | 394 HgFileRevision copiedFrom = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, null); |
| 377 copiedTo = new HgFileRevision(renameInfo.first(), oldestOfTheNextChunk.fileRevision, copiedFrom.getPath()); | 395 HgFileRevision copiedTo = new HgFileRevision(renameInfo.first(), oldestOfTheNextChunk.fileRevision, copiedFrom.getPath()); |
| 378 shallReportRenameAfter1Step = true; // report rename after A(n) | 396 renameHandler.copy(copiedFrom, copiedTo); |
| 379 } | 397 } |
| 380 } else { | 398 } else { |
| 381 assert iterateDirection == IterateDirection.FromNewToOld; | 399 assert iterateDirection == IterateDirection.FromNewToOld; |
| 382 // A renamed to B. A(0..n) -> B(0..m). | 400 // A renamed to B. A(0..n) -> B(0..m). |
| 383 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)), report B(0), A(n)... | 401 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)), report B(0), A(n)... |
| 384 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) | 402 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) |
| 385 newestOfNextChunk.bindChild(lastFromPrevIteration); | 403 newestOfNextChunk.bindChild(lastFromPrevIteration); |
| 386 changeHistory.add(lastFromPrevIteration); | 404 dispatcher.once(lastFromPrevIteration); |
| 387 if (renameHandler != null) { | 405 if (renameHandler != null) { |
| 388 assert namesIndex > 0; | 406 assert namesIndex > 0; |
| 389 // renameInfo points to chunk of name A now, and lastFromPrevIteration (from namesIndex-1) is B | 407 // renameInfo points to chunk of name A now, and lastFromPrevIteration (from namesIndex-1) is B |
| 390 copiedFrom = new HgFileRevision(renameInfo.first(), newestOfNextChunk.fileRevision, null); | 408 HgFileRevision copiedFrom = new HgFileRevision(renameInfo.first(), newestOfNextChunk.fileRevision, null); |
| 391 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // B | 409 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // B |
| 392 copiedTo = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, copiedFrom.getPath()); | 410 HgFileRevision copiedTo = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, copiedFrom.getPath()); |
| 393 shallReportRenameAfter1Step = true; // report rename after B(0) | 411 renameHandler.copy(copiedFrom, copiedTo); |
| 394 } | 412 } |
| 395 } | 413 } |
| 396 } | 414 } |
| 397 if (namesIndex + 1 < renamesQueueSize) { | 415 if (namesIndex + 1 < renamesQueueSize) { |
| 398 // there's at least one more name we are going to look at, save | 416 // there's at least one more name we are going to look at, save |
| 407 lastFromPrevIteration = changeHistory.remove(0); | 425 lastFromPrevIteration = changeHistory.remove(0); |
| 408 } | 426 } |
| 409 } else { | 427 } else { |
| 410 lastFromPrevIteration = null; // just for the sake of no references to old items | 428 lastFromPrevIteration = null; // just for the sake of no references to old items |
| 411 } | 429 } |
| 430 dispatcher.switchTo(renameInfo.first()); | |
| 412 // XXX shall sort changeHistory according to changeset numbers? | 431 // XXX shall sort changeHistory according to changeset numbers? |
| 413 Iterator<HistoryNode> it; | 432 Iterator<HistoryNode> it; |
| 414 if (iterateDirection == IterateDirection.FromOldToNew) { | 433 if (iterateDirection == IterateDirection.FromOldToNew) { |
| 415 it = changeHistory.listIterator(); | 434 it = changeHistory.listIterator(); |
| 416 } else { | 435 } else { |
| 417 assert iterateDirection == IterateDirection.FromNewToOld; | 436 assert iterateDirection == IterateDirection.FromNewToOld; |
| 418 it = new ReverseIterator<HistoryNode>(changeHistory); | 437 it = new ReverseIterator<HistoryNode>(changeHistory); |
| 419 } | 438 } |
| 420 while(it.hasNext()) { | 439 while(it.hasNext()) { |
| 421 HistoryNode n = it.next(); | 440 HistoryNode n = it.next(); |
| 422 handler.treeElement(ei.init(n)); | 441 dispatcher.once(n); |
| 423 ph2.worked(1); | |
| 424 cancelHelper.checkCancelled(); | |
| 425 if (shallReportRenameAfter1Step) { | |
| 426 assert renameHandler != null; | |
| 427 assert copiedFrom != null; | |
| 428 assert copiedTo != null; | |
| 429 renameHandler.copy(copiedFrom, copiedTo); | |
| 430 shallReportRenameAfter1Step = false; | |
| 431 copiedFrom = copiedTo = null; | |
| 432 } | |
| 433 } | 442 } |
| 434 } // for fileRenamesQueue; | 443 } // for fileRenamesQueue; |
| 435 progressHelper.done(); | 444 progressHelper.done(); |
| 436 } | 445 } |
| 437 | 446 |
| 716 } | 725 } |
| 717 } | 726 } |
| 718 | 727 |
| 719 private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { | 728 private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { |
| 720 private HistoryNode historyNode; | 729 private HistoryNode historyNode; |
| 730 private HgDataFile fileNode; | |
| 721 private Pair<HgChangeset, HgChangeset> parents; | 731 private Pair<HgChangeset, HgChangeset> parents; |
| 722 private List<HgChangeset> children; | 732 private List<HgChangeset> children; |
| 723 private IntMap<HgChangeset> cachedChangesets; | 733 private IntMap<HgChangeset> cachedChangesets; |
| 724 private ChangesetTransformer.Transformation transform; | 734 private ChangesetTransformer.Transformation transform; |
| 725 private Nodeid changesetRevision; | 735 private Nodeid changesetRevision; |
| 728 | 738 |
| 729 public ElementImpl(int total) { | 739 public ElementImpl(int total) { |
| 730 cachedChangesets = new IntMap<HgChangeset>(total); | 740 cachedChangesets = new IntMap<HgChangeset>(total); |
| 731 } | 741 } |
| 732 | 742 |
| 733 ElementImpl init(HistoryNode n) { | 743 ElementImpl init(HistoryNode n, HgDataFile df) { |
| 734 historyNode = n; | 744 historyNode = n; |
| 745 fileNode = df; | |
| 735 parents = null; | 746 parents = null; |
| 736 children = null; | 747 children = null; |
| 737 changesetRevision = null; | 748 changesetRevision = null; |
| 738 parentRevisions = null; | 749 parentRevisions = null; |
| 739 childRevisions = null; | 750 childRevisions = null; |
| 740 return this; | 751 return this; |
| 741 } | 752 } |
| 742 | 753 |
| 743 public Nodeid fileRevision() { | 754 public Nodeid fileRevision() { |
| 744 return historyNode.fileRevision; | 755 return historyNode.fileRevision; |
| 756 } | |
| 757 | |
| 758 public HgDataFile file() { | |
| 759 return fileNode; | |
| 745 } | 760 } |
| 746 | 761 |
| 747 public HgChangeset changeset() { | 762 public HgChangeset changeset() { |
| 748 return get(historyNode.changeset)[0]; | 763 return get(historyNode.changeset)[0]; |
| 749 } | 764 } |
