Mercurial > hg4j
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 515:e6c8b9b654b2
Provide access to HgDataFile being iterated into HgChangesetTreeHandler.TreeElement to give context for renamed files
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Mon, 17 Dec 2012 20:51:12 +0100 |
parents | 5dcb4581c8ef |
children | 0ae5768081aa |
comparison
equal
deleted
inserted
replaced
514:5dcb4581c8ef | 515:e6c8b9b654b2 |
---|---|
295 progressHelper.done(); | 295 progressHelper.done(); |
296 } | 296 } |
297 } | 297 } |
298 | 298 |
299 /** | 299 /** |
300 * Tree-wise iteration of a file history, with handy access to parent-child relations between changesets. | 300 * Tree-wise iteration of a file history, with handy access to parent-child relations between changesets. |
301 * When file history is being followed, handler may additionally implement {@link HgFileRenameHandlerMixin} | |
302 * to get notified about switching between history chunks that belong to different names. | |
301 * | 303 * |
302 * @param handler callback to process changesets. | 304 * @param handler callback to process changesets. |
305 * @see HgFileRenameHandlerMixin | |
303 * @throws HgCallbackTargetException propagated exception from the handler | 306 * @throws HgCallbackTargetException propagated exception from the handler |
304 * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state | 307 * @throws HgException subclass thereof to indicate specific issue with the command arguments or repository state |
305 * @throws CancelledException if execution of the command was cancelled | 308 * @throws CancelledException if execution of the command was cancelled |
306 * @throws IllegalArgumentException if command is not satisfied with its arguments | 309 * @throws IllegalArgumentException if command is not satisfied with its arguments |
307 * @throws ConcurrentModificationException if this log command instance is already running | 310 * @throws ConcurrentModificationException if this log command instance is already running |
308 */ | 311 */ |
309 public void execute(HgChangesetTreeHandler handler) throws HgCallbackTargetException, HgException, CancelledException { | 312 public void execute(final HgChangesetTreeHandler handler) throws HgCallbackTargetException, HgException, CancelledException { |
310 if (handler == null) { | 313 if (handler == null) { |
311 throw new IllegalArgumentException(); | 314 throw new IllegalArgumentException(); |
312 } | 315 } |
313 if (csetTransform != null) { | 316 if (csetTransform != null) { |
314 throw new ConcurrentModificationException(); | 317 throw new ConcurrentModificationException(); |
324 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followRenames); | 327 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followRenames); |
325 // we iterate separate histories of each filename, need to connect | 328 // we iterate separate histories of each filename, need to connect |
326 // last node of historyA with first node of historyB (A renamed to B case) | 329 // last node of historyA with first node of historyB (A renamed to B case) |
327 // to make overall history smooth. | 330 // to make overall history smooth. |
328 HistoryNode lastFromPrevIteration = null; | 331 HistoryNode lastFromPrevIteration = null; |
329 HgFileRevision copiedFrom = null, copiedTo = null; | 332 |
330 boolean shallReportRenameAfter1Step = false; | 333 class HandlerDispatcher { |
331 | 334 private final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ |
332 final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ | 335 private ElementImpl ei = null; |
333 ElementImpl ei = null; | 336 private ProgressSupport progress; |
337 private HgDataFile currentFileNode; | |
338 | |
339 public void prepare(ProgressSupport parentProgress, int historyNodeCount, TreeBuildInspector treeBuildInspector) { | |
340 if (ei == null) { | |
341 // when follow is true, changeHistory.size() of the first revision might be quite short | |
342 // (e.g. bad fname recognized soon), hence ensure at least cache size at once | |
343 ei = new ElementImpl(Math.max(CACHE_CSET_IN_ADVANCE_THRESHOLD, historyNodeCount)); | |
344 } | |
345 if (historyNodeCount < CACHE_CSET_IN_ADVANCE_THRESHOLD ) { | |
346 int[] commitRevisions = treeBuildInspector.getCommitRevisions(); | |
347 // read bunch of changesets at once and cache 'em | |
348 ei.initTransform(); | |
349 repo.getChangelog().range(ei, commitRevisions); | |
350 parentProgress.worked(1); | |
351 progress = new ProgressSupport.Sub(parentProgress, 2); | |
352 } else { | |
353 progress = new ProgressSupport.Sub(parentProgress, 3); | |
354 } | |
355 progress.start(historyNodeCount); | |
356 } | |
357 public void once(HistoryNode n) throws HgCallbackTargetException, CancelledException { | |
358 handler.treeElement(ei.init(n, currentFileNode)); | |
359 progress.worked(1); | |
360 cancelHelper.checkCancelled(); | |
361 } | |
362 | |
363 public void switchTo(HgDataFile df) { | |
364 // from now on, use df in TreeElement | |
365 currentFileNode = df; | |
366 } | |
367 }; | |
368 final HandlerDispatcher dispatcher = new HandlerDispatcher(); | |
334 | 369 |
335 // renamed files in the queue are placed with respect to #iterateDirection | 370 // renamed files in the queue are placed with respect to #iterateDirection |
336 // i.e. if we iterate from new to old, recent filenames come first | 371 // i.e. if we iterate from new to old, recent filenames come first |
337 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); | 372 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); |
338 progressHelper.start(4 * fileRenamesQueue.size()); | 373 progressHelper.start(4 * fileRenamesQueue.size()); |
342 cancelHelper.checkCancelled(); | 377 cancelHelper.checkCancelled(); |
343 final List<HistoryNode> changeHistory = treeBuildInspector.go(renameInfo.first(), renameInfo.second()); | 378 final List<HistoryNode> changeHistory = treeBuildInspector.go(renameInfo.first(), renameInfo.second()); |
344 assert changeHistory.size() > 0; | 379 assert changeHistory.size() > 0; |
345 progressHelper.worked(1); | 380 progressHelper.worked(1); |
346 cancelHelper.checkCancelled(); | 381 cancelHelper.checkCancelled(); |
347 final ProgressSupport ph2; | 382 dispatcher.prepare(progressHelper, changeHistory.size(), treeBuildInspector); |
348 if (ei == null) { | |
349 // when follow is true, changeHistory.size() of the first revision might be quite short | |
350 // (e.g. bad fname recognized soon), hence ensure at least cache size at once | |
351 ei = new ElementImpl(Math.max(CACHE_CSET_IN_ADVANCE_THRESHOLD, changeHistory.size())); | |
352 } | |
353 if (changeHistory.size() < CACHE_CSET_IN_ADVANCE_THRESHOLD ) { | |
354 int[] commitRevisions = treeBuildInspector.getCommitRevisions(); | |
355 assert changeHistory.size() == commitRevisions.length; | |
356 // read bunch of changesets at once and cache 'em | |
357 ei.initTransform(); | |
358 repo.getChangelog().range(ei, commitRevisions); | |
359 progressHelper.worked(1); | |
360 ph2 = new ProgressSupport.Sub(progressHelper, 2); | |
361 } else { | |
362 ph2 = new ProgressSupport.Sub(progressHelper, 3); | |
363 } | |
364 ph2.start(changeHistory.size()); | |
365 if (lastFromPrevIteration != null) { | 383 if (lastFromPrevIteration != null) { |
366 if (iterateDirection == IterateDirection.FromOldToNew) { | 384 if (iterateDirection == IterateDirection.FromOldToNew) { |
367 // forward, from old to new: | 385 // forward, from old to new: |
368 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) | 386 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) |
369 // then A(n).bind(B(0)) | 387 // then A(n).bind(B(0)) |
370 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); // B(0) | 388 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); // B(0) |
371 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); // lastFromPrevIteration is A(n) | 389 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); // lastFromPrevIteration is A(n) |
372 changeHistory.add(0, lastFromPrevIteration); | 390 dispatcher.once(lastFromPrevIteration); |
373 if (renameHandler != null) { // shall report renames | 391 if (renameHandler != null) { // shall report renames |
374 assert namesIndex > 0; | 392 assert namesIndex > 0; |
375 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // A | 393 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // A |
376 copiedFrom = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, null); | 394 HgFileRevision copiedFrom = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, null); |
377 copiedTo = new HgFileRevision(renameInfo.first(), oldestOfTheNextChunk.fileRevision, copiedFrom.getPath()); | 395 HgFileRevision copiedTo = new HgFileRevision(renameInfo.first(), oldestOfTheNextChunk.fileRevision, copiedFrom.getPath()); |
378 shallReportRenameAfter1Step = true; // report rename after A(n) | 396 renameHandler.copy(copiedFrom, copiedTo); |
379 } | 397 } |
380 } else { | 398 } else { |
381 assert iterateDirection == IterateDirection.FromNewToOld; | 399 assert iterateDirection == IterateDirection.FromNewToOld; |
382 // A renamed to B. A(0..n) -> B(0..m). | 400 // A renamed to B. A(0..n) -> B(0..m). |
383 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)), report B(0), A(n)... | 401 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)), report B(0), A(n)... |
384 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) | 402 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) |
385 newestOfNextChunk.bindChild(lastFromPrevIteration); | 403 newestOfNextChunk.bindChild(lastFromPrevIteration); |
386 changeHistory.add(lastFromPrevIteration); | 404 dispatcher.once(lastFromPrevIteration); |
387 if (renameHandler != null) { | 405 if (renameHandler != null) { |
388 assert namesIndex > 0; | 406 assert namesIndex > 0; |
389 // renameInfo points to chunk of name A now, and lastFromPrevIteration (from namesIndex-1) is B | 407 // renameInfo points to chunk of name A now, and lastFromPrevIteration (from namesIndex-1) is B |
390 copiedFrom = new HgFileRevision(renameInfo.first(), newestOfNextChunk.fileRevision, null); | 408 HgFileRevision copiedFrom = new HgFileRevision(renameInfo.first(), newestOfNextChunk.fileRevision, null); |
391 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // B | 409 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // B |
392 copiedTo = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, copiedFrom.getPath()); | 410 HgFileRevision copiedTo = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, copiedFrom.getPath()); |
393 shallReportRenameAfter1Step = true; // report rename after B(0) | 411 renameHandler.copy(copiedFrom, copiedTo); |
394 } | 412 } |
395 } | 413 } |
396 } | 414 } |
397 if (namesIndex + 1 < renamesQueueSize) { | 415 if (namesIndex + 1 < renamesQueueSize) { |
398 // there's at least one more name we are going to look at, save | 416 // there's at least one more name we are going to look at, save |
407 lastFromPrevIteration = changeHistory.remove(0); | 425 lastFromPrevIteration = changeHistory.remove(0); |
408 } | 426 } |
409 } else { | 427 } else { |
410 lastFromPrevIteration = null; // just for the sake of no references to old items | 428 lastFromPrevIteration = null; // just for the sake of no references to old items |
411 } | 429 } |
430 dispatcher.switchTo(renameInfo.first()); | |
412 // XXX shall sort changeHistory according to changeset numbers? | 431 // XXX shall sort changeHistory according to changeset numbers? |
413 Iterator<HistoryNode> it; | 432 Iterator<HistoryNode> it; |
414 if (iterateDirection == IterateDirection.FromOldToNew) { | 433 if (iterateDirection == IterateDirection.FromOldToNew) { |
415 it = changeHistory.listIterator(); | 434 it = changeHistory.listIterator(); |
416 } else { | 435 } else { |
417 assert iterateDirection == IterateDirection.FromNewToOld; | 436 assert iterateDirection == IterateDirection.FromNewToOld; |
418 it = new ReverseIterator<HistoryNode>(changeHistory); | 437 it = new ReverseIterator<HistoryNode>(changeHistory); |
419 } | 438 } |
420 while(it.hasNext()) { | 439 while(it.hasNext()) { |
421 HistoryNode n = it.next(); | 440 HistoryNode n = it.next(); |
422 handler.treeElement(ei.init(n)); | 441 dispatcher.once(n); |
423 ph2.worked(1); | |
424 cancelHelper.checkCancelled(); | |
425 if (shallReportRenameAfter1Step) { | |
426 assert renameHandler != null; | |
427 assert copiedFrom != null; | |
428 assert copiedTo != null; | |
429 renameHandler.copy(copiedFrom, copiedTo); | |
430 shallReportRenameAfter1Step = false; | |
431 copiedFrom = copiedTo = null; | |
432 } | |
433 } | 442 } |
434 } // for fileRenamesQueue; | 443 } // for fileRenamesQueue; |
435 progressHelper.done(); | 444 progressHelper.done(); |
436 } | 445 } |
437 | 446 |
716 } | 725 } |
717 } | 726 } |
718 | 727 |
719 private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { | 728 private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { |
720 private HistoryNode historyNode; | 729 private HistoryNode historyNode; |
730 private HgDataFile fileNode; | |
721 private Pair<HgChangeset, HgChangeset> parents; | 731 private Pair<HgChangeset, HgChangeset> parents; |
722 private List<HgChangeset> children; | 732 private List<HgChangeset> children; |
723 private IntMap<HgChangeset> cachedChangesets; | 733 private IntMap<HgChangeset> cachedChangesets; |
724 private ChangesetTransformer.Transformation transform; | 734 private ChangesetTransformer.Transformation transform; |
725 private Nodeid changesetRevision; | 735 private Nodeid changesetRevision; |
728 | 738 |
729 public ElementImpl(int total) { | 739 public ElementImpl(int total) { |
730 cachedChangesets = new IntMap<HgChangeset>(total); | 740 cachedChangesets = new IntMap<HgChangeset>(total); |
731 } | 741 } |
732 | 742 |
733 ElementImpl init(HistoryNode n) { | 743 ElementImpl init(HistoryNode n, HgDataFile df) { |
734 historyNode = n; | 744 historyNode = n; |
745 fileNode = df; | |
735 parents = null; | 746 parents = null; |
736 children = null; | 747 children = null; |
737 changesetRevision = null; | 748 changesetRevision = null; |
738 parentRevisions = null; | 749 parentRevisions = null; |
739 childRevisions = null; | 750 childRevisions = null; |
740 return this; | 751 return this; |
741 } | 752 } |
742 | 753 |
743 public Nodeid fileRevision() { | 754 public Nodeid fileRevision() { |
744 return historyNode.fileRevision; | 755 return historyNode.fileRevision; |
756 } | |
757 | |
758 public HgDataFile file() { | |
759 return fileNode; | |
745 } | 760 } |
746 | 761 |
747 public HgChangeset changeset() { | 762 public HgChangeset changeset() { |
748 return get(historyNode.changeset)[0]; | 763 return get(historyNode.changeset)[0]; |
749 } | 764 } |