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 14KB

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