View Javadoc
1   /*
2    * Copyright (C) 2010-2012, Christian Halstrick <christian.halstrick@sap.com> and others
3    *
4    * This program and the accompanying materials are made available under the
5    * terms of the Eclipse Distribution License v. 1.0 which is available at
6    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  package org.eclipse.jgit.api;
11  
12  import java.io.IOException;
13  import java.io.InputStream;
14  import java.io.PrintStream;
15  import java.text.MessageFormat;
16  import java.util.ArrayList;
17  import java.util.Collections;
18  import java.util.HashMap;
19  import java.util.LinkedList;
20  import java.util.List;
21  
22  import org.eclipse.jgit.annotations.NonNull;
23  import org.eclipse.jgit.api.errors.AbortedByHookException;
24  import org.eclipse.jgit.api.errors.CanceledException;
25  import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
26  import org.eclipse.jgit.api.errors.EmptyCommitException;
27  import org.eclipse.jgit.api.errors.GitAPIException;
28  import org.eclipse.jgit.api.errors.JGitInternalException;
29  import org.eclipse.jgit.api.errors.NoFilepatternException;
30  import org.eclipse.jgit.api.errors.NoHeadException;
31  import org.eclipse.jgit.api.errors.NoMessageException;
32  import org.eclipse.jgit.api.errors.ServiceUnavailableException;
33  import org.eclipse.jgit.api.errors.UnmergedPathsException;
34  import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
35  import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
36  import org.eclipse.jgit.dircache.DirCache;
37  import org.eclipse.jgit.dircache.DirCacheBuildIterator;
38  import org.eclipse.jgit.dircache.DirCacheBuilder;
39  import org.eclipse.jgit.dircache.DirCacheEntry;
40  import org.eclipse.jgit.dircache.DirCacheIterator;
41  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
42  import org.eclipse.jgit.errors.MissingObjectException;
43  import org.eclipse.jgit.errors.UnmergedPathException;
44  import org.eclipse.jgit.hooks.CommitMsgHook;
45  import org.eclipse.jgit.hooks.Hooks;
46  import org.eclipse.jgit.hooks.PostCommitHook;
47  import org.eclipse.jgit.hooks.PreCommitHook;
48  import org.eclipse.jgit.internal.JGitText;
49  import org.eclipse.jgit.lib.CommitBuilder;
50  import org.eclipse.jgit.lib.CommitConfig;
51  import org.eclipse.jgit.lib.CommitConfig.CleanupMode;
52  import org.eclipse.jgit.lib.Constants;
53  import org.eclipse.jgit.lib.FileMode;
54  import org.eclipse.jgit.lib.GpgConfig;
55  import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
56  import org.eclipse.jgit.lib.GpgObjectSigner;
57  import org.eclipse.jgit.lib.GpgSigner;
58  import org.eclipse.jgit.lib.ObjectId;
59  import org.eclipse.jgit.lib.ObjectInserter;
60  import org.eclipse.jgit.lib.PersonIdent;
61  import org.eclipse.jgit.lib.Ref;
62  import org.eclipse.jgit.lib.RefUpdate;
63  import org.eclipse.jgit.lib.RefUpdate.Result;
64  import org.eclipse.jgit.lib.Repository;
65  import org.eclipse.jgit.lib.RepositoryState;
66  import org.eclipse.jgit.revwalk.RevCommit;
67  import org.eclipse.jgit.revwalk.RevObject;
68  import org.eclipse.jgit.revwalk.RevTag;
69  import org.eclipse.jgit.revwalk.RevWalk;
70  import org.eclipse.jgit.transport.CredentialsProvider;
71  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
72  import org.eclipse.jgit.treewalk.FileTreeIterator;
73  import org.eclipse.jgit.treewalk.TreeWalk;
74  import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
75  import org.eclipse.jgit.util.ChangeIdUtil;
76  import org.slf4j.Logger;
77  import org.slf4j.LoggerFactory;
78  
79  /**
80   * A class used to execute a {@code Commit} command. It has setters for all
81   * supported options and arguments of this command and a {@link #call()} method
82   * to finally execute the command.
83   *
84   * @see <a
85   *      href="http://www.kernel.org/pub/software/scm/git/docs/git-commit.html"
86   *      >Git documentation about Commit</a>
87   */
88  public class CommitCommand extends GitCommand<RevCommit> {
89  	private static final Logger log = LoggerFactory
90  			.getLogger(CommitCommand.class);
91  
92  	private PersonIdent author;
93  
94  	private PersonIdent committer;
95  
96  	private String message;
97  
98  	private boolean all;
99  
100 	private List<String> only = new ArrayList<>();
101 
102 	private boolean[] onlyProcessed;
103 
104 	private boolean amend;
105 
106 	private boolean insertChangeId;
107 
108 	/**
109 	 * parents this commit should have. The current HEAD will be in this list
110 	 * and also all commits mentioned in .git/MERGE_HEAD
111 	 */
112 	private List<ObjectId> parents = new LinkedList<>();
113 
114 	private String reflogComment;
115 
116 	private boolean useDefaultReflogMessage = true;
117 
118 	/**
119 	 * Setting this option bypasses the pre-commit and commit-msg hooks.
120 	 */
121 	private boolean noVerify;
122 
123 	private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3);
124 
125 	private HashMap<String, PrintStream> hookErrRedirect = new HashMap<>(3);
126 
127 	private Boolean allowEmpty;
128 
129 	private Boolean signCommit;
130 
131 	private String signingKey;
132 
133 	private GpgSigner gpgSigner;
134 
135 	private GpgConfig gpgConfig;
136 
137 	private CredentialsProvider credentialsProvider;
138 
139 	private @NonNull CleanupMode cleanupMode = CleanupMode.VERBATIM;
140 
141 	private boolean cleanDefaultIsStrip = true;
142 
143 	private Character commentChar;
144 
145 	/**
146 	 * Constructor for CommitCommand
147 	 *
148 	 * @param repo
149 	 *            the {@link org.eclipse.jgit.lib.Repository}
150 	 */
151 	protected CommitCommand(Repository repo) {
152 		super(repo);
153 		this.credentialsProvider = CredentialsProvider.getDefault();
154 	}
155 
156 	/**
157 	 * {@inheritDoc}
158 	 * <p>
159 	 * Executes the {@code commit} command with all the options and parameters
160 	 * collected by the setter methods of this class. Each instance of this
161 	 * class should only be used for one invocation of the command (means: one
162 	 * call to {@link #call()})
163 	 *
164 	 * @throws ServiceUnavailableException
165 	 *             if signing service is not available e.g. since it isn't
166 	 *             installed
167 	 */
168 	@Override
169 	public RevCommit call() throws GitAPIException, AbortedByHookException,
170 			ConcurrentRefUpdateException, NoHeadException, NoMessageException,
171 			ServiceUnavailableException, UnmergedPathsException,
172 			WrongRepositoryStateException {
173 		checkCallable();
174 		Collections.sort(only);
175 
176 		try (RevWalk rw = new RevWalk(repo)) {
177 			RepositoryState state = repo.getRepositoryState();
178 			if (!state.canCommit())
179 				throw new WrongRepositoryStateException(MessageFormat.format(
180 						JGitText.get().cannotCommitOnARepoWithState,
181 						state.name()));
182 
183 			if (!noVerify) {
184 				Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME),
185 						hookErrRedirect.get(PreCommitHook.NAME))
186 						.call();
187 			}
188 
189 			processOptions(state, rw);
190 
191 			if (all && !repo.isBare()) {
192 				try (Git git = new Git(repo)) {
193 					git.add().addFilepattern(".") //$NON-NLS-1$
194 							.setUpdate(true).call();
195 				} catch (NoFilepatternException e) {
196 					// should really not happen
197 					throw new JGitInternalException(e.getMessage(), e);
198 				}
199 			}
200 
201 			Ref head = repo.exactRef(Constants.HEAD);
202 			if (head == null)
203 				throw new NoHeadException(
204 						JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);
205 
206 			// determine the current HEAD and the commit it is referring to
207 			ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$
208 			if (headId == null && amend)
209 				throw new WrongRepositoryStateException(
210 						JGitText.get().commitAmendOnInitialNotPossible);
211 
212 			if (headId != null) {
213 				if (amend) {
214 					RevCommit previousCommit = rw.parseCommit(headId);
215 					for (RevCommit p : previousCommit.getParents())
216 						parents.add(p.getId());
217 					if (author == null)
218 						author = previousCommit.getAuthorIdent();
219 				} else {
220 					parents.add(0, headId);
221 				}
222 			}
223 			if (!noVerify) {
224 				message = Hooks
225 						.commitMsg(repo,
226 								hookOutRedirect.get(CommitMsgHook.NAME),
227 								hookErrRedirect.get(CommitMsgHook.NAME))
228 						.setCommitMessage(message).call();
229 			}
230 
231 			CommitConfig config = null;
232 			if (CleanupMode.DEFAULT.equals(cleanupMode)) {
233 				config = repo.getConfig().get(CommitConfig.KEY);
234 				cleanupMode = config.resolve(cleanupMode, cleanDefaultIsStrip);
235 			}
236 			char comments = (char) 0;
237 			if (CleanupMode.STRIP.equals(cleanupMode)
238 					|| CleanupMode.SCISSORS.equals(cleanupMode)) {
239 				if (commentChar == null) {
240 					if (config == null) {
241 						config = repo.getConfig().get(CommitConfig.KEY);
242 					}
243 					if (config.isAutoCommentChar()) {
244 						// We're supposed to pick a character that isn't used,
245 						// but then cleaning up won't remove any lines. So don't
246 						// bother.
247 						comments = (char) 0;
248 						cleanupMode = CleanupMode.WHITESPACE;
249 					} else {
250 						comments = config.getCommentChar();
251 					}
252 				} else {
253 					comments = commentChar.charValue();
254 				}
255 			}
256 			message = CommitConfig.cleanText(message, cleanupMode, comments);
257 
258 			RevCommit revCommit;
259 			DirCache index = repo.lockDirCache();
260 			try (ObjectInserter odi = repo.newObjectInserter()) {
261 				if (!only.isEmpty())
262 					index = createTemporaryIndex(headId, index, rw);
263 
264 				// Write the index as tree to the object database. This may
265 				// fail for example when the index contains unmerged paths
266 				// (unresolved conflicts)
267 				ObjectId indexTreeId = index.writeTree(odi);
268 
269 				if (insertChangeId)
270 					insertChangeId(indexTreeId);
271 
272 				checkIfEmpty(rw, headId, indexTreeId);
273 
274 				// Create a Commit object, populate it and write it
275 				CommitBuilder commit = new CommitBuilder();
276 				commit.setCommitter(committer);
277 				commit.setAuthor(author);
278 				commit.setMessage(message);
279 				commit.setParentIds(parents);
280 				commit.setTreeId(indexTreeId);
281 
282 				if (signCommit.booleanValue()) {
283 					sign(commit);
284 				}
285 
286 				ObjectId commitId = odi.insert(commit);
287 				odi.flush();
288 				revCommit = rw.parseCommit(commitId);
289 
290 				updateRef(state, headId, revCommit, commitId);
291 			} finally {
292 				index.unlock();
293 			}
294 			try {
295 				Hooks.postCommit(repo, hookOutRedirect.get(PostCommitHook.NAME),
296 						hookErrRedirect.get(PostCommitHook.NAME)).call();
297 			} catch (Exception e) {
298 				log.error(MessageFormat.format(
299 						JGitText.get().postCommitHookFailed, e.getMessage()),
300 						e);
301 			}
302 			return revCommit;
303 		} catch (UnmergedPathException e) {
304 			throw new UnmergedPathsException(e);
305 		} catch (IOException e) {
306 			throw new JGitInternalException(
307 					JGitText.get().exceptionCaughtDuringExecutionOfCommitCommand, e);
308 		}
309 	}
310 
311 	private void checkIfEmpty(RevWalk rw, ObjectId headId, ObjectId indexTreeId)
312 			throws EmptyCommitException, MissingObjectException,
313 			IncorrectObjectTypeException, IOException {
314 		if (headId != null && !allowEmpty.booleanValue()) {
315 			RevCommit headCommit = rw.parseCommit(headId);
316 			headCommit.getTree();
317 			if (indexTreeId.equals(headCommit.getTree())) {
318 				throw new EmptyCommitException(JGitText.get().emptyCommit);
319 			}
320 		}
321 	}
322 
323 	private void sign(CommitBuilder commit) throws ServiceUnavailableException,
324 			CanceledException, UnsupportedSigningFormatException {
325 		if (gpgSigner == null) {
326 			gpgSigner = GpgSigner.getDefault();
327 			if (gpgSigner == null) {
328 				throw new ServiceUnavailableException(
329 						JGitText.get().signingServiceUnavailable);
330 			}
331 		}
332 		if (signingKey == null) {
333 			signingKey = gpgConfig.getSigningKey();
334 		}
335 		if (gpgSigner instanceof GpgObjectSigner) {
336 			((GpgObjectSigner) gpgSigner).signObject(commit,
337 					signingKey, committer, credentialsProvider,
338 					gpgConfig);
339 		} else {
340 			if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
341 				throw new UnsupportedSigningFormatException(JGitText
342 						.get().onlyOpenPgpSupportedForSigning);
343 			}
344 			gpgSigner.sign(commit, signingKey, committer,
345 					credentialsProvider);
346 		}
347 	}
348 
349 	private void updateRef(RepositoryState state, ObjectId headId,
350 			RevCommit revCommit, ObjectId commitId)
351 			throws ConcurrentRefUpdateException, IOException {
352 		RefUpdate ru = repo.updateRef(Constants.HEAD);
353 		ru.setNewObjectId(commitId);
354 		if (!useDefaultReflogMessage) {
355 			ru.setRefLogMessage(reflogComment, false);
356 		} else {
357 			String prefix = amend ? "commit (amend): " //$NON-NLS-1$
358 					: parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$
359 							: "commit: "; //$NON-NLS-1$
360 			ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
361 					false);
362 		}
363 		if (headId != null) {
364 			ru.setExpectedOldObjectId(headId);
365 		} else {
366 			ru.setExpectedOldObjectId(ObjectId.zeroId());
367 		}
368 		Result rc = ru.forceUpdate();
369 		switch (rc) {
370 		case NEW:
371 		case FORCED:
372 		case FAST_FORWARD: {
373 			setCallable(false);
374 			if (state == RepositoryState.MERGING_RESOLVED
375 					|| isMergeDuringRebase(state)) {
376 				// Commit was successful. Now delete the files
377 				// used for merge commits
378 				repo.writeMergeCommitMsg(null);
379 				repo.writeMergeHeads(null);
380 			} else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
381 				repo.writeMergeCommitMsg(null);
382 				repo.writeCherryPickHead(null);
383 			} else if (state == RepositoryState.REVERTING_RESOLVED) {
384 				repo.writeMergeCommitMsg(null);
385 				repo.writeRevertHead(null);
386 			}
387 			break;
388 		}
389 		case REJECTED:
390 		case LOCK_FAILURE:
391 			throw new ConcurrentRefUpdateException(
392 					JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
393 		default:
394 			throw new JGitInternalException(MessageFormat.format(
395 					JGitText.get().updatingRefFailed, Constants.HEAD,
396 					commitId.toString(), rc));
397 		}
398 	}
399 
400 	private void insertChangeId(ObjectId treeId) {
401 		ObjectId firstParentId = null;
402 		if (!parents.isEmpty())
403 			firstParentId = parents.get(0);
404 		ObjectId changeId = ChangeIdUtil.computeChangeId(treeId, firstParentId,
405 				author, committer, message);
406 		message = ChangeIdUtil.insertId(message, changeId);
407 		if (changeId != null)
408 			message = message.replaceAll("\nChange-Id: I" //$NON-NLS-1$
409 					+ ObjectId.zeroId().getName() + "\n", "\nChange-Id: I" //$NON-NLS-1$ //$NON-NLS-2$
410 					+ changeId.getName() + "\n"); //$NON-NLS-1$
411 	}
412 
413 	private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
414 			RevWalk rw)
415 			throws IOException {
416 		ObjectInserter inserter = null;
417 
418 		// get DirCacheBuilder for existing index
419 		DirCacheBuilder existingBuilder = index.builder();
420 
421 		// get DirCacheBuilder for newly created in-core index to build a
422 		// temporary index for this commit
423 		DirCache inCoreIndex = DirCache.newInCore();
424 		DirCacheBuilder tempBuilder = inCoreIndex.builder();
425 
426 		onlyProcessed = new boolean[only.size()];
427 		boolean emptyCommit = true;
428 
429 		try (TreeWalk treeWalk = new TreeWalk(repo)) {
430 			treeWalk.setOperationType(OperationType.CHECKIN_OP);
431 			int dcIdx = treeWalk
432 					.addTree(new DirCacheBuildIterator(existingBuilder));
433 			FileTreeIterator fti = new FileTreeIterator(repo);
434 			fti.setDirCacheIterator(treeWalk, 0);
435 			int fIdx = treeWalk.addTree(fti);
436 			int hIdx = -1;
437 			if (headId != null)
438 				hIdx = treeWalk.addTree(rw.parseTree(headId));
439 			treeWalk.setRecursive(true);
440 
441 			String lastAddedFile = null;
442 			while (treeWalk.next()) {
443 				String path = treeWalk.getPathString();
444 				// check if current entry's path matches a specified path
445 				int pos = lookupOnly(path);
446 
447 				CanonicalTreeParser hTree = null;
448 				if (hIdx != -1)
449 					hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);
450 
451 				DirCacheIterator dcTree = treeWalk.getTree(dcIdx,
452 						DirCacheIterator.class);
453 
454 				if (pos >= 0) {
455 					// include entry in commit
456 
457 					FileTreeIterator fTree = treeWalk.getTree(fIdx,
458 							FileTreeIterator.class);
459 
460 					// check if entry refers to a tracked file
461 					boolean tracked = dcTree != null || hTree != null;
462 					if (!tracked)
463 						continue;
464 
465 					// for an unmerged path, DirCacheBuildIterator will yield 3
466 					// entries, we only want to add one
467 					if (path.equals(lastAddedFile))
468 						continue;
469 
470 					lastAddedFile = path;
471 
472 					if (fTree != null) {
473 						// create a new DirCacheEntry with data retrieved from
474 						// disk
475 						final DirCacheEntry dcEntry = new DirCacheEntry(path);
476 						long entryLength = fTree.getEntryLength();
477 						dcEntry.setLength(entryLength);
478 						dcEntry.setLastModified(fTree.getEntryLastModifiedInstant());
479 						dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));
480 
481 						boolean objectExists = (dcTree != null
482 								&& fTree.idEqual(dcTree))
483 								|| (hTree != null && fTree.idEqual(hTree));
484 						if (objectExists) {
485 							dcEntry.setObjectId(fTree.getEntryObjectId());
486 						} else {
487 							if (FileMode.GITLINK.equals(dcEntry.getFileMode()))
488 								dcEntry.setObjectId(fTree.getEntryObjectId());
489 							else {
490 								// insert object
491 								if (inserter == null)
492 									inserter = repo.newObjectInserter();
493 								long contentLength = fTree
494 										.getEntryContentLength();
495 								try (InputStream inputStream = fTree
496 										.openEntryStream()) {
497 									dcEntry.setObjectId(inserter.insert(
498 											Constants.OBJ_BLOB, contentLength,
499 											inputStream));
500 								}
501 							}
502 						}
503 
504 						// add to existing index
505 						existingBuilder.add(dcEntry);
506 						// add to temporary in-core index
507 						tempBuilder.add(dcEntry);
508 
509 						if (emptyCommit
510 								&& (hTree == null || !hTree.idEqual(fTree)
511 										|| hTree.getEntryRawMode() != fTree
512 												.getEntryRawMode()))
513 							// this is a change
514 							emptyCommit = false;
515 					} else {
516 						// if no file exists on disk, neither add it to
517 						// index nor to temporary in-core index
518 
519 						if (emptyCommit && hTree != null)
520 							// this is a change
521 							emptyCommit = false;
522 					}
523 
524 					// keep track of processed path
525 					onlyProcessed[pos] = true;
526 				} else {
527 					// add entries from HEAD for all other paths
528 					if (hTree != null) {
529 						// create a new DirCacheEntry with data retrieved from
530 						// HEAD
531 						final DirCacheEntry dcEntry = new DirCacheEntry(path);
532 						dcEntry.setObjectId(hTree.getEntryObjectId());
533 						dcEntry.setFileMode(hTree.getEntryFileMode());
534 
535 						// add to temporary in-core index
536 						tempBuilder.add(dcEntry);
537 					}
538 
539 					// preserve existing entry in index
540 					if (dcTree != null)
541 						existingBuilder.add(dcTree.getDirCacheEntry());
542 				}
543 			}
544 		}
545 
546 		// there must be no unprocessed paths left at this point; otherwise an
547 		// untracked or unknown path has been specified
548 		for (int i = 0; i < onlyProcessed.length; i++)
549 			if (!onlyProcessed[i])
550 				throw new JGitInternalException(MessageFormat.format(
551 						JGitText.get().entryNotFoundByPath, only.get(i)));
552 
553 		// there must be at least one change
554 		if (emptyCommit && !allowEmpty.booleanValue())
555 			// Would like to throw a EmptyCommitException. But this would break the API
556 			// TODO(ch): Change this in the next release
557 			throw new JGitInternalException(JGitText.get().emptyCommit);
558 
559 		// update index
560 		existingBuilder.commit();
561 		// finish temporary in-core index used for this commit
562 		tempBuilder.finish();
563 		return inCoreIndex;
564 	}
565 
566 	/**
567 	 * Look an entry's path up in the list of paths specified by the --only/ -o
568 	 * option
569 	 *
570 	 * In case the complete (file) path (e.g. "d1/d2/f1") cannot be found in
571 	 * <code>only</code>, lookup is also tried with (parent) directory paths
572 	 * (e.g. "d1/d2" and "d1").
573 	 *
574 	 * @param pathString
575 	 *            entry's path
576 	 * @return the item's index in <code>only</code>; -1 if no item matches
577 	 */
578 	private int lookupOnly(String pathString) {
579 		String p = pathString;
580 		while (true) {
581 			int position = Collections.binarySearch(only, p);
582 			if (position >= 0)
583 				return position;
584 			int l = p.lastIndexOf('/');
585 			if (l < 1)
586 				break;
587 			p = p.substring(0, l);
588 		}
589 		return -1;
590 	}
591 
592 	/**
593 	 * Sets default values for not explicitly specified options. Then validates
594 	 * that all required data has been provided.
595 	 *
596 	 * @param state
597 	 *            the state of the repository we are working on
598 	 * @param rw
599 	 *            the RevWalk to use
600 	 *
601 	 * @throws NoMessageException
602 	 *             if the commit message has not been specified
603 	 * @throws UnsupportedSigningFormatException
604 	 *             if the configured gpg.format is not supported
605 	 */
606 	private void processOptions(RepositoryState state, RevWalk rw)
607 			throws NoMessageException, UnsupportedSigningFormatException {
608 		if (committer == null)
609 			committer = new PersonIdent(repo);
610 		if (author == null && !amend)
611 			author = committer;
612 		if (allowEmpty == null)
613 			// JGit allows empty commits by default. Only when pathes are
614 			// specified the commit should not be empty. This behaviour differs
615 			// from native git but can only be adapted in the next release.
616 			// TODO(ch) align the defaults with native git
617 			allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE;
618 
619 		// when doing a merge commit parse MERGE_HEAD and MERGE_MSG files
620 		if (state == RepositoryState.MERGING_RESOLVED
621 				|| isMergeDuringRebase(state)) {
622 			try {
623 				parents = repo.readMergeHeads();
624 				if (parents != null)
625 					for (int i = 0; i < parents.size(); i++) {
626 						RevObject ro = rw.parseAny(parents.get(i));
627 						if (ro instanceof RevTag)
628 							parents.set(i, rw.peel(ro));
629 					}
630 			} catch (IOException e) {
631 				throw new JGitInternalException(MessageFormat.format(
632 						JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
633 						Constants.MERGE_HEAD, e), e);
634 			}
635 			if (message == null) {
636 				try {
637 					message = repo.readMergeCommitMsg();
638 				} catch (IOException e) {
639 					throw new JGitInternalException(MessageFormat.format(
640 							JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
641 							Constants.MERGE_MSG, e), e);
642 				}
643 			}
644 		} else if (state == RepositoryState.SAFE && message == null) {
645 			try {
646 				message = repo.readSquashCommitMsg();
647 				if (message != null)
648 					repo.writeSquashCommitMsg(null /* delete */);
649 			} catch (IOException e) {
650 				throw new JGitInternalException(MessageFormat.format(
651 						JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
652 						Constants.MERGE_MSG, e), e);
653 			}
654 
655 		}
656 		if (message == null)
657 			// as long as we don't support -C option we have to have
658 			// an explicit message
659 			throw new NoMessageException(JGitText.get().commitMessageNotSpecified);
660 
661 		if (gpgConfig == null) {
662 			gpgConfig = new GpgConfig(repo.getConfig());
663 		}
664 		if (signCommit == null) {
665 			signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE
666 					: Boolean.FALSE;
667 		}
668 	}
669 
670 	private boolean isMergeDuringRebase(RepositoryState state) {
671 		if (state != RepositoryState.REBASING_INTERACTIVE
672 				&& state != RepositoryState.REBASING_MERGE)
673 			return false;
674 		try {
675 			return repo.readMergeHeads() != null;
676 		} catch (IOException e) {
677 			throw new JGitInternalException(MessageFormat.format(
678 					JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
679 					Constants.MERGE_HEAD, e), e);
680 		}
681 	}
682 
683 	/**
684 	 * Set the commit message
685 	 *
686 	 * @param message
687 	 *            the commit message used for the {@code commit}
688 	 * @return {@code this}
689 	 */
690 	public CommitCommand setMessage(String message) {
691 		checkCallable();
692 		this.message = message;
693 		return this;
694 	}
695 
696 	/**
697 	 * Sets the {@link CleanupMode} to apply to the commit message. If not
698 	 * called, {@link CommitCommand} applies {@link CleanupMode#VERBATIM}.
699 	 *
700 	 * @param mode
701 	 *            {@link CleanupMode} to set
702 	 * @return {@code this}
703 	 * @since 6.1
704 	 */
705 	public CommitCommand setCleanupMode(@NonNull CleanupMode mode) {
706 		checkCallable();
707 		this.cleanupMode = mode;
708 		return this;
709 	}
710 
711 	/**
712 	 * Sets the default clean mode if {@link #setCleanupMode(CleanupMode)
713 	 * setCleanupMode(CleanupMode.DEFAULT)} is set and git config
714 	 * {@code commit.cleanup = default} or is not set.
715 	 *
716 	 * @param strip
717 	 *            if {@code true}, default to {@link CleanupMode#STRIP};
718 	 *            otherwise default to {@link CleanupMode#WHITESPACE}
719 	 * @return {@code this}
720 	 * @since 6.1
721 	 */
722 	public CommitCommand setDefaultClean(boolean strip) {
723 		checkCallable();
724 		this.cleanDefaultIsStrip = strip;
725 		return this;
726 	}
727 
728 	/**
729 	 * Sets the comment character to apply when cleaning a commit message. If
730 	 * {@code null} (the default) and the {@link #setCleanupMode(CleanupMode)
731 	 * clean-up mode} is {@link CleanupMode#STRIP} or
732 	 * {@link CleanupMode#SCISSORS}, the value of git config
733 	 * {@code core.commentChar} will be used.
734 	 *
735 	 * @param commentChar
736 	 *            the comment character, or {@code null} to use the value from
737 	 *            the git config
738 	 * @return {@code this}
739 	 * @since 6.1
740 	 */
741 	public CommitCommand setCommentCharacter(Character commentChar) {
742 		checkCallable();
743 		this.commentChar = commentChar;
744 		return this;
745 	}
746 
747 	/**
748 	 * Set whether to allow to create an empty commit
749 	 *
750 	 * @param allowEmpty
751 	 *            whether it should be allowed to create a commit which has the
752 	 *            same tree as it's sole predecessor (a commit which doesn't
753 	 *            change anything). By default when creating standard commits
754 	 *            (without specifying paths) JGit allows to create such commits.
755 	 *            When this flag is set to false an attempt to create an "empty"
756 	 *            standard commit will lead to an EmptyCommitException.
757 	 *            <p>
758 	 *            By default when creating a commit containing only specified
759 	 *            paths an attempt to create an empty commit leads to a
760 	 *            {@link org.eclipse.jgit.api.errors.JGitInternalException}. By
761 	 *            setting this flag to <code>true</code> this exception will not
762 	 *            be thrown.
763 	 * @return {@code this}
764 	 * @since 4.2
765 	 */
766 	public CommitCommand setAllowEmpty(boolean allowEmpty) {
767 		this.allowEmpty = Boolean.valueOf(allowEmpty);
768 		return this;
769 	}
770 
771 	/**
772 	 * Get the commit message
773 	 *
774 	 * @return the commit message used for the <code>commit</code>
775 	 */
776 	public String getMessage() {
777 		return message;
778 	}
779 
780 	/**
781 	 * Sets the committer for this {@code commit}. If no committer is explicitly
782 	 * specified because this method is never called or called with {@code null}
783 	 * value then the committer will be deduced from config info in repository,
784 	 * with current time.
785 	 *
786 	 * @param committer
787 	 *            the committer used for the {@code commit}
788 	 * @return {@code this}
789 	 */
790 	public CommitCommand setCommitter(PersonIdent committer) {
791 		checkCallable();
792 		this.committer = committer;
793 		return this;
794 	}
795 
796 	/**
797 	 * Sets the committer for this {@code commit}. If no committer is explicitly
798 	 * specified because this method is never called then the committer will be
799 	 * deduced from config info in repository, with current time.
800 	 *
801 	 * @param name
802 	 *            the name of the committer used for the {@code commit}
803 	 * @param email
804 	 *            the email of the committer used for the {@code commit}
805 	 * @return {@code this}
806 	 */
807 	public CommitCommand setCommitter(String name, String email) {
808 		checkCallable();
809 		return setCommitter(new PersonIdent(name, email));
810 	}
811 
812 	/**
813 	 * Get the committer
814 	 *
815 	 * @return the committer used for the {@code commit}. If no committer was
816 	 *         specified {@code null} is returned and the default
817 	 *         {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used
818 	 *         during execution of the command
819 	 */
820 	public PersonIdent getCommitter() {
821 		return committer;
822 	}
823 
824 	/**
825 	 * Sets the author for this {@code commit}. If no author is explicitly
826 	 * specified because this method is never called or called with {@code null}
827 	 * value then the author will be set to the committer or to the original
828 	 * author when amending.
829 	 *
830 	 * @param author
831 	 *            the author used for the {@code commit}
832 	 * @return {@code this}
833 	 */
834 	public CommitCommand setAuthor(PersonIdent author) {
835 		checkCallable();
836 		this.author = author;
837 		return this;
838 	}
839 
840 	/**
841 	 * Sets the author for this {@code commit}. If no author is explicitly
842 	 * specified because this method is never called then the author will be set
843 	 * to the committer or to the original author when amending.
844 	 *
845 	 * @param name
846 	 *            the name of the author used for the {@code commit}
847 	 * @param email
848 	 *            the email of the author used for the {@code commit}
849 	 * @return {@code this}
850 	 */
851 	public CommitCommand setAuthor(String name, String email) {
852 		checkCallable();
853 		return setAuthor(new PersonIdent(name, email));
854 	}
855 
856 	/**
857 	 * Get the author
858 	 *
859 	 * @return the author used for the {@code commit}. If no author was
860 	 *         specified {@code null} is returned and the default
861 	 *         {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used
862 	 *         during execution of the command
863 	 */
864 	public PersonIdent getAuthor() {
865 		return author;
866 	}
867 
868 	/**
869 	 * If set to true the Commit command automatically stages files that have
870 	 * been modified and deleted, but new files not known by the repository are
871 	 * not affected. This corresponds to the parameter -a on the command line.
872 	 *
873 	 * @param all
874 	 *            whether to auto-stage all files that have been modified and
875 	 *            deleted
876 	 * @return {@code this}
877 	 * @throws JGitInternalException
878 	 *             in case of an illegal combination of arguments/ options
879 	 */
880 	public CommitCommand setAll(boolean all) {
881 		checkCallable();
882 		if (all && !only.isEmpty())
883 			throw new JGitInternalException(MessageFormat.format(
884 					JGitText.get().illegalCombinationOfArguments, "--all", //$NON-NLS-1$
885 					"--only")); //$NON-NLS-1$
886 		this.all = all;
887 		return this;
888 	}
889 
890 	/**
891 	 * Used to amend the tip of the current branch. If set to {@code true}, the
892 	 * previous commit will be amended. This is equivalent to --amend on the
893 	 * command line.
894 	 *
895 	 * @param amend
896 	 *            whether to amend the tip of the current branch
897 	 * @return {@code this}
898 	 */
899 	public CommitCommand setAmend(boolean amend) {
900 		checkCallable();
901 		this.amend = amend;
902 		return this;
903 	}
904 
905 	/**
906 	 * Commit dedicated path only.
907 	 * <p>
908 	 * This method can be called several times to add multiple paths. Full file
909 	 * paths are supported as well as directory paths; in the latter case this
910 	 * commits all files/directories below the specified path.
911 	 *
912 	 * @param only
913 	 *            path to commit (with <code>/</code> as separator)
914 	 * @return {@code this}
915 	 */
916 	public CommitCommand setOnly(String only) {
917 		checkCallable();
918 		if (all)
919 			throw new JGitInternalException(MessageFormat.format(
920 					JGitText.get().illegalCombinationOfArguments, "--only", //$NON-NLS-1$
921 					"--all")); //$NON-NLS-1$
922 		String o = only.endsWith("/") ? only.substring(0, only.length() - 1) //$NON-NLS-1$
923 				: only;
924 		// ignore duplicates
925 		if (!this.only.contains(o))
926 			this.only.add(o);
927 		return this;
928 	}
929 
930 	/**
931 	 * If set to true a change id will be inserted into the commit message
932 	 *
933 	 * An existing change id is not replaced. An initial change id (I000...)
934 	 * will be replaced by the change id.
935 	 *
936 	 * @param insertChangeId
937 	 *            whether to insert a change id
938 	 * @return {@code this}
939 	 */
940 	public CommitCommand setInsertChangeId(boolean insertChangeId) {
941 		checkCallable();
942 		this.insertChangeId = insertChangeId;
943 		return this;
944 	}
945 
946 	/**
947 	 * Override the message written to the reflog
948 	 *
949 	 * @param reflogComment
950 	 *            the comment to be written into the reflog or <code>null</code>
951 	 *            to specify that no reflog should be written
952 	 * @return {@code this}
953 	 */
954 	public CommitCommand setReflogComment(String reflogComment) {
955 		this.reflogComment = reflogComment;
956 		useDefaultReflogMessage = false;
957 		return this;
958 	}
959 
960 	/**
961 	 * Sets the {@link #noVerify} option on this commit command.
962 	 * <p>
963 	 * Both the pre-commit and commit-msg hooks can block a commit by their
964 	 * return value; setting this option to <code>true</code> will bypass these
965 	 * two hooks.
966 	 * </p>
967 	 *
968 	 * @param noVerify
969 	 *            Whether this commit should be verified by the pre-commit and
970 	 *            commit-msg hooks.
971 	 * @return {@code this}
972 	 * @since 3.7
973 	 */
974 	public CommitCommand setNoVerify(boolean noVerify) {
975 		this.noVerify = noVerify;
976 		return this;
977 	}
978 
979 	/**
980 	 * Set the output stream for all hook scripts executed by this command
981 	 * (pre-commit, commit-msg, post-commit). If not set it defaults to
982 	 * {@code System.out}.
983 	 *
984 	 * @param hookStdOut
985 	 *            the output stream for hook scripts executed by this command
986 	 * @return {@code this}
987 	 * @since 3.7
988 	 */
989 	public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
990 		setHookOutputStream(PreCommitHook.NAME, hookStdOut);
991 		setHookOutputStream(CommitMsgHook.NAME, hookStdOut);
992 		setHookOutputStream(PostCommitHook.NAME, hookStdOut);
993 		return this;
994 	}
995 
996 	/**
997 	 * Set the error stream for all hook scripts executed by this command
998 	 * (pre-commit, commit-msg, post-commit). If not set it defaults to
999 	 * {@code System.err}.
1000 	 *
1001 	 * @param hookStdErr
1002 	 *            the error stream for hook scripts executed by this command
1003 	 * @return {@code this}
1004 	 * @since 5.6
1005 	 */
1006 	public CommitCommand setHookErrorStream(PrintStream hookStdErr) {
1007 		setHookErrorStream(PreCommitHook.NAME, hookStdErr);
1008 		setHookErrorStream(CommitMsgHook.NAME, hookStdErr);
1009 		setHookErrorStream(PostCommitHook.NAME, hookStdErr);
1010 		return this;
1011 	}
1012 
1013 	/**
1014 	 * Set the output stream for a selected hook script executed by this command
1015 	 * (pre-commit, commit-msg, post-commit). If not set it defaults to
1016 	 * {@code System.out}.
1017 	 *
1018 	 * @param hookName
1019 	 *            name of the hook to set the output stream for
1020 	 * @param hookStdOut
1021 	 *            the output stream to use for the selected hook
1022 	 * @return {@code this}
1023 	 * @since 4.5
1024 	 */
1025 	public CommitCommand setHookOutputStream(String hookName,
1026 			PrintStream hookStdOut) {
1027 		if (!(PreCommitHook.NAME.equals(hookName)
1028 				|| CommitMsgHook.NAME.equals(hookName)
1029 				|| PostCommitHook.NAME.equals(hookName))) {
1030 			throw new IllegalArgumentException(
1031 					MessageFormat.format(JGitText.get().illegalHookName,
1032 							hookName));
1033 		}
1034 		hookOutRedirect.put(hookName, hookStdOut);
1035 		return this;
1036 	}
1037 
1038 	/**
1039 	 * Set the error stream for a selected hook script executed by this command
1040 	 * (pre-commit, commit-msg, post-commit). If not set it defaults to
1041 	 * {@code System.err}.
1042 	 *
1043 	 * @param hookName
1044 	 *            name of the hook to set the output stream for
1045 	 * @param hookStdErr
1046 	 *            the output stream to use for the selected hook
1047 	 * @return {@code this}
1048 	 * @since 5.6
1049 	 */
1050 	public CommitCommand setHookErrorStream(String hookName,
1051 			PrintStream hookStdErr) {
1052 		if (!(PreCommitHook.NAME.equals(hookName)
1053 				|| CommitMsgHook.NAME.equals(hookName)
1054 				|| PostCommitHook.NAME.equals(hookName))) {
1055 			throw new IllegalArgumentException(MessageFormat
1056 					.format(JGitText.get().illegalHookName, hookName));
1057 		}
1058 		hookErrRedirect.put(hookName, hookStdErr);
1059 		return this;
1060 	}
1061 
1062 	/**
1063 	 * Sets the signing key
1064 	 * <p>
1065 	 * Per spec of user.signingKey: this will be sent to the GPG program as is,
1066 	 * i.e. can be anything supported by the GPG program.
1067 	 * </p>
1068 	 * <p>
1069 	 * Note, if none was set or <code>null</code> is specified a default will be
1070 	 * obtained from the configuration.
1071 	 * </p>
1072 	 *
1073 	 * @param signingKey
1074 	 *            signing key (maybe <code>null</code>)
1075 	 * @return {@code this}
1076 	 * @since 5.3
1077 	 */
1078 	public CommitCommand setSigningKey(String signingKey) {
1079 		checkCallable();
1080 		this.signingKey = signingKey;
1081 		return this;
1082 	}
1083 
1084 	/**
1085 	 * Sets whether the commit should be signed.
1086 	 *
1087 	 * @param sign
1088 	 *            <code>true</code> to sign, <code>false</code> to not sign and
1089 	 *            <code>null</code> for default behavior (read from
1090 	 *            configuration)
1091 	 * @return {@code this}
1092 	 * @since 5.3
1093 	 */
1094 	public CommitCommand setSign(Boolean sign) {
1095 		checkCallable();
1096 		this.signCommit = sign;
1097 		return this;
1098 	}
1099 
1100 	/**
1101 	 * Sets the {@link GpgSigner} to use if the commit is to be signed.
1102 	 *
1103 	 * @param signer
1104 	 *            to use; if {@code null}, the default signer will be used
1105 	 * @return {@code this}
1106 	 * @since 5.11
1107 	 */
1108 	public CommitCommand setGpgSigner(GpgSigner signer) {
1109 		checkCallable();
1110 		this.gpgSigner = signer;
1111 		return this;
1112 	}
1113 
1114 	/**
1115 	 * Sets an external {@link GpgConfig} to use. Whether it will be used is at
1116 	 * the discretion of the {@link #setGpgSigner(GpgSigner)}.
1117 	 *
1118 	 * @param config
1119 	 *            to set; if {@code null}, the config will be loaded from the
1120 	 *            git config of the repository
1121 	 * @return {@code this}
1122 	 * @since 5.11
1123 	 */
1124 	public CommitCommand setGpgConfig(GpgConfig config) {
1125 		checkCallable();
1126 		this.gpgConfig = config;
1127 		return this;
1128 	}
1129 
1130 	/**
1131 	 * Sets a {@link CredentialsProvider}
1132 	 *
1133 	 * @param credentialsProvider
1134 	 *            the provider to use when querying for credentials (eg., during
1135 	 *            signing)
1136 	 * @return {@code this}
1137 	 * @since 6.0
1138 	 */
1139 	public CommitCommand setCredentialsProvider(
1140 			CredentialsProvider credentialsProvider) {
1141 		this.credentialsProvider = credentialsProvider;
1142 		return this;
1143 	}
1144 }