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 import java.time.Instant;
57
58 import org.eclipse.jgit.dircache.DirCacheIterator;
59 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
60 import org.eclipse.jgit.lib.Constants;
61 import org.eclipse.jgit.lib.FileMode;
62 import org.eclipse.jgit.lib.ObjectReader;
63 import org.eclipse.jgit.lib.Repository;
64 import org.eclipse.jgit.util.FS;
65
66 /**
67 * Working directory iterator for standard Java IO.
68 * <p>
69 * This iterator uses the standard <code>java.io</code> package to read the
70 * specified working directory as part of a
71 * {@link org.eclipse.jgit.treewalk.TreeWalk}.
72 */
73 public class FileTreeIterator extends WorkingTreeIterator {
74
75 /**
76 * the starting directory of this Iterator. All entries are located directly
77 * in this directory.
78 */
79 protected final File directory;
80
81 /**
82 * the file system abstraction which will be necessary to perform certain
83 * file system operations.
84 */
85 protected final FS fs;
86
87 /**
88 * the strategy used to compute the FileMode for a FileEntry. Can be used to
89 * control things such as whether to recurse into a directory or create a
90 * gitlink.
91 *
92 * @since 4.3
93 */
94 protected final FileModeStrategy fileModeStrategy;
95
96 /**
97 * Create a new iterator to traverse the work tree and its children.
98 *
99 * @param repo
100 * the repository whose working tree will be scanned.
101 */
102 public FileTreeIterator(Repository repo) {
103 this(repo,
104 repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ?
105 NoGitlinksStrategy.INSTANCE :
106 DefaultFileModeStrategy.INSTANCE);
107 }
108
109 /**
110 * Create a new iterator to traverse the work tree and its children.
111 *
112 * @param repo
113 * the repository whose working tree will be scanned.
114 * @param fileModeStrategy
115 * the strategy to use to determine the FileMode for a FileEntry;
116 * controls gitlinks etc.
117 * @since 4.3
118 */
119 public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) {
120 this(repo.getWorkTree(), repo.getFS(),
121 repo.getConfig().get(WorkingTreeOptions.KEY),
122 fileModeStrategy);
123 initRootIterator(repo);
124 }
125
126 /**
127 * Create a new iterator to traverse the given directory and its children.
128 *
129 * @param root
130 * the starting directory. This directory should correspond to
131 * the root of the repository.
132 * @param fs
133 * the file system abstraction which will be necessary to perform
134 * certain file system operations.
135 * @param options
136 * working tree options to be used
137 */
138 public FileTreeIterator(File root, FS fs, WorkingTreeOptions options) {
139 this(root, fs, options, DefaultFileModeStrategy.INSTANCE);
140 }
141
142 /**
143 * Create a new iterator to traverse the given directory and its children.
144 *
145 * @param root
146 * the starting directory. This directory should correspond to
147 * the root of the repository.
148 * @param fs
149 * the file system abstraction which will be necessary to perform
150 * certain file system operations.
151 * @param options
152 * working tree options to be used
153 * @param fileModeStrategy
154 * the strategy to use to determine the FileMode for a FileEntry;
155 * controls gitlinks etc.
156 * @since 4.3
157 */
158 public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options,
159 FileModeStrategy fileModeStrategy) {
160 super(options);
161 directory = root;
162 this.fs = fs;
163 this.fileModeStrategy = fileModeStrategy;
164 init(entries());
165 }
166
167 /**
168 * Create a new iterator to traverse a subdirectory.
169 *
170 * @param p
171 * the parent iterator we were created from.
172 * @param root
173 * the subdirectory. This should be a directory contained within
174 * the parent directory.
175 * @param fs
176 * the file system abstraction which will be necessary to perform
177 * certain file system operations.
178 * @since 4.3
179 */
180 protected FileTreeIteratorreeIterator.html#FileTreeIterator">FileTreeIterator(final FileTreeIterator p, final File root,
181 FS fs) {
182 this(p, root, fs, p.fileModeStrategy);
183 }
184
185 /**
186 * Create a new iterator to traverse a subdirectory, given the specified
187 * FileModeStrategy.
188 *
189 * @param p
190 * the parent iterator we were created from.
191 * @param root
192 * the subdirectory. This should be a directory contained within
193 * the parent directory
194 * @param fs
195 * the file system abstraction which will be necessary to perform
196 * certain file system operations.
197 * @param fileModeStrategy
198 * the strategy to use to determine the FileMode for a given
199 * FileEntry.
200 * @since 4.3
201 */
202 protected FileTreeIterator(final WorkingTreeIterator p, final File root,
203 FS fs, FileModeStrategy fileModeStrategy) {
204 super(p);
205 directory = root;
206 this.fs = fs;
207 this.fileModeStrategy = fileModeStrategy;
208 init(entries());
209 }
210
211 /** {@inheritDoc} */
212 @Override
213 public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
214 throws IncorrectObjectTypeException, IOException {
215 if (!walksIgnoredDirectories() && isEntryIgnored()) {
216 DirCacheIterator iterator = getDirCacheIterator();
217 if (iterator == null) {
218 return new EmptyTreeIterator(this);
219 }
220 // Only enter if we have an associated DirCacheIterator that is
221 // at the same entry (which indicates there is some already
222 // tracked file underneath this directory). Otherwise the
223 // directory is indeed ignored and can be skipped entirely.
224 }
225 return enterSubtree();
226 }
227
228
229 /**
230 * Create a new iterator for the current entry's subtree.
231 * <p>
232 * The parent reference of the iterator must be <code>this</code>, otherwise
233 * the caller would not be able to exit out of the subtree iterator
234 * correctly and return to continue walking <code>this</code>.
235 *
236 * @return a new iterator that walks over the current subtree.
237 * @since 5.0
238 */
239 protected AbstractTreeIterator enterSubtree() {
240 return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs,
241 fileModeStrategy);
242 }
243
244 private Entry[] entries() {
245 return fs.list(directory, fileModeStrategy);
246 }
247
248 /**
249 * An interface representing the methods used to determine the FileMode for
250 * a FileEntry.
251 *
252 * @since 4.3
253 */
254 public interface FileModeStrategy {
255 /**
256 * Compute the FileMode for a given File, based on its attributes.
257 *
258 * @param f
259 * the file to return a FileMode for
260 * @param attributes
261 * the attributes of a file
262 * @return a FileMode indicating whether the file is a regular file, a
263 * directory, a gitlink, etc.
264 */
265 FileMode getMode(File f, FS.Attributes attributes);
266 }
267
268 /**
269 * A default implementation of a FileModeStrategy; defaults to treating
270 * nested .git directories as gitlinks, etc.
271 *
272 * @since 4.3
273 */
274 public static class DefaultFileModeStrategy implements FileModeStrategy {
275 /**
276 * a singleton instance of the default FileModeStrategy
277 */
278 public final static DefaultFileModeStrategy INSTANCE =
279 new DefaultFileModeStrategy();
280
281 @Override
282 public FileMode getMode(File f, FS.Attributes attributes) {
283 if (attributes.isSymbolicLink()) {
284 return FileMode.SYMLINK;
285 } else if (attributes.isDirectory()) {
286 if (new File(f, Constants.DOT_GIT).exists()) {
287 return FileMode.GITLINK;
288 }
289 return FileMode.TREE;
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 @Deprecated
410 public long getLastModified() {
411 return attributes.getLastModifiedInstant().toEpochMilli();
412 }
413
414 /**
415 * @since 5.1.9
416 */
417 @Override
418 public Instant getLastModifiedInstant() {
419 return attributes.getLastModifiedInstant();
420 }
421
422 @Override
423 public InputStream openInputStream() throws IOException {
424 if (attributes.isSymbolicLink()) {
425 return new ByteArrayInputStream(fs.readSymLink(getFile())
426 .getBytes(UTF_8));
427 }
428 return new FileInputStream(getFile());
429 }
430
431 /**
432 * Get the underlying file of this entry.
433 *
434 * @return the underlying file of this entry
435 */
436 public File getFile() {
437 return attributes.getFile();
438 }
439 }
440
441 /**
442 * <p>Getter for the field <code>directory</code>.</p>
443 *
444 * @return The root directory of this iterator
445 */
446 public File getDirectory() {
447 return directory;
448 }
449
450 /**
451 * Get the location of the working file.
452 *
453 * @return The location of the working file. This is the same as {@code new
454 * File(getDirectory(), getEntryPath())} but may be faster by
455 * reusing an internal File instance.
456 */
457 public File getEntryFile() {
458 return ((FileEntry) current()).getFile();
459 }
460
461 /** {@inheritDoc} */
462 @Override
463 protected byte[] idSubmodule(Entry e) {
464 return idSubmodule(getDirectory(), e);
465 }
466
467 /** {@inheritDoc} */
468 @Override
469 protected String readSymlinkTarget(Entry entry) throws IOException {
470 return fs.readSymLink(getEntryFile());
471 }
472 }