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