comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 692:e970b333f284

Refactor HgLogCommand to utilize correct file.isCopy(int)
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Sat, 03 Aug 2013 17:11:33 +0200
parents 98ff1fb49abe
children
comparison
equal deleted inserted replaced
691:72fc7774b87e 692:e970b333f284
34 import java.util.TreeSet; 34 import java.util.TreeSet;
35 35
36 import org.tmatesoft.hg.internal.AdapterPlug; 36 import org.tmatesoft.hg.internal.AdapterPlug;
37 import org.tmatesoft.hg.internal.BatchRangeHelper; 37 import org.tmatesoft.hg.internal.BatchRangeHelper;
38 import org.tmatesoft.hg.internal.CsetParamKeeper; 38 import org.tmatesoft.hg.internal.CsetParamKeeper;
39 import org.tmatesoft.hg.internal.FileRenameHistory;
40 import org.tmatesoft.hg.internal.FileRenameHistory.Chunk;
39 import org.tmatesoft.hg.internal.IntMap; 41 import org.tmatesoft.hg.internal.IntMap;
40 import org.tmatesoft.hg.internal.IntVector; 42 import org.tmatesoft.hg.internal.IntVector;
41 import org.tmatesoft.hg.internal.Internals; 43 import org.tmatesoft.hg.internal.Internals;
42 import org.tmatesoft.hg.internal.Lifecycle; 44 import org.tmatesoft.hg.internal.Lifecycle;
43 import org.tmatesoft.hg.internal.LifecycleProxy; 45 import org.tmatesoft.hg.internal.LifecycleProxy;
327 final ProgressSupport progressHelper = getProgressSupport(handler); 329 final ProgressSupport progressHelper = getProgressSupport(handler);
328 try { 330 try {
329 if (repo.getChangelog().getRevisionCount() == 0) { 331 if (repo.getChangelog().getRevisionCount() == 0) {
330 return; 332 return;
331 } 333 }
334 final int firstCset = startRev;
332 final int lastCset = endRev == TIP ? repo.getChangelog().getLastRevision() : endRev; 335 final int lastCset = endRev == TIP ? repo.getChangelog().getLastRevision() : endRev;
333 // XXX pretty much like HgInternals.checkRevlogRange 336 // XXX pretty much like HgInternals.checkRevlogRange
334 if (lastCset < 0 || lastCset > repo.getChangelog().getLastRevision()) { 337 if (lastCset < 0 || lastCset > repo.getChangelog().getLastRevision()) {
335 throw new HgBadArgumentException(String.format("Bad value %d for end revision", lastCset), null); 338 throw new HgBadArgumentException(String.format("Bad value %d for end revision", lastCset), null);
336 } 339 }
337 if (startRev < 0 || startRev > lastCset) { 340 if (firstCset < 0 || firstCset > lastCset) {
338 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", startRev, lastCset), null); 341 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", firstCset, lastCset), null);
339 } 342 }
340 final int BATCH_SIZE = 100; 343 final int BATCH_SIZE = 100;
341 count = 0; 344 count = 0;
342 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo 345 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo
343 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above 346 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above
345 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); 348 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true));
346 // FilteringInspector is responsible to check command arguments: users, branches, limit, etc. 349 // FilteringInspector is responsible to check command arguments: users, branches, limit, etc.
347 // prior to passing cset to next Inspector, which is either (a) collector to reverse cset order, then invokes 350 // prior to passing cset to next Inspector, which is either (a) collector to reverse cset order, then invokes
348 // transformer from (b), below, with alternative cset order or (b) transformer to hi-level csets. 351 // transformer from (b), below, with alternative cset order or (b) transformer to hi-level csets.
349 FilteringInspector filterInsp = new FilteringInspector(); 352 FilteringInspector filterInsp = new FilteringInspector();
350 filterInsp.changesets(startRev, lastCset); 353 filterInsp.changesets(firstCset, lastCset);
351 if (file == null) { 354 if (file == null) {
352 progressHelper.start(lastCset - startRev + 1); 355 progressHelper.start(lastCset - firstCset + 1);
353 if (iterateDirection == HgIterateDirection.OldToNew) { 356 if (iterateDirection == HgIterateDirection.OldToNew) {
354 filterInsp.delegateTo(csetTransform); 357 filterInsp.delegateTo(csetTransform);
355 repo.getChangelog().range(startRev, lastCset, filterInsp); 358 repo.getChangelog().range(firstCset, lastCset, filterInsp);
356 csetTransform.checkFailure(); 359 csetTransform.checkFailure();
357 } else { 360 } else {
358 assert iterateDirection == HgIterateDirection.NewToOld; 361 assert iterateDirection == HgIterateDirection.NewToOld;
359 BatchRangeHelper brh = new BatchRangeHelper(startRev, lastCset, BATCH_SIZE, true); 362 BatchRangeHelper brh = new BatchRangeHelper(firstCset, lastCset, BATCH_SIZE, true);
360 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(lastCset-startRev+1, BATCH_SIZE)); 363 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(lastCset-firstCset+1, BATCH_SIZE));
361 filterInsp.delegateTo(batchInspector); 364 filterInsp.delegateTo(batchInspector);
365 // XXX this batching code is bit verbose, refactor
362 while (brh.hasNext()) { 366 while (brh.hasNext()) {
363 brh.next(); 367 brh.next();
364 repo.getChangelog().range(brh.start(), brh.end(), filterInsp); 368 repo.getChangelog().range(brh.start(), brh.end(), filterInsp);
365 for (BatchChangesetInspector.BatchRecord br : batchInspector.iterate(true)) { 369 for (BatchChangesetInspector.BatchRecord br : batchInspector.iterate(true)) {
366 csetTransform.next(br.csetIndex, br.csetRevision, br.cset); 370 csetTransform.next(br.csetIndex, br.csetRevision, br.cset);
371 } 375 }
372 } else { 376 } else {
373 filterInsp.delegateTo(csetTransform); 377 filterInsp.delegateTo(csetTransform);
374 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); 378 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null);
375 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder(); 379 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder();
376 List<Pair<HgDataFile, Nodeid>> fileRenames = frqBuilder.buildFileRenamesQueue(); 380 List<QueueElement> fileRenames = frqBuilder.buildFileRenamesQueue(firstCset, lastCset);
377 progressHelper.start(fileRenames.size()); 381 progressHelper.start(fileRenames.size());
378 for (int nameIndex = 0, fileRenamesSize = fileRenames.size(); nameIndex < fileRenamesSize; nameIndex++) { 382 for (int nameIndex = 0, fileRenamesSize = fileRenames.size(); nameIndex < fileRenamesSize; nameIndex++) {
379 Pair<HgDataFile, Nodeid> curRename = fileRenames.get(nameIndex); 383 QueueElement curRename = fileRenames.get(nameIndex);
380 HgDataFile fileNode = curRename.first(); 384 HgDataFile fileNode = curRename.file();
381 if (followAncestry) { 385 if (followAncestry) {
382 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry); 386 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry);
383 @SuppressWarnings("unused") 387 @SuppressWarnings("unused")
384 List<HistoryNode> fileAncestry = treeBuilder.go(fileNode, curRename.second()); 388 List<HistoryNode> fileAncestry = treeBuilder.go(curRename);
385 int[] commitRevisions = narrowChangesetRange(treeBuilder.getCommitRevisions(), startRev, lastCset); 389 int[] commitRevisions = narrowChangesetRange(treeBuilder.getCommitRevisions(), firstCset, lastCset);
386 if (iterateDirection == HgIterateDirection.OldToNew) { 390 if (iterateDirection == HgIterateDirection.OldToNew) {
387 repo.getChangelog().range(filterInsp, commitRevisions); 391 repo.getChangelog().range(filterInsp, commitRevisions);
388 csetTransform.checkFailure(); 392 csetTransform.checkFailure();
389 } else { 393 } else {
390 assert iterateDirection == HgIterateDirection.NewToOld; 394 assert iterateDirection == HgIterateDirection.NewToOld;
394 repo.getChangelog().range(csetWithFileChange, csetWithFileChange, filterInsp); 398 repo.getChangelog().range(csetWithFileChange, csetWithFileChange, filterInsp);
395 } 399 }
396 } 400 }
397 } else { 401 } else {
398 // report complete file history (XXX may narrow range with [startRev, endRev], but need to go from file rev to link rev) 402 // report complete file history (XXX may narrow range with [startRev, endRev], but need to go from file rev to link rev)
399 int fileStartRev = 0; //fileNode.getChangesetRevisionIndex(0) >= startRev 403 int fileStartRev = curRename.fileFrom();
400 int fileEndRev = fileNode.getLastRevision(); 404 int fileEndRev = curRename.file().getLastRevision(); //curRename.fileTo();
401 if (iterateDirection == HgIterateDirection.OldToNew) { 405 if (iterateDirection == HgIterateDirection.OldToNew) {
402 fileNode.history(fileStartRev, fileEndRev, filterInsp); 406 fileNode.history(fileStartRev, fileEndRev, filterInsp);
403 csetTransform.checkFailure(); 407 csetTransform.checkFailure();
404 } else { 408 } else {
405 assert iterateDirection == HgIterateDirection.NewToOld; 409 assert iterateDirection == HgIterateDirection.NewToOld;
416 batchInspector.reset(); 420 batchInspector.reset();
417 } 421 }
418 } 422 }
419 } 423 }
420 if (withCopyHandler != null && nameIndex + 1 < fileRenamesSize) { 424 if (withCopyHandler != null && nameIndex + 1 < fileRenamesSize) {
421 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1); 425 QueueElement nextRename = fileRenames.get(nameIndex+1);
422 HgFileRevision src, dst; 426 HgFileRevision src, dst;
423 // A -> B 427 // A -> B
424 if (iterateDirection == HgIterateDirection.OldToNew) { 428 if (iterateDirection == HgIterateDirection.OldToNew) {
425 // curRename: A, nextRename: B 429 // curRename: A, nextRename: B
426 src = new HgFileRevision(fileNode, curRename.second(), null); 430 src = curRename.last();
427 dst = new HgFileRevision(nextRename.first(), nextRename.first().getRevision(0), src.getPath()); 431 dst = nextRename.first(src);
428 } else { 432 } else {
429 assert iterateDirection == HgIterateDirection.NewToOld; 433 assert iterateDirection == HgIterateDirection.NewToOld;
430 // curRename: B, nextRename: A 434 // curRename: B, nextRename: A
431 src = new HgFileRevision(nextRename.first(), nextRename.second(), null); 435 src = nextRename.last();
432 dst = new HgFileRevision(fileNode, fileNode.getRevision(0), src.getPath()); 436 dst = curRename.first(src);
433 } 437 }
434 withCopyHandler.copy(src, dst); 438 withCopyHandler.copy(src, dst);
435 } 439 }
436 progressHelper.worked(1); 440 progressHelper.worked(1);
437 } // for renames 441 } // for renames
538 throw new ConcurrentModificationException(); 542 throw new ConcurrentModificationException();
539 } 543 }
540 if (file == null) { 544 if (file == null) {
541 throw new IllegalArgumentException("History tree is supported for files only (at least now), please specify file"); 545 throw new IllegalArgumentException("History tree is supported for files only (at least now), please specify file");
542 } 546 }
547 final int firstCset = startRev;
548 final int lastCset = endRev == TIP ? repo.getChangelog().getLastRevision() : endRev;
549 // XXX pretty much like HgInternals.checkRevlogRange
550 if (lastCset < 0 || lastCset > repo.getChangelog().getLastRevision()) {
551 throw new HgBadArgumentException(String.format("Bad value %d for end revision", lastCset), null);
552 }
553 if (firstCset < 0 || startRev > lastCset) {
554 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", startRev, lastCset), null);
555 }
543 final ProgressSupport progressHelper = getProgressSupport(handler); 556 final ProgressSupport progressHelper = getProgressSupport(handler);
544 final CancelSupport cancelHelper = getCancelSupport(handler, true); 557 final CancelSupport cancelHelper = getCancelSupport(handler, true);
545 final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); 558 final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null);
546 559
547 try { 560 try {
557 }; 570 };
558 571
559 // renamed files in the queue are placed with respect to #iterateDirection 572 // renamed files in the queue are placed with respect to #iterateDirection
560 // i.e. if we iterate from new to old, recent filenames come first 573 // i.e. if we iterate from new to old, recent filenames come first
561 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder(); 574 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder();
562 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = frqBuilder.buildFileRenamesQueue(); 575 List<QueueElement> fileRenamesQueue = frqBuilder.buildFileRenamesQueue(firstCset, lastCset);
563 // XXX perhaps, makes sense to look at selected file's revision when followAncestry is true 576 // XXX perhaps, makes sense to look at selected file's revision when followAncestry is true
564 // to ensure file we attempt to trace is in the WC's parent. Native hg aborts if not. 577 // to ensure file we attempt to trace is in the WC's parent. Native hg aborts if not.
565 progressHelper.start(4 * fileRenamesQueue.size()); 578 progressHelper.start(4 * fileRenamesQueue.size());
566 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) { 579 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) {
567 580
568 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex); 581 final QueueElement renameInfo = fileRenamesQueue.get(namesIndex);
569 dispatcher.prepare(progressHelper, renameInfo); 582 dispatcher.prepare(progressHelper, renameInfo);
570 cancelHelper.checkCancelled(); 583 cancelHelper.checkCancelled();
571 if (namesIndex > 0) { 584 if (namesIndex > 0) {
572 dispatcher.connectWithLastJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex - 1)); 585 dispatcher.connectWithLastJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex - 1));
573 } 586 }
585 frqBuilder.reportRenameIfNotInQueue(fileRenamesQueue, renameHandler); 598 frqBuilder.reportRenameIfNotInQueue(fileRenamesQueue, renameHandler);
586 } catch (HgRuntimeException ex) { 599 } catch (HgRuntimeException ex) {
587 throw new HgLibraryFailureException(ex); 600 throw new HgLibraryFailureException(ex);
588 } 601 }
589 progressHelper.done(); 602 progressHelper.done();
603 }
604
605 private static class QueueElement {
606 private final HgDataFile df;
607 private final Nodeid lastRev;
608 private final int firstRevIndex, lastRevIndex;
609
610 QueueElement(HgDataFile file, Nodeid fileLastRev) {
611 df = file;
612 lastRev = fileLastRev;
613 firstRevIndex = 0;
614 lastRevIndex = lastRev == null ? df.getLastRevision() : df.getRevisionIndex(lastRev);
615 }
616 QueueElement(HgDataFile file, int firstFileRev, int lastFileRev) {
617 df = file;
618 firstRevIndex = firstFileRev;
619 lastRevIndex = lastFileRev;
620 lastRev = null;
621 }
622 HgDataFile file() {
623 return df;
624 }
625 int fileFrom() {
626 return firstRevIndex;
627 }
628 int fileTo() {
629 return lastRevIndex;
630 }
631 // never null
632 Nodeid lastFileRev() {
633 return lastRev == null ? df.getRevision(fileTo()) : lastRev;
634 }
635 HgFileRevision last() {
636 return new HgFileRevision(df, lastFileRev(), null);
637 }
638 HgFileRevision first(HgFileRevision from) {
639 return new HgFileRevision(df, df.getRevision(0), from.getPath());
640 }
590 } 641 }
591 642
592 /** 643 /**
593 * Utility to build sequence of file renames 644 * Utility to build sequence of file renames
594 */ 645 */
609 * and possibly reuse this functionality 660 * and possibly reuse this functionality
610 * 661 *
611 * @return list of file renames, ordered with respect to {@link #iterateDirection} 662 * @return list of file renames, ordered with respect to {@link #iterateDirection}
612 * @throws HgRuntimeException 663 * @throws HgRuntimeException
613 */ 664 */
614 public List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() throws HgPathNotFoundException, HgRuntimeException { 665 public List<QueueElement> buildFileRenamesQueue(int csetStart, int csetEnd) throws HgPathNotFoundException, HgRuntimeException {
615 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); 666 LinkedList<QueueElement> rv = new LinkedList<QueueElement>();
616 Nodeid startRev = null; 667 Nodeid startRev = null;
617 HgDataFile fileNode = repo.getFileNode(file); 668 HgDataFile fileNode = repo.getFileNode(file);
618 if (!fileNode.exists()) { 669 if (!fileNode.exists()) {
619 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); 670 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file);
620 } 671 }
626 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); 677 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset);
627 startRev = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath()); 678 startRev = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath());
628 } 679 }
629 // else fall-through, assume null (eventually, lastRevision()) is ok here 680 // else fall-through, assume null (eventually, lastRevision()) is ok here
630 } 681 }
631 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, startRev); 682 QueueElement p = new QueueElement(fileNode, startRev);
632 rv.add(p);
633 if (!followRenames) { 683 if (!followRenames) {
684 rv.add(p);
634 return rv; 685 return rv;
635 } 686 }
636 while (hasOrigin(p)) { 687 FileRenameHistory frh = new FileRenameHistory(csetStart, csetEnd);
637 p = origin(p); 688 frh.build(fileNode, p.fileTo());
638 if (iterateDirection == HgIterateDirection.OldToNew) { 689 for (Chunk c : frh.iterate(iterateDirection)) {
639 rv.addFirst(p); 690 rv.add(new QueueElement(c.file(), c.firstFileRev(), c.lastFileRev()));
640 } else { 691 }
641 assert iterateDirection == HgIterateDirection.NewToOld;
642 rv.addLast(p);
643 }
644 };
645 return rv; 692 return rv;
646 }
647
648 public boolean hasOrigin(Pair<HgDataFile, Nodeid> p) throws HgRuntimeException {
649 return p.first().isCopy();
650 }
651
652 public Pair<HgDataFile, Nodeid> origin(Pair<HgDataFile, Nodeid> p) throws HgRuntimeException {
653 HgDataFile fileNode = p.first();
654 assert fileNode.isCopy();
655 Path fp = fileNode.getCopySourceName();
656 Nodeid copyRev = fileNode.getCopySourceRevision();
657 fileNode = repo.getFileNode(fp);
658 return new Pair<HgDataFile, Nodeid>(fileNode, copyRev);
659 } 693 }
660 694
661 /** 695 /**
662 * Shall report renames based solely on HgFileRenameHandlerMixin presence, 696 * Shall report renames based solely on HgFileRenameHandlerMixin presence,
663 * even if queue didn't get rename information due to followRenames == false 697 * even if queue didn't get rename information due to followRenames == false
664 * 698 *
665 * @param queue value from {@link #buildFileRenamesQueue()} 699 * @param queue value from {@link #buildFileRenamesQueue()}
666 * @param renameHandler may be <code>null</code> 700 * @param renameHandler may be <code>null</code>
667 */ 701 */
668 public void reportRenameIfNotInQueue(List<Pair<HgDataFile, Nodeid>> queue, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException, HgRuntimeException { 702 public void reportRenameIfNotInQueue(List<QueueElement> queue, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException, HgRuntimeException {
669 if (renameHandler != null && !followRenames) { 703 if (renameHandler != null && !followRenames) {
670 // If followRenames is true, all the historical names were in the queue and are processed already. 704 // If followRenames is true, all the historical names were in the queue and are processed already.
671 // Hence, shall process origin explicitly only when renameHandler is present but followRenames is not requested. 705 // Hence, shall process origin explicitly only when renameHandler is present but followRenames is not requested.
672 assert queue.size() == 1; // see the way queue is constructed above 706 assert queue.size() == 1; // see the way queue is constructed above
673 Pair<HgDataFile, Nodeid> curRename = queue.get(0); 707 QueueElement curRename = queue.get(0);
674 if (hasOrigin(curRename)) { 708 if (curRename.file().isCopy(curRename.fileFrom())) {
675 Pair<HgDataFile, Nodeid> origin = origin(curRename); 709 final HgFileRevision src = curRename.file().getCopySource(curRename.fileFrom());
676 HgFileRevision src, dst; 710 HgFileRevision dst = curRename.first(src);
677 src = new HgFileRevision(origin.first(), origin.second(), null);
678 dst = new HgFileRevision(curRename.first(), curRename.first().getRevision(0), src.getPath());
679 renameHandler.copy(src, dst); 711 renameHandler.copy(src, dst);
680 } 712 }
681 } 713 }
682 } 714 }
683 } 715 }
684 716
717 /**
718 * Builds list of {@link HistoryNode HistoryNodes} to visit for a given chunk of file rename history
719 */
685 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { 720 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector {
686 private final boolean followAncestry; 721 private final boolean followAncestry;
687 722
688 private HistoryNode[] completeHistory; 723 private HistoryNode[] completeHistory;
689 private int[] commitRevisions; 724 private int[] commitRevisions;
731 resultHistory = Collections.singletonList(rv); 766 resultHistory = Collections.singletonList(rv);
732 return rv; 767 return rv;
733 } 768 }
734 769
735 /** 770 /**
771 * FIXME pretty much the same as FileRevisionHistoryChunk
772 *
736 * Builds history of file changes (in natural order, from oldest to newest) up to (and including) file revision specified. 773 * Builds history of file changes (in natural order, from oldest to newest) up to (and including) file revision specified.
737 * If {@link TreeBuildInspector} follows ancestry, only elements that are on the line of ancestry of the revision at 774 * If {@link TreeBuildInspector} follows ancestry, only elements that are on the line of ancestry of the revision at
738 * lastRevisionIndex would be included. 775 * lastRevisionIndex would be included.
739 * 776 *
740 * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list 777 * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list
741 * is modifiable (to further augment with last/first elements of renamed file histories) 778 * is modifiable (to further augment with last/first elements of renamed file histories)
742 */ 779 */
743 List<HistoryNode> go(HgDataFile fileNode, Nodeid fileLastRevisionToVisit) throws HgRuntimeException { 780 List<HistoryNode> go(QueueElement qe) throws HgRuntimeException {
744 resultHistory = null; 781 resultHistory = null;
745 int fileLastRevIndexToVisit = fileLastRevisionToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevisionToVisit); 782 HgDataFile fileNode = qe.file();
783 // TODO int fileLastRevIndexToVisit = qe.fileTo
784 int fileLastRevIndexToVisit = followAncestry ? fileNode.getRevisionIndex(qe.lastFileRev()) : fileNode.getLastRevision();
746 completeHistory = new HistoryNode[fileLastRevIndexToVisit+1]; 785 completeHistory = new HistoryNode[fileLastRevIndexToVisit+1];
747 commitRevisions = new int[completeHistory.length]; 786 commitRevisions = new int[completeHistory.length];
748 fileNode.indexWalk(0, fileLastRevIndexToVisit, this); 787 fileNode.indexWalk(qe.fileFrom(), fileLastRevIndexToVisit, this);
749 if (!followAncestry) { 788 if (!followAncestry) {
750 // in case when ancestor not followed, it's safe to return unmodifiable list 789 resultHistory = new ArrayList<HistoryNode>(fileLastRevIndexToVisit - qe.fileFrom() + 1);
751 resultHistory = Arrays.asList(completeHistory); 790 // items in completeHistory with index < qe.fileFrom are empty
791 for (int i = qe.fileFrom(); i <= fileLastRevIndexToVisit; i++) {
792 resultHistory.add(completeHistory[i]);
793 }
752 completeHistory = null; 794 completeHistory = null;
753 // keep commitRevisions initialized, no need to recalculate them 795 commitRevisions = null;
754 // as they correspond 1:1 to resultHistory
755 return resultHistory; 796 return resultHistory;
756 } 797 }
757 /* 798 /*
758 * Changesets, newest at the top: 799 * Changesets, newest at the top:
759 * o <-- cset from working dir parent (as in dirstate), file not changed (file revision recorded points to that from A) 800 * o <-- cset from working dir parent (as in dirstate), file not changed (file revision recorded points to that from A)
799 return o1.changeset - o2.changeset; 840 return o1.changeset - o2.changeset;
800 } 841 }
801 }); 842 });
802 completeHistory = null; 843 completeHistory = null;
803 commitRevisions = null; 844 commitRevisions = null;
804 // collected values are no longer valid - shall
805 // strip off elements for missing HistoryNodes, but it's easier just to re-create the array
806 // from resultHistory later, once (and if) needed
807 return resultHistory = strippedHistoryList; 845 return resultHistory = strippedHistoryList;
808 } 846 }
809 847
810 /** 848 /**
811 * handy access to all HistoryNode[i].changeset values 849 * handy access to all HistoryNode[i].changeset values
820 } 858 }
821 return commitRevisions; 859 return commitRevisions;
822 } 860 }
823 }; 861 };
824 862
863 /**
864 * Sends {@link ElementImpl} for each {@link HistoryNode}, and keeps track of junction points - revisions with renames
865 */
825 private abstract class HandlerDispatcher { 866 private abstract class HandlerDispatcher {
826 private final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ 867 private final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */
827 // builds tree of nodes according to parents in file's revlog 868 // builds tree of nodes according to parents in file's revlog
828 private final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followAncestry); 869 private final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followAncestry);
829 private List<HistoryNode> changeHistory; 870 private List<HistoryNode> changeHistory;
835 private HistoryNode junctionNode; 876 private HistoryNode junctionNode;
836 // initialized when there's HgFileRenameHandlerMixin 877 // initialized when there's HgFileRenameHandlerMixin
837 private HgFileRevision copiedFrom, copiedTo; 878 private HgFileRevision copiedFrom, copiedTo;
838 879
839 // parentProgress shall be initialized with 4 XXX refactor all this stuff with parentProgress 880 // parentProgress shall be initialized with 4 XXX refactor all this stuff with parentProgress
840 public void prepare(ProgressSupport parentProgress, Pair<HgDataFile, Nodeid> renameInfo) throws HgRuntimeException { 881 public void prepare(ProgressSupport parentProgress, QueueElement renameInfo) throws HgRuntimeException {
841 // if we don't followAncestry, take complete history 882 changeHistory = treeBuildInspector.go(renameInfo);
842 // XXX treeBuildInspector knows followAncestry, perhaps the logic
843 // whether to take specific revision or the last one shall be there?
844 changeHistory = treeBuildInspector.go(renameInfo.first(), followAncestry ? renameInfo.second() : null);
845 assert changeHistory.size() > 0; 883 assert changeHistory.size() > 0;
846 parentProgress.worked(1); 884 parentProgress.worked(1);
847 int historyNodeCount = changeHistory.size(); 885 int historyNodeCount = changeHistory.size();
848 if (ei == null) { 886 if (ei == null) {
849 // when follow is true, changeHistory.size() of the first revision might be quite short 887 // when follow is true, changeHistory.size() of the first revision might be quite short
861 } else { 899 } else {
862 progress = new ProgressSupport.Sub(parentProgress, 3); 900 progress = new ProgressSupport.Sub(parentProgress, 3);
863 } 901 }
864 progress.start(historyNodeCount); 902 progress.start(historyNodeCount);
865 // switch to present chunk's file node 903 // switch to present chunk's file node
866 switchTo(renameInfo.first()); 904 switchTo(renameInfo.file());
867 } 905 }
868 906
869 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename, boolean needCopyFromTo) throws HgRuntimeException { 907 public void updateJunctionPoint(QueueElement curRename, QueueElement nextRename, boolean needCopyFromTo) throws HgRuntimeException {
870 copiedFrom = copiedTo = null; 908 copiedFrom = copiedTo = null;
871 // 909 //
872 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n 910 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n
873 // curRename.second() points to A(k) 911 // curRename.second() points to A(k)
874 if (iterateDirection == HgIterateDirection.OldToNew) { 912 if (iterateDirection == HgIterateDirection.OldToNew) {
875 // looking at A chunk (curRename), nextRename points to B 913 // looking at A chunk (curRename), nextRename points to B
876 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) 914 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.lastFileRev()); // A(k)
877 HistoryNode junctionDestMock = treeBuildInspector.one(nextRename.first(), 0); // B(0) 915 HistoryNode junctionDestMock = treeBuildInspector.one(nextRename.file(), 0); // B(0)
878 // junstionDestMock is mock object, once we iterate next rename, there'd be different HistoryNode 916 // junstionDestMock is mock object, once we iterate next rename, there'd be different HistoryNode
879 // for B's first revision. This means we read it twice, but this seems to be reasonable 917 // for B's first revision. This means we read it twice, but this seems to be reasonable
880 // price for simplicity of the code (and opportunity to follow renames while not following ancestry) 918 // price for simplicity of the code (and opportunity to follow renames while not following ancestry)
881 junctionSrc.bindChild(junctionDestMock); 919 junctionSrc.bindChild(junctionDestMock);
882 // 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 920 // 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
883 // moreover, children of original A(k) (junctionSrc) would list mock B(0) which is undesired once we iterate over real B 921 // moreover, children of original A(k) (junctionSrc) would list mock B(0) which is undesired once we iterate over real B
884 junctionNode = new HistoryNode(junctionSrc.changeset, junctionSrc.fileRevision, null, null); 922 junctionNode = new HistoryNode(junctionSrc.changeset, junctionSrc.fileRevision, null, null);
885 if (needCopyFromTo) { 923 if (needCopyFromTo) {
886 copiedFrom = new HgFileRevision(curRename.first(), junctionNode.fileRevision, null); // "A", A(k) 924 copiedFrom = new HgFileRevision(curRename.file(), junctionNode.fileRevision, null); // "A", A(k)
887 copiedTo = new HgFileRevision(nextRename.first(), junctionDestMock.fileRevision, copiedFrom.getPath()); // "B", B(0) 925 copiedTo = new HgFileRevision(nextRename.file(), junctionDestMock.fileRevision, copiedFrom.getPath()); // "B", B(0)
888 } 926 }
889 } else { 927 } else {
890 assert iterateDirection == HgIterateDirection.NewToOld; 928 assert iterateDirection == HgIterateDirection.NewToOld;
891 // looking at B chunk (curRename), nextRename points at A 929 // looking at B chunk (curRename), nextRename points at A
892 HistoryNode junctionDest = changeHistory.get(0); // B(0) 930 HistoryNode junctionDest = changeHistory.get(0); // B(0)
893 // prepare mock A(k) 931 // prepare mock A(k)
894 HistoryNode junctionSrcMock = treeBuildInspector.one(nextRename.first(), nextRename.second()); // A(k) 932 HistoryNode junctionSrcMock = treeBuildInspector.one(nextRename.file(), nextRename.lastFileRev()); // A(k)
895 // B(0) to list A(k) as its parent 933 // B(0) to list A(k) as its parent
896 // NOTE, A(k) would be different when we reach A chunk on the next iteration, 934 // NOTE, A(k) would be different when we reach A chunk on the next iteration,
897 // but we do not care as long as TreeElement needs only parent/child changesets 935 // but we do not care as long as TreeElement needs only parent/child changesets
898 // and not other TreeElements; so that it's enough to have mock parent node (just 936 // and not other TreeElements; so that it's enough to have mock parent node (just
899 // for the sake of parent cset revisions). We have to, indeed, update real A(k), 937 // for the sake of parent cset revisions). We have to, indeed, update real A(k),
900 // once we get to iteration over A, with B(0) (junctionDest) as one more child. 938 // once we get to iteration over A, with B(0) (junctionDest) as one more child.
901 junctionSrcMock.bindChild(junctionDest); 939 junctionSrcMock.bindChild(junctionDest);
902 // Save mock B(0), for reasons see above for opposite direction 940 // Save mock B(0), for reasons see above for opposite direction
903 junctionNode = new HistoryNode(junctionDest.changeset, junctionDest.fileRevision, null, null); 941 junctionNode = new HistoryNode(junctionDest.changeset, junctionDest.fileRevision, null, null);
904 if (needCopyFromTo) { 942 if (needCopyFromTo) {
905 copiedFrom = new HgFileRevision(nextRename.first(), junctionSrcMock.fileRevision, null); // "A", A(k) 943 copiedFrom = new HgFileRevision(nextRename.file(), junctionSrcMock.fileRevision, null); // "A", A(k)
906 copiedTo = new HgFileRevision(curRename.first(), junctionNode.fileRevision, copiedFrom.getPath()); // "B", B(0) 944 copiedTo = new HgFileRevision(curRename.file(), junctionNode.fileRevision, copiedFrom.getPath()); // "B", B(0)
907 } 945 }
908 } 946 }
909 } 947 }
910 948
911 public void reportRenames(HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException, HgRuntimeException { 949 public void reportRenames(HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException, HgRuntimeException {
922 } 960 }
923 961
924 /** 962 /**
925 * Replace mock src/dest HistoryNode connected to junctionNode with a real one 963 * Replace mock src/dest HistoryNode connected to junctionNode with a real one
926 */ 964 */
927 public void connectWithLastJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> prevRename) { 965 public void connectWithLastJunctionPoint(QueueElement curRename, QueueElement prevRename) {
928 assert junctionNode != null; 966 assert junctionNode != null;
929 // A renamed to B. A(0..k..n) -> B(0..m). If followAncestry: k == n 967 // A renamed to B. A(0..k..n) -> B(0..m). If followAncestry: k == n
930 if (iterateDirection == HgIterateDirection.OldToNew) { 968 if (iterateDirection == HgIterateDirection.OldToNew) {
931 // forward, from old to new: 969 // forward, from old to new:
932 // changeHistory points to B 970 // changeHistory points to B
939 assert iterateDirection == HgIterateDirection.NewToOld; 977 assert iterateDirection == HgIterateDirection.NewToOld;
940 // changeHistory points to A 978 // changeHistory points to A
941 // Already reported B(m), B(m-1)...B(0), B(0) is in junctionNode 979 // Already reported B(m), B(m-1)...B(0), B(0) is in junctionNode
942 // Shall connect histories A(k).bind(B(0)) 980 // Shall connect histories A(k).bind(B(0))
943 // if followAncestry: A(k) is latest in changeHistory (k == n) 981 // if followAncestry: A(k) is latest in changeHistory (k == n)
944 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) 982 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.lastFileRev()); // A(k)
945 junctionSrc.bindChild(junctionNode); 983 junctionSrc.bindChild(junctionNode);
946 } 984 }
947 } 985 }
948 986
949 private HistoryNode findJunctionPointInCurrentChunk(Nodeid fileRevision) { 987 private HistoryNode findJunctionPointInCurrentChunk(Nodeid fileRevision) {