2 * Copyright 2000-2009 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.
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
;
33 import java
.io
.FileInputStream
;
34 import java
.io
.IOException
;
35 import java
.io
.InputStreamReader
;
37 import java
.util
.regex
.Matcher
;
38 import java
.util
.regex
.Pattern
;
43 public final class GitRemote
{
45 * The name of the remote
47 private final String myName
;
49 * The fetch url of the remote
51 private final String myFetchUrl
;
53 * The push url of the remote
55 private String myPushUrl
;
57 * Prefix for url in "git remote show -n {branch}"
59 @NonNls private static final String SHOW_URL_PREFIX
= " URL: ";
61 * Prefix for url in "git remote show -n {branch}"
63 @NonNls private static final String SHOW_FETCH_URL_PREFIX
= " Fetch URL: ";
65 * Prefix for url in "git remote show -n {branch}"
67 @NonNls private static final String SHOW_PUSH_URL_PREFIX
= " Push URL: ";
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 ";
73 * line that starts branches section in "git remote show -n {branch}"
75 @NonNls private static final String SHOW_BRANCHES_LINE
= " Tracked remote branch";
77 * US-ASCII encoding name
79 @NonNls private static final String US_ASCII_ENCODING
= "US-ASCII";
81 * Pattern that parses pull spec
83 private static final Pattern PULL_PATTERN
= Pattern
.compile("(\\S+)\\s+merges with remote (\\S+)");
88 * @param name the name
91 public GitRemote(@NotNull final String name
, final String url
) {
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
) {
104 myFetchUrl
= fetchUrl
;
109 * @return the name of the remote
111 public String
name() {
116 * @return the fetch url of the remote
118 public String
fetchUrl() {
123 * @return the push url of the remote
125 public String
pushUrl() {
133 public int hashCode() {
134 return myName
.hashCode();
141 public boolean equals(final Object obj
) {
142 return (obj
instanceof GitRemote
) && myName
.equals(((GitRemote
)obj
).myName
);
149 public String
toString() {
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
);
182 while (s
.hasMoreData()) {
183 String n
= s
.tabToken();
184 if (name
!= null && !n
.equals(name
) && fetch
!= null) {
188 remotes
.add(new GitRemote(name
, fetch
, push
));
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());
205 if (name
!= null && fetch
!= null) {
209 remotes
.add(new GitRemote(name
, fetch
, push
));
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
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) {
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
);
255 if (in
.tryConsume(SHOW_URL_PREFIX
)) {
259 else if (in
.tryConsume(SHOW_FETCH_URL_PREFIX
)) {
261 if (in
.tryConsume(SHOW_PUSH_URL_PREFIX
)) {
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
);
310 if (s
.tryConsume(SHOW_MAPPING_PREFIX
)) {
312 String local
= s
.line();
313 String remote
= s
.line().trim();
314 mapping
.put(local
, remote
);
316 else if (s
.tryConsume(SHOW_BRANCHES_LINE
)) {
318 if (s
.tryConsume(" ")) {
319 branches
.addAll(Arrays
.asList(s
.line().split(" ")));
322 else if (s
.tryConsume(" Remote branch")) {
324 while (s
.tryConsume(" ")) {
325 branches
.add(s
.line().trim());
328 else if (s
.tryConsume(" Local branch configured for 'git pull':")) {
330 while (s
.tryConsume(" ")) {
331 Matcher m
= PULL_PATTERN
.matcher(s
.line());
333 String local
= m
.group(1);
334 String remote
= m
.group(2);
335 mapping
.put(local
, remote
);
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()) {
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
);
379 // try .git/config file
380 for (Pair
<String
, String
> pair
: GitConfigUtil
.getAllValues(project
, root
, "remote." + remoteName
+ ".fetch")) {
388 * Information about git remote
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
425 public String
getRemoteForLocal(final String localBranchName
) {
426 if (localBranchName
== 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
;