Mercurial > hg4j
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 510:90093ee56c0d
Full-fledged test repo to follow file history. Investigating iteration direction alternatives (from new to old in addition to existing old to new)
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Thu, 13 Dec 2012 15:46:40 +0100 |
| parents | a30e74dca193 |
| children | 122e0600799f |
comparison
equal
deleted
inserted
replaced
| 509:a30e74dca193 | 510:90093ee56c0d |
|---|---|
| 23 import java.util.Arrays; | 23 import java.util.Arrays; |
| 24 import java.util.Calendar; | 24 import java.util.Calendar; |
| 25 import java.util.Collection; | 25 import java.util.Collection; |
| 26 import java.util.Collections; | 26 import java.util.Collections; |
| 27 import java.util.ConcurrentModificationException; | 27 import java.util.ConcurrentModificationException; |
| 28 import java.util.Iterator; | |
| 28 import java.util.LinkedList; | 29 import java.util.LinkedList; |
| 29 import java.util.List; | 30 import java.util.List; |
| 31 import java.util.ListIterator; | |
| 30 import java.util.Set; | 32 import java.util.Set; |
| 31 import java.util.TreeSet; | 33 import java.util.TreeSet; |
| 32 | 34 |
| 33 import org.tmatesoft.hg.internal.IntMap; | 35 import org.tmatesoft.hg.internal.IntMap; |
| 34 import org.tmatesoft.hg.internal.IntVector; | 36 import org.tmatesoft.hg.internal.IntVector; |
| 312 HistoryNode lastFromPrevIteration = null; | 314 HistoryNode lastFromPrevIteration = null; |
| 313 | 315 |
| 314 final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ | 316 final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ |
| 315 ElementImpl ei = null; | 317 ElementImpl ei = null; |
| 316 | 318 |
| 317 // most recent file is first in the queue | 319 // renamed files in the queue are placed with respect to #iterateDirection |
| 320 // i.e. if we iterate from new to old, recent filenames come first | |
| 318 LinkedList<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); | 321 LinkedList<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); |
| 319 progressHelper.start(4 * fileRenamesQueue.size()); | 322 progressHelper.start(4 * fileRenamesQueue.size()); |
| 320 do { | 323 do { |
| 321 // to maintain iteration order from older to newer, take oldest name (in case of renames) first | 324 |
| 322 Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.removeLast(); | 325 Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.removeFirst(); |
| 323 cancelHelper.checkCancelled(); | 326 cancelHelper.checkCancelled(); |
| 324 HgDataFile fileNode = renameInfo.first(); | 327 HgDataFile fileNode = renameInfo.first(); |
| 325 Nodeid fileLastRevToVisit = null; | 328 Nodeid fileLastRevToVisit = null; |
| 326 if (followHistory) { | 329 if (followHistory) { |
| 327 fileLastRevToVisit = renameInfo.second(); | 330 fileLastRevToVisit = renameInfo.second(); |
| 328 if (fileLastRevToVisit == null) { | 331 if (fileLastRevToVisit == null) { |
| 329 // only recent file name should not have a changeset when rename has happened. | 332 // it's either first or last item in the queue, depending on iteration order |
| 330 assert fileRenamesQueue.isEmpty(); | 333 assert fileRenamesQueue.isEmpty() || /*awful way to find out it's first iteration*/ lastFromPrevIteration == null; |
| 331 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) | 334 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) |
| 332 // or in the HgDataFile (getWorkingCopyOriginRevision) | 335 // or in the HgDataFile (getWorkingCopyOriginRevision) |
| 333 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); | 336 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); |
| 334 if (!wdParentChangeset.isNull()) { | 337 if (!wdParentChangeset.isNull()) { |
| 335 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); | 338 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); |
| 360 } else { | 363 } else { |
| 361 ph2 = new ProgressSupport.Sub(progressHelper, 3); | 364 ph2 = new ProgressSupport.Sub(progressHelper, 3); |
| 362 } | 365 } |
| 363 ph2.start(changeHistory.size()); | 366 ph2.start(changeHistory.size()); |
| 364 if (lastFromPrevIteration != null) { | 367 if (lastFromPrevIteration != null) { |
| 365 // forward, from old to new: | 368 if (iterateDirection == IterateDirection.FromOldToNew) { |
| 366 lastFromPrevIteration.bindChild(changeHistory.get(0)); | 369 // forward, from old to new: |
| 367 changeHistory.add(0, lastFromPrevIteration); | 370 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) |
| 371 // then A(n).bind(B(0)) | |
| 372 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); | |
| 373 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); | |
| 374 changeHistory.add(0, lastFromPrevIteration); | |
| 375 } else { | |
| 376 assert iterateDirection == IterateDirection.FromNewToOld; | |
| 377 // A renamed to B. A(0..n) -> B(0..m). | |
| 378 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)) | |
| 379 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) | |
| 380 newestOfNextChunk.bindChild(lastFromPrevIteration); | |
| 381 changeHistory.add(lastFromPrevIteration); | |
| 382 } | |
| 368 } | 383 } |
| 369 if (!fileRenamesQueue.isEmpty()) { | 384 if (!fileRenamesQueue.isEmpty()) { |
| 370 lastFromPrevIteration = changeHistory.remove(changeHistory.size()-1); | 385 if (iterateDirection == IterateDirection.FromOldToNew) { |
| 386 // save newest, and exclude it from this iteration (postpone for next) | |
| 387 lastFromPrevIteration = changeHistory.remove(changeHistory.size()-1); | |
| 388 } else { | |
| 389 assert iterateDirection == IterateDirection.FromNewToOld; | |
| 390 // save oldest, and exclude it from thi iteration (postpone for next) | |
| 391 lastFromPrevIteration = changeHistory.remove(0); | |
| 392 } | |
| 371 } else { | 393 } else { |
| 372 lastFromPrevIteration = null; // just for the sake of no references to old items | 394 lastFromPrevIteration = null; // just for the sake of no references to old items |
| 373 } | 395 } |
| 374 // XXX shall sort changeHistory according to changeset numbers? | 396 // XXX shall sort changeHistory according to changeset numbers? |
| 375 for (HistoryNode n : changeHistory) { | 397 Iterator<HistoryNode> it; |
| 398 if (iterateDirection == IterateDirection.FromOldToNew) { | |
| 399 it = changeHistory.listIterator(); | |
| 400 } else { | |
| 401 assert iterateDirection == IterateDirection.FromNewToOld; | |
| 402 it = new ReverseIterator<HistoryNode>(changeHistory); | |
| 403 } | |
| 404 while(it.hasNext()) { | |
| 405 HistoryNode n = it.next(); | |
| 376 handler.treeElement(ei.init(n)); | 406 handler.treeElement(ei.init(n)); |
| 377 ph2.worked(1); | 407 ph2.worked(1); |
| 378 cancelHelper.checkCancelled(); | 408 cancelHelper.checkCancelled(); |
| 379 } | 409 } |
| 380 } while (!fileRenamesQueue.isEmpty()); | 410 } while (!fileRenamesQueue.isEmpty()); |
| 381 progressHelper.done(); | 411 progressHelper.done(); |
| 382 } | 412 } |
| 383 | 413 |
| 414 private IterateDirection iterateDirection = IterateDirection.FromOldToNew; | |
| 415 | |
| 416 private static class ReverseIterator<E> implements Iterator<E> { | |
| 417 private final ListIterator<E> listIterator; | |
| 418 | |
| 419 public ReverseIterator(List<E> list) { | |
| 420 listIterator = list.listIterator(list.size()); | |
| 421 } | |
| 422 | |
| 423 public boolean hasNext() { | |
| 424 return listIterator.hasPrevious(); | |
| 425 } | |
| 426 public E next() { | |
| 427 return listIterator.previous(); | |
| 428 } | |
| 429 public void remove() { | |
| 430 listIterator.remove(); | |
| 431 } | |
| 432 } | |
| 433 | |
| 384 /** | 434 /** |
| 385 * Follows file renames and build a list of all corresponding file nodes and revisions they were | 435 * Follows file renames and build a list of all corresponding file nodes and revisions they were |
| 386 * copied/renamed/branched at (IOW, their latest revision to look at). | 436 * copied/renamed/branched at (IOW, their latest revision to look at). |
| 387 * | 437 * |
| 388 * If {@link #followHistory} is <code>false</code>, the list contains one element only, | 438 * If {@link #followHistory} is <code>false</code>, the list contains one element only, |
| 391 * For the most recent file revision is null. | 441 * For the most recent file revision is null. |
| 392 * | 442 * |
| 393 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair | 443 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair |
| 394 * and possibly reuse this functionality | 444 * and possibly reuse this functionality |
| 395 * | 445 * |
| 396 * @return list of file renames, with most recent file first | 446 * @return list of file renames, ordered with respect to {@link #iterateDirection} |
| 397 */ | 447 */ |
| 398 private LinkedList<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() { | 448 private LinkedList<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() { |
| 399 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); | 449 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); |
| 400 if (!followHistory) { | 450 if (!followHistory) { |
| 401 rv.add(new Pair<HgDataFile, Nodeid>(repo.getFileNode(file), null)); | 451 rv.add(new Pair<HgDataFile, Nodeid>(repo.getFileNode(file), null)); |
| 404 Path fp = file; | 454 Path fp = file; |
| 405 Nodeid copyRev = null; | 455 Nodeid copyRev = null; |
| 406 boolean isCopy; | 456 boolean isCopy; |
| 407 do { | 457 do { |
| 408 HgDataFile fileNode = repo.getFileNode(fp); | 458 HgDataFile fileNode = repo.getFileNode(fp); |
| 409 rv.addLast(new Pair<HgDataFile, Nodeid>(fileNode, copyRev)); | 459 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, copyRev); |
| 460 if (iterateDirection == IterateDirection.FromOldToNew) { | |
| 461 rv.addFirst(p); | |
| 462 } else { | |
| 463 assert iterateDirection == IterateDirection.FromNewToOld; | |
| 464 rv.addLast(p); | |
| 465 } | |
| 410 if (isCopy = fileNode.isCopy()) { | 466 if (isCopy = fileNode.isCopy()) { |
| 411 fp = fileNode.getCopySourceName(); | 467 fp = fileNode.getCopySourceName(); |
| 412 copyRev = fileNode.getCopySourceRevision(); | 468 copyRev = fileNode.getCopySourceRevision(); |
| 413 } | 469 } |
| 414 } while (isCopy); | 470 } while (isCopy); |
| 792 } else { | 848 } else { |
| 793 return repo.getChangelog().getRevision(changelogRevisionNumber); | 849 return repo.getChangelog().getRevision(changelogRevisionNumber); |
| 794 } | 850 } |
| 795 } | 851 } |
| 796 } | 852 } |
| 853 | |
| 854 private enum IterateDirection { | |
| 855 FromOldToNew, FromNewToOld | |
| 856 } | |
| 797 } | 857 } |
