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