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