You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RevTag.java 12KB

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