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