1 /*
2 * Copyright (C) 2008, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
4 *
5 * This program and the accompanying materials are made available under the
6 * terms of the Eclipse Distribution License v. 1.0 which is available at
7 * https://www.eclipse.org/org/documents/edl-v10.php.
8 *
9 * SPDX-License-Identifier: BSD-3-Clause
10 */
11
12 package org.eclipse.jgit.dircache;
13
14 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
15 import static org.eclipse.jgit.util.Paths.compareSameName;
16
17 import java.io.IOException;
18
19 import org.eclipse.jgit.errors.DirCacheNameConflictException;
20
21 /**
22 * Generic update/editing support for {@link DirCache}.
23 * <p>
24 * The different update strategies extend this class to provide their own unique
25 * services to applications.
26 */
27 abstract class BaseDirCacheEditor {
28 /** The cache instance this editor updates during {@link #finish()}. */
29 protected DirCache cache;
30
31 /**
32 * Entry table this builder will eventually replace into {@link #cache}.
33 * <p>
34 * Use {@link #fastAdd(DirCacheEntry)} or {@link #fastKeep(int, int)} to
35 * make additions to this table. The table is automatically expanded if it
36 * is too small for a new addition.
37 * <p>
38 * Typically the entries in here are sorted by their path names, just like
39 * they are in the DirCache instance.
40 */
41 protected DirCacheEntry[] entries;
42
43 /** Total number of valid entries in {@link #entries}. */
44 protected int entryCnt;
45
46 /**
47 * Construct a new editor.
48 *
49 * @param dc
50 * the cache this editor will eventually update.
51 * @param ecnt
52 * estimated number of entries the editor will have upon
53 * completion. This sizes the initial entry table.
54 */
55 protected BaseDirCacheEditor(DirCache dc, int ecnt) {
56 cache = dc;
57 entries = new DirCacheEntry[ecnt];
58 }
59
60 /**
61 * Get the {@code DirCache}
62 *
63 * @return the cache we will update on {@link #finish()}.
64 */
65 public DirCache getDirCache() {
66 return cache;
67 }
68
69 /**
70 * Append one entry into the resulting entry list.
71 * <p>
72 * The entry is placed at the end of the entry list. The caller is
73 * responsible for making sure the final table is correctly sorted.
74 * <p>
75 * The {@link #entries} table is automatically expanded if there is
76 * insufficient space for the new addition.
77 *
78 * @param newEntry
79 * the new entry to add.
80 */
81 protected void fastAdd(DirCacheEntry newEntry) {
82 if (entries.length == entryCnt) {
83 final DirCacheEntryheEntry.html#DirCacheEntry">DirCacheEntry[] n = new DirCacheEntry[(entryCnt + 16) * 3 / 2];
84 System.arraycopy(entries, 0, n, 0, entryCnt);
85 entries = n;
86 }
87 entries[entryCnt++] = newEntry;
88 }
89
90 /**
91 * Add a range of existing entries from the destination cache.
92 * <p>
93 * The entries are placed at the end of the entry list, preserving their
94 * current order. The caller is responsible for making sure the final table
95 * is correctly sorted.
96 * <p>
97 * This method copies from the destination cache, which has not yet been
98 * updated with this editor's new table. So all offsets into the destination
99 * cache are not affected by any updates that may be currently taking place
100 * in this editor.
101 * <p>
102 * The {@link #entries} table is automatically expanded if there is
103 * insufficient space for the new additions.
104 *
105 * @param pos
106 * first entry to copy from the destination cache.
107 * @param cnt
108 * number of entries to copy.
109 */
110 protected void fastKeep(int pos, int cnt) {
111 if (entryCnt + cnt > entries.length) {
112 final int m1 = (entryCnt + 16) * 3 / 2;
113 final int m2 = entryCnt + cnt;
114 final DirCacheEntryheEntry.html#DirCacheEntry">DirCacheEntry[] n = new DirCacheEntry[Math.max(m1, m2)];
115 System.arraycopy(entries, 0, n, 0, entryCnt);
116 entries = n;
117 }
118
119 cache.toArray(pos, entries, entryCnt, cnt);
120 entryCnt += cnt;
121 }
122
123 /**
124 * Finish this builder and update the destination
125 * {@link org.eclipse.jgit.dircache.DirCache}.
126 * <p>
127 * When this method completes this builder instance is no longer usable by
128 * the calling application. A new builder must be created to make additional
129 * changes to the index entries.
130 * <p>
131 * After completion the DirCache returned by {@link #getDirCache()} will
132 * contain all modifications.
133 * <p>
134 * <i>Note to implementors:</i> Make sure {@link #entries} is fully sorted
135 * then invoke {@link #replace()} to update the DirCache with the new table.
136 */
137 public abstract void finish();
138
139 /**
140 * Update the DirCache with the contents of {@link #entries}.
141 * <p>
142 * This method should be invoked only during an implementation of
143 * {@link #finish()}, and only after {@link #entries} is sorted.
144 */
145 protected void replace() {
146 checkNameConflicts();
147 if (entryCnt < entries.length / 2) {
148 final DirCacheEntryheEntry.html#DirCacheEntry">DirCacheEntry[] n = new DirCacheEntry[entryCnt];
149 System.arraycopy(entries, 0, n, 0, entryCnt);
150 entries = n;
151 }
152 cache.replace(entries, entryCnt);
153 }
154
155 private void checkNameConflicts() {
156 int end = entryCnt - 1;
157 for (int eIdx = 0; eIdx < end; eIdx++) {
158 DirCacheEntry e = entries[eIdx];
159 if (e.getStage() != 0) {
160 continue;
161 }
162
163 byte[] ePath = e.path;
164 int prefixLen = lastSlash(ePath) + 1;
165
166 for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
167 DirCacheEntry n = entries[nIdx];
168 if (n.getStage() != 0) {
169 continue;
170 }
171
172 byte[] nPath = n.path;
173 if (!startsWith(ePath, nPath, prefixLen)) {
174 // Different prefix; this entry is in another directory.
175 break;
176 }
177
178 int s = nextSlash(nPath, prefixLen);
179 int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
180 int cmp = compareSameName(
181 ePath, prefixLen, ePath.length,
182 nPath, prefixLen, s, m);
183 if (cmp < 0) {
184 break;
185 } else if (cmp == 0) {
186 throw new DirCacheNameConflictException(
187 e.getPathString(),
188 n.getPathString());
189 }
190 }
191 }
192 }
193
194 private static int lastSlash(byte[] path) {
195 for (int i = path.length - 1; i >= 0; i--) {
196 if (path[i] == '/') {
197 return i;
198 }
199 }
200 return -1;
201 }
202
203 private static int nextSlash(byte[] b, int p) {
204 final int n = b.length;
205 for (; p < n; p++) {
206 if (b[p] == '/') {
207 return p;
208 }
209 }
210 return n;
211 }
212
213 private static boolean startsWith(byte[] a, byte[] b, int n) {
214 if (b.length < n) {
215 return false;
216 }
217 for (n--; n >= 0; n--) {
218 if (a[n] != b[n]) {
219 return false;
220 }
221 }
222 return true;
223 }
224
225 /**
226 * Finish, write, commit this change, and release the index lock.
227 * <p>
228 * If this method fails (returns false) the lock is still released.
229 * <p>
230 * This is a utility method for applications as the finish-write-commit
231 * pattern is very common after using a builder to update entries.
232 *
233 * @return true if the commit was successful and the file contains the new
234 * data; false if the commit failed and the file remains with the
235 * old data.
236 * @throws java.lang.IllegalStateException
237 * the lock is not held.
238 * @throws java.io.IOException
239 * the output file could not be created. The caller no longer
240 * holds the lock.
241 */
242 public boolean commit() throws IOException {
243 finish();
244 cache.write();
245 return cache.commit();
246 }
247 }