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 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 } else {
289 return FileMode.TREE;
290 }
291 } else if (attributes.isExecutable()) {
292 return FileMode.EXECUTABLE_FILE;
293 } else {
294 return FileMode.REGULAR_FILE;
295 }
296 }
297 }
298
299 /**
300 * A FileModeStrategy that implements native git's DIR_NO_GITLINKS
301 * behavior. This is the same as the default FileModeStrategy, except
302 * all directories will be treated as directories regardless of whether
303 * or not they contain a .git directory or file.
304 *
305 * @since 4.3
306 */
307 public static class NoGitlinksStrategy implements FileModeStrategy {
308
309 /**
310 * a singleton instance of the default FileModeStrategy
311 */
312 public final static NoGitlinksStrategy INSTANCE = new NoGitlinksStrategy();
313
314 @Override
315 public FileMode getMode(File f, FS.Attributes attributes) {
316 if (attributes.isSymbolicLink()) {
317 return FileMode.SYMLINK;
318 } else if (attributes.isDirectory()) {
319 return FileMode.TREE;
320 } else if (attributes.isExecutable()) {
321 return FileMode.EXECUTABLE_FILE;
322 } else {
323 return FileMode.REGULAR_FILE;
324 }
325 }
326 }
327
328
329 /**
330 * Wrapper for a standard Java IO file
331 */
332 public static class FileEntry extends Entry {
333 private final FileMode mode;
334
335 private FS.Attributes attributes;
336
337 private FS fs;
338
339 /**
340 * Create a new file entry.
341 *
342 * @param f
343 * file
344 * @param fs
345 * file system
346 */
347 public FileEntry(File f, FS fs) {
348 this(f, fs, DefaultFileModeStrategy.INSTANCE);
349 }
350
351 /**
352 * Create a new file entry given the specified FileModeStrategy
353 *
354 * @param f
355 * file
356 * @param fs
357 * file system
358 * @param fileModeStrategy
359 * the strategy to use when determining the FileMode of a
360 * file; controls gitlinks etc.
361 *
362 * @since 4.3
363 */
364 public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) {
365 this.fs = fs;
366 f = fs.normalize(f);
367 attributes = fs.getAttributes(f);
368 mode = fileModeStrategy.getMode(f, attributes);
369 }
370
371 /**
372 * Create a new file entry given the specified FileModeStrategy
373 *
374 * @param f
375 * file
376 * @param fs
377 * file system
378 * @param attributes
379 * of the file
380 * @param fileModeStrategy
381 * the strategy to use when determining the FileMode of a
382 * file; controls gitlinks etc.
383 *
384 * @since 5.0
385 */
386 public FileEntry(File f, FS fs, FS.Attributes attributes,
387 FileModeStrategy fileModeStrategy) {
388 this.fs = fs;
389 this.attributes = attributes;
390 f = fs.normalize(f);
391 mode = fileModeStrategy.getMode(f, attributes);
392 }
393
394 @Override
395 public FileMode getMode() {
396 return mode;
397 }
398
399 @Override
400 public String getName() {
401 return attributes.getName();
402 }
403
404 @Override
405 public long getLength() {
406 return attributes.getLength();
407 }
408
409 @Override
410 @Deprecated
411 public long getLastModified() {
412 return attributes.getLastModifiedInstant().toEpochMilli();
413 }
414
415 @Override
416 public Instant getLastModifiedInstant() {
417 return attributes.getLastModifiedInstant();
418 }
419
420 @Override
421 public InputStream openInputStream() throws IOException {
422 if (attributes.isSymbolicLink()) {
423 return new ByteArrayInputStream(fs.readSymLink(getFile())
424 .getBytes(UTF_8));
425 } else {
426 return new FileInputStream(getFile());
427 }
428 }
429
430 /**
431 * Get the underlying file of this entry.
432 *
433 * @return the underlying file of this entry
434 */
435 public File getFile() {
436 return attributes.getFile();
437 }
438 }
439
440 /**
441 * <p>Getter for the field <code>directory</code>.</p>
442 *
443 * @return The root directory of this iterator
444 */
445 public File getDirectory() {
446 return directory;
447 }
448
449 /**
450 * Get the location of the working file.
451 *
452 * @return The location of the working file. This is the same as {@code new
453 * File(getDirectory(), getEntryPath())} but may be faster by
454 * reusing an internal File instance.
455 */
456 public File getEntryFile() {
457 return ((FileEntry) current()).getFile();
458 }
459
460 /** {@inheritDoc} */
461 @Override
462 protected byte[] idSubmodule(Entry e) {
463 return idSubmodule(getDirectory(), e);
464 }
465
466 /** {@inheritDoc} */
467 @Override
468 protected String readSymlinkTarget(Entry entry) throws IOException {
469 return fs.readSymLink(getEntryFile());
470 }
471 }