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 }