View Javadoc
1   /*
2    * Copyright (C) 2006, 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4    * Copyright (C) 2010, 2020, Chris Aniszczyk <caniszczyk@gmail.com> and others
5    *
6    * This program and the accompanying materials are made available under the
7    * terms of the Eclipse Distribution License v. 1.0 which is available at
8    * https://www.eclipse.org/org/documents/edl-v10.php.
9    *
10   * SPDX-License-Identifier: BSD-3-Clause
11   */
12  
13  package org.eclipse.jgit.lib;
14  
15  import static java.nio.charset.StandardCharsets.UTF_8;
16  
17  import java.io.ByteArrayOutputStream;
18  import java.io.IOException;
19  import java.io.OutputStreamWriter;
20  import java.io.UnsupportedEncodingException;
21  import java.nio.charset.Charset;
22  
23  import org.eclipse.jgit.api.errors.JGitInternalException;
24  import org.eclipse.jgit.internal.JGitText;
25  import org.eclipse.jgit.revwalk.RevObject;
26  import org.eclipse.jgit.util.References;
27  
28  /**
29   * Mutable builder to construct an annotated tag recording a project state.
30   *
31   * Applications should use this object when they need to manually construct a
32   * tag and want precise control over its fields.
33   *
34   * To read a tag object, construct a {@link org.eclipse.jgit.revwalk.RevWalk}
35   * and obtain a {@link org.eclipse.jgit.revwalk.RevTag} instance by calling
36   * {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)}.
37   */
38  public class TagBuilder extends ObjectBuilder {
39  
40  	private static final byte[] hobject = Constants.encodeASCII("object"); //$NON-NLS-1$
41  
42  	private static final byte[] htype = Constants.encodeASCII("type"); //$NON-NLS-1$
43  
44  	private static final byte[] htag = Constants.encodeASCII("tag"); //$NON-NLS-1$
45  
46  	private static final byte[] htagger = Constants.encodeASCII("tagger"); //$NON-NLS-1$
47  
48  	private ObjectId object;
49  
50  	private int type = Constants.OBJ_BAD;
51  
52  	private String tag;
53  
54  	/**
55  	 * Get the type of object this tag refers to.
56  	 *
57  	 * @return the type of object this tag refers to.
58  	 */
59  	public int getObjectType() {
60  		return type;
61  	}
62  
63  	/**
64  	 * Get the object this tag refers to.
65  	 *
66  	 * @return the object this tag refers to.
67  	 */
68  	public ObjectId getObjectId() {
69  		return object;
70  	}
71  
72  	/**
73  	 * Set the object this tag refers to, and its type.
74  	 *
75  	 * @param obj
76  	 *            the object.
77  	 * @param objType
78  	 *            the type of {@code obj}. Must be a valid type code.
79  	 */
80  	public void setObjectId(AnyObjectId obj, int objType) {
81  		object = obj.copy();
82  		type = objType;
83  	}
84  
85  	/**
86  	 * Set the object this tag refers to, and infer its type.
87  	 *
88  	 * @param obj
89  	 *            the object the tag will refer to.
90  	 */
91  	public void setObjectId(RevObject obj) {
92  		setObjectId(obj, obj.getType());
93  	}
94  
95  	/**
96  	 * Get short name of the tag (no {@code refs/tags/} prefix).
97  	 *
98  	 * @return short name of the tag (no {@code refs/tags/} prefix).
99  	 */
100 	public String getTag() {
101 		return tag;
102 	}
103 
104 	/**
105 	 * Set the name of this tag.
106 	 *
107 	 * @param shortName
108 	 *            new short name of the tag. This short name should not start
109 	 *            with {@code refs/} as typically a tag is stored under the
110 	 *            reference derived from {@code "refs/tags/" + getTag()}.
111 	 */
112 	public void setTag(String shortName) {
113 		this.tag = shortName;
114 	}
115 
116 	/**
117 	 * Get creator of this tag.
118 	 *
119 	 * @return creator of this tag. May be null.
120 	 */
121 	public PersonIdent getTagger() {
122 		return getAuthor();
123 	}
124 
125 	/**
126 	 * Set the creator of this tag.
127 	 *
128 	 * @param taggerIdent
129 	 *            the creator. May be null.
130 	 */
131 	public void setTagger(PersonIdent taggerIdent) {
132 		setAuthor(taggerIdent);
133 	}
134 
135 	/**
136 	 * Format this builder's state as an annotated tag object.
137 	 *
138 	 * @return this object in the canonical annotated tag format, suitable for
139 	 *         storage in a repository.
140 	 */
141 	@Override
142 	public byte[] build() throws UnsupportedEncodingException {
143 		ByteArrayOutputStream os = new ByteArrayOutputStream();
144 		try (OutputStreamWriter w = new OutputStreamWriter(os,
145 				getEncoding())) {
146 
147 			os.write(hobject);
148 			os.write(' ');
149 			getObjectId().copyTo(os);
150 			os.write('\n');
151 
152 			os.write(htype);
153 			os.write(' ');
154 			os.write(Constants
155 					.encodeASCII(Constants.typeString(getObjectType())));
156 			os.write('\n');
157 
158 			os.write(htag);
159 			os.write(' ');
160 			w.write(getTag());
161 			w.flush();
162 			os.write('\n');
163 
164 			if (getTagger() != null) {
165 				os.write(htagger);
166 				os.write(' ');
167 				w.write(getTagger().toExternalString());
168 				w.flush();
169 				os.write('\n');
170 			}
171 
172 			writeEncoding(getEncoding(), os);
173 
174 			os.write('\n');
175 			String msg = getMessage();
176 			if (msg != null) {
177 				w.write(msg);
178 				w.flush();
179 			}
180 
181 			GpgSignature signature = getGpgSignature();
182 			if (signature != null) {
183 				if (msg != null && !msg.isEmpty() && !msg.endsWith("\n")) { //$NON-NLS-1$
184 					// If signed, the message *must* end with a linefeed
185 					// character, otherwise signature verification will fail.
186 					// (The signature will have been computed over the payload
187 					// containing the message without LF, but will be verified
188 					// against a payload with the LF.) The signature must start
189 					// on a new line.
190 					throw new JGitInternalException(
191 							JGitText.get().signedTagMessageNoLf);
192 				}
193 				String externalForm = signature.toExternalString();
194 				w.write(externalForm);
195 				w.flush();
196 				if (!externalForm.endsWith("\n")) { //$NON-NLS-1$
197 					os.write('\n');
198 				}
199 			}
200 		} catch (IOException err) {
201 			// This should never occur, the only way to get it above is
202 			// for the ByteArrayOutputStream to throw, but it doesn't.
203 			//
204 			throw new RuntimeException(err);
205 		}
206 		return os.toByteArray();
207 	}
208 
209 	/**
210 	 * Format this builder's state as an annotated tag object.
211 	 *
212 	 * @return this object in the canonical annotated tag format, suitable for
213 	 *         storage in a repository, or {@code null} if the tag cannot be
214 	 *         encoded
215 	 * @deprecated since 5.11; use {@link #build()} instead
216 	 */
217 	@Deprecated
218 	public byte[] toByteArray() {
219 		try {
220 			return build();
221 		} catch (UnsupportedEncodingException e) {
222 			return null;
223 		}
224 	}
225 
226 	/** {@inheritDoc} */
227 	@SuppressWarnings("nls")
228 	@Override
229 	public String toString() {
230 		StringBuilder r = new StringBuilder();
231 		r.append("Tag");
232 		r.append("={\n");
233 
234 		r.append("object ");
235 		r.append(object != null ? object.name() : "NOT_SET");
236 		r.append("\n");
237 
238 		r.append("type ");
239 		r.append(object != null ? Constants.typeString(type) : "NOT_SET");
240 		r.append("\n");
241 
242 		r.append("tag ");
243 		r.append(tag != null ? tag : "NOT_SET");
244 		r.append("\n");
245 
246 		if (getTagger() != null) {
247 			r.append("tagger ");
248 			r.append(getTagger());
249 			r.append("\n");
250 		}
251 
252 		Charset encoding = getEncoding();
253 		if (!References.isSameObject(encoding, UTF_8)) {
254 			r.append("encoding ");
255 			r.append(encoding.name());
256 			r.append("\n");
257 		}
258 
259 		r.append("\n");
260 		r.append(getMessage() != null ? getMessage() : "");
261 		GpgSignature signature = getGpgSignature();
262 		r.append(signature != null ? signature.toExternalString() : "");
263 		r.append("}");
264 		return r.toString();
265 	}
266 }