IDEA-80512 Watch for refs/remotes and refs/heads dirs recursively; update branches...
[idea/community.git] / plugins / git4idea / src / git4idea / update / GitFetcher.java
1 /*
2  * Copyright 2000-2011 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package git4idea.update;
17
18 import com.intellij.notification.NotificationType;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.progress.ProgressIndicator;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.openapi.vcs.VcsException;
23 import com.intellij.openapi.vfs.VirtualFile;
24 import git4idea.GitVcs;
25 import git4idea.commands.*;
26 import git4idea.jgit.GitHttpAdapter;
27 import git4idea.repo.GitRemote;
28 import git4idea.repo.GitRepository;
29 import git4idea.repo.GitRepositoryManager;
30 import git4idea.util.GitUIUtil;
31 import org.jetbrains.annotations.NotNull;
32 import org.jetbrains.annotations.Nullable;
33
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.concurrent.atomic.AtomicBoolean;
37
38 /**
39  * @author Kirill Likhodedov
40  */
41 public class GitFetcher {
42
43   private static final Logger LOG = Logger.getInstance(GitFetcher.class);
44
45   private final Project myProject;
46   private final GitRepositoryManager myRepositoryManager;
47   private final ProgressIndicator myProgressIndicator;
48
49   private final Collection<Exception> myErrors = new ArrayList<Exception>();
50
51   public GitFetcher(@NotNull Project project, @NotNull ProgressIndicator progressIndicator) {
52     myProject = project;
53     myProgressIndicator = progressIndicator;
54     myRepositoryManager = GitRepositoryManager.getInstance(project);
55   }
56
57   /**
58    * Invokes 'git fetch'.
59    * @return true if fetch was successful, false in the case of error.
60    */
61   public GitFetchResult fetch(@NotNull VirtualFile root) {
62     GitRepository repository = myRepositoryManager.getRepositoryForRoot(root);
63     assert repository != null : "Repository can't be null for " + root + "\n" + myRepositoryManager;
64     
65     GitFetchResult result = GitFetchResult.success();
66     for (GitRemote remote : repository.getRemotes()) {
67       String url = remote.getFirstUrl();
68       if (url == null) {
69         continue;
70       }
71       if (GitHttpAdapter.shouldUseJGit(url)) {
72         GitFetchResult res = GitHttpAdapter.fetch(repository, remote, url);
73         myErrors.addAll(res.getErrors());
74         if (!res.isSuccess()) {
75           result = res;
76           break;
77         }
78       } else {
79         if (!fetchNatively(root, remote)) {
80           result = GitFetchResult.error(myErrors);
81           break;
82         }
83       }
84     }
85     
86     repository.update(GitRepository.TrackedTopic.BRANCHES);
87     return result;
88   }
89
90   private boolean fetchNatively(@NotNull VirtualFile root, @NotNull GitRemote remote) {
91     final GitLineHandlerPasswordRequestAware h = new GitLineHandlerPasswordRequestAware(myProject, root, GitCommand.FETCH);
92     h.addParameters(remote.getName());
93     final GitTask fetchTask = new GitTask(myProject, h, "Fetching...");
94     fetchTask.setProgressIndicator(myProgressIndicator);
95     fetchTask.setProgressAnalyzer(new GitStandardProgressAnalyzer());
96     final AtomicBoolean success = new AtomicBoolean();
97     fetchTask.execute(true, false, new GitTaskResultHandlerAdapter() {
98       @Override
99       protected void onSuccess() {
100         success.set(true);
101       }
102
103       @Override
104       protected void onCancel() {
105         LOG.info("Cancelled fetch.");
106       }
107       
108       @Override
109       protected void onFailure() {
110         LOG.info("Error fetching: " + h.errors());
111         if (!h.hadAuthRequest()) {
112           myErrors.addAll(h.errors());
113         } else {
114           myErrors.add(new VcsException("Authentication failed"));
115         }
116       }
117     });
118     return success.get();
119   }
120
121   @NotNull
122   public Collection<Exception> getErrors() {
123     return myErrors;
124   }
125
126   public static void displayFetchResult(@NotNull Project project,
127                                         @NotNull GitFetchResult result,
128                                         @Nullable String errorNotificationTitle, @NotNull Collection<? extends Exception> errors) {
129     if (result.isSuccess()) {
130       GitVcs.NOTIFICATION_GROUP_ID.createNotification("Fetched successfully", NotificationType.WARNING).notify(project);
131     } else if (result.isCancelled()) {
132       GitVcs.NOTIFICATION_GROUP_ID.createNotification("Fetch cancelled by user", NotificationType.WARNING).notify(project);
133     } else if (result.isNotAuthorized()) {
134       String title;
135       String description;
136       if (errorNotificationTitle != null) {
137         title = errorNotificationTitle;
138         description = "Fetch failed: couldn't authorize";
139       } else {
140         title = "Fetch failed";
141         description = "Couldn't authorize";
142       }
143       GitUIUtil.notifyMessage(project, title, description, NotificationType.ERROR, true, null);
144     } else {
145       GitVcs instance = GitVcs.getInstance(project);
146       if (instance != null && instance.getExecutableValidator().isExecutableValid()) {
147         GitUIUtil.notifyMessage(project, "Fetch failed", null, NotificationType.ERROR, true, errors);
148       }
149     }
150   }
151
152   /**
153    * Fetches all specified roots.
154    * Once a root has failed, stops and displays the notification.
155    * If needed, displays the successful notification at the end.
156    * @param roots                   roots to fetch.
157    * @param errorNotificationTitle  if specified, this notification title will be used instead of the standard "Fetch failed".
158    *                                Use this when fetch is a part of a compound process.
159    * @param notifySuccess           if set to {@code true} successful notification will be displayed.
160    * @return true if all fetches were successful, false if at least one fetch failed.
161    */
162   public boolean fetchRootsAndNotify(@NotNull Collection<VirtualFile> roots, @Nullable String errorNotificationTitle, boolean notifySuccess) {
163     for (VirtualFile root : roots) {
164       GitFetchResult result = fetch(root);
165       if (!result.isSuccess()) {
166         displayFetchResult(myProject, result, errorNotificationTitle, getErrors());
167         return false;
168       }
169     }
170     if (notifySuccess) {
171       GitUIUtil.notifySuccess(myProject, "", "Fetched successfully");
172     }
173     return true;
174   }
175 }