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