1
2
3
4
5
6
7
8
9
10
11
12 package org.eclipse.jgit.api;
13
14 import java.io.IOException;
15 import java.text.MessageFormat;
16
17 import org.eclipse.jgit.annotations.Nullable;
18 import org.eclipse.jgit.api.MergeCommand.FastForwardMode;
19 import org.eclipse.jgit.api.MergeCommand.FastForwardMode.Merge;
20 import org.eclipse.jgit.api.RebaseCommand.Operation;
21 import org.eclipse.jgit.api.errors.CanceledException;
22 import org.eclipse.jgit.api.errors.GitAPIException;
23 import org.eclipse.jgit.api.errors.InvalidConfigurationException;
24 import org.eclipse.jgit.api.errors.InvalidRemoteException;
25 import org.eclipse.jgit.api.errors.JGitInternalException;
26 import org.eclipse.jgit.api.errors.NoHeadException;
27 import org.eclipse.jgit.api.errors.RefNotAdvertisedException;
28 import org.eclipse.jgit.api.errors.RefNotFoundException;
29 import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
30 import org.eclipse.jgit.dircache.DirCacheCheckout;
31 import org.eclipse.jgit.internal.JGitText;
32 import org.eclipse.jgit.lib.AnyObjectId;
33 import org.eclipse.jgit.lib.BranchConfig.BranchRebaseMode;
34 import org.eclipse.jgit.lib.Config;
35 import org.eclipse.jgit.lib.ConfigConstants;
36 import org.eclipse.jgit.lib.Constants;
37 import org.eclipse.jgit.lib.NullProgressMonitor;
38 import org.eclipse.jgit.lib.ObjectId;
39 import org.eclipse.jgit.lib.ProgressMonitor;
40 import org.eclipse.jgit.lib.Ref;
41 import org.eclipse.jgit.lib.RefUpdate;
42 import org.eclipse.jgit.lib.RefUpdate.Result;
43 import org.eclipse.jgit.lib.Repository;
44 import org.eclipse.jgit.lib.RepositoryState;
45 import org.eclipse.jgit.lib.SubmoduleConfig.FetchRecurseSubmodulesMode;
46 import org.eclipse.jgit.merge.MergeStrategy;
47 import org.eclipse.jgit.revwalk.RevCommit;
48 import org.eclipse.jgit.revwalk.RevWalk;
49 import org.eclipse.jgit.transport.FetchResult;
50 import org.eclipse.jgit.transport.TagOpt;
51
52
53
54
55
56
57
58 public class PullCommand extends TransportCommand<PullCommand, PullResult> {
59
60 private static final String DOT = ".";
61
62 private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
63
64 private BranchRebaseMode pullRebaseMode = null;
65
66 private String remote;
67
68 private String remoteBranchName;
69
70 private MergeStrategy strategy = MergeStrategy.RECURSIVE;
71
72 private TagOpt tagOption;
73
74 private FastForwardMode fastForwardMode;
75
76 private FetchRecurseSubmodulesMode submoduleRecurseMode = null;
77
78
79
80
81
82
83
84 protected PullCommand(Repository repo) {
85 super(repo);
86 }
87
88
89
90
91
92
93
94
95 public PullCommand setProgressMonitor(ProgressMonitor monitor) {
96 if (monitor == null) {
97 monitor = NullProgressMonitor.INSTANCE;
98 }
99 this.monitor = monitor;
100 return this;
101 }
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122 public PullCommand setRebase(boolean useRebase) {
123 checkCallable();
124 pullRebaseMode = useRebase ? BranchRebaseMode.REBASE
125 : BranchRebaseMode.NONE;
126 return this;
127 }
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163 public PullCommand setRebase(BranchRebaseMode rebaseMode) {
164 checkCallable();
165 pullRebaseMode = rebaseMode;
166 return this;
167 }
168
169
170
171
172
173
174
175
176
177
178 @Override
179 public PullResult call() throws GitAPIException,
180 WrongRepositoryStateException, InvalidConfigurationException,
181 InvalidRemoteException, CanceledException,
182 RefNotFoundException, RefNotAdvertisedException, NoHeadException,
183 org.eclipse.jgit.api.errors.TransportException {
184 checkCallable();
185
186 monitor.beginTask(JGitText.get().pullTaskName, 2);
187 Config repoConfig = repo.getConfig();
188
189 String branchName = null;
190 try {
191 String fullBranch = repo.getFullBranch();
192 if (fullBranch != null
193 && fullBranch.startsWith(Constants.R_HEADS)) {
194 branchName = fullBranch.substring(Constants.R_HEADS.length());
195 }
196 } catch (IOException e) {
197 throw new JGitInternalException(
198 JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
199 e);
200 }
201 if (remoteBranchName == null && branchName != null) {
202
203
204 remoteBranchName = repoConfig.getString(
205 ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
206 ConfigConstants.CONFIG_KEY_MERGE);
207 }
208 if (remoteBranchName == null) {
209 remoteBranchName = branchName;
210 }
211 if (remoteBranchName == null) {
212 throw new NoHeadException(
213 JGitText.get().cannotCheckoutFromUnbornBranch);
214 }
215
216 if (!repo.getRepositoryState().equals(RepositoryState.SAFE))
217 throw new WrongRepositoryStateException(MessageFormat.format(
218 JGitText.get().cannotPullOnARepoWithState, repo
219 .getRepositoryState().name()));
220
221 if (remote == null && branchName != null) {
222
223
224 remote = repoConfig.getString(
225 ConfigConstants.CONFIG_BRANCH_SECTION, branchName,
226 ConfigConstants.CONFIG_KEY_REMOTE);
227 }
228 if (remote == null) {
229
230 remote = Constants.DEFAULT_REMOTE_NAME;
231 }
232
233
234 if (pullRebaseMode == null && branchName != null) {
235 pullRebaseMode = getRebaseMode(branchName, repoConfig);
236 }
237
238
239 final boolean isRemote = !remote.equals(".");
240 String remoteUri;
241 FetchResult fetchRes;
242 if (isRemote) {
243 remoteUri = repoConfig.getString(
244 ConfigConstants.CONFIG_REMOTE_SECTION, remote,
245 ConfigConstants.CONFIG_KEY_URL);
246 if (remoteUri == null) {
247 String missingKey = ConfigConstants.CONFIG_REMOTE_SECTION + DOT
248 + remote + DOT + ConfigConstants.CONFIG_KEY_URL;
249 throw new InvalidConfigurationException(MessageFormat.format(
250 JGitText.get().missingConfigurationForKey, missingKey));
251 }
252
253 if (monitor.isCancelled())
254 throw new CanceledException(MessageFormat.format(
255 JGitText.get().operationCanceled,
256 JGitText.get().pullTaskName));
257
258 FetchCommand fetch = new FetchCommand(repo).setRemote(remote)
259 .setProgressMonitor(monitor).setTagOpt(tagOption)
260 .setRecurseSubmodules(submoduleRecurseMode);
261 configure(fetch);
262
263 fetchRes = fetch.call();
264 } else {
265
266 remoteUri = JGitText.get().localRepository;
267 fetchRes = null;
268 }
269
270 monitor.update(1);
271
272 if (monitor.isCancelled())
273 throw new CanceledException(MessageFormat.format(
274 JGitText.get().operationCanceled,
275 JGitText.get().pullTaskName));
276
277
278
279
280 AnyObjectId commitToMerge;
281 if (isRemote) {
282 Ref r = null;
283 if (fetchRes != null) {
284 r = fetchRes.getAdvertisedRef(remoteBranchName);
285 if (r == null) {
286 r = fetchRes.getAdvertisedRef(Constants.R_HEADS
287 + remoteBranchName);
288 }
289 }
290 if (r == null) {
291 throw new RefNotAdvertisedException(MessageFormat.format(
292 JGitText.get().couldNotGetAdvertisedRef, remote,
293 remoteBranchName));
294 }
295 commitToMerge = r.getObjectId();
296 } else {
297 try {
298 commitToMerge = repo.resolve(remoteBranchName);
299 if (commitToMerge == null) {
300 throw new RefNotFoundException(MessageFormat.format(
301 JGitText.get().refNotResolved, remoteBranchName));
302 }
303 } catch (IOException e) {
304 throw new JGitInternalException(
305 JGitText.get().exceptionCaughtDuringExecutionOfPullCommand,
306 e);
307 }
308 }
309
310 String upstreamName = MessageFormat.format(
311 JGitText.get().upstreamBranchName,
312 Repository.shortenRefName(remoteBranchName), remoteUri);
313
314 PullResult result;
315 if (pullRebaseMode != BranchRebaseMode.NONE) {
316 try {
317 Ref head = repo.exactRef(Constants.HEAD);
318 if (head == null) {
319 throw new NoHeadException(JGitText
320 .get().commitOnRepoWithoutHEADCurrentlyNotSupported);
321 }
322 ObjectId headId = head.getObjectId();
323 if (headId == null) {
324
325 try (RevWalklk.html#RevWalk">RevWalk revWalk = new RevWalk(repo)) {
326 RevCommit srcCommit = revWalk
327 .parseCommit(commitToMerge);
328 DirCacheCheckout dco = new DirCacheCheckout(repo,
329 repo.lockDirCache(), srcCommit.getTree());
330 dco.setFailOnConflict(true);
331 dco.setProgressMonitor(monitor);
332 dco.checkout();
333 RefUpdate refUpdate = repo
334 .updateRef(head.getTarget().getName());
335 refUpdate.setNewObjectId(commitToMerge);
336 refUpdate.setExpectedOldObjectId(null);
337 refUpdate.setRefLogMessage("initial pull", false);
338 if (refUpdate.update() != Result.NEW) {
339 throw new NoHeadException(JGitText
340 .get().commitOnRepoWithoutHEADCurrentlyNotSupported);
341 }
342 monitor.endTask();
343 return new PullResult(fetchRes, remote,
344 RebaseResult.result(
345 RebaseResult.Status.FAST_FORWARD,
346 srcCommit));
347 }
348 }
349 } catch (NoHeadException e) {
350 throw e;
351 } catch (IOException e) {
352 throw new JGitInternalException(JGitText
353 .get().exceptionCaughtDuringExecutionOfPullCommand, e);
354 }
355 RebaseCommand rebase = new RebaseCommand(repo);
356 RebaseResult rebaseRes = rebase.setUpstream(commitToMerge)
357 .setUpstreamName(upstreamName).setProgressMonitor(monitor)
358 .setOperation(Operation.BEGIN).setStrategy(strategy)
359 .setPreserveMerges(
360 pullRebaseMode == BranchRebaseMode.PRESERVE)
361 .call();
362 result = new PullResult(fetchRes, remote, rebaseRes);
363 } else {
364 MergeCommand merge = new MergeCommand(repo);
365 MergeResult mergeRes = merge.include(upstreamName, commitToMerge)
366 .setStrategy(strategy).setProgressMonitor(monitor)
367 .setFastForward(getFastForwardMode()).call();
368 monitor.update(1);
369 result = new PullResult(fetchRes, remote, mergeRes);
370 }
371 monitor.endTask();
372 return result;
373 }
374
375
376
377
378
379
380
381
382
383
384
385
386
387 public PullCommand setRemote(String remote) {
388 checkCallable();
389 this.remote = remote;
390 return this;
391 }
392
393
394
395
396
397
398
399
400
401
402
403
404 public PullCommand setRemoteBranchName(String remoteBranchName) {
405 checkCallable();
406 this.remoteBranchName = remoteBranchName;
407 return this;
408 }
409
410
411
412
413
414
415
416 public String getRemote() {
417 return remote;
418 }
419
420
421
422
423
424
425
426
427 public String getRemoteBranchName() {
428 return remoteBranchName;
429 }
430
431
432
433
434
435
436
437
438
439 public PullCommand setStrategy(MergeStrategy strategy) {
440 this.strategy = strategy;
441 return this;
442 }
443
444
445
446
447
448
449
450
451
452 public PullCommand setTagOpt(TagOpt tagOpt) {
453 checkCallable();
454 this.tagOption = tagOpt;
455 return this;
456 }
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472 public PullCommand setFastForward(
473 @Nullable FastForwardMode fastForwardMode) {
474 checkCallable();
475 this.fastForwardMode = fastForwardMode;
476 return this;
477 }
478
479
480
481
482
483
484
485
486
487
488
489
490 public PullCommand setRecurseSubmodules(
491 @Nullable FetchRecurseSubmodulesMode recurse) {
492 this.submoduleRecurseMode = recurse;
493 return this;
494 }
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510 public static BranchRebaseMode getRebaseMode(String branchName,
511 Config config) {
512 BranchRebaseMode mode = config.getEnum(BranchRebaseMode.values(),
513 ConfigConstants.CONFIG_BRANCH_SECTION,
514 branchName, ConfigConstants.CONFIG_KEY_REBASE, null);
515 if (mode == null) {
516 mode = config.getEnum(BranchRebaseMode.values(),
517 ConfigConstants.CONFIG_PULL_SECTION, null,
518 ConfigConstants.CONFIG_KEY_REBASE, BranchRebaseMode.NONE);
519 }
520 return mode;
521 }
522
523 private FastForwardMode getFastForwardMode() {
524 if (fastForwardMode != null) {
525 return fastForwardMode;
526 }
527 Config config = repo.getConfig();
528 Merge ffMode = config.getEnum(Merge.values(),
529 ConfigConstants.CONFIG_PULL_SECTION, null,
530 ConfigConstants.CONFIG_KEY_FF, null);
531 return ffMode != null ? FastForwardMode.valueOf(ffMode) : null;
532 }
533 }