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
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
;
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
;
67 * Wraps the JGit API {@link PullCommand} into an operation
69 public class PullOperation
implements IEGitOperation
{
72 * This describe a specification of a Pull command
75 public static class PullReferenceConfig
{
76 private String remote
;
78 private String reference
;
80 private BranchRebaseMode upstreamConfig
;
85 * @param upstreamConfig
87 public PullReferenceConfig(@Nullable String remote
,
88 @Nullable String reference
,
89 @Nullable BranchRebaseMode upstreamConfig
) {
91 this.reference
= reference
;
92 this.upstreamConfig
= upstreamConfig
;
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...)
101 public String
getRemote() {
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
112 public String
getReference() {
113 return this.reference
;
117 * @return the upstream config strategy to use for the specified pull
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
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
156 public PullOperation(
157 @NonNull Map
<Repository
, PullReferenceConfig
> repositories
,
159 this(repositories
.keySet(), timeout
);
160 this.configs
= repositories
;
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
);
183 // No timeout for the group: each single job has a timeout
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
215 protected boolean shouldCancel(IStatus lastCompletedJobResult
,
216 int numberOfFailedJobs
, int numberOfCancelledJobs
) {
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
));
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));
268 return Status
.OK_STATUS
;
269 } catch (DetachedHeadException e
) {
270 return Activator
.error(
271 CoreText
.PullOperation_DetachedHeadMessage
, e
);
272 } catch (InvalidConfigurationException e
) {
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
)) {
283 return Activator
.error(cause
.getMessage(), cause
);
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();
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$
308 String fullBranch
= repo
.getFullBranch();
309 branchName
= fullBranch
!= null
310 ? fullBranch
.substring(Constants
.R_HEADS
.length())
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) {
338 RebaseResult rebaseResult
= pullResult
.getRebaseResult();
339 if (rebaseResult
!= null
340 && rebaseResult
.getStatus() == RebaseResult
.Status
.UP_TO_DATE
) {
343 MergeResult mergeResult
= pullResult
.getMergeResult();
344 if (mergeResult
!= null && mergeResult
345 .getMergeStatus() == MergeStatus
.ALREADY_UP_TO_DATE
) {
352 * @return the results, or an empty Map if this has not been executed
354 public Map
<Repository
, PullResult
> getResults() {
359 public ISchedulingRule
getSchedulingRule() {
360 // main job does not block anyone, but the sub tasks are blocking and
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
;