View Javadoc
1   /*
2    * Copyright (C) 2016, 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.reftree;
45  
46  import static org.eclipse.jgit.lib.Constants.HEAD;
47  import static org.eclipse.jgit.lib.Ref.Storage.LOOSE;
48  import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
49  
50  import java.io.IOException;
51  import java.util.ArrayList;
52  import java.util.Collection;
53  import java.util.Collections;
54  import java.util.HashMap;
55  import java.util.List;
56  import java.util.Map;
57  
58  import org.eclipse.jgit.annotations.Nullable;
59  import org.eclipse.jgit.lib.BatchRefUpdate;
60  import org.eclipse.jgit.lib.Config;
61  import org.eclipse.jgit.lib.ObjectId;
62  import org.eclipse.jgit.lib.ObjectIdRef;
63  import org.eclipse.jgit.lib.Ref;
64  import org.eclipse.jgit.lib.Ref.Storage;
65  import org.eclipse.jgit.lib.RefDatabase;
66  import org.eclipse.jgit.lib.RefRename;
67  import org.eclipse.jgit.lib.RefUpdate;
68  import org.eclipse.jgit.lib.Repository;
69  import org.eclipse.jgit.lib.SymbolicRef;
70  import org.eclipse.jgit.revwalk.RevObject;
71  import org.eclipse.jgit.revwalk.RevTag;
72  import org.eclipse.jgit.revwalk.RevWalk;
73  import org.eclipse.jgit.util.RefList;
74  import org.eclipse.jgit.util.RefMap;
75  
76  /**
77   * Reference database backed by a
78   * {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
79   * <p>
80   * The storage for RefTreeDatabase has two parts. The main part is a native Git
81   * tree object stored under the {@code refs/txn} namespace. To avoid cycles,
82   * references to {@code refs/txn} are not stored in that tree object, but
83   * instead in a "bootstrap" layer, which is a separate
84   * {@link org.eclipse.jgit.lib.RefDatabase} such as
85   * {@link org.eclipse.jgit.internal.storage.file.RefDirectory} using local
86   * reference files inside of {@code $GIT_DIR/refs}.
87   */
88  public class RefTreeDatabase extends RefDatabase {
89  	private final Repository repo;
90  	private final RefDatabase bootstrap;
91  	private final String txnCommitted;
92  
93  	@Nullable
94  	private final String txnNamespace;
95  	private volatile Scanner.Result refs;
96  
97  	/**
98  	 * Create a RefTreeDb for a repository.
99  	 *
100 	 * @param repo
101 	 *            the repository using references in this database.
102 	 * @param bootstrap
103 	 *            bootstrap reference database storing the references that
104 	 *            anchor the
105 	 *            {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
106 	 */
107 	public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
108 		Config cfg = repo.getConfig();
109 		String committed = cfg.getString("reftree", null, "committedRef"); //$NON-NLS-1$ //$NON-NLS-2$
110 		if (committed == null || committed.isEmpty()) {
111 			committed = "refs/txn/committed"; //$NON-NLS-1$
112 		}
113 
114 		this.repo = repo;
115 		this.bootstrap = bootstrap;
116 		this.txnNamespace = initNamespace(committed);
117 		this.txnCommitted = committed;
118 	}
119 
120 	/**
121 	 * Create a RefTreeDb for a repository.
122 	 *
123 	 * @param repo
124 	 *            the repository using references in this database.
125 	 * @param bootstrap
126 	 *            bootstrap reference database storing the references that
127 	 *            anchor the
128 	 *            {@link org.eclipse.jgit.internal.storage.reftree.RefTree}.
129 	 * @param txnCommitted
130 	 *            name of the bootstrap reference holding the committed RefTree.
131 	 */
132 	public RefTreeDatabase(Repository repo, RefDatabase bootstrap,
133 			String txnCommitted) {
134 		this.repo = repo;
135 		this.bootstrap = bootstrap;
136 		this.txnNamespace = initNamespace(txnCommitted);
137 		this.txnCommitted = txnCommitted;
138 	}
139 
140 	private static String initNamespace(String committed) {
141 		int s = committed.lastIndexOf('/');
142 		if (s < 0) {
143 			return null;
144 		}
145 		return committed.substring(0, s + 1); // Keep trailing '/'.
146 	}
147 
148 	Repository getRepository() {
149 		return repo;
150 	}
151 
152 	/**
153 	 * Get the bootstrap reference database
154 	 *
155 	 * @return the bootstrap reference database, which must be used to access
156 	 *         {@link #getTxnCommitted()}, {@link #getTxnNamespace()}.
157 	 */
158 	public RefDatabase getBootstrap() {
159 		return bootstrap;
160 	}
161 
162 	/**
163 	 * Get name of bootstrap reference anchoring committed RefTree.
164 	 *
165 	 * @return name of bootstrap reference anchoring committed RefTree.
166 	 */
167 	public String getTxnCommitted() {
168 		return txnCommitted;
169 	}
170 
171 	/**
172 	 * Get namespace used by bootstrap layer.
173 	 *
174 	 * @return namespace used by bootstrap layer, e.g. {@code refs/txn/}. Always
175 	 *         ends in {@code '/'}.
176 	 */
177 	@Nullable
178 	public String getTxnNamespace() {
179 		return txnNamespace;
180 	}
181 
182 	/** {@inheritDoc} */
183 	@Override
184 	public void create() throws IOException {
185 		bootstrap.create();
186 	}
187 
188 	/** {@inheritDoc} */
189 	@Override
190 	public boolean performsAtomicTransactions() {
191 		return true;
192 	}
193 
194 	/** {@inheritDoc} */
195 	@Override
196 	public void refresh() {
197 		bootstrap.refresh();
198 	}
199 
200 	/** {@inheritDoc} */
201 	@Override
202 	public void close() {
203 		refs = null;
204 		bootstrap.close();
205 	}
206 
207 	/** {@inheritDoc} */
208 	@Override
209 	public Ref getRef(String name) throws IOException {
210 		String[] needle = new String[SEARCH_PATH.length];
211 		for (int i = 0; i < SEARCH_PATH.length; i++) {
212 			needle[i] = SEARCH_PATH[i] + name;
213 		}
214 		return firstExactRef(needle);
215 	}
216 
217 	/** {@inheritDoc} */
218 	@Override
219 	public Ref exactRef(String name) throws IOException {
220 		if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
221 			// Pass through names like MERGE_HEAD, ORIG_HEAD, FETCH_HEAD.
222 			return bootstrap.exactRef(name);
223 		} else if (conflictsWithBootstrap(name)) {
224 			return null;
225 		}
226 
227 		boolean partial = false;
228 		Ref src = bootstrap.exactRef(txnCommitted);
229 		Scanner.Result c = refs;
230 		if (c == null || !c.refTreeId.equals(idOf(src))) {
231 			c = Scanner.scanRefTree(repo, src, prefixOf(name), false);
232 			partial = true;
233 		}
234 
235 		Ref r = c.all.get(name);
236 		if (r != null && r.isSymbolic()) {
237 			r = c.sym.get(name);
238 			if (partial && r.getObjectId() == null) {
239 				// Attempting exactRef("HEAD") with partial scan will leave
240 				// an unresolved symref as its target e.g. refs/heads/master
241 				// was not read by the partial scan. Scan everything instead.
242 				return getRefs(ALL).get(name);
243 			}
244 		}
245 		return r;
246 	}
247 
248 	private static String prefixOf(String name) {
249 		int s = name.lastIndexOf('/');
250 		if (s >= 0) {
251 			return name.substring(0, s);
252 		}
253 		return ""; //$NON-NLS-1$
254 	}
255 
256 	/** {@inheritDoc} */
257 	@Override
258 	public Map<String, Ref> getRefs(String prefix) throws IOException {
259 		if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') {
260 			return new HashMap<>(0);
261 		}
262 
263 		Ref src = bootstrap.exactRef(txnCommitted);
264 		Scanner.Result c = refs;
265 		if (c == null || !c.refTreeId.equals(idOf(src))) {
266 			c = Scanner.scanRefTree(repo, src, prefix, true);
267 			if (prefix.isEmpty()) {
268 				refs = c;
269 			}
270 		}
271 		return new RefMap(prefix, RefList.<Ref> emptyList(), c.all, c.sym);
272 	}
273 
274 	private static ObjectId idOf(@Nullable Ref src) {
275 		return src != null && src.getObjectId() != null
276 				? src.getObjectId()
277 				: ObjectId.zeroId();
278 	}
279 
280 	/** {@inheritDoc} */
281 	@Override
282 	public List<Ref> getAdditionalRefs() throws IOException {
283 		Collection<Ref> txnRefs;
284 		if (txnNamespace != null) {
285 			txnRefs = bootstrap.getRefsByPrefix(txnNamespace);
286 		} else {
287 			Ref r = bootstrap.exactRef(txnCommitted);
288 			if (r != null && r.getObjectId() != null) {
289 				txnRefs = Collections.singleton(r);
290 			} else {
291 				txnRefs = Collections.emptyList();
292 			}
293 		}
294 
295 		List<Ref> otherRefs = bootstrap.getAdditionalRefs();
296 		List<Ref> all = new ArrayList<>(txnRefs.size() + otherRefs.size());
297 		all.addAll(txnRefs);
298 		all.addAll(otherRefs);
299 		return all;
300 	}
301 
302 	/** {@inheritDoc} */
303 	@Override
304 	public Ref peel(Ref ref) throws IOException {
305 		Ref i = ref.getLeaf();
306 		ObjectId id = i.getObjectId();
307 		if (i.isPeeled() || id == null) {
308 			return ref;
309 		}
310 		try (RevWalk rw = new RevWalk(repo)) {
311 			RevObject obj = rw.parseAny(id);
312 			if (obj instanceof RevTag) {
313 				ObjectId p = rw.peel(obj).copy();
314 				i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p);
315 			} else {
316 				i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id);
317 			}
318 		}
319 		return recreate(ref, i);
320 	}
321 
322 	private static Ref recreate(Ref old, Ref leaf) {
323 		if (old.isSymbolic()) {
324 			Ref dst = recreate(old.getTarget(), leaf);
325 			return new SymbolicRef(old.getName(), dst);
326 		}
327 		return leaf;
328 	}
329 
330 	/** {@inheritDoc} */
331 	@Override
332 	public boolean isNameConflicting(String name) throws IOException {
333 		return conflictsWithBootstrap(name)
334 				|| !getConflictingNames(name).isEmpty();
335 	}
336 
337 	/** {@inheritDoc} */
338 	@Override
339 	public BatchRefUpdate newBatchUpdate() {
340 		return new RefTreeBatch(this);
341 	}
342 
343 	/** {@inheritDoc} */
344 	@Override
345 	public RefUpdate newUpdate(String name, boolean detach) throws IOException {
346 		if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
347 			return bootstrap.newUpdate(name, detach);
348 		}
349 		if (conflictsWithBootstrap(name)) {
350 			return new AlwaysFailUpdate(this, name);
351 		}
352 
353 		Ref r = exactRef(name);
354 		if (r == null) {
355 			r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null);
356 		}
357 
358 		boolean detaching = detach && r.isSymbolic();
359 		if (detaching) {
360 			r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId());
361 		}
362 
363 		RefTreeUpdate u = new RefTreeUpdate(this, r);
364 		if (detaching) {
365 			u.setDetachingSymbolicRef();
366 		}
367 		return u;
368 	}
369 
370 	/** {@inheritDoc} */
371 	@Override
372 	public RefRename newRename(String fromName, String toName)
373 			throws IOException {
374 		RefUpdate from = newUpdate(fromName, true);
375 		RefUpdate to = newUpdate(toName, true);
376 		return new RefTreeRename(this, from, to);
377 	}
378 
379 	boolean conflictsWithBootstrap(String name) {
380 		if (txnNamespace != null && name.startsWith(txnNamespace)) {
381 			return true;
382 		} else if (txnCommitted.equals(name)) {
383 			return true;
384 		}
385 
386 		if (name.indexOf('/') < 0 && !HEAD.equals(name)) {
387 			return true;
388 		}
389 
390 		if (name.length() > txnCommitted.length()
391 				&& name.charAt(txnCommitted.length()) == '/'
392 				&& name.startsWith(txnCommitted)) {
393 			return true;
394 		}
395 		return false;
396 	}
397 }