From 724f69afb4698d36277a0ffb274cf2e314c1b516 Mon Sep 17 00:00:00 2001 From: Ivan Semenov Date: Fri, 30 Mar 2018 16:32:15 +0300 Subject: [PATCH] [github] Multiple accounts support for rebase fork action --- .../plugins/github/GithubRebaseAction.java | 400 +++++++++--------- 1 file changed, 194 insertions(+), 206 deletions(-) diff --git a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java index 30763d8492a3..6ca2d5219749 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java +++ b/plugins/github/src/org/jetbrains/plugins/github/GithubRebaseAction.java @@ -1,30 +1,14 @@ -/* - * Copyright 2000-2014 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// 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.dvcs.DvcsUtil; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.application.AccessToken; -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.util.Pair; +import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitUtil; import git4idea.actions.BasicAction; @@ -32,6 +16,7 @@ import git4idea.commands.*; import git4idea.config.GitVcsSettings; import git4idea.rebase.GitRebaseProblemDetector; import git4idea.rebase.GitRebaser; +import git4idea.repo.GitRemote; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; import git4idea.update.GitFetchResult; @@ -41,10 +26,15 @@ import git4idea.util.GitPreservingProcess; import icons.GithubIcons; 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.GithubRepoDetailed; -import org.jetbrains.plugins.github.util.*; +import org.jetbrains.plugins.github.authentication.accounts.GithubAccount; +import org.jetbrains.plugins.github.util.GithubGitHelper; +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.Collections; @@ -52,13 +42,7 @@ import java.util.List; import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.CHECKOUT; -/** - * Created by IntelliJ IDEA. - * - * @author oleg - * @date 12/8/10 - */ -public class GithubRebaseAction extends DumbAwareAction { +public class GithubRebaseAction extends LegacySingleAccountActionGroup { private static final Logger LOG = GithubUtil.LOG; private static final String CANNOT_PERFORM_GITHUB_REBASE = "Can't perform GitHub rebase"; @@ -66,209 +50,213 @@ public class GithubRebaseAction extends DumbAwareAction { super("Rebase my GitHub fork", "Rebase your GitHub forked repository relative to the origin", 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(final 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; - } - - rebaseMyGithubFork(project, file); - } - - private static void rebaseMyGithubFork(@NotNull final Project project, @Nullable final VirtualFile file) { - final GitRepository gitRepository = GithubUtil.getGitRepository(project, file); - if (gitRepository == null) { - GithubNotifications.showError(project, CANNOT_PERFORM_GITHUB_REBASE, "Can't find git repository"); - return; - } + public void actionPerformed(@NotNull Project project, + @Nullable VirtualFile file, + @NotNull GitRepository gitRepository, + @NotNull GithubAccount account) { BasicAction.saveAll(); - new Task.Backgroundable(project, "Rebasing GitHub Fork...") { - @Override - public void run(@NotNull ProgressIndicator indicator) { - gitRepository.update(); - String upstreamRemoteUrl = GithubUtil.findUpstreamRemote(gitRepository); - - if (upstreamRemoteUrl == null) { - LOG.info("Configuring upstream remote"); - indicator.setText("Configuring upstream remote..."); - upstreamRemoteUrl = configureUpstreamRemote(project, gitRepository, indicator); - if (upstreamRemoteUrl == null) { - return; - } - } - - if (!GithubUrlUtil.isGithubUrl(upstreamRemoteUrl)) { - GithubNotifications - .showError(project, CANNOT_PERFORM_GITHUB_REBASE, "Configured upstream is not a GitHub repository: " + upstreamRemoteUrl); - return; - } - else { - final GithubFullPath userAndRepo = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(upstreamRemoteUrl); - final String login = GithubSettings.getInstance().getLogin(); - if (userAndRepo != null) { - if (userAndRepo.getUser().equals(login)) { - GithubNotifications.showError(project, CANNOT_PERFORM_GITHUB_REBASE, - "Configured upstream seems to be your own repository: " + upstreamRemoteUrl); - return; - } - } - } - - LOG.info("Fetching upstream"); - indicator.setText("Fetching upstream..."); - if (!fetchParent(project, gitRepository, indicator)) { - return; - } - - LOG.info("Rebasing current branch"); - indicator.setText("Rebasing current branch..."); - rebaseCurrentBranch(project, gitRepository, indicator); - } - }.queue(); + new RebaseTask(project, GithubApiTaskExecutor.getInstance(), Git.getInstance(), gitRepository, account, "upstream/master").queue(); } @Nullable - static String configureUpstreamRemote(@NotNull Project project, - @NotNull GitRepository gitRepository, - @NotNull ProgressIndicator indicator) { - GithubRepoDetailed repositoryInfo = loadRepositoryInfo(project, gitRepository, indicator); - if (repositoryInfo == null) { + @Override + protected Pair getRemote(@NotNull GithubAccount account, @NotNull GitRepository repository) { + for (GitRemote gitRemote : repository.getRemotes()) { + String remoteName = gitRemote.getName(); + if ("upstream".equals(remoteName)) { + for (String remoteUrl : gitRemote.getUrls()) { + if (account.getServer().matches(remoteUrl)) { + return Pair.pair(gitRemote, remoteUrl); + } + } + } + } + return null; + } + + private class RebaseTask extends Task.Backgroundable { + @NotNull private final GithubApiTaskExecutor myApiTaskExecutor; + @NotNull private final Git myGit; + @NotNull private final GitRepository myRepository; + @NotNull private final GithubAccount myAccount; + @NotNull private final String myOnto; + + public RebaseTask(@NotNull Project project, + @NotNull GithubApiTaskExecutor apiTaskExecutor, + @NotNull Git git, + @NotNull GitRepository repository, + @NotNull GithubAccount account, + @NotNull String rebaseOnto) { + super(project, "Rebasing GitHub Fork..."); + myApiTaskExecutor = apiTaskExecutor; + myGit = git; + myRepository = repository; + myAccount = account; + myOnto = rebaseOnto; + } + + @Override + public void run(@NotNull ProgressIndicator indicator) { + myRepository.update(); + Pair remote = getRemote(myAccount, myRepository); + String upstreamRemoteUrl = remote != null ? remote.second : null; + if (upstreamRemoteUrl == null) { + indicator.setText("Configuring upstream remote..."); + LOG.info("Configuring upstream remote"); + if ((upstreamRemoteUrl = configureUpstreamRemote(indicator)) == null) return; + } + + if (isUpstreamWithSameUsername(indicator, upstreamRemoteUrl)) { + GithubNotifications.showError(myProject, CANNOT_PERFORM_GITHUB_REBASE, + "Configured upstream seems to be your own repository: " + upstreamRemoteUrl); + return; + } + + LOG.info("Fetching upstream"); + indicator.setText("Fetching upstream..."); + if (!fetchParent(indicator)) { + return; + } + + LOG.info("Rebasing current branch"); + indicator.setText("Rebasing current branch..."); + rebaseCurrentBranch(indicator); + } + + private boolean isUpstreamWithSameUsername(@NotNull ProgressIndicator indicator, @NotNull String upstreamRemoteUrl) { + try { + GithubFullPath userAndRepo = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(upstreamRemoteUrl); + if (userAndRepo == null) { + GithubNotifications.showError(myProject, CANNOT_PERFORM_GITHUB_REBASE, "Can't validate upstream remote: " + upstreamRemoteUrl); + return true; + } + String username = myApiTaskExecutor.execute(indicator, myAccount, c -> GithubApiUtil.getCurrentUser(c).getLogin()); + return userAndRepo.getUser().equals(username); + } + catch (IOException e) { + GithubNotifications.showError(myProject, CANNOT_PERFORM_GITHUB_REBASE, "Can't get user information"); + return true; + } + } + + @Nullable + private GithubFullPath findGithubRepositoryPath() { + for (GitRemote gitRemote : myRepository.getRemotes()) { + for (String remoteUrl : gitRemote.getUrls()) { + if (myAccount.getServer().matches(remoteUrl)) { + GithubFullPath fullPath = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl); + if (fullPath != null) return fullPath; + } + } + } return null; } - if (!repositoryInfo.isFork() || repositoryInfo.getParent() == null) { - GithubNotifications.showWarningURL(project, CANNOT_PERFORM_GITHUB_REBASE, "GitHub repository ", "'" + repositoryInfo.getName() + "'", - " is not a fork", repositoryInfo.getHtmlUrl()); - return null; - } + @Nullable + private String configureUpstreamRemote(@NotNull ProgressIndicator indicator) { + GithubFullPath fullPath = findGithubRepositoryPath(); + if (fullPath == null) { + GithubNotifications.showError(myProject, CANNOT_PERFORM_GITHUB_REBASE, "Can't find GitHub remote"); + return null; + } - final String parentRepoUrl = GithubUrlUtil.getCloneUrl(repositoryInfo.getParent().getFullPath()); + GithubRepoDetailed repositoryInfo = loadRepositoryInfo(indicator, fullPath); + if (repositoryInfo == null) { + return null; + } - LOG.info("Adding GitHub parent as a remote host"); - indicator.setText("Adding GitHub parent as a remote host..."); + if (!repositoryInfo.isFork() || repositoryInfo.getParent() == null) { + GithubNotifications + .showWarningURL(myProject, CANNOT_PERFORM_GITHUB_REBASE, "GitHub repository ", "'" + repositoryInfo.getName() + "'", + " is not a fork", repositoryInfo.getHtmlUrl()); + return null; + } - if (GithubUtil.addGithubRemote(project, gitRepository, "upstream", parentRepoUrl)) { + String parentRepoUrl = GithubGitHelper.getInstance().getRemoteUrl(myAccount.getServer(), fullPath); + + LOG.info("Adding GitHub parent as a remote host"); + indicator.setText("Adding GitHub parent as a remote host..."); + try { + myGit.addRemote(myRepository, "upstream", parentRepoUrl).getOutputOrThrow(); + } + catch (VcsException e) { + GithubNotifications + .showError(myProject, CANNOT_PERFORM_GITHUB_REBASE, "Could not configure \"upstream\" remote:\n" + e.getMessage()); + return null; + } + myRepository.update(); return parentRepoUrl; } - else { - return null; - } - } - @Nullable - private static GithubRepoDetailed loadRepositoryInfo(@NotNull Project project, - @NotNull GitRepository gitRepository, - @NotNull ProgressIndicator indicator) { - final String remoteUrl = GithubUtil.findGithubRemoteUrl(gitRepository); - if (remoteUrl == null) { - GithubNotifications.showError(project, CANNOT_PERFORM_GITHUB_REBASE, "Can't find GitHub remote"); - return null; - } - final GithubFullPath userAndRepo = GithubUrlUtil.getUserAndRepositoryFromRemoteUrl(remoteUrl); - if (userAndRepo == null) { - GithubNotifications.showError(project, CANNOT_PERFORM_GITHUB_REBASE, "Can't process remote: " + remoteUrl); - return null; + @Nullable + private GithubRepoDetailed loadRepositoryInfo(@NotNull ProgressIndicator indicator, @NotNull GithubFullPath fullPath) { + try { + return myApiTaskExecutor.execute(indicator, myAccount, c -> + GithubApiUtil.getDetailedRepoInfo(c, fullPath.getUser(), fullPath.getRepository())); + } + catch (IOException e) { + GithubNotifications.showError(myProject, "Can't load repository info", e); + return null; + } } - try { - return GithubUtil.runTask(project, GithubAuthDataHolder.createFromSettings(), indicator, connection -> - GithubApiUtil.getDetailedRepoInfo(connection, userAndRepo.getUser(), userAndRepo.getRepository())); + private boolean fetchParent(@NotNull ProgressIndicator indicator) { + GitFetchResult result = new GitFetcher(myProject, indicator, false).fetch(myRepository.getRoot(), "upstream", null); + if (!result.isSuccess()) { + GitFetcher.displayFetchResult(myProject, result, null, result.getErrors()); + return false; + } + return true; } - catch (IOException e) { - GithubNotifications.showError(project, "Can't load repository info", e); - return null; + + private void rebaseCurrentBranch(@NotNull ProgressIndicator indicator) { + try (AccessToken ignore = DvcsUtil.workingTreeChangeStarted(myProject, "Rebase")) { + List rootsToSave = Collections.singletonList(myRepository.getRoot()); + GitPreservingProcess process = new GitPreservingProcess(myProject, myGit, rootsToSave, "Rebasing", myOnto, + GitVcsSettings.UpdateChangesPolicy.STASH, indicator, + () -> doRebaseCurrentBranch(indicator)); + process.execute(); + } } - } - private static boolean fetchParent(@NotNull final Project project, - @NotNull final GitRepository repository, - @NotNull final ProgressIndicator indicator) { - GitFetchResult result = new GitFetcher(project, indicator, false).fetch(repository.getRoot(), "upstream", null); - if (!result.isSuccess()) { - GitFetcher.displayFetchResult(project, result, null, result.getErrors()); - return false; - } - return true; - } + private void doRebaseCurrentBranch(@NotNull ProgressIndicator indicator) { + GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(myProject); + GitRebaser rebaser = new GitRebaser(myProject, myGit, indicator); + VirtualFile root = myRepository.getRoot(); - private static void rebaseCurrentBranch(@NotNull final Project project, - @NotNull final GitRepository gitRepository, - @NotNull final ProgressIndicator indicator) { - final Git git = ServiceManager.getService(project, Git.class); - try (AccessToken ignore = DvcsUtil.workingTreeChangeStarted(project, "Rebase")) { - List rootsToSave = Collections.singletonList(gitRepository.getRoot()); - GitPreservingProcess process = new GitPreservingProcess(project, git, rootsToSave, "Rebasing", "upstream/master", - GitVcsSettings.UpdateChangesPolicy.STASH, indicator, - () -> { - doRebaseCurrentBranch(project, gitRepository.getRoot(), indicator); - }); - process.execute(); - } - } + GitLineHandler handler = new GitLineHandler(myProject, root, GitCommand.REBASE); + handler.setStdoutSuppressed(false); + handler.addParameters(myOnto); - private static void doRebaseCurrentBranch(@NotNull final Project project, - @NotNull final VirtualFile root, - @NotNull final ProgressIndicator indicator) { - final GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); - Git git = ServiceManager.getService(Git.class); - final GitRebaser rebaser = new GitRebaser(project, git, indicator); + final GitRebaseProblemDetector rebaseConflictDetector = new GitRebaseProblemDetector(); + handler.addLineListener(rebaseConflictDetector); - final GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE); - handler.setStdoutSuppressed(false); - handler.addParameters("upstream/master"); + final GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector = + new GitUntrackedFilesOverwrittenByOperationDetector(root); + final GitLocalChangesWouldBeOverwrittenDetector localChangesDetector = new GitLocalChangesWouldBeOverwrittenDetector(root, CHECKOUT); + handler.addLineListener(untrackedFilesDetector); + handler.addLineListener(localChangesDetector); + handler.addLineListener(GitStandardProgressAnalyzer.createListener(indicator)); - final GitRebaseProblemDetector rebaseConflictDetector = new GitRebaseProblemDetector(); - handler.addLineListener(rebaseConflictDetector); - - final GitUntrackedFilesOverwrittenByOperationDetector untrackedFilesDetector = - new GitUntrackedFilesOverwrittenByOperationDetector(root); - final GitLocalChangesWouldBeOverwrittenDetector localChangesDetector = new GitLocalChangesWouldBeOverwrittenDetector(root, CHECKOUT); - handler.addLineListener(untrackedFilesDetector); - handler.addLineListener(localChangesDetector); - handler.addLineListener(GitStandardProgressAnalyzer.createListener(indicator)); - - String oldText = indicator.getText(); - indicator.setText("Rebasing from upstream/master..."); - GitCommandResult rebaseResult = git.runCommand(handler); - indicator.setText(oldText); - repositoryManager.updateRepository(root); - if (rebaseResult.success()) { - root.refresh(false, true); - GithubNotifications.showInfo(project, "Success", "Successfully rebased GitHub fork"); - } - else { - GitUpdateResult result = rebaser.handleRebaseFailure(handler, root, rebaseResult, rebaseConflictDetector, - untrackedFilesDetector, localChangesDetector); - if (result == GitUpdateResult.NOTHING_TO_UPDATE || - result == GitUpdateResult.SUCCESS || - result == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS) { - GithubNotifications.showInfo(project, "Success", "Successfully rebased GitHub fork"); + String oldText = indicator.getText(); + indicator.setText("Rebasing onto " + myOnto + "..."); + GitCommandResult rebaseResult = myGit.runCommand(handler); + indicator.setText(oldText); + repositoryManager.updateRepository(root); + if (rebaseResult.success()) { + root.refresh(false, true); + GithubNotifications.showInfo(myProject, "Success", "Successfully rebased GitHub fork"); + } + else { + GitUpdateResult result = rebaser.handleRebaseFailure(handler, root, rebaseResult, rebaseConflictDetector, + untrackedFilesDetector, localChangesDetector); + if (result == GitUpdateResult.NOTHING_TO_UPDATE || + result == GitUpdateResult.SUCCESS || + result == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS) { + GithubNotifications.showInfo(myProject, "Success", "Successfully rebased GitHub fork"); + } } } }