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.OBJ_BLOB;
47 import static org.eclipse.jgit.lib.Constants.R_REFS;
48 import static org.eclipse.jgit.lib.Constants.encode;
49 import static org.eclipse.jgit.lib.FileMode.TYPE_GITLINK;
50 import static org.eclipse.jgit.lib.FileMode.TYPE_SYMLINK;
51 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
52 import static org.eclipse.jgit.lib.Ref.Storage.NEW;
53 import static org.eclipse.jgit.lib.Ref.Storage.PACKED;
54 import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
55
56 import java.io.IOException;
57
58 import org.eclipse.jgit.annotations.Nullable;
59 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
60 import org.eclipse.jgit.lib.AnyObjectId;
61 import org.eclipse.jgit.lib.ObjectId;
62 import org.eclipse.jgit.lib.ObjectIdRef;
63 import org.eclipse.jgit.lib.ObjectReader;
64 import org.eclipse.jgit.lib.Ref;
65 import org.eclipse.jgit.lib.Repository;
66 import org.eclipse.jgit.lib.SymbolicRef;
67 import org.eclipse.jgit.revwalk.RevTree;
68 import org.eclipse.jgit.revwalk.RevWalk;
69 import org.eclipse.jgit.treewalk.AbstractTreeIterator;
70 import org.eclipse.jgit.treewalk.CanonicalTreeParser;
71 import org.eclipse.jgit.treewalk.TreeWalk;
72 import org.eclipse.jgit.util.Paths;
73 import org.eclipse.jgit.util.RawParseUtils;
74 import org.eclipse.jgit.util.RefList;
75
76
77 class Scanner {
78 private static final int MAX_SYMLINK_BYTES = 10 << 10;
79 private static final byte[] BINARY_R_REFS = encode(R_REFS);
80 private static final byte[] REFS_DOT_DOT = encode("refs/..");
81
82 static class Result {
83 final ObjectId refTreeId;
84 final RefList<Ref> all;
85 final RefList<Ref> sym;
86
87 Result(ObjectId id, RefList<Ref> all, RefList<Ref> sym) {
88 this.refTreeId = id;
89 this.all = all;
90 this.sym = sym;
91 }
92 }
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119 static Result scanRefTree(Repository repo, @Nullable Ref src, String prefix,
120 boolean recursive) throws IOException {
121 RefList.Builder<Ref> all = new RefList.Builder<>();
122 RefList.Builder<Ref> sym = new RefList.Builder<>();
123
124 ObjectId srcId;
125 if (src != null && src.getObjectId() != null) {
126 try (ObjectReader reader = repo.newObjectReader()) {
127 srcId = src.getObjectId();
128 scan(reader, srcId, prefix, recursive, all, sym);
129 }
130 } else {
131 srcId = ObjectId.zeroId();
132 }
133
134 RefList<Ref> aList = all.toRefList();
135 for (int idx = 0; idx < sym.size();) {
136 Ref s = sym.get(idx);
137 Ref r = resolve(s, 0, aList);
138 if (r != null) {
139 sym.set(idx++, r);
140 } else {
141
142 sym.remove(idx);
143 int rm = aList.find(s.getName());
144 if (0 <= rm) {
145 aList = aList.remove(rm);
146 }
147 }
148 }
149 return new Result(srcId, aList, sym.toRefList());
150 }
151
152 private static void scan(ObjectReader reader, AnyObjectId srcId,
153 String prefix, boolean recursive,
154 RefList.Builder<Ref> all, RefList.Builder<Ref> sym)
155 throws IncorrectObjectTypeException, IOException {
156 CanonicalTreeParser p = createParserAtPath(reader, srcId, prefix);
157 if (p == null) {
158 return;
159 }
160
161 while (!p.eof()) {
162 int mode = p.getEntryRawMode();
163 if (mode == TYPE_TREE) {
164 if (recursive) {
165 p = p.createSubtreeIterator(reader);
166 } else {
167 p = p.next();
168 }
169 continue;
170 }
171
172 if (!curElementHasPeelSuffix(p)) {
173 Ref r = toRef(reader, mode, p);
174 if (r != null) {
175 all.add(r);
176 if (r.isSymbolic()) {
177 sym.add(r);
178 }
179 }
180 } else if (mode == TYPE_GITLINK) {
181 peel(all, p);
182 }
183 p = p.next();
184 }
185 }
186
187 private static CanonicalTreeParser createParserAtPath(ObjectReader reader,
188 AnyObjectId srcId, String prefix) throws IOException {
189 ObjectId root = toTree(reader, srcId);
190 if (prefix.isEmpty()) {
191 return new CanonicalTreeParser(BINARY_R_REFS, reader, root);
192 }
193
194 String dir = RefTree.refPath(Paths.stripTrailingSeparator(prefix));
195 TreeWalk tw = TreeWalk.forPath(reader, dir, root);
196 if (tw == null || !tw.isSubtree()) {
197 return null;
198 }
199
200 ObjectId id = tw.getObjectId(0);
201 return new CanonicalTreeParser(encode(prefix), reader, id);
202 }
203
204 private static Ref resolve(Ref ref, int depth, RefList<Ref> refs)
205 throws IOException {
206 if (!ref.isSymbolic()) {
207 return ref;
208 } else if (MAX_SYMBOLIC_REF_DEPTH <= depth) {
209 return null;
210 }
211
212 Ref r = refs.get(ref.getTarget().getName());
213 if (r == null) {
214 return ref;
215 }
216
217 Ref dst = resolve(r, depth + 1, refs);
218 if (dst == null) {
219 return null;
220 }
221 return new SymbolicRef(ref.getName(), dst);
222 }
223
224 @SuppressWarnings("resource")
225 private static RevTree toTree(ObjectReader reader, AnyObjectId id)
226 throws IOException {
227 return new RevWalk(reader).parseTree(id);
228 }
229
230 private static boolean curElementHasPeelSuffix(AbstractTreeIterator itr) {
231 int n = itr.getEntryPathLength();
232 byte[] c = itr.getEntryPathBuffer();
233 return n > 2 && c[n - 2] == ' ' && c[n - 1] == '^';
234 }
235
236 private static void peel(RefList.Builder<Ref> all, CanonicalTreeParser p) {
237 String name = refName(p, true);
238 for (int idx = all.size() - 1; 0 <= idx; idx--) {
239 Ref r = all.get(idx);
240 int cmp = r.getName().compareTo(name);
241 if (cmp == 0) {
242 all.set(idx, new ObjectIdRef.PeeledTag(PACKED, r.getName(),
243 r.getObjectId(), p.getEntryObjectId()));
244 break;
245 } else if (cmp < 0) {
246
247 break;
248 }
249 }
250 }
251
252 private static Ref toRef(ObjectReader reader, int mode,
253 CanonicalTreeParser p) throws IOException {
254 if (mode == TYPE_GITLINK) {
255 String name = refName(p, false);
256 ObjectId id = p.getEntryObjectId();
257 return new ObjectIdRef.PeeledNonTag(PACKED, name, id);
258
259 } else if (mode == TYPE_SYMLINK) {
260 ObjectId id = p.getEntryObjectId();
261 byte[] bin = reader.open(id, OBJ_BLOB)
262 .getCachedBytes(MAX_SYMLINK_BYTES);
263 String dst = RawParseUtils.decode(bin);
264 Ref trg = new ObjectIdRef.Unpeeled(NEW, dst, null);
265 String name = refName(p, false);
266 return new SymbolicRef(name, trg);
267 }
268 return null;
269 }
270
271 private static String refName(CanonicalTreeParser p, boolean peel) {
272 byte[] buf = p.getEntryPathBuffer();
273 int len = p.getEntryPathLength();
274 if (peel) {
275 len -= 2;
276 }
277 int ptr = 0;
278 if (RawParseUtils.match(buf, ptr, REFS_DOT_DOT) > 0) {
279 ptr = 7;
280 }
281 return RawParseUtils.decode(buf, ptr, len);
282 }
283
284 private Scanner() {
285 }
286 }