[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
This commit is contained in:
Ivan Semenov
2018-04-17 15:14:42 +03:00
parent 9ec2a3c229
commit 1660fb1f6e
9 changed files with 279 additions and 157 deletions
@@ -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<GitRemote, String> 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;
}
@@ -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<ForkInfo> 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<GitRemote, String> remote = GithubUtil.findGithubRemote(gitRepository);
Pair<GitRemote, String> 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<GitRemote, String> findGithubRemote(@NotNull GithubAccount account, @NotNull GitRepository repository) {
Pair<GitRemote, String> 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<String> loadBranches(@NotNull final GithubFullPath fork, @NotNull ProgressIndicator indicator) throws IOException {
List<GithubBranch> branches = GithubUtil.runTask(myProject, myAuthHolder, indicator, connection ->
List<GithubBranch> 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<Void> 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<GitCommit> 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<VcsCommitMetadata> 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:<br/>" + result.getErrorOutputAsHtmlString());
GithubNotifications.showError(GithubCreatePullRequestWorker.this.myProject, CANNOT_CREATE_PULL_REQUEST,
"Push failed:<br/>" + 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<String, ForkInfo> 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<GithubFullPath> getAvailableForks(@NotNull ProgressIndicator indicator) {
try {
List<GithubRepo> forks = GithubUtil.runTask(myProject, myAuthHolder, indicator, connection ->
List<GithubRepo> forks = myTaskExecutor.execute(indicator, myAccount, connection ->
GithubApiUtil.getForks(connection, mySource.getUser(), mySource.getRepository())
);
List<GithubFullPath> 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())) {
@@ -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<GithubAccount> {
val authenticationManager = service<GithubAuthenticationManager>()
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<GitRemote, String>?
}
private class ChooseAccountDialog(project: Project, accounts: List<GithubAccount>) : 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<GithubAccount>(accounts).apply {
cellRenderer = object : ColoredListCellRenderer<GithubAccount>() {
override fun customizeCellRenderer(list: JList<out GithubAccount>,
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)
}
}
@@ -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<GithubProjectDefaultAccountHolder>().account
}
@TestOnly
fun setDefaultAccount(project: Project, account: GithubAccount?) {
project.service<GithubProjectDefaultAccountHolder>().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<GithubProjectDefaultAccountHolder>().account?.let { return it }
val accounts = accountManager.accounts
if (accounts.size == 1) return accounts.first()
return null
}
companion object {
@JvmStatic
fun getInstance(): GithubAuthenticationManager {
@@ -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<String> {
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? {
@@ -282,37 +282,6 @@ public class GithubUtil {
});
}
public static <T> T computeValueInModal(@NotNull Project project,
@NotNull String caption,
@NotNull final Convertor<ProgressIndicator, T> task) {
return computeValueInModal(project, caption, true, task);
}
public static <T> T computeValueInModal(@NotNull Project project,
@NotNull String caption,
boolean canBeCancelled,
@NotNull final Convertor<ProgressIndicator, T> task) {
return ProgressManager.getInstance().run(new Task.WithResult<T, RuntimeException>(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<ProgressIndicator> task) {
ProgressManager.getInstance().run(new Task.WithResult<Void, RuntimeException>(project, caption, canBeCancelled) {
@Override
protected Void compute(@NotNull ProgressIndicator indicator) {
task.consume(indicator);
return null;
}
});
}
public static <T> T runInterruptable(@NotNull final ProgressIndicator indicator,
@NotNull ThrowableComputable<T, IOException> task) throws IOException {
ScheduledFuture<?> future = null;
@@ -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();
@@ -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);
@@ -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 {