Mercurial > jhg
comparison src/org/tmatesoft/hg/core/HgLogCommand.java @ 514:5dcb4581c8ef
Report renames when following file history tree with HgFileRenameHandlerMixin
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Mon, 17 Dec 2012 19:06:07 +0100 |
| parents | 122e0600799f |
| children | e6c8b9b654b2 |
comparison
equal
deleted
inserted
replaced
| 513:a41d955dc360 | 514:5dcb4581c8ef |
|---|---|
| 43 import org.tmatesoft.hg.repo.HgInvalidStateException; | 43 import org.tmatesoft.hg.repo.HgInvalidStateException; |
| 44 import org.tmatesoft.hg.repo.HgParentChildMap; | 44 import org.tmatesoft.hg.repo.HgParentChildMap; |
| 45 import org.tmatesoft.hg.repo.HgRepository; | 45 import org.tmatesoft.hg.repo.HgRepository; |
| 46 import org.tmatesoft.hg.repo.HgRuntimeException; | 46 import org.tmatesoft.hg.repo.HgRuntimeException; |
| 47 import org.tmatesoft.hg.repo.HgStatusCollector; | 47 import org.tmatesoft.hg.repo.HgStatusCollector; |
| 48 import org.tmatesoft.hg.util.Adaptable; | |
| 48 import org.tmatesoft.hg.util.CancelSupport; | 49 import org.tmatesoft.hg.util.CancelSupport; |
| 49 import org.tmatesoft.hg.util.CancelledException; | 50 import org.tmatesoft.hg.util.CancelledException; |
| 50 import org.tmatesoft.hg.util.Pair; | 51 import org.tmatesoft.hg.util.Pair; |
| 51 import org.tmatesoft.hg.util.Path; | 52 import org.tmatesoft.hg.util.Path; |
| 52 import org.tmatesoft.hg.util.ProgressSupport; | 53 import org.tmatesoft.hg.util.ProgressSupport; |
| 71 private Set<String> branches; | 72 private Set<String> branches; |
| 72 private int limit = 0, count = 0; | 73 private int limit = 0, count = 0; |
| 73 private int startRev = 0, endRev = TIP; | 74 private int startRev = 0, endRev = TIP; |
| 74 private Calendar date; | 75 private Calendar date; |
| 75 private Path file; | 76 private Path file; |
| 76 private boolean followHistory; // makes sense only when file != null | 77 /* |
| 78 * Whether to iterate file origins, if any. | |
| 79 * Makes sense only when file != null | |
| 80 */ | |
| 81 private boolean followRenames; | |
| 82 /* | |
| 83 * Whether to track history of the selected file version (based on file revision | |
| 84 * in working dir parent), follow ancestors only. | |
| 85 * Note, 'hg log --follow' combines both #followHistory and #followAncestry | |
| 86 */ | |
| 87 private boolean followAncestry; | |
| 77 private ChangesetTransformer csetTransform; | 88 private ChangesetTransformer csetTransform; |
| 78 private HgParentChildMap<HgChangelog> parentHelper; | 89 private HgParentChildMap<HgChangelog> parentHelper; |
| 79 | 90 |
| 80 public HgLogCommand(HgRepository hgRepo) { | 91 public HgLogCommand(HgRepository hgRepo) { |
| 81 repo = hgRepo; | 92 repo = hgRepo; |
| 182 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. | 193 * @param followCopyRename true to report changesets of the original file(-s), if copy/rename ever occured to the file. |
| 183 */ | 194 */ |
| 184 public HgLogCommand file(Path file, boolean followCopyRename) { | 195 public HgLogCommand file(Path file, boolean followCopyRename) { |
| 185 // multiple? Bad idea, would need to include extra method into Handler to tell start of next file | 196 // multiple? Bad idea, would need to include extra method into Handler to tell start of next file |
| 186 this.file = file; | 197 this.file = file; |
| 187 followHistory = followCopyRename; | 198 followRenames = followAncestry = followCopyRename; |
| 188 return this; | 199 return this; |
| 189 } | 200 } |
| 190 | 201 |
| 191 /** | 202 /** |
| 192 * Handy analog of {@link #file(Path, boolean)} when clients' paths come from filesystem and need conversion to repository's | 203 * Handy analog of {@link #file(Path, boolean)} when clients' paths come from filesystem and need conversion to repository's |
| 254 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); | 265 throw new HgPathNotFoundException(String.format("File %s not found in the repository", file), file); |
| 255 } | 266 } |
| 256 // FIXME startRev and endRev ARE CHANGESET REVISIONS, not that of FILE!!! | 267 // FIXME startRev and endRev ARE CHANGESET REVISIONS, not that of FILE!!! |
| 257 fileNode.history(startRev, endRev, this); | 268 fileNode.history(startRev, endRev, this); |
| 258 csetTransform.checkFailure(); | 269 csetTransform.checkFailure(); |
| 270 final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); | |
| 259 if (fileNode.isCopy()) { | 271 if (fileNode.isCopy()) { |
| 260 // even if we do not follow history, report file rename | 272 // even if we do not follow history, report file rename |
| 261 do { | 273 do { |
| 262 if (handler instanceof HgChangesetHandler.WithCopyHistory) { | 274 if (withCopyHandler != null) { |
| 263 HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), null, fileNode.getCopySourceName()); | 275 HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), null, fileNode.getCopySourceName()); |
| 264 HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), null, fileNode.getPath(), src.getPath()); | 276 HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), null, fileNode.getPath(), src.getPath()); |
| 265 ((HgChangesetHandler.WithCopyHistory) handler).copy(src, dst); | 277 withCopyHandler.copy(src, dst); |
| 266 } | 278 } |
| 267 if (limit > 0 && count >= limit) { | 279 if (limit > 0 && count >= limit) { |
| 268 // if limit reach, follow is useless. | 280 // if limit reach, follow is useless. |
| 269 break; | 281 break; |
| 270 } | 282 } |
| 271 if (followHistory) { | 283 if (followRenames) { |
| 272 fileNode = repo.getFileNode(fileNode.getCopySourceName()); | 284 fileNode = repo.getFileNode(fileNode.getCopySourceName()); |
| 273 fileNode.history(this); | 285 fileNode.history(this); |
| 274 csetTransform.checkFailure(); | 286 csetTransform.checkFailure(); |
| 275 } | 287 } |
| 276 } while (followHistory && fileNode.isCopy()); | 288 } while (followRenames && fileNode.isCopy()); |
| 277 } | 289 } |
| 278 } | 290 } |
| 279 } catch (HgRuntimeException ex) { | 291 } catch (HgRuntimeException ex) { |
| 280 throw new HgLibraryFailureException(ex); | 292 throw new HgLibraryFailureException(ex); |
| 281 } finally { | 293 } finally { |
| 304 if (file == null) { | 316 if (file == null) { |
| 305 throw new IllegalArgumentException("History tree is supported for files only (at least now), please specify file"); | 317 throw new IllegalArgumentException("History tree is supported for files only (at least now), please specify file"); |
| 306 } | 318 } |
| 307 final ProgressSupport progressHelper = getProgressSupport(handler); | 319 final ProgressSupport progressHelper = getProgressSupport(handler); |
| 308 final CancelSupport cancelHelper = getCancelSupport(handler, true); | 320 final CancelSupport cancelHelper = getCancelSupport(handler, true); |
| 321 final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); | |
| 309 | 322 |
| 310 // builds tree of nodes according to parents in file's revlog | 323 // builds tree of nodes according to parents in file's revlog |
| 311 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followHistory); | 324 final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followRenames); |
| 312 // we iterate separate histories of each filename, need to connect | 325 // we iterate separate histories of each filename, need to connect |
| 313 // last node of historyA with first node of historyB (A renamed to B case) | 326 // last node of historyA with first node of historyB (A renamed to B case) |
| 314 // to make overall history smooth. | 327 // to make overall history smooth. |
| 315 HistoryNode lastFromPrevIteration = null; | 328 HistoryNode lastFromPrevIteration = null; |
| 329 HgFileRevision copiedFrom = null, copiedTo = null; | |
| 330 boolean shallReportRenameAfter1Step = false; | |
| 316 | 331 |
| 317 final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ | 332 final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ |
| 318 ElementImpl ei = null; | 333 ElementImpl ei = null; |
| 319 | 334 |
| 320 // renamed files in the queue are placed with respect to #iterateDirection | 335 // renamed files in the queue are placed with respect to #iterateDirection |
| 321 // i.e. if we iterate from new to old, recent filenames come first | 336 // i.e. if we iterate from new to old, recent filenames come first |
| 322 LinkedList<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); | 337 List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); |
| 323 progressHelper.start(4 * fileRenamesQueue.size()); | 338 progressHelper.start(4 * fileRenamesQueue.size()); |
| 324 do { | 339 for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) { |
| 325 | 340 |
| 326 Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.removeFirst(); | 341 final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex); |
| 327 cancelHelper.checkCancelled(); | 342 cancelHelper.checkCancelled(); |
| 328 HgDataFile fileNode = renameInfo.first(); | 343 final List<HistoryNode> changeHistory = treeBuildInspector.go(renameInfo.first(), renameInfo.second()); |
| 329 Nodeid fileLastRevToVisit = null; | |
| 330 if (followHistory) { | |
| 331 fileLastRevToVisit = renameInfo.second(); | |
| 332 if (fileLastRevToVisit == null) { | |
| 333 // it's either first or last item in the queue, depending on iteration order | |
| 334 assert fileRenamesQueue.isEmpty() || /*awful way to find out it's first iteration*/ lastFromPrevIteration == null; | |
| 335 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) | |
| 336 // or in the HgDataFile (getWorkingCopyOriginRevision) | |
| 337 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); | |
| 338 if (!wdParentChangeset.isNull()) { | |
| 339 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); | |
| 340 fileLastRevToVisit = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath()); | |
| 341 } | |
| 342 // else fall-through, assume lastRevision() is ok here | |
| 343 } | |
| 344 } | |
| 345 int fileLastRevIndexToVisit = fileLastRevToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevToVisit); | |
| 346 final List<HistoryNode> changeHistory = treeBuildInspector.go(fileNode, fileLastRevIndexToVisit); | |
| 347 assert changeHistory.size() > 0; | 344 assert changeHistory.size() > 0; |
| 348 progressHelper.worked(1); | 345 progressHelper.worked(1); |
| 349 cancelHelper.checkCancelled(); | 346 cancelHelper.checkCancelled(); |
| 350 final ProgressSupport ph2; | 347 final ProgressSupport ph2; |
| 351 if (ei == null) { | 348 if (ei == null) { |
| 368 if (lastFromPrevIteration != null) { | 365 if (lastFromPrevIteration != null) { |
| 369 if (iterateDirection == IterateDirection.FromOldToNew) { | 366 if (iterateDirection == IterateDirection.FromOldToNew) { |
| 370 // forward, from old to new: | 367 // forward, from old to new: |
| 371 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) | 368 // A(0..n) -> B(0..m). First, report A(0)..A(n-1) |
| 372 // then A(n).bind(B(0)) | 369 // then A(n).bind(B(0)) |
| 373 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); | 370 HistoryNode oldestOfTheNextChunk = changeHistory.get(0); // B(0) |
| 374 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); | 371 lastFromPrevIteration.bindChild(oldestOfTheNextChunk); // lastFromPrevIteration is A(n) |
| 375 changeHistory.add(0, lastFromPrevIteration); | 372 changeHistory.add(0, lastFromPrevIteration); |
| 373 if (renameHandler != null) { // shall report renames | |
| 374 assert namesIndex > 0; | |
| 375 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // A | |
| 376 copiedFrom = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, null); | |
| 377 copiedTo = new HgFileRevision(renameInfo.first(), oldestOfTheNextChunk.fileRevision, copiedFrom.getPath()); | |
| 378 shallReportRenameAfter1Step = true; // report rename after A(n) | |
| 379 } | |
| 376 } else { | 380 } else { |
| 377 assert iterateDirection == IterateDirection.FromNewToOld; | 381 assert iterateDirection == IterateDirection.FromNewToOld; |
| 378 // A renamed to B. A(0..n) -> B(0..m). | 382 // A renamed to B. A(0..n) -> B(0..m). |
| 379 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)) | 383 // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)), report B(0), A(n)... |
| 380 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) | 384 HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) |
| 381 newestOfNextChunk.bindChild(lastFromPrevIteration); | 385 newestOfNextChunk.bindChild(lastFromPrevIteration); |
| 382 changeHistory.add(lastFromPrevIteration); | 386 changeHistory.add(lastFromPrevIteration); |
| 383 } | 387 if (renameHandler != null) { |
| 384 } | 388 assert namesIndex > 0; |
| 385 if (!fileRenamesQueue.isEmpty()) { | 389 // renameInfo points to chunk of name A now, and lastFromPrevIteration (from namesIndex-1) is B |
| 390 copiedFrom = new HgFileRevision(renameInfo.first(), newestOfNextChunk.fileRevision, null); | |
| 391 HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // B | |
| 392 copiedTo = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, copiedFrom.getPath()); | |
| 393 shallReportRenameAfter1Step = true; // report rename after B(0) | |
| 394 } | |
| 395 } | |
| 396 } | |
| 397 if (namesIndex + 1 < renamesQueueSize) { | |
| 398 // there's at least one more name we are going to look at, save | |
| 399 // one element for later binding | |
| 400 // | |
| 386 if (iterateDirection == IterateDirection.FromOldToNew) { | 401 if (iterateDirection == IterateDirection.FromOldToNew) { |
| 387 // save newest, and exclude it from this iteration (postpone for next) | 402 // save newest, and exclude it from this iteration (postpone for next) |
| 388 lastFromPrevIteration = changeHistory.remove(changeHistory.size()-1); | 403 lastFromPrevIteration = changeHistory.remove(changeHistory.size()-1); |
| 389 } else { | 404 } else { |
| 390 assert iterateDirection == IterateDirection.FromNewToOld; | 405 assert iterateDirection == IterateDirection.FromNewToOld; |
| 391 // save oldest, and exclude it from thi iteration (postpone for next) | 406 // save oldest, and exclude it from this iteration (postpone for next) |
| 392 lastFromPrevIteration = changeHistory.remove(0); | 407 lastFromPrevIteration = changeHistory.remove(0); |
| 393 } | 408 } |
| 394 } else { | 409 } else { |
| 395 lastFromPrevIteration = null; // just for the sake of no references to old items | 410 lastFromPrevIteration = null; // just for the sake of no references to old items |
| 396 } | 411 } |
| 405 while(it.hasNext()) { | 420 while(it.hasNext()) { |
| 406 HistoryNode n = it.next(); | 421 HistoryNode n = it.next(); |
| 407 handler.treeElement(ei.init(n)); | 422 handler.treeElement(ei.init(n)); |
| 408 ph2.worked(1); | 423 ph2.worked(1); |
| 409 cancelHelper.checkCancelled(); | 424 cancelHelper.checkCancelled(); |
| 410 } | 425 if (shallReportRenameAfter1Step) { |
| 411 } while (!fileRenamesQueue.isEmpty()); | 426 assert renameHandler != null; |
| 427 assert copiedFrom != null; | |
| 428 assert copiedTo != null; | |
| 429 renameHandler.copy(copiedFrom, copiedTo); | |
| 430 shallReportRenameAfter1Step = false; | |
| 431 copiedFrom = copiedTo = null; | |
| 432 } | |
| 433 } | |
| 434 } // for fileRenamesQueue; | |
| 412 progressHelper.done(); | 435 progressHelper.done(); |
| 413 } | 436 } |
| 414 | 437 |
| 415 private IterateDirection iterateDirection = IterateDirection.FromOldToNew; | 438 private IterateDirection iterateDirection = IterateDirection.FromOldToNew; |
| 416 | 439 |
| 434 | 457 |
| 435 /** | 458 /** |
| 436 * Follows file renames and build a list of all corresponding file nodes and revisions they were | 459 * Follows file renames and build a list of all corresponding file nodes and revisions they were |
| 437 * copied/renamed/branched at (IOW, their latest revision to look at). | 460 * copied/renamed/branched at (IOW, their latest revision to look at). |
| 438 * | 461 * |
| 439 * If {@link #followHistory} is <code>false</code>, the list contains one element only, | 462 * If {@link #followRenames} is <code>false</code>, the list contains one element only, |
| 440 * file node with the name of the file as it was specified by the user. | 463 * file node with the name of the file as it was specified by the user. |
| 441 * | 464 * |
| 442 * For the most recent file revision is null. | 465 * For the most recent file revision depends on {@link #followAncestry}, and is file revision from working copy parent |
| 466 * in it's true. <code>null</code> indicates file's TIP revision shall be used. | |
| 443 * | 467 * |
| 444 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair | 468 * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair |
| 445 * and possibly reuse this functionality | 469 * and possibly reuse this functionality |
| 446 * | 470 * |
| 447 * @return list of file renames, ordered with respect to {@link #iterateDirection} | 471 * @return list of file renames, ordered with respect to {@link #iterateDirection} |
| 448 */ | 472 */ |
| 449 private LinkedList<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() { | 473 private List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() { |
| 450 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); | 474 LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); |
| 451 if (!followHistory) { | 475 Nodeid startRev = null; |
| 452 rv.add(new Pair<HgDataFile, Nodeid>(repo.getFileNode(file), null)); | 476 HgDataFile fileNode = repo.getFileNode(file); |
| 477 if (followAncestry) { | |
| 478 // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) | |
| 479 // or in the HgDataFile (getWorkingCopyOriginRevision) | |
| 480 Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); | |
| 481 if (!wdParentChangeset.isNull()) { | |
| 482 int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); | |
| 483 startRev = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath()); | |
| 484 } | |
| 485 // else fall-through, assume null (eventually, lastRevision()) is ok here | |
| 486 } | |
| 487 rv.add(new Pair<HgDataFile, Nodeid>(fileNode, startRev)); | |
| 488 if (!followRenames) { | |
| 453 return rv; | 489 return rv; |
| 454 } | 490 } |
| 455 Path fp = file; | 491 while (fileNode.isCopy()) { |
| 456 Nodeid copyRev = null; | 492 Path fp = fileNode.getCopySourceName(); |
| 457 boolean isCopy; | 493 Nodeid copyRev = fileNode.getCopySourceRevision(); |
| 458 do { | 494 fileNode = repo.getFileNode(fp); |
| 459 HgDataFile fileNode = repo.getFileNode(fp); | |
| 460 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, copyRev); | 495 Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, copyRev); |
| 461 if (iterateDirection == IterateDirection.FromOldToNew) { | 496 if (iterateDirection == IterateDirection.FromOldToNew) { |
| 462 rv.addFirst(p); | 497 rv.addFirst(p); |
| 463 } else { | 498 } else { |
| 464 assert iterateDirection == IterateDirection.FromNewToOld; | 499 assert iterateDirection == IterateDirection.FromNewToOld; |
| 465 rv.addLast(p); | 500 rv.addLast(p); |
| 466 } | 501 } |
| 467 if (isCopy = fileNode.isCopy()) { | 502 }; |
| 468 fp = fileNode.getCopySourceName(); | |
| 469 copyRev = fileNode.getCopySourceRevision(); | |
| 470 } | |
| 471 } while (isCopy); | |
| 472 return rv; | 503 return rv; |
| 473 } | 504 } |
| 474 | 505 |
| 475 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { | 506 private static class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { |
| 476 private final boolean followAncestry; | 507 private final boolean followAncestry; |
| 504 * lastRevisionIndex would be included. | 535 * lastRevisionIndex would be included. |
| 505 * | 536 * |
| 506 * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list | 537 * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list |
| 507 * is modifiable (to further augment with last/first elements of renamed file histories) | 538 * is modifiable (to further augment with last/first elements of renamed file histories) |
| 508 */ | 539 */ |
| 509 List<HistoryNode> go(HgDataFile fileNode, int lastRevisionIndex) throws HgInvalidControlFileException { | 540 List<HistoryNode> go(HgDataFile fileNode, Nodeid fileLastRevisionToVisit) throws HgInvalidControlFileException { |
| 510 resultHistory = null; | 541 resultHistory = null; |
| 511 completeHistory = new HistoryNode[lastRevisionIndex+1]; | 542 int fileLastRevIndexToVisit = fileLastRevisionToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevisionToVisit); |
| 543 completeHistory = new HistoryNode[fileLastRevIndexToVisit+1]; | |
| 512 commitRevisions = new int[completeHistory.length]; | 544 commitRevisions = new int[completeHistory.length]; |
| 513 fileNode.indexWalk(0, lastRevisionIndex, this); | 545 fileNode.indexWalk(0, fileLastRevIndexToVisit, this); |
| 514 if (!followAncestry) { | 546 if (!followAncestry) { |
| 515 // in case when ancestor not followed, it's safe to return unmodifiable list | 547 // in case when ancestor not followed, it's safe to return unmodifiable list |
| 516 resultHistory = Arrays.asList(completeHistory); | 548 resultHistory = Arrays.asList(completeHistory); |
| 517 completeHistory = null; | 549 completeHistory = null; |
| 518 // keep commitRevisions initialized, no need to recalculate them | 550 // keep commitRevisions initialized, no need to recalculate them |
| 537 // strippedHistory: only those HistoryNodes from completeHistory that are on the same | 569 // strippedHistory: only those HistoryNodes from completeHistory that are on the same |
| 538 // line of descendant, in order from older to newer | 570 // line of descendant, in order from older to newer |
| 539 LinkedList<HistoryNode> strippedHistoryList = new LinkedList<HistoryNode>(); | 571 LinkedList<HistoryNode> strippedHistoryList = new LinkedList<HistoryNode>(); |
| 540 LinkedList<HistoryNode> queue = new LinkedList<HistoryNode>(); | 572 LinkedList<HistoryNode> queue = new LinkedList<HistoryNode>(); |
| 541 // look for ancestors of the selected history node | 573 // look for ancestors of the selected history node |
| 542 queue.add(completeHistory[lastRevisionIndex]); | 574 queue.add(completeHistory[fileLastRevIndexToVisit]); |
| 543 do { | 575 do { |
| 544 HistoryNode withFileChange = queue.removeFirst(); | 576 HistoryNode withFileChange = queue.removeFirst(); |
| 545 if (strippedHistoryList.contains(withFileChange)) { | 577 if (strippedHistoryList.contains(withFileChange)) { |
| 546 // fork point for the change that was later merged (and we traced | 578 // fork point for the change that was later merged (and we traced |
| 547 // both lines of development by now. | 579 // both lines of development by now. |
