[git] IDEA-132736 Define remote right from the push dialog
authorKirill Likhodedov <Kirill.Likhodedov@jetbrains.com>
Wed, 14 Jan 2015 14:23:35 +0000 (17:23 +0300)
committerKirill Likhodedov <Kirill.Likhodedov@jetbrains.com>
Thu, 26 Feb 2015 12:58:24 +0000 (15:58 +0300)
plugins/git4idea/src/git4idea/commands/Git.java
plugins/git4idea/src/git4idea/commands/GitImpl.java
plugins/git4idea/src/git4idea/push/GitPushSupport.java
plugins/git4idea/src/git4idea/push/GitPushTargetPanel.java

index 14cb10262be4ed1d34fc4d0b5f7b0e5e76a7eda0..fb3ec3c204b4f9e4ef9af4c242f76f7bbb3f2c90 100644 (file)
@@ -144,4 +144,7 @@ public interface Git {
   @NotNull
   GitCommandResult fetch(@NotNull GitRepository repository, @NotNull String url, @NotNull String remote,
                          @NotNull List<GitLineHandlerListener> listeners, String... params);
+
+  @NotNull
+  GitCommandResult addRemote(@NotNull GitRepository repository, @NotNull String name, @NotNull String url);
 }
index 3fdaa5bd8b7731d1cef043e43463a7ea127e7bb1..ed2c88f743323600490b153feda547dc16be3a01 100644 (file)
@@ -22,6 +22,7 @@ import com.intellij.openapi.util.Key;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.TimeoutUtil;
 import com.intellij.vcsUtil.VcsFileUtil;
 import git4idea.*;
 import git4idea.config.GitVersionSpecialty;
@@ -497,6 +498,14 @@ public class GitImpl implements Git {
     });
   }
 
+  @NotNull
+  @Override
+  public GitCommandResult addRemote(@NotNull GitRepository repository, @NotNull String name, @NotNull String url) {
+    GitLineHandler h = new GitLineHandler(repository.getProject(), repository.getRoot(), GitCommand.REMOTE);
+    h.addParameters("add", name, url);
+    return run(h);
+  }
+
   private static void addListeners(@NotNull GitLineHandler handler, @NotNull GitLineHandlerListener... listeners) {
     addListeners(handler, Arrays.asList(listeners));
   }
index 4d97eca8b730a2355a9b9af2bb061c64e4249965..31e78486ffdf20d68c499030cf79a4f32ea6532e 100644 (file)
@@ -152,7 +152,7 @@ public class GitPushSupport extends PushSupport<GitRepository, GitPushSource, Gi
   @NotNull
   @Override
   public PushTargetPanel<GitPushTarget> createTargetPanel(@NotNull GitRepository repository, @Nullable GitPushTarget defaultTarget) {
-    return new GitPushTargetPanel(repository, defaultTarget);
+    return new GitPushTargetPanel(this, repository, defaultTarget);
   }
 
   @Override
index 4161e903eadd2f7c2c0a32dbada97552e471d77d..3565c0d23f959da8a3e891a8936ac54e8b074cfb 100644 (file)
@@ -17,11 +17,17 @@ package git4idea.push;
 
 import com.intellij.dvcs.push.PushTargetPanel;
 import com.intellij.dvcs.push.ui.*;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonShortcuts;
+import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.ui.MessageType;
 import com.intellij.openapi.ui.ValidationInfo;
-import com.intellij.openapi.ui.popup.JBPopupFactory;
-import com.intellij.openapi.ui.popup.ListPopup;
-import com.intellij.openapi.ui.popup.PopupStep;
+import com.intellij.openapi.ui.popup.*;
 import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
 import com.intellij.ui.ColoredTreeCellRenderer;
 import com.intellij.ui.SimpleTextAttributes;
@@ -30,7 +36,11 @@ import com.intellij.ui.components.JBLabel;
 import com.intellij.util.Function;
 import com.intellij.util.ObjectUtils;
 import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.GridBag;
+import com.intellij.util.ui.UIUtil;
 import git4idea.GitRemoteBranch;
+import git4idea.commands.Git;
+import git4idea.commands.GitCommandResult;
 import git4idea.repo.GitRemote;
 import git4idea.repo.GitRepository;
 import org.jetbrains.annotations.NotNull;
@@ -51,7 +61,10 @@ public class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
   private static final Comparator<GitRemoteBranch> REMOTE_BRANCH_COMPARATOR = new MyRemoteBranchComparator();
   private static final String SEPARATOR = " : ";
 
+  @NotNull private final GitPushSupport myPushSupport;
   @NotNull private final GitRepository myRepository;
+  @NotNull private final Git myGit;
+
   @NotNull private final VcsEditableTextComponent myTargetRenderer;
   @NotNull private final PushTargetTextField myTargetEditor;
   @NotNull private final VcsLinkedTextComponent myRemoteRenderer;
