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