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(); + } + }