Avoid refresh on up-to-date pull operation
[egit/eclipse.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / op / PullOperation.java
blob5b3da395ba2c2eb16e6988557ad6ca0ad6e60bb2
1 /*******************************************************************************
2 * Copyright (c) 2010, 2016 SAP AG and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License 2.0
5 * which accompanies this distribution, and is available at
6 * https://www.eclipse.org/legal/epl-2.0/
8 * SPDX-License-Identifier: EPL-2.0
10 * Contributors:
11 * Mathias Kinzler <mathias.kinzler@sap.com> - initial implementation
12 * Laurent Delaigue (Obeo) - use of preferred merge strategy
13 * Stephan Hackstedt - bug 477695
14 * Mickael Istria (Red Hat Inc.) - [485124] Introduce PullReferenceConfig
15 *******************************************************************************/
16 package org.eclipse.egit.core.op;
18 import java.io.IOException;
19 import java.util.Arrays;
20 import java.util.Collections;
21 import java.util.LinkedHashMap;
22 import java.util.Map;
23 import java.util.Set;
25 import org.eclipse.core.resources.IProject;
26 import org.eclipse.core.resources.IWorkspace;
27 import org.eclipse.core.resources.IWorkspaceRunnable;
28 import org.eclipse.core.resources.ResourcesPlugin;
29 import org.eclipse.core.runtime.CoreException;
30 import org.eclipse.core.runtime.IProgressMonitor;
31 import org.eclipse.core.runtime.IStatus;
32 import org.eclipse.core.runtime.Status;
33 import org.eclipse.core.runtime.SubMonitor;
34 import org.eclipse.core.runtime.jobs.ISchedulingRule;
35 import org.eclipse.egit.core.Activator;
36 import org.eclipse.egit.core.EclipseGitProgressTransformer;
37 import org.eclipse.egit.core.internal.CoreText;
38 import org.eclipse.egit.core.internal.job.RuleUtil;
39 import org.eclipse.egit.core.internal.util.ProjectUtil;
40 import org.eclipse.jgit.annotations.NonNull;
41 import org.eclipse.jgit.annotations.Nullable;
42 import org.eclipse.jgit.api.Git;
43 import org.eclipse.jgit.api.MergeResult;
44 import org.eclipse.jgit.api.MergeResult.MergeStatus;
45 import org.eclipse.jgit.api.PullCommand;
46 import org.eclipse.jgit.api.PullResult;
47 import org.eclipse.jgit.api.RebaseResult;
48 import org.eclipse.jgit.api.errors.DetachedHeadException;
49 import org.eclipse.jgit.api.errors.GitAPIException;
50 import org.eclipse.jgit.api.errors.InvalidConfigurationException;
51 import org.eclipse.jgit.api.errors.JGitInternalException;
52 import org.eclipse.jgit.errors.TransportException;
53 import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
54 import org.eclipse.jgit.lib.ConfigConstants;
55 import org.eclipse.jgit.lib.Constants;
56 import org.eclipse.jgit.lib.Repository;
57 import org.eclipse.jgit.lib.StoredConfig;
58 import org.eclipse.jgit.merge.MergeStrategy;
59 import org.eclipse.jgit.transport.CredentialsProvider;
60 import org.eclipse.osgi.util.NLS;
62 /**
63 * Wraps the JGit API {@link PullCommand} into an operation
65 public class PullOperation implements IEGitOperation {
67 /**
68 * This describe a specification of a Pull command
69 * @since 4.2
71 public static class PullReferenceConfig {
72 private String remote;
74 private String reference;
76 private BranchRebaseMode upstreamConfig;
78 /**
79 * @param remote
80 * @param reference
81 * @param upstreamConfig
83 public PullReferenceConfig(@Nullable String remote,
84 @Nullable String reference,
85 @Nullable BranchRebaseMode upstreamConfig) {
86 this.remote = remote;
87 this.reference = reference;
88 this.upstreamConfig = upstreamConfig;
91 /**
92 * @return the remote to pull from. Can be null (in which case client is
93 * free to either ignore the pull, send an error, use default
94 * configuration for the current branch...)
96 @Nullable
97 public String getRemote() {
98 return this.remote;
102 * @return the reference (commit, tag, id...) to pull from. Can be null
103 * (in which case client is free to either ignore the pull, send
104 * an error, use default configuration for the current
105 * branch...)
107 @Nullable
108 public String getReference() {
109 return this.reference;
113 * @return the upstream config strategy to use for the specified pull
115 @Nullable
116 public BranchRebaseMode getUpstreamConfig() {
117 return this.upstreamConfig;
121 private final Repository[] repositories;
123 private Map<Repository, PullReferenceConfig> configs;
125 private final Map<Repository, Object> results = new LinkedHashMap<Repository, Object>();
127 private final int timeout;
129 private CredentialsProvider credentialsProvider;
132 * @param repositories
133 * the repositories
134 * @param timeout
135 * in seconds
137 public PullOperation(Set<Repository> repositories, int timeout) {
138 this.timeout = timeout;
139 this.repositories = repositories.toArray(new Repository[repositories
140 .size()]);
141 this.configs = Collections.emptyMap();
145 * @param repositories
146 * Repositories to pull, with specific configuration
147 * @param timeout
148 * in seconds
149 * @since 4.2
151 public PullOperation(
152 @NonNull Map<Repository, PullReferenceConfig> repositories,
153 int timeout) {
154 this(repositories.keySet(), timeout);
155 this.configs = repositories;
158 @Override
159 public void execute(IProgressMonitor m) throws CoreException {
160 if (!results.isEmpty())
161 throw new CoreException(new Status(IStatus.ERROR, Activator
162 .getPluginId(), CoreText.OperationAlreadyExecuted));
163 SubMonitor totalProgress = SubMonitor.convert(m,
164 NLS.bind(CoreText.PullOperation_TaskName,
165 Integer.valueOf(repositories.length)),
167 IWorkspaceRunnable action = new IWorkspaceRunnable() {
168 @Override
169 public void run(IProgressMonitor mymonitor) throws CoreException {
170 if (mymonitor.isCanceled())
171 throw new CoreException(Status.CANCEL_STATUS);
172 SubMonitor progress = SubMonitor.convert(mymonitor,
173 repositories.length * 2);
174 for (int i = 0; i < repositories.length; i++) {
175 Repository repository = repositories[i];
176 IProject[] validProjects = ProjectUtil.getValidOpenProjects(repository);
177 PullResult pullResult = null;
178 try (Git git = new Git(repository)) {
179 PullCommand pull = git.pull();
180 SubMonitor newChild = progress.newChild(1,
181 SubMonitor.SUPPRESS_NONE);
182 pull.setProgressMonitor(new EclipseGitProgressTransformer(
183 newChild));
184 pull.setTimeout(timeout);
185 pull.setCredentialsProvider(credentialsProvider);
186 PullReferenceConfig config = configs.get(repository);
187 newChild.setTaskName(
188 getPullTaskName(repository, config));
189 if (config != null) {
190 if (config.getRemote() != null) {
191 pull.setRemote(config.getRemote());
193 if (config.getReference() != null) {
194 pull.setRemoteBranchName(config.getReference());
196 pull.setRebase(config.getUpstreamConfig());
198 MergeStrategy strategy = Activator.getDefault()
199 .getPreferredMergeStrategy();
200 if (strategy != null) {
201 pull.setStrategy(strategy);
203 pullResult = pull.call();
204 results.put(repository, pullResult);
205 } catch (DetachedHeadException e) {
206 results.put(repository, Activator.error(
207 CoreText.PullOperation_DetachedHeadMessage, e));
208 } catch (InvalidConfigurationException e) {
209 IStatus error = Activator
210 .error(CoreText.PullOperation_PullNotConfiguredMessage,
212 results.put(repository, error);
213 } catch (GitAPIException e) {
214 results.put(repository,
215 Activator.error(e.getMessage(), e));
216 } catch (JGitInternalException e) {
217 Throwable cause = e.getCause();
218 if (cause == null || !(cause instanceof TransportException))
219 cause = e;
220 results.put(repository,
221 Activator.error(cause.getMessage(), cause));
222 } finally {
223 if (refreshNeeded(pullResult)) {
224 ProjectUtil.refreshValidProjects(validProjects,
225 progress.newChild(1,
226 SubMonitor.SUPPRESS_NONE));
227 } else {
228 progress.worked(1);
234 // lock workspace to protect working tree changes
235 ResourcesPlugin.getWorkspace().run(action, getSchedulingRule(),
236 IWorkspace.AVOID_UPDATE, totalProgress);
239 static String getPullTaskName(Repository repo,
240 PullReferenceConfig rc) {
242 StoredConfig config = repo.getConfig();
243 if (rc != null) {
244 String remoteUri = config.getString(
245 ConfigConstants.CONFIG_REMOTE_SECTION, rc.remote,
246 ConfigConstants.CONFIG_KEY_URL);
247 return "Pulling " + rc.remote + " from " + remoteUri; //$NON-NLS-1$ //$NON-NLS-2$
250 String branchName;
251 try {
252 String fullBranch = repo.getFullBranch();
253 branchName = fullBranch != null
254 ? fullBranch.substring(Constants.R_HEADS.length())
255 : ""; //$NON-NLS-1$
256 } catch (IOException e) {
257 return "Pulling from " + repo.toString(); //$NON-NLS-1$
260 // get the configured remote for the currently checked out branch
261 // stored in configuration key branch.<branch name>.remote
262 String remote = config.getString(ConfigConstants.CONFIG_BRANCH_SECTION,
263 branchName, ConfigConstants.CONFIG_KEY_REMOTE);
264 if (remote == null) {
265 // fall back to default remote
266 remote = Constants.DEFAULT_REMOTE_NAME;
269 String remoteUri = config.getString(
270 ConfigConstants.CONFIG_REMOTE_SECTION, remote,
271 ConfigConstants.CONFIG_KEY_URL);
272 if (remoteUri != null) {
273 return "Pulling " + remote + " from " + remoteUri; //$NON-NLS-1$ //$NON-NLS-2$
275 return "Pulling from " + repo.getDirectory(); //$NON-NLS-1$
278 boolean refreshNeeded(PullResult pullResult) {
279 if (pullResult == null) {
280 return true;
282 RebaseResult rebaseResult = pullResult.getRebaseResult();
283 if (rebaseResult != null
284 && rebaseResult.getStatus() == RebaseResult.Status.UP_TO_DATE) {
285 return false;
287 MergeResult mergeResult = pullResult.getMergeResult();
288 if (mergeResult != null && mergeResult
289 .getMergeStatus() == MergeStatus.ALREADY_UP_TO_DATE) {
290 return false;
292 return true;
296 * @return the results, or an empty Map if this has not been executed
298 public Map<Repository, Object> getResults() {
299 return this.results;
302 @Override
303 public ISchedulingRule getSchedulingRule() {
304 return RuleUtil.getRuleForRepositories(Arrays.asList(repositories));
308 * @param credentialsProvider
310 public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
311 this.credentialsProvider = credentialsProvider;
315 * @return the operation's credentials provider
317 public CredentialsProvider getCredentialsProvider() {
318 return credentialsProvider;