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