Update org.apache.commons:commons-compress to 1.25.0
[egit/eclipse.git] / org.eclipse.egit.core / src / org / eclipse / egit / core / op / PullOperation.java
blob5513f44cd7431759857a4f3efd44cba4e7284206
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 * Karsten Thoms (itemis) - [540548] Parallelize pull jobs per repository
16 *******************************************************************************/
17 package org.eclipse.egit.core.op;
19 import java.io.IOException;
20 import java.text.MessageFormat;
21 import java.util.Collections;
22 import java.util.LinkedHashMap;
23 import java.util.Map;
24 import java.util.Set;
26 import org.eclipse.core.resources.IProject;
27 import org.eclipse.core.runtime.CoreException;
28 import org.eclipse.core.runtime.IProgressMonitor;
29 import org.eclipse.core.runtime.IStatus;
30 import org.eclipse.core.runtime.OperationCanceledException;
31 import org.eclipse.core.runtime.Platform;
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.core.runtime.jobs.Job;
36 import org.eclipse.core.runtime.jobs.JobGroup;
37 import org.eclipse.egit.core.Activator;
38 import org.eclipse.egit.core.EclipseGitProgressTransformer;
39 import org.eclipse.egit.core.GitCorePreferences;
40 import org.eclipse.egit.core.JobFamilies;
41 import org.eclipse.egit.core.internal.CoreText;
42 import org.eclipse.egit.core.internal.MergeStrategies;
43 import org.eclipse.egit.core.internal.job.RuleUtil;
44 import org.eclipse.egit.core.internal.util.ProjectUtil;
45 import org.eclipse.jgit.annotations.NonNull;
46 import org.eclipse.jgit.annotations.Nullable;
47 import org.eclipse.jgit.api.Git;
48 import org.eclipse.jgit.api.MergeResult;
49 import org.eclipse.jgit.api.MergeResult.MergeStatus;
50 import org.eclipse.jgit.api.PullCommand;
51 import org.eclipse.jgit.api.PullResult;
52 import org.eclipse.jgit.api.RebaseResult;
53 import org.eclipse.jgit.api.errors.DetachedHeadException;
54 import org.eclipse.jgit.api.errors.GitAPIException;
55 import org.eclipse.jgit.api.errors.InvalidConfigurationException;
56 import org.eclipse.jgit.api.errors.JGitInternalException;
57 import org.eclipse.jgit.errors.TransportException;
58 import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
59 import org.eclipse.jgit.lib.ConfigConstants;
60 import org.eclipse.jgit.lib.Constants;
61 import org.eclipse.jgit.lib.Repository;
62 import org.eclipse.jgit.lib.StoredConfig;
63 import org.eclipse.jgit.merge.MergeStrategy;
64 import org.eclipse.jgit.transport.CredentialsProvider;
66 /**
67 * Wraps the JGit API {@link PullCommand} into an operation
69 public class PullOperation implements IEGitOperation {
71 /**
72 * This describe a specification of a Pull command
73 * @since 4.2
75 public static class PullReferenceConfig {
76 private String remote;
78 private String reference;
80 private BranchRebaseMode upstreamConfig;
82 /**
83 * @param remote
84 * @param reference
85 * @param upstreamConfig
87 public PullReferenceConfig(@Nullable String remote,
88 @Nullable String reference,
89 @Nullable BranchRebaseMode upstreamConfig) {
90 this.remote = remote;
91 this.reference = reference;
92 this.upstreamConfig = upstreamConfig;
95 /**
96 * @return the remote to pull from. Can be null (in which case client is
97 * free to either ignore the pull, send an error, use default
98 * configuration for the current branch...)
100 @Nullable
101 public String getRemote() {
102 return this.remote;
106 * @return the reference (commit, tag, id...) to pull from. Can be null
107 * (in which case client is free to either ignore the pull, send
108 * an error, use default configuration for the current
109 * branch...)
111 @Nullable
112 public String getReference() {
113 return this.reference;
117 * @return the upstream config strategy to use for the specified pull
119 @Nullable
120 public BranchRebaseMode getUpstreamConfig() {
121 return this.upstreamConfig;
125 private final Repository[] repositories;
127 private Map<Repository, PullReferenceConfig> configs;
129 private final Map<Repository, PullResult> results;
131 private final int timeout;
133 private CredentialsProvider credentialsProvider;
136 * @param repositories
137 * the repositories
138 * @param timeout
139 * in seconds
141 public PullOperation(Set<Repository> repositories, int timeout) {
142 this.timeout = timeout;
143 this.repositories = repositories
144 .toArray(new Repository[0]);
145 this.configs = Collections.emptyMap();
146 this.results = new LinkedHashMap<>();
150 * @param repositories
151 * Repositories to pull, with specific configuration
152 * @param timeout
153 * in seconds
154 * @since 4.2
156 public PullOperation(
157 @NonNull Map<Repository, PullReferenceConfig> repositories,
158 int timeout) {
159 this(repositories.keySet(), timeout);
160 this.configs = repositories;
163 @Override
164 public void execute(IProgressMonitor m) throws CoreException {
165 if (!results.isEmpty()) {
166 throw new CoreException(
167 new Status(IStatus.ERROR, Activator.PLUGIN_ID,
168 CoreText.OperationAlreadyExecuted));
170 int workers = repositories.length;
171 String taskName = MessageFormat.format(CoreText.PullOperation_TaskName,
172 Integer.valueOf(workers));
174 int maxThreads = getMaxPullThreadsCount();
175 JobGroup jobGroup = new PullJobGroup(taskName, maxThreads, workers);
177 SubMonitor progress = SubMonitor.convert(m, workers);
178 for (Repository repository : repositories) {
179 PullJob pullJob = new PullJob(repository, configs.get(repository));
180 pullJob.setJobGroup(jobGroup);
181 pullJob.schedule();
183 // No timeout for the group: each single job has a timeout
184 long noTimeout = 0;
185 try {
186 jobGroup.join(noTimeout, progress);
187 } catch (InterruptedException e) {
188 Thread.currentThread().interrupt();
189 throw new CoreException(Activator.cancel(e.getMessage(), e));
190 } catch (OperationCanceledException e) {
191 throw new CoreException(Activator.cancel(e.getMessage(), e));
195 private int getMaxPullThreadsCount() {
196 String key = GitCorePreferences.core_maxPullThreadsCount;
197 int defaultValue = 1;
198 int value = Platform.getPreferencesService().getInt(Activator.PLUGIN_ID,
199 key, defaultValue, null);
200 return Math.max(defaultValue, value);
204 * JobGroup for multiple pulls.
206 private static class PullJobGroup extends JobGroup {
207 public PullJobGroup(String name, int maxThreads, int initialJobCount) {
208 super(name, maxThreads, initialJobCount);
212 * Always continue processing all other pulls
214 @Override
215 protected boolean shouldCancel(IStatus lastCompletedJobResult,
216 int numberOfFailedJobs, int numberOfCancelledJobs) {
217 return false;
221 private final class PullJob extends Job {
222 private final Repository repository;
223 private final PullReferenceConfig config;
225 PullJob(Repository repository, PullReferenceConfig config) {
226 super(getPullTaskName(repository, config));
227 this.repository = repository;
228 this.config = config;
229 setRule(RuleUtil.getRule(repository));
232 @Override
233 public IStatus run(IProgressMonitor mymonitor) {
234 PullResult pullResult = null;
235 try (Git git = new Git(repository)) {
236 PullCommand pull = git.pull();
237 SubMonitor monitor = SubMonitor.convert(mymonitor, 4);
238 pull.setProgressMonitor(new EclipseGitProgressTransformer(
239 monitor.newChild(3)));
240 pull.setTimeout(timeout);
241 pull.setCredentialsProvider(credentialsProvider);
242 if (config != null) {
243 if (config.getRemote() != null) {
244 pull.setRemote(config.getRemote());
246 if (config.getReference() != null) {
247 pull.setRemoteBranchName(config.getReference());
249 pull.setRebase(config.getUpstreamConfig());
251 MergeStrategy strategy = MergeStrategies
252 .getPreferredMergeStrategy();
253 if (strategy != null) {
254 pull.setStrategy(strategy);
256 pullResult = pull.call();
257 synchronized (results) {
258 results.put(repository, pullResult);
260 IProject[] projects = ProjectUtil
261 .getValidOpenProjects(repository);
262 if (refreshNeeded(pullResult)) {
263 ProjectUtil.refreshValidProjects(projects,
264 monitor.newChild(1));
265 } else {
266 monitor.worked(1);
268 return Status.OK_STATUS;
269 } catch (DetachedHeadException e) {
270 return Activator.error(
271 CoreText.PullOperation_DetachedHeadMessage, e);
272 } catch (InvalidConfigurationException e) {
273 return Activator
274 .error(CoreText.PullOperation_PullNotConfiguredMessage,
276 } catch (GitAPIException | CoreException e) {
277 return Activator.error(e.getMessage(), e);
278 } catch (JGitInternalException e) {
279 Throwable cause = e.getCause();
280 if (cause == null || !(cause instanceof TransportException)) {
281 cause = e;
283 return Activator.error(cause.getMessage(), cause);
284 } finally {
285 mymonitor.done();
289 @Override
290 public boolean belongsTo(Object family) {
291 return JobFamilies.PULL.equals(family);
295 static String getPullTaskName(Repository repo,
296 PullReferenceConfig rc) {
298 StoredConfig config = repo.getConfig();
299 if (rc != null) {
300 String remoteUri = config.getString(
301 ConfigConstants.CONFIG_REMOTE_SECTION, rc.remote,
302 ConfigConstants.CONFIG_KEY_URL);
303 return "Pulling " + rc.remote + " from " + remoteUri; //$NON-NLS-1$ //$NON-NLS-2$
306 String branchName;
307 try {
308 String fullBranch = repo.getFullBranch();
309 branchName = fullBranch != null
310 ? fullBranch.substring(Constants.R_HEADS.length())
311 : ""; //$NON-NLS-1$
312 } catch (IOException e) {
313 return "Pulling from " + repo.toString(); //$NON-NLS-1$
316 // get the configured remote for the currently checked out branch
317 // stored in configuration key branch.<branch name>.remote
318 String remote = config.getString(ConfigConstants.CONFIG_BRANCH_SECTION,
319 branchName, ConfigConstants.CONFIG_KEY_REMOTE);
320 if (remote == null) {
321 // fall back to default remote
322 remote = Constants.DEFAULT_REMOTE_NAME;
325 String remoteUri = config.getString(
326 ConfigConstants.CONFIG_REMOTE_SECTION, remote,
327 ConfigConstants.CONFIG_KEY_URL);
328 if (remoteUri != null) {
329 return "Pulling " + remote + " from " + remoteUri; //$NON-NLS-1$ //$NON-NLS-2$
331 return "Pulling from " + repo.getDirectory(); //$NON-NLS-1$
334 boolean refreshNeeded(PullResult pullResult) {
335 if (pullResult == null) {
336 return true;
338 RebaseResult rebaseResult = pullResult.getRebaseResult();
339 if (rebaseResult != null
340 && rebaseResult.getStatus() == RebaseResult.Status.UP_TO_DATE) {
341 return false;
343 MergeResult mergeResult = pullResult.getMergeResult();
344 if (mergeResult != null && mergeResult
345 .getMergeStatus() == MergeStatus.ALREADY_UP_TO_DATE) {
346 return false;
348 return true;
352 * @return the results, or an empty Map if this has not been executed
354 public Map<Repository, PullResult> getResults() {
355 return this.results;
358 @Override
359 public ISchedulingRule getSchedulingRule() {
360 // main job does not block anyone, but the sub tasks are blocking and
361 // have own rules
362 return null;
366 * @param credentialsProvider
368 public void setCredentialsProvider(CredentialsProvider credentialsProvider) {
369 this.credentialsProvider = credentialsProvider;
373 * @return the operation's credentials provider
375 public CredentialsProvider getCredentialsProvider() {
376 return credentialsProvider;