diff options
author | Tor Norbye <tnorbye@google.com> | 2014-09-18 11:43:07 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2014-09-18 11:43:28 -0700 |
commit | e782c57d74000722f9db4c9426317410520670c6 (patch) | |
tree | 6e5d3e8934107ffabb7661f8bfc0e1a08eb37faf /plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java | |
parent | c3d3a90f6b4ead083d63e28e6b9fcea93d675678 (diff) | |
download | idea-e782c57d74000722f9db4c9426317410520670c6.tar.gz |
Snapshot idea/138.2210 from git://git.jetbrains.org/idea/community.git
Change-Id: I8f0204d7887ee78cf1fd8c09f936c5afff0edd2f
Diffstat (limited to 'plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java')
-rw-r--r-- | plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java | 1083 |
1 files changed, 665 insertions, 418 deletions
diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java index bf9803b6f46f..07ed51bfca7d 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java @@ -1,30 +1,16 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ package org.jetbrains.plugins.github; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Couple; import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.Ref; +import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.Change; @@ -34,11 +20,10 @@ import com.intellij.util.Function; import com.intellij.util.ThrowableConvertor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; -import com.intellij.util.containers.HashMap; +import com.intellij.vcs.log.VcsCommitMetadata; import git4idea.DialogManager; import git4idea.GitCommit; import git4idea.GitLocalBranch; -import git4idea.GitRemoteBranch; import git4idea.changes.GitChangeUtils; import git4idea.commands.Git; import git4idea.commands.GitCommandResult; @@ -52,18 +37,19 @@ import git4idea.util.GitCommitCompareInfo; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.github.api.*; +import org.jetbrains.plugins.github.exceptions.GithubOperationCanceledException; import org.jetbrains.plugins.github.ui.GithubSelectForkDialog; import org.jetbrains.plugins.github.util.*; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; -/** - * @author Aleksey Pivovarov - */ public class GithubCreatePullRequestWorker { private static final Logger LOG = GithubUtil.LOG; private static final String CANNOT_CREATE_PULL_REQUEST = "Can't Create Pull Request"; @@ -71,40 +57,36 @@ public class GithubCreatePullRequestWorker { @NotNull private final Project myProject; @NotNull private final Git myGit; @NotNull private final GitRepository myGitRepository; + @NotNull private final GithubAuthDataHolder myAuthHolder; + @NotNull private final GithubFullPath myPath; @NotNull private final String myRemoteName; @NotNull private final String myRemoteUrl; @NotNull private final String myCurrentBranch; - @NotNull private final GithubAuthDataHolder myAuthHolder; - @NotNull private final Map<String, FutureTask<DiffInfo>> myDiffInfos; + @NotNull private GithubFullPath mySource; - private volatile GithubFullPath myForkPath; - private volatile String myTargetRemote; + @NotNull private final List<ForkInfo> myForks; + @Nullable private List<GithubFullPath> myAvailableForks; private GithubCreatePullRequestWorker(@NotNull Project project, @NotNull Git git, @NotNull GitRepository gitRepository, + @NotNull GithubAuthDataHolder authHolder, @NotNull GithubFullPath path, @NotNull String remoteName, @NotNull String remoteUrl, - @NotNull String currentBranch, - @NotNull GithubAuthDataHolder authHolder) { + @NotNull String currentBranch) { myProject = project; myGit = git; myGitRepository = gitRepository; + myAuthHolder = authHolder; myPath = path; myRemoteName = remoteName; myRemoteUrl = remoteUrl; myCurrentBranch = currentBranch; - myAuthHolder = authHolder; - - myDiffInfos = new HashMap<String, FutureTask<DiffInfo>>(); - } - @NotNull - public Project getProject() { - return myProject; + myForks = new ArrayList<ForkInfo>(); } @NotNull @@ -112,231 +94,416 @@ public class GithubCreatePullRequestWorker { return myCurrentBranch; } - public boolean canShowDiff() { - return myTargetRemote != null; + @NotNull + public List<ForkInfo> getForks() { + return myForks; } @Nullable - public static GithubCreatePullRequestWorker createPullRequestWorker(@NotNull final Project project, @Nullable final VirtualFile file) { - Git git = ServiceManager.getService(Git.class); + public static GithubCreatePullRequestWorker create(@NotNull final Project project, @Nullable final VirtualFile file) { + return GithubUtil.computeValueInModal(project, "Loading data...", new Convertor<ProgressIndicator, GithubCreatePullRequestWorker>() { + @Override + public GithubCreatePullRequestWorker convert(ProgressIndicator indicator) { + Git git = ServiceManager.getService(Git.class); - GitRepository gitRepository = GithubUtil.getGitRepository(project, file); - if (gitRepository == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository"); - return null; + GitRepository gitRepository = GithubUtil.getGitRepository(project, file); + if (gitRepository == null) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository"); + return null; + } + gitRepository.update(); + + Pair<GitRemote, String> remote = GithubUtil.findGithubRemote(gitRepository); + if (remote == null) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote"); + return null; + } + String remoteName = remote.getFirst().getName(); + String remoteUrl = remote.getSecond(); + + GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl); + if (path == null) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl); + return null; + } + + GitLocalBranch currentBranch = gitRepository.getCurrentBranch(); + if (currentBranch == null) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "No current branch"); + return null; + } + + GithubAuthDataHolder authHolder; + try { + authHolder = GithubUtil.getValidAuthDataHolderFromConfig(project, indicator); + } + catch (IOException e) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + return null; + } + + GithubCreatePullRequestWorker worker = + new GithubCreatePullRequestWorker(project, git, gitRepository, authHolder, path, remoteName, remoteUrl, currentBranch.getName()); + + try { + worker.initForks(indicator); + } + catch (IOException e) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + return null; + } + + return worker; + } + }); + } + + private void initForks(@NotNull ProgressIndicator indicator) throws IOException { + doLoadForksFromGithub(indicator); + doLoadForksFromGit(indicator); + doLoadForksFromSettings(indicator); + } + + @Nullable + private ForkInfo doAddFork(@NotNull GithubFullPath path, + @Nullable String remoteName, + @NotNull ProgressIndicator indicator) { + for (ForkInfo fork : myForks) { + if (fork.getPath().equals(path)) { + if (fork.getRemoteName() == null && remoteName != null) { + fork.setRemoteName(remoteName); + } + return fork; + } } - gitRepository.update(); - Pair<GitRemote, String> remote = GithubUtil.findGithubRemote(gitRepository); - if (remote == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote"); - return null; + try { + List<String> branches = loadBranches(path, indicator); + String defaultBranch = doLoadDefaultBranch(path, indicator); + + ForkInfo fork = new ForkInfo(path, branches, defaultBranch); + myForks.add(fork); + if (remoteName != null) { + fork.setRemoteName(remoteName); + } + return fork; } - String remoteName = remote.getFirst().getName(); - String remoteUrl = remote.getSecond(); - GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl); - if (path == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl); + catch (IOException e) { + GithubNotifications.showWarning(myProject, "Can't load branches for " + path.getFullName(), e); return null; } + } - GitLocalBranch currentBranch = gitRepository.getCurrentBranch(); - if (currentBranch == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "No current branch"); - return null; + @Nullable + private ForkInfo doAddFork(@NotNull GithubRepo repo, @NotNull ProgressIndicator indicator) { + GithubFullPath path = repo.getFullPath(); + for (ForkInfo fork : myForks) { + if (fork.getPath().equals(path)) { + return fork; + } } - GithubAuthDataHolder authHolder; try { - authHolder = GithubUtil - .computeValueInModal(project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubAuthDataHolder, IOException>() { - @NotNull - @Override - public GithubAuthDataHolder convert(ProgressIndicator indicator) throws IOException { - return GithubUtil.getValidAuthDataHolderFromConfig(project, indicator); - } - }); + List<String> branches = loadBranches(path, indicator); + String defaultBranch = repo.getDefaultBranch(); + + ForkInfo fork = new ForkInfo(path, branches, defaultBranch); + myForks.add(fork); + return fork; } catch (IOException e) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + GithubNotifications.showWarning(myProject, "Can't load branches for " + path.getFullName(), e); return null; } + } - return new GithubCreatePullRequestWorker(project, git, gitRepository, path, remoteName, remoteUrl, currentBranch.getName(), authHolder); + private void doLoadForksFromSettings(@NotNull ProgressIndicator indicator) throws IOException { + GithubFullPath savedRepo = GithubProjectSettings.getInstance(myProject).getCreatePullRequestDefaultRepo(); + if (savedRepo != null) { + doAddFork(savedRepo, null, indicator); + } } - @Nullable - public GithubTargetInfo setTarget(@NotNull final GithubFullPath forkPath) { - try { - GithubInfo info = - GithubUtil.computeValueInModal(myProject, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubInfo, IOException>() { - @NotNull - @Override - public GithubInfo convert(ProgressIndicator indicator) throws IOException { - // configure remote - GitRemote targetRemote = GithubUtil.findGithubRemote(myGitRepository, forkPath); - String targetRemoteName = targetRemote == null ? null : targetRemote.getName(); - if (targetRemoteName == null) { - final Ref<Integer> responseRef = new Ref<Integer>(); - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - @Override - public void run() { - responseRef.set(GithubNotifications - .showYesNoDialog(myProject, "Can't Find Remote", "Configure remote for '" + forkPath.getUser() + "'?")); - } - }, indicator.getModalityState()); - if (responseRef.get() == Messages.YES) { - targetRemoteName = configureRemote(myProject, myGitRepository, forkPath); - } - } + private void doLoadForksFromGit(@NotNull ProgressIndicator indicator) { + for (GitRemote remote : myGitRepository.getRemotes()) { + for (String url : remote.getUrls()) { + if (GithubUrlUtil.isGithubUrl(url)) { + GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(url); + if (path != null) { + doAddFork(path, remote.getName(), indicator); + break; + } + } + } + } + } - // load available branches - List<String> branches = ContainerUtil.map(GithubUtil.runTask(myProject, myAuthHolder, indicator, - new ThrowableConvertor<GithubAuthData, List<GithubBranch>, IOException>() { - @Override - public List<GithubBranch> convert(@NotNull GithubAuthData auth) - throws IOException { - return GithubApiUtil.getRepoBranches(auth, forkPath.getUser(), - forkPath.getRepository()); - } - } - ), new Function<GithubBranch, String>() { - @Override - public String fun(GithubBranch githubBranch) { - return githubBranch.getName(); - } - }); + private void doLoadForksFromGithub(@NotNull ProgressIndicator indicator) throws IOException { + GithubRepoDetailed repo = + GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepoDetailed, IOException>() { + @NotNull + @Override + public GithubRepoDetailed convert(@NotNull GithubConnection connection) throws IOException { + return GithubApiUtil.getDetailedRepoInfo(connection, myPath.getUser(), myPath.getRepository()); + } + }); - // fetch - if (targetRemoteName != null) { - GitFetchResult result = new GitFetcher(myProject, indicator, false).fetch(myGitRepository.getRoot(), targetRemoteName, null); - if (!result.isSuccess()) { - GitFetcher.displayFetchResult(myProject, result, null, result.getErrors()); - targetRemoteName = null; - } - } + doAddFork(repo, indicator); + if (repo.getParent() != null) { + doAddFork(repo.getParent(), indicator); + } + if (repo.getSource() != null) { + doAddFork(repo.getSource(), indicator); + } - return new GithubInfo(branches, targetRemoteName); - } - }); + mySource = repo.getSource() == null ? repo.getFullPath() : repo.getSource().getFullPath(); + } - myForkPath = forkPath; - myTargetRemote = info.getTargetRemote(); - - myDiffInfos.clear(); - if (canShowDiff()) { - for (final String branch : info.getBranches()) { - myDiffInfos.put(branch, new FutureTask<DiffInfo>(new Callable<DiffInfo>() { - @Nullable - @Override - public DiffInfo call() throws Exception { - return loadDiffInfo(myProject, myGitRepository, myCurrentBranch, myTargetRemote + "/" + branch); - } - })); + @NotNull + private List<String> loadBranches(@NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException { + return ContainerUtil.map( + GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, List<GithubBranch>, IOException>() { + @Override + public List<GithubBranch> convert(@NotNull GithubConnection connection) throws IOException { + return GithubApiUtil.getRepoBranches(connection, fork.getUser(), fork.getRepository()); + } + }), + new Function<GithubBranch, String>() { + @Override + public String fun(@NotNull GithubBranch branch) { + return branch.getName(); } } + ); + } + + @Nullable + private String doLoadDefaultBranch(@NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException { + GithubRepo repo = + GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() { + @Override + public GithubRepo convert(@NotNull GithubConnection connection) throws IOException { + return GithubApiUtil.getDetailedRepoInfo(connection, fork.getUser(), fork.getRepository()); + } + }); + return repo.getDefaultBranch(); + } + + public void launchFetchRemote(@NotNull final ForkInfo fork) { + if (fork.getRemoteName() == null) return; + + if (fork.getFetchTask() != null) return; + synchronized (fork.LOCK) { + if (fork.getFetchTask() != null) return; - return new GithubTargetInfo(info.getBranches()); + final MasterFutureTask<Void> task = new MasterFutureTask<Void>(new Callable<Void>() { + @Override + public Void call() throws Exception { + doFetchRemote(fork); + return null; + } + }); + fork.setFetchTask(task); + + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + task.run(); + } + }); } - catch (IOException e) { - GithubNotifications.showErrorDialog(myProject, CANNOT_CREATE_PULL_REQUEST, e); + } + + public void launchLoadDiffInfo(@NotNull final BranchInfo branch) { + if (branch.getForkInfo().getRemoteName() == null) return; + + if (branch.getDiffInfoTask() != null) return; + synchronized (branch.LOCK) { + if (branch.getDiffInfoTask() != null) return; + + launchFetchRemote(branch.getForkInfo()); + MasterFutureTask<Void> masterTask = branch.getForkInfo().getFetchTask(); + assert masterTask != null; + + final SlaveFutureTask<DiffInfo> task = new SlaveFutureTask<DiffInfo>(masterTask, new Callable<DiffInfo>() { + @Override + public DiffInfo call() throws VcsException { + return doLoadDiffInfo(branch); + } + }); + branch.setDiffInfoTask(task); + + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + task.run(); + } + }); + } + } + + @Nullable + public DiffInfo getDiffInfo(@NotNull final BranchInfo branch) throws IOException { + if (branch.getForkInfo().getRemoteName() == null) return null; + + launchLoadDiffInfo(branch); + + assert branch.getDiffInfoTask() != null; + try { + return branch.getDiffInfoTask().get(); + } + catch (InterruptedException e) { + throw new GithubOperationCanceledException(e); + } + catch (ExecutionException e) { + Throwable ex = e.getCause(); + if (ex instanceof VcsException) throw new IOException(ex); + LOG.error(ex); return null; } } - public void showDiffDialog(@NotNull String branch) { - if (canShowDiff()) { - DiffInfo info = getDiffInfoWithModal(branch); - if (info == null) { - GithubNotifications.showErrorDialog(myProject, "Can't Show Diff", "Can't get diff info"); - return; - } + private boolean doFetchRemote(@NotNull ForkInfo fork) { + if (fork.getRemoteName() == null) return false; - GitCompareBranchesDialog dialog = - new GitCompareBranchesDialog(myProject, info.getTo(), info.getFrom(), info.getInfo(), myGitRepository); - dialog.show(); + GitFetchResult result = + new GitFetcher(myProject, new EmptyProgressIndicator(), false).fetch(myGitRepository.getRoot(), fork.getRemoteName(), null); + if (!result.isSuccess()) { + GitFetcher.displayFetchResult(myProject, result, null, result.getErrors()); + return false; } + return true; } - @Nullable - public GithubFullPath showTargetDialog() { - return showTargetDialog(false); + @NotNull + private DiffInfo doLoadDiffInfo(@NotNull final BranchInfo branch) throws VcsException { + // TODO: make cancelable and abort old speculative requests (when git4idea will allow to do so) + String currentBranch = myCurrentBranch; + String targetBranch = branch.getForkInfo().getRemoteName() + "/" + branch.getRemoteName(); + + List<GitCommit> commits1 = GitHistoryUtils.history(myProject, myGitRepository.getRoot(), ".." + targetBranch); + List<GitCommit> commits2 = GitHistoryUtils.history(myProject, myGitRepository.getRoot(), targetBranch + ".."); + Collection<Change> diff = GitChangeUtils.getDiff(myProject, myGitRepository.getRoot(), targetBranch, myCurrentBranch, null); + GitCommitCompareInfo info = new GitCommitCompareInfo(GitCommitCompareInfo.InfoType.BRANCH_TO_HEAD); + info.put(myGitRepository, diff); + info.put(myGitRepository, Couple.of(commits1, commits2)); + + return new DiffInfo(info, currentBranch, targetBranch); } - @Nullable - public GithubFullPath showTargetDialog(boolean firstTime) { - final GithubInfo2 info = getAvailableForksInModal(myProject, myGitRepository, myAuthHolder, myPath); - if (info == null) { - return null; + private void doConfigureRemote(@NotNull ForkInfo fork) { + if (fork.getRemoteName() != null) return; + + GithubFullPath path = fork.getPath(); + String url = GithubUrlUtil.getCloneUrl(path); + + if (GithubUtil.addGithubRemote(myProject, myGitRepository, path.getUser(), url)) { + fork.setRemoteName(path.getUser()); } + } - if (firstTime) { - if (info.getForks().size() == 1) { - return info.getForks().iterator().next(); + public void configureRemote(@NotNull final ForkInfo fork) { + GithubUtil.computeValueInModal(myProject, "Creating remote..", false, new Consumer<ProgressIndicator>() { + @Override + public void consume(ProgressIndicator indicator) { + doConfigureRemote(fork); } - if (info.getForks().size() == 2) { - Iterator<GithubFullPath> it = info.getForks().iterator(); - GithubFullPath path1 = it.next(); - GithubFullPath path2 = it.next(); + }); + } - if (myPath.equals(path1)) { - return path2; - } - if (myPath.equals(path2)) { - return path1; + @NotNull + public Couple<String> getDefaultDescriptionMessage(@NotNull final BranchInfo branch) { + Couple<String> message = branch.getDefaultMessage(); + if (message != null) return message; + + if (branch.getForkInfo().getRemoteName() == null) { + return getSimpleDefaultDescriptionMessage(branch); + } + + return GithubUtil + .computeValueInModal(myProject, "Collecting additional data...", false, new Convertor<ProgressIndicator, Couple<String>>() { + @Override + public Couple<String> convert(ProgressIndicator o) { + String localBranch = myCurrentBranch; + String targetBranch = branch.getForkInfo().getRemoteName() + "/" + branch.getRemoteName(); + try { + List<VcsCommitMetadata> commits = + GitHistoryUtils.readLastCommits(myProject, myGitRepository.getRoot(), localBranch, targetBranch); + if (commits == null) return getSimpleDefaultDescriptionMessage(branch); + + VcsCommitMetadata localCommit = commits.get(0); + VcsCommitMetadata targetCommit = commits.get(1); + + if (localCommit.getParents().contains(targetCommit.getId())) { + return Couple.of(localCommit.getSubject(), localCommit.getFullMessage()); + } + return getSimpleDefaultDescriptionMessage(branch); + } + catch (VcsException e) { + GithubNotifications.showWarning(myProject, "Can't collect additional data", e); + return getSimpleDefaultDescriptionMessage(branch); + } } - } + }); + } + + @NotNull + public Couple<String> getSimpleDefaultDescriptionMessage(@NotNull final BranchInfo branch) { + Couple<String> message = Couple.of(myCurrentBranch, ""); + branch.setDefaultMessage(message); + return message; + } + + public boolean checkAction(@Nullable final BranchInfo branch) { + if (branch == null) { + GithubNotifications.showWarningDialog(myProject, CANNOT_CREATE_PULL_REQUEST, "Target branch is not selected"); + return false; } - Convertor<String, GithubFullPath> getForkPath = new Convertor<String, GithubFullPath>() { - @Nullable - @Override - public GithubFullPath convert(@NotNull final String user) { - return GithubUtil.computeValueInModal(myProject, "Access to GitHub", new Convertor<ProgressIndicator, GithubFullPath>() { - @Nullable + DiffInfo info; + try { + info = GithubUtil + .computeValueInModal(myProject, "Collecting diff data...", new ThrowableConvertor<ProgressIndicator, DiffInfo, IOException>() { @Override - public GithubFullPath convert(ProgressIndicator indicator) { - return findRepositoryByUser(myProject, myAuthHolder, indicator, user, info.getForks(), info.getSource()); + public DiffInfo convert(ProgressIndicator indicator) throws IOException { + return GithubUtil.runInterruptable(indicator, new ThrowableComputable<DiffInfo, IOException>() { + @Override + public DiffInfo compute() throws IOException { + return getDiffInfo(branch); + } + }); } }); - } - }; - GithubSelectForkDialog dialog = new GithubSelectForkDialog(myProject, info.getForks(), getForkPath); - DialogManager.show(dialog); - if (!dialog.isOK()) { - return null; } - return dialog.getPath(); - } - - public boolean checkAction(@NotNull String targetBranch) { - DiffInfo info = getDiffInfoWithModal(targetBranch); + catch (IOException e) { + GithubNotifications.showError(myProject, "Can't collect diff data", e); + return true; + } if (info == null) { return true; } - String localBranchName = "'" + getCurrentBranch() + "'"; - String targetBranchName = "'" + myTargetRemote + ":" + targetBranch + "'"; + ForkInfo fork = branch.getForkInfo(); + + String localBranchName = "'" + myCurrentBranch + "'"; + String targetBranchName = "'" + fork.getRemoteName() + "/" + branch.getRemoteName() + "'"; if (info.getInfo().getBranchToHeadCommits(myGitRepository).isEmpty()) { - GithubNotifications - .showWarningDialog(myProject, CANNOT_CREATE_PULL_REQUEST, - "Can't create empty pull request: the branch " + localBranchName + - " is fully merged to the branch " + targetBranchName - ); - return false; + return Messages.YES == GithubNotifications + .showYesNoDialog(myProject, "Do you want to proceed anyway?", + "Empty pull request: the branch " + localBranchName + " is fully merged to the branch " + targetBranchName); } if (!info.getInfo().getHeadToBranchCommits(myGitRepository).isEmpty()) { - return GithubNotifications - .showYesNoDialog(myProject, "Do you want to proceed anyway?", - "The branch " + targetBranchName + " is not fully merged to the branch " + localBranchName) == Messages.YES; + return Messages.YES == GithubNotifications + .showYesNoDialog(myProject, "Do you want to proceed anyway?", + "The branch " + targetBranchName + " is not fully merged to the branch " + localBranchName); } return true; } - public void performAction(@NotNull final String title, @NotNull final String description, @NotNull final String targetBranch) { - @NotNull final Project project = myProject; - + public void createPullRequest(@NotNull final BranchInfo branch, + @NotNull final String title, + @NotNull final String description) { new Task.Backgroundable(myProject, "Creating pull request...") { @Override public void run(@NotNull ProgressIndicator indicator) { @@ -344,298 +511,322 @@ public class GithubCreatePullRequestWorker { indicator.setText("Pushing current branch..."); GitCommandResult result = myGit.push(myGitRepository, myRemoteName, myRemoteUrl, myCurrentBranch, true); if (!result.success()) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Push failed:<br/>" + result.getErrorOutputAsHtmlString()); + GithubNotifications.showError(myProject, CANNOT_CREATE_PULL_REQUEST, "Push failed:<br/>" + result.getErrorOutputAsHtmlString()); return; } - String headBranch = myPath.getUser() + ":" + myCurrentBranch; - LOG.info("Creating pull request"); indicator.setText("Creating pull request..."); - GithubPullRequest request = - createPullRequest(project, myAuthHolder, indicator, myForkPath, title, description, headBranch, targetBranch); + GithubPullRequest request = doCreatePullRequest(indicator, branch, title, description); if (request == null) { return; } - GithubNotifications - .showInfoURL(project, "Successfully created pull request", "Pull request #" + request.getNumber(), request.getHtmlUrl()); + GithubNotifications.showInfoURL(myProject, "Successfully created pull request", + "Pull request #" + request.getNumber(), request.getHtmlUrl()); } }.queue(); } @Nullable - private static String configureRemote(@NotNull Project project, @NotNull GitRepository gitRepository, @NotNull GithubFullPath forkPath) { - String url = GithubUrlUtil.getCloneUrl(forkPath); + private GithubPullRequest doCreatePullRequest(@NotNull ProgressIndicator indicator, + @NotNull final BranchInfo branch, + @NotNull final String title, + @NotNull final String description) { + final ForkInfo fork = branch.getForkInfo(); - if (GithubUtil.addGithubRemote(project, gitRepository, forkPath.getUser(), url)) { - return forkPath.getUser(); - } - else { - return null; - } - } + final String head = myPath.getUser() + ":" + myCurrentBranch; + final String base = branch.getRemoteName(); - @Nullable - private static GithubPullRequest createPullRequest(@NotNull Project project, - @NotNull GithubAuthDataHolder authHolder, - @NotNull ProgressIndicator indicator, - @NotNull final GithubFullPath targetRepo, - @NotNull final String title, - @NotNull final String description, - @NotNull final String head, - @NotNull final String base) { try { - return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubPullRequest, IOException>() { - @NotNull - @Override - public GithubPullRequest convert(@NotNull GithubAuthData auth) throws IOException { - return GithubApiUtil.createPullRequest(auth, targetRepo.getUser(), targetRepo.getRepository(), title, description, head, base); - } - }); + return GithubUtil + .runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubPullRequest, IOException>() { + @NotNull + @Override + public GithubPullRequest convert(@NotNull GithubConnection connection) throws IOException { + return GithubApiUtil + .createPullRequest(connection, fork.getPath().getUser(), fork.getPath().getRepository(), title, description, head, base); + } + }); } catch (IOException e) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + GithubNotifications.showError(myProject, CANNOT_CREATE_PULL_REQUEST, e); return null; } } - @Nullable - private DiffInfo getDiffInfo(@NotNull String branch) { + public void showDiffDialog(@Nullable final BranchInfo branch) { + if (branch == null) { + GithubNotifications.showWarningDialog(myProject, "Can't Show Diff", "Target branch is not selected"); + return; + } + + DiffInfo info; try { - FutureTask<DiffInfo> future = myDiffInfos.get(branch); - if (future == null) { - return null; - } - future.run(); - return future.get(); + info = GithubUtil + .computeValueInModal(myProject, "Collecting diff data...", new ThrowableConvertor<ProgressIndicator, DiffInfo, IOException>() { + @Override + public DiffInfo convert(ProgressIndicator indicator) throws IOException { + return GithubUtil.runInterruptable(indicator, new ThrowableComputable<DiffInfo, IOException>() { + @Override + public DiffInfo compute() throws IOException { + return getDiffInfo(branch); + } + }); + } + }); } - catch (InterruptedException e) { - LOG.error(e); - return null; + catch (IOException e) { + GithubNotifications.showError(myProject, "Can't collect diff data", e); + return; } - catch (ExecutionException e) { - LOG.error(e); - return null; + if (info == null) { + GithubNotifications.showErrorDialog(myProject, "Can't Show Diff", "Can't collect diff data"); + return; } + + GitCompareBranchesDialog dialog = + new GitCompareBranchesDialog(myProject, info.getTo(), info.getFrom(), info.getInfo(), myGitRepository, true); + dialog.show(); } @Nullable - private DiffInfo getDiffInfoWithModal(@NotNull final String branch) { - return GithubUtil.computeValueInModal(myProject, "Collecting diff data...", new Convertor<ProgressIndicator, DiffInfo>() { - @Override - @Nullable - public DiffInfo convert(ProgressIndicator indicator) { - return getDiffInfo(branch); - } - }); - } + public ForkInfo showTargetDialog() { + if (myAvailableForks == null) { + myAvailableForks = GithubUtil + .computeValueInModal(myProject, myCurrentBranch, new Convertor<ProgressIndicator, List<GithubFullPath>>() { + @Override + public List<GithubFullPath> convert(ProgressIndicator indicator) { + return getAvailableForks(indicator); + } + }); + } - public void getDiffDescriptionInPooledThread(@NotNull final String branch, @NotNull final Consumer<DiffDescription> after) { - ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + Convertor<String, ForkInfo> getForkPath = new Convertor<String, ForkInfo>() { + @Nullable @Override - public void run() { - after.consume(getDefaultDescriptionMessage(branch, getDiffInfo(branch), myGitRepository)); + public ForkInfo convert(@NotNull final String user) { + return GithubUtil.computeValueInModal(myProject, "Access to GitHub", new Convertor<ProgressIndicator, ForkInfo>() { + @Nullable + @Override + public ForkInfo convert(ProgressIndicator indicator) { + return findRepositoryByUser(indicator, user); + } + }); } - }); + }; + GithubSelectForkDialog dialog = new GithubSelectForkDialog(myProject, myAvailableForks, getForkPath); + DialogManager.show(dialog); + if (!dialog.isOK()) { + return null; + } + return dialog.getPath(); } @Nullable - private static DiffInfo loadDiffInfo(@NotNull final Project project, - @NotNull final GitRepository repository, - @NotNull final String currentBranch, - @NotNull final String targetBranch) { + private List<GithubFullPath> getAvailableForks(@NotNull ProgressIndicator indicator) { try { - List<GitCommit> commits1 = GitHistoryUtils.history(project, repository.getRoot(), ".." + targetBranch); - List<GitCommit> commits2 = GitHistoryUtils.history(project, repository.getRoot(), targetBranch + ".."); - Collection<Change> diff = GitChangeUtils.getDiff(repository.getProject(), repository.getRoot(), targetBranch, currentBranch, null); - GitCommitCompareInfo info = new GitCommitCompareInfo(GitCommitCompareInfo.InfoType.BRANCH_TO_HEAD); - info.put(repository, diff); - info.put(repository, Couple.of(commits1, commits2)); - return new DiffInfo(info, currentBranch, targetBranch); - } - catch (VcsException e) { - LOG.info(e); + List<GithubFullPath> forks = ContainerUtil.map( + GithubUtil.runTask(myProject, myAuthHolder, indicator, + new ThrowableConvertor<GithubConnection, List<GithubRepo>, IOException>() { + @NotNull + @Override + public List<GithubRepo> convert(@NotNull GithubConnection connection) + throws IOException { + return GithubApiUtil.getForks(connection, mySource.getUser(), mySource.getRepository()); + } + } + ), + new Function<GithubRepo, GithubFullPath>() { + @Override + public GithubFullPath fun(GithubRepo repo) { + return repo.getFullPath(); + } + } + ); + if (!forks.contains(mySource)) forks.add(mySource); + return forks; + } + catch (IOException e) { + GithubNotifications.showWarning(myProject, "Can't load available forks", e); return null; } } - @NotNull - private static DiffDescription getDefaultDescriptionMessage(@NotNull String branch, - @Nullable DiffInfo info, - @NotNull GitRepository gitRepository) { - if (info == null) { - return new DiffDescription(branch, null, null); - } - - if (info.getInfo().getBranchToHeadCommits(gitRepository).size() != 1) { - return new DiffDescription(branch, info.getFrom(), null); + @Nullable + private ForkInfo findRepositoryByUser(@NotNull final ProgressIndicator indicator, @NotNull final String user) { + for (ForkInfo fork : myForks) { + if (StringUtil.equalsIgnoreCase(user, fork.getPath().getUser())) { + return fork; + } } - GitCommit commit = info.getInfo().getBranchToHeadCommits(gitRepository).get(0); - return new DiffDescription(branch, commit.getSubject(), commit.getFullMessage()); - } - - @Nullable - private static GithubInfo2 getAvailableForksInModal(@NotNull final Project project, - @NotNull final GitRepository gitRepository, - @NotNull final GithubAuthDataHolder authHolder, - @NotNull final GithubFullPath path) { try { - return GithubUtil - .computeValueInModal(project, "Access to GitHub", new ThrowableConvertor<ProgressIndicator, GithubInfo2, IOException>() { - @NotNull + GithubRepo repo = + GithubUtil.runTask(myProject, myAuthHolder, indicator, new ThrowableConvertor<GithubConnection, GithubRepo, IOException>() { + @Nullable @Override - public GithubInfo2 convert(ProgressIndicator indicator) throws IOException { - final Set<GithubFullPath> forks = new HashSet<GithubFullPath>(); - - // GitHub - GithubRepoDetailed repo = - GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubRepoDetailed, IOException>() { - @NotNull - @Override - public GithubRepoDetailed convert(@NotNull GithubAuthData auth) throws IOException { - return GithubApiUtil.getDetailedRepoInfo(auth, path.getUser(), path.getRepository()); - } - }); - - forks.add(path); - if (repo.getParent() != null) { - forks.add(repo.getParent().getFullPath()); + public GithubRepo convert(@NotNull GithubConnection connection) throws IOException { + try { + GithubRepoDetailed target = GithubApiUtil.getDetailedRepoInfo(connection, user, mySource.getRepository()); + if (target.getSource() != null && StringUtil.equals(target.getSource().getUserName(), mySource.getUser())) { + return target; + } } - if (repo.getSource() != null) { - forks.add(repo.getSource().getFullPath()); + catch (IOException ignore) { + // such repo may not exist } - // Git - forks.addAll(getAvailableForksFromGit(gitRepository)); - - GithubRepo forkTreeRoot = repo.getSource() == null ? repo : repo.getSource(); - return new GithubInfo2(forks, forkTreeRoot); + return GithubApiUtil.findForkByUser(connection, mySource.getUser(), mySource.getRepository(), user); } }); + + if (repo == null) return null; + return doAddFork(repo, indicator); } catch (IOException e) { - GithubNotifications.showErrorDialog(project, CANNOT_CREATE_PULL_REQUEST, e); + GithubNotifications.showError(myProject, "Can't find repository", e); return null; } } - @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; - } - } + public static class ForkInfo { + @NotNull public final Object LOCK = new Object(); + + // initial loading + @NotNull private final GithubFullPath myPath; + + @NotNull private final String myDefaultBranch; + @NotNull private final List<BranchInfo> myBranches; + + @Nullable private String myRemoteName; + private boolean myProposedToCreateRemote; + + @Nullable private MasterFutureTask<Void> myFetchTask; + + public ForkInfo(@NotNull GithubFullPath path, @NotNull List<String> branches, @Nullable String defaultBranch) { + myPath = path; + myDefaultBranch = defaultBranch == null ? "master" : defaultBranch; + myBranches = new ArrayList<BranchInfo>(); + for (String branchName : branches) { + myBranches.add(new BranchInfo(branchName, this)); } } - return forks; - } - @Nullable - private static GithubFullPath findRepositoryByUser(@NotNull Project project, - @NotNull GithubAuthDataHolder authHolder, - @NotNull ProgressIndicator indicator, - @NotNull final String user, - @NotNull Set<GithubFullPath> forks, - @NotNull final GithubRepo source) { - for (GithubFullPath path : forks) { - if (StringUtil.equalsIgnoreCase(user, path.getUser())) { - return path; - } + @NotNull + public GithubFullPath getPath() { + return myPath; } - try { - return GithubUtil.runTask(project, authHolder, indicator, new ThrowableConvertor<GithubAuthData, GithubFullPath, IOException>() { - @Nullable - @Override - public GithubFullPath convert(@NotNull GithubAuthData auth) throws IOException { - try { - GithubRepoDetailed target = GithubApiUtil.getDetailedRepoInfo(auth, user, source.getName()); - if (target.getSource() != null && StringUtil.equals(target.getSource().getUserName(), source.getUserName())) { - return target.getFullPath(); - } - } - catch (IOException ignore) { - // such repo may not exist - } + @Nullable + public String getRemoteName() { + return myRemoteName; + } - GithubRepo fork = GithubApiUtil.findForkByUser(auth, source.getUserName(), source.getName(), user); - if (fork != null) { - return fork.getFullPath(); - } + @NotNull + public String getDefaultBranch() { + return myDefaultBranch; + } - return null; - } - }); + @NotNull + public List<BranchInfo> getBranches() { + return myBranches; } - catch (IOException e) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + + public void setRemoteName(@NotNull String remoteName) { + myRemoteName = remoteName; } - return null; - } + public boolean isProposedToCreateRemote() { + return myProposedToCreateRemote; + } + + public void setProposedToCreateRemote(boolean proposedToCreateRemote) { + myProposedToCreateRemote = proposedToCreateRemote; + } - private static class GithubInfo { - @NotNull private final List<String> myBranches; - @Nullable private final String myTargetRemote; + @Nullable + public MasterFutureTask<Void> getFetchTask() { + return myFetchTask; + } - private GithubInfo(@NotNull List<String> repo, @Nullable String targetRemote) { - myBranches = repo; - myTargetRemote = targetRemote; + public void setFetchTask(@NotNull MasterFutureTask<Void> fetchTask) { + myFetchTask = fetchTask; } - @NotNull - public List<String> getBranches() { - return myBranches; + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ForkInfo info = (ForkInfo)o; + + if (!myPath.equals(info.myPath)) return false; + + return true; } - @Nullable - public String getTargetRemote() { - return myTargetRemote; + @Override + public int hashCode() { + return myPath.hashCode(); + } + + @Override + public String toString() { + return myPath.getUser() + ":" + myPath.getRepository(); } } - private static class GithubInfo2 { - @NotNull private final Set<GithubFullPath> myForks; - @NotNull private final GithubRepo mySource; + public static class BranchInfo { + @NotNull public final Object LOCK = new Object(); - private GithubInfo2(@NotNull Set<GithubFullPath> forks, @NotNull GithubRepo source) { - myForks = forks; - mySource = source; + @NotNull private final ForkInfo myForkInfo; + @NotNull private final String myRemoteName; + + @Nullable private SlaveFutureTask<DiffInfo> myDiffInfoTask; + + @Nullable private Couple<String> myDefaultMessage; + + public BranchInfo(@NotNull String remoteName, @NotNull ForkInfo fork) { + myRemoteName = remoteName; + myForkInfo = fork; } @NotNull - public Set<GithubFullPath> getForks() { - return myForks; + public ForkInfo getForkInfo() { + return myForkInfo; } @NotNull - public GithubRepo getSource() { - return mySource; + public String getRemoteName() { + return myRemoteName; } - } - public static class GithubTargetInfo { - @NotNull private final List<String> myBranches; + @Nullable + public SlaveFutureTask<DiffInfo> getDiffInfoTask() { + return myDiffInfoTask; + } - private GithubTargetInfo(@NotNull List<String> branches) { - myBranches = branches; + public void setDiffInfoTask(@NotNull SlaveFutureTask<DiffInfo> diffInfoTask) { + myDiffInfoTask = diffInfoTask; } - @NotNull - public List<String> getBranches() { - return myBranches; + @Nullable + public Couple<String> getDefaultMessage() { + return myDefaultMessage; + } + + public void setDefaultMessage(@NotNull Couple<String> message) { + myDefaultMessage = message; + } + + @Override + public String toString() { + return myRemoteName; } } - private static class DiffInfo { + public static class DiffInfo { @NotNull private final GitCommitCompareInfo myInfo; @NotNull private final String myFrom; @NotNull private final String myTo; @@ -662,30 +853,86 @@ public class GithubCreatePullRequestWorker { } } - public static class DiffDescription { - @NotNull private final String myBranch; - @Nullable private final String myTitle; - @Nullable private final String myDescription; + public static class SlaveFutureTask<T> extends FutureTask<T> { + @NotNull private final MasterFutureTask myMaster; - public DiffDescription(@NotNull String branch, @Nullable String title, @Nullable String description) { - myBranch = branch; - myTitle = title; - myDescription = description; + public SlaveFutureTask(@NotNull MasterFutureTask master, @NotNull Callable<T> callable) { + super(callable); + myMaster = master; } - @NotNull - public String getBranch() { - return myBranch; + @Override + public void run() { + if (myMaster.isDone()) { + super.run(); + } + else { + if (!myMaster.addSlave(this)) { + super.run(); + } + } } - @Nullable - public String getTitle() { - return myTitle; + public T safeGet() { + try { + return super.get(); + } + catch (InterruptedException e) { + return null; + } + catch (CancellationException e) { + return null; + } + catch (ExecutionException e) { + return null; + } } + } - @Nullable - public String getDescription() { - return myDescription; + public static class MasterFutureTask<T> extends FutureTask<T> { + @NotNull private final Object LOCK = new Object(); + private boolean myDone = false; + + @Nullable private List<SlaveFutureTask> mySlaves; + + public MasterFutureTask(@NotNull Callable<T> callable) { + super(callable); + } + + boolean addSlave(@NotNull SlaveFutureTask slave) { + if (isDone()) { + return false; + } + else { + synchronized (LOCK) { + if (myDone) return false; + if (mySlaves == null) mySlaves = new ArrayList<SlaveFutureTask>(); + mySlaves.add(slave); + return true; + } + } + } + + @Override + protected void done() { + synchronized (LOCK) { + myDone = true; + if (mySlaves != null) { + for (final SlaveFutureTask slave : mySlaves) { + runSlave(slave); + } + mySlaves = null; + } + } + } + + protected void runSlave(@NotNull final SlaveFutureTask slave) { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + slave.run(); + } + }); } } } |