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