1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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
78
79
80
81
82
83
84
85
86
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
99
100
101
102
103
104
105
106
107 public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
108 Config cfg = repo.getConfig();
109 String committed = cfg.getString("reftree", null, "committedRef");
110 if (committed == null || committed.isEmpty()) {
111 committed = "refs/txn/committed";
112 }
113
114 this.repo = repo;
115 this.bootstrap = bootstrap;
116 this.txnNamespace = initNamespace(committed);
117 this.txnCommitted = committed;
118 }
119
120
121
122
123
124
125
126
127
128
129
130
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);
146 }
147
148 Repository getRepository() {
149 return repo;
150 }
151
152
153
154
155
156
157
158 public RefDatabase getBootstrap() {
159 return bootstrap;
160 }
161
162
163
164
165
166
167 public String getTxnCommitted() {
168 return txnCommitted;
169 }
170
171
172
173
174
175
176
177 @Nullable
178 public String getTxnNamespace() {
179 return txnNamespace;
180 }
181
182
183 @Override
184 public void create() throws IOException {
185 bootstrap.create();
186 }
187
188
189 @Override
190 public boolean performsAtomicTransactions() {
191 return true;
192 }
193
194
195 @Override
196 public void refresh() {
197 bootstrap.refresh();
198 }
199
200
201 @Override
202 public void close() {
203 refs = null;
204 bootstrap.close();
205 }
206
207
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
218 @Override
219 public Ref exactRef(String name) throws IOException {
220 if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
221
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
240
241
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 "";
254 }
255
256
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
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
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
331 @Override
332 public boolean isNameConflicting(String name) throws IOException {
333 return conflictsWithBootstrap(name)
334 || !getConflictingNames(name).isEmpty();
335 }
336
337
338 @Override
339 public BatchRefUpdate newBatchUpdate() {
340 return new RefTreeBatch(this);
341 }
342
343
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
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 }