diff options
Diffstat (limited to 'org.eclipse.jgit')
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> |