summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java184
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java59
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java225
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java137
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java62
7 files changed, 458 insertions, 211 deletions
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 12902b9004..6d15464d5a 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -617,6 +617,7 @@ shortCompressedStreamAt=Short compressed stream at {0}
shortReadOfBlock=Short read of block.
shortReadOfOptionalDIRCExtensionExpectedAnotherBytes=Short read of optional DIRC extension {0}; expected another {1} bytes within the section.
shortSkipOfBlock=Short skip of block.
+signedTagMessageNoLf=A non-empty message of a signed tag must end in LF.
signingNotSupportedOnTag=Signing isn't supported on tag operations yet.
signingServiceUnavailable=Signing service is not available
similarityScoreMustBeWithinBounds=Similarity score must be between 0 and 100.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 892657d5d3..a7daed1318 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -645,6 +645,7 @@ public class JGitText extends TranslationBundle {
/***/ public String shortReadOfBlock;
/***/ public String shortReadOfOptionalDIRCExtensionExpectedAnotherBytes;
/***/ public String shortSkipOfBlock;
+ /***/ public String signedTagMessageNoLf;
/***/ public String signingNotSupportedOnTag;
/***/ public String signingServiceUnavailable;
/***/ public String similarityScoreMustBeWithinBounds;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
index 4f93fda49f..1665f051e9 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CommitBuilder.java
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
- * Copyright (C) 2006-2007, Robin Rosenberg <robin.rosenberg@dewire.com>
- * Copyright (C) 2006-2007, Shawn O. Pearce <spearce@spearce.org> and others
+ * Copyright (C) 2006, 2007, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, 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
@@ -16,14 +16,11 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
-import java.text.MessageFormat;
import java.util.List;
-import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.util.References;
/**
@@ -37,7 +34,7 @@ import org.eclipse.jgit.util.References;
* and obtain a {@link org.eclipse.jgit.revwalk.RevCommit} instance by calling
* {@link org.eclipse.jgit.revwalk.RevWalk#parseCommit(AnyObjectId)}.
*/
-public class CommitBuilder {
+public class CommitBuilder extends ObjectBuilder {
private static final ObjectId[] EMPTY_OBJECTID_LIST = new ObjectId[0];
private static final byte[] htree = Constants.encodeASCII("tree"); //$NON-NLS-1$
@@ -50,28 +47,17 @@ public class CommitBuilder {
private static final byte[] hgpgsig = Constants.encodeASCII("gpgsig"); //$NON-NLS-1$
- private static final byte[] hencoding = Constants.encodeASCII("encoding"); //$NON-NLS-1$
-
private ObjectId treeId;
private ObjectId[] parentIds;
- private PersonIdent author;
-
private PersonIdent committer;
- private GpgSignature gpgSignature;
-
- private String message;
-
- private Charset encoding;
-
/**
* Initialize an empty commit.
*/
public CommitBuilder() {
parentIds = EMPTY_OBJECTID_LIST;
- encoding = UTF_8;
}
/**
@@ -98,8 +84,9 @@ public class CommitBuilder {
*
* @return the author of this commit (who wrote it).
*/
+ @Override
public PersonIdent getAuthor() {
- return author;
+ return super.getAuthor();
}
/**
@@ -108,8 +95,9 @@ public class CommitBuilder {
* @param newAuthor
* the new author. Should not be null.
*/
+ @Override
public void setAuthor(PersonIdent newAuthor) {
- author = newAuthor;
+ super.setAuthor(newAuthor);
}
/**
@@ -132,38 +120,6 @@ public class CommitBuilder {
}
/**
- * Set the GPG signature of this commit.
- * <p>
- * Note, the signature set here will change the payload of the commit, i.e.
- * the output of {@link #build()} will include the signature. Thus, the
- * typical flow will be:
- * <ol>
- * <li>call {@link #build()} without a signature set to obtain payload</li>
- * <li>create {@link GpgSignature} from payload</li>
- * <li>set {@link GpgSignature}</li>
- * </ol>
- * </p>
- *
- * @param newSignature
- * the signature to set or <code>null</code> to unset
- * @since 5.3
- */
- public void setGpgSignature(GpgSignature newSignature) {
- gpgSignature = newSignature;
- }
-
- /**
- * Get the GPG signature of this commit.
- *
- * @return the GPG signature of this commit, maybe <code>null</code> if the
- * commit is not to be signed
- * @since 5.3
- */
- public GpgSignature getGpgSignature() {
- return gpgSignature;
- }
-
- /**
* Get the ancestors of this commit.
*
* @return the ancestors of this commit. Never null.
@@ -239,25 +195,6 @@ public class CommitBuilder {
}
/**
- * Get the complete commit message.
- *
- * @return the complete commit message.
- */
- public String getMessage() {
- return message;
- }
-
- /**
- * Set the commit message.
- *
- * @param newMessage
- * the commit message. Should not be null.
- */
- public void setMessage(String newMessage) {
- message = newMessage;
- }
-
- /**
* Set the encoding for the commit information.
*
* @param encodingName
@@ -267,37 +204,10 @@ public class CommitBuilder {
*/
@Deprecated
public void setEncoding(String encodingName) {
- encoding = Charset.forName(encodingName);
- }
-
- /**
- * Set the encoding for the commit information.
- *
- * @param enc
- * the encoding to use.
- */
- public void setEncoding(Charset enc) {
- encoding = enc;
+ setEncoding(Charset.forName(encodingName));
}
- /**
- * Get the encoding that should be used for the commit message text.
- *
- * @return the encoding that should be used for the commit message text.
- */
- public Charset getEncoding() {
- return encoding;
- }
-
- /**
- * Format this builder's state as a commit object.
- *
- * @return this object in the canonical commit format, suitable for storage
- * in a repository.
- * @throws java.io.UnsupportedEncodingException
- * the encoding specified by {@link #getEncoding()} is not
- * supported by this Java runtime.
- */
+ @Override
public byte[] build() throws UnsupportedEncodingException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStreamWriter w = new OutputStreamWriter(os, getEncoding());
@@ -326,19 +236,16 @@ public class CommitBuilder {
w.flush();
os.write('\n');
- if (getGpgSignature() != null) {
+ GpgSignature signature = getGpgSignature();
+ if (signature != null) {
os.write(hgpgsig);
os.write(' ');
- writeGpgSignatureString(getGpgSignature().toExternalString(), os);
+ writeMultiLineHeader(signature.toExternalString(), os,
+ true);
os.write('\n');
}
- if (!References.isSameObject(getEncoding(), UTF_8)) {
- os.write(hencoding);
- os.write(' ');
- os.write(Constants.encodeASCII(getEncoding().name()));
- os.write('\n');
- }
+ writeEncoding(getEncoding(), os);
os.write('\n');
@@ -356,58 +263,6 @@ public class CommitBuilder {
}
/**
- * Writes signature to output as per <a href=
- * "https://github.com/git/git/blob/master/Documentation/technical/signature-format.txt#L66,L89">gpgsig
- * header</a>.
- * <p>
- * CRLF and CR will be sanitized to LF and signature will have a hanging
- * indent of one space starting with line two. A trailing line break is
- * <em>not</em> written; the caller is supposed to terminate the GPG
- * signature header by writing a single newline.
- * </p>
- *
- * @param in
- * signature string with line breaks
- * @param out
- * output stream
- * @throws IOException
- * thrown by the output stream
- * @throws IllegalArgumentException
- * if the signature string contains non 7-bit ASCII chars
- */
- static void writeGpgSignatureString(String in, OutputStream out)
- throws IOException, IllegalArgumentException {
- int length = in.length();
- for (int i = 0; i < length; ++i) {
- char ch = in.charAt(i);
- switch (ch) {
- case '\r':
- if (i + 1 < length && in.charAt(i + 1) == '\n') {
- ++i;
- }
- if (i + 1 < length) {
- out.write('\n');
- out.write(' ');
- }
- break;
- case '\n':
- if (i + 1 < length) {
- out.write('\n');
- out.write(' ');
- }
- break;
- default:
- // sanity check
- if (ch > 127)
- throw new IllegalArgumentException(MessageFormat
- .format(JGitText.get().notASCIIString, in));
- out.write(ch);
- break;
- }
- }
- }
-
- /**
* Format this builder's state as a commit object.
*
* @return this object in the canonical commit format, suitable for storage
@@ -439,7 +294,7 @@ public class CommitBuilder {
}
r.append("author ");
- r.append(author != null ? author.toString() : "NOT_SET");
+ r.append(getAuthor() != null ? getAuthor().toString() : "NOT_SET");
r.append("\n");
r.append("committer ");
@@ -447,17 +302,20 @@ public class CommitBuilder {
r.append("\n");
r.append("gpgSignature ");
- r.append(gpgSignature != null ? gpgSignature.toString() : "NOT_SET");
+ GpgSignature signature = getGpgSignature();
+ r.append(signature != null ? signature.toString()
+ : "NOT_SET");
r.append("\n");
- if (encoding != null && !References.isSameObject(encoding, UTF_8)) {
+ Charset encoding = getEncoding();
+ if (!References.isSameObject(encoding, UTF_8)) {
r.append("encoding ");
r.append(encoding.name());
r.append("\n");
}
r.append("\n");
- r.append(message != null ? message : "");
+ r.append(getMessage() != null ? getMessage() : "");
r.append("}");
return r.toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java
new file mode 100644
index 0000000000..6fb767774c
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/GpgObjectSigner.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2020 Thomas Wolf <thomas.wolf@paranor.ch> 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.lib;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.api.errors.CanceledException;
+import org.eclipse.jgit.transport.CredentialsProvider;
+
+/**
+ * Creates GPG signatures for Git objects.
+ *
+ * @since 5.11
+ */
+public interface GpgObjectSigner {
+
+ /**
+ * Signs the specified object.
+ *
+ * <p>
+ * Implementors should obtain the payload for signing from the specified
+ * object via {@link ObjectBuilder#build()} and create a proper
+ * {@link GpgSignature}. The generated signature must be set on the
+ * specified {@code object} (see
+ * {@link ObjectBuilder#setGpgSignature(GpgSignature)}).
+ * </p>
+ * <p>
+ * Any existing signature on the object must be discarded prior obtaining
+ * the payload via {@link ObjectBuilder#build()}.
+ * </p>
+ *
+ * @param object
+ * the object to sign (must not be {@code null} and must be
+ * complete to allow proper calculation of payload)
+ * @param gpgSigningKey
+ * the signing key to locate (passed as is to the GPG signing
+ * tool as is; eg., value of <code>user.signingkey</code>)
+ * @param committer
+ * the signing identity (to help with key lookup in case signing
+ * key is not specified)
+ * @param credentialsProvider
+ * provider to use when querying for signing key credentials (eg.
+ * passphrase)
+ * @throws CanceledException
+ * when signing was canceled (eg., user aborted when entering
+ * passphrase)
+ */
+ void signObject(@NonNull ObjectBuilder object,
+ @Nullable String gpgSigningKey, @NonNull PersonIdent committer,
+ CredentialsProvider credentialsProvider) throws CanceledException;
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java
new file mode 100644
index 0000000000..4b7054f72b
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectBuilder.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2020, Thomas Wolf <thomas.wolf@paranor.ch> 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.lib;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.text.MessageFormat;
+import java.util.Objects;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.util.References;
+
+/**
+ * Common base class for {@link CommitBuilder} and {@link TagBuilder}.
+ *
+ * @since 5.11
+ */
+public abstract class ObjectBuilder {
+
+ /** Byte representation of "encoding". */
+ private static final byte[] hencoding = Constants.encodeASCII("encoding"); //$NON-NLS-1$
+
+ private PersonIdent author;
+
+ private GpgSignature gpgSignature;
+
+ private String message;
+
+ private Charset encoding = StandardCharsets.UTF_8;
+
+ /**
+ * Retrieves the author of this object.
+ *
+ * @return the author of this object, or {@code null} if not set yet
+ */
+ protected PersonIdent getAuthor() {
+ return author;
+ }
+
+ /**
+ * Sets the author (name, email address, and date) of this object.
+ *
+ * @param newAuthor
+ * the new author, must be non-{@code null}
+ */
+ protected void setAuthor(PersonIdent newAuthor) {
+ author = Objects.requireNonNull(newAuthor);
+ }
+
+ /**
+ * Sets the GPG signature of this object.
+ * <p>
+ * Note, the signature set here will change the payload of the object, i.e.
+ * the output of {@link #build()} will include the signature. Thus, the
+ * typical flow will be:
+ * <ol>
+ * <li>call {@link #build()} without a signature set to obtain payload</li>
+ * <li>create {@link GpgSignature} from payload</li>
+ * <li>set {@link GpgSignature}</li>
+ * </ol>
+ * </p>
+ *
+ * @param gpgSignature
+ * the signature to set or {@code null} to unset
+ * @since 5.3
+ */
+ public void setGpgSignature(@Nullable GpgSignature gpgSignature) {
+ this.gpgSignature = gpgSignature;
+ }
+
+ /**
+ * Retrieves the GPG signature of this object.
+ *
+ * @return the GPG signature of this object, or {@code null} if the object
+ * is not signed
+ * @since 5.3
+ */
+ @Nullable
+ public GpgSignature getGpgSignature() {
+ return gpgSignature;
+ }
+
+ /**
+ * Retrieves the complete message of the object.
+ *
+ * @return the complete message; can be {@code null}.
+ */
+ @Nullable
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Sets the message (commit message, or message of an annotated tag).
+ *
+ * @param message
+ * the message.
+ */
+ public void setMessage(@Nullable String message) {
+ this.message = message;
+ }
+
+ /**
+ * Retrieves the encoding that should be used for the message text.
+ *
+ * @return the encoding that should be used for the message text.
+ */
+ @NonNull
+ public Charset getEncoding() {
+ return encoding;
+ }
+
+ /**
+ * Sets the encoding for the object message.
+ *
+ * @param encoding
+ * the encoding to use.
+ */
+ public void setEncoding(@NonNull Charset encoding) {
+ this.encoding = encoding;
+ }
+
+ /**
+ * Format this builder's state as a git object.
+ *
+ * @return this object in the canonical git format, suitable for storage in
+ * a repository.
+ * @throws java.io.UnsupportedEncodingException
+ * the encoding specified by {@link #getEncoding()} is not
+ * supported by this Java runtime.
+ */
+ @NonNull
+ public abstract byte[] build() throws UnsupportedEncodingException;
+
+ /**
+ * Writes signature to output as per <a href=
+ * "https://github.com/git/git/blob/master/Documentation/technical/signature-format.txt#L66,L89">gpgsig
+ * header</a>.
+ * <p>
+ * CRLF and CR will be sanitized to LF and signature will have a hanging
+ * indent of one space starting with line two. A trailing line break is
+ * <em>not</em> written; the caller is supposed to terminate the GPG
+ * signature header by writing a single newline.
+ * </p>
+ *
+ * @param in
+ * signature string with line breaks
+ * @param out
+ * output stream
+ * @param enforceAscii
+ * whether to throw {@link IllegalArgumentException} if non-ASCII
+ * characters are encountered
+ * @throws IOException
+ * thrown by the output stream
+ * @throws IllegalArgumentException
+ * if the signature string contains non 7-bit ASCII chars and
+ * {@code enforceAscii == true}
+ */
+ static void writeMultiLineHeader(@NonNull String in,
+ @NonNull OutputStream out, boolean enforceAscii)
+ throws IOException, IllegalArgumentException {
+ int length = in.length();
+ for (int i = 0; i < length; ++i) {
+ char ch = in.charAt(i);
+ switch (ch) {
+ case '\r':
+ if (i + 1 < length && in.charAt(i + 1) == '\n') {
+ ++i;
+ }
+ if (i + 1 < length) {
+ out.write('\n');
+ out.write(' ');
+ }
+ break;
+ case '\n':
+ if (i + 1 < length) {
+ out.write('\n');
+ out.write(' ');
+ }
+ break;
+ default:
+ // sanity check
+ if (ch > 127 && enforceAscii)
+ throw new IllegalArgumentException(MessageFormat
+ .format(JGitText.get().notASCIIString, in));
+ out.write(ch);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Writes an "encoding" header.
+ *
+ * @param encoding
+ * to write
+ * @param out
+ * to write to
+ * @throws IOException
+ * if writing fails
+ */
+ static void writeEncoding(@NonNull Charset encoding,
+ @NonNull OutputStream out) throws IOException {
+ if (!References.isSameObject(encoding, UTF_8)) {
+ out.write(hencoding);
+ out.write(' ');
+ out.write(Constants.encodeASCII(encoding.name()));
+ out.write('\n');
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
index 71f01150c9..facb4a54be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/TagBuilder.java
@@ -1,7 +1,7 @@
/*
- * Copyright (C) 2006-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
+ * Copyright (C) 2006, 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
- * Copyright (C) 2010, 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
@@ -17,8 +17,13 @@ import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.util.References;
/**
* Mutable builder to construct an annotated tag recording a project state.
@@ -30,17 +35,22 @@ import org.eclipse.jgit.revwalk.RevObject;
* and obtain a {@link org.eclipse.jgit.revwalk.RevTag} instance by calling
* {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)}.
*/
-public class TagBuilder {
+public class TagBuilder extends ObjectBuilder {
+
+ private static final byte[] hobject = Constants.encodeASCII("object"); //$NON-NLS-1$
+
+ private static final byte[] htype = Constants.encodeASCII("type"); //$NON-NLS-1$
+
+ private static final byte[] htag = Constants.encodeASCII("tag"); //$NON-NLS-1$
+
+ private static final byte[] htagger = Constants.encodeASCII("tagger"); //$NON-NLS-1$
+
private ObjectId object;
private int type = Constants.OBJ_BAD;
private String tag;
- private PersonIdent tagger;
-
- private String message;
-
/**
* Get the type of object this tag refers to.
*
@@ -109,7 +119,7 @@ public class TagBuilder {
* @return creator of this tag. May be null.
*/
public PersonIdent getTagger() {
- return tagger;
+ return getAuthor();
}
/**
@@ -119,26 +129,7 @@ public class TagBuilder {
* the creator. May be null.
*/
public void setTagger(PersonIdent taggerIdent) {
- tagger = taggerIdent;
- }
-
- /**
- * Get the complete commit message.
- *
- * @return the complete commit message.
- */
- public String getMessage() {
- return message;
- }
-
- /**
- * Set the tag's message.
- *
- * @param newMessage
- * the tag's message.
- */
- public void setMessage(String newMessage) {
- message = newMessage;
+ setAuthor(taggerIdent);
}
/**
@@ -147,31 +138,65 @@ public class TagBuilder {
* @return this object in the canonical annotated tag format, suitable for
* storage in a repository.
*/
- public byte[] build() {
+ @Override
+ public byte[] build() throws UnsupportedEncodingException {
ByteArrayOutputStream os = new ByteArrayOutputStream();
try (OutputStreamWriter w = new OutputStreamWriter(os,
- UTF_8)) {
- w.write("object "); //$NON-NLS-1$
- getObjectId().copyTo(w);
- w.write('\n');
+ getEncoding())) {
- w.write("type "); //$NON-NLS-1$
- w.write(Constants.typeString(getObjectType()));
- w.write("\n"); //$NON-NLS-1$
+ os.write(hobject);
+ os.write(' ');
+ getObjectId().copyTo(os);
+ os.write('\n');
- w.write("tag "); //$NON-NLS-1$
+ os.write(htype);
+ os.write(' ');
+ os.write(Constants
+ .encodeASCII(Constants.typeString(getObjectType())));
+ os.write('\n');
+
+ os.write(htag);
+ os.write(' ');
w.write(getTag());
- w.write("\n"); //$NON-NLS-1$
+ w.flush();
+ os.write('\n');
if (getTagger() != null) {
- w.write("tagger "); //$NON-NLS-1$
+ os.write(htagger);
+ os.write(' ');
w.write(getTagger().toExternalString());
- w.write('\n');
+ w.flush();
+ os.write('\n');
+ }
+
+ writeEncoding(getEncoding(), os);
+
+ os.write('\n');
+ String msg = getMessage();
+ if (msg != null) {
+ w.write(msg);
+ w.flush();
}
- w.write('\n');
- if (getMessage() != null)
- w.write(getMessage());
+ GpgSignature signature = getGpgSignature();
+ if (signature != null) {
+ if (msg != null && !msg.isEmpty() && !msg.endsWith("\n")) { //$NON-NLS-1$
+ // If signed, the message *must* end with a linefeed
+ // character, otherwise signature verification will fail.
+ // (The signature will have been computed over the payload
+ // containing the message without LF, but will be verified
+ // against a payload with the LF.) The signature must start
+ // on a new line.
+ throw new JGitInternalException(
+ JGitText.get().signedTagMessageNoLf);
+ }
+ String externalForm = signature.toExternalString();
+ w.write(externalForm);
+ w.flush();
+ if (!externalForm.endsWith("\n")) { //$NON-NLS-1$
+ os.write('\n');
+ }
+ }
} catch (IOException err) {
// This should never occur, the only way to get it above is
// for the ByteArrayOutputStream to throw, but it doesn't.
@@ -185,10 +210,17 @@ public class TagBuilder {
* Format this builder's state as an annotated tag object.
*
* @return this object in the canonical annotated tag format, suitable for
- * storage in a repository.
+ * storage in a repository, or {@code null} if the tag cannot be
+ * encoded
+ * @deprecated since 5.11; use {@link #build()} instead
*/
+ @Deprecated
public byte[] toByteArray() {
- return build();
+ try {
+ return build();
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
}
/** {@inheritDoc} */
@@ -211,14 +243,23 @@ public class TagBuilder {
r.append(tag != null ? tag : "NOT_SET");
r.append("\n");
- if (tagger != null) {
+ if (getTagger() != null) {
r.append("tagger ");
- r.append(tagger);
+ r.append(getTagger());
+ r.append("\n");
+ }
+
+ Charset encoding = getEncoding();
+ if (!References.isSameObject(encoding, UTF_8)) {
+ r.append("encoding ");
+ r.append(encoding.name());
r.append("\n");
}
r.append("\n");
- r.append(message != null ? message : "");
+ r.append(getMessage() != null ? getMessage() : "");
+ GpgSignature signature = getGpgSignature();
+ r.append(signature != null ? signature.toExternalString() : "");
r.append("}");
return r.toString();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
index cac257199f..3bcdfafea7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevTag.java
@@ -18,7 +18,9 @@ import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
+import java.util.Arrays;
+import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
@@ -35,6 +37,10 @@ import org.eclipse.jgit.util.StringUtils;
* An annotated tag.
*/
public class RevTag extends RevObject {
+
+ private static final byte[] hSignature = Constants
+ .encodeASCII("-----BEGIN PGP SIGNATURE-----"); //$NON-NLS-1$
+
/**
* Parse an annotated tag from its canonical format.
*
@@ -171,6 +177,62 @@ public class RevTag extends RevObject {
return RawParseUtils.parsePersonIdent(raw, nameB);
}
+ private static int nextStart(byte[] prefix, byte[] buffer, int from) {
+ int stop = buffer.length - prefix.length + 1;
+ int ptr = from;
+ if (ptr > 0) {
+ ptr = RawParseUtils.nextLF(buffer, ptr - 1);
+ }
+ while (ptr < stop) {
+ int lineStart = ptr;
+ boolean found = true;
+ for (byte element : prefix) {
+ if (element != buffer[ptr++]) {
+ found = false;
+ break;
+ }
+ }
+ if (found) {
+ return lineStart;
+ }
+ do {
+ ptr = RawParseUtils.nextLF(buffer, ptr);
+ } while (ptr < stop && buffer[ptr] == '\n');
+ }
+ return -1;
+ }
+
+ /**
+ * Parse the GPG signature from the raw buffer.
+ *
+ * @return contents of the GPG signature; {@code null} if the tag was not
+ * signed.
+ * @since 5.11
+ */
+ @Nullable
+ public final byte[] getRawGpgSignature() {
+ byte[] raw = buffer;
+ int msgB = RawParseUtils.tagMessage(raw, 0);
+ if (msgB < 0) {
+ return null;
+ }
+ // Find the last signature start and return the rest
+ int start = nextStart(hSignature, raw, msgB);
+ if (start < 0) {
+ return null;
+ }
+ int next = RawParseUtils.nextLF(raw, start);
+ while (next < raw.length) {
+ int newStart = nextStart(hSignature, raw, next);
+ if (newStart < 0) {
+ break;
+ }
+ start = newStart;
+ next = RawParseUtils.nextLF(raw, start);
+ }
+ return Arrays.copyOfRange(raw, start, raw.length);
+ }
+
/**
* Parse the complete tag message and decode it to a string.
* <p>