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