comparison cmdline/org/tmatesoft/hg/console/Incoming.java @ 178:62665d8f0686

Complete logic to discover all branches missing locally. Most of wire protocol in HgRemoteRepository
author Artem Tikhomirov <tikhomirov.artem@gmail.com>
date Wed, 06 Apr 2011 01:34:16 +0200
parents e10225daface
children cd3371670f0b
comparison
equal deleted inserted replaced
177:e10225daface 178:62665d8f0686
18 18
19 import static org.tmatesoft.hg.core.Nodeid.NULL; 19 import static org.tmatesoft.hg.core.Nodeid.NULL;
20 20
21 import java.io.File; 21 import java.io.File;
22 import java.net.URL; 22 import java.net.URL;
23 import java.util.ArrayList;
23 import java.util.Arrays; 24 import java.util.Arrays;
24 import java.util.Collections; 25 import java.util.Collections;
25 import java.util.Comparator; 26 import java.util.Comparator;
26 import java.util.HashMap; 27 import java.util.HashMap;
27 import java.util.HashSet; 28 import java.util.HashSet;
28 import java.util.Iterator; 29 import java.util.Iterator;
29 import java.util.LinkedHashMap; 30 import java.util.LinkedHashMap;
30 import java.util.LinkedList; 31 import java.util.LinkedList;
31 import java.util.List; 32 import java.util.List;
33 import java.util.ListIterator;
32 import java.util.Map; 34 import java.util.Map;
33 import java.util.Map.Entry; 35 import java.util.Map.Entry;
34 36
35 import org.tmatesoft.hg.core.HgBadStateException; 37 import org.tmatesoft.hg.core.HgBadStateException;
36 import org.tmatesoft.hg.core.HgException; 38 import org.tmatesoft.hg.core.HgException;
77 // in fact, all we need from changelog is set of all nodeids. However, since ParentWalker reuses same Nodeids, it's not too expensive 79 // in fact, all we need from changelog is set of all nodeids. However, since ParentWalker reuses same Nodeids, it's not too expensive
78 // to reuse it here, XXX although later this may need to be refactored 80 // to reuse it here, XXX although later this may need to be refactored
79 final HgChangelog.ParentWalker pw = hgRepo.getChangelog().new ParentWalker(); 81 final HgChangelog.ParentWalker pw = hgRepo.getChangelog().new ParentWalker();
80 pw.init(); 82 pw.init();
81 // 83 //
82 List<RemoteBranch> missingBranches = calculateMissingBranches(hgRepo, hgRemote); 84 List<BranchChain> missingBranches0 = calculateMissingBranches(pw, hgRemote);
83 LinkedList<Nodeid> missing = new LinkedList<Nodeid>(); 85 for (BranchChain bc : missingBranches0) {
84 for (RemoteBranch rb : missingBranches) { 86 bc.dump();
85 List<Nodeid> completeBranch = completeBranch(hgRemote, rb); 87
86 // FIXME ensure topologically sorted result 88 List<Nodeid> missing = visitBranches(hgRemote, bc);
87 missing.addAll(completeBranch); 89 // Collections.reverse(missing); // useful to test output, from newer to older
88 } 90 for (Nodeid n : missing) {
89 // Collections.reverse(missing); // useful to test output, from newer to older 91 if (pw.knownNode(n)) {
90 for (Nodeid n : missing) { 92 System.out.println("Erroneous to fetch:" + n);
91 if (pw.knownNode(n)) { 93 } else {
92 System.out.println("Erroneous to fetch:" + n); 94 System.out.println(n);
93 } else { 95 }
94 System.out.println(n); 96 }
95 } 97 System.out.println("Branch done");
96 } 98 }
99
97 } 100 }
98 101
99 private static List<RemoteBranch> calculateMissingBranches(HgRepository hgRepo, HgRemoteRepository hgRemote) { 102 private static class BranchChain {
100 // FIXME implement 103 // when we construct a chain, we know head which is missing locally, hence init it right away.
104 // as for root (branch unknown start), we might happen to have one locally, and need further digging to find out right branch start
105 public final Nodeid branchHead;
106 public Nodeid branchRoot;
107 // either of these can be null, or both.
108 // although RemoteBranch has either both parents null, or both non-null, when we construct a chain
109 // we might encounter that we locally know one of branch's parent, hence in the chain corresponding field will be blank.
110 public BranchChain p1;
111 public BranchChain p2;
112
113 public BranchChain(Nodeid head) {
114 assert head != null;
115 branchHead = head;
116 }
117 public boolean isTerminal() {
118 return p1 == null || p2 == null;
119 }
120
121 @Override
122 public String toString() {
123 return String.format("BranchChain [%s, %s]", branchRoot, branchHead);
124 }
125 void dump() {
126 System.out.println(toString());
127 internalDump(" ");
128 }
129 void internalDump(String prefix) {
130 if (p1 != null) {
131 System.out.println(prefix + p1.toString());
132 }
133 if (p2 != null) {
134 System.out.println(prefix + p2.toString());
135 }
136 prefix += " ";
137 if (p1 != null) {
138 p1.internalDump(prefix);
139 }
140 if (p2 != null) {
141 p2.internalDump(prefix);
142 }
143 }
144 }
145
146 private static List<Nodeid> visitBranches(HgRemoteRepository hgRemote, BranchChain bc) throws HgException {
147 if (bc == null) {
148 return Collections.emptyList();
149 }
150 List<Nodeid> mine = completeBranch(hgRemote, bc.branchRoot, bc.branchHead);
151 if (bc.isTerminal()) {
152 return mine;
153 }
154 List<Nodeid> parentBranch1 = visitBranches(hgRemote, bc.p1);
155 List<Nodeid> parentBranch2 = visitBranches(hgRemote, bc.p2);
156 // merge
157 LinkedList<Nodeid> merged = new LinkedList<Nodeid>();
158 ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator();
159 while (i1.hasNext() && i2.hasNext()) {
160 Nodeid n1 = i1.next();
161 Nodeid n2 = i2.next();
162 if (n1.equals(n2)) {
163 merged.addLast(n1);
164 } else {
165 // first different => add both, and continue adding both tails sequentially
166 merged.add(n2);
167 merged.add(n1);
168 break;
169 }
170 }
171 // copy rest of second parent branch
172 while (i2.hasNext()) {
173 merged.add(i2.next());
174 }
175 // copy rest of first parent branch
176 while (i1.hasNext()) {
177 merged.add(i1.next());
178 }
101 // 179 //
102 // sample 0..52 180 ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size());
103 Nodeid head = Nodeid.fromAscii("30bd389788464287cee22ccff54c330a4b715de5"); 181 rv.addAll(merged);
104 Nodeid root = Nodeid.fromAscii("dbd663faec1f0175619cf7668bddc6350548b8d6"); 182 rv.addAll(mine);
105 Nodeid p1 = NULL, p2 = NULL; 183 return rv;
106 RemoteBranch fake = new RemoteBranch(head, root, p1, p2);
107 return Collections.singletonList(fake);
108 } 184 }
109 185
110 // RemoteBranch not necessarily a 'true' remote branch. I use this structure to keep root, head, and root's parents, any 186 // somewhat similar to Outgoing.findCommonWithRemote()
111 // of them can be known locally, parent might be only one (when I split 'true' RemoteBranch in between because of locally known node 187 private static List<BranchChain> calculateMissingBranches(HgChangelog.ParentWalker pwLocal, HgRemoteRepository hgRemote) throws HgException {
112 private static List<Nodeid> completeBranch(HgRemoteRepository hgRemote, RemoteBranch rb) throws HgException { 188 List<Nodeid> remoteHeads = hgRemote.heads();
189 LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local
190 LinkedList<Nodeid> toQuery = new LinkedList<Nodeid>(); // these need further queries to find common
191 for (Nodeid rh : remoteHeads) {
192 if (pwLocal.knownNode(rh)) {
193 common.add(rh);
194 } else {
195 toQuery.add(rh);
196 }
197 }
198 if (toQuery.isEmpty()) {
199 return Collections.emptyList(); // no incoming changes
200 }
201 LinkedList<BranchChain> branches2load = new LinkedList<BranchChain>(); // return value
202 // detailed comments are in Outgoing.findCommonWithRemote
203 LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>();
204 // records relation between branch head and its parent branch, if any
205 HashMap<Nodeid, BranchChain> head2chain = new HashMap<Nodeid, Incoming.BranchChain>();
206 while (!toQuery.isEmpty()) {
207 List<RemoteBranch> remoteBranches = hgRemote.branches(toQuery); //head, root, first parent, second parent
208 toQuery.clear();
209 while(!remoteBranches.isEmpty()) {
210 RemoteBranch rb = remoteBranches.remove(0);
211 BranchChain chainElement = head2chain.get(rb.head);
212 if (chainElement == null) {
213 chainElement = new BranchChain(rb.head);
214 // record this unknown branch to download later
215 branches2load.add(chainElement);
216 }
217 if (pwLocal.knownNode(rb.root)) {
218 // we known branch start, common head is somewhere in its descendants line
219 checkUp2Head.add(rb);
220 } else {
221 chainElement.branchRoot = rb.root;
222 // dig deeper in the history, if necessary
223 if (!NULL.equals(rb.p1) && !pwLocal.knownNode(rb.p1)) {
224 toQuery.add(rb.p1);
225 head2chain.put(rb.p1, chainElement.p1 = new BranchChain(rb.p1));
226 }
227 if (!NULL.equals(rb.p2) && !pwLocal.knownNode(rb.p2)) {
228 toQuery.add(rb.p2);
229 head2chain.put(rb.p2, chainElement.p2 = new BranchChain(rb.p2));
230 }
231 }
232 }
233 }
234 for (RemoteBranch rb : checkUp2Head) {
235 Nodeid h = rb.head;
236 Nodeid r = rb.root;
237 int watchdog = 1000;
238 BranchChain bc = head2chain.get(h);
239 assert bc != null;
240 // if we know branch root locally, there could be no parent branch chain elements.
241 assert bc.p1 == null;
242 assert bc.p2 == null;
243 do {
244 List<Nodeid> between = hgRemote.between(h, r);
245 if (between.isEmpty()) {
246 bc.branchRoot = r;
247 break;
248 } else {
249 Collections.reverse(between);
250 for (Nodeid n : between) {
251 if (pwLocal.knownNode(n)) {
252 r = n;
253 } else {
254 h = n;
255 break;
256 }
257 }
258 Nodeid lastInBetween = between.get(between.size() - 1);
259 if (r.equals(lastInBetween)) {
260 bc.branchRoot = r;
261 break;
262 } else if (h.equals(lastInBetween)) { // the only chance for current head pointer to point to the sequence tail
263 // is when r is second from the between list end (iow, head,1,[2],4,8...,root)
264 bc.branchRoot = r;
265 break;
266 }
267 }
268 } while(--watchdog > 0);
269 if (watchdog == 0) {
270 throw new HgBadStateException(String.format("Can't narrow down branch [%s, %s]", rb.head.shortNotation(), rb.root.shortNotation()));
271 }
272 }
273 return branches2load;
274 }
275
276 /**
277 * @return list of nodeids from branchRoot to branchHead, inclusive. IOW, first element of the list is always root of the branch
278 */
279 private static List<Nodeid> completeBranch(HgRemoteRepository hgRemote, final Nodeid branchRoot, final Nodeid branchHead) throws HgException {
113 class DataEntry { 280 class DataEntry {
114 public final Nodeid queryHead; 281 public final Nodeid queryHead;
115 public final int headIndex; 282 public final int headIndex;
116 public List<Nodeid> entries; 283 public List<Nodeid> entries;
117 284
120 headIndex = index; 287 headIndex = index;
121 entries = data; 288 entries = data;
122 } 289 }
123 }; 290 };
124 291
125 List<Nodeid> initial = hgRemote.between(rb.head, rb.root); 292 List<Nodeid> initial = hgRemote.between(branchHead, branchRoot);
126 Nodeid[] result = new Nodeid[1 << initial.size()]; 293 Nodeid[] result = new Nodeid[1 + (1 << initial.size())];
127 result[0] = rb.head; 294 result[0] = branchHead;
128 int rootIndex = -1; // index in the result, where to place branche's root. 295 int rootIndex = -1; // index in the result, where to place branche's root.
296 if (initial.isEmpty()) {
297 rootIndex = 1;
298 } else if (initial.size() == 1) {
299 rootIndex = 2;
300 }
129 LinkedList<DataEntry> datas = new LinkedList<DataEntry>(); 301 LinkedList<DataEntry> datas = new LinkedList<DataEntry>();
130 // DataEntry in datas has entries list filled with 'between' data, whereas 302 // DataEntry in datas has entries list filled with 'between' data, whereas
131 // DataEntry in toQuery keeps only nodeid and its index, with entries to be initialized before 303 // DataEntry in toQuery keeps only nodeid and its index, with entries to be initialized before
132 // moving to datas. 304 // moving to datas.
133 LinkedList<DataEntry> toQuery = new LinkedList<DataEntry>(); 305 LinkedList<DataEntry> toQuery = new LinkedList<DataEntry>();
134 // 306 //
135 datas.add(new DataEntry(rb.head, 0, initial)); 307 datas.add(new DataEntry(branchHead, 0, initial));
136 int totalQueries = 1; 308 int totalQueries = 1;
137 HashSet<Nodeid> queried = new HashSet<Nodeid>(); 309 HashSet<Nodeid> queried = new HashSet<Nodeid>();
138 while(!datas.isEmpty()) { 310 while(!datas.isEmpty()) {
139 // keep record of those planned to be queried next time we call between() 311 // keep record of those planned to be queried next time we call between()
140 // although may keep these in queried, if really don't want separate collection 312 // although may keep these in queried, if really don't want separate collection
169 // for each query, create an between request range, keep record Range->DataEntry to know range's start index 341 // for each query, create an between request range, keep record Range->DataEntry to know range's start index
170 LinkedList<HgRemoteRepository.Range> betweenBatch = new LinkedList<HgRemoteRepository.Range>(); 342 LinkedList<HgRemoteRepository.Range> betweenBatch = new LinkedList<HgRemoteRepository.Range>();
171 HashMap<HgRemoteRepository.Range, DataEntry> rangeToEntry = new HashMap<HgRemoteRepository.Range, DataEntry>(); 343 HashMap<HgRemoteRepository.Range, DataEntry> rangeToEntry = new HashMap<HgRemoteRepository.Range, DataEntry>();
172 for (DataEntry de : toQuery) { 344 for (DataEntry de : toQuery) {
173 queried.add(de.queryHead); 345 queried.add(de.queryHead);
174 HgRemoteRepository.Range r = new HgRemoteRepository.Range(rb.root, de.queryHead); 346 HgRemoteRepository.Range r = new HgRemoteRepository.Range(branchRoot, de.queryHead);
175 betweenBatch.add(r); 347 betweenBatch.add(r);
176 rangeToEntry.put(r, de); 348 rangeToEntry.put(r, de);
177 } 349 }
178 if (!betweenBatch.isEmpty()) { 350 if (!betweenBatch.isEmpty()) {
179 Map<Range, List<Nodeid>> between = hgRemote.between(betweenBatch); 351 Map<Range, List<Nodeid>> between = hgRemote.between(betweenBatch);
195 toQuery.clear(); 367 toQuery.clear();
196 } 368 }
197 if (rootIndex == -1) { 369 if (rootIndex == -1) {
198 throw new HgBadStateException("Shall not happen, provided between output is correct"); // FIXME 370 throw new HgBadStateException("Shall not happen, provided between output is correct"); // FIXME
199 } 371 }
200 result[rootIndex] = rb.root; 372 result[rootIndex] = branchRoot;
201 boolean resultOk = true; 373 boolean resultOk = true;
202 LinkedList<Nodeid> fromRootToHead = new LinkedList<Nodeid>(); 374 LinkedList<Nodeid> fromRootToHead = new LinkedList<Nodeid>();
203 for (int i = 0; i <= rootIndex; i++) { 375 for (int i = 0; i <= rootIndex; i++) {
204 Nodeid n = result[i]; 376 Nodeid n = result[i];
205 if (n == null) { 377 if (n == null) {