/**
  * 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;
  }