1 /* 2 * Copyright (C) 2008-2009, 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 java.io.IOException; 48 import java.text.MessageFormat; 49 import java.util.Arrays; 50 51 import org.eclipse.jgit.internal.JGitText; 52 import org.eclipse.jgit.lib.AnyObjectId; 53 import org.eclipse.jgit.lib.ObjectReader; 54 import org.eclipse.jgit.treewalk.AbstractTreeIterator; 55 import org.eclipse.jgit.treewalk.CanonicalTreeParser; 56 import org.eclipse.jgit.treewalk.TreeWalk; 57 58 /** 59 * Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s. 60 * <p> 61 * A builder always starts from a clean slate and appends in every single 62 * <code>DirCacheEntry</code> which the final updated index must have to reflect 63 * its new content. 64 * <p> 65 * For maximum performance applications should add entries in path name order. 66 * Adding entries out of order is permitted, however a final sorting pass will 67 * be implicitly performed during {@link #finish()} to correct any out-of-order 68 * entries. Duplicate detection is also delayed until the sorting is complete. 69 * 70 * @see DirCacheEditor 71 */ 72 public class DirCacheBuilder extends BaseDirCacheEditor { 73 private boolean sorted; 74 75 /** 76 * Construct a new builder. 77 * 78 * @param dc 79 * the cache this builder will eventually update. 80 * @param ecnt 81 * estimated number of entries the builder will have upon 82 * completion. This sizes the initial entry table. 83 */ 84 protected DirCacheBuilder(final DirCache dc, final int ecnt) { 85 super(dc, ecnt); 86 } 87 88 /** 89 * Append one entry into the resulting entry list. 90 * <p> 91 * The entry is placed at the end of the entry list. If the entry causes the 92 * list to now be incorrectly sorted a final sorting phase will be 93 * automatically enabled within {@link #finish()}. 94 * <p> 95 * The internal entry table is automatically expanded if there is 96 * insufficient space for the new addition. 97 * 98 * @param newEntry 99 * the new entry to add. 100 * @throws IllegalArgumentException 101 * If the FileMode of the entry was not set by the caller. 102 */ 103 public void add(final DirCacheEntry newEntry) { 104 if (newEntry.getRawMode() == 0) 105 throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath 106 , newEntry.getPathString())); 107 beforeAdd(newEntry); 108 fastAdd(newEntry); 109 } 110 111 /** 112 * Add a range of existing entries from the destination cache. 113 * <p> 114 * The entries are placed at the end of the entry list. If any of the 115 * entries causes the list to now be incorrectly sorted a final sorting 116 * phase will be automatically enabled within {@link #finish()}. 117 * <p> 118 * This method copies from the destination cache, which has not yet been 119 * updated with this editor's new table. So all offsets into the destination 120 * cache are not affected by any updates that may be currently taking place 121 * in this editor. 122 * <p> 123 * The internal entry table is automatically expanded if there is 124 * insufficient space for the new additions. 125 * 126 * @param pos 127 * first entry to copy from the destination cache. 128 * @param cnt 129 * number of entries to copy. 130 */ 131 public void keep(final int pos, int cnt) { 132 beforeAdd(cache.getEntry(pos)); 133 fastKeep(pos, cnt); 134 } 135 136 /** 137 * Recursively add an entire tree into this builder. 138 * <p> 139 * If pathPrefix is "a/b" and the tree contains file "c" then the resulting 140 * DirCacheEntry will have the path "a/b/c". 141 * <p> 142 * All entries are inserted at stage 0, therefore assuming that the 143 * application will not insert any other paths with the same pathPrefix. 144 * 145 * @param pathPrefix 146 * UTF-8 encoded prefix to mount the tree's entries at. If the 147 * path does not end with '/' one will be automatically inserted 148 * as necessary. 149 * @param stage 150 * stage of the entries when adding them. 151 * @param reader 152 * reader the tree(s) will be read from during recursive 153 * traversal. This must be the same repository that the resulting 154 * DirCache would be written out to (or used in) otherwise the 155 * caller is simply asking for deferred MissingObjectExceptions. 156 * Caller is responsible for releasing this reader when done. 157 * @param tree 158 * the tree to recursively add. This tree's contents will appear 159 * under <code>pathPrefix</code>. The ObjectId must be that of a 160 * tree; the caller is responsible for dereferencing a tag or 161 * commit (if necessary). 162 * @throws IOException 163 * a tree cannot be read to iterate through its entries. 164 */ 165 public void addTree(final byte[] pathPrefix, final int stage, 166 final ObjectReader reader, final AnyObjectId tree) throws IOException { 167 final TreeWalk tw = new TreeWalk(reader); 168 tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree 169 .toObjectId())); 170 tw.setRecursive(true); 171 if (tw.next()) { 172 final DirCacheEntry newEntry = toEntry(stage, tw); 173 beforeAdd(newEntry); 174 fastAdd(newEntry); 175 while (tw.next()) 176 fastAdd(toEntry(stage, tw)); 177 } 178 } 179 180 private DirCacheEntry toEntry(final int stage, final TreeWalk tw) { 181 final DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage); 182 final AbstractTreeIterator i; 183 184 i = tw.getTree(0, AbstractTreeIterator.class); 185 e.setFileMode(tw.getFileMode(0)); 186 e.setObjectIdFromRaw(i.idBuffer(), i.idOffset()); 187 return e; 188 } 189 190 public void finish() { 191 if (!sorted) 192 resort(); 193 replace(); 194 } 195 196 private void beforeAdd(final DirCacheEntry newEntry) { 197 if (sorted && entryCnt > 0) { 198 final DirCacheEntry lastEntry = entries[entryCnt - 1]; 199 final int cr = DirCache.cmp(lastEntry, newEntry); 200 if (cr > 0) { 201 // The new entry sorts before the old entry; we are 202 // no longer sorted correctly. We'll need to redo 203 // the sorting before we can close out the build. 204 // 205 sorted = false; 206 } else if (cr == 0) { 207 // Same file path; we can only insert this if the 208 // stages won't be violated. 209 // 210 final int peStage = lastEntry.getStage(); 211 final int dceStage = newEntry.getStage(); 212 if (peStage == dceStage) 213 throw bad(newEntry, JGitText.get().duplicateStagesNotAllowed); 214 if (peStage == 0 || dceStage == 0) 215 throw bad(newEntry, JGitText.get().mixedStagesNotAllowed); 216 if (peStage > dceStage) 217 sorted = false; 218 } 219 } 220 } 221 222 private void resort() { 223 Arrays.sort(entries, 0, entryCnt, DirCache.ENT_CMP); 224 225 for (int entryIdx = 1; entryIdx < entryCnt; entryIdx++) { 226 final DirCacheEntry pe = entries[entryIdx - 1]; 227 final DirCacheEntry ce = entries[entryIdx]; 228 final int cr = DirCache.cmp(pe, ce); 229 if (cr == 0) { 230 // Same file path; we can only allow this if the stages 231 // are 1-3 and no 0 exists. 232 // 233 final int peStage = pe.getStage(); 234 final int ceStage = ce.getStage(); 235 if (peStage == ceStage) 236 throw bad(ce, JGitText.get().duplicateStagesNotAllowed); 237 if (peStage == 0 || ceStage == 0) 238 throw bad(ce, JGitText.get().mixedStagesNotAllowed); 239 } 240 } 241 242 sorted = true; 243 } 244 245 private static IllegalStateException bad(final DirCacheEntry a, 246 final String msg) { 247 return new IllegalStateException(msg + ": " + a.getStage() + " " //$NON-NLS-1$ //$NON-NLS-2$ 248 + a.getPathString()); 249 } 250 }