Mercurial > jhg
comparison cmdline/org/tmatesoft/hg/console/Incoming.java @ 181:cd3371670f0b
Refactor incoming and outgoing code to be shared with RepositoryComparator. Placeholders for in/out commands. Refactor common remote lookup code
| author | Artem Tikhomirov <tikhomirov.artem@gmail.com> |
|---|---|
| date | Tue, 12 Apr 2011 19:10:38 +0200 |
| parents | 62665d8f0686 |
| children | f26ffe04ced0 |
comparison
equal
deleted
inserted
replaced
| 180:42fe9a94b9d0 | 181:cd3371670f0b |
|---|---|
| 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.io.File; | 19 import java.io.File; |
| 22 import java.net.URL; | 20 import java.net.URL; |
| 23 import java.util.ArrayList; | 21 import java.util.ArrayList; |
| 24 import java.util.Arrays; | 22 import java.util.Arrays; |
| 25 import java.util.Collections; | 23 import java.util.Collections; |
| 26 import java.util.Comparator; | 24 import java.util.Comparator; |
| 27 import java.util.HashMap; | |
| 28 import java.util.HashSet; | 25 import java.util.HashSet; |
| 29 import java.util.Iterator; | 26 import java.util.Iterator; |
| 30 import java.util.LinkedHashMap; | 27 import java.util.LinkedHashMap; |
| 31 import java.util.LinkedList; | 28 import java.util.LinkedList; |
| 32 import java.util.List; | 29 import java.util.List; |
| 33 import java.util.ListIterator; | 30 import java.util.ListIterator; |
| 34 import java.util.Map; | |
| 35 import java.util.Map.Entry; | 31 import java.util.Map.Entry; |
| 36 | 32 |
| 37 import org.tmatesoft.hg.core.HgBadStateException; | |
| 38 import org.tmatesoft.hg.core.HgException; | 33 import org.tmatesoft.hg.core.HgException; |
| 39 import org.tmatesoft.hg.core.Nodeid; | 34 import org.tmatesoft.hg.core.Nodeid; |
| 40 import org.tmatesoft.hg.internal.ConfigFile; | 35 import org.tmatesoft.hg.internal.ConfigFile; |
| 41 import org.tmatesoft.hg.internal.Internals; | 36 import org.tmatesoft.hg.internal.Internals; |
| 37 import org.tmatesoft.hg.internal.RepositoryComparator; | |
| 38 import org.tmatesoft.hg.internal.RepositoryComparator.BranchChain; | |
| 42 import org.tmatesoft.hg.repo.HgChangelog; | 39 import org.tmatesoft.hg.repo.HgChangelog; |
| 43 import org.tmatesoft.hg.repo.HgLookup; | 40 import org.tmatesoft.hg.repo.HgLookup; |
| 44 import org.tmatesoft.hg.repo.HgRemoteRepository; | 41 import org.tmatesoft.hg.repo.HgRemoteRepository; |
| 45 import org.tmatesoft.hg.repo.HgRemoteRepository.Range; | |
| 46 import org.tmatesoft.hg.repo.HgRemoteRepository.RemoteBranch; | |
| 47 import org.tmatesoft.hg.repo.HgRepository; | 42 import org.tmatesoft.hg.repo.HgRepository; |
| 48 | 43 |
| 49 | 44 |
| 50 /** | 45 /** |
| 51 * WORK IN PROGRESS, DO NOT USE | 46 * WORK IN PROGRESS, DO NOT USE |
| 65 HgRepository hgRepo = cmdLineOpts.findRepository(); | 60 HgRepository hgRepo = cmdLineOpts.findRepository(); |
| 66 if (hgRepo.isInvalid()) { | 61 if (hgRepo.isInvalid()) { |
| 67 System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); | 62 System.err.printf("Can't find repository in: %s\n", hgRepo.getLocation()); |
| 68 return; | 63 return; |
| 69 } | 64 } |
| 70 String key = "svnkit"; | 65 HgRemoteRepository hgRemote = new HgLookup().detectRemote("svnkit", hgRepo); |
| 71 ConfigFile cfg = new Internals().newConfigFile(); | 66 if (hgRemote.isInvalid()) { |
| 72 cfg.addLocation(new File(System.getProperty("user.home"), ".hgrc")); | 67 System.err.printf("Remote repository %s is not valid", hgRemote.getLocation()); |
| 73 String server = cfg.getSection("paths").get(key); | 68 return; |
| 74 if (server == null) { | 69 } |
| 75 throw new HgException(String.format("Can't find server %s specification in the config", key)); | |
| 76 } | |
| 77 HgRemoteRepository hgRemote = new HgLookup().detect(new URL(server)); | |
| 78 // | 70 // |
| 79 // in fact, all we need from changelog is set of all nodeids. However, since ParentWalker reuses same Nodeids, it's not too expensive | 71 // in fact, all we need from changelog is set of all nodeids. However, since ParentWalker reuses same Nodeids, it's not too expensive |
| 80 // to reuse it here, XXX although later this may need to be refactored | 72 // to reuse it here, XXX although later this may need to be refactored |
| 81 final HgChangelog.ParentWalker pw = hgRepo.getChangelog().new ParentWalker(); | 73 final HgChangelog.ParentWalker pw = hgRepo.getChangelog().new ParentWalker(); |
| 82 pw.init(); | 74 pw.init(); |
| 83 // | 75 // |
| 84 List<BranchChain> missingBranches0 = calculateMissingBranches(pw, hgRemote); | 76 RepositoryComparator repoCompare = new RepositoryComparator(pw, hgRemote); |
| 77 repoCompare.compare(null); | |
| 78 List<BranchChain> missingBranches0 = repoCompare.calculateMissingBranches(); | |
| 85 for (BranchChain bc : missingBranches0) { | 79 for (BranchChain bc : missingBranches0) { |
| 86 bc.dump(); | 80 bc.dump(); |
| 87 | 81 |
| 88 List<Nodeid> missing = visitBranches(hgRemote, bc); | 82 List<Nodeid> missing = visitBranches(repoCompare, bc); |
| 89 // Collections.reverse(missing); // useful to test output, from newer to older | 83 // Collections.reverse(missing); // useful to test output, from newer to older |
| 90 for (Nodeid n : missing) { | 84 for (Nodeid n : missing) { |
| 91 if (pw.knownNode(n)) { | 85 if (pw.knownNode(n)) { |
| 92 System.out.println("Erroneous to fetch:" + n); | 86 System.out.println("Erroneous to fetch:" + n); |
| 93 } else { | 87 } else { |
| 97 System.out.println("Branch done"); | 91 System.out.println("Branch done"); |
| 98 } | 92 } |
| 99 | 93 |
| 100 } | 94 } |
| 101 | 95 |
| 102 private static class BranchChain { | |
| 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 | 96 |
| 146 private static List<Nodeid> visitBranches(HgRemoteRepository hgRemote, BranchChain bc) throws HgException { | 97 private static List<Nodeid> visitBranches(RepositoryComparator repoCompare, BranchChain bc) throws HgException { |
| 147 if (bc == null) { | 98 if (bc == null) { |
| 148 return Collections.emptyList(); | 99 return Collections.emptyList(); |
| 149 } | 100 } |
| 150 List<Nodeid> mine = completeBranch(hgRemote, bc.branchRoot, bc.branchHead); | 101 List<Nodeid> mine = repoCompare.completeBranch(bc.branchRoot, bc.branchHead); |
| 151 if (bc.isTerminal()) { | 102 if (bc.isTerminal()) { |
| 152 return mine; | 103 return mine; |
| 153 } | 104 } |
| 154 List<Nodeid> parentBranch1 = visitBranches(hgRemote, bc.p1); | 105 List<Nodeid> parentBranch1 = visitBranches(repoCompare, bc.p1); |
| 155 List<Nodeid> parentBranch2 = visitBranches(hgRemote, bc.p2); | 106 List<Nodeid> parentBranch2 = visitBranches(repoCompare, bc.p2); |
| 156 // merge | 107 // merge |
| 157 LinkedList<Nodeid> merged = new LinkedList<Nodeid>(); | 108 LinkedList<Nodeid> merged = new LinkedList<Nodeid>(); |
| 158 ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator(); | 109 ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator(); |
| 159 while (i1.hasNext() && i2.hasNext()) { | 110 while (i1.hasNext() && i2.hasNext()) { |
| 160 Nodeid n1 = i1.next(); | 111 Nodeid n1 = i1.next(); |
| 180 ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size()); | 131 ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size()); |
| 181 rv.addAll(merged); | 132 rv.addAll(merged); |
| 182 rv.addAll(mine); | 133 rv.addAll(mine); |
| 183 return rv; | 134 return rv; |
| 184 } | 135 } |
| 185 | 136 |
| 186 // somewhat similar to Outgoing.findCommonWithRemote() | |
| 187 private static List<BranchChain> calculateMissingBranches(HgChangelog.ParentWalker pwLocal, HgRemoteRepository hgRemote) 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 { | |
| 280 class DataEntry { | |
| 281 public final Nodeid queryHead; | |
| 282 public final int headIndex; | |
| 283 public List<Nodeid> entries; | |
| 284 | |
| 285 public DataEntry(Nodeid head, int index, List<Nodeid> data) { | |
| 286 queryHead = head; | |
| 287 headIndex = index; | |
| 288 entries = data; | |
| 289 } | |
| 290 }; | |
| 291 | |
| 292 List<Nodeid> initial = hgRemote.between(branchHead, branchRoot); | |
| 293 Nodeid[] result = new Nodeid[1 + (1 << initial.size())]; | |
| 294 result[0] = branchHead; | |
| 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 } | |
| 301 LinkedList<DataEntry> datas = new LinkedList<DataEntry>(); | |
| 302 // DataEntry in datas has entries list filled with 'between' data, whereas | |
| 303 // DataEntry in toQuery keeps only nodeid and its index, with entries to be initialized before | |
| 304 // moving to datas. | |
| 305 LinkedList<DataEntry> toQuery = new LinkedList<DataEntry>(); | |
| 306 // | |
| 307 datas.add(new DataEntry(branchHead, 0, initial)); | |
| 308 int totalQueries = 1; | |
| 309 HashSet<Nodeid> queried = new HashSet<Nodeid>(); | |
| 310 while(!datas.isEmpty()) { | |
| 311 // keep record of those planned to be queried next time we call between() | |
| 312 // although may keep these in queried, if really don't want separate collection | |
| 313 HashSet<Nodeid> scheduled = new HashSet<Nodeid>(); | |
| 314 do { | |
| 315 DataEntry de = datas.removeFirst(); | |
| 316 // populate result with discovered elements between de.qiueryRoot and branch's head | |
| 317 for (int i = 1, j = 0; j < de.entries.size(); i = i << 1, j++) { | |
| 318 int idx = de.headIndex + i; | |
| 319 result[idx] = de.entries.get(j); | |
| 320 } | |
| 321 // form next query entries from new unknown elements | |
| 322 if (de.entries.size() > 1) { | |
| 323 /* when entries has only one element, it means de.queryRoot was at head-2 position, and thus | |
| 324 * no new information can be obtained. E.g. when it's 2, it might be case of [0..4] query with | |
| 325 * [1,2] result, and we need one more query to get element 3. | |
| 326 */ | |
| 327 for (int i =1, j = 0; j < de.entries.size(); i = i<<1, j++) { | |
| 328 int idx = de.headIndex + i; | |
| 329 Nodeid x = de.entries.get(j); | |
| 330 if (!queried.contains(x) && !scheduled.contains(x) && (rootIndex == -1 || rootIndex - de.headIndex > 1)) { | |
| 331 /*queries for elements right before head is senseless, but unless we know head's index, do it anyway*/ | |
| 332 toQuery.add(new DataEntry(x, idx, null)); | |
| 333 scheduled.add(x); | |
| 334 } | |
| 335 } | |
| 336 } | |
| 337 } while (!datas.isEmpty()); | |
| 338 if (!toQuery.isEmpty()) { | |
| 339 totalQueries++; | |
| 340 } | |
| 341 // for each query, create an between request range, keep record Range->DataEntry to know range's start index | |
| 342 LinkedList<HgRemoteRepository.Range> betweenBatch = new LinkedList<HgRemoteRepository.Range>(); | |
| 343 HashMap<HgRemoteRepository.Range, DataEntry> rangeToEntry = new HashMap<HgRemoteRepository.Range, DataEntry>(); | |
| 344 for (DataEntry de : toQuery) { | |
| 345 queried.add(de.queryHead); | |
| 346 HgRemoteRepository.Range r = new HgRemoteRepository.Range(branchRoot, de.queryHead); | |
| 347 betweenBatch.add(r); | |
| 348 rangeToEntry.put(r, de); | |
| 349 } | |
| 350 if (!betweenBatch.isEmpty()) { | |
| 351 Map<Range, List<Nodeid>> between = hgRemote.between(betweenBatch); | |
| 352 for (Entry<Range, List<Nodeid>> e : between.entrySet()) { | |
| 353 DataEntry de = rangeToEntry.get(e.getKey()); | |
| 354 assert de != null; | |
| 355 de.entries = e.getValue(); | |
| 356 if (rootIndex == -1 && de.entries.size() == 1) { | |
| 357 // returned sequence of length 1 means we used element from [head-2] as root | |
| 358 int numberOfElementsExcludingRootAndHead = de.headIndex + 1; | |
| 359 rootIndex = numberOfElementsExcludingRootAndHead + 1; | |
| 360 System.out.printf("On query %d found out exact number of missing elements: %d\n", totalQueries, numberOfElementsExcludingRootAndHead); | |
| 361 } | |
| 362 datas.add(de); // queue up to record result and construct further requests | |
| 363 } | |
| 364 betweenBatch.clear(); | |
| 365 rangeToEntry.clear(); | |
| 366 } | |
| 367 toQuery.clear(); | |
| 368 } | |
| 369 if (rootIndex == -1) { | |
| 370 throw new HgBadStateException("Shall not happen, provided between output is correct"); // FIXME | |
| 371 } | |
| 372 result[rootIndex] = branchRoot; | |
| 373 boolean resultOk = true; | |
| 374 LinkedList<Nodeid> fromRootToHead = new LinkedList<Nodeid>(); | |
| 375 for (int i = 0; i <= rootIndex; i++) { | |
| 376 Nodeid n = result[i]; | |
| 377 if (n == null) { | |
| 378 System.out.printf("ERROR: element %d wasn't found\n",i); | |
| 379 resultOk = false; | |
| 380 } | |
| 381 fromRootToHead.addFirst(n); // reverse order | |
| 382 } | |
| 383 System.out.println("Total queries:" + totalQueries); | |
| 384 if (!resultOk) { | |
| 385 throw new HgBadStateException("See console for details"); // FIXME | |
| 386 } | |
| 387 return fromRootToHead; | |
| 388 } | |
| 389 | 137 |
| 390 private static class SequenceConstructor { | 138 private static class SequenceConstructor { |
| 391 | 139 |
| 392 private int[] between(int root, int head) { | 140 private int[] between(int root, int head) { |
| 393 if (head <= (root+1)) { | 141 if (head <= (root+1)) { |
