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.

NameRevCommand.java 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. /*
  2. * Copyright (C) 2013, Google Inc. and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.api;
  11. import java.io.IOException;
  12. import java.util.ArrayList;
  13. import java.util.HashMap;
  14. import java.util.LinkedHashMap;
  15. import java.util.List;
  16. import java.util.Map;
  17. import org.eclipse.jgit.api.errors.GitAPIException;
  18. import org.eclipse.jgit.api.errors.JGitInternalException;
  19. import org.eclipse.jgit.errors.MissingObjectException;
  20. import org.eclipse.jgit.lib.AnyObjectId;
  21. import org.eclipse.jgit.lib.Constants;
  22. import org.eclipse.jgit.lib.ObjectId;
  23. import org.eclipse.jgit.lib.Ref;
  24. import org.eclipse.jgit.lib.Repository;
  25. import org.eclipse.jgit.revwalk.FIFORevQueue;
  26. import org.eclipse.jgit.revwalk.RevCommit;
  27. import org.eclipse.jgit.revwalk.RevObject;
  28. import org.eclipse.jgit.revwalk.RevTag;
  29. import org.eclipse.jgit.revwalk.RevWalk;
  30. /**
  31. * Command to find human-readable names of revisions.
  32. *
  33. * @see <a
  34. * href="http://www.kernel.org/pub/software/scm/git/docs/git-name-rev.html"
  35. * >Git documentation about name-rev</a>
  36. * @since 3.0
  37. */
  38. public class NameRevCommand extends GitCommand<Map<ObjectId, String>> {
  39. /** Amount of slop to allow walking past the earliest requested commit. */
  40. private static final int COMMIT_TIME_SLOP = 60 * 60 * 24;
  41. /** Cost of traversing a merge commit compared to a linear history. */
  42. private static final int MERGE_COST = 65535;
  43. private static class NameRevCommit extends RevCommit {
  44. private String tip;
  45. private int distance;
  46. private long cost;
  47. private NameRevCommit(AnyObjectId id) {
  48. super(id);
  49. }
  50. private StringBuilder format() {
  51. StringBuilder sb = new StringBuilder(tip);
  52. if (distance > 0)
  53. sb.append('~').append(distance);
  54. return sb;
  55. }
  56. @Override
  57. public String toString() {
  58. StringBuilder sb = new StringBuilder(getClass().getSimpleName())
  59. .append('[');
  60. if (tip != null)
  61. sb.append(format());
  62. else
  63. sb.append((Object) null);
  64. sb.append(',').append(cost).append(']').append(' ')
  65. .append(super.toString()).toString();
  66. return sb.toString();
  67. }
  68. }
  69. private final RevWalk walk;
  70. private final List<String> prefixes;
  71. private final List<ObjectId> revs;
  72. private List<Ref> refs;
  73. private int mergeCost;
  74. /**
  75. * Create a new name-rev command.
  76. *
  77. * @param repo
  78. * the {@link org.eclipse.jgit.lib.Repository}
  79. */
  80. protected NameRevCommand(Repository repo) {
  81. super(repo);
  82. mergeCost = MERGE_COST;
  83. prefixes = new ArrayList<>(2);
  84. revs = new ArrayList<>(2);
  85. walk = new RevWalk(repo) {
  86. @Override
  87. public NameRevCommit createCommit(AnyObjectId id) {
  88. return new NameRevCommit(id);
  89. }
  90. };
  91. }
  92. /** {@inheritDoc} */
  93. @Override
  94. public Map<ObjectId, String> call() throws GitAPIException {
  95. try {
  96. Map<ObjectId, String> nonCommits = new HashMap<>();
  97. FIFORevQueue pending = new FIFORevQueue();
  98. if (refs != null) {
  99. for (Ref ref : refs)
  100. addRef(ref, nonCommits, pending);
  101. }
  102. addPrefixes(nonCommits, pending);
  103. int cutoff = minCommitTime() - COMMIT_TIME_SLOP;
  104. while (true) {
  105. NameRevCommit c = (NameRevCommit) pending.next();
  106. if (c == null)
  107. break;
  108. if (c.getCommitTime() < cutoff)
  109. continue;
  110. for (int i = 0; i < c.getParentCount(); i++) {
  111. NameRevCommit p = (NameRevCommit) walk.parseCommit(c.getParent(i));
  112. long cost = c.cost + (i > 0 ? mergeCost : 1);
  113. if (p.tip == null || compare(c.tip, cost, p.tip, p.cost) < 0) {
  114. if (i > 0) {
  115. p.tip = c.format().append('^').append(i + 1).toString();
  116. p.distance = 0;
  117. } else {
  118. p.tip = c.tip;
  119. p.distance = c.distance + 1;
  120. }
  121. p.cost = cost;
  122. pending.add(p);
  123. }
  124. }
  125. }
  126. Map<ObjectId, String> result =
  127. new LinkedHashMap<>(revs.size());
  128. for (ObjectId id : revs) {
  129. RevObject o = walk.parseAny(id);
  130. if (o instanceof NameRevCommit) {
  131. NameRevCommit c = (NameRevCommit) o;
  132. if (c.tip != null)
  133. result.put(id, simplify(c.format().toString()));
  134. } else {
  135. String name = nonCommits.get(id);
  136. if (name != null)
  137. result.put(id, simplify(name));
  138. }
  139. }
  140. setCallable(false);
  141. return result;
  142. } catch (IOException e) {
  143. throw new JGitInternalException(e.getMessage(), e);
  144. } finally {
  145. walk.close();
  146. }
  147. }
  148. /**
  149. * Add an object to search for.
  150. *
  151. * @param id
  152. * object ID to add.
  153. * @return {@code this}
  154. * @throws org.eclipse.jgit.errors.MissingObjectException
  155. * the object supplied is not available from the object
  156. * database.
  157. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  158. * a low-level exception of JGit has occurred. The original
  159. * exception can be retrieved by calling
  160. * {@link java.lang.Exception#getCause()}.
  161. */
  162. public NameRevCommand add(ObjectId id) throws MissingObjectException,
  163. JGitInternalException {
  164. checkCallable();
  165. try {
  166. walk.parseAny(id);
  167. } catch (MissingObjectException e) {
  168. throw e;
  169. } catch (IOException e) {
  170. throw new JGitInternalException(e.getMessage(), e);
  171. }
  172. revs.add(id.copy());
  173. return this;
  174. }
  175. /**
  176. * Add multiple objects to search for.
  177. *
  178. * @param ids
  179. * object IDs to add.
  180. * @return {@code this}
  181. * @throws org.eclipse.jgit.errors.MissingObjectException
  182. * the object supplied is not available from the object
  183. * database.
  184. * @throws org.eclipse.jgit.api.errors.JGitInternalException
  185. * a low-level exception of JGit has occurred. The original
  186. * exception can be retrieved by calling
  187. * {@link java.lang.Exception#getCause()}.
  188. */
  189. public NameRevCommand add(Iterable<ObjectId> ids)
  190. throws MissingObjectException, JGitInternalException {
  191. for (ObjectId id : ids)
  192. add(id);
  193. return this;
  194. }
  195. /**
  196. * Add a ref prefix to the set that results must match.
  197. * <p>
  198. * If an object matches multiple refs equally well, the first matching ref
  199. * added with {@link #addRef(Ref)} is preferred, or else the first matching
  200. * prefix added by {@link #addPrefix(String)}.
  201. *
  202. * @param prefix
  203. * prefix to add; the prefix must end with a slash
  204. * @return {@code this}
  205. */
  206. public NameRevCommand addPrefix(String prefix) {
  207. checkCallable();
  208. prefixes.add(prefix);
  209. return this;
  210. }
  211. /**
  212. * Add all annotated tags under {@code refs/tags/} to the set that all
  213. * results must match.
  214. * <p>
  215. * Calls {@link #addRef(Ref)}; see that method for a note on matching
  216. * priority.
  217. *
  218. * @return {@code this}
  219. * @throws JGitInternalException
  220. * a low-level exception of JGit has occurred. The original
  221. * exception can be retrieved by calling
  222. * {@link java.lang.Exception#getCause()}.
  223. */
  224. public NameRevCommand addAnnotatedTags() {
  225. checkCallable();
  226. if (refs == null)
  227. refs = new ArrayList<>();
  228. try {
  229. for (Ref ref : repo.getRefDatabase()
  230. .getRefsByPrefix(Constants.R_TAGS)) {
  231. ObjectId id = ref.getObjectId();
  232. if (id != null && (walk.parseAny(id) instanceof RevTag))
  233. addRef(ref);
  234. }
  235. } catch (IOException e) {
  236. throw new JGitInternalException(e.getMessage(), e);
  237. }
  238. return this;
  239. }
  240. /**
  241. * Add a ref to the set that all results must match.
  242. * <p>
  243. * If an object matches multiple refs equally well, the first matching ref
  244. * added with {@link #addRef(Ref)} is preferred, or else the first matching
  245. * prefix added by {@link #addPrefix(String)}.
  246. *
  247. * @param ref
  248. * ref to add.
  249. * @return {@code this}
  250. */
  251. public NameRevCommand addRef(Ref ref) {
  252. checkCallable();
  253. if (refs == null)
  254. refs = new ArrayList<>();
  255. refs.add(ref);
  256. return this;
  257. }
  258. NameRevCommand setMergeCost(int cost) {
  259. mergeCost = cost;
  260. return this;
  261. }
  262. private void addPrefixes(Map<ObjectId, String> nonCommits,
  263. FIFORevQueue pending) throws IOException {
  264. if (!prefixes.isEmpty()) {
  265. for (String prefix : prefixes)
  266. addPrefix(prefix, nonCommits, pending);
  267. } else if (refs == null)
  268. addPrefix(Constants.R_REFS, nonCommits, pending);
  269. }
  270. private void addPrefix(String prefix, Map<ObjectId, String> nonCommits,
  271. FIFORevQueue pending) throws IOException {
  272. for (Ref ref : repo.getRefDatabase().getRefsByPrefix(prefix))
  273. addRef(ref, nonCommits, pending);
  274. }
  275. private void addRef(Ref ref, Map<ObjectId, String> nonCommits,
  276. FIFORevQueue pending) throws IOException {
  277. if (ref.getObjectId() == null)
  278. return;
  279. RevObject o = walk.parseAny(ref.getObjectId());
  280. while (o instanceof RevTag) {
  281. RevTag t = (RevTag) o;
  282. nonCommits.put(o, ref.getName());
  283. o = t.getObject();
  284. walk.parseHeaders(o);
  285. }
  286. if (o instanceof NameRevCommit) {
  287. NameRevCommit c = (NameRevCommit) o;
  288. if (c.tip == null)
  289. c.tip = ref.getName();
  290. pending.add(c);
  291. } else if (!nonCommits.containsKey(o))
  292. nonCommits.put(o, ref.getName());
  293. }
  294. private int minCommitTime() throws IOException {
  295. int min = Integer.MAX_VALUE;
  296. for (ObjectId id : revs) {
  297. RevObject o = walk.parseAny(id);
  298. while (o instanceof RevTag) {
  299. o = ((RevTag) o).getObject();
  300. walk.parseHeaders(o);
  301. }
  302. if (o instanceof RevCommit) {
  303. RevCommit c = (RevCommit) o;
  304. if (c.getCommitTime() < min)
  305. min = c.getCommitTime();
  306. }
  307. }
  308. return min;
  309. }
  310. private long compare(String leftTip, long leftCost, String rightTip, long rightCost) {
  311. long c = leftCost - rightCost;
  312. if (c != 0 || prefixes.isEmpty())
  313. return c;
  314. int li = -1;
  315. int ri = -1;
  316. for (int i = 0; i < prefixes.size(); i++) {
  317. String prefix = prefixes.get(i);
  318. if (li < 0 && leftTip.startsWith(prefix))
  319. li = i;
  320. if (ri < 0 && rightTip.startsWith(prefix))
  321. ri = i;
  322. }
  323. // Don't tiebreak if prefixes are the same, in order to prefer first-parent
  324. // paths.
  325. return li - ri;
  326. }
  327. private static String simplify(String refName) {
  328. if (refName.startsWith(Constants.R_HEADS))
  329. return refName.substring(Constants.R_HEADS.length());
  330. if (refName.startsWith(Constants.R_TAGS))
  331. return refName.substring(Constants.R_TAGS.length());
  332. if (refName.startsWith(Constants.R_REFS))
  333. return refName.substring(Constants.R_REFS.length());
  334. return refName;
  335. }
  336. }