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