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