CommitCommand.java

  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. import java.io.IOException;
  12. import java.io.InputStream;
  13. import java.io.PrintStream;
  14. import java.text.MessageFormat;
  15. import java.util.ArrayList;
  16. import java.util.Collections;
  17. import java.util.HashMap;
  18. import java.util.LinkedList;
  19. import java.util.List;

  20. import org.eclipse.jgit.api.errors.AbortedByHookException;
  21. import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
  22. import org.eclipse.jgit.api.errors.EmptyCommitException;
  23. import org.eclipse.jgit.api.errors.GitAPIException;
  24. import org.eclipse.jgit.api.errors.JGitInternalException;
  25. import org.eclipse.jgit.api.errors.NoFilepatternException;
  26. import org.eclipse.jgit.api.errors.NoHeadException;
  27. import org.eclipse.jgit.api.errors.NoMessageException;
  28. import org.eclipse.jgit.api.errors.ServiceUnavailableException;
  29. import org.eclipse.jgit.api.errors.UnmergedPathsException;
  30. import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
  31. import org.eclipse.jgit.api.errors.WrongRepositoryStateException;
  32. import org.eclipse.jgit.dircache.DirCache;
  33. import org.eclipse.jgit.dircache.DirCacheBuildIterator;
  34. import org.eclipse.jgit.dircache.DirCacheBuilder;
  35. import org.eclipse.jgit.dircache.DirCacheEntry;
  36. import org.eclipse.jgit.dircache.DirCacheIterator;
  37. import org.eclipse.jgit.errors.UnmergedPathException;
  38. import org.eclipse.jgit.hooks.CommitMsgHook;
  39. import org.eclipse.jgit.hooks.Hooks;
  40. import org.eclipse.jgit.hooks.PostCommitHook;
  41. import org.eclipse.jgit.hooks.PreCommitHook;
  42. import org.eclipse.jgit.internal.JGitText;
  43. import org.eclipse.jgit.lib.CommitBuilder;
  44. import org.eclipse.jgit.lib.Constants;
  45. import org.eclipse.jgit.lib.FileMode;
  46. import org.eclipse.jgit.lib.GpgConfig;
  47. import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
  48. import org.eclipse.jgit.lib.GpgObjectSigner;
  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.revwalk.RevCommit;
  59. import org.eclipse.jgit.revwalk.RevObject;
  60. import org.eclipse.jgit.revwalk.RevTag;
  61. import org.eclipse.jgit.revwalk.RevWalk;
  62. import org.eclipse.jgit.transport.CredentialsProvider;
  63. import org.eclipse.jgit.treewalk.CanonicalTreeParser;
  64. import org.eclipse.jgit.treewalk.FileTreeIterator;
  65. import org.eclipse.jgit.treewalk.TreeWalk;
  66. import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
  67. import org.eclipse.jgit.util.ChangeIdUtil;

  68. /**
  69.  * A class used to execute a {@code Commit} command. It has setters for all
  70.  * supported options and arguments of this command and a {@link #call()} method
  71.  * to finally execute the command.
  72.  *
  73.  * @see <a
  74.  *      href="http://www.kernel.org/pub/software/scm/git/docs/git-commit.html"
  75.  *      >Git documentation about Commit</a>
  76.  */
  77. public class CommitCommand extends GitCommand<RevCommit> {
  78.     private PersonIdent author;

  79.     private PersonIdent committer;

  80.     private String message;

  81.     private boolean all;

  82.     private List<String> only = new ArrayList<>();

  83.     private boolean[] onlyProcessed;

  84.     private boolean amend;

  85.     private boolean insertChangeId;

  86.     /**
  87.      * parents this commit should have. The current HEAD will be in this list
  88.      * and also all commits mentioned in .git/MERGE_HEAD
  89.      */
  90.     private List<ObjectId> parents = new LinkedList<>();

  91.     private String reflogComment;

  92.     private boolean useDefaultReflogMessage = true;

  93.     /**
  94.      * Setting this option bypasses the pre-commit and commit-msg hooks.
  95.      */
  96.     private boolean noVerify;

  97.     private HashMap<String, PrintStream> hookOutRedirect = new HashMap<>(3);

  98.     private HashMap<String, PrintStream> hookErrRedirect = new HashMap<>(3);

  99.     private Boolean allowEmpty;

  100.     private Boolean signCommit;

  101.     private String signingKey;

  102.     private GpgSigner gpgSigner;

  103.     private GpgConfig gpgConfig;

  104.     private CredentialsProvider credentialsProvider;

  105.     /**
  106.      * Constructor for CommitCommand
  107.      *
  108.      * @param repo
  109.      *            the {@link org.eclipse.jgit.lib.Repository}
  110.      */
  111.     protected CommitCommand(Repository repo) {
  112.         super(repo);
  113.         this.credentialsProvider = CredentialsProvider.getDefault();
  114.     }

  115.     /**
  116.      * {@inheritDoc}
  117.      * <p>
  118.      * Executes the {@code commit} command with all the options and parameters
  119.      * collected by the setter methods of this class. Each instance of this
  120.      * class should only be used for one invocation of the command (means: one
  121.      * call to {@link #call()})
  122.      *
  123.      * @throws ServiceUnavailableException
  124.      *             if signing service is not available e.g. since it isn't
  125.      *             installed
  126.      */
  127.     @Override
  128.     public RevCommit call() throws GitAPIException, AbortedByHookException,
  129.             ConcurrentRefUpdateException, NoHeadException, NoMessageException,
  130.             ServiceUnavailableException, UnmergedPathsException,
  131.             WrongRepositoryStateException {
  132.         checkCallable();
  133.         Collections.sort(only);

  134.         try (RevWalk rw = new RevWalk(repo)) {
  135.             RepositoryState state = repo.getRepositoryState();
  136.             if (!state.canCommit())
  137.                 throw new WrongRepositoryStateException(MessageFormat.format(
  138.                         JGitText.get().cannotCommitOnARepoWithState,
  139.                         state.name()));

  140.             if (!noVerify) {
  141.                 Hooks.preCommit(repo, hookOutRedirect.get(PreCommitHook.NAME),
  142.                         hookErrRedirect.get(PreCommitHook.NAME))
  143.                         .call();
  144.             }

  145.             processOptions(state, rw);

  146.             if (all && !repo.isBare()) {
  147.                 try (Git git = new Git(repo)) {
  148.                     git.add()
  149.                             .addFilepattern(".") //$NON-NLS-1$
  150.                             .setUpdate(true).call();
  151.                 } catch (NoFilepatternException e) {
  152.                     // should really not happen
  153.                     throw new JGitInternalException(e.getMessage(), e);
  154.                 }
  155.             }

  156.             Ref head = repo.exactRef(Constants.HEAD);
  157.             if (head == null)
  158.                 throw new NoHeadException(
  159.                         JGitText.get().commitOnRepoWithoutHEADCurrentlyNotSupported);

  160.             // determine the current HEAD and the commit it is referring to
  161.             ObjectId headId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$
  162.             if (headId == null && amend)
  163.                 throw new WrongRepositoryStateException(
  164.                         JGitText.get().commitAmendOnInitialNotPossible);

  165.             if (headId != null)
  166.                 if (amend) {
  167.                     RevCommit previousCommit = rw.parseCommit(headId);
  168.                     for (RevCommit p : previousCommit.getParents())
  169.                         parents.add(p.getId());
  170.                     if (author == null)
  171.                         author = previousCommit.getAuthorIdent();
  172.                 } else {
  173.                     parents.add(0, headId);
  174.                 }

  175.             if (!noVerify) {
  176.                 message = Hooks
  177.                         .commitMsg(repo,
  178.                                 hookOutRedirect.get(CommitMsgHook.NAME),
  179.                                 hookErrRedirect.get(CommitMsgHook.NAME))
  180.                         .setCommitMessage(message).call();
  181.             }

  182.             // lock the index
  183.             DirCache index = repo.lockDirCache();
  184.             try (ObjectInserter odi = repo.newObjectInserter()) {
  185.                 if (!only.isEmpty())
  186.                     index = createTemporaryIndex(headId, index, rw);

  187.                 // Write the index as tree to the object database. This may
  188.                 // fail for example when the index contains unmerged paths
  189.                 // (unresolved conflicts)
  190.                 ObjectId indexTreeId = index.writeTree(odi);

  191.                 if (insertChangeId)
  192.                     insertChangeId(indexTreeId);

  193.                 // Check for empty commits
  194.                 if (headId != null && !allowEmpty.booleanValue()) {
  195.                     RevCommit headCommit = rw.parseCommit(headId);
  196.                     headCommit.getTree();
  197.                     if (indexTreeId.equals(headCommit.getTree())) {
  198.                         throw new EmptyCommitException(
  199.                                 JGitText.get().emptyCommit);
  200.                     }
  201.                 }

  202.                 // Create a Commit object, populate it and write it
  203.                 CommitBuilder commit = new CommitBuilder();
  204.                 commit.setCommitter(committer);
  205.                 commit.setAuthor(author);
  206.                 commit.setMessage(message);

  207.                 commit.setParentIds(parents);
  208.                 commit.setTreeId(indexTreeId);

  209.                 if (signCommit.booleanValue()) {
  210.                     if (gpgSigner == null) {
  211.                         throw new ServiceUnavailableException(
  212.                                 JGitText.get().signingServiceUnavailable);
  213.                     }
  214.                     if (gpgSigner instanceof GpgObjectSigner) {
  215.                         ((GpgObjectSigner) gpgSigner).signObject(commit,
  216.                                 signingKey, committer, credentialsProvider,
  217.                                 gpgConfig);
  218.                     } else {
  219.                         if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
  220.                             throw new UnsupportedSigningFormatException(JGitText
  221.                                     .get().onlyOpenPgpSupportedForSigning);
  222.                         }
  223.                         gpgSigner.sign(commit, signingKey, committer,
  224.                                 credentialsProvider);
  225.                     }
  226.                 }

  227.                 ObjectId commitId = odi.insert(commit);
  228.                 odi.flush();

  229.                 RevCommit revCommit = rw.parseCommit(commitId);
  230.                 RefUpdate ru = repo.updateRef(Constants.HEAD);
  231.                 ru.setNewObjectId(commitId);
  232.                 if (!useDefaultReflogMessage) {
  233.                     ru.setRefLogMessage(reflogComment, false);
  234.                 } else {
  235.                     String prefix = amend ? "commit (amend): " //$NON-NLS-1$
  236.                             : parents.isEmpty() ? "commit (initial): " //$NON-NLS-1$
  237.                                     : "commit: "; //$NON-NLS-1$
  238.                     ru.setRefLogMessage(prefix + revCommit.getShortMessage(),
  239.                             false);
  240.                 }
  241.                 if (headId != null)
  242.                     ru.setExpectedOldObjectId(headId);
  243.                 else
  244.                     ru.setExpectedOldObjectId(ObjectId.zeroId());
  245.                 Result rc = ru.forceUpdate();
  246.                 switch (rc) {
  247.                 case NEW:
  248.                 case FORCED:
  249.                 case FAST_FORWARD: {
  250.                     setCallable(false);
  251.                     if (state == RepositoryState.MERGING_RESOLVED
  252.                             || isMergeDuringRebase(state)) {
  253.                         // Commit was successful. Now delete the files
  254.                         // used for merge commits
  255.                         repo.writeMergeCommitMsg(null);
  256.                         repo.writeMergeHeads(null);
  257.                     } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) {
  258.                         repo.writeMergeCommitMsg(null);
  259.                         repo.writeCherryPickHead(null);
  260.                     } else if (state == RepositoryState.REVERTING_RESOLVED) {
  261.                         repo.writeMergeCommitMsg(null);
  262.                         repo.writeRevertHead(null);
  263.                     }
  264.                     Hooks.postCommit(repo,
  265.                             hookOutRedirect.get(PostCommitHook.NAME),
  266.                             hookErrRedirect.get(PostCommitHook.NAME)).call();

  267.                     return revCommit;
  268.                 }
  269.                 case REJECTED:
  270.                 case LOCK_FAILURE:
  271.                     throw new ConcurrentRefUpdateException(
  272.                             JGitText.get().couldNotLockHEAD, ru.getRef(), rc);
  273.                 default:
  274.                     throw new JGitInternalException(MessageFormat.format(
  275.                             JGitText.get().updatingRefFailed, Constants.HEAD,
  276.                             commitId.toString(), rc));
  277.                 }
  278.             } finally {
  279.                 index.unlock();
  280.             }
  281.         } catch (UnmergedPathException e) {
  282.             throw new UnmergedPathsException(e);
  283.         } catch (IOException e) {
  284.             throw new JGitInternalException(
  285.                     JGitText.get().exceptionCaughtDuringExecutionOfCommitCommand, e);
  286.         }
  287.     }

  288.     private void insertChangeId(ObjectId treeId) {
  289.         ObjectId firstParentId = null;
  290.         if (!parents.isEmpty())
  291.             firstParentId = parents.get(0);
  292.         ObjectId changeId = ChangeIdUtil.computeChangeId(treeId, firstParentId,
  293.                 author, committer, message);
  294.         message = ChangeIdUtil.insertId(message, changeId);
  295.         if (changeId != null)
  296.             message = message.replaceAll("\nChange-Id: I" //$NON-NLS-1$
  297.                     + ObjectId.zeroId().getName() + "\n", "\nChange-Id: I" //$NON-NLS-1$ //$NON-NLS-2$
  298.                     + changeId.getName() + "\n"); //$NON-NLS-1$
  299.     }

  300.     private DirCache createTemporaryIndex(ObjectId headId, DirCache index,
  301.             RevWalk rw)
  302.             throws IOException {
  303.         ObjectInserter inserter = null;

  304.         // get DirCacheBuilder for existing index
  305.         DirCacheBuilder existingBuilder = index.builder();

  306.         // get DirCacheBuilder for newly created in-core index to build a
  307.         // temporary index for this commit
  308.         DirCache inCoreIndex = DirCache.newInCore();
  309.         DirCacheBuilder tempBuilder = inCoreIndex.builder();

  310.         onlyProcessed = new boolean[only.size()];
  311.         boolean emptyCommit = true;

  312.         try (TreeWalk treeWalk = new TreeWalk(repo)) {
  313.             treeWalk.setOperationType(OperationType.CHECKIN_OP);
  314.             int dcIdx = treeWalk
  315.                     .addTree(new DirCacheBuildIterator(existingBuilder));
  316.             FileTreeIterator fti = new FileTreeIterator(repo);
  317.             fti.setDirCacheIterator(treeWalk, 0);
  318.             int fIdx = treeWalk.addTree(fti);
  319.             int hIdx = -1;
  320.             if (headId != null)
  321.                 hIdx = treeWalk.addTree(rw.parseTree(headId));
  322.             treeWalk.setRecursive(true);

  323.             String lastAddedFile = null;
  324.             while (treeWalk.next()) {
  325.                 String path = treeWalk.getPathString();
  326.                 // check if current entry's path matches a specified path
  327.                 int pos = lookupOnly(path);

  328.                 CanonicalTreeParser hTree = null;
  329.                 if (hIdx != -1)
  330.                     hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class);

  331.                 DirCacheIterator dcTree = treeWalk.getTree(dcIdx,
  332.                         DirCacheIterator.class);

  333.                 if (pos >= 0) {
  334.                     // include entry in commit

  335.                     FileTreeIterator fTree = treeWalk.getTree(fIdx,
  336.                             FileTreeIterator.class);

  337.                     // check if entry refers to a tracked file
  338.                     boolean tracked = dcTree != null || hTree != null;
  339.                     if (!tracked)
  340.                         continue;

  341.                     // for an unmerged path, DirCacheBuildIterator will yield 3
  342.                     // entries, we only want to add one
  343.                     if (path.equals(lastAddedFile))
  344.                         continue;

  345.                     lastAddedFile = path;

  346.                     if (fTree != null) {
  347.                         // create a new DirCacheEntry with data retrieved from
  348.                         // disk
  349.                         final DirCacheEntry dcEntry = new DirCacheEntry(path);
  350.                         long entryLength = fTree.getEntryLength();
  351.                         dcEntry.setLength(entryLength);
  352.                         dcEntry.setLastModified(fTree.getEntryLastModifiedInstant());
  353.                         dcEntry.setFileMode(fTree.getIndexFileMode(dcTree));

  354.                         boolean objectExists = (dcTree != null
  355.                                 && fTree.idEqual(dcTree))
  356.                                 || (hTree != null && fTree.idEqual(hTree));
  357.                         if (objectExists) {
  358.                             dcEntry.setObjectId(fTree.getEntryObjectId());
  359.                         } else {
  360.                             if (FileMode.GITLINK.equals(dcEntry.getFileMode()))
  361.                                 dcEntry.setObjectId(fTree.getEntryObjectId());
  362.                             else {
  363.                                 // insert object
  364.                                 if (inserter == null)
  365.                                     inserter = repo.newObjectInserter();
  366.                                 long contentLength = fTree
  367.                                         .getEntryContentLength();
  368.                                 try (InputStream inputStream = fTree
  369.                                         .openEntryStream()) {
  370.                                     dcEntry.setObjectId(inserter.insert(
  371.                                             Constants.OBJ_BLOB, contentLength,
  372.                                             inputStream));
  373.                                 }
  374.                             }
  375.                         }

  376.                         // add to existing index
  377.                         existingBuilder.add(dcEntry);
  378.                         // add to temporary in-core index
  379.                         tempBuilder.add(dcEntry);

  380.                         if (emptyCommit
  381.                                 && (hTree == null || !hTree.idEqual(fTree)
  382.                                         || hTree.getEntryRawMode() != fTree
  383.                                                 .getEntryRawMode()))
  384.                             // this is a change
  385.                             emptyCommit = false;
  386.                     } else {
  387.                         // if no file exists on disk, neither add it to
  388.                         // index nor to temporary in-core index

  389.                         if (emptyCommit && hTree != null)
  390.                             // this is a change
  391.                             emptyCommit = false;
  392.                     }

  393.                     // keep track of processed path
  394.                     onlyProcessed[pos] = true;
  395.                 } else {
  396.                     // add entries from HEAD for all other paths
  397.                     if (hTree != null) {
  398.                         // create a new DirCacheEntry with data retrieved from
  399.                         // HEAD
  400.                         final DirCacheEntry dcEntry = new DirCacheEntry(path);
  401.                         dcEntry.setObjectId(hTree.getEntryObjectId());
  402.                         dcEntry.setFileMode(hTree.getEntryFileMode());

  403.                         // add to temporary in-core index
  404.                         tempBuilder.add(dcEntry);
  405.                     }

  406.                     // preserve existing entry in index
  407.                     if (dcTree != null)
  408.                         existingBuilder.add(dcTree.getDirCacheEntry());
  409.                 }
  410.             }
  411.         }

  412.         // there must be no unprocessed paths left at this point; otherwise an
  413.         // untracked or unknown path has been specified
  414.         for (int i = 0; i < onlyProcessed.length; i++)
  415.             if (!onlyProcessed[i])
  416.                 throw new JGitInternalException(MessageFormat.format(
  417.                         JGitText.get().entryNotFoundByPath, only.get(i)));

  418.         // there must be at least one change
  419.         if (emptyCommit && !allowEmpty.booleanValue())
  420.             // Would like to throw a EmptyCommitException. But this would break the API
  421.             // TODO(ch): Change this in the next release
  422.             throw new JGitInternalException(JGitText.get().emptyCommit);

  423.         // update index
  424.         existingBuilder.commit();
  425.         // finish temporary in-core index used for this commit
  426.         tempBuilder.finish();
  427.         return inCoreIndex;
  428.     }

  429.     /**
  430.      * Look an entry's path up in the list of paths specified by the --only/ -o
  431.      * option
  432.      *
  433.      * In case the complete (file) path (e.g. "d1/d2/f1") cannot be found in
  434.      * <code>only</code>, lookup is also tried with (parent) directory paths
  435.      * (e.g. "d1/d2" and "d1").
  436.      *
  437.      * @param pathString
  438.      *            entry's path
  439.      * @return the item's index in <code>only</code>; -1 if no item matches
  440.      */
  441.     private int lookupOnly(String pathString) {
  442.         String p = pathString;
  443.         while (true) {
  444.             int position = Collections.binarySearch(only, p);
  445.             if (position >= 0)
  446.                 return position;
  447.             int l = p.lastIndexOf('/');
  448.             if (l < 1)
  449.                 break;
  450.             p = p.substring(0, l);
  451.         }
  452.         return -1;
  453.     }

  454.     /**
  455.      * Sets default values for not explicitly specified options. Then validates
  456.      * that all required data has been provided.
  457.      *
  458.      * @param state
  459.      *            the state of the repository we are working on
  460.      * @param rw
  461.      *            the RevWalk to use
  462.      *
  463.      * @throws NoMessageException
  464.      *             if the commit message has not been specified
  465.      * @throws UnsupportedSigningFormatException
  466.      *             if the configured gpg.format is not supported
  467.      */
  468.     private void processOptions(RepositoryState state, RevWalk rw)
  469.             throws NoMessageException, UnsupportedSigningFormatException {
  470.         if (committer == null)
  471.             committer = new PersonIdent(repo);
  472.         if (author == null && !amend)
  473.             author = committer;
  474.         if (allowEmpty == null)
  475.             // JGit allows empty commits by default. Only when pathes are
  476.             // specified the commit should not be empty. This behaviour differs
  477.             // from native git but can only be adapted in the next release.
  478.             // TODO(ch) align the defaults with native git
  479.             allowEmpty = (only.isEmpty()) ? Boolean.TRUE : Boolean.FALSE;

  480.         // when doing a merge commit parse MERGE_HEAD and MERGE_MSG files
  481.         if (state == RepositoryState.MERGING_RESOLVED
  482.                 || isMergeDuringRebase(state)) {
  483.             try {
  484.                 parents = repo.readMergeHeads();
  485.                 if (parents != null)
  486.                     for (int i = 0; i < parents.size(); i++) {
  487.                         RevObject ro = rw.parseAny(parents.get(i));
  488.                         if (ro instanceof RevTag)
  489.                             parents.set(i, rw.peel(ro));
  490.                     }
  491.             } catch (IOException e) {
  492.                 throw new JGitInternalException(MessageFormat.format(
  493.                         JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
  494.                         Constants.MERGE_HEAD, e), e);
  495.             }
  496.             if (message == null) {
  497.                 try {
  498.                     message = repo.readMergeCommitMsg();
  499.                 } catch (IOException e) {
  500.                     throw new JGitInternalException(MessageFormat.format(
  501.                             JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
  502.                             Constants.MERGE_MSG, e), e);
  503.                 }
  504.             }
  505.         } else if (state == RepositoryState.SAFE && message == null) {
  506.             try {
  507.                 message = repo.readSquashCommitMsg();
  508.                 if (message != null)
  509.                     repo.writeSquashCommitMsg(null /* delete */);
  510.             } catch (IOException e) {
  511.                 throw new JGitInternalException(MessageFormat.format(
  512.                         JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
  513.                         Constants.MERGE_MSG, e), e);
  514.             }

  515.         }
  516.         if (message == null)
  517.             // as long as we don't support -C option we have to have
  518.             // an explicit message
  519.             throw new NoMessageException(JGitText.get().commitMessageNotSpecified);

  520.         if (gpgConfig == null) {
  521.             gpgConfig = new GpgConfig(repo.getConfig());
  522.         }
  523.         if (signCommit == null) {
  524.             signCommit = gpgConfig.isSignCommits() ? Boolean.TRUE
  525.                     : Boolean.FALSE;
  526.         }
  527.         if (signingKey == null) {
  528.             signingKey = gpgConfig.getSigningKey();
  529.         }
  530.         if (gpgSigner == null) {
  531.             gpgSigner = GpgSigner.getDefault();
  532.         }
  533.     }

  534.     private boolean isMergeDuringRebase(RepositoryState state) {
  535.         if (state != RepositoryState.REBASING_INTERACTIVE
  536.                 && state != RepositoryState.REBASING_MERGE)
  537.             return false;
  538.         try {
  539.             return repo.readMergeHeads() != null;
  540.         } catch (IOException e) {
  541.             throw new JGitInternalException(MessageFormat.format(
  542.                     JGitText.get().exceptionOccurredDuringReadingOfGIT_DIR,
  543.                     Constants.MERGE_HEAD, e), e);
  544.         }
  545.     }

  546.     /**
  547.      * Set the commit message
  548.      *
  549.      * @param message
  550.      *            the commit message used for the {@code commit}
  551.      * @return {@code this}
  552.      */
  553.     public CommitCommand setMessage(String message) {
  554.         checkCallable();
  555.         this.message = message;
  556.         return this;
  557.     }

  558.     /**
  559.      * Set whether to allow to create an empty commit
  560.      *
  561.      * @param allowEmpty
  562.      *            whether it should be allowed to create a commit which has the
  563.      *            same tree as it's sole predecessor (a commit which doesn't
  564.      *            change anything). By default when creating standard commits
  565.      *            (without specifying paths) JGit allows to create such commits.
  566.      *            When this flag is set to false an attempt to create an "empty"
  567.      *            standard commit will lead to an EmptyCommitException.
  568.      *            <p>
  569.      *            By default when creating a commit containing only specified
  570.      *            paths an attempt to create an empty commit leads to a
  571.      *            {@link org.eclipse.jgit.api.errors.JGitInternalException}. By
  572.      *            setting this flag to <code>true</code> this exception will not
  573.      *            be thrown.
  574.      * @return {@code this}
  575.      * @since 4.2
  576.      */
  577.     public CommitCommand setAllowEmpty(boolean allowEmpty) {
  578.         this.allowEmpty = Boolean.valueOf(allowEmpty);
  579.         return this;
  580.     }

  581.     /**
  582.      * Get the commit message
  583.      *
  584.      * @return the commit message used for the <code>commit</code>
  585.      */
  586.     public String getMessage() {
  587.         return message;
  588.     }

  589.     /**
  590.      * Sets the committer for this {@code commit}. If no committer is explicitly
  591.      * specified because this method is never called or called with {@code null}
  592.      * value then the committer will be deduced from config info in repository,
  593.      * with current time.
  594.      *
  595.      * @param committer
  596.      *            the committer used for the {@code commit}
  597.      * @return {@code this}
  598.      */
  599.     public CommitCommand setCommitter(PersonIdent committer) {
  600.         checkCallable();
  601.         this.committer = committer;
  602.         return this;
  603.     }

  604.     /**
  605.      * Sets the committer for this {@code commit}. If no committer is explicitly
  606.      * specified because this method is never called then the committer will be
  607.      * deduced from config info in repository, with current time.
  608.      *
  609.      * @param name
  610.      *            the name of the committer used for the {@code commit}
  611.      * @param email
  612.      *            the email of the committer used for the {@code commit}
  613.      * @return {@code this}
  614.      */
  615.     public CommitCommand setCommitter(String name, String email) {
  616.         checkCallable();
  617.         return setCommitter(new PersonIdent(name, email));
  618.     }

  619.     /**
  620.      * Get the committer
  621.      *
  622.      * @return the committer used for the {@code commit}. If no committer was
  623.      *         specified {@code null} is returned and the default
  624.      *         {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used
  625.      *         during execution of the command
  626.      */
  627.     public PersonIdent getCommitter() {
  628.         return committer;
  629.     }

  630.     /**
  631.      * Sets the author for this {@code commit}. If no author is explicitly
  632.      * specified because this method is never called or called with {@code null}
  633.      * value then the author will be set to the committer or to the original
  634.      * author when amending.
  635.      *
  636.      * @param author
  637.      *            the author used for the {@code commit}
  638.      * @return {@code this}
  639.      */
  640.     public CommitCommand setAuthor(PersonIdent author) {
  641.         checkCallable();
  642.         this.author = author;
  643.         return this;
  644.     }

  645.     /**
  646.      * Sets the author for this {@code commit}. If no author is explicitly
  647.      * specified because this method is never called then the author will be set
  648.      * to the committer or to the original author when amending.
  649.      *
  650.      * @param name
  651.      *            the name of the author used for the {@code commit}
  652.      * @param email
  653.      *            the email of the author used for the {@code commit}
  654.      * @return {@code this}
  655.      */
  656.     public CommitCommand setAuthor(String name, String email) {
  657.         checkCallable();
  658.         return setAuthor(new PersonIdent(name, email));
  659.     }

  660.     /**
  661.      * Get the author
  662.      *
  663.      * @return the author used for the {@code commit}. If no author was
  664.      *         specified {@code null} is returned and the default
  665.      *         {@link org.eclipse.jgit.lib.PersonIdent} of this repo is used
  666.      *         during execution of the command
  667.      */
  668.     public PersonIdent getAuthor() {
  669.         return author;
  670.     }

  671.     /**
  672.      * If set to true the Commit command automatically stages files that have
  673.      * been modified and deleted, but new files not known by the repository are
  674.      * not affected. This corresponds to the parameter -a on the command line.
  675.      *
  676.      * @param all
  677.      *            whether to auto-stage all files that have been modified and
  678.      *            deleted
  679.      * @return {@code this}
  680.      * @throws JGitInternalException
  681.      *             in case of an illegal combination of arguments/ options
  682.      */
  683.     public CommitCommand setAll(boolean all) {
  684.         checkCallable();
  685.         if (all && !only.isEmpty())
  686.             throw new JGitInternalException(MessageFormat.format(
  687.                     JGitText.get().illegalCombinationOfArguments, "--all", //$NON-NLS-1$
  688.                     "--only")); //$NON-NLS-1$
  689.         this.all = all;
  690.         return this;
  691.     }

  692.     /**
  693.      * Used to amend the tip of the current branch. If set to {@code true}, the
  694.      * previous commit will be amended. This is equivalent to --amend on the
  695.      * command line.
  696.      *
  697.      * @param amend
  698.      *            whether to ammend the tip of the current branch
  699.      * @return {@code this}
  700.      */
  701.     public CommitCommand setAmend(boolean amend) {
  702.         checkCallable();
  703.         this.amend = amend;
  704.         return this;
  705.     }

  706.     /**
  707.      * Commit dedicated path only.
  708.      * <p>
  709.      * This method can be called several times to add multiple paths. Full file
  710.      * paths are supported as well as directory paths; in the latter case this
  711.      * commits all files/directories below the specified path.
  712.      *
  713.      * @param only
  714.      *            path to commit (with <code>/</code> as separator)
  715.      * @return {@code this}
  716.      */
  717.     public CommitCommand setOnly(String only) {
  718.         checkCallable();
  719.         if (all)
  720.             throw new JGitInternalException(MessageFormat.format(
  721.                     JGitText.get().illegalCombinationOfArguments, "--only", //$NON-NLS-1$
  722.                     "--all")); //$NON-NLS-1$
  723.         String o = only.endsWith("/") ? only.substring(0, only.length() - 1) //$NON-NLS-1$
  724.                 : only;
  725.         // ignore duplicates
  726.         if (!this.only.contains(o))
  727.             this.only.add(o);
  728.         return this;
  729.     }

  730.     /**
  731.      * If set to true a change id will be inserted into the commit message
  732.      *
  733.      * An existing change id is not replaced. An initial change id (I000...)
  734.      * will be replaced by the change id.
  735.      *
  736.      * @param insertChangeId
  737.      *            whether to insert a change id
  738.      * @return {@code this}
  739.      */
  740.     public CommitCommand setInsertChangeId(boolean insertChangeId) {
  741.         checkCallable();
  742.         this.insertChangeId = insertChangeId;
  743.         return this;
  744.     }

  745.     /**
  746.      * Override the message written to the reflog
  747.      *
  748.      * @param reflogComment
  749.      *            the comment to be written into the reflog or <code>null</code>
  750.      *            to specify that no reflog should be written
  751.      * @return {@code this}
  752.      */
  753.     public CommitCommand setReflogComment(String reflogComment) {
  754.         this.reflogComment = reflogComment;
  755.         useDefaultReflogMessage = false;
  756.         return this;
  757.     }

  758.     /**
  759.      * Sets the {@link #noVerify} option on this commit command.
  760.      * <p>
  761.      * Both the pre-commit and commit-msg hooks can block a commit by their
  762.      * return value; setting this option to <code>true</code> will bypass these
  763.      * two hooks.
  764.      * </p>
  765.      *
  766.      * @param noVerify
  767.      *            Whether this commit should be verified by the pre-commit and
  768.      *            commit-msg hooks.
  769.      * @return {@code this}
  770.      * @since 3.7
  771.      */
  772.     public CommitCommand setNoVerify(boolean noVerify) {
  773.         this.noVerify = noVerify;
  774.         return this;
  775.     }

  776.     /**
  777.      * Set the output stream for all hook scripts executed by this command
  778.      * (pre-commit, commit-msg, post-commit). If not set it defaults to
  779.      * {@code System.out}.
  780.      *
  781.      * @param hookStdOut
  782.      *            the output stream for hook scripts executed by this command
  783.      * @return {@code this}
  784.      * @since 3.7
  785.      */
  786.     public CommitCommand setHookOutputStream(PrintStream hookStdOut) {
  787.         setHookOutputStream(PreCommitHook.NAME, hookStdOut);
  788.         setHookOutputStream(CommitMsgHook.NAME, hookStdOut);
  789.         setHookOutputStream(PostCommitHook.NAME, hookStdOut);
  790.         return this;
  791.     }

  792.     /**
  793.      * Set the error stream for all hook scripts executed by this command
  794.      * (pre-commit, commit-msg, post-commit). If not set it defaults to
  795.      * {@code System.err}.
  796.      *
  797.      * @param hookStdErr
  798.      *            the error stream for hook scripts executed by this command
  799.      * @return {@code this}
  800.      * @since 5.6
  801.      */
  802.     public CommitCommand setHookErrorStream(PrintStream hookStdErr) {
  803.         setHookErrorStream(PreCommitHook.NAME, hookStdErr);
  804.         setHookErrorStream(CommitMsgHook.NAME, hookStdErr);
  805.         setHookErrorStream(PostCommitHook.NAME, hookStdErr);
  806.         return this;
  807.     }

  808.     /**
  809.      * Set the output stream for a selected hook script executed by this command
  810.      * (pre-commit, commit-msg, post-commit). If not set it defaults to
  811.      * {@code System.out}.
  812.      *
  813.      * @param hookName
  814.      *            name of the hook to set the output stream for
  815.      * @param hookStdOut
  816.      *            the output stream to use for the selected hook
  817.      * @return {@code this}
  818.      * @since 4.5
  819.      */
  820.     public CommitCommand setHookOutputStream(String hookName,
  821.             PrintStream hookStdOut) {
  822.         if (!(PreCommitHook.NAME.equals(hookName)
  823.                 || CommitMsgHook.NAME.equals(hookName)
  824.                 || PostCommitHook.NAME.equals(hookName))) {
  825.             throw new IllegalArgumentException(
  826.                     MessageFormat.format(JGitText.get().illegalHookName,
  827.                             hookName));
  828.         }
  829.         hookOutRedirect.put(hookName, hookStdOut);
  830.         return this;
  831.     }

  832.     /**
  833.      * Set the error stream for a selected hook script executed by this command
  834.      * (pre-commit, commit-msg, post-commit). If not set it defaults to
  835.      * {@code System.err}.
  836.      *
  837.      * @param hookName
  838.      *            name of the hook to set the output stream for
  839.      * @param hookStdErr
  840.      *            the output stream to use for the selected hook
  841.      * @return {@code this}
  842.      * @since 5.6
  843.      */
  844.     public CommitCommand setHookErrorStream(String hookName,
  845.             PrintStream hookStdErr) {
  846.         if (!(PreCommitHook.NAME.equals(hookName)
  847.                 || CommitMsgHook.NAME.equals(hookName)
  848.                 || PostCommitHook.NAME.equals(hookName))) {
  849.             throw new IllegalArgumentException(MessageFormat
  850.                     .format(JGitText.get().illegalHookName, hookName));
  851.         }
  852.         hookErrRedirect.put(hookName, hookStdErr);
  853.         return this;
  854.     }

  855.     /**
  856.      * Sets the signing key
  857.      * <p>
  858.      * Per spec of user.signingKey: this will be sent to the GPG program as is,
  859.      * i.e. can be anything supported by the GPG program.
  860.      * </p>
  861.      * <p>
  862.      * Note, if none was set or <code>null</code> is specified a default will be
  863.      * obtained from the configuration.
  864.      * </p>
  865.      *
  866.      * @param signingKey
  867.      *            signing key (maybe <code>null</code>)
  868.      * @return {@code this}
  869.      * @since 5.3
  870.      */
  871.     public CommitCommand setSigningKey(String signingKey) {
  872.         checkCallable();
  873.         this.signingKey = signingKey;
  874.         return this;
  875.     }

  876.     /**
  877.      * Sets whether the commit should be signed.
  878.      *
  879.      * @param sign
  880.      *            <code>true</code> to sign, <code>false</code> to not sign and
  881.      *            <code>null</code> for default behavior (read from
  882.      *            configuration)
  883.      * @return {@code this}
  884.      * @since 5.3
  885.      */
  886.     public CommitCommand setSign(Boolean sign) {
  887.         checkCallable();
  888.         this.signCommit = sign;
  889.         return this;
  890.     }

  891.     /**
  892.      * Sets the {@link GpgSigner} to use if the commit is to be signed.
  893.      *
  894.      * @param signer
  895.      *            to use; if {@code null}, the default signer will be used
  896.      * @return {@code this}
  897.      * @since 5.11
  898.      */
  899.     public CommitCommand setGpgSigner(GpgSigner signer) {
  900.         checkCallable();
  901.         this.gpgSigner = signer;
  902.         return this;
  903.     }

  904.     /**
  905.      * Sets an external {@link GpgConfig} to use. Whether it will be used is at
  906.      * the discretion of the {@link #setGpgSigner(GpgSigner)}.
  907.      *
  908.      * @param config
  909.      *            to set; if {@code null}, the config will be loaded from the
  910.      *            git config of the repository
  911.      * @return {@code this}
  912.      * @since 5.11
  913.      */
  914.     public CommitCommand setGpgConfig(GpgConfig config) {
  915.         checkCallable();
  916.         this.gpgConfig = config;
  917.         return this;
  918.     }

  919.     /**
  920.      * Sets a {@link CredentialsProvider}
  921.      *
  922.      * @param credentialsProvider
  923.      *            the provider to use when querying for credentials (eg., during
  924.      *            signing)
  925.      * @since 5.3
  926.      */
  927.     public void setCredentialsProvider(
  928.             CredentialsProvider credentialsProvider) {
  929.         this.credentialsProvider = credentialsProvider;
  930.     }
  931. }