/* * Copyright 2000-2012 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 git4idea.cherrypick; import com.intellij.dvcs.DvcsUtil; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileEditor.FileDocumentManager; 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.ThrowableComputable; import com.intellij.openapi.vcs.changes.Change; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; import com.intellij.vcs.log.*; import git4idea.GitLocalBranch; import git4idea.GitPlatformFacade; import git4idea.GitVcs; import git4idea.commands.Git; import git4idea.config.GitVcsSettings; import git4idea.history.browser.GitHeavyCommit; import git4idea.history.wholeTree.AbstractHash; import git4idea.history.wholeTree.GitCommitDetailsProvider; import git4idea.repo.GitRepository; import icons.Git4ideaIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; /** * @author Kirill Likhodedov */ public class GitCherryPickAction extends DumbAwareAction { private static final String NAME = "Cherry-Pick"; private static final Logger LOG = Logger.getInstance(GitCherryPickAction.class); @NotNull private final GitPlatformFacade myPlatformFacade; @NotNull private final Git myGit; @NotNull private final Set myIdsInProgress; public GitCherryPickAction() { super(NAME, null, Git4ideaIcons.CherryPick); myGit = ServiceManager.getService(Git.class); myPlatformFacade = ServiceManager.getService(GitPlatformFacade.class); myIdsInProgress = ContainerUtil.newHashSet(); } @Override public void actionPerformed(AnActionEvent e) { final Project project = e.getProject(); final List commits = getSelectedCommits(e); if (project == null || commits == null || commits.isEmpty()) { LOG.info(String.format("Cherry-pick action should be disabled. Project: %s, commits: %s", project, commits)); return; } for (VcsFullCommitDetails commit : commits) { myIdsInProgress.add(commit.getId()); } FileDocumentManager.getInstance().saveAllDocuments(); myPlatformFacade.getChangeListManager(project).blockModalNotifications(); new Task.Backgroundable(project, "Cherry-picking", false) { public void run(@NotNull ProgressIndicator indicator) { try { Map> commitsInRoots = sortCommits(groupCommitsByRoots(project, commits)); new GitCherryPicker(project, myGit, myPlatformFacade, isAutoCommit(project)).cherryPick(commitsInRoots); } finally { ApplicationManager.getApplication().invokeLater(new Runnable() { public void run() { myPlatformFacade.getChangeListManager(project).unblockModalNotifications(); for (VcsFullCommitDetails commit : commits) { myIdsInProgress.remove(commit.getId()); } } }); } } }.queue(); } /** * Sort commits so that earliest ones come first: they need to be cherry-picked first. */ @NotNull private static Map> sortCommits(Map> groupedCommits) { for (List gitCommits : groupedCommits.values()) { Collections.reverse(gitCommits); } return groupedCommits; } @NotNull private Map> groupCommitsByRoots(@NotNull Project project, @NotNull List commits) { Map> groupedCommits = ContainerUtil.newHashMap(); for (VcsFullCommitDetails commit : commits) { GitRepository repository = myPlatformFacade.getRepositoryManager(project).getRepositoryForRoot(commit.getRoot()); if (repository == null) { LOG.info("No repository found for commit " + commit); continue; } List commitsInRoot = groupedCommits.get(repository); if (commitsInRoot == null) { commitsInRoot = ContainerUtil.newArrayList(); groupedCommits.put(repository, commitsInRoot); } commitsInRoot.add(commit); } return groupedCommits; } private static boolean isAutoCommit(@NotNull Project project) { return GitVcsSettings.getInstance(project).isAutoCommitOnCherryPick(); } @Override public void update(AnActionEvent e) { super.update(e); VcsLog log = getVcsLog(e); Project project = getEventProject(e); if (project == null || log == null || !DvcsUtil.logHasRootForVcs(log, GitVcs.getKey())) { e.getPresentation().setEnabledAndVisible(false); } else { e.getPresentation().setEnabled(enabled(e)); e.getPresentation().setText(isAutoCommit(project) ? NAME : NAME + "..."); } } private boolean enabled(AnActionEvent e) { final List commits = getSelectedCommits(e); final Project project = e.getProject(); if (commits == null || commits.isEmpty() || project == null) { return false; } for (VcsFullCommitDetails commit : commits) { if (myIdsInProgress.contains(commit.getId())) { return false; } GitRepository repository = myPlatformFacade.getRepositoryManager(project).getRepositoryForRoot(commit.getRoot()); if (repository == null) { return false; } GitLocalBranch currentBranch = repository.getCurrentBranch(); Collection containingBranches = getContainingBranches(e, commit, repository); if (currentBranch != null && containingBranches != null && containingBranches.contains(currentBranch.getName())) { // already is contained in the current branch return false; } } return true; } // TODO remove after removing the old Vcs Log implementation @Nullable private List getSelectedCommits(AnActionEvent e) { final Project project = e.getProject(); if (project == null) { return null; } List commits = e.getData(GitVcs.SELECTED_COMMITS); if (commits != null) { return convertHeavyCommitToFullDetails(commits, project); } final VcsLog log = getVcsLog(e); if (log == null) { return null; } List selectedCommits = log.getSelectedCommits(); List selectedDetails = ContainerUtil.newArrayList(); for (Hash commit : selectedCommits) { VcsFullCommitDetails details = log.getDetailsIfAvailable(commit); if (details == null) { // let the action be unavailable until all details are loaded return null; } GitRepository root = myPlatformFacade.getRepositoryManager(project).getRepositoryForRoot(details.getRoot()); // don't allow to cherry-pick if a non-Git commit was selected // we could cherry-pick just Git commits filtered from the list, but it might provide confusion if (root == null) { return null; } selectedDetails.add(details); } return selectedDetails; } private static List convertHeavyCommitToFullDetails(List commits, final Project project) { return ContainerUtil.map(commits, new Function() { @Override public VcsFullCommitDetails fun(GitHeavyCommit commit) { final VcsLogObjectsFactory factory = ServiceManager.getService(project, VcsLogObjectsFactory.class); List parents = ContainerUtil.map(commit.getParentsHashes(), new Function() { @Override public Hash fun(String hashValue) { return factory.createHash(hashValue); } }); final List changes = commit.getChanges(); return factory.createFullDetails( factory.createHash(commit.getHash().getValue()), parents, commit.getAuthorTime(), commit.getRoot(), commit.getSubject(), commit.getAuthor(), commit.getAuthorEmail(), commit.getDescription(), commit.getCommitter(), commit.getCommitterEmail(), commit.getDate().getTime(), new ThrowableComputable, Exception>() { @Override public Collection compute() throws Exception { return changes; } } ); } }); } private static VcsLog getVcsLog(@NotNull AnActionEvent event) { return event.getData(VcsLogDataKeys.VSC_LOG); } // TODO remove after removing the old Vcs Log implementation @Nullable private static Collection getContainingBranches(AnActionEvent event, VcsFullCommitDetails commit, GitRepository repository) { GitCommitDetailsProvider detailsProvider = event.getData(GitVcs.COMMIT_DETAILS_PROVIDER); if (detailsProvider != null) { return detailsProvider.getContainingBranches(repository.getRoot(), AbstractHash.create(commit.getId().toShortString())); } if (event.getProject() == null) { return null; } VcsLog log = getVcsLog(event); if (log == null) { return null; } return log.getContainingBranches(commit.getId()); } }