/**
  * Update the tree according to the list of loaded roots
  *
  * @param roots the list of roots to add to the tree
  * @param uncheckedCommits the map from vcs root to commit identifiers that should be
  *     uncheckedCommits
  */
 private void updateTree(List<Root> roots, Map<VirtualFile, Set<String>> uncheckedCommits) {
   myTreeRoot.removeAllChildren();
   if (roots == null) {
     roots = Collections.emptyList();
   }
   for (Root r : roots) {
     CheckedTreeNode rootNode = new CheckedTreeNode(r);
     Status status = new Status();
     status.root = r;
     rootNode.add(new DefaultMutableTreeNode(status, false));
     Set<String> unchecked =
         uncheckedCommits != null && uncheckedCommits.containsKey(r.root)
             ? uncheckedCommits.get(r.root)
             : Collections.<String>emptySet();
     for (Commit c : r.commits) {
       CheckedTreeNode child = new CheckedTreeNode(c);
       rootNode.add(child);
       child.setChecked(r.remoteName != null && !unchecked.contains(c.commitId()));
     }
     myTreeRoot.add(rootNode);
   }
 }
  /**
   * Load VCS roots
   *
   * @param project the project
   * @param roots the VCS root list
   * @param exceptions the list of of exceptions to use
   * @param fetchData if true, the data for remote is fetched.
   * @return the loaded information about vcs roots
   */
  private static List<Root> loadRoots(
      final Project project,
      final List<VirtualFile> roots,
      final Collection<VcsException> exceptions,
      final boolean fetchData) {
    final ArrayList<Root> rc = new ArrayList<Root>();
    for (VirtualFile root : roots) {
      try {
        Root r = new Root();
        rc.add(r);
        r.root = root;
        GitBranch b = GitBranch.current(project, root);
        if (b != null) {
          r.currentBranch = b.getFullName();
          r.remoteName = b.getTrackedRemoteName(project, root);
          r.remoteBranch = b.getTrackedBranchName(project, root);
          if (r.remoteName != null) {
            if (fetchData && !r.remoteName.equals(".")) {
              GitLineHandler fetch = new GitLineHandler(project, root, GitCommand.FETCH);
              fetch.addParameters(r.remoteName, "-v");
              Collection<VcsException> exs = GitHandlerUtil.doSynchronouslyWithExceptions(fetch);
              exceptions.addAll(exs);
            }
            GitBranch tracked = b.tracked(project, root);
            assert tracked != null : "Tracked branch cannot be null here";
            final boolean trackedBranchExists = tracked.exists(root);
            if (!trackedBranchExists) {
              LOG.info("loadRoots tracked branch " + tracked + " doesn't exist yet");
            }

            // check what remote commits are not yet merged
            if (trackedBranchExists) {
              GitSimpleHandler toPull = new GitSimpleHandler(project, root, GitCommand.LOG);
              toPull.addParameters(
                  "--pretty=format:%H", r.currentBranch + ".." + tracked.getFullName());
              toPull.setNoSSH(true);
              toPull.setStdoutSuppressed(true);
              StringScanner su = new StringScanner(toPull.run());
              while (su.hasMoreData()) {
                if (su.line().trim().length() != 0) {
                  r.remoteCommits++;
                }
              }
            }

            // check what local commits are to be pushed
            GitSimpleHandler toPush = new GitSimpleHandler(project, root, GitCommand.LOG);
            // if the tracked branch doesn't exist yet (nobody pushed the branch yet), show all
            // commits on this branch.
            final String revisions =
                trackedBranchExists
                    ? tracked.getFullName() + ".." + r.currentBranch
                    : r.currentBranch;
            toPush.addParameters("--pretty=format:%H%x20%ct%x20%at%x20%s%n%P", revisions);
            toPush.setNoSSH(true);
            toPush.setStdoutSuppressed(true);
            StringScanner sp = new StringScanner(toPush.run());
            while (sp.hasMoreData()) {
              if (sp.isEol()) {
                sp.line();
                continue;
              }
              Commit c = new Commit();
              c.root = r;
              String hash = sp.spaceToken();
              String time = sp.spaceToken();
              c.revision = new GitRevisionNumber(hash, new Date(Long.parseLong(time) * 1000L));
              c.authorTime = sp.spaceToken();
              c.message = sp.line();
              c.isMerge = sp.line().indexOf(' ') != -1;
              r.commits.add(c);
            }
          }
        }
      } catch (VcsException e) {
        exceptions.add(e);
      }
    }
    return rc;
  }
  private RebaseInfo collectRebaseInfo() {
    final Set<VirtualFile> roots = new HashSet<VirtualFile>();
    final Set<VirtualFile> rootsWithMerges = new HashSet<VirtualFile>();
    final Map<VirtualFile, List<String>> reorderedCommits =
        new HashMap<VirtualFile, List<String>>();
    final Map<VirtualFile, Set<String>> uncheckedCommits = new HashMap<VirtualFile, Set<String>>();
    for (int i = 0; i < myTreeRoot.getChildCount(); i++) {
      CheckedTreeNode node = (CheckedTreeNode) myTreeRoot.getChildAt(i);
      Root r = (Root) node.getUserObject();
      Set<String> unchecked = new HashSet<String>();
      uncheckedCommits.put(r.root, unchecked);
      if (r.commits.size() == 0) {
        if (r.remoteCommits > 0) {
          roots.add(r.root);
        }
        continue;
      }
      boolean seenCheckedNode = false;
      boolean reorderNeeded = false;
      boolean seenMerges = false;
      for (int j = 0; j < node.getChildCount(); j++) {
        if (node.getChildAt(j) instanceof CheckedTreeNode) {
          CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j);
          Commit commit = (Commit) commitNode.getUserObject();
          seenMerges |= commit.isMerge;
          if (commitNode.isChecked()) {
            seenCheckedNode = true;
          } else {
            unchecked.add(commit.commitId());
            if (seenCheckedNode) {
              reorderNeeded = true;
            }
          }
        }
      }
      if (seenMerges) {
        rootsWithMerges.add(r.root);
      }
      if (r.remoteCommits > 0 || reorderNeeded) {
        roots.add(r.root);
      }
      if (reorderNeeded) {
        List<String> reordered = new ArrayList<String>();
        for (int j = 0; j < node.getChildCount(); j++) {
          if (node.getChildAt(j) instanceof CheckedTreeNode) {
            CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j);
            if (!commitNode.isChecked()) {
              Commit commit = (Commit) commitNode.getUserObject();
              reordered.add(commit.revision.asString());
            }
          }
        }
        for (int j = 0; j < node.getChildCount(); j++) {
          if (node.getChildAt(j) instanceof CheckedTreeNode) {
            CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j);
            if (commitNode.isChecked()) {
              Commit commit = (Commit) commitNode.getUserObject();
              reordered.add(commit.revision.asString());
            }
          }
        }
        Collections.reverse(reordered);
        reorderedCommits.put(r.root, reordered);
      }
    }
    final GitVcsSettings.UpdateChangesPolicy p =
        UpdatePolicyUtils.getUpdatePolicy(myStashRadioButton, myShelveRadioButton);
    assert p == GitVcsSettings.UpdateChangesPolicy.STASH
        || p == GitVcsSettings.UpdateChangesPolicy.SHELVE;

    return new RebaseInfo(reorderedCommits, rootsWithMerges, uncheckedCommits, roots, p);
  }