Browse Source

Introduce FileModeStrategy to FileTreeIterator

This commit introduces a FileModeStrategy to
the FileTreeIterator class.  This provides a way to
allow different modes of traversing a file tree;
for example, to control whether or not a nested
.git directory should be treated as a gitlink.

Bug: 436200
Change-Id: Ibf85defee28cdeec1e1463e596d0dcd03090dddd
Signed-off-by: Preben Ingvaldsen <preben@puppetlabs.com>
tags/v4.3.0.201603230630-rc1
Preben Ingvaldsen 8 years ago
parent
commit
cff546b0cb

+ 85
- 5
org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorTest.java View File

@@ -62,15 +62,14 @@ import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.junit.JGitTestUtil;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.*;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.treewalk.WorkingTreeIterator.MetadataDiff;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.RawParseUtils;
import org.junit.Before;
@@ -537,6 +536,87 @@ public class FileTreeIteratorTest extends RepositoryTestCase {
}
}

private final FileTreeIterator.FileModeStrategy NO_GITLINKS_STRATEGY =
new FileTreeIterator.FileModeStrategy() {
@Override
public FileMode getMode(File f, FS.Attributes attributes) {
if (attributes.isSymbolicLink()) {
return FileMode.SYMLINK;
} else if (attributes.isDirectory()) {
// NOTE: in the production DefaultFileModeStrategy, there is
// a check here for a subdirectory called '.git', and if it
// exists, we create a GITLINK instead of recursing into the
// tree. In this custom strategy, we ignore nested git dirs
// and treat all directories the same.
return FileMode.TREE;
} else if (attributes.isExecutable()) {
return FileMode.EXECUTABLE_FILE;
} else {
return FileMode.REGULAR_FILE;
}
}
};

private Repository createNestedRepo() throws IOException {
File gitdir = createUniqueTestGitDir(false);
FileRepositoryBuilder builder = new FileRepositoryBuilder();
builder.setGitDir(gitdir);
Repository nestedRepo = builder.build();
nestedRepo.create();

JGitTestUtil.writeTrashFile(nestedRepo, "sub", "a.txt", "content");

File nestedRepoPath = new File(nestedRepo.getWorkTree(), "sub/nested");
FileRepositoryBuilder nestedBuilder = new FileRepositoryBuilder();
nestedBuilder.setWorkTree(nestedRepoPath);
nestedBuilder.build().create();

JGitTestUtil.writeTrashFile(nestedRepo, "sub/nested", "b.txt",
"content b");

return nestedRepo;
}

@Test
public void testCustomFileModeStrategy() throws Exception {
Repository nestedRepo = createNestedRepo();

Git git = new Git(nestedRepo);
// validate that our custom strategy is honored
WorkingTreeIterator customIterator =
new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
git.add().setWorkingTreeIterator(customIterator)
.addFilepattern(".").call();
assertEquals(
"[sub/a.txt, mode:100644, content:content]" +
"[sub/nested/b.txt, mode:100644, content:content b]",
indexState(nestedRepo, CONTENT));

}

@Test
public void testCustomFileModeStrategyFromParentIterator() throws Exception {
Repository nestedRepo = createNestedRepo();

Git git = new Git(nestedRepo);

FileTreeIterator customIterator =
new FileTreeIterator(nestedRepo, NO_GITLINKS_STRATEGY);
File r = new File(nestedRepo.getWorkTree(), "sub");

// here we want to validate that if we create a new iterator using the
// constructor that accepts a parent iterator, that the child iterator
// correctly inherits the FileModeStrategy from the parent iterator.
FileTreeIterator childIterator =
new FileTreeIterator(customIterator, r, nestedRepo.getFS());
git.add().setWorkingTreeIterator(childIterator).addFilepattern(".").call();
assertEquals(
"[sub/a.txt, mode:100644, content:content]" +
"[sub/nested/b.txt, mode:100644, content:content b]",
indexState(nestedRepo, CONTENT));
}


