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