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; | 
