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 public class RefTreeDatabase extends RefDatabase {
87 private final Repository repo;
88 private final RefDatabase bootstrap;
89 private final String txnCommitted;
90
91 @Nullable
92 private final String txnNamespace;
93 private volatile Scanner.Result refs;
94
95
96
97
98
99
100
101
102
103
104 public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
105 Config cfg = repo.getConfig();
106 String committed = cfg.getString("reftree", null, "committedRef");
107 if (committed == null || committed.isEmpty()) {
108 committed = "refs/txn/committed";
109 }
110
111 this.repo = repo;
112 this.bootstrap = bootstrap;
113 this.txnNamespace = initNamespace(committed);
114 this.txnCommitted = committed;
115 }
116
117
118
119
120
121
122
123
124
125
126
127
128 public RefTreeDatabase(Repository repo, RefDatabase bootstrap,
129 String txnCommitted) {
130 this.repo = repo;
131 this.bootstrap = bootstrap;
132 this.txnNamespace = initNamespace(txnCommitted);
133 this.txnCommitted = txnCommitted;
134 }
135
136 private static String initNamespace(String committed) {
137 int s = committed.lastIndexOf('/');
138 if (s < 0) {
139 return null;
140 }
141 return committed.substring(0, s + 1);
142 }
143
144 Repository getRepository() {
145 return repo;
146 }
147
148
149
150
151
152 public RefDatabase getBootstrap() {
153 return bootstrap;
154 }
155
156
157 public String getTxnCommitted() {
158 return txnCommitted;
159 }
160
161
162
163
164
165 @Nullable
166 public String getTxnNamespace() {
167 return txnNamespace;
168 }
169
170 @Override
171 public void create() throws IOException {
172 bootstrap.create();
173 }
174
175 @Override
176 public boolean performsAtomicTransactions() {
177 return true;
178 }
179
180 @Override
181 public void refresh() {
182 bootstrap.refresh();
183 }
184
185 @Override
186 public void close() {
187 refs = null;
188 bootstrap.close();
189 }
190
191 @Override
192 public Ref getRef(String name) throws IOException {
193 String[] needle = new String[SEARCH_PATH.length];
194 for (int i = 0; i < SEARCH_PATH.length; i++) {
195 needle[i] = SEARCH_PATH[i] + name;
196 }
197 return firstExactRef(needle);
198 }
199
200 @Override
201 public Ref exactRef(String name) throws IOException {
202 if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
203
204 return bootstrap.exactRef(name);
205 } else if (conflictsWithBootstrap(name)) {
206 return null;
207 }
208
209 boolean partial = false;
210 Ref src = bootstrap.exactRef(txnCommitted);
211 Scanner.Result c = refs;
212 if (c == null || !c.refTreeId.equals(idOf(src))) {
213 c = Scanner.scanRefTree(repo, src, prefixOf(name), false);
214 partial = true;
215 }
216
217 Ref r = c.all.get(name);
218 if (r != null && r.isSymbolic()) {
219 r = c.sym.get(name);
220 if (partial && r.getObjectId() == null) {
221
222
223
224 return getRefs(ALL).get(name);
225 }
226 }
227 return r;
228 }
229
230 private static String prefixOf(String name) {
231 int s = name.lastIndexOf('/');
232 if (s >= 0) {
233 return name.substring(0, s);
234 }
235 return "";
236 }
237
238 @Override
239 public Map<String, Ref> getRefs(String prefix) throws IOException {
240 if (!prefix.isEmpty() && prefix.charAt(prefix.length() - 1) != '/') {
241 return new HashMap<>(0);
242 }
243
244 Ref src = bootstrap.exactRef(txnCommitted);
245 Scanner.Result c = refs;
246 if (c == null || !c.refTreeId.equals(idOf(src))) {
247 c = Scanner.scanRefTree(repo, src, prefix, true);
248 if (prefix.isEmpty()) {
249 refs = c;
250 }
251 }
252 return new RefMap(prefix, RefList.<Ref> emptyList(), c.all, c.sym);
253 }
254
255 private static ObjectId idOf(@Nullable Ref src) {
256 return src != null && src.getObjectId() != null
257 ? src.getObjectId()
258 : ObjectId.zeroId();
259 }
260
261 @Override
262 public List<Ref> getAdditionalRefs() throws IOException {
263 Collection<Ref> txnRefs;
264 if (txnNamespace != null) {
265 txnRefs = bootstrap.getRefs(txnNamespace).values();
266 } else {
267 Ref r = bootstrap.exactRef(txnCommitted);
268 if (r != null && r.getObjectId() != null) {
269 txnRefs = Collections.singleton(r);
270 } else {
271 txnRefs = Collections.emptyList();
272 }
273 }
274
275 List<Ref> otherRefs = bootstrap.getAdditionalRefs();
276 List<Ref> all = new ArrayList<>(txnRefs.size() + otherRefs.size());
277 all.addAll(txnRefs);
278 all.addAll(otherRefs);
279 return all;
280 }
281
282 @Override
283 public Ref peel(Ref ref) throws IOException {
284 Ref i = ref.getLeaf();
285 ObjectId id = i.getObjectId();
286 if (i.isPeeled() || id == null) {
287 return ref;
288 }
289 try (RevWalk rw = new RevWalk(repo)) {
290 RevObject obj = rw.parseAny(id);
291 if (obj instanceof RevTag) {
292 ObjectId p = rw.peel(obj).copy();
293 i = new ObjectIdRef.PeeledTag(PACKED, i.getName(), id, p);
294 } else {
295 i = new ObjectIdRef.PeeledNonTag(PACKED, i.getName(), id);
296 }
297 }
298 return recreate(ref, i);
299 }
300
301 private static Ref recreate(Ref old, Ref leaf) {
302 if (old.isSymbolic()) {
303 Ref dst = recreate(old.getTarget(), leaf);
304 return new SymbolicRef(old.getName(), dst);
305 }
306 return leaf;
307 }
308
309 @Override
310 public boolean isNameConflicting(String name) throws IOException {
311 return conflictsWithBootstrap(name)
312 || !getConflictingNames(name).isEmpty();
313 }
314
315 @Override
316 public BatchRefUpdate newBatchUpdate() {
317 return new RefTreeBatch(this);
318 }
319
320 @Override
321 public RefUpdate newUpdate(String name, boolean detach) throws IOException {
322 if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
323 return bootstrap.newUpdate(name, detach);
324 }
325 if (conflictsWithBootstrap(name)) {
326 return new AlwaysFailUpdate(this, name);
327 }
328
329 Ref r = exactRef(name);
330 if (r == null) {
331 r = new ObjectIdRef.Unpeeled(Storage.NEW, name, null);
332 }
333
334 boolean detaching = detach && r.isSymbolic();
335 if (detaching) {
336 r = new ObjectIdRef.Unpeeled(LOOSE, name, r.getObjectId());
337 }
338
339 RefTreeUpdate u = new RefTreeUpdate(this, r);
340 if (detaching) {
341 u.setDetachingSymbolicRef();
342 }
343 return u;
344 }
345
346 @Override
347 public RefRename newRename(String fromName, String toName)
348 throws IOException {
349 RefUpdate from = newUpdate(fromName, true);
350 RefUpdate to = newUpdate(toName, true);
351 return new RefTreeRename(this, from, to);
352 }
353
354 boolean conflictsWithBootstrap(String name) {
355 if (txnNamespace != null && name.startsWith(txnNamespace)) {
356 return true;
357 } else if (txnCommitted.equals(name)) {
358 return true;
359 }
360
361 if (name.indexOf('/') < 0 && !HEAD.equals(name)) {
362 return true;
363 }
364
365 if (name.length() > txnCommitted.length()
366 && name.charAt(txnCommitted.length()) == '/'
367 && name.startsWith(txnCommitted)) {
368 return true;
369 }
370 return false;
371 }
372 }