View Javadoc
1   /*
2    * Copyright (C) 2008, 2009, Google Inc.
3    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
4    * Copyright (C) 2008, 2021, Shawn O. Pearce <spearce@spearce.org> 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.revwalk;
14  
15  import static java.nio.charset.StandardCharsets.UTF_8;
16  
17  import java.io.IOException;
18  import java.nio.charset.Charset;
19  import java.nio.charset.IllegalCharsetNameException;
20  import java.nio.charset.UnsupportedCharsetException;
21  import java.util.Arrays;
22  
23  import org.eclipse.jgit.annotations.Nullable;
24  import org.eclipse.jgit.errors.CorruptObjectException;
25  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
26  import org.eclipse.jgit.errors.MissingObjectException;
27  import org.eclipse.jgit.lib.AnyObjectId;
28  import org.eclipse.jgit.lib.Constants;
29  import org.eclipse.jgit.lib.ObjectInserter;
30  import org.eclipse.jgit.lib.ObjectReader;
31  import org.eclipse.jgit.lib.PersonIdent;
32  import org.eclipse.jgit.util.MutableInteger;
33  import org.eclipse.jgit.util.RawParseUtils;
34  import org.eclipse.jgit.util.StringUtils;
35  
36  /**
37   * An annotated tag.
38   */
39  public class RevTag extends RevObject {
40  
41  	private static final byte[] hSignature = Constants
42  			.encodeASCII("-----BEGIN PGP SIGNATURE-----"); //$NON-NLS-1$
43  
44  	/**
45  	 * Parse an annotated tag from its canonical format.
46  	 *
47  	 * This method constructs a temporary revision pool, parses the tag as
48  	 * supplied, and returns it to the caller. Since the tag was built inside of
49  	 * a private revision pool its object pointer will be initialized, but will
50  	 * not have its headers loaded.
51  	 *
52  	 * Applications are discouraged from using this API. Callers usually need
53  	 * more than one object. Use
54  	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)} to obtain
55  	 * a RevTag from an existing repository.
56  	 *
57  	 * @param raw
58  	 *            the canonical formatted tag to be parsed.
59  	 * @return the parsed tag, in an isolated revision pool that is not
60  	 *         available to the caller.
61  	 * @throws org.eclipse.jgit.errors.CorruptObjectException
62  	 *             the tag contains a malformed header that cannot be handled.
63  	 */
64  	public static RevTag parse(byte[] raw) throws CorruptObjectException {
65  		return parse(new RevWalk((ObjectReader) null), raw);
66  	}
67  
68  	/**
69  	 * Parse an annotated tag from its canonical format.
70  	 * <p>
71  	 * This method inserts the tag directly into the caller supplied revision
72  	 * pool, making it appear as though the tag exists in the repository, even
73  	 * if it doesn't. The repository under the pool is not affected.
74  	 * <p>
75  	 * The body of the tag (message, tagger, signature) is always retained in
76  	 * the returned {@code RevTag}, even if the supplied {@code RevWalk} has
77  	 * been configured with {@code setRetainBody(false)}.
78  	 *
79  	 * @param rw
80  	 *            the revision pool to allocate the tag within. The tag's object
81  	 *            pointer will be obtained from this pool.
82  	 * @param raw
83  	 *            the canonical formatted tag to be parsed. This buffer will be
84  	 *            retained by the returned {@code RevTag} and must not be
85  	 *            modified by the caller.
86  	 * @return the parsed tag, in an isolated revision pool that is not
87  	 *         available to the caller.
88  	 * @throws org.eclipse.jgit.errors.CorruptObjectException
89  	 *             the tag contains a malformed header that cannot be handled.
90  	 */
91  	public static RevTag parse(RevWalk rw, byte[] raw)
92  			throws CorruptObjectException {
93  		try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
94  			RevTag r = rw.lookupTag(fmt.idFor(Constants.OBJ_TAG, raw));
95  			r.parseCanonical(rw, raw);
96  			r.buffer = raw;
97  			return r;
98  		}
99  	}
100 
101 	private RevObject object;
102 
103 	private byte[] buffer;
104 
105 	private String tagName;
106 
107 	/**
108 	 * Create a new tag reference.
109 	 *
110 	 * @param id
111 	 *            object name for the tag.
112 	 */
113 	protected RevTag(AnyObjectId id) {
114 		super(id);
115 	}
116 
117 	@Override
118 	void parseHeaders(RevWalk walk) throws MissingObjectException,
119 			IncorrectObjectTypeException, IOException {
120 		parseCanonical(walk, walk.getCachedBytes(this));
121 	}
122 
123 	@Override
124 	void parseBody(RevWalk walk) throws MissingObjectException,
125 			IncorrectObjectTypeException, IOException {
126 		if (buffer == null) {
127 			buffer = walk.getCachedBytes(this);
128 			if ((flags & PARSED) == 0)
129 				parseCanonical(walk, buffer);
130 		}
131 	}
132 
133 	void parseCanonical(RevWalk walk, byte[] rawTag)
134 			throws CorruptObjectException {
135 		final MutableInteger pos = new MutableInteger();
136 		final int oType;
137 
138 		pos.value = 53; // "object $sha1\ntype "
139 		oType = Constants.decodeTypeString(this, rawTag, (byte) '\n', pos);
140 		walk.idBuffer.fromString(rawTag, 7);
141 		object = walk.lookupAny(walk.idBuffer, oType);
142 
143 		int p = pos.value += 4; // "tag "
144 		final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1;
145 		tagName = RawParseUtils.decode(UTF_8, rawTag, p, nameEnd);
146 
147 		if (walk.isRetainBody())
148 			buffer = rawTag;
149 		flags |= PARSED;
150 	}
151 
152 	/** {@inheritDoc} */
153 	@Override
154 	public final int getType() {
155 		return Constants.OBJ_TAG;
156 	}
157 
158 	/**
159 	 * Parse the tagger identity from the raw buffer.
160 	 * <p>
161 	 * This method parses and returns the content of the tagger line, after
162 	 * taking the tag's character set into account and decoding the tagger
163 	 * name and email address. This method is fairly expensive and produces a
164 	 * new PersonIdent instance on each invocation. Callers should invoke this
165 	 * method only if they are certain they will be outputting the result, and
166 	 * should cache the return value for as long as necessary to use all
167 	 * information from it.
168 	 *
169 	 * @return identity of the tagger (name, email) and the time the tag
170 	 *         was made by the tagger; null if no tagger line was found.
171 	 */
172 	public final PersonIdent getTaggerIdent() {
173 		final byte[] raw = buffer;
174 		final int nameB = RawParseUtils.tagger(raw, 0);
175 		if (nameB < 0)
176 			return null;
177 		return RawParseUtils.parsePersonIdent(raw, nameB);
178 	}
179 
180 	private static int nextStart(byte[] prefix, byte[] buffer, int from) {
181 		int stop = buffer.length - prefix.length + 1;
182 		int ptr = from;
183 		if (ptr > 0) {
184 			ptr = RawParseUtils.nextLF(buffer, ptr - 1);
185 		}
186 		while (ptr < stop) {
187 			int lineStart = ptr;
188 			boolean found = true;
189 			for (byte element : prefix) {
190 				if (element != buffer[ptr++]) {
191 					found = false;
192 					break;
193 				}
194 			}
195 			if (found) {
196 				return lineStart;
197 			}
198 			do {
199 				ptr = RawParseUtils.nextLF(buffer, ptr);
200 			} while (ptr < stop && buffer[ptr] == '\n');
201 		}
202 		return -1;
203 	}
204 
205 	private int getSignatureStart() {
206 		byte[] raw = buffer;
207 		int msgB = RawParseUtils.tagMessage(raw, 0);
208 		if (msgB < 0) {
209 			return msgB;
210 		}
211 		// Find the last signature start and return the rest
212 		int start = nextStart(hSignature, raw, msgB);
213 		if (start < 0) {
214 			return start;
215 		}
216 		int next = RawParseUtils.nextLF(raw, start);
217 		while (next < raw.length) {
218 			int newStart = nextStart(hSignature, raw, next);
219 			if (newStart < 0) {
220 				break;
221 			}
222 			start = newStart;
223 			next = RawParseUtils.nextLF(raw, start);
224 		}
225 		return start;
226 	}
227 
228 	/**
229 	 * Parse the GPG signature from the raw buffer.
230 	 *
231 	 * @return contents of the GPG signature; {@code null} if the tag was not
232 	 *         signed.
233 	 * @since 5.11
234 	 */
235 	@Nullable
236 	public final byte[] getRawGpgSignature() {
237 		byte[] raw = buffer;
238 		int start = getSignatureStart();
239 		if (start < 0) {
240 			return null;
241 		}
242 		return Arrays.copyOfRange(raw, start, raw.length);
243 	}
244 
245 	/**
246 	 * Parse the complete tag message and decode it to a string.
247 	 * <p>
248 	 * This method parses and returns the message portion of the tag buffer,
249 	 * after taking the tag's character set into account and decoding the buffer
250 	 * using that character set. This method is a fairly expensive operation and
251 	 * produces a new string on each invocation.
252 	 *
253 	 * @return decoded tag message as a string. Never null.
254 	 */
255 	public final String getFullMessage() {
256 		byte[] raw = buffer;
257 		int msgB = RawParseUtils.tagMessage(raw, 0);
258 		if (msgB < 0) {
259 			return ""; //$NON-NLS-1$
260 		}
261 		int signatureStart = getSignatureStart();
262 		int end = signatureStart < 0 ? raw.length : signatureStart;
263 		if (end == msgB) {
264 			return ""; //$NON-NLS-1$
265 		}
266 		return RawParseUtils.decode(guessEncoding(), raw, msgB, end);
267 	}
268 
269 	/**
270 	 * Parse the tag message and return the first "line" of it.
271 	 * <p>
272 	 * The first line is everything up to the first pair of LFs. This is the
273 	 * "oneline" format, suitable for output in a single line display.
274 	 * <p>
275 	 * This method parses and returns the message portion of the tag buffer,
276 	 * after taking the tag's character set into account and decoding the buffer
277 	 * using that character set. This method is a fairly expensive operation and
278 	 * produces a new string on each invocation.
279 	 *
280 	 * @return decoded tag message as a string. Never null. The returned string
281 	 *         does not contain any LFs, even if the first paragraph spanned
282 	 *         multiple lines. Embedded LFs are converted to spaces.
283 	 */
284 	public final String getShortMessage() {
285 		byte[] raw = buffer;
286 		int msgB = RawParseUtils.tagMessage(raw, 0);
287 		if (msgB < 0) {
288 			return ""; //$NON-NLS-1$
289 		}
290 
291 		int msgE = RawParseUtils.endOfParagraph(raw, msgB);
292 		int signatureStart = getSignatureStart();
293 		if (signatureStart >= msgB && msgE > signatureStart) {
294 			msgE = signatureStart;
295 			if (msgE > msgB) {
296 				msgE--;
297 			}
298 			if (msgB == msgE) {
299 				return ""; //$NON-NLS-1$
300 			}
301 		}
302 		String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
303 		if (RevCommit.hasLF(raw, msgB, msgE)) {
304 			str = StringUtils.replaceLineBreaksWithSpace(str);
305 		}
306 		return str;
307 	}
308 
309 	private Charset guessEncoding() {
310 		try {
311 			return RawParseUtils.parseEncoding(buffer);
312 		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
313 			return UTF_8;
314 		}
315 	}
316 
317 	/**
318 	 * Get a reference to the object this tag was placed on.
319 	 * <p>
320 	 * Note that the returned object has only been looked up (see
321 	 * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}. To
322 	 * access the contents it needs to be parsed, see
323 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseHeaders(RevObject)} and
324 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}.
325 	 * <p>
326 	 * As an alternative, use
327 	 * {@link org.eclipse.jgit.revwalk.RevWalk#peel(RevObject)} and pass this
328 	 * {@link org.eclipse.jgit.revwalk.RevTag} to peel it until the first
329 	 * non-tag object.
330 	 *
331 	 * @return object this tag refers to (only looked up, not parsed)
332 	 */
333 	public final RevObject getObject() {
334 		return object;
335 	}
336 
337 	/**
338 	 * Get the name of this tag, from the tag header.
339 	 *
340 	 * @return name of the tag, according to the tag header.
341 	 */
342 	public final String getTagName() {
343 		return tagName;
344 	}
345 
346 	/**
347 	 * Obtain the raw unparsed tag body (<b>NOTE - THIS IS NOT A COPY</b>).
348 	 * <p>
349 	 * This method is exposed only to provide very fast, efficient access to
350 	 * this tag's message buffer. Applications relying on this buffer should be
351 	 * very careful to ensure they do not modify its contents during their use
352 	 * of it.
353 	 *
354 	 * @return the raw unparsed tag body. This is <b>NOT A COPY</b>. Do not
355 	 *         alter the returned array.
356 	 * @since 5.11
357 	 */
358 	public final byte[] getRawBuffer() {
359 		return buffer;
360 	}
361 
362 	/**
363 	 * Discard the message buffer to reduce memory usage.
364 	 * <p>
365 	 * After discarding the memory usage of the {@code RevTag} is reduced to
366 	 * only the {@link #getObject()} pointer and {@link #getTagName()}.
367 	 * Accessing other properties such as {@link #getTaggerIdent()} or either
368 	 * message function requires reloading the buffer by invoking
369 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}.
370 	 *
371 	 * @since 4.0
372 	 */
373 	public final void disposeBody() {
374 		buffer = null;
375 	}
376 }