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 }