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