1 /*
2 * Copyright (C) 2008, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * and other copyright owners as documented in the project's IP log.
5 *
6 * This program and the accompanying materials are made available
7 * under the terms of the Eclipse Distribution License v1.0 which
8 * accompanies this distribution, is reproduced below, and is
9 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 *
11 * All rights reserved.
12 *
13 * Redistribution and use in source and binary forms, with or
14 * without modification, are permitted provided that the following
15 * conditions are met:
16 *
17 * - Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 *
20 * - Redistributions in binary form must reproduce the above
21 * copyright notice, this list of conditions and the following
22 * disclaimer in the documentation and/or other materials provided
23 * with the distribution.
24 *
25 * - Neither the name of the Eclipse Foundation, Inc. nor the
26 * names of its contributors may be used to endorse or promote
27 * products derived from this software without specific prior
28 * written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
31 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
32 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
33 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
35 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
36 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
37 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
38 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
39 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
42 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 */
44
45 package org.eclipse.jgit.dircache;
46
47 import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
48 import static org.eclipse.jgit.util.Paths.compareSameName;
49
50 import java.io.IOException;
51
52 import org.eclipse.jgit.errors.DirCacheNameConflictException;
53
54 /**
55 * Generic update/editing support for {@link DirCache}.
56 * <p>
57 * The different update strategies extend this class to provide their own unique
58 * services to applications.
59 */
60 abstract class BaseDirCacheEditor {
61 /** The cache instance this editor updates during {@link #finish()}. */
62 protected DirCache cache;
63
64 /**
65 * Entry table this builder will eventually replace into {@link #cache}.
66 * <p>
67 * Use {@link #fastAdd(DirCacheEntry)} or {@link #fastKeep(int, int)} to
68 * make additions to this table. The table is automatically expanded if it
69 * is too small for a new addition.
70 * <p>
71 * Typically the entries in here are sorted by their path names, just like
72 * they are in the DirCache instance.
73 */
74 protected DirCacheEntry[] entries;
75
76 /** Total number of valid entries in {@link #entries}. */
77 protected int entryCnt;
78
79 /**
80 * Construct a new editor.
81 *
82 * @param dc
83 * the cache this editor will eventually update.
84 * @param ecnt
85 * estimated number of entries the editor will have upon
86 * completion. This sizes the initial entry table.
87 */
88 protected BaseDirCacheEditor(DirCache dc, int ecnt) {
89 cache = dc;
90 entries = new DirCacheEntry[ecnt];
91 }
92
93 /**
94 * Get the {@code DirCache}
95 *
96 * @return the cache we will update on {@link #finish()}.
97 */
98 public DirCache getDirCache() {
99 return cache;
100 }
101
102 /**
103 * Append one entry into the resulting entry list.
104 * <p>
105 * The entry is placed at the end of the entry list. The caller is
106 * responsible for making sure the final table is correctly sorted.
107 * <p>
108 * The {@link #entries} table is automatically expanded if there is
109 * insufficient space for the new addition.
110 *
111 * @param newEntry
112 * the new entry to add.
113 */
114 protected void fastAdd(DirCacheEntry newEntry) {
115 if (entries.length == entryCnt) {
116 final DirCacheEntry[] n = new DirCacheEntry[(entryCnt + 16) * 3 / 2];
117 System.arraycopy(entries, 0, n, 0, entryCnt);
118 entries = n;
119 }
120 entries[entryCnt++] = newEntry;
121 }
122
123 /**
124 * Add a range of existing entries from the destination cache.
125 * <p>
126 * The entries are placed at the end of the entry list, preserving their
127 * current order. The caller is responsible for making sure the final table
128 * is correctly sorted.
129 * <p>
130 * This method copies from the destination cache, which has not yet been
131 * updated with this editor's new table. So all offsets into the destination
132 * cache are not affected by any updates that may be currently taking place
133 * in this editor.
134 * <p>
135 * The {@link #entries} table is automatically expanded if there is
136 * insufficient space for the new additions.
137 *
138 * @param pos
139 * first entry to copy from the destination cache.
140 * @param cnt
141 * number of entries to copy.
142 */
143 protected void fastKeep(int pos, int cnt) {
144 if (entryCnt + cnt > entries.length) {
145 final int m1 = (entryCnt + 16) * 3 / 2;
146 final int m2 = entryCnt + cnt;
147 final DirCacheEntry[] n = new DirCacheEntry[Math.max(m1, m2)];
148 System.arraycopy(entries, 0, n, 0, entryCnt);
149 entries = n;
150 }
151
152 cache.toArray(pos, entries, entryCnt, cnt);
153 entryCnt += cnt;
154 }
155
156 /**
157 * Finish this builder and update the destination
158 * {@link org.eclipse.jgit.dircache.DirCache}.
159 * <p>
160 * When this method completes this builder instance is no longer usable by
161 * the calling application. A new builder must be created to make additional
162 * changes to the index entries.
163 * <p>
164 * After completion the DirCache returned by {@link #getDirCache()} will
165 * contain all modifications.
166 * <p>
167 * <i>Note to implementors:</i> Make sure {@link #entries} is fully sorted
168 * then invoke {@link #replace()} to update the DirCache with the new table.
169 */
170 public abstract void finish();
171
172 /**
173 * Update the DirCache with the contents of {@link #entries}.
174 * <p>
175 * This method should be invoked only during an implementation of
176 * {@link #finish()}, and only after {@link #entries} is sorted.
177 */
178 protected void replace() {
179 checkNameConflicts();
180 if (entryCnt < entries.length / 2) {
181 final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
182 System.arraycopy(entries, 0, n, 0, entryCnt);
183 entries = n;
184 }
185 cache.replace(entries, entryCnt);
186 }
187
188 private void checkNameConflicts() {
189 int end = entryCnt - 1;
190 for (int eIdx = 0; eIdx < end; eIdx++) {
191 DirCacheEntry e = entries[eIdx];
192 if (e.getStage() != 0) {
193 continue;
194 }
195
196 byte[] ePath = e.path;
197 int prefixLen = lastSlash(ePath) + 1;
198
199 for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
200 DirCacheEntry n = entries[nIdx];
201 if (n.getStage() != 0) {
202 continue;
203 }
204
205 byte[] nPath = n.path;
206 if (!startsWith(ePath, nPath, prefixLen)) {
207 // Different prefix; this entry is in another directory.
208 break;
209 }
210
211 int s = nextSlash(nPath, prefixLen);
212 int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
213 int cmp = compareSameName(
214 ePath, prefixLen, ePath.length,
215 nPath, prefixLen, s, m);
216 if (cmp < 0) {
217 break;
218 } else if (cmp == 0) {
219 throw new DirCacheNameConflictException(
220 e.getPathString(),
221 n.getPathString());
222 }
223 }
224 }
225 }
226
227 private static int lastSlash(byte[] path) {
228 for (int i = path.length - 1; i >= 0; i--) {
229 if (path[i] == '/') {
230 return i;
231 }
232 }
233 return -1;
234 }
235
236 private static int nextSlash(byte[] b, int p) {
237 final int n = b.length;
238 for (; p < n; p++) {
239 if (b[p] == '/') {
240 return p;
241 }
242 }
243 return n;
244 }
245
246 private static boolean startsWith(byte[] a, byte[] b, int n) {
247 if (b.length < n) {
248 return false;
249 }
250 for (n--; n >= 0; n--) {
251 if (a[n] != b[n]) {
252 return false;
253 }
254 }
255 return true;
256 }
257
258 /**
259 * Finish, write, commit this change, and release the index lock.
260 * <p>
261 * If this method fails (returns false) the lock is still released.
262 * <p>
263 * This is a utility method for applications as the finish-write-commit
264 * pattern is very common after using a builder to update entries.
265 *
266 * @return true if the commit was successful and the file contains the new
267 * data; false if the commit failed and the file remains with the
268 * old data.
269 * @throws java.lang.IllegalStateException
270 * the lock is not held.
271 * @throws java.io.IOException
272 * the output file could not be created. The caller no longer
273 * holds the lock.
274 */
275 public boolean commit() throws IOException {
276 finish();
277 cache.write();
278 return cache.commit();
279 }
280 }