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