@@ -60,54 +73,136 @@ public class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
   @Nullable private String myError;
   @Nullable private Runnable myFireOnChangeAction;
 
-  public GitPushTargetPanel(@NotNull GitRepository repository, @Nullable GitPushTarget defaultTarget) {
+  public GitPushTargetPanel(@NotNull GitPushSupport support, @NotNull GitRepository repository, @Nullable GitPushTarget defaultTarget) {
+    myPushSupport = support;
     myRepository = repository;
+    myGit = ServiceManager.getService(Git.class);
 
-    myCurrentTarget = defaultTarget;
+    myTargetRenderer = new VcsEditableTextComponent("", null);
+    myTargetEditor = new PushTargetTextField(repository.getProject(), getTargetNames(myRepository), "");
+    myRemoteRenderer = new VcsLinkedTextComponent("", new VcsLinkListener() {
+      @Override
+      public void hyperlinkActivated(@NotNull DefaultMutableTreeNode sourceNode, @NotNull MouseEvent event) {
+        if (myRepository.getRemotes().isEmpty()) {
+          showDefineRemotePopup(event);
+        }
+        else {
+          showRemoteSelector(event);
+        }
+      }
+    });
+
+    setLayout(new BorderLayout());
+    setOpaque(false);
+    JPanel remoteAndSeparator = new JPanel(new BorderLayout());
+    remoteAndSeparator.setOpaque(false);
+    remoteAndSeparator.add(myRemoteRenderer, BorderLayout.CENTER);
+    remoteAndSeparator.add(new JBLabel(SEPARATOR), BorderLayout.EAST);
+
+    add(remoteAndSeparator, BorderLayout.WEST);
+    add(myTargetEditor, BorderLayout.CENTER);
+
+    updateComponents(defaultTarget);
+  }
+
+  private void updateComponents(@Nullable GitPushTarget target) {
+    myCurrentTarget = target;
 
     String initialBranch = "";
     String initialRemote = "";
-    if (defaultTarget == null) {
-      if (repository.getCurrentBranch() == null) {
+    boolean noRemotes = myRepository.getRemotes().isEmpty();
+    if (target == null) {
+      if (myRepository.getCurrentBranch() == null) {
         myError = "Detached HEAD";
       }
-      else if (repository.getRemotes().isEmpty()) {
-        myError = "No remotes";
-      }
-      else if (repository.isFresh()) {
+      else if (myRepository.isFresh()) {
         myError = "Empty repository";
       }
-      else {
+      else if (!noRemotes) {
         myError = "Can't push";
       }
     }
     else {
-      initialBranch = getTextFieldText(defaultTarget);
-      initialRemote = defaultTarget.getBranch().getRemote().getName();
+      initialBranch = getTextFieldText(target);
+      initialRemote = target.getBranch().getRemote().getName();
     }
-    myTargetRenderer = new VcsEditableTextComponent("<a href=''>" + initialBranch + "</a>", null);
-    myTargetEditor = new PushTargetTextField(repository.getProject(), getTargetNames(myRepository), initialBranch);
-    myRemoteRenderer = new VcsLinkedTextComponent("<a href=''>" + initialRemote + "</a>", new VcsLinkListener() {
+
+    myTargetRenderer.updateLinkText(initialBranch);
+    myTargetEditor.setText(initialBranch);
+    myRemoteRenderer.updateLinkText(noRemotes ? "Define remote" : initialRemote);
+
+    myTargetEditor.setVisible(!noRemotes);
+  }
+
+  private void showDefineRemotePopup(@NotNull final MouseEvent event) {
+    final JTextField remoteName = new JTextField(GitRemote.ORIGIN_NAME, 20);
+    final JTextField remoteUrl = new JTextField(20);
+    final JBPopup popup = createDefineRemotePopup(remoteName, remoteUrl);
+
+    popup.addListener(new JBPopupListener.Adapter() {
       @Override
-      public void hyperlinkActivated(@NotNull DefaultMutableTreeNode sourceNode, @NotNull MouseEvent event) {
-        showRemoteSelector(event);
+      public void onClosed(final LightweightWindowEvent popupEvent) {
+        if (popupEvent.isOk()) {
+          ProgressManager.getInstance().run(new Task.Modal(myRepository.getProject(), "Adding remote", false) {
+            private GitCommandResult myResult;
+            @Override
+            public void run(@NotNull ProgressIndicator indicator) {
+              myResult = myGit.addRemote(myRepository, remoteName.getText().trim(), remoteUrl.getText().trim());
+              myRepository.update();
+            }
+
+            @Override
+            public void onSuccess() {
+              if (myResult.success()) {
+                updateComponents(myPushSupport.getDefaultTarget(myRepository));
+                if (myFireOnChangeAction != null) {
+                  myFireOnChangeAction.run();
+                }
+              }
+              else {
+                String message = "Couldn't add remote: " + myResult.getErrorOutputAsHtmlString();
+                LOG.warn(message);
+                Balloon balloon = JBPopupFactory.getInstance().createHtmlTextBalloonBuilder(message, MessageType.ERROR, null).createBalloon();
+                balloon.show(new RelativePoint(event),Balloon.Position.above);
+              }
+            }
+          });
+        }
       }
     });
+    popup.show(new RelativePoint(event));
+  }
 
-    setLayout(new BorderLayout());
-    setOpaque(false);
-    JPanel remoteAndSeparator = new JPanel(new BorderLayout());
-    remoteAndSeparator.setOpaque(false);
-    remoteAndSeparator.add(myRemoteRenderer, BorderLayout.CENTER);
-    remoteAndSeparator.add(new JBLabel(SEPARATOR), BorderLayout.EAST);
+  @NotNull
+  private static JBPopup createDefineRemotePopup(@NotNull final JTextField remoteName, @NotNull final JTextField remoteUrl) {
+    JPanel defineRemoteComponent = new JPanel(new GridBagLayout());
+    GridBag gb = new GridBag().
+      setDefaultAnchor(GridBagConstraints.LINE_START).
+      setDefaultInsets(UIUtil.DEFAULT_VGAP, UIUtil.DEFAULT_HGAP, 0, 0);
+    defineRemoteComponent.add(new JBLabel("Name:"), gb.nextLine().next().anchor(GridBagConstraints.EAST));
+    defineRemoteComponent.add(remoteName, gb.next());
+    defineRemoteComponent.add(new JBLabel("URL: "),
+                              gb.nextLine().next().anchor(GridBagConstraints.EAST).insets(0, UIUtil.DEFAULT_HGAP, UIUtil.DEFAULT_VGAP, 0));
+    defineRemoteComponent.add(remoteUrl, gb.next());
 
-    add(remoteAndSeparator, BorderLayout.WEST);
-    add(myTargetEditor, BorderLayout.CENTER);
-    updateTextField();
+    final JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(defineRemoteComponent, remoteUrl).
+      setRequestFocus(true).createPopup();
+
+    new AnAction() {
+      @Override
+      public void actionPerformed(AnActionEvent e) {
+        if (isRemoteDefinitionCorrect(remoteName, remoteUrl)) {
+          popup.closeOk(e.getInputEvent());
+        }
+      }
+    }.registerCustomShortcutSet(CommonShortcuts.ENTER, popup.getContent());
+    return popup;
   }
 
-  private void updateTextField() {
-    myTargetEditor.setVisible(!myRepository.getRemotes().isEmpty());
+  private static boolean isRemoteDefinitionCorrect(@NotNull JTextField remoteName, @NotNull JTextField remoteUrl) {
+    String url = remoteUrl.getText().trim();
+    String name = remoteName.getText().trim();
+    return !url.isEmpty() && !name.isEmpty() && !url.contains(" ") && !name.contains(" ");
   }
 
   private void showRemoteSelector(@NotNull MouseEvent event) {
@@ -147,7 +242,8 @@ public class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
     }
     else {
       String currentRemote = myRemoteRenderer.getText();
-      if (getRemotes().size() > 1) {
+      List<String> remotes = getRemotes();
+      if (remotes.isEmpty() || remotes.size() > 1) {
         myRemoteRenderer.setSelected(isSelected);
         myRemoteRenderer.setTransparent(!isActive);
         myRemoteRenderer.render(renderer);
@@ -155,15 +251,17 @@ public class GitPushTargetPanel extends PushTargetPanel<GitPushTarget> {
       else {
         renderer.append(currentRemote, targetTextAttributes);
       }
-      renderer.append(SEPARATOR, targetTextAttributes);
+      if (!remotes.isEmpty()) {
+        renderer.append(SEPARATOR, targetTextAttributes);
 
-      GitPushTarget target = getValue();
-      if (target.isNewBranchCreated()) {
-        renderer.append("+", PushLogTreeUtil.addTransparencyIfNeeded(SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, isActive), this);
+        GitPushTarget target = getValue();
+        if (target.isNewBranchCreated()) {
+          renderer.append("+", PushLogTreeUtil.addTransparencyIfNeeded(SimpleTextAttributes.SYNTHETIC_ATTRIBUTES, isActive), this);
+        }
+        myTargetRenderer.setSelected(isSelected);
+        myTargetRenderer.setTransparent(!isActive);
+        myTargetRenderer.render(renderer);
       }
-      myTargetRenderer.setSelected(isSelected);
-      myTargetRenderer.setTransparent(!isActive);
-      myTargetRenderer.render(renderer);
     }
   }