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 byObjectId(AnyObjectId name) throws IOException {
117 		MergedRefCursor m = new MergedRefCursor();
118 		for (int i = 0; i < tables.length; i++) {
119 			m.add(new RefQueueEntry(tables[i].byObjectId(name), i));
120 		}
121 		return m;
122 	}
123 
124 	/** {@inheritDoc} */
125 	@Override
126 	public LogCursor allLogs() throws IOException {
127 		MergedLogCursor m = new MergedLogCursor();
128 		for (int i = 0; i < tables.length; i++) {
129 			m.add(new LogQueueEntry(tables[i].allLogs(), i));
130 		}
131 		return m;
132 	}
133 
134 	/** {@inheritDoc} */
135 	@Override
136 	public LogCursor seekLog(String refName, long updateIdx)
137 			throws IOException {
138 		MergedLogCursor m = new MergedLogCursor();
139 		for (int i = 0; i < tables.length; i++) {
140 			m.add(new LogQueueEntry(tables[i].seekLog(refName, updateIdx), i));
141 		}
142 		return m;
143 	}
144 
145 	/** {@inheritDoc} */
146 	@Override
147 	public void close() throws IOException {
148 		for (Reftable t : tables) {
149 			t.close();
150 		}
151 	}
152 
153 	int queueSize() {
154 		return Math.max(1, tables.length);
155 	}
156 
157 	private class MergedRefCursor extends RefCursor {
158 		private final PriorityQueue<RefQueueEntry> queue;
159 		private RefQueueEntry head;
160 		private Ref ref;
161 		private long updateIndex;
162 
163 		MergedRefCursor() {
164 			queue = new PriorityQueue<>(queueSize(), RefQueueEntry::compare);
165 		}
166 
167 		void add(RefQueueEntry t) throws IOException {
168 			// Common case is many iterations over the same RefQueueEntry
169 			// for the bottom of the stack (scanning all refs). Its almost
170 			// always less than the top of the queue. Avoid the queue's
171 			// O(log N) insertion and removal costs for this common case.
172 			if (!t.rc.next()) {
173 				t.rc.close();
174 			} else if (head == null) {
175 				RefQueueEntry p = queue.peek();
176 				if (p == null || RefQueueEntry.compare(t, p) < 0) {
177 					head = t;
178 				} else {
179 					head = queue.poll();
180 					queue.add(t);
181 				}
182 			} else if (RefQueueEntry.compare(t, head) > 0) {
183 				queue.add(t);
184 			} else {
185 				queue.add(head);
186 				head = t;
187 			}
188 		}
189 
190 		@Override
191 		public boolean next() throws IOException {
192 			for (;;) {
193 				RefQueueEntry t = poll();
194 				if (t == null) {
195 					return false;
196 				}
197 
198 				ref = t.rc.getRef();
199 				updateIndex = t.rc.getUpdateIndex();
200 				boolean include = includeDeletes || !t.rc.wasDeleted();
201 				skipShadowedRefs(ref.getName());
202 				add(t);
203 				if (include) {
204 					return true;
205 				}
206 			}
207 		}
208 
209 		private RefQueueEntry poll() {
210 			RefQueueEntry e = head;
211 			if (e != null) {
212 				head = null;
213 				return e;
214 			}
215 			return queue.poll();
216 		}
217 
218 		private void skipShadowedRefs(String name) throws IOException {
219 			for (;;) {
220 				RefQueueEntry t = head != null ? head : queue.peek();
221 				if (t != null && name.equals(t.name())) {
222 					add(poll());
223 				} else {
224 					break;
225 				}
226 			}
227 		}
228 
229 		@Override
230 		public Ref getRef() {
231 			return ref;
232 		}
233 
234 		@Override
235 		public long getUpdateIndex() {
236 			return updateIndex;
237 		}
238 
239 		@Override
240 		public void close() {
241 			if (head != null) {
242 				head.rc.close();
243 				head = null;
244 			}
245 			while (!queue.isEmpty()) {
246 				queue.remove().rc.close();
247 			}
248 		}
249 	}
250 
251 	private static class RefQueueEntry {
252 		static int compare(RefQueueEntry a, RefQueueEntry b) {
253 			int cmp = a.name().compareTo(b.name());
254 			if (cmp == 0) {
255 				// higher updateIndex shadows lower updateIndex.
256 				cmp = Long.signum(b.updateIndex() - a.updateIndex());
257 			}
258 			if (cmp == 0) {
259 				// higher index shadows lower index, so higher index first.
260 				cmp = b.stackIdx - a.stackIdx;
261 			}
262 			return cmp;
263 		}
264 
265 		final RefCursor rc;
266 		final int stackIdx;
267 
268 		RefQueueEntry(RefCursor rc, int stackIdx) {
269 			this.rc = rc;
270 			this.stackIdx = stackIdx;
271 		}
272 
273 		String name() {
274 			return rc.getRef().getName();
275 		}
276 
277 		long updateIndex() {
278 			return rc.getUpdateIndex();
279 		}
280 	}
281 
282 	private class MergedLogCursor extends LogCursor {
283 		private final PriorityQueue<LogQueueEntry> queue;
284 		private String refName;
285 		private long updateIndex;
286 		private ReflogEntry entry;
287 
288 		MergedLogCursor() {
289 			queue = new PriorityQueue<>(queueSize(), LogQueueEntry::compare);
290 		}
291 
292 		void add(LogQueueEntry t) throws IOException {
293 			if (t.lc.next()) {
294 				queue.add(t);
295 			} else {
296 				t.lc.close();
297 			}
298 		}
299 
300 		@Override
301 		public boolean next() throws IOException {
302 			for (;;) {
303 				LogQueueEntry t = queue.poll();
304 				if (t == null) {
305 					return false;
306 				}
307 
308 				refName = t.lc.getRefName();
309 				updateIndex = t.lc.getUpdateIndex();
310 				entry = t.lc.getReflogEntry();
311 				boolean include = includeDeletes || entry != null;
312 				skipShadowed(refName, updateIndex);
313 				add(t);
314 				if (include) {
315 					return true;
316 				}
317 			}
318 		}
319 
320 		private void skipShadowed(String name, long index) throws IOException {
321 			for (;;) {
322 				LogQueueEntry t = queue.peek();
323 				if (t != null && name.equals(t.name()) && index == t.index()) {
324 					add(queue.remove());
325 				} else {
326 					break;
327 				}
328 			}
329 		}
330 
331 		@Override
332 		public String getRefName() {
333 			return refName;
334 		}
335 
336 		@Override
337 		public long getUpdateIndex() {
338 			return updateIndex;
339 		}
340 
341 		@Override
342 		public ReflogEntry getReflogEntry() {
343 			return entry;
344 		}
345 
346 		@Override
347 		public void close() {
348 			while (!queue.isEmpty()) {
349 				queue.remove().lc.close();
350 			}
351 		}
352 	}
353 
354 	private static class LogQueueEntry {
355 		static int compare(LogQueueEntry a, LogQueueEntry b) {
356 			int cmp = a.name().compareTo(b.name());
357 			if (cmp == 0) {
358 				// higher update index sorts first.
359 				cmp = Long.signum(b.index() - a.index());
360 			}
361 			if (cmp == 0) {
362 				// higher index comes first.
363 				cmp = b.stackIdx - a.stackIdx;
364 			}
365 			return cmp;
366 		}
367 
368 		final LogCursor lc;
369 		final int stackIdx;
370 
371 		LogQueueEntry(LogCursor lc, int stackIdx) {
372 			this.lc = lc;
373 			this.stackIdx = stackIdx;
374 		}
375 
376 		String name() {
377 			return lc.getRefName();
378 		}
379 
380 		long index() {
381 			return lc.getUpdateIndex();
382 		}
383 	}
384 }