From 937b3b63f77aadc7df8b71cefc7d0367f7f274ac Mon Sep 17 00:00:00 2001 From: Kirill Likhodedov Date: Fri, 19 Sep 2014 16:21:48 +0400 Subject: [PATCH] [push] Improve UX of initially selected repositories to push Rules: 1. While commits are initially loaded, checkboxes are not shown (the "loading" indicator is shown instead), and such roots are not ready to be pushed. 2. After outgoing commits from a repository has loaded, the checkbox appears back, and it selection is defined by following: A. If roots are synced, checkbox is selected iff there are commits OR something else to push (e.g. new branch is created). B. Otherwise, checkbox is selected iff (there are commits OR something else to push) AND this root was preselected from the context. 3. Diverged roots behave the same way as not-synced. 4. The Push button is always enabled for 1-repo setup. In multi-repo-sync setup, it is disabled until all repositories are initialized. In multi-repo-async setup, it is disabled until at least one repository with commits is loaded. 5. Additional changes: * Disable checkbox if target is null. * Remove "There are no selected repositories to push" validation error message since it is obvious. --- .../com/intellij/dvcs/push/PushTarget.java | 8 + .../intellij/dvcs/repo/RepositoryManager.java | 6 + .../intellij/dvcs/push/PushController.java | 146 ++++++++++++------ .../dvcs/push/ui/EditableTreeNode.java | 5 +- .../dvcs/push/ui/LoadingTreeNode.java | 27 ++-- .../com/intellij/dvcs/push/ui/PushLog.java | 13 +- .../intellij/dvcs/push/ui/RepositoryNode.java | 46 ++++-- .../dvcs/push/ui/SingleRepositoryNode.java | 12 +- .../intellij/dvcs/push/ui/VcsPushDialog.java | 23 +-- .../src/git4idea/actions/GitPushAction.java | 35 ++--- .../src/git4idea/push/GitPushTarget.java | 11 +- .../src/git4idea/push/GitPushTargetPanel.java | 13 +- .../git4idea/repo/GitRepositoryManager.java | 13 +- .../test/GitMockRepositoryManager.java | 7 +- .../src/org/zmlx/hg4idea/push/HgTarget.java | 6 + .../hg4idea/repo/HgRepositoryManager.java | 13 ++ 16 files changed, 247 insertions(+), 137 deletions(-) diff --git a/platform/dvcs-api/src/com/intellij/dvcs/push/PushTarget.java b/platform/dvcs-api/src/com/intellij/dvcs/push/PushTarget.java index df6bfaefa79b..8d99a0fa0145 100644 --- a/platform/dvcs-api/src/com/intellij/dvcs/push/PushTarget.java +++ b/platform/dvcs-api/src/com/intellij/dvcs/push/PushTarget.java @@ -20,4 +20,12 @@ package com.intellij.dvcs.push; */ public interface PushTarget { + /** + * Returns true if pushing to this target is guaranteed to introduce something new: e.g. new branch or tag. + *

+ * Returning false doesn't mean that this target has nothing to push (e.g. commits to push are calculated separately), + * it means rather that "we don't know". + */ + boolean hasSomethingToPush(); + } diff --git a/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java b/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java index d27694cd05d0..d5413283309f 100644 --- a/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java +++ b/platform/dvcs-api/src/com/intellij/dvcs/repo/RepositoryManager.java @@ -66,4 +66,10 @@ public interface RepositoryManager { void updateAllRepositories(); void waitUntilInitialized(); + + /** + * Returns true if repositories under this repository manager are controlled synchronously. + */ + boolean isSyncEnabled(); + } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java index 395ab3a7ce85..efb078622bc1 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/PushController.java @@ -18,19 +18,18 @@ package com.intellij.dvcs.push; import com.intellij.dvcs.DvcsUtil; import com.intellij.dvcs.push.ui.*; import com.intellij.dvcs.repo.Repository; +import com.intellij.dvcs.repo.RepositoryManager; import com.intellij.openapi.Disposable; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.vcs.AbstractVcs; import com.intellij.ui.CheckedTreeNode; import com.intellij.util.Function; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.hash.HashMap; import com.intellij.util.ui.UIUtil; import com.intellij.vcs.log.VcsFullCommitDetails; import org.jetbrains.annotations.NotNull; @@ -45,6 +44,7 @@ import java.util.concurrent.atomic.AtomicReference; public class PushController implements Disposable { @NotNull private final Project myProject; + @NotNull private final List myPreselectedRepositories; @NotNull private final List> myPushSupports; @NotNull private final PushLog myPushLog; @NotNull private final VcsPushDialog myDialog; @@ -59,16 +59,15 @@ public class PushController implements Disposable { @NotNull VcsPushDialog dialog, @NotNull List preselectedRepositories) { myProject = project; + myPreselectedRepositories = preselectedRepositories; myPushSupports = getAffectedSupports(myProject); mySingleRepoProject = isSingleRepoProject(myPushSupports); CheckedTreeNode rootNode = new CheckedTreeNode(null); - createTreeModel(rootNode, preselectedRepositories); + createTreeModel(rootNode); myPushLog = new PushLog(myProject, rootNode); myDialog = dialog; - myDialog.updateButtons(); startLoadingCommits(); Disposer.register(dialog.getDisposable(), this); - selectFirstChecked(); } private static boolean isSingleRepoProject(@NotNull List> pushSupports) { @@ -107,39 +106,13 @@ public class PushController implements Disposable { }); } - private void selectFirstChecked() { - Map.Entry selected = - ContainerUtil.find(myView2Model.entrySet(), new Condition>() { - @Override - public boolean value(Map.Entry entry) { - return entry.getValue().isSelected(); - } - }); - if (selected != null) { - myPushLog.selectNode(selected.getKey()); - } - } - - @Nullable - public ValidationInfo validate() { - ValidationInfo validInfo = new ValidationInfo("There are no selected repository to push!"); - for (Map.Entry entry : myView2Model.entrySet()) { - MyRepoModel model = entry.getValue(); - if (model.isSelected()) { - if (model.hasError()) return new ValidationInfo(model.getError().getText()); - validInfo = null; - } - } - return validInfo; - } - private void startLoadingCommits() { //todo should be reworked - Map priorityLoading = new HashMap(); - Map others = new HashMap(); + Map priorityLoading = ContainerUtil.newLinkedHashMap(); + Map others = ContainerUtil.newLinkedHashMap(); for (Map.Entry entry : myView2Model.entrySet()) { MyRepoModel model = entry.getValue(); - if (model.isSelected()) { + if (myPreselectedRepositories.contains(model.getRepository())) { priorityLoading.put(entry.getKey(), model); } else if (model.getSupport().shouldRequestIncomingChangesForNotCheckedRepositories()) { @@ -157,30 +130,27 @@ public class PushController implements Disposable { } } - private void createTreeModel(@NotNull CheckedTreeNode rootNode, @NotNull List preselectedRepositories) { + private void createTreeModel(@NotNull CheckedTreeNode rootNode) { for (PushSupport support : myPushSupports) { - createNodesForVcs(support, rootNode, preselectedRepositories); + createNodesForVcs(support, rootNode); } } private void createNodesForVcs( - @NotNull PushSupport pushSupport, - @NotNull CheckedTreeNode rootNode, - @NotNull List preselectedRepositories) + @NotNull PushSupport pushSupport, @NotNull CheckedTreeNode rootNode) { for (R repository : pushSupport.getRepositoryManager().getRepositories()) { - createRepoNode(pushSupport, repository, rootNode, preselectedRepositories.contains(repository)); + createRepoNode(pushSupport, repository, rootNode); } } private void createRepoNode(@NotNull final PushSupport support, @NotNull final R repository, - @NotNull CheckedTreeNode rootNode, - boolean isSelected) { + @NotNull CheckedTreeNode rootNode) { T target = support.getDefaultTarget(repository); String repoName = DvcsUtil.getShortRepositoryName(repository); S source = support.getSource(repository); - final MyRepoModel model = new MyRepoModel(repository, support, mySingleRepoProject || isSelected, + final MyRepoModel model = new MyRepoModel(repository, support, mySingleRepoProject, source, target, DEFAULT_CHILDREN_PRESENTATION_NUMBER); if (target == null) { @@ -191,12 +161,13 @@ public class PushController implements Disposable { source.getPresentation(), pushTargetPanel); final RepositoryNode repoNode = mySingleRepoProject ? new SingleRepositoryNode(repoPanel) - : new RepositoryNode(repoPanel); + : new RepositoryNode(repoPanel, target != null); myView2Model.put(repoNode, model); - repoNode.setChecked(model.isSelected()); repoPanel.addRepoNodeListener(new RepositoryNodeListener() { @Override public void onTargetChanged(T newTarget) { + model.setSelected(true); + repoNode.setChecked(true); model.setTarget(newTarget); model.clearErrors(); loadCommits(model, repoNode, false); @@ -216,27 +187,78 @@ public class PushController implements Disposable { rootNode.add(repoNode); } + public boolean isPushAllowed() { + return ContainerUtil.exists(myPushSupports, new Condition>() { + @Override + public boolean value(PushSupport support) { + return isPushAllowed(support); + } + }); + } + + private boolean isPushAllowed(@NotNull PushSupport pushSupport) { + Collection nodes = getNodesForSupport(pushSupport); + if (pushSupport.getRepositoryManager().isSyncEnabled()) { + return hasCheckedNode(nodes) && allNodesAreLoaded(nodes); + } + return hasCheckedNode(nodes); + } + + private static boolean allNodesAreLoaded(@NotNull Collection nodes) { + return !ContainerUtil.exists(nodes, new Condition() { + @Override + public boolean value(@NotNull RepositoryNode node) { + return node.isLoading(); + } + }); + } + + private static boolean hasCheckedNode(@NotNull Collection nodes) { + return ContainerUtil.exists(nodes, new Condition() { + @Override + public boolean value(@NotNull RepositoryNode node) { + return node.isChecked(); + } + }); + } + + @NotNull + private Collection getNodesForSupport(final PushSupport support) { + return ContainerUtil.mapNotNull(myView2Model.entrySet(), new Function, RepositoryNode>() { + @Override + public RepositoryNode fun(Map.Entry entry) { + return entry.getValue().getSupport().equals(support) ? entry.getKey() : null; + } + }); + } + private void loadCommits(@NotNull final MyRepoModel model, @NotNull final RepositoryNode node, final boolean initial) { - node.stopLoading(); + node.cancelLoading(); final T target = model.getTarget(); - if (target == null) return; //todo should be removed when commit loader executor will be modified - myPushLog.startLoading(node); + if (target == null) { + node.stopLoading(); + return; + } + node.setEnabled(true); final PushSupport support = model.getSupport(); final AtomicReference result = new AtomicReference(); Runnable task = new Runnable() { @Override public void run() { + final R repository = model.getRepository(); OutgoingResult outgoing = support.getOutgoingCommitsProvider() - .getOutgoingCommits(model.getRepository(), new PushSpec(model.getSource(), model.getTarget()), initial); + .getOutgoingCommits(repository, new PushSpec(model.getSource(), model.getTarget()), initial); result.compareAndSet(null, outgoing); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { OutgoingResult outgoing = result.get(); List errors = outgoing.getErrors(); + boolean shouldBeSelected; if (!errors.isEmpty()) { + shouldBeSelected = false; myPushLog.setChildren(node, ContainerUtil.map(errors, new Function() { @Override public DefaultMutableTreeNode fun(final VcsError error) { @@ -256,16 +278,38 @@ public class PushController implements Disposable { })); } else { - model.setLoadedCommits(outgoing.getCommits()); + List commits = outgoing.getCommits(); + shouldBeSelected = shouldSelectInitially(target, repository, model.getSupport().getRepositoryManager(), !commits.isEmpty()); + model.setLoadedCommits(commits); myPushLog.setChildren(node, getPresentationForCommits(PushController.this.myProject, model.getLoadedCommits(), model.getNumberOfShownCommits())); } + node.stopLoading(); + if (shouldBeSelected) { // never remove selection; initially all checkboxes are not selected + node.setChecked(true); + } + node.fireOnSelectionChange(shouldBeSelected); } }); } }; - node.startLoading(myExecutorService.submit(task, result)); + node.startLoading(myPushLog.getTree(), myExecutorService.submit(task, result)); + } + + private boolean shouldSelectInitially(@NotNull PushTarget target, @NotNull Repository repository, + @NotNull RepositoryManager repositoryManager, boolean hasCommits) { + boolean shouldBeSelected; + if (mySingleRepoProject) { + shouldBeSelected = true; + } + else if (repositoryManager.isSyncEnabled()) { + shouldBeSelected = hasCommits || target.hasSomethingToPush(); + } + else { + shouldBeSelected = (hasCommits || target.hasSomethingToPush()) && myPreselectedRepositories.contains(repository); + } + return shouldBeSelected; } public PushLog getPushPanelLog() { diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java index aab70818082d..cdc762bca518 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/EditableTreeNode.java @@ -18,6 +18,7 @@ package com.intellij.dvcs.push.ui; import com.intellij.dvcs.push.OutgoingResult; import org.jetbrains.annotations.NotNull; +import javax.swing.*; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicReference; @@ -29,7 +30,7 @@ public interface EditableTreeNode extends CustomRenderedTreeNode { void fireOnSelectionChange(boolean isSelected); - void stopLoading(); + void cancelLoading(); - void startLoading(@NotNull Future> future); + void startLoading(@NotNull JTree tree, @NotNull Future> future); } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/LoadingTreeNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/LoadingTreeNode.java index 67ab197c1543..36958a00c64b 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/LoadingTreeNode.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/LoadingTreeNode.java @@ -20,27 +20,29 @@ import com.intellij.ui.JBColor; import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.ImageLoader; import com.intellij.util.ui.JBImageIcon; +import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.tree.DefaultMutableTreeNode; import java.awt.*; -import java.net.URL; +import java.awt.image.BufferedImage; public class LoadingTreeNode extends DefaultMutableTreeNode implements CustomRenderedTreeNode { - @NotNull protected ImageIcon myLoadingIcon; - private static final String LOADING_ICON = "/icons/loading.gif"; - @NotNull - public ImageIcon getIcon() { - return myLoadingIcon; - } + private static final String LOADING_ICON = "/icons/loading.gif"; + private static final JBImageIcon EMPTY_ICON = new JBImageIcon(UIUtil.createImage(18, 18, BufferedImage.TYPE_3BYTE_BGR)); + @NotNull private ImageIcon myLoadingIcon; public LoadingTreeNode() { super(null, false); - URL loadingIconUrl = getClass().getResource(LOADING_ICON); - Image image = ImageLoader.loadFromUrl(loadingIconUrl); - myLoadingIcon = new JBImageIcon(image); + myLoadingIcon = getLoadingIcon(); + } + + @NotNull + public static JBImageIcon getLoadingIcon() { + Image image = ImageLoader.loadFromResource(LOADING_ICON); + return image == null ? EMPTY_ICON : new JBImageIcon(image); } @Override @@ -50,4 +52,9 @@ public class LoadingTreeNode extends DefaultMutableTreeNode implements CustomRen renderer.setIconOnTheRight(false); renderer.append("Loading Commits...", new SimpleTextAttributes(SimpleTextAttributes.STYLE_SMALLER, JBColor.GRAY)); } + + @NotNull + public ImageIcon getIcon() { + return myLoadingIcon; + } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java index a3853e3b25aa..370217b9894c 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/PushLog.java @@ -242,13 +242,12 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { return super.processKeyBinding(ks, e, condition, pressed); } - public void startLoading(DefaultMutableTreeNode parentNode) { - LoadingTreeNode loading = new LoadingTreeNode(); - loading.getIcon().setImageObserver(new NodeImageObserver(myTree, loading)); - setChildren(parentNode, Collections.singleton(loading)); + public JComponent getPreferredFocusedComponent() { + return myTree; } - public JComponent getPreferredFocusedComponent() { + @NotNull + public JTree getTree() { return myTree; } @@ -341,9 +340,7 @@ public class PushLog extends JPanel implements TypeSafeDataProvider { if (node.getChildCount() <= 0) return; if (node instanceof RepositoryNode) { TreePath path = TreeUtil.getPathFromRoot(node); - if (((RepositoryNode)node).isChecked()) { - myTree.expandPath(path); - } + myTree.expandPath(path); return; } for (DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)node.getFirstChild(); diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java index 4808064f5c8d..be57fee704c1 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/RepositoryNode.java @@ -21,31 +21,48 @@ import com.intellij.ui.CheckedTreeNode; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.SimpleTextAttributes; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.border.EmptyBorder; import java.awt.*; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; public class RepositoryNode extends CheckedTreeNode implements EditableTreeNode, Comparable { - @NotNull private final RepositoryWithBranchPanel myRepositoryPanel; - private Future> myFuture; - public RepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel) { + private static final int CHECKBOX_WIDTH = new JCheckBox().getPreferredSize().width; + + @NotNull protected final ImageIcon myLoadingIcon; + @NotNull protected final AtomicBoolean myLoading = new AtomicBoolean(true); + + @NotNull private final RepositoryWithBranchPanel myRepositoryPanel; + @Nullable private Future> myFuture; + + public RepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel, boolean enabled) { super(repositoryPanel); + setChecked(false); + setEnabled(enabled); myRepositoryPanel = repositoryPanel; + myLoadingIcon = LoadingTreeNode.getLoadingIcon(); } public boolean isCheckboxVisible() { - return true; + return !myLoading.get(); } @Override public void render(@NotNull ColoredTreeCellRenderer renderer) { - String repositoryPath = myRepositoryPanel.getRepositoryName(); - renderer.append(repositoryPath, SimpleTextAttributes.GRAY_ATTRIBUTES); - renderer.appendFixedTextFragmentWidth(120); + int repoFixedWidth = 120; + if (myLoading.get()) { + renderer.setIcon(myLoadingIcon); + renderer.setIconOnTheRight(false); + renderer.setIconTextGap(CHECKBOX_WIDTH - myLoadingIcon.getIconWidth()); + repoFixedWidth += CHECKBOX_WIDTH; + } + renderer.append(myRepositoryPanel.getRepositoryName(), SimpleTextAttributes.GRAY_ATTRIBUTES); + renderer.appendFixedTextFragmentWidth(repoFixedWidth); renderer.append(myRepositoryPanel.getSourceName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); renderer.append(myRepositoryPanel.getArrow(), SimpleTextAttributes.REGULAR_ATTRIBUTES); PushTargetPanel pushTargetPanel = myRepositoryPanel.getTargetPanel(); @@ -75,15 +92,17 @@ public class RepositoryNode extends CheckedTreeNode implements EditableTreeNode, } @Override - public void stopLoading() { + public void cancelLoading() { if (myFuture != null && !myFuture.isDone()) { myFuture.cancel(true); } } @Override - public void startLoading(@NotNull Future> future) { + public void startLoading(@NotNull JTree tree, @NotNull Future> future) { myFuture = future; + myLoading.set(true); + myLoadingIcon.setImageObserver(new NodeImageObserver(tree, this)); } public int compareTo(@NotNull RepositoryNode repositoryNode) { @@ -91,4 +110,13 @@ public class RepositoryNode extends CheckedTreeNode implements EditableTreeNode, RepositoryWithBranchPanel panel = (RepositoryWithBranchPanel)repositoryNode.getUserObject(); return name.compareTo(panel.getRepositoryName()); } + + public void stopLoading() { + myLoading.set(false); + } + + public boolean isLoading() { + return myLoading.get(); + } + } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java index 3f5ab8d17496..65fecd19e010 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/SingleRepositoryNode.java @@ -18,7 +18,6 @@ package com.intellij.dvcs.push.ui; import com.intellij.dvcs.push.PushTargetPanel; import com.intellij.ui.ColoredTreeCellRenderer; import com.intellij.ui.SimpleTextAttributes; -import com.intellij.util.ui.EmptyIcon; import org.jetbrains.annotations.NotNull; public class SingleRepositoryNode extends RepositoryNode { @@ -26,7 +25,7 @@ public class SingleRepositoryNode extends RepositoryNode { @NotNull private final RepositoryWithBranchPanel myRepositoryPanel; public SingleRepositoryNode(@NotNull RepositoryWithBranchPanel repositoryPanel) { - super(repositoryPanel); + super(repositoryPanel, true); myRepositoryPanel = repositoryPanel; } @@ -37,13 +36,14 @@ public class SingleRepositoryNode extends RepositoryNode { @Override public void render(@NotNull ColoredTreeCellRenderer renderer) { + if (myLoading.get()) { + renderer.setIcon(myLoadingIcon); + renderer.setIconOnTheRight(false); + } + renderer.append(myRepositoryPanel.getSourceName(), SimpleTextAttributes.REGULAR_ATTRIBUTES); renderer.append(myRepositoryPanel.getArrow(), SimpleTextAttributes.REGULAR_ATTRIBUTES); PushTargetPanel pushTargetPanel = myRepositoryPanel.getTargetPanel(); pushTargetPanel.render(renderer); - - // hack to fix vertical size of the editor - renderer.setIcon(EmptyIcon.ICON_18); - renderer.setIconOnTheRight(true); } } diff --git a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsPushDialog.java b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsPushDialog.java index 86bec903b32b..42287266c161 100644 --- a/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsPushDialog.java +++ b/platform/dvcs-impl/src/com/intellij/dvcs/push/ui/VcsPushDialog.java @@ -16,13 +16,15 @@ package com.intellij.dvcs.push.ui; import com.intellij.CommonBundle; -import com.intellij.dvcs.push.*; +import com.intellij.dvcs.push.PushController; +import com.intellij.dvcs.push.PushSupport; +import com.intellij.dvcs.push.VcsPushOptionValue; +import com.intellij.dvcs.push.VcsPushOptionsPanel; import com.intellij.dvcs.repo.Repository; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.OptionAction; -import com.intellij.openapi.ui.ValidationInfo; import net.miginfocom.swing.MigLayout; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -54,6 +56,7 @@ public class VcsPushDialog extends DialogWrapper { myListPanel = myController.getPushPanelLog(); init(); + updateButtons(); setOKButtonText("Push"); setOKButtonMnemonic('P'); setTitle("Push Dialog"); @@ -107,27 +110,17 @@ public class VcsPushDialog extends DialogWrapper { return myPushAction; } - @Nullable - @Override - protected ValidationInfo doValidate() { - return myController.validate(); - } - @Override protected String getHelpId() { return "reference.mercurial.push.dialog"; } public void updateButtons() { + boolean pushAllowed = myController.isPushAllowed(); + myPushAction.setEnabled(pushAllowed); if (myForcePushAction != null) { - myForcePushAction.setEnabled(myController.isForcePushAllowed()); + myForcePushAction.setEnabled(pushAllowed && myController.isForcePushAllowed()); } - initValidation(); - } - - @Override - protected boolean postponeValidation() { - return false; } @Nullable diff --git a/plugins/git4idea/src/git4idea/actions/GitPushAction.java b/plugins/git4idea/src/git4idea/actions/GitPushAction.java index d8c478076f00..04e139fa8934 100644 --- a/plugins/git4idea/src/git4idea/actions/GitPushAction.java +++ b/plugins/git4idea/src/git4idea/actions/GitPushAction.java @@ -15,7 +15,6 @@ */ package git4idea.actions; -import com.intellij.dvcs.branch.DvcsSyncSettings; import com.intellij.dvcs.push.ui.VcsPushDialog; import com.intellij.dvcs.repo.RepositoryUtil; import com.intellij.openapi.actionSystem.AnActionEvent; @@ -26,55 +25,41 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.containers.ContainerUtil; import git4idea.GitUtil; import git4idea.branch.GitBranchUtil; -import git4idea.config.GitVcsSettings; import git4idea.repo.GitRepository; import git4idea.repo.GitRepositoryManager; -import git4idea.ui.branch.GitMultiRootBranchConfig; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Collections; -import java.util.List; public class GitPushAction extends DumbAwareAction { @Override - public void actionPerformed(AnActionEvent e) { + public void actionPerformed(@NotNull AnActionEvent e) { Project project = e.getRequiredData(CommonDataKeys.PROJECT); Collection repositories = collectRepositories(project, e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY)); - new VcsPushDialog(project, RepositoryUtil.sortRepositories(repositories)).show(); } @NotNull private static Collection collectRepositories(@NotNull Project project, @Nullable VirtualFile[] files) { + if (files == null) { + return Collections.singletonList(GitBranchUtil.getCurrentRepository(project)); + } GitRepositoryManager manager = GitUtil.getRepositoryManager(project); - Collection repositories; - if (GitVcsSettings.getInstance(project).getSyncSetting() == DvcsSyncSettings.Value.SYNC && !diverged(manager.getRepositories())) { - repositories = manager.getRepositories(); - } - else if (files == null) { - repositories = Collections.singletonList(GitBranchUtil.getCurrentRepository(project)); - } - else { - repositories = ContainerUtil.newHashSet(); - for (VirtualFile file : files) { - GitRepository repo = manager.getRepositoryForFile(file); - if (repo != null) { - repositories.add(repo); - } + Collection repositories = ContainerUtil.newHashSet(); + for (VirtualFile file : files) { + GitRepository repo = manager.getRepositoryForFile(file); + if (repo != null) { + repositories.add(repo); } } return repositories; } - private static boolean diverged(List repositories) { - return new GitMultiRootBranchConfig(repositories).diverged(); - } - @Override - public void update(AnActionEvent e) { + public void update(@NotNull AnActionEvent e) { super.update(e); Project project = e.getProject(); e.getPresentation().setEnabledAndVisible(project != null && !GitUtil.getRepositoryManager(project).getRepositories().isEmpty()); diff --git a/plugins/git4idea/src/git4idea/push/GitPushTarget.java b/plugins/git4idea/src/git4idea/push/GitPushTarget.java index 084eabbad826..032f98169cb4 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushTarget.java +++ b/plugins/git4idea/src/git4idea/push/GitPushTarget.java @@ -49,13 +49,22 @@ class GitPushTarget implements PushTarget { return myRemoteBranch; } + @Override + public boolean hasSomethingToPush() { + return isNewBranchCreated(); + } + boolean isNewBranchCreated() { return myIsNewBranchCreated; } @NotNull - static GitPushTarget parse(@NotNull GitRepository repository, @NotNull String remoteName, @NotNull String branchName) throws + static GitPushTarget parse(@NotNull GitRepository repository, @Nullable String remoteName, @NotNull String branchName) throws ParseException { + if (remoteName == null) { + throw new ParseException("No remotes defined", -1); + } + if (!GitRefNameValidator.getInstance().checkInput(branchName)) { throw new ParseException("Invalid destination branch name: " + branchName, -1); } diff --git a/plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java b/plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java index aa8ce0b54287..a09262c351a7 100644 --- a/plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java +++ b/plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java @@ -52,6 +52,7 @@ class GitPushTargetPanel extends PushTargetPanel { private static final Comparator REMOTE_BRANCH_COMPARATOR = new MyRemoteBranchComparator(); public static final String SEPARATOR = " \u25BE "; + public static final String NO_REMOTES = "No remotes" + SEPARATOR; private final GitRepository myRepository; private final PushTargetTextField myTargetTextField; @@ -69,7 +70,7 @@ class GitPushTargetPanel extends PushTargetPanel { if (defaultTarget == null) { // TODO initialBranch = ""; - initialRemote = "No remotes"; + initialRemote = NO_REMOTES; } else { initialBranch = getTextFieldText(defaultTarget); @@ -150,7 +151,7 @@ class GitPushTargetPanel extends PushTargetPanel { @Override public void fireOnChange() { - String remoteName = myRemoteLabel.getText().replace(SEPARATOR, ""); + String remoteName = getEnteredRemote(); String branchName = myTargetTextField.getText(); try { myCurrentTarget = GitPushTarget.parse(myRepository, remoteName, branchName); @@ -164,7 +165,7 @@ class GitPushTargetPanel extends PushTargetPanel { @Override public ValidationInfo verify() { try { - String remoteLabel = myRemoteLabel.getText().replace(SEPARATOR, ""); + String remoteLabel = getEnteredRemote(); GitPushTarget.parse(myRepository, remoteLabel, myTargetTextField.getText()); return null; } @@ -173,6 +174,12 @@ class GitPushTargetPanel extends PushTargetPanel { } } + @Nullable + private String getEnteredRemote() { + String text = myRemoteLabel.getText(); + return text.equals(NO_REMOTES) ? null : text.replace(SEPARATOR, ""); + } + @NotNull public static List getTargetNames(@NotNull GitRepository repository) { List remoteBranches = ContainerUtil.sorted(repository.getBranches().getRemoteBranches(), REMOTE_BRANCH_COMPARATOR); diff --git a/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java b/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java index fb40b58045a2..10b9391ff1ab 100644 --- a/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java +++ b/plugins/git4idea/src/git4idea/repo/GitRepositoryManager.java @@ -15,17 +15,16 @@ */ package git4idea.repo; +import com.intellij.dvcs.branch.DvcsSyncSettings; import com.intellij.dvcs.repo.AbstractRepositoryManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vfs.VirtualFile; import git4idea.GitPlatformFacade; import git4idea.GitUtil; +import git4idea.ui.branch.GitMultiRootBranchConfig; import org.jetbrains.annotations.NotNull; -/** - * @author Kirill Likhodedov - */ public class GitRepositoryManager extends AbstractRepositoryManager { @NotNull private final GitPlatformFacade myPlatformFacade; @@ -41,5 +40,11 @@ public class GitRepositoryManager extends AbstractRepositoryManager { private final List myRepositories = new ArrayList(); @@ -81,4 +78,8 @@ public class GitMockRepositoryManager implements RepositoryManager { + private final HgProjectSettings mySettings; + public HgRepositoryManager(@NotNull Project project, @NotNull ProjectLevelVcsManager vcsManager) { super(project, vcsManager, HgVcs.getInstance(project), HgUtil.DOT_HG); + mySettings = ObjectUtils.assertNotNull(HgVcs.getInstance(project)).getProjectSettings(); } @NotNull @@ -23,4 +30,10 @@ public class HgRepositoryManager extends AbstractRepositoryManager protected HgRepository createRepository(@NotNull VirtualFile root) { return HgRepositoryImpl.getInstance(root, myProject, this); } + + @Override + public boolean isSyncEnabled() { + return mySettings.getSyncSetting() == DvcsSyncSettings.Value.SYNC && !new HgMultiRootBranchConfig(getRepositories()).diverged(); + } + }