1
2
3
4
5
6
7
8
9
10
11
12 package org.eclipse.jgit.dircache;
13
14 import static java.nio.charset.StandardCharsets.UTF_8;
15 import static org.eclipse.jgit.lib.FileMode.TREE;
16 import static org.eclipse.jgit.lib.TreeFormatter.entrySize;
17
18 import java.io.IOException;
19 import java.io.OutputStream;
20 import java.nio.ByteBuffer;
21 import java.util.Arrays;
22 import java.util.Comparator;
23
24 import org.eclipse.jgit.errors.UnmergedPathException;
25 import org.eclipse.jgit.lib.Constants;
26 import org.eclipse.jgit.lib.ObjectId;
27 import org.eclipse.jgit.lib.ObjectInserter;
28 import org.eclipse.jgit.lib.TreeFormatter;
29 import org.eclipse.jgit.util.MutableInteger;
30 import org.eclipse.jgit.util.RawParseUtils;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46 public class DirCacheTree {
47 private static final byte[] NO_NAME = {};
48
49 private static final DirCacheTree[] NO_CHILDREN = {};
50
51 private static final Comparator<DirCacheTree> TREE_CMP = (DirCacheTree o1,
52 DirCacheTree o2) -> {
53 final byte[] a = o1.encodedName;
54 final byte[] b = o2.encodedName;
55 final int aLen = a.length;
56 final int bLen = b.length;
57 int cPos;
58 for (cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
59 final int cmp = (a[cPos] & 0xff) - (b[cPos] & 0xff);
60 if (cmp != 0) {
61 return cmp;
62 }
63 }
64 if (aLen == bLen) {
65 return 0;
66 }
67 if (aLen < bLen) {
68 return '/' - (b[cPos] & 0xff);
69 }
70 return (a[cPos] & 0xff) - '/';
71 };
72
73
74 private DirCacheTree parent;
75
76
77 byte[] encodedName;
78
79
80 private int entrySpan;
81
82
83 private ObjectId id;
84
85
86 private DirCacheTree[] children;
87
88
89 private int childCnt;
90
91 DirCacheTree() {
92 encodedName = NO_NAME;
93 children = NO_CHILDREN;
94 childCnt = 0;
95 entrySpan = -1;
96 }
97
98 private DirCacheTree(final DirCacheTree myParent, final byte[] path,
99 final int pathOff, final int pathLen) {
100 parent = myParent;
101 encodedName = new byte[pathLen];
102 System.arraycopy(path, pathOff, encodedName, 0, pathLen);
103 children = NO_CHILDREN;
104 childCnt = 0;
105 entrySpan = -1;
106 }
107
108 DirCacheTree(final byte[] in, final MutableInteger off,
109 final DirCacheTree myParent) {
110 parent = myParent;
111
112 int ptr = RawParseUtils.next(in, off.value, '\0');
113 final int nameLen = ptr - off.value - 1;
114 if (nameLen > 0) {
115 encodedName = new byte[nameLen];
116 System.arraycopy(in, off.value, encodedName, 0, nameLen);
117 } else
118 encodedName = NO_NAME;
119
120 entrySpan = RawParseUtils.parseBase10(in, ptr, off);
121 final int subcnt = RawParseUtils.parseBase10(in, off.value, off);
122 off.value = RawParseUtils.next(in, off.value, '\n');
123
124 if (entrySpan >= 0) {
125
126
127
128 id = ObjectId.fromRaw(in, off.value);
129 off.value += Constants.OBJECT_ID_LENGTH;
130 }
131
132 if (subcnt > 0) {
133 boolean alreadySorted = true;
134 children = new DirCacheTree[subcnt];
135 for (int i = 0; i < subcnt; i++) {
136 children[i] = new DirCacheTree(in, off, this);
137
138
139
140
141
142
143 if (alreadySorted && i > 0
144 && TREE_CMP.compare(children[i - 1], children[i]) > 0)
145 alreadySorted = false;
146 }
147 if (!alreadySorted)
148 Arrays.sort(children, 0, subcnt, TREE_CMP);
149 } else {
150
151
152 children = NO_CHILDREN;
153 }
154 childCnt = subcnt;
155 }
156
157 void write(byte[] tmp, OutputStream os) throws IOException {
158 int ptr = tmp.length;
159 tmp[--ptr] = '\n';
160 ptr = RawParseUtils.formatBase10(tmp, ptr, childCnt);
161 tmp[--ptr] = ' ';
162 ptr = RawParseUtils.formatBase10(tmp, ptr, isValid() ? entrySpan : -1);
163 tmp[--ptr] = 0;
164
165 os.write(encodedName);
166 os.write(tmp, ptr, tmp.length - ptr);
167 if (isValid()) {
168 id.copyRawTo(tmp, 0);
169 os.write(tmp, 0, Constants.OBJECT_ID_LENGTH);
170 }
171 for (int i = 0; i < childCnt; i++)
172 children[i].write(tmp, os);
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186
187 public boolean isValid() {
188 return id != null;
189 }
190
191
192
193
194
195
196
197
198
199
200 public int getEntrySpan() {
201 return entrySpan;
202 }
203
204
205
206
207
208
209 public int getChildCount() {
210 return childCnt;
211 }
212
213
214
215
216
217
218
219
220 public DirCacheTree getChild(int i) {
221 return children[i];
222 }
223
224
225
226
227
228
229
230
231
232 public ObjectId getObjectId() {
233 return id;
234 }
235
236
237
238
239
240
241
242
243
244
245
246 public String getNameString() {
247 final ByteBuffer bb = ByteBuffer.wrap(encodedName);
248 return UTF_8.decode(bb).toString();
249 }
250
251
252
253
254
255
256
257
258
259
260
261
262
263 public String getPathString() {
264 final StringBuilder r = new StringBuilder();
265 appendName(r);
266 return r.toString();
267 }
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293 ObjectId writeTree(final DirCacheEntry[] cache, int cIdx,
294 final int pathOffset, final ObjectInserter ow)
295 throws UnmergedPathException, IOException {
296 if (id == null) {
297 final int endIdx = cIdx + entrySpan;
298 final TreeFormatter fmt = new TreeFormatter(computeSize(cache,
299 cIdx, pathOffset, ow));
300 int childIdx = 0;
301 int entryIdx = cIdx;
302
303 while (entryIdx < endIdx) {
304 final DirCacheEntry e = cache[entryIdx];
305 final byte[] ep = e.path;
306 if (childIdx < childCnt) {
307 final DirCacheTree st = children[childIdx];
308 if (st.contains(ep, pathOffset, ep.length)) {
309 fmt.append(st.encodedName, TREE, st.id);
310 entryIdx += st.entrySpan;
311 childIdx++;
312 continue;
313 }
314 }
315
316 fmt.append(ep, pathOffset, ep.length - pathOffset, e
317 .getFileMode(), e.idBuffer(), e.idOffset());
318 entryIdx++;
319 }
320
321 id = ow.insert(fmt);
322 }
323 return id;
324 }
325
326 private int computeSize(final DirCacheEntry[] cache, int cIdx,
327 final int pathOffset, final ObjectInserter ow)
328 throws UnmergedPathException, IOException {
329 final int endIdx = cIdx + entrySpan;
330 int childIdx = 0;
331 int entryIdx = cIdx;
332 int size = 0;
333
334 while (entryIdx < endIdx) {
335 final DirCacheEntry e = cache[entryIdx];
336 if (e.getStage() != 0)
337 throw new UnmergedPathException(e);
338
339 final byte[] ep = e.path;
340 if (childIdx < childCnt) {
341 final DirCacheTree st = children[childIdx];
342 if (st.contains(ep, pathOffset, ep.length)) {
343 final int stOffset = pathOffset + st.nameLength() + 1;
344 st.writeTree(cache, entryIdx, stOffset, ow);
345
346 size += entrySize(TREE, st.nameLength());
347
348 entryIdx += st.entrySpan;
349 childIdx++;
350 continue;
351 }
352 }
353
354 size += entrySize(e.getFileMode(), ep.length - pathOffset);
355 entryIdx++;
356 }
357
358 return size;
359 }
360
361 private void appendName(StringBuilder r) {
362 if (parent != null) {
363 parent.appendName(r);
364 r.append(getNameString());
365 r.append('/');
366 } else if (nameLength() > 0) {
367 r.append(getNameString());
368 r.append('/');
369 }
370 }
371
372 final int nameLength() {
373 return encodedName.length;
374 }
375
376 final boolean contains(byte[] a, int aOff, int aLen) {
377 final byte[] e = encodedName;
378 final int eLen = e.length;
379 for (int eOff = 0; eOff < eLen && aOff < aLen; eOff++, aOff++)
380 if (e[eOff] != a[aOff])
381 return false;
382 if (aOff >= aLen)
383 return false;
384 return a[aOff] == '/';
385 }
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406 void validate(final DirCacheEntry[] cache, final int cCnt, int cIdx,
407 final int pathOff) {
408 if (entrySpan >= 0 && cIdx + entrySpan <= cCnt) {
409
410
411
412 return;
413 }
414
415 entrySpan = 0;
416 if (cCnt == 0) {
417
418
419 return;
420 }
421
422 final byte[] firstPath = cache[cIdx].path;
423 int stIdx = 0;
424 while (cIdx < cCnt) {
425 final byte[] currPath = cache[cIdx].path;
426 if (pathOff > 0 && !peq(firstPath, currPath, pathOff)) {
427
428
429
430 break;
431 }
432
433 DirCacheTree st = stIdx < childCnt ? children[stIdx] : null;
434 final int cc = namecmp(currPath, pathOff, st);
435 if (cc > 0) {
436
437
438 removeChild(stIdx);
439 continue;
440 }
441
442 if (cc < 0) {
443 final int p = slash(currPath, pathOff);
444 if (p < 0) {
445
446
447
448 cIdx++;
449 entrySpan++;
450 continue;
451 }
452
453
454
455 st = new DirCacheTree(this, currPath, pathOff, p - pathOff);
456 insertChild(stIdx, st);
457 }
458
459
460
461 assert(st != null);
462 st.validate(cache, cCnt, cIdx, pathOff + st.nameLength() + 1);
463 cIdx += st.entrySpan;
464 entrySpan += st.entrySpan;
465 stIdx++;
466 }
467
468
469
470
471 while (stIdx < childCnt)
472 removeChild(childCnt - 1);
473 }
474
475 private void insertChild(int stIdx, DirCacheTree st) {
476 final DirCacheTree[] c = children;
477 if (childCnt + 1 <= c.length) {
478 if (stIdx < childCnt)
479 System.arraycopy(c, stIdx, c, stIdx + 1, childCnt - stIdx);
480 c[stIdx] = st;
481 childCnt++;
482 return;
483 }
484
485 final int n = c.length;
486 final DirCacheTree[] a = new DirCacheTree[n + 1];
487 if (stIdx > 0)
488 System.arraycopy(c, 0, a, 0, stIdx);
489 a[stIdx] = st;
490 if (stIdx < n)
491 System.arraycopy(c, stIdx, a, stIdx + 1, n - stIdx);
492 children = a;
493 childCnt++;
494 }
495
496 private void removeChild(int stIdx) {
497 final int n = --childCnt;
498 if (stIdx < n)
499 System.arraycopy(children, stIdx + 1, children, stIdx, n - stIdx);
500 children[n] = null;
501 }
502
503 static boolean peq(byte[] a, byte[] b, int aLen) {
504 if (b.length < aLen)
505 return false;
506 for (aLen--; aLen >= 0; aLen--)
507 if (a[aLen] != b[aLen])
508 return false;
509 return true;
510 }
511
512 private static int namecmp(byte[] a, int aPos, DirCacheTree ct) {
513 if (ct == null)
514 return -1;
515 final byte[] b = ct.encodedName;
516 final int aLen = a.length;
517 final int bLen = b.length;
518 int bPos = 0;
519 for (; aPos < aLen && bPos < bLen; aPos++, bPos++) {
520 final int cmp = (a[aPos] & 0xff) - (b[bPos] & 0xff);
521 if (cmp != 0)
522 return cmp;
523 }
524 if (bPos == bLen)
525 return a[aPos] == '/' ? 0 : -1;
526 return aLen - bLen;
527 }
528
529 private static int slash(byte[] a, int aPos) {
530 final int aLen = a.length;
531 for (; aPos < aLen; aPos++)
532 if (a[aPos] == '/')
533 return aPos;
534 return -1;
535 }
536
537
538 @Override
539 public String toString() {
540 return getNameString();
541 }
542 }