comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 628:6526d8adbc0f

Explicit HgRuntimeException to facilitate easy switch from runtime to checked exceptions
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 22 May 2013 15:52:31 +0200
parents 43cfa08ff3fd
children 98ff1fb49abe
comparison
equal deleted inserted replaced
627:5153eb73b18d 628:6526d8adbc0f
43 import org.tmatesoft.hg.internal.LifecycleProxy; 43 import org.tmatesoft.hg.internal.LifecycleProxy;
44 import org.tmatesoft.hg.internal.ReverseIterator; 44 import org.tmatesoft.hg.internal.ReverseIterator;
45 import org.tmatesoft.hg.repo.HgChangelog; 45 import org.tmatesoft.hg.repo.HgChangelog;
46 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; 46 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
47 import org.tmatesoft.hg.repo.HgDataFile; 47 import org.tmatesoft.hg.repo.HgDataFile;
48 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
49 import org.tmatesoft.hg.repo.HgInvalidStateException; 48 import org.tmatesoft.hg.repo.HgInvalidStateException;
50 import org.tmatesoft.hg.repo.HgParentChildMap; 49 import org.tmatesoft.hg.repo.HgParentChildMap;
51 import org.tmatesoft.hg.repo.HgRepository; 50 import org.tmatesoft.hg.repo.HgRepository;
52 import org.tmatesoft.hg.repo.HgRuntimeException; 51 import org.tmatesoft.hg.repo.HgRuntimeException;
53 import org.tmatesoft.hg.repo.HgStatusCollector; 52 import org.tmatesoft.hg.repo.HgStatusCollector;
295 throw new IllegalArgumentException(); 294 throw new IllegalArgumentException();
296 } 295 }
297 if (csetTransform != null) { 296 if (csetTransform != null) {
298 throw new ConcurrentModificationException(); 297 throw new ConcurrentModificationException();
299 } 298 }
300 if (repo.getChangelog().getRevisionCount() == 0) {
301 return;
302 }
303 final int lastCset = endRev == TIP ? repo.getChangelog().getLastRevision() : endRev;
304 // XXX pretty much like HgInternals.checkRevlogRange
305 if (lastCset < 0 || lastCset > repo.getChangelog().getLastRevision()) {
306 throw new HgBadArgumentException(String.format("Bad value %d for end revision", lastCset), null);
307 }
308 if (startRev < 0 || startRev > lastCset) {
309 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", startRev, lastCset), null);
310 }
311 final ProgressSupport progressHelper = getProgressSupport(handler); 299 final ProgressSupport progressHelper = getProgressSupport(handler);
312 final int BATCH_SIZE = 100;
313 try { 300 try {
301 if (repo.getChangelog().getRevisionCount() == 0) {
302 return;
303 }
304 final int lastCset = endRev == TIP ? repo.getChangelog().getLastRevision() : endRev;
305 // XXX pretty much like HgInternals.checkRevlogRange
306 if (lastCset < 0 || lastCset > repo.getChangelog().getLastRevision()) {
307 throw new HgBadArgumentException(String.format("Bad value %d for end revision", lastCset), null);
308 }
309 if (startRev < 0 || startRev > lastCset) {
310 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", startRev, lastCset), null);
311 }
312 final int BATCH_SIZE = 100;
314 count = 0; 313 count = 0;
315 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo 314 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo
316 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above 315 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above
317 // may utilize it as well. CommandContext? How about StatusCollector there as well? 316 // may utilize it as well. CommandContext? How about StatusCollector there as well?
318 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); 317 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true));
515 } 514 }
516 final ProgressSupport progressHelper = getProgressSupport(handler); 515 final ProgressSupport progressHelper = getProgressSupport(handler);
517 final CancelSupport cancelHelper = getCancelSupport(handler, true); 516 final CancelSupport cancelHelper = getCancelSupport(handler, true);
518 final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); 517 final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null);
519 518
520 519 try {
521 // XXX rename. dispatcher is not a proper name (most of the job done - managing history chunk interconnection) 520
522 final HandlerDispatcher dispatcher = new HandlerDispatcher() { 521 // XXX rename. dispatcher is not a proper name (most of the job done - managing history chunk interconnection)
523 522 final HandlerDispatcher dispatcher = new HandlerDispatcher() {
524 @Override 523
525 protected void once(HistoryNode n) throws HgCallbackTargetException, CancelledException { 524 @Override
526 handler.treeElement(ei.init(n, currentFileNode)); 525 protected void once(HistoryNode n) throws HgCallbackTargetException, CancelledException, HgRuntimeException {
526 handler.treeElement(ei.init(n, currentFileNode));
527 cancelHelper.checkCancelled();
528 }
529 };
530
531 // renamed files in the queue are placed with respect to #iterateDirection
532 // i.e. if we iterate from new to old, recent filenames come first
533 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder();
534 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = frqBuilder.buildFileRenamesQueue();
535 // 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.
537 progressHelper.start(4 * fileRenamesQueue.size());
538 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) {
539
540 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex);
541 dispatcher.prepare(progressHelper, renameInfo);
527 cancelHelper.checkCancelled(); 542 cancelHelper.checkCancelled();
528 } 543 if (namesIndex > 0) {
529 }; 544 dispatcher.connectWithLastJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex - 1));
530 545 }
531 // renamed files in the queue are placed with respect to #iterateDirection 546 if (namesIndex + 1 < renamesQueueSize) {
532 // i.e. if we iterate from new to old, recent filenames come first 547 // there's at least one more name we are going to look at
533 FileRenameQueueBuilder frqBuilder = new FileRenameQueueBuilder(); 548 dispatcher.updateJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex+1), renameHandler != null);
534 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = frqBuilder.buildFileRenamesQueue(); 549 } else {
535 // XXX perhaps, makes sense to look at selected file's revision when followAncestry is true 550 dispatcher.clearJunctionPoint();
536 // to ensure file we attempt to trace is in the WC's parent. Native hg aborts if not. 551 }
537 progressHelper.start(4 * fileRenamesQueue.size()); 552 dispatcher.dispatchAllChanges();
538 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) { 553 if (renameHandler != null && namesIndex + 1 < renamesQueueSize) {
539 554 dispatcher.reportRenames(renameHandler);
540 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex); 555 }
541 dispatcher.prepare(progressHelper, renameInfo); 556 } // for fileRenamesQueue;
542 cancelHelper.checkCancelled(); 557 frqBuilder.reportRenameIfNotInQueue(fileRenamesQueue, renameHandler);
543 if (namesIndex > 0) { 558 } catch (HgRuntimeException ex) {
544 dispatcher.connectWithLastJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex - 1)); 559 throw new HgLibraryFailureException(ex);
545 } 560 }
546 if (namesIndex + 1 < renamesQueueSize) {
547 // there's at least one more name we are going to look at
548 dispatcher.updateJunctionPoint(renameInfo, fileRenamesQueue.get(namesIndex+1), renameHandler != null);
549 } else {
550 dispatcher.clearJunctionPoint();
551 }
552 dispatcher.dispatchAllChanges();
553 if (renameHandler != null && namesIndex + 1 < renamesQueueSize) {
554 dispatcher.reportRenames(renameHandler);
555 }
556 } // for fileRenamesQueue;
557 frqBuilder.reportRenameIfNotInQueue(fileRenamesQueue, renameHandler);
558 progressHelper.done(); 561 progressHelper.done();
559 } 562 }
560 563
561 /** 564 /**
562 * Utility to build sequence of file renames 565 * Utility to build sequence of file renames
576 * 579 *
577 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair 580 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair
578 * and possibly reuse this functionality 581 * and possibly reuse this functionality
579 * 582 *
580 * @return list of file renames, ordered with respect to {@link #iterateDirection} 583 * @return list of file renames, ordered with respect to {@link #iterateDirection}
584 * @throws HgRuntimeException
581 */ 585 */
582 public List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() throws HgPathNotFoundException { 586 public List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() throws HgPathNotFoundException, HgRuntimeException {
583 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); 587 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>();
584 Nodeid startRev = null; 588 Nodeid startRev = null;
585 HgDataFile fileNode = repo.getFileNode(file); 589 HgDataFile fileNode = repo.getFileNode(file);
586 if (!fileNode.exists()) { 590 if (!fileNode.exists()) {
587 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); 591 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file);
611 } 615 }
612 }; 616 };
613 return rv; 617 return rv;
614 } 618 }
615 619
616 public boolean hasOrigin(Pair<HgDataFile, Nodeid> p) { 620 public boolean hasOrigin(Pair<HgDataFile, Nodeid> p) throws HgRuntimeException {
617 return p.first().isCopy(); 621 return p.first().isCopy();
618 } 622 }
619 623
620 public Pair<HgDataFile, Nodeid> origin(Pair<HgDataFile, Nodeid> p) { 624 public Pair<HgDataFile, Nodeid> origin(Pair<HgDataFile, Nodeid> p) throws HgRuntimeException {
621 HgDataFile fileNode = p.first(); 625 HgDataFile fileNode = p.first();
622 assert fileNode.isCopy(); 626 assert fileNode.isCopy();
623 Path fp = fileNode.getCopySourceName(); 627 Path fp = fileNode.getCopySourceName();
624 Nodeid copyRev = fileNode.getCopySourceRevision(); 628 Nodeid copyRev = fileNode.getCopySourceRevision();
625 fileNode = repo.getFileNode(fp); 629 fileNode = repo.getFileNode(fp);
631 * even if queue didn't get rename information due to followRenames == false 635 * even if queue didn't get rename information due to followRenames == false
632 * 636 *
633 * @param queue value from {@link #buildFileRenamesQueue()} 637 * @param queue value from {@link #buildFileRenamesQueue()}
634 * @param renameHandler may be <code>null</code> 638 * @param renameHandler may be <code>null</code>
635 */ 639 */
636 public void reportRenameIfNotInQueue(List<Pair<HgDataFile, Nodeid>> queue, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException { 640 public void reportRenameIfNotInQueue(List<Pair<HgDataFile, Nodeid>> queue, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException, HgRuntimeException {
637 if (renameHandler != null && !followRenames) { 641 if (renameHandler != null && !followRenames) {
638 // If followRenames is true, all the historical names were in the queue and are processed already. 642 // If followRenames is true, all the historical names were in the queue and are processed already.
639 // Hence, shall process origin explicitly only when renameHandler is present but followRenames is not requested. 643 // Hence, shall process origin explicitly only when renameHandler is present but followRenames is not requested.
640 assert queue.size() == 1; // see the way queue is constructed above 644 assert queue.size() == 1; // see the way queue is constructed above
641 Pair<HgDataFile, Nodeid> curRename = queue.get(0); 645 Pair<HgDataFile, Nodeid> curRename = queue.get(0);
675 p2 = completeHistory[parent2]; 679 p2 = completeHistory[parent2];
676 } 680 }
677 completeHistory[revisionNumber] = new HistoryNode(commitRevisions[revisionNumber], revision, p1, p2); 681 completeHistory[revisionNumber] = new HistoryNode(commitRevisions[revisionNumber], revision, p1, p2);
678 } 682 }
679 683
680 HistoryNode one(HgDataFile fileNode, Nodeid fileRevision) throws HgInvalidControlFileException { 684 HistoryNode one(HgDataFile fileNode, Nodeid fileRevision) throws HgRuntimeException {
681 int fileRevIndexToVisit = fileNode.getRevisionIndex(fileRevision); 685 int fileRevIndexToVisit = fileNode.getRevisionIndex(fileRevision);
682 return one(fileNode, fileRevIndexToVisit); 686 return one(fileNode, fileRevIndexToVisit);
683 } 687 }
684 688
685 HistoryNode one(HgDataFile fileNode, int fileRevIndexToVisit) throws HgInvalidControlFileException { 689 HistoryNode one(HgDataFile fileNode, int fileRevIndexToVisit) throws HgRuntimeException {
686 resultHistory = null; 690 resultHistory = null;
687 if (fileRevIndexToVisit == HgRepository.TIP) { 691 if (fileRevIndexToVisit == HgRepository.TIP) {
688 fileRevIndexToVisit = fileNode.getLastRevision(); 692 fileRevIndexToVisit = fileNode.getLastRevision();
689 } 693 }
690 // still, allocate whole array, for #next to be able to get null parent values 694 // still, allocate whole array, for #next to be able to get null parent values
706 * lastRevisionIndex would be included. 710 * lastRevisionIndex would be included.
707 * 711 *
708 * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list 712 * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list
709 * is modifiable (to further augment with last/first elements of renamed file histories) 713 * is modifiable (to further augment with last/first elements of renamed file histories)
710 */ 714 */
711 List<HistoryNode> go(HgDataFile fileNode, Nodeid fileLastRevisionToVisit) throws HgInvalidControlFileException { 715 List<HistoryNode> go(HgDataFile fileNode, Nodeid fileLastRevisionToVisit) throws HgRuntimeException {
712 resultHistory = null; 716 resultHistory = null;
713 int fileLastRevIndexToVisit = fileLastRevisionToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevisionToVisit); 717 int fileLastRevIndexToVisit = fileLastRevisionToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevisionToVisit);
714 completeHistory = new HistoryNode[fileLastRevIndexToVisit+1]; 718 completeHistory = new HistoryNode[fileLastRevIndexToVisit+1];
715 commitRevisions = new int[completeHistory.length]; 719 commitRevisions = new int[completeHistory.length];
716 fileNode.indexWalk(0, fileLastRevIndexToVisit, this); 720 fileNode.indexWalk(0, fileLastRevIndexToVisit, this);
803 private HistoryNode junctionNode; 807 private HistoryNode junctionNode;
804 // initialized when there's HgFileRenameHandlerMixin 808 // initialized when there's HgFileRenameHandlerMixin
805 private HgFileRevision copiedFrom, copiedTo; 809 private HgFileRevision copiedFrom, copiedTo;
806 810
807 // parentProgress shall be initialized with 4 XXX refactor all this stuff with parentProgress 811 // parentProgress shall be initialized with 4 XXX refactor all this stuff with parentProgress
808 public void prepare(ProgressSupport parentProgress, Pair<HgDataFile, Nodeid> renameInfo) { 812 public void prepare(ProgressSupport parentProgress, Pair<HgDataFile, Nodeid> renameInfo) throws HgRuntimeException {
809 // if we don't followAncestry, take complete history 813 // if we don't followAncestry, take complete history
810 // XXX treeBuildInspector knows followAncestry, perhaps the logic 814 // XXX treeBuildInspector knows followAncestry, perhaps the logic
811 // whether to take specific revision or the last one shall be there? 815 // whether to take specific revision or the last one shall be there?
812 changeHistory = treeBuildInspector.go(renameInfo.first(), followAncestry ? renameInfo.second() : null); 816 changeHistory = treeBuildInspector.go(renameInfo.first(), followAncestry ? renameInfo.second() : null);
813 assert changeHistory.size() > 0; 817 assert changeHistory.size() > 0;
832 progress.start(historyNodeCount); 836 progress.start(historyNodeCount);
833 // switch to present chunk's file node 837 // switch to present chunk's file node
834 switchTo(renameInfo.first()); 838 switchTo(renameInfo.first());
835 } 839 }
836 840
837 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename, boolean needCopyFromTo) { 841 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename, boolean needCopyFromTo) throws HgRuntimeException {
838 copiedFrom = copiedTo = null; 842 copiedFrom = copiedTo = null;
839 // 843 //
840 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n 844 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n
841 // curRename.second() points to A(k) 845 // curRename.second() points to A(k)
842 if (iterateDirection == HgIterateDirection.OldToNew) { 846 if (iterateDirection == HgIterateDirection.OldToNew) {
874 copiedTo = new HgFileRevision(curRename.first(), junctionNode.fileRevision, copiedFrom.getPath()); // "B", B(0) 878 copiedTo = new HgFileRevision(curRename.first(), junctionNode.fileRevision, copiedFrom.getPath()); // "B", B(0)
875 } 879 }
876 } 880 }
877 } 881 }
878 882
879 public void reportRenames(HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException { 883 public void reportRenames(HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException, HgRuntimeException {
880 if (renameHandler != null) { // shall report renames 884 if (renameHandler != null) { // shall report renames
881 assert copiedFrom != null; 885 assert copiedFrom != null;
882 assert copiedTo != null; 886 assert copiedTo != null;
883 renameHandler.copy(copiedFrom, copiedTo); 887 renameHandler.copy(copiedFrom, copiedTo);
884 } 888 }
929 int csetStart = changeHistory.get(0).changeset; 933 int csetStart = changeHistory.get(0).changeset;
930 int csetEnd = changeHistory.get(changeHistory.size() - 1).changeset; 934 int csetEnd = changeHistory.get(changeHistory.size() - 1).changeset;
931 throw new HgInvalidStateException(String.format("For change history (cset[%d..%d]) could not find node for file change %s", csetStart, csetEnd, fileRevision.shortNotation())); 935 throw new HgInvalidStateException(String.format("For change history (cset[%d..%d]) could not find node for file change %s", csetStart, csetEnd, fileRevision.shortNotation()));
932 } 936 }
933 937
934 protected abstract void once(HistoryNode n) throws HgCallbackTargetException, CancelledException; 938 protected abstract void once(HistoryNode n) throws HgCallbackTargetException, CancelledException, HgRuntimeException;
935 939
936 public void dispatchAllChanges() throws HgCallbackTargetException, CancelledException { 940 public void dispatchAllChanges() throws HgCallbackTargetException, CancelledException, HgRuntimeException {
937 // XXX shall sort changeHistory according to changeset numbers? 941 // XXX shall sort changeHistory according to changeset numbers?
938 Iterator<HistoryNode> it; 942 Iterator<HistoryNode> it;
939 if (iterateDirection == HgIterateDirection.OldToNew) { 943 if (iterateDirection == HgIterateDirection.OldToNew) {
940 it = changeHistory.listIterator(); 944 it = changeHistory.listIterator();
941 } else { 945 } else {
981 } else { 985 } else {
982 lifecycleProxy.init(inspector); 986 lifecycleProxy.init(inspector);
983 } 987 }
984 } 988 }
985 989
986 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { 990 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) throws HgRuntimeException {
987 if (limit > 0 && count >= limit) { 991 if (limit > 0 && count >= limit) {
988 return; 992 return;
989 } 993 }
990 // XXX may benefit from optional interface with #isInterested(int csetRev) - to avoid 994 // XXX may benefit from optional interface with #isInterested(int csetRev) - to avoid
991 // RawChangeset instantiation 995 // RawChangeset instantiation
1020 lifecycleProxy.stop(); 1024 lifecycleProxy.stop();
1021 } 1025 }
1022 } 1026 }
1023 } 1027 }
1024 1028
1025 private HgParentChildMap<HgChangelog> getParentHelper(boolean create) throws HgInvalidControlFileException { 1029 private HgParentChildMap<HgChangelog> getParentHelper(boolean create) throws HgRuntimeException {
1026 if (parentHelper == null && create) { 1030 if (parentHelper == null && create) {
1027 parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog()); 1031 parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog());
1028 parentHelper.init(); 1032 parentHelper.init();
1029 } 1033 }
1030 return parentHelper; 1034 return parentHelper;
1118 1122
1119 public HgDataFile file() { 1123 public HgDataFile file() {
1120 return fileNode; 1124 return fileNode;
1121 } 1125 }
1122 1126
1123 public HgChangeset changeset() { 1127 public HgChangeset changeset() throws HgRuntimeException {
1124 return get(historyNode.changeset)[0]; 1128 return get(historyNode.changeset)[0];
1125 } 1129 }
1126 1130
1127 public Pair<HgChangeset, HgChangeset> parents() { 1131 public Pair<HgChangeset, HgChangeset> parents() throws HgRuntimeException {
1128 if (parents != null) { 1132 if (parents != null) {
1129 return parents; 1133 return parents;
1130 } 1134 }
1131 HistoryNode p; 1135 HistoryNode p;
1132 final int p1, p2; 1136 final int p1, p2;
1142 } 1146 }
1143 HgChangeset[] r = get(p1, p2); 1147 HgChangeset[] r = get(p1, p2);
1144 return parents = new Pair<HgChangeset, HgChangeset>(r[0], r[1]); 1148 return parents = new Pair<HgChangeset, HgChangeset>(r[0], r[1]);
1145 } 1149 }
1146 1150
1147 public Collection<HgChangeset> children() { 1151 public Collection<HgChangeset> children() throws HgRuntimeException {
1148 if (children != null) { 1152 if (children != null) {
1149 return children; 1153 return children;
1150 } 1154 }
1151 if (historyNode.children == null) { 1155 if (historyNode.children == null) {
1152 children = Collections.emptyList(); 1156 children = Collections.emptyList();
1163 1167
1164 void populate(HgChangeset cs) { 1168 void populate(HgChangeset cs) {
1165 cachedChangesets.put(cs.getRevisionIndex(), cs); 1169 cachedChangesets.put(cs.getRevisionIndex(), cs);
1166 } 1170 }
1167 1171
1168 private HgChangeset[] get(int... changelogRevisionIndex) { 1172 private HgChangeset[] get(int... changelogRevisionIndex) throws HgRuntimeException {
1169 HgChangeset[] rv = new HgChangeset[changelogRevisionIndex.length]; 1173 HgChangeset[] rv = new HgChangeset[changelogRevisionIndex.length];
1170 IntVector misses = new IntVector(changelogRevisionIndex.length, -1); 1174 IntVector misses = new IntVector(changelogRevisionIndex.length, -1);
1171 for (int i = 0; i < changelogRevisionIndex.length; i++) { 1175 for (int i = 0; i < changelogRevisionIndex.length; i++) {
1172 if (changelogRevisionIndex[i] == -1) { 1176 if (changelogRevisionIndex[i] == -1) {
1173 rv[i] = null; 1177 rv[i] = null;
1185 initTransform(); 1189 initTransform();
1186 repo.getChangelog().range(this, changesets2read); 1190 repo.getChangelog().range(this, changesets2read);
1187 for (int changeset2read : changesets2read) { 1191 for (int changeset2read : changesets2read) {
1188 HgChangeset cs = cachedChangesets.get(changeset2read); 1192 HgChangeset cs = cachedChangesets.get(changeset2read);
1189 if (cs == null) { 1193 if (cs == null) {
1190 HgInvalidStateException t = new HgInvalidStateException(String.format("Can't get changeset for revision %d", changeset2read)); 1194 throw new HgInvalidStateException(String.format("Can't get changeset for revision %d", changeset2read));
1191 throw t.setRevisionIndex(changeset2read);
1192 } 1195 }
1193 // HgChangelog.range may reorder changesets according to their order in the changelog 1196 // HgChangelog.range may reorder changesets according to their order in the changelog
1194 // thus need to find original index 1197 // thus need to find original index
1195 boolean sanity = false; 1198 boolean sanity = false;
1196 for (int i = 0; i < changelogRevisionIndex.length; i++) { 1199 for (int i = 0; i < changelogRevisionIndex.length; i++) {
1219 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { 1222 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
1220 HgChangeset cs = transform.handle(revisionNumber, nodeid, cset); 1223 HgChangeset cs = transform.handle(revisionNumber, nodeid, cset);
1221 populate(cs.clone()); 1224 populate(cs.clone());
1222 } 1225 }
1223 1226
1224 public Nodeid changesetRevision() { 1227 public Nodeid changesetRevision() throws HgRuntimeException {
1225 if (changesetRevision == null) { 1228 if (changesetRevision == null) {
1226 changesetRevision = getRevision(historyNode.changeset); 1229 changesetRevision = getRevision(historyNode.changeset);
1227 } 1230 }
1228 return changesetRevision; 1231 return changesetRevision;
1229 } 1232 }
1230 1233
1231 public Pair<Nodeid, Nodeid> parentRevisions() { 1234 public Pair<Nodeid, Nodeid> parentRevisions() throws HgRuntimeException {
1232 if (parentRevisions == null) { 1235 if (parentRevisions == null) {
1233 HistoryNode p; 1236 HistoryNode p;
1234 final Nodeid p1, p2; 1237 final Nodeid p1, p2;
1235 if ((p = historyNode.parent1) != null) { 1238 if ((p = historyNode.parent1) != null) {
1236 p1 = getRevision(p.changeset); 1239 p1 = getRevision(p.changeset);
1245 parentRevisions = new Pair<Nodeid, Nodeid>(p1, p2); 1248 parentRevisions = new Pair<Nodeid, Nodeid>(p1, p2);
1246 } 1249 }
1247 return parentRevisions; 1250 return parentRevisions;
1248 } 1251 }
1249 1252
1250 public Collection<Nodeid> childRevisions() { 1253 public Collection<Nodeid> childRevisions() throws HgRuntimeException {
1251 if (childRevisions != null) { 1254 if (childRevisions != null) {
1252 return childRevisions; 1255 return childRevisions;
1253 } 1256 }
1254 if (historyNode.children == null) { 1257 if (historyNode.children == null) {
1255 childRevisions = Collections.emptyList(); 1258 childRevisions = Collections.emptyList();
1262 } 1265 }
1263 return childRevisions; 1266 return childRevisions;
1264 } 1267 }
1265 1268
1266 // reading nodeid involves reading index only, guess, can afford not to optimize multiple reads 1269 // reading nodeid involves reading index only, guess, can afford not to optimize multiple reads
1267 private Nodeid getRevision(int changelogRevisionNumber) { 1270 private Nodeid getRevision(int changelogRevisionNumber) throws HgRuntimeException {
1268 // TODO post-1.0 pipe through pool 1271 // TODO post-1.0 pipe through pool
1269 HgChangeset cs = cachedChangesets.get(changelogRevisionNumber); 1272 HgChangeset cs = cachedChangesets.get(changelogRevisionNumber);
1270 if (cs != null) { 1273 if (cs != null) {
1271 return cs.getNodeid(); 1274 return cs.getNodeid();
1272 } else { 1275 } else {