1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.internal.storage.reftable;
12
13 import java.io.IOException;
14 import java.util.ArrayList;
15 import java.util.List;
16 import java.util.PriorityQueue;
17
18 import org.eclipse.jgit.lib.AnyObjectId;
19 import org.eclipse.jgit.lib.Ref;
20 import org.eclipse.jgit.lib.ReflogEntry;
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37 public class MergedReftable extends Reftable {
38 private final ReftableReader[] tables;
39
40
41
42
43
44
45
46
47
48
49
50 public MergedReftable(List<ReftableReader> tableStack) {
51 tables = tableStack.toArray(new ReftableReader[0]);
52
53
54
55 for (ReftableReader t : tables) {
56 t.setIncludeDeletes(true);
57 }
58 }
59
60
61
62
63 @Override
64 public long maxUpdateIndex() throws IOException {
65 if (tables.length == 0) {
66 return 0;
67 }
68 long maxUpdateIndex = tables[tables.length - 1].maxUpdateIndex();
69 for (int i = tables.length - 2; i >= 0; i--) {
70 if (maxUpdateIndex < tables[i].maxUpdateIndex()) {
71 maxUpdateIndex = tables[i].maxUpdateIndex();
72 }
73 }
74 return maxUpdateIndex;
75 }
76
77
78
79
80 @Override
81 public long minUpdateIndex() throws IOException {
82 if (tables.length == 0) {
83 return 0;
84 }
85 long minUpdateIndex = tables[0].minUpdateIndex();
86 for (int i = 1; i < tables.length; i++) {
87 if (tables[i].minUpdateIndex() < minUpdateIndex) {
88 minUpdateIndex = tables[i].minUpdateIndex();
89 }
90 }
91 return minUpdateIndex;
92 }
93
94
95 @Override
96 public boolean hasObjectMap() throws IOException {
97 boolean has = true;
98 for (int i = 0; has && i < tables.length; i++) {
99 has = has && tables[i].hasObjectMap();
100 }
101 return has;
102 }
103
104
105 @Override
106 public RefCursor allRefs() throws IOException {
107 MergedRefCursor m = new MergedRefCursor();
108 for (int i = 0; i < tables.length; i++) {
109 m.add(new RefQueueEntry(tables[i].allRefs(), i));
110 }
111 return m;
112 }
113
114
115 @Override
116 public RefCursor seekRef(String name) throws IOException {
117 MergedRefCursor m = new MergedRefCursor();
118 for (int i = 0; i < tables.length; i++) {
119 m.add(new RefQueueEntry(tables[i].seekRef(name), i));
120 }
121 return m;
122 }
123
124
125 @Override
126 public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
127 MergedRefCursor m = new MergedRefCursor();
128 for (int i = 0; i < tables.length; i++) {
129 m.add(new RefQueueEntry(tables[i].seekRefsWithPrefix(prefix), i));
130 }
131 return m;
132 }
133
134
135 @Override
136 public RefCursor byObjectId(AnyObjectId name) throws IOException {
137 MergedRefCursor m = new FilteringMergedRefCursor(name);
138 for (int i = 0; i < tables.length; i++) {
139 m.add(new RefQueueEntry(tables[i].byObjectId(name), i));
140 }
141 return m;
142 }
143
144
145 @Override
146 public LogCursor allLogs() throws IOException {
147 MergedLogCursor m = new MergedLogCursor();
148 for (int i = 0; i < tables.length; i++) {
149 m.add(new LogQueueEntry(tables[i].allLogs(), i));
150 }
151 return m;
152 }
153
154
155 @Override
156 public LogCursor seekLog(String refName, long updateIdx)
157 throws IOException {
158 MergedLogCursor m = new MergedLogCursor();
159 for (int i = 0; i < tables.length; i++) {
160 m.add(new LogQueueEntry(tables[i].seekLog(refName, updateIdx), i));
161 }
162 return m;
163 }
164
165 int queueSize() {
166 return Math.max(1, tables.length);
167 }
168
169 private class MergedRefCursor extends RefCursor {
170 private final PriorityQueue<RefQueueEntry> queue;
171 private RefQueueEntry head;
172 private Ref ref;
173
174 MergedRefCursor() {
175 queue = new PriorityQueue<>(queueSize(), RefQueueEntry::compare);
176 }
177
178 void add(RefQueueEntry t) throws IOException {
179
180
181
182
183 if (!t.rc.next()) {
184 t.rc.close();
185 } else if (head == null) {
186 RefQueueEntry p = queue.peek();
187 if (p == null || RefQueueEntry.compare(t, p) < 0) {
188 head = t;
189 } else {
190 head = queue.poll();
191 queue.add(t);
192 }
193 } else if (RefQueueEntry.compare(t, head) > 0) {
194 queue.add(t);
195 } else {
196 queue.add(head);
197 head = t;
198 }
199 }
200
201 @Override
202 public boolean next() throws IOException {
203 for (;;) {
204 RefQueueEntry t = poll();
205 if (t == null) {
206 return false;
207 }
208
209 ref = t.rc.getRef();
210 boolean include = includeDeletes || !t.rc.wasDeleted();
211 add(t);
212 skipShadowedRefs(ref.getName());
213 if (include) {
214 return true;
215 }
216 }
217 }
218
219 @Override
220 public void seekPastPrefix(String prefixName) throws IOException {
221 List<RefQueueEntry> entriesToAdd = new ArrayList<>();
222 entriesToAdd.addAll(queue);
223 if (head != null) {
224 entriesToAdd.add(head);
225 }
226
227 head = null;
228 queue.clear();
229
230 for(RefQueueEntry entry : entriesToAdd){
231 entry.rc.seekPastPrefix(prefixName);
232 add(entry);
233 }
234 }
235
236 private RefQueueEntry poll() {
237 RefQueueEntry e = head;
238 if (e != null) {
239 head = null;
240 return e;
241 }
242 return queue.poll();
243 }
244
245 private void skipShadowedRefs(String name) throws IOException {
246 for (;;) {
247 RefQueueEntry t = head != null ? head : queue.peek();
248 if (t != null && name.equals(t.name())) {
249 add(poll());
250 } else {
251 break;
252 }
253 }
254 }
255
256 @Override
257 public Ref getRef() {
258 return ref;
259 }
260
261 @Override
262 public void close() {
263 if (head != null) {
264 head.rc.close();
265 head = null;
266 }
267 while (!queue.isEmpty()) {
268 queue.remove().rc.close();
269 }
270 }
271 }
272
273 private class FilteringMergedRefCursor extends MergedRefCursor {
274 final AnyObjectId filterId;
275 Ref filteredRef;
276
277 FilteringMergedRefCursor(AnyObjectId id) {
278 filterId = id;
279 filteredRef = null;
280 }
281
282 @Override
283 public Ref getRef() {
284 return filteredRef;
285 }
286
287 @Override
288 public boolean next() throws IOException {
289 for (;;) {
290 boolean ok = super.next();
291 if (!ok) {
292 return false;
293 }
294
295 String name = super.getRef().getName();
296
297 try (RefCursor c = seekRef(name)) {
298 if (c.next()) {
299 if (filterId.equals(c.getRef().getObjectId())) {
300 filteredRef = c.getRef();
301 return true;
302 }
303 }
304 }
305 }
306 }
307 }
308
309 private static class RefQueueEntry {
310 static int compare(RefQueueEntry a, RefQueueEntry b) {
311 int cmp = a.name().compareTo(b.name());
312 if (cmp == 0) {
313
314 cmp = Long.signum(b.updateIndex() - a.updateIndex());
315 }
316 if (cmp == 0) {
317
318 cmp = b.stackIdx - a.stackIdx;
319 }
320 return cmp;
321 }
322
323 final RefCursor rc;
324 final int stackIdx;
325
326 RefQueueEntry(RefCursor rc, int stackIdx) {
327 this.rc = rc;
328 this.stackIdx = stackIdx;
329 }
330
331 String name() {
332 return rc.getRef().getName();
333 }
334
335 long updateIndex() {
336 return rc.getRef().getUpdateIndex();
337 }
338 }
339
340 private class MergedLogCursor extends LogCursor {
341 private final PriorityQueue<LogQueueEntry> queue;
342 private String refName;
343 private long updateIndex;
344 private ReflogEntry entry;
345
346 MergedLogCursor() {
347 queue = new PriorityQueue<>(queueSize(), LogQueueEntry::compare);
348 }
349
350 void add(LogQueueEntry t) throws IOException {
351 if (t.lc.next()) {
352 queue.add(t);
353 } else {
354 t.lc.close();
355 }
356 }
357
358 @Override
359 public boolean next() throws IOException {
360 for (;;) {
361 LogQueueEntry t = queue.poll();
362 if (t == null) {
363 return false;
364 }
365
366 refName = t.lc.getRefName();
367 updateIndex = t.lc.getUpdateIndex();
368 entry = t.lc.getReflogEntry();
369 boolean include = includeDeletes || entry != null;
370 skipShadowed(refName, updateIndex);
371 add(t);
372 if (include) {
373 return true;
374 }
375 }
376 }
377
378 private void skipShadowed(String name, long index) throws IOException {
379 for (;;) {
380 LogQueueEntry t = queue.peek();
381 if (t != null && name.equals(t.name()) && index == t.index()) {
382 add(queue.remove());
383 } else {
384 break;
385 }
386 }
387 }
388
389 @Override
390 public String getRefName() {
391 return refName;
392 }
393
394 @Override
395 public long getUpdateIndex() {
396 return updateIndex;
397 }
398
399 @Override
400 public ReflogEntry getReflogEntry() {
401 return entry;
402 }
403
404 @Override
405 public void close() {
406 while (!queue.isEmpty()) {
407 queue.remove().lc.close();
408 }
409 }
410 }
411
412 private static class LogQueueEntry {
413 static int compare(LogQueueEntry a, LogQueueEntry b) {
414 int cmp = a.name().compareTo(b.name());
415 if (cmp == 0) {
416
417 cmp = Long.signum(b.index() - a.index());
418 }
419 if (cmp == 0) {
420
421 cmp = b.stackIdx - a.stackIdx;
422 }
423 return cmp;
424 }
425
426 final LogCursor lc;
427 final int stackIdx;
428
429 LogQueueEntry(LogCursor lc, int stackIdx) {
430 this.lc = lc;
431 this.stackIdx = stackIdx;
432 }
433
434 String name() {
435 return lc.getRefName();
436 }
437
438 long index() {
439 return lc.getUpdateIndex();
440 }
441 }
442 }