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 *******************************************************************************/
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
;
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
;
63 * Wraps the JGit API {@link PullCommand} into an operation
65 public class PullOperation
implements IEGitOperation
{
68 * This describe a specification of a Pull command
71 public static class PullReferenceConfig
{
72 private String remote
;
74 private String reference
;
76 private BranchRebaseMode upstreamConfig
;
81 * @param upstreamConfig
83 public PullReferenceConfig(@Nullable String remote
,
84 @Nullable String reference
,
85 @Nullable BranchRebaseMode upstreamConfig
) {
87 this.reference
= reference
;
88 this.upstreamConfig
= upstreamConfig
;
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...)
97 public String
getRemote() {
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
108 public String
getReference() {
109 return this.reference
;
113 * @return the upstream config strategy to use for the specified pull
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
137 public PullOperation(Set
<Repository
> repositories
, int timeout
) {
138 this.timeout
= timeout
;
139 this.repositories
= repositories
.toArray(new Repository
[repositories
141 this.configs
= Collections
.emptyMap();
145 * @param repositories
146 * Repositories to pull, with specific configuration
151 public PullOperation(
152 @NonNull Map
<Repository
, PullReferenceConfig
> repositories
,
154 this(repositories
.keySet(), timeout
);
155 this.configs
= repositories
;
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() {
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(
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
))
220 results
.put(repository
,
221 Activator
.error(cause
.getMessage(), cause
));
223 if (refreshNeeded(pullResult
)) {
224 ProjectUtil
.refreshValidProjects(validProjects
,
226 SubMonitor
.SUPPRESS_NONE
));
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();
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$
252 String fullBranch
= repo
.getFullBranch();
253 branchName
= fullBranch
!= null
254 ? fullBranch
.substring(Constants
.R_HEADS
.length())
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) {
282 RebaseResult rebaseResult
= pullResult
.getRebaseResult();
283 if (rebaseResult
!= null
284 && rebaseResult
.getStatus() == RebaseResult
.Status
.UP_TO_DATE
) {
287 MergeResult mergeResult
= pullResult
.getMergeResult();
288 if (mergeResult
!= null && mergeResult
289 .getMergeStatus() == MergeStatus
.ALREADY_UP_TO_DATE
) {
296 * @return the results, or an empty Map if this has not been executed
298 public Map
<Repository
, Object
> getResults() {
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
;