/** * returns in order from branch root to head for a non-empty BranchChain, shall return modifiable * list */ public List<Nodeid> visitBranches(BranchChain bc) throws HgRemoteConnectionException { if (bc == null) { return Collections.emptyList(); } List<Nodeid> mine = completeBranch(bc.branchRoot, bc.branchHead); if (bc.isTerminal() || bc.isRepoStart()) { return mine; } List<Nodeid> parentBranch1 = visitBranches(bc.p1); List<Nodeid> parentBranch2 = visitBranches(bc.p2); // merge LinkedList<Nodeid> merged = new LinkedList<Nodeid>(); ListIterator<Nodeid> i1 = parentBranch1.listIterator(), i2 = parentBranch2.listIterator(); while (i1.hasNext() && i2.hasNext()) { Nodeid n1 = i1.next(); Nodeid n2 = i2.next(); if (n1.equals(n2)) { merged.addLast(n1); } else { // first different => add both, and continue adding both tails sequentially merged.add(n2); merged.add(n1); break; } } // copy rest of second parent branch while (i2.hasNext()) { merged.add(i2.next()); } // copy rest of first parent branch while (i1.hasNext()) { merged.add(i1.next()); } // ArrayList<Nodeid> rv = new ArrayList<Nodeid>(mine.size() + merged.size()); rv.addAll(merged); rv.addAll(mine); return rv; }
// somewhat similar to Outgoing.findCommonWithRemote() public List<BranchChain> calculateMissingBranches() throws HgRemoteConnectionException { List<Nodeid> remoteHeads = remoteRepo.heads(); LinkedList<Nodeid> common = new LinkedList<Nodeid>(); // these remotes are known in local LinkedHashSet<Nodeid> toQuery = new LinkedHashSet<Nodeid>(); // these need further queries to find common for (Nodeid rh : remoteHeads) { if (localRepo.knownNode(rh)) { common.add(rh); } else { toQuery.add(rh); } } if (toQuery.isEmpty()) { return Collections.emptyList(); // no incoming changes } LinkedList<BranchChain> branches2load = new LinkedList<BranchChain>(); // return value // detailed comments are in Outgoing.findCommonWithRemote LinkedList<RemoteBranch> checkUp2Head = new LinkedList<RemoteBranch>(); // records relation between branch head and its parent branch, if any HashMap<Nodeid, BranchChain> head2chain = new HashMap<Nodeid, BranchChain>(); while (!toQuery.isEmpty()) { List<RemoteBranch> remoteBranches = remoteRepo.branches( new ArrayList<Nodeid>(toQuery)); // head, root, first parent, second parent toQuery.clear(); while (!remoteBranches.isEmpty()) { RemoteBranch rb = remoteBranches.remove(0); BranchChain chainElement = head2chain.get(rb.head); if (chainElement == null) { chainElement = new BranchChain(rb.head); // record this unknown branch to download later branches2load.add(chainElement); // the only chance we'll need chainElement in the head2chain is when we know this branch's // root head2chain.put(rb.head, chainElement); } if (localRepo.knownNode(rb.root)) { // we known branch start, common head is somewhere in its descendants line checkUp2Head.add(rb); } else { chainElement.branchRoot = rb.root; // dig deeper in the history, if necessary boolean hasP1 = !rb.p1.isNull(), hasP2 = !rb.p2.isNull(); if (hasP1 && !localRepo.knownNode(rb.p1)) { toQuery.add(rb.p1); // we might have seen parent node already, and recorded it as a branch chain // we shall reuse existing BC to get it completely initialized (head2chain map // on second put with the same key would leave first BC uninitialized. // It seems there's no reason to be afraid (XXX although shall double-check) // that BC's chain would get corrupt (its p1 and p2 fields assigned twice with different // values) // as parents are always the same (and likely, BC that is common would be the last // unknown) BranchChain bc = head2chain.get(rb.p1); if (bc == null) { head2chain.put(rb.p1, bc = new BranchChain(rb.p1)); } chainElement.p1 = bc; } if (hasP2 && !localRepo.knownNode(rb.p2)) { toQuery.add(rb.p2); BranchChain bc = head2chain.get(rb.p2); if (bc == null) { head2chain.put(rb.p2, bc = new BranchChain(rb.p2)); } chainElement.p2 = bc; } if (!hasP1 && !hasP2) { // special case, when we do incoming against blank repository, chainElement.branchRoot // is first unknown element (revision 0). We need to add another fake BranchChain // to fill the promise that terminal BranchChain has branchRoot that is known both // locally and remotely BranchChain fake = new BranchChain(NULL); fake.branchRoot = NULL; chainElement.p1 = chainElement.p2 = fake; } } } } for (RemoteBranch rb : checkUp2Head) { Nodeid h = rb.head; Nodeid r = rb.root; int watchdog = 1000; assert head2chain.containsKey(h); BranchChain bc = head2chain.get(h); assert bc != null : h.toString(); // if we know branch root locally, there could be no parent branch chain elements. assert bc.p1 == null; assert bc.p2 == null; do { List<Nodeid> between = remoteRepo.between(h, r); if (between.isEmpty()) { bc.branchRoot = r; break; } else { Collections.reverse(between); for (Nodeid n : between) { if (localRepo.knownNode(n)) { r = n; } else { h = n; break; } } Nodeid lastInBetween = between.get(between.size() - 1); if (r.equals(lastInBetween)) { bc.branchRoot = r; break; } else if (h.equals( lastInBetween)) { // the only chance for current head pointer to point to the sequence // tail // is when r is second from the between list end (iow, head,1,[2],4,8...,root) bc.branchRoot = r; break; } } } while (--watchdog > 0); if (watchdog == 0) { throw new HgInvalidStateException( String.format( "Can't narrow down branch [%s, %s]", rb.head.shortNotation(), rb.root.shortNotation())); } } if (debug) { System.out.println("calculateMissingBranches:"); for (BranchChain bc : branches2load) { bc.dump(); } } return branches2load; }