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
44 package org.eclipse.jgit.api;
45
46 import java.io.IOException;
47 import java.util.ArrayList;
48 import java.util.HashMap;
49 import java.util.LinkedHashMap;
50 import java.util.List;
51 import java.util.Map;
52
53 import org.eclipse.jgit.api.errors.GitAPIException;
54 import org.eclipse.jgit.api.errors.JGitInternalException;
55 import org.eclipse.jgit.errors.MissingObjectException;
56 import org.eclipse.jgit.lib.AnyObjectId;
57 import org.eclipse.jgit.lib.Constants;
58 import org.eclipse.jgit.lib.ObjectId;
59 import org.eclipse.jgit.lib.Ref;
60 import org.eclipse.jgit.lib.Repository;
61 import org.eclipse.jgit.revwalk.FIFORevQueue;
62 import org.eclipse.jgit.revwalk.RevCommit;
63 import org.eclipse.jgit.revwalk.RevObject;
64 import org.eclipse.jgit.revwalk.RevTag;
65 import org.eclipse.jgit.revwalk.RevWalk;
66
67
68
69
70
71
72
73
74
75 public class NameRevCommand extends GitCommand<Map<ObjectId, String>> {
76
77 private static final int COMMIT_TIME_SLOP = 60 * 60 * 24;
78
79
80 private static final int MERGE_COST = 65535;
81
82 private static class NameRevCommit extends RevCommit {
83 private String tip;
84 private int distance;
85 private long cost;
86
87 private NameRevCommit(AnyObjectId id) {
88 super(id);
89 }
90
91 private StringBuilder format() {
92 StringBuilder sb = new StringBuilder(tip);
93 if (distance > 0)
94 sb.append('~').append(distance);
95 return sb;
96 }
97
98 @Override
99 public String toString() {
100 StringBuilder sb = new StringBuilder(getClass().getSimpleName())
101 .append('[');
102 if (tip != null)
103 sb.append(format());
104 else
105 sb.append((Object) null);
106 sb.append(',').append(cost).append(']').append(' ')
107 .append(super.toString()).toString();
108 return sb.toString();
109 }
110 }
111
112 private final RevWalk walk;
113 private final List<String> prefixes;
114 private final List<ObjectId> revs;
115 private List<Ref> refs;
116 private int mergeCost;
117
118
119
120
121
122
123
124 protected NameRevCommand(Repository repo) {
125 super(repo);
126 mergeCost = MERGE_COST;
127 prefixes = new ArrayList<>(2);
128 revs = new ArrayList<>(2);
129 walk = new RevWalk(repo) {
130 @Override
131 public NameRevCommit createCommit(AnyObjectId id) {
132 return new NameRevCommit(id);
133 }
134 };
135 }
136
137
138 @Override
139 public Map<ObjectId, String> call() throws GitAPIException {
140 try {
141 Map<ObjectId, String> nonCommits = new HashMap<>();
142 FIFORevQueue pending = new FIFORevQueue();
143 if (refs != null) {
144 for (Ref ref : refs)
145 addRef(ref, nonCommits, pending);
146 }
147 addPrefixes(nonCommits, pending);
148 int cutoff = minCommitTime() - COMMIT_TIME_SLOP;
149
150 while (true) {
151 NameRevCommit c = (NameRevCommit) pending.next();
152 if (c == null)
153 break;
154 if (c.getCommitTime() < cutoff)
155 continue;
156 for (int i = 0; i < c.getParentCount(); i++) {
157 NameRevCommit p = (NameRevCommit) walk.parseCommit(c.getParent(i));
158 long cost = c.cost + (i > 0 ? mergeCost : 1);
159 if (p.tip == null || compare(c.tip, cost, p.tip, p.cost) < 0) {
160 if (i > 0) {
161 p.tip = c.format().append('^').append(i + 1).toString();
162 p.distance = 0;
163 } else {
164 p.tip = c.tip;
165 p.distance = c.distance + 1;
166 }
167 p.cost = cost;
168 pending.add(p);
169 }
170 }
171 }
172
173 Map<ObjectId, String> result =
174 new LinkedHashMap<>(revs.size());
175 for (ObjectId id : revs) {
176 RevObject o = walk.parseAny(id);
177 if (o instanceof NameRevCommit) {
178 NameRevCommit c = (NameRevCommit) o;
179 if (c.tip != null)
180 result.put(id, simplify(c.format().toString()));
181 } else {
182 String name = nonCommits.get(id);
183 if (name != null)
184 result.put(id, simplify(name));
185 }
186 }
187
188 setCallable(false);
189 return result;
190 } catch (IOException e) {
191 throw new JGitInternalException(e.getMessage(), e);
192 } finally {
193 walk.close();
194 }
195 }
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211 public NameRevCommand add(ObjectId id) throws MissingObjectException,
212 JGitInternalException {
213 checkCallable();
214 try {
215 walk.parseAny(id);
216 } catch (MissingObjectException e) {
217 throw e;
218 } catch (IOException e) {
219 throw new JGitInternalException(e.getMessage(), e);
220 }
221 revs.add(id.copy());
222 return this;
223 }
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 public NameRevCommand add(Iterable<ObjectId> ids)
240 throws MissingObjectException, JGitInternalException {
241 for (ObjectId id : ids)
242 add(id);
243 return this;
244 }
245
246
247
248
249
250
251
252
253
254
255
256
257
258 public NameRevCommand addPrefix(String prefix) {
259 checkCallable();
260 prefixes.add(prefix);
261 return this;
262 }
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277 public NameRevCommand addAnnotatedTags() {
278 checkCallable();
279 if (refs == null)
280 refs = new ArrayList<>();
281 try {
282 for (Ref ref : repo.getRefDatabase().getRefs(Constants.R_TAGS).values()) {
283 ObjectId id = ref.getObjectId();
284 if (id != null && (walk.parseAny(id) instanceof RevTag))
285 addRef(ref);
286 }
287 } catch (IOException e) {
288 throw new JGitInternalException(e.getMessage(), e);
289 }
290 return this;
291 }
292
293
294
295
296
297
298
299
300
301
302
303
304 public NameRevCommand addRef(Ref ref) {
305 checkCallable();
306 if (refs == null)
307 refs = new ArrayList<>();
308 refs.add(ref);
309 return this;
310 }
311
312 NameRevCommand setMergeCost(int cost) {
313 mergeCost = cost;
314 return this;
315 }
316
317 private void addPrefixes(Map<ObjectId, String> nonCommits,
318 FIFORevQueue pending) throws IOException {
319 if (!prefixes.isEmpty()) {
320 for (String prefix : prefixes)
321 addPrefix(prefix, nonCommits, pending);
322 } else if (refs == null)
323 addPrefix(Constants.R_REFS, nonCommits, pending);
324 }
325
326 private void addPrefix(String prefix, Map<ObjectId, String> nonCommits,
327 FIFORevQueue pending) throws IOException {
328 for (Ref ref : repo.getRefDatabase().getRefs(prefix).values())
329 addRef(ref, nonCommits, pending);
330 }
331
332 private void addRef(Ref ref, Map<ObjectId, String> nonCommits,
333 FIFORevQueue pending) throws IOException {
334 if (ref.getObjectId() == null)
335 return;
336 RevObject o = walk.parseAny(ref.getObjectId());
337 while (o instanceof RevTag) {
338 RevTag t = (RevTag) o;
339 nonCommits.put(o, ref.getName());
340 o = t.getObject();
341 walk.parseHeaders(o);
342 }
343 if (o instanceof NameRevCommit) {
344 NameRevCommit c = (NameRevCommit) o;
345 if (c.tip == null)
346 c.tip = ref.getName();
347 pending.add(c);
348 } else if (!nonCommits.containsKey(o))
349 nonCommits.put(o, ref.getName());
350 }
351
352 private int minCommitTime() throws IOException {
353 int min = Integer.MAX_VALUE;
354 for (ObjectId id : revs) {
355 RevObject o = walk.parseAny(id);
356 while (o instanceof RevTag) {
357 o = ((RevTag) o).getObject();
358 walk.parseHeaders(o);
359 }
360 if (o instanceof RevCommit) {
361 RevCommit c = (RevCommit) o;
362 if (c.getCommitTime() < min)
363 min = c.getCommitTime();
364 }
365 }
366 return min;
367 }
368
369 private long compare(String leftTip, long leftCost, String rightTip, long rightCost) {
370 long c = leftCost - rightCost;
371 if (c != 0 || prefixes.isEmpty())
372 return c;
373 int li = -1;
374 int ri = -1;
375 for (int i = 0; i < prefixes.size(); i++) {
376 String prefix = prefixes.get(i);
377 if (li < 0 && leftTip.startsWith(prefix))
378 li = i;
379 if (ri < 0 && rightTip.startsWith(prefix))
380 ri = i;
381 }
382
383
384 return li - ri;
385 }
386
387 private static String simplify(String refName) {
388 if (refName.startsWith(Constants.R_HEADS))
389 return refName.substring(Constants.R_HEADS.length());
390 if (refName.startsWith(Constants.R_TAGS))
391 return refName.substring(Constants.R_TAGS.length());
392 if (refName.startsWith(Constants.R_REFS))
393 return refName.substring(Constants.R_REFS.length());
394 return refName;
395 }
396 }