View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
4    * Copyright (C) 2008, 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  
22  import org.eclipse.jgit.errors.CorruptObjectException;
23  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
24  import org.eclipse.jgit.errors.MissingObjectException;
25  import org.eclipse.jgit.lib.AnyObjectId;
26  import org.eclipse.jgit.lib.Constants;
27  import org.eclipse.jgit.lib.ObjectInserter;
28  import org.eclipse.jgit.lib.ObjectReader;
29  import org.eclipse.jgit.lib.PersonIdent;
30  import org.eclipse.jgit.util.MutableInteger;
31  import org.eclipse.jgit.util.RawParseUtils;
32  import org.eclipse.jgit.util.StringUtils;
33  
34  /**
35   * An annotated tag.
36   */
37  public class RevTag extends RevObject {
38  	/**
39  	 * Parse an annotated tag from its canonical format.
40  	 *
41  	 * This method constructs a temporary revision pool, parses the tag as
42  	 * supplied, and returns it to the caller. Since the tag was built inside of
43  	 * a private revision pool its object pointer will be initialized, but will
44  	 * not have its headers loaded.
45  	 *
46  	 * Applications are discouraged from using this API. Callers usually need
47  	 * more than one object. Use
48  	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)} to obtain
49  	 * a RevTag from an existing repository.
50  	 *
51  	 * @param raw
52  	 *            the canonical formatted tag to be parsed.
53  	 * @return the parsed tag, in an isolated revision pool that is not
54  	 *         available to the caller.
55  	 * @throws org.eclipse.jgit.errors.CorruptObjectException
56  	 *             the tag contains a malformed header that cannot be handled.
57  	 */
58  	public static RevTag parse(byte[] raw) throws CorruptObjectException {
59  		return parse(new RevWalk((ObjectReader) null), raw);
60  	}
61  
62  	/**
63  	 * Parse an annotated tag from its canonical format.
64  	 * <p>
65  	 * This method inserts the tag directly into the caller supplied revision
66  	 * pool, making it appear as though the tag exists in the repository, even
67  	 * if it doesn't. The repository under the pool is not affected.
68  	 * <p>
69  	 * The body of the tag (message, tagger, signature) is always retained in
70  	 * the returned {@code RevTag}, even if the supplied {@code RevWalk} has
71  	 * been configured with {@code setRetainBody(false)}.
72  	 *
73  	 * @param rw
74  	 *            the revision pool to allocate the tag within. The tag's object
75  	 *            pointer will be obtained from this pool.
76  	 * @param raw
77  	 *            the canonical formatted tag to be parsed. This buffer will be
78  	 *            retained by the returned {@code RevTag} and must not be
79  	 *            modified by the caller.
80  	 * @return the parsed tag, in an isolated revision pool that is not
81  	 *         available to the caller.
82  	 * @throws org.eclipse.jgit.errors.CorruptObjectException
83  	 *             the tag contains a malformed header that cannot be handled.
84  	 */
85  	public static RevTag parse(RevWalk rw, byte[] raw)
86  			throws CorruptObjectException {
87  		try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) {
88  			RevTag r = rw.lookupTag(fmt.idFor(Constants.OBJ_TAG, raw));
89  			r.parseCanonical(rw, raw);
90  			r.buffer = raw;
91  			return r;
92  		}
93  	}
94  
95  	private RevObject object;
96  
97  	private byte[] buffer;
98  
99  	private String tagName;
100 
101 	/**
102 	 * Create a new tag reference.
103 	 *
104 	 * @param id
105 	 *            object name for the tag.
106 	 */
107 	protected RevTag(AnyObjectId id) {
108 		super(id);
109 	}
110 
111 	@Override
112 	void parseHeaders(RevWalk walk) throws MissingObjectException,
113 			IncorrectObjectTypeException, IOException {
114 		parseCanonical(walk, walk.getCachedBytes(this));
115 	}
116 
117 	@Override
118 	void parseBody(RevWalk walk) throws MissingObjectException,
119 			IncorrectObjectTypeException, IOException {
120 		if (buffer == null) {
121 			buffer = walk.getCachedBytes(this);
122 			if ((flags & PARSED) == 0)
123 				parseCanonical(walk, buffer);
124 		}
125 	}
126 
127 	void parseCanonical(RevWalk walk, byte[] rawTag)
128 			throws CorruptObjectException {
129 		final MutableIntegerger.html#MutableInteger">MutableInteger pos = new MutableInteger();
130 		final int oType;
131 
132 		pos.value = 53; // "object $sha1\ntype "
133 		oType = Constants.decodeTypeString(this, rawTag, (byte) '\n', pos);
134 		walk.idBuffer.fromString(rawTag, 7);
135 		object = walk.lookupAny(walk.idBuffer, oType);
136 
137 		int p = pos.value += 4; // "tag "
138 		final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1;
139 		tagName = RawParseUtils.decode(UTF_8, rawTag, p, nameEnd);
140 
141 		if (walk.isRetainBody())
142 			buffer = rawTag;
143 		flags |= PARSED;
144 	}
145 
146 	/** {@inheritDoc} */
147 	@Override
148 	public final int getType() {
149 		return Constants.OBJ_TAG;
150 	}
151 
152 	/**
153 	 * Parse the tagger identity from the raw buffer.
154 	 * <p>
155 	 * This method parses and returns the content of the tagger line, after
156 	 * taking the tag's character set into account and decoding the tagger
157 	 * name and email address. This method is fairly expensive and produces a
158 	 * new PersonIdent instance on each invocation. Callers should invoke this
159 	 * method only if they are certain they will be outputting the result, and
160 	 * should cache the return value for as long as necessary to use all
161 	 * information from it.
162 	 *
163 	 * @return identity of the tagger (name, email) and the time the tag
164 	 *         was made by the tagger; null if no tagger line was found.
165 	 */
166 	public final PersonIdent getTaggerIdent() {
167 		final byte[] raw = buffer;
168 		final int nameB = RawParseUtils.tagger(raw, 0);
169 		if (nameB < 0)
170 			return null;
171 		return RawParseUtils.parsePersonIdent(raw, nameB);
172 	}
173 
174 	/**
175 	 * Parse the complete tag message and decode it to a string.
176 	 * <p>
177 	 * This method parses and returns the message portion of the tag buffer,
178 	 * after taking the tag's character set into account and decoding the buffer
179 	 * using that character set. This method is a fairly expensive operation and
180 	 * produces a new string on each invocation.
181 	 *
182 	 * @return decoded tag message as a string. Never null.
183 	 */
184 	public final String getFullMessage() {
185 		byte[] raw = buffer;
186 		int msgB = RawParseUtils.tagMessage(raw, 0);
187 		if (msgB < 0) {
188 			return ""; //$NON-NLS-1$
189 		}
190 		return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length);
191 	}
192 
193 	/**
194 	 * Parse the tag message and return the first "line" of it.
195 	 * <p>
196 	 * The first line is everything up to the first pair of LFs. This is the
197 	 * "oneline" format, suitable for output in a single line display.
198 	 * <p>
199 	 * This method parses and returns the message portion of the tag buffer,
200 	 * after taking the tag's character set into account and decoding the buffer
201 	 * using that character set. This method is a fairly expensive operation and
202 	 * produces a new string on each invocation.
203 	 *
204 	 * @return decoded tag message as a string. Never null. The returned string
205 	 *         does not contain any LFs, even if the first paragraph spanned
206 	 *         multiple lines. Embedded LFs are converted to spaces.
207 	 */
208 	public final String getShortMessage() {
209 		byte[] raw = buffer;
210 		int msgB = RawParseUtils.tagMessage(raw, 0);
211 		if (msgB < 0) {
212 			return ""; //$NON-NLS-1$
213 		}
214 
215 		int msgE = RawParseUtils.endOfParagraph(raw, msgB);
216 		String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE);
217 		if (RevCommit.hasLF(raw, msgB, msgE)) {
218 			str = StringUtils.replaceLineBreaksWithSpace(str);
219 		}
220 		return str;
221 	}
222 
223 	private Charset guessEncoding() {
224 		try {
225 			return RawParseUtils.parseEncoding(buffer);
226 		} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
227 			return UTF_8;
228 		}
229 	}
230 
231 	/**
232 	 * Get a reference to the object this tag was placed on.
233 	 * <p>
234 	 * Note that the returned object has only been looked up (see
235 	 * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}. To
236 	 * access the contents it needs to be parsed, see
237 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseHeaders(RevObject)} and
238 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}.
239 	 * <p>
240 	 * As an alternative, use
241 	 * {@link org.eclipse.jgit.revwalk.RevWalk#peel(RevObject)} and pass this
242 	 * {@link org.eclipse.jgit.revwalk.RevTag} to peel it until the first
243 	 * non-tag object.
244 	 *
245 	 * @return object this tag refers to (only looked up, not parsed)
246 	 */
247 	public final RevObject getObject() {
248 		return object;
249 	}
250 
251 	/**
252 	 * Get the name of this tag, from the tag header.
253 	 *
254 	 * @return name of the tag, according to the tag header.
255 	 */
256 	public final String getTagName() {
257 		return tagName;
258 	}
259 
260 	/**
261 	 * Discard the message buffer to reduce memory usage.
262 	 * <p>
263 	 * After discarding the memory usage of the {@code RevTag} is reduced to
264 	 * only the {@link #getObject()} pointer and {@link #getTagName()}.
265 	 * Accessing other properties such as {@link #getTaggerIdent()} or either
266 	 * message function requires reloading the buffer by invoking
267 	 * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}.
268 	 *
269 	 * @since 4.0
270 	 */
271 	public final void disposeBody() {
272 		buffer = null;
273 	}
274 }