Mercurial > jhg
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 522:2103388d4010 v1.1m2
Expose option to report changesets in reversed order
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Wed, 26 Dec 2012 18:14:53 +0100 |
| parents | 1ee452f31187 |
| children | 2f9ed6bcefa2 |
comparison
equal
deleted
inserted
replaced
| 521:59e555c85da0 | 522:2103388d4010 |
|---|---|
| 34 import java.util.Set; | 34 import java.util.Set; |
| 35 import java.util.TreeSet; | 35 import java.util.TreeSet; |
| 36 | 36 |
| 37 import org.tmatesoft.hg.internal.AdapterPlug; | 37 import org.tmatesoft.hg.internal.AdapterPlug; |
| 38 import org.tmatesoft.hg.internal.BatchRangeHelper; | 38 import org.tmatesoft.hg.internal.BatchRangeHelper; |
| 39 import org.tmatesoft.hg.internal.Experimental; | |
| 40 import org.tmatesoft.hg.internal.IntMap; | 39 import org.tmatesoft.hg.internal.IntMap; |
| 41 import org.tmatesoft.hg.internal.IntVector; | 40 import org.tmatesoft.hg.internal.IntVector; |
| 42 import org.tmatesoft.hg.internal.Lifecycle; | 41 import org.tmatesoft.hg.internal.Lifecycle; |
| 43 import org.tmatesoft.hg.internal.LifecycleProxy; | 42 import org.tmatesoft.hg.internal.LifecycleProxy; |
| 44 import org.tmatesoft.hg.repo.HgChangelog; | 43 import org.tmatesoft.hg.repo.HgChangelog; |
| 89 * Whether to track history of the selected file version (based on file revision | 88 * Whether to track history of the selected file version (based on file revision |
| 90 * in working dir parent), follow ancestors only. | 89 * in working dir parent), follow ancestors only. |
| 91 * Note, 'hg log --follow' combines both #followHistory and #followAncestry | 90 * Note, 'hg log --follow' combines both #followHistory and #followAncestry |
| 92 */ | 91 */ |
| 93 private boolean followAncestry; | 92 private boolean followAncestry; |
| 93 | |
| 94 private HgIterateDirection iterateDirection = HgIterateDirection.OldToNew; | |
| 95 | |
| 94 private ChangesetTransformer csetTransform; | 96 private ChangesetTransformer csetTransform; |
| 95 private HgParentChildMap<HgChangelog> parentHelper; | 97 private HgParentChildMap<HgChangelog> parentHelper; |
| 96 | 98 |
| 97 public HgLogCommand(HgRepository hgRepo) { | 99 public HgLogCommand(HgRepository hgRepo) { |
| 98 repo = hgRepo; | 100 repo = hgRepo; |
| 237 * Handy analog to {@link #file(Path, boolean, boolean)} when clients' paths come from filesystem and need conversion to repository's | 239 * Handy analog to {@link #file(Path, boolean, boolean)} when clients' paths come from filesystem and need conversion to repository's |
| 238 * @return <code>this</code> for convenience | 240 * @return <code>this</code> for convenience |
| 239 */ | 241 */ |
| 240 public HgLogCommand file(String file, boolean followCopyRename, boolean followFileAncestry) { | 242 public HgLogCommand file(String file, boolean followCopyRename, boolean followFileAncestry) { |
| 241 return file(Path.create(repo.getToRepoPathHelper().rewrite(file)), followCopyRename, followFileAncestry); | 243 return file(Path.create(repo.getToRepoPathHelper().rewrite(file)), followCopyRename, followFileAncestry); |
| 244 } | |
| 245 | |
| 246 /** | |
| 247 * Specifies order for changesets reported through #execute(...) methods. | |
| 248 * By default, command reports changeset in their natural repository order, older first, | |
| 249 * newer last (i.e. {@link HgIterateDirection#OldToNew} | |
| 250 * | |
| 251 * @param order {@link HgIterateDirection#NewToOld} to get newer revisions first | |
| 252 * @return <code>this</code> for convenience | |
| 253 */ | |
| 254 public HgLogCommand order(HgIterateDirection order) { | |
| 255 iterateDirection = order; | |
| 256 return this; | |
| 242 } | 257 } |
| 243 | 258 |
| 244 /** | 259 /** |
| 245 * Similar to {@link #execute(HgChangesetHandler)}, collects and return result as a list. | 260 * Similar to {@link #execute(HgChangesetHandler)}, collects and return result as a list. |
| 246 * | 261 * |
| 303 // transformer from (b), below, with alternative cset order or (b) transformer to hi-level csets. | 318 // transformer from (b), below, with alternative cset order or (b) transformer to hi-level csets. |
| 304 FilteringInspector filterInsp = new FilteringInspector(); | 319 FilteringInspector filterInsp = new FilteringInspector(); |
| 305 filterInsp.changesets(startRev, lastCset); | 320 filterInsp.changesets(startRev, lastCset); |
| 306 if (file == null) { | 321 if (file == null) { |
| 307 progressHelper.start(lastCset - startRev + 1); | 322 progressHelper.start(lastCset - startRev + 1); |
| 308 if (iterateDirection == IterateDirection.FromOldToNew) { | 323 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 309 filterInsp.delegateTo(csetTransform); | 324 filterInsp.delegateTo(csetTransform); |
| 310 repo.getChangelog().range(startRev, lastCset, filterInsp); | 325 repo.getChangelog().range(startRev, lastCset, filterInsp); |
| 311 csetTransform.checkFailure(); | 326 csetTransform.checkFailure(); |
| 312 } else { | 327 } else { |
| 313 assert iterateDirection == IterateDirection.FromNewToOld; | 328 assert iterateDirection == HgIterateDirection.NewToOld; |
| 314 BatchRangeHelper brh = new BatchRangeHelper(startRev, lastCset, BATCH_SIZE, true); | 329 BatchRangeHelper brh = new BatchRangeHelper(startRev, lastCset, BATCH_SIZE, true); |
| 315 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(lastCset-startRev+1, BATCH_SIZE)); | 330 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(lastCset-startRev+1, BATCH_SIZE)); |
| 316 filterInsp.delegateTo(batchInspector); | 331 filterInsp.delegateTo(batchInspector); |
| 317 while (brh.hasNext()) { | 332 while (brh.hasNext()) { |
| 318 brh.next(); | 333 brh.next(); |
| 336 if (followAncestry) { | 351 if (followAncestry) { |
| 337 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry); | 352 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry); |
| 338 @SuppressWarnings("unused") | 353 @SuppressWarnings("unused") |
| 339 List<HistoryNode> fileAncestry = treeBuilder.go(fileNode, curRename.second()); | 354 List<HistoryNode> fileAncestry = treeBuilder.go(fileNode, curRename.second()); |
| 340 int[] commitRevisions = narrowChangesetRange(treeBuilder.getCommitRevisions(), startRev, lastCset); | 355 int[] commitRevisions = narrowChangesetRange(treeBuilder.getCommitRevisions(), startRev, lastCset); |
| 341 if (iterateDirection == IterateDirection.FromOldToNew) { | 356 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 342 repo.getChangelog().range(filterInsp, commitRevisions); | 357 repo.getChangelog().range(filterInsp, commitRevisions); |
| 343 csetTransform.checkFailure(); | 358 csetTransform.checkFailure(); |
| 344 } else { | 359 } else { |
| 345 assert iterateDirection == IterateDirection.FromNewToOld; | 360 assert iterateDirection == HgIterateDirection.NewToOld; |
| 346 // visit one by one in the opposite direction | 361 // visit one by one in the opposite direction |
| 347 for (int i = commitRevisions.length-1; i >= 0; i--) { | 362 for (int i = commitRevisions.length-1; i >= 0; i--) { |
| 348 int csetWithFileChange = commitRevisions[i]; | 363 int csetWithFileChange = commitRevisions[i]; |
| 349 repo.getChangelog().range(csetWithFileChange, csetWithFileChange, filterInsp); | 364 repo.getChangelog().range(csetWithFileChange, csetWithFileChange, filterInsp); |
| 350 } | 365 } |
| 351 } | 366 } |
| 352 } else { | 367 } else { |
| 353 // report complete file history (XXX may narrow range with [startRev, endRev], but need to go from file rev to link rev) | 368 // report complete file history (XXX may narrow range with [startRev, endRev], but need to go from file rev to link rev) |
| 354 int fileStartRev = 0; //fileNode.getChangesetRevisionIndex(0) >= startRev | 369 int fileStartRev = 0; //fileNode.getChangesetRevisionIndex(0) >= startRev |
| 355 int fileEndRev = fileNode.getLastRevision(); | 370 int fileEndRev = fileNode.getLastRevision(); |
| 356 if (iterateDirection == IterateDirection.FromOldToNew) { | 371 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 357 fileNode.history(fileStartRev, fileEndRev, filterInsp); | 372 fileNode.history(fileStartRev, fileEndRev, filterInsp); |
| 358 csetTransform.checkFailure(); | 373 csetTransform.checkFailure(); |
| 359 } else { | 374 } else { |
| 360 assert iterateDirection == IterateDirection.FromNewToOld; | 375 assert iterateDirection == HgIterateDirection.NewToOld; |
| 361 BatchRangeHelper brh = new BatchRangeHelper(fileStartRev, fileEndRev, BATCH_SIZE, true); | 376 BatchRangeHelper brh = new BatchRangeHelper(fileStartRev, fileEndRev, BATCH_SIZE, true); |
| 362 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(fileEndRev-fileStartRev+1, BATCH_SIZE)); | 377 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(fileEndRev-fileStartRev+1, BATCH_SIZE)); |
| 363 filterInsp.delegateTo(batchInspector); | 378 filterInsp.delegateTo(batchInspector); |
| 364 while (brh.hasNext()) { | 379 while (brh.hasNext()) { |
| 365 brh.next(); | 380 brh.next(); |
| 374 } | 389 } |
| 375 if (followRenames && withCopyHandler != null && nameIndex + 1 < fileRenamesSize) { | 390 if (followRenames && withCopyHandler != null && nameIndex + 1 < fileRenamesSize) { |
| 376 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1); | 391 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1); |
| 377 HgFileRevision src, dst; | 392 HgFileRevision src, dst; |
| 378 // A -> B | 393 // A -> B |
| 379 if (iterateDirection == IterateDirection.FromOldToNew) { | 394 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 380 // curRename: A, nextRename: B | 395 // curRename: A, nextRename: B |
| 381 src = new HgFileRevision(fileNode, curRename.second(), null); | 396 src = new HgFileRevision(fileNode, curRename.second(), null); |
| 382 dst = new HgFileRevision(nextRename.first(), nextRename.first().getRevision(0), src.getPath()); | 397 dst = new HgFileRevision(nextRename.first(), nextRename.first().getRevision(0), src.getPath()); |
| 383 } else { | 398 } else { |
| 384 assert iterateDirection == IterateDirection.FromNewToOld; | 399 assert iterateDirection == HgIterateDirection.NewToOld; |
| 385 // curRename: B, nextRename: A | 400 // curRename: B, nextRename: A |
| 386 src = new HgFileRevision(nextRename.first(), nextRename.second(), null); | 401 src = new HgFileRevision(nextRename.first(), nextRename.second(), null); |
| 387 dst = new HgFileRevision(fileNode, fileNode.getRevision(0), src.getPath()); | 402 dst = new HgFileRevision(fileNode, fileNode.getRevision(0), src.getPath()); |
| 388 } | 403 } |
| 389 withCopyHandler.copy(src, dst); | 404 withCopyHandler.copy(src, dst); |
| 536 dispatcher.dispatchAllChanges(); | 551 dispatcher.dispatchAllChanges(); |
| 537 } // for fileRenamesQueue; | 552 } // for fileRenamesQueue; |
| 538 progressHelper.done(); | 553 progressHelper.done(); |
| 539 } | 554 } |
| 540 | 555 |
| 541 /** | |
| 542 * DO NOT USE THIS METHOD, DEBUG PURPOSES ONLY!!! | |
| 543 */ | |
| 544 @Experimental(reason="Work in progress") | |
| 545 public HgLogCommand debugSwitch1() { | |
| 546 // FIXME can't expose iteration direction unless general iteration (changelog, not a file) supports it, too. | |
| 547 // however, need to test the code already there, hence this debug switch | |
| 548 if (iterateDirection == IterateDirection.FromOldToNew) { | |
| 549 iterateDirection = IterateDirection.FromNewToOld; | |
| 550 } else { | |
| 551 iterateDirection = IterateDirection.FromOldToNew; | |
| 552 } | |
| 553 return this; | |
| 554 } | |
| 555 | |
| 556 private IterateDirection iterateDirection = IterateDirection.FromOldToNew; | |
| 557 | |
| 558 private static class ReverseIterator<E> implements Iterator<E> { | 556 private static class ReverseIterator<E> implements Iterator<E> { |
| 559 private final ListIterator<E> listIterator; | 557 private final ListIterator<E> listIterator; |
| 560 | 558 |
| 561 public ReverseIterator(List<E> list) { | 559 public ReverseIterator(List<E> list) { |
| 562 listIterator = list.listIterator(list.size()); | 560 listIterator = list.listIterator(list.size()); |
| 612 while (fileNode.isCopy()) { | 610 while (fileNode.isCopy()) { |
| 613 Path fp = fileNode.getCopySourceName(); | 611 Path fp = fileNode.getCopySourceName(); |
| 614 Nodeid copyRev = fileNode.getCopySourceRevision(); | 612 Nodeid copyRev = fileNode.getCopySourceRevision(); |
| 615 fileNode = repo.getFileNode(fp); | 613 fileNode = repo.getFileNode(fp); |
| 616 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, copyRev); | 614 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, copyRev); |
| 617 if (iterateDirection == IterateDirection.FromOldToNew) { | 615 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 618 rv.addFirst(p); | 616 rv.addFirst(p); |
| 619 } else { | 617 } else { |
| 620 assert iterateDirection == IterateDirection.FromNewToOld; | 618 assert iterateDirection == HgIterateDirection.NewToOld; |
| 621 rv.addLast(p); | 619 rv.addLast(p); |
| 622 } | 620 } |
| 623 }; | 621 }; |
| 624 return rv; | 622 return rv; |
| 625 } | 623 } |
| 807 } | 805 } |
| 808 | 806 |
| 809 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename) { | 807 public void updateJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> nextRename) { |
| 810 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n | 808 // A (old) renamed to B(new). A(0..k..n) -> B(0..m). If followAncestry, k == n |
| 811 // curRename.second() points to A(k) | 809 // curRename.second() points to A(k) |
| 812 if (iterateDirection == IterateDirection.FromOldToNew) { | 810 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 813 // looking at A chunk (curRename), nextRename points to B | 811 // looking at A chunk (curRename), nextRename points to B |
| 814 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) | 812 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) |
| 815 HistoryNode junctionDestMock = treeBuildInspector.one(nextRename.first(), 0); // B(0) | 813 HistoryNode junctionDestMock = treeBuildInspector.one(nextRename.first(), 0); // B(0) |
| 816 // junstionDestMock is mock object, once we iterate next rename, there'd be different HistoryNode | 814 // junstionDestMock is mock object, once we iterate next rename, there'd be different HistoryNode |
| 817 // for B's first revision. This means we read it twice, but this seems to be reasonable | 815 // for B's first revision. This means we read it twice, but this seems to be reasonable |
| 819 junctionSrc.bindChild(junctionDestMock); | 817 junctionSrc.bindChild(junctionDestMock); |
| 820 // 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 | 818 // 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 |
| 821 // moreover, children of original A(k) (junctionSrc) would list mock B(0) which is undesired once we iterate over real B | 819 // moreover, children of original A(k) (junctionSrc) would list mock B(0) which is undesired once we iterate over real B |
| 822 junctionNode = new HistoryNode(junctionSrc.changeset, junctionSrc.fileRevision, null, null); | 820 junctionNode = new HistoryNode(junctionSrc.changeset, junctionSrc.fileRevision, null, null); |
| 823 } else { | 821 } else { |
| 824 assert iterateDirection == IterateDirection.FromNewToOld; | 822 assert iterateDirection == HgIterateDirection.NewToOld; |
| 825 // looking at B chunk (curRename), nextRename points at A | 823 // looking at B chunk (curRename), nextRename points at A |
| 826 HistoryNode junctionDest = changeHistory.get(0); // B(0) | 824 HistoryNode junctionDest = changeHistory.get(0); // B(0) |
| 827 // prepare mock A(k) | 825 // prepare mock A(k) |
| 828 HistoryNode junctionSrcMock = treeBuildInspector.one(nextRename.first(), nextRename.second()); // A(k) | 826 HistoryNode junctionSrcMock = treeBuildInspector.one(nextRename.first(), nextRename.second()); // A(k) |
| 829 // B(0) to list A(k) as its parent | 827 // B(0) to list A(k) as its parent |
| 843 } | 841 } |
| 844 | 842 |
| 845 public void connectWithLastJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> prevRename, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException { | 843 public void connectWithLastJunctionPoint(Pair<HgDataFile, Nodeid> curRename, Pair<HgDataFile, Nodeid> prevRename, HgFileRenameHandlerMixin renameHandler) throws HgCallbackTargetException { |
| 846 assert junctionNode != null; | 844 assert junctionNode != null; |
| 847 // A renamed to B. A(0..k..n) -> B(0..m). If followAncestry: k == n | 845 // A renamed to B. A(0..k..n) -> B(0..m). If followAncestry: k == n |
| 848 if (iterateDirection == IterateDirection.FromOldToNew) { | 846 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 849 // forward, from old to new: | 847 // forward, from old to new: |
| 850 // changeHistory points to B | 848 // changeHistory points to B |
| 851 // Already reported: A(0)..A(n), A(k) is in junctionNode | 849 // Already reported: A(0)..A(n), A(k) is in junctionNode |
| 852 // Shall connect histories: A(k).bind(B(0)) | 850 // Shall connect histories: A(k).bind(B(0)) |
| 853 HistoryNode junctionDest = changeHistory.get(0); // B(0) | 851 HistoryNode junctionDest = changeHistory.get(0); // B(0) |
| 857 HgFileRevision copiedFrom = new HgFileRevision(prevRename.first(), junctionNode.fileRevision, null); // "A", A(k) | 855 HgFileRevision copiedFrom = new HgFileRevision(prevRename.first(), junctionNode.fileRevision, null); // "A", A(k) |
| 858 HgFileRevision copiedTo = new HgFileRevision(curRename.first(), junctionDest.fileRevision, copiedFrom.getPath()); // "B", B(0) | 856 HgFileRevision copiedTo = new HgFileRevision(curRename.first(), junctionDest.fileRevision, copiedFrom.getPath()); // "B", B(0) |
| 859 renameHandler.copy(copiedFrom, copiedTo); | 857 renameHandler.copy(copiedFrom, copiedTo); |
| 860 } | 858 } |
| 861 } else { | 859 } else { |
| 862 assert iterateDirection == IterateDirection.FromNewToOld; | 860 assert iterateDirection == HgIterateDirection.NewToOld; |
| 863 // changeHistory points to A | 861 // changeHistory points to A |
| 864 // Already reported B(m), B(m-1)...B(0), B(0) is in junctionNode | 862 // Already reported B(m), B(m-1)...B(0), B(0) is in junctionNode |
| 865 // Shall connect histories A(k).bind(B(0)) | 863 // Shall connect histories A(k).bind(B(0)) |
| 866 // if followAncestry: A(k) is latest in changeHistory (k == n) | 864 // if followAncestry: A(k) is latest in changeHistory (k == n) |
| 867 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) | 865 HistoryNode junctionSrc = findJunctionPointInCurrentChunk(curRename.second()); // A(k) |
| 894 protected abstract void once(HistoryNode n) throws HgCallbackTargetException, CancelledException; | 892 protected abstract void once(HistoryNode n) throws HgCallbackTargetException, CancelledException; |
| 895 | 893 |
| 896 public void dispatchAllChanges() throws HgCallbackTargetException, CancelledException { | 894 public void dispatchAllChanges() throws HgCallbackTargetException, CancelledException { |
| 897 // XXX shall sort changeHistory according to changeset numbers? | 895 // XXX shall sort changeHistory according to changeset numbers? |
| 898 Iterator<HistoryNode> it; | 896 Iterator<HistoryNode> it; |
| 899 if (iterateDirection == IterateDirection.FromOldToNew) { | 897 if (iterateDirection == HgIterateDirection.OldToNew) { |
| 900 it = changeHistory.listIterator(); | 898 it = changeHistory.listIterator(); |
| 901 } else { | 899 } else { |
| 902 assert iterateDirection == IterateDirection.FromNewToOld; | 900 assert iterateDirection == HgIterateDirection.NewToOld; |
| 903 it = new ReverseIterator<HistoryNode>(changeHistory); | 901 it = new ReverseIterator<HistoryNode>(changeHistory); |
| 904 } | 902 } |
| 905 while(it.hasNext()) { | 903 while(it.hasNext()) { |
| 906 HistoryNode n = it.next(); | 904 HistoryNode n = it.next(); |
| 907 once(n); | 905 once(n); |
| 1232 } else { | 1230 } else { |
| 1233 return repo.getChangelog().getRevision(changelogRevisionNumber); | 1231 return repo.getChangelog().getRevision(changelogRevisionNumber); |
| 1234 } | 1232 } |
| 1235 } | 1233 } |
| 1236 } | 1234 } |
| 1237 | |
| 1238 private enum IterateDirection { | |
| 1239 FromOldToNew, FromNewToOld | |
| 1240 } | |
| 1241 } | 1235 } |
