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> 5 * and other copyright owners as documented in the project's IP log. 6 * 7 * This program and the accompanying materials are made available 8 * under the terms of the Eclipse Distribution License v1.0 which 9 * accompanies this distribution, is reproduced below, and is 10 * available at http://www.eclipse.org/org/documents/edl-v10.php 11 * 12 * All rights reserved. 13 * 14 * Redistribution and use in source and binary forms, with or 15 * without modification, are permitted provided that the following 16 * conditions are met: 17 * 18 * - Redistributions of source code must retain the above copyright 19 * notice, this list of conditions and the following disclaimer. 20 * 21 * - Redistributions in binary form must reproduce the above 22 * copyright notice, this list of conditions and the following 23 * disclaimer in the documentation and/or other materials provided 24 * with the distribution. 25 * 26 * - Neither the name of the Eclipse Foundation, Inc. nor the 27 * names of its contributors may be used to endorse or promote 28 * products derived from this software without specific prior 29 * written permission. 30 * 31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 32 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 33 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 36 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 38 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 39 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 40 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 41 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 43 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 44 */ 45 46 package org.eclipse.jgit.revwalk; 47 48 import static org.eclipse.jgit.lib.Constants.CHARSET; 49 50 import java.io.IOException; 51 import java.nio.charset.Charset; 52 import java.nio.charset.IllegalCharsetNameException; 53 import java.nio.charset.UnsupportedCharsetException; 54 55 import org.eclipse.jgit.errors.CorruptObjectException; 56 import org.eclipse.jgit.errors.IncorrectObjectTypeException; 57 import org.eclipse.jgit.errors.MissingObjectException; 58 import org.eclipse.jgit.lib.AnyObjectId; 59 import org.eclipse.jgit.lib.Constants; 60 import org.eclipse.jgit.lib.ObjectInserter; 61 import org.eclipse.jgit.lib.ObjectReader; 62 import org.eclipse.jgit.lib.PersonIdent; 63 import org.eclipse.jgit.util.MutableInteger; 64 import org.eclipse.jgit.util.RawParseUtils; 65 import org.eclipse.jgit.util.StringUtils; 66 67 /** 68 * An annotated tag. 69 */ 70 public class RevTag extends RevObject { 71 /** 72 * Parse an annotated tag from its canonical format. 73 * 74 * This method constructs a temporary revision pool, parses the tag as 75 * supplied, and returns it to the caller. Since the tag was built inside of 76 * a private revision pool its object pointer will be initialized, but will 77 * not have its headers loaded. 78 * 79 * Applications are discouraged from using this API. Callers usually need 80 * more than one object. Use 81 * {@link org.eclipse.jgit.revwalk.RevWalk#parseTag(AnyObjectId)} to obtain 82 * a RevTag from an existing repository. 83 * 84 * @param raw 85 * the canonical formatted tag to be parsed. 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(byte[] raw) throws CorruptObjectException { 92 return parse(new RevWalk((ObjectReader) null), raw); 93 } 94 95 /** 96 * Parse an annotated tag from its canonical format. 97 * <p> 98 * This method inserts the tag directly into the caller supplied revision 99 * pool, making it appear as though the tag exists in the repository, even 100 * if it doesn't. The repository under the pool is not affected. 101 * <p> 102 * The body of the tag (message, tagger, signature) is always retained in 103 * the returned {@code RevTag}, even if the supplied {@code RevWalk} has 104 * been configured with {@code setRetainBody(false)}. 105 * 106 * @param rw 107 * the revision pool to allocate the tag within. The tag's object 108 * pointer will be obtained from this pool. 109 * @param raw 110 * the canonical formatted tag to be parsed. This buffer will be 111 * retained by the returned {@code RevTag} and must not be 112 * modified by the caller. 113 * @return the parsed tag, in an isolated revision pool that is not 114 * available to the caller. 115 * @throws org.eclipse.jgit.errors.CorruptObjectException 116 * the tag contains a malformed header that cannot be handled. 117 */ 118 public static RevTag parse(RevWalk rw, byte[] raw) 119 throws CorruptObjectException { 120 try (ObjectInserter.Formatter fmt = new ObjectInserter.Formatter()) { 121 RevTag r = rw.lookupTag(fmt.idFor(Constants.OBJ_TAG, raw)); 122 r.parseCanonical(rw, raw); 123 r.buffer = raw; 124 return r; 125 } 126 } 127 128 private RevObject object; 129 130 private byte[] buffer; 131 132 private String tagName; 133 134 /** 135 * Create a new tag reference. 136 * 137 * @param id 138 * object name for the tag. 139 */ 140 protected RevTag(AnyObjectId id) { 141 super(id); 142 } 143 144 @Override 145 void parseHeaders(RevWalk walk) throws MissingObjectException, 146 IncorrectObjectTypeException, IOException { 147 parseCanonical(walk, walk.getCachedBytes(this)); 148 } 149 150 @Override 151 void parseBody(RevWalk walk) throws MissingObjectException, 152 IncorrectObjectTypeException, IOException { 153 if (buffer == null) { 154 buffer = walk.getCachedBytes(this); 155 if ((flags & PARSED) == 0) 156 parseCanonical(walk, buffer); 157 } 158 } 159 160 void parseCanonical(RevWalk walk, byte[] rawTag) 161 throws CorruptObjectException { 162 final MutableInteger pos = new MutableInteger(); 163 final int oType; 164 165 pos.value = 53; // "object $sha1\ntype " 166 oType = Constants.decodeTypeString(this, rawTag, (byte) '\n', pos); 167 walk.idBuffer.fromString(rawTag, 7); 168 object = walk.lookupAny(walk.idBuffer, oType); 169 170 int p = pos.value += 4; // "tag " 171 final int nameEnd = RawParseUtils.nextLF(rawTag, p) - 1; 172 tagName = RawParseUtils.decode(CHARSET, rawTag, p, nameEnd); 173 174 if (walk.isRetainBody()) 175 buffer = rawTag; 176 flags |= PARSED; 177 } 178 179 /** {@inheritDoc} */ 180 @Override 181 public final int getType() { 182 return Constants.OBJ_TAG; 183 } 184 185 /** 186 * Parse the tagger identity from the raw buffer. 187 * <p> 188 * This method parses and returns the content of the tagger line, after 189 * taking the tag's character set into account and decoding the tagger 190 * name and email address. This method is fairly expensive and produces a 191 * new PersonIdent instance on each invocation. Callers should invoke this 192 * method only if they are certain they will be outputting the result, and 193 * should cache the return value for as long as necessary to use all 194 * information from it. 195 * 196 * @return identity of the tagger (name, email) and the time the tag 197 * was made by the tagger; null if no tagger line was found. 198 */ 199 public final PersonIdent getTaggerIdent() { 200 final byte[] raw = buffer; 201 final int nameB = RawParseUtils.tagger(raw, 0); 202 if (nameB < 0) 203 return null; 204 return RawParseUtils.parsePersonIdent(raw, nameB); 205 } 206 207 /** 208 * Parse the complete tag message and decode it to a string. 209 * <p> 210 * This method parses and returns the message portion of the tag buffer, 211 * after taking the tag's character set into account and decoding the buffer 212 * using that character set. This method is a fairly expensive operation and 213 * produces a new string on each invocation. 214 * 215 * @return decoded tag message as a string. Never null. 216 */ 217 public final String getFullMessage() { 218 byte[] raw = buffer; 219 int msgB = RawParseUtils.tagMessage(raw, 0); 220 if (msgB < 0) { 221 return ""; //$NON-NLS-1$ 222 } 223 return RawParseUtils.decode(guessEncoding(), raw, msgB, raw.length); 224 } 225 226 /** 227 * Parse the tag message and return the first "line" of it. 228 * <p> 229 * The first line is everything up to the first pair of LFs. This is the 230 * "oneline" format, suitable for output in a single line display. 231 * <p> 232 * This method parses and returns the message portion of the tag buffer, 233 * after taking the tag's character set into account and decoding the buffer 234 * using that character set. This method is a fairly expensive operation and 235 * produces a new string on each invocation. 236 * 237 * @return decoded tag message as a string. Never null. The returned string 238 * does not contain any LFs, even if the first paragraph spanned 239 * multiple lines. Embedded LFs are converted to spaces. 240 */ 241 public final String getShortMessage() { 242 byte[] raw = buffer; 243 int msgB = RawParseUtils.tagMessage(raw, 0); 244 if (msgB < 0) { 245 return ""; //$NON-NLS-1$ 246 } 247 248 int msgE = RawParseUtils.endOfParagraph(raw, msgB); 249 String str = RawParseUtils.decode(guessEncoding(), raw, msgB, msgE); 250 if (RevCommit.hasLF(raw, msgB, msgE)) { 251 str = StringUtils.replaceLineBreaksWithSpace(str); 252 } 253 return str; 254 } 255 256 private Charset guessEncoding() { 257 try { 258 return RawParseUtils.parseEncoding(buffer); 259 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) { 260 return CHARSET; 261 } 262 } 263 264 /** 265 * Get a reference to the object this tag was placed on. 266 * <p> 267 * Note that the returned object has only been looked up (see 268 * {@link org.eclipse.jgit.revwalk.RevWalk#lookupAny(AnyObjectId, int)}. To 269 * access the contents it needs to be parsed, see 270 * {@link org.eclipse.jgit.revwalk.RevWalk#parseHeaders(RevObject)} and 271 * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}. 272 * <p> 273 * As an alternative, use 274 * {@link org.eclipse.jgit.revwalk.RevWalk#peel(RevObject)} and pass this 275 * {@link org.eclipse.jgit.revwalk.RevTag} to peel it until the first 276 * non-tag object. 277 * 278 * @return object this tag refers to (only looked up, not parsed) 279 */ 280 public final RevObject getObject() { 281 return object; 282 } 283 284 /** 285 * Get the name of this tag, from the tag header. 286 * 287 * @return name of the tag, according to the tag header. 288 */ 289 public final String getTagName() { 290 return tagName; 291 } 292 293 /** 294 * Discard the message buffer to reduce memory usage. 295 * <p> 296 * After discarding the memory usage of the {@code RevTag} is reduced to 297 * only the {@link #getObject()} pointer and {@link #getTagName()}. 298 * Accessing other properties such as {@link #getTaggerIdent()} or either 299 * message function requires reloading the buffer by invoking 300 * {@link org.eclipse.jgit.revwalk.RevWalk#parseBody(RevObject)}. 301 * 302 * @since 4.0 303 */ 304 public final void disposeBody() { 305 buffer = null; 306 } 307 }