View Javadoc
1   /*
2    * Copyright (C) 2017, Google Inc.
3    * and other copyright owners as documented in the project's IP log.
4    *
5    * This program and the accompanying materials are made available
6    * under the terms of the Eclipse Distribution License v1.0 which
7    * accompanies this distribution, is reproduced below, and is
8    * available at http://www.eclipse.org/org/documents/edl-v10.php
9    *
10   * All rights reserved.
11   *
12   * Redistribution and use in source and binary forms, with or
13   * without modification, are permitted provided that the following
14   * conditions are met:
15   *
16   * - Redistributions of source code must retain the above copyright
17   *   notice, this list of conditions and the following disclaimer.
18   *
19   * - Redistributions in binary form must reproduce the above
20   *   copyright notice, this list of conditions and the following
21   *   disclaimer in the documentation and/or other materials provided
22   *   with the distribution.
23   *
24   * - Neither the name of the Eclipse Foundation, Inc. nor the
25   *   names of its contributors may be used to endorse or promote
26   *   products derived from this software without specific prior
27   *   written permission.
28   *
29   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42   */
43  
44  package org.eclipse.jgit.internal.storage.reftable;
45  
46  import java.io.IOException;
47  import java.util.List;
48  import java.util.PriorityQueue;
49  
50  import org.eclipse.jgit.lib.AnyObjectId;
51  import org.eclipse.jgit.lib.Ref;
52  import org.eclipse.jgit.lib.ReflogEntry;
53  
54  /**
55   * Merges multiple reference tables together.
56   * <p>
57   * A {@link org.eclipse.jgit.internal.storage.reftable.MergedReftable}
58   * merge-joins multiple
59   * {@link org.eclipse.jgit.internal.storage.reftable.ReftableReader} on the fly.
60   * Tables higher/later in the stack shadow lower/earlier tables, hiding
61   * references that been updated/replaced.
62   * <p>
63   * By default deleted references are skipped and not returned to the caller.
64   * {@link #setIncludeDeletes(boolean)} can be used to modify this behavior if
65   * the caller needs to preserve deletions during partial compaction.
66   * <p>
67   * A {@code MergedReftable} is not thread-safe.
68   */
69  public class MergedReftable extends Reftable {
70  	private final Reftable[] tables;
71  
72  	/**
73  	 * Initialize a merged table reader.
74  	 * <p>
75  	 * The tables in {@code tableStack} will be closed when this
76  	 * {@code MergedReftable} is closed.
77  	 *
78  	 * @param tableStack
79  	 *            stack of tables to read from. The base of the stack is at
80  	 *            index 0, the most recent should be at the top of the stack at
81  	 *            {@code tableStack.size() - 1}. The top of the stack (higher
82  	 *            index) shadows the base of the stack (lower index).
83  	 */
84  	public MergedReftable(List<Reftable> tableStack) {
85  		tables = tableStack.toArray(new Reftable[0]);
86  
87  		// Tables must expose deletes to this instance to correctly
88  		// shadow references from lower tables.
89  		for (Reftable t : tables) {
90  			t.setIncludeDeletes(true);
91  		}
92  	}
93  
94  	/** {@inheritDoc} */
95  	@Override
96  	public RefCursor allRefs() throws IOException {
97  		MergedRefCursor m = new MergedRefCursor();
98  		for (int i = 0; i < tables.length; i++) {
99  			m.add(new RefQueueEntry(tables[i].allRefs(), i));
100 		}
101 		return m;
102 	}
103 
104 	/** {@inheritDoc} */
105 	@Override
106 	public RefCursor seekRef(String name) throws IOException {
107 		MergedRefCursor m = new MergedRefCursor();
108 		for (int i = 0; i < tables.length; i++) {
109 			m.add(new RefQueueEntry(tables[i].seekRef(name), i));
110 		}
111 		return m;
112 	}
113 
114 	/** {@inheritDoc} */
115 	@Override
116 	public RefCursor seekRefsWithPrefix(String prefix) throws IOException {
117 		MergedRefCursor m = new MergedRefCursor();
118 		for (int i = 0; i < tables.length; i++) {
119 			m.add(new RefQueueEntry(tables[i].seekRefsWithPrefix(prefix), i));
120 		}
121 		return m;
122 	}
123 
124 	/** {@inheritDoc} */
125 	@Override
126 	public RefCursor byObjectId(AnyObjectId name) throws IOException {
127 		MergedRefCursor m = new MergedRefCursor();
128 		for (int i = 0; i < tables.length; i++) {
129 			m.add(new RefQueueEntry(tables[i].byObjectId(name), i));
130 		}
131 		return m;
132 	}
133 
134 	/** {@inheritDoc} */
135 	@Override
136 	public LogCursor allLogs() throws IOException {
137 		MergedLogCursor m = new MergedLogCursor();
138 		for (int i = 0; i < tables.length; i++) {
139 			m.add(new LogQueueEntry(tables[i].allLogs(), i));
140 		}
141 		return m;
142 	}
143 
144 	/** {@inheritDoc} */
145 	@Override
146 	public LogCursor seekLog(String refName, long updateIdx)
147 			throws IOException {
148 		MergedLogCursor m = new MergedLogCursor();
149 		for (int i = 0; i < tables.length; i++) {
150 			m.add(new LogQueueEntry(tables[i].seekLog(refName, updateIdx), i));
151 		}
152 		return m;
153 	}
154 
155 	/** {@inheritDoc} */
156 	@Override
157 	public void close() throws IOException {
158 		for (Reftable t : tables) {
159 			t.close();
160 		}
161 	}
162 
163 	int queueSize() {
164 		return Math.max(1, tables.length);
165 	}
166 
167 	private class MergedRefCursor extends RefCursor {
168 		private final PriorityQueue<RefQueueEntry> queue;
169 		private RefQueueEntry head;
170 		private Ref ref;
171 		private long updateIndex;
172 
173 		MergedRefCursor() {
174 			queue = new PriorityQueue<>(queueSize(), RefQueueEntry::compare);
175 		}
176 
177 		void add(RefQueueEntry t) throws IOException {
178 			// Common case is many iterations over the same RefQueueEntry
179 			// for the bottom of the stack (scanning all refs). Its almost
180 			// always less than the top of the queue. Avoid the queue's
181 			// O(log N) insertion and removal costs for this common case.
182 			if (!t.rc.next()) {
183 				t.rc.close();
184 			} else if (head == null) {
185 				RefQueueEntry p = queue.peek();
186 				if (p == null || RefQueueEntry.compare(t, p) < 0) {
187 					head = t;
188 				} else {
189 					head = queue.poll();
190 					queue.add(t);
191 				}
192 			} else if (RefQueueEntry.compare(t, head) > 0) {
193 				queue.add(t);
194 			} else {
195 				queue.add(head);
196 				head = t;
197 			}
198 		}
199 
200 		@Override
201 		public boolean next() throws IOException {
202 			for (;;) {
203 				RefQueueEntry t = poll();
204 				if (t == null) {
205 					return false;
206 				}
207 
208 				ref = t.rc.getRef();
209 				updateIndex = t.rc.getUpdateIndex();
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 		private RefQueueEntry poll() {
220 			RefQueueEntry e = head;
221 			if (e != null) {
222 				head = null;
223 				return e;
224 			}
225 			return queue.poll();
226 		}
227 
228 		private void skipShadowedRefs(String name) throws IOException {
229 			for (;;) {
230 				RefQueueEntry t = head != null ? head : queue.peek();
231 				if (t != null && name.equals(t.name())) {
232 					add(poll());
233 				} else {
234 					break;
235 				}
236 			}
237 		}
238 
239 		@Override
240 		public Ref getRef() {
241 			return ref;
242 		}
243 
244 		@Override
245 		public long getUpdateIndex() {
246 			return updateIndex;
247 		}
248 
249 		@Override
250 		public void close() {
251 			if (head != null) {
252 				head.rc.close();
253 				head = null;
254 			}
255 			while (!queue.isEmpty()) {
256 				queue.remove().rc.close();
257 			}
258 		}
259 	}
260 
261 	private static class RefQueueEntry {
262 		static int compare(RefQueueEntry a, RefQueueEntry b) {
263 			int cmp = a.name().compareTo(b.name());
264 			if (cmp == 0) {
265 				// higher updateIndex shadows lower updateIndex.
266 				cmp = Long.signum(b.updateIndex() - a.updateIndex());
267 			}
268 			if (cmp == 0) {
269 				// higher index shadows lower index, so higher index first.
270 				cmp = b.stackIdx - a.stackIdx;
271 			}
272 			return cmp;
273 		}
274 
275 		final RefCursor rc;
276 		final int stackIdx;
277 
278 		RefQueueEntry(RefCursor rc, int stackIdx) {
279 			this.rc = rc;
280 			this.stackIdx = stackIdx;
281 		}
282 
283 		String name() {
284 			return rc.getRef().getName();
285 		}
286 
287 		long updateIndex() {
288 			return rc.getUpdateIndex();
289 		}
290 	}
291 
292 	private class MergedLogCursor extends LogCursor {
293 		private final PriorityQueue<LogQueueEntry> queue;
294 		private String refName;
295 		private long updateIndex;
296 		private ReflogEntry entry;
297 
298 		MergedLogCursor() {
299 			queue = new PriorityQueue<>(queueSize(), LogQueueEntry::compare);
300 		}
301 
302 		void add(LogQueueEntry t) throws IOException {
303 			if (t.lc.next()) {
304 				queue.add(t);
305 			} else {
306 				t.lc.close();
307 			}
308 		}
309 
310 		@Override
311 		public boolean next() throws IOException {
312 			for (;;) {
313 				LogQueueEntry t = queue.poll();
314 				if (t == null) {
315 					return false;
316 				}
317 
318 				refName = t.lc.getRefName();
319 				updateIndex = t.lc.getUpdateIndex();
320 				entry = t.lc.getReflogEntry();
321 				boolean include = includeDeletes || entry != null;
322 				skipShadowed(refName, updateIndex);
323 				add(t);
324 				if (include) {
325 					return true;
326 				}
327 			}
328 		}
329 
330 		private void skipShadowed(String name, long index) throws IOException {
331 			for (;;) {
332 				LogQueueEntry t = queue.peek();
333 				if (t != null && name.equals(t.name()) && index == t.index()) {
334 					add(queue.remove());
335 				} else {
336 					break;
337 				}
338 			}
339 		}
340 
341 		@Override
342 		public String getRefName() {
343 			return refName;
344 		}
345 
346 		@Override
347 		public long getUpdateIndex() {
348 			return updateIndex;
349 		}
350 
351 		@Override
352 		public ReflogEntry getReflogEntry() {
353 			return entry;
354 		}
355 
356 		@Override
357 		public void close() {
358 			while (!queue.isEmpty()) {
359 				queue.remove().lc.close();
360 			}
361 		}
362 	}
363 
364 	private static class LogQueueEntry {
365 		static int compare(LogQueueEntry a, LogQueueEntry b) {
366 			int cmp = a.name().compareTo(b.name());
367 			if (cmp == 0) {
368 				// higher update index sorts first.
369 				cmp = Long.signum(b.index() - a.index());
370 			}
371 			if (cmp == 0) {
372 				// higher index comes first.
373 				cmp = b.stackIdx - a.stackIdx;
374 			}
375 			return cmp;
376 		}
377 
378 		final LogCursor lc;
379 		final int stackIdx;
380 
381 		LogQueueEntry(LogCursor lc, int stackIdx) {
382 			this.lc = lc;
383 			this.stackIdx = stackIdx;
384 		}
385 
386 		String name() {
387 			return lc.getRefName();
388 		}
389 
390 		long index() {
391 			return lc.getUpdateIndex();
392 		}
393 	}
394 }