1 /* 2 * Copyright (C) 2008, Google Inc. 3 * Copyright (C) 2007-2010, Robin Rosenberg <robin.rosenberg@dewire.com> 4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 5 * Copyright (C) 2009, Tor Arne Vestbø <torarnv@gmail.com> 6 * and other copyright owners as documented in the project's IP log. 7 * 8 * This program and the accompanying materials are made available 9 * under the terms of the Eclipse Distribution License v1.0 which 10 * accompanies this distribution, is reproduced below, and is 11 * available at http://www.eclipse.org/org/documents/edl-v10.php 12 * 13 * All rights reserved. 14 * 15 * Redistribution and use in source and binary forms, with or 16 * without modification, are permitted provided that the following 17 * conditions are met: 18 * 19 * - Redistributions of source code must retain the above copyright 20 * notice, this list of conditions and the following disclaimer. 21 * 22 * - Redistributions in binary form must reproduce the above 23 * copyright notice, this list of conditions and the following 24 * disclaimer in the documentation and/or other materials provided 25 * with the distribution. 26 * 27 * - Neither the name of the Eclipse Foundation, Inc. nor the 28 * names of its contributors may be used to endorse or promote 29 * products derived from this software without specific prior 30 * written permission. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 33 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 34 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 37 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 39 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 40 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 41 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 42 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 43 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 44 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 */ 46 47 package org.eclipse.jgit.treewalk; 48 49 import java.io.ByteArrayInputStream; 50 import java.io.File; 51 import java.io.FileInputStream; 52 import java.io.IOException; 53 import java.io.InputStream; 54 55 import org.eclipse.jgit.errors.IncorrectObjectTypeException; 56 import org.eclipse.jgit.lib.Constants; 57 import org.eclipse.jgit.lib.FileMode; 58 import org.eclipse.jgit.lib.ObjectReader; 59 import org.eclipse.jgit.lib.Repository; 60 import org.eclipse.jgit.util.FS; 61 62 /** 63 * Working directory iterator for standard Java IO. 64 * <p> 65 * This iterator uses the standard <code>java.io</code> package to read the 66 * specified working directory as part of a 67 * {@link org.eclipse.jgit.treewalk.TreeWalk}. 68 */ 69 public class FileTreeIterator extends WorkingTreeIterator { 70 /** 71 * the starting directory of this Iterator. All entries are located directly 72 * in this directory. 73 */ 74 protected final File directory; 75 76 /** 77 * the file system abstraction which will be necessary to perform certain 78 * file system operations. 79 */ 80 protected final FS fs; 81 82 /** 83 * the strategy used to compute the FileMode for a FileEntry. Can be used to 84 * control things such as whether to recurse into a directory or create a 85 * gitlink. 86 * 87 * @since 4.3 88 */ 89 protected final FileModeStrategy fileModeStrategy; 90 91 /** 92 * Create a new iterator to traverse the work tree and its children. 93 * 94 * @param repo 95 * the repository whose working tree will be scanned. 96 */ 97 public FileTreeIterator(Repository repo) { 98 this(repo, 99 repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ? 100 NoGitlinksStrategy.INSTANCE : 101 DefaultFileModeStrategy.INSTANCE); 102 } 103 104 /** 105 * Create a new iterator to traverse the work tree and its children. 106 * 107 * @param repo 108 * the repository whose working tree will be scanned. 109 * @param fileModeStrategy 110 * the strategy to use to determine the FileMode for a FileEntry; 111 * controls gitlinks etc. 112 * @since 4.3 113 */ 114 public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) { 115 this(repo.getWorkTree(), repo.getFS(), 116 repo.getConfig().get(WorkingTreeOptions.KEY), 117 fileModeStrategy); 118 initRootIterator(repo); 119 } 120 121 /** 122 * Create a new iterator to traverse the given directory and its children. 123 * 124 * @param root 125 * the starting directory. This directory should correspond to 126 * the root of the repository. 127 * @param fs 128 * the file system abstraction which will be necessary to perform 129 * certain file system operations. 130 * @param options 131 * working tree options to be used 132 */ 133 public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options) { 134 this(root, fs, options, DefaultFileModeStrategy.INSTANCE); 135 } 136 137 /** 138 * Create a new iterator to traverse the given directory and its children. 139 * 140 * @param root 141 * the starting directory. This directory should correspond to 142 * the root of the repository. 143 * @param fs 144 * the file system abstraction which will be necessary to perform 145 * certain file system operations. 146 * @param options 147 * working tree options to be used 148 * @param fileModeStrategy 149 * the strategy to use to determine the FileMode for a FileEntry; 150 * controls gitlinks etc. 151 * @since 4.3 152 */ 153 public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options, 154 FileModeStrategy fileModeStrategy) { 155 super(options); 156 directory = root; 157 this.fs = fs; 158 this.fileModeStrategy = fileModeStrategy; 159 init(entries()); 160 } 161 162 /** 163 * Create a new iterator to traverse a subdirectory. 164 * 165 * @param p 166 * the parent iterator we were created from. 167 * @param root 168 * the subdirectory. This should be a directory contained within 169 * the parent directory. 170 * @param fs 171 * the file system abstraction which will be necessary to perform 172 * certain file system operations. 173 * @since 4.3 174 * @deprecated use {@link #FileTreeIterator(FileTreeIterator, File, FS)} 175 * instead. 176 */ 177 @Deprecated 178 protected FileTreeIterator(final WorkingTreeIterator p, final File root, 179 FS fs) { 180 this(p, root, fs, DefaultFileModeStrategy.INSTANCE); 181 } 182 183 /** 184 * Create a new iterator to traverse a subdirectory. 185 * 186 * @param p 187 * the parent iterator we were created from. 188 * @param root 189 * the subdirectory. This should be a directory contained within 190 * the parent directory. 191 * @param fs 192 * the file system abstraction which will be necessary to perform 193 * certain file system operations. 194 * @since 4.3 195 */ 196 protected FileTreeIterator(final FileTreeIterator p, final File root, 197 FS fs) { 198 this(p, root, fs, p.fileModeStrategy); 199 } 200 201 /** 202 * Create a new iterator to traverse a subdirectory, given the specified 203 * FileModeStrategy. 204 * 205 * @param p 206 * the parent iterator we were created from. 207 * @param root 208 * the subdirectory. This should be a directory contained within 209 * the parent directory 210 * @param fs 211 * the file system abstraction which will be necessary to perform 212 * certain file system operations. 213 * @param fileModeStrategy 214 * the strategy to use to determine the FileMode for a given 215 * FileEntry. 216 * @since 4.3 217 */ 218 protected FileTreeIterator(final WorkingTreeIterator p, final File root, 219 FS fs, FileModeStrategy fileModeStrategy) { 220 super(p); 221 directory = root; 222 this.fs = fs; 223 this.fileModeStrategy = fileModeStrategy; 224 init(entries()); 225 } 226 227 /** {@inheritDoc} */ 228 @Override 229 public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) 230 throws IncorrectObjectTypeException, IOException { 231 return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, fileModeStrategy); 232 } 233 234 private Entry[] entries() { 235 final File[] all = directory.listFiles(); 236 if (all == null) 237 return EOF; 238 final Entry[] r = new Entry[all.length]; 239 for (int i = 0; i < r.length; i++) 240 r[i] = new FileEntry(all[i], fs, fileModeStrategy); 241 return r; 242 } 243 244 /** 245 * An interface representing the methods used to determine the FileMode for 246 * a FileEntry. 247 * 248 * @since 4.3 249 */ 250 public interface FileModeStrategy { 251 /** 252 * Compute the FileMode for a given File, based on its attributes. 253 * 254 * @param f 255 * the file to return a FileMode for 256 * @param attributes 257 * the attributes of a file 258 * @return a FileMode indicating whether the file is a regular file, a 259 * directory, a gitlink, etc. 260 */ 261 FileMode getMode(File f, FS.Attributes attributes); 262 } 263 264 /** 265 * A default implementation of a FileModeStrategy; defaults to treating 266 * nested .git directories as gitlinks, etc. 267 * 268 * @since 4.3 269 */ 270 static public class DefaultFileModeStrategy implements FileModeStrategy { 271 /** 272 * a singleton instance of the default FileModeStrategy 273 */ 274 public final static DefaultFileModeStrategy INSTANCE = 275 new DefaultFileModeStrategy(); 276 277 @Override 278 public FileMode getMode(File f, FS.Attributes attributes) { 279 if (attributes.isSymbolicLink()) { 280 return FileMode.SYMLINK; 281 } else if (attributes.isDirectory()) { 282 if (new File(f, Constants.DOT_GIT).exists()) { 283 return FileMode.GITLINK; 284 } else { 285 return FileMode.TREE; 286 } 287 } else if (attributes.isExecutable()) { 288 return FileMode.EXECUTABLE_FILE; 289 } else { 290 return FileMode.REGULAR_FILE; 291 } 292 } 293 } 294 295 /** 296 * A FileModeStrategy that implements native git's DIR_NO_GITLINKS 297 * behavior. This is the same as the default FileModeStrategy, except 298 * all directories will be treated as directories regardless of whether 299 * or not they contain a .git directory or file. 300 * 301 * @since 4.3 302 */ 303 static public class NoGitlinksStrategy implements FileModeStrategy { 304 305 /** 306 * a singleton instance of the default FileModeStrategy 307 */ 308 public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy(); 309 310 @Override 311 public FileMode getMode(File f, FS.Attributes attributes) { 312 if (attributes.isSymbolicLink()) { 313 return FileMode.SYMLINK; 314 } else if (attributes.isDirectory()) { 315 return FileMode.TREE; 316 } else if (attributes.isExecutable()) { 317 return FileMode.EXECUTABLE_FILE; 318 } else { 319 return FileMode.REGULAR_FILE; 320 } 321 } 322 } 323 324 325 /** 326 * Wrapper for a standard Java IO file 327 */ 328 static public class FileEntry extends Entry { 329 private final FileMode mode; 330 331 private FS.Attributes attributes; 332 333 private FS fs; 334 335 /** 336 * Create a new file entry. 337 * 338 * @param f 339 * file 340 * @param fs 341 * file system 342 */ 343 public FileEntry(File f, FS fs) { 344 this(f, fs, DefaultFileModeStrategy.INSTANCE); 345 } 346 347 /** 348 * Create a new file entry given the specified FileModeStrategy 349 * 350 * @param f 351 * file 352 * @param fs 353 * file system 354 * @param fileModeStrategy 355 * the strategy to use when determining the FileMode of a 356 * file; controls gitlinks etc. 357 * 358 * @since 4.3 359 */ 360 public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) { 361 this.fs = fs; 362 f = fs.normalize(f); 363 attributes = fs.getAttributes(f); 364 mode = fileModeStrategy.getMode(f, attributes); 365 } 366 367 @Override 368 public FileMode getMode() { 369 return mode; 370 } 371 372 @Override 373 public String getName() { 374 return attributes.getName(); 375 } 376 377 @Override 378 public long getLength() { 379 return attributes.getLength(); 380 } 381 382 @Override 383 public long getLastModified() { 384 return attributes.getLastModifiedTime(); 385 } 386 387 @Override 388 public InputStream openInputStream() throws IOException { 389 if (fs.isSymLink(getFile())) 390 return new ByteArrayInputStream(fs.readSymLink(getFile()) 391 .getBytes( 392 Constants.CHARACTER_ENCODING)); 393 else 394 return new FileInputStream(getFile()); 395 } 396 397 /** 398 * Get the underlying file of this entry. 399 * 400 * @return the underlying file of this entry 401 */ 402 public File getFile() { 403 return attributes.getFile(); 404 } 405 } 406 407 /** 408 * <p>Getter for the field <code>directory</code>.</p> 409 * 410 * @return The root directory of this iterator 411 */ 412 public File getDirectory() { 413 return directory; 414 } 415 416 /** 417 * Get the location of the working file. 418 * 419 * @return The location of the working file. This is the same as {@code new 420 * File(getDirectory(), getEntryPath())} but may be faster by 421 * reusing an internal File instance. 422 */ 423 public File getEntryFile() { 424 return ((FileEntry) current()).getFile(); 425 } 426 427 /** {@inheritDoc} */ 428 @Override 429 protected byte[] idSubmodule(final Entry e) { 430 return idSubmodule(getDirectory(), e); 431 } 432 433 /** {@inheritDoc} */ 434 @Override 435 protected String readSymlinkTarget(Entry entry) throws IOException { 436 return fs.readSymLink(getEntryFile()); 437 } 438 }