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