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