Mercurial > hg4j
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 } |