Mercurial > jhg
diff 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 |
line wrap: on
line diff
--- a/src/org/tmatesoft/hg/core/HgLogCommand.java Mon Dec 17 15:01:57 2012 +0100 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Mon Dec 17 19:06:07 2012 +0100 @@ -45,6 +45,7 @@ import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.repo.HgRuntimeException; import org.tmatesoft.hg.repo.HgStatusCollector; +import org.tmatesoft.hg.util.Adaptable; import org.tmatesoft.hg.util.CancelSupport; import org.tmatesoft.hg.util.CancelledException; import org.tmatesoft.hg.util.Pair; @@ -73,7 +74,17 @@ private int startRev = 0, endRev = TIP; private Calendar date; private Path file; - private boolean followHistory; // makes sense only when file != null + /* + * Whether to iterate file origins, if any. + * Makes sense only when file != null + */ + private boolean followRenames; + /* + * Whether to track history of the selected file version (based on file revision + * in working dir parent), follow ancestors only. + * Note, 'hg log --follow' combines both #followHistory and #followAncestry + */ + private boolean followAncestry; private ChangesetTransformer csetTransform; private HgParentChildMap<HgChangelog> parentHelper; @@ -184,7 +195,7 @@ public HgLogCommand file(Path file, boolean followCopyRename) { // multiple? Bad idea, would need to include extra method into Handler to tell start of next file this.file = file; - followHistory = followCopyRename; + followRenames = followAncestry = followCopyRename; return this; } @@ -256,24 +267,25 @@ // FIXME startRev and endRev ARE CHANGESET REVISIONS, not that of FILE!!! fileNode.history(startRev, endRev, this); csetTransform.checkFailure(); + final HgFileRenameHandlerMixin withCopyHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); if (fileNode.isCopy()) { // even if we do not follow history, report file rename do { - if (handler instanceof HgChangesetHandler.WithCopyHistory) { + if (withCopyHandler != null) { HgFileRevision src = new HgFileRevision(repo, fileNode.getCopySourceRevision(), null, fileNode.getCopySourceName()); HgFileRevision dst = new HgFileRevision(repo, fileNode.getRevision(0), null, fileNode.getPath(), src.getPath()); - ((HgChangesetHandler.WithCopyHistory) handler).copy(src, dst); + withCopyHandler.copy(src, dst); } if (limit > 0 && count >= limit) { // if limit reach, follow is useless. break; } - if (followHistory) { + if (followRenames) { fileNode = repo.getFileNode(fileNode.getCopySourceName()); fileNode.history(this); csetTransform.checkFailure(); } - } while (followHistory && fileNode.isCopy()); + } while (followRenames && fileNode.isCopy()); } } } catch (HgRuntimeException ex) { @@ -306,44 +318,29 @@ } final ProgressSupport progressHelper = getProgressSupport(handler); final CancelSupport cancelHelper = getCancelSupport(handler, true); + final HgFileRenameHandlerMixin renameHandler = Adaptable.Factory.getAdapter(handler, HgFileRenameHandlerMixin.class, null); // builds tree of nodes according to parents in file's revlog - final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followHistory); + final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(followRenames); // we iterate separate histories of each filename, need to connect // last node of historyA with first node of historyB (A renamed to B case) // to make overall history smooth. HistoryNode lastFromPrevIteration = null; + HgFileRevision copiedFrom = null, copiedTo = null; + boolean shallReportRenameAfter1Step = false; final int CACHE_CSET_IN_ADVANCE_THRESHOLD = 100; /* XXX is it really worth it? */ ElementImpl ei = null; // renamed files in the queue are placed with respect to #iterateDirection // i.e. if we iterate from new to old, recent filenames come first - LinkedList<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); + List<Pair<HgDataFile, Nodeid>> fileRenamesQueue = buildFileRenamesQueue(); progressHelper.start(4 * fileRenamesQueue.size()); - do { + for (int namesIndex = 0, renamesQueueSize = fileRenamesQueue.size(); namesIndex < renamesQueueSize; namesIndex++) { - Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.removeFirst(); + final Pair<HgDataFile, Nodeid> renameInfo = fileRenamesQueue.get(namesIndex); cancelHelper.checkCancelled(); - HgDataFile fileNode = renameInfo.first(); - Nodeid fileLastRevToVisit = null; - if (followHistory) { - fileLastRevToVisit = renameInfo.second(); - if (fileLastRevToVisit == null) { - // it's either first or last item in the queue, depending on iteration order - assert fileRenamesQueue.isEmpty() || /*awful way to find out it's first iteration*/ lastFromPrevIteration == null; - // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) - // or in the HgDataFile (getWorkingCopyOriginRevision) - Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); - if (!wdParentChangeset.isNull()) { - int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); - fileLastRevToVisit = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath()); - } - // else fall-through, assume lastRevision() is ok here - } - } - int fileLastRevIndexToVisit = fileLastRevToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevToVisit); - final List<HistoryNode> changeHistory = treeBuildInspector.go(fileNode, fileLastRevIndexToVisit); + final List<HistoryNode> changeHistory = treeBuildInspector.go(renameInfo.first(), renameInfo.second()); assert changeHistory.size() > 0; progressHelper.worked(1); cancelHelper.checkCancelled(); @@ -370,25 +367,43 @@ // forward, from old to new: // A(0..n) -> B(0..m). First, report A(0)..A(n-1) // then A(n).bind(B(0)) - HistoryNode oldestOfTheNextChunk = changeHistory.get(0); - lastFromPrevIteration.bindChild(oldestOfTheNextChunk); + HistoryNode oldestOfTheNextChunk = changeHistory.get(0); // B(0) + lastFromPrevIteration.bindChild(oldestOfTheNextChunk); // lastFromPrevIteration is A(n) changeHistory.add(0, lastFromPrevIteration); + if (renameHandler != null) { // shall report renames + assert namesIndex > 0; + HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // A + copiedFrom = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, null); + copiedTo = new HgFileRevision(renameInfo.first(), oldestOfTheNextChunk.fileRevision, copiedFrom.getPath()); + shallReportRenameAfter1Step = true; // report rename after A(n) + } } else { assert iterateDirection == IterateDirection.FromNewToOld; // A renamed to B. A(0..n) -> B(0..m). - // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)) + // First, report B(m), B(m-1)...B(1), then A(n).bind(B(0)), report B(0), A(n)... HistoryNode newestOfNextChunk = changeHistory.get(changeHistory.size() - 1); // A(n) newestOfNextChunk.bindChild(lastFromPrevIteration); changeHistory.add(lastFromPrevIteration); + if (renameHandler != null) { + assert namesIndex > 0; + // renameInfo points to chunk of name A now, and lastFromPrevIteration (from namesIndex-1) is B + copiedFrom = new HgFileRevision(renameInfo.first(), newestOfNextChunk.fileRevision, null); + HgDataFile lastIterationFileNode = fileRenamesQueue.get(namesIndex-1).first(); // B + copiedTo = new HgFileRevision(lastIterationFileNode, lastFromPrevIteration.fileRevision, copiedFrom.getPath()); + shallReportRenameAfter1Step = true; // report rename after B(0) + } } } - if (!fileRenamesQueue.isEmpty()) { + if (namesIndex + 1 < renamesQueueSize) { + // there's at least one more name we are going to look at, save + // one element for later binding + // if (iterateDirection == IterateDirection.FromOldToNew) { // save newest, and exclude it from this iteration (postpone for next) lastFromPrevIteration = changeHistory.remove(changeHistory.size()-1); } else { assert iterateDirection == IterateDirection.FromNewToOld; - // save oldest, and exclude it from thi iteration (postpone for next) + // save oldest, and exclude it from this iteration (postpone for next) lastFromPrevIteration = changeHistory.remove(0); } } else { @@ -407,8 +422,16 @@ handler.treeElement(ei.init(n)); ph2.worked(1); cancelHelper.checkCancelled(); + if (shallReportRenameAfter1Step) { + assert renameHandler != null; + assert copiedFrom != null; + assert copiedTo != null; + renameHandler.copy(copiedFrom, copiedTo); + shallReportRenameAfter1Step = false; + copiedFrom = copiedTo = null; + } } - } while (!fileRenamesQueue.isEmpty()); + } // for fileRenamesQueue; progressHelper.done(); } @@ -436,27 +459,39 @@ * Follows file renames and build a list of all corresponding file nodes and revisions they were * copied/renamed/branched at (IOW, their latest revision to look at). * - * If {@link #followHistory} is <code>false</code>, the list contains one element only, + * If {@link #followRenames} is <code>false</code>, the list contains one element only, * file node with the name of the file as it was specified by the user. * - * For the most recent file revision is null. + * For the most recent file revision depends on {@link #followAncestry}, and is file revision from working copy parent + * in it's true. <code>null</code> indicates file's TIP revision shall be used. * * TODO may use HgFileRevision (after some refactoring to accept HgDataFile and Nodeid) instead of Pair * and possibly reuse this functionality * * @return list of file renames, ordered with respect to {@link #iterateDirection} */ - private LinkedList<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() { + private List<Pair<HgDataFile, Nodeid>> buildFileRenamesQueue() { LinkedList<Pair<HgDataFile, Nodeid>> rv = new LinkedList<Pair<HgDataFile, Nodeid>>(); - if (!followHistory) { - rv.add(new Pair<HgDataFile, Nodeid>(repo.getFileNode(file), null)); + Nodeid startRev = null; + HgDataFile fileNode = repo.getFileNode(file); + if (followAncestry) { + // TODO subject to dedicated method either in HgRepository (getWorkingCopyParentRevisionIndex) + // or in the HgDataFile (getWorkingCopyOriginRevision) + Nodeid wdParentChangeset = repo.getWorkingCopyParents().first(); + if (!wdParentChangeset.isNull()) { + int wdParentRevIndex = repo.getChangelog().getRevisionIndex(wdParentChangeset); + startRev = repo.getManifest().getFileRevision(wdParentRevIndex, fileNode.getPath()); + } + // else fall-through, assume null (eventually, lastRevision()) is ok here + } + rv.add(new Pair<HgDataFile, Nodeid>(fileNode, startRev)); + if (!followRenames) { return rv; } - Path fp = file; - Nodeid copyRev = null; - boolean isCopy; - do { - HgDataFile fileNode = repo.getFileNode(fp); + while (fileNode.isCopy()) { + Path fp = fileNode.getCopySourceName(); + Nodeid copyRev = fileNode.getCopySourceRevision(); + fileNode = repo.getFileNode(fp); Pair<HgDataFile, Nodeid> p = new Pair<HgDataFile, Nodeid>(fileNode, copyRev); if (iterateDirection == IterateDirection.FromOldToNew) { rv.addFirst(p); @@ -464,11 +499,7 @@ assert iterateDirection == IterateDirection.FromNewToOld; rv.addLast(p); } - if (isCopy = fileNode.isCopy()) { - fp = fileNode.getCopySourceName(); - copyRev = fileNode.getCopySourceRevision(); - } - } while (isCopy); + }; return rv; } @@ -506,11 +537,12 @@ * @return list of history elements, from oldest to newest. In case {@link #followAncestry} is <code>true</code>, the list * is modifiable (to further augment with last/first elements of renamed file histories) */ - List<HistoryNode> go(HgDataFile fileNode, int lastRevisionIndex) throws HgInvalidControlFileException { + List<HistoryNode> go(HgDataFile fileNode, Nodeid fileLastRevisionToVisit) throws HgInvalidControlFileException { resultHistory = null; - completeHistory = new HistoryNode[lastRevisionIndex+1]; + int fileLastRevIndexToVisit = fileLastRevisionToVisit == null ? fileNode.getLastRevision() : fileNode.getRevisionIndex(fileLastRevisionToVisit); + completeHistory = new HistoryNode[fileLastRevIndexToVisit+1]; commitRevisions = new int[completeHistory.length]; - fileNode.indexWalk(0, lastRevisionIndex, this); + fileNode.indexWalk(0, fileLastRevIndexToVisit, this); if (!followAncestry) { // in case when ancestor not followed, it's safe to return unmodifiable list resultHistory = Arrays.asList(completeHistory); @@ -539,7 +571,7 @@ LinkedList<HistoryNode> strippedHistoryList = new LinkedList<HistoryNode>(); LinkedList<HistoryNode> queue = new LinkedList<HistoryNode>(); // look for ancestors of the selected history node - queue.add(completeHistory[lastRevisionIndex]); + queue.add(completeHistory[fileLastRevIndexToVisit]); do { HistoryNode withFileChange = queue.removeFirst(); if (strippedHistoryList.contains(withFileChange)) {