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 }