| /* |
| * Copyright 2000-2010 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.jetbrains.plugins.github; |
| |
| import com.intellij.openapi.actionSystem.AnActionEvent; |
| import com.intellij.openapi.actionSystem.CommonDataKeys; |
| 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.vfs.VirtualFile; |
| import com.intellij.util.ThrowableConvertor; |
| import git4idea.GitPlatformFacade; |
| import git4idea.GitUtil; |
| import git4idea.actions.BasicAction; |
| import git4idea.commands.*; |
| import git4idea.rebase.GitRebaseProblemDetector; |
| import git4idea.rebase.GitRebaser; |
| import git4idea.repo.GitRepository; |
| import git4idea.repo.GitRepositoryManager; |
| import git4idea.update.GitFetchResult; |
| import git4idea.update.GitFetcher; |
| import git4idea.update.GitUpdateResult; |
| import git4idea.util.GitPreservingProcess; |
| import icons.GithubIcons; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.github.api.GithubApiUtil; |
| import org.jetbrains.plugins.github.api.GithubConnection; |
| import org.jetbrains.plugins.github.api.GithubFullPath; |
| import org.jetbrains.plugins.github.api.GithubRepoDetailed; |
| import org.jetbrains.plugins.github.util.*; |
| |
| import java.io.IOException; |
| import java.util.Collections; |
| |
| import static git4idea.commands.GitLocalChangesWouldBeOverwrittenDetector.Operation.CHECKOUT; |
| import static org.jetbrains.plugins.github.util.GithubUtil.setVisibleEnabled; |
| |
| /** |
| * Created by IntelliJ IDEA. |
| * |
| * @author oleg |
| * @date 12/8/10 |
| */ |
| public class GithubRebaseAction extends DumbAwareAction { |
| private static final Logger LOG = GithubUtil.LOG; |
| private static final String CANNOT_PERFORM_GITHUB_REBASE = "Can't perform github rebase"; |
| |
| public GithubRebaseAction() { |
| 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()) { |
| setVisibleEnabled(e, false, false); |
| return; |
| } |
| |
| final GitRepository gitRepository = GithubUtil.getGitRepository(project, file); |
| if (gitRepository == null) { |
| setVisibleEnabled(e, false, false); |
| return; |
| } |
| |
| if (!GithubUtil.isRepositoryOnGitHub(gitRepository)) { |
| setVisibleEnabled(e, false, false); |
| return; |
| } |
| |
| setVisibleEnabled(e, true, 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; |
| } |
| 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(); |
| } |
| |
| @Nullable |
| static String configureUpstreamRemote(@NotNull Project project, |
| @NotNull GitRepository gitRepository, |
| @NotNull ProgressIndicator indicator) { |
| GithubRepoDetailed repositoryInfo = loadRepositoryInfo(project, gitRepository, indicator); |
| if (repositoryInfo == null) { |
| return null; |
| } |
| |
| if (!repositoryInfo.isFork() || repositoryInfo.getParent() == null) { |
| GithubNotifications.showWarningURL(project, CANNOT_PERFORM_GITHUB_REBASE, "GitHub repository ", "'" + repositoryInfo.getName() + "'", |
| " is not a forked one", repositoryInfo.getHtmlUrl()); |
| return null; |
| } |
| |
| final String parentRepoUrl = GithubUrlUtil.getCloneUrl(repositoryInfo.getParent().getFullPath()); |
| |
| LOG.info("Adding GitHub parent as a remote host"); |
| indicator.setText("Adding GitHub parent as a remote host..."); |
| |
| if (GithubUtil.addGithubRemote(project, gitRepository, "upstream", parentRepoUrl)) { |
| 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; |
| } |
| |
| try { |
| return GithubUtil.runTask(project, GithubAuthDataHolder.createFromSettings(), indicator, |
| new ThrowableConvertor<GithubConnection, GithubRepoDetailed, IOException>() { |
| @NotNull |
| @Override |
| public GithubRepoDetailed convert(@NotNull GithubConnection connection) throws IOException { |
| return GithubApiUtil.getDetailedRepoInfo(connection, userAndRepo.getUser(), userAndRepo.getRepository()); |
| } |
| }); |
| } |
| catch (IOException e) { |
| GithubNotifications.showError(project, "Can't load repository info", e); |
| return null; |
| } |
| } |
| |
| 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 static void rebaseCurrentBranch(@NotNull final Project project, |
| @NotNull final GitRepository gitRepository, |
| @NotNull final ProgressIndicator indicator) { |
| final Git git = ServiceManager.getService(project, Git.class); |
| final GitPlatformFacade facade = ServiceManager.getService(project, GitPlatformFacade.class); |
| GitUtil.workingTreeChangeStarted(project); |
| GitPreservingProcess process = |
| new GitPreservingProcess(project, facade, git, Collections.singletonList(gitRepository), "Rebasing", "upstream/master", indicator, |
| new Runnable() { |
| @Override |
| public void run() { |
| doRebaseCurrentBranch(project, gitRepository.getRoot(), indicator); |
| } |
| } |
| ); |
| process.execute(); |
| GitUtil.workingTreeChangeFinished(project); |
| } |
| |
| private static void doRebaseCurrentBranch(@NotNull final Project project, |
| @NotNull final VirtualFile root, |
| @NotNull final ProgressIndicator indicator) { |
| final GitRepositoryManager repositoryManager = GitUtil.getRepositoryManager(project); |
| |
| final GitRebaser rebaser = new GitRebaser(project, ServiceManager.getService(Git.class), indicator); |
| |
| final GitLineHandler handler = new GitLineHandler(project, root, GitCommand.REBASE); |
| handler.setStdoutSuppressed(false); |
| handler.addParameters("upstream/master"); |
| |
| 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); |
| |
| GitTask pullTask = new GitTask(project, handler, "Rebasing from upstream/master"); |
| pullTask.setProgressIndicator(indicator); |
| pullTask.setProgressAnalyzer(new GitStandardProgressAnalyzer()); |
| pullTask.execute(true, false, new GitTaskResultHandlerAdapter() { |
| @Override |
| protected void onSuccess() { |
| root.refresh(false, true); |
| repositoryManager.updateRepository(root); |
| GithubNotifications.showInfo(project, "Success", "Successfully rebased GitHub fork"); |
| } |
| |
| @Override |
| protected void onFailure() { |
| GitUpdateResult result = rebaser.handleRebaseFailure(handler, root, rebaseConflictDetector, |
| untrackedFilesDetector, localChangesDetector); |
| repositoryManager.updateRepository(root); |
| if (result == GitUpdateResult.NOTHING_TO_UPDATE || |
| result == GitUpdateResult.SUCCESS || |
| result == GitUpdateResult.SUCCESS_WITH_RESOLVED_CONFLICTS) { |
| GithubNotifications.showInfo(project, "Success", "Successfully rebased GitHub fork"); |
| } |
| } |
| }); |
| } |
| } |