1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.api;
44
45 import static org.eclipse.jgit.lib.Constants.R_TAGS;
46
47 import java.io.IOException;
48 import java.text.MessageFormat;
49 import java.util.ArrayList;
50 import java.util.Collection;
51 import java.util.Collections;
52 import java.util.Comparator;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Optional;
56 import java.util.stream.Collectors;
57
58 import org.eclipse.jgit.api.errors.GitAPIException;
59 import org.eclipse.jgit.api.errors.JGitInternalException;
60 import org.eclipse.jgit.api.errors.RefNotFoundException;
61 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
62 import org.eclipse.jgit.errors.InvalidPatternException;
63 import org.eclipse.jgit.errors.MissingObjectException;
64 import org.eclipse.jgit.ignore.internal.IMatcher;
65 import org.eclipse.jgit.ignore.internal.PathMatcher;
66 import org.eclipse.jgit.internal.JGitText;
67 import org.eclipse.jgit.lib.Constants;
68 import org.eclipse.jgit.lib.ObjectId;
69 import org.eclipse.jgit.lib.Ref;
70 import org.eclipse.jgit.lib.Repository;
71 import org.eclipse.jgit.revwalk.RevCommit;
72 import org.eclipse.jgit.revwalk.RevFlag;
73 import org.eclipse.jgit.revwalk.RevFlagSet;
74 import org.eclipse.jgit.revwalk.RevWalk;
75
76
77
78
79
80
81 public class DescribeCommand extends GitCommand<String> {
82 private final RevWalk w;
83
84
85
86
87 private RevCommit target;
88
89
90
91
92
93
94 private int maxCandidates = 10;
95
96
97
98
99 private boolean longDesc;
100
101
102
103
104 private List<IMatcher> matchers = new ArrayList<>();
105
106
107
108
109 private boolean useTags = false;
110
111
112
113
114
115
116
117 protected DescribeCommand(Repository repo) {
118 super(repo);
119 w = new RevWalk(repo);
120 w.setRetainBody(false);
121 }
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136 public DescribeCommand setTarget(ObjectId target) throws IOException {
137 this.target = w.parseCommit(target);
138 return this;
139 }
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156 public DescribeCommand setTarget(String rev) throws IOException,
157 RefNotFoundException {
158 ObjectId id = repo.resolve(rev);
159 if (id == null)
160 throw new RefNotFoundException(MessageFormat.format(JGitText.get().refNotResolved, rev));
161 return setTarget(id);
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176 public DescribeCommand setLong(boolean longDesc) {
177 this.longDesc = longDesc;
178 return this;
179 }
180
181
182
183
184
185
186
187
188
189
190
191
192 public DescribeCommand setTags(boolean tags) {
193 this.useTags = tags;
194 return this;
195 }
196
197 private String longDescription(Ref tag, int depth, ObjectId tip)
198 throws IOException {
199 return String.format(
200 "%s-%d-g%s", tag.getName().substring(R_TAGS.length()),
201 Integer.valueOf(depth), w.getObjectReader().abbreviate(tip)
202 .name());
203 }
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220 public DescribeCommand setMatch(String... patterns) throws InvalidPatternException {
221 for (String p : patterns) {
222 matchers.add(PathMatcher.createPathMatcher(p, null, false));
223 }
224 return this;
225 }
226
227 private Optional<Ref> getBestMatch(List<Ref> tags) {
228 if (tags == null || tags.size() == 0) {
229 return Optional.empty();
230 } else if (matchers.size() == 0) {
231
232 return Optional.of(tags.get(0));
233 } else {
234
235 for (IMatcher matcher : matchers) {
236 Optional<Ref> match = tags.stream()
237 .filter(tag -> matcher.matches(tag.getName(), false,
238 false))
239 .findFirst();
240 if (match.isPresent()) {
241 return match;
242 }
243 }
244 return Optional.empty();
245 }
246 }
247
248 private ObjectId getObjectIdFromRef(Ref r) throws JGitInternalException {
249 try {
250 ObjectId key = repo.getRefDatabase().peel(r).getPeeledObjectId();
251 if (key == null) {
252 key = r.getObjectId();
253 }
254 return key;
255 } catch (IOException e) {
256 throw new JGitInternalException(e.getMessage(), e);
257 }
258 }
259
260
261
262
263
264
265
266 @Override
267 public String call() throws GitAPIException {
268 try {
269 checkCallable();
270 if (target == null) {
271 setTarget(Constants.HEAD);
272 }
273
274 Collection<Ref> tagList = repo.getRefDatabase()
275 .getRefsByPrefix(R_TAGS);
276 Map<ObjectId, List<Ref>> tags = tagList.stream()
277 .filter(this::filterLightweightTags)
278 .collect(Collectors.groupingBy(this::getObjectIdFromRef));
279
280
281 final RevFlagSet allFlags = new RevFlagSet();
282
283
284
285
286 class Candidate {
287 final Ref tag;
288 final RevFlag flag;
289
290
291
292
293
294 int depth;
295
296 Candidate(RevCommit commit, Ref tag) {
297 this.tag = tag;
298 this.flag = w.newFlag(tag.getName());
299
300 allFlags.add(flag);
301 w.carry(flag);
302 commit.add(flag);
303
304
305
306
307 commit.carry(flag);
308 }
309
310
311
312
313 boolean reaches(RevCommit c) {
314 return c.has(flag);
315 }
316
317 String describe(ObjectId tip) throws IOException {
318 return longDescription(tag, depth, tip);
319 }
320
321 }
322 List<Candidate> candidates = new ArrayList<>();
323
324
325 Optional<Ref> bestMatch = getBestMatch(tags.get(target));
326 if (bestMatch.isPresent()) {
327 return longDesc ? longDescription(bestMatch.get(), 0, target) :
328 bestMatch.get().getName().substring(R_TAGS.length());
329 }
330
331 w.markStart(target);
332
333 int seen = 0;
334 RevCommit c;
335 while ((c = w.next()) != null) {
336 if (!c.hasAny(allFlags)) {
337
338
339
340 bestMatch = getBestMatch(tags.get(c));
341 if (bestMatch.isPresent()) {
342 Candidate cd = new Candidate(c, bestMatch.get());
343 candidates.add(cd);
344 cd.depth = seen;
345 }
346 }
347
348
349
350 for (Candidate cd : candidates) {
351 if (!cd.reaches(c))
352 cd.depth++;
353 }
354
355
356
357
358 if (candidates.size() >= maxCandidates)
359 break;
360
361
362
363
364 seen++;
365 }
366
367
368
369 while ((c = w.next()) != null) {
370 if (c.hasAll(allFlags)) {
371
372 for (RevCommit p : c.getParents())
373 p.add(RevFlag.SEEN);
374 } else {
375 for (Candidate cd : candidates) {
376 if (!cd.reaches(c))
377 cd.depth++;
378 }
379 }
380 }
381
382
383 if (candidates.isEmpty())
384 return null;
385
386 Candidate best = Collections.min(candidates, new Comparator<Candidate>() {
387 @Override
388 public int compare(Candidate o1, Candidate o2) {
389 return o1.depth - o2.depth;
390 }
391 });
392
393 return best.describe(target);
394 } catch (IOException e) {
395 throw new JGitInternalException(e.getMessage(), e);
396 } finally {
397 setCallable(false);
398 w.close();
399 }
400 }
401
402
403
404
405
406
407
408
409
410 @SuppressWarnings("null")
411 private boolean filterLightweightTags(Ref ref) {
412 ObjectId id = ref.getObjectId();
413 try {
414 return this.useTags || (id != null && (w.parseTag(id) != null));
415 } catch (IOException e) {
416 return false;
417 }
418 }
419 }