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