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