mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[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.
This commit is contained in:
@@ -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.
|
||||
* <p/>
|
||||
* 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();
|
||||
|
||||
}
|
||||
|
||||
@@ -66,4 +66,10 @@ public interface RepositoryManager<T extends Repository> {
|
||||
void updateAllRepositories();
|
||||
|
||||
void waitUntilInitialized();
|
||||
|
||||
/**
|
||||
* Returns true if repositories under this repository manager are controlled synchronously.
|
||||
*/
|
||||
boolean isSyncEnabled();
|
||||
|
||||
}
|
||||
|
||||
@@ -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<? extends Repository> myPreselectedRepositories;
|
||||
@NotNull private final List<PushSupport<?,?,?>> 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<? extends Repository> 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<PushSupport<?,?,?>> pushSupports) {
|
||||
@@ -107,39 +106,13 @@ public class PushController implements Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
private void selectFirstChecked() {
|
||||
Map.Entry<RepositoryNode, MyRepoModel> selected =
|
||||
ContainerUtil.find(myView2Model.entrySet(), new Condition<Map.Entry<RepositoryNode, MyRepoModel>>() {
|
||||
@Override
|
||||
public boolean value(Map.Entry<RepositoryNode, MyRepoModel> 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<RepositoryNode, MyRepoModel> 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<RepositoryNode, MyRepoModel> priorityLoading = new HashMap<RepositoryNode, MyRepoModel>();
|
||||
Map<RepositoryNode, MyRepoModel> others = new HashMap<RepositoryNode, MyRepoModel>();
|
||||
Map<RepositoryNode, MyRepoModel> priorityLoading = ContainerUtil.newLinkedHashMap();
|
||||
Map<RepositoryNode, MyRepoModel> others = ContainerUtil.newLinkedHashMap();
|
||||
for (Map.Entry<RepositoryNode, MyRepoModel> 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<? extends Repository> preselectedRepositories) {
|
||||
private void createTreeModel(@NotNull CheckedTreeNode rootNode) {
|
||||
for (PushSupport<? extends Repository, ? extends PushSource, ? extends PushTarget> support : myPushSupports) {
|
||||
createNodesForVcs(support, rootNode, preselectedRepositories);
|
||||
createNodesForVcs(support, rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
private <R extends Repository, S extends PushSource, T extends PushTarget> void createNodesForVcs(
|
||||
@NotNull PushSupport<R, S, T> pushSupport,
|
||||
@NotNull CheckedTreeNode rootNode,
|
||||
@NotNull List<? extends Repository> preselectedRepositories)
|
||||
@NotNull PushSupport<R, S, T> pushSupport, @NotNull CheckedTreeNode rootNode)
|
||||
{
|
||||
for (R repository : pushSupport.getRepositoryManager().getRepositories()) {
|
||||
createRepoNode(pushSupport, repository, rootNode, preselectedRepositories.contains(repository));
|
||||
createRepoNode(pushSupport, repository, rootNode);
|
||||
}
|
||||
}
|
||||
|
||||
private <R extends Repository, S extends PushSource, T extends PushTarget> void createRepoNode(@NotNull final PushSupport<R, S, T> 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<R, S, T> model = new MyRepoModel<R, S, T>(repository, support, mySingleRepoProject || isSelected,
|
||||
final MyRepoModel<R, S, T> model = new MyRepoModel<R, S, T>(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<T>() {
|
||||
@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<PushSupport<?, ?, ?>>() {
|
||||
@Override
|
||||
public boolean value(PushSupport<?, ?, ?> support) {
|
||||
return isPushAllowed(support);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isPushAllowed(@NotNull PushSupport<?, ?, ?> pushSupport) {
|
||||
Collection<RepositoryNode> nodes = getNodesForSupport(pushSupport);
|
||||
if (pushSupport.getRepositoryManager().isSyncEnabled()) {
|
||||
return hasCheckedNode(nodes) && allNodesAreLoaded(nodes);
|
||||
}
|
||||
return hasCheckedNode(nodes);
|
||||
}
|
||||
|
||||
private static boolean allNodesAreLoaded(@NotNull Collection<RepositoryNode> nodes) {
|
||||
return !ContainerUtil.exists(nodes, new Condition<RepositoryNode>() {
|
||||
@Override
|
||||
public boolean value(@NotNull RepositoryNode node) {
|
||||
return node.isLoading();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static boolean hasCheckedNode(@NotNull Collection<RepositoryNode> nodes) {
|
||||
return ContainerUtil.exists(nodes, new Condition<RepositoryNode>() {
|
||||
@Override
|
||||
public boolean value(@NotNull RepositoryNode node) {
|
||||
return node.isChecked();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Collection<RepositoryNode> getNodesForSupport(final PushSupport<?, ?, ?> support) {
|
||||
return ContainerUtil.mapNotNull(myView2Model.entrySet(), new Function<Map.Entry<RepositoryNode,MyRepoModel>, RepositoryNode>() {
|
||||
@Override
|
||||
public RepositoryNode fun(Map.Entry<RepositoryNode, MyRepoModel> entry) {
|
||||
return entry.getValue().getSupport().equals(support) ? entry.getKey() : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <R extends Repository, S extends PushSource, T extends PushTarget> void loadCommits(@NotNull final MyRepoModel<R, S, T> 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<R, S, T> support = model.getSupport();
|
||||
final AtomicReference<OutgoingResult> result = new AtomicReference<OutgoingResult>();
|
||||
Runnable task = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final R repository = model.getRepository();
|
||||
OutgoingResult outgoing = support.getOutgoingCommitsProvider()
|
||||
.getOutgoingCommits(model.getRepository(), new PushSpec<S, T>(model.getSource(), model.getTarget()), initial);
|
||||
.getOutgoingCommits(repository, new PushSpec<S, T>(model.getSource(), model.getTarget()), initial);
|
||||
result.compareAndSet(null, outgoing);
|
||||
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
OutgoingResult outgoing = result.get();
|
||||
List<VcsError> errors = outgoing.getErrors();
|
||||
boolean shouldBeSelected;
|
||||
if (!errors.isEmpty()) {
|
||||
shouldBeSelected = false;
|
||||
myPushLog.setChildren(node, ContainerUtil.map(errors, new Function<VcsError, DefaultMutableTreeNode>() {
|
||||
@Override
|
||||
public DefaultMutableTreeNode fun(final VcsError error) {
|
||||
@@ -256,16 +278,38 @@ public class PushController implements Disposable {
|
||||
}));
|
||||
}
|
||||
else {
|
||||
model.setLoadedCommits(outgoing.getCommits());
|
||||
List<? extends VcsFullCommitDetails> 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() {
|
||||
|
||||
@@ -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<AtomicReference<OutgoingResult>> future);
|
||||
void startLoading(@NotNull JTree tree, @NotNull Future<AtomicReference<OutgoingResult>> future);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<RepositoryNode> {
|
||||
@NotNull private final RepositoryWithBranchPanel myRepositoryPanel;
|
||||
private Future<AtomicReference<OutgoingResult>> 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<AtomicReference<OutgoingResult>> 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<AtomicReference<OutgoingResult>> future) {
|
||||
public void startLoading(@NotNull JTree tree, @NotNull Future<AtomicReference<OutgoingResult>> 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<GitRepository> repositories = collectRepositories(project, e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY));
|
||||
|
||||
new VcsPushDialog(project, RepositoryUtil.sortRepositories(repositories)).show();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Collection<GitRepository> collectRepositories(@NotNull Project project, @Nullable VirtualFile[] files) {
|
||||
if (files == null) {
|
||||
return Collections.singletonList(GitBranchUtil.getCurrentRepository(project));
|
||||
}
|
||||
GitRepositoryManager manager = GitUtil.getRepositoryManager(project);
|
||||
Collection<GitRepository> 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<GitRepository> 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<GitRepository> 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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
|
||||
|
||||
private static final Comparator<GitRemoteBranch> 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<GitPushTarget> {
|
||||
if (defaultTarget == null) {
|
||||
// TODO
|
||||
initialBranch = "";
|
||||
initialRemote = "No remotes";
|
||||
initialRemote = NO_REMOTES;
|
||||
}
|
||||
else {
|
||||
initialBranch = getTextFieldText(defaultTarget);
|
||||
@@ -150,7 +151,7 @@ class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
|
||||
|
||||
@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<GitPushTarget> {
|
||||
@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<GitPushTarget> {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getEnteredRemote() {
|
||||
String text = myRemoteLabel.getText();
|
||||
return text.equals(NO_REMOTES) ? null : text.replace(SEPARATOR, "");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static List<String> getTargetNames(@NotNull GitRepository repository) {
|
||||
List<GitRemoteBranch> remoteBranches = ContainerUtil.sorted(repository.getBranches().getRemoteBranches(), REMOTE_BRANCH_COMPARATOR);
|
||||
|
||||
@@ -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<GitRepository> {
|
||||
|
||||
@NotNull private final GitPlatformFacade myPlatformFacade;
|
||||
@@ -41,5 +40,11 @@ public class GitRepositoryManager extends AbstractRepositoryManager<GitRepositor
|
||||
protected GitRepository createRepository(@NotNull VirtualFile root) {
|
||||
return GitRepositoryImpl.getFullInstance(root, myProject, myPlatformFacade, this);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean isSyncEnabled() {
|
||||
return myPlatformFacade.getSettings(myProject).getSyncSetting() == DvcsSyncSettings.Value.SYNC &&
|
||||
!new GitMultiRootBranchConfig(getRepositories()).diverged();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,9 +25,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author Kirill Likhodedov
|
||||
*/
|
||||
public class GitMockRepositoryManager implements RepositoryManager<GitRepository> {
|
||||
private final List<GitRepository> myRepositories = new ArrayList<GitRepository>();
|
||||
|
||||
@@ -81,4 +78,8 @@ public class GitMockRepositoryManager implements RepositoryManager<GitRepository
|
||||
public void waitUntilInitialized() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSyncEnabled() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,4 +30,10 @@ public class HgTarget implements PushTarget {
|
||||
public String getPresentation() {
|
||||
return HgUtil.removePasswordIfNeeded(myTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasSomethingToPush() {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
package org.zmlx.hg4idea.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 com.intellij.util.ObjectUtils;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.zmlx.hg4idea.HgProjectSettings;
|
||||
import org.zmlx.hg4idea.HgVcs;
|
||||
import org.zmlx.hg4idea.branch.HgMultiRootBranchConfig;
|
||||
import org.zmlx.hg4idea.util.HgUtil;
|
||||
|
||||
/**
|
||||
@@ -13,9 +17,12 @@ import org.zmlx.hg4idea.util.HgUtil;
|
||||
*/
|
||||
public class HgRepositoryManager extends AbstractRepositoryManager<HgRepository> {
|
||||
|
||||
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<HgRepository>
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user