Fix groovy compilation on buildserver
[idea/community.git] / plugins / git4idea / src / git4idea / push / GitPushDialog.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.push;
17
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.progress.EmptyProgressIndicator;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.ui.DialogWrapper;
23 import com.intellij.openapi.util.Pair;
24 import com.intellij.openapi.vcs.VcsException;
25 import com.intellij.ui.components.JBLoadingPanel;
26 import com.intellij.util.Consumer;
27 import com.intellij.util.ui.UIUtil;
28 import git4idea.GitBranch;
29 import git4idea.GitUtil;
30 import git4idea.branch.GitBranchUtil;
31 import git4idea.history.browser.GitCommit;
32 import git4idea.repo.GitRemote;
33 import git4idea.repo.GitRepository;
34 import git4idea.repo.GitRepositoryManager;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 import javax.swing.*;
39 import java.awt.*;
40 import java.util.*;
41 import java.util.List;
42 import java.util.concurrent.atomic.AtomicReference;
43
44 /**
45  * @author Kirill Likhodedov
46  */
47 public class GitPushDialog extends DialogWrapper {
48
49   private static final Logger LOG = Logger.getInstance(GitPushDialog.class);
50   private static final String DEFAULT_REMOTE = "origin";
51
52   private Project myProject;
53   private final GitRepositoryManager myRepositoryManager;
54   private final GitPusher myPusher;
55   private final GitPushLog myListPanel;
56   private GitCommitsByRepoAndBranch myGitCommitsToPush;
57   private Map<GitRepository, GitPushSpec> myPushSpecs;
58   private final Collection<GitRepository> myRepositories;
59   private final JBLoadingPanel myLoadingPanel;
60   private final Object COMMITS_LOADING_LOCK = new Object();
61   private final GitManualPushToBranch myRefspecPanel;
62   private final AtomicReference<String> myDestBranchInfoOnRefresh = new AtomicReference<String>();
63
64   private final boolean myPushPossible;
65
66   public GitPushDialog(@NotNull Project project) {
67     super(project);
68     myProject = project;
69     myPusher = new GitPusher(myProject, new EmptyProgressIndicator());
70     myRepositoryManager = GitUtil.getRepositoryManager(myProject);
71
72     myRepositories = getRepositoriesWithRemotes();
73
74     myLoadingPanel = new JBLoadingPanel(new BorderLayout(), this.getDisposable());
75
76     myListPanel = new GitPushLog(myProject, myRepositories, new RepositoryCheckboxListener());
77     myRefspecPanel = new GitManualPushToBranch(myRepositories, new RefreshButtonListener());
78
79     if (GitManualPushToBranch.getRemotesWithCommonNames(myRepositories).isEmpty()) {
80       myRefspecPanel.setVisible(false);
81       setErrorText("Can't push, because no remotes are defined");
82       setOKActionEnabled(false);
83       myPushPossible = false;
84     } else {
85       myPushPossible = true;
86     }
87
88     init();
89     setOKButtonText("Push");
90     setOKButtonMnemonic('P');
91     setTitle("Git Push");
92   }
93
94   @NotNull
95   private List<GitRepository> getRepositoriesWithRemotes() {
96     List<GitRepository> repositories = new ArrayList<GitRepository>();
97     for (GitRepository repository : myRepositoryManager.getRepositories()) {
98       if (!repository.getRemotes().isEmpty()) {
99         repositories.add(repository);
100       }
101     }
102     return repositories;
103   }
104
105   @Override
106   protected JComponent createCenterPanel() {
107     JPanel optionsPanel = new JPanel(new BorderLayout());
108     optionsPanel.add(myRefspecPanel);
109
110     JComponent rootPanel = new JPanel(new BorderLayout(0, 15));
111     rootPanel.add(createCommitListPanel(), BorderLayout.CENTER);
112     rootPanel.add(optionsPanel, BorderLayout.SOUTH);
113     return rootPanel;
114   }
115
116   @Override
117   protected String getHelpId() {
118     return "reference.VersionControl.Git.PushDialog";
119   }
120
121   private JComponent createCommitListPanel() {
122     myLoadingPanel.add(myListPanel, BorderLayout.CENTER);
123     if (myPushPossible) {
124       loadCommitsInBackground();
125     } else {
126       myLoadingPanel.startLoading();
127       myLoadingPanel.stopLoading();
128     }
129
130     JPanel commitListPanel = new JPanel(new BorderLayout());
131     commitListPanel.add(myLoadingPanel, BorderLayout.CENTER);
132     return commitListPanel;
133   }
134
135   private void loadCommitsInBackground() {
136     myLoadingPanel.startLoading();
137
138     ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
139       public void run() {
140         final AtomicReference<String> error = new AtomicReference<String>();
141         synchronized (COMMITS_LOADING_LOCK) {
142           error.set(collectInfoToPush());
143         }
144
145         final Pair<String, String> remoteAndBranch = getRemoteAndTrackedBranchForCurrentBranch();
146         UIUtil.invokeLaterIfNeeded(new Runnable() {
147           @Override
148           public void run() {
149             if (error.get() != null) {
150               myListPanel.displayError(error.get());
151             } else {
152               myListPanel.setCommits(myGitCommitsToPush);
153             }
154             if (!myRefspecPanel.turnedOn()) {
155               myRefspecPanel.selectRemote(remoteAndBranch.getFirst());
156               myRefspecPanel.setBranchToPushIfNotSet(remoteAndBranch.getSecond());
157             }
158             myLoadingPanel.stopLoading();
159           }
160         });
161       }
162     });
163   }
164
165   @NotNull
166   private Pair<String, String> getRemoteAndTrackedBranchForCurrentBranch() {
167     if (myGitCommitsToPush != null) {
168       Collection<GitRepository> repositories = myGitCommitsToPush.getRepositories();
169       if (!repositories.isEmpty()) {
170         GitRepository repository = repositories.iterator().next();
171         GitBranch currentBranch = repository.getCurrentBranch();
172         assert currentBranch != null;
173         if (myGitCommitsToPush.get(repository).get(currentBranch).getDestBranch() == GitPusher.NO_TARGET_BRANCH) { // push to branch with the same name
174           return Pair.create(DEFAULT_REMOTE, currentBranch.getName());
175         }
176         String remoteName;
177         try {
178           remoteName = currentBranch.getTrackedRemoteName(myProject, repository.getRoot());
179           if (remoteName == null) {
180             remoteName = DEFAULT_REMOTE;
181           }
182         }
183         catch (VcsException e) {
184           LOG.info("Couldn't retrieve tracked branch for current branch " + currentBranch, e);
185           remoteName = DEFAULT_REMOTE;
186         }
187         String targetBranch = myGitCommitsToPush.get(repository).get(currentBranch).getDestBranch().getShortName();
188         return Pair.create(remoteName, targetBranch);
189       }
190     }
191     return Pair.create(DEFAULT_REMOTE, "");
192   }
193
194   @Nullable
195   private String collectInfoToPush() {
196     try {
197       LOG.info("collectInfoToPush...");
198       myPushSpecs = pushSpecsForCurrentOrEnteredBranches();
199       myGitCommitsToPush = myPusher.collectCommitsToPush(myPushSpecs);
200       LOG.info("collectInfoToPush | Collected commits to push: " + logMessageForCommits(myGitCommitsToPush));
201       return null;
202     }
203     catch (VcsException e) {
204       myGitCommitsToPush = GitCommitsByRepoAndBranch.empty();
205       LOG.error("collectInfoToPush | Couldn't collect commits to push. Push spec: " + myPushSpecs, e);
206       return e.getMessage();
207     }
208   }
209
210   private static String logMessageForCommits(GitCommitsByRepoAndBranch commitsToPush) {
211     StringBuilder logMessage = new StringBuilder();
212     for (GitCommit commit : commitsToPush.getAllCommits()) {
213       logMessage.append(commit.getShortHash());
214     }
215     return logMessage.toString();
216   }
217
218   private Map<GitRepository, GitPushSpec> pushSpecsForCurrentOrEnteredBranches() throws VcsException {
219     Map<GitRepository, GitPushSpec> defaultSpecs = new HashMap<GitRepository, GitPushSpec>();
220     for (GitRepository repository : myRepositories) {
221       GitBranch currentBranch = repository.getCurrentBranch();
222       if (currentBranch == null) {
223         continue;
224       }
225       String remoteName = currentBranch.getTrackedRemoteName(repository.getProject(), repository.getRoot());
226       String trackedBranchName = currentBranch.getTrackedBranchName(repository.getProject(), repository.getRoot());
227       GitRemote remote = GitUtil.findRemoteByName(repository, remoteName);
228       GitBranch tracked = GitBranchUtil.findRemoteBranchByName(repository, remote, trackedBranchName);
229       if (remote == null || tracked == null) {
230         Pair<GitRemote,GitBranch> remoteAndBranch = GitUtil.findMatchingRemoteBranch(repository, currentBranch);
231         if (remoteAndBranch == null) {
232           remote = myRefspecPanel.getSelectedRemote();
233           tracked = GitPusher.NO_TARGET_BRANCH;
234         } else {
235           remote = remoteAndBranch.getFirst();
236           tracked = remoteAndBranch.getSecond();
237         }
238       }
239
240       if (myRefspecPanel.turnedOn()) {
241         String manualBranchName = myRefspecPanel.getBranchToPush();
242         remote = myRefspecPanel.getSelectedRemote();
243         GitBranch manualBranch = GitBranchUtil.findRemoteBranchByName(repository, remote, manualBranchName);
244         if (manualBranch == null) {
245           if (!manualBranchName.startsWith("refs/remotes/")) {
246             manualBranchName = myRefspecPanel.getSelectedRemote().getName() + "/" + manualBranchName;
247           }
248           manualBranch = new GitBranch(manualBranchName, false, true);
249         }
250         tracked = manualBranch;
251       }
252
253       GitPushSpec pushSpec = new GitPushSpec(remote, currentBranch, tracked);
254       defaultSpecs.put(repository, pushSpec);
255     }
256     return defaultSpecs;
257   }
258
259   @Override
260   public JComponent getPreferredFocusedComponent() {
261     return myListPanel.getPreferredFocusComponent();
262   }
263
264   @Override
265   protected String getDimensionServiceKey() {
266     return GitPushDialog.class.getName();
267   }
268
269   @NotNull
270   public GitPushInfo getPushInfo() {
271     // waiting for commit list loading, because this information is needed to correctly handle rejected push situation and correctly
272     // notify about pushed commits
273     // TODO optimize: don't refresh: information about pushed commits can be achieved from the successful push output
274     LOG.info("getPushInfo start");
275     synchronized (COMMITS_LOADING_LOCK) {
276       GitCommitsByRepoAndBranch selectedCommits;
277       if (myGitCommitsToPush == null) {
278         LOG.info("getPushInfo | myGitCommitsToPush == null. collecting...");
279         collectInfoToPush();
280         selectedCommits = myGitCommitsToPush;
281       }
282       else {
283         if (refreshNeeded()) {
284           LOG.info("getPushInfo | refresh is needed, collecting...");
285           collectInfoToPush();
286         }
287         Collection<GitRepository> selectedRepositories = myListPanel.getSelectedRepositories();
288         selectedCommits = myGitCommitsToPush.retainAll(selectedRepositories);
289       }
290       LOG.info("getPushInfo | selectedCommits: " + logMessageForCommits(selectedCommits));
291       return new GitPushInfo(selectedCommits, myPushSpecs);
292     }
293   }
294
295   private boolean refreshNeeded() {
296     String currentDestBranchValue = myRefspecPanel.turnedOn() ? myRefspecPanel.getBranchToPush(): null;
297     String savedValue = myDestBranchInfoOnRefresh.get();
298     if (savedValue == null) {
299       return currentDestBranchValue != null;
300     }
301     return !savedValue.equals(myDestBranchInfoOnRefresh.get());
302   }
303
304   private class RepositoryCheckboxListener implements Consumer<Boolean> {
305     @Override public void consume(Boolean checked) {
306       if (checked) {
307         setOKActionEnabled(true);
308       } else {
309         Collection<GitRepository> repositories = myListPanel.getSelectedRepositories();
310         if (repositories.isEmpty()) {
311           setOKActionEnabled(false);
312         } else {
313           setOKActionEnabled(true);
314         }
315       }
316     }
317   }
318
319   private class RefreshButtonListener implements Runnable {
320     @Override
321     public void run() {
322       myDestBranchInfoOnRefresh.set(myRefspecPanel.turnedOn() ? myRefspecPanel.getBranchToPush(): null);
323       loadCommitsInBackground();
324     }
325   }
326
327 }