git4idea: initial version of git rebase
authorConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Wed, 3 Dec 2008 12:29:48 +0000 (15:29 +0300)
committerConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Wed, 3 Dec 2008 12:29:48 +0000 (15:29 +0300)
32 files changed:
plugins/git4idea/src/META-INF/plugin.xml
plugins/git4idea/src/git4idea/GitBranch.java
plugins/git4idea/src/git4idea/GitReference.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/GitTag.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/actions/GitAbstractRebaseAction.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/actions/GitRebase.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/actions/GitRebaseAbort.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/actions/GitRebaseContinue.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/actions/GitRebaseSkip.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/checkin/GitPushDialog.java
plugins/git4idea/src/git4idea/checkout/GitCheckoutDialog.java
plugins/git4idea/src/git4idea/commands/GitHandler.java
plugins/git4idea/src/git4idea/commands/GitHandlerUtil.java
plugins/git4idea/src/git4idea/commands/ScriptGenerator.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/i18n/GitBundle.properties
plugins/git4idea/src/git4idea/merge/GitMergeUtil.java
plugins/git4idea/src/git4idea/rebase/GitRebaseActionDialog.form [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseActionDialog.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.form [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseEditor.form [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseEditor.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseEditorHandler.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseEditorMain.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseEditorService.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseEntry.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/rebase/GitRebaseUtils.java [new file with mode: 0644]
plugins/git4idea/src/git4idea/ui/GitRefspecAddRefsDialog.java
plugins/git4idea/src/git4idea/ui/GitRefspecPanel.java
plugins/git4idea/src/git4idea/ui/GitUIUtil.java
plugins/git4idea/src/git4idea/ui/GitUnstashDialog.java
plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitSSHService.java

index b04275028f5729cd8456fe6e50de1010eda3f325..5a06d25be8f04c1a282d87596265a02db09ace14 100644 (file)
       <action id="Git.Stash" class="git4idea.actions.GitStash" text="Stash Changes"/>
       <action id="Git.Unstash" class="git4idea.actions.GitUnstash" text="UnStash Changes"/>
       <separator/>
+      <action id="Git.Rebase" class="git4idea.actions.GitRebase" text="Rebase ..."/>
+      <action id="Git.Rebase.Abort" class="git4idea.actions.GitRebaseAbort" text="Abort Rebasing"/>
+      <action id="Git.Rebase.Continue" class="git4idea.actions.GitRebaseContinue" text="Continue Rebasing"/>
+      <action id="Git.Rebase.Skip" class="git4idea.actions.GitRebaseSkip" text="Skip Commit in Rebasing"/>
+      <separator/>
 
       <add-to-group group-id="VcsGroup" anchor="last"/>
       <add-to-group group-id="VcsGroups" anchor="last"/>
@@ -92,6 +97,9 @@
     <applicationService
         serviceInterface="org.jetbrains.git4idea.ssh.GitSSHService"
         serviceImplementation="org.jetbrains.git4idea.ssh.GitSSHService"/>
+    <applicationService
+        serviceInterface="git4idea.rebase.GitRebaseEditorService"
+        serviceImplementation="git4idea.rebase.GitRebaseEditorService"/>
     <ComponentRoamingType component="Git.Settings" type="DISABLED"/>
   </extensions>
 
index 3ccde7858d90208ec468adabe103ddaa012e0944..6ceb566475b5a79ff30c08b773f4cfb2c18d9ba4 100644 (file)
@@ -12,6 +12,7 @@ package git4idea;
  * Copyright 2007 Decentrix Inc
  * Copyright 2007 Aspiro AS
  * Copyright 2008 MQSoftware
+ * Copyright 2008 JetBrains s.r.o.
  * Authors: gevession, Erlend Simonsen & Mark Scott
  *
  * This code was originally derived from the MKS & Mercurial IDEA VCS plugins
@@ -26,46 +27,60 @@ import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.StringTokenizer;
 
 /**
  * This data class represents a Git branch
  */
-public class GitBranch {
-  private final Project project;
-  private final String name;
-  private final boolean remote;
-  private final boolean active;
+public class GitBranch extends GitReference {
   /**
-   * The name that specifies that git is on specific commit rather then on some branch
+   * If true, the branch is remote
    */
-  @NonNls public static final String NO_BRANCH_NAME = "(no " + GitHandler.BRANCH + ")";
-
-  public GitBranch(@NotNull Project project, @NotNull String name, boolean active, boolean remote) {
-    this.project = project;
-    this.name = name;
-    this.remote = remote;
-    this.active = active;
-  }
-
-  @NotNull
-  public Project getProject() {
-    return project;
-  }
+  private final boolean myRemote;
+  /**
+   * If true, the branch is active
+   */
+  private final boolean myActive;
+  /**
+   * The name that specifies that git is on specific commit rather then on some branch ({@value})
+   */
+  @NonNls public static final String NO_BRANCH_NAME = "(no branch)";
+  /**
+   * Prefix for local branches ({@value})
+   */
+  @NonNls public static final String REFS_HEADS_PREFIX = "refs/heads/";
+  /**
+   * Prefix for remote branches ({@value})
+   */
+  @NonNls public static final String REFS_REMOTES_PREFIX = "refs/remotes/";
 
-  @NotNull
-  public String getName() {
-    return name;
+  /**
+   * The constructor for the branch
+   *
+   * @param name   the name of the branch
+   * @param active if true, the branch is active
+   * @param remote if true, the branch is remote
+   */
+  public GitBranch(@NotNull String name, boolean active, boolean remote) {
+    super(name);
+    myRemote = remote;
+    myActive = active;
   }
 
+  /**
+   * @return true if the branch is remtoe
+   */
   public boolean isRemote() {
-    return remote;
+    return myRemote;
   }
 
+  /**
+   * @return true if the branch is active
+   */
   public boolean isActive() {
-    return active;
+    return myActive;
   }
 
   /**
@@ -89,7 +104,7 @@ public class GitBranch {
           return null;
         }
         else {
-          return new GitBranch(project, line.substring(2), true, false);
+          return new GitBranch(line.substring(2), true, false);
         }
       }
     }
@@ -106,11 +121,11 @@ public class GitBranch {
    * @param branches the collection used to store branches
    * @throws VcsException if there is a problem with running git
    */
-  public static void list(final Project project,
-                          final VirtualFile root,
-                          final boolean remote,
-                          final boolean local,
-                          final Collection<String> branches) throws VcsException {
+  public static void listAsStrings(final Project project,
+                                   final VirtualFile root,
+                                   final boolean remote,
+                                   final boolean local,
+                                   final Collection<String> branches) throws VcsException {
     if (!local && !remote) {
       // no need to run hanler
       return;
@@ -133,24 +148,41 @@ public class GitBranch {
   }
 
   /**
-   * List tags for the git root
+   * {@inheritDoc}
+   */
+  @NotNull
+  public String getFullName() {
+    return (myRemote ? REFS_REMOTES_PREFIX : REFS_HEADS_PREFIX) + myName;
+  }
+
+  /**
+   * List branches for the git root
    *
-   * @param project the context
-   * @param root    the git root
-   * @param tags    the tag list
-   * @throws com.intellij.openapi.vcs.VcsException
-   *          if there is a problem with running git
+   * @param project  the context project
+   * @param root     the git root
+   * @param remote   if true remote branches are listed
+   * @param local    if true local branches are listed
+   * @param branches the collection used to store branches
+   * @throws VcsException if there is a problem with running git
    */
-  public static void listTags(final Project project, final VirtualFile root, final List<String> tags) throws VcsException {
-    GitSimpleHandler handler = new GitSimpleHandler(project, root, GitHandler.TAG);
-    handler.setNoSSH(true);
-    handler.setSilent(true);
-    handler.addParameters("-l");
-    for (String line : handler.run().split("\n")) {
-      if (line.length() == 0) {
-        continue;
+  public static void list(final Project project,
+                          final VirtualFile root,
+                          final boolean local,
+                          final boolean remote,
+                          final Collection<GitBranch> branches) throws VcsException {
+    ArrayList<String> temp = new ArrayList<String>();
+    if (local) {
+      listAsStrings(project, root, false, true, temp);
+      for (String b : temp) {
+        branches.add(new GitBranch(b, false, false));
+      }
+      temp.clear();
+    }
+    if (remote) {
+      listAsStrings(project, root, true, false, temp);
+      for (String b : temp) {
+        branches.add(new GitBranch(b, false, true));
       }
-      tags.add(line);
     }
   }
 }
diff --git a/plugins/git4idea/src/git4idea/GitReference.java b/plugins/git4idea/src/git4idea/GitReference.java
new file mode 100644 (file)
index 0000000..e191fdc
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * The base class for named git references
+ */
+public abstract class GitReference implements Comparable<GitReference> {
+  /**
+   * The name of the reference
+   */
+  protected final String myName;
+
+  /**
+   * The constructor
+   *
+   * @param name the used name
+   */
+  public GitReference(@NotNull String name) {
+    myName = name;
+  }
+
+  /**
+   * @return the local name of the reference
+   */
+  @NotNull
+  public String getName() {
+    return myName;
+  }
+
+  /**
+   * @return the full name of the object
+   */
+  @NotNull
+  public abstract String getFullName();
+
+  /**
+   * @return the full name for the reference ({@link #getFullName()}.
+   */
+  @Override
+  public String toString() {
+    return getFullName();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean equals(final Object obj) {
+    return obj instanceof GitReference &&
+           toString().equals(obj.toString());    //To change body of overridden methods use File | Settings | File Templates.
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int hashCode() {
+    return toString().hashCode();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public int compareTo(final GitReference o) {
+    return o == null ? 1 : getFullName().compareTo(o.getFullName());
+  }
+
+  /**
+   * Get name clashes for the for the sequence of the collections
+   *
+   * @param collections the collection list
+   * @return the conflict set
+   */
+  public static Set<String> getNameClashes(Collection<? extends GitReference>... collections) {
+    ArrayList<HashSet<String>> individual = new ArrayList<HashSet<String>>();
+    // collect individual key sets
+    for (Collection<? extends GitReference> c : collections) {
+      HashSet<String> s = new HashSet<String>();
+      individual.add(s);
+      for (GitReference r : c) {
+        s.add(r.getName());
+      }
+    }
+    HashSet<String> rc = new HashSet<String>();
+    // all pairs from array
+    for (int i = 0; i < collections.length - 1; i++) {
+      HashSet<String> si = individual.get(i);
+      for (int j = i + 1; j < collections.length; j++) {
+        HashSet<String> sj = individual.get(i);
+        final HashSet<String> copy = new HashSet<String>(si);
+        copy.retainAll(sj);
+        rc.addAll(copy);
+      }
+    }
+    return rc;
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/GitTag.java b/plugins/git4idea/src/git4idea/GitTag.java
new file mode 100644 (file)
index 0000000..546378d
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.commands.GitHandler;
+import git4idea.commands.GitSimpleHandler;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * The tag reference object
+ */
+public class GitTag extends GitReference {
+  /**
+   * Prefix for tags ({@value})
+   */
+  @NonNls public static final String REFS_TAGS_PREFIX = "refs/tags/";
+
+  /**
+   * The constructor
+   *
+   * @param name the used name
+   */
+  public GitTag(@NotNull String name) {
+    super(name);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @NotNull
+  public String getFullName() {
+    return REFS_TAGS_PREFIX + myName;
+  }
+
+  /**
+   * List tags for the git root
+   *
+   * @param project the context
+   * @param root    the git root
+   * @param tags    the tag list
+   * @throws com.intellij.openapi.vcs.VcsException
+   *          if there is a problem with running git
+   */
+  public static void listAsStrings(final Project project, final VirtualFile root, final Collection<String> tags) throws VcsException {
+    GitSimpleHandler handler = new GitSimpleHandler(project, root, GitHandler.TAG);
+    handler.setNoSSH(true);
+    handler.setSilent(true);
+    handler.addParameters("-l");
+    for (String line : handler.run().split("\n")) {
+      if (line.length() == 0) {
+        continue;
+      }
+      tags.add(line);
+    }
+  }
+
+  /**
+   * List tags for the git root
+   *
+   * @param project the context
+   * @param root    the git root
+   * @param tags    the tag list
+   * @throws com.intellij.openapi.vcs.VcsException
+   *          if there is a problem with running git
+   */
+  public static void list(final Project project, final VirtualFile root, final Collection<? super GitTag> tags) throws VcsException {
+    ArrayList<String> temp = new ArrayList<String>();
+    listAsStrings(project, root, temp);
+    for (String t : temp) {
+      tags.add(new GitTag(t));
+    }
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/actions/GitAbstractRebaseAction.java b/plugins/git4idea/src/git4idea/actions/GitAbstractRebaseAction.java
new file mode 100644 (file)
index 0000000..330a863
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.actions;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.commands.GitHandler;
+import git4idea.commands.GitHandlerUtil;
+import git4idea.commands.GitSimpleHandler;
+import git4idea.i18n.GitBundle;
+import git4idea.rebase.GitRebaseActionDialog;
+import git4idea.rebase.GitRebaseUtils;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Base class for git rebase [--skip, --abort, and --continue] actions
+ */
+public abstract class GitAbstractRebaseAction extends GitRepositoryAction {
+
+  /**
+   * {@inheritDoc}
+   */
+  protected void perform(@NotNull Project project,
+                         @NotNull List<VirtualFile> gitRoots,
+                         @NotNull VirtualFile defaultRoot,
+                         Set<VirtualFile> affectedRoots,
+                         List<VcsException> exceptions) throws VcsException {
+    // remote all roots where there are no rebase in progress
+    for (Iterator<VirtualFile> i = gitRoots.iterator(); i.hasNext();) {
+      if (!GitRebaseUtils.isRebaseInTheProgress(i.next())) {
+        i.remove();
+      }
+    }
+    if (gitRoots.size() == 0) {
+      Messages.showErrorDialog(project, GitBundle.getString("rebase.action.no.root"), GitBundle.getString("rebase.action.error"));
+      return;
+    }
+    final VirtualFile root;
+    if (gitRoots.size() == 1) {
+      root = gitRoots.get(0);
+    }
+    else {
+      if (!gitRoots.contains(defaultRoot)) {
+        defaultRoot = gitRoots.get(0);
+      }
+      GitRebaseActionDialog d = new GitRebaseActionDialog(project, getActionTitle(), gitRoots, defaultRoot);
+      root = d.selectRoot();
+      if (root == null) {
+        return;
+      }
+    }
+    GitSimpleHandler h = new GitSimpleHandler(project, root, GitHandler.REBASE);
+    h.addParameters(getOptionName());
+    GitHandlerUtil.doSynchronously(h, getActionTitle(), h.printableCommandLine());
+  }
+
+  /**
+   * @return title for rebase operation
+   */
+  @NonNls
+  protected abstract String getOptionName();
+
+  /**
+   * @return title for root selection dialog
+   */
+  protected abstract String getActionTitle();
+}
diff --git a/plugins/git4idea/src/git4idea/actions/GitRebase.java b/plugins/git4idea/src/git4idea/actions/GitRebase.java
new file mode 100644 (file)
index 0000000..cc769b3
--- /dev/null
@@ -0,0 +1,65 @@
+package git4idea.actions;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.commands.GitHandler;
+import git4idea.commands.GitHandlerUtil;
+import git4idea.commands.GitLineHandler;
+import git4idea.i18n.GitBundle;
+import git4idea.rebase.GitRebaseDialog;
+import git4idea.rebase.GitRebaseEditorHandler;
+import git4idea.rebase.GitRebaseEditorMain;
+import git4idea.rebase.GitRebaseEditorService;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Git rebase action
+ */
+public class GitRebase extends GitRepositoryAction {
+
+  /**
+   * {@inheritDoc}
+   */
+  @NotNull
+  protected String getActionName() {
+    return GitBundle.getString("rebase.action.name");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  protected void perform(@NotNull final Project project,
+                         @NotNull final List<VirtualFile> gitRoots,
+                         @NotNull final VirtualFile defaultRoot,
+                         final Set<VirtualFile> affectedRoots,
+                         final List<VcsException> exceptions) throws VcsException {
+    GitRebaseDialog dialog = new GitRebaseDialog(project, gitRoots, defaultRoot);
+    dialog.show();
+    if (!dialog.isOK()) {
+      return;
+    }
+    GitLineHandler h = dialog.handler();
+    GitRebaseEditorService service = GitRebaseEditorService.getInstance();
+    GitRebaseEditorHandler editor = service.getHandler(project, dialog.gitRoot());
+    affectedRoots.add(dialog.gitRoot());
+    try {
+      String editorPath = service.getScriptPath().getPath();
+      if (SystemInfo.isWindows) {
+        editorPath = editorPath.replace('\\', '/');
+      }
+      h.setenv(GitHandler.GIT_EDITOR_ENV, editorPath);
+      h.setenv(GitRebaseEditorMain.IDEA_REBASE_HANDER_NO, Integer.toString(editor.getHandlerNo()));
+      GitHandlerUtil.doSynchronously(h, GitBundle.getString("rebasing.title"), h.printableCommandLine());
+    }
+    finally {
+      editor.close();
+    }
+
+    //To change body of implemented methods use File | Settings | File Templates.
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/actions/GitRebaseAbort.java b/plugins/git4idea/src/git4idea/actions/GitRebaseAbort.java
new file mode 100644 (file)
index 0000000..ff8f533
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.actions;
+
+import git4idea.i18n.GitBundle;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Rebase abort action
+ */
+public class GitRebaseAbort extends GitAbstractRebaseAction {
+
+  /**
+   * {@inheritDoc}
+   */
+  @NotNull
+  protected String getActionName() {
+    return GitBundle.getString("rebase.abort.action.name");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @NonNls
+  protected String getOptionName() {
+    return "--abort";
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  protected String getActionTitle() {
+    return GitBundle.getString("rebase.abort.action.name");
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/actions/GitRebaseContinue.java b/plugins/git4idea/src/git4idea/actions/GitRebaseContinue.java
new file mode 100644 (file)
index 0000000..d615048
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.actions;
+
+import git4idea.i18n.GitBundle;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Rebase abort action
+ */
+public class GitRebaseContinue extends GitAbstractRebaseAction {
+
+  /**
+   * {@inheritDoc}
+   */
+  @NotNull
+  protected String getActionName() {
+    return GitBundle.getString("rebase.continue.action.name");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @NonNls
+  protected String getOptionName() {
+    return "--continue";
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  protected String getActionTitle() {
+    return GitBundle.getString("rebase.continue.action.name");
+  }
+}
\ No newline at end of file
diff --git a/plugins/git4idea/src/git4idea/actions/GitRebaseSkip.java b/plugins/git4idea/src/git4idea/actions/GitRebaseSkip.java
new file mode 100644 (file)
index 0000000..8583e5c
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.actions;
+
+import git4idea.i18n.GitBundle;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * Rebase abort action
+ */
+public class GitRebaseSkip extends GitAbstractRebaseAction {
+
+  /**
+   * {@inheritDoc}
+   */
+  @NotNull
+  protected String getActionName() {
+    return GitBundle.getString("rebase.skip.action.name");
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @NonNls
+  protected String getOptionName() {
+    return "--skip";
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  protected String getActionTitle() {
+    return GitBundle.getString("rebase.skip.action.name");
+  }
+}
\ No newline at end of file
index a0d47661165a1d74e3ec60331cb3198d78efce24..15fde5005fe22c4b3f5205d6f76770d56c7ea82e 100644 (file)
@@ -26,6 +26,7 @@ import com.intellij.ui.DocumentAdapter;
 import com.intellij.util.containers.HashMap;
 import git4idea.GitBranch;
 import git4idea.GitRemote;
+import git4idea.GitTag;
 import git4idea.commands.GitHandler;
 import git4idea.commands.GitLineHandler;
 import git4idea.config.GitConfigUtil;
@@ -359,8 +360,8 @@ public class GitPushDialog extends DialogWrapper {
         myBranchNames.clear();
         myTagNames.clear();
         try {
-          GitBranch.list(myProject, getGitRoot(), false, true, myBranchNames);
-          GitBranch.listTags(myProject, getGitRoot(), myTagNames);
+          GitBranch.listAsStrings(myProject, getGitRoot(), false, true, myBranchNames);
+          GitTag.listAsStrings(myProject, getGitRoot(), myTagNames);
         }
         catch (VcsException ex) {
           LOG.warn("Exception in branchlist: \n" + StringUtil.getThrowableText(ex));
index 3ef32a91246e2d9dc87340580f3e83b6ee80cd6a..e06c110d6b27ec97734d14d5c768052ff958704b 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.ui.DocumentAdapter;
 import git4idea.GitBranch;
+import git4idea.GitTag;
 import git4idea.GitVcs;
 import git4idea.commands.GitHandler;
 import git4idea.commands.GitLineHandler;
@@ -238,14 +239,14 @@ public class GitCheckoutDialog extends DialogWrapper {
         try {
           List<String> branchesAndTags = new ArrayList<String>();
           // get branches
-          GitBranch.list(myProject, gitRoot(), true, true, branchesAndTags);
+          GitBranch.listAsStrings(myProject, gitRoot(), true, true, branchesAndTags);
           existingBranches.clear();
           existingBranches.addAll(branchesAndTags);
           Collections.sort(branchesAndTags);
           // get tags
           if (myIncludeTagsCheckBox.isSelected()) {
             int mark = branchesAndTags.size();
-            GitBranch.listTags(myProject, gitRoot(), branchesAndTags);
+            GitTag.listAsStrings(myProject, gitRoot(), branchesAndTags);
             Collections.sort(branchesAndTags.subList(mark, branchesAndTags.size()));
           }
           myBranchToCkeckout.removeAllItems();
index 8ceb0c4234af1ca074231beac10425e0cb194363..2f1f977345daef980e52f07afe7ae2c2e8f43d68 100644 (file)
@@ -210,10 +210,22 @@ public abstract class GitHandler {
    * The constant for git command {@value}
    */
   @NonNls public static final String STASH = "stash";
+  /**
+   * The constant for git command {@value}
+   */
+  @NonNls public static final String REBASE = "rebase";
+  /**
+   * Name of environement variable that specifies editor for the git
+   */
+  @NonNls public static final String GIT_EDITOR_ENV = "GIT_EDITOR";
   /**
    * The vcs object
    */
   protected final GitVcs myVcs;
+  /**
+   * The environment
+   */
+  private final Map<String, String> myEnv;
 
   /**
    * A constructor
@@ -225,6 +237,7 @@ public abstract class GitHandler {
   protected GitHandler(@NotNull Project project, @NotNull File directory, @NotNull String command) {
     myProject = project;
     GitVcsSettings settings = GitVcsSettings.getInstance(project);
+    myEnv = new HashMap<String, String>(System.getenv());
     myVcs = GitVcs.getInstance(project);
     if (myVcs != null) {
       myVcs.checkVersion();
@@ -438,15 +451,14 @@ public abstract class GitHandler {
       if (log.isDebugEnabled()) {
         log.debug("running git: " + myCommandLine.getCommandLineString() + " in " + myWorkingDirectory);
       }
-      final Map<String, String> env = new HashMap<String, String>(System.getenv());
       if (!myNoSSHFlag) {
         GitSSHService ssh = GitSSHService.getInstance();
-        env.put(GitSSHService.GIT_SSH_ENV, ssh.getScriptPath().getPath());
+        myEnv.put(GitSSHService.GIT_SSH_ENV, ssh.getScriptPath().getPath());
         myHandlerNo = ssh.registerHandler(new GitSSHGUIHandler(myProject));
         myEnvironmentCleanedUp = false;
-        env.put(GitSSHService.SSH_HANDLER_ENV, Integer.toString(myHandlerNo));
+        myEnv.put(GitSSHService.SSH_HANDLER_ENV, Integer.toString(myHandlerNo));
       }
-      myCommandLine.setEnvParams(env);
+      myCommandLine.setEnvParams(myEnv);
       // start process
       myProcess = myCommandLine.createProcess();
       myHandler = new OSProcessHandler(myProcess, myCommandLine.getCommandLineString()) {
@@ -627,6 +639,16 @@ public abstract class GitHandler {
     myStderrSuppressed = stderrSuppressed;
   }
 
+  /**
+   * Set environement variable
+   *
+   * @param name  the variable name
+   * @param value the varianble value
+   */
+  public void setenv(String name, String value) {
+    myEnv.put(name, value);
+  }
+
   /**
    * Translate parameter
    *
index 3cd161b3158e2abb035966825eadab34e292bf04..ea5286b3b19cc677602a0dabc9678c4c8301360f 100644 (file)
@@ -285,7 +285,8 @@ public class GitHandlerUtil {
     /**
      * Error indicators for the line
      */
-    @NonNls private static final String[] ERROR_INIDCATORS = {"ERROR:", "error:", "FATAL:", "fatal:", "Cannot apply"};
+    @NonNls private static final String[] ERROR_INIDCATORS =
+      {"ERROR:", "error:", "FATAL:", "fatal:", "Cannot apply", "Could not", "Interactive rebase already started"};
 
     /**
      * Check if the line is an error line
diff --git a/plugins/git4idea/src/git4idea/commands/ScriptGenerator.java b/plugins/git4idea/src/git4idea/commands/ScriptGenerator.java
new file mode 100644 (file)
index 0000000..eb99abe
--- /dev/null
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.commands;
+
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Script generator utility class. It uses to generate a temporary scripts that
+ * are removed after application ends.
+ */
+public class ScriptGenerator {
+  /**
+   * The extension of the ssh script name
+   */
+  @NonNls public static final String SCRIPT_EXT;
+
+  static {
+    if (SystemInfo.isWindows) {
+      SCRIPT_EXT = ".cmd";
+    }
+    else {
+      SCRIPT_EXT = ".sh";
+    }
+  }
+
+  /**
+   * The script prefix
+   */
+  private final String myPrefix;
+  /**
+   * The scripts may class
+   */
+  private final Class myMainClass;
+  /**
+   * The class paths for the script
+   */
+  private final ArrayList<String> myPaths = new ArrayList<String>();
+  /**
+   * The internal parameteers for the script
+   */
+  private final ArrayList<String> myInternalParameters = new ArrayList<String>();
+
+  /**
+   * A constructor
+   *
+   * @param prefix    the script prefix
+   * @param mainClass the script main class
+   */
+  public ScriptGenerator(final String prefix, final Class mainClass) {
+    myPrefix = prefix;
+    myMainClass = mainClass;
+    addClasses(myMainClass);
+  }
+
+  /**
+   * Add jar or directory that contains the class to the classpath
+   *
+   * @param classes classes which sources will be added
+   * @return this script generator
+   */
+  public ScriptGenerator addClasses(final Class... classes) {
+    for (Class<?> c : classes) {
+      addPath(PathUtil.getJarPathForClass(c));
+    }
+    return this;
+  }
+
+  /**
+   * Add path to class path. The methods checks if the path has been already added to the classpath.
+   *
+   * @param path the path to add
+   */
+  private void addPath(final String path) {
+    if (!myPaths.contains(path)) {
+      // the size of path is expceted to be quite small, so no optimization is done here
+      myPaths.add(path);
+    }
+  }
+
+  /**
+   * Add source for the specified resource
+   *
+   * @param base     the resource base
+   * @param resource the resource name
+   * @return this script generator
+   */
+  public ScriptGenerator addResource(final Class base, @NonNls String resource) {
+    addPath(getJarForResource(base, resource));
+    return this;
+  }
+
+  /**
+   * Add internal parameters for the script
+   *
+   * @param params internal parameters
+   * @return this script generator
+   */
+  public ScriptGenerator addInternal(String... params) {
+    myInternalParameters.addAll(Arrays.asList(params));
+    return this;
+  }
+
+  /**
+   * Generate script according to specified parameters
+   *
+   * @return the path to generated script
+   * @throws IOException if there is a problem with creating script
+   */
+  @SuppressWarnings({"HardCodedStringLiteral"})
+  public File generate() throws IOException {
+    File scriptPath = File.createTempFile(myPrefix, SCRIPT_EXT);
+    scriptPath.deleteOnExit();
+    PrintWriter out = new PrintWriter(new FileWriter(scriptPath));
+    try {
+      if (SystemInfo.isWindows) {
+        out.println("@echo off");
+      }
+      else {
+        out.println("#!/bin/sh");
+      }
+      out.print(System.getProperty("java.home") + File.separatorChar + "bin" + File.separatorChar + "java -cp \"");
+      boolean first = true;
+      for (String p : myPaths) {
+        if (!first) {
+          out.print(File.pathSeparatorChar);
+        }
+        else {
+          first = false;
+        }
+        out.print(p);
+      }
+      out.print("\" ");
+      out.print(myMainClass.getName());
+      for (String p : myInternalParameters) {
+        out.print(' ');
+        out.print(p);
+      }
+      if (SystemInfo.isWindows) {
+        out.println(" %1 %2 %3 %4 %5 %6 %7 %8 %9");
+      }
+      else {
+        out.println(" \"$@\"");
+      }
+    }
+    finally {
+      out.close();
+    }
+    FileUtil.setExectuableAttribute(scriptPath.getAbsolutePath(), true);
+    return scriptPath;
+  }
+
+  /**
+   * Get path for resources.jar
+   *
+   * @param context a context class
+   * @param res     a resource
+   * @return a path to classpath entry
+   */
+  @SuppressWarnings({"SameParameterValue"})
+  public static String getJarForResource(Class context, String res) {
+    String resourceRoot = PathManager.getResourceRoot(context, res);
+    return new File(resourceRoot).getAbsoluteFile().getAbsolutePath();
+  }
+}
index b30168fb05d0abbe5d76a6c7316c109b9aa5bef5..e53e52a79930b7f21f8eca8d39246aee73712481 100644 (file)
@@ -163,6 +163,51 @@ push.title=Push Changes
 push.use.thin.pack.tooltip=If this option is selected, the push will spend extra CPU cycles to minimize amount of data transferred (use it for slow connections)
 push.use.thin.pack=&Use thin pack
 pushing.all.changes=Pushing all commited changes, refs & tags to remote repos
+rebase.abort.action.name=Abort Rebasing
+rebase.action.error=Git Rebase Error
+rebase.action.message=Mulitple git roots have unfinished rebase process, please select root to perform action on.
+rebase.action.name=Rebase
+rebase.action.no.root=There is no rebase operation in progress in the project
+rebase.branch.tooltip=Select branch to rebase (if branch is different from the current branch, it will be checked out first)
+rebase.branch=&Branch:
+rebase.continue.action.name=Continue Rebasing
+rebase.editor.action.column=Action
+rebase.editor.comment.column=Comment
+rebase.editor.commit.column=Commit
+rebase.editor.invalid.squash=There should be at least two consequent squashed commits
+rebase.editor.message=Reorder and edit &rebased commits
+rebase.editor.move.down.tooltip=Move commit down (commit will be applied later)
+rebase.editor.move.down=Move &Down
+rebase.editor.move.up.tooltip=Move commit up in the list (commit will be applied earlier)
+rebase.editor.move.up=Move &Up
+rebase.editor.title=Rebasing commits
+rebase.editor.view.tooltip=View commit contents
+rebase.editor.view=&View
+rebase.from.tooltip=Sepecify actual base for the branch. Leave blank to onto.
+rebase.from=&From:
+rebase.in.progress=Interactive rebase has been already started for this git root.
+rebase.interactive.tooltip=If selected, the interactive rebase will be preformed.
+rebase.interactive=&Interactive
+rebase.invalid.from=\"From\" reference expression is invalid.
+rebase.invalid.onto=\"Onto\" reference expression is invalid.
+rebase.merge.strategy.tooltip=Select merge strategy to use
+rebase.merge.strategy=Merge &Strategy:
+rebase.no.merge.tooltip=If selected, no merge strategies will be applied during the rebase.
+rebase.no.merge=&Do not use merge strategies
+rebase.onto.tooltip=The reference that will become a new base for selected branch.
+rebase.onto.validate=&Validate
+rebase.onto=&Onto:
+rebase.preserve.merges.tooltip=Preserve merges during rebease instead of squashing them.
+rebase.preserve.merges=&Preserve Merges
+rebase.show.remote.branches.tooltip=If selected, remote branches are shown in drop down as well.
+rebase.show.remote.branches=Show Re&mote Branches
+rebase.show.tags.tooltip=Show tags in \"from\" and \"onto\" dropdowns.
+rebase.skip.action.name=Skip Commit in Rebasing
+rebase.title=Rebase branch
+rebase.valdate.onto.tooltip=Valdate "onto" reference.
+rebase.validate.from.tooltip=Validate \"from\" reference
+rebase.validate.from=Va&lidate
+rebasing.title=Rebasing...
 refspec.add.all.branches.tooltip=Add refspec that maps all remote branches by glob spec.
 refspec.add.all.branches=Add A&ll Branches
 refspec.add.all.tags.tooltip=Adds mapping entry for all tags
@@ -181,6 +226,7 @@ refspec.remove=Remo&ve
 refspec.title=Reference mapping
 refspec.validation.remote.invalid=The invalid local name for remote
 refspec.validation.remote.is.blank=The local name for remote is blank
+regase.show.tags=Show &tags
 repository.action.missing.roots.misconfigured=Neither of configured git roots are is under git. The configured directory or some of its ancestors must have ".git" directory in it.
 repository.action.missing.roots.title=No git roots
 repository.action.missing.roots.unconfigured.message=No git roots are configured for the project.
index 79b6e9d221b6d98f18332cd472cabb993e096194..f628d5a2ab87822de022588144e45b7a7c5b73a5 100644 (file)
@@ -55,7 +55,7 @@ public class GitMergeUtil {
    * @return an array of strategy names
    */
   @NonNls
-  private static String[] getMergeStrategies(int branchCount) {
+  public static String[] getMergeStrategies(int branchCount) {
     if (branchCount < 0) {
       throw new IllegalArgumentException("Brach count must be non-negative: " + branchCount);
     }
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseActionDialog.form b/plugins/git4idea/src/git4idea/rebase/GitRebaseActionDialog.form
new file mode 100644 (file)
index 0000000..f3d8c31
--- /dev/null
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.rebase.GitRebaseActionDialog">
+  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+    <margin top="0" left="0" bottom="0" right="0"/>
+    <constraints>
+      <xy x="20" y="20" width="440" height="72"/>
+    </constraints>
+    <properties/>
+    <border type="none"/>
+    <children>
+      <component id="19057" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.action.message"/>
+        </properties>
+      </component>
+      <component id="cc3bb" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <labelFor value="13488"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="common.git.root"/>
+        </properties>
+      </component>
+      <vspacer id="2ef5c">
+        <constraints>
+          <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+        </constraints>
+      </vspacer>
+      <component id="13488" class="javax.swing.JComboBox" binding="myGitRootComboBox" default-binding="true">
+        <constraints>
+          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="common.git.root.tooltip"/>
+        </properties>
+      </component>
+    </children>
+  </grid>
+</form>
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseActionDialog.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseActionDialog.java
new file mode 100644 (file)
index 0000000..946ed05
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.ui.GitUIUtil;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.List;
+
+/**
+ * The rebase action dialog
+ */
+public class GitRebaseActionDialog extends DialogWrapper {
+  /**
+   * The root selector
+   */
+  private JComboBox myGitRootComboBox;
+  /**
+   * The root panel
+   */
+  private JPanel myPanel;
+
+  /**
+   * A constructor
+   *
+   * @param project     a project to select
+   * @param roots       a git repository roots for the project
+   * @param defaultRoot a guessed default root
+   */
+  public GitRebaseActionDialog(Project project, String title, List<VirtualFile> roots, VirtualFile defaultRoot) {
+    super(project, true);
+    GitUIUtil.setupRootChooser(project, roots, defaultRoot, myGitRootComboBox, null);
+    setTitle(title);
+    init();
+  }
+
+
+  /**
+   * Show dialog and select root
+   *
+   * @return selected root or null if the dialog has been cancelled
+   */
+  @Nullable
+  public VirtualFile selectRoot() {
+    show();
+    return isOK() ? (VirtualFile)myGitRootComboBox.getSelectedItem() : null;
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  protected JComponent createCenterPanel() {
+    return myPanel;
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.form b/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.form
new file mode 100644 (file)
index 0000000..e83dfa0
--- /dev/null
@@ -0,0 +1,217 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.rebase.GitRebaseDialog">
+  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="8" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+    <margin top="0" left="0" bottom="0" right="0"/>
+    <constraints>
+      <xy x="20" y="20" width="583" height="255"/>
+    </constraints>
+    <properties/>
+    <border type="none"/>
+    <children>
+      <component id="76105" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <labelFor value="bebca"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="common.git.root"/>
+        </properties>
+      </component>
+      <vspacer id="67a13">
+        <constraints>
+          <grid row="7" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+        </constraints>
+      </vspacer>
+      <component id="bebca" class="javax.swing.JComboBox" binding="myGitRootComboBox" default-binding="true">
+        <constraints>
+          <grid row="0" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="common.git.root.tooltip"/>
+        </properties>
+      </component>
+      <component id="141cd" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <labelFor value="e8e3d"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.branch"/>
+        </properties>
+      </component>
+      <component id="e8e3d" class="javax.swing.JComboBox" binding="myBranchComboBox" default-binding="true">
+        <constraints>
+          <grid row="1" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.branch.tooltip"/>
+        </properties>
+      </component>
+      <component id="2b513" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <labelFor value="b3800"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.onto"/>
+        </properties>
+      </component>
+      <component id="b3800" class="javax.swing.JComboBox" binding="myOntoComboBox" default-binding="true">
+        <constraints>
+          <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <editable value="true"/>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.onto.tooltip"/>
+        </properties>
+      </component>
+      <component id="ddd04" class="javax.swing.JButton" binding="myOntoValidateButton">
+        <constraints>
+          <grid row="3" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.onto.validate"/>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.valdate.onto.tooltip"/>
+        </properties>
+      </component>
+      <component id="cb44a" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <labelFor value="b0f9a"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.merge.strategy"/>
+        </properties>
+      </component>
+      <component id="4aaae" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <labelFor value="438d6"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.from"/>
+        </properties>
+      </component>
+      <component id="438d6" class="javax.swing.JComboBox" binding="myFromComboBox">
+        <constraints>
+          <grid row="4" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <editable value="true"/>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.from.tooltip"/>
+        </properties>
+      </component>
+      <component id="775c4" class="javax.swing.JButton" binding="myFromValidateButton">
+        <constraints>
+          <grid row="4" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.validate.from"/>
+          <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.validate.from.tooltip"/>
+        </properties>
+      </component>
+      <grid id="f939d" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="6" column="1" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <component id="b0f9a" class="javax.swing.JComboBox" binding="myMergeStrategyComboBox" default-binding="true">
+            <constraints>
+              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.merge.strategy.tooltip"/>
+            </properties>
+          </component>
+          <hspacer id="1ba1f">
+            <constraints>
+              <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+          </hspacer>
+          <component id="3a627" class="javax.swing.JCheckBox" binding="myDoNotUseMergeCheckBox" default-binding="true">
+            <constraints>
+              <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="1" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <selected value="false"/>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.no.merge"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.no.merge.tooltip"/>
+            </properties>
+          </component>
+        </children>
+      </grid>
+      <grid id="63e88" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="2" column="1" row-span="1" col-span="2" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <hspacer id="7eb2f">
+            <constraints>
+              <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+          </hspacer>
+          <component id="e5f3b" class="javax.swing.JCheckBox" binding="myPreserveMergesCheckBox" default-binding="true">
+            <constraints>
+              <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <selected value="true"/>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.preserve.merges"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.preserve.merges.tooltip"/>
+            </properties>
+          </component>
+          <component id="281fe" class="javax.swing.JCheckBox" binding="myInteractiveCheckBox" default-binding="true">
+            <constraints>
+              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <enabled value="true"/>
+              <selected value="true"/>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.interactive"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.interactive.tooltip"/>
+            </properties>
+          </component>
+        </children>
+      </grid>
+      <grid id="46bb8" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="5" column="1" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <component id="de965" class="javax.swing.JCheckBox" binding="myShowTagsCheckBox" default-binding="true">
+            <constraints>
+              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="regase.show.tags"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.show.tags.tooltip"/>
+            </properties>
+          </component>
+          <component id="a0269" class="javax.swing.JCheckBox" binding="myShowRemoteBranchesCheckBox" default-binding="true">
+            <constraints>
+              <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.show.remote.branches"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.show.remote.branches.tooltip"/>
+            </properties>
+          </component>
+          <hspacer id="b158">
+            <constraints>
+              <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+          </hspacer>
+        </children>
+      </grid>
+    </children>
+  </grid>
+</form>
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseDialog.java
new file mode 100644 (file)
index 0000000..7fcaaa6
--- /dev/null
@@ -0,0 +1,393 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.DocumentAdapter;
+import git4idea.GitBranch;
+import git4idea.GitTag;
+import git4idea.commands.GitHandler;
+import git4idea.commands.GitLineHandler;
+import git4idea.config.GitConfigUtil;
+import git4idea.i18n.GitBundle;
+import git4idea.merge.GitMergeUtil;
+import git4idea.ui.GitReferenceValidator;
+import git4idea.ui.GitUIUtil;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The dialog that allows initiating git rebase activity
+ */
+public class GitRebaseDialog extends DialogWrapper {
+  /**
+   * Git root selector
+   */
+  private JComboBox myGitRootComboBox;
+  /**
+   * The selector for branch to rebase
+   */
+  private JComboBox myBranchComboBox;
+  /**
+   * The from branch combo box. This is used as base branch if different from onto branch
+   */
+  private JComboBox myFromComboBox;
+  /**
+   * The validation button for from branch
+   */
+  private JButton myFromValidateButton;
+  /**
+   * The onto branch combobox.
+   */
+  private JComboBox myOntoComboBox;
+  /**
+   * The validate button for onto branch
+   */
+  private JButton myOntoValidateButton;
+  /**
+   * Show tags in drop down
+   */
+  private JCheckBox myShowTagsCheckBox;
+  /**
+   * Merge strategy drop down
+   */
+  private JComboBox myMergeStrategyComboBox;
+  /**
+   * If selected, rebase is interactive
+   */
+  private JCheckBox myInteractiveCheckBox;
+  /**
+   * No merges are performed if selected.
+   */
+  private JCheckBox myDoNotUseMergeCheckBox;
+  /**
+   * The root panel of the dialog
+   */
+  private JPanel myPanel;
+  /**
+   * If selected, remote branches are shown as well
+   */
+  private JCheckBox myShowRemoteBranchesCheckBox;
+  /**
+   * Preserve mereges checkbox
+   */
+  private JCheckBox myPreserveMergesCheckBox;
+  /**
+   * The current project
+   */
+  private Project myProject;
+  /**
+   * The list of local branches
+   */
+  private List<GitBranch> myLocalBranches = new ArrayList<GitBranch>();
+  /**
+   * The list of remote branches
+   */
+  private List<GitBranch> myRemoteBranches = new ArrayList<GitBranch>();
+  /**
+   * The current branch
+   */
+  private GitBranch myCurrentBranch;
+  /**
+   * The tags
+   */
+  private List<GitTag> myTags = new ArrayList<GitTag>();
+  /**
+   * The validator for onto field
+   */
+  private final GitReferenceValidator myOntoValidator;
+  /**
+   * The validator for from field
+   */
+  private final GitReferenceValidator myFromValidator;
+
+  /**
+   * A constructor
+   *
+   * @param project     a project to select
+   * @param roots       a git repository roots for the project
+   * @param defaultRoot a guessed default root
+   */
+  public GitRebaseDialog(Project project, List<VirtualFile> roots, VirtualFile defaultRoot) {
+    super(project, true);
+    setTitle(GitBundle.getString("rebase.title"));
+    init();
+    myProject = project;
+    final Runnable validateRunnable = new Runnable() {
+      public void run() {
+        validateFields();
+      }
+    };
+    myOntoValidator = new GitReferenceValidator(myProject, myGitRootComboBox, GitUIUtil.getTextField(myOntoComboBox), myOntoValidateButton,
+                                                validateRunnable);
+    myFromValidator = new GitReferenceValidator(myProject, myGitRootComboBox, GitUIUtil.getTextField(myFromComboBox), myFromValidateButton,
+                                                validateRunnable);
+    GitUIUtil.setupRootChooser(myProject, roots, defaultRoot, myGitRootComboBox, null);
+    myGitRootComboBox.addActionListener(new ActionListener() {
+      public void actionPerformed(ActionEvent e) {
+        validateFields();
+      }
+    });
+    setupBranches();
+    setupStrategy();
+    validateFields();
+  }
+
+  public GitLineHandler handler() {
+    GitLineHandler h = new GitLineHandler(myProject, gitRoot(), GitHandler.REBASE);
+    h.setNoSSH(true);
+    if (myInteractiveCheckBox.isSelected() && myInteractiveCheckBox.isEnabled()) {
+      h.addParameters("-i");
+    }
+    h.addParameters("-v");
+    if (!myDoNotUseMergeCheckBox.isSelected()) {
+      if (myMergeStrategyComboBox.getSelectedItem().equals(GitMergeUtil.DEFAULT_STRATEGY)) {
+        h.addParameters("-m");
+      }
+      else {
+        h.addParameters("-s", myMergeStrategyComboBox.getSelectedItem().toString());
+      }
+    }
+    if (myPreserveMergesCheckBox.isSelected()) {
+      h.addParameters("-p");
+    }
+    String from = GitUIUtil.getTextField(myFromComboBox).getText();
+    String onto = GitUIUtil.getTextField(myOntoComboBox).getText();
+    if (from.length() == 0) {
+      h.addParameters(onto);
+    }
+    else {
+      h.addParameters("--onto", onto, from);
+    }
+    final String selectedBranch = (String)myBranchComboBox.getSelectedItem();
+    if (myCurrentBranch != null && !myCurrentBranch.getName().equals(selectedBranch)) {
+      h.addParameters(selectedBranch);
+    }
+    return h;
+  }
+
+  /**
+   * Setup strategy
+   */
+  private void setupStrategy() {
+    for (String s : GitMergeUtil.getMergeStrategies(1)) {
+      myMergeStrategyComboBox.addItem(s);
+    }
+    myMergeStrategyComboBox.setSelectedItem(GitMergeUtil.DEFAULT_STRATEGY);
+    myDoNotUseMergeCheckBox.addActionListener(new ActionListener() {
+      public void actionPerformed(final ActionEvent e) {
+        myMergeStrategyComboBox.setEnabled(!myDoNotUseMergeCheckBox.isSelected());
+      }
+    });
+  }
+
+
+  /**
+   * Validate fields
+   */
+  private void validateFields() {
+    if (GitUIUtil.getTextField(myOntoComboBox).getText().length() == 0) {
+      setErrorText(null);
+      setOKActionEnabled(false);
+      return;
+    }
+    else if (myOntoValidator.isInvalid()) {
+      setErrorText(GitBundle.getString("rebase.invalid.onto"));
+      setOKActionEnabled(false);
+      return;
+    }
+    if (GitUIUtil.getTextField(myFromComboBox).getText().length() != 0 && myFromValidator.isInvalid()) {
+      setErrorText(GitBundle.getString("rebase.invalid.from"));
+      setOKActionEnabled(false);
+      return;
+    }
+    if (GitRebaseUtils.isRebaseInTheProgress(gitRoot())) {
+      setErrorText(GitBundle.getString("rebase.in.progress"));
+      setOKActionEnabled(false);
+      return;
+    }
+    setErrorText(null);
+    setOKActionEnabled(true);
+  }
+
+  /**
+   * Setup branch drop down.
+   */
+  private void setupBranches() {
+    GitUIUtil.getTextField(myOntoComboBox).getDocument().addDocumentListener(new DocumentAdapter() {
+      protected void textChanged(final DocumentEvent e) {
+        validateFields();
+      }
+    });
+    final ActionListener rootListener = new ActionListener() {
+      public void actionPerformed(final ActionEvent e) {
+        loadRefs();
+        updateBranches();
+      }
+    };
+    final ActionListener showListener = new ActionListener() {
+      public void actionPerformed(final ActionEvent e) {
+        updateOntoFrom();
+      }
+    };
+    myShowRemoteBranchesCheckBox.addActionListener(showListener);
+    myShowTagsCheckBox.addActionListener(showListener);
+    rootListener.actionPerformed(null);
+    myGitRootComboBox.addActionListener(rootListener);
+    myBranchComboBox.addActionListener(new ActionListener() {
+      public void actionPerformed(final ActionEvent e) {
+        updateTrackedBranch();
+      }
+    });
+  }
+
+  /**
+   * Update branches when git root changed
+   */
+  private void updateBranches() {
+    myBranchComboBox.removeAllItems();
+    for (GitBranch b : myLocalBranches) {
+      myBranchComboBox.addItem(b.getName());
+    }
+    if (myCurrentBranch != null) {
+      myBranchComboBox.setSelectedItem(myCurrentBranch.getName());
+    }
+    else {
+      myBranchComboBox.setSelectedItem(0);
+    }
+    updateOntoFrom();
+    updateTrackedBranch();
+  }
+
+  /**
+   * Update onto and from dropdowns.
+   */
+  private void updateOntoFrom() {
+    String onto = GitUIUtil.getTextField(myOntoComboBox).getText();
+    String from = GitUIUtil.getTextField(myFromComboBox).getText();
+    myFromComboBox.removeAllItems();
+    myOntoComboBox.removeAllItems();
+    for (GitBranch b : myLocalBranches) {
+      myFromComboBox.addItem(b);
+      myOntoComboBox.addItem(b);
+    }
+    if (myShowRemoteBranchesCheckBox.isSelected()) {
+      for (GitBranch b : myRemoteBranches) {
+        myFromComboBox.addItem(b);
+        myOntoComboBox.addItem(b);
+      }
+    }
+    if (myShowTagsCheckBox.isSelected()) {
+      for (GitTag t : myTags) {
+        myFromComboBox.addItem(t);
+        myOntoComboBox.addItem(t);
+      }
+    }
+    GitUIUtil.getTextField(myOntoComboBox).setText(onto);
+    GitUIUtil.getTextField(myFromComboBox).setText(from);
+  }
+
+  /**
+   * Load tags and branches
+   */
+  private void loadRefs() {
+    try {
+      myLocalBranches.clear();
+      myRemoteBranches.clear();
+      myTags.clear();
+      final VirtualFile root = gitRoot();
+      GitBranch.list(myProject, root, true, false, myLocalBranches);
+      GitBranch.list(myProject, root, false, true, myRemoteBranches);
+      GitTag.list(myProject, root, myTags);
+      myCurrentBranch = GitBranch.current(myProject, root);
+    }
+    catch (VcsException e) {
+      GitUIUtil.showOperationError(myProject, e, "git branch -a");
+    }
+  }
+
+  /**
+   * Update tracked branch basing on the currently selected branch
+   */
+  private void updateTrackedBranch() {
+    try {
+      final VirtualFile root = gitRoot();
+      String currentBranch = (String)myBranchComboBox.getSelectedItem();
+      final GitBranch trackedBranch;
+      if (currentBranch != null) {
+        String remote = GitConfigUtil.getValue(myProject, root, "branch." + currentBranch + ".remote");
+        String merge = GitConfigUtil.getValue(myProject, root, "branch." + currentBranch + ".merge");
+        String name =
+          (merge != null && merge.startsWith(GitBranch.REFS_HEADS_PREFIX)) ? merge.substring(GitBranch.REFS_HEADS_PREFIX.length()) : null;
+        if (remote == null || merge == null || name == null) {
+          trackedBranch = null;
+        }
+        else {
+          if (remote.equals(".")) {
+            trackedBranch = new GitBranch(name, false, false);
+          }
+          else {
+            trackedBranch = new GitBranch(remote + "/" + name, false, true);
+          }
+        }
+      }
+      else {
+        trackedBranch = null;
+      }
+      if (trackedBranch != null) {
+        myOntoComboBox.setSelectedItem(trackedBranch);
+      }
+      else {
+        GitUIUtil.getTextField(myOntoComboBox).setText("");
+      }
+      GitUIUtil.getTextField(myFromComboBox).setText("");
+    }
+    catch (VcsException e) {
+      GitUIUtil.showOperationError(myProject, e, "git config");
+    }
+  }
+
+  /**
+   * @return the currently selected git root
+   */
+  public VirtualFile gitRoot() {
+    return (VirtualFile)myGitRootComboBox.getSelectedItem();
+  }
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected String getDimensionServiceKey() {
+    return getClass().getName();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  protected JComponent createCenterPanel() {
+    return myPanel;
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseEditor.form b/plugins/git4idea/src/git4idea/rebase/GitRebaseEditor.form
new file mode 100644 (file)
index 0000000..2904ec5
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="git4idea.rebase.GitRebaseEditor">
+  <grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+    <margin top="0" left="0" bottom="0" right="0"/>
+    <constraints>
+      <xy x="20" y="20" width="500" height="228"/>
+    </constraints>
+    <properties/>
+    <border type="none"/>
+    <children>
+      <grid id="19dce" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+        <margin top="0" left="0" bottom="0" right="0"/>
+        <constraints>
+          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <component id="49f7c" class="javax.swing.JButton" binding="myMoveUpButton" default-binding="true">
+            <constraints>
+              <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <enabled value="false"/>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.editor.move.up"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.editor.move.up.tooltip"/>
+            </properties>
+          </component>
+          <vspacer id="23afb">
+            <constraints>
+              <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+            </constraints>
+          </vspacer>
+          <component id="f607e" class="javax.swing.JButton" binding="myMoveDownButton" default-binding="true">
+            <constraints>
+              <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <enabled value="false"/>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.editor.move.down"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.editor.move.down.tooltip"/>
+            </properties>
+          </component>
+          <component id="578eb" class="javax.swing.JButton" binding="myViewButton" default-binding="true">
+            <constraints>
+              <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+            </constraints>
+            <properties>
+              <enabled value="false"/>
+              <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.editor.view"/>
+              <toolTipText resource-bundle="git4idea/i18n/GitBundle" key="rebase.editor.view.tooltip"/>
+            </properties>
+          </component>
+        </children>
+      </grid>
+      <scrollpane id="2f6c5">
+        <constraints>
+          <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties/>
+        <border type="none"/>
+        <children>
+          <component id="22e0d" class="javax.swing.JTable" binding="myCommitsTable">
+            <constraints/>
+            <properties/>
+          </component>
+        </children>
+      </scrollpane>
+      <component id="11e7a" class="javax.swing.JLabel">
+        <constraints>
+          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+        </constraints>
+        <properties>
+          <labelFor value="2f6c5"/>
+          <text resource-bundle="git4idea/i18n/GitBundle" key="rebase.editor.message"/>
+        </properties>
+      </component>
+    </children>
+  </grid>
+</form>
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseEditor.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseEditor.java
new file mode 100644 (file)
index 0000000..27f4ea4
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ListWithSelection;
+import com.intellij.util.ui.ComboBoxTableCellEditor;
+import com.intellij.util.ui.ComboBoxTableCellRenderer;
+import git4idea.actions.GitShowAllSubmittedFilesAction;
+import git4idea.commands.StringScanner;
+import git4idea.i18n.GitBundle;
+import org.jetbrains.annotations.NonNls;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.TableColumn;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Editor for rebase entries. It allows reordering of
+ * the entries and changing commit status.
+ */
+public class GitRebaseEditor extends DialogWrapper {
+  /**
+   * The table that lists all commits
+   */
+  private JTable myCommitsTable;
+  /**
+   * The move up button
+   */
+  private JButton myMoveUpButton;
+  /**
+   * The move down button
+   */
+  private JButton myMoveDownButton;
+  /**
+   * The view commit button
+   */
+  private JButton myViewButton;
+  /**
+   * The root panel
+   */
+  private JPanel myPanel;
+  /**
+   * Table model
+   */
+  private final MyTableModel myTableModel;
+  /**
+   * The file name
+   */
+  private final String myFile;
+  /**
+   * The cygwin drive prefix
+   */
+  @NonNls private static final String CYGDRIVE_PREFIX = "/cygdrive/";
+
+  /**
+   * The constructor
+   *
+   * @param project the project
+   * @param file    the file to edit
+   */
+  protected GitRebaseEditor(final Project project, final VirtualFile gitRoot, String file) throws IOException {
+    super(project, true);
+    setTitle(GitBundle.getString("rebase.editor.title"));
+    if (SystemInfo.isWindows && file.startsWith(CYGDRIVE_PREFIX)) {
+      final int pfx = CYGDRIVE_PREFIX.length();
+      file = file.substring(pfx, pfx + 1) + ":" + file.substring(pfx + 1);
+    }
+    myFile = file;
+    myTableModel = new MyTableModel();
+    myTableModel.load(file);
+    myCommitsTable.setModel(myTableModel);
+    myCommitsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+    TableColumn actionColumn = myCommitsTable.getColumnModel().getColumn(MyTableModel.ACTION);
+    actionColumn.setCellEditor(ComboBoxTableCellEditor.INSTANCE);
+    actionColumn.setCellRenderer(ComboBoxTableCellRenderer.INSTANCE);
+    myCommitsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+      public void valueChanged(final ListSelectionEvent e) {
+        boolean selected = myCommitsTable.getSelectedRowCount() != 0;
+        myMoveUpButton.setEnabled(selected);
+        if (selected) {
+          myViewButton.setEnabled(true);
+          int row = myCommitsTable.getSelectedRow();
+          myMoveUpButton.setEnabled(row != 0);
+          myMoveDownButton.setEnabled(row != myTableModel.myEntries.size() - 1);
+        }
+        else {
+          myMoveUpButton.setEnabled(false);
+          myMoveDownButton.setEnabled(false);
+          myViewButton.setEnabled(false);
+        }
+      }
+    });
+    myViewButton.addActionListener(new ActionListener() {
+      public void actionPerformed(final ActionEvent e) {
+        int row = myCommitsTable.getSelectedRow();
+        if (row < 0) {
+          return;
+        }
+        GitRebaseEntry entry = myTableModel.myEntries.get(row);
+        GitShowAllSubmittedFilesAction.showSubmittedFiles(project, entry.getCommit(), gitRoot);
+      }
+    });
+    myMoveUpButton.addActionListener(new ActionListener() {
+      public void actionPerformed(final ActionEvent e) {
+        final int row = myCommitsTable.getSelectedRow();
+        if (myTableModel.moveUp(row)) {
+          myCommitsTable.getSelectionModel().setSelectionInterval(row - 1, row - 1);
+        }
+      }
+    });
+    myMoveDownButton.addActionListener(new ActionListener() {
+      public void actionPerformed(final ActionEvent e) {
+        final int row = myCommitsTable.getSelectedRow();
+        if (myTableModel.moveDown(row)) {
+          myCommitsTable.getSelectionModel().setSelectionInterval(row + 1, row + 1);
+        }
+      }
+    });
+    myTableModel.addTableModelListener(new TableModelListener() {
+      public void tableChanged(final TableModelEvent e) {
+        validateFields();
+      }
+    });
+    init();
+  }
+
+  /**
+   * Validate fields
+   */
+  private void validateFields() {
+    final ArrayList<GitRebaseEntry> entries = myTableModel.myEntries;
+    final GitRebaseEntry.Action squash = GitRebaseEntry.Action.squash;
+    for (int i = 0; i < entries.size(); i++) {
+      if (entries.get(i).getAction() == squash) {
+        if (!(i > 0 && entries.get(i - 1).getAction() == squash || i < entries.size() - 1 && entries.get(i + 1).getAction() == squash)) {
+          setErrorText(GitBundle.getString("rebase.editor.invalid.squash"));
+          setOKActionEnabled(false);
+          return;
+        }
+      }
+    }
+    setErrorText(null);
+    setOKActionEnabled(true);
+  }
+
+  /**
+   * Save entries back to the file
+   *
+   * @throws IOException if there is IO problem with saving
+   */
+  public void save() throws IOException {
+    myTableModel.save(myFile);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  protected JComponent createCenterPanel() {
+    return myPanel;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  protected String getDimensionServiceKey() {
+    return getClass().getName();
+  }
+
+  /**
+   * Cancel rebase
+   */
+  public void cancel() throws IOException {
+    myTableModel.cancel(myFile);
+  }
+
+
+  /**
+   * The table model for the commits
+   */
+  private static class MyTableModel extends AbstractTableModel {
+    /**
+     * The action column
+     */
+    private static final int ACTION = 0;
+    /**
+     * The commit hash column
+     */
+    private static final int COMMIT = 1;
+    /**
+     * The subject column
+     */
+    private static final int SUBJECT = 2;
+
+    /**
+     * The entries
+     */
+    final ArrayList<GitRebaseEntry> myEntries = new ArrayList<GitRebaseEntry>();
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Class<?> getColumnClass(final int columnIndex) {
+      return columnIndex == ACTION ? ListWithSelection.class : String.class;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getColumnName(final int column) {
+      switch (column) {
+        case ACTION:
+          return GitBundle.getString("rebase.editor.action.column");
+        case COMMIT:
+          return GitBundle.getString("rebase.editor.commit.column");
+        case SUBJECT:
+          return GitBundle.getString("rebase.editor.comment.column");
+        default:
+          throw new IllegalArgumentException("Unsupported column index: " + column);
+      }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getRowCount() {
+      return myEntries.size();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int getColumnCount() {
+      return SUBJECT + 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Object getValueAt(final int rowIndex, final int columnIndex) {
+      GitRebaseEntry e = myEntries.get(rowIndex);
+      switch (columnIndex) {
+        case ACTION:
+          return new ListWithSelection<GitRebaseEntry.Action>(Arrays.asList(GitRebaseEntry.Action.values()), e.getAction());
+        case COMMIT:
+          return e.getCommit();
+        case SUBJECT:
+          return e.getSubject();
+        default:
+          throw new IllegalArgumentException("Unsupported column index: " + columnIndex);
+      }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @SuppressWarnings({"unchecked"})
+    public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) {
+      assert columnIndex == ACTION;
+      GitRebaseEntry e = myEntries.get(rowIndex);
+      e.setAction((GitRebaseEntry.Action)aValue);
+      fireTableCellUpdated(rowIndex, columnIndex);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isCellEditable(final int rowIndex, final int columnIndex) {
+      return columnIndex == ACTION;
+    }
+
+    /**
+     * Load data from the file
+     *
+     * @param file the file to load
+     */
+    public void load(final String file) throws IOException {
+      final StringScanner s = new StringScanner(new String(FileUtil.loadFileText(new File(file))));
+      while (s.hasMoreData()) {
+        if (s.isEol() || s.startsWith('#')) {
+          s.nextLine();
+          continue;
+        }
+        String action = s.spaceToken();
+        assert "pick".equals(action) : "Initial action should be pick: " + action;
+        String hash = s.spaceToken();
+        String comment = s.line();
+        myEntries.add(new GitRebaseEntry(hash, comment));
+      }
+    }
+
+    /**
+     * Save text to the file
+     *
+     * @param file the file to save to
+     * @throws IOException if there is IO problem
+     */
+    public void save(final String file) throws IOException {
+      PrintWriter out = new PrintWriter(new FileWriter(file));
+      try {
+        for (GitRebaseEntry e : myEntries) {
+          if (e.getAction() != GitRebaseEntry.Action.skip) {
+            out.println(e.getAction().toString() + " " + e.getCommit() + " " + e.getSubject());
+          }
+        }
+      }
+      finally {
+        out.close();
+      }
+    }
+
+    /**
+     * Save text to the file
+     *
+     * @param file the file to save to
+     * @throws IOException if there is IO problem
+     */
+    public static void cancel(final String file) throws IOException {
+      PrintWriter out = new PrintWriter(new FileWriter(file));
+      try {
+        //noinspection HardCodedStringLiteral
+        out.println("# rebase is cancelled");
+      }
+      finally {
+        out.close();
+      }
+    }
+
+
+    /**
+     * Move selected row up. If row cannot be moved up, do nothing and return false.
+     *
+     * @param row a row to move
+     * @return true if row was moved
+     */
+    public boolean moveUp(final int row) {
+      if (row < 1 || row >= myEntries.size()) {
+        return false;
+      }
+      GitRebaseEntry e = myEntries.get(row);
+      myEntries.set(row, myEntries.get(row - 1));
+      myEntries.set(row - 1, e);
+      fireTableRowsUpdated(row - 1, row);
+      return true;
+    }
+
+    /**
+     * Move selected row down. If row cannot be moved down, do nothing and return false.
+     *
+     * @param row a row to move
+     * @return true if row was moved
+     */
+    public boolean moveDown(final int row) {
+      if (row < 0 || row >= myEntries.size() - 1) {
+        return false;
+      }
+      GitRebaseEntry e = myEntries.get(row);
+      myEntries.set(row, myEntries.get(row + 1));
+      myEntries.set(row + 1, e);
+      fireTableRowsUpdated(row, row + 1);
+      return true;
+    }
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseEditorHandler.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseEditorHandler.java
new file mode 100644 (file)
index 0000000..3fb8eb3
--- /dev/null
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ui.UIUtil;
+
+import java.io.Closeable;
+
+/**
+ * The handler for rebase editor request. The handler shows {@link git4idea.rebase.GitRebaseEditor}
+ * dialog with the specified file. If user accepts the changes, it saves file and returns 0,
+ * otherwise it just returns error code.
+ */
+public class GitRebaseEditorHandler implements Closeable {
+  /**
+   * The logger
+   */
+  private final static Logger LOG = Logger.getInstance(GitRebaseEditorHandler.class.getName());
+  /**
+   * The service object that has created this handler
+   */
+  private final GitRebaseEditorService myService;
+  /**
+   * The context project
+   */
+  private final Project myProject;
+  /**
+   * The git repository root
+   */
+  private final VirtualFile myRoot;
+  /**
+   * The handler number
+   */
+  private final int myHandlerNo;
+  /**
+   * If true, the handler has been closed
+   */
+  private boolean myIsClosed;
+
+  /**
+   * The constructor from fields that is expected to be
+   * accessed only from {@link git4idea.rebase.GitRebaseEditorService}.
+   *
+   * @param service   the service object that has created this handler
+   * @param project   the context project
+   * @param root      the git repository root
+   * @param handlerNo the handler no for this editor
+   */
+  GitRebaseEditorHandler(final GitRebaseEditorService service, final Project project, final VirtualFile root, final int handlerNo) {
+    myService = service;
+    myProject = project;
+    myRoot = root;
+    myHandlerNo = handlerNo;
+  }
+
+  /**
+   * Edit commits request
+   *
+   * @param path the path to eding
+   * @return the exit code to be returned from editor
+   */
+  public int editCommits(final String path) {
+    ensureOpen();
+    final Ref<Boolean> isSuccess = new Ref<Boolean>();
+    UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+      public void run() {
+        try {
+          GitRebaseEditor editor = new GitRebaseEditor(myProject, myRoot, path);
+          editor.show();
+          if (editor.isOK()) {
+            editor.save();
+            isSuccess.set(true);
+            return;
+          }
+          else {
+            editor.cancel();
+            isSuccess.set(true);
+          }
+        }
+        catch (Exception e) {
+          LOG.error("Failed to edit the git rebase file: " + path, e);
+        }
+        isSuccess.set(false);
+      }
+    });
+    return (isSuccess.isNull() || !isSuccess.get().booleanValue()) ? GitRebaseEditorMain.ERROR_EXIT_CODE : 0;
+  }
+
+  /**
+   * Check that handler has not yet been closed
+   */
+  private void ensureOpen() {
+    if (myIsClosed) {
+      throw new IllegalStateException("The handler was already closed");
+    }
+  }
+
+  /**
+   * Stop using the handler
+   */
+  public void close() {
+    ensureOpen();
+    myIsClosed = true;
+    myService.unregisterHandler(myHandlerNo);
+  }
+
+  /**
+   * @return the handler number
+   */
+  public int getHandlerNo() {
+    return myHandlerNo;
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseEditorMain.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseEditorMain.java
new file mode 100644 (file)
index 0000000..4cb25e6
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+import org.apache.xmlrpc.XmlRpcClientLite;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Vector;
+
+/**
+ * The rebase editor application, this editor is invoked by the git.
+ * The application passes its parameter using XML RCP service
+ * registered on the host passed as the first parameter. The application
+ * exits with exit code returned from the service.
+ */
+public class GitRebaseEditorMain {
+  /**
+   * The environment variable for handler no
+   */
+  @NonNls @NotNull public static final String IDEA_REBASE_HANDER_NO = "IDEA_REBASE_HANDER_NO";
+  /**
+   * The exit code used to indicate that editing was canceled or has failed in some other way.
+   */
+  public final static int ERROR_EXIT_CODE = 2;
+  /**
+   * Rebase editor handler name
+   */
+  @NonNls static final String HANDLER_NAME = "Git4ideaRebaseEditorHandler";
+
+  /**
+   * A private constructor for static class
+   */
+  private GitRebaseEditorMain() {
+  }
+
+  /**
+   * The application entry point
+   *
+   * @param args application arguments
+   */
+  @SuppressWarnings(
+    {"UseOfSystemOutOrSystemErr", "HardCodedStringLiteral", "CallToPrintStackTrace", "UseOfObsoleteCollectionType"})
+  public static void main(String[] args) {
+    if (args.length != 2) {
+      System.err.println("Invalid amount of arguments: " + Arrays.asList(args));
+      System.exit(ERROR_EXIT_CODE);
+    }
+    int port;
+    try {
+      port = Integer.parseInt(args[0]);
+    }
+    catch (NumberFormatException ex) {
+      System.err.println("Invalid port number: " + args[0]);
+      System.exit(ERROR_EXIT_CODE);
+      return;
+    }
+    final String handlerValue = System.getenv(IDEA_REBASE_HANDER_NO);
+    if (handlerValue == null) {
+      System.err.println("Handler no is not specified");
+      System.exit(ERROR_EXIT_CODE);
+    }
+    int handler;
+    try {
+      handler = Integer.parseInt(handlerValue);
+    }
+    catch (NumberFormatException ex) {
+      System.err.println("Invalid handler number: " + handlerValue);
+      System.exit(ERROR_EXIT_CODE);
+      return;
+    }
+    String file = args[1];
+    try {
+      XmlRpcClientLite client = new XmlRpcClientLite("localhost", port);
+      Vector<Object> params = new Vector<Object>();
+      params.add(handler);
+      params.add(file);
+      Integer exitCode = (Integer)client.execute(HANDLER_NAME + ".editCommits", params);
+      if (exitCode == null) {
+        exitCode = ERROR_EXIT_CODE;
+      }
+      System.exit(exitCode.intValue());
+    }
+    catch (Exception e) {
+      System.err.println("Unable to contact IDEA: " + e);
+      e.printStackTrace();
+      System.exit(ERROR_EXIT_CODE);
+    }
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseEditorService.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseEditorService.java
new file mode 100644 (file)
index 0000000..20174f4
--- /dev/null
@@ -0,0 +1,226 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+import com.intellij.ide.XmlRpcServer;
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.VcsException;
+import com.intellij.openapi.vfs.VirtualFile;
+import git4idea.commands.ScriptGenerator;
+import gnu.trove.THashMap;
+import org.apache.commons.codec.DecoderException;
+import org.apache.xmlrpc.XmlRpcClientLite;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * The service that generates editor script for
+ */
+public class GitRebaseEditorService implements ApplicationComponent {
+  /**
+   * the logger
+   */
+  private static final Logger LOG = Logger.getInstance(GitRebaseEditorService.class.getName());
+
+  /**
+   * The path to editor script
+   */
+  private File myScriptPath;
+  /**
+   * The lock object
+   */
+  private final Object myScriptLock = new Object();
+  /**
+   * The handlers to use
+   */
+  private final Map<Integer, GitRebaseEditorHandler> myHandlers = new THashMap<Integer, GitRebaseEditorHandler>();
+  /**
+   * The lock for the handlers
+   */
+  private final Object myHandlersLock = new Object();
+  /**
+   * XML rcp server
+   */
+  private final XmlRpcServer myXmlRpcServer;
+  /**
+   * Random number generator
+   */
+  private final static Random oursRandom = new Random();
+  /**
+   * If true, the component has been intialized
+   */
+  private boolean myInitialized = false;
+  /**
+   * The prefix for rebase editors
+   */
+  @NonNls private static final String GIT_REBASE_EDITOR_PREFIX = "git-rebase-editor-";
+
+  /**
+   * The constructor
+   *
+   * @param xmlRpcServer the XML RCP server instance
+   */
+  public GitRebaseEditorService(@NotNull final XmlRpcServer xmlRpcServer) {
+    myXmlRpcServer = xmlRpcServer;
+  }
+
+  /**
+   * @return an instance of the server
+   */
+  @NotNull
+  public static GitRebaseEditorService getInstance() {
+    final GitRebaseEditorService service = ServiceManager.getService(GitRebaseEditorService.class);
+    if (service == null) {
+      throw new IllegalStateException("The service " + GitRebaseEditorService.class.getName() + " cannot be located");
+    }
+    return service;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @NotNull
+  public String getComponentName() {
+    return getClass().getSimpleName();
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void initComponent() {
+    if (!myInitialized) {
+      myXmlRpcServer.addHandler(GitRebaseEditorMain.HANDLER_NAME, new InternalHandler());
+      myInitialized = true;
+    }
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  public void disposeComponent() {
+    myXmlRpcServer.removeHandler(GitRebaseEditorMain.HANDLER_NAME);
+    synchronized (myScriptLock) {
+      if (myScriptPath != null) {
+        if (!myScriptPath.delete()) {
+          LOG.warn("The temporary file " + myScriptPath + " generated by git4idea plugin failed to be removed during disposing.");
+        }
+        myScriptPath = null;
+      }
+    }
+  }
+
+  /**
+   * Get file to the script service
+   *
+   * @return path to the script
+   * @throws java.io.IOException if script cannot be generated
+   */
+  @NotNull
+  public synchronized File getScriptPath() throws VcsException {
+    try {
+      synchronized (myScriptLock) {
+        if (myScriptPath == null) {
+          ScriptGenerator generator = new ScriptGenerator(GIT_REBASE_EDITOR_PREFIX, GitRebaseEditorMain.class);
+          generator.addInternal(Integer.toString(myXmlRpcServer.getPortNumber()));
+          generator.addClasses(XmlRpcClientLite.class, DecoderException.class);
+          myScriptPath = generator.generate();
+        }
+        return myScriptPath;
+      }
+    }
+    catch (IOException ex) {
+      throw new VcsException("Unable to generate script file: " + ex, ex);
+    }
+  }
+
+  /**
+   * @return the handler instance
+   */
+  public GitRebaseEditorHandler getHandler(Project project, VirtualFile root) {
+    initComponent();
+    GitRebaseEditorHandler rc = null;
+    synchronized (myHandlersLock) {
+      for (int i = Integer.MAX_VALUE; i > 0; i--) {
+        int code = Math.abs(oursRandom.nextInt());
+        // note that code might still be negative at this point if it is Integer.MIN_VALUE.
+        if (code > 0 && !myHandlers.containsKey(code)) {
+          rc = new GitRebaseEditorHandler(this, project, root, code);
+          break;
+        }
+      }
+      if (rc == null) {
+        throw new IllegalStateException("There is a problem with random number allocation");
+      }
+      myHandlers.put(rc.getHandlerNo(), rc);
+    }
+    return rc;
+  }
+
+
+  /**
+   * Unregister handler
+   *
+   * @param handlerNo the handler number.
+   */
+  void unregisterHandler(final int handlerNo) {
+    synchronized (myHandlersLock) {
+      if (myHandlers.remove(handlerNo) == null) {
+        throw new IllegalStateException("The handler " + handlerNo + " has been already remoted");
+      }
+    }
+  }
+
+  /**
+   * Unregister handler
+   *
+   * @param handlerNo the handler number.
+   */
+  @NotNull
+  GitRebaseEditorHandler getHandler(final int handlerNo) {
+    synchronized (myHandlersLock) {
+      GitRebaseEditorHandler h = myHandlers.get(handlerNo);
+      if (h == null) {
+        throw new IllegalStateException("The handler " + handlerNo + " has been already remoted");
+      }
+      return h;
+    }
+  }
+
+
+  /**
+   * The internal xml rcp handler
+   */
+  public class InternalHandler {
+    /**
+     * Edit commits for the rebase operation
+     *
+     * @param handlerNo the handler no
+     * @param path      the path to edit
+     * @return exit code
+     */
+    public int editCommits(int handlerNo, String path) {
+      return getHandler(handlerNo).editCommits(path);
+    }
+  }
+}
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseEntry.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseEntry.java
new file mode 100644 (file)
index 0000000..e39266d
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+/**
+ * The entry for rebase editor
+ */
+class GitRebaseEntry {
+  /**
+   * The commit hash
+   */
+  private final String myCommit;
+  /**
+   * The commit comment subject line
+   */
+  private final String mySubject;
+  /**
+   * The action associated with the entry
+   */
+  private Action myAction = Action.pick;
+
+  /**
+   * The constructor
+   *
+   * @param commit  the commit hash
+   * @param subject the commit subject
+   */
+  public GitRebaseEntry(final String commit, final String subject) {
+    myCommit = commit;
+    mySubject = subject;
+  }
+
+  /**
+   * @return the commit hash
+   */
+  public String getCommit() {
+    return myCommit;
+  }
+
+  /**
+   * @return the commit subject
+   */
+  public String getSubject() {
+    return mySubject;
+  }
+
+  /**
+   * @return the action associated with the commit
+   */
+  public Action getAction() {
+    return myAction;
+  }
+
+  /**
+   * @param action a new action to set
+   */
+  public void setAction(final Action action) {
+    myAction = action;
+  }
+
+
+  /**
+   * The action associated with the commit
+   */
+  static public enum Action {
+    /**
+     * the pick action
+     */
+    pick,
+    /**
+     * the edit action, the user will be offered to alter commit
+     */
+    edit,
+    /**
+     * the skip action
+     */
+    skip,
+    /**
+     * the squash action (for two or more commits)
+     */
+    squash, }
+}
diff --git a/plugins/git4idea/src/git4idea/rebase/GitRebaseUtils.java b/plugins/git4idea/src/git4idea/rebase/GitRebaseUtils.java
new file mode 100644 (file)
index 0000000..dae3721
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2008 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package git4idea.rebase;
+
+import com.intellij.openapi.vfs.VirtualFile;
+
+/**
+ * The utilities related to rebase functionality
+ */
+public class GitRebaseUtils {
+  /**
+   * A private constructor for utility class
+   */
+  private GitRebaseUtils() {
+  }
+
+  /**
+   * Checks if the rebase is in the progress for the specified git root
+   *
+   * @param root the git root
+   * @return true if the rebase directory presents in the root
+   */
+  public static boolean isRebaseInTheProgress(VirtualFile root) {
+    return root.findFileByRelativePath(".git/rebase-merge") != null;
+  }
+}
index ceec9978ed00c976fadd1513ffba4ecfc64d677f..568bca878818d99095a1eae7a63291ba3156addd 100644 (file)
@@ -26,6 +26,8 @@ import com.intellij.ui.SimpleTextAttributes;
 import com.intellij.util.Icons;
 import com.intellij.util.ui.Tree;
 import com.intellij.util.ui.tree.TreeUtil;
+import git4idea.GitBranch;
+import git4idea.GitTag;
 import git4idea.commands.GitHandler;
 import git4idea.commands.GitHandlerUtil;
 import git4idea.commands.GitSimpleHandler;
@@ -154,10 +156,10 @@ public class GitRefspecAddRefsDialog extends DialogWrapper {
           while (s.hasMoreData()) {
             s.tabToken(); // skip last commit hash
             String ref = s.line();
-            if (ref.startsWith(GitRefspecPanel.REFS_HEADS_PREFIX)) {
+            if (ref.startsWith(GitBranch.REFS_HEADS_PREFIX)) {
               myBranches.add(ref);
             }
-            else if (ref.startsWith(GitRefspecPanel.REFS_TAGS_PREFIX)) {
+            else if (ref.startsWith(GitTag.REFS_TAGS_PREFIX)) {
               myTags.add(ref);
             }
             else {
index f9442027ef95e86eb6253a25d21af74ccc8cc789..f4dfcdfc32cbbef4c5e4a6748a6fd6a7e572b2f2 100644 (file)
@@ -21,7 +21,9 @@ import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
 import com.intellij.ui.DocumentAdapter;
 import com.intellij.util.containers.HashMap;
+import git4idea.GitBranch;
 import git4idea.GitRemote;
+import git4idea.GitTag;
 import git4idea.commands.StringScanner;
 import git4idea.i18n.GitBundle;
 import git4idea.validators.GitBranchNameValidator;
@@ -113,18 +115,6 @@ public class GitRefspecPanel extends JPanel {
    * Mapping table model
    */
   private final MyMappingTableModel myReferencesModel = new MyMappingTableModel();
-  /**
-   * Prefix for local branches
-   */
-  @NonNls public static final String REFS_HEADS_PREFIX = "refs/heads/";
-  /**
-   * Prefix for tags
-   */
-  @NonNls public static final String REFS_TAGS_PREFIX = "refs/tags/";
-  /**
-   * Prefix for remotes
-   */
-  @NonNls public static final String REFS_REMOTES_PREFIX = "refs/remotes/";
 
   /**
    * A constructor
@@ -216,7 +206,7 @@ public class GitRefspecPanel extends JPanel {
           myReferencesModel.addMapping(false, tag, tag);
         }
         for (String head : d.getSelected(false)) {
-          myReferencesModel.addMapping(true, head, remoteName(head.substring(REFS_HEADS_PREFIX.length())));
+          myReferencesModel.addMapping(true, head, remoteName(head.substring(GitBranch.REFS_HEADS_PREFIX.length())));
         }
       }
     });
@@ -245,7 +235,7 @@ public class GitRefspecPanel extends JPanel {
    * @return the full path to the head
    */
   private static String tagRemoteName(final String remoteName, final String tagName) {
-    return REFS_TAGS_PREFIX + remoteName + "/" + tagName;
+    return GitTag.REFS_TAGS_PREFIX + remoteName + "/" + tagName;
   }
 
   /**
@@ -255,7 +245,7 @@ public class GitRefspecPanel extends JPanel {
    * @return the tag name
    */
   private static String tagName(final String tagName) {
-    return REFS_TAGS_PREFIX + tagName;
+    return GitTag.REFS_TAGS_PREFIX + tagName;
   }
 
   /**
@@ -276,7 +266,7 @@ public class GitRefspecPanel extends JPanel {
    * @return the full path to the head
    */
   private static String remoteName(final String remote, final String headName) {
-    return remote.length() != 0 ? REFS_REMOTES_PREFIX + remote + "/" + headName : headName(headName);
+    return remote.length() != 0 ? GitBranch.REFS_REMOTES_PREFIX + remote + "/" + headName : headName(headName);
   }
 
   /**
@@ -286,7 +276,7 @@ public class GitRefspecPanel extends JPanel {
    * @return the full path to the head
    */
   private static String headName(final String head) {
-    return REFS_HEADS_PREFIX + head;
+    return GitBranch.REFS_HEADS_PREFIX + head;
   }
 
   /**
index d8b562d34b1bb4ee54c2a71f2993e288b6bbf8e1..e3e27f0bfc1a764574fbc6b6fb80f4c252c0c340 100644 (file)
@@ -67,6 +67,16 @@ public class GitUIUtil {
 
   }
 
+  /**
+   * Get text field from combobox
+   *
+   * @param cb a combobox to examine
+   * @return the text field reference
+   */
+  public static JTextField getTextField(JComboBox cb) {
+    return (JTextField)cb.getEditor().getEditorComponent();
+  }
+
   /**
    * Create list cell renderd for remotes. It shows both name and url and highlights the default
    * remote for the branch with bold.
index d65458255f5e6e22d9715b1686a092129461f9c0..1fcf9f51293917b699304473a3008cff22d4a1e5 100644 (file)
@@ -240,7 +240,7 @@ public class GitUnstashDialog extends DialogWrapper {
     }
     myBranches.clear();
     try {
-      GitBranch.list(myProject, getGitRoot(), false, true, myBranches);
+      GitBranch.listAsStrings(myProject, getGitRoot(), false, true, myBranches);
     }
     catch (VcsException e) {
       // ignore error
index c67e534c098500511c8314d0a1b0bcd7f10c2197..16e2ca1077fe5a1dd6b03c9c42ee1cb1e5fdda73 100644 (file)
 package org.jetbrains.git4idea.ssh;
 
 import com.intellij.ide.XmlRpcServer;
-import com.intellij.openapi.application.PathManager;
 import com.intellij.openapi.components.ApplicationComponent;
 import com.intellij.openapi.components.ServiceManager;
 import com.intellij.openapi.diagnostic.Logger;
-import com.intellij.openapi.util.SystemInfo;
 import com.intellij.openapi.util.io.FileUtil;
-import com.intellij.util.PathUtil;
 import com.trilead.ssh2.KnownHosts;
+import git4idea.commands.ScriptGenerator;
 import git4idea.i18n.GitBundle;
 import gnu.trove.THashMap;
 import org.apache.commons.codec.DecoderException;
@@ -32,9 +30,7 @@ import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 
 import java.io.File;
-import java.io.FileWriter;
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.util.Random;
 import java.util.Vector;
 
@@ -64,19 +60,9 @@ public class GitSSHService implements ApplicationComponent {
    */
   @NonNls private static final String GIT_SSH_PREFIX = "git-ssh-";
   /**
-   * The extension of the ssh script name
+   * If true, the component has been intialized
    */
-  @NonNls private static final String GIT_SSH_EXT;
-
-  static {
-    if (SystemInfo.isWindows) {
-      GIT_SSH_EXT = ".cmd";
-    }
-    else {
-      GIT_SSH_EXT = ".sh";
-    }
-  }
-
+  private boolean myInitialized = false;
   /**
    * Path to the generated script
    */
@@ -125,74 +111,21 @@ public class GitSSHService implements ApplicationComponent {
    * Get file to the script service
    *
    * @return path to the script
-   * @throws IOException
+   * @throws IOException if script cannot be generated
    */
-  @SuppressWarnings({"HardCodedStringLiteral"})
   @NotNull
   public synchronized File getScriptPath() throws IOException {
-    myXmlRpcServer.addHandler(HANDLER_NAME, new InternalRequestHandler());
     if (myScriptPath == null) {
-      myScriptPath = File.createTempFile(GIT_SSH_PREFIX, GIT_SSH_EXT);
-      myScriptPath.deleteOnExit();
-      PrintWriter out = new PrintWriter(new FileWriter(myScriptPath));
-      try {
-        if (SystemInfo.isWindows) {
-          out.println("@echo off");
-        }
-        else {
-          out.println("#!/bin/sh");
-        }
-        String mainPath = PathUtil.getJarPathForClass(SSHMain.class);
-        String sshPath = PathUtil.getJarPathForClass(KnownHosts.class);
-        String xmlRcpPath = PathUtil.getJarPathForClass(XmlRpcClientLite.class);
-        String codecPath = PathUtil.getJarPathForClass(DecoderException.class);
-        String resPath = getJarForResource(GitBundle.class, "/git4idea/i18n/GitBundle.properties");
-        String utilPath = PathUtil.getJarPathForClass(FileUtil.class);
-        // six parameters are enough for the git case (actually 4 are enough)
-        out.print("java -cp \"" +
-                  mainPath +
-                  File.pathSeparator +
-                  sshPath +
-                  File.pathSeparator +
-                  codecPath +
-                  File.pathSeparator +
-                  xmlRcpPath +
-                  File.pathSeparator +
-                  resPath +
-                  File.pathSeparator +
-                  utilPath +
-                  "\" " +
-                  SSHMain.class.getName() +
-                  " " +
-                  myXmlRpcServer.getPortNumber());
-        if (SystemInfo.isWindows) {
-          out.println(" %1 %2 %3 %4 %5 %6");
-        }
-        else {
-          out.println(" \"$@\"");
-        }
-      }
-      finally {
-        out.close();
-      }
-      FileUtil.setExectuableAttribute(myScriptPath.getAbsolutePath(), true);
+      ScriptGenerator generator = new ScriptGenerator(GIT_SSH_PREFIX, SSHMain.class);
+      generator.addInternal(Integer.toString(myXmlRpcServer.getPortNumber()));
+      generator.addClasses(XmlRpcClientLite.class, DecoderException.class);
+      generator.addClasses(KnownHosts.class, FileUtil.class);
+      generator.addResource(GitBundle.class, "/git4idea/i18n/GitBundle.properties");
+      myScriptPath = generator.generate();
     }
     return myScriptPath;
   }
 
-  /**
-   * Get path for resources.jar
-   *
-   * @param context a context class
-   * @param res     a resource
-   * @return a path to classpath entry
-   */
-  @SuppressWarnings({"SameParameterValue"})
-  private static String getJarForResource(Class context, String res) {
-    String resourceRoot = PathManager.getResourceRoot(context, res);
-    return new File(resourceRoot).getAbsoluteFile().getAbsolutePath();
-  }
-
   /**
    * {@inheritDoc}
    */
@@ -205,8 +138,10 @@ public class GitSSHService implements ApplicationComponent {
    * {@inheritDoc}
    */
   public void initComponent() {
-    myXmlRpcServer.addHandler(HANDLER_NAME, new InternalRequestHandler());
-    // do nothing
+    if (!myInitialized) {
+      myXmlRpcServer.addHandler(HANDLER_NAME, new InternalRequestHandler());
+      myInitialized = true;
+    }
   }
 
   /**
@@ -228,6 +163,7 @@ public class GitSSHService implements ApplicationComponent {
    * @return an identifier to pass to the environment variable
    */
   public synchronized int registerHandler(@NotNull Handler handler) {
+    initComponent();
     while (true) {
       int rnd = RANDOM.nextInt();
       if (rnd == Integer.MIN_VALUE) {