diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java index 97983028b2cd..636415e5fcb6 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java @@ -17,56 +17,16 @@ package org.jetbrains.plugins.github; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.components.ServiceManager; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.VcsException; -import com.intellij.openapi.vcs.changes.Change; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.util.Consumer; -import com.intellij.util.Function; -import com.intellij.util.ThrowableConsumer; -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.util.containers.HashSet; import git4idea.DialogManager; -import git4idea.GitCommit; -import git4idea.GitLocalBranch; -import git4idea.GitRemoteBranch; -import git4idea.branch.GitBranchUtil; -import git4idea.changes.GitChangeUtils; -import git4idea.commands.Git; -import git4idea.commands.GitCommandResult; -import git4idea.history.GitHistoryUtils; -import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; -import git4idea.ui.branch.GitCompareBranchesDialog; -import git4idea.update.GitFetchResult; -import git4idea.update.GitFetcher; -import git4idea.util.GitCommitCompareInfo; import icons.GithubIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.plugins.github.api.*; -import org.jetbrains.plugins.github.exceptions.GithubAuthenticationCanceledException; import org.jetbrains.plugins.github.ui.GithubCreatePullRequestDialog; -import org.jetbrains.plugins.github.util.GithubAuthData; -import org.jetbrains.plugins.github.util.GithubNotifications; -import org.jetbrains.plugins.github.util.GithubUrlUtil; -import org.jetbrains.plugins.github.util.GithubUtil; - -import java.io.IOException; -import java.util.*; -import java.util.concurrent.atomic.AtomicReference; +import org.jetbrains.plugins.github.util.*; import static org.jetbrains.plugins.github.util.GithubUtil.setVisibleEnabled; @@ -74,9 +34,6 @@ import static org.jetbrains.plugins.github.util.GithubUtil.setVisibleEnabled; * @author Aleksey Pivovarov */ public class GithubCreatePullRequestAction extends DumbAwareAction { - private static final Logger LOG = GithubUtil.LOG; - private static final String CANNOT_CREATE_PULL_REQUEST = "Can't create pull request"; - public GithubCreatePullRequestAction() { super("Create Pull Request", "Create pull request from current branch", GithubIcons.Github_icon); } @@ -115,543 +72,13 @@ public class GithubCreatePullRequestAction extends DumbAwareAction { createPullRequest(project, file); } - static void createPullRequest(@NotNull final Project project, @Nullable final VirtualFile file) { - final Git git = ServiceManager.getService(Git.class); - - final GitRepository repository = GithubUtil.getGitRepository(project, file); - if (repository == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find git repository"); - return; - } - repository.update(); - - Pair remote = GithubUtil.findGithubRemote(repository); - if (remote == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote"); - return; - } - final String remoteUrl = remote.getSecond(); - final String remoteName = remote.getFirst().getName(); - - GithubFullPath userAndRepo = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl); - if (userAndRepo == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't process remote: " + remoteUrl); + static void createPullRequest(@NotNull Project project, @Nullable VirtualFile file) { + GithubCreatePullRequestWorker worker = GithubCreatePullRequestWorker.createPullRequestWorker(project, file); + if (worker == null) { return; } - final GitLocalBranch currentBranch = repository.getCurrentBranch(); - if (currentBranch == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "No current branch"); - return; - } - - String upstreamUrl = GithubUtil.findUpstreamRemote(repository); - GithubFullPath upstreamUserAndRepo = - upstreamUrl == null || !GithubUrlUtil.isGithubUrl(upstreamUrl) ? null : GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(upstreamUrl); - - final Map forks = new HashMap(); - final Set branches = new HashSet(); - addAvailableBranchesFromGit(repository, forks, branches); - GithubInfo info = loadGithubInfoAndBranchesWithModal(project, userAndRepo, upstreamUserAndRepo, forks, branches); - if (info == null) { - return; - } - final GithubRepoDetailed repo = info.getRepo(); - final GithubAuthData auth = info.getAuthData(); - - GithubRepo parent = repo.getParent(); - String defaultBranch = - parent == null || parent.getDefaultBranch() == null ? null : parent.getUserName() + ":" + parent.getDefaultBranch(); - Collection suggestions = ContainerUtil.map(branches, new Function() { - @Override - public String fun(RemoteBranch remoteBranch) { - return remoteBranch.getReference(); - } - }); - Consumer showDiff = new Consumer() { - @Override - public void consume(String ref) { - showDiffByRef(project, ref, repository, currentBranch.getName(), auth, forks, branches, repo.getSource()); - } - }; - final GithubCreatePullRequestDialog dialog = new GithubCreatePullRequestDialog(project, suggestions, defaultBranch, showDiff); + GithubCreatePullRequestDialog dialog = new GithubCreatePullRequestDialog(worker); DialogManager.show(dialog); - if (!dialog.isOK()) { - return; - } - - new Task.Backgroundable(project, "Creating pull request...") { - @Override - public void run(@NotNull ProgressIndicator indicator) { - LOG.info("Pushing current branch"); - indicator.setText("Pushing current branch..."); - GitCommandResult result = git.push(repository, remoteName, remoteUrl, currentBranch.getName(), true); - if (!result.success()) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Push failed:
" + result.getErrorOutputAsHtmlString()); - return; - } - - String from = repo.getUserName() + ":" + currentBranch.getName(); - String onto = dialog.getTargetBranch(); - String targetUser = onto.substring(0, onto.indexOf(':')); - - GithubFullPath targetRepo = findRepositoryByUser(project, targetUser, forks, auth, repo.getSource()); - if (targetRepo == null) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find repository for specified branch: " + onto); - return; - } - - LOG.info("Creating pull request"); - indicator.setText("Creating pull request..."); - GithubPullRequest request = - createPullRequest(project, auth, targetRepo, dialog.getRequestTitle(), dialog.getDescription(), from, onto); - if (request == null) { - return; - } - - GithubNotifications - .showInfoURL(project, "Successfully created pull request", "Pull Request #" + request.getNumber(), request.getHtmlUrl()); - } - }.queue(); } - - @Nullable - private static GithubInfo loadGithubInfoAndBranchesWithModal(@NotNull final Project project, - @NotNull final GithubFullPath userAndRepo, - @Nullable final GithubFullPath upstreamUserAndRepo, - @NotNull final Map forks, - @NotNull final Set branches) { - try { - return GithubUtil - .computeValueInModal(project, "Access to GitHub", new ThrowableConvertor() { - @Override - public GithubInfo convert(ProgressIndicator indicator) throws IOException { - final AtomicReference reposRef = new AtomicReference(); - final GithubAuthData auth = - GithubUtil.runAndGetValidAuth(project, indicator, new ThrowableConsumer() { - @Override - public void consume(GithubAuthData authData) throws IOException { - reposRef.set(GithubApiUtil.getDetailedRepoInfo(authData, userAndRepo.getUser(), userAndRepo.getRepository())); - } - }); - addAvailableBranchesFromGithub(project, auth, reposRef.get(), upstreamUserAndRepo, forks, branches); - return new GithubInfo(auth, reposRef.get()); - } - }); - } - catch (GithubAuthenticationCanceledException e) { - return null; - } - catch (IOException e) { - GithubNotifications.showErrorDialog(project, CANNOT_CREATE_PULL_REQUEST, e); - return null; - } - } - - @Nullable - private static GithubFullPath findRepositoryByUser(@NotNull Project project, - @NotNull String user, - @NotNull Map forks, - @NotNull GithubAuthData auth, - @Nullable GithubRepo source) { - for (Map.Entry entry : forks.entrySet()) { - if (StringUtil.equalsIgnoreCase(user, entry.getKey())) { - return new GithubFullPath(entry.getKey(), entry.getValue()); - } - } - - if (source != null) { - try { - GithubRepoDetailed target = GithubApiUtil.getDetailedRepoInfo(auth, user, source.getName()); - if (target.getSource() != null && StringUtil.equals(target.getSource().getUserName(), source.getUserName())) { - forks.put(target.getUserName(), target.getName()); - return target.getFullPath(); - } - } - catch (IOException ignore) { - // such repo may not exist - } - - try { - GithubRepo fork = GithubApiUtil.findForkByUser(auth, source.getUserName(), source.getName(), user); - if (fork != null) { - forks.put(fork.getUserName(), fork.getName()); - return fork.getFullPath(); - } - } - catch (IOException e) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); - } - } - - return null; - } - - @Nullable - private static GithubPullRequest createPullRequest(@NotNull Project project, - @NotNull GithubAuthData auth, - @NotNull GithubFullPath targetRepo, - @NotNull String title, - @NotNull String description, - @NotNull String from, - @NotNull String onto) { - try { - return GithubApiUtil.createPullRequest(auth, targetRepo.getUser(), targetRepo.getRepository(), title, description, from, onto); - } - catch (IOException e) { - GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); - return null; - } - } - - private static void addAvailableBranchesFromGit(@NotNull GitRepository gitRepository, - @NotNull Map forks, - @NotNull Set branches) { - 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.put(path.getUser(), path.getRepository()); - branches.add(new RemoteBranch(path.getUser(), remoteBranch.getNameForRemoteOperations(), remoteBranch)); - break; - } - } - } - } - } - - private static void addAvailableBranchesFromGithub(@NotNull final Project project, - @NotNull final GithubAuthData auth, - @NotNull final GithubRepoDetailed repo, - @Nullable final GithubFullPath upstreamPath, - @NotNull Map forks, - @NotNull Set branches) { - try { - final GithubRepo parent = repo.getParent(); - final GithubRepo source = repo.getSource(); - - forks.put(repo.getUserName(), repo.getName()); - branches.addAll(getBranches(auth, repo.getUserName(), repo.getName())); - - if (parent != null) { - forks.put(parent.getUserName(), parent.getName()); - branches.addAll(getBranches(auth, parent.getUserName(), parent.getName())); - } - - if (source != null && !equals(source, parent)) { - forks.put(source.getUserName(), source.getName()); - branches.addAll(getBranches(auth, source.getUserName(), source.getName())); - } - - if (upstreamPath != null && !equals(upstreamPath, repo) && !equals(upstreamPath, parent) && !equals(upstreamPath, source)) { - forks.put(upstreamPath.getUser(), upstreamPath.getRepository()); - branches.addAll(getBranches(auth, upstreamPath.getUser(), upstreamPath.getRepository())); - } - } - catch (IOException e) { - GithubNotifications.showError(project, "Can't load available branches", e); - } - } - - @NotNull - private static List getBranches(@NotNull GithubAuthData auth, @NotNull final String user, @NotNull final String repo) - throws IOException { - List branches = GithubApiUtil.getRepoBranches(auth, user, repo); - return ContainerUtil.map(branches, new Function() { - @Override - public RemoteBranch fun(GithubBranch branch) { - return new RemoteBranch(user, branch.getName()); - } - }); - } - - private static boolean equals(@NotNull GithubRepo repo1, @Nullable GithubRepo repo2) { - if (repo2 == null) { - return false; - } - return StringUtil.equals(repo1.getUserName(), repo2.getUserName()); - } - - private static boolean equals(@NotNull GithubFullPath repo1, @Nullable GithubRepo repo2) { - if (repo2 == null) { - return false; - } - return StringUtil.equals(repo1.getUser(), repo2.getUserName()); - } - - private static void showDiffByRef(@NotNull final Project project, - @Nullable final String ref, - @NotNull final GitRepository gitRepository, - @NotNull final String currentBranch, - @NotNull final GithubAuthData auth, - @NotNull final Map forks, - @NotNull final Set branches, - @Nullable final GithubRepo source) { - if (ref == null) { - return; - } - - DiffInfo info = GithubUtil.computeValueInModal(project, "Collecting diff data...", new Convertor() { - @Override - @Nullable - public DiffInfo convert(ProgressIndicator indicator) { - List list = StringUtil.split(ref, ":"); - assert list.size() == 2 : ref; - final String user = list.get(0); - final String branch = list.get(1); - - TargetBranchInfo targetBranchInfo; - RemoteBranch remoteBranch = findRemoteBranch(branches, user, branch); - if (remoteBranch != null && remoteBranch.getRemoteBranch() != null) { - targetBranchInfo = getTargetBranchInfo(remoteBranch.getRemoteBranch()); - } - else { - GithubFullPath forkPath = findRepositoryByUser(project, user, forks, auth, source); - if (forkPath == null) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - GithubNotifications.showErrorDialog(project, "Can't show diff", "Can't find fork for user '" + user + "'"); - } - }, indicator.getModalityState()); - return null; - } - - targetBranchInfo = findRemote(branch, gitRepository, forkPath); - if (targetBranchInfo == null) { - final AtomicReference responseRef = new AtomicReference(); - ApplicationManager.getApplication().invokeAndWait(new Runnable() { - @Override - public void run() { - responseRef.set(GithubNotifications.showYesNoDialog(project, "Can't find remote", "Configure remote for '" + user + "'?")); - } - }, indicator.getModalityState()); - if (responseRef.get() != Messages.YES) { - return null; - } - - targetBranchInfo = configureRemote(project, user, branch, gitRepository, forkPath); - } - } - if (targetBranchInfo == null) { - return null; - } - - GitFetchResult result = new GitFetcher(project, indicator, false) - .fetch(gitRepository.getRoot(), targetBranchInfo.getRemote(), targetBranchInfo.getBranchNameForRemoteOperations()); - if (!result.isSuccess()) { - GitFetcher.displayFetchResult(project, result, null, result.getErrors()); - return null; - } - - DiffInfo info = getDiffInfo(project, gitRepository, currentBranch, targetBranchInfo.getBranchNameForLocalOperations()); - if (info == null) { - ApplicationManager.getApplication().invokeLater(new Runnable() { - @Override - public void run() { - GithubNotifications.showErrorDialog(project, "Can't show diff", "Can't get diff info"); - } - }, indicator.getModalityState()); - return null; - } - return info; - } - }); - if (info == null) { - return; - } - - GitCompareBranchesDialog dialog = new GitCompareBranchesDialog(project, info.getTo(), info.getFrom(), info.getInfo(), gitRepository); - dialog.show(); - } - - private static TargetBranchInfo getTargetBranchInfo(@NotNull GitRemoteBranch remoteBranch) { - return new TargetBranchInfo(remoteBranch.getRemote().getName(), remoteBranch.getNameForRemoteOperations()); - } - - @Nullable - private static TargetBranchInfo findRemote(@NotNull String branch, - @NotNull GitRepository gitRepository, - @NotNull GithubFullPath forkPath) { - GitRemote remote = GithubUtil.findGithubRemote(gitRepository, forkPath); - return remote == null ? null : new TargetBranchInfo(remote.getName(), branch); - } - - @Nullable - private static TargetBranchInfo configureRemote(@NotNull Project project, - @NotNull String user, - @NotNull String branch, - @NotNull GitRepository gitRepository, - @NotNull GithubFullPath forkPath) { - String url = GithubUrlUtil.getCloneUrl(forkPath); - - if (GithubUtil.addGithubRemote(project, gitRepository, user, url)) { - return new TargetBranchInfo(user, branch); - } - else { - return null; - } - } - - @Nullable - private static RemoteBranch findRemoteBranch(@NotNull Set branches, @NotNull String user, @NotNull String branch) { - for (RemoteBranch remoteBranch : branches) { - if (StringUtil.equalsIgnoreCase(user, remoteBranch.getUser()) && StringUtil.equals(branch, remoteBranch.getBranch())) { - return remoteBranch; - } - } - - return null; - } - - @Nullable - private static DiffInfo getDiffInfo(@NotNull final Project project, - @NotNull final GitRepository repository, - @NotNull final String currentBranch, - @NotNull final String targetBranch) { - try { - List commits = GitHistoryUtils.history(project, repository.getRoot(), targetBranch + ".."); - Collection 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, Pair., List>create(new ArrayList(), commits)); - return new DiffInfo(info, currentBranch, targetBranch); - } - catch (VcsException e) { - LOG.info(e); - return null; - } - } - - private static class RemoteBranch { - @NotNull final String myUser; - @NotNull final String myBranch; - - @Nullable final GitRemoteBranch myRemoteBranch; - - private RemoteBranch(@NotNull String user, @NotNull String branch) { - this(user, branch, null); - } - - public RemoteBranch(@NotNull String user, @NotNull String branch, @Nullable GitRemoteBranch localBranch) { - myUser = user; - myBranch = branch; - myRemoteBranch = localBranch; - } - - @NotNull - public String getReference() { - return myUser + ":" + myBranch; - } - - @NotNull - public String getUser() { - return myUser; - } - - @NotNull - public String getBranch() { - return myBranch; - } - - @Nullable - public GitRemoteBranch getRemoteBranch() { - return myRemoteBranch; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - RemoteBranch that = (RemoteBranch)o; - - if (!StringUtil.equals(myUser, that.myUser)) return false; - if (!StringUtil.equals(myBranch, that.myBranch)) return false; - - return true; - } - - @Override - public int hashCode() { - int result = myUser.hashCode(); - result = 31 * result + myBranch.hashCode(); - return result; - } - } - - private static class GithubInfo { - @NotNull private final GithubRepoDetailed myRepo; - @NotNull private final GithubAuthData myAuthData; - - private GithubInfo(@NotNull GithubAuthData authData, @NotNull GithubRepoDetailed repo) { - myAuthData = authData; - myRepo = repo; - } - - @NotNull - public GithubRepoDetailed getRepo() { - return myRepo; - } - - @NotNull - public GithubAuthData getAuthData() { - return myAuthData; - } - } - - private static class DiffInfo { - @NotNull private final GitCommitCompareInfo myInfo; - @NotNull private final String myFrom; - @NotNull private final String myTo; - - private DiffInfo(@NotNull GitCommitCompareInfo info, @NotNull String from, @NotNull String to) { - myInfo = info; - myFrom = from; - myTo = to; - } - - @NotNull - public GitCommitCompareInfo getInfo() { - return myInfo; - } - - @NotNull - public String getFrom() { - return myFrom; - } - - @NotNull - public String getTo() { - return myTo; - } - } - - private static class TargetBranchInfo { - @NotNull private final String myRemote; - @NotNull private final String myName; - @NotNull private final String myNameAtRemote; - - private TargetBranchInfo(@NotNull String remote, @NotNull String nameAtRemote) { - myRemote = remote; - myNameAtRemote = GitBranchUtil.stripRefsPrefix(nameAtRemote); - myName = myRemote + "/" + myNameAtRemote; - } - - @NotNull - public String getRemote() { - return myRemote; - } - - @NotNull - public String getBranchNameForLocalOperations() { - return myName; - } - - @NotNull - public String getBranchNameForRemoteOperations() { - return myNameAtRemote; - } - } -} +} \ No newline at end of file diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java new file mode 100644 index 000000000000..6e074fff44a0 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java @@ -0,0 +1,623 @@ +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.ProgressIndicator; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vcs.VcsException; +import com.intellij.openapi.vcs.changes.Change; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.util.Consumer; +import com.intellij.util.Function; +import com.intellij.util.ThrowableConvertor; +import com.intellij.util.containers.*; +import com.intellij.util.containers.HashMap; +import git4idea.DialogManager; +import git4idea.GitCommit; +import git4idea.GitLocalBranch; +import git4idea.GitRemoteBranch; +import git4idea.changes.GitChangeUtils; +import git4idea.commands.Git; +import git4idea.commands.GitCommandResult; +import git4idea.history.GitHistoryUtils; +import git4idea.repo.GitRemote; +import git4idea.repo.GitRepository; +import git4idea.ui.branch.GitCompareBranchesDialog; +import git4idea.update.GitFetchResult; +import git4idea.update.GitFetcher; +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.GithubAuthenticationCanceledException; +import org.jetbrains.plugins.github.ui.GithubSelectForkDialog; +import org.jetbrains.plugins.github.util.GithubAuthData; +import org.jetbrains.plugins.github.util.GithubNotifications; +import org.jetbrains.plugins.github.util.GithubUrlUtil; +import org.jetbrains.plugins.github.util.GithubUtil; + +import java.io.IOException; +import java.util.*; +import java.util.HashSet; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @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"; + + @NotNull private final Project myProject; + @NotNull private final Git myGit; + @NotNull private final GitRepository myGitRepository; + @NotNull private final GithubFullPath myPath; + @NotNull private final String myRemoteName; + @NotNull private final String myRemoteUrl; + @NotNull private final String myCurrentBranch; + @NotNull private final GithubAuthData myAuth; + + @NotNull private final Map> myDiffInfos; + + private volatile GithubFullPath myForkPath; + private volatile String myTargetRemote; + + private GithubCreatePullRequestWorker(@NotNull Project project, + @NotNull Git git, + @NotNull GitRepository gitRepository, + @NotNull GithubFullPath path, + @NotNull String remoteName, + @NotNull String remoteUrl, + @NotNull String currentBranch, + @NotNull GithubAuthData auth) { + myProject = project; + myGit = git; + myGitRepository = gitRepository; + myPath = path; + myRemoteName = remoteName; + myRemoteUrl = remoteUrl; + myCurrentBranch = currentBranch; + myAuth = auth; + + myDiffInfos = new HashMap>(); + } + + @NotNull + public Project getProject() { + return myProject; + } + + @NotNull + public String getCurrentBranch() { + return myCurrentBranch; + } + + public boolean canShowDiff() { + return myTargetRemote != null; + } + + @Nullable + public static GithubCreatePullRequestWorker createPullRequestWorker(@NotNull final Project project, @Nullable final VirtualFile file) { + 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.update(); + + Pair 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; + } + + GithubAuthData auth; + try { + auth = GithubUtil + .computeValueInModal(project, "Access to GitHub", new ThrowableConvertor() { + @Override + public GithubAuthData convert(ProgressIndicator indicator) throws IOException { + return GithubUtil.getValidAuthDataFromConfig(project, indicator); + } + }); + } + catch (GithubAuthenticationCanceledException e) { + return null; + } + catch (IOException e) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + return null; + } + + return new GithubCreatePullRequestWorker(project, git, gitRepository, path, remoteName, remoteUrl, currentBranch.getName(), auth); + } + + @Nullable + public GithubTargetInfo setTarget(@NotNull final GithubFullPath forkPath) { + try { + GithubInfo info = + GithubUtil.computeValueInModal(myProject, "Access to GitHub", new ThrowableConvertor() { + @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 AtomicReference responseRef = new AtomicReference(); + 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); + } + } + + // load available branches + List branches = ContainerUtil.map(GithubApiUtil.getRepoBranches(myAuth, forkPath.getUser(), forkPath.getRepository()), + new Function() { + @Override + public String fun(GithubBranch githubBranch) { + return githubBranch.getName(); + } + }); + + // 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; + } + } + + return new GithubInfo(branches, targetRemoteName); + } + }); + + myForkPath = forkPath; + myTargetRemote = info.getTargetRemote(); + + myDiffInfos.clear(); + if (canShowDiff()) { + for (final String branch : info.getBranches()) { + myDiffInfos.put(branch, new FutureTask(new Callable() { + @Override + public DiffInfo call() throws Exception { + return loadDiffInfo(myProject, myGitRepository, myCurrentBranch, myTargetRemote + "/" + branch); + } + })); + } + } + + return new GithubTargetInfo(info.getBranches()); + } + catch (GithubAuthenticationCanceledException e) { + return null; + } + catch (IOException e) { + GithubNotifications.showErrorDialog(myProject, CANNOT_CREATE_PULL_REQUEST, e); + 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; + } + + GitCompareBranchesDialog dialog = + new GitCompareBranchesDialog(myProject, info.getTo(), info.getFrom(), info.getInfo(), myGitRepository); + dialog.show(); + } + } + + @Nullable + public GithubFullPath showTargetDialog() { + final GithubInfo2 info = getAvailableForksInModal(myProject, myGitRepository, myAuth, myPath); + if (info == null) { + return null; + } + + Convertor getForkPath = new Convertor() { + @Nullable + @Override + public GithubFullPath convert(@NotNull final String user) { + return GithubUtil.computeValueInModal(myProject, "Access to GitHub", new Convertor() { + @Nullable + @Override + public GithubFullPath convert(ProgressIndicator o) { + return findRepositoryByUser(myProject, user, info.getForks(), myAuth, info.getSource()); + } + }); + } + }; + 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); + if (info == null) { + return true; + } + if (info.getInfo().getBranchToHeadCommits(myGitRepository).isEmpty()) { + GithubNotifications.showWarningDialog(myProject, CANNOT_CREATE_PULL_REQUEST, + "Can't create empty pull request: the branch" + getCurrentBranch() + " in fully merged to the branch " + targetBranch + "."); + return false; + } + if (info.getInfo().getHeadToBranchCommits(myGitRepository).isEmpty()) { + return GithubNotifications + .showYesNoDialog(myProject, "The branch" + targetBranch + " in not fully merged to the branch " + getCurrentBranch(), + "Do you want to proceed anyway?") == Messages.YES; + } + + return true; + } + + public void performAction(@NotNull final String title, @NotNull final String description, @NotNull final String targetBranch) { + @NotNull final Project project = myProject; + + new Task.Backgroundable(myProject, "Creating pull request...") { + @Override + public void run(@NotNull ProgressIndicator indicator) { + LOG.info("Pushing current branch"); + 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:
" + result.getErrorOutputAsHtmlString()); + return; + } + + String headBranch = myPath.getUser() + ":" + myCurrentBranch; + + LOG.info("Creating pull request"); + indicator.setText("Creating pull request..."); + GithubPullRequest request = createPullRequest(project, myAuth, myForkPath, title, description, headBranch, targetBranch); + if (request == null) { + return; + } + + GithubNotifications + .showInfoURL(project, "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); + + if (GithubUtil.addGithubRemote(project, gitRepository, forkPath.getUser(), url)) { + return forkPath.getUser(); + } + else { + return null; + } + } + + @Nullable + private static GithubPullRequest createPullRequest(@NotNull Project project, + @NotNull GithubAuthData auth, + @NotNull GithubFullPath targetRepo, + @NotNull String title, + @NotNull String description, + @NotNull String head, + @NotNull String base) { + try { + return GithubApiUtil.createPullRequest(auth, targetRepo.getUser(), targetRepo.getRepository(), title, description, head, base); + } + catch (IOException e) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + return null; + } + } + + @Nullable + private DiffInfo getDiffInfo(@NotNull String branch) { + try { + FutureTask future = myDiffInfos.get(branch); + if (future == null) { + return null; + } + future.run(); + return future.get(); + } + catch (InterruptedException e) { + LOG.error(e); + return null; + } + catch (ExecutionException e) { + LOG.error(e); + return null; + } + } + + @Nullable + private DiffInfo getDiffInfoWithModal(@NotNull final String branch) { + return GithubUtil.computeValueInModal(myProject, "Collecting diff data...", new Convertor() { + @Override + @Nullable + public DiffInfo convert(ProgressIndicator indicator) { + return getDiffInfo(branch); + } + }); + } + + public void getDiffDescriptionInPooledThread(@NotNull final String branch, @NotNull final Consumer after) { + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + after.consume(getDefaultDescriptionMessage(branch, getDiffInfo(branch), myGitRepository)); + } + }); + } + + @Nullable + private static DiffInfo loadDiffInfo(@NotNull final Project project, + @NotNull final GitRepository repository, + @NotNull final String currentBranch, + @NotNull final String targetBranch) { + try { + List commits1 = GitHistoryUtils.history(project, repository.getRoot(), ".." + targetBranch); + List commits2 = GitHistoryUtils.history(project, repository.getRoot(), targetBranch + ".."); + Collection 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, Pair.create(commits1, commits2)); + return new DiffInfo(info, currentBranch, targetBranch); + } + catch (VcsException e) { + LOG.info(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); + } + + 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 GithubAuthData auth, + @NotNull final GithubFullPath path) { + try { + return GithubUtil + .computeValueInModal(project, "Access to GitHub", new ThrowableConvertor() { + @Override + public GithubInfo2 convert(ProgressIndicator indicator) throws IOException { + final Set forks = new HashSet(); + + // GitHub + GithubRepoDetailed repo = GithubApiUtil.getDetailedRepoInfo(auth, path.getUser(), path.getRepository()); + forks.add(path); + if (repo.getParent() != null) { + forks.add(repo.getParent().getFullPath()); + } + if (repo.getSource() != null) { + forks.add(repo.getSource().getFullPath()); + } + + // Git + forks.addAll(getAvailableForksFromGit(gitRepository)); + + GithubRepo forkTreeRoot = repo.getSource() == null ? repo : repo.getSource(); + return new GithubInfo2(forks, forkTreeRoot); + } + }); + } + catch (GithubAuthenticationCanceledException e) { + return null; + } + catch (IOException e) { + GithubNotifications.showErrorDialog(project, CANNOT_CREATE_PULL_REQUEST, e); + return null; + } + } + + @NotNull + private static List getAvailableForksFromGit(@NotNull GitRepository gitRepository) { + List forks = new ArrayList(); + 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; + } + + @Nullable + private static GithubFullPath findRepositoryByUser(@NotNull Project project, + @NotNull String user, + @NotNull Set forks, + @NotNull GithubAuthData auth, + @NotNull GithubRepo source) { + for (GithubFullPath path : forks) { + if (StringUtil.equalsIgnoreCase(user, path.getUser())) { + return path; + } + } + + 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 + } + + try { + GithubRepo fork = GithubApiUtil.findForkByUser(auth, source.getUserName(), source.getName(), user); + if (fork != null) { + return fork.getFullPath(); + } + } + catch (IOException e) { + GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); + } + + return null; + } + + private static class GithubInfo { + @NotNull private final List myBranches; + @Nullable private final String myTargetRemote; + + private GithubInfo(@NotNull List repo, @Nullable String targetRemote) { + myBranches = repo; + myTargetRemote = targetRemote; + } + + @NotNull + public List getBranches() { + return myBranches; + } + + @Nullable + public String getTargetRemote() { + return myTargetRemote; + } + } + + private static class GithubInfo2 { + @NotNull private final Set myForks; + @NotNull private final GithubRepo mySource; + + private GithubInfo2(@NotNull Set forks, @NotNull GithubRepo source) { + myForks = forks; + mySource = source; + } + + @NotNull + public Set getForks() { + return myForks; + } + + @NotNull + public GithubRepo getSource() { + return mySource; + } + } + + public static class GithubTargetInfo { + @NotNull private final List myBranches; + + private GithubTargetInfo(@NotNull List branches) { + myBranches = branches; + } + + @NotNull + public List getBranches() { + return myBranches; + } + } + + private static class DiffInfo { + @NotNull private final GitCommitCompareInfo myInfo; + @NotNull private final String myFrom; + @NotNull private final String myTo; + + private DiffInfo(@NotNull GitCommitCompareInfo info, @NotNull String from, @NotNull String to) { + myInfo = info; + myFrom = from; // HEAD + myTo = to; // BASE + } + + @NotNull + public GitCommitCompareInfo getInfo() { + return myInfo; + } + + @NotNull + public String getFrom() { + return myFrom; + } + + @NotNull + public String getTo() { + return myTo; + } + } + + public static class DiffDescription { + @NotNull private final String myBranch; + @Nullable private final String myTitle; + @Nullable private final String myDescription; + + public DiffDescription(@NotNull String branch, @Nullable String title, @Nullable String description) { + myBranch = branch; + myTitle = title; + myDescription = description; + } + + @NotNull + public String getBranch() { + return myBranch; + } + + @Nullable + public String getTitle() { + return myTitle; + } + + @Nullable + public String getDescription() { + return myDescription; + } + } +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java index 1ae53b6ff2ed..27947217d265 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java +++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubApiUtil.java @@ -597,10 +597,10 @@ public class GithubApiUtil { @NotNull String repo, @NotNull String title, @NotNull String description, - @NotNull String from, - @NotNull String onto) throws IOException { + @NotNull String head, + @NotNull String base) throws IOException { try { - String request = gson.toJson(new GithubPullRequestRequest(title, description, from, onto)); + String request = gson.toJson(new GithubPullRequestRequest(title, description, head, base)); return createDataFromRaw(fromJson(postRequest(auth, "/repos/" + user + "/" + repo + "/pulls", request), GithubPullRequestRaw.class), GithubPullRequest.class); } diff --git a/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java b/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java index c2c7c7f63d86..740d34a2837f 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java +++ b/plugins/github/src/org/jetbrains/plugins/github/api/GithubFullPath.java @@ -41,7 +41,7 @@ public class GithubFullPath { } @NotNull - public String toString() { + public String getFullName() { return myUserName + '/' + myRepositoryName; } diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java index e0c648e16568..338003eb4dc7 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestDialog.java @@ -15,43 +15,140 @@ */ package org.jetbrains.plugins.github.ui; -import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Consumer; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; +import org.jetbrains.plugins.github.GithubCreatePullRequestWorker; +import org.jetbrains.plugins.github.api.GithubFullPath; import org.jetbrains.plugins.github.util.GithubProjectSettings; import javax.swing.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ItemEvent; +import java.awt.event.ItemListener; import java.util.Collection; -import java.util.regex.Pattern; +import java.util.Collections; /** * @author Aleksey Pivovarov */ public class GithubCreatePullRequestDialog extends DialogWrapper { @NotNull private final GithubCreatePullRequestPanel myGithubCreatePullRequestPanel; - @NotNull private static final Pattern GITHUB_REPO_PATTERN = Pattern.compile("[a-zA-Z0-9_.-]+:[a-zA-Z0-9_.-]+"); - @NotNull private final Project myProject; + @NotNull private final GithubCreatePullRequestWorker myWorker; + @NotNull private final GithubProjectSettings myProjectSettings; - public GithubCreatePullRequestDialog(@NotNull final Project project, - @NotNull Collection branches, - @Nullable String suggestedBranch, - @NotNull Consumer showDiff) { - super(project, true); - myGithubCreatePullRequestPanel = new GithubCreatePullRequestPanel(showDiff); + public GithubCreatePullRequestDialog(@NotNull GithubCreatePullRequestWorker worker) { + super(worker.getProject(), true); + myWorker = worker; + myProjectSettings = GithubProjectSettings.getInstance(myWorker.getProject()); + + myGithubCreatePullRequestPanel = new GithubCreatePullRequestPanel(); + + myGithubCreatePullRequestPanel.getShowDiffButton().addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + myWorker.showDiffDialog(myGithubCreatePullRequestPanel.getBranch()); + } + }); + myGithubCreatePullRequestPanel.getSelectForkButton().addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showTargetDialog(); + } + }); + myGithubCreatePullRequestPanel.getBranchComboBox().addItemListener(new ItemListener() { + @Override + public void itemStateChanged(ItemEvent e) { + if (e.getStateChange() == ItemEvent.SELECTED) { + if (myWorker.canShowDiff()) { + myGithubCreatePullRequestPanel.setBusy(true); + myWorker.getDiffDescriptionInPooledThread(getTargetBranch(), new Consumer() { + @Override + public void consume(final GithubCreatePullRequestWorker.DiffDescription info) { + UIUtil.invokeLaterIfNeeded(new Runnable() { + @Override + public void run() { + if (info == null) { + myGithubCreatePullRequestPanel.setBusy(false); + return; + } + if (getTargetBranch().equals(info.getBranch())) { + myGithubCreatePullRequestPanel.setBusy(false); + if (myGithubCreatePullRequestPanel.isTitleDescriptionEmptyOrNotModified()) { + myGithubCreatePullRequestPanel.setTitle(info.getTitle()); + myGithubCreatePullRequestPanel.setDescription(info.getDescription()); + } + } + } + }); + } + }); + } + } + } + }); + + setTitle("Create Pull Request - " + myWorker.getCurrentBranch()); + init(); + } + + @Override + public void show() { + GithubFullPath defaultForkPath = myProjectSettings.getCreatePullRequestDefaultRepo(); + if (defaultForkPath != null) { + setTarget(defaultForkPath); + } + else { + if (!showTargetDialog()) { + close(CANCEL_EXIT_CODE); + return; + } + } + super.show(); + } + + private boolean showTargetDialog() { + GithubFullPath forkPath = myWorker.showTargetDialog(); + if (forkPath == null) { + return false; + } + return setTarget(forkPath); + } + + private boolean setTarget(@NotNull GithubFullPath forkPath) { + GithubCreatePullRequestWorker.GithubTargetInfo forkInfo = myWorker.setTarget(forkPath); + if (forkInfo == null) { + return false; + } + myProjectSettings.setCreatePullRequestDefaultRepo(forkPath); + myGithubCreatePullRequestPanel.setDiffEnabled(myWorker.canShowDiff()); + updateBranches(forkInfo.getBranches(), forkPath); + return true; + } + + private void updateBranches(@NotNull Collection branches, @NotNull GithubFullPath forkPath) { myGithubCreatePullRequestPanel.setBranches(branches); - myProject = project; - String configBranch = GithubProjectSettings.getInstance(myProject).getCreatePullRequestDefaultBranch(); - myGithubCreatePullRequestPanel.setSelectedBranch(configBranch != null ? configBranch : suggestedBranch); + String configBranch = myProjectSettings.getCreatePullRequestDefaultBranch(); + if (configBranch != null) myGithubCreatePullRequestPanel.setSelectedBranch(configBranch); - setTitle("Create Pull Request"); - init(); + myGithubCreatePullRequestPanel.setForkName(forkPath.getFullName()); + } + + @Override + protected void doOKAction() { + if (myWorker.checkAction(getTargetBranch())) { + myProjectSettings.setCreatePullRequestDefaultBranch(getTargetBranch()); + myWorker.performAction(getRequestTitle(), getDescription(), getTargetBranch()); + super.doOKAction(); + } } @Nullable @@ -77,47 +174,51 @@ public class GithubCreatePullRequestDialog extends DialogWrapper { } @NotNull - public String getRequestTitle() { + private String getRequestTitle() { return myGithubCreatePullRequestPanel.getTitle(); } @NotNull - public String getDescription() { + private String getDescription() { return myGithubCreatePullRequestPanel.getDescription(); } @NotNull - public String getTargetBranch() { + private String getTargetBranch() { return myGithubCreatePullRequestPanel.getBranch(); } - @Override - protected void doOKAction() { - super.doOKAction(); - GithubProjectSettings.getInstance(myProject).setCreatePullRequestDefaultBranch(getTargetBranch()); - } - @Nullable @Override protected ValidationInfo doValidate() { if (StringUtil.isEmptyOrSpaces(getRequestTitle())) { return new ValidationInfo("Title can't be empty'", myGithubCreatePullRequestPanel.getTitleTextField()); } - - if (!GITHUB_REPO_PATTERN.matcher(getTargetBranch()).matches()) { - return new ValidationInfo("Branch must be specified like 'username:branch'", myGithubCreatePullRequestPanel.getBranchEditor()); - } - return null; } @TestOnly - public void setRequestTitle(String title) { + public void testSetRequestTitle(String title) { myGithubCreatePullRequestPanel.setTitle(title); } @TestOnly - public void setBranch(String branch) { - myGithubCreatePullRequestPanel.setSelectedBranch(branch); + public void testSetBranch(String branch) { + myGithubCreatePullRequestPanel.setBranches(Collections.singleton(branch)); + } + + @TestOnly + public void testCreatePullRequest() { + myWorker.performAction(getRequestTitle(), getDescription(), getTargetBranch()); + } + + @TestOnly + public void testSetTarget(@NotNull GithubFullPath forkPath) { + GithubCreatePullRequestWorker.GithubTargetInfo forkInfo = myWorker.setTarget(forkPath); + if (forkInfo == null) { + doCancelAction(); + return; + } + myGithubCreatePullRequestPanel.setDiffEnabled(myWorker.canShowDiff()); } } diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form index 46864f055405..e19b8601fa01 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.form @@ -1,6 +1,6 @@
- + @@ -10,15 +10,15 @@ - + - + - + @@ -26,7 +26,7 @@ - + @@ -34,29 +34,20 @@ - + - - - - - - - - - - + - + @@ -66,12 +57,51 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java index e4a4521bb89b..41512465eeea 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubCreatePullRequestPanel.java @@ -17,14 +17,15 @@ package org.jetbrains.plugins.github.ui; import com.intellij.openapi.ui.ComboBox; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.DocumentAdapter; import com.intellij.ui.SortedComboBoxModel; -import com.intellij.util.Consumer; +import com.intellij.util.ui.AsyncProcessIcon; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import java.util.Collection; import java.util.Comparator; @@ -38,8 +39,13 @@ public class GithubCreatePullRequestPanel { private SortedComboBoxModel myBranchModel; private JPanel myPanel; private JButton myShowDiffButton; + private JButton mySelectForkButton; + private JLabel myForkLabel; + private AsyncProcessIcon myBusyIcon; - public GithubCreatePullRequestPanel(@NotNull final Consumer showDiff) { + private boolean myTitleDescriptionUserModified = false; + + public GithubCreatePullRequestPanel() { myDescriptionTextArea.setBorder(BorderFactory.createEtchedBorder()); myBranchModel = new SortedComboBoxModel(new Comparator() { @Override @@ -48,12 +54,15 @@ public class GithubCreatePullRequestPanel { } }); myBranchComboBox.setModel(myBranchModel); - myShowDiffButton.addActionListener(new ActionListener() { + + DocumentListener userModifiedDocumentListener = new DocumentAdapter() { @Override - public void actionPerformed(ActionEvent e) { - showDiff.consume(getBranch()); + protected void textChanged(DocumentEvent e) { + myTitleDescriptionUserModified = true; } - }); + }; + myTitleTextField.getDocument().addDocumentListener(userModifiedDocumentListener); + myDescriptionTextArea.getDocument().addDocumentListener(userModifiedDocumentListener); } @NotNull @@ -71,21 +80,24 @@ public class GithubCreatePullRequestPanel { return myBranchComboBox.getSelectedItem().toString(); } + public void setDiffEnabled(boolean enabled) { + myShowDiffButton.setEnabled(enabled); + } + public void setSelectedBranch(@Nullable String branch) { if (StringUtil.isEmptyOrSpaces(branch)) { - myBranchComboBox.setSelectedItem(""); return; } - if (myBranchModel.indexOf(branch) == -1) { - myBranchModel.add(branch); - } myBranchComboBox.setSelectedItem(branch); } public void setBranches(@NotNull Collection branches) { myBranchModel.clear(); myBranchModel.addAll(branches); + if (branches.size() > 0) { + myBranchComboBox.setSelectedIndex(0); + } } public JPanel getPanel() { @@ -97,15 +109,61 @@ public class GithubCreatePullRequestPanel { return myTitleTextField; } + @NotNull public JComponent getBranchEditor() { return myBranchComboBox; } + @NotNull public JComponent getTitleTextField() { return myTitleTextField; } - public void setTitle(String title) { + @NotNull + public JButton getShowDiffButton() { + return myShowDiffButton; + } + + @NotNull + public JButton getSelectForkButton() { + return mySelectForkButton; + } + + @NotNull + public ComboBox getBranchComboBox() { + return myBranchComboBox; + } + + public void setTitle(@Nullable String title) { myTitleTextField.setText(title); + myTitleDescriptionUserModified = false; + } + + public void setDescription(@Nullable String title) { + myDescriptionTextArea.setText(title); + myTitleDescriptionUserModified = false; + } + + public boolean isTitleDescriptionEmptyOrNotModified() { + return !myTitleDescriptionUserModified || + (StringUtil.isEmptyOrSpaces(myTitleTextField.getText()) && StringUtil.isEmptyOrSpaces(myDescriptionTextArea.getText())); + } + + public void setForkName(@NotNull String forkName) { + myForkLabel.setText(forkName); + } + + public void setBusy(boolean enabled) { + if (enabled) { + myBusyIcon.resume(); + } + else { + myBusyIcon.suspend(); + } + } + + private void createUIComponents() { + myBusyIcon = new AsyncProcessIcon("Loading diff..."); + myBusyIcon.suspend(); } } diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java new file mode 100644 index 000000000000..d8ce684eeb38 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkDialog.java @@ -0,0 +1,89 @@ +/* + * Copyright 2000-2013 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.ui; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.util.Function; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.Convertor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; +import org.jetbrains.plugins.github.api.GithubFullPath; +import org.jetbrains.plugins.github.util.GithubNotifications; + +import javax.swing.*; +import java.util.Set; + +/** + * @author Aleksey Pivovarov + */ +public class GithubSelectForkDialog extends DialogWrapper { + @NotNull private final GithubSelectForkPanel myPanel; + @NotNull private final Project myProject; + @NotNull private final Convertor myCheckFork; + private GithubFullPath myFullPath; + + + public GithubSelectForkDialog(@NotNull Project project, + @NotNull Set forks, + @NotNull Convertor checkFork) { + super(project); + myProject = project; + myCheckFork = checkFork; + + myPanel = new GithubSelectForkPanel(); + + myPanel.setUsers(ContainerUtil.map(forks, new Function() { + @Override + public String fun(GithubFullPath path) { + return path.getUser(); + } + })); + + setTitle("Select Base Fork Repository"); + init(); + } + + @Override + protected void doOKAction() { + GithubFullPath path = myCheckFork.convert(myPanel.getUser()); + if (path == null) { + GithubNotifications.showErrorDialog(myProject, "Can't Find Repository", "Can't find fork for selected user"); + } + else { + myFullPath = path; + super.doOKAction(); + } + } + + @Nullable + @Override + protected JComponent createCenterPanel() { + return myPanel.getPanel(); + } + + @NotNull + public GithubFullPath getPath() { + return myFullPath; + } + + @TestOnly + public void testSetUser(@NotNull String user) { + myPanel.setSelectedUser(user); + } +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.form b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.form new file mode 100644 index 000000000000..8f23fd224598 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.form @@ -0,0 +1,34 @@ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java new file mode 100644 index 000000000000..754afc617057 --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/ui/GithubSelectForkPanel.java @@ -0,0 +1,71 @@ +/* + * Copyright 2000-2013 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.ui; + +import com.intellij.openapi.ui.ComboBox; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.ui.SortedComboBoxModel; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.Collection; +import java.util.Comparator; + +/** + * @author Aleksey Pivovarov + */ +public class GithubSelectForkPanel { + private final SortedComboBoxModel myModel; + private JPanel myPanel; + private ComboBox myComboBox; + + public GithubSelectForkPanel() { + myModel = new SortedComboBoxModel(new Comparator() { + @Override + public int compare(String o1, String o2) { + return StringUtil.naturalCompare(o1, o2); + } + }); + + myComboBox.setModel(myModel); + } + + public void setUsers(@NotNull Collection users) { + myModel.clear(); + myModel.addAll(users); + if (users.size() > 0) { + myComboBox.setSelectedIndex(0); + } + } + + @NotNull + public String getUser() { + return myComboBox.getSelectedItem().toString(); + } + + public void setSelectedUser(@Nullable String user) { + if (StringUtil.isEmptyOrSpaces(user)) { + return; + } + + myComboBox.setSelectedItem(user); + } + + public JPanel getPanel() { + return myPanel; + } +} diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubProjectSettings.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubProjectSettings.java index 716da7479ff8..4b6ae29cdc69 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubProjectSettings.java +++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubProjectSettings.java @@ -19,6 +19,7 @@ import com.intellij.openapi.components.*; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.github.api.GithubFullPath; /** * @author Aleksey Pivovarov @@ -38,8 +39,14 @@ public class GithubProjectSettings implements PersistentStateComponent() { @Override public int handleDialog(GithubCreatePullRequestDialog dialog) { - dialog.setRequestTitle(BRANCH_NAME); - dialog.setBranch(branch); + dialog.testSetRequestTitle(BRANCH_NAME); + dialog.testSetBranch(branch); + dialog.testSetTarget(new GithubFullPath(user, PROJECT_NAME)); + dialog.testCreatePullRequest(); return DialogWrapper.OK_EXIT_CODE; } }); @@ -84,6 +90,10 @@ public abstract class GithubCreatePullRequestTestBase extends GithubTest { GitInit.refreshAndConfigureVcsMappings(myProject, myProjectRoot, myProjectRoot.getPath()); } + protected void addRemote(@NotNull String user) { + git("remote add somename " + GithubUrlUtil.getCloneUrl(new GithubFullPath(user, PROJECT_NAME))); + } + protected void createBranch() { git("branch " + BRANCH_NAME); git("checkout " + BRANCH_NAME);