IDEA-78280 (Grails: 'Evaluate Expression' broken after controller reload)
[idea/community.git] / plugins / git4idea / src / git4idea / checkin / GitCheckinHandlerFactory.java
1 /*
2  * Copyright 2000-2010 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.checkin;
17
18 import com.intellij.openapi.ui.Messages;
19 import com.intellij.openapi.util.text.StringUtil;
20 import com.intellij.openapi.vcs.CheckinProjectPanel;
21 import com.intellij.openapi.vcs.changes.CommitExecutor;
22 import com.intellij.openapi.vcs.checkin.CheckinHandler;
23 import com.intellij.openapi.vcs.checkin.VcsCheckinHandlerFactory;
24 import com.intellij.openapi.vfs.VirtualFile;
25 import com.intellij.util.PairConsumer;
26 import git4idea.GitVcs;
27 import git4idea.i18n.GitBundle;
28 import git4idea.repo.GitRepository;
29 import git4idea.repo.GitRepositoryManager;
30 import org.jetbrains.annotations.NotNull;
31 import org.jetbrains.annotations.Nullable;
32
33 /**
34  * Prohibits commiting with an empty messages.
35  * @author Kirill Likhodedov
36 */
37 public class GitCheckinHandlerFactory extends VcsCheckinHandlerFactory {
38   public GitCheckinHandlerFactory() {
39     super(GitVcs.getKey());
40   }
41
42   @NotNull
43   @Override
44   protected CheckinHandler createVcsHandler(final CheckinProjectPanel panel) {
45     return new MyCheckinHandler(panel);
46   }
47
48   private class MyCheckinHandler extends CheckinHandler {
49     private CheckinProjectPanel myPanel;
50
51     public MyCheckinHandler(CheckinProjectPanel panel) {
52       myPanel = panel;
53     }
54
55     @Override
56     public ReturnResult beforeCheckin(@Nullable CommitExecutor executor, PairConsumer<Object, Object> additionalDataConsumer) {
57       // empty commit message check
58       if (myPanel.getCommitMessage().trim().isEmpty()) {
59         Messages.showMessageDialog(myPanel.getComponent(), GitBundle.message("git.commit.message.empty"),
60                                    GitBundle.message("git.commit.message.empty.title"), Messages.getErrorIcon());
61         return ReturnResult.CANCEL;
62       }
63       
64       if (!commitOrCommitAndPush(executor)) {
65         return ReturnResult.COMMIT;
66       }
67
68       // Warning: commit on a detached HEAD
69       DetachedRoot detachedRoot = getDetachedRoot();
70       if (detachedRoot == null) {
71         return ReturnResult.COMMIT;
72       }
73
74       final String title;
75       final String message;
76       final CharSequence rootPath = StringUtil.last(detachedRoot.myRoot.getPresentableUrl(), 50, true);
77       final String messageCommonStart = "The Git repository <code>" + rootPath + "</code>";
78       if (detachedRoot.myRebase) {
79         title = "Unfinished rebase process";
80         message = messageCommonStart + " <br/> has an <b>unfinished rebase</b> process. <br/>" +
81                   "You probably want to <b>continue rebase</b> instead of committing. <br/>" +
82                   "Committing during rebase may lead to the commit loss. <br/>" +
83                   readMore("http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html", "Read more about Git rebase");
84       } else {
85         title = "Commit in detached HEAD may be dangerous";
86         message = messageCommonStart + " is in the <b>detached HEAD</b> state. <br/>" +
87                   "You can look around, make experimental changes and commit them, but be sure to checkout a branch not to lose your work. <br/>" +
88                   "Otherwise you risk losing your changes. <br/>" +
89                   readMore("http://sitaramc.github.com/concepts/detached-head.html", "Read more about detached HEAD");
90       }
91
92       final int choice = Messages.showOkCancelDialog(myPanel.getComponent(), "<html>" + message + "</html>", title,
93                                              "Cancel", "Commit", Messages.getWarningIcon());
94       if (choice == 1) {
95         return ReturnResult.COMMIT;
96       } else {
97         return ReturnResult.CLOSE_WINDOW;
98       }
99     }
100
101     private boolean commitOrCommitAndPush(@Nullable CommitExecutor executor) {
102       return executor == null || executor instanceof GitCommitAndPushExecutor;
103     }
104
105     private String readMore(String link, String message) {
106       if (Messages.canShowMacSheetPanel()) {
107         return message + ":\n" + link;
108       }
109       else {
110         return String.format("<a href='%s'>%s</a>.", link, message);
111       }
112     }
113
114     /**
115      * Scans the Git roots, selected for commit, for the root which is on a detached HEAD.
116      * Returns null, if all repositories are on the branch.
117      * There might be several detached repositories, - in that case only one is returned.
118      * This is because the situation is very rare, while it requires a lot of additional effort of making a well-formed message.
119      */
120     @Nullable
121     private DetachedRoot getDetachedRoot() {
122       GitRepositoryManager repositoryManager = GitRepositoryManager.getInstance(myPanel.getProject());
123       for (VirtualFile root : myPanel.getRoots()) {
124         GitRepository repository = repositoryManager.getRepositoryForRoot(root);
125         if (repository == null) {
126           continue;
127         }
128         if (!repository.isOnBranch()) {
129           return new DetachedRoot(root, repository.isRebaseInProgress());
130         }
131       }
132       return null;
133     }
134
135     private class DetachedRoot {
136       final VirtualFile myRoot;
137       final boolean myRebase; // rebase in progress, or just detached due to a checkout of a commit.
138
139       public DetachedRoot(@NotNull VirtualFile root, boolean rebase) {
140         myRoot = root;
141         myRebase = rebase;
142       }
143     }
144
145   }
146
147 }