Merge remote-tracking branch 'origin/master'
[idea/community.git] / plugins / git4idea / src / git4idea / ui / branch / GitBranchPopup.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package git4idea.ui.branch;
17
18 import com.intellij.notification.Notification;
19 import com.intellij.notification.NotificationListener;
20 import com.intellij.notification.NotificationType;
21 import com.intellij.openapi.actionSystem.ActionGroup;
22 import com.intellij.openapi.actionSystem.AnAction;
23 import com.intellij.openapi.actionSystem.AnActionEvent;
24 import com.intellij.openapi.actionSystem.DefaultActionGroup;
25 import com.intellij.openapi.actionSystem.impl.SimpleDataContext;
26 import com.intellij.openapi.diagnostic.Logger;
27 import com.intellij.openapi.options.ShowSettingsUtil;
28 import com.intellij.openapi.project.DumbAwareAction;
29 import com.intellij.openapi.project.Project;
30 import com.intellij.openapi.ui.popup.JBPopupFactory;
31 import com.intellij.openapi.ui.popup.ListPopup;
32 import com.intellij.openapi.util.IconLoader;
33 import git4idea.GitVcs;
34 import git4idea.config.GitVcsSettings;
35 import git4idea.repo.GitRepository;
36 import git4idea.repo.GitRepositoryManager;
37 import git4idea.util.GitUIUtil;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40
41 import javax.swing.event.HyperlinkEvent;
42 import java.util.List;
43
44 /**
45  * <p>
46  *   The popup which allows to quickly switch and control Git branches.
47  * </p>
48  * <p>
49  *   Use {@link #asListPopup()} to achieve the {@link ListPopup} itself.
50  * </p>
51  * 
52  * @author Kirill Likhodedov
53  */
54 class GitBranchPopup  {
55
56   private static final Logger LOG = Logger.getInstance(GitBranchPopup.class);
57   
58   private final Project myProject;
59   private final GitRepository myCurrentRepository;
60   private final ListPopup myPopup;
61   private GitMultiRootBranchConfig myMultiRootBranchConfig;
62
63   ListPopup asListPopup() {
64     return myPopup;
65   }
66
67   /**
68    * @param currentRepository Current repository, which means the repository of the currently open or selected file.
69    *                          In the case of synchronized branch operations current repository matter much less, but sometimes is used,
70    *                          for example, it is preselected in the repositories combobox in the compare branches dialog.
71    */
72   static GitBranchPopup getInstance(@NotNull Project project, @NotNull GitRepository currentRepository) {
73     return new GitBranchPopup(project, currentRepository);
74   }
75
76   private GitBranchPopup(@NotNull Project project, @NotNull GitRepository currentRepository) {
77     myProject = project;
78     myCurrentRepository = currentRepository;
79
80     GitRepositoryManager repositoryManager = GitRepositoryManager.getInstance(project);
81     myMultiRootBranchConfig = new GitMultiRootBranchConfig(repositoryManager.getRepositories());
82     
83     String title = "Git Branches";
84     if (repositoryManager.moreThanOneRoot() && (myMultiRootBranchConfig.diverged() || getSyncSetting() == GitBranchSyncSetting.DONT)) {
85       title += " on [" + GitUIUtil.getShortRepositoryName(currentRepository) + "]";
86     }
87
88     myPopup = JBPopupFactory.getInstance().createActionGroupPopup(
89       title, createActions(),
90       SimpleDataContext.getProjectContext(project),
91       JBPopupFactory.ActionSelectionAid.SPEEDSEARCH, true);
92
93     if (repositoryManager.moreThanOneRoot() && getSyncSetting() == GitBranchSyncSetting.NOT_DECIDED) {
94       if (!myMultiRootBranchConfig.diverged()) {
95         notifyAboutSyncedBranches();
96         GitVcsSettings.getInstance(project).setSyncSetting(GitBranchSyncSetting.SYNC);
97       }
98       else {
99         GitVcsSettings.getInstance(project).setSyncSetting(GitBranchSyncSetting.DONT);
100       }
101     }
102   }
103
104   private void notifyAboutSyncedBranches() {
105     GitVcs.IMPORTANT_ERROR_NOTIFICATION.createNotification("Synchronous branch control enabled",
106       "You have several Git roots in the project and they all are checked out at the same branch. " +
107       "We've enabled synchronous branch control for the project. <br/>" +
108       "If you wish to control branches in different roots separately, you may <a href='settings'>disable</a> the setting.",
109       NotificationType.INFORMATION, new NotificationListener() {
110       @Override public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) {
111         if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
112           ShowSettingsUtil.getInstance().showSettingsDialog(myProject, GitVcs.getInstance(myProject).getConfigurable().getDisplayName());
113           if (getSyncSetting() == GitBranchSyncSetting.DONT) {
114             notification.expire();
115           }
116         }
117       }
118     }).notify(myProject);
119   }
120
121   @NotNull
122   private GitBranchSyncSetting getSyncSetting() {
123     return GitVcsSettings.getInstance(myProject).getSyncSetting();
124   }
125
126   private ActionGroup createActions() {
127     DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
128
129     GitRepositoryManager repositoryManager = GitRepositoryManager.getInstance(myProject);
130     if (repositoryManager.moreThanOneRoot()) {
131
132       if (!myMultiRootBranchConfig.diverged() && userWantsSyncControl()) {
133         fillWithCommonRepositoryActions(popupGroup, repositoryManager);
134       }
135       else {
136         if (myMultiRootBranchConfig.diverged() && userWantsSyncControl()) {
137           warnThatBranchesDiverged(popupGroup);
138         }
139
140         fillPopupWithCurrentRepositoryActions(popupGroup, createRepositoriesActions());
141       }
142     } 
143     else {
144       fillPopupWithCurrentRepositoryActions(popupGroup, null);
145     }
146
147     popupGroup.addSeparator();
148     //popupGroup.addAction(new ConfigureAction());
149     return popupGroup;
150   }
151
152   private boolean userWantsSyncControl() {
153     return (getSyncSetting() != GitBranchSyncSetting.DONT);
154   }
155
156   private void fillWithCommonRepositoryActions(DefaultActionGroup popupGroup, GitRepositoryManager repositoryManager) {
157     List<GitRepository> repositories = repositoryManager.getRepositories();
158     String currentBranch = myMultiRootBranchConfig.getCurrentBranch();
159     assert currentBranch != null : "Current branch can't be null if branches have not diverged";
160     popupGroup.add(new GitBranchPopupActions.CurrentBranchAction(currentBranch, " in all roots"));
161     popupGroup.add(new GitBranchPopupActions.NewBranchAction(myProject, repositories, myCurrentRepository));
162
163     popupGroup.addAll(createRepositoriesActions());
164
165     popupGroup.addSeparator("Common Local Branches");
166     for (String branch : myMultiRootBranchConfig.getLocalBranches()) {
167       if (!branch.equals(currentBranch)) {
168         popupGroup.add(new GitBranchPopupActions.LocalBranchActions(myProject, repositories, branch, myCurrentRepository));
169       }
170     }
171
172     popupGroup.addSeparator("Common Remote Branches");
173     for (String branch : myMultiRootBranchConfig.getRemoteBranches()) {
174       popupGroup.add(new GitBranchPopupActions.RemoteBranchActions(myProject, repositories, branch, myCurrentRepository));
175     }
176   }
177
178   private void warnThatBranchesDiverged(@NotNull DefaultActionGroup popupGroup) {
179     popupGroup.add(new BranchesHaveDivergedMessage(myCurrentRepository));
180     popupGroup.addSeparator();
181   }
182
183   private DefaultActionGroup createRepositoriesActions() {
184     DefaultActionGroup popupGroup = new DefaultActionGroup(null, false);
185     popupGroup.addSeparator("Repositories");
186     for (GitRepository repository : GitRepositoryManager.getInstance(myProject).getRepositories()) {
187       popupGroup.add(new RootAction(repository, highlightCurrentRepo() ? myCurrentRepository : null));
188     }
189     return popupGroup;
190   }
191
192   private boolean highlightCurrentRepo() {
193     return !userWantsSyncControl() || myMultiRootBranchConfig.diverged();
194   }
195
196   private void fillPopupWithCurrentRepositoryActions(@NotNull DefaultActionGroup popupGroup, @Nullable DefaultActionGroup actions) {
197     popupGroup.addAll(new GitBranchPopupActions(myCurrentRepository.getProject(), myCurrentRepository).createActions(actions));
198   }
199
200   private static class RootAction extends ActionGroup {
201
202     private final GitRepository myRepository;
203
204     /**
205      * @param currentRepository Pass null in the case of common repositories - none repository will be highlighted then.
206      */
207     RootAction(@NotNull GitRepository repository, @Nullable GitRepository currentRepository) {
208       super(GitUIUtil.getShortRepositoryName(repository), true);
209       myRepository = repository;
210       if (repository.equals(currentRepository)) {
211         getTemplatePresentation().setIcon(IconLoader.getIcon("/actions/checked.png"));
212       }
213     }
214
215     @NotNull
216     @Override
217     public AnAction[] getChildren(@Nullable AnActionEvent e) {
218       ActionGroup group = new GitBranchPopupActions(myRepository.getProject(), myRepository).createActions(null);
219       return group.getChildren(e);
220     }
221   }
222
223   private static class BranchesHaveDivergedMessage extends DumbAwareAction {
224
225     BranchesHaveDivergedMessage(GitRepository currentRepository) {
226       super("Branches have diverged, showing current root " + GitUIUtil.getShortRepositoryName(currentRepository), "", IconLoader.getIcon("/general/ideFatalError.png"));
227     }
228
229     @Override
230     public void actionPerformed(AnActionEvent e) {
231     }
232
233     @Override public void update(AnActionEvent e) {
234       e.getPresentation().setEnabled(false);         // this action works as a label
235     }
236   }
237
238   /**
239    * "Configure" opens a dialog to configure branches in the repository, i.e. set up tracked branches, fetch/push branches, etc.
240    */
241   private static class ConfigureAction extends DumbAwareAction {
242     public ConfigureAction() {
243       super("Configure", null, IconLoader.getIcon("/general/ideOptions.png")); // TODO description
244     }
245
246     @Override public void actionPerformed(AnActionEvent e) {
247     }
248
249     @Override
250     public void update(AnActionEvent e) {
251       //e.getPresentation().setVisible(false);
252     }
253   }
254
255 }