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