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