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> and others
6 *
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Distribution License v. 1.0 which is available at
9 * https://www.eclipse.org/org/documents/edl-v10.php.
10 *
11 * SPDX-License-Identifier: BSD-3-Clause
12 */
13
14 package org.eclipse.jgit.treewalk;
15
16 import static java.nio.charset.StandardCharsets.UTF_8;
17
18 import java.io.ByteArrayInputStream;
19 import java.io.File;
20 import java.io.FileInputStream;
21 import java.io.IOException;
22 import java.io.InputStream;
23 import java.time.Instant;
24
25 import org.eclipse.jgit.dircache.DirCacheIterator;
26 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
27 import org.eclipse.jgit.lib.Constants;
28 import org.eclipse.jgit.lib.FileMode;
29 import org.eclipse.jgit.lib.ObjectReader;
30 import org.eclipse.jgit.lib.Repository;
31 import org.eclipse.jgit.util.FS;
32
33 /**
34 * Working directory iterator for standard Java IO.
35 * <p>
36 * This iterator uses the standard <code>java.io</code> package to read the
37 * specified working directory as part of a
38 * {@link org.eclipse.jgit.treewalk.TreeWalk}.
39 */
40 public class FileTreeIterator extends WorkingTreeIterator {
41
42 /**
43 * the starting directory of this Iterator. All entries are located directly
44 * in this directory.
45 */
46 protected final File directory;
47
48 /**
49 * the file system abstraction which will be necessary to perform certain
50 * file system operations.
51 */
52 protected final FS fs;
53
54 /**
55 * the strategy used to compute the FileMode for a FileEntry. Can be used to
56 * control things such as whether to recurse into a directory or create a
57 * gitlink.
58 *
59 * @since 4.3
60 */
61 protected final FileModeStrategy fileModeStrategy;
62
63 /**
64 * Create a new iterator to traverse the work tree and its children.
65 *
66 * @param repo
67 * the repository whose working tree will be scanned.
68 */
69 public FileTreeIterator(Repository repo) {
70 this(repo,
71 repo.getConfig().get(WorkingTreeOptions.KEY).isDirNoGitLinks() ?
72 NoGitlinksStrategy.INSTANCE :
73 DefaultFileModeStrategy.INSTANCE);
74 }
75
76 /**
77 * Create a new iterator to traverse the work tree and its children.
78 *
79 * @param repo
80 * the repository whose working tree will be scanned.
81 * @param fileModeStrategy
82 * the strategy to use to determine the FileMode for a FileEntry;
83 * controls gitlinks etc.
84 * @since 4.3
85 */
86 public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) {
87 this(repo.getWorkTree(), repo.getFS(),
88 repo.getConfig().get(WorkingTreeOptions.KEY),
89 fileModeStrategy);
90 initRootIterator(repo);
91 }
92
93 /**
94 * Create a new iterator to traverse the given directory and its children.
95 *
96 * @param root
97 * the starting directory. This directory should correspond to
98 * the root of the repository.
99 * @param fs
100 * the file system abstraction which will be necessary to perform
101 * certain file system operations.
102 * @param options
103 * working tree options to be used
104 */
105 public FileTreeIterator(File root, FS fs, WorkingTreeOptions options) {
106 this(root, fs, options, DefaultFileModeStrategy.INSTANCE);
107 }
108
109 /**
110 * Create a new iterator to traverse the given directory and its children.
111 *
112 * @param root
113 * the starting directory. This directory should correspond to
114 * the root of the repository.
115 * @param fs
116 * the file system abstraction which will be necessary to perform
117 * certain file system operations.
118 * @param options
119 * working tree options to be used
120 * @param fileModeStrategy
121 * the strategy to use to determine the FileMode for a FileEntry;
122 * controls gitlinks etc.
123 * @since 4.3
124 */
125 public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options,
126 FileModeStrategy fileModeStrategy) {
127 super(options);
128 directory = root;
129 this.fs = fs;
130 this.fileModeStrategy = fileModeStrategy;
131 init(entries());
132 }
133
134 /**
135 * Create a new iterator to traverse a subdirectory.
136 *
137 * @param p
138 * the parent iterator we were created from.
139 * @param root
140 * the subdirectory. This should be a directory contained within
141 * the parent directory.
142 * @param fs
143 * the file system abstraction which will be necessary to perform
144 * certain file system operations.
145 * @since 4.3
146 */
147 protected FileTreeIteratorreeIterator.html#FileTreeIterator">FileTreeIterator(final FileTreeIterator p, final File root,
148 FS fs) {
149 this(p, root, fs, p.fileModeStrategy);
150 }
151
152 /**
153 * Create a new iterator to traverse a subdirectory, given the specified
154 * FileModeStrategy.
155 *
156 * @param p
157 * the parent iterator we were created from.
158 * @param root
159 * the subdirectory. This should be a directory contained within
160 * the parent directory
161 * @param fs
162 * the file system abstraction which will be necessary to perform
163 * certain file system operations.
164 * @param fileModeStrategy
165 * the strategy to use to determine the FileMode for a given
166 * FileEntry.
167 * @since 4.3
168 */
169 protected FileTreeIterator(final WorkingTreeIterator p, final File root,
170 FS fs, FileModeStrategy fileModeStrategy) {
171 super(p);
172 directory = root;
173 this.fs = fs;
174 this.fileModeStrategy = fileModeStrategy;
175 init(entries());
176 }
177
178 /** {@inheritDoc} */
179 @Override
180 public AbstractTreeIterator createSubtreeIterator(ObjectReader reader)
181 throws IncorrectObjectTypeException, IOException {
182 if (!walksIgnoredDirectories() && isEntryIgnored()) {
183 DirCacheIterator iterator = getDirCacheIterator();
184 if (iterator == null) {
185 return new EmptyTreeIterator(this);
186 }
187 // Only enter if we have an associated DirCacheIterator that is
188 // at the same entry (which indicates there is some already
189 // tracked file underneath this directory). Otherwise the
190 // directory is indeed ignored and can be skipped entirely.
191 }
192 return enterSubtree();
193 }
194
195
196 /**
197 * Create a new iterator for the current entry's subtree.
198 * <p>
199 * The parent reference of the iterator must be <code>this</code>, otherwise
200 * the caller would not be able to exit out of the subtree iterator
201 * correctly and return to continue walking <code>this</code>.
202 *
203 * @return a new iterator that walks over the current subtree.
204 * @since 5.0
205 */
206 protected AbstractTreeIterator enterSubtree() {
207 return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs,
208 fileModeStrategy);
209 }
210
211 private Entry[] entries() {
212 return fs.list(directory, fileModeStrategy);
213 }
214
215 /**
216 * An interface representing the methods used to determine the FileMode for
217 * a FileEntry.
218 *
219 * @since 4.3
220 */
221 public interface FileModeStrategy {
222 /**
223 * Compute the FileMode for a given File, based on its attributes.
224 *
225 * @param f
226 * the file to return a FileMode for
227 * @param attributes
228 * the attributes of a file
229 * @return a FileMode indicating whether the file is a regular file, a
230 * directory, a gitlink, etc.
231 */
232 FileMode getMode(File f, FS.Attributes attributes);
233 }
234
235 /**
236 * A default implementation of a FileModeStrategy; defaults to treating
237 * nested .git directories as gitlinks, etc.
238 *
239 * @since 4.3
240 */
241 public static class DefaultFileModeStrategy implements FileModeStrategy {
242 /**
243 * a singleton instance of the default FileModeStrategy
244 */
245 public static final DefaultFileModeStrategy INSTANCE =
246 new DefaultFileModeStrategy();
247
248 @Override
249 public FileMode getMode(File f, FS.Attributes attributes) {
250 if (attributes.isSymbolicLink()) {
251 return FileMode.SYMLINK;
252 } else if (attributes.isDirectory()) {
253 if (new File(f, Constants.DOT_GIT).exists()) {
254 return FileMode.GITLINK;
255 }
256 return FileMode.TREE;
257 } else if (attributes.isExecutable()) {
258 return FileMode.EXECUTABLE_FILE;
259 } else {
260 return FileMode.REGULAR_FILE;
261 }
262 }
263 }
264
265 /**
266 * A FileModeStrategy that implements native git's DIR_NO_GITLINKS
267 * behavior. This is the same as the default FileModeStrategy, except
268 * all directories will be treated as directories regardless of whether
269 * or not they contain a .git directory or file.
270 *
271 * @since 4.3
272 */
273 public static class NoGitlinksStrategy implements FileModeStrategy {
274
275 /**
276 * a singleton instance of the default FileModeStrategy
277 */
278 public static final NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();
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 return FileMode.TREE;
286 } else if (attributes.isExecutable()) {
287 return FileMode.EXECUTABLE_FILE;
288 } else {
289 return FileMode.REGULAR_FILE;
290 }
291 }
292 }
293
294
295 /**
296 * Wrapper for a standard Java IO file
297 */
298 public static class FileEntry extends Entry {
299 private final FileMode mode;
300
301 private FS.Attributes attributes;
302
303 private FS fs;
304
305 /**
306 * Create a new file entry.
307 *
308 * @param f
309 * file
310 * @param fs
311 * file system
312 */
313 public FileEntry(File f, FS fs) {
314 this(f, fs, DefaultFileModeStrategy.INSTANCE);
315 }
316
317 /**
318 * Create a new file entry given the specified FileModeStrategy
319 *
320 * @param f
321 * file
322 * @param fs
323 * file system
324 * @param fileModeStrategy
325 * the strategy to use when determining the FileMode of a
326 * file; controls gitlinks etc.
327 *
328 * @since 4.3
329 */
330 public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) {
331 this.fs = fs;
332 f = fs.normalize(f);
333 attributes = fs.getAttributes(f);
334 mode = fileModeStrategy.getMode(f, attributes);
335 }
336
337 /**
338 * Create a new file entry given the specified FileModeStrategy
339 *
340 * @param f
341 * file
342 * @param fs
343 * file system
344 * @param attributes
345 * of the file
346 * @param fileModeStrategy
347 * the strategy to use when determining the FileMode of a
348 * file; controls gitlinks etc.
349 *
350 * @since 5.0
351 */
352 public FileEntry(File f, FS fs, FS.Attributes attributes,
353 FileModeStrategy fileModeStrategy) {
354 this.fs = fs;
355 this.attributes = attributes;
356 f = fs.normalize(f);
357 mode = fileModeStrategy.getMode(f, attributes);
358 }
359
360 @Override
361 public FileMode getMode() {
362 return mode;
363 }
364
365 @Override
366 public String getName() {
367 return attributes.getName();
368 }
369
370 @Override
371 public long getLength() {
372 return attributes.getLength();
373 }
374
375 @Override
376 @Deprecated
377 public long getLastModified() {
378 return attributes.getLastModifiedInstant().toEpochMilli();
379 }
380
381 /**
382 * @since 5.1.9
383 */
384 @Override
385 public Instant getLastModifiedInstant() {
386 return attributes.getLastModifiedInstant();
387 }
388
389 @Override
390 public InputStream openInputStream() throws IOException {
391 if (attributes.isSymbolicLink()) {
392 return new ByteArrayInputStream(fs.readSymLink(getFile())
393 .getBytes(UTF_8));
394 }
395 return new FileInputStream(getFile());
396 }
397
398 /**
399 * Get the underlying file of this entry.
400 *
401 * @return the underlying file of this entry
402 */
403 public File getFile() {
404 return attributes.getFile();
405 }
406 }
407
408 /**
409 * <p>Getter for the field <code>directory</code>.</p>
410 *
411 * @return The root directory of this iterator
412 */
413 public File getDirectory() {
414 return directory;
415 }
416
417 /**
418 * Get the location of the working file.
419 *
420 * @return The location of the working file. This is the same as {@code new
421 * File(getDirectory(), getEntryPath())} but may be faster by
422 * reusing an internal File instance.
423 */
424 public File getEntryFile() {
425 return ((FileEntry) current()).getFile();
426 }
427
428 /** {@inheritDoc} */
429 @Override
430 protected byte[] idSubmodule(Entry e) {
431 return idSubmodule(getDirectory(), e);
432 }
433
434 /** {@inheritDoc} */
435 @Override
436 protected String readSymlinkTarget(Entry entry) throws IOException {
437 return fs.readSymLink(getEntryFile());
438 }
439 }