git4idea: Added push active branches functionality
[fedora-idea.git] / plugins / git4idea / src / git4idea / checkin / GitPushActiveBranchesDialog.java
blob47714cba7668f6b3edcabffb8f149151dda9b08f
1 package git4idea.checkin;
3 import com.intellij.openapi.progress.ProgressManager;
4 import com.intellij.openapi.project.Project;
5 import com.intellij.openapi.ui.DialogWrapper;
6 import com.intellij.openapi.ui.Messages;
7 import com.intellij.openapi.vcs.VcsException;
8 import com.intellij.openapi.vfs.VirtualFile;
9 import com.intellij.util.ui.tree.TreeUtil;
10 import git4idea.GitBranch;
11 import git4idea.GitRevisionNumber;
12 import git4idea.actions.GitShowAllSubmittedFilesAction;
13 import git4idea.commands.*;
14 import git4idea.i18n.GitBundle;
16 import javax.swing.*;
17 import javax.swing.event.TreeSelectionEvent;
18 import javax.swing.event.TreeSelectionListener;
19 import javax.swing.tree.DefaultMutableTreeNode;
20 import javax.swing.tree.DefaultTreeModel;
21 import javax.swing.tree.TreeNode;
22 import javax.swing.tree.TreePath;
23 import java.awt.event.ActionEvent;
24 import java.awt.event.ActionListener;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Date;
28 import java.util.List;
30 /**
31 * The dialog that allows pushing active branches.
33 public class GitPushActiveBranchesDialog extends DialogWrapper {
34 /**
35 * Amount of digits to show in commit prefix
37 private final static int HASH_PREFIX_SIZE = 8;
38 /**
39 * The view commit button
41 private JButton myViewButton;
42 /**
43 * The root panel
45 private JPanel myPanel;
46 /**
47 * The commit tree control
49 private JTree myCommitTree;
50 /**
51 * The root information structure
53 private List<Root> myRoots;
55 /**
56 * The constructor
58 * @param project the project
59 * @param roots the loaded roots
61 private GitPushActiveBranchesDialog(final Project project, List<Root> roots) {
62 super(project, true);
63 myRoots = roots;
64 myCommitTree.setModel(new DefaultTreeModel(createTree()));
65 TreeUtil.expandAll(myCommitTree);
66 for (Root r : roots) {
67 if (r.branch == null) {
68 setErrorText(GitBundle.getString("push.active.error.no.branch"));
69 setOKActionEnabled(false);
70 break;
72 if (r.remoteCommits != 0) {
73 setErrorText(GitBundle.getString("push.active.error.behind"));
74 setOKActionEnabled(false);
75 break;
78 myCommitTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
79 public void valueChanged(TreeSelectionEvent e) {
80 TreePath path = myCommitTree.getSelectionModel().getSelectionPath();
81 if (path == null) {
82 myViewButton.setEnabled(false);
83 return;
85 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
86 myViewButton.setEnabled(node != null && myCommitTree.getSelectionCount() == 1 && node.getUserObject() instanceof Commit);
88 });
89 myViewButton.addActionListener(new ActionListener() {
90 public void actionPerformed(ActionEvent e) {
91 TreePath path = myCommitTree.getSelectionModel().getSelectionPath();
92 if (path == null) {
93 return;
95 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
96 if (node == null || !(node.getUserObject() instanceof Commit)) {
97 return;
99 Commit c = (Commit)node.getUserObject();
100 GitShowAllSubmittedFilesAction.showSubmittedFiles(project, c.revision.asString(), c.root.root);
103 setTitle(GitBundle.getString("push.active.title"));
104 setOKButtonText(GitBundle.getString("push.active.button"));
105 init();
109 * @return the created tree
111 private TreeNode createTree() {
112 DefaultMutableTreeNode treeRoot = new DefaultMutableTreeNode("ROOT", true);
113 for (Root r : myRoots) {
114 DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode(r, true);
115 Status status = new Status();
116 status.root = r;
117 rootNode.add(new DefaultMutableTreeNode(status, false));
118 for (Commit c : r.commits) {
119 rootNode.add(new DefaultMutableTreeNode(c, false));
121 treeRoot.add(rootNode);
123 return treeRoot;
128 * {@inheritDoc}
130 @Override
131 protected JComponent createCenterPanel() {
132 return myPanel;
136 * {@inheritDoc}
138 @Override
139 protected String getDimensionServiceKey() {
140 return getClass().getName();
144 * Load VCS roots
146 * @param project the project
147 * @param roots the VCS root list
148 * @return the loaded information about vcs roots
150 static List<Root> loadRoots(final Project project, final List<VirtualFile> roots, final Collection<VcsException> exceptions) {
151 final ProgressManager manager = ProgressManager.getInstance();
152 final ArrayList<Root> rc = new ArrayList<Root>();
153 manager.runProcessWithProgressSynchronously(new Runnable() {
154 public void run() {
155 for (VirtualFile root : roots) {
156 try {
157 Root r = new Root();
158 rc.add(r);
159 r.root = root;
160 GitBranch b = GitBranch.current(project, root);
161 if (b != null) {
162 r.branch = b.getFullName();
163 r.remote = b.getTrackedRemoteName(project, root);
164 r.remoteBranch = b.getTrackedBranchName(project, root);
165 if (r.remote != null) {
166 if(!r.remote.equals(".")) {
167 GitLineHandler fetch = new GitLineHandler(project, root, GitHandler.FETCH);
168 fetch.addParameters(r.remote, "-v");
169 Collection<VcsException> exs = GitHandlerUtil.doSynchronouslyWithExceptions(fetch);
170 exceptions.addAll(exs);
172 GitBranch tracked = b.tracked(project, root);
173 assert tracked != null : "Tracked branch cannot be null here";
174 GitSimpleHandler unmerged = new GitSimpleHandler(project, root, GitHandler.LOG);
175 unmerged.addParameters("--pretty=format:%H", r.branch + ".." + tracked.getFullName());
176 unmerged.setNoSSH(true);
177 unmerged.setStdoutSuppressed(true);
178 StringScanner su = new StringScanner(unmerged.run());
179 while (su.hasMoreData()) {
180 if (su.line().trim().length() != 0) {
181 r.remoteCommits++;
184 GitSimpleHandler toPush = new GitSimpleHandler(project, root, GitHandler.LOG);
185 toPush.addParameters("--pretty=format:%H%x20%ct%x20%s", tracked.getFullName() + ".." + r.branch);
186 toPush.setNoSSH(true);
187 toPush.setStdoutSuppressed(true);
188 StringScanner sp = new StringScanner(toPush.run());
189 while (sp.hasMoreData()) {
190 if (sp.isEol()) {
191 sp.line();
192 continue;
194 Commit c = new Commit();
195 c.root = r;
196 String hash = sp.spaceToken();
197 String time = sp.spaceToken();
198 c.revision = new GitRevisionNumber(hash, new Date(Long.parseLong(time) * 1000L));
199 c.message = sp.line();
200 r.commits.add(c);
205 catch (VcsException e) {
206 exceptions.add(e);
210 }, GitBundle.getString("push.active.fetching"), false, project);
211 return rc;
215 * Show the dialog
217 * @param project the context project
218 * @param vcsRoots the vcs roots in the project
219 * @param exceptions the collected exceptions
221 public static void showDialog(final Project project, List<VirtualFile> vcsRoots, final Collection<VcsException> exceptions) {
222 final List<Root> roots = loadRoots(project, vcsRoots, exceptions);
223 if (!exceptions.isEmpty()) {
224 Messages
225 .showErrorDialog(project, GitBundle.getString("push.active.fetch.failed"), GitBundle.getString("push.active.fetch.failed.title"));
226 return;
228 GitPushActiveBranchesDialog d = new GitPushActiveBranchesDialog(project, roots);
229 d.show();
230 if (d.isOK()) {
231 final ProgressManager manager = ProgressManager.getInstance();
232 manager.runProcessWithProgressSynchronously(new Runnable() {
233 public void run() {
234 for (Root r : roots) {
235 if (r.remote != null && r.commits.size() != 0) {
236 GitLineHandler h = new GitLineHandler(project, r.root, GitHandler.PUSH);
237 h.addParameters("-v", r.remote, r.branch+":"+r.remoteBranch);
238 GitHandlerUtil.doSynchronouslyWithExceptions(h);
242 }, GitBundle.getString("push.active.pushing"), false, project);
248 * The commit descriptor
250 static class Status {
252 * The root
254 Root root;
257 * {@inheritDoc}
259 @Override
260 public String toString() {
261 if (root.commits.size() == 0) {
262 return GitBundle.message("push.active.status.no.commits");
264 if (root.remoteCommits != 0) {
265 return GitBundle.message("push.active.status.behind", root.remoteCommits);
267 if (root.branch == null) {
268 return GitBundle.message("push.active.status.no.branch");
270 if (root.remote == null) {
271 return GitBundle.message("push.active.status.no.tracked");
273 return GitBundle.message("push.active.status.push", root.commits.size());
278 * The commit descriptor
280 static class Commit {
282 * The root
284 Root root;
286 * The revision
288 GitRevisionNumber revision;
290 * The message
292 String message;
295 * {@inheritDoc}
297 @Override
298 public String toString() {
299 return GitBundle.message("push.active.commit.node", revision.asString().substring(0, HASH_PREFIX_SIZE), message);
304 * The root node
306 static class Root {
308 * if true, the update is required
310 int remoteCommits;
312 * the path to vcs root
314 VirtualFile root;
316 * the current branch
318 String branch;
320 * the remote name
322 String remote;
324 * the remote branch name
326 String remoteBranch;
328 * the commit
330 List<Commit> commits = new ArrayList<Commit>();
333 * {@inheritDoc}
335 @Override
336 public String toString() {
337 if (branch == null) {
338 return GitBundle.message("push.active.root.node.no.branch", root.getPresentableUrl());
340 if (remote == null) {
341 return GitBundle.message("push.active.root.node.no.tracked", root.getPresentableUrl(), branch);
343 if (commits.size() == 0) {
344 return GitBundle.message("push.active.root.node.no.commits", root.getPresentableUrl(), branch, remote, remoteBranch);
346 if (remoteCommits != 0) {
347 return GitBundle.message("push.active.root.node.behind", root.getPresentableUrl(), branch, remote, remoteBranch);
349 return GitBundle.message("push.active.root.node.push", root.getPresentableUrl(), branch, remote, remoteBranch);