View Javadoc
1   /*
2    * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com>
3    * Copyright (C) 2010-2014, Stefan Lay <stefan.lay@sap.com>
4    * Copyright (C) 2016, Laurent Delaigue <laurent.delaigue@obeo.fr>
5    * and other copyright owners as documented in the project's IP log.
6    *
7    * This program and the accompanying materials are made available
8    * under the terms of the Eclipse Distribution License v1.0 which
9    * accompanies this distribution, is reproduced below, and is
10   * available at http://www.eclipse.org/org/documents/edl-v10.php
11   *
12   * All rights reserved.
13   *
14   * Redistribution and use in source and binary forms, with or
15   * without modification, are permitted provided that the following
16   * conditions are met:
17   *
18   * - Redistributions of source code must retain the above copyright
19   *   notice, this list of conditions and the following disclaimer.
20   *
21   * - Redistributions in binary form must reproduce the above
22   *   copyright notice, this list of conditions and the following
23   *   disclaimer in the documentation and/or other materials provided
24   *   with the distribution.
25   *
26   * - Neither the name of the Eclipse Foundation, Inc. nor the
27   *   names of its contributors may be used to endorse or promote
28   *   products derived from this software without specific prior
29   *   written permission.
30   *
31   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44   */
45  package org.eclipse.jgit.api;
46  
47  import java.io.IOException;
48  import java.text.MessageFormat;
49  import java.util.Arrays;
50  import java.util.Collections;
51  import java.util.LinkedList;
52  import java.util.List;
53  import java.util.Locale;
54  import java.util.Map;
55  
56  import org.eclipse.jgit.annotations.Nullable;
57  import org.eclipse.jgit.api.MergeResult.MergeStatus;
58  import org.eclipse.jgit.api.errors.CheckoutConflictException;
59  import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
60  import org.eclipse.jgit.api.errors.GitAPIException;
61  import org.eclipse.jgit.api.errors.InvalidMergeHeadsException;
62  import org.eclipse.jgit.api.errors.JGitInternalException;
63  import org.eclipse.jgit.api.errors.NoHeadException;
64  import org.eclipse.jgit.api.errors.NoMessageException;
65  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
66  import org.eclipse.jgit.dircache.DirCacheCheckout;
67  import org.eclipse.jgit.events.WorkingTreeModifiedEvent;
68  import org.eclipse.jgit.internal.JGitText;
69  import org.eclipse.jgit.lib.AnyObjectId;
70  import org.eclipse.jgit.lib.Config.ConfigEnum;
71  import org.eclipse.jgit.lib.Constants;
72  import org.eclipse.jgit.lib.NullProgressMonitor;
73  import org.eclipse.jgit.lib.ObjectId;
74  import org.eclipse.jgit.lib.ObjectIdRef;
75  import org.eclipse.jgit.lib.ProgressMonitor;
76  import org.eclipse.jgit.lib.Ref;
77  import org.eclipse.jgit.lib.Ref.Storage;
78  import org.eclipse.jgit.lib.RefUpdate;
79  import org.eclipse.jgit.lib.RefUpdate.Result;
80  import org.eclipse.jgit.lib.Repository;
81  import org.eclipse.jgit.merge.MergeConfig;
82  import org.eclipse.jgit.merge.MergeMessageFormatter;
83  import org.eclipse.jgit.merge.MergeStrategy;
84  import org.eclipse.jgit.merge.Merger;
85  import org.eclipse.jgit.merge.ResolveMerger;
86  import org.eclipse.jgit.merge.ResolveMerger.MergeFailureReason;
87  import org.eclipse.jgit.merge.SquashMessageFormatter;
88  import org.eclipse.jgit.revwalk.RevCommit;
89  import org.eclipse.jgit.revwalk.RevWalk;
90  import org.eclipse.jgit.revwalk.RevWalkUtils;
91  import org.eclipse.jgit.treewalk.FileTreeIterator;
92  import org.eclipse.jgit.util.StringUtils;
93  
94  /**
95   * A class used to execute a {@code Merge} command. It has setters for all
96   * supported options and arguments of this command and a {@link #call()} method
97   * to finally execute the command. Each instance of this class should only be
98   * used for one invocation of the command (means: one call to {@link #call()})
99   *
100  * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-merge.html"
101  *      >Git documentation about Merge</a>
102  */
103 public class MergeCommand extends GitCommand<MergeResult> {
104 
105 	private MergeStrategy mergeStrategy = MergeStrategy.RECURSIVE;
106 
107 	private List<Ref> commits = new LinkedList<>();
108 
109 	private Boolean squash;
110 
111 	private FastForwardMode fastForwardMode;
112 
113 	private String message;
114 
115 	private boolean insertChangeId;
116 
117 	private ProgressMonitor monitor = NullProgressMonitor.INSTANCE;
118 
119 	/**
120 	 * The modes available for fast forward merges corresponding to the
121 	 * <code>--ff</code>, <code>--no-ff</code> and <code>--ff-only</code>
122 	 * options under <code>branch.&lt;name&gt;.mergeoptions</code>.
123 	 */
124 	public enum FastForwardMode implements ConfigEnum {
125 		/**
126 		 * Corresponds to the default --ff option (for a fast forward update the
127 		 * branch pointer only).
128 		 */
129 		FF,
130 		/**
131 		 * Corresponds to the --no-ff option (create a merge commit even for a
132 		 * fast forward).
133 		 */
134 		NO_FF,
135 		/**
136 		 * Corresponds to the --ff-only option (abort unless the merge is a fast
137 		 * forward).
138 		 */
139 		FF_ONLY;
140 
141 		@Override
142 		public String toConfigValue() {
143 			return "--" + name().toLowerCase(Locale.ROOT).replace('_', '-'); //$NON-NLS-1$
144 		}
145 
146 		@Override
147 		public boolean matchConfigValue(String in) {
148 			if (StringUtils.isEmptyOrNull(in))
149 				return false;
150 			if (!in.startsWith("--")) //$NON-NLS-1$
151 				return false;
152 			return name().equalsIgnoreCase(in.substring(2).replace('-', '_'));
153 		}
154 
155 		/**
156 		 * The modes available for fast forward merges corresponding to the
157 		 * options under <code>merge.ff</code>.
158 		 */
159 		public enum Merge {
160 			/**
161 			 * {@link FastForwardMode#FF}.
162 			 */
163 			TRUE,
164 			/**
165 			 * {@link FastForwardMode#NO_FF}.
166 			 */
167 			FALSE,
168 			/**
169 			 * {@link FastForwardMode#FF_ONLY}.
170 			 */
171 			ONLY;
172 
173 			/**
174 			 * Map from <code>FastForwardMode</code> to
175 			 * <code>FastForwardMode.Merge</code>.
176 			 *
177 			 * @param ffMode
178 			 *            the <code>FastForwardMode</code> value to be mapped
179 			 * @return the mapped <code>FastForwardMode.Merge</code> value
180 			 */
181 			public static Merge valueOf(FastForwardMode ffMode) {
182 				switch (ffMode) {
183 				case NO_FF:
184 					return FALSE;
185 				case FF_ONLY:
186 					return ONLY;
187 				default:
188 					return TRUE;
189 				}
190 			}
191 		}
192 
193 		/**
194 		 * Map from <code>FastForwardMode.Merge</code> to
195 		 * <code>FastForwardMode</code>.
196 		 *
197 		 * @param ffMode
198 		 *            the <code>FastForwardMode.Merge</code> value to be mapped
199 		 * @return the mapped <code>FastForwardMode</code> value
200 		 */
201 		public static FastForwardMode valueOf(FastForwardMode.Merge ffMode) {
202 			switch (ffMode) {
203 			case FALSE:
204 				return NO_FF;
205 			case ONLY:
206 				return FF_ONLY;
207 			default:
208 				return FF;
209 			}
210 		}
211 	}
212 
213 	private Boolean commit;
214 
215 	/**
216 	 * Constructor for MergeCommand.
217 	 *
218 	 * @param repo
219 	 *            the {@link org.eclipse.jgit.lib.Repository}
220 	 */
221 	protected MergeCommand(Repository repo) {
222 		super(repo);
223 	}
224 
225 	/**
226 	 * {@inheritDoc}
227 	 * <p>
228 	 * Execute the {@code Merge} command with all the options and parameters
229 	 * collected by the setter methods (e.g. {@link #include(Ref)}) of this
230 	 * class. Each instance of this class should only be used for one invocation
231 	 * of the command. Don't call this method twice on an instance.
232 	 */
233 	@Override
234 	@SuppressWarnings("boxing")
235 	public MergeResult call() throws GitAPIException, NoHeadException,
236 			ConcurrentRefUpdateException, CheckoutConflictException,
237 			InvalidMergeHeadsException, WrongRepositoryStateException, NoMessageException {
238 		checkCallable();
239 		fallBackToConfiguration();
240 		checkParameters();
241 
242 		DirCacheCheckout dco = null;
243 		try (RevWalk revWalk = new RevWalk(repo)) {
244 			Ref head = repo.exactRef(Constants.HEAD);
245 			if (head == null)
246 				throw new NoHeadException(
247 						JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
248 			StringBuilder refLogMessage = new StringBuilder("merge "); //$NON-NLS-1$
249 
250 			// Check for FAST_FORWARD, ALREADY_UP_TO_DATE
251 
252 			// we know for now there is only one commit
253 			Ref ref = commits.get(0);
254 
255 			refLogMessage.append(ref.getName());
256 
257 			// handle annotated tags
258 			ref = repo.getRefDatabase().peel(ref);
259 			ObjectId objectId = ref.getPeeledObjectId();
260 			if (objectId == null)
261 				objectId = ref.getObjectId();
262 
263 			RevCommit srcCommit = revWalk.lookupCommit(objectId);
264 
265 			ObjectId headId = head.getObjectId();
266 			if (headId == null) {
267 				revWalk.parseHeaders(srcCommit);
268 				dco = new DirCacheCheckout(repo,
269 						repo.lockDirCache(), srcCommit.getTree());
270 				dco.setFailOnConflict(true);
271 				dco.setProgressMonitor(monitor);
272 				dco.checkout();
273 				RefUpdate refUpdate = repo
274 						.updateRef(head.getTarget().getName());
275 				refUpdate.setNewObjectId(objectId);
276 				refUpdate.setExpectedOldObjectId(null);
277 				refUpdate.setRefLogMessage("initial pull", false); //$NON-NLS-1$
278 				if (refUpdate.update() != Result.NEW)
279 					throw new NoHeadException(
280 							JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
281 				setCallable(false);
282 				return new MergeResult(srcCommit, srcCommit, new ObjectId[] {
283 						null, srcCommit }, MergeStatus.FAST_FORWARD,
284 						mergeStrategy, null, null);
285 			}
286 
287 			RevCommit headCommit = revWalk.lookupCommit(headId);
288 
289 			if (revWalk.isMergedInto(srcCommit, headCommit)) {
290 				setCallable(false);
291 				return new MergeResult(headCommit, srcCommit, new ObjectId[] {
292 						headCommit, srcCommit },
293 						MergeStatus.ALREADY_UP_TO_DATE, mergeStrategy, null, null);
294 			} else if (revWalk.isMergedInto(headCommit, srcCommit)
295 					&& fastForwardMode != FastForwardMode.NO_FF) {
296 				// FAST_FORWARD detected: skip doing a real merge but only
297 				// update HEAD
298 				refLogMessage.append(": " + MergeStatus.FAST_FORWARD); //$NON-NLS-1$
299 				dco = new DirCacheCheckout(repo,
300 						headCommit.getTree(), repo.lockDirCache(),
301 						srcCommit.getTree());
302 				dco.setProgressMonitor(monitor);
303 				dco.setFailOnConflict(true);
304 				dco.checkout();
305 				String msg = null;
306 				ObjectId newHead, base = null;
307 				MergeStatus mergeStatus = null;
308 				if (!squash) {
309 					updateHead(refLogMessage, srcCommit, headId);
310 					newHead = base = srcCommit;
311 					mergeStatus = MergeStatus.FAST_FORWARD;
312 				} else {
313 					msg = JGitText.get().squashCommitNotUpdatingHEAD;
314 					newHead = base = headId;
315 					mergeStatus = MergeStatus.FAST_FORWARD_SQUASHED;
316 					List<RevCommit> squashedCommits = RevWalkUtils.find(
317 							revWalk, srcCommit, headCommit);
318 					String squashMessage = new SquashMessageFormatter().format(
319 							squashedCommits, head);
320 					repo.writeSquashCommitMsg(squashMessage);
321 				}
322 				setCallable(false);
323 				return new MergeResult(newHead, base, new ObjectId[] {
324 						headCommit, srcCommit }, mergeStatus, mergeStrategy,
325 						null, msg);
326 			} else {
327 				if (fastForwardMode == FastForwardMode.FF_ONLY) {
328 					return new MergeResult(headCommit, srcCommit,
329 							new ObjectId[] { headCommit, srcCommit },
330 							MergeStatus.ABORTED, mergeStrategy, null, null);
331 				}
332 				String mergeMessage = ""; //$NON-NLS-1$
333 				if (!squash) {
334 					if (message != null)
335 						mergeMessage = message;
336 					else
337 						mergeMessage = new MergeMessageFormatter().format(
338 							commits, head);
339 					repo.writeMergeCommitMsg(mergeMessage);
340 					repo.writeMergeHeads(Arrays.asList(ref.getObjectId()));
341 				} else {
342 					List<RevCommit> squashedCommits = RevWalkUtils.find(
343 							revWalk, srcCommit, headCommit);
344 					String squashMessage = new SquashMessageFormatter().format(
345 							squashedCommits, head);
346 					repo.writeSquashCommitMsg(squashMessage);
347 				}
348 				Merger merger = mergeStrategy.newMerger(repo);
349 				merger.setProgressMonitor(monitor);
350 				boolean noProblems;
351 				Map<String, org.eclipse.jgit.merge.MergeResult<?>> lowLevelResults = null;
352 				Map<String, MergeFailureReason> failingPaths = null;
353 				List<String> unmergedPaths = null;
354 				if (merger instanceof ResolveMerger) {
355 					ResolveMerger resolveMerger = (ResolveMerger) merger;
356 					resolveMerger.setCommitNames(new String[] {
357 							"BASE", "HEAD", ref.getName() }); //$NON-NLS-1$ //$NON-NLS-2$
358 					resolveMerger.setWorkingTreeIterator(new FileTreeIterator(repo));
359 					noProblems = merger.merge(headCommit, srcCommit);
360 					lowLevelResults = resolveMerger
361 							.getMergeResults();
362 					failingPaths = resolveMerger.getFailingPaths();
363 					unmergedPaths = resolveMerger.getUnmergedPaths();
364 					if (!resolveMerger.getModifiedFiles().isEmpty()) {
365 						repo.fireEvent(new WorkingTreeModifiedEvent(
366 								resolveMerger.getModifiedFiles(), null));
367 					}
368 				} else
369 					noProblems = merger.merge(headCommit, srcCommit);
370 				refLogMessage.append(": Merge made by "); //$NON-NLS-1$
371 				if (!revWalk.isMergedInto(headCommit, srcCommit))
372 					refLogMessage.append(mergeStrategy.getName());
373 				else
374 					refLogMessage.append("recursive"); //$NON-NLS-1$
375 				refLogMessage.append('.');
376 				if (noProblems) {
377 					dco = new DirCacheCheckout(repo,
378 							headCommit.getTree(), repo.lockDirCache(),
379 							merger.getResultTreeId());
380 					dco.setFailOnConflict(true);
381 					dco.setProgressMonitor(monitor);
382 					dco.checkout();
383 
384 					String msg = null;
385 					ObjectId newHeadId = null;
386 					MergeStatus mergeStatus = null;
387 					if (!commit && squash) {
388 						mergeStatus = MergeStatus.MERGED_SQUASHED_NOT_COMMITTED;
389 					}
390 					if (!commit && !squash) {
391 						mergeStatus = MergeStatus.MERGED_NOT_COMMITTED;
392 					}
393 					if (commit && !squash) {
394 						try (Git git = new Git(getRepository())) {
395 							newHeadId = git.commit()
396 									.setReflogComment(refLogMessage.toString())
397 									.setInsertChangeId(insertChangeId)
398 									.call().getId();
399 						}
400 						mergeStatus = MergeStatus.MERGED;
401 						getRepository().autoGC(monitor);
402 					}
403 					if (commit && squash) {
404 						msg = JGitText.get().squashCommitNotUpdatingHEAD;
405 						newHeadId = headCommit.getId();
406 						mergeStatus = MergeStatus.MERGED_SQUASHED;
407 					}
408 					return new MergeResult(newHeadId, null,
409 							new ObjectId[] { headCommit.getId(),
410 									srcCommit.getId() }, mergeStatus,
411 							mergeStrategy, null, msg);
412 				} else {
413 					if (failingPaths != null) {
414 						repo.writeMergeCommitMsg(null);
415 						repo.writeMergeHeads(null);
416 						return new MergeResult(null, merger.getBaseCommitId(),
417 								new ObjectId[] {
418 										headCommit.getId(), srcCommit.getId() },
419 								MergeStatus.FAILED, mergeStrategy,
420 								lowLevelResults, failingPaths, null);
421 					} else {
422 						String mergeMessageWithConflicts = new MergeMessageFormatter()
423 								.formatWithConflicts(mergeMessage,
424 										unmergedPaths);
425 						repo.writeMergeCommitMsg(mergeMessageWithConflicts);
426 						return new MergeResult(null, merger.getBaseCommitId(),
427 								new ObjectId[] { headCommit.getId(),
428 										srcCommit.getId() },
429 								MergeStatus.CONFLICTING, mergeStrategy,
430 								lowLevelResults, null);
431 					}
432 				}
433 			}
434 		} catch (org.eclipse.jgit.errors.CheckoutConflictException e) {
435 			List<String> conflicts = (dco == null) ? Collections
436 					.<String> emptyList() : dco.getConflicts();
437 			throw new CheckoutConflictException(conflicts, e);
438 		} catch (IOException e) {
439 			throw new JGitInternalException(
440 					MessageFormat.format(
441 							JGitText.get().exceptionCaughtDuringExecutionOfMergeCommand,
442 							e), e);
443 		}
444 	}
445 
446 	private void checkParameters() throws InvalidMergeHeadsException {
447 		if (squash.booleanValue() && fastForwardMode == FastForwardMode.NO_FF) {
448 			throw new JGitInternalException(
449 					JGitText.get().cannotCombineSquashWithNoff);
450 		}
451 
452 		if (commits.size() != 1)
453 			throw new InvalidMergeHeadsException(
454 					commits.isEmpty() ? JGitText.get().noMergeHeadSpecified
455 							: MessageFormat.format(
456 									JGitText.get().mergeStrategyDoesNotSupportHeads,
457 									mergeStrategy.getName(),
458 									Integer.valueOf(commits.size())));
459 	}
460 
461 	/**
462 	 * Use values from the configuation if they have not been explicitly defined
463 	 * via the setters
464 	 */
465 	private void fallBackToConfiguration() {
466 		MergeConfig config = MergeConfig.getConfigForCurrentBranch(repo);
467 		if (squash == null)
468 			squash = Boolean.valueOf(config.isSquash());
469 		if (commit == null)
470 			commit = Boolean.valueOf(config.isCommit());
471 		if (fastForwardMode == null)
472 			fastForwardMode = config.getFastForwardMode();
473 	}
474 
475 	private void updateHead(StringBuilder refLogMessage, ObjectId newHeadId,
476 			ObjectId oldHeadID) throws IOException,
477 			ConcurrentRefUpdateException {
478 		RefUpdate refUpdate = repo.updateRef(Constants.HEAD);
479 		refUpdate.setNewObjectId(newHeadId);
480 		refUpdate.setRefLogMessage(refLogMessage.toString(), false);
481 		refUpdate.setExpectedOldObjectId(oldHeadID);
482 		Result rc = refUpdate.update();
483 		switch (rc) {
484 		case NEW:
485 		case FAST_FORWARD:
486 			return;
487 		case REJECTED:
488 		case LOCK_FAILURE:
489 			throw new ConcurrentRefUpdateException(
490 					JGitText.get().couldNotLockHEAD, refUpdate.getRef(), rc);
491 		default:
492 			throw new JGitInternalException(MessageFormat.format(
493 					JGitText.get().updatingRefFailed, Constants.HEAD,
494 					newHeadId.toString(), rc));
495 		}
496 	}
497 
498 	/**
499 	 * Set merge strategy
500 	 *
501 	 * @param mergeStrategy
502 	 *            the {@link org.eclipse.jgit.merge.MergeStrategy} to be used
503 	 * @return {@code this}
504 	 */
505 	public MergeCommand setStrategy(MergeStrategy mergeStrategy) {
506 		checkCallable();
507 		this.mergeStrategy = mergeStrategy;
508 		return this;
509 	}
510 
511 	/**
512 	 * Reference to a commit to be merged with the current head
513 	 *
514 	 * @param aCommit
515 	 *            a reference to a commit which is merged with the current head
516 	 * @return {@code this}
517 	 */
518 	public MergeCommand include(Ref aCommit) {
519 		checkCallable();
520 		commits.add(aCommit);
521 		return this;
522 	}
523 
524 	/**
525 	 * Id of a commit which is to be merged with the current head
526 	 *
527 	 * @param aCommit
528 	 *            the Id of a commit which is merged with the current head
529 	 * @return {@code this}
530 	 */
531 	public MergeCommand include(AnyObjectId aCommit) {
532 		return include(aCommit.getName(), aCommit);
533 	}
534 
535 	/**
536 	 * Include a commit
537 	 *
538 	 * @param name
539 	 *            a name of a {@code Ref} pointing to the commit
540 	 * @param aCommit
541 	 *            the Id of a commit which is merged with the current head
542 	 * @return {@code this}
543 	 */
544 	public MergeCommand include(String name, AnyObjectId aCommit) {
545 		return include(new ObjectIdRef.Unpeeled(Storage.LOOSE, name,
546 				aCommit.copy()));
547 	}
548 
549 	/**
550 	 * If <code>true</code>, will prepare the next commit in working tree and
551 	 * index as if a real merge happened, but do not make the commit or move the
552 	 * HEAD. Otherwise, perform the merge and commit the result.
553 	 * <p>
554 	 * In case the merge was successful but this flag was set to
555 	 * <code>true</code> a {@link org.eclipse.jgit.api.MergeResult} with status
556 	 * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_SQUASHED} or
557 	 * {@link org.eclipse.jgit.api.MergeResult.MergeStatus#FAST_FORWARD_SQUASHED}
558 	 * is returned.
559 	 *
560 	 * @param squash
561 	 *            whether to squash commits or not
562 	 * @return {@code this}
563 	 * @since 2.0
564 	 */
565 	public MergeCommand setSquash(boolean squash) {
566 		checkCallable();
567 		this.squash = Boolean.valueOf(squash);
568 		return this;
569 	}
570 
571 	/**
572 	 * Sets the fast forward mode.
573 	 *
574 	 * @param fastForwardMode
575 	 *            corresponds to the --ff/--no-ff/--ff-only options. If
576 	 *            {@code null} use the value of the {@code merge.ff} option
577 	 *            configured in git config. If this option is not configured
578 	 *            --ff is the built-in default.
579 	 * @return {@code this}
580 	 * @since 2.2
581 	 */
582 	public MergeCommand setFastForward(
583 			@Nullable FastForwardMode fastForwardMode) {
584 		checkCallable();
585 		this.fastForwardMode = fastForwardMode;
586 		return this;
587 	}
588 
589 	/**
590 	 * Controls whether the merge command should automatically commit after a
591 	 * successful merge
592 	 *
593 	 * @param commit
594 	 *            <code>true</code> if this command should commit (this is the
595 	 *            default behavior). <code>false</code> if this command should
596 	 *            not commit. In case the merge was successful but this flag was
597 	 *            set to <code>false</code> a
598 	 *            {@link org.eclipse.jgit.api.MergeResult} with type
599 	 *            {@link org.eclipse.jgit.api.MergeResult} with status
600 	 *            {@link org.eclipse.jgit.api.MergeResult.MergeStatus#MERGED_NOT_COMMITTED}
601 	 *            is returned
602 	 * @return {@code this}
603 	 * @since 3.0
604 	 */
605 	public MergeCommand setCommit(boolean commit) {
606 		this.commit = Boolean.valueOf(commit);
607 		return this;
608 	}
609 
610 	/**
611 	 * Set the commit message to be used for the merge commit (in case one is
612 	 * created)
613 	 *
614 	 * @param message
615 	 *            the message to be used for the merge commit
616 	 * @return {@code this}
617 	 * @since 3.5
618 	 */
619 	public MergeCommand setMessage(String message) {
620 		this.message = message;
621 		return this;
622 	}
623 
624 	/**
625 	 * If set to true a change id will be inserted into the commit message
626 	 *
627 	 * An existing change id is not replaced. An initial change id (I000...)
628 	 * will be replaced by the change id.
629 	 *
630 	 * @param insertChangeId
631 	 *            whether to insert a change id
632 	 * @return {@code this}
633 	 * @since 5.0
634 	 */
635 	public MergeCommand setInsertChangeId(boolean insertChangeId) {
636 		checkCallable();
637 		this.insertChangeId = insertChangeId;
638 		return this;
639 	}
640 
641 	/**
642 	 * The progress monitor associated with the diff operation. By default, this
643 	 * is set to <code>NullProgressMonitor</code>
644 	 *
645 	 * @see NullProgressMonitor
646 	 * @param monitor
647 	 *            A progress monitor
648 	 * @return this instance
649 	 * @since 4.2
650 	 */
651 	public MergeCommand setProgressMonitor(ProgressMonitor monitor) {
652 		if (monitor == null) {
653 			monitor = NullProgressMonitor.INSTANCE;
654 		}
655 		this.monitor = monitor;
656 		return this;
657 	}
658 }