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