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