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.dfs;
45  
46  import java.io.IOException;
47  import java.util.Arrays;
48  import java.util.ArrayList;
49  import java.util.Collections;
50  import java.util.HashSet;
51  import java.util.List;
52  import java.util.Map;
53  import java.util.Set;
54  import java.util.concurrent.locks.ReentrantLock;
55  
56  import org.eclipse.jgit.annotations.Nullable;
57  import org.eclipse.jgit.internal.storage.reftable.MergedReftable;
58  import org.eclipse.jgit.internal.storage.reftable.RefCursor;
59  import org.eclipse.jgit.internal.storage.reftable.Reftable;
60  import org.eclipse.jgit.internal.storage.reftable.ReftableConfig;
61  import org.eclipse.jgit.lib.BatchRefUpdate;
62  import org.eclipse.jgit.lib.NullProgressMonitor;
63  import org.eclipse.jgit.lib.ObjectId;
64  import org.eclipse.jgit.lib.Ref;
65  import org.eclipse.jgit.revwalk.RevWalk;
66  import org.eclipse.jgit.transport.ReceiveCommand;
67  import org.eclipse.jgit.util.RefList;
68  import org.eclipse.jgit.util.RefMap;
69  
70  /**
71   * A {@link org.eclipse.jgit.internal.storage.dfs.DfsRefDatabase} that uses
72   * reftable for storage.
73   * <p>
74   * A {@code DfsRefDatabase} instance is thread-safe.
75   * <p>
76   * Implementors may wish to use
77   * {@link org.eclipse.jgit.internal.storage.dfs.DfsPackDescription#getMaxUpdateIndex()}
78   * as the primary key identifier for a
79   * {@link org.eclipse.jgit.internal.storage.pack.PackExt#REFTABLE} only pack
80   * description, ensuring that when there are competing transactions one wins,
81   * and one will fail.
82   */
83  public class DfsReftableDatabase extends DfsRefDatabase {
84  	private final ReentrantLock lock = new ReentrantLock(true);
85  
86  	private DfsReader ctx;
87  
88  	private ReftableStack tableStack;
89  
90  	private MergedReftable mergedTables;
91  
92  	/**
93  	 * Initialize the reference database for a repository.
94  	 *
95  	 * @param repo
96  	 *            the repository this database instance manages references for.
97  	 */
98  	protected DfsReftableDatabase(DfsRepository repo) {
99  		super(repo);
100 	}
101 
102 	/** {@inheritDoc} */
103 	@Override
104 	public boolean hasVersioning() {
105 		return true;
106 	}
107 
108 	/** {@inheritDoc} */
109 	@Override
110 	public boolean performsAtomicTransactions() {
111 		return true;
112 	}
113 
114 	/** {@inheritDoc} */
115 	@Override
116 	public BatchRefUpdate newBatchUpdate() {
117 		DfsObjDatabase odb = getRepository().getObjectDatabase();
118 		return new ReftableBatchRefUpdate(this, odb);
119 	}
120 
121 	/**
122 	 * Get configuration to write new reftables with.
123 	 *
124 	 * @return configuration to write new reftables with.
125 	 */
126 	public ReftableConfig getReftableConfig() {
127 		return new ReftableConfig(getRepository().getConfig());
128 	}
129 
130 	/**
131 	 * Get the lock protecting this instance's state.
132 	 *
133 	 * @return the lock protecting this instance's state.
134 	 */
135 	protected ReentrantLock getLock() {
136 		return lock;
137 	}
138 
139 	/**
140 	 * Whether to compact reftable instead of extending the stack depth.
141 	 *
142 	 * @return {@code true} if commit of a new small reftable should try to
143 	 *         replace a prior small reftable by performing a compaction,
144 	 *         instead of extending the stack depth.
145 	 */
146 	protected boolean compactDuringCommit() {
147 		return true;
148 	}
149 
150 	/**
151 	 * Obtain a handle to the merged reader.
152 	 *
153 	 * @return (possibly cached) handle to the merged reader.
154 	 * @throws java.io.IOException
155 	 *             if tables cannot be opened.
156 	 */
157 	protected Reftable reader() throws IOException {
158 		lock.lock();
159 		try {
160 			if (mergedTables == null) {
161 				mergedTables = new MergedReftable(stack().readers());
162 			}
163 			return mergedTables;
164 		} finally {
165 			lock.unlock();
166 		}
167 	}
168 
169 	/**
170 	 * Obtain a handle to the stack of reftables.
171 	 *
172 	 * @return (possibly cached) handle to the stack.
173 	 * @throws java.io.IOException
174 	 *             if tables cannot be opened.
175 	 */
176 	protected ReftableStack stack() throws IOException {
177 		lock.lock();
178 		try {
179 			if (tableStack == null) {
180 				DfsObjDatabase odb = getRepository().getObjectDatabase();
181 				if (ctx == null) {
182 					ctx = odb.newReader();
183 				}
184 				tableStack = ReftableStack.open(ctx,
185 						Arrays.asList(odb.getReftables()));
186 			}
187 			return tableStack;
188 		} finally {
189 			lock.unlock();
190 		}
191 	}
192 
193 	/** {@inheritDoc} */
194 	@Override
195 	public boolean isNameConflicting(String refName) throws IOException {
196 		lock.lock();
197 		try {
198 			Reftable table = reader();
199 
200 			// Cannot be nested within an existing reference.
201 			int lastSlash = refName.lastIndexOf('/');
202 			while (0 < lastSlash) {
203 				if (table.hasRef(refName.substring(0, lastSlash))) {
204 					return true;
205 				}
206 				lastSlash = refName.lastIndexOf('/', lastSlash - 1);
207 			}
208 
209 			// Cannot be the container of an existing reference.
210 			return table.hasRefsWithPrefix(refName + '/');
211 		} finally {
212 			lock.unlock();
213 		}
214 	}
215 
216 	/** {@inheritDoc} */
217 	@Override
218 	public Ref exactRef(String name) throws IOException {
219 		lock.lock();
220 		try {
221 			Reftable table = reader();
222 			Ref ref = table.exactRef(name);
223 			if (ref != null && ref.isSymbolic()) {
224 				return table.resolve(ref);
225 			}
226 			return ref;
227 		} finally {
228 			lock.unlock();
229 		}
230 	}
231 
232 	/** {@inheritDoc} */
233 	@Override
234 	public Map<String, Ref> getRefs(String prefix) throws IOException {
235 		RefList.Builder<Ref> all = new RefList.Builder<>();
236 		lock.lock();
237 		try {
238 			Reftable table = reader();
239 			try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
240 					: (prefix.endsWith("/") ? table.seekRefsWithPrefix(prefix) //$NON-NLS-1$
241 							: table.seekRef(prefix))) {
242 				while (rc.next()) {
243 					Ref ref = table.resolve(rc.getRef());
244 					if (ref != null && ref.getObjectId() != null) {
245 						all.add(ref);
246 					}
247 				}
248 			}
249 		} finally {
250 			lock.unlock();
251 		}
252 
253 		RefList<Ref> none = RefList.emptyList();
254 		return new RefMap(prefix, all.toRefList(), none, none);
255 	}
256 
257 	/** {@inheritDoc} */
258 	@Override
259 	public List<Ref> getRefsByPrefix(String prefix) throws IOException {
260 		List<Ref> all = new ArrayList<>();
261 		lock.lock();
262 		try {
263 			Reftable table = reader();
264 			try (RefCursor rc = ALL.equals(prefix) ? table.allRefs()
265 					: table.seekRefsWithPrefix(prefix)) {
266 				while (rc.next()) {
267 					Ref ref = table.resolve(rc.getRef());
268 					if (ref != null && ref.getObjectId() != null) {
269 						all.add(ref);
270 					}
271 				}
272 			}
273 		} finally {
274 			lock.unlock();
275 		}
276 
277 		return Collections.unmodifiableList(all);
278 	}
279 
280 	/** {@inheritDoc} */
281 	@Override
282 	public Set<Ref> getTipsWithSha1(ObjectId id) throws IOException {
283 		if (!getReftableConfig().isIndexObjects()) {
284 			return super.getTipsWithSha1(id);
285 		}
286 		lock.lock();
287 		try {
288 			RefCursor cursor = reader().byObjectId(id);
289 			Set<Ref> refs = new HashSet<>();
290 			while (cursor.next()) {
291 				refs.add(cursor.getRef());
292 			}
293 			return refs;
294 		} finally {
295 			lock.unlock();
296 		}
297 	}
298 
299 	/** {@inheritDoc} */
300 	@Override
301 	public Ref" href="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref peel(Ref ref) throws IOException {
302 		Ref oldLeaf = ref.getLeaf();
303 		if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null) {
304 			return ref;
305 		}
306 		return recreate(ref, doPeel(oldLeaf), hasVersioning());
307 	}
308 
309 	@Override
310 	boolean exists() throws IOException {
311 		DfsObjDatabase odb = getRepository().getObjectDatabase();
312 		return odb.getReftables().length > 0;
313 	}
314 
315 	@Override
316 	void clearCache() {
317 		lock.lock();
318 		try {
319 			if (tableStack != null) {
320 				tableStack.close();
321 				tableStack = null;
322 			}
323 			if (ctx != null) {
324 				ctx.close();
325 				ctx = null;
326 			}
327 			mergedTables = null;
328 		} finally {
329 			lock.unlock();
330 		}
331 	}
332 
333 	/** {@inheritDoc} */
334 	@Override
335 	protected boolean compareAndPut(Ref/../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref oldRef, @Nullable Ref newRef)
336 			throws IOException {
337 		ReceiveCommand cmd = toCommand(oldRef, newRef);
338 		try (RevWalkvwalk/RevWalk.html#RevWalk">RevWalk rw = new RevWalk(getRepository())) {
339 			rw.setRetainBody(false);
340 			newBatchUpdate().setAllowNonFastForwards(true).addCommand(cmd)
341 					.execute(rw, NullProgressMonitor.INSTANCE);
342 		}
343 		switch (cmd.getResult()) {
344 		case OK:
345 			return true;
346 		case REJECTED_OTHER_REASON:
347 			throw new IOException(cmd.getMessage());
348 		case LOCK_FAILURE:
349 		default:
350 			return false;
351 		}
352 	}
353 
354 	private static ReceiveCommand toCommand(Refref="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref oldRef, Ref newRef) {
355 		ObjectId oldId = toId(oldRef);
356 		ObjectId newId = toId(newRef);
357 		String name = toName(oldRef, newRef);
358 
359 		if (oldRef != null && oldRef.isSymbolic()) {
360 			if (newRef != null) {
361 				if (newRef.isSymbolic()) {
362 					return ReceiveCommand.link(oldRef.getTarget().getName(),
363 							newRef.getTarget().getName(), name);
364 				} else {
365 					return ReceiveCommand.unlink(oldRef.getTarget().getName(),
366 							newId, name);
367 				}
368 			} else {
369 				return ReceiveCommand.unlink(oldRef.getTarget().getName(),
370 						ObjectId.zeroId(), name);
371 			}
372 		}
373 
374 		if (newRef != null && newRef.isSymbolic()) {
375 			if (oldRef != null) {
376 				if (oldRef.isSymbolic()) {
377 					return ReceiveCommand.link(oldRef.getTarget().getName(),
378 							newRef.getTarget().getName(), name);
379 				} else {
380 					return ReceiveCommand.link(oldId,
381 							newRef.getTarget().getName(), name);
382 				}
383 			} else {
384 				return ReceiveCommand.link(ObjectId.zeroId(),
385 						newRef.getTarget().getName(), name);
386 			}
387 		}
388 
389 		return new ReceiveCommand(oldId, newId, name);
390 	}
391 
392 	private static ObjectId toId(Ref ref) {
393 		if (ref != null) {
394 			ObjectId id = ref.getObjectId();
395 			if (id != null) {
396 				return id;
397 			}
398 		}
399 		return ObjectId.zeroId();
400 	}
401 
402 	private static String toName(Refref="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref oldRef, Ref newRef) {
403 		return oldRef != null ? oldRef.getName() : newRef.getName();
404 	}
405 
406 	/** {@inheritDoc} */
407 	@Override
408 	protected boolean compareAndRemove(Ref oldRef) throws IOException {
409 		return compareAndPut(oldRef, null);
410 	}
411 
412 	/** {@inheritDoc} */
413 	@Override
414 	protected RefCache scanAllRefs() throws IOException {
415 		throw new UnsupportedOperationException();
416 	}
417 
418 	@Override
419 	void stored(Ref ref) {
420 		// Unnecessary; ReftableBatchRefUpdate calls clearCache().
421 	}
422 
423 	@Override
424 	void removed(String refName) {
425 		// Unnecessary; ReftableBatchRefUpdate calls clearCache().
426 	}
427 
428 	/** {@inheritDoc} */
429 	@Override
430 	protected void cachePeeledState(Refef="../../../../../../org/eclipse/jgit/lib/Ref.html#Ref">Ref oldLeaf, Ref newLeaf) {
431 		// Do not cache peeled state in reftable.
432 	}
433 }