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;
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));
+ }
}
}
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* A git remotes
*/
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}"
*/
/**
* 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
* @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;
}
/**
}
/**
- * @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;
}
/**
* @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;
}
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);
}
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));
}
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));
}
}
};
* Update remotes
*/
private void updateRemotes() {
- GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemoteComboBox);
+ GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemoteComboBox, false);
}
/**
* Update remotes for the git root
*/
private void updateRemotes() {
- GitUIUtil.setupRemotes(myProject, gitRoot(), currentBranch(), myRemote);
+ GitUIUtil.setupRemotes(myProject, gitRoot(), currentBranch(), myRemote, true);
}
/**
* Update remotes
*/
private void updateRemotes() {
- GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemote);
+ GitUIUtil.setupRemotes(myProject, getGitRoot(), myRemote, true);
}
/**
* 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,
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);
}
* @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);
// ignore error
}
final String branch = gitBranch != null ? gitBranch.getName() : null;
- setupRemotes(project, root, branch, remoteCombobox);
+ setupRemotes(project, root, branch, remoteCombobox, fetchUrl);
}
* @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) {
--- /dev/null
+/*
+ * 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());
+ }
+}
--- /dev/null
+origin git@host/dir.git
--- /dev/null
+origin git://host/dir.git (fetch)
+origin git@host/dir.git (push)
--- /dev/null
+* remote origin
+ URL: git@host/dir.git
+ Remote branch merged with 'git pull' while on branch master
+ master
+ Tracked remote branch
+ master
--- /dev/null
+* 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)