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