Mercurial > jhg
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) { |