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