Mercurial > jhg
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 509:a30e74dca193
Establish parent-child between first and last elements of history chunks for two renamed files
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Thu, 13 Dec 2012 13:18:35 +0100 |
parents | ca5202afea90 |
children | 90093ee56c0d |
comparison
equal
deleted
inserted
replaced
508:ca5202afea90 | 509:a30e74dca193 |
---|---|
248 progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/); | 248 progressHelper.start(-1/*XXX enum const, or a dedicated method startUnspecified(). How about startAtLeast(int)?*/); |
249 HgDataFile fileNode = repo.getFileNode(file); | 249 HgDataFile fileNode = repo.getFileNode(file); |
250 if (!fileNode.exists()) { | 250 if (!fileNode.exists()) { |
251 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); | 251 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); |
252 } | 252 } |
253 // FIXME startRev and endRev ARE CHANGESET REVISIONS, not that of FILE!!! | |
253 fileNode.history(startRev, endRev, this); | 254 fileNode.history(startRev, endRev, this); |
254 csetTransform.checkFailure(); | 255 csetTransform.checkFailure(); |
255 if (fileNode.isCopy()) { | 256 if (fileNode.isCopy()) { |
256 // even if we do not follow history, report file rename | 257 // even if we do not follow history, report file rename |
257 do { | 258 do { |
303 final ProgressSupport progressHelper = getProgressSupport(handler); | 304 final ProgressSupport progressHelper = getProgressSupport(handler); |
304 final CancelSupport cancelHelper = getCancelSupport(handler, true); | 305 final CancelSupport cancelHelper = getCancelSupport(handler, true); |
305 | 306 |
306 // builds tree of nodes according to parents in file's revlog | 307 // builds tree of nodes according to parents in file's revlog |
307 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followHistory); | 308 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followHistory); |
309 // we iterate separate histories of each filename, need to connect | |
310 // last node of historyA with first node of historyB (A renamed to B case) | |
311 // to make overall history smooth. | |
312 HistoryNode lastFromPrevIteration = null; | |
313 | |
314 final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ | |
315 ElementImpl ei = null; | |
308 | 316 |
309 // most recent file is first in the queue | 317 // most recent file is first in the queue |
310 LinkedList<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); | 318 LinkedList<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); |
311 progressHelper.start(4 * fileRenamesQueue.size()); | 319 progressHelper.start(4 * fileRenamesQueue.size()); |
312 do { | 320 do { |
329 } | 337 } |
330 // else fall-through, assume lastRevision() is ok here | 338 // else fall-through, assume lastRevision() is ok here |
331 } | 339 } |
332 } | 340 } |
333 int fileLastRevIndexToVisit = fileLastRevToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevToVisit); | 341 int fileLastRevIndexToVisit = fileLastRevToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevToVisit); |
334 final HistoryNode[] changeHistory = treeBuildInspector.go(fileNode, fileLastRevIndexToVisit); | 342 final List<HistoryNode> changeHistory = treeBuildInspector.go(fileNode, fileLastRevIndexToVisit); |
335 int[] commitRevisions = treeBuildInspector.getCommitRevisions(); | 343 assert changeHistory.size() > 0; |
336 assert changeHistory.length == commitRevisions.length; | |
337 progressHelper.worked(1); | 344 progressHelper.worked(1); |
338 cancelHelper.checkCancelled(); | 345 cancelHelper.checkCancelled(); |
339 ElementImpl ei = new ElementImpl(commitRevisions.length); | |
340 final ProgressSupport ph2; | 346 final ProgressSupport ph2; |
341 if (commitRevisions.length < 100 /*XXX is it really worth it? */) { | 347 if (ei == null) { |
348 // when follow is true, changeHistory.size() of the first revision might be quite short | |
349 // (e.g. bad fname recognized soon), hence ensure at least cache size at once | |
350 ei = new ElementImpl(Math.max(CACHE_CSET_IN_ADVANCE_THRESHOLD, changeHistory.size())); | |
351 } | |
352 if (changeHistory.size() < CACHE_CSET_IN_ADVANCE_THRESHOLD ) { | |
353 int[] commitRevisions = treeBuildInspector.getCommitRevisions(); | |
354 assert changeHistory.size() == commitRevisions.length; | |
342 // read bunch of changesets at once and cache 'em | 355 // read bunch of changesets at once and cache 'em |
343 ei.initTransform(); | 356 ei.initTransform(); |
344 repo.getChangelog().range(ei, commitRevisions); | 357 repo.getChangelog().range(ei, commitRevisions); |
345 progressHelper.worked(1); | 358 progressHelper.worked(1); |
346 ph2 = new ProgressSupport.Sub(progressHelper, 2); | 359 ph2 = new ProgressSupport.Sub(progressHelper, 2); |
347 } else { | 360 } else { |
348 ph2 = new ProgressSupport.Sub(progressHelper, 3); | 361 ph2 = new ProgressSupport.Sub(progressHelper, 3); |
349 } | 362 } |
350 ph2.start(changeHistory.length); | 363 ph2.start(changeHistory.size()); |
351 // XXX shall sort completeHistory according to changeset numbers? | 364 if (lastFromPrevIteration != null) { |
352 for (int i = 0; i < changeHistory.length; i++ ) { | 365 // forward, from old to new: |
353 final HistoryNode n = changeHistory[i]; | 366 lastFromPrevIteration.bindChild(changeHistory.get(0)); |
367 changeHistory.add(0, lastFromPrevIteration); | |
368 } | |
369 if (!fileRenamesQueue.isEmpty()) { | |
370 lastFromPrevIteration = changeHistory.remove(changeHistory.size()-1); | |
371 } else { | |
372 lastFromPrevIteration = null; // just for the sake of no references to old items | |
373 } | |
374 // XXX shall sort changeHistory according to changeset numbers? | |
375 for (HistoryNode n : changeHistory) { | |
354 handler.treeElement(ei.init(n)); | 376 handler.treeElement(ei.init(n)); |
355 ph2.worked(1); | 377 ph2.worked(1); |
356 cancelHelper.checkCancelled(); | 378 cancelHelper.checkCancelled(); |
357 } | 379 } |
358 } while (!fileRenamesQueue.isEmpty()); | 380 } while (!fileRenamesQueue.isEmpty()); |
396 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { | 418 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { |
397 private final boolean followAncestry; | 419 private final boolean followAncestry; |
398 | 420 |
399 private HistoryNode[] completeHistory; | 421 private HistoryNode[] completeHistory; |
400 private int[] commitRevisions; | 422 private int[] commitRevisions; |
423 private List<HistoryNode> resultHistory; | |
401 | 424 |
402 TreeBuildInspector(boolean _followAncestry) { | 425 TreeBuildInspector(boolean _followAncestry) { |
403 followAncestry = _followAncestry; | 426 followAncestry = _followAncestry; |
404 } | 427 } |
405 | 428 |
420 | 443 |
421 /** | 444 /** |
422 * Builds history of file changes (in natural order, from oldest to newest) up to (and including) file revision specified. | 445 * Builds history of file changes (in natural order, from oldest to newest) up to (and including) file revision specified. |
423 * If {@link TreeBuildInspector} follows ancestry, only elements that are on the line of ancestry of the revision at | 446 * If {@link TreeBuildInspector} follows ancestry, only elements that are on the line of ancestry of the revision at |
424 * lastRevisionIndex would be included. | 447 * lastRevisionIndex would be included. |
448 * | |
449 * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list | |
450 * is modifiable (to further augment with last/first elements of renamed file histories) | |
425 */ | 451 */ |
426 HistoryNode[] go(HgDataFile fileNode, int lastRevisionIndex) throws HgInvalidControlFileException { | 452 List<HistoryNode> go(HgDataFile fileNode, int lastRevisionIndex) throws HgInvalidControlFileException { |
453 resultHistory = null; | |
427 completeHistory = new HistoryNode[lastRevisionIndex+1]; | 454 completeHistory = new HistoryNode[lastRevisionIndex+1]; |
428 commitRevisions = new int[completeHistory.length]; | 455 commitRevisions = new int[completeHistory.length]; |
429 fileNode.indexWalk(0, lastRevisionIndex, this); | 456 fileNode.indexWalk(0, lastRevisionIndex, this); |
430 if (!followAncestry) { | 457 if (!followAncestry) { |
431 return completeHistory; | 458 // in case when ancestor not followed, it's safe to return unmodifiable list |
459 resultHistory = Arrays.asList(completeHistory); | |
460 completeHistory = null; | |
461 // keep commitRevisions initialized, no need to recalculate them | |
462 // as they correspond 1:1 to resultHistory | |
463 return resultHistory; | |
432 } | 464 } |
433 /* | 465 /* |
434 * Changesets: | 466 * Changesets, newest at the top: |
435 * o <-- cset from working dir parent (as in dirstate), file not changed (file revision recorded points to that from A) | 467 * o <-- cset from working dir parent (as in dirstate), file not changed (file revision recorded points to that from A) |
436 * | x <-- revision with file changed (B') | 468 * | x <-- revision with file changed (B') |
437 * x / <-- revision with file changed (A) | 469 * x / <-- revision with file changed (A) |
438 * | x <-- revision with file changed (B) | 470 * | x <-- revision with file changed (B) |
439 * |/ | 471 * |/ |
462 } | 494 } |
463 if (withFileChange.parent2 != null) { | 495 if (withFileChange.parent2 != null) { |
464 queue.addLast(withFileChange.parent2); | 496 queue.addLast(withFileChange.parent2); |
465 } | 497 } |
466 } while (!queue.isEmpty()); | 498 } while (!queue.isEmpty()); |
467 HistoryNode[] strippedHistory = strippedHistoryList.toArray(new HistoryNode[strippedHistoryList.size()]); | |
468 completeHistory = null; | 499 completeHistory = null; |
469 commitRevisions = null; | 500 commitRevisions = null; |
470 // collected values are no longer valid - shall | 501 // collected values are no longer valid - shall |
471 // strip off elements for missing HistoryNodes, but it's easier just to re-create the array | 502 // strip off elements for missing HistoryNodes, but it's easier just to re-create the array |
472 commitRevisions = new int[strippedHistory.length]; | 503 // from resultHistory later, once (and if) needed |
473 for (int i = 0; i < commitRevisions.length; i++) { | 504 return resultHistory = strippedHistoryList; |
474 commitRevisions[i] = strippedHistory[i].changeset; | |
475 } | |
476 return strippedHistory; | |
477 } | 505 } |
478 | 506 |
479 /** | 507 /** |
480 * handy access to all HistoryNode[i].changeset values | 508 * handy access to all HistoryNode[i].changeset values |
481 */ | 509 */ |
482 int[] getCommitRevisions() { | 510 int[] getCommitRevisions() { |
511 if (commitRevisions == null) { | |
512 commitRevisions = new int[resultHistory.size()]; | |
513 int i = 0; | |
514 for (HistoryNode n : resultHistory) { | |
515 commitRevisions[i++] = n.changeset; | |
516 } | |
517 } | |
483 return commitRevisions; | 518 return commitRevisions; |
484 } | 519 } |
485 }; | 520 }; |
486 | 521 |
487 | 522 |
536 } | 571 } |
537 | 572 |
538 private static class HistoryNode { | 573 private static class HistoryNode { |
539 final int changeset; | 574 final int changeset; |
540 final Nodeid fileRevision; | 575 final Nodeid fileRevision; |
541 final HistoryNode parent1, parent2; | 576 HistoryNode parent1; // there's special case when we can alter it, see #bindChild() |
577 final HistoryNode parent2; | |
542 List<HistoryNode> children; | 578 List<HistoryNode> children; |
543 | 579 |
544 HistoryNode(int cs, Nodeid revision, HistoryNode p1, HistoryNode p2) { | 580 HistoryNode(int cs, Nodeid revision, HistoryNode p1, HistoryNode p2) { |
545 changeset = cs; | 581 changeset = cs; |
546 fileRevision = revision; | 582 fileRevision = revision; |
552 if (p2 != null) { | 588 if (p2 != null) { |
553 p2.addChild(this); | 589 p2.addChild(this); |
554 } | 590 } |
555 } | 591 } |
556 | 592 |
557 void addChild(HistoryNode child) { | 593 private void addChild(HistoryNode child) { |
558 if (children == null) { | 594 if (children == null) { |
559 children = new ArrayList<HistoryNode>(2); | 595 children = new ArrayList<HistoryNode>(2); |
560 } | 596 } |
561 children.add(child); | 597 children.add(child); |
598 } | |
599 | |
600 /** | |
601 * method to merge two history chunks for renamed file so that | |
602 * this node's history continues with that of child | |
603 * @param child | |
604 */ | |
605 public void bindChild(HistoryNode child) { | |
606 assert child.parent1 == null && child.parent2 == null; | |
607 // for the last element in history empty children are by construction: | |
608 // we don't iterate further than last element of interest in TreeBuildInspector#go | |
609 assert children == null || children.isEmpty(); | |
610 child.parent1 = this; | |
611 addChild(child); | |
562 } | 612 } |
563 } | 613 } |
564 | 614 |
565 private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { | 615 private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { |
566 private HistoryNode historyNode; | 616 private HistoryNode historyNode; |