/*
* 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.ui.branch;
import com.intellij.dvcs.DvcsUtil;
import com.intellij.dvcs.ui.BranchActionGroupPopup;
import com.intellij.dvcs.ui.RootAction;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationListener;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.ListPopup;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vcs.VcsNotifier;
import com.intellij.ui.popup.list.ListPopupImpl;
import com.intellij.util.containers.ContainerUtil;
import git4idea.GitLocalBranch;
import git4idea.GitUtil;
import git4idea.GitVcs;
import git4idea.branch.GitBranchUtil;
import git4idea.config.GitVcsSettings;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import java.util.List;
/**
* The popup which allows to quickly switch and control Git branches.
*
*/
class GitBranchPopup {
private final Project myProject;
private final GitRepositoryManager myRepositoryManager;
private final GitVcsSettings myVcsSettings;
private final GitVcs myVcs;
private final GitMultiRootBranchConfig myMultiRootBranchConfig;
private final GitRepository myCurrentRepository;
private final ListPopupImpl myPopup;
ListPopup asListPopup() {
return myPopup;
}
/**
* @param currentRepository Current repository, which means the repository of the currently open or selected file.
* In the case of synchronized branch operations current repository matter much less, but sometimes is used,
* for example, it is preselected in the repositories combobox in the compare branches dialog.
*/
static GitBranchPopup getInstance(@NotNull Project project, @NotNull GitRepository currentRepository) {
return new GitBranchPopup(project, currentRepository);
}
private GitBranchPopup(@NotNull Project project, @NotNull GitRepository currentRepository) {
myProject = project;
myCurrentRepository = currentRepository;
myRepositoryManager = GitUtil.getRepositoryManager(project);
myVcs = GitVcs.getInstance(project);
myVcsSettings = GitVcsSettings.getInstance(myProject);
myMultiRootBranchConfig = new GitMultiRootBranchConfig(myRepositoryManager.getRepositories());
String title = createPopupTitle(currentRepository);
Condition preselectActionCondition = new Condition() {
@Override
public boolean value(AnAction action) {
if (action instanceof GitBranchPopupActions.LocalBranchActions) {
GitBranchPopupActions.LocalBranchActions branchAction = (GitBranchPopupActions.LocalBranchActions)action;
String branchName = branchAction.getBranchName();
String recentBranch;
List repositories = branchAction.getRepositories();
if (repositories.size() == 1) {
recentBranch = myVcsSettings.getRecentBranchesByRepository().get(repositories.iterator().next().getRoot().getPath());
}
else {
recentBranch = myVcsSettings.getRecentCommonBranch();
}
if (recentBranch != null && recentBranch.equals(branchName)) {
return true;
}
}
return false;
}
};
myPopup = new BranchActionGroupPopup(title, project, preselectActionCondition, createActions());
initBranchSyncPolicyIfNotInitialized();
setCurrentBranchInfo();
warnThatBranchesDivergedIfNeeded();
}
private void initBranchSyncPolicyIfNotInitialized() {
if (myRepositoryManager.moreThanOneRoot() && myVcsSettings.getSyncSetting() == GitBranchSyncSetting.NOT_DECIDED) {
if (!myMultiRootBranchConfig.diverged()) {
notifyAboutSyncedBranches();
myVcsSettings.setSyncSetting(GitBranchSyncSetting.SYNC);
}
else {
myVcsSettings.setSyncSetting(GitBranchSyncSetting.DONT);
}
}
}
@NotNull
private String createPopupTitle(@NotNull GitRepository currentRepository) {
String title = "Git Branches";
if (myRepositoryManager.moreThanOneRoot() &&
(myMultiRootBranchConfig.diverged() || myVcsSettings.getSyncSetting() == GitBranchSyncSetting.DONT)) {
title += " in " + DvcsUtil.getShortRepositoryName(currentRepository);
}
return title;
}
private void setCurrentBranchInfo() {
String currentBranchText = "Current branch";
if (myRepositoryManager.moreThanOneRoot()) {
if (myMultiRootBranchConfig.diverged()) {
currentBranchText += " in " + DvcsUtil.getShortRepositoryName(myCurrentRepository) + ": " +
GitBranchUtil.getDisplayableBranchText(myCurrentRepository);
}
else {
currentBranchText += ": " + myMultiRootBranchConfig.getCurrentBranch();
}
}
else {
currentBranchText += ": " + GitBranchUtil.getDisplayableBranchText(myCurrentRepository);
}
myPopup.setAdText(currentBranchText, SwingConstants.CENTER);
}
private void notifyAboutSyncedBranches() {
String description = "You have several Git roots in the project and they all are checked out at the same branch. " +
"We've enabled synchronous branch control for the project.
" +
"If you wish to control branches in different roots separately, " +
"you may disable the setting.";
NotificationListener listener = new NotificationListener() {
@Override
public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
ShowSettingsUtil.getInstance().showSettingsDialog(myProject, myVcs.getConfigurable().getDisplayName());
if (myVcsSettings.getSyncSetting() == GitBranchSyncSetting.DONT) {
notification.expire();
}
}
}
};
VcsNotifier.getInstance(myProject).notifyImportantInfo("Synchronous branch control enabled", description, listener);
}
private ActionGroup createActions() {
DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
GitRepositoryManager repositoryManager = myRepositoryManager;
if (repositoryManager.moreThanOneRoot()) {
if (userWantsSyncControl()) {
fillWithCommonRepositoryActions(popupGroup, repositoryManager);
}
else {
fillPopupWithCurrentRepositoryActions(popupGroup, createRepositoriesActions());
}
}
else {
fillPopupWithCurrentRepositoryActions(popupGroup, null);
}
popupGroup.addSeparator();
return popupGroup;
}
private boolean userWantsSyncControl() {
return (myVcsSettings.getSyncSetting() != GitBranchSyncSetting.DONT);
}
private void fillWithCommonRepositoryActions(DefaultActionGroup popupGroup, GitRepositoryManager repositoryManager) {
List allRepositories = repositoryManager.getRepositories();
popupGroup.add(new GitBranchPopupActions.GitNewBranchAction(myProject, allRepositories));
popupGroup.addAll(createRepositoriesActions());
popupGroup.addSeparator("Common Local Branches");
for (String branch : myMultiRootBranchConfig.getLocalBranches()) {
List repositories = filterRepositoriesNotOnThisBranch(branch, allRepositories);
if (!repositories.isEmpty()) {
popupGroup.add(new GitBranchPopupActions.LocalBranchActions(myProject, repositories, branch, myCurrentRepository));
}
}
popupGroup.addSeparator("Common Remote Branches");
for (String branch : myMultiRootBranchConfig.getRemoteBranches()) {
popupGroup.add(new GitBranchPopupActions.RemoteBranchActions(myProject, allRepositories, branch, myCurrentRepository));
}
}
@NotNull
private static List filterRepositoriesNotOnThisBranch(@NotNull final String branch,
@NotNull List allRepositories) {
return ContainerUtil.filter(allRepositories, new Condition() {
@Override
public boolean value(GitRepository repository) {
GitLocalBranch currentBranch = repository.getCurrentBranch();
return currentBranch == null || !branch.equals(currentBranch.getName());
}
});
}
private void warnThatBranchesDivergedIfNeeded() {
if (myRepositoryManager.moreThanOneRoot() && myMultiRootBranchConfig.diverged() && userWantsSyncControl()) {
myPopup.setWarning("Branches have diverged");
}
}
private DefaultActionGroup createRepositoriesActions() {
DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
popupGroup.addSeparator("Repositories");
for (GitRepository repository : myRepositoryManager.getRepositories()) {
popupGroup.add(new RootAction(repository, highlightCurrentRepo() ? myCurrentRepository : null,
new GitBranchPopupActions(repository.getProject(), repository).createActions(null),
GitBranchUtil.getDisplayableBranchText(repository)));
}
return popupGroup;
}
private boolean highlightCurrentRepo() {
return !userWantsSyncControl() || myMultiRootBranchConfig.diverged();
}
private void fillPopupWithCurrentRepositoryActions(@NotNull DefaultActionGroup popupGroup, @Nullable DefaultActionGroup actions) {
popupGroup.addAll(new GitBranchPopupActions(myCurrentRepository.getProject(), myCurrentRepository).createActions(actions));
}
}