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(); }