Java tutorial
/* * Copyright 2000-2009 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.community.intellij.plugins.communitycase.checkin; import com.intellij.notification.Notification; import com.intellij.notification.NotificationType; import com.intellij.notification.Notifications; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.ui.CheckboxTree; import com.intellij.ui.CheckedTreeNode; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.Function; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import org.community.intellij.plugins.communitycase.Branch; import org.community.intellij.plugins.communitycase.Util; import org.community.intellij.plugins.communitycase.Vcs; import org.community.intellij.plugins.communitycase.actions.RepositoryAction; import org.community.intellij.plugins.communitycase.actions.ShowAllSubmittedFilesAction; import org.community.intellij.plugins.communitycase.commands.*; import org.community.intellij.plugins.communitycase.config.VcsSettings; import org.community.intellij.plugins.communitycase.history.HistoryUtils; import org.community.intellij.plugins.communitycase.i18n.Bundle; import org.community.intellij.plugins.communitycase.ui.UiUtil; import org.community.intellij.plugins.communitycase.update.UpdatePolicyUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreePath; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.*; import java.util.List; import java.util.concurrent.atomic.AtomicReference; /** * The dialog that allows pushing active branches. */ public class PushActiveBranchesDialog extends DialogWrapper { private static final int HASH_PREFIX_SIZE = 8; // Amount of digits to show in commit prefix private final Project myProject; private final List<VirtualFile> myVcsRoots; private JPanel myRootPanel; private JButton myViewButton; // view commits private JButton myFetchButton; private JButton myRebaseButton; private JButton myPushButton; private CheckboxTree myCommitTree; // The commit tree (sorted by vcs roots) private CheckedTreeNode myTreeRoot; private JRadioButton myStashRadioButton; // Save files policy option private JRadioButton myShelveRadioButton; private Vcs myVcs; /** * A modification of Runnable with the roots-parameter. * Also for user code simplification myInvokeInAwt variable stores the need of calling run in AWT thread. */ private static abstract class PushActiveBranchRunnable { abstract void run(List<Root> roots); } /** * Constructs new dialog. Loads settings, registers listeners. * @param project the project * @param vcsRoots the vcs roots * @param roots the loaded information about roots */ private PushActiveBranchesDialog(final Project project, List<VirtualFile> vcsRoots, List<Root> roots) { super(project, true); myVcs = Vcs.getInstance(project); myProject = project; myVcsRoots = vcsRoots; updateTree(roots, null); updateUI(); myCommitTree.getSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { public void valueChanged(TreeSelectionEvent e) { TreePath path = myCommitTree.getSelectionModel().getSelectionPath(); if (path == null) { myViewButton.setEnabled(false); return; } DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); myViewButton.setEnabled(node != null && myCommitTree.getSelectionCount() == 1 && node.getUserObject() instanceof Commit); } }); myViewButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { TreePath path = myCommitTree.getSelectionModel().getSelectionPath(); if (path == null) { return; } DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); if (node == null || !(node.getUserObject() instanceof Commit)) { return; } Commit c = (Commit) node.getUserObject(); ShowAllSubmittedFilesAction.showSubmittedFiles(project, c.revision.asString(), c.root.root); } }); myFetchButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { fetch(); } }); myRebaseButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { rebase(); } }); myPushButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { push(); } }); setTitle(Bundle.getString("push.active.title")); setOKButtonText(Bundle.getString("push.active.rebase.and.push")); init(); } /** * Show dialog for the project */ public static void showDialogForProject(final Project project) { Vcs vcs = Vcs.getInstance(project); List<VirtualFile> roots = RepositoryAction.getRoots(project, vcs); if (roots == null) { return; } List<VcsException> pushExceptions = new ArrayList<VcsException>(); showDialog(project, roots, pushExceptions); vcs.showErrors(pushExceptions, Bundle.getString("push.active.action.name")); } /** * Show the dialog * @param project the context project * @param vcsRoots the vcs roots in the project * @param exceptions the collected exceptions */ public static void showDialog(final Project project, final List<VirtualFile> vcsRoots, final Collection<VcsException> exceptions) { final List<Root> emptyRoots = loadRoots(project, vcsRoots, exceptions, false); // collect roots without fetching - just to show dialog if (!exceptions.isEmpty()) { exceptions.addAll(exceptions); return; } final PushActiveBranchesDialog d = new PushActiveBranchesDialog(project, vcsRoots, emptyRoots); d.refreshTree(true, null); // start initial fetch d.show(); if (d.isOK()) { d.rebaseAndPush(); } } /** * This is called when "Rebase and Push" button (default button) is pressed. * 1. Closes the dialog. * 2. Fetches project and rebases. * 3. Repeats step 2 if needed - while current repository is behind the parent one. * 4. Then pushes. * It may fail on one of these steps (especially on rebasing with conflict) - then a notification error will be shown and the process * will be interrupted. */ private void rebaseAndPush() { final Task.Backgroundable rebaseAndPushTask = new Task.Backgroundable(myProject, Bundle.getString("push.active.fetching")) { public void run(@NotNull ProgressIndicator indicator) { List<VcsException> exceptions = new ArrayList<VcsException>(1); do { final RebaseInfo rebaseInfo = collectRebaseInfo(); final List<Root> roots = loadRoots(myProject, myVcsRoots, exceptions, true); // fetch if (!exceptions.isEmpty()) { notifyExceptionWhenClosed("Failed to fetch.", exceptions); return; } updateTree(roots, rebaseInfo.uncheckedCommits); executeRebase(exceptions, rebaseInfo); if (!exceptions.isEmpty()) { notifyExceptionWhenClosed("Failed to rebase.", exceptions); return; } Util.refreshFiles(myProject, rebaseInfo.roots); } while (isRebaseNeeded()); final Collection<Root> rootsToPush = getRootsToPush(); // collect roots from the dialog exceptions = executePushCommand(rootsToPush); if (!exceptions.isEmpty()) { notifyExceptionWhenClosed("Failed to push", exceptions); return; } } }; myVcs.runInBackground(rebaseAndPushTask); } /** * Notifies about error, when 'rebase and push' task is executed, i.e. when the dialog is closed. */ private void notifyExceptionWhenClosed(String title, Collection<VcsException> exceptions) { final String content = StringUtil.join(exceptions, new Function<VcsException, String>() { @Override public String fun(VcsException e) { return e.getLocalizedMessage(); } }, "<br/>"); Notifications.Bus.notify( new Notification(Vcs.NOTIFICATION_GROUP_ID, title, content, NotificationType.ERROR), myProject); } /** * Pushes selected commits synchronously in foreground. */ private void push() { final Collection<Root> rootsToPush = getRootsToPush(); final AtomicReference<Collection<VcsException>> errors = new AtomicReference<Collection<VcsException>>(); ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { public void run() { errors.set(executePushCommand(rootsToPush)); } }, Bundle.getString("push.active.pushing"), true, myProject); if (errors.get() != null && !errors.get().isEmpty()) { UiUtil.showOperationErrors(myProject, errors.get(), Bundle.getString("push.active.pushing")); } refreshTree(false, null); } /** * Executes 'git push' for the given roots to push. * Returns the list of errors if there were any. */ private List<VcsException> executePushCommand(final Collection<Root> rootsToPush) { final ArrayList<VcsException> errors = new ArrayList<VcsException>(); for (Root r : rootsToPush) { LineHandler h = new LineHandler(myProject, r.root, Command.PUSH); String src = r.commitToPush != null ? r.commitToPush : r.branch; h.addParameters("-v", r.remote, src + ":" + r.remoteBranch); PushUtils.trackPushRejectedAsError(h, "Rejected push (" + r.root.getPresentableUrl() + "): "); errors.addAll(HandlerUtil.doSynchronouslyWithExceptions(h)); } return errors; } /** * From the dialog collects roots and commits to be pushed. * @return roots to be pushed. */ private Collection<Root> getRootsToPush() { final ArrayList<Root> rootsToPush = new ArrayList<Root>(); for (int i = 0; i < myTreeRoot.getChildCount(); i++) { CheckedTreeNode node = (CheckedTreeNode) myTreeRoot.getChildAt(i); Root r = (Root) node.getUserObject(); if (r.remote == null || r.commits.size() == 0) { continue; } boolean topCommit = true; for (int j = 0; j < node.getChildCount(); j++) { if (node.getChildAt(j) instanceof CheckedTreeNode) { CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j); if (commitNode.isChecked()) { Commit commit = (Commit) commitNode.getUserObject(); if (!topCommit) { r.commitToPush = commit.revision.asString(); } rootsToPush.add(r); break; } topCommit = false; } } } return rootsToPush; } /** * Executes when FETCH button is pressed. * Fetches repository in background. Then updates the commit tree. */ private void fetch() { Map<VirtualFile, Set<String>> unchecked = new HashMap<VirtualFile, Set<String>>(); for (int i = 0; i < myTreeRoot.getChildCount(); i++) { Set<String> uncheckedCommits = new HashSet<String>(); CheckedTreeNode node = (CheckedTreeNode) myTreeRoot.getChildAt(i); Root r = (Root) node.getUserObject(); for (int j = 0; j < node.getChildCount(); j++) { if (node.getChildAt(j) instanceof CheckedTreeNode) { CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j); if (!commitNode.isChecked()) { uncheckedCommits.add(((Commit) commitNode.getUserObject()).commitId()); } } } if (!uncheckedCommits.isEmpty()) { unchecked.put(r.root, uncheckedCommits); } } refreshTree(true, unchecked); } /** * The rebase operation is needed if the current branch is behind remote branch or if some commit is not selected. * @return true if rebase is needed for at least one vcs root */ private boolean isRebaseNeeded() { for (int i = 0; i < myTreeRoot.getChildCount(); i++) { CheckedTreeNode node = (CheckedTreeNode) myTreeRoot.getChildAt(i); Root r = (Root) node.getUserObject(); if (r.commits.size() == 0) { continue; } boolean seenCheckedNode = false; for (int j = 0; j < node.getChildCount(); j++) { if (node.getChildAt(j) instanceof CheckedTreeNode) { CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j); if (commitNode.isChecked()) { seenCheckedNode = true; } else { if (seenCheckedNode) { return true; } } } } if (seenCheckedNode && r.remoteCommits > 0) { return true; } } return false; } /** * This is called when rebase is pressed: executes rebase in background. */ private void rebase() { final List<VcsException> exceptions = new ArrayList<VcsException>(); final RebaseInfo rebaseInfo = collectRebaseInfo(); ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { public void run() { executeRebase(exceptions, rebaseInfo); } }, Bundle.getString("push.active.rebasing"), true, myProject); if (!exceptions.isEmpty()) { UiUtil.showOperationErrors(myProject, exceptions, "git rebase"); } refreshTree(false, rebaseInfo.uncheckedCommits); Util.refreshFiles(myProject, rebaseInfo.roots); } private void executeRebase(final List<VcsException> exceptions, final RebaseInfo rebaseInfo) { PushRebaseProcess process = new PushRebaseProcess(Vcs.getInstance(myProject), myProject, exceptions, rebaseInfo.policy, rebaseInfo.reorderedCommits, rebaseInfo.rootsWithMerges); process.doUpdate(ProgressManager.getInstance().getProgressIndicator(), rebaseInfo.roots); } private static class RebaseInfo { final Set<VirtualFile> rootsWithMerges; private final Map<VirtualFile, Set<String>> uncheckedCommits; private final Set<VirtualFile> roots; private final VcsSettings.UpdateChangesPolicy policy; final Map<VirtualFile, List<String>> reorderedCommits; public RebaseInfo(Map<VirtualFile, List<String>> reorderedCommits, Set<VirtualFile> rootsWithMerges, Map<VirtualFile, Set<String>> uncheckedCommits, Set<VirtualFile> roots, VcsSettings.UpdateChangesPolicy policy) { this.reorderedCommits = reorderedCommits; this.rootsWithMerges = rootsWithMerges; this.uncheckedCommits = uncheckedCommits; this.roots = roots; this.policy = policy; } } private RebaseInfo collectRebaseInfo() { final Set<VirtualFile> roots = new HashSet<VirtualFile>(); final Set<VirtualFile> rootsWithMerges = new HashSet<VirtualFile>(); final Map<VirtualFile, List<String>> reorderedCommits = new HashMap<VirtualFile, List<String>>(); final Map<VirtualFile, Set<String>> uncheckedCommits = new HashMap<VirtualFile, Set<String>>(); for (int i = 0; i < myTreeRoot.getChildCount(); i++) { CheckedTreeNode node = (CheckedTreeNode) myTreeRoot.getChildAt(i); Root r = (Root) node.getUserObject(); Set<String> unchecked = new HashSet<String>(); uncheckedCommits.put(r.root, unchecked); if (r.commits.size() == 0) { if (r.remoteCommits > 0) { roots.add(r.root); } continue; } boolean seenCheckedNode = false; boolean reorderNeeded = false; boolean seenMerges = false; for (int j = 0; j < node.getChildCount(); j++) { if (node.getChildAt(j) instanceof CheckedTreeNode) { CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j); Commit commit = (Commit) commitNode.getUserObject(); seenMerges |= commit.isMerge; if (commitNode.isChecked()) { seenCheckedNode = true; } else { unchecked.add(commit.commitId()); if (seenCheckedNode) { reorderNeeded = true; } } } } if (seenMerges) { rootsWithMerges.add(r.root); } if (r.remoteCommits > 0 || reorderNeeded) { roots.add(r.root); } if (reorderNeeded) { List<String> reordered = new ArrayList<String>(); for (int j = 0; j < node.getChildCount(); j++) { if (node.getChildAt(j) instanceof CheckedTreeNode) { CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j); if (!commitNode.isChecked()) { Commit commit = (Commit) commitNode.getUserObject(); reordered.add(commit.revision.asString()); } } } for (int j = 0; j < node.getChildCount(); j++) { if (node.getChildAt(j) instanceof CheckedTreeNode) { CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j); if (commitNode.isChecked()) { Commit commit = (Commit) commitNode.getUserObject(); reordered.add(commit.revision.asString()); } } } Collections.reverse(reordered); reorderedCommits.put(r.root, reordered); } } final VcsSettings.UpdateChangesPolicy p = UpdatePolicyUtils.getUpdatePolicy(myStashRadioButton, myShelveRadioButton, null); assert p == VcsSettings.UpdateChangesPolicy.STASH || p == VcsSettings.UpdateChangesPolicy.SHELVE; return new RebaseInfo(reorderedCommits, rootsWithMerges, uncheckedCommits, roots, p); } /** * Refresh tree * * @param fetchData if true, the current state is fetched from remote * @param unchecked the map from vcs root to commit identifiers that should be unchecked */ private void refreshTree(final boolean fetchData, final Map<VirtualFile, Set<String>> unchecked) { myCommitTree.setPaintBusy(true); loadRootsInBackground(fetchData, new PushActiveBranchRunnable() { @Override void run(List<Root> roots) { updateTree(roots, unchecked); updateUI(); myCommitTree.setPaintBusy(false); } }); } /** * Update the tree according to the list of loaded roots * * * @param roots the list of roots to add to the tree * @param uncheckedCommits the map from vcs root to commit identifiers that should be uncheckedCommits */ private void updateTree(List<Root> roots, Map<VirtualFile, Set<String>> uncheckedCommits) { myTreeRoot.removeAllChildren(); if (roots == null) { roots = Collections.emptyList(); } for (Root r : roots) { CheckedTreeNode rootNode = new CheckedTreeNode(r); Status status = new Status(); status.root = r; rootNode.add(new DefaultMutableTreeNode(status, false)); Set<String> unchecked = uncheckedCommits != null && uncheckedCommits.containsKey(r.root) ? uncheckedCommits.get(r.root) : Collections.<String>emptySet(); for (Commit c : r.commits) { CheckedTreeNode child = new CheckedTreeNode(c); rootNode.add(child); child.setChecked(r.remote != null && !unchecked.contains(c.commitId())); } myTreeRoot.add(rootNode); } } // Execute from AWT thread. private void updateUI() { ((DefaultTreeModel) myCommitTree.getModel()).reload(myTreeRoot); TreeUtil.expandAll(myCommitTree); updateButtons(); } /** * Update buttons on the form */ private void updateButtons() { String error = null; boolean wasCheckedNode = false; boolean reorderMerges = false; for (int i = 0; i < myTreeRoot.getChildCount(); i++) { CheckedTreeNode node = (CheckedTreeNode) myTreeRoot.getChildAt(i); boolean seenCheckedNode = false; boolean reorderNeeded = false; boolean seenMerges = false; boolean seenUnchecked = false; for (int j = 0; j < node.getChildCount(); j++) { if (node.getChildAt(j) instanceof CheckedTreeNode) { CheckedTreeNode commitNode = (CheckedTreeNode) node.getChildAt(j); Commit commit = (Commit) commitNode.getUserObject(); seenMerges |= commit.isMerge; if (commitNode.isChecked()) { seenCheckedNode = true; } else { seenUnchecked = true; if (seenCheckedNode) { reorderNeeded = true; } } } } if (!seenCheckedNode) { continue; } Root r = (Root) node.getUserObject(); if (seenMerges && seenUnchecked) { error = Bundle.getString("push.active.error.merges.unchecked"); } if (seenMerges && reorderNeeded) { reorderMerges = true; error = Bundle.getString("push.active.error.reorder.merges"); } if (reorderNeeded) { if (error == null) { error = Bundle.getString("push.active.error.reorder.needed"); } } if (r.branch == null) { if (error == null) { error = Bundle.getString("push.active.error.no.branch"); } break; } wasCheckedNode |= r.remoteBranch != null; if (r.remoteCommits != 0 && r.commits.size() != 0) { if (error == null) { error = Bundle.getString("push.active.error.behind"); } break; } } boolean rebaseNeeded = isRebaseNeeded(); myPushButton.setEnabled(wasCheckedNode && error == null && !rebaseNeeded); setErrorText(error); myRebaseButton.setEnabled(rebaseNeeded && !reorderMerges); } /** * {@inheritDoc} */ @Override protected JComponent createCenterPanel() { return myRootPanel; } /** * {@inheritDoc} */ @Override protected String getDimensionServiceKey() { return getClass().getName(); } /** * {@inheritDoc} */ @Override protected String getHelpId() { return "reference.VersionControl.Git.PushActiveBranches"; } /** * Load VCS roots * * @param project the project * @param roots the VCS root list * @param exceptions the list of of exceptions to use * @param fetchData if true, the data for remote is fetched. * @return the loaded information about vcs roots */ private static List<Root> loadRoots(final Project project, final List<VirtualFile> roots, final Collection<VcsException> exceptions, final boolean fetchData) { final ArrayList<Root> rc = new ArrayList<Root>(); for (VirtualFile root : roots) { try { Root r = new Root(); rc.add(r); r.root = root; Branch b = Branch.current(project, root); if (b != null) { r.branch = b.getFullName(); r.remote = b.getTrackedRemoteName(project, root); r.remoteBranch = b.getTrackedBranchName(project, root); if (r.remote != null) { if (fetchData && !r.remote.equals(".")) { LineHandler fetch = new LineHandler(project, root, Command.FETCH); fetch.addParameters(r.remote, "-v"); Collection<VcsException> exs = HandlerUtil.doSynchronouslyWithExceptions(fetch); exceptions.addAll(exs); } Branch tracked = b.tracked(project, root); assert tracked != null : "Tracked branch cannot be null here"; SimpleHandler unmerged = new SimpleHandler(project, root, Command.LOG); unmerged.addParameters("--pretty=format:%H", r.branch + ".." + tracked.getFullName()); unmerged.setRemote(true); unmerged.setStdoutSuppressed(true); StringScanner su = new StringScanner(unmerged.run()); while (su.hasMoreData()) { if (su.line().trim().length() != 0) { r.remoteCommits++; } } SimpleHandler toPush = new SimpleHandler(project, root, Command.LOG); toPush.addParameters("--pretty=format:%H%x20%ct%x20%at%x20%s%n%P", tracked.getFullName() + ".." + r.branch); toPush.setRemote(true); toPush.setStdoutSuppressed(true); StringScanner sp = new StringScanner(toPush.run()); while (sp.hasMoreData()) { if (sp.isEol()) { sp.line(); continue; } Commit c = new Commit(); c.root = r; String hash = sp.spaceToken(); String time = sp.spaceToken(); c.revision = HistoryUtils.createUnvalidatedRevisionNumber(hash); c.authorTime = sp.spaceToken(); c.message = sp.line(); c.isMerge = sp.line().indexOf(' ') != -1; r.commits.add(c); } } } } catch (VcsException e) { exceptions.add(e); } } return rc; } /** * Loads roots (fetches) in background. When finished, executes the given task in the AWT thread. * @param postUiTask */ private void loadRootsInBackground(final boolean fetchData, @Nullable final PushActiveBranchRunnable postUiTask) { Task.Backgroundable fetchTask = new Task.Backgroundable(myProject, Bundle.getString("push.active.fetching")) { public void run(@NotNull ProgressIndicator indicator) { final Collection<VcsException> exceptions = new HashSet<VcsException>(1); final List<Root> roots = loadRoots(myProject, myVcsRoots, exceptions, fetchData); if (!exceptions.isEmpty()) { setErrorText(Bundle.getString("push.active.fetch.failed")); return; } if (postUiTask != null) { ApplicationManager.getApplication().invokeAndWait(new Runnable() { @Override public void run() { postUiTask.run(roots); } }, ModalityState.stateForComponent(getRootPane())); } } }; myVcs.runInBackground(fetchTask); } /** * Create UI components for the dialog */ private void createUIComponents() { myTreeRoot = new CheckedTreeNode("ROOT"); myCommitTree = new CheckboxTree(new CheckboxTree.CheckboxTreeCellRenderer() { @Override public void customizeRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { // Fix GTK background if (UIUtil.isUnderGTKLookAndFeel()) { final Color background = selected ? UIUtil.getTreeSelectionBackground() : UIUtil.getTreeTextBackground(); UIUtil.changeBackGround(this, background); } ColoredTreeCellRenderer r = getTextRenderer(); if (!(value instanceof DefaultMutableTreeNode)) { // unknown node type renderUnknown(r, value); return; } DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; if (!(node.getUserObject() instanceof Node)) { // unknown node type renderUnknown(r, node.getUserObject()); return; } ((Node) node.getUserObject()).render(r); } /** * Render unknown node * * @param r a renderer to use * @param value the unknown value */ private void renderUnknown(ColoredTreeCellRenderer r, Object value) { r.append("UNSUPPORTED NODE TYPE: " + (value == null ? "null" : value.getClass().getName()), SimpleTextAttributes.ERROR_ATTRIBUTES); } }, myTreeRoot) { @Override protected void onNodeStateChanged(CheckedTreeNode node) { updateButtons(); super.onNodeStateChanged(node); } }; } /** * The base class for nodes in the tree */ static abstract class Node { /** * Render the node text * * @param renderer the renderer to use */ protected abstract void render(ColoredTreeCellRenderer renderer); } /** * The commit descriptor */ static class Status extends Node { /** * The root */ Root root; /** * {@inheritDoc} */ @Override protected void render(ColoredTreeCellRenderer renderer) { renderer.append(Bundle.getString("push.active.status.status")); if (root.branch == null) { renderer.append(Bundle.message("push.active.status.no.branch"), SimpleTextAttributes.ERROR_ATTRIBUTES); } else if (root.remote == null) { renderer.append(Bundle.message("push.active.status.no.tracked"), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); } else if (root.remoteCommits != 0 && root.commits.size() == 0) { renderer.append(Bundle.message("push.active.status.no.commits.behind", root.remoteCommits), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); } else if (root.commits.size() == 0) { renderer.append(Bundle.message("push.active.status.no.commits"), SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES); } else if (root.remoteCommits != 0) { renderer.append(Bundle.message("push.active.status.behind", root.remoteCommits), SimpleTextAttributes.ERROR_ATTRIBUTES); } else { renderer.append(Bundle.message("push.active.status.push", root.commits.size())); } } } /** * The commit descriptor */ static class Commit extends Node { /** * The root */ Root root; /** * The revision */ VcsRevisionNumber revision; /** * The message */ String message; /** * The author time */ String authorTime; /** * If true, the commit is a merge */ boolean isMerge; /** * {@inheritDoc} */ @Override protected void render(ColoredTreeCellRenderer renderer) { renderer.append(revision.asString().substring(0, HASH_PREFIX_SIZE), SimpleTextAttributes.GRAYED_ATTRIBUTES); renderer.append(": "); renderer.append(message); if (isMerge) { renderer.append(Bundle.getString("push.active.commit.node.merge"), SimpleTextAttributes.GRAYED_ATTRIBUTES); } } /** * @return the identifier that is supposed to be stable with respect to rebase */ String commitId() { return authorTime + ":" + message; } } /** * The root node */ static class Root extends Node { /** * if true, the update is required */ int remoteCommits; /** * the path to vcs root */ VirtualFile root; /** * the current branch */ String branch; /** * the remote name */ String remote; /** * the remote branch name */ String remoteBranch; /** * The commit that will be actually pushed */ String commitToPush; /** * the commit */ List<Commit> commits = new ArrayList<Commit>(); /** * {@inheritDoc} */ @Override protected void render(ColoredTreeCellRenderer renderer) { SimpleTextAttributes rootAttributes; SimpleTextAttributes branchAttributes; if (remote != null && commits.size() != 0 && remoteCommits != 0 || branch == null) { rootAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES.derive(SimpleTextAttributes.STYLE_BOLD, null, null, null); branchAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES; } else if (remote == null || commits.size() == 0) { rootAttributes = SimpleTextAttributes.GRAYED_BOLD_ATTRIBUTES; branchAttributes = SimpleTextAttributes.GRAYED_ATTRIBUTES; } else { branchAttributes = SimpleTextAttributes.REGULAR_ATTRIBUTES; rootAttributes = SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES; } renderer.append(root.getPresentableUrl(), rootAttributes); if (branch != null) { renderer.append(" [" + branch, branchAttributes); if (remote != null) { renderer.append(" -> " + remote + "#" + remoteBranch, branchAttributes); } renderer.append("]", branchAttributes); } } } }