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 }