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.

TagCommand.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. /*
  2. * Copyright (C) 2010, 2020 Chris Aniszczyk <caniszczyk@gmail.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.text.MessageFormat;
  13. import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException;
  14. import org.eclipse.jgit.api.errors.GitAPIException;
  15. import org.eclipse.jgit.api.errors.InvalidTagNameException;
  16. import org.eclipse.jgit.api.errors.JGitInternalException;
  17. import org.eclipse.jgit.api.errors.NoHeadException;
  18. import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
  19. import org.eclipse.jgit.api.errors.ServiceUnavailableException;
  20. import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException;
  21. import org.eclipse.jgit.internal.JGitText;
  22. import org.eclipse.jgit.lib.Constants;
  23. import org.eclipse.jgit.lib.GpgConfig;
  24. import org.eclipse.jgit.lib.GpgObjectSigner;
  25. import org.eclipse.jgit.lib.GpgSigner;
  26. import org.eclipse.jgit.lib.ObjectId;
  27. import org.eclipse.jgit.lib.ObjectInserter;
  28. import org.eclipse.jgit.lib.PersonIdent;
  29. import org.eclipse.jgit.lib.Ref;
  30. import org.eclipse.jgit.lib.RefUpdate;
  31. import org.eclipse.jgit.lib.RefUpdate.Result;
  32. import org.eclipse.jgit.lib.Repository;
  33. import org.eclipse.jgit.lib.RepositoryState;
  34. import org.eclipse.jgit.lib.TagBuilder;
  35. import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
  36. import org.eclipse.jgit.revwalk.RevObject;
  37. import org.eclipse.jgit.revwalk.RevWalk;
  38. import org.eclipse.jgit.transport.CredentialsProvider;
  39. /**
  40. * Create/update an annotated tag object or a simple unannotated tag
  41. * <p>
  42. * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance):
  43. * <p>
  44. * Create a new tag for the current commit:
  45. *
  46. * <pre>
  47. * git.tag().setName(&quot;v1.0&quot;).setMessage(&quot;First stable release&quot;).call();
  48. * </pre>
  49. * <p>
  50. *
  51. * <p>
  52. * Create a new unannotated tag for the current commit:
  53. *
  54. * <pre>
  55. * git.tag().setName(&quot;v1.0&quot;).setAnnotated(false).call();
  56. * </pre>
  57. * <p>
  58. *
  59. * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-tag.html"
  60. * >Git documentation about Tag</a>
  61. */
  62. public class TagCommand extends GitCommand<Ref> {
  63. private RevObject id;
  64. private String name;
  65. private String message;
  66. private PersonIdent tagger;
  67. private Boolean signed;
  68. private boolean forceUpdate;
  69. private Boolean annotated;
  70. private String signingKey;
  71. private GpgObjectSigner gpgSigner;
  72. private CredentialsProvider credentialsProvider;
  73. /**
  74. * <p>Constructor for TagCommand.</p>
  75. *
  76. * @param repo a {@link org.eclipse.jgit.lib.Repository} object.
  77. */
  78. protected TagCommand(Repository repo) {
  79. super(repo);
  80. this.credentialsProvider = CredentialsProvider.getDefault();
  81. }
  82. /**
  83. * {@inheritDoc}
  84. * <p>
  85. * Executes the {@code tag} command with all the options and parameters
  86. * collected by the setter methods of this class. Each instance of this
  87. * class should only be used for one invocation of the command (means: one
  88. * call to {@link #call()})
  89. *
  90. * @since 2.0
  91. */
  92. @Override
  93. public Ref call() throws GitAPIException, ConcurrentRefUpdateException,
  94. InvalidTagNameException, NoHeadException {
  95. checkCallable();
  96. RepositoryState state = repo.getRepositoryState();
  97. processOptions(state);
  98. try (RevWalk revWalk = new RevWalk(repo)) {
  99. // if no id is set, we should attempt to use HEAD
  100. if (id == null) {
  101. ObjectId objectId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$
  102. if (objectId == null)
  103. throw new NoHeadException(
  104. JGitText.get().tagOnRepoWithoutHEADCurrentlyNotSupported);
  105. id = revWalk.parseCommit(objectId);
  106. }
  107. if (!isAnnotated()) {
  108. return updateTagRef(id, revWalk, name,
  109. "SimpleTag[" + name + " : " + id //$NON-NLS-1$ //$NON-NLS-2$
  110. + "]"); //$NON-NLS-1$
  111. }
  112. // create the tag object
  113. TagBuilder newTag = new TagBuilder();
  114. newTag.setTag(name);
  115. newTag.setMessage(message);
  116. newTag.setTagger(tagger);
  117. newTag.setObjectId(id);
  118. if (gpgSigner != null) {
  119. gpgSigner.signObject(newTag, signingKey, tagger,
  120. credentialsProvider);
  121. }
  122. // write the tag object
  123. try (ObjectInserter inserter = repo.newObjectInserter()) {
  124. ObjectId tagId = inserter.insert(newTag);
  125. inserter.flush();
  126. String tag = newTag.getTag();
  127. return updateTagRef(tagId, revWalk, tag, newTag.toString());
  128. }
  129. } catch (IOException e) {
  130. throw new JGitInternalException(
  131. JGitText.get().exceptionCaughtDuringExecutionOfTagCommand,
  132. e);
  133. }
  134. }
  135. private Ref updateTagRef(ObjectId tagId, RevWalk revWalk,
  136. String tagName, String newTagToString) throws IOException,
  137. ConcurrentRefUpdateException, RefAlreadyExistsException {
  138. String refName = Constants.R_TAGS + tagName;
  139. RefUpdate tagRef = repo.updateRef(refName);
  140. tagRef.setNewObjectId(tagId);
  141. tagRef.setForceUpdate(forceUpdate);
  142. tagRef.setRefLogMessage("tagged " + name, false); //$NON-NLS-1$
  143. Result updateResult = tagRef.update(revWalk);
  144. switch (updateResult) {
  145. case NEW:
  146. case FORCED:
  147. return repo.exactRef(refName);
  148. case LOCK_FAILURE:
  149. throw new ConcurrentRefUpdateException(
  150. JGitText.get().couldNotLockHEAD, tagRef.getRef(),
  151. updateResult);
  152. case NO_CHANGE:
  153. if (forceUpdate) {
  154. return repo.exactRef(refName);
  155. }
  156. throw new RefAlreadyExistsException(MessageFormat
  157. .format(JGitText.get().tagAlreadyExists, newTagToString),
  158. updateResult);
  159. case REJECTED:
  160. throw new RefAlreadyExistsException(MessageFormat.format(
  161. JGitText.get().tagAlreadyExists, newTagToString),
  162. updateResult);
  163. default:
  164. throw new JGitInternalException(MessageFormat.format(
  165. JGitText.get().updatingRefFailed, refName, newTagToString,
  166. updateResult));
  167. }
  168. }
  169. /**
  170. * Sets default values for not explicitly specified options. Then validates
  171. * that all required data has been provided.
  172. *
  173. * @param state
  174. * the state of the repository we are working on
  175. *
  176. * @throws InvalidTagNameException
  177. * if the tag name is null or invalid
  178. * @throws ServiceUnavailableException
  179. * if the tag should be signed but no signer can be found
  180. * @throws UnsupportedSigningFormatException
  181. * if the tag should be signed but {@code gpg.format} is not
  182. * {@link GpgFormat#OPENPGP}
  183. */
  184. private void processOptions(RepositoryState state)
  185. throws InvalidTagNameException, ServiceUnavailableException,
  186. UnsupportedSigningFormatException {
  187. if (name == null
  188. || !Repository.isValidRefName(Constants.R_TAGS + name)) {
  189. throw new InvalidTagNameException(
  190. MessageFormat.format(JGitText.get().tagNameInvalid,
  191. name == null ? "<null>" : name)); //$NON-NLS-1$
  192. }
  193. if (!isAnnotated()) {
  194. if ((message != null && !message.isEmpty()) || tagger != null) {
  195. throw new JGitInternalException(JGitText
  196. .get().messageAndTaggerNotAllowedInUnannotatedTags);
  197. }
  198. } else {
  199. if (tagger == null) {
  200. tagger = new PersonIdent(repo);
  201. }
  202. // Figure out whether to sign.
  203. if (!(Boolean.FALSE.equals(signed) && signingKey == null)) {
  204. GpgConfig gpgConfig = new GpgConfig(repo.getConfig());
  205. boolean doSign = isSigned() || gpgConfig.isSignAllTags();
  206. if (!Boolean.TRUE.equals(annotated) && !doSign) {
  207. doSign = gpgConfig.isSignAnnotated();
  208. }
  209. if (doSign) {
  210. if (signingKey == null) {
  211. signingKey = gpgConfig.getSigningKey();
  212. }
  213. if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
  214. throw new UnsupportedSigningFormatException(
  215. JGitText.get().onlyOpenPgpSupportedForSigning);
  216. }
  217. GpgSigner signer = GpgSigner.getDefault();
  218. if (!(signer instanceof GpgObjectSigner)) {
  219. throw new ServiceUnavailableException(
  220. JGitText.get().signingServiceUnavailable);
  221. }
  222. gpgSigner = (GpgObjectSigner) signer;
  223. // The message of a signed tag must end in a newline because
  224. // the signature will be appended.
  225. if (message != null && !message.isEmpty()
  226. && !message.endsWith("\n")) { //$NON-NLS-1$
  227. message += '\n';
  228. }
  229. }
  230. }
  231. }
  232. }
  233. /**
  234. * Set the tag <code>name</code>.
  235. *
  236. * @param name
  237. * the tag name used for the {@code tag}
  238. * @return {@code this}
  239. */
  240. public TagCommand setName(String name) {
  241. checkCallable();
  242. this.name = name;
  243. return this;
  244. }
  245. /**
  246. * Get the tag <code>name</code>.
  247. *
  248. * @return the tag name used for the <code>tag</code>
  249. */
  250. public String getName() {
  251. return name;
  252. }
  253. /**
  254. * Get the tag <code>message</code>.
  255. *
  256. * @return the tag message used for the <code>tag</code>
  257. */
  258. public String getMessage() {
  259. return message;
  260. }
  261. /**
  262. * Set the tag <code>message</code>.
  263. *
  264. * @param message
  265. * the tag message used for the {@code tag}
  266. * @return {@code this}
  267. */
  268. public TagCommand setMessage(String message) {
  269. checkCallable();
  270. this.message = message;
  271. return this;
  272. }
  273. /**
  274. * Whether {@link #setSigned(boolean) setSigned(true)} has been called or
  275. * whether a {@link #setSigningKey(String) signing key ID} has been set;
  276. * i.e., whether -s or -u was specified explicitly.
  277. *
  278. * @return whether the tag is signed
  279. */
  280. public boolean isSigned() {
  281. return Boolean.TRUE.equals(signed) || signingKey != null;
  282. }
  283. /**
  284. * If set to true the Tag command creates a signed tag object. This
  285. * corresponds to the parameter -s (--sign or --no-sign) on the command
  286. * line.
  287. * <p>
  288. * If {@code true}, the tag will be a signed annotated tag.
  289. * </p>
  290. *
  291. * @param signed
  292. * whether to sign
  293. * @return {@code this}
  294. */
  295. public TagCommand setSigned(boolean signed) {
  296. checkCallable();
  297. this.signed = Boolean.valueOf(signed);
  298. return this;
  299. }
  300. /**
  301. * Sets the tagger of the tag. If the tagger is null, a PersonIdent will be
  302. * created from the info in the repository.
  303. *
  304. * @param tagger
  305. * a {@link org.eclipse.jgit.lib.PersonIdent} object.
  306. * @return {@code this}
  307. */
  308. public TagCommand setTagger(PersonIdent tagger) {
  309. checkCallable();
  310. this.tagger = tagger;
  311. return this;
  312. }
  313. /**
  314. * Get the <code>tagger</code> who created the tag.
  315. *
  316. * @return the tagger of the tag
  317. */
  318. public PersonIdent getTagger() {
  319. return tagger;
  320. }
  321. /**
  322. * Get the tag's object id
  323. *
  324. * @return the object id of the tag
  325. */
  326. public RevObject getObjectId() {
  327. return id;
  328. }
  329. /**
  330. * Sets the object id of the tag. If the object id is {@code null}, the
  331. * commit pointed to from HEAD will be used.
  332. *
  333. * @param id
  334. * a {@link org.eclipse.jgit.revwalk.RevObject} object.
  335. * @return {@code this}
  336. */
  337. public TagCommand setObjectId(RevObject id) {
  338. checkCallable();
  339. this.id = id;
  340. return this;
  341. }
  342. /**
  343. * Whether this is a forced update
  344. *
  345. * @return is this a force update
  346. */
  347. public boolean isForceUpdate() {
  348. return forceUpdate;
  349. }
  350. /**
  351. * If set to true the Tag command may replace an existing tag object. This
  352. * corresponds to the parameter -f on the command line.
  353. *
  354. * @param forceUpdate
  355. * whether this is a forced update
  356. * @return {@code this}
  357. */
  358. public TagCommand setForceUpdate(boolean forceUpdate) {
  359. checkCallable();
  360. this.forceUpdate = forceUpdate;
  361. return this;
  362. }
  363. /**
  364. * Configure this tag to be created as an annotated tag
  365. *
  366. * @param annotated
  367. * whether this shall be an annotated tag
  368. * @return {@code this}
  369. * @since 3.0
  370. */
  371. public TagCommand setAnnotated(boolean annotated) {
  372. checkCallable();
  373. this.annotated = Boolean.valueOf(annotated);
  374. return this;
  375. }
  376. /**
  377. * Whether this will create an annotated tag.
  378. *
  379. * @return true if this command will create an annotated tag (default is
  380. * true)
  381. * @since 3.0
  382. */
  383. public boolean isAnnotated() {
  384. boolean setExplicitly = Boolean.TRUE.equals(annotated) || isSigned();
  385. if (setExplicitly) {
  386. return true;
  387. }
  388. // Annotated at default (not set explicitly)
  389. return annotated == null;
  390. }
  391. /**
  392. * Sets the signing key.
  393. * <p>
  394. * Per spec of {@code user.signingKey}: this will be sent to the GPG program
  395. * as is, i.e. can be anything supported by the GPG program.
  396. * </p>
  397. * <p>
  398. * Note, if none was set or {@code null} is specified a default will be
  399. * obtained from the configuration.
  400. * </p>
  401. * <p>
  402. * If set to a non-{@code null} value, the tag will be a signed annotated
  403. * tag.
  404. * </p>
  405. *
  406. * @param signingKey
  407. * signing key; {@code null} allowed
  408. * @return {@code this}
  409. * @since 5.11
  410. */
  411. public TagCommand setSigningKey(String signingKey) {
  412. checkCallable();
  413. this.signingKey = signingKey;
  414. return this;
  415. }
  416. /**
  417. * Retrieves the signing key ID.
  418. *
  419. * @return the key ID set, or {@code null} if none is set
  420. * @since 5.11
  421. */
  422. public String getSigningKey() {
  423. return signingKey;
  424. }
  425. /**
  426. * Sets a {@link CredentialsProvider}
  427. *
  428. * @param credentialsProvider
  429. * the provider to use when querying for credentials (eg., during
  430. * signing)
  431. * @return {@code this}
  432. * @since 5.11
  433. */
  434. public TagCommand setCredentialsProvider(
  435. CredentialsProvider credentialsProvider) {
  436. checkCallable();
  437. this.credentialsProvider = credentialsProvider;
  438. return this;
  439. }
  440. }