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