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 }