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