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