Mercurial > hg4j
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); |