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 }