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