git4idea: Fixed some compatibility problem with Git 1.6.4
authorConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Fri, 21 Aug 2009 15:31:55 +0000 (19:31 +0400)
committerConstantine Plotnikov <Constantine.Plotnikov@jetbrains.com>
Fri, 21 Aug 2009 15:31:55 +0000 (19:31 +0400)
plugins/git4idea/src/git4idea/GitBranch.java
plugins/git4idea/src/git4idea/GitRemote.java
plugins/git4idea/src/git4idea/checkin/GitPushDialog.java
plugins/git4idea/src/git4idea/merge/GitPullDialog.java
plugins/git4idea/src/git4idea/ui/GitFetchDialog.java
plugins/git4idea/src/git4idea/ui/GitUIUtil.java
plugins/git4idea/tests/git4idea/tests/GitRemoteTest.java [new file with mode: 0644]
plugins/git4idea/tests/git4idea/tests/data/git-remote-list-1_6_1.txt [new file with mode: 0644]
plugins/git4idea/tests/git4idea/tests/data/git-remote-list-1_6_4.txt [new file with mode: 0644]
plugins/git4idea/tests/git4idea/tests/data/git-remote-show-1_6_1.txt [new file with mode: 0644]
plugins/git4idea/tests/git4idea/tests/data/git-remote-show-1_6_4.txt [new file with mode: 0644]

index 00ba10a0c9524ec018bcec11e1ceaab02bd9b70e..673067891a1f5fc6c3c9c957611aeed2e02ec7dc 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.openapi.vcs.VcsException;
 import com.intellij.openapi.vfs.VirtualFile;
 import git4idea.commands.GitHandler;
 import git4idea.commands.GitSimpleHandler;
+import git4idea.commands.StringScanner;
 import git4idea.config.GitConfigUtil;
 import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
