View Javadoc
1   /*
2    * Copyright (C) 2011, 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 static org.eclipse.jgit.lib.Ref.Storage.NEW;
47  
48  import java.io.IOException;
49  import java.util.Collections;
50  import java.util.List;
51  import java.util.Map;
52  import java.util.concurrent.atomic.AtomicReference;
53  
54  import org.eclipse.jgit.errors.MissingObjectException;
55  import org.eclipse.jgit.lib.ObjectIdRef;
56  import org.eclipse.jgit.lib.Ref;
57  import org.eclipse.jgit.lib.RefDatabase;
58  import org.eclipse.jgit.lib.RefRename;
59  import org.eclipse.jgit.lib.RefUpdate;
60  import org.eclipse.jgit.lib.SymbolicRef;
61  import org.eclipse.jgit.revwalk.RevObject;
62  import org.eclipse.jgit.revwalk.RevTag;
63  import org.eclipse.jgit.revwalk.RevWalk;
64  import org.eclipse.jgit.util.RefList;
65  import org.eclipse.jgit.util.RefMap;
66  
67  /**
68   * Abstract DfsRefDatabase class.
69   *
70   */
71  public abstract class DfsRefDatabase extends RefDatabase {
72  	private final DfsRepository repository;
73  
74  	private final AtomicReference<RefCache> cache;
75  
76  	/**
77  	 * Initialize the reference database for a repository.
78  	 *
79  	 * @param repository
80  	 *            the repository this database instance manages references for.
81  	 */
82  	protected DfsRefDatabase(DfsRepository repository) {
83  		this.repository = repository;
84  		this.cache = new AtomicReference<>();
85  	}
86  
87  	/**
88  	 * Get the repository the database holds the references of.
89  	 *
90  	 * @return the repository the database holds the references of.
91  	 */
92  	protected DfsRepository getRepository() {
93  		return repository;
94  	}
95  
96  	boolean exists() throws IOException {
97  		return 0 < read().size();
98  	}
99  
100 	/** {@inheritDoc} */
101 	@Override
102 	public Ref exactRef(String name) throws IOException {
103 		RefCache curr = read();
104 		Ref ref = curr.ids.get(name);
105 		return ref != null ? resolve(ref, 0, curr.ids) : null;
106 	}
107 
108 	/** {@inheritDoc} */
109 	@Override
110 	public Ref getRef(String needle) throws IOException {
111 		RefCache curr = read();
112 		for (String prefix : SEARCH_PATH) {
113 			Ref ref = curr.ids.get(prefix + needle);
114 			if (ref != null) {
115 				ref = resolve(ref, 0, curr.ids);
116 				return ref;
117 			}
118 		}
119 		return null;
120 	}
121 
122 	/** {@inheritDoc} */
123 	@Override
124 	public List<Ref> getAdditionalRefs() {
125 		return Collections.emptyList();
126 	}
127 
128 	/** {@inheritDoc} */
129 	@Override
130 	public Map<String, Ref> getRefs(String prefix) throws IOException {
131 		RefCache curr = read();
132 		RefList<Ref> packed = RefList.emptyList();
133 		RefList<Ref> loose = curr.ids;
134 		RefList.Builder<Ref> sym = new RefList.Builder<>(curr.sym.size());
135 
136 		for (int idx = 0; idx < curr.sym.size(); idx++) {
137 			Ref ref = curr.sym.get(idx);
138 			String name = ref.getName();
139 			ref = resolve(ref, 0, loose);
140 			if (ref != null && ref.getObjectId() != null) {
141 				sym.add(ref);
142 			} else {
143 				// A broken symbolic reference, we have to drop it from the
144 				// collections the client is about to receive. Should be a
145 				// rare occurrence so pay a copy penalty.
146 				int toRemove = loose.find(name);
147 				if (0 <= toRemove)
148 					loose = loose.remove(toRemove);
149 			}
150 		}
151 
152 		return new RefMap(prefix, packed, loose, sym.toRefList());
153 	}
154 
155 	private Ref resolve(Ref ref, int depth, RefList<Ref> loose)
156 			throws IOException {
157 		if (!ref.isSymbolic())
158 			return ref;
159 
160 		Ref dst = ref.getTarget();
161 
162 		if (MAX_SYMBOLIC_REF_DEPTH <= depth)
163 			return null; // claim it doesn't exist
164 
165 		dst = loose.get(dst.getName());
166 		if (dst == null)
167 			return ref;
168 
169 		dst = resolve(dst, depth + 1, loose);
170 		if (dst == null)
171 			return null;
172 		return new SymbolicRef(ref.getName(), dst);
173 	}
174 
175 	/** {@inheritDoc} */
176 	@Override
177 	public Ref peel(Ref ref) throws IOException {
178 		final Ref oldLeaf = ref.getLeaf();
179 		if (oldLeaf.isPeeled() || oldLeaf.getObjectId() == null)
180 			return ref;
181 
182 		Ref newLeaf = doPeel(oldLeaf);
183 
184 		RefCache cur = read();
185 		int idx = cur.ids.find(oldLeaf.getName());
186 		if (0 <= idx && cur.ids.get(idx) == oldLeaf) {
187 			RefList<Ref> newList = cur.ids.set(idx, newLeaf);
188 			cache.compareAndSet(cur, new RefCache(newList, cur));
189 			cachePeeledState(oldLeaf, newLeaf);
190 		}
191 
192 		return recreate(ref, newLeaf);
193 	}
194 
195 	Ref doPeel(Ref leaf) throws MissingObjectException,
196 			IOException {
197 		try (RevWalk rw = new RevWalk(repository)) {
198 			RevObject obj = rw.parseAny(leaf.getObjectId());
199 			if (obj instanceof RevTag) {
200 				return new ObjectIdRef.PeeledTag(
201 						leaf.getStorage(),
202 						leaf.getName(),
203 						leaf.getObjectId(),
204 						rw.peel(obj).copy());
205 			} else {
206 				return new ObjectIdRef.PeeledNonTag(
207 						leaf.getStorage(),
208 						leaf.getName(),
209 						leaf.getObjectId());
210 			}
211 		}
212 	}
213 
214 	static Ref recreate(Ref old, Ref leaf) {
215 		if (old.isSymbolic()) {
216 			Ref dst = recreate(old.getTarget(), leaf);
217 			return new SymbolicRef(old.getName(), dst);
218 		}
219 		return leaf;
220 	}
221 
222 	/** {@inheritDoc} */
223 	@Override
224 	public RefUpdate newUpdate(String refName, boolean detach)
225 			throws IOException {
226 		boolean detachingSymbolicRef = false;
227 		Ref ref = exactRef(refName);
228 		if (ref == null)
229 			ref = new ObjectIdRef.Unpeeled(NEW, refName, null);
230 		else
231 			detachingSymbolicRef = detach && ref.isSymbolic();
232 
233 		DfsRefUpdate update = new DfsRefUpdate(this, ref);
234 		if (detachingSymbolicRef)
235 			update.setDetachingSymbolicRef();
236 		return update;
237 	}
238 
239 	/** {@inheritDoc} */
240 	@Override
241 	public RefRename newRename(String fromName, String toName)
242 			throws IOException {
243 		RefUpdate src = newUpdate(fromName, true);
244 		RefUpdate dst = newUpdate(toName, true);
245 		return new DfsRefRename(src, dst);
246 	}
247 
248 	/** {@inheritDoc} */
249 	@Override
250 	public boolean isNameConflicting(String refName) throws IOException {
251 		RefList<Ref> all = read().ids;
252 
253 		// Cannot be nested within an existing reference.
254 		int lastSlash = refName.lastIndexOf('/');
255 		while (0 < lastSlash) {
256 			String needle = refName.substring(0, lastSlash);
257 			if (all.contains(needle))
258 				return true;
259 			lastSlash = refName.lastIndexOf('/', lastSlash - 1);
260 		}
261 
262 		// Cannot be the container of an existing reference.
263 		String prefix = refName + '/';
264 		int idx = -(all.find(prefix) + 1);
265 		if (idx < all.size() && all.get(idx).getName().startsWith(prefix))
266 			return true;
267 		return false;
268 	}
269 
270 	/** {@inheritDoc} */
271 	@Override
272 	public void create() {
273 		// Nothing to do.
274 	}
275 
276 	/** {@inheritDoc} */
277 	@Override
278 	public void refresh() {
279 		clearCache();
280 	}
281 
282 	/** {@inheritDoc} */
283 	@Override
284 	public void close() {
285 		clearCache();
286 	}
287 
288 	void clearCache() {
289 		cache.set(null);
290 	}
291 
292 	void stored(Ref ref) {
293 		RefCache oldCache, newCache;
294 		do {
295 			oldCache = cache.get();
296 			if (oldCache == null)
297 				return;
298 			newCache = oldCache.put(ref);
299 		} while (!cache.compareAndSet(oldCache, newCache));
300 	}
301 
302 	void removed(String refName) {
303 		RefCache oldCache, newCache;
304 		do {
305 			oldCache = cache.get();
306 			if (oldCache == null)
307 				return;
308 			newCache = oldCache.remove(refName);
309 		} while (!cache.compareAndSet(oldCache, newCache));
310 	}
311 
312 	private RefCache read() throws IOException {
313 		RefCache c = cache.get();
314 		if (c == null) {
315 			c = scanAllRefs();
316 			cache.set(c);
317 		}
318 		return c;
319 	}
320 
321 	/**
322 	 * Read all known references in the repository.
323 	 *
324 	 * @return all current references of the repository.
325 	 * @throws java.io.IOException
326 	 *             references cannot be accessed.
327 	 */
328 	protected abstract RefCache scanAllRefs() throws IOException;
329 
330 	/**
331 	 * Compare a reference, and put if it matches.
332 	 * <p>
333 	 * Two reference match if and only if they satisfy the following:
334 	 *
335 	 * <ul>
336 	 * <li>If one reference is a symbolic ref, the other one should be a symbolic
337 	 * ref.
338 	 * <li>If both are symbolic refs, the target names should be same.
339 	 * <li>If both are object ID refs, the object IDs should be same.
340 	 * </ul>
341 	 *
342 	 * @param oldRef
343 	 *            old value to compare to. If the reference is expected to not
344 	 *            exist the old value has a storage of
345 	 *            {@link org.eclipse.jgit.lib.Ref.Storage#NEW} and an ObjectId
346 	 *            value of {@code null}.
347 	 * @param newRef
348 	 *            new reference to store.
349 	 * @return true if the put was successful; false otherwise.
350 	 * @throws java.io.IOException
351 	 *             the reference cannot be put due to a system error.
352 	 */
353 	protected abstract boolean compareAndPut(Ref oldRef, Ref newRef)
354 			throws IOException;
355 
356 	/**
357 	 * Compare a reference, and delete if it matches.
358 	 *
359 	 * @param oldRef
360 	 *            the old reference information that was previously read.
361 	 * @return true if the remove was successful; false otherwise.
362 	 * @throws java.io.IOException
363 	 *             the reference could not be removed due to a system error.
364 	 */
365 	protected abstract boolean compareAndRemove(Ref oldRef) throws IOException;
366 
367 	/**
368 	 * Update the cached peeled state of a reference
369 	 * <p>
370 	 * The ref database invokes this method after it peels a reference that had
371 	 * not been peeled before. This allows the storage to cache the peel state
372 	 * of the reference, and if it is actually peelable, the target that it
373 	 * peels to, so that on-the-fly peeling doesn't have to happen on the next
374 	 * reference read.
375 	 *
376 	 * @param oldLeaf
377 	 *            the old reference.
378 	 * @param newLeaf
379 	 *            the new reference, with peel information.
380 	 */
381 	protected void cachePeeledState(Ref oldLeaf, Ref newLeaf) {
382 		try {
383 			compareAndPut(oldLeaf, newLeaf);
384 		} catch (IOException e) {
385 			// Ignore an exception during caching.
386 		}
387 	}
388 
389 	/** Collection of references managed by this database. */
390 	public static class RefCache {
391 		final RefList<Ref> ids;
392 
393 		final RefList<Ref> sym;
394 
395 		/**
396 		 * Initialize a new reference cache.
397 		 * <p>
398 		 * The two reference lists supplied must be sorted in correct order
399 		 * (string compare order) by name.
400 		 *
401 		 * @param ids
402 		 *            references that carry an ObjectId, and all of {@code sym}.
403 		 * @param sym
404 		 *            references that are symbolic references to others.
405 		 */
406 		public RefCache(RefList<Ref> ids, RefList<Ref> sym) {
407 			this.ids = ids;
408 			this.sym = sym;
409 		}
410 
411 		RefCache(RefList<Ref> ids, RefCache old) {
412 			this(ids, old.sym);
413 		}
414 
415 		/** @return number of references in this cache. */
416 		public int size() {
417 			return ids.size();
418 		}
419 
420 		/**
421 		 * Find a reference by name.
422 		 *
423 		 * @param name
424 		 *            full name of the reference.
425 		 * @return the reference, if it exists, otherwise null.
426 		 */
427 		public Ref get(String name) {
428 			return ids.get(name);
429 		}
430 
431 		/**
432 		 * Obtain a modified copy of the cache with a ref stored.
433 		 * <p>
434 		 * This cache instance is not modified by this method.
435 		 *
436 		 * @param ref
437 		 *            reference to add or replace.
438 		 * @return a copy of this cache, with the reference added or replaced.
439 		 */
440 		public RefCache put(Ref ref) {
441 			RefList<Ref> newIds = this.ids.put(ref);
442 			RefList<Ref> newSym = this.sym;
443 			if (ref.isSymbolic()) {
444 				newSym = newSym.put(ref);
445 			} else {
446 				int p = newSym.find(ref.getName());
447 				if (0 <= p)
448 					newSym = newSym.remove(p);
449 			}
450 			return new RefCache(newIds, newSym);
451 		}
452 
453 		/**
454 		 * Obtain a modified copy of the cache with the ref removed.
455 		 * <p>
456 		 * This cache instance is not modified by this method.
457 		 *
458 		 * @param refName
459 		 *            reference to remove, if it exists.
460 		 * @return a copy of this cache, with the reference removed.
461 		 */
462 		public RefCache remove(String refName) {
463 			RefList<Ref> newIds = this.ids;
464 			int p = newIds.find(refName);
465 			if (0 <= p)
466 				newIds = newIds.remove(p);
467 
468 			RefList<Ref> newSym = this.sym;
469 			p = newSym.find(refName);
470 			if (0 <= p)
471 				newSym = newSym.remove(p);
472 			return new RefCache(newIds, newSym);
473 		}
474 	}
475 }