comparison 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
comparison
equal deleted inserted replaced
170:71ddbf8603e8 171:2c3e96674e2a
14 * the terms of a license other than GNU General Public License 14 * the terms of a license other than GNU General Public License
15 * contact TMate Software at support@hg4j.com 15 * contact TMate Software at support@hg4j.com
16 */ 16 */
17 package org.tmatesoft.hg.console; 17 package org.tmatesoft.hg.console;
18 18
19 import static org.tmatesoft.hg.core.Nodeid.NULL;
20
21 import java.net.URL;
19 import java.util.Collection; 22 import java.util.Collection;
20 import java.util.LinkedHashSet;
21 import java.util.LinkedList; 23 import java.util.LinkedList;
22 import java.util.List; 24 import java.util.List;
23 25
26 import org.tmatesoft.hg.core.HgException;
24 import org.tmatesoft.hg.core.Nodeid; 27 import org.tmatesoft.hg.core.Nodeid;
25 import org.tmatesoft.hg.repo.HgChangelog; 28 import org.tmatesoft.hg.repo.HgChangelog;
29 import org.tmatesoft.hg.repo.HgLookup;
30 import org.tmatesoft.hg.repo.HgRemoteRepository;
31 import org.tmatesoft.hg.repo.HgRemoteRepository.RemoteBranch;
26 import org.tmatesoft.hg.repo.HgRepository; 32 import org.tmatesoft.hg.repo.HgRepository;
27 33
28 34
29 /** 35 /**
30 * WORK IN PROGRESS, DO NOT USE 36 * WORK IN PROGRESS, DO NOT USE
31 * hg out 37 * hg outgoing
32 * 38 *
33 * @author Artem Tikhomirov 39 * @author Artem Tikhomirov
34 * @author TMate Software Ltd. 40 * @author TMate Software Ltd.
35 */ 41 */
36 public class Outgoing { 42 public class Outgoing {
40 HgRepository hgRepo = cmdLineOpts.findRepository(); 46 HgRepository hgRepo = cmdLineOpts.findRepository();
41 if (hgRepo.isInvalid()) { 47 if (hgRepo.isInvalid()) {
42 System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); 48 System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation());
43 return; 49 return;
44 } 50 }
45 // FIXME detection of 51 HgRemoteRepository hgRemote = new HgLookup().detect(new URL("hg4j-gc"));
46 List<Nodeid> base = new LinkedList<Nodeid>(); 52
47 base.add(Nodeid.fromAscii("d6d2a630f4a6d670c90a5ca909150f2b426ec88f".getBytes(), 0, 40));
48 //
49 // fill with all known
50 HgChangelog.ParentWalker pw = hgRepo.getChangelog().new ParentWalker(); 53 HgChangelog.ParentWalker pw = hgRepo.getChangelog().new ParentWalker();
51 pw.init(); 54 pw.init();
52 LinkedHashSet<Nodeid> sendToRemote = new LinkedHashSet<Nodeid>(pw.allNodes()); 55
53 dump("initial state", sendToRemote); 56 List<Nodeid> commonKnown = findCommonWithRemote(pw, hgRemote);
54 // remove base and its parents 57 dump("Nodes known to be both locally and at remote server", commonKnown);
55 LinkedList<Nodeid> queueToClean = new LinkedList<Nodeid>(base); 58 // sanity check
56 while (!queueToClean.isEmpty()) { 59 for (Nodeid n : commonKnown) {
57 Nodeid nid = queueToClean.removeFirst(); 60 if (!pw.knownNode(n)) {
58 if (sendToRemote.remove(nid)) { 61 throw new HgException("Unknown node reported as common:" + n);
59 pw.appendParentsOf(nid, queueToClean);
60 } 62 }
61 } 63 }
62 dump("Clean from known parents", sendToRemote); 64 // find all local children of commonKnown
63 // XXX I think sendToRemote is what we actually need here - everything local, missing from remote 65 List<Nodeid> result = pw.childrenOf(commonKnown);
64 // however, if we need to send only a subset of these, need to proceed. 66 dump("Result", result);
65 LinkedList<Nodeid> result = new LinkedList<Nodeid>(); 67 }
66 // find among left those without parents 68
67 for (Nodeid nid : sendToRemote) { 69 private static List<Nodeid> findCommonWithRemote(HgChangelog.ParentWalker pwLocal, HgRemoteRepository hgRemote) {
68 Nodeid p1 = pw.firstParent(nid); 70 List<Nodeid> remoteHeads = hgRemote.heads();
69 // in fact, we may assume nulls are never part of sendToRemote 71 LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local
70 if (p1 != null && !sendToRemote.contains(p1)) { 72 LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
71 Nodeid p2 = pw.secondParent(nid); 73 for (Nodeid rh : remoteHeads) {
72 if (p2 == null || !sendToRemote.contains(p2)) { 74 if (pwLocal.knownNode(rh)) {
73 result.add(nid); 75 common.add(rh);
76 } else {
77 toQuery.add(rh);
78 }
79 }
80 if (toQuery.isEmpty()) {
81 return common;
82 }
83 LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>(); // branch.root and branch.head are of interest only.
84 // these are branches with unknown head but known root, which might not be the last common known,
85 // i.e. there might be children changeset that are also available at remote, [..?..common-head..remote-head] - need to
86 // scroll up to common head.
87 while (!toQuery.isEmpty()) {
88 List<RemoteBranch> remoteBranches = hgRemote.branches(toQuery); //head, root, first parent, second parent
89 toQuery.clear();
90 while(!remoteBranches.isEmpty()) {
91 RemoteBranch rb = remoteBranches.remove(0);
92 // I assume branches remote call gives branches with head equal to what I pass there, i.e.
93 // that I don't need to check whether rb.head is unknown.
94 if (pwLocal.knownNode(rb.root)) {
95 // we known branch start, common head is somewhere in its descendants line
96 checkUp2Head.add(rb);
97 } else {
98 // dig deeper in the history, if necessary
99 if (!NULL.equals(rb.p1) && !pwLocal.knownNode(rb.p1)) {
100 toQuery.add(rb.p1);
101 }
102 if (!NULL.equals(rb.p2) && !pwLocal.knownNode(rb.p2)) {
103 toQuery.add(rb.p2);
104 }
74 } 105 }
75 } 106 }
76 } 107 }
77 dump("Result", result); 108 // can't check nodes between checkUp2Head element and local heads, remote might have distinct descendants sequence
78 // final outcome is the collection of nodes between(lastresult and revision/tip) 109 for (RemoteBranch rb : checkUp2Head) {
79 // 110 // rb.root is known locally
80 System.out.println("TODO: nodes between result and tip"); 111 List<Nodeid> remoteRevisions = hgRemote.between(rb.root, rb.head);
112 if (remoteRevisions.isEmpty()) {
113 // head is immediate child
114 common.add(rb.root);
115 } else {
116 Nodeid root = rb.root;
117 while(!remoteRevisions.isEmpty()) {
118 Nodeid n = remoteRevisions.remove(0);
119 if (pwLocal.knownNode(n)) {
120 if (remoteRevisions.isEmpty()) {
121 // this is the last known node before an unknown
122 common.add(n);
123 break;
124 }
125 if (remoteRevisions.size() == 1) {
126 // there's only one left between known n and unknown head
127 // this check is to save extra between query, not really essential
128 Nodeid last = remoteRevisions.remove(0);
129 common.add(pwLocal.knownNode(last) ? last : n);
130 break;
131 }
132 // might get handy for next between query, to narrow search down
133 root = n;
134 } else {
135 remoteRevisions = hgRemote.between(root, n);
136 if (remoteRevisions.isEmpty()) {
137 common.add(root);
138 }
139 }
140 }
141 }
142 }
143 // TODO ensure unique elements in the list
144 return common;
81 } 145 }
82 146
83 private static void dump(String s, Collection<Nodeid> c) { 147 private static void dump(String s, Collection<Nodeid> c) {
84 System.out.println(s); 148 System.out.println(s);
85 for (Nodeid n : c) { 149 for (Nodeid n : c) {