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 }