Mercurial > jhg
diff cmdline/org/tmatesoft/hg/console/Outgoing.java @ 171:2c3e96674e2a
Towards outgoing changes - initial detection logic, get connected with remote repo stub
author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
---|---|
date | Fri, 25 Mar 2011 00:05:52 +0100 |
parents | a3a2e5deb320 |
children | 87f40938c9b2 |
line wrap: on
line diff
--- a/cmdline/org/tmatesoft/hg/console/Outgoing.java Wed Mar 23 20:46:00 2011 +0100 +++ b/cmdline/org/tmatesoft/hg/console/Outgoing.java Fri Mar 25 00:05:52 2011 +0100 @@ -16,19 +16,25 @@ */ package org.tmatesoft.hg.console; +import static org.tmatesoft.hg.core.Nodeid.NULL; + +import java.net.URL; import java.util.Collection; -import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import org.tmatesoft.hg.core.HgException; import org.tmatesoft.hg.core.Nodeid; import org.tmatesoft.hg.repo.HgChangelog; +import org.tmatesoft.hg.repo.HgLookup; +import org.tmatesoft.hg.repo.HgRemoteRepository; +import org.tmatesoft.hg.repo.HgRemoteRepository.RemoteBranch; import org.tmatesoft.hg.repo.HgRepository; /** * WORK IN PROGRESS, DO NOT USE - * hg out + * hg outgoing * * @author Artem Tikhomirov * @author TMate Software Ltd. @@ -42,42 +48,100 @@ System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); return; } - // FIXME detection of - List<Nodeid> base = new LinkedList<Nodeid>(); - base.add(Nodeid.fromAscii("d6d2a630f4a6d670c90a5ca909150f2b426ec88f".getBytes(), 0, 40)); - // - // fill with all known + HgRemoteRepository hgRemote = new HgLookup().detect(new URL("hg4j-gc")); + HgChangelog.ParentWalker pw = hgRepo.getChangelog().new ParentWalker(); pw.init(); - LinkedHashSet<Nodeid> sendToRemote = new LinkedHashSet<Nodeid>(pw.allNodes()); - dump("initial state", sendToRemote); - // remove base and its parents - LinkedList<Nodeid> queueToClean = new LinkedList<Nodeid>(base); - while (!queueToClean.isEmpty()) { - Nodeid nid = queueToClean.removeFirst(); - if (sendToRemote.remove(nid)) { - pw.appendParentsOf(nid, queueToClean); + + List<Nodeid> commonKnown = findCommonWithRemote(pw, hgRemote); + dump("Nodes known to be both locally and at remote server", commonKnown); + // sanity check + for (Nodeid n : commonKnown) { + if (!pw.knownNode(n)) { + throw new HgException("Unknown node reported as common:" + n); } } - dump("Clean from known parents", sendToRemote); - // XXX I think sendToRemote is what we actually need here - everything local, missing from remote - // however, if we need to send only a subset of these, need to proceed. - LinkedList<Nodeid> result = new LinkedList<Nodeid>(); - // find among left those without parents - for (Nodeid nid : sendToRemote) { - Nodeid p1 = pw.firstParent(nid); - // in fact, we may assume nulls are never part of sendToRemote - if (p1 != null && !sendToRemote.contains(p1)) { - Nodeid p2 = pw.secondParent(nid); - if (p2 == null || !sendToRemote.contains(p2)) { - result.add(nid); + // find all local children of commonKnown + List<Nodeid> result = pw.childrenOf(commonKnown); + dump("Result", result); + } + + private static List<Nodeid> findCommonWithRemote(HgChangelog.ParentWalker pwLocal, HgRemoteRepository hgRemote) { + List<Nodeid> remoteHeads = hgRemote.heads(); + LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local + LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common + for (Nodeid rh : remoteHeads) { + if (pwLocal.knownNode(rh)) { + common.add(rh); + } else { + toQuery.add(rh); + } + } + if (toQuery.isEmpty()) { + return common; + } + LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>(); // branch.root and branch.head are of interest only. + // these are branches with unknown head but known root, which might not be the last common known, + // i.e. there might be children changeset that are also available at remote, [..?..common-head..remote-head] - need to + // scroll up to common head. + while (!toQuery.isEmpty()) { + List<RemoteBranch> remoteBranches = hgRemote.branches(toQuery); //head, root, first parent, second parent + toQuery.clear(); + while(!remoteBranches.isEmpty()) { + RemoteBranch rb = remoteBranches.remove(0); + // I assume branches remote call gives branches with head equal to what I pass there, i.e. + // that I don't need to check whether rb.head is unknown. + if (pwLocal.knownNode(rb.root)) { + // we known branch start, common head is somewhere in its descendants line + checkUp2Head.add(rb); + } else { + // dig deeper in the history, if necessary + if (!NULL.equals(rb.p1) && !pwLocal.knownNode(rb.p1)) { + toQuery.add(rb.p1); + } + if (!NULL.equals(rb.p2) && !pwLocal.knownNode(rb.p2)) { + toQuery.add(rb.p2); + } } } } - dump("Result", result); - // final outcome is the collection of nodes between(lastresult and revision/tip) - // - System.out.println("TODO: nodes between result and tip"); + // can't check nodes between checkUp2Head element and local heads, remote might have distinct descendants sequence + for (RemoteBranch rb : checkUp2Head) { + // rb.root is known locally + List<Nodeid> remoteRevisions = hgRemote.between(rb.root, rb.head); + if (remoteRevisions.isEmpty()) { + // head is immediate child + common.add(rb.root); + } else { + Nodeid root = rb.root; + while(!remoteRevisions.isEmpty()) { + Nodeid n = remoteRevisions.remove(0); + if (pwLocal.knownNode(n)) { + if (remoteRevisions.isEmpty()) { + // this is the last known node before an unknown + common.add(n); + break; + } + if (remoteRevisions.size() == 1) { + // there's only one left between known n and unknown head + // this check is to save extra between query, not really essential + Nodeid last = remoteRevisions.remove(0); + common.add(pwLocal.knownNode(last) ? last : n); + break; + } + // might get handy for next between query, to narrow search down + root = n; + } else { + remoteRevisions = hgRemote.between(root, n); + if (remoteRevisions.isEmpty()) { + common.add(root); + } + } + } + } + } + // TODO ensure unique elements in the list + return common; } private static void dump(String s, Collection<Nodeid> c) {