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;