1
2
3
4
5
6
7
8
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
45
46
47
48
49
50
51
52
53
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
66
67
68
69
70
71
72
73
74 public RefTreeDatabase(Repository repo, RefDatabase bootstrap) {
75 Config cfg = repo.getConfig();
76 String committed = cfg.getString("reftree", null, "committedRef");
77 if (committed == null || committed.isEmpty()) {
78 committed = "refs/txn/committed";
79 }
80
81 this.repo = repo;
82 this.bootstrap = bootstrap;
83 this.txnNamespace = initNamespace(committed);
84 this.txnCommitted = committed;
85 }
86
87
88
89
90
91
92
93
94
95
96
97
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);
113 }
114
115 Repository getRepository() {
116 return repo;
117 }
118
119
120
121
122
123
124
125 public RefDatabase getBootstrap() {
126 return bootstrap;
127 }
128
129
130
131
132
133
134 public String getTxnCommitted() {
135 return txnCommitted;
136 }
137
138
139
140
141
142
143
144 @Nullable
145 public String getTxnNamespace() {
146 return txnNamespace;
147 }
148
149
150 @Override
151 public void create() throws IOException {
152 bootstrap.create();
153 }
154
155
156 @Override
157 public boolean performsAtomicTransactions() {
158 return true;
159 }
160
161
162 @Override
163 public void refresh() {
164 bootstrap.refresh();
165 }
166
167
168 @Override
169 public void close() {
170 refs = null;
171 bootstrap.close();
172 }
173
174
175 @Override
176 public Ref exactRef(String name) throws IOException {
177 if (!repo.isBare() && name.indexOf('/') < 0 && !HEAD.equals(name)) {
178
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
197
198
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 "";
211 }
212
213
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
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
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
288 @Override
289 public boolean isNameConflicting(String name) throws IOException {
290 return conflictsWithBootstrap(name)
291 || !getConflictingNames(name).isEmpty();
292 }
293
294
295 @Override
296 public BatchRefUpdate newBatchUpdate() {
297 return new RefTreeBatch(this);
298 }
299
300
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
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 }