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