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