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