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.Constants.OBJ_BLOB;
48 import static org.eclipse.jgit.lib.Constants.R_REFS;
49 import static org.eclipse.jgit.lib.Constants.encode;
50 import static org.eclipse.jgit.lib.FileMode.GITLINK;
51 import static org.eclipse.jgit.lib.FileMode.SYMLINK;
52 import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
53 import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
54 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
55 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
56 import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
57 import static org.eclipse.jgit.transport.ReceiveCommand.Result.LOCK_FAILURE;
58 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
59
60 import java.io.IOException;
61 import java.util.Collection;
62 import java.util.HashMap;
63 import java.util.Map;
64
65 import org.eclipse.jgit.annotations.Nullable;
66 import org.eclipse.jgit.dircache.DirCache;
67 import org.eclipse.jgit.dircache.DirCacheBuilder;
68 import org.eclipse.jgit.dircache.DirCacheEditor;
69 import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
70 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
71 import org.eclipse.jgit.dircache.DirCacheEntry;
72 import org.eclipse.jgit.errors.CorruptObjectException;
73 import org.eclipse.jgit.errors.DirCacheNameConflictException;
74 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
75 import org.eclipse.jgit.errors.MissingObjectException;
76 import org.eclipse.jgit.internal.JGitText;
77 import org.eclipse.jgit.lib.FileMode;
78 import org.eclipse.jgit.lib.ObjectId;
79 import org.eclipse.jgit.lib.ObjectIdRef;
80 import org.eclipse.jgit.lib.ObjectInserter;
81 import org.eclipse.jgit.lib.ObjectReader;
82 import org.eclipse.jgit.lib.Ref;
83 import org.eclipse.jgit.lib.Repository;
84 import org.eclipse.jgit.lib.SymbolicRef;
85 import org.eclipse.jgit.revwalk.RevTree;
86 import org.eclipse.jgit.util.RawParseUtils;
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108 public class RefTree {
109
110 public static final String PEELED_SUFFIX = " ^";
111 static final String ROOT_DOTDOT = "..";
112
113
114
115
116
117
118 public static RefTree newEmptyTree() {
119 return new RefTree(DirCache.newInCore());
120 }
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139 public static RefTree read(ObjectReader reader, RevTree tree)
140 throws MissingObjectException, IncorrectObjectTypeException,
141 CorruptObjectException, IOException {
142 return new RefTree(DirCache.read(reader, tree));
143 }
144
145 private DirCache contents;
146 private Map<ObjectId, String> pendingBlobs;
147
148 private RefTree(DirCache dc) {
149 this.contents = dc;
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172 @Nullable
173 public Ref exactRef(ObjectReader reader, String name) throws IOException {
174 Ref r = readRef(reader, name);
175 if (r == null) {
176 return null;
177 } else if (r.isSymbolic()) {
178 return resolve(reader, r, 0);
179 }
180
181 DirCacheEntry p = contents.getEntry(peeledPath(name));
182 if (p != null && p.getRawMode() == TYPE_GITLINK) {
183 return new ObjectIdRef.PeeledTag(PACKED, r.getName(),
184 r.getObjectId(), p.getObjectId());
185 }
186 return r;
187 }
188
189 private Ref readRef(ObjectReader reader, String name) throws IOException {
190 DirCacheEntry e = contents.getEntry(refPath(name));
191 return e != null ? toRef(reader, e, name) : null;
192 }
193
194 private Ref toRef(ObjectReader reader, DirCacheEntry e, String name)
195 throws IOException {
196 int mode = e.getRawMode();
197 if (mode == TYPE_GITLINK) {
198 ObjectId id = e.getObjectId();
199 return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
200 }
201
202 if (mode == TYPE_SYMLINK) {
203 ObjectId id = e.getObjectId();
204 String n = pendingBlobs != null ? pendingBlobs.get(id) : null;
205 if (n == null) {
206 byte[] bin = reader.open(id, OBJ_BLOB).getCachedBytes();
207 n = RawParseUtils.decode(bin);
208 }
209 Ref dst = new ObjectIdRef.Unpeeled(NEW, n, null);
210 return new SymbolicRef(name, dst);
211 }
212
213 return null;
214 }
215
216 private Ref resolve(ObjectReader reader, Ref ref, int depth)
217 throws IOException {
218 if (ref.isSymbolic() && depth < MAX_SYMBOLIC_REF_DEPTH) {
219 Ref r = readRef(reader, ref.getTarget().getName());
220 if (r == null) {
221 return ref;
222 }
223 Ref dst = resolve(reader, r, depth + 1);
224 return new SymbolicRef(ref.getName(), dst);
225 }
226 return ref;
227 }
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243 public boolean apply(Collection<Command> cmdList) {
244 try {
245 DirCacheEditor ed = contents.editor();
246 for (Command cmd : cmdList) {
247 if (!isValidRef(cmd)) {
248 cmd.setResult(REJECTED_OTHER_REASON,
249 JGitText.get().funnyRefname);
250 Command.abort(cmdList, null);
251 return false;
252 }
253 apply(ed, cmd);
254 }
255 ed.finish();
256 return true;
257 } catch (DirCacheNameConflictException e) {
258 String r1 = refName(e.getPath1());
259 String r2 = refName(e.getPath2());
260 for (Command cmd : cmdList) {
261 if (r1.equals(cmd.getRefName())
262 || r2.equals(cmd.getRefName())) {
263 cmd.setResult(LOCK_FAILURE);
264 break;
265 }
266 }
267 Command.abort(cmdList, null);
268 return false;
269 } catch (LockFailureException e) {
270 Command.abort(cmdList, null);
271 return false;
272 }
273 }
274
275 private static boolean isValidRef(Command cmd) {
276 String n = cmd.getRefName();
277 return HEAD.equals(n) || Repository.isValidRefName(n);
278 }
279
280 private void apply(DirCacheEditor ed, final Command cmd) {
281 String path = refPath(cmd.getRefName());
282 Ref oldRef = cmd.getOldRef();
283 final Ref newRef = cmd.getNewRef();
284
285 if (newRef == null) {
286 checkRef(contents.getEntry(path), cmd);
287 ed.add(new DeletePath(path));
288 cleanupPeeledRef(ed, oldRef);
289 return;
290 }
291
292 if (newRef.isSymbolic()) {
293 final String dst = newRef.getTarget().getName();
294 ed.add(new PathEdit(path) {
295 @Override
296 public void apply(DirCacheEntry ent) {
297 checkRef(ent, cmd);
298 ObjectId id = Command.symref(dst);
299 ent.setFileMode(SYMLINK);
300 ent.setObjectId(id);
301 if (pendingBlobs == null) {
302 pendingBlobs = new HashMap<>(4);
303 }
304 pendingBlobs.put(id, dst);
305 }
306 }.setReplace(false));
307 cleanupPeeledRef(ed, oldRef);
308 return;
309 }
310
311 ed.add(new PathEdit(path) {
312 @Override
313 public void apply(DirCacheEntry ent) {
314 checkRef(ent, cmd);
315 ent.setFileMode(GITLINK);
316 ent.setObjectId(newRef.getObjectId());
317 }
318 }.setReplace(false));
319
320 if (newRef.getPeeledObjectId() != null) {
321 ed.add(new PathEdit(peeledPath(newRef.getName())) {
322 @Override
323 public void apply(DirCacheEntry ent) {
324 ent.setFileMode(GITLINK);
325 ent.setObjectId(newRef.getPeeledObjectId());
326 }
327 }.setReplace(false));
328 } else {
329 cleanupPeeledRef(ed, oldRef);
330 }
331 }
332
333 private static void checkRef(@Nullable DirCacheEntry ent, Command cmd) {
334 if (!cmd.checkRef(ent)) {
335 cmd.setResult(LOCK_FAILURE);
336 throw new LockFailureException();
337 }
338 }
339
340 private static void cleanupPeeledRef(DirCacheEditor ed, Ref ref) {
341 if (ref != null && !ref.isSymbolic()
342 && (!ref.isPeeled() || ref.getPeeledObjectId() != null)) {
343 ed.add(new DeletePath(peeledPath(ref.getName())));
344 }
345 }
346
347
348
349
350
351
352
353
354
355 public static String refName(String path) {
356 if (path.startsWith(ROOT_DOTDOT)) {
357 return path.substring(2);
358 }
359 return R_REFS + path;
360 }
361
362 static String refPath(String name) {
363 if (name.startsWith(R_REFS)) {
364 return name.substring(R_REFS.length());
365 }
366 return ROOT_DOTDOT + name;
367 }
368
369 private static String peeledPath(String name) {
370 return refPath(name) + PEELED_SUFFIX;
371 }
372
373
374
375
376
377
378
379
380
381
382
383
384 public ObjectId writeTree(ObjectInserter inserter) throws IOException {
385 if (pendingBlobs != null) {
386 for (String s : pendingBlobs.values()) {
387 inserter.insert(OBJ_BLOB, encode(s));
388 }
389 pendingBlobs = null;
390 }
391 return contents.writeTree(inserter);
392 }
393
394
395 public RefTree copy() {
396 RefTree r = new RefTree(DirCache.newInCore());
397 DirCacheBuilder b = r.contents.builder();
398 for (int i = 0; i < contents.getEntryCount(); i++) {
399 b.add(new DirCacheEntry(contents.getEntry(i)));
400 }
401 b.finish();
402 if (pendingBlobs != null) {
403 r.pendingBlobs = new HashMap<>(pendingBlobs);
404 }
405 return r;
406 }
407
408 private static class LockFailureException extends RuntimeException {
409 private static final long serialVersionUID = 1L;
410 }
411 }