From 1660fb1f6ed259822bcb9802b4a6a11bf47bfa1a Mon Sep 17 00:00:00 2001 From: Ivan Semenov Date: Tue, 3 Apr 2018 14:13:46 +0300 Subject: [PATCH] [github] Multiple accounts support for create pull request action Action currently requires single account, so selector dialog will be shown in case IDEA is not able to determine the account automatically --- .../github/GithubCreatePullRequestAction.java | 57 +++---- .../github/GithubCreatePullRequestWorker.java | 154 ++++++++++-------- .../github/LegacySingleAccountActionGroup.kt | 137 ++++++++++++++++ .../GithubAuthenticationManager.kt | 15 +- .../plugins/github/util/GithubGitHelper.kt | 14 +- .../plugins/github/util/GithubUtil.java | 31 ---- .../github/GithubCreatePullRequestTest.java | 15 +- .../GithubCreatePullRequestTestBase.java | 10 +- .../plugins/github/test/GithubTest.java | 3 +- 9 files changed, 279 insertions(+), 157 deletions(-) create mode 100644 plugins/github/src/org/jetbrains/plugins/github/LegacySingleAccountActionGroup.kt diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java index 0a613e211625..15ea2c71b54e 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestAction.java @@ -15,63 +15,44 @@ */ package org.jetbrains.plugins.github; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; -import com.intellij.openapi.project.DumbAwareAction; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; import git4idea.DialogManager; +import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import icons.GithubIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.github.authentication.accounts.GithubAccount; import org.jetbrains.plugins.github.ui.GithubCreatePullRequestDialog; -import org.jetbrains.plugins.github.util.GithubUtil; /** * @author Aleksey Pivovarov */ -public class GithubCreatePullRequestAction extends DumbAwareAction { +public class GithubCreatePullRequestAction extends LegacySingleAccountActionGroup { public GithubCreatePullRequestAction() { super("Create Pull Request", "Create pull request from current branch", GithubIcons.Github_icon); } - public void update(AnActionEvent e) { - final Project project = e.getData(CommonDataKeys.PROJECT); - final VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE); - if (project == null || project.isDefault()) { - e.getPresentation().setEnabledAndVisible(false); - return; - } - - final GitRepository gitRepository = GithubUtil.getGitRepository(project, file); - if (gitRepository == null) { - e.getPresentation().setEnabledAndVisible(false); - return; - } - - if (!GithubUtil.isRepositoryOnGitHub(gitRepository)) { - e.getPresentation().setEnabledAndVisible(false); - return; - } - - e.getPresentation().setEnabledAndVisible(true); - } - @Override - public void actionPerformed(AnActionEvent e) { - final Project project = e.getData(CommonDataKeys.PROJECT); - final VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE); - - if (project == null || project.isDisposed() || !GithubUtil.testGitExecutable(project)) { - return; - } - - createPullRequest(project, file); + public void actionPerformed(@NotNull Project project, + @Nullable VirtualFile file, + @NotNull GitRepository gitRepository, + @NotNull GithubAccount account) { + createPullRequest(project, gitRepository, account); } - static void createPullRequest(@NotNull Project project, @Nullable VirtualFile file) { - GithubCreatePullRequestWorker worker = GithubCreatePullRequestWorker.create(project, file); + @Nullable + @Override + protected Pair getRemote(@NotNull GithubAccount account, @NotNull GitRepository repository) { + return GithubCreatePullRequestWorker.findGithubRemote(account, repository); + } + + static void createPullRequest(@NotNull Project project, + @NotNull GitRepository gitRepository, + @NotNull GithubAccount account) { + GithubCreatePullRequestWorker worker = GithubCreatePullRequestWorker.create(project, gitRepository, account); if (worker == null) { return; } diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java index eabfb7c69f76..173f8c313fa7 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubCreatePullRequestWorker.java @@ -18,10 +18,7 @@ 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.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.Task; +import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.util.BackgroundTaskUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Couple; @@ -48,12 +45,14 @@ import git4idea.update.GitFetcher; import git4idea.util.GitCommitCompareInfo; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.github.api.GithubApiTaskExecutor; import org.jetbrains.plugins.github.api.GithubApiUtil; import org.jetbrains.plugins.github.api.GithubFullPath; import org.jetbrains.plugins.github.api.data.GithubBranch; import org.jetbrains.plugins.github.api.data.GithubPullRequest; import org.jetbrains.plugins.github.api.data.GithubRepo; import org.jetbrains.plugins.github.api.data.GithubRepoDetailed; +import org.jetbrains.plugins.github.authentication.accounts.GithubAccount; import org.jetbrains.plugins.github.exceptions.GithubOperationCanceledException; import org.jetbrains.plugins.github.ui.GithubSelectForkDialog; import org.jetbrains.plugins.github.util.*; @@ -74,13 +73,17 @@ 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 GithubAccount myAccount; + @NotNull private final GithubApiTaskExecutor myTaskExecutor; + @NotNull private final GithubGitHelper myGitHelper; + @NotNull private final ProgressManager myProgressManager; @NotNull private final GithubFullPath myPath; @NotNull private final String myRemoteName; @NotNull private final String myRemoteUrl; @NotNull private final String myCurrentBranch; + @SuppressWarnings("NullableProblems") @NotNull private GithubFullPath mySource; @NotNull private final List myForks; @@ -89,7 +92,9 @@ public class GithubCreatePullRequestWorker { private GithubCreatePullRequestWorker(@NotNull Project project, @NotNull Git git, @NotNull GitRepository gitRepository, - @NotNull GithubAuthDataHolder authHolder, + @NotNull GithubAccount account, + @NotNull GithubApiTaskExecutor executor, + @NotNull GithubGitHelper helper, @NotNull ProgressManager progressManager, @NotNull GithubFullPath path, @NotNull String remoteName, @NotNull String remoteUrl, @@ -97,7 +102,10 @@ public class GithubCreatePullRequestWorker { myProject = project; myGit = git; myGitRepository = gitRepository; - myAuthHolder = authHolder; + myAccount = account; + myTaskExecutor = executor; + myGitHelper = helper; + myProgressManager = progressManager; myPath = path; myRemoteName = remoteName; myRemoteUrl = remoteUrl; @@ -117,18 +125,15 @@ public class GithubCreatePullRequestWorker { } @Nullable - public static GithubCreatePullRequestWorker create(@NotNull final Project project, @Nullable final VirtualFile file) { - return GithubUtil.computeValueInModal(project, "Loading data...", indicator -> { + public static GithubCreatePullRequestWorker create(@NotNull final Project project, + @NotNull GitRepository gitRepository, + @NotNull GithubAccount account) { + ProgressManager progressManager = ProgressManager.getInstance(); + return progressManager.runProcessWithProgressSynchronously(() -> { 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); + Pair remote = findGithubRemote(account, gitRepository); if (remote == null) { GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, "Can't find GitHub remote"); return null; @@ -148,20 +153,13 @@ public class GithubCreatePullRequestWorker { return null; } - GithubAuthDataHolder authHolder; - try { - authHolder = GithubUtil.getValidAuthDataHolderFromConfig(project, AuthLevel.LOGGED, 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()); + new GithubCreatePullRequestWorker(project, git, gitRepository, account, GithubApiTaskExecutor.getInstance(), + GithubGitHelper.getInstance(), progressManager, path, remoteName, remoteUrl, + currentBranch.getName()); try { - worker.initForks(indicator); + worker.initForks(progressManager.getProgressIndicator()); } catch (IOException e) { GithubNotifications.showError(project, CANNOT_CREATE_PULL_REQUEST, e); @@ -169,7 +167,27 @@ public class GithubCreatePullRequestWorker { } return worker; - }); + }, "Loading Data...", true, project); + } + + @Nullable + static Pair findGithubRemote(@NotNull GithubAccount account, @NotNull GitRepository repository) { + Pair githubRemote = null; + for (GitRemote gitRemote : repository.getRemotes()) { + for (String remoteUrl : gitRemote.getUrls()) { + if (account.getServer().matches(remoteUrl)) { + String remoteName = gitRemote.getName(); + if ("github" == remoteName || "origin" == remoteName) { + return Pair.create(gitRemote, remoteUrl); + } + if (githubRemote == null) { + githubRemote = Pair.create(gitRemote, remoteUrl); + } + break; + } + } + } + return githubRemote; } private void initForks(@NotNull ProgressIndicator indicator) throws IOException { @@ -178,16 +196,15 @@ public class GithubCreatePullRequestWorker { doLoadForksFromSettings(indicator); } - @Nullable - private ForkInfo doAddFork(@NotNull GithubFullPath path, - @Nullable String remoteName, - @NotNull ProgressIndicator indicator) { + private void 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; + return; } } @@ -200,11 +217,9 @@ public class GithubCreatePullRequestWorker { if (remoteName != null) { fork.setRemoteName(remoteName); } - return fork; } catch (IOException e) { GithubNotifications.showWarning(myProject, "Can't load branches for " + path.getFullName(), e); - return null; } } @@ -241,7 +256,7 @@ public class GithubCreatePullRequestWorker { private void doLoadForksFromGit(@NotNull ProgressIndicator indicator) { for (GitRemote remote : myGitRepository.getRemotes()) { for (String url : remote.getUrls()) { - if (GithubUrlUtil.isGithubUrl(url)) { + if (myAccount.getServer().matches(url)) { GithubFullPath path = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(url); if (path != null) { doAddFork(path, remote.getName(), indicator); @@ -253,7 +268,7 @@ public class GithubCreatePullRequestWorker { } private void doLoadForksFromGithub(@NotNull ProgressIndicator indicator) throws IOException { - GithubRepoDetailed repo = GithubUtil.runTask(myProject, myAuthHolder, indicator, connection -> + GithubRepoDetailed repo = myTaskExecutor.execute(indicator, myAccount, connection -> GithubApiUtil.getDetailedRepoInfo(connection, myPath.getUser(), myPath.getRepository())); doAddFork(repo, indicator); @@ -269,14 +284,14 @@ public class GithubCreatePullRequestWorker { @NotNull private List loadBranches(@NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException { - List branches = GithubUtil.runTask(myProject, myAuthHolder, indicator, connection -> + List branches = myTaskExecutor.execute(indicator, myAccount, connection -> GithubApiUtil.getRepoBranches(connection, fork.getUser(), fork.getRepository())); return ContainerUtil.map(branches, GithubBranch::getName); } @Nullable private String doLoadDefaultBranch(@NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException { - GithubRepo repo = GithubUtil.runTask(myProject, myAuthHolder, indicator, connection -> + GithubRepo repo = myTaskExecutor.execute(indicator, myAccount, connection -> GithubApiUtil.getDetailedRepoInfo(connection, fork.getUser(), fork.getRepository())); return repo.getDefaultBranch(); } @@ -289,9 +304,7 @@ public class GithubCreatePullRequestWorker { if (fork.getFetchTask() != null) return; final MasterFutureTask task = new MasterFutureTask<>(() -> { - BackgroundTaskUtil.runUnderDisposeAwareIndicator(myProject, () -> { - doFetchRemote(fork); - }); + BackgroundTaskUtil.runUnderDisposeAwareIndicator(myProject, () -> doFetchRemote(fork)); return null; }); fork.setFetchTask(task); @@ -339,22 +352,19 @@ public class GithubCreatePullRequestWorker { } } - private boolean doFetchRemote(@NotNull ForkInfo fork) { - if (fork.getRemoteName() == null) return false; + private void doFetchRemote(@NotNull ForkInfo fork) { + if (fork.getRemoteName() == null) return; 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; } @NotNull private DiffInfo doLoadDiffInfo(@NotNull final BranchInfo branch) throws VcsException { // TODO: make cancelable and abort old speculative requests (when intellij.vcs.git will allow to do so) - String currentBranch = myCurrentBranch; String targetBranch = branch.getForkInfo().getRemoteName() + "/" + branch.getRemoteName(); List commits1 = GitHistoryUtils.history(myProject, myGitRepository.getRoot(), ".." + targetBranch); @@ -364,24 +374,28 @@ public class GithubCreatePullRequestWorker { info.put(myGitRepository, diff); info.put(myGitRepository, Couple.of(commits1, commits2)); - return new DiffInfo(info, currentBranch, targetBranch); + return new DiffInfo(info, myCurrentBranch, targetBranch); } private void doConfigureRemote(@NotNull ForkInfo fork) { if (fork.getRemoteName() != null) return; GithubFullPath path = fork.getPath(); - String url = GithubUrlUtil.getCloneUrl(path); + String url = myGitHelper.getRemoteUrl(myAccount.getServer(), path); - if (GithubUtil.addGithubRemote(myProject, myGitRepository, path.getUser(), url)) { + try { + myGit.addRemote(myGitRepository, path.getUser(), url).getOutputOrThrow(); + myGitRepository.update(); fork.setRemoteName(path.getUser()); } + catch (VcsException e) { + GithubNotifications.showError(myProject, "Can't add remote", "Failed to add GitHub remote: '" + url + "'. " + e.getMessage()); + } } public void configureRemote(@NotNull final ForkInfo fork) { - GithubUtil.computeValueInModal(myProject, "Creating remote..", false, indicator -> { - doConfigureRemote(fork); - }); + myProgressManager.runProcessWithProgressSynchronously(() -> doConfigureRemote(fork), + "Creating Remote..", false, myProject); } @NotNull @@ -393,12 +407,11 @@ public class GithubCreatePullRequestWorker { return getSimpleDefaultDescriptionMessage(branch); } - return GithubUtil.computeValueInModal(myProject, "Collecting last commits...", true, indicator -> { - String localBranch = myCurrentBranch; + return myProgressManager.runProcessWithProgressSynchronously(() -> { String targetBranch = branch.getForkInfo().getRemoteName() + "/" + branch.getRemoteName(); try { List commits = - GitHistoryUtils.readLastCommits(myProject, myGitRepository.getRoot(), localBranch, targetBranch); + GitHistoryUtils.readLastCommits(myProject, myGitRepository.getRoot(), myCurrentBranch, targetBranch); if (commits == null) return getSimpleDefaultDescriptionMessage(branch); VcsCommitMetadata localCommit = commits.get(0); @@ -416,7 +429,7 @@ public class GithubCreatePullRequestWorker { GithubNotifications.showWarning(myProject, "Can't collect additional data", e); return getSimpleDefaultDescriptionMessage(branch); } - }); + }, "Collecting Last Commits...", true, myProject); } @NotNull @@ -434,8 +447,9 @@ public class GithubCreatePullRequestWorker { DiffInfo info; try { - info = GithubUtil.computeValueInModalIO(myProject, "Collecting diff data...", indicator -> - GithubUtil.runInterruptable(indicator, () -> getDiffInfo(branch))); + info = myProgressManager.runProcessWithProgressSynchronously( + () -> GithubUtil.runInterruptable(myProgressManager.getProgressIndicator(), () -> getDiffInfo(branch)), + "Collecting Diff Data...", false, myProject); } catch (IOException e) { GithubNotifications.showError(myProject, "Can't collect diff data", e); @@ -475,7 +489,8 @@ public class GithubCreatePullRequestWorker { indicator.setText("Pushing current branch..."); GitCommandResult result = myGit.push(myGitRepository, myRemoteName, myRemoteUrl, myCurrentBranch, true); if (!result.success()) { - GithubNotifications.showError(myProject, CANNOT_CREATE_PULL_REQUEST, "Push failed:
" + result.getErrorOutputAsHtmlString()); + GithubNotifications.showError(GithubCreatePullRequestWorker.this.myProject, CANNOT_CREATE_PULL_REQUEST, + "Push failed:
" + result.getErrorOutputAsHtmlString()); return; } @@ -486,7 +501,7 @@ public class GithubCreatePullRequestWorker { return; } - GithubNotifications.showInfoURL(myProject, "Successfully created pull request", + GithubNotifications.showInfoURL(GithubCreatePullRequestWorker.this.myProject, "Successfully created pull request", "Pull request #" + request.getNumber(), request.getHtmlUrl()); } }.queue(); @@ -503,7 +518,7 @@ public class GithubCreatePullRequestWorker { final String base = branch.getRemoteName(); try { - return GithubUtil.runTask(myProject, myAuthHolder, indicator, connection -> + return myTaskExecutor.execute(indicator, myAccount, connection -> GithubApiUtil.createPullRequest(connection, forkPath.getUser(), forkPath.getRepository(), title, description, head, base)); } catch (IOException e) { @@ -520,8 +535,9 @@ public class GithubCreatePullRequestWorker { DiffInfo info; try { - info = GithubUtil.computeValueInModalIO(myProject, "Collecting diff data...", indicator -> - GithubUtil.runInterruptable(indicator, () -> getDiffInfo(branch))); + info = myProgressManager.runProcessWithProgressSynchronously( + () -> GithubUtil.runInterruptable(myProgressManager.getProgressIndicator(), () -> getDiffInfo(branch)), + "Collecting Diff Data...", true, myProject); } catch (IOException e) { GithubNotifications.showError(myProject, "Can't collect diff data", e); @@ -541,14 +557,16 @@ public class GithubCreatePullRequestWorker { public ForkInfo showTargetDialog() { if (myAvailableForks == null) { try { - myAvailableForks = GithubUtil.computeValueInModal(myProject, myCurrentBranch, indicator -> getAvailableForks(indicator)); + myAvailableForks = myProgressManager.runProcessWithProgressSynchronously( + () -> getAvailableForks(myProgressManager.getProgressIndicator()), myCurrentBranch, false, myProject); } catch (ProcessCanceledException ignore) { } } Convertor getForkPath = user -> - GithubUtil.computeValueInModal(myProject, "Access to GitHub", indicator -> findRepositoryByUser(indicator, user)); + myProgressManager.runProcessWithProgressSynchronously(() -> findRepositoryByUser(myProgressManager.getProgressIndicator(), user), + "Access to GitHub", false, myProject); GithubSelectForkDialog dialog = new GithubSelectForkDialog(myProject, myAvailableForks, getForkPath); DialogManager.show(dialog); @@ -561,7 +579,7 @@ public class GithubCreatePullRequestWorker { @Nullable private List getAvailableForks(@NotNull ProgressIndicator indicator) { try { - List forks = GithubUtil.runTask(myProject, myAuthHolder, indicator, connection -> + List forks = myTaskExecutor.execute(indicator, myAccount, connection -> GithubApiUtil.getForks(connection, mySource.getUser(), mySource.getRepository()) ); List forkPaths = ContainerUtil.map(forks, GithubRepo::getFullPath); @@ -583,7 +601,7 @@ public class GithubCreatePullRequestWorker { } try { - GithubRepo repo = GithubUtil.runTask(myProject, myAuthHolder, indicator, connection -> { + GithubRepo repo = myTaskExecutor.execute(indicator, myAccount, connection -> { try { GithubRepoDetailed target = GithubApiUtil.getDetailedRepoInfo(connection, user, mySource.getRepository()); if (target.getSource() != null && StringUtil.equals(target.getSource().getUserName(), mySource.getUser())) { diff --git a/plugins/github/src/org/jetbrains/plugins/github/LegacySingleAccountActionGroup.kt b/plugins/github/src/org/jetbrains/plugins/github/LegacySingleAccountActionGroup.kt new file mode 100644 index 000000000000..04ccca0eb68f --- /dev/null +++ b/plugins/github/src/org/jetbrains/plugins/github/LegacySingleAccountActionGroup.kt @@ -0,0 +1,137 @@ +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. +package org.jetbrains.plugins.github + +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.components.service +import com.intellij.openapi.project.DumbAwareAction +import com.intellij.openapi.project.Project +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.ui.ValidationInfo +import com.intellij.openapi.util.Pair +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.ui.ColoredListCellRenderer +import com.intellij.ui.SimpleTextAttributes +import com.intellij.ui.components.JBList +import com.intellij.ui.components.JBScrollPane +import com.intellij.util.ui.JBDimension +import com.intellij.util.ui.JBUI +import com.intellij.util.ui.UIUtil +import git4idea.DialogManager +import git4idea.repo.GitRemote +import git4idea.repo.GitRepository +import org.jetbrains.plugins.github.authentication.GithubAuthenticationManager +import org.jetbrains.plugins.github.authentication.accounts.GithubAccount +import org.jetbrains.plugins.github.util.GithubGitHelper +import javax.swing.Icon +import javax.swing.JComponent +import javax.swing.JList +import javax.swing.JTextArea + +abstract class LegacySingleAccountActionGroup(text: String?, description: String?, icon: Icon?) : DumbAwareAction(text, description, icon) { + override fun update(e: AnActionEvent?) { + if (e == null) return + val project = e.getData(CommonDataKeys.PROJECT) + val file = e.getData(CommonDataKeys.VIRTUAL_FILE) + if (project == null || project.isDefault) { + e.presentation.isEnabledAndVisible = false + return + } + + val gitRepository = GithubGitHelper.findGitRepository(project, file) + if (gitRepository == null) { + e.presentation.isEnabledAndVisible = false + return + } + + if (getAccountsForRemotes(project, gitRepository).isEmpty()) { + e.presentation.isEnabledAndVisible = false + return + } + + e.presentation.isEnabledAndVisible = true + } + + override fun actionPerformed(e: AnActionEvent?) { + if (e == null) return + val project = e.getData(CommonDataKeys.PROJECT) + val file = e.getData(CommonDataKeys.VIRTUAL_FILE) + if (project == null || project.isDefault) return + + val gitRepository = GithubGitHelper.findGitRepository(project, file) + if (gitRepository == null) return + gitRepository.update() + + val accounts = getAccountsForRemotes(project, gitRepository) + if (accounts.isEmpty()) return + val account = if (accounts.size == 1) accounts.first() + else { + val dialog = ChooseAccountDialog(project, accounts) + DialogManager.show(dialog) + if (!dialog.isOK) return + dialog.getAccount() + } + + actionPerformed(project, file, gitRepository, account) + } + + abstract fun actionPerformed(project: Project, file: VirtualFile?, gitRepository: GitRepository, account: GithubAccount) + + private fun getAccountsForRemotes(project: Project, repository: GitRepository): List { + val authenticationManager = service() + val defaultAccount = authenticationManager.getDefaultAccount(project) + return if (defaultAccount != null && getRemote(defaultAccount, repository) != null) + listOf(defaultAccount) + else { + authenticationManager.getAccounts().filter { getRemote(it, repository) != null } + } + } + + protected abstract fun getRemote(account: GithubAccount, repository: GitRepository): Pair? +} + +private class ChooseAccountDialog(project: Project, accounts: List) : DialogWrapper(project) { + private val text = JTextArea().apply { + font = UIUtil.getLabelFont() + text = "Default account is not configured for this project. Choose Github account:" + lineWrap = true + wrapStyleWord = true + isEditable = false + isFocusable = false + isOpaque = false + border = null + margin = JBUI.emptyInsets() + } + private val list = JBList(accounts).apply { + cellRenderer = object : ColoredListCellRenderer() { + override fun customizeCellRenderer(list: JList, + value: GithubAccount, + index: Int, + selected: Boolean, + hasFocus: Boolean) { + append(value.name) + append(" ") + append(value.server.toString(), SimpleTextAttributes.GRAYED_ATTRIBUTES) + border = JBUI.Borders.empty(0, UIUtil.DEFAULT_HGAP) + } + } + } + + init { + title = "Choose Github Account" + setOKButtonText("Choose") + init() + } + + override fun doValidate(): ValidationInfo? { + return if (list.selectedValue == null) ValidationInfo("Account is not selected", list) else null + } + + fun getAccount(): GithubAccount = list.selectedValue + + override fun createCenterPanel(): JComponent? { + return JBUI.Panels.simplePanel(UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP) + .addToCenter(JBScrollPane(list).apply { preferredSize = JBDimension(150, 80) }) + .addToTop(text) + } +} \ No newline at end of file diff --git a/plugins/github/src/org/jetbrains/plugins/github/authentication/GithubAuthenticationManager.kt b/plugins/github/src/org/jetbrains/plugins/github/authentication/GithubAuthenticationManager.kt index a6b09ed08c43..bc41129e1c9e 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/authentication/GithubAuthenticationManager.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/authentication/GithubAuthenticationManager.kt @@ -4,7 +4,6 @@ package org.jetbrains.plugins.github.authentication import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import git4idea.DialogManager -import com.intellij.openapi.components.service import org.jetbrains.annotations.CalledInAny import org.jetbrains.annotations.CalledInAwt import org.jetbrains.annotations.TestOnly @@ -12,8 +11,8 @@ import org.jetbrains.plugins.github.api.GithubServerPath import org.jetbrains.plugins.github.authentication.accounts.GithubAccount import org.jetbrains.plugins.github.authentication.accounts.GithubAccountManager import org.jetbrains.plugins.github.authentication.accounts.GithubProjectDefaultAccountHolder -import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException import org.jetbrains.plugins.github.authentication.ui.GithubLoginDialog +import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException /** * Entry point for interactions with Github authentication subsystem @@ -66,6 +65,11 @@ class GithubAuthenticationManager internal constructor(private val accountManage return project.service().account } + @TestOnly + fun setDefaultAccount(project: Project, account: GithubAccount?) { + project.service().account = account + } + fun ensureHasAccounts(project: Project): Boolean { if (!hasAccounts()) { if (requestNewAccount(project) == null) { @@ -75,6 +79,13 @@ class GithubAuthenticationManager internal constructor(private val accountManage return true } + fun getSingleOrDefaultAccount(project: Project): GithubAccount? { + project.service().account?.let { return it } + val accounts = accountManager.accounts + if (accounts.size == 1) return accounts.first() + return null + } + companion object { @JvmStatic fun getInstance(): GithubAuthenticationManager { diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubGitHelper.kt b/plugins/github/src/org/jetbrains/plugins/github/util/GithubGitHelper.kt index 30772727fcd7..e551891b10fd 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubGitHelper.kt +++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubGitHelper.kt @@ -6,6 +6,7 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.vfs.VirtualFile import git4idea.GitUtil import git4idea.repo.GitRepository +import org.jetbrains.plugins.github.api.GithubFullPath import org.jetbrains.plugins.github.api.GithubServerPath import org.jetbrains.plugins.github.authentication.GithubAuthenticationManager @@ -15,6 +16,10 @@ import org.jetbrains.plugins.github.authentication.GithubAuthenticationManager class GithubGitHelper(private val githubSettings: GithubSettings, private val authenticationManager: GithubAuthenticationManager) { + fun getRemoteUrl(server: GithubServerPath, repoPath: GithubFullPath): String { + return getRemoteUrl(server, repoPath.user, repoPath.repository) + } + fun getRemoteUrl(server: GithubServerPath, user: String, repo: String): String { return if (githubSettings.isCloneGitUsingSsh) { "git@${server.host}:${server.suffix?.substring(1).orEmpty()}/$user/$repo.git" @@ -25,10 +30,15 @@ class GithubGitHelper(private val githubSettings: GithubSettings, } fun getAccessibleRemoteUrls(repository: GitRepository): List { - return repository.remotes.map { it.urls }.flatten() - .filter { url -> authenticationManager.getAccounts().find { it.server.matches(url) } != null } + return repository.remotes.map { it.urls }.flatten().filter(::isRemoteUrlAccessible) } + fun hasAccessibleRemotes(repository: GitRepository): Boolean { + return repository.remotes.map { it.urls }.flatten().any(::isRemoteUrlAccessible) + } + + private fun isRemoteUrlAccessible(url: String) = authenticationManager.getAccounts().find { it.server.matches(url) } != null + companion object { @JvmStatic fun findGitRepository(project: Project, file: VirtualFile?): GitRepository? { diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java index 341583948ce9..c8609c475d7a 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java +++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubUtil.java @@ -282,37 +282,6 @@ public class GithubUtil { }); } - public static T computeValueInModal(@NotNull Project project, - @NotNull String caption, - @NotNull final Convertor task) { - return computeValueInModal(project, caption, true, task); - } - - public static T computeValueInModal(@NotNull Project project, - @NotNull String caption, - boolean canBeCancelled, - @NotNull final Convertor task) { - return ProgressManager.getInstance().run(new Task.WithResult(project, caption, canBeCancelled) { - @Override - protected T compute(@NotNull ProgressIndicator indicator) { - return task.convert(indicator); - } - }); - } - - public static void computeValueInModal(@NotNull Project project, - @NotNull String caption, - boolean canBeCancelled, - @NotNull final Consumer task) { - ProgressManager.getInstance().run(new Task.WithResult(project, caption, canBeCancelled) { - @Override - protected Void compute(@NotNull ProgressIndicator indicator) { - task.consume(indicator); - return null; - } - }); - } - public static T runInterruptable(@NotNull final ProgressIndicator indicator, @NotNull ThrowableComputable task) throws IOException { ScheduledFuture future = null; diff --git a/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTest.java b/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTest.java index f781cda704dc..65ccce06c518 100644 --- a/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTest.java +++ b/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTest.java @@ -16,15 +16,16 @@ package org.jetbrains.plugins.github; import com.intellij.notification.NotificationType; +import org.jetbrains.plugins.github.util.GithubGitHelper; /** * @author Aleksey Pivovarov */ public class GithubCreatePullRequestTest extends GithubCreatePullRequestTestBase { public void testSimple() { - registerDefaultCreatePullRequestDialogHandler("master", myLogin1); - - GithubCreatePullRequestAction.createPullRequest(myProject, projectRoot); + registerDefaultCreatePullRequestDialogHandler("master", myUsername); + myAuthenticationManager.setDefaultAccount(myProject, myAccount); + GithubCreatePullRequestAction.createPullRequest(myProject, myRepository, myAccount); checkNotification(NotificationType.INFORMATION, "Successfully created pull request", null); checkRemoteConfigured(); @@ -32,10 +33,10 @@ public class GithubCreatePullRequestTest extends GithubCreatePullRequestTestBase } public void testParent() { - registerDefaultCreatePullRequestDialogHandler("file2", myLogin2); - addRemote(myLogin2); - - GithubCreatePullRequestAction.createPullRequest(myProject, projectRoot); + registerDefaultCreatePullRequestDialogHandler("file2", myUsername2); + git("remote add somename " + GithubGitHelper.getInstance().getRemoteUrl(myAccount2.getServer(), myUsername2, PROJECT_NAME)); + myAuthenticationManager.setDefaultAccount(myProject, myAccount); + GithubCreatePullRequestAction.createPullRequest(myProject, myRepository, myAccount); checkNotification(NotificationType.INFORMATION, "Successfully created pull request", null); checkRemoteConfigured(); diff --git a/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTestBase.java b/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTestBase.java index ef4a30bb192c..49e1fa3b6180 100644 --- a/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTestBase.java +++ b/plugins/github/test/org/jetbrains/plugins/github/GithubCreatePullRequestTestBase.java @@ -22,14 +22,12 @@ import com.intellij.util.text.DateFormatUtil; import git4idea.actions.GitInit; import git4idea.commands.Git; import git4idea.repo.GitRepository; -import git4idea.test.GitExecutor; import git4idea.test.TestDialogHandler; import org.jetbrains.annotations.NotNull; import org.jetbrains.plugins.github.api.GithubFullPath; import org.jetbrains.plugins.github.test.GithubTest; import org.jetbrains.plugins.github.ui.GithubCreatePullRequestDialog; -import org.jetbrains.plugins.github.util.GithubUrlUtil; -import org.jetbrains.plugins.github.util.GithubUtil; +import org.jetbrains.plugins.github.util.GithubGitHelper; import java.util.Random; @@ -64,7 +62,7 @@ public abstract class GithubCreatePullRequestTestBase extends GithubTest { } protected void deleteRemoteBranch() { - GitRepository repository = GithubUtil.getGitRepository(myProject, projectRoot); + GitRepository repository = GithubGitHelper.findGitRepository(myProject, projectRoot); if (repository != null) { Git.getInstance().push(repository, "origin", PROJECT_URL, ":" + BRANCH_NAME, false); } @@ -94,10 +92,6 @@ public abstract class GithubCreatePullRequestTestBase extends GithubTest { GitInit.refreshAndConfigureVcsMappings(myProject, projectRoot, projectRoot.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); diff --git a/plugins/github/test/org/jetbrains/plugins/github/test/GithubTest.java b/plugins/github/test/org/jetbrains/plugins/github/test/GithubTest.java index 85f844f61591..df3db3b219ce 100644 --- a/plugins/github/test/org/jetbrains/plugins/github/test/GithubTest.java +++ b/plugins/github/test/org/jetbrains/plugins/github/test/GithubTest.java @@ -163,7 +163,7 @@ public abstract class GithubTest extends GitPlatformTest { protected void checkRemoteConfigured() { assertNotNull(myRepository); - assertTrue("GitHub remote is not configured", !GithubGitHelper.getInstance().getAccessibleRemoteUrls(myRepository).isEmpty()); + assertTrue("GitHub remote is not configured", GithubGitHelper.getInstance().hasAccessibleRemotes(myRepository)); } protected void checkLastCommitPushed() { @@ -224,6 +224,7 @@ public abstract class GithubTest extends GitPlatformTest { protected final void tearDown() throws Exception { try { afterTest(); + myAuthenticationManager.setDefaultAccount(myProject, null); myAuthenticationManager.clearAccounts(); } finally {