git4idea: in push active branches dialog push is enabled if no commits are selected...
[fedora-idea.git] / plugins / git4idea / src / git4idea / checkin / GitPushActiveBranchesDialog.java
blobbab3713e72e7a6f0eb18a29f63a7e39afd2dc7e2
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package git4idea.checkin;
18 import com.intellij.openapi.progress.ProgressManager;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.ui.DialogWrapper;
21 import com.intellij.openapi.ui.Messages;
22 import com.intellij.openapi.vcs.VcsException;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import com.intellij.ui.CheckboxTree;
25 import com.intellij.ui.CheckedTreeNode;
26 import com.intellij.ui.ColoredTreeCellRenderer;
27 import com.intellij.ui.SimpleTextAttributes;
28 import com.intellij.util.ui.tree.TreeUtil;
29 import git4idea.GitBranch;
30 import git4idea.GitRevisionNumber;
31 import git4idea.GitVcs;
32 import git4idea.actions.GitShowAllSubmittedFilesAction;
33 import git4idea.commands.*;
34 import git4idea.i18n.GitBundle;
35 import git4idea.ui.GitUIUtil;
37 import javax.swing.*;
38 import javax.swing.event.TreeSelectionEvent;
39 import javax.swing.event.TreeSelectionListener;
40 import javax.swing.tree.DefaultMutableTreeNode;
41 import javax.swing.tree.DefaultTreeModel;
42 import javax.swing.tree.TreePath;
43 import java.awt.event.ActionEvent;
44 import java.awt.event.ActionListener;
45 import java.util.*;
47 /**
48 * The dialog that allows pushing active branches.
50 public class GitPushActiveBranchesDialog extends DialogWrapper {
51 /**
52 * Amount of digits to show in commit prefix
54 private final static int HASH_PREFIX_SIZE = 8;
55 /**
56 * The view commit button
58 private JButton myViewButton;
59 /**
60 * The root panel
62 private JPanel myPanel;
63 /**
64 * Fetch changes from remote repository
66 private JButton myFetchButton;
67 /**
68 * Rebase commits to new roots
70 private JButton myRebaseButton;
71 /**
72 * If selected, the changes are auto-stashed before rebase
74 private JCheckBox myAutoStashCheckBox;
75 /**
76 * The commit tree (sorted by vcs roots)
78 private CheckboxTree myCommitTree;
79 /**
80 * The root node
82 private CheckedTreeNode myTreeRoot;
83 /**
84 * The context project
86 private Project myProject;
87 /**
88 * The vcs roots for the project
90 private List<VirtualFile> myVcsRoots;
92 /**
93 * The constructor
95 * @param project the project
96 * @param vcsRoots the vcs roots
97 * @param roots the loaded information about roots
99 private GitPushActiveBranchesDialog(final Project project, List<VirtualFile> vcsRoots, List<Root> roots) {
100 super(project, true);
101 myProject = project;
102 myVcsRoots = vcsRoots;
103 updateTree(roots, null);
104 TreeUtil.expandAll(myCommitTree);
105 myCommitTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() {
106 public void valueChanged(TreeSelectionEvent e) {
107 TreePath path = myCommitTree.getSelectionModel().getSelectionPath();
108 if (path == null) {
109 myViewButton.setEnabled(false);
110 return;
112 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
113 myViewButton.setEnabled(node != null && myCommitTree.getSelectionCount() == 1 && node.getUserObject() instanceof Commit);
116 myViewButton.addActionListener(new ActionListener() {
117 public void actionPerformed(ActionEvent e) {
118 TreePath path = myCommitTree.getSelectionModel().getSelectionPath();
119 if (path == null) {
120 return;
122 DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
123 if (node == null || !(node.getUserObject() instanceof Commit)) {
124 return;
126 Commit c = (Commit)node.getUserObject();
127 GitShowAllSubmittedFilesAction.showSubmittedFiles(project, c.revision.asString(), c.root.root);
130 myFetchButton.addActionListener(new ActionListener() {
131 public void actionPerformed(ActionEvent e) {
132 doFetch();
135 myRebaseButton.addActionListener(new ActionListener() {
136 public void actionPerformed(ActionEvent e) {
137 doRebase();
140 setTitle(GitBundle.getString("push.active.title"));
141 setOKButtonText(GitBundle.getString("push.active.button"));
142 init();
146 * Perform fetch operation
148 private void doFetch() {
149 Map<VirtualFile, Set<String>> unchecked = new HashMap<VirtualFile, Set<String>>();
150 for (int i = 0; i < myTreeRoot.getChildCount(); i++) {
151 Set<String> uncheckedCommits = new HashSet<String>();
152 CheckedTreeNode node = (CheckedTreeNode)myTreeRoot.getChildAt(i);
153 Root r = (Root)node.getUserObject();
154 for (int j = 0; j < node.getChildCount(); j++) {
155 if (node.getChildAt(j) instanceof CheckedTreeNode) {
156 CheckedTreeNode commitNode = (CheckedTreeNode)node.getChildAt(j);
157 if (!commitNode.isChecked()) {
158 uncheckedCommits.add(((Commit)commitNode.getUserObject()).commitId());
162 if (!uncheckedCommits.isEmpty()) {
163 unchecked.put(r.root, uncheckedCommits);
166 refreshTree(true, unchecked);
170 * The rebase operation is needed if the current branch is behind remote branch or if some commit is not selected.
172 * @return true if rebase is needed for at least one vcs root
174 private boolean isRebaseNeeded() {
175 for (int i = 0; i < myTreeRoot.getChildCount(); i++) {
176 CheckedTreeNode node = (CheckedTreeNode)myTreeRoot.getChildAt(i);
177 Root r = (Root)node.getUserObject();
178 if (r.commits.size() == 0) {
179 continue;
181 boolean seenCheckedNode = false;
182 for (int j = 0; j < node.getChildCount(); j++) {
183 if (node.getChildAt(j) instanceof CheckedTreeNode) {
184 CheckedTreeNode commitNode = (CheckedTreeNode)node.getChildAt(j);
185 if (commitNode.isChecked()) {
186 seenCheckedNode = true;
188 else {
189 if (seenCheckedNode) {
190 return true;
195 if (seenCheckedNode && r.remoteCommits > 0) {
196 return true;
199 return false;
203 * Preform rebase operation
205 private void doRebase() {
206 final Set<VirtualFile> roots = new HashSet<VirtualFile>();
207 final Set<VirtualFile> rootsWithMerges = new HashSet<VirtualFile>();
208 final Map<VirtualFile, List<String>> reorderedCommits = new HashMap<VirtualFile, List<String>>();
209 final Map<VirtualFile, Set<String>> uncheckedCommits = new HashMap<VirtualFile, Set<String>>();
210 for (int i = 0; i < myTreeRoot.getChildCount(); i++) {
211 CheckedTreeNode node = (CheckedTreeNode)myTreeRoot.getChildAt(i);
212 Root r = (Root)node.getUserObject();
213 Set<String> unchecked = new HashSet<String>();
214 uncheckedCommits.put(r.root, unchecked);
215 if (r.commits.size() == 0) {
216 continue;
218 boolean seenCheckedNode = false;
219 boolean reorderNeeded = false;
220 boolean seenMerges = false;
221 for (int j = 0; j < node.getChildCount(); j++) {
222 if (node.getChildAt(j) instanceof CheckedTreeNode) {
223 CheckedTreeNode commitNode = (CheckedTreeNode)node.getChildAt(j);
224 Commit commit = (Commit)commitNode.getUserObject();
225 seenMerges |= commit.isMerge;
226 if (commitNode.isChecked()) {
227 seenCheckedNode = true;
229 else {
230 unchecked.add(commit.commitId());
231 if (seenCheckedNode) {
232 reorderNeeded = true;
237 if (seenMerges) {
238 rootsWithMerges.add(r.root);
240 if (r.remoteCommits > 0 && seenCheckedNode || reorderNeeded) {
241 roots.add(r.root);
243 if (reorderNeeded) {
244 List<String> reordered = new ArrayList<String>();
245 for (int j = 0; j < node.getChildCount(); j++) {
246 if (node.getChildAt(j) instanceof CheckedTreeNode) {
247 CheckedTreeNode commitNode = (CheckedTreeNode)node.getChildAt(j);
248 if (!commitNode.isChecked()) {
249 Commit commit = (Commit)commitNode.getUserObject();
250 reordered.add(commit.revision.asString());
254 for (int j = 0; j < node.getChildCount(); j++) {
255 if (node.getChildAt(j) instanceof CheckedTreeNode) {
256 CheckedTreeNode commitNode = (CheckedTreeNode)node.getChildAt(j);
257 if (commitNode.isChecked()) {
258 Commit commit = (Commit)commitNode.getUserObject();
259 reordered.add(commit.revision.asString());
263 Collections.reverse(reordered);
264 reorderedCommits.put(r.root, reordered);
267 final List<VcsException> exceptions = new ArrayList<VcsException>();
268 final boolean autoStash = myAutoStashCheckBox.isSelected();
269 final ProgressManager progressManager = ProgressManager.getInstance();
270 final GitVcs vcs = GitVcs.getInstance(myProject);
271 progressManager.runProcessWithProgressSynchronously(new Runnable() {
272 public void run() {
273 GitPushRebaseProcess process = new GitPushRebaseProcess(vcs, myProject, exceptions, autoStash, reorderedCommits, rootsWithMerges);
274 process.doUpdate(progressManager.getProgressIndicator(), roots);
276 }, GitBundle.getString("push.active.rebasing"), false, myProject);
277 refreshTree(false, uncheckedCommits);
278 if (!exceptions.isEmpty()) {
279 GitUIUtil.showOperationErrors(myProject, exceptions, "git rebase");
284 * Refresh tree
286 * @param fetchData if true, the current state is fetched from remote
287 * @param unchecked the map from vcs root to commit identifiers that should be unchecked
289 private void refreshTree(final boolean fetchData, Map<VirtualFile, Set<String>> unchecked) {
290 ArrayList<VcsException> exceptions = new ArrayList<VcsException>();
291 List<Root> roots = loadRoots(myProject, myVcsRoots, exceptions, fetchData);
292 if (!exceptions.isEmpty()) {
293 //noinspection ThrowableResultOfMethodCallIgnored
294 GitUIUtil.showOperationErrors(myProject, exceptions, "Refreshing root information");
295 return;
297 updateTree(roots, unchecked);
301 * Update the tree according to the list of loaded roots
303 * @param roots the list of roots to add to the tree
304 * @param uncheckedCommits the map from vcs root to commit identifiers that should be uncheckedCommits
306 private void updateTree(List<Root> roots, Map<VirtualFile, Set<String>> uncheckedCommits) {
307 myTreeRoot.removeAllChildren();
308 for (Root r : roots) {
309 CheckedTreeNode rootNode = new CheckedTreeNode(r);
310 Status status = new Status();
311 status.root = r;
312 rootNode.add(new DefaultMutableTreeNode(status, false));
313 Set<String> unchecked =
314 uncheckedCommits != null && uncheckedCommits.containsKey(r.root) ? uncheckedCommits.get(r.root) : Collections.<String>emptySet();
315 for (Commit c : r.commits) {
316 CheckedTreeNode child = new CheckedTreeNode(c);
317 rootNode.add(child);
318 child.setChecked(r.remote != null && !unchecked.contains(c.commitId()));
320 myTreeRoot.add(rootNode);
322 ((DefaultTreeModel)myCommitTree.getModel()).reload(myTreeRoot);
323 TreeUtil.expandAll(myCommitTree);
324 updateButtons();
328 * Update buttons on the form
330 private void updateButtons() {
331 String error = null;
332 boolean wasCheckedNode = false;
333 boolean reorderMerges = false;
334 for (int i = 0; i < myTreeRoot.getChildCount(); i++) {
335 CheckedTreeNode node = (CheckedTreeNode)myTreeRoot.getChildAt(i);
336 boolean seenCheckedNode = false;
337 boolean reorderNeeded = false;
338 boolean seenMerges = false;
339 boolean seenUnchecked = false;
340 for (int j = 0; j < node.getChildCount(); j++) {
341 if (node.getChildAt(j) instanceof CheckedTreeNode) {
342 CheckedTreeNode commitNode = (CheckedTreeNode)node.getChildAt(j);
343 Commit commit = (Commit)commitNode.getUserObject();
344 seenMerges |= commit.isMerge;
345 if (commitNode.isChecked()) {
346 seenCheckedNode = true;
348 else {
349 seenUnchecked = true;
350 if (seenCheckedNode) {
351 reorderNeeded = true;
356 if (!seenCheckedNode) {
357 continue;
359 Root r = (Root)node.getUserObject();
360 if (seenMerges && seenUnchecked) {
361 error = GitBundle.getString("push.active.error.merges.unchecked");
363 if (seenMerges && reorderNeeded) {
364 reorderMerges = true;
365 error = GitBundle.getString("push.active.error.reorder.merges");
367 if (reorderNeeded) {
368 if (error == null) {
369 error = GitBundle.getString("push.active.error.reorder.needed");
372 if (r.branch == null) {
373 if (error == null) {
374 error = GitBundle.getString("push.active.error.no.branch");
376 break;
378 wasCheckedNode |= r.remoteBranch != null;
379 if (r.remoteCommits != 0 && r.commits.size() != 0) {
380 if (error == null) {
381 error = GitBundle.getString("push.active.error.behind");
383 break;
386 boolean rebaseNeeded = isRebaseNeeded();
387 setOKActionEnabled(wasCheckedNode && error == null && !rebaseNeeded);
388 setErrorText(error);
389 myRebaseButton.setEnabled(rebaseNeeded && !reorderMerges);
393 * {@inheritDoc}
395 @Override
396 protected JComponent createCenterPanel() {
397 return myPanel;
401 * {@inheritDoc}
403 @Override
404 protected String getDimensionServiceKey() {
405 return getClass().getName();
409 * Load VCS roots
411 * @param project the project
412 * @param roots the VCS root list
413 * @param exceptions the list of of exceptions to use
414 * @param fetchData if true, the data for remote is fetched.
415 * @return the loaded information about vcs roots
417 static List<Root> loadRoots(final Project project,
418 final List<VirtualFile> roots,
419 final Collection<VcsException> exceptions,
420 final boolean fetchData) {
421 final ProgressManager manager = ProgressManager.getInstance();
422 final ArrayList<Root> rc = new ArrayList<Root>();
423 manager.runProcessWithProgressSynchronously(new Runnable() {
424 public void run() {
425 for (VirtualFile root : roots) {
426 try {
427 Root r = new Root();
428 rc.add(r);
429 r.root = root;
430 GitBranch b = GitBranch.current(project, root);
431 if (b != null) {
432 r.branch = b.getFullName();
433 r.remote = b.getTrackedRemoteName(project, root);
434 r.remoteBranch = b.getTrackedBranchName(project, root);
435 if (r.remote != null) {
436 if (fetchData && !r.remote.equals(".")) {
437 GitLineHandler fetch = new GitLineHandler(project, root, GitHandler.FETCH);
438 fetch.addParameters(r.remote, "-v");
439 Collection<VcsException> exs = GitHandlerUtil.doSynchronouslyWithExceptions(fetch);
440 exceptions.addAll(exs);
442 GitBranch tracked = b.tracked(project, root);
443 assert tracked != null : "Tracked branch cannot be null here";
444 GitSimpleHandler unmerged = new GitSimpleHandler(project, root, GitHandler.LOG);
445 unmerged.addParameters("--pretty=format:%H", r.branch + ".." + tracked.getFullName());
446 unmerged.setNoSSH(true);
447 unmerged.setStdoutSuppressed(true);
448 StringScanner su = new StringScanner(unmerged.run());
449 while (su.hasMoreData()) {
450 if (su.line().trim().length() != 0) {
451 r.remoteCommits++;
454 GitSimpleHandler toPush = new GitSimpleHandler(project, root, GitHandler.LOG);
455 toPush.addParameters("--pretty=format:%H%x20%ct%x20%at%x20%s%n%P", tracked.getFullName() + ".." + r.branch);
456 toPush.setNoSSH(true);
457 toPush.setStdoutSuppressed(true);
458 StringScanner sp = new StringScanner(toPush.run());
459 while (sp.hasMoreData()) {
460 if (sp.isEol()) {
461 sp.line();
462 continue;
464 Commit c = new Commit();
465 c.root = r;
466 String hash = sp.spaceToken();
467 String time = sp.spaceToken();
468 c.revision = new GitRevisionNumber(hash, new Date(Long.parseLong(time) * 1000L));
469 c.authorTime = sp.spaceToken();
470 c.message = sp.line();
471 c.isMerge = sp.line().indexOf(' ') != -1;
472 r.commits.add(c);
477 catch (VcsException e) {
478 exceptions.add(e);
482 }, GitBundle.getString("push.active.fetching"), false, project);
483 return rc;
487 * Show the dialog
489 * @param project the context project
490 * @param vcsRoots the vcs roots in the project
491 * @param exceptions the collected exceptions
493 public static void showDialog(final Project project, List<VirtualFile> vcsRoots, final Collection<VcsException> exceptions) {
494 final List<Root> roots = loadRoots(project, vcsRoots, exceptions, true);
495 if (!exceptions.isEmpty()) {
496 Messages
497 .showErrorDialog(project, GitBundle.getString("push.active.fetch.failed"), GitBundle.getString("push.active.fetch.failed.title"));
498 return;
500 GitPushActiveBranchesDialog d = new GitPushActiveBranchesDialog(project, vcsRoots, roots);
501 d.show();
502 if (d.isOK()) {
503 final ArrayList<Root> rootsToPush = new ArrayList<Root>();
504 for (int i = 0; i < d.myTreeRoot.getChildCount(); i++) {
505 CheckedTreeNode node = (CheckedTreeNode)d.myTreeRoot.getChildAt(i);
506 Root r = (Root)node.getUserObject();
507 if (r.remote == null || r.commits.size() == 0) {
508 continue;
510 boolean topCommit = true;
511 for (int j = 0; j < node.getChildCount(); j++) {
512 if (node.getChildAt(j) instanceof CheckedTreeNode) {
513 CheckedTreeNode commitNode = (CheckedTreeNode)node.getChildAt(j);
514 if (commitNode.isChecked()) {
515 Commit commit = (Commit)commitNode.getUserObject();
516 if (!topCommit) {
517 r.commitToPush = commit.revision.asString();
519 rootsToPush.add(r);
520 break;
522 topCommit = false;
526 final ProgressManager manager = ProgressManager.getInstance();
527 manager.runProcessWithProgressSynchronously(new Runnable() {
528 public void run() {
529 for (Root r : rootsToPush) {
530 GitLineHandler h = new GitLineHandler(project, r.root, GitHandler.PUSH);
531 String src = r.commitToPush != null ? r.commitToPush : r.branch;
532 h.addParameters("-v", r.remote, src + ":" + r.remoteBranch);
533 GitHandlerUtil.doSynchronouslyWithExceptions(h);
536 }, GitBundle.getString("push.active.pushing"), false, project);
541 * Create UI components for the dialog
543 private void createUIComponents() {
544 myTreeRoot = new CheckedTreeNode("ROOT");
545 myCommitTree = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer() {
546 @Override
547 public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
548 ColoredTreeCellRenderer r = getTextRenderer();
549 if (!(value instanceof DefaultMutableTreeNode)) {
550 // unknown node type
551 renderUnknown(r, value);
552 return;
554 DefaultMutableTreeNode node = (DefaultMutableTreeNode)value;
555 if (!(node.getUserObject() instanceof Node)) {
556 // unknown node type
557 renderUnknown(r, node.getUserObject());
558 return;
560 ((Node)node.getUserObject()).render(r);
564 * Render unknown node
566 * @param r a renderer to use
567 * @param value the unknown value
569 private void renderUnknown(ColoredTreeCellRenderer r, Object value) {
570 r.append("UNSUPPORTED NODE TYPE: " + (value == null ? "null" : value.getClass().getName()), SimpleTextAttributes.ERROR_ATTRIBUTES);
572 }, myTreeRoot) {
573 @Override
574 protected void onNodeStateChanged(CheckedTreeNode node) {
575 updateButtons();
576 super.onNodeStateChanged(node);
583 * The base class for nodes in the tree
585 static abstract class Node {
587 * Render the node text
589 * @param renderer the renderer to use
591 protected abstract void render(ColoredTreeCellRenderer renderer);
595 * The commit descriptor
597 static class Status extends Node {
599 * The root
601 Root root;
604 * {@inheritDoc}
606 @Override
607 protected void render(ColoredTreeCellRenderer renderer) {
608 renderer.append(GitBundle.getString("push.active.status.status"));
609 if (root.branch == null) {
610 renderer.append(GitBundle.message("push.active.status.no.branch"), SimpleTextAttributes.ERROR_ATTRIBUTES);
612 else if (root.remote == null) {
613 renderer.append(GitBundle.message("push.active.status.no.tracked"), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
615 else if (root.remoteCommits != 0 && root.commits.size() == 0) {
616 renderer.append(GitBundle.message("push.active.status.no.commits.behind", root.remoteCommits),
617 SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
619 else if (root.commits.size() == 0) {
620 renderer.append(GitBundle.message("push.active.status.no.commits"), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES);
622 else if (root.remoteCommits != 0) {
623 renderer.append(GitBundle.message("push.active.status.behind", root.remoteCommits), SimpleTextAttributes.ERROR_ATTRIBUTES);
625 else {
626 renderer.append(GitBundle.message("push.active.status.push", root.commits.size()));
632 * The commit descriptor
634 static class Commit extends Node {
636 * The root
638 Root root;
640 * The revision
642 GitRevisionNumber revision;
644 * The message
646 String message;
648 * The author time
650 String authorTime;
652 * If true, the commit is a merge
654 boolean isMerge;
657 * {@inheritDoc}
659 @Override
660 protected void render(ColoredTreeCellRenderer renderer) {
661 renderer.append(revision.asString().substring(0, HASH_PREFIX_SIZE), SimpleTextAttributes.GRAYED_ATTRIBUTES);
662 renderer.append(": ");
663 renderer.append(message);
664 if (isMerge) {
665 renderer.append(GitBundle.getString("push.active.commit.node.merge"), SimpleTextAttributes.GRAYED_ATTRIBUTES);
670 * @return the identifier that is supposed to be stable with respect to rebase
672 String commitId() {
673 return authorTime + ":" + message;
678 * The root node
680 static class Root extends Node {
682 * if true, the update is required
684 int remoteCommits;
686 * the path to vcs root
688 VirtualFile root;
690 * the current branch
692 String branch;
694 * the remote name
696 String remote;
698 * the remote branch name
700 String remoteBranch;
702 * The commit that will be actually pushed
704 String commitToPush;
706 * the commit
708 List<Commit> commits = new ArrayList<Commit>();
711 * {@inheritDoc}
713 @Override
714 protected void render(ColoredTreeCellRenderer renderer) {
715 SimpleTextAttributes rootAttributes;
716 SimpleTextAttributes branchAttributes;
717 if (remote != null && commits.size() != 0 && remoteCommits != 0 || branch == null) {
718 rootAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES.derive(SimpleTextAttributes.STYLE_BOLD, null, null, null);
719 branchAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES;
721 else if (remote == null || commits.size() == 0) {
722 rootAttributes = SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES;
723 branchAttributes = SimpleTextAttributes.GRAYED_ATTRIBUTES;
725 else {
726 branchAttributes = SimpleTextAttributes.REGULAR_ATTRIBUTES;
727 rootAttributes = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES;
729 renderer.append(root.getPresentableUrl(), rootAttributes);
730 if (branch != null) {
731 renderer.append(" [" + branch, branchAttributes);
732 if (remote != null) {
733 renderer.append(" -> " + remote + "#" + remoteBranch, branchAttributes);
735 renderer.append("]", branchAttributes);