private void doCreatePullRequest(
      @NotNull final Project project,
      @NotNull final ServerContext context,
      @NotNull final String title,
      @NotNull final String description,
      @NotNull final String branchNameOnRemoteServer,
      @NotNull final GitRemoteBranch targetBranch) {
    // should this be a method on the serverContext object?
    final URI collectionURI =
        URI.create(
            String.format(
                "%s/%s",
                context.getUri().toString(),
                context.getTeamProjectCollectionReference().getName()));
    final GitHttpClient gitClient = new GitHttpClient(context.getClient(), collectionURI);

    try {
      final UUID repositoryId = context.getGitRepository().getId();
      final UUID projectId = context.getTeamProjectReference().getId();

      final GitPullRequest pullRequestToBeCreated =
          pullRequestHelper.generateGitPullRequest(
              title, description, branchNameOnRemoteServer, targetBranch);

      final GitPullRequest gitPullRequest =
          gitClient.createPullRequest(pullRequestToBeCreated, projectId, repositoryId);

      final String repositoryRemoteUrl = context.getGitRepository().getRemoteUrl();
      notifySuccess(
          project,
          TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_CREATED_TITLE),
          pullRequestHelper.getHtmlMsg(repositoryRemoteUrl, gitPullRequest.getPullRequestId()));

    } catch (Throwable t) {
      // catch everything so we don't bubble up to Intellij
      final Pair<PRCreateStatus, String> parsed =
          pullRequestHelper.parseException(
              t, branchNameOnRemoteServer, targetBranch, context, gitClient);

      if (parsed.getFirst() == PRCreateStatus.DUPLICATE) {
        notifySuccess(
            project,
            TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_ALREADY_EXISTS_TITLE),
            parsed.getSecond());
      } else {
        notifyCreateFailedError(project, parsed.getSecond());
        logger.warn("Create pull request failed", t);
      }
    }
  }
  public String createDefaultTitle(
      final List<GitCommit> commits, final String sourceBranchName, final String targetBranchName) {

    if (commits == null || commits.isEmpty()) {
      return StringUtils.EMPTY;
    }

    if (commits.size() == 1) {
      // if we only have one commit, use it's title as the title of pull request
      final GitCommit commit = commits.get(0);
      final String commitMessage = commit.getSubject();

      // WebAcess use 80 (because it's common code for many things, and 80 is such a magic number),
      // but IMHO 80 is too short for title, set it to 120
      final int titleLength = 120;
      if (commitMessage.length() < titleLength) {
        return commitMessage;
      } else {
        // break at last whitespace right before length 120
        final String shortCommitMessage = commitMessage.substring(0, titleLength);
        return StringUtils.substringBeforeLast(shortCommitMessage, "\\s+");
      }
    }

    // Standard title "merging source branch to target branch"
    return TfPluginBundle.message(
        TfPluginBundle.KEY_CREATE_PR_DEFAULT_TITLE, sourceBranchName, targetBranchName);
  }
  public ModelValidationInfo validate() {
    if (StringUtils.isEmpty(this.getTitle())) {
      return ModelValidationInfo.createWithResource(
          PROP_TITLE, TfPluginBundle.KEY_CREATE_PR_ERRORS_TITLE_EMPTY);
    }

    if (this.getTitle().length() > MAX_SIZE_TITLE) {
      return ModelValidationInfo.createWithResource(
          PROP_TITLE,
          TfPluginBundle.message(
              TfPluginBundle.KEY_CREATE_PR_ERRORS_TITLE_TOO_LONG, MAX_SIZE_TITLE));
    }

    if (StringUtils.isEmpty(this.getDescription())) {
      return ModelValidationInfo.createWithResource(
          PROP_DESCRIPTION, TfPluginBundle.KEY_CREATE_PR_ERRORS_DESCRIPTION_EMPTY);
    }

    if (this.getDescription().length() > MAX_SIZE_DESCRIPTION) {
      return ModelValidationInfo.createWithResource(
          PROP_DESCRIPTION,
          TfPluginBundle.message(
              TfPluginBundle.KEY_CREATE_PR_ERRORS_DESCRIPTION_TOO_LONG, MAX_SIZE_DESCRIPTION));
    }

    if (this.getSourceBranch() == null) {
      return ModelValidationInfo.createWithResource(
          PROP_SOURCE_BRANCH, TfPluginBundle.KEY_CREATE_PR_ERRORS_SOURCE_EMPTY);
    }

    if (this.getTargetBranch() == null) {
      return ModelValidationInfo.createWithResource(
          PROP_TARGET_BRANCH, TfPluginBundle.KEY_CREATE_PR_ERRORS_TARGET_NOT_SELECTED);
    }

    return ModelValidationInfo.NO_ERRORS;
  }
  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;
  }
 public String getHtmlMsg(final String repositoryRemoteUrl, final int id) {
   final String text = TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_CREATED_MESSAGE, id);
   final String webAccessUrl = String.format(WEB_ACCESS_PR_FORMAT, repositoryRemoteUrl, id);
   return String.format(UrlHelper.SHORT_HTTP_LINK_FORMATTER, webAccessUrl, text);
 }
 private void notifyCreateFailedError(final Project project, final String message) {
   notifyError(
       project,
       TfPluginBundle.message(TfPluginBundle.KEY_CREATE_PR_ERRORS_CREATE_FAILED_TITLE),
       message);
 }
  /**
   * 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();
  }