@@ -142,11 +143,19 @@ public class GitBranch extends GitReference {
     else if (remote) {
       handler.addParameters("-r");
     }
-    for (String line : handler.run().split("\n")) {
+    StringScanner s = new StringScanner(handler.run());
+    while (s.hasMoreData()) {
+      String line = s.line();
       if (line.length() == 0 || line.endsWith(NO_BRANCH_NAME)) {
         continue;
       }
-      branches.add(line.substring(2));
+      int sp = line.indexOf(' ', 2);
+      if (sp != -1) {
+        branches.add(line.substring(2, sp));
+      }
+      else {
+        branches.add(line.substring(2));
+      }
     }
   }
 
index 848d1baf37c1cd58e790fe693253f390e9bbf0e8..4137ce2fcadf6b6d0c1ed9a22a057698d7f836e9 100644 (file)
@@ -34,6 +34,8 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * A git remotes
@@ -44,13 +46,25 @@ public final class GitRemote {
    */
   private final String myName;
   /**
-   * The url of the remote
+   * The fetch url of the remote
    */
-  private final String myUrl;
+  private final String myFetchUrl;
+  /**
+   * The push url of the remote
+   */
+  private String myPushUrl;
   /**
    * Prefix for url in "git remote show -n {branch}"
    */
   @NonNls private static final String SHOW_URL_PREFIX = "  URL: ";
+  /**
+   * Prefix for url in "git remote show -n {branch}"
+   */
+  @NonNls private static final String SHOW_FETCH_URL_PREFIX = "  Fetch URL: ";
+  /**
+   * Prefix for url in "git remote show -n {branch}"
+   */
+  @NonNls private static final String SHOW_PUSH_URL_PREFIX = "  Push  URL: ";
   /**
    * Prefix for local branch mapping in "git remote show -n {branch}"
    */
@@ -58,11 +72,15 @@ public final class GitRemote {
   /**
    * line that starts branches section in "git remote show -n {branch}"
    */
-  @NonNls private static final String SHOW_BRANCHES_LINE = "  Tracked remote branches";
+  @NonNls private static final String SHOW_BRANCHES_LINE = "  Tracked remote branch";
   /**
    * US-ASCII encoding name
    */
   @NonNls private static final String US_ASCII_ENCODING = "US-ASCII";
+  /**
+   * Pattern that parses pull spec
+   */
+  private static final Pattern PULL_PATTERN = Pattern.compile("(\\S+)\\s+merges with remote (\\S+)");
 
   /**
    * A constructor
@@ -71,8 +89,20 @@ public final class GitRemote {
    * @param url  the url
    */
   public GitRemote(@NotNull final String name, final String url) {
+    this(name, url, url);
+  }
+
+  /**
+   * A constructor
+   *
+   * @param name     the name
+   * @param fetchUrl the fetch url
+   * @param pushUrl  the fetch url
+   */
+  public GitRemote(String name, String fetchUrl, String pushUrl) {
     myName = name;
-    myUrl = url;
+    myFetchUrl = fetchUrl;
+    myPushUrl = pushUrl;
   }
 
   /**
@@ -83,10 +113,17 @@ public final class GitRemote {
   }
 
   /**
-   * @return the url of the remote
+   * @return the fetch url of the remote
    */
-  public String url() {
-    return myUrl;
+  public String fetchUrl() {
+    return myFetchUrl;
+  }
+
+  /**
+   * @return the push url of the remote
+   */
+  public String pushUrl() {
+    return myPushUrl;
   }
 
   /**
@@ -122,19 +159,54 @@ public final class GitRemote {
    * @throws VcsException in case of git error
    */
   public static List<GitRemote> list(Project project, VirtualFile root) throws VcsException {
-    ArrayList<GitRemote> remotes = new ArrayList<GitRemote>();
     GitSimpleHandler handler = new GitSimpleHandler(project, root, GitHandler.REMOTE);
     handler.setNoSSH(true);
     handler.setSilent(true);
     handler.addParameters("-v");
-    for (String line : handler.run().split("\n")) {
-      int i = line.indexOf('\t');
-      if (i == -1) {
-        continue;
+    String output = handler.run();
+    return parseRemoteListInternal(output);
+  }
+
+  /**
+   * Parse list of remotes (internal method)
+   *
+   * @param output the output to parse
+   * @return list of remotes
+   */
+  public static List<GitRemote> parseRemoteListInternal(String output) {
+    ArrayList<GitRemote> remotes = new ArrayList<GitRemote>();
+    StringScanner s = new StringScanner(output);
+    String name = null;
+    String fetch = null;
+    String push = null;
+    while (s.hasMoreData()) {
+      String n = s.tabToken();
+      if (name != null && !n.equals(name) && fetch != null) {
+        if (push == null) {
+          push = fetch;
+        }
+        remotes.add(new GitRemote(name, fetch, push));
+        fetch = null;
+        push = null;
+      }
+      name = n;
+      String url = s.line();
+      if (url.endsWith(" (push)")) {
+        push = url.substring(0, url.length() - " (push)".length());
+      }
+      else if (url.endsWith(" (fetch)")) {
+        fetch = url.substring(0, url.length() - " (fetch)".length());
+      }
+      else {
+        fetch = url;
+        push = url;
       }
-      String name = line.substring(0, i);
-      String url = line.substring(i + 1);
-      remotes.add(new GitRemote(name, url));
+    }
+    if (name != null && fetch != null) {
+      if (push == null) {
+        push = fetch;
+      }
+      remotes.add(new GitRemote(name, fetch, push));
     }
     return remotes;
   }
@@ -155,17 +227,48 @@ public final class GitRemote {
     handler.setSilent(true);
     handler.ignoreErrorCode(1);
     handler.addParameters("show", "-n", name);
-    StringScanner in = new StringScanner(handler.run());
+    String output = handler.run();
     if (handler.getExitCode() != 0) {
       return null;
     }
-    if (!in.tryConsume("* ") || !name.equals(in.line()) || !in.hasMoreData()) {
+    return parseRemoteInternal(name, output);
+  }
+
+  /**
+   * Parse output of the remote (internal method)
+   *
+   * @param name   the name of the remote
+   * @param output the output of "git remote show -n {name}" command
+   * @return the parsed remote
+   */
+  public static GitRemote parseRemoteInternal(String name, String output) {
+    StringScanner in = new StringScanner(output);
+    if (!in.tryConsume("* ")) {
       throw new IllegalStateException("Unexpected format for 'git remote show'");
     }
-    if (!in.tryConsume(SHOW_URL_PREFIX)) {
-      throw new IllegalStateException("Unexpected format for 'git remote show'");
+    String nameLine = in.line();
+    if (!nameLine.endsWith(name)) {
+      throw new IllegalStateException("Name line of 'git remote show' ends with wrong name: " + nameLine);
     }
-    return new GitRemote(name, in.line());
+    String fetch = null;
+    String push = null;
+    if (in.tryConsume(SHOW_URL_PREFIX)) {
+      fetch = in.line();
+      push = fetch;
+    }
+    else if (in.tryConsume(SHOW_FETCH_URL_PREFIX)) {
+      fetch = in.line();
+      if (in.tryConsume(SHOW_PUSH_URL_PREFIX)) {
+        push = in.line();
+      }
+      else {
+        push = fetch;
+      }
+    }
+    else {
+      throw new IllegalStateException("Unexpected format for 'git remote show':\n" + output);
+    }
+    return new GitRemote(name, fetch, push);
   }
 
 
@@ -182,32 +285,62 @@ public final class GitRemote {
     handler.setNoSSH(true);
     handler.setSilent(true);
     handler.addParameters("show", "-n", myName);
-    String[] lines = handler.run().split("\n");
+    String output = handler.run();
+    return parseInfoInternal(output);
+  }
+
+
+  /**
+   * Parse remote information
+   *
+   * @param output the output of "git remote show -n {name}" command
+   * @return the parsed remote
+   */
+  public Info parseInfoInternal(String output) {
     TreeMap<String, String> mapping = new TreeMap<String, String>();
     TreeSet<String> branches = new TreeSet<String>();
-    int i = 0;
-    if (!lines[i].startsWith("*") || !lines[i].endsWith(myName)) {
-      throw new IllegalStateException("Unexpected format for 'git remote show' line " + i + ":" + lines[i]);
+    StringScanner s = new StringScanner(output);
+    if (s.tryConsume("* ") && !s.line().endsWith(myName)) {
+      throw new IllegalStateException("Unexpected format for 'git remote show'" + output);
     }
-    if (i >= lines.length) {
-      throw new IllegalStateException("Premature end from 'git remote show' at line " + i);
+    if (!s.hasMoreData()) {
+      throw new IllegalStateException("Premature end from 'git remote show'" + output);
     }
-    i++;
-    if (!lines[i].startsWith(SHOW_URL_PREFIX) || !lines[i].endsWith(myUrl)) {
-      throw new IllegalStateException("Unexpected format for 'git remote show' line " + i + ":" + lines[i]);
-    }
-    i++;
-    while (i < lines.length && lines[i].startsWith(SHOW_MAPPING_PREFIX)) {
-      String local = lines[i].substring(SHOW_MAPPING_PREFIX.length());
-      i++;
-      String remote = lines[i].trim();
-      i++;
-      mapping.put(local, remote);
-    }
-    if (i < lines.length && lines[i].equals(SHOW_BRANCHES_LINE)) {
-      i++;
-      branches.addAll(Arrays.asList(lines[i].substring(4).split(" ")));
+    do {
+      if (s.tryConsume(SHOW_MAPPING_PREFIX)) {
+        // old format
+        String local = s.line();
+        String remote = s.line().trim();
+        mapping.put(local, remote);
+      }
+      else if (s.tryConsume(SHOW_BRANCHES_LINE)) {
+        s.line();
+        if (s.tryConsume("    ")) {
+          branches.addAll(Arrays.asList(s.line().split(" ")));
+        }
+      }
+      else if (s.tryConsume("  Remote branch")) {
+        s.line();
+        while (s.tryConsume("    ")) {
+          branches.add(s.line().trim());
+        }
+      }
+      else if (s.tryConsume("  Local branch configured for 'git pull':")) {
+        s.line();
+        while (s.tryConsume("    ")) {
+          Matcher m = PULL_PATTERN.matcher(s.line());
+          if (m.matches()) {
+            String local = m.group(1);
+            String remote = m.group(2);
+            mapping.put(local, remote);
+          }
+        }
+      }
+      else {
+        s.line();
+      }
     }
+    while (s.hasMoreData());
     return new Info(Collections.unmodifiableSortedMap(mapping), Collections.unmodifiableSortedSet(branches));
   }
 
index 9665d14c4b3fdfd2f48292743558ce7a1dd40d22..ce8fe3a2e94505b360b1eade69dbcf42c27258fe 100644 (file)
@@ -373,7 +373,7 @@ public class GitPushDialog extends DialogWrapper {
           GitTag.listAsStrings(myProject, getGitRoot(), myTagNames);
         }
         catch (VcsException ex) {
-          LOG.warn("Exception in branchlist: \n" + StringUtil.getThrowableText(ex));
+          LOG.warn("Exception in branch list: \n" + StringUtil.getThrowableText(ex));
         }
       }
     };
@@ -385,7 +385,7 @@ public class GitPushDialog extends DialogWrapper {
    * Update remotes
    */
   private void updateRemotes() {
-    GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemoteComboBox);
+    GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemoteComboBox, false);
   }
 
   /**
index 87d2933ec4a40229b09a9fc6d271345b8165b72e..1ecb1e462ba04b5eeb4d62ad54e7ead0ac257ebc 100644 (file)
@@ -264,7 +264,7 @@ public class GitPullDialog extends DialogWrapper {
    * Update remotes for the git root
    */
   private void updateRemotes() {
-    GitUIUtil.setupRemotes(myProject, gitRoot(), currentBranch(), myRemote);
+    GitUIUtil.setupRemotes(myProject, gitRoot(), currentBranch(), myRemote, true);
   }
 
   /**
index 0b28f51fb05817e300c0dce3dcf92e4bc3a93160..2848571a48cdb4a382b5674e2c95d3b12b281750 100644 (file)
@@ -171,7 +171,7 @@ public class GitFetchDialog extends DialogWrapper {
    * Update remotes
    */
   private void updateRemotes() {
-    GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemote);
+    GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemote, true);
   }
 
   /**
index 2aaccdac0e2bce53e0b7f335cfeda036e1878ccc..73576bbcdd7ddc53dcb639d1d0243304b64e0ec3 100644 (file)
@@ -82,9 +82,10 @@ public class GitUIUtil {
    * remote for the branch with bold.
    *
    * @param defaultRemote a default remote
+   * @param fetchUrl      if true, the fetch url is shown
    * @return a list cell renderer for virtual files (it renders presentable URL
    */
-  public static ListCellRenderer getGitRemoteListCellRenderer(final String defaultRemote) {
+  public static ListCellRenderer getGitRemoteListCellRenderer(final String defaultRemote, final boolean fetchUrl) {
     return new DefaultListCellRenderer() {
       public Component getListCellRendererComponent(final JList list,
                                                     final Object value,
@@ -107,7 +108,7 @@ public class GitUIUtil {
           else {
             key = "util.remote.renderer.normal";
           }
-          text = GitBundle.message(key, remote.name(), remote.url());
+          text = GitBundle.message(key, remote.name(), fetchUrl ? remote.fetchUrl() : remote.pushUrl());
         }
         return super.getListCellRendererComponent(list, text, index, isSelected, cellHasFocus);
       }
@@ -198,8 +199,9 @@ public class GitUIUtil {
    * @param project        the project
    * @param root           the git root
    * @param remoteCombobox the combobox to update
+   * @param fetchUrl       if true, the fetch url is shown instead of push url
    */
-  public static void setupRemotes(final Project project, final VirtualFile root, final JComboBox remoteCombobox) {
+  public static void setupRemotes(final Project project, final VirtualFile root, final JComboBox remoteCombobox, final boolean fetchUrl) {
     GitBranch gitBranch = null;
     try {
       gitBranch = GitBranch.current(project, root);
@@ -208,7 +210,7 @@ public class GitUIUtil {
       // ignore error
     }
     final String branch = gitBranch != null ? gitBranch.getName() : null;
-    setupRemotes(project, root, branch, remoteCombobox);
+    setupRemotes(project, root, branch, remoteCombobox, fetchUrl);
 
   }
 
@@ -220,18 +222,20 @@ public class GitUIUtil {
    * @param root           the git root
    * @param currentBranch  the current branch
    * @param remoteCombobox the combobox to update
+   * @param fetchUrl       if true, the fetch url is shown for remotes, push otherwise
    */
   public static void setupRemotes(final Project project,
                                   final VirtualFile root,
                                   final String currentBranch,
-                                  final JComboBox remoteCombobox) {
+                                  final JComboBox remoteCombobox,
+                                  final boolean fetchUrl) {
     try {
       List<GitRemote> remotes = GitRemote.list(project, root);
       String remote = null;
       if (currentBranch != null) {
         remote = GitConfigUtil.getValue(project, root, "branch." + currentBranch + ".remote");
       }
-      remoteCombobox.setRenderer(getGitRemoteListCellRenderer(remote));
+      remoteCombobox.setRenderer(getGitRemoteListCellRenderer(remote, fetchUrl));
       GitRemote toSelect = null;
       remoteCombobox.removeAllItems();
       for (GitRemote r : remotes) {
diff --git a/plugins/git4idea/tests/git4idea/tests/GitRemoteTest.java b/plugins/git4idea/tests/git4idea/tests/GitRemoteTest.java
new file mode 100644 (file)
index 0000000..d20d549
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2000-2009 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.tests;
+
+import com.intellij.openapi.util.io.FileUtil;
+import git4idea.GitRemote;
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.List;
+import java.util.TreeSet;
+
+/**
+ * Test for Git remotes
+ */
+public class GitRemoteTest {
+  /**
+   * Get output of git remote show
+   *
+   * @param version the version of output
+   * @param op      operation to test
+   * @return the output text
+   * @throws IOException if some problem during loading
+   */
+  String getOutput(String version, String op) throws IOException {
+    return FileUtil.loadTextAndClose(
+      new InputStreamReader(getClass().getResourceAsStream("/git4idea/tests/data/git-remote-" + op + "-" + version + ".txt"), "UTF-8"));
+  }
+
+  @Test
+  public void testFindRemote() throws IOException {
+    String out161 = getOutput("1_6_1", "show");
+    GitRemote r1 = GitRemote.parseRemoteInternal("origin", out161);
+    assertEquals(r1.name(), "origin");
+    assertEquals(r1.fetchUrl(), "git@host/dir.git");
+    assertEquals(r1.pushUrl(), "git@host/dir.git");
+    GitRemote.Info ri1 = r1.parseInfoInternal(out161);
+    TreeSet<String> branches = new TreeSet<String>();
+    branches.add("master");
+    assertEquals(branches, ri1.trackedBranches());
+    assertEquals("master", ri1.getRemoteForLocal("master"));
+    String out164 = getOutput("1_6_4", "show");
+    GitRemote r2 = GitRemote.parseRemoteInternal("origin", out164);
+    assertEquals(r2.name(), "origin");
+    assertEquals(r2.fetchUrl(), "git://host/dir.git");
+    assertEquals(r2.pushUrl(), "git@host/dir.git");
+    GitRemote.Info ri2 = r1.parseInfoInternal(out164);
+    branches.add("master");
+    assertEquals(branches, ri2.trackedBranches());
+    assertEquals("master", ri2.getRemoteForLocal("master"));
+  }
+
+  @Test
+  public void testListRemote() throws IOException {
+    String out161 = getOutput("1_6_1", "list");
+    List<GitRemote> list = GitRemote.parseRemoteListInternal(out161);
+    assertEquals(1, list.size());
+    GitRemote r1 = list.get(0);
+    assertEquals("origin", r1.name());
+    assertEquals("git@host/dir.git", r1.fetchUrl());
+    assertEquals("git@host/dir.git", r1.pushUrl());
+    String out164 = getOutput("1_6_4", "list");
+    List<GitRemote> list2 = GitRemote.parseRemoteListInternal(out164);
+    assertEquals(1, list2.size());
+    GitRemote r2 = list2.get(0);
+    assertEquals("git://host/dir.git", r2.fetchUrl());
+    assertEquals("git@host/dir.git", r2.pushUrl());
+  }
+}
diff --git a/plugins/git4idea/tests/git4idea/tests/data/git-remote-list-1_6_1.txt b/plugins/git4idea/tests/git4idea/tests/data/git-remote-list-1_6_1.txt
new file mode 100644 (file)
index 0000000..0255108
--- /dev/null
@@ -0,0 +1 @@
+origin git@host/dir.git
diff --git a/plugins/git4idea/tests/git4idea/tests/data/git-remote-list-1_6_4.txt b/plugins/git4idea/tests/git4idea/tests/data/git-remote-list-1_6_4.txt
new file mode 100644 (file)
index 0000000..6fb3cf1
--- /dev/null
@@ -0,0 +1,2 @@
+origin git://host/dir.git (fetch)
+origin git@host/dir.git (push)
diff --git a/plugins/git4idea/tests/git4idea/tests/data/git-remote-show-1_6_1.txt b/plugins/git4idea/tests/git4idea/tests/data/git-remote-show-1_6_1.txt
new file mode 100644 (file)
index 0000000..69a7881
--- /dev/null
@@ -0,0 +1,6 @@
+* remote origin
+  URL: git@host/dir.git
+  Remote branch merged with 'git pull' while on branch master
+    master
+  Tracked remote branch
+    master
diff --git a/plugins/git4idea/tests/git4idea/tests/data/git-remote-show-1_6_4.txt b/plugins/git4idea/tests/git4idea/tests/data/git-remote-show-1_6_4.txt
new file mode 100644 (file)
index 0000000..2e5f5e2
--- /dev/null
@@ -0,0 +1,10 @@
+* remote origin
+  Fetch URL: git://host/dir.git
+  Push  URL: git@host/dir.git
+  HEAD branch: (not queried)
+  Remote branch: (status not queried)
+    master
+  Local branch configured for 'git pull':
+    master merges with remote master
+  Local ref configured for 'git push' (status not queried):
+    (matching) pushes to (matching)