* Copyright (C) 2008, Charles O'Farrell <charleso@charleso.org>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg.lists@dewire.com>
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> 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
@Option(name = "-d", usage = "usage_tagDelete")
private boolean delete;
+ @Option(name = "--annotate", aliases = {
+ "-a" }, usage = "usage_tagAnnotated")
+ private boolean annotated;
+
@Option(name = "-m", metaVar = "metaVar_message", usage = "usage_tagMessage")
- private String message = ""; //$NON-NLS-1$
+ private String message;
+
+ @Option(name = "--sign", aliases = { "-s" }, forbids = {
+ "--no-sign" }, usage = "usage_tagSign")
+ private boolean sign;
+
+ @Option(name = "--no-sign", usage = "usage_tagNoSign", forbids = {
+ "--sign" })
+ private boolean noSign;
+
+ @Option(name = "--local-user", aliases = {
+ "-u" }, metaVar = "metaVar_tagLocalUser", usage = "usage_tagLocalUser")
+ private String gpgKeyId;
@Argument(index = 0, metaVar = "metaVar_name")
private String tagName;
command.setObjectId(walk.parseAny(object));
}
}
+ if (noSign) {
+ command.setSigned(false);
+ } else if (sign) {
+ command.setSigned(true);
+ }
+ if (annotated) {
+ command.setAnnotated(true);
+ } else if (message == null && !sign && gpgKeyId == null) {
+ // None of -a, -m, -s, -u given
+ command.setAnnotated(false);
+ }
+ command.setSigningKey(gpgKeyId);
try {
command.call();
} catch (RefAlreadyExistsException e) {
/*
- * Copyright (C) 2010, 2013 Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * 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
import static org.eclipse.jgit.lib.Constants.R_TAGS;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
public class TagCommandTest extends RepositoryTestCase {
+ @Test
+ public void testTagKind() {
+ try (Git git = new Git(db)) {
+ assertTrue(git.tag().isAnnotated());
+ assertTrue(git.tag().setSigned(true).isAnnotated());
+ assertTrue(git.tag().setSigned(false).isAnnotated());
+ assertTrue(git.tag().setSigningKey(null).isAnnotated());
+ assertTrue(git.tag().setSigningKey("something").isAnnotated());
+ assertTrue(git.tag().setSigned(false).setSigningKey(null)
+ .isAnnotated());
+ assertTrue(git.tag().setSigned(false).setSigningKey("something")
+ .isAnnotated());
+ assertTrue(git.tag().setSigned(true).setSigningKey(null)
+ .isAnnotated());
+ assertTrue(git.tag().setSigned(true).setSigningKey("something")
+ .isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).isAnnotated());
+ assertTrue(
+ git.tag().setAnnotated(true).setSigned(true).isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).setSigned(false)
+ .isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).setSigningKey(null)
+ .isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).setSigningKey("something")
+ .isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).setSigned(false)
+ .setSigningKey(null).isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).setSigned(false)
+ .setSigningKey("something").isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).setSigned(true)
+ .setSigningKey(null).isAnnotated());
+ assertTrue(git.tag().setAnnotated(true).setSigned(true)
+ .setSigningKey("something").isAnnotated());
+ assertFalse(git.tag().setAnnotated(false).isAnnotated());
+ assertTrue(git.tag().setAnnotated(false).setSigned(true)
+ .isAnnotated());
+ assertFalse(git.tag().setAnnotated(false).setSigned(false)
+ .isAnnotated());
+ assertFalse(git.tag().setAnnotated(false).setSigningKey(null)
+ .isAnnotated());
+ assertTrue(git.tag().setAnnotated(false).setSigningKey("something")
+ .isAnnotated());
+ assertFalse(git.tag().setAnnotated(false).setSigned(false)
+ .setSigningKey(null).isAnnotated());
+ assertTrue(git.tag().setAnnotated(false).setSigned(false)
+ .setSigningKey("something").isAnnotated());
+ assertTrue(git.tag().setAnnotated(false).setSigned(true)
+ .setSigningKey(null).isAnnotated());
+ assertTrue(git.tag().setAnnotated(false).setSigned(true)
+ .setSigningKey("something").isAnnotated());
+ }
+ }
+
@Test
public void testTaggingOnHead() throws GitAPIException, IOException {
try (Git git = new Git(db);
}
}
- @Test
- public void testFailureOnSignedTags() throws GitAPIException {
- try (Git git = new Git(db)) {
- git.commit().setMessage("initial commit").call();
- try {
- git.tag().setSigned(true).setName("tag").call();
- fail("We should have failed with an UnsupportedOperationException due to signed tag");
- } catch (UnsupportedOperationException e) {
- // should hit here
- }
- }
- }
-
private List<Ref> getTags() throws Exception {
return db.getRefDatabase().getRefsByPrefix(R_TAGS);
}
/*
- * Copyright (C) 2010, 2013 Chris Aniszczyk <caniszczyk@gmail.com> and others
+ * 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
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.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.Repository;
import org.eclipse.jgit.lib.RepositoryState;
import org.eclipse.jgit.lib.TagBuilder;
+import org.eclipse.jgit.lib.GpgConfig.GpgFormat;
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
* >Git documentation about Tag</a>
*/
public class TagCommand extends GitCommand<Ref> {
+
private RevObject id;
private String name;
private PersonIdent tagger;
- private boolean signed;
+ private Boolean signed;
private boolean forceUpdate;
- private boolean annotated = true;
+ private Boolean annotated;
+
+ private String signingKey;
+
+ private GpgObjectSigner gpgSigner;
+
+ private CredentialsProvider credentialsProvider;
/**
* <p>Constructor for TagCommand.</p>
*/
protected TagCommand(Repository repo) {
super(repo);
+ this.credentialsProvider = CredentialsProvider.getDefault();
}
/**
id = revWalk.parseCommit(objectId);
}
- if (!annotated) {
- if (message != null || tagger != null)
- throw new JGitInternalException(
- JGitText.get().messageAndTaggerNotAllowedInUnannotatedTags);
+ if (!isAnnotated()) {
return updateTagRef(id, revWalk, name,
"SimpleTag[" + name + " : " + id //$NON-NLS-1$ //$NON-NLS-2$
+ "]"); //$NON-NLS-1$
newTag.setTagger(tagger);
newTag.setObjectId(id);
+ if (gpgSigner != null) {
+ gpgSigner.signObject(newTag, signingKey, tagger,
+ credentialsProvider);
+ }
+
// write the tag object
try (ObjectInserter inserter = repo.newObjectInserter()) {
ObjectId tagId = inserter.insert(newTag);
*
* @throws InvalidTagNameException
* if the tag name is null or invalid
- * @throws UnsupportedOperationException
- * if the tag is signed (not supported yet)
+ * @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 {
- if (tagger == null && annotated)
- tagger = new PersonIdent(repo);
- if (name == null || !Repository.isValidRefName(Constants.R_TAGS + name))
+ 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 (signed)
- throw new UnsupportedOperationException(
- JGitText.get().signingNotSupportedOnTag);
+ }
+ 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)) {
+ GpgConfig 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 (gpgConfig.getKeyFormat() != GpgFormat.OPENPGP) {
+ throw new UnsupportedSigningFormatException(
+ JGitText.get().onlyOpenPgpSupportedForSigning);
+ }
+ 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';
+ }
+ }
+ }
+ }
}
/**
}
/**
- * Whether this tag is signed
+ * 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 signed;
+ 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 on the command line.
+ * 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
- * a boolean.
+ * whether to sign
* @return {@code this}
*/
public TagCommand setSigned(boolean signed) {
- this.signed = signed;
+ checkCallable();
+ this.signed = Boolean.valueOf(signed);
return this;
}
* @return {@code this}
*/
public TagCommand setTagger(PersonIdent tagger) {
+ checkCallable();
this.tagger = tagger;
return this;
}
}
/**
- * Sets the object id of the tag. If the object id is null, the commit
- * pointed to from HEAD will be used.
+ * 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;
}
* @return {@code this}
*/
public TagCommand setForceUpdate(boolean forceUpdate) {
+ checkCallable();
this.forceUpdate = forceUpdate;
return this;
}
* @since 3.0
*/
public TagCommand setAnnotated(boolean annotated) {
- this.annotated = annotated;
+ checkCallable();
+ this.annotated = Boolean.valueOf(annotated);
return this;
}
/**
- * Whether this will create an annotated command
+ * 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() {
- return annotated;
+ 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;
+ }
+
}