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  
57  import org.eclipse.jgit.dircache.DirCacheIterator;
58  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
59  import org.eclipse.jgit.lib.Constants;
60  import org.eclipse.jgit.lib.FileMode;
61  import org.eclipse.jgit.lib.ObjectReader;
62  import org.eclipse.jgit.lib.Repository;
63  import org.eclipse.jgit.util.FS;
64  
65  /**
66   * Working directory iterator for standard Java IO.
67   * <p>
68   * This iterator uses the standard <code>java.io</code> package to read the
69   * specified working directory as part of a
70   * {@link org.eclipse.jgit.treewalk.TreeWalk}.
71   */
72  public class FileTreeIterator extends WorkingTreeIterator {
73  
74  	/**
75  	 * the starting directory of this Iterator. All entries are located directly
76  	 * in this directory.
77  	 */
78  	protected final File directory;
79  
80  	/**
81  	 * the file system abstraction which will be necessary to perform certain
82  	 * file system operations.
83  	 */
84  	protected final FS fs;
85  
86  	/**
87  	 * the strategy used to compute the FileMode for a FileEntry. Can be used to
88  	 * control things such as whether to recurse into a directory or create a
89  	 * gitlink.
90  	 *
91  	 * @since 4.3
92  	 */
93  	protected final FileModeStrategy fileModeStrategy;
94  
95  	/**
96  	 * Create a new iterator to traverse the work tree and its children.
97  	 *
98  	 * @param repo
99  	 *            the repository whose working tree will be scanned.
100 	 */
101 	public FileTreeIterator(Repository repo) {
102 		this(repo,
103 				repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ?
104 						NoGitlinksStrategy.INSTANCE :
105 						DefaultFileModeStrategy.INSTANCE);
106 	}
107 
108 	/**
109 	 * Create a new iterator to traverse the work tree and its children.
110 	 *
111 	 * @param repo
112 	 *            the repository whose working tree will be scanned.
113 	 * @param fileModeStrategy
114 	 *            the strategy to use to determine the FileMode for a FileEntry;
115 	 *            controls gitlinks etc.
116 	 * @since 4.3
117 	 */
118 	public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) {
119 		this(repo.getWorkTree(), repo.getFS(),
120 				repo.getConfig().get(WorkingTreeOptions.KEY),
121 				fileModeStrategy);
122 		initRootIterator(repo);
123 	}
124 
125 	/**
126 	 * Create a new iterator to traverse the given directory and its children.
127 	 *
128 	 * @param root
129 	 *            the starting directory. This directory should correspond to
130 	 *            the root of the repository.
131 	 * @param fs
132 	 *            the file system abstraction which will be necessary to perform
133 	 *            certain file system operations.
134 	 * @param options
135 	 *            working tree options to be used
136 	 */
137 	public FileTreeIterator(File root, FS fs, WorkingTreeOptions options) {
138 		this(root, fs, options, DefaultFileModeStrategy.INSTANCE);
139 	}
140 
141 	/**
142 	 * Create a new iterator to traverse the given directory and its children.
143 	 *
144 	 * @param root
145 	 *            the starting directory. This directory should correspond to
146 	 *            the root of the repository.
147 	 * @param fs
148 	 *            the file system abstraction which will be necessary to perform
149 	 *            certain file system operations.
150 	 * @param options
151 	 *            working tree options to be used
152 	 * @param fileModeStrategy
153 	 *            the strategy to use to determine the FileMode for a FileEntry;
154 	 *            controls gitlinks etc.
155 	 * @since 4.3
156 	 */
157 	public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options,
158 							FileModeStrategy fileModeStrategy) {
159 		super(options);
160 		directory = root;
161 		this.fs = fs;
162 		this.fileModeStrategy = fileModeStrategy;
163 		init(entries());
164 	}
165 
166 	/**
167 	 * Create a new iterator to traverse a subdirectory.
168 	 *
169 	 * @param p
170 	 *            the parent iterator we were created from.
171 	 * @param root
172 	 *            the subdirectory. This should be a directory contained within
173 	 *            the parent directory.
174 	 * @param fs
175 	 *            the file system abstraction which will be necessary to perform
176 	 *            certain file system operations.
177 	 * @since 4.3
178 	 */
179 	protected FileTreeIteratorreeIterator.html#FileTreeIterator">FileTreeIterator(final FileTreeIterator p, final File root,
180 			FS fs) {
181 		this(p, root, fs, p.fileModeStrategy);
182 	}
183 
184 	/**
185 	 * Create a new iterator to traverse a subdirectory, given the specified
186 	 * FileModeStrategy.
187 	 *
188 	 * @param p
189 	 *            the parent iterator we were created from.
190 	 * @param root
191 	 *            the subdirectory. This should be a directory contained within
192 	 *            the parent directory
193 	 * @param fs
194 	 *            the file system abstraction which will be necessary to perform
195 	 *            certain file system operations.
196 	 * @param fileModeStrategy
197 	 *            the strategy to use to determine the FileMode for a given
198 	 *            FileEntry.
199 	 * @since 4.3
200 	 */
201 	protected FileTreeIterator(final WorkingTreeIterator p, final File root,
202 			FS fs, FileModeStrategy fileModeStrategy) {
203 		super(p);
204 		directory = root;
205 		this.fs = fs;
206 		this.fileModeStrategy = fileModeStrategy;
207 		init(entries());
208 	}
209 
210 	/** {@inheritDoc} */
211 	@Override
212 	public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
213 			throws IncorrectObjectTypeException, IOException {
214 		if (!walksIgnoredDirectories() && isEntryIgnored()) {
215 			DirCacheIterator iterator = getDirCacheIterator();
216 			if (iterator == null) {
217 				return new EmptyTreeIterator(this);
218 			}
219 			// Only enter if we have an associated DirCacheIterator that is
220 			// at the same entry (which indicates there is some already
221 			// tracked file underneath this directory). Otherwise the
222 			// directory is indeed ignored and can be skipped entirely.
223 		}
224 		return enterSubtree();
225 	}
226 
227 
228 	/**
229 	 * Create a new iterator for the current entry's subtree.
230 	 * <p>
231 	 * The parent reference of the iterator must be <code>this</code>, otherwise
232 	 * the caller would not be able to exit out of the subtree iterator
233 	 * correctly and return to continue walking <code>this</code>.
234 	 *
235 	 * @return a new iterator that walks over the current subtree.
236 	 * @since 5.0
237 	 */
238 	protected AbstractTreeIterator enterSubtree() {
239 		return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs,
240 				fileModeStrategy);
241 	}
242 
243 	private Entry[] entries() {
244 		return fs.list(directory, fileModeStrategy);
245 	}
246 
247 	/**
248 	 * An interface representing the methods used to determine the FileMode for
249 	 * a FileEntry.
250 	 *
251 	 * @since 4.3
252 	 */
253 	public interface FileModeStrategy {
254 		/**
255 		 * Compute the FileMode for a given File, based on its attributes.
256 		 *
257 		 * @param f
258 		 *            the file to return a FileMode for
259 		 * @param attributes
260 		 *            the attributes of a file
261 		 * @return a FileMode indicating whether the file is a regular file, a
262 		 *         directory, a gitlink, etc.
263 		 */
264 		FileMode getMode(File f, FS.Attributes attributes);
265 	}
266 
267 	/**
268 	 * A default implementation of a FileModeStrategy; defaults to treating
269 	 * nested .git directories as gitlinks, etc.
270 	 *
271 	 * @since 4.3
272 	 */
273 	public static class DefaultFileModeStrategy implements FileModeStrategy {
274 		/**
275 		 * a singleton instance of the default FileModeStrategy
276 		 */
277 		public final static DefaultFileModeStrategy INSTANCE =
278 				new DefaultFileModeStrategy();
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 				if (new File(f, Constants.DOT_GIT).exists()) {
286 					return FileMode.GITLINK;
287 				} else {
288 					return FileMode.TREE;
289 				}
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 		public long getLastModified() {
410 			return attributes.getLastModifiedTime();
411 		}
412 
413 		@Override
414 		public InputStream openInputStream() throws IOException {
415 			if (attributes.isSymbolicLink()) {
416 				return new ByteArrayInputStream(fs.readSymLink(getFile())
417 						.getBytes(UTF_8));
418 			} else {
419 				return new FileInputStream(getFile());
420 			}
421 		}
422 
423 		/**
424 		 * Get the underlying file of this entry.
425 		 *
426 		 * @return the underlying file of this entry
427 		 */
428 		public File getFile() {
429 			return attributes.getFile();
430 		}
431 	}
432 
433 	/**
434 	 * <p>Getter for the field <code>directory</code>.</p>
435 	 *
436 	 * @return The root directory of this iterator
437 	 */
438 	public File getDirectory() {
439 		return directory;
440 	}
441 
442 	/**
443 	 * Get the location of the working file.
444 	 *
445 	 * @return The location of the working file. This is the same as {@code new
446 	 *         File(getDirectory(), getEntryPath())} but may be faster by
447 	 *         reusing an internal File instance.
448 	 */
449 	public File getEntryFile() {
450 		return ((FileEntry) current()).getFile();
451 	}
452 
453 	/** {@inheritDoc} */
454 	@Override
455 	protected byte[] idSubmodule(Entry e) {
456 		return idSubmodule(getDirectory(), e);
457 	}
458 
459 	/** {@inheritDoc} */
460 	@Override
461 	protected String readSymlinkTarget(Entry entry) throws IOException {
462 		return fs.readSymLink(getEntryFile());
463 	}
464 }