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 }