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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483
  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. case REJECTED:
  159. throw new RefAlreadyExistsException(MessageFormat.format(
  160. JGitText.get().tagAlreadyExists, newTagToString));
  161. default:
  162. throw new JGitInternalException(MessageFormat.format(
  163. JGitText.get().updatingRefFailed, refName, newTagToString,
  164. updateResult));
  165. }
  166. }
  167. /**
  168. * Sets default values for not explicitly specified options. Then validates
  169. * that all required data has been provided.
  170. *
  171. * @param state
  172. * the state of the repository we are working on
  173. *
  174. * @throws InvalidTagNameException
  175. * if the tag name is null or invalid
  176. * @throws ServiceUnavailableException
  177. * if the tag should be signed but no signer can be found
  178. * @throws UnsupportedSigningFormatException
  179. * if the tag should be signed but {@code gpg.format} is not
  180. * {@link GpgFormat#OPENPGP}
  181. */
  182. private void processOptions(RepositoryState state)
  183. throws InvalidTagNameException, ServiceUnavailableException,
  184. UnsupportedSigningFormatException {
  185. if (name == null
  186. || !Repository.isValidRefName(Constants.R_TAGS + name)) {
  187. throw new InvalidTagNameException(
  188. MessageFormat.format(JGitText.get().tagNameInvalid,
  189. name == null ? "<null>" : name)); //$NON-NLS-1$
  190. }
  191. if (!isAnnotated()) {
  192. if ((message != null && !message.isEmpty()) || tagger != null) {
  193. throw new JGitInternalException(JGitText
  194. .get().messageAndTaggerNotAllowedInUnannotatedTags);
  195. }
  196. } else {
  197. if (tagger == null) {
  198. tagger = new PersonIdent(repo);
  199. }
  200. // Figure out whether to sign.
  201. if (!(Boolean.FALSE.equals(signed) && signingKey == null)) {
  202. GpgConfig gpgConfig = new GpgConfig(repo.getConfig());
  203. boolean doSign = isSigned() || gpgConfig.isSignAllTags();
  204. if (!Boolean.TRUE.equals(annotated) && !doSign) {
  205. doSign = gpgConfig.isSignAnnotated();
  206. }
  207. if (doSign) {
  208. if (signingKey == null) {
  209. signingKey = gpgConfig.getSigningKey();
  210. }
  211. if (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
  212. throw new UnsupportedSigningFormatException(
  213. JGitText.get().onlyOpenPgpSupportedForSigning);
  214. }
  215. GpgSigner signer = GpgSigner.getDefault();
  216. if (!(signer instanceof GpgObjectSigner)) {
  217. throw new ServiceUnavailableException(
  218. JGitText.get().signingServiceUnavailable);
  219. }
  220. gpgSigner = (GpgObjectSigner) signer;
  221. // The message of a signed tag must end in a newline because
  222. // the signature will be appended.
  223. if (message != null && !message.isEmpty()
  224. && !message.endsWith("\n")) { //$NON-NLS-1$
  225. message += '\n';
  226. }
  227. }
  228. }
  229. }
  230. }
  231. /**
  232. * Set the tag <code>name</code>.
  233. *
  234. * @param name
  235. * the tag name used for the {@code tag}
  236. * @return {@code this}
  237. */
  238. public TagCommand setName(String name) {
  239. checkCallable();
  240. this.name = name;
  241. return this;
  242. }
  243. /**
  244. * Get the tag <code>name</code>.
  245. *
  246. * @return the tag name used for the <code>tag</code>
  247. */
  248. public String getName() {
  249. return name;
  250. }
  251. /**
  252. * Get the tag <code>message</code>.
  253. *
  254. * @return the tag message used for the <code>tag</code>
  255. */
  256. public String getMessage() {
  257. return message;
  258. }
  259. /**
  260. * Set the tag <code>message</code>.
  261. *
  262. * @param message
  263. * the tag message used for the {@code tag}
  264. * @return {@code this}
  265. */
  266. public TagCommand setMessage(String message) {
  267. checkCallable();
  268. this.message = message;
  269. return this;
  270. }
  271. /**
  272. * Whether {@link #setSigned(boolean) setSigned(true)} has been called or
  273. * whether a {@link #setSigningKey(String) signing key ID} has been set;
  274. * i.e., whether -s or -u was specified explicitly.
  275. *
  276. * @return whether the tag is signed
  277. */
  278. public boolean isSigned() {
  279. return Boolean.TRUE.equals(signed) || signingKey != null;
  280. }
  281. /**
  282. * If set to true the Tag command creates a signed tag object. This
  283. * corresponds to the parameter -s (--sign or --no-sign) on the command
  284. * line.
  285. * <p>
  286. * If {@code true}, the tag will be a signed annotated tag.
  287. * </p>
  288. *
  289. * @param signed
  290. * whether to sign
  291. * @return {@code this}
  292. */
  293. public TagCommand setSigned(boolean signed) {
  294. checkCallable();
  295. this.signed = Boolean.valueOf(signed);
  296. return this;
  297. }
  298. /**
  299. * Sets the tagger of the tag. If the tagger is null, a PersonIdent will be
  300. * created from the info in the repository.
  301. *
  302. * @param tagger
  303. * a {@link org.eclipse.jgit.lib.PersonIdent} object.
  304. * @return {@code this}
  305. */
  306. public TagCommand setTagger(PersonIdent tagger) {
  307. checkCallable();
  308. this.tagger = tagger;
  309. return this;
  310. }
  311. /**
  312. * Get the <code>tagger</code> who created the tag.
  313. *
  314. * @return the tagger of the tag
  315. */
  316. public PersonIdent getTagger() {
  317. return tagger;
  318. }
  319. /**
  320. * Get the tag's object id
  321. *
  322. * @return the object id of the tag
  323. */
  324. public RevObject getObjectId() {
  325. return id;
  326. }
  327. /**
  328. * Sets the object id of the tag. If the object id is {@code null}, the
  329. * commit pointed to from HEAD will be used.
  330. *
  331. * @param id
  332. * a {@link org.eclipse.jgit.revwalk.RevObject} object.
  333. * @return {@code this}
  334. */
  335. public TagCommand setObjectId(RevObject id) {
  336. checkCallable();
  337. this.id = id;
  338. return this;
  339. }
  340. /**
  341. * Whether this is a forced update
  342. *
  343. * @return is this a force update
  344. */
  345. public boolean isForceUpdate() {
  346. return forceUpdate;
  347. }
  348. /**
  349. * If set to true the Tag command may replace an existing tag object. This
  350. * corresponds to the parameter -f on the command line.
  351. *
  352. * @param forceUpdate
  353. * whether this is a forced update
  354. * @return {@code this}
  355. */
  356. public TagCommand setForceUpdate(boolean forceUpdate) {
  357. checkCallable();
  358. this.forceUpdate = forceUpdate;
  359. return this;
  360. }
  361. /**
  362. * Configure this tag to be created as an annotated tag
  363. *
  364. * @param annotated
  365. * whether this shall be an annotated tag
  366. * @return {@code this}
  367. * @since 3.0
  368. */
  369. public TagCommand setAnnotated(boolean annotated) {
  370. checkCallable();
  371. this.annotated = Boolean.valueOf(annotated);
  372. return this;
  373. }
  374. /**
  375. * Whether this will create an annotated tag.
  376. *
  377. * @return true if this command will create an annotated tag (default is
  378. * true)
  379. * @since 3.0
  380. */
  381. public boolean isAnnotated() {
  382. boolean setExplicitly = Boolean.TRUE.equals(annotated) || isSigned();
  383. if (setExplicitly) {
  384. return true;
  385. }
  386. // Annotated at default (not set explicitly)
  387. return annotated == null;
  388. }
  389. /**
  390. * Sets the signing key.
  391. * <p>
  392. * Per spec of {@code user.signingKey}: this will be sent to the GPG program
  393. * as is, i.e. can be anything supported by the GPG program.
  394. * </p>
  395. * <p>
  396. * Note, if none was set or {@code null} is specified a default will be
  397. * obtained from the configuration.
  398. * </p>
  399. * <p>
  400. * If set to a non-{@code null} value, the tag will be a signed annotated
  401. * tag.
  402. * </p>
  403. *
  404. * @param signingKey
  405. * signing key; {@code null} allowed
  406. * @return {@code this}
  407. * @since 5.11
  408. */
  409. public TagCommand setSigningKey(String signingKey) {
  410. checkCallable();
  411. this.signingKey = signingKey;
  412. return this;
  413. }
  414. /**
  415. * Retrieves the signing key ID.
  416. *
  417. * @return the key ID set, or {@code null} if none is set
  418. * @since 5.11
  419. */
  420. public String getSigningKey() {
  421. return signingKey;
  422. }
  423. /**
  424. * Sets a {@link CredentialsProvider}
  425. *
  426. * @param credentialsProvider
  427. * the provider to use when querying for credentials (eg., during
  428. * signing)
  429. * @return {@code this}
  430. * @since 5.11
  431. */
  432. public TagCommand setCredentialsProvider(
  433. CredentialsProvider credentialsProvider) {
  434. checkCallable();
  435. this.credentialsProvider = credentialsProvider;
  436. return this;
  437. }
  438. }