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.dircache.DirCacheIterator; 56 import org.eclipse.jgit.errors.IncorrectObjectTypeException; 57 import org.eclipse.jgit.lib.Constants; 58 import org.eclipse.jgit.lib.FileMode; 59 import org.eclipse.jgit.lib.ObjectReader; 60 import org.eclipse.jgit.lib.Repository; 61 import org.eclipse.jgit.util.FS; 62 63 /** 64 * Working directory iterator for standard Java IO. 65 * <p> 66 * This iterator uses the standard <code>java.io</code> package to read the 67 * specified working directory as part of a 68 * {@link org.eclipse.jgit.treewalk.TreeWalk}. 69 */ 70 public class FileTreeIterator extends WorkingTreeIterator { 71 72 /** 73 * the starting directory of this Iterator. All entries are located directly 74 * in this directory. 75 */ 76 protected final File directory; 77 78 /** 79 * the file system abstraction which will be necessary to perform certain 80 * file system operations. 81 */ 82 protected final FS fs; 83 84 /** 85 * the strategy used to compute the FileMode for a FileEntry. Can be used to 86 * control things such as whether to recurse into a directory or create a 87 * gitlink. 88 * 89 * @since 4.3 90 */ 91 protected final FileModeStrategy fileModeStrategy; 92 93 /** 94 * Create a new iterator to traverse the work tree and its children. 95 * 96 * @param repo 97 * the repository whose working tree will be scanned. 98 */ 99 public FileTreeIterator(Repository repo) { 100 this(repo, 101 repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ? 102 NoGitlinksStrategy.INSTANCE : 103 DefaultFileModeStrategy.INSTANCE); 104 } 105 106 /** 107 * Create a new iterator to traverse the work tree and its children. 108 * 109 * @param repo 110 * the repository whose working tree will be scanned. 111 * @param fileModeStrategy 112 * the strategy to use to determine the FileMode for a FileEntry; 113 * controls gitlinks etc. 114 * @since 4.3 115 */ 116 public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) { 117 this(repo.getWorkTree(), repo.getFS(), 118 repo.getConfig().get(WorkingTreeOptions.KEY), 119 fileModeStrategy); 120 initRootIterator(repo); 121 } 122 123 /** 124 * Create a new iterator to traverse the given directory and its children. 125 * 126 * @param root 127 * the starting directory. This directory should correspond to 128 * the root of the repository. 129 * @param fs 130 * the file system abstraction which will be necessary to perform 131 * certain file system operations. 132 * @param options 133 * working tree options to be used 134 */ 135 public FileTreeIterator(File root, FS fs, WorkingTreeOptions options) { 136 this(root, fs, options, DefaultFileModeStrategy.INSTANCE); 137 } 138 139 /** 140 * Create a new iterator to traverse the given directory and its children. 141 * 142 * @param root 143 * the starting directory. This directory should correspond to 144 * the root of the repository. 145 * @param fs 146 * the file system abstraction which will be necessary to perform 147 * certain file system operations. 148 * @param options 149 * working tree options to be used 150 * @param fileModeStrategy 151 * the strategy to use to determine the FileMode for a FileEntry; 152 * controls gitlinks etc. 153 * @since 4.3 154 */ 155 public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options, 156 FileModeStrategy fileModeStrategy) { 157 super(options); 158 directory = root; 159 this.fs = fs; 160 this.fileModeStrategy = fileModeStrategy; 161 init(entries()); 162 } 163 164 /** 165 * Create a new iterator to traverse a subdirectory. 166 * 167 * @param p 168 * the parent iterator we were created from. 169 * @param root 170 * the subdirectory. This should be a directory contained within 171 * the parent directory. 172 * @param fs 173 * the file system abstraction which will be necessary to perform 174 * certain file system operations. 175 * @since 4.3 176 */ 177 protected FileTreeIterator(final FileTreeIterator p, final File root, 178 FS fs) { 179 this(p, root, fs, p.fileModeStrategy); 180 } 181 182 /** 183 * Create a new iterator to traverse a subdirectory, given the specified 184 * FileModeStrategy. 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 * @param fileModeStrategy 195 * the strategy to use to determine the FileMode for a given 196 * FileEntry. 197 * @since 4.3 198 */ 199 protected FileTreeIterator(final WorkingTreeIterator p, final File root, 200 FS fs, FileModeStrategy fileModeStrategy) { 201 super(p); 202 directory = root; 203 this.fs = fs; 204 this.fileModeStrategy = fileModeStrategy; 205 init(entries()); 206 } 207 208 /** {@inheritDoc} */ 209 @Override 210 public AbstractTreeIterator createSubtreeIterator(ObjectReader reader) 211 throws IncorrectObjectTypeException, IOException { 212 if (!walksIgnoredDirectories() && isEntryIgnored()) { 213 DirCacheIterator iterator = getDirCacheIterator(); 214 if (iterator == null) { 215 return new EmptyTreeIterator(this); 216 } 217 // Only enter if we have an associated DirCacheIterator that is 218 // at the same entry (which indicates there is some already 219 // tracked file underneath this directory). Otherwise the 220 // directory is indeed ignored and can be skipped entirely. 221 } 222 return enterSubtree(); 223 } 224 225 226 /** 227 * Create a new iterator for the current entry's subtree. 228 * <p> 229 * The parent reference of the iterator must be <code>this</code>, otherwise 230 * the caller would not be able to exit out of the subtree iterator 231 * correctly and return to continue walking <code>this</code>. 232 * 233 * @return a new iterator that walks over the current subtree. 234 * @since 5.0 235 */ 236 protected AbstractTreeIterator enterSubtree() { 237 return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, 238 fileModeStrategy); 239 } 240 241 private Entry[] entries() { 242 return fs.list(directory, fileModeStrategy); 243 } 244 245 /** 246 * An interface representing the methods used to determine the FileMode for 247 * a FileEntry. 248 * 249 * @since 4.3 250 */ 251 public interface FileModeStrategy { 252 /** 253 * Compute the FileMode for a given File, based on its attributes. 254 * 255 * @param f 256 * the file to return a FileMode for 257 * @param attributes 258 * the attributes of a file 259 * @return a FileMode indicating whether the file is a regular file, a 260 * directory, a gitlink, etc. 261 */ 262 FileMode getMode(File f, FS.Attributes attributes); 263 } 264 265 /** 266 * A default implementation of a FileModeStrategy; defaults to treating 267 * nested .git directories as gitlinks, etc. 268 * 269 * @since 4.3 270 */ 271 public static class DefaultFileModeStrategy implements FileModeStrategy { 272 /** 273 * a singleton instance of the default FileModeStrategy 274 */ 275 public final static DefaultFileModeStrategy INSTANCE = 276 new DefaultFileModeStrategy(); 277 278 @Override 279 public FileMode getMode(File f, FS.Attributes attributes) { 280 if (attributes.isSymbolicLink()) { 281 return FileMode.SYMLINK; 282 } else if (attributes.isDirectory()) { 283 if (new File(f, Constants.DOT_GIT).exists()) { 284 return FileMode.GITLINK; 285 } else { 286 return FileMode.TREE; 287 } 288 } else if (attributes.isExecutable()) { 289 return FileMode.EXECUTABLE_FILE; 290 } else { 291 return FileMode.REGULAR_FILE; 292 } 293 } 294 } 295 296 /** 297 * A FileModeStrategy that implements native git's DIR_NO_GITLINKS 298 * behavior. This is the same as the default FileModeStrategy, except 299 * all directories will be treated as directories regardless of whether 300 * or not they contain a .git directory or file. 301 * 302 * @since 4.3 303 */ 304 public static class NoGitlinksStrategy implements FileModeStrategy { 305 306 /** 307 * a singleton instance of the default FileModeStrategy 308 */ 309 public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy(); 310 311 @Override 312 public FileMode getMode(File f, FS.Attributes attributes) { 313 if (attributes.isSymbolicLink()) { 314 return FileMode.SYMLINK; 315 } else if (attributes.isDirectory()) { 316 return FileMode.TREE; 317 } else if (attributes.isExecutable()) { 318 return FileMode.EXECUTABLE_FILE; 319 } else { 320 return FileMode.REGULAR_FILE; 321 } 322 } 323 } 324 325 326 /** 327 * Wrapper for a standard Java IO file 328 */ 329 public static class FileEntry extends Entry { 330 private final FileMode mode; 331 332 private FS.Attributes attributes; 333 334 private FS fs; 335 336 /** 337 * Create a new file entry. 338 * 339 * @param f 340 * file 341 * @param fs 342 * file system 343 */ 344 public FileEntry(File f, FS fs) { 345 this(f, fs, DefaultFileModeStrategy.INSTANCE); 346 } 347 348 /** 349 * Create a new file entry given the specified FileModeStrategy 350 * 351 * @param f 352 * file 353 * @param fs 354 * file system 355 * @param fileModeStrategy 356 * the strategy to use when determining the FileMode of a 357 * file; controls gitlinks etc. 358 * 359 * @since 4.3 360 */ 361 public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) { 362 this.fs = fs; 363 f = fs.normalize(f); 364 attributes = fs.getAttributes(f); 365 mode = fileModeStrategy.getMode(f, attributes); 366 } 367 368 /** 369 * Create a new file entry given the specified FileModeStrategy 370 * 371 * @param f 372 * file 373 * @param fs 374 * file system 375 * @param attributes 376 * of the file 377 * @param fileModeStrategy 378 * the strategy to use when determining the FileMode of a 379 * file; controls gitlinks etc. 380 * 381 * @since 5.0 382 */ 383 public FileEntry(File f, FS fs, FS.Attributes attributes, 384 FileModeStrategy fileModeStrategy) { 385 this.fs = fs; 386 this.attributes = attributes; 387 f = fs.normalize(f); 388 mode = fileModeStrategy.getMode(f, attributes); 389 } 390 391 @Override 392 public FileMode getMode() { 393 return mode; 394 } 395 396 @Override 397 public String getName() { 398 return attributes.getName(); 399 } 400 401 @Override 402 public long getLength() { 403 return attributes.getLength(); 404 } 405 406 @Override 407 public long getLastModified() { 408 return attributes.getLastModifiedTime(); 409 } 410 411 @Override 412 public InputStream openInputStream() throws IOException { 413 if (attributes.isSymbolicLink()) { 414 return new ByteArrayInputStream(fs.readSymLink(getFile()) 415 .getBytes(Constants.CHARACTER_ENCODING)); 416 } else { 417 return new FileInputStream(getFile()); 418 } 419 } 420 421 /** 422 * Get the underlying file of this entry. 423 * 424 * @return the underlying file of this entry 425 */ 426 public File getFile() { 427 return attributes.getFile(); 428 } 429 } 430 431 /** 432 * <p>Getter for the field <code>directory</code>.</p> 433 * 434 * @return The root directory of this iterator 435 */ 436 public File getDirectory() { 437 return directory; 438 } 439 440 /** 441 * Get the location of the working file. 442 * 443 * @return The location of the working file. This is the same as {@code new 444 * File(getDirectory(), getEntryPath())} but may be faster by 445 * reusing an internal File instance. 446 */ 447 public File getEntryFile() { 448 return ((FileEntry) current()).getFile(); 449 } 450 451 /** {@inheritDoc} */ 452 @Override 453 protected byte[] idSubmodule(Entry e) { 454 return idSubmodule(getDirectory(), e); 455 } 456 457 /** {@inheritDoc} */ 458 @Override 459 protected String readSymlinkTarget(Entry entry) throws IOException { 460 return fs.readSymLink(getEntryFile()); 461 } 462 }