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