You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

CommitCommand.java 33KB

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