comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 520:1ee452f31187

Experimental support for inverse direction history walking. Refactored/streamlined cancellation in HgLogCommand and down the stack
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Fri, 21 Dec 2012 21:20:26 +0100
parents 0d5e1ea7955e
children 2103388d4010
comparison
equal deleted inserted replaced
519:934037edbea0 520:1ee452f31187
32 import java.util.List; 32 import java.util.List;
33 import java.util.ListIterator; 33 import java.util.ListIterator;
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;
38 import org.tmatesoft.hg.internal.BatchRangeHelper;
39 import org.tmatesoft.hg.internal.Experimental;
37 import org.tmatesoft.hg.internal.IntMap; 40 import org.tmatesoft.hg.internal.IntMap;
38 import org.tmatesoft.hg.internal.IntVector; 41 import org.tmatesoft.hg.internal.IntVector;
39 import org.tmatesoft.hg.internal.Lifecycle; 42 import org.tmatesoft.hg.internal.Lifecycle;
43 import org.tmatesoft.hg.internal.LifecycleProxy;
40 import org.tmatesoft.hg.repo.HgChangelog; 44 import org.tmatesoft.hg.repo.HgChangelog;
41 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; 45 import org.tmatesoft.hg.repo.HgChangelog.RawChangeset;
42 import org.tmatesoft.hg.repo.HgDataFile; 46 import org.tmatesoft.hg.repo.HgDataFile;
43 import org.tmatesoft.hg.repo.HgInvalidControlFileException; 47 import org.tmatesoft.hg.repo.HgInvalidControlFileException;
44 import org.tmatesoft.hg.repo.HgInvalidRevisionException; 48 import org.tmatesoft.hg.repo.HgInvalidRevisionException;
285 } 289 }
286 if (startRev < 0 || startRev > lastCset) { 290 if (startRev < 0 || startRev > lastCset) {
287 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", startRev, lastCset), null); 291 throw new HgBadArgumentException(String.format("Bad value %d for start revision for range [%1$d..%d]", startRev, lastCset), null);
288 } 292 }
289 final ProgressSupport progressHelper = getProgressSupport(handler); 293 final ProgressSupport progressHelper = getProgressSupport(handler);
294 final int BATCH_SIZE = 100;
290 try { 295 try {
291 count = 0; 296 count = 0;
292 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo 297 HgParentChildMap<HgChangelog> pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo
293 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above 298 // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above
294 // may utilize it as well. CommandContext? How about StatusCollector there as well? 299 // may utilize it as well. CommandContext? How about StatusCollector there as well?
295 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); 300 csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true));
301 // FilteringInspector is responsible to check command arguments: users, branches, limit, etc.
302 // prior to passing cset to next Inspector, which is either (a) collector to reverse cset order, then invokes
303 // transformer from (b), below, with alternative cset order or (b) transformer to hi-level csets.
296 FilteringInspector filterInsp = new FilteringInspector(); 304 FilteringInspector filterInsp = new FilteringInspector();
297 filterInsp.changesets(startRev, lastCset); 305 filterInsp.changesets(startRev, lastCset);
298 if (file == null) { 306 if (file == null) {
299 progressHelper.start(endRev - startRev + 1); 307 progressHelper.start(lastCset - startRev + 1);
300 repo.getChangelog().range(startRev, endRev, filterInsp); 308 if (iterateDirection == IterateDirection.FromOldToNew) {
301 csetTransform.checkFailure(); 309 filterInsp.delegateTo(csetTransform);
310 repo.getChangelog().range(startRev, lastCset, filterInsp);
311 csetTransform.checkFailure();
312 } else {
313 assert iterateDirection == IterateDirection.FromNewToOld;
314 BatchRangeHelper brh = new BatchRangeHelper(startRev, lastCset, BATCH_SIZE, true);
315 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(lastCset-startRev+1, BATCH_SIZE));
316 filterInsp.delegateTo(batchInspector);
317 while (brh.hasNext()) {
318 brh.next();
319 repo.getChangelog().range(brh.start(), brh.end(), filterInsp);
320 for (BatchChangesetInspector.BatchRecord br : batchInspector.iterate(true)) {
321 csetTransform.next(br.csetIndex, br.csetRevision, br.cset);
322 csetTransform.checkFailure();
323 }
324 batchInspector.reset();
325 }
326 }
302 } else { 327 } else {
328 filterInsp.delegateTo(csetTransform);
303 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); 329 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null);
304 List<Pair<HgDataFile, Nodeid>> fileRenames = buildFileRenamesQueue(); 330 List<Pair<HgDataFile, Nodeid>> fileRenames = buildFileRenamesQueue();
305 progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/); 331 progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/);
306 332
307 for (int nameIndex = 0, fileRenamesSize = fileRenames.size(); nameIndex < fileRenamesSize; nameIndex++) { 333 for (int nameIndex = 0, fileRenamesSize = fileRenames.size(); nameIndex < fileRenamesSize; nameIndex++) {
308 Pair<HgDataFile, Nodeid> curRename = fileRenames.get(nameIndex); 334 Pair<HgDataFile, Nodeid> curRename = fileRenames.get(nameIndex);
309 HgDataFile fileNode = curRename.first(); 335 HgDataFile fileNode = curRename.first();
310 if (followAncestry) { 336 if (followAncestry) {
311 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry); 337 TreeBuildInspector treeBuilder = new TreeBuildInspector(followAncestry);
338 @SuppressWarnings("unused")
312 List<HistoryNode> fileAncestry = treeBuilder.go(fileNode, curRename.second()); 339 List<HistoryNode> fileAncestry = treeBuilder.go(fileNode, curRename.second());
313 int[] commitRevisions = narrowChangesetRange(treeBuilder.getCommitRevisions(), startRev, lastCset); 340 int[] commitRevisions = narrowChangesetRange(treeBuilder.getCommitRevisions(), startRev, lastCset);
314 if (iterateDirection == IterateDirection.FromOldToNew) { 341 if (iterateDirection == IterateDirection.FromOldToNew) {
315 repo.getChangelog().range(filterInsp, commitRevisions); 342 repo.getChangelog().range(filterInsp, commitRevisions);
343 csetTransform.checkFailure();
316 } else { 344 } else {
317 assert iterateDirection == IterateDirection.FromNewToOld; 345 assert iterateDirection == IterateDirection.FromNewToOld;
318 // visit one by one in the opposite direction 346 // visit one by one in the opposite direction
319 for (int i = commitRevisions.length-1; i >= 0; i--) { 347 for (int i = commitRevisions.length-1; i >= 0; i--) {
320 int csetWithFileChange = commitRevisions[i]; 348 int csetWithFileChange = commitRevisions[i];
323 } 351 }
324 } else { 352 } else {
325 // report complete file history (XXX may narrow range with [startRev, endRev], but need to go from file rev to link rev) 353 // report complete file history (XXX may narrow range with [startRev, endRev], but need to go from file rev to link rev)
326 int fileStartRev = 0; //fileNode.getChangesetRevisionIndex(0) >= startRev 354 int fileStartRev = 0; //fileNode.getChangesetRevisionIndex(0) >= startRev
327 int fileEndRev = fileNode.getLastRevision(); 355 int fileEndRev = fileNode.getLastRevision();
328 fileNode.history(fileStartRev, fileEndRev, filterInsp); 356 if (iterateDirection == IterateDirection.FromOldToNew) {
329 csetTransform.checkFailure(); 357 fileNode.history(fileStartRev, fileEndRev, filterInsp);
358 csetTransform.checkFailure();
359 } else {
360 assert iterateDirection == IterateDirection.FromNewToOld;
361 BatchRangeHelper brh = new BatchRangeHelper(fileStartRev, fileEndRev, BATCH_SIZE, true);
362 BatchChangesetInspector batchInspector = new BatchChangesetInspector(Math.min(fileEndRev-fileStartRev+1, BATCH_SIZE));
363 filterInsp.delegateTo(batchInspector);
364 while (brh.hasNext()) {
365 brh.next();
366 fileNode.history(brh.start(), brh.end(), filterInsp);
367 for (BatchChangesetInspector.BatchRecord br : batchInspector.iterate(true /*iterateDirection == IterateDirection.FromNewToOld*/)) {
368 csetTransform.next(br.csetIndex, br.csetRevision, br.cset);
369 csetTransform.checkFailure();
370 }
371 batchInspector.reset();
372 }
373 }
330 } 374 }
331 if (followRenames && withCopyHandler != null && nameIndex + 1 < fileRenamesSize) { 375 if (followRenames && withCopyHandler != null && nameIndex + 1 < fileRenamesSize) {
332 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1); 376 Pair<HgDataFile, Nodeid> nextRename = fileRenames.get(nameIndex+1);
333 HgFileRevision src, dst; 377 HgFileRevision src, dst;
334 // A -> B 378 // A -> B
352 csetTransform = null; 396 csetTransform = null;
353 progressHelper.done(); 397 progressHelper.done();
354 } 398 }
355 } 399 }
356 400
401 private static class BatchChangesetInspector extends AdapterPlug implements HgChangelog.Inspector {
402 private static class BatchRecord {
403 public final int csetIndex;
404 public final Nodeid csetRevision;
405 public final RawChangeset cset;
406
407 public BatchRecord(int index, Nodeid nodeid, RawChangeset changeset) {
408 csetIndex = index;
409 csetRevision = nodeid;
410 cset = changeset;
411 }
412 }
413 private final ArrayList<BatchRecord> batch;
414
415 public BatchChangesetInspector(int batchSizeHint) {
416 batch = new ArrayList<BatchRecord>(batchSizeHint);
417 }
418
419 public BatchChangesetInspector reset() {
420 batch.clear();
421 return this;
422 }
423
424 public void next(int revisionIndex, Nodeid nodeid, RawChangeset cset) {
425 batch.add(new BatchRecord(revisionIndex, nodeid, cset.clone()));
426 }
427
428 public Iterable<BatchRecord> iterate(final boolean reverse) {
429 return new Iterable<BatchRecord>() {
430
431 public Iterator<BatchRecord> iterator() {
432 return reverse ? new ReverseIterator<BatchRecord>(batch) : batch.iterator();
433 }
434 };
435 }
436
437 // alternative would be dispatch(HgChangelog.Inspector) and dispatchReverse()
438 // methods, but progress and cancellation might get messy then
439 }
440
357 // public static void main(String[] args) { 441 // public static void main(String[] args) {
358 // int[] r = new int[] {17, 19, 21, 23, 25, 29}; 442 // int[] r = new int[] {17, 19, 21, 23, 25, 29};
359 // System.out.println(Arrays.toString(narrowChangesetRange(r, 0, 45))); 443 // System.out.println(Arrays.toString(narrowChangesetRange(r, 0, 45)));
360 // System.out.println(Arrays.toString(narrowChangesetRange(r, 0, 25))); 444 // System.out.println(Arrays.toString(narrowChangesetRange(r, 0, 25)));
361 // System.out.println(Arrays.toString(narrowChangesetRange(r, 5, 26))); 445 // System.out.println(Arrays.toString(narrowChangesetRange(r, 5, 26)));
450 dispatcher.clearJunctionPoint(); 534 dispatcher.clearJunctionPoint();
451 } 535 }
452 dispatcher.dispatchAllChanges(); 536 dispatcher.dispatchAllChanges();
453 } // for fileRenamesQueue; 537 } // for fileRenamesQueue;
454 progressHelper.done(); 538 progressHelper.done();
539 }
540
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;
455 } 554 }
456 555
457 private IterateDirection iterateDirection = IterateDirection.FromOldToNew; 556 private IterateDirection iterateDirection = IterateDirection.FromOldToNew;
458 557
459 private static class ReverseIterator<E> implements Iterator<E> { 558 private static class ReverseIterator<E> implements Iterator<E> {
818 } 917 }
819 918
820 919
821 // 920 //
822 921
823 private class FilteringInspector implements HgChangelog.Inspector, Lifecycle { 922 private class FilteringInspector extends AdapterPlug implements HgChangelog.Inspector, Adaptable {
824 923
825 private Callback lifecycle;
826 private int firstCset = BAD_REVISION, lastCset = BAD_REVISION; 924 private int firstCset = BAD_REVISION, lastCset = BAD_REVISION;
925 private HgChangelog.Inspector delegate;
926 // we use lifecycle to stop when limit is reached.
927 // delegate, however, may use lifecycle, too, so give it a chance
928 private LifecycleProxy lifecycleProxy;
827 929
828 // limit to changesets in this range only 930 // limit to changesets in this range only
829 public void changesets(int start, int end) { 931 public void changesets(int start, int end) {
830 firstCset = start; 932 firstCset = start;
831 lastCset = end; 933 lastCset = end;
934 }
935
936 public void delegateTo(HgChangelog.Inspector inspector) {
937 delegate = inspector;
938 // let delegate control life cycle, too
939 if (lifecycleProxy == null) {
940 super.attachAdapter(Lifecycle.class, lifecycleProxy = new LifecycleProxy(inspector));
941 } else {
942 lifecycleProxy.init(inspector);
943 }
832 } 944 }
833 945
834 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { 946 public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) {
835 if (limit > 0 && count >= limit) { 947 if (limit > 0 && count >= limit) {
836 return; 948 return;
860 } 972 }
861 } 973 }
862 if (date != null) { 974 if (date != null) {
863 // TODO post-1.0 implement date support for log 975 // TODO post-1.0 implement date support for log
864 } 976 }
865 csetTransform.next(revisionNumber, nodeid, cset); 977 delegate.next(revisionNumber, nodeid, cset);
866 if (++count >= limit) { 978 count++;
867 if (lifecycle != null) { // FIXME support Lifecycle delegation 979 if (limit > 0 && count >= limit) {
868 lifecycle.stop(); 980 lifecycleProxy.stop();
869 } 981 }
870 } 982 }
871 } 983 }
872 984
873 public void start(int count, Callback callback, Object token) {
874 lifecycle = callback;
875 }
876
877 public void finish(Object token) {
878 }
879 }
880
881 private HgParentChildMap<HgChangelog> getParentHelper(boolean create) throws HgInvalidControlFileException { 985 private HgParentChildMap<HgChangelog> getParentHelper(boolean create) throws HgInvalidControlFileException {
882 if (parentHelper == null && create) { 986 if (parentHelper == null && create) {
883 parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog()); 987 parentHelper = new HgParentChildMap<HgChangelog>(repo.getChangelog());
884 parentHelper.init(); 988 parentHelper.init();
885 } 989 }
886 return parentHelper; 990 return parentHelper;
887 } 991 }
888 992
889
890 public static class CollectHandler implements HgChangesetHandler { 993 public static class CollectHandler implements HgChangesetHandler {
891 private final List<HgChangeset> result = new LinkedList<HgChangeset>(); 994 private final List<HgChangeset> result = new LinkedList<HgChangeset>();
892 995
893 public List<HgChangeset> getChanges() { 996 public List<HgChangeset> getChanges() {
894 return Collections.unmodifiableList(result); 997 return Collections.unmodifiableList(result);