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 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 }