View Javadoc
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 }