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.dircache.DirCache.cmp;
48 import static org.eclipse.jgit.dircache.DirCacheTree.peq;
49 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
50
51 import java.io.IOException;
52 import java.text.MessageFormat;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.Comparator;
56 import java.util.List;
57
58 import org.eclipse.jgit.internal.JGitText;
59 import org.eclipse.jgit.lib.Constants;
60 import org.eclipse.jgit.util.Paths;
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 public class DirCacheEditor extends BaseDirCacheEditor {
78 private static final Comparator<PathEdit> EDIT_CMP = (PathEdit o1,
79 PathEdit o2) -> {
80 final byte[] a = o1.path;
81 final byte[] b = o2.path;
82 return cmp(a, a.length, b, b.length);
83 };
84
85 private final List<PathEdit> edits;
86 private int editIdx;
87
88
89
90
91
92
93
94
95
96
97 protected DirCacheEditor(DirCache dc, int ecnt) {
98 super(dc, ecnt);
99 edits = new ArrayList<>();
100 }
101
102
103
104
105
106
107
108
109
110
111
112 public void add(PathEdit edit) {
113 edits.add(edit);
114 }
115
116
117 @Override
118 public boolean commit() throws IOException {
119 if (edits.isEmpty()) {
120
121
122 cache.unlock();
123 return true;
124 }
125 return super.commit();
126 }
127
128
129 @Override
130 public void finish() {
131 if (!edits.isEmpty()) {
132 applyEdits();
133 replace();
134 }
135 }
136
137 private void applyEdits() {
138 Collections.sort(edits, EDIT_CMP);
139 editIdx = 0;
140
141 final int maxIdx = cache.getEntryCount();
142 int lastIdx = 0;
143 while (editIdx < edits.size()) {
144 PathEdit e = edits.get(editIdx++);
145 int eIdx = cache.findEntry(lastIdx, e.path, e.path.length);
146 final boolean missing = eIdx < 0;
147 if (eIdx < 0)
148 eIdx = -(eIdx + 1);
149 final int cnt = Math.min(eIdx, maxIdx) - lastIdx;
150 if (cnt > 0)
151 fastKeep(lastIdx, cnt);
152
153 if (e instanceof DeletePath) {
154 lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
155 continue;
156 }
157 if (e instanceof DeleteTree) {
158 lastIdx = cache.nextEntry(e.path, e.path.length, eIdx);
159 continue;
160 }
161
162 if (missing) {
163 DirCacheEntry ent = new DirCacheEntry(e.path);
164 e.apply(ent);
165 if (ent.getRawMode() == 0) {
166 throw new IllegalArgumentException(MessageFormat.format(
167 JGitText.get().fileModeNotSetForPath,
168 ent.getPathString()));
169 }
170 lastIdx = e.replace
171 ? deleteOverlappingSubtree(ent, eIdx)
172 : eIdx;
173 fastAdd(ent);
174 } else {
175
176 lastIdx = cache.nextEntry(eIdx);
177 for (int i = eIdx; i < lastIdx; i++) {
178 final DirCacheEntry ent = cache.getEntry(i);
179 e.apply(ent);
180 fastAdd(ent);
181 }
182 }
183 }
184
185 final int cnt = maxIdx - lastIdx;
186 if (cnt > 0)
187 fastKeep(lastIdx, cnt);
188 }
189
190 private int deleteOverlappingSubtree(DirCacheEntry ent, int eIdx) {
191 byte[] entPath = ent.path;
192 int entLen = entPath.length;
193
194
195
196
197
198 for (int p = pdir(entPath, entLen); p > 0; p = pdir(entPath, p)) {
199 int i = findEntry(entPath, p);
200 if (i >= 0) {
201
202
203
204 int n = --entryCnt - i;
205 System.arraycopy(entries, i + 1, entries, i, n);
206 break;
207 }
208
209
210
211 i = -(i + 1);
212 if (i < entryCnt && inDir(entries[i], entPath, p)) {
213 break;
214 }
215 }
216
217 int maxEnt = cache.getEntryCount();
218 if (eIdx >= maxEnt) {
219 return maxEnt;
220 }
221
222 DirCacheEntry next = cache.getEntry(eIdx);
223 if (Paths.compare(next.path, 0, next.path.length, 0,
224 entPath, 0, entLen, TYPE_TREE) < 0) {
225
226
227
228 insertEdit(new DeleteTree(entPath));
229 return eIdx;
230 }
231
232
233 while (eIdx < maxEnt && inDir(cache.getEntry(eIdx), entPath, entLen)) {
234 eIdx++;
235 }
236 return eIdx;
237 }
238
239 private int findEntry(byte[] p, int pLen) {
240 int low = 0;
241 int high = entryCnt;
242 while (low < high) {
243 int mid = (low + high) >>> 1;
244 int cmp = cmp(p, pLen, entries[mid]);
245 if (cmp < 0) {
246 high = mid;
247 } else if (cmp == 0) {
248 while (mid > 0 && cmp(p, pLen, entries[mid - 1]) == 0) {
249 mid--;
250 }
251 return mid;
252 } else {
253 low = mid + 1;
254 }
255 }
256 return -(low + 1);
257 }
258
259 private void insertEdit(DeleteTree d) {
260 for (int i = editIdx; i < edits.size(); i++) {
261 int cmp = EDIT_CMP.compare(d, edits.get(i));
262 if (cmp < 0) {
263 edits.add(i, d);
264 return;
265 } else if (cmp == 0) {
266 return;
267 }
268 }
269 edits.add(d);
270 }
271
272 private static boolean inDir(DirCacheEntry e, byte[] path, int pLen) {
273 return e.path.length > pLen && e.path[pLen] == '/'
274 && peq(path, e.path, pLen);
275 }
276
277 private static int pdir(byte[] path, int e) {
278 for (e--; e > 0; e--) {
279 if (path[e] == '/') {
280 return e;
281 }
282 }
283 return 0;
284 }
285
286
287
288
289
290
291
292
293
294
295 public abstract static class PathEdit {
296 final byte[] path;
297 boolean replace = true;
298
299
300
301
302
303
304
305 public PathEdit(String entryPath) {
306 path = Constants.encode(entryPath);
307 }
308
309 PathEdit(byte[] path) {
310 this.path = path;
311 }
312
313
314
315
316
317
318
319
320 public PathEdit(DirCacheEntry ent) {
321 path = ent.path;
322 }
323
324
325
326
327
328
329
330
331
332
333
334
335 public PathEdit setReplace(boolean ok) {
336 replace = ok;
337 return this;
338 }
339
340
341
342
343
344
345
346
347
348
349
350 public abstract void apply(DirCacheEntry ent);
351
352 @Override
353 public String toString() {
354 String p = DirCacheEntry.toString(path);
355 return getClass().getSimpleName() + '[' + p + ']';
356 }
357 }
358
359
360
361
362
363
364
365
366
367
368 public static final class DeletePath extends PathEdit {
369
370
371
372
373
374
375 public DeletePath(String entryPath) {
376 super(entryPath);
377 }
378
379
380
381
382
383
384
385
386 public DeletePath(DirCacheEntry ent) {
387 super(ent);
388 }
389
390 @Override
391 public void apply(DirCacheEntry ent) {
392 throw new UnsupportedOperationException(JGitText.get().noApplyInDelete);
393 }
394 }
395
396
397
398
399
400
401
402
403
404
405
406
407
408 public static final class DeleteTree extends PathEdit {
409
410
411
412
413
414
415
416
417
418 public DeleteTree(String entryPath) {
419 super(entryPath.isEmpty()
420 || entryPath.charAt(entryPath.length() - 1) == '/'
421 ? entryPath
422 : entryPath + '/');
423 }
424
425 DeleteTree(byte[] path) {
426 super(appendSlash(path));
427 }
428
429 private static byte[] appendSlash(byte[] path) {
430 int n = path.length;
431 if (n > 0 && path[n - 1] != '/') {
432 byte[] r = new byte[n + 1];
433 System.arraycopy(path, 0, r, 0, n);
434 r[n] = '/';
435 return r;
436 }
437 return path;
438 }
439
440 @Override
441 public void apply(DirCacheEntry ent) {
442 throw new UnsupportedOperationException(JGitText.get().noApplyInDelete);
443 }
444 }
445 }