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