git4idea: Fixed some compatibility problem with Git 1.6.4
[fedora-idea.git] / plugins / git4idea / src / git4idea / GitRemote.java
blob4137ce2fcadf6b6d0c1ed9a22a057698d7f836e9
1 /*
2 * Copyright 2000-2008 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package git4idea;
18 import com.intellij.openapi.project.Project;
19 import com.intellij.openapi.util.Pair;
20 import com.intellij.openapi.util.io.FileUtil;
21 import com.intellij.openapi.vcs.VcsException;
22 import com.intellij.openapi.vfs.VfsUtil;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import git4idea.commands.GitHandler;
25 import git4idea.commands.GitSimpleHandler;
26 import git4idea.commands.StringScanner;
27 import git4idea.config.GitConfigUtil;
28 import org.jetbrains.annotations.NonNls;
29 import org.jetbrains.annotations.NotNull;
30 import org.jetbrains.annotations.Nullable;
32 import java.io.File;
33 import java.io.FileInputStream;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.util.*;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
40 /**
41 * A git remotes
43 public final class GitRemote {
44 /**
45 * The name of the remote
47 private final String myName;
48 /**
49 * The fetch url of the remote
51 private final String myFetchUrl;
52 /**
53 * The push url of the remote
55 private String myPushUrl;
56 /**
57 * Prefix for url in "git remote show -n {branch}"
59 @NonNls private static final String SHOW_URL_PREFIX = " URL: ";
60 /**
61 * Prefix for url in "git remote show -n {branch}"
63 @NonNls private static final String SHOW_FETCH_URL_PREFIX = " Fetch URL: ";
64 /**
65 * Prefix for url in "git remote show -n {branch}"
67 @NonNls private static final String SHOW_PUSH_URL_PREFIX = " Push URL: ";
68 /**
69 * Prefix for local branch mapping in "git remote show -n {branch}"
71 @NonNls private static final String SHOW_MAPPING_PREFIX = " Remote branch merged with 'git pull' while on branch ";
72 /**
73 * line that starts branches section in "git remote show -n {branch}"
75 @NonNls private static final String SHOW_BRANCHES_LINE = " Tracked remote branch";
76 /**
77 * US-ASCII encoding name
79 @NonNls private static final String US_ASCII_ENCODING = "US-ASCII";
80 /**
81 * Pattern that parses pull spec
83 private static final Pattern PULL_PATTERN = Pattern.compile("(\\S+)\\s+merges with remote (\\S+)");
85 /**
86 * A constructor
88 * @param name the name
89 * @param url the url
91 public GitRemote(@NotNull final String name, final String url) {
92 this(name, url, url);
95 /**
96 * A constructor
98 * @param name the name
99 * @param fetchUrl the fetch url
100 * @param pushUrl the fetch url
102 public GitRemote(String name, String fetchUrl, String pushUrl) {
103 myName = name;
104 myFetchUrl = fetchUrl;
105 myPushUrl = pushUrl;
109 * @return the name of the remote
111 public String name() {
112 return myName;
116 * @return the fetch url of the remote
118 public String fetchUrl() {
119 return myFetchUrl;
123 * @return the push url of the remote
125 public String pushUrl() {
126 return myPushUrl;
130 * {@inheritDoc}
132 @Override
133 public int hashCode() {
134 return myName.hashCode();
138 * {@inheritDoc}
140 @Override
141 public boolean equals(final Object obj) {
142 return (obj instanceof GitRemote) && myName.equals(((GitRemote)obj).myName);
146 * {@inheritDoc}
148 @Override
149 public String toString() {
150 return myName;
154 * List all remotes for the git root (git remote -v)
156 * @param project the context project
157 * @param root the git root
158 * @return a list of registered remotes
159 * @throws VcsException in case of git error
161 public static List<GitRemote> list(Project project, VirtualFile root) throws VcsException {
162 GitSimpleHandler handler = new GitSimpleHandler(project, root, GitHandler.REMOTE);
163 handler.setNoSSH(true);
164 handler.setSilent(true);
165 handler.addParameters("-v");
166 String output = handler.run();
167 return parseRemoteListInternal(output);
171 * Parse list of remotes (internal method)
173 * @param output the output to parse
174 * @return list of remotes
176 public static List<GitRemote> parseRemoteListInternal(String output) {
177 ArrayList<GitRemote> remotes = new ArrayList<GitRemote>();
178 StringScanner s = new StringScanner(output);
179 String name = null;
180 String fetch = null;
181 String push = null;
182 while (s.hasMoreData()) {
183 String n = s.tabToken();
184 if (name != null && !n.equals(name) && fetch != null) {
185 if (push == null) {
186 push = fetch;
188 remotes.add(new GitRemote(name, fetch, push));
189 fetch = null;
190 push = null;
192 name = n;
193 String url = s.line();
194 if (url.endsWith(" (push)")) {
195 push = url.substring(0, url.length() - " (push)".length());
197 else if (url.endsWith(" (fetch)")) {
198 fetch = url.substring(0, url.length() - " (fetch)".length());
200 else {
201 fetch = url;
202 push = url;
205 if (name != null && fetch != null) {
206 if (push == null) {
207 push = fetch;
209 remotes.add(new GitRemote(name, fetch, push));
211 return remotes;
215 * Get information about remote stored in locally (remote end is not queried about branches)
217 * @param project the context project
218 * @param root the VCS root
219 * @param name the name of the of the remote to find
220 * @return a information about remotes
221 * @throws VcsException if there is a problem with running git
223 @Nullable
224 public static GitRemote find(Project project, VirtualFile root, String name) throws VcsException {
225 GitSimpleHandler handler = new GitSimpleHandler(project, root, GitHandler.REMOTE);
226 handler.setNoSSH(true);
227 handler.setSilent(true);
228 handler.ignoreErrorCode(1);
229 handler.addParameters("show", "-n", name);
230 String output = handler.run();
231 if (handler.getExitCode() != 0) {
232 return null;
234 return parseRemoteInternal(name, output);
238 * Parse output of the remote (internal method)
240 * @param name the name of the remote
241 * @param output the output of "git remote show -n {name}" command
242 * @return the parsed remote
244 public static GitRemote parseRemoteInternal(String name, String output) {
245 StringScanner in = new StringScanner(output);
246 if (!in.tryConsume("* ")) {
247 throw new IllegalStateException("Unexpected format for 'git remote show'");
249 String nameLine = in.line();
250 if (!nameLine.endsWith(name)) {
251 throw new IllegalStateException("Name line of 'git remote show' ends with wrong name: " + nameLine);
253 String fetch = null;
254 String push = null;
255 if (in.tryConsume(SHOW_URL_PREFIX)) {
256 fetch = in.line();
257 push = fetch;
259 else if (in.tryConsume(SHOW_FETCH_URL_PREFIX)) {
260 fetch = in.line();
261 if (in.tryConsume(SHOW_PUSH_URL_PREFIX)) {
262 push = in.line();
264 else {
265 push = fetch;
268 else {
269 throw new IllegalStateException("Unexpected format for 'git remote show':\n" + output);
271 return new GitRemote(name, fetch, push);
276 * Get information about remote stored in locally (remote end is not queried about branches)
278 * @param project the current project
279 * @param root the VCS root
280 * @return a information about remotes
281 * @throws VcsException if there is a problem with running git
283 public Info localInfo(Project project, VirtualFile root) throws VcsException {
284 GitSimpleHandler handler = new GitSimpleHandler(project, root, GitHandler.REMOTE);
285 handler.setNoSSH(true);
286 handler.setSilent(true);
287 handler.addParameters("show", "-n", myName);
288 String output = handler.run();
289 return parseInfoInternal(output);
294 * Parse remote information
296 * @param output the output of "git remote show -n {name}" command
297 * @return the parsed remote
299 public Info parseInfoInternal(String output) {
300 TreeMap<String, String> mapping = new TreeMap<String, String>();
301 TreeSet<String> branches = new TreeSet<String>();
302 StringScanner s = new StringScanner(output);
303 if (s.tryConsume("* ") && !s.line().endsWith(myName)) {
304 throw new IllegalStateException("Unexpected format for 'git remote show'" + output);
306 if (!s.hasMoreData()) {
307 throw new IllegalStateException("Premature end from 'git remote show'" + output);
309 do {
310 if (s.tryConsume(SHOW_MAPPING_PREFIX)) {
311 // old format
312 String local = s.line();
313 String remote = s.line().trim();
314 mapping.put(local, remote);
316 else if (s.tryConsume(SHOW_BRANCHES_LINE)) {
317 s.line();
318 if (s.tryConsume(" ")) {
319 branches.addAll(Arrays.asList(s.line().split(" ")));
322 else if (s.tryConsume(" Remote branch")) {
323 s.line();
324 while (s.tryConsume(" ")) {
325 branches.add(s.line().trim());
328 else if (s.tryConsume(" Local branch configured for 'git pull':")) {
329 s.line();
330 while (s.tryConsume(" ")) {
331 Matcher m = PULL_PATTERN.matcher(s.line());
332 if (m.matches()) {
333 String local = m.group(1);
334 String remote = m.group(2);
335 mapping.put(local, remote);
339 else {
340 s.line();
343 while (s.hasMoreData());
344 return new Info(Collections.unmodifiableSortedMap(mapping), Collections.unmodifiableSortedSet(branches));
348 * Get list of fetch specifications for the configured remote
350 * @param project the project name
351 * @param root the git root
352 * @param remoteName the name of the remote
353 * @return the configured fetch specifications for remote
354 * @throws VcsException if there is a problem with running git
356 public static List<String> getFetchSpecs(Project project, VirtualFile root, String remoteName) throws VcsException {
357 ArrayList<String> rc = new ArrayList<String>();
358 final File rootFile = VfsUtil.virtualToIoFile(root);
359 @NonNls final File remotesFile = new File(rootFile, ".git" + File.separator + "remotes" + File.separator + remoteName);
360 // TODO try branches file?
361 if (remotesFile.exists() && !remotesFile.isDirectory()) {
362 // try remotes file
363 try {
364 //noinspection IOResourceOpenedButNotSafelyClosed
365 String text = FileUtil.loadTextAndClose(new InputStreamReader(new FileInputStream(remotesFile), US_ASCII_ENCODING));
366 @NonNls String pullPrefix = "Pull:";
367 for (StringScanner s = new StringScanner(text); s.hasMoreData();) {
368 String line = s.line();
369 if (line.startsWith(pullPrefix)) {
370 rc.add(line.substring(pullPrefix.length()).trim());
374 catch (IOException e) {
375 throw new VcsException("Unable to read remotes file: " + remotesFile, e);
378 else {
379 // try .git/config file
380 for (Pair<String, String> pair : GitConfigUtil.getAllValues(project, root, "remote." + remoteName + ".fetch")) {
381 rc.add(pair.second);
384 return rc;
388 * Information about git remote
390 public class Info {
392 * Branch mappings
394 private final Map<String, String> myBranchMapping;
396 * Tracked remote branches
398 private final Set<String> myTrackedRemotes;
401 * A constructor from fields
403 * @param branchMapping a map from local branches to remote branches
404 * @param trackedRemotes a set of tracked remotes
406 public Info(final Map<String, String> branchMapping, final Set<String> trackedRemotes) {
407 myBranchMapping = branchMapping;
408 myTrackedRemotes = trackedRemotes;
412 * @return a remote for this information object
414 public GitRemote remote() {
415 return GitRemote.this;
419 * Get remote branch for the local branch
421 * @param localBranchName a local branch name
422 * @return a remote branch name or null if the mapping is not found
424 @Nullable
425 public String getRemoteForLocal(final String localBranchName) {
426 if (localBranchName == null) {
427 return null;
429 return myBranchMapping.get(localBranchName);
433 * A set of tracked remotes
435 * @return a set of tracked remotes
437 public Set<String> trackedBranches() {
438 return myTrackedRemotes;