private static void assertEntry(String sha1string, String path, TreeWalk tw)
throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException {

+ 168
- 15
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java View File

@@ -78,6 +78,15 @@ public class FileTreeIterator extends WorkingTreeIterator {
*/
protected final FS fs;

/**
* the strategy used to compute the FileMode for a FileEntry. Can be used to
* control things such as whether to recurse into a directory or create a
* gitlink.
*
* @since 4.3
*/
protected final FileModeStrategy fileModeStrategy;

/**
* Create a new iterator to traverse the work tree and its children.
*
@@ -85,8 +94,24 @@ public class FileTreeIterator extends WorkingTreeIterator {
* the repository whose working tree will be scanned.
*/
public FileTreeIterator(Repository repo) {
this(repo, DefaultFileModeStrategy.INSTANCE);
}

/**
* Create a new iterator to traverse the work tree and its children.
*
* @param repo
* the repository whose working tree will be scanned.
* @param fileModeStrategy
* the strategy to use to determine the FileMode for a FileEntry;
* controls gitlinks etc.
*
* @since 4.3
*/
public FileTreeIterator(Repository repo, FileModeStrategy fileModeStrategy) {
this(repo.getWorkTree(), repo.getFS(),
repo.getConfig().get(WorkingTreeOptions.KEY));
repo.getConfig().get(WorkingTreeOptions.KEY),
fileModeStrategy);
initRootIterator(repo);
}

@@ -103,9 +128,32 @@ public class FileTreeIterator extends WorkingTreeIterator {
* working tree options to be used
*/
public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options) {
this(root, fs, options, DefaultFileModeStrategy.INSTANCE);
}

/**
* Create a new iterator to traverse the given directory and its children.
*
* @param root
* the starting directory. This directory should correspond to
* the root of the repository.
* @param fs
* the file system abstraction which will be necessary to perform
* certain file system operations.
* @param options
* working tree options to be used
* @param fileModeStrategy
* the strategy to use to determine the FileMode for a FileEntry;
* controls gitlinks etc.
*
* @since 4.3
*/
public FileTreeIterator(final File root, FS fs, WorkingTreeOptions options,
FileModeStrategy fileModeStrategy) {
super(options);
directory = root;
this.fs = fs;
this.fileModeStrategy = fileModeStrategy;
init(entries());
}

@@ -114,25 +162,71 @@ public class FileTreeIterator extends WorkingTreeIterator {
*
* @param p
* the parent iterator we were created from.
* @param root
* the subdirectory. This should be a directory contained within
* the parent directory.
* @param fs
* the file system abstraction which will be necessary to perform
* certain file system operations.
* @since 4.3
* @deprecated use {@link #FileTreeIterator(FileTreeIterator, File, FS)}
* instead.
*/
protected FileTreeIterator(final WorkingTreeIterator p, final File root,
FS fs) {
this(p, root, fs, DefaultFileModeStrategy.INSTANCE);
}

/**
* Create a new iterator to traverse a subdirectory.
*
* @param p
* the parent iterator we were created from.
* @param root
* the subdirectory. This should be a directory contained within
* the parent directory.
* @param fs
* the file system abstraction which will be necessary to perform
* certain file system operations.
*
* @since 4.3
*/
protected FileTreeIterator(final WorkingTreeIterator p, final File root,
protected FileTreeIterator(final FileTreeIterator p, final File root,
FS fs) {
this(p, root, fs, p.fileModeStrategy);
}

/**
* Create a new iterator to traverse a subdirectory, given the specified
* FileModeStrategy.
*
* @param p
* the parent iterator we were created from.
* @param root
* the subdirectory. This should be a directory contained within
* the parent directory
* @param fs
* the file system abstraction which will be necessary to perform
* certain file system operations.
* @param fileModeStrategy
* the strategy to use to determine the FileMode for a given
* FileEntry.
*
* @since 4.3
*/
protected FileTreeIterator(final WorkingTreeIterator p, final File root,
FS fs, FileModeStrategy fileModeStrategy) {
super(p);
directory = root;
this.fs = fs;
this.fileModeStrategy = fileModeStrategy;
init(entries());
}

@Override
public AbstractTreeIterator createSubtreeIterator(final ObjectReader reader)
throws IncorrectObjectTypeException, IOException {
return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs);
return new FileTreeIterator(this, ((FileEntry) current()).getFile(), fs, fileModeStrategy);
}

private Entry[] entries() {
@@ -141,10 +235,62 @@ public class FileTreeIterator extends WorkingTreeIterator {
return EOF;
final Entry[] r = new Entry[all.length];
for (int i = 0; i < r.length; i++)
r[i] = new FileEntry(all[i], fs);
r[i] = new FileEntry(all[i], fs, fileModeStrategy);
return r;
}

/**
* An interface representing the methods used to determine the FileMode for
* a FileEntry.
*
* @since 4.3
*/
public interface FileModeStrategy {
/**
* Compute the FileMode for a given File, based on its attributes.
*
* @param f
* the file to return a FileMode for
* @param attributes
* the attributes of a file
* @return a FileMode indicating whether the file is a regular file, a
* directory, a gitlink, etc.
*/
FileMode getMode(File f, FS.Attributes attributes);
}

/**
* A default implementation of a FileModeStrategy; defaults to treating
* nested .git directories as gitlinks, etc.
*
* @since 4.3
*/
static public class DefaultFileModeStrategy implements FileModeStrategy {
/**
* a singleton instance of the default FileModeStrategy
*/
public final static DefaultFileModeStrategy INSTANCE =
new DefaultFileModeStrategy();

@Override
public FileMode getMode(File f, FS.Attributes attributes) {
if (attributes.isSymbolicLink()) {
return FileMode.SYMLINK;
} else if (attributes.isDirectory()) {
if (new File(f, Constants.DOT_GIT).exists()) {
return FileMode.GITLINK;
} else {
return FileMode.TREE;
}
} else if (attributes.isExecutable()) {
return FileMode.EXECUTABLE_FILE;
} else {
return FileMode.REGULAR_FILE;
}
}
}


/**
* Wrapper for a standard Java IO file
*/
@@ -164,20 +310,27 @@ public class FileTreeIterator extends WorkingTreeIterator {
* file system
*/
public FileEntry(File f, FS fs) {
this(f, fs, DefaultFileModeStrategy.INSTANCE);
}

/**
* Create a new file entry given the specified FileModeStrategy
*
* @param f
* file
* @param fs
* file system
* @param fileModeStrategy
* the strategy to use when determining the FileMode of a
* file; controls gitlinks etc.
*
* @since 4.3
*/
public FileEntry(File f, FS fs, FileModeStrategy fileModeStrategy) {
this.fs = fs;
f = fs.normalize(f);
attributes = fs.getAttributes(f);
if (attributes.isSymbolicLink())
mode = FileMode.SYMLINK;
else if (attributes.isDirectory()) {
if (new File(f, Constants.DOT_GIT).exists())
mode = FileMode.GITLINK;
else
mode = FileMode.TREE;
} else if (attributes.isExecutable())
mode = FileMode.EXECUTABLE_FILE;
else
mode = FileMode.REGULAR_FILE;
mode = fileModeStrategy.getMode(f, attributes);
}

@Override

Loading…
Cancel
Save