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