# HG changeset patch # User Artem Tikhomirov # Date 1317791637 -7200 # Node ID a674b859036273426fc094ab758ee821afd58356 # Parent 3f09b8c1914237be2c685fbeffcfda72c2cb9572 Move file tree history to upper API level diff -r 3f09b8c19142 -r a674b8590362 cmdline/org/tmatesoft/hg/console/Main.java --- a/cmdline/org/tmatesoft/hg/console/Main.java Tue Oct 04 07:24:44 2011 +0200 +++ b/cmdline/org/tmatesoft/hg/console/Main.java Wed Oct 05 07:13:57 2011 +0200 @@ -22,12 +22,15 @@ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; import org.junit.Assert; import org.tmatesoft.hg.core.HgBadStateException; import org.tmatesoft.hg.core.HgCatCommand; +import org.tmatesoft.hg.core.HgChangeset; +import org.tmatesoft.hg.core.HgChangesetTreeHandler; import org.tmatesoft.hg.core.HgDataStreamException; import org.tmatesoft.hg.core.HgFileInformer; import org.tmatesoft.hg.core.HgFileRevision; @@ -108,30 +111,46 @@ // m.bunchOfTests(); } - private void buildFileLog() { - final HgDataFile fn = hgRepo.getFileNode("file1"); - HgChangelog.TreeInspector insp = new HgChangelog.TreeInspector() { - - public void next(Nodeid changesetRevision, Pair parentChangesets, Collection childChangesets) { + private void buildFileLog() throws Exception { + HgLogCommand cmd = new HgLogCommand(hgRepo); + cmd.file("file1", false); + cmd.execute(new HgChangesetTreeHandler() { + public void next(org.tmatesoft.hg.core.HgChangesetTreeHandler.TreeElement entry) { StringBuilder sb = new StringBuilder(); - for (Nodeid cc : childChangesets) { - sb.append(cc.shortNotation()); + HashSet test = new HashSet(entry.childRevisions()); + for (HgChangeset cc : entry.children()) { + sb.append(cc.getRevision()); + sb.append(':'); + sb.append(cc.getNodeid().shortNotation()); sb.append(", "); } - final boolean isJoin = !parentChangesets.first().isNull() && !parentChangesets.second().isNull(); - final boolean isFork = childChangesets.size() > 1; + final Pair parents = entry.parentRevisions(); + final boolean isJoin = !parents.first().isNull() && !parents.second().isNull(); + final boolean isFork = entry.children().size() > 1; + final HgChangeset cset = entry.changeset(); + System.out.printf("%d:%s - %s\n", cset.getRevision(), cset.getNodeid().shortNotation(), cset.getComment()); + if (!isJoin && !isFork && !entry.children().isEmpty()) { + System.out.printf("\t=> %s\n", sb); + } if (isJoin) { - System.out.printf("join[(%s, %s) => %s]\n", parentChangesets.first().shortNotation(), parentChangesets.second().shortNotation(), changesetRevision.shortNotation()); + HgChangeset p1 = entry.parents().first(); + HgChangeset p2 = entry.parents().second(); + System.out.printf("\tjoin <= (%d:%s, %d:%s)", p1.getRevision(), p1.getNodeid().shortNotation(), p2.getRevision(), p2.getNodeid().shortNotation()); + if (isFork) { + System.out.print(", "); + } } if (isFork) { - System.out.printf("fork[%s => %s]\n", changesetRevision.shortNotation(), sb); + if (!isJoin) { + System.out.print('\t'); + } + System.out.printf("fork => [%s]", sb); } - if (!isFork && !isJoin && !childChangesets.isEmpty()) { - System.out.printf("%s => %s\n", changesetRevision.shortNotation(), sb); + if (isJoin || isFork) { + System.out.println(); } } - }; - fn.history(insp); + }); } private void buildFileLogOld() { diff -r 3f09b8c19142 -r a674b8590362 src/org/tmatesoft/hg/core/ChangesetTransformer.java --- a/src/org/tmatesoft/hg/core/ChangesetTransformer.java Tue Oct 04 07:24:44 2011 +0200 +++ b/src/org/tmatesoft/hg/core/ChangesetTransformer.java Wed Oct 05 07:13:57 2011 +0200 @@ -37,9 +37,9 @@ */ /*package-local*/ class ChangesetTransformer implements HgChangelog.Inspector { private final HgChangesetHandler handler; - private final HgChangeset changeset; private final ProgressSupport progressHelper; private final CancelSupport cancelHelper; + private final Transformation t; private Set branches; private HgCallbackTargetException failure; private CancelledException cancellation; @@ -54,11 +54,7 @@ throw new IllegalArgumentException(); } HgStatusCollector statusCollector = new HgStatusCollector(hgRepo); - // files listed in a changeset don't need their names to be rewritten (they are normalized already) - PathPool pp = new PathPool(new PathRewrite.Empty()); - statusCollector.setPathPool(pp); - changeset = new HgChangeset(statusCollector, pp); - changeset.setParentHelper(pw); + t = new Transformation(statusCollector, pw); handler = delegate; cancelHelper = cs; progressHelper = ps; @@ -72,7 +68,7 @@ return; } - changeset.init(revisionNumber, nodeid, cset); + HgChangeset changeset = t.handle(revisionNumber, nodeid, cset); try { handler.next(changeset); progressHelper.worked(1); @@ -100,4 +96,22 @@ public void limitBranches(Set branches) { this.branches = branches; } + + // part relevant to RawChangeset->HgChangeset transformation + static class Transformation { + private final HgChangeset changeset; + + public Transformation(HgStatusCollector statusCollector, HgChangelog.ParentWalker pw) { + // files listed in a changeset don't need their names to be rewritten (they are normalized already) + PathPool pp = new PathPool(new PathRewrite.Empty()); + statusCollector.setPathPool(pp); + changeset = new HgChangeset(statusCollector, pp); + changeset.setParentHelper(pw); + } + + HgChangeset handle(int revisionNumber, Nodeid nodeid, RawChangeset cset) { + changeset.init(revisionNumber, nodeid, cset); + return changeset; + } + } } \ No newline at end of file diff -r 3f09b8c19142 -r a674b8590362 src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/core/HgChangesetTreeHandler.java Wed Oct 05 07:13:57 2011 +0200 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.core; + +import java.util.Collection; + +import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.Pair; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public interface HgChangesetTreeHandler { + /** + * @param entry access to various pieces of information about current tree node. Instances might be + * reused across calls and shall not be kept by client's code + */ + public void next(HgChangesetTreeHandler.TreeElement entry) throws CancelledException; + + interface TreeElement { + /** + * Revision of the revlog being iterated. For example, when walking file history, return value represents file revisions. + * + * @return revision of the revlog being iterated. + */ + public Nodeid fileRevision(); + /** + * @return changeset associated with the current revision + */ + public HgChangeset changeset(); + + /** + * Lightweight alternative to {@link #changeset()}, identifies changeset in which current file node has been modified + * @return changeset {@link Nodeid} + */ + public Nodeid changesetRevision(); + + /** + * Node, these are not necessarily in direct relation to parents of changeset from {@link #changeset()} + * @return changesets that correspond to parents of the current file node, either pair element may be null. + */ + public Pair parents(); + + /** + * Lightweight alternative to {@link #parents()}, give {@link Nodeid nodeids} only + * @return two values, neither is null, use {@link Nodeid#isNull()} to identify parent not set + */ + public Pair parentRevisions(); + + public Collection children(); + + /** + * Lightweight alternative to {@link #children()}. + * @return never null + */ + public Collection childRevisions(); + } +} \ No newline at end of file diff -r 3f09b8c19142 -r a674b8590362 src/org/tmatesoft/hg/core/HgLogCommand.java --- a/src/org/tmatesoft/hg/core/HgLogCommand.java Tue Oct 04 07:24:44 2011 +0200 +++ b/src/org/tmatesoft/hg/core/HgLogCommand.java Wed Oct 05 07:13:57 2011 +0200 @@ -18,7 +18,10 @@ import static org.tmatesoft.hg.repo.HgRepository.TIP; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; +import java.util.Collection; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.LinkedList; @@ -26,11 +29,16 @@ import java.util.Set; import java.util.TreeSet; +import org.tmatesoft.hg.internal.IntMap; +import org.tmatesoft.hg.internal.IntVector; import org.tmatesoft.hg.repo.HgChangelog; import org.tmatesoft.hg.repo.HgChangelog.RawChangeset; import org.tmatesoft.hg.repo.HgDataFile; import org.tmatesoft.hg.repo.HgRepository; +import org.tmatesoft.hg.repo.HgStatusCollector; +import org.tmatesoft.hg.util.CancelSupport; import org.tmatesoft.hg.util.CancelledException; +import org.tmatesoft.hg.util.Pair; import org.tmatesoft.hg.util.Path; import org.tmatesoft.hg.util.ProgressSupport; @@ -205,10 +213,7 @@ final ProgressSupport progressHelper = getProgressSupport(handler); try { count = 0; - HgChangelog.ParentWalker pw = parentHelper; // leave it uninitialized unless we iterate whole repo - if (file == null) { - pw = getParentHelper(); - } + HgChangelog.ParentWalker pw = getParentHelper(file == null); // leave it uninitialized unless we iterate whole repo // ChangesetTransfrom creates a blank PathPool, and #file(String, boolean) above // may utilize it as well. CommandContext? How about StatusCollector there as well? csetTransform = new ChangesetTransformer(repo, handler, pw, progressHelper, getCancelSupport(handler, true)); @@ -250,7 +255,77 @@ progressHelper.done(); } } + + public void execute(HgChangesetTreeHandler handler) throws CancelledException { + if (handler == null) { + throw new IllegalArgumentException(); + } + if (csetTransform != null) { + throw new ConcurrentModificationException(); + } + if (file == null) { + throw new IllegalArgumentException("History tree is supported for files only (at least now), please specify file"); + } + if (followHistory) { + throw new UnsupportedOperationException("Can't follow file history when building tree (yet?)"); + } + class TreeBuildInspector implements HgChangelog.ParentInspector, HgChangelog.RevisionInspector { + HistoryNode[] completeHistory; + int[] commitRevisions; + public void next(int revisionNumber, Nodeid revision, int linkedRevision) { + commitRevisions[revisionNumber] = linkedRevision; + } + + public void next(int revisionNumber, Nodeid revision, int parent1, int parent2, Nodeid nidParent1, Nodeid nidParent2) { + HistoryNode p1 = null, p2 = null; + if (parent1 != -1) { + p1 = completeHistory[parent1]; + } + if (parent2!= -1) { + p2 = completeHistory[parent2]; + } + completeHistory[revisionNumber] = new HistoryNode(commitRevisions[revisionNumber], revision, p1, p2); + } + + HistoryNode[] go(HgDataFile fileNode) { + completeHistory = new HistoryNode[fileNode.getRevisionCount()]; + commitRevisions = new int[completeHistory.length]; + fileNode.walk(0, TIP, this); + return completeHistory; + } + }; + final ProgressSupport progressHelper = getProgressSupport(handler); + progressHelper.start(4); + final CancelSupport cancelHelper = getCancelSupport(handler, true); + cancelHelper.checkCancelled(); + HgDataFile fileNode = repo.getFileNode(file); + // build tree of nodes according to parents in file's revlog + final TreeBuildInspector treeBuildInspector = new TreeBuildInspector(); + final HistoryNode[] completeHistory = treeBuildInspector.go(fileNode); + progressHelper.worked(1); + cancelHelper.checkCancelled(); + ElementImpl ei = new ElementImpl(treeBuildInspector.commitRevisions.length); + final ProgressSupport ph2; + if (treeBuildInspector.commitRevisions.length < 100 /*XXX is it really worth it? */) { + ei.initTransform(); + repo.getChangelog().range(ei, treeBuildInspector.commitRevisions); + progressHelper.worked(1); + ph2 = new ProgressSupport.Sub(progressHelper, 2); + } else { + ph2 = new ProgressSupport.Sub(progressHelper, 3); + } + ph2.start(completeHistory.length); + // XXX shall sort completeHistory according to changeset numbers? + for (int i = 0; i < completeHistory.length; i++ ) { + final HistoryNode n = completeHistory[i]; + handler.next(ei.init(n)); + ph2.worked(1); + cancelHelper.checkCancelled(); + } + progressHelper.done(); + } + // public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { @@ -280,8 +355,8 @@ csetTransform.next(revisionNumber, nodeid, cset); } - private HgChangelog.ParentWalker getParentHelper() { - if (parentHelper == null) { + private HgChangelog.ParentWalker getParentHelper(boolean create) { + if (parentHelper == null && create) { parentHelper = repo.getChangelog().new ParentWalker(); parentHelper.init(); } @@ -317,4 +392,210 @@ result.add(changeset.clone()); } } + + private static class HistoryNode { + final int changeset; + final Nodeid fileRevision; + final HistoryNode parent1, parent2; + List children; + + HistoryNode(int cs, Nodeid revision, HistoryNode p1, HistoryNode p2) { + changeset = cs; + fileRevision = revision; + parent1 = p1; + parent2 = p2; + if (p1 != null) { + p1.addChild(this); + } + if (p2 != null) { + p2.addChild(this); + } + } + + void addChild(HistoryNode child) { + if (children == null) { + children = new ArrayList(2); + } + children.add(child); + } + } + + private class ElementImpl implements HgChangesetTreeHandler.TreeElement, HgChangelog.Inspector { + private HistoryNode historyNode; + private Pair parents; + private List children; + private IntMap cachedChangesets; + private ChangesetTransformer.Transformation transform; + private Nodeid changesetRevision; + private Pair parentRevisions; + private List childRevisions; + + public ElementImpl(int total) { + cachedChangesets = new IntMap(total); + } + + ElementImpl init(HistoryNode n) { + historyNode = n; + parents = null; + children = null; + changesetRevision = null; + parentRevisions = null; + childRevisions = null; + return this; + } + + public Nodeid fileRevision() { + return historyNode.fileRevision; + } + + public HgChangeset changeset() { + return get(historyNode.changeset)[0]; + } + + public Pair parents() { + if (parents != null) { + return parents; + } + HistoryNode p; + final int p1, p2; + if ((p = historyNode.parent1) != null) { + p1 = p.changeset; + } else { + p1 = -1; + } + if ((p = historyNode.parent2) != null) { + p2 = p.changeset; + } else { + p2 = -1; + } + HgChangeset[] r = get(p1, p2); + return parents = new Pair(r[0], r[1]); + } + + public Collection children() { + if (children != null) { + return children; + } + if (historyNode.children == null) { + children = Collections.emptyList(); + } else { + int[] childrentChangesetNumbers = new int[historyNode.children.size()]; + int j = 0; + for (HistoryNode hn : historyNode.children) { + childrentChangesetNumbers[j++] = hn.changeset; + } + children = Arrays.asList(get(childrentChangesetNumbers)); + } + return children; + } + + void populate(HgChangeset cs) { + cachedChangesets.put(cs.getRevision(), cs); + } + + private HgChangeset[] get(int... changelogRevisionNumber) { + HgChangeset[] rv = new HgChangeset[changelogRevisionNumber.length]; + IntVector misses = new IntVector(changelogRevisionNumber.length, -1); + for (int i = 0; i < changelogRevisionNumber.length; i++) { + if (changelogRevisionNumber[i] == -1) { + rv[i] = null; + continue; + } + HgChangeset cached = cachedChangesets.get(changelogRevisionNumber[i]); + if (cached != null) { + rv[i] = cached; + } else { + misses.add(changelogRevisionNumber[i]); + } + } + if (misses.size() > 0) { + final int[] changesets2read = misses.toArray(); + initTransform(); + repo.getChangelog().range(this, changesets2read); + for (int changeset2read : changesets2read) { + HgChangeset cs = cachedChangesets.get(changeset2read); + if (cs == null) { + throw new HgBadStateException(); + } + // HgChangelog.range may reorder changesets according to their order in the changelog + // thus need to find original index + boolean sanity = false; + for (int i = 0; i < changelogRevisionNumber.length; i++) { + if (changelogRevisionNumber[i] == cs.getRevision()) { + rv[i] = cs; + sanity = true; + break; + } + } + assert sanity; + } + } + return rv; + } + + // init only when needed + void initTransform() { + if (transform == null) { + transform = new ChangesetTransformer.Transformation(new HgStatusCollector(repo)/*XXX try to reuse from context?*/, getParentHelper(false)); + } + } + + public void next(int revisionNumber, Nodeid nodeid, RawChangeset cset) { + HgChangeset cs = transform.handle(revisionNumber, nodeid, cset); + populate(cs.clone()); + } + + public Nodeid changesetRevision() { + if (changesetRevision == null) { + changesetRevision = getRevision(historyNode.changeset); + } + return changesetRevision; + } + + public Pair parentRevisions() { + if (parentRevisions == null) { + HistoryNode p; + final Nodeid p1, p2; + if ((p = historyNode.parent1) != null) { + p1 = getRevision(p.changeset); + } else { + p1 = Nodeid.NULL;; + } + if ((p = historyNode.parent2) != null) { + p2 = getRevision(p.changeset); + } else { + p2 = Nodeid.NULL; + } + parentRevisions = new Pair(p1, p2); + } + return parentRevisions; + } + + public Collection childRevisions() { + if (childRevisions != null) { + return childRevisions; + } + if (historyNode.children == null) { + childRevisions = Collections.emptyList(); + } else { + ArrayList rv = new ArrayList(historyNode.children.size()); + for (HistoryNode hn : historyNode.children) { + rv.add(getRevision(hn.changeset)); + } + childRevisions = Collections.unmodifiableList(rv); + } + return childRevisions; + } + + // reading nodeid involves reading index only, guess, can afford not to optimize multiple reads + private Nodeid getRevision(int changelogRevisionNumber) { + // XXX pipe through pool + HgChangeset cs = cachedChangesets.get(changelogRevisionNumber); + if (cs != null) { + return cs.getNodeid(); + } else { + return repo.getChangelog().getRevision(changelogRevisionNumber); + } + } + } } diff -r 3f09b8c19142 -r a674b8590362 src/org/tmatesoft/hg/internal/IntVector.java --- a/src/org/tmatesoft/hg/internal/IntVector.java Tue Oct 04 07:24:44 2011 +0200 +++ b/src/org/tmatesoft/hg/internal/IntVector.java Wed Oct 05 07:13:57 2011 +0200 @@ -17,11 +17,12 @@ package org.tmatesoft.hg.internal; /** - * + * Vector of primitive values + * * @author Artem Tikhomirov * @author TMate Software Ltd. */ -class IntVector { +public class IntVector { private int[] data; private final int increment; diff -r 3f09b8c19142 -r a674b8590362 src/org/tmatesoft/hg/repo/HgChangelog.java --- a/src/org/tmatesoft/hg/repo/HgChangelog.java Tue Oct 04 07:24:44 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgChangelog.java Wed Oct 05 07:13:57 2011 +0200 @@ -32,6 +32,7 @@ import java.util.TimeZone; import org.tmatesoft.hg.core.HgBadStateException; +import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.IterateControlMediator; @@ -110,7 +111,9 @@ * Unlike regular {@link Inspector}, this one supplies changeset revision along with its parents and children according * to parent information of the revlog this inspector visits. * @see HgDataFile#history(TreeInspector) + * @deprecated use {@link HgChangesetTreeHandler} and HgLogCommand#execute(HgChangesetTreeHandler)} */ + @Deprecated public interface TreeInspector { // the reason TreeInsector is in HgChangelog, not in Revlog, because despite the fact it can // be applied to any revlog, it's not meant to provide revisions of any revlog it's beeing applied to, diff -r 3f09b8c19142 -r a674b8590362 src/org/tmatesoft/hg/repo/HgDataFile.java --- a/src/org/tmatesoft/hg/repo/HgDataFile.java Tue Oct 04 07:24:44 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgDataFile.java Wed Oct 05 07:13:57 2011 +0200 @@ -33,6 +33,7 @@ import org.tmatesoft.hg.core.HgDataStreamException; import org.tmatesoft.hg.core.HgException; +import org.tmatesoft.hg.core.HgLogCommand; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.internal.DataAccess; import org.tmatesoft.hg.internal.FilterByteChannel; @@ -259,6 +260,10 @@ } } + /** + * @deprecated use {@link HgLogCommand#execute(org.tmatesoft.hg.core.HgChangesetTreeHandler)} instead + */ + @Deprecated public void history(HgChangelog.TreeInspector inspector) { final CancelSupport cancelSupport = CancelSupport.Factory.get(inspector); try { diff -r 3f09b8c19142 -r a674b8590362 src/org/tmatesoft/hg/repo/Revlog.java --- a/src/org/tmatesoft/hg/repo/Revlog.java Tue Oct 04 07:24:44 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/Revlog.java Wed Oct 05 07:13:57 2011 +0200 @@ -87,10 +87,13 @@ } public final Nodeid getRevision(int revision) { - // XXX cache nodeids? + // XXX cache nodeids? Rather, if context.getCache(this).getRevisionMap(create == false) != null, use it return Nodeid.fromBinary(content.nodeid(revision), 0); } + /** + * FIXME need to be careful about (1) ordering of the revisions in the return list; (2) modifications (sorting) of the argument array + */ public final List getRevisions(int... revisions) { ArrayList rv = new ArrayList(revisions.length); Arrays.sort(revisions);