| /* |
| * Copyright (C) 2010, 2020 Chris Aniszczyk <caniszczyk@gmail.com> and others |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0 which is available at |
| * https://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| package org.eclipse.jgit.api; |
| |
| import java.io.IOException; |
| import java.text.MessageFormat; |
| |
| import org.eclipse.jgit.api.errors.ConcurrentRefUpdateException; |
| import org.eclipse.jgit.api.errors.GitAPIException; |
| import org.eclipse.jgit.api.errors.InvalidTagNameException; |
| import org.eclipse.jgit.api.errors.JGitInternalException; |
| import org.eclipse.jgit.api.errors.NoHeadException; |
| import org.eclipse.jgit.api.errors.RefAlreadyExistsException; |
| import org.eclipse.jgit.api.errors.ServiceUnavailableException; |
| import org.eclipse.jgit.api.errors.UnsupportedSigningFormatException; |
| import org.eclipse.jgit.internal.JGitText; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.GpgConfig; |
| import org.eclipse.jgit.lib.GpgConfig.GpgFormat; |
| import org.eclipse.jgit.lib.GpgObjectSigner; |
| import org.eclipse.jgit.lib.GpgSigner; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectInserter; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.RefUpdate.Result; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.lib.RepositoryState; |
| import org.eclipse.jgit.lib.TagBuilder; |
| import org.eclipse.jgit.revwalk.RevObject; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.eclipse.jgit.transport.CredentialsProvider; |
| |
| /** |
| * Create/update an annotated tag object or a simple unannotated tag |
| * <p> |
| * Examples (<code>git</code> is a {@link org.eclipse.jgit.api.Git} instance): |
| * <p> |
| * Create a new tag for the current commit: |
| * |
| * <pre> |
| * git.tag().setName("v1.0").setMessage("First stable release").call(); |
| * </pre> |
| * <p> |
| * |
| * <p> |
| * Create a new unannotated tag for the current commit: |
| * |
| * <pre> |
| * git.tag().setName("v1.0").setAnnotated(false).call(); |
| * </pre> |
| * <p> |
| * |
| * @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-tag.html" |
| * >Git documentation about Tag</a> |
| */ |
| public class TagCommand extends GitCommand<Ref> { |
| |
| private RevObject id; |
| |
| private String name; |
| |
| private String message; |
| |
| private PersonIdent tagger; |
| |
| private Boolean signed; |
| |
| private boolean forceUpdate; |
| |
| private Boolean annotated; |
| |
| private String signingKey; |
| |
| private GpgConfig gpgConfig; |
| |
| private GpgObjectSigner gpgSigner; |
| |
| private CredentialsProvider credentialsProvider; |
| |
| /** |
| * <p>Constructor for TagCommand.</p> |
| * |
| * @param repo a {@link org.eclipse.jgit.lib.Repository} object. |
| */ |
| protected TagCommand(Repository repo) { |
| super(repo); |
| this.credentialsProvider = CredentialsProvider.getDefault(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * <p> |
| * Executes the {@code tag} command with all the options and parameters |
| * collected by the setter methods of this class. Each instance of this |
| * class should only be used for one invocation of the command (means: one |
| * call to {@link #call()}) |
| * |
| * @since 2.0 |
| */ |
| @Override |
| public Ref call() throws GitAPIException, ConcurrentRefUpdateException, |
| InvalidTagNameException, NoHeadException { |
| checkCallable(); |
| |
| RepositoryState state = repo.getRepositoryState(); |
| processOptions(state); |
| |
| try (RevWalk revWalk = new RevWalk(repo)) { |
| // if no id is set, we should attempt to use HEAD |
| if (id == null) { |
| ObjectId objectId = repo.resolve(Constants.HEAD + "^{commit}"); //$NON-NLS-1$ |
| if (objectId == null) |
| throw new NoHeadException( |
| JGitText.get().tagOnRepoWithoutHEADCurrentlyNotSupported); |
| |
| id = revWalk.parseCommit(objectId); |
| } |
| |
| if (!isAnnotated()) { |
| return updateTagRef(id, revWalk, name, |
| "SimpleTag[" + name + " : " + id //$NON-NLS-1$ //$NON-NLS-2$ |
| + "]"); //$NON-NLS-1$ |
| } |
| |
| // create the tag object |
| TagBuilder newTag = new TagBuilder(); |
| newTag.setTag(name); |
| newTag.setMessage(message); |
| newTag.setTagger(tagger); |
| newTag.setObjectId(id); |
| |
| if (gpgSigner != null) { |
| gpgSigner.signObject(newTag, signingKey, tagger, |
| credentialsProvider, gpgConfig); |
| } |
| |
| // write the tag object |
| try (ObjectInserter inserter = repo.newObjectInserter()) { |
| ObjectId tagId = inserter.insert(newTag); |
| inserter.flush(); |
| |
| String tag = newTag.getTag(); |
| return updateTagRef(tagId, revWalk, tag, newTag.toString()); |
| |
| } |
| |
| } catch (IOException e) { |
| throw new JGitInternalException( |
| JGitText.get().exceptionCaughtDuringExecutionOfTagCommand, |
| e); |
| } |
| } |
| |
| private Ref updateTagRef(ObjectId tagId, RevWalk revWalk, |
| String tagName, String newTagToString) throws IOException, |
| ConcurrentRefUpdateException, RefAlreadyExistsException { |
| String refName = Constants.R_TAGS + tagName; |
| RefUpdate tagRef = repo.updateRef(refName); |
| tagRef.setNewObjectId(tagId); |
| tagRef.setForceUpdate(forceUpdate); |
| tagRef.setRefLogMessage("tagged " + name, false); //$NON-NLS-1$ |
| Result updateResult = tagRef.update(revWalk); |
| switch (updateResult) { |
| case NEW: |
| case FORCED: |
| return repo.exactRef(refName); |
| case LOCK_FAILURE: |
| throw new ConcurrentRefUpdateException( |
| JGitText.get().couldNotLockHEAD, tagRef.getRef(), |
| updateResult); |
| case NO_CHANGE: |
| if (forceUpdate) { |
| return repo.exactRef(refName); |
| } |
| throw new RefAlreadyExistsException(MessageFormat |
| .format(JGitText.get().tagAlreadyExists, newTagToString), |
| updateResult); |
| case REJECTED: |
| throw new RefAlreadyExistsException(MessageFormat.format( |
| JGitText.get().tagAlreadyExists, newTagToString), |
| updateResult); |
| default: |
| throw new JGitInternalException(MessageFormat.format( |
| JGitText.get().updatingRefFailed, refName, newTagToString, |
| updateResult)); |
| } |
| } |
| |
| /** |
| * Sets default values for not explicitly specified options. Then validates |
| * that all required data has been provided. |
| * |
| * @param state |
| * the state of the repository we are working on |
| * |
| * @throws InvalidTagNameException |
| * if the tag name is null or invalid |
| * @throws ServiceUnavailableException |
| * if the tag should be signed but no signer can be found |
| * @throws UnsupportedSigningFormatException |
| * if the tag should be signed but {@code gpg.format} is not |
| * {@link GpgFormat#OPENPGP} |
| */ |
| private void processOptions(RepositoryState state) |
| throws InvalidTagNameException, ServiceUnavailableException, |
| UnsupportedSigningFormatException { |
| if (name == null |
| || !Repository.isValidRefName(Constants.R_TAGS + name)) { |
| throw new InvalidTagNameException( |
| MessageFormat.format(JGitText.get().tagNameInvalid, |
| name == null ? "<null>" : name)); //$NON-NLS-1$ |
| } |
| if (!isAnnotated()) { |
| if ((message != null && !message.isEmpty()) || tagger != null) { |
| throw new JGitInternalException(JGitText |
| .get().messageAndTaggerNotAllowedInUnannotatedTags); |
| } |
| } else { |
| if (tagger == null) { |
| tagger = new PersonIdent(repo); |
| } |
| // Figure out whether to sign. |
| if (!(Boolean.FALSE.equals(signed) && signingKey == null)) { |
| if (gpgConfig == null) { |
| gpgConfig = new GpgConfig(repo.getConfig()); |
| } |
| boolean doSign = isSigned() || gpgConfig.isSignAllTags(); |
| if (!Boolean.TRUE.equals(annotated) && !doSign) { |
| doSign = gpgConfig.isSignAnnotated(); |
| } |
| if (doSign) { |
| if (signingKey == null) { |
| signingKey = gpgConfig.getSigningKey(); |
| } |
| if (gpgSigner == null) { |
| GpgSigner signer = GpgSigner.getDefault(); |
| if (!(signer instanceof GpgObjectSigner)) { |
| throw new ServiceUnavailableException( |
| JGitText.get().signingServiceUnavailable); |
| } |
| gpgSigner = (GpgObjectSigner) signer; |
| } |
| // The message of a signed tag must end in a newline because |
| // the signature will be appended. |
| if (message != null && !message.isEmpty() |
| && !message.endsWith("\n")) { //$NON-NLS-1$ |
| message += '\n'; |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Set the tag <code>name</code>. |
| * |
| * @param name |
| * the tag name used for the {@code tag} |
| * @return {@code this} |
| */ |
| public TagCommand setName(String name) { |
| checkCallable(); |
| this.name = name; |
| return this; |
| } |
| |
| /** |
| * Get the tag <code>name</code>. |
| * |
| * @return the tag name used for the <code>tag</code> |
| */ |
| public String getName() { |
| return name; |
| } |
| |
| /** |
| * Get the tag <code>message</code>. |
| * |
| * @return the tag message used for the <code>tag</code> |
| */ |
| public String getMessage() { |
| return message; |
| } |
| |
| /** |
| * Set the tag <code>message</code>. |
| * |
| * @param message |
| * the tag message used for the {@code tag} |
| * @return {@code this} |
| */ |
| public TagCommand setMessage(String message) { |
| checkCallable(); |
| this.message = message; |
| return this; |
| } |
| |
| /** |
| * Whether {@link #setSigned(boolean) setSigned(true)} has been called or |
| * whether a {@link #setSigningKey(String) signing key ID} has been set; |
| * i.e., whether -s or -u was specified explicitly. |
| * |
| * @return whether the tag is signed |
| */ |
| public boolean isSigned() { |
| return Boolean.TRUE.equals(signed) || signingKey != null; |
| } |
| |
| /** |
| * If set to true the Tag command creates a signed tag object. This |
| * corresponds to the parameter -s (--sign or --no-sign) on the command |
| * line. |
| * <p> |
| * If {@code true}, the tag will be a signed annotated tag. |
| * </p> |
| * |
| * @param signed |
| * whether to sign |
| * @return {@code this} |
| */ |
| public TagCommand setSigned(boolean signed) { |
| checkCallable(); |
| this.signed = Boolean.valueOf(signed); |
| return this; |
| } |
| |
| /** |
| * Sets the {@link GpgSigner} to use if the commit is to be signed. |
| * |
| * @param signer |
| * to use; if {@code null}, the default signer will be used |
| * @return {@code this} |
| * @since 5.11 |
| */ |
| public TagCommand setGpgSigner(GpgObjectSigner signer) { |
| checkCallable(); |
| this.gpgSigner = signer; |
| return this; |
| } |
| |
| /** |
| * Sets an external {@link GpgConfig} to use. Whether it will be used is at |
| * the discretion of the {@link #setGpgSigner(GpgObjectSigner)}. |
| * |
| * @param config |
| * to set; if {@code null}, the config will be loaded from the |
| * git config of the repository |
| * @return {@code this} |
| * @since 5.11 |
| */ |
| public TagCommand setGpgConfig(GpgConfig config) { |
| checkCallable(); |
| this.gpgConfig = config; |
| return this; |
| } |
| |
| /** |
| * Sets the tagger of the tag. If the tagger is null, a PersonIdent will be |
| * created from the info in the repository. |
| * |
| * @param tagger |
| * a {@link org.eclipse.jgit.lib.PersonIdent} object. |
| * @return {@code this} |
| */ |
| public TagCommand setTagger(PersonIdent tagger) { |
| checkCallable(); |
| this.tagger = tagger; |
| return this; |
| } |
| |
| /** |
| * Get the <code>tagger</code> who created the tag. |
| * |
| * @return the tagger of the tag |
| */ |
| public PersonIdent getTagger() { |
| return tagger; |
| } |
| |
| /** |
| * Get the tag's object id |
| * |
| * @return the object id of the tag |
| */ |
| public RevObject getObjectId() { |
| return id; |
| } |
| |
| /** |
| * Sets the object id of the tag. If the object id is {@code null}, the |
| * commit pointed to from HEAD will be used. |
| * |
| * @param id |
| * a {@link org.eclipse.jgit.revwalk.RevObject} object. |
| * @return {@code this} |
| */ |
| public TagCommand setObjectId(RevObject id) { |
| checkCallable(); |
| this.id = id; |
| return this; |
| } |
| |
| /** |
| * Whether this is a forced update |
| * |
| * @return is this a force update |
| */ |
| public boolean isForceUpdate() { |
| return forceUpdate; |
| } |
| |
| /** |
| * If set to true the Tag command may replace an existing tag object. This |
| * corresponds to the parameter -f on the command line. |
| * |
| * @param forceUpdate |
| * whether this is a forced update |
| * @return {@code this} |
| */ |
| public TagCommand setForceUpdate(boolean forceUpdate) { |
| checkCallable(); |
| this.forceUpdate = forceUpdate; |
| return this; |
| } |
| |
| /** |
| * Configure this tag to be created as an annotated tag |
| * |
| * @param annotated |
| * whether this shall be an annotated tag |
| * @return {@code this} |
| * @since 3.0 |
| */ |
| public TagCommand setAnnotated(boolean annotated) { |
| checkCallable(); |
| this.annotated = Boolean.valueOf(annotated); |
| return this; |
| } |
| |
| /** |
| * Whether this will create an annotated tag. |
| * |
| * @return true if this command will create an annotated tag (default is |
| * true) |
| * @since 3.0 |
| */ |
| public boolean isAnnotated() { |
| boolean setExplicitly = Boolean.TRUE.equals(annotated) || isSigned(); |
| if (setExplicitly) { |
| return true; |
| } |
| // Annotated at default (not set explicitly) |
| return annotated == null; |
| } |
| |
| /** |
| * Sets the signing key. |
| * <p> |
| * Per spec of {@code user.signingKey}: this will be sent to the GPG program |
| * as is, i.e. can be anything supported by the GPG program. |
| * </p> |
| * <p> |
| * Note, if none was set or {@code null} is specified a default will be |
| * obtained from the configuration. |
| * </p> |
| * <p> |
| * If set to a non-{@code null} value, the tag will be a signed annotated |
| * tag. |
| * </p> |
| * |
| * @param signingKey |
| * signing key; {@code null} allowed |
| * @return {@code this} |
| * @since 5.11 |
| */ |
| public TagCommand setSigningKey(String signingKey) { |
| checkCallable(); |
| this.signingKey = signingKey; |
| return this; |
| } |
| |
| /** |
| * Retrieves the signing key ID. |
| * |
| * @return the key ID set, or {@code null} if none is set |
| * @since 5.11 |
| */ |
| public String getSigningKey() { |
| return signingKey; |
| } |
| |
| /** |
| * Sets a {@link CredentialsProvider} |
| * |
| * @param credentialsProvider |
| * the provider to use when querying for credentials (eg., during |
| * signing) |
| * @return {@code this} |
| * @since 5.11 |
| */ |
| public TagCommand setCredentialsProvider( |
| CredentialsProvider credentialsProvider) { |
| checkCallable(); |
| this.credentialsProvider = credentialsProvider; |
| return this; |
| } |
| |
| } |