Mercurial > jhg
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 516:0ae5768081aa
Allow walking file rename history independently from file ancestry (native hg log --follow does both at once)
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Tue, 18 Dec 2012 18:57:03 +0100 |
parents | e6c8b9b654b2 |
children | 9922d1f7cb2a |
comparison
equal
deleted
inserted
replaced
515:e6c8b9b654b2 | 516:0ae5768081aa |
---|---|
186 throw new HgBadArgumentException("Can't find revision", ex).setRevision(nid); | 186 throw new HgBadArgumentException("Can't find revision", ex).setRevision(nid); |
187 } | 187 } |
188 } | 188 } |
189 | 189 |
190 /** | 190 /** |
191 * Visit history of a given file only. | 191 * Visit history of a given file only. Note, unlike native <code>hg log</code> command argument <code>--follow</code>, this method doesn't |
192 * @param file path relative to repository root. Pass <code>null</code> to reset. | 192 * follow file ancestry, but reports complete file history (with <code>followCopyRenames == true</code>, for each |
193 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. | 193 * name of the file known in sequence). To achieve output similar to that of <code>hg log --follow filePath</code>, use |
194 */ | 194 * {@link #file(Path, boolean, boolean) file(filePath, true, true)} alternative. |
195 public HgLogCommand file(Path file, boolean followCopyRename) { | 195 * |
196 // multiple? Bad idea, would need to include extra method into Handler to tell start of next file | 196 * @param filePath path relative to repository root. Pass <code>null</code> to reset. |
197 this.file = file; | 197 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. |
198 followRenames = followAncestry = followCopyRename; | 198 * @return <code>this</code> for convenience |
199 */ | |
200 public HgLogCommand file(Path filePath, boolean followCopyRename) { | |
201 return file(filePath, followCopyRename, false); | |
202 } | |
203 | |
204 /** | |
205 * Full control over file history iteration. | |
206 * | |
207 * @param filePath path relative to repository root. Pass <code>null</code> to reset. | |
208 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. | |
209 * @param followFileAncestry true to follow file history starting from revision at working copy parent. Note, only revisions | |
210 * accessible (i.e. on direct parent line) from the selected one will be reported. This is how <code>hg log --follow filePath</code> | |
211 * behaves, with the difference that this method allows separate control whether to follow renames or not. | |
212 * | |
213 * @return <code>this</code> for convenience | |
214 */ | |
215 public HgLogCommand file(Path filePath, boolean followCopyRename, boolean followFileAncestry) { | |
216 file = filePath; | |
217 followRenames = followCopyRename; | |
218 followAncestry = followFileAncestry; | |
199 return this; | 219 return this; |
200 } | 220 } |
201 | 221 |
202 /** | 222 /** |
203 * Handy analog of {@link #file(Path, boolean)} when clients' paths come from filesystem and need conversion to repository's | 223 * Handy analog to {@link #file(Path, boolean)} when clients' paths come from filesystem and need conversion to repository's |
224 * @return <code>this</code> for convenience | |
204 */ | 225 */ |
205 public HgLogCommand file(String file, boolean followCopyRename) { | 226 public HgLogCommand file(String file, boolean followCopyRename) { |
206 return file(Path.create(repo.getToRepoPathHelper().rewrite(file)), followCopyRename); | 227 return file(Path.create(repo.getToRepoPathHelper().rewrite(file)), followCopyRename); |
228 } | |
229 | |
230 /** | |
231 * Handy analog to {@link #file(Path, boolean, boolean)} when clients' paths come from filesystem and need conversion to repository's | |
232 * @return <code>this</code> for convenience | |
233 */ | |
234 public HgLogCommand file(String file, boolean followCopyRename, boolean followFileAncestry) { | |
235 return file(Path.create(repo.getToRepoPathHelper().rewrite(file)), followCopyRename, followFileAncestry); | |
207 } | 236 } |
208 | 237 |
209 /** | 238 /** |
210 * Similar to {@link #execute(HgChangesetHandler)}, collects and return result as a list. | 239 * Similar to {@link #execute(HgChangesetHandler)}, collects and return result as a list. |
211 * | 240 * |
321 } | 350 } |
322 final ProgressSupport progressHelper = getProgressSupport(handler); | 351 final ProgressSupport progressHelper = getProgressSupport(handler); |
323 final CancelSupport cancelHelper = getCancelSupport(handler, true); | 352 final CancelSupport cancelHelper = getCancelSupport(handler, true); |
324 final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); | 353 final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); |
325 | 354 |
326 // builds tree of nodes according to parents in file's revlog | 355 |
327 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followRenames); | 356 final HandlerDispatcher dispatcher = new HandlerDispatcher() { |
328 // we iterate separate histories of each filename, need to connect | 357 |
329 // last node of historyA with first node of historyB (A renamed to B case) | 358 @Override |
330 // to make overall history smooth. | 359 protected void once(HistoryNode n) throws HgCallbackTargetException, CancelledException { |
331 HistoryNode lastFromPrevIteration = null; | |
332 | |
333 class HandlerDispatcher { | |
334 private final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ | |
335 private 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)); | 360 handler.treeElement(ei.init(n, currentFileNode)); |
359 progress.worked(1); | |
360 cancelHelper.checkCancelled(); | 361 cancelHelper.checkCancelled(); |
361 } | 362 } |
362 | |
363 public void switchTo(HgDataFile df) { | |
364 // from now on, use df in TreeElement | |
365 currentFileNode = df; | |
366 } | |
367 }; | 363 }; |
368 final HandlerDispatcher dispatcher = new HandlerDispatcher(); | |
369 | 364 |
370 // renamed files in the queue are placed with respect to #iterateDirection | 365 // renamed files in the queue are placed with respect to #iterateDirection |
371 // i.e. if we iterate from new to old, recent filenames come first | 366 // i.e. if we iterate from new to old, recent filenames come first |
372 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); | 367 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); |
373 progressHelper.start(4 * fileRenamesQueue.size()); | 368 progressHelper.start(4 * fileRenamesQueue.size()); |
374 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) { | 369 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) { |
375 | 370 |
376 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex); | 371 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex); |
372 dispatcher.prepare(progressHelper, renameInfo); | |
377 cancelHelper.checkCancelled(); | 373 cancelHelper.checkCancelled(); |
378 final List<HistoryNode> changeHistory = treeBuildInspector.go(renameInfo.first(), renameInfo.second()); | 374 if (namesIndex > 0) { |
379 assert changeHistory.size() > 0; | 375 dispatcher.connectWithLastJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex - 1), renameHandler); |
380 progressHelper.worked(1); | |
381 cancelHelper.checkCancelled(); | |
382 dispatcher.prepare(progressHelper, changeHistory.size(), treeBuildInspector); | |
383 if (lastFromPrevIteration != null) { | |
384 if (iterateDirection == IterateDirection.FromOldToNew) { | |
385 // forward, from old to new: | |
386 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) | |
387 // then A(n).bind(B(0)) | |
388 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); // B(0) | |
389 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); // lastFromPrevIteration is A(n) | |
390 dispatcher.once(lastFromPrevIteration); | |
391 if (renameHandler != null) { // shall report renames | |
392 assert namesIndex > 0; | |
393 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // A | |
394 HgFileRevision copiedFrom = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, null); | |
395 HgFileRevision copiedTo = new HgFileRevision(renameInfo.first(), oldestOfTheNextChunk.fileRevision, copiedFrom.getPath()); | |
396 renameHandler.copy(copiedFrom, copiedTo); | |
397 } | |
398 } else { | |
399 assert iterateDirection == IterateDirection.FromNewToOld; | |
400 // A renamed to B. A(0..n) -> B(0..m). | |
401 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)), report B(0), A(n)... | |
402 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) | |
403 newestOfNextChunk.bindChild(lastFromPrevIteration); | |
404 dispatcher.once(lastFromPrevIteration); | |
405 if (renameHandler != null) { | |
406 assert namesIndex > 0; | |
407 // renameInfo points to chunk of name A now, and lastFromPrevIteration (from namesIndex-1) is B | |
408 HgFileRevision copiedFrom = new HgFileRevision(renameInfo.first(), newestOfNextChunk.fileRevision, null); | |
409 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // B | |
410 HgFileRevision copiedTo = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, copiedFrom.getPath()); | |
411 renameHandler.copy(copiedFrom, copiedTo); | |
412 } | |
413 } | |
414 } | 376 } |
415 if (namesIndex + 1 < renamesQueueSize) { | 377 if (namesIndex + 1 < renamesQueueSize) { |
416 // there's at least one more name we are going to look at, save | 378 // there's at least one more name we are going to look at |
417 // one element for later binding | 379 dispatcher.updateJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex+1)); |
418 // | 380 } else { |
419 if (iterateDirection == IterateDirection.FromOldToNew) { | 381 dispatcher.clearJunctionPoint(); |
420 // save newest, and exclude it from this iteration (postpone for next) | 382 } |
421 lastFromPrevIteration = changeHistory.remove(changeHistory.size()-1); | 383 dispatcher.dispatchAllChanges(); |
422 } else { | |
423 assert iterateDirection == IterateDirection.FromNewToOld; | |
424 // save oldest, and exclude it from this iteration (postpone for next) | |
425 lastFromPrevIteration = changeHistory.remove(0); | |
426 } | |
427 } else { | |
428 lastFromPrevIteration = null; // just for the sake of no references to old items | |
429 } | |
430 dispatcher.switchTo(renameInfo.first()); | |
431 // XXX shall sort changeHistory according to changeset numbers? | |
432 Iterator<HistoryNode> it; | |
433 if (iterateDirection == IterateDirection.FromOldToNew) { | |
434 it = changeHistory.listIterator(); | |
435 } else { | |
436 assert iterateDirection == IterateDirection.FromNewToOld; | |
437 it = new ReverseIterator<HistoryNode>(changeHistory); | |
438 } | |
439 while(it.hasNext()) { | |
440 HistoryNode n = it.next(); | |
441 dispatcher.once(n); | |
442 } | |
443 } // for fileRenamesQueue; | 384 } // for fileRenamesQueue; |
444 progressHelper.done(); | 385 progressHelper.done(); |
445 } | 386 } |
446 | 387 |
447 private IterateDirection iterateDirection = IterateDirection.FromOldToNew; | 388 private IterateDirection iterateDirection = IterateDirection.FromOldToNew; |
527 commitRevisions[revisionNumber] = linkedRevision; | 468 commitRevisions[revisionNumber] = linkedRevision; |
528 } | 469 } |
529 | 470 |
530 public void next(int revisionNumber, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) { | 471 public void next(int revisionNumber, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) { |
531 HistoryNode p1 = null, p2 = null; | 472 HistoryNode p1 = null, p2 = null; |
473 // IMPORTANT: method #one(), below, doesn't expect this code expects reasonable values at parent indexes | |
532 if (parent1 != -1) { | 474 if (parent1 != -1) { |
533 p1 = completeHistory[parent1]; | 475 p1 = completeHistory[parent1]; |
534 } | 476 } |
535 if (parent2!= -1) { | 477 if (parent2!= -1) { |
536 p2 = completeHistory[parent2]; | 478 p2 = completeHistory[parent2]; |
537 } | 479 } |
538 completeHistory[revisionNumber] = new HistoryNode(commitRevisions[revisionNumber], revision, p1, p2); | 480 completeHistory[revisionNumber] = new HistoryNode(commitRevisions[revisionNumber], revision, p1, p2); |
481 } | |
482 | |
483 HistoryNode one(HgDataFile fileNode, Nodeid fileRevision) throws HgInvalidControlFileException { | |
484 int fileRevIndexToVisit = fileNode.getRevisionIndex(fileRevision); | |
485 return one(fileNode, fileRevIndexToVisit); | |
486 } | |
487 | |
488 HistoryNode one(HgDataFile fileNode, int fileRevIndexToVisit) throws HgInvalidControlFileException { | |
489 resultHistory = null; | |
490 if (fileRevIndexToVisit == HgRepository.TIP) { | |
491 fileRevIndexToVisit = fileNode.getLastRevision(); | |
492 } | |
493 // still, allocate whole array, for #next to be able to get null parent values | |
494 completeHistory = new HistoryNode[fileRevIndexToVisit+1]; | |
495 commitRevisions = new int[completeHistory.length]; | |
496 fileNode.indexWalk(fileRevIndexToVisit, fileRevIndexToVisit, this); | |
497 // it's only single revision, no need to care about followAncestry | |
498 // but won't hurt to keep resultHistory != null and commitRevisions initialized just in case | |
499 HistoryNode rv = completeHistory[fileRevIndexToVisit]; | |
500 commitRevisions = new int[] { commitRevisions[fileRevIndexToVisit] }; | |
501 completeHistory = null; // no need to keep almost empty array in memory | |
502 resultHistory = Collections.singletonList(rv); | |
503 return rv; | |
539 } | 504 } |
540 | 505 |
541 /** | 506 /** |
542 * Builds history of file changes (in natural order, from oldest to newest) up to (and including) file revision specified. | 507 * Builds history of file changes (in natural order, from oldest to newest) up to (and including) file revision specified. |
543 * If {@link TreeBuildInspector} follows ancestry, only elements that are on the line of ancestry of the revision at | 508 * If {@link TreeBuildInspector} follows ancestry, only elements that are on the line of ancestry of the revision at |
626 } | 591 } |
627 return commitRevisions; | 592 return commitRevisions; |
628 } | 593 } |
629 }; | 594 }; |
630 | 595 |
596 private abstract class HandlerDispatcher { | |
597 private final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ | |
598 // builds tree of nodes according to parents in file's revlog | |
599 private final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followAncestry); | |
600 private List<HistoryNode> changeHistory; | |
601 protected ElementImpl ei = null; | |
602 private ProgressSupport progress; | |
603 protected HgDataFile currentFileNode; | |
604 // node where current file history chunk intersects with same file under other name history | |
605 // either mock of B(0) or A(k), depending on iteration order | |
606 private HistoryNode junctionNode; | |
607 | |
608 // parentProgress shall be initialized with 4 XXX refactor all this stuff with parentProgress | |
609 public void prepare(ProgressSupport parentProgress, Pair<HgDataFile, Nodeid> renameInfo) { | |
610 // if we don't followAncestry, take complete history | |
611 // XXX treeBuildInspector knows followAncestry, perhaps the logic | |
612 // whether to take specific revision or the last one shall be there? | |
613 changeHistory = treeBuildInspector.go(renameInfo.first(), followAncestry ? renameInfo.second() : null); | |
614 assert changeHistory.size() > 0; | |
615 parentProgress.worked(1); | |
616 int historyNodeCount = changeHistory.size(); | |
617 if (ei == null) { | |
618 // when follow is true, changeHistory.size() of the first revision might be quite short | |
619 // (e.g. bad fname recognized soon), hence ensure at least cache size at once | |
620 ei = new ElementImpl(Math.max(CACHE_CSET_IN_ADVANCE_THRESHOLD, historyNodeCount)); | |
621 } | |
622 if (historyNodeCount < CACHE_CSET_IN_ADVANCE_THRESHOLD ) { | |
623 int[] commitRevisions = treeBuildInspector.getCommitRevisions(); | |
624 assert commitRevisions.length == changeHistory.size(); | |
625 // read bunch of changesets at once and cache 'em | |
626 ei.initTransform(); | |
627 repo.getChangelog().range(ei, commitRevisions); | |
628 parentProgress.worked(1); | |
629 progress = new ProgressSupport.Sub(parentProgress, 2); | |
630 } else { | |
631 progress = new ProgressSupport.Sub(parentProgress, 3); | |
632 } | |
633 progress.start(historyNodeCount); | |
634 // switch to present chunk's file node | |
635 switchTo(renameInfo.first()); | |
636 } | |
637 | |
638 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename) { | |
639 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n | |
640 // curRename.second() points to A(k) | |
641 if (iterateDirection == IterateDirection.FromOldToNew) { | |
642 // looking at A chunk (curRename), nextRename points to B | |
643 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) | |
644 HistoryNode junctionDestMock = treeBuildInspector.one(nextRename.first(), 0); // B(0) | |
645 // junstionDestMock is mock object, once we iterate next rename, there'd be different HistoryNode | |
646 // for B's first revision. This means we read it twice, but this seems to be reasonable | |
647 // price for simplicity of the code (and opportunity to follow renames while not following ancestry) | |
648 junctionSrc.bindChild(junctionDestMock); | |
649 // Save mock A(k) 1) not to keep whole A history in memory 2) Don't need it's parent and children once get to B | |
650 // moreover, children of original A(k) (junctionSrc) would list mock B(0) which is undesired once we iterate over real B | |
651 junctionNode = new HistoryNode(junctionSrc.changeset, junctionSrc.fileRevision, null, null); | |
652 } else { | |
653 assert iterateDirection == IterateDirection.FromNewToOld; | |
654 // looking at B chunk (curRename), nextRename points at A | |
655 HistoryNode junctionDest = changeHistory.get(0); // B(0) | |
656 // prepare mock A(k) | |
657 HistoryNode junctionSrcMock = treeBuildInspector.one(nextRename.first(), nextRename.second()); // A(k) | |
658 // B(0) to list A(k) as its parent | |
659 // NOTE, A(k) would be different when we reach A chunk on the next iteration, | |
660 // but we do not care as long as TreeElement needs only parent/child changesets | |
661 // and not other TreeElements; so that it's enough to have mock parent node (just | |
662 // for the sake of parent cset revisions). We have to, indeed, update real A(k), | |
663 // once we get to iteration over A, with B(0) (junctionDest) as one more child. | |
664 junctionSrcMock.bindChild(junctionDest); | |
665 // Save mock B(0), for reasons see above for opposite direction | |
666 junctionNode = new HistoryNode(junctionDest.changeset, junctionDest.fileRevision, null, null); | |
667 } | |
668 } | |
669 | |
670 public void clearJunctionPoint() { | |
671 junctionNode = null; | |
672 } | |
673 | |
674 public void connectWithLastJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> prevRename, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException { | |
675 assert junctionNode != null; | |
676 // A renamed to B. A(0..k..n) -> B(0..m). If followAncestry: k == n | |
677 if (iterateDirection == IterateDirection.FromOldToNew) { | |
678 // forward, from old to new: | |
679 // changeHistory points to B | |
680 // Already reported: A(0)..A(n), A(k) is in junctionNode | |
681 // Shall connect histories: A(k).bind(B(0)) | |
682 HistoryNode junctionDest = changeHistory.get(0); // B(0) | |
683 // junctionNode is A(k) | |
684 junctionNode.bindChild(junctionDest); | |
685 if (renameHandler != null) { // shall report renames | |
686 HgFileRevision copiedFrom = new HgFileRevision(prevRename.first(), junctionNode.fileRevision, null); // "A", A(k) | |
687 HgFileRevision copiedTo = new HgFileRevision(curRename.first(), junctionDest.fileRevision, copiedFrom.getPath()); // "B", B(0) | |
688 renameHandler.copy(copiedFrom, copiedTo); | |
689 } | |
690 } else { | |
691 assert iterateDirection == IterateDirection.FromNewToOld; | |
692 // changeHistory points to A | |
693 // Already reported B(m), B(m-1)...B(0), B(0) is in junctionNode | |
694 // Shall connect histories A(k).bind(B(0)) | |
695 // if followAncestry: A(k) is latest in changeHistory (k == n) | |
696 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) | |
697 junctionSrc.bindChild(junctionNode); | |
698 if (renameHandler != null) { | |
699 HgFileRevision copiedFrom = new HgFileRevision(curRename.first(), junctionSrc.fileRevision, null); // "A", A(k) | |
700 HgFileRevision copiedTo = new HgFileRevision(prevRename.first(), junctionNode.fileRevision, copiedFrom.getPath()); // "B", B(0) | |
701 renameHandler.copy(copiedFrom, copiedTo); | |
702 } | |
703 } | |
704 } | |
705 | |
706 private HistoryNode findJunctionPointInCurrentChunk(Nodeid fileRevision) { | |
707 if (followAncestry) { | |
708 // use the fact we don't go past junction point when followAncestry == true | |
709 HistoryNode rv = changeHistory.get(changeHistory.size() - 1); | |
710 assert rv.fileRevision.equals(fileRevision); | |
711 return rv; | |
712 } | |
713 for (HistoryNode n : changeHistory) { | |
714 if (n.fileRevision.equals(fileRevision)) { | |
715 return n; | |
716 } | |
717 } | |
718 int csetStart = changeHistory.get(0).changeset; | |
719 int csetEnd = changeHistory.get(changeHistory.size() - 1).changeset; | |
720 throw new HgInvalidStateException(String.format("For change history (cset[%d..%d]) could not find node for file change %s", csetStart, csetEnd, fileRevision.shortNotation())); | |
721 } | |
722 | |
723 protected abstract void once(HistoryNode n) throws HgCallbackTargetException, CancelledException; | |
724 | |
725 public void dispatchAllChanges() throws HgCallbackTargetException, CancelledException { | |
726 // XXX shall sort changeHistory according to changeset numbers? | |
727 Iterator<HistoryNode> it; | |
728 if (iterateDirection == IterateDirection.FromOldToNew) { | |
729 it = changeHistory.listIterator(); | |
730 } else { | |
731 assert iterateDirection == IterateDirection.FromNewToOld; | |
732 it = new ReverseIterator<HistoryNode>(changeHistory); | |
733 } | |
734 while(it.hasNext()) { | |
735 HistoryNode n = it.next(); | |
736 once(n); | |
737 progress.worked(1); | |
738 } | |
739 changeHistory = null; | |
740 } | |
741 | |
742 public void switchTo(HgDataFile df) { | |
743 // from now on, use df in TreeElement | |
744 currentFileNode = df; | |
745 } | |
746 } | |
747 | |
631 | 748 |
632 // | 749 // |
633 | 750 |
634 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { | 751 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { |
635 if (limit > 0 && count >= limit) { | 752 if (limit > 0 && count >= limit) { |