Mercurial > hg4j
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) { |