comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 528:f7fbf48b9383

Report rename when walking file history regardless of followRenames parameter, solely based on HgFileRenameHandlerMixin presence
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Thu, 17 Jan 2013 19:23:52 +0100
parents 2f9ed6bcefa2
children 6ca3d0c5b4bc
comparison
equal deleted inserted replaced
527:47b7bedf0569 528:f7fbf48b9383
341 } 341 }
342 } 342 }
343 } else { 343 } else {
344 filterInsp.delegateTo(csetTransform); 344 filterInsp.delegateTo(csetTransform);
345 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); 345 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null);
346 List<Pair<HgDataFile, Nodeid>> fileRenames = buildFileRenamesQueue(); 346 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder();
347 progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/); 347 List<Pair<HgDataFile, Nodeid>> fileRenames = frqBuilder.buildFileRenamesQueue();
348 348 progressHelper.start(fileRenames.size());
349 for (int nameIndex = 0, fileRenamesSize = fileRenames.size(); nameIndex < fileRenamesSize; nameIndex++) { 349 for (int nameIndex = 0, fileRenamesSize = fileRenames.size(); nameIndex < fileRenamesSize; nameIndex++) {
350 Pair<HgDataFile, Nodeid> curRename = fileRenames.get(nameIndex); 350 Pair<HgDataFile, Nodeid> curRename = fileRenames.get(nameIndex);
351 HgDataFile fileNode = curRename.first(); 351 HgDataFile fileNode = curRename.first();
352 if (followAncestry) { 352 if (followAncestry) {
353 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry); 353 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry);
386 } 386 }
387 batchInspector.reset(); 387 batchInspector.reset();
388 } 388 }
389 } 389 }
390 } 390 }
391 if (followRenames && withCopyHandler != null && nameIndex + 1 < fileRenamesSize) { 391 if (withCopyHandler != null && nameIndex + 1 < fileRenamesSize) {
392 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1); 392 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1);
393 HgFileRevision src, dst; 393 HgFileRevision src, dst;
394 // A -> B 394 // A -> B
395 if (iterateDirection == HgIterateDirection.OldToNew) { 395 if (iterateDirection == HgIterateDirection.OldToNew) {
396 // curRename: A, nextRename: B 396 // curRename: A, nextRename: B
402 src = new HgFileRevision(nextRename.first(), nextRename.second(), null); 402 src = new HgFileRevision(nextRename.first(), nextRename.second(), null);
403 dst = new HgFileRevision(fileNode, fileNode.getRevision(0), src.getPath()); 403 dst = new HgFileRevision(fileNode, fileNode.getRevision(0), src.getPath());
404 } 404 }
405 withCopyHandler.copy(src, dst); 405 withCopyHandler.copy(src, dst);
406 } 406 }
407 progressHelper.worked(1);
407 } // for renames 408 } // for renames
409 frqBuilder.reportRenameIfNotInQueue(fileRenames, withCopyHandler);
408 } // file != null 410 } // file != null
409 } catch (HgRuntimeException ex) { 411 } catch (HgRuntimeException ex) {
410 throw new HgLibraryFailureException(ex); 412 throw new HgLibraryFailureException(ex);
411 } finally { 413 } finally {
412 csetTransform = null; 414 csetTransform = null;
529 } 531 }
530 }; 532 };
531 533
532 // renamed files in the queue are placed with respect to #iterateDirection 534 // renamed files in the queue are placed with respect to #iterateDirection
533 // i.e. if we iterate from new to old, recent filenames come first 535 // i.e. if we iterate from new to old, recent filenames come first
534 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); 536 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder();
537 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = frqBuilder.buildFileRenamesQueue();
535 // XXX perhaps, makes sense to look at selected file's revision when followAncestry is true 538 // XXX perhaps, makes sense to look at selected file's revision when followAncestry is true
536 // to ensure file we attempt to trace is in the WC's parent. Native hg aborts if not. 539 // to ensure file we attempt to trace is in the WC's parent. Native hg aborts if not.
537 progressHelper.start(4 * fileRenamesQueue.size()); 540 progressHelper.start(4 * fileRenamesQueue.size());
538 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) { 541 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) {
539 542
540 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex); 543 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex);
541 dispatcher.prepare(progressHelper, renameInfo); 544 dispatcher.prepare(progressHelper, renameInfo);
542 cancelHelper.checkCancelled(); 545 cancelHelper.checkCancelled();
543 if (namesIndex > 0) { 546 if (namesIndex > 0) {
544 dispatcher.connectWithLastJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex - 1), renameHandler); 547 dispatcher.connectWithLastJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex - 1));
545 } 548 }
546 if (namesIndex + 1 < renamesQueueSize) { 549 if (namesIndex + 1 < renamesQueueSize) {
547 // there's at least one more name we are going to look at 550 // there's at least one more name we are going to look at
548 dispatcher.updateJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex+1)); 551 dispatcher.updateJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex+1), renameHandler != null);
549 } else { 552 } else {
550 dispatcher.clearJunctionPoint(); 553 dispatcher.clearJunctionPoint();
551 } 554 }
552 dispatcher.dispatchAllChanges(); 555 dispatcher.dispatchAllChanges();
556 if (renameHandler != null && namesIndex + 1 < renamesQueueSize) {
557 dispatcher.reportRenames(renameHandler);
558 }
553 } // for fileRenamesQueue; 559 } // for fileRenamesQueue;
560 frqBuilder.reportRenameIfNotInQueue(fileRenamesQueue, renameHandler);
554 progressHelper.done(); 561 progressHelper.done();
555 } 562 }
556 563
557 private static class ReverseIterator<E> implements Iterator<E> { 564 private static class ReverseIterator<E> implements Iterator<E> {
558 private final ListIterator<E> listIterator; 565 private final ListIterator<E> listIterator;
571 listIterator.remove(); 578 listIterator.remove();
572 } 579 }
573 } 580 }
574 581
575 /** 582 /**
576 * Follows file renames and build a list of all corresponding file nodes and revisions they were 583 * Utility to build sequence of file renames
577 * copied/renamed/branched at (IOW, their latest revision to look at). 584 */
578 * 585 private class FileRenameQueueBuilder {
579 * If {@link #followRenames} is <code>false</code>, the list contains one element only, 586
580 * file node with the name of the file as it was specified by the user. 587 /**
581 * 588 * Follows file renames and build a list of all corresponding file nodes and revisions they were
582 * For the most recent file revision depends on {@link #followAncestry}, and is file revision from working copy parent 589 * copied/renamed/branched at (IOW, their latest revision to look at).
583 * in it's true. <code>null</code> indicates file's TIP revision shall be used. 590 *
584 * 591 * @param followRename when <code>false</code>, the list contains one element only,
585 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair 592 * file node with the name of the file as it was specified by the user.
586 * and possibly reuse this functionality 593 *
587 * 594 * @param followAncestry the most recent file revision reported depends on this parameter,
588 * @return list of file renames, ordered with respect to {@link #iterateDirection} 595 * and it is file revision from working copy parent in there when it's true.
589 */ 596 * <code>null</code> as Pair's second indicates file's TIP revision shall be used.
590 private List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() throws HgPathNotFoundException { 597 *
591 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); 598 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair
592 Nodeid startRev = null; 599 * and possibly reuse this functionality
593 HgDataFile fileNode = repo.getFileNode(file); 600 *
594 if (!fileNode.exists()) { 601 * @return list of file renames, ordered with respect to {@link #iterateDirection}
595 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); 602 */
596 } 603 public List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() throws HgPathNotFoundException {
597 if (followAncestry) { 604 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>();
598 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) 605 Nodeid startRev = null;
599 // or in the HgDataFile (getWorkingCopyOriginRevision) 606 HgDataFile fileNode = repo.getFileNode(file);
600 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); 607 if (!fileNode.exists()) {
601 if (!wdParentChangeset.isNull()) { 608 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file);
602 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); 609 }
603 startRev = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath()); 610 if (followAncestry) {
604 } 611 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex)
605 // else fall-through, assume null (eventually, lastRevision()) is ok here 612 // or in the HgDataFile (getWorkingCopyOriginRevision)
606 } 613 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first();
607 rv.add(new Pair<HgDataFile, Nodeid>(fileNode, startRev)); 614 if (!wdParentChangeset.isNull()) {
608 if (!followRenames) { 615 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset);
616 startRev = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath());
617 }
618 // else fall-through, assume null (eventually, lastRevision()) is ok here
619 }
620 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, startRev);
621 rv.add(p);
622 if (!followRenames) {
623 return rv;
624 }
625 while (hasOrigin(p)) {
626 p = origin(p);
627 if (iterateDirection == HgIterateDirection.OldToNew) {
628 rv.addFirst(p);
629 } else {
630 assert iterateDirection == HgIterateDirection.NewToOld;
631 rv.addLast(p);
632 }
633 };
609 return rv; 634 return rv;
610 } 635 }
611 while (fileNode.isCopy()) { 636
637 public boolean hasOrigin(Pair<HgDataFile, Nodeid> p) {
638 return p.first().isCopy();
639 }
640
641 public Pair<HgDataFile, Nodeid> origin(Pair<HgDataFile, Nodeid> p) {
642 HgDataFile fileNode = p.first();
643 assert fileNode.isCopy();
612 Path fp = fileNode.getCopySourceName(); 644 Path fp = fileNode.getCopySourceName();
613 Nodeid copyRev = fileNode.getCopySourceRevision(); 645 Nodeid copyRev = fileNode.getCopySourceRevision();
614 fileNode = repo.getFileNode(fp); 646 fileNode = repo.getFileNode(fp);
615 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, copyRev); 647 return new Pair<HgDataFile, Nodeid>(fileNode, copyRev);
616 if (iterateDirection == HgIterateDirection.OldToNew) { 648 }
617 rv.addFirst(p); 649
618 } else { 650 /**
619 assert iterateDirection == HgIterateDirection.NewToOld; 651 * Shall report renames based solely on HgFileRenameHandlerMixin presence,
620 rv.addLast(p); 652 * even if queue didn't get rename information due to followRenames == false
621 } 653 *
622 }; 654 * @param queue value from {@link #buildFileRenamesQueue()}
623 return rv; 655 * @param renameHandler may be <code>null</code>
656 */
657 public void reportRenameIfNotInQueue(List<Pair<HgDataFile, Nodeid>> queue, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException {
658 if (renameHandler != null && !followRenames) {
659 // If followRenames is true, all the historical names were in the queue and are processed already.
660 // Hence, shall process origin explicitly only when renameHandler is present but followRenames is not requested.
661 assert queue.size() == 1; // see the way queue is constructed above
662 Pair<HgDataFile, Nodeid> curRename = queue.get(0);
663 if (hasOrigin(curRename)) {
664 Pair<HgDataFile, Nodeid> origin = origin(curRename);
665 HgFileRevision src, dst;
666 src = new HgFileRevision(origin.first(), origin.second(), null);
667 dst = new HgFileRevision(curRename.first(), curRename.first().getRevision(0), src.getPath());
668 renameHandler.copy(src, dst);
669 }
670 }
671 }
624 } 672 }
625 673
626 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { 674 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector {
627 private final boolean followAncestry; 675 private final boolean followAncestry;
628 676
772 private ProgressSupport progress; 820 private ProgressSupport progress;
773 protected HgDataFile currentFileNode; 821 protected HgDataFile currentFileNode;
774 // node where current file history chunk intersects with same file under other name history 822 // node where current file history chunk intersects with same file under other name history
775 // either mock of B(0) or A(k), depending on iteration order 823 // either mock of B(0) or A(k), depending on iteration order
776 private HistoryNode junctionNode; 824 private HistoryNode junctionNode;
825 // initialized when there's HgFileRenameHandlerMixin
826 private HgFileRevision copiedFrom, copiedTo;
777 827
778 // parentProgress shall be initialized with 4 XXX refactor all this stuff with parentProgress 828 // parentProgress shall be initialized with 4 XXX refactor all this stuff with parentProgress
779 public void prepare(ProgressSupport parentProgress, Pair<HgDataFile, Nodeid> renameInfo) { 829 public void prepare(ProgressSupport parentProgress, Pair<HgDataFile, Nodeid> renameInfo) {
780 // if we don't followAncestry, take complete history 830 // if we don't followAncestry, take complete history
781 // XXX treeBuildInspector knows followAncestry, perhaps the logic 831 // XXX treeBuildInspector knows followAncestry, perhaps the logic
802 } 852 }
803 progress.start(historyNodeCount); 853 progress.start(historyNodeCount);
804 // switch to present chunk's file node 854 // switch to present chunk's file node
805 switchTo(renameInfo.first()); 855 switchTo(renameInfo.first());
806 } 856 }
807 857
808 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename) { 858 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename, boolean needCopyFromTo) {
859 copiedFrom = copiedTo = null;
860 //
809 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n 861 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n
810 // curRename.second() points to A(k) 862 // curRename.second() points to A(k)
811 if (iterateDirection == HgIterateDirection.OldToNew) { 863 if (iterateDirection == HgIterateDirection.OldToNew) {
812 // looking at A chunk (curRename), nextRename points to B 864 // looking at A chunk (curRename), nextRename points to B
813 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) 865 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k)
817 // price for simplicity of the code (and opportunity to follow renames while not following ancestry) 869 // price for simplicity of the code (and opportunity to follow renames while not following ancestry)
818 junctionSrc.bindChild(junctionDestMock); 870 junctionSrc.bindChild(junctionDestMock);
819 // 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 871 // 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
820 // moreover, children of original A(k) (junctionSrc) would list mock B(0) which is undesired once we iterate over real B 872 // moreover, children of original A(k) (junctionSrc) would list mock B(0) which is undesired once we iterate over real B
821 junctionNode = new HistoryNode(junctionSrc.changeset, junctionSrc.fileRevision, null, null); 873 junctionNode = new HistoryNode(junctionSrc.changeset, junctionSrc.fileRevision, null, null);
874 if (needCopyFromTo) {
875 copiedFrom = new HgFileRevision(curRename.first(), junctionNode.fileRevision, null); // "A", A(k)
876 copiedTo = new HgFileRevision(nextRename.first(), junctionDestMock.fileRevision, copiedFrom.getPath()); // "B", B(0)
877 }
822 } else { 878 } else {
823 assert iterateDirection == HgIterateDirection.NewToOld; 879 assert iterateDirection == HgIterateDirection.NewToOld;
824 // looking at B chunk (curRename), nextRename points at A 880 // looking at B chunk (curRename), nextRename points at A
825 HistoryNode junctionDest = changeHistory.get(0); // B(0) 881 HistoryNode junctionDest = changeHistory.get(0); // B(0)
826 // prepare mock A(k) 882 // prepare mock A(k)
832 // for the sake of parent cset revisions). We have to, indeed, update real A(k), 888 // for the sake of parent cset revisions). We have to, indeed, update real A(k),
833 // once we get to iteration over A, with B(0) (junctionDest) as one more child. 889 // once we get to iteration over A, with B(0) (junctionDest) as one more child.
834 junctionSrcMock.bindChild(junctionDest); 890 junctionSrcMock.bindChild(junctionDest);
835 // Save mock B(0), for reasons see above for opposite direction 891 // Save mock B(0), for reasons see above for opposite direction
836 junctionNode = new HistoryNode(junctionDest.changeset, junctionDest.fileRevision, null, null); 892 junctionNode = new HistoryNode(junctionDest.changeset, junctionDest.fileRevision, null, null);
893 if (needCopyFromTo) {
894 copiedFrom = new HgFileRevision(nextRename.first(), junctionSrcMock.fileRevision, null); // "A", A(k)
895 copiedTo = new HgFileRevision(curRename.first(), junctionNode.fileRevision, copiedFrom.getPath()); // "B", B(0)
896 }
897 }
898 }
899
900 public void reportRenames(HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException {
901 if (renameHandler != null) { // shall report renames
902 assert copiedFrom != null;
903 assert copiedTo != null;
904 renameHandler.copy(copiedFrom, copiedTo);
837 } 905 }
838 } 906 }
839 907
840 public void clearJunctionPoint() { 908 public void clearJunctionPoint() {
841 junctionNode = null; 909 junctionNode = null;
842 } 910 copiedFrom = copiedTo = null;
843 911 }
844 public void connectWithLastJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> prevRename, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException { 912
913 /**
914 * Replace mock src/dest HistoryNode connected to junctionNode with a real one
915 */
916 public void connectWithLastJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> prevRename) {
845 assert junctionNode != null; 917 assert junctionNode != null;
846 // A renamed to B. A(0..k..n) -> B(0..m). If followAncestry: k == n 918 // A renamed to B. A(0..k..n) -> B(0..m). If followAncestry: k == n
847 if (iterateDirection == HgIterateDirection.OldToNew) { 919 if (iterateDirection == HgIterateDirection.OldToNew) {
848 // forward, from old to new: 920 // forward, from old to new:
849 // changeHistory points to B 921 // changeHistory points to B
850 // Already reported: A(0)..A(n), A(k) is in junctionNode 922 // Already reported: A(0)..A(n), A(k) is in junctionNode
851 // Shall connect histories: A(k).bind(B(0)) 923 // Shall connect histories: A(k).bind(B(0))
852 HistoryNode junctionDest = changeHistory.get(0); // B(0) 924 HistoryNode junctionDest = changeHistory.get(0); // B(0)
853 // junctionNode is A(k) 925 // junctionNode is A(k)
854 junctionNode.bindChild(junctionDest); 926 junctionNode.bindChild(junctionDest);
855 if (renameHandler != null) { // shall report renames
856 HgFileRevision copiedFrom = new HgFileRevision(prevRename.first(), junctionNode.fileRevision, null); // "A", A(k)
857 HgFileRevision copiedTo = new HgFileRevision(curRename.first(), junctionDest.fileRevision, copiedFrom.getPath()); // "B", B(0)
858 renameHandler.copy(copiedFrom, copiedTo);
859 }
860 } else { 927 } else {
861 assert iterateDirection == HgIterateDirection.NewToOld; 928 assert iterateDirection == HgIterateDirection.NewToOld;
862 // changeHistory points to A 929 // changeHistory points to A
863 // Already reported B(m), B(m-1)...B(0), B(0) is in junctionNode 930 // Already reported B(m), B(m-1)...B(0), B(0) is in junctionNode
864 // Shall connect histories A(k).bind(B(0)) 931 // Shall connect histories A(k).bind(B(0))
865 // if followAncestry: A(k) is latest in changeHistory (k == n) 932 // if followAncestry: A(k) is latest in changeHistory (k == n)
866 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) 933 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k)
867 junctionSrc.bindChild(junctionNode); 934 junctionSrc.bindChild(junctionNode);
868 if (renameHandler != null) {
869 HgFileRevision copiedFrom = new HgFileRevision(curRename.first(), junctionSrc.fileRevision, null); // "A", A(k)
870 HgFileRevision copiedTo = new HgFileRevision(prevRename.first(), junctionNode.fileRevision, copiedFrom.getPath()); // "B", B(0)
871 renameHandler.copy(copiedFrom, copiedTo);
872 }
873 } 935 }
874 } 936 }
875 937
876 private HistoryNode findJunctionPointInCurrentChunk(Nodeid fileRevision) { 938 private HistoryNode findJunctionPointInCurrentChunk(Nodeid fileRevision) {
877 if (followAncestry) { 939 if (followAncestry) {