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 }