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