@NotNull
 private Set<VcsRef> readBranches(@NotNull GitRepository repository) {
   StopWatch sw = StopWatch.start("readBranches in " + repository.getRoot().getName());
   VirtualFile root = repository.getRoot();
   repository.update();
   Collection<GitLocalBranch> localBranches = repository.getBranches().getLocalBranches();
   Collection<GitRemoteBranch> remoteBranches = repository.getBranches().getRemoteBranches();
   Set<VcsRef> refs = new THashSet<VcsRef>(localBranches.size() + remoteBranches.size());
   for (GitLocalBranch localBranch : localBranches) {
     refs.add(
         myVcsObjectsFactory.createRef(
             localBranch.getHash(), localBranch.getName(), GitRefManager.LOCAL_BRANCH, root));
   }
   for (GitRemoteBranch remoteBranch : remoteBranches) {
     refs.add(
         myVcsObjectsFactory.createRef(
             remoteBranch.getHash(),
             remoteBranch.getNameForLocalOperations(),
             GitRefManager.REMOTE_BRANCH,
             root));
   }
   String currentRevision = repository.getCurrentRevision();
   if (currentRevision != null) { // null => fresh repository
     refs.add(
         myVcsObjectsFactory.createRef(
             HashImpl.build(currentRevision), "HEAD", GitRefManager.HEAD, root));
   }
   sw.report();
   return refs;
 }
  /**
   * This method calculates the commits and diff information against the tip of current branch and
   * the common ancestor of source branch (current branch) and target branch (selected remote
   * branch).
   *
   * <p>If there is no common parent (two branches are parallel), return an empty
   * GitCommitCompareInfo
   *
   * <p>This is potentially an expensive calculation, probably should do it on a background thread.
   * We will also attempt to cache the result
   *
   * <p>default access for testing so we bypass UI code, TODO: reevaluate the testing to properly
   * shutoff the access level
   *
   * @return gitChangesContainer on what has changed on source branch
   */
  GitChangesContainer getMyChangesCompareInfo() throws VcsException {

    final GitBranch currBranch = this.getSourceBranch();

    final GitRemoteBranch selectedRemoteBranch = this.getTargetBranch();

    // if source branch or currentBranch isn't set, just return empty diff
    if (selectedRemoteBranch == null || currBranch == null) {
      return GitChangesContainer.createChangesContainer(
          null,
          null,
          null,
          null,
          getDiffCompareInfoProvider().getEmptyDiff(this.gitRepository),
          this.gitRepository);
    }

    final String remoteBranchHash = selectedRemoteBranch.getHash().asString();
    final String currBranchHash = currBranch.getHash().asString();

    try {
      GitCommitCompareInfo changes =
          this.diffCache.get(new Pair<String, String>(currBranchHash, remoteBranchHash));

      return GitChangesContainer.createChangesContainer(
          currBranch.getName(),
          selectedRemoteBranch.getName(),
          currBranchHash,
          remoteBranchHash,
          changes,
          this.gitRepository);
    } catch (ExecutionException e) {
      throw new VcsException(e.getCause());
    }
  }
 @NotNull
 private static List<GithubFullPath> getAvailableForksFromGit(
     @NotNull GitRepository gitRepository) {
   List<GithubFullPath> forks = new ArrayList<GithubFullPath>();
   for (GitRemoteBranch remoteBranch : gitRepository.getBranches().getRemoteBranches()) {
     for (String url : remoteBranch.getRemote().getUrls()) {
       if (GithubUrlUtil.isGithubUrl(url)) {
         GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(url);
         if (path != null) {
           forks.add(path);
           break;
         }
       }
     }
   }
   return forks;
 }
  /**
   * This method for now assumes the default branch name is master
   *
   * <p>If there is no master, return the first branch on the list or null for empty list
   *
   * <p>We should get the default branch from TF if necessary, but that's a server call
   */
  @Nullable
  private GitRemoteBranch getDefaultBranch(@NotNull final List<GitRemoteBranch> remoteBranches) {
    assert remoteBranches != null;
    if (remoteBranches.isEmpty() || this.tfGitRemotes.isEmpty()) {
      return null;
    }

    final GitRemote firstTfRemote = this.tfGitRemotes.iterator().next();

    final String masterBranchName = String.format("%s/master", firstTfRemote.getName());
    for (GitRemoteBranch remoteBranch : remoteBranches) {
      if (remoteBranch.getName().equals(masterBranchName)) {
        return remoteBranch;
      }
    }

    return remoteBranches.get(0);
  }
  /* if user has changed the dropdown while we calculate the diff, this diff is out of date */
  private boolean isChangesUpToDate(final GitChangesContainer changesContainer) {

    // target branches must match
    final GitRemoteBranch targetBranch = this.getTargetBranch();
    if (changesContainer.getTargetBranchName() != null && targetBranch != null) {
      if (!changesContainer.getTargetBranchName().equals(targetBranch.getName())) {
        return false;
      }
    }

    // source branches must match
    final GitLocalBranch sourceBranch = this.getSourceBranch();
    if (changesContainer.getSourceBranchName() != null && sourceBranch != null) {
      if (!changesContainer.getSourceBranchName().equals(sourceBranch.getName())) {
        return false;
      }
    }

    return true;
  }
  private ListenableFuture<Pair<String, GitCommandResult>> doPushCommits(
      @NotNull final GitRepository gitRepository,
      @NotNull final GitLocalBranch localBranch,
      @NotNull final GitRemote gitRemote,
      @NotNull final ProgressIndicator indicator) {
    // just set the result without going off to another thread, we should already be in a background
    // task
    SettableFuture<Pair<String, GitCommandResult>> pushResult =
        SettableFuture.<Pair<String, GitCommandResult>>create();

    indicator.setText(TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_PUSH_TITLE));
    final Git git = ServiceManager.getService(Git.class);

    final GitRemoteBranch trackingBranch = localBranch.findTrackedBranch(gitRepository);

    final String createdBranchNameOnServer;
    final StringBuilder pushSpec = new StringBuilder(localBranch.getName());
    if (trackingBranch != null && trackingBranch.getRemote().equals(gitRemote)) {
      // if the tracking branch is on the same remote, we should update that
      pushSpec.append(":").append(trackingBranch.getNameForRemoteOperations());
      createdBranchNameOnServer = trackingBranch.getNameForRemoteOperations();
    } else {
      createdBranchNameOnServer = localBranch.getName();
    }

    final String fetchUrl = getFetchUrl(gitRemote);
    final String pushSpecStr = pushSpec.toString();
    final String gitRemoteName = gitRemote.getName();
    logger.debug("Pushing {} to {}: {}", pushSpecStr, gitRemoteName, fetchUrl);
    final GitCommandResult result =
        git.push(gitRepository, gitRemoteName, fetchUrl, pushSpecStr, true);

    if (result.success()) {
      pushResult.set(Pair.create(createdBranchNameOnServer, result));
    } else {
      final String errMsg = result.getErrorOutputAsJoinedString();
      pushResult.setException(new GitExecutionException(errMsg, null));
    }

    return pushResult;
  }
  @NotNull
  @Override
  public Collection<VcsRef> readAllRefs(@NotNull VirtualFile root) throws VcsException {
    if (!isRepositoryReady(root)) {
      return Collections.emptyList();
    }

    GitRepository repository = getRepository(root);
    repository.update();
    Collection<GitLocalBranch> localBranches = repository.getBranches().getLocalBranches();
    Collection<GitRemoteBranch> remoteBranches = repository.getBranches().getRemoteBranches();
    Collection<VcsRef> refs = new ArrayList<VcsRef>(localBranches.size() + remoteBranches.size());
    for (GitLocalBranch localBranch : localBranches) {
      refs.add(
          myVcsObjectsFactory.createRef(
              HashImpl.build(localBranch.getHash()),
              localBranch.getName(),
              GitRefManager.LOCAL_BRANCH,
              root));
    }
    for (GitRemoteBranch remoteBranch : remoteBranches) {
      refs.add(
          myVcsObjectsFactory.createRef(
              HashImpl.build(remoteBranch.getHash()),
              remoteBranch.getNameForLocalOperations(),
              GitRefManager.REMOTE_BRANCH,
              root));
    }
    String currentRevision = repository.getCurrentRevision();
    if (currentRevision != null) { // null => fresh repository
      refs.add(
          myVcsObjectsFactory.createRef(
              HashImpl.build(currentRevision), "HEAD", GitRefManager.HEAD, root));
    }

    refs.addAll(readTags(root));
    return refs;
  }
  public GitPullRequest generateGitPullRequest(
      @NotNull final String title,
      @NotNull final String description,
      @NotNull final String branchNameOnRemoteServer,
      @NotNull final GitRemoteBranch targetBranch) {
    final GitPullRequest pullRequest = new GitPullRequest();
    pullRequest.setTitle(title);
    pullRequest.setDescription(description);
    pullRequest.setSourceRefName(String.format(TF_REF_FORMATTER, branchNameOnRemoteServer));
    pullRequest.setTargetRefName(
        String.format(TF_REF_FORMATTER, targetBranch.getNameForRemoteOperations()));

    return pullRequest;
  }
 @Override
 public int compare(GitRemoteBranch branch1, GitRemoteBranch branch2) {
   return StringUtil.naturalCompare(branch1.getFullName(), branch2.getFullName());
 }
  /**
   * Create pull request on a background thread
   *
   * <p>This method will first check to see if the local branch has a tracking branch: yes: push the
   * commits to the remote tracking branch no: try create a remote branch matching the local branch
   * name exactly, with the remote set to the GitRemote of the target branch
   *
   * <p>If push fails for whatever reason, stop and show an error message
   *
   * <p>After we push the local branch, then create the pull request. Pull request link should be
   * returned in a notification bubble
   */
  public void createPullRequest() {
    /* verifying branch selections */
    final GitLocalBranch sourceBranch = this.getSourceBranch();
    final GitRemoteBranch targetBranch = this.getTargetBranch();

    if (sourceBranch == null) {
      // how did we get here? validation failed?
      notifyCreateFailedError(
          project, TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_ERRORS_SOURCE_EMPTY));
      return;
    }

    if (targetBranch == null) {
      // how did we get here? validation failed?
      notifyCreateFailedError(
          project, TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_ERRORS_TARGET_NOT_SELECTED));
      return;
    }

    if (targetBranch.equals(this.getRemoteTrackingBranch())) {
      // how did we get here? Didn't we filter you out?
      notifyCreateFailedError(
          project,
          TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_ERRORS_TARGET_IS_LOCAL_TRACKING));
      return;
    }

    // TODO Determine the correct/best way to get the remote url
    final String gitRemoteUrl = TfGitHelper.getTfGitRemote(gitRepository).getFirstUrl();
    final CreatePullRequestModel createModel = this;
    /* Let's keep all server interactions to a background thread */
    final Task.Backgroundable createPullRequestTask =
        new Task.Backgroundable(
            project,
            TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_DIALOG_TITLE),
            true,
            PerformInBackgroundOption.DEAF) {
          @Override
          public void run(@NotNull ProgressIndicator progressIndicator) {
            // get context from manager, create PAT if needed, and store in active context
            final ServerContext context =
                ServerContextManager.getInstance()
                    .getAuthenticatedContext(
                        gitRemoteUrl,
                        TfPluginBundle.message(TfPluginBundle.KEY_PAT_TOKEN_DESC),
                        true);

            if (context == null) {
              notifyCreateFailedError(
                  project,
                  TfPluginBundle.message(
                      TfPluginBundle.KEY_ERRORS_AUTH_NOT_SUCCESSFUL, gitRemoteUrl));
              return;
            }

            ListenableFuture<Pair<String, GitCommandResult>> pushResult =
                doPushCommits(
                    gitRepository, sourceBranch, targetBranch.getRemote(), progressIndicator);

            Futures.addCallback(
                pushResult,
                new FutureCallback<Pair<String, GitCommandResult>>() {
                  @Override
                  public void onSuccess(@Nullable Pair<String, GitCommandResult> result) {
                    if (result != null && StringUtils.isNotEmpty(result.getFirst())) {
                      final String title = createModel.getTitle();
                      final String description = createModel.getDescription();
                      final String branchNameOnRemoteServer = result.getFirst();

                      doCreatePullRequest(
                          project,
                          context,
                          title,
                          description,
                          branchNameOnRemoteServer,
                          targetBranch);
                    } else {
                      // I really don't have anything else to say, push failed, the title says it
                      // all
                      // I have no error message to be more specific
                      notifyPushFailedError(createModel.getProject(), StringUtils.EMPTY);
                    }
                  }

                  @Override
                  public void onFailure(Throwable t) {
                    notifyPushFailedError(createModel.getProject(), t.getLocalizedMessage());
                  }
                });
          }
        };

    createPullRequestTask.queue();
  }