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 {@link TreeWalk}. 67 */ 68 public class FileTreeIterator extends WorkingTreeIterator { 69 /** 70 * the starting directory of this Iterator. All entries are located directly 71 * in this directory. 72 */ 73 protected final File directory; 74 75 /** 76 * the file system abstraction which will be necessary to perform certain 77 * file system operations. 78 */ 79 protected final FS fs; 80 81 /** 82 * the strategy used to compute the FileMode for a FileEntry. Can be used to 83 * control things such as whether to recurse into a directory or create a 84 * gitlink. 85 * 86 * @since 4.3 87 */ 88 protected final FileModeStrategy fileModeStrategy; 89 90 /** 91 * Create a new iterator to traverse the work tree and its children. 92 * 93 * @param repo 94 * the repository whose working tree will be scanned. 95 */ 96 public FileTreeIterator(Repository repo) { 97 this(repo, 98 repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ? 99 NoGitlinksStrategy.INSTANCE : 100 DefaultFileModeStrategy.INSTANCE); 101 } 102 103 /** 104 * Create a new iterator to traverse the work tree and its children. 105 * 106 * @param repo 107 * the repository whose working tree will be scanned. 108 * @param fileModeStrategy 109 * the strategy to use to determine the FileMode for a FileEntry; 110 * controls gitlinks etc. 111 * 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 * 152 * @since 4.3 153 */ 154 public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options, 155 FileModeStrategy fileModeStrategy) { 156 super(options); 157 directory = root; 158 this.fs = fs; 159 this.fileModeStrategy = fileModeStrategy; 160 init(entries()); 161 } 162 163 /** 164 * Create a new iterator to traverse a subdirectory. 165 * 166 * @param p 167 * the parent iterator we were created from. 168 * @param root 169 * the subdirectory. This should be a directory contained within 170 * the parent directory. 171 * @param fs 172 * the file system abstraction which will be necessary to perform 173 * certain file system operations. 174 * @since 4.3 175 * @deprecated use {@link #FileTreeIterator(FileTreeIterator, File, FS)} 176 * instead. 177 */ 178 @Deprecated 179 protected FileTreeIterator(final WorkingTreeIterator p, final File root, 180 FS fs) { 181 this(p, root, fs, DefaultFileModeStrategy.INSTANCE); 182 } 183 184 /** 185 * Create a new iterator to traverse a subdirectory. 186 * 187 * @param p 188 * the parent iterator we were created from. 189 * @param root 190 * the subdirectory. This should be a directory contained within 191 * the parent directory. 192 * @param fs 193 * the file system abstraction which will be necessary to perform 194 * certain file system operations. 195 * 196 * @since 4.3 197 */ 198 protected FileTreeIterator(final FileTreeIterator p, final File root, 199 FS fs) { 200 this(p, root, fs, p.fileModeStrategy); 201 } 202 203 /** 204 * Create a new iterator to traverse a subdirectory, given the specified 205 * FileModeStrategy. 206 * 207 * @param p 208 * the parent iterator we were created from. 209 * @param root 210 * the subdirectory. This should be a directory contained within 211 * the parent directory 212 * @param fs 213 * the file system abstraction which will be necessary to perform 214 * certain file system operations. 215 * @param fileModeStrategy 216 * the strategy to use to determine the FileMode for a given 217 * FileEntry. 218 * 219 * @since 4.3 220 */ 221 protected FileTreeIterator(final WorkingTreeIterator p, final File root, 222 FS fs, FileModeStrategy fileModeStrategy) { 223 super(p); 224 directory = root; 225 this.fs = fs; 226 this.fileModeStrategy = fileModeStrategy; 227 init(entries()); 228 } 229 230 @Override 231 public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader) 232 throws IncorrectObjectTypeException, IOException { 233 return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, fileModeStrategy); 234 } 235 236 private Entry[] entries() { 237 final File[] all = directory.listFiles(); 238 if (all == null) 239 return EOF; 240 final Entry[] r = new Entry[all.length]; 241 for (int i = 0; i < r.length; i++) 242 r[i] = new FileEntry(all[i], fs, fileModeStrategy); 243 return r; 244 } 245 246 /** 247 * An interface representing the methods used to determine the FileMode for 248 * a FileEntry. 249 * 250 * @since 4.3 251 */ 252 public interface FileModeStrategy { 253 /** 254 * Compute the FileMode for a given File, based on its attributes. 255 * 256 * @param f 257 * the file to return a FileMode for 258 * @param attributes 259 * the attributes of a file 260 * @return a FileMode indicating whether the file is a regular file, a 261 * directory, a gitlink, etc. 262 */ 263 FileMode getMode(File f, FS.Attributes attributes); 264 } 265 266 /** 267 * A default implementation of a FileModeStrategy; defaults to treating 268 * nested .git directories as gitlinks, etc. 269 * 270 * @since 4.3 271 */ 272 static public class DefaultFileModeStrategy implements FileModeStrategy { 273 /** 274 * a singleton instance of the default FileModeStrategy 275 */ 276 public final static DefaultFileModeStrategy INSTANCE = 277 new DefaultFileModeStrategy(); 278 279 @Override 280 public FileMode getMode(File f, FS.Attributes attributes) { 281 if (attributes.isSymbolicLink()) { 282 return FileMode.SYMLINK; 283 } else if (attributes.isDirectory()) { 284 if (new File(f, Constants.DOT_GIT).exists()) { 285 return FileMode.GITLINK; 286 } else { 287 return FileMode.TREE; 288 } 289 } else if (attributes.isExecutable()) { 290 return FileMode.EXECUTABLE_FILE; 291 } else { 292 return FileMode.REGULAR_FILE; 293 } 294 } 295 } 296 297 /** 298 * A FileModeStrategy that implements native git's DIR_NO_GITLINKS 299 * behavior. This is the same as the default FileModeStrategy, except 300 * all directories will be treated as directories regardless of whether 301 * or not they contain a .git directory or file. 302 * 303 * @since 4.3 304 */ 305 static public class NoGitlinksStrategy implements FileModeStrategy { 306 307 /** 308 * a singleton instance of the default FileModeStrategy 309 */ 310 public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy(); 311 312 @Override 313 public FileMode getMode(File f, FS.Attributes attributes) { 314 if (attributes.isSymbolicLink()) { 315 return FileMode.SYMLINK; 316 } else if (attributes.isDirectory()) { 317 return FileMode.TREE; 318 } else if (attributes.isExecutable()) { 319 return FileMode.EXECUTABLE_FILE; 320 } else { 321 return FileMode.REGULAR_FILE; 322 } 323 } 324 } 325 326 327 /** 328 * Wrapper for a standard Java IO file 329 */ 330 static public class FileEntry extends Entry { 331 private final FileMode mode; 332 333 private FS.Attributes attributes; 334 335 private FS fs; 336 337 /** 338 * Create a new file entry. 339 * 340 * @param f 341 * file 342 * @param fs 343 * file system 344 */ 345 public FileEntry(File f, FS fs) { 346 this(f, fs, DefaultFileModeStrategy.INSTANCE); 347 } 348 349 /** 350 * Create a new file entry given the specified FileModeStrategy 351 * 352 * @param f 353 * file 354 * @param fs 355 * file system 356 * @param fileModeStrategy 357 * the strategy to use when determining the FileMode of a 358 * file; controls gitlinks etc. 359 * 360 * @since 4.3 361 */ 362 public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) { 363 this.fs = fs; 364 f = fs.normalize(f); 365 attributes = fs.getAttributes(f); 366 mode = fileModeStrategy.getMode(f, attributes); 367 } 368 369 @Override 370 public FileMode getMode() { 371 return mode; 372 } 373 374 @Override 375 public String getName() { 376 return attributes.getName(); 377 } 378 379 @Override 380 public long getLength() { 381 return attributes.getLength(); 382 } 383 384 @Override 385 public long getLastModified() { 386 return attributes.getLastModifiedTime(); 387 } 388 389 @Override 390 public InputStream openInputStream() throws IOException { 391 if (fs.isSymLink(getFile())) 392 return new ByteArrayInputStream(fs.readSymLink(getFile()) 393 .getBytes( 394 Constants.CHARACTER_ENCODING)); 395 else 396 return new FileInputStream(getFile()); 397 } 398 399 /** 400 * Get the underlying file of this entry. 401 * 402 * @return the underlying file of this entry 403 */ 404 public File getFile() { 405 return attributes.getFile(); 406 } 407 } 408 409 /** 410 * @return The root directory of this iterator 411 */ 412 public File getDirectory() { 413 return directory; 414 } 415 416 /** 417 * @return The location of the working file. This is the same as {@code new 418 * File(getDirectory(), getEntryPath())} but may be faster by 419 * reusing an internal File instance. 420 */ 421 public File getEntryFile() { 422 return ((FileEntry) current()).getFile(); 423 } 424 425 @Override 426 protected byte[] idSubmodule(final Entry e) { 427 return idSubmodule(getDirectory(), e); 428 } 429 430 @Override 431 protected String readSymlinkTarget(Entry entry) throws IOException { 432 return fs.readSymLink(getEntryFile()); 433 } 434 }