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(final DirCache dc, final int ecnt) {
89 cache = dc;
90 entries = new DirCacheEntry[ecnt];
91 }
92
93 /**
94 * @return the cache we will update on {@link #finish()}.
95 */
96 public DirCache getDirCache() {
97 return cache;
98 }
99
100 /**
101 * Append one entry into the resulting entry list.
102 * <p>
103 * The entry is placed at the end of the entry list. The caller is
104 * responsible for making sure the final table is correctly sorted.
105 * <p>
106 * The {@link #entries} table is automatically expanded if there is
107 * insufficient space for the new addition.
108 *
109 * @param newEntry
110 * the new entry to add.
111 */
112 protected void fastAdd(final DirCacheEntry newEntry) {
113 if (entries.length == entryCnt) {
114 final DirCacheEntry[] n = new DirCacheEntry[(entryCnt + 16) * 3 / 2];
115 System.arraycopy(entries, 0, n, 0, entryCnt);
116 entries = n;
117 }
118 entries[entryCnt++] = newEntry;
119 }
120
121 /**
122 * Add a range of existing entries from the destination cache.
123 * <p>
124 * The entries are placed at the end of the entry list, preserving their
125 * current order. The caller is responsible for making sure the final table
126 * is correctly sorted.
127 * <p>
128 * This method copies from the destination cache, which has not yet been
129 * updated with this editor's new table. So all offsets into the destination
130 * cache are not affected by any updates that may be currently taking place
131 * in this editor.
132 * <p>
133 * The {@link #entries} table is automatically expanded if there is
134 * insufficient space for the new additions.
135 *
136 * @param pos
137 * first entry to copy from the destination cache.
138 * @param cnt
139 * number of entries to copy.
140 */
141 protected void fastKeep(final int pos, int cnt) {
142 if (entryCnt + cnt > entries.length) {
143 final int m1 = (entryCnt + 16) * 3 / 2;
144 final int m2 = entryCnt + cnt;
145 final DirCacheEntry[] n = new DirCacheEntry[Math.max(m1, m2)];
146 System.arraycopy(entries, 0, n, 0, entryCnt);
147 entries = n;
148 }
149
150 cache.toArray(pos, entries, entryCnt, cnt);
151 entryCnt += cnt;
152 }
153
154 /**
155 * Finish this builder and update the destination {@link DirCache}.
156 * <p>
157 * When this method completes this builder instance is no longer usable by
158 * the calling application. A new builder must be created to make additional
159 * changes to the index entries.
160 * <p>
161 * After completion the DirCache returned by {@link #getDirCache()} will
162 * contain all modifications.
163 * <p>
164 * <i>Note to implementors:</i> Make sure {@link #entries} is fully sorted
165 * then invoke {@link #replace()} to update the DirCache with the new table.
166 */
167 public abstract void finish();
168
169 /**
170 * Update the DirCache with the contents of {@link #entries}.
171 * <p>
172 * This method should be invoked only during an implementation of
173 * {@link #finish()}, and only after {@link #entries} is sorted.
174 */
175 protected void replace() {
176 checkNameConflicts();
177 if (entryCnt < entries.length / 2) {
178 final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
179 System.arraycopy(entries, 0, n, 0, entryCnt);
180 entries = n;
181 }
182 cache.replace(entries, entryCnt);
183 }
184
185 private void checkNameConflicts() {
186 int end = entryCnt - 1;
187 for (int eIdx = 0; eIdx < end; eIdx++) {
188 DirCacheEntry e = entries[eIdx];
189 if (e.getStage() != 0) {
190 continue;
191 }
192
193 byte[] ePath = e.path;
194 int prefixLen = lastSlash(ePath) + 1;
195
196 for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
197 DirCacheEntry n = entries[nIdx];
198 if (n.getStage() != 0) {
199 continue;
200 }
201
202 byte[] nPath = n.path;
203 if (!startsWith(ePath, nPath, prefixLen)) {
204 // Different prefix; this entry is in another directory.
205 break;
206 }
207
208 int s = nextSlash(nPath, prefixLen);
209 int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
210 int cmp = compareSameName(
211 ePath, prefixLen, ePath.length,
212 nPath, prefixLen, s, m);
213 if (cmp < 0) {
214 break;
215 } else if (cmp == 0) {
216 throw new DirCacheNameConflictException(
217 e.getPathString(),
218 n.getPathString());
219 }
220 }
221 }
222 }
223
224 private static int lastSlash(byte[] path) {
225 for (int i = path.length - 1; i >= 0; i--) {
226 if (path[i] == '/') {
227 return i;
228 }
229 }
230 return -1;
231 }
232
233 private static int nextSlash(byte[] b, int p) {
234 final int n = b.length;
235 for (; p < n; p++) {
236 if (b[p] == '/') {
237 return p;
238 }
239 }
240 return n;
241 }
242
243 private static boolean startsWith(byte[] a, byte[] b, int n) {
244 if (b.length < n) {
245 return false;
246 }
247 for (n--; n >= 0; n--) {
248 if (a[n] != b[n]) {
249 return false;
250 }
251 }
252 return true;
253 }
254
255 /**
256 * Finish, write, commit this change, and release the index lock.
257 * <p>
258 * If this method fails (returns false) the lock is still released.
259 * <p>
260 * This is a utility method for applications as the finish-write-commit
261 * pattern is very common after using a builder to update entries.
262 *
263 * @return true if the commit was successful and the file contains the new
264 * data; false if the commit failed and the file remains with the
265 * old data.
266 * @throws IllegalStateException
267 * the lock is not held.
268 * @throws IOException
269 * the output file could not be created. The caller no longer
270 * holds the lock.
271 */
272 public boolean commit() throws IOException {
273 finish();
274 cache.write();
275 return cache.commit();
276 }
277 }