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>
This commit is contained in:
parent
6bcd8c6e90
commit
cff546b0cb
|
@ -62,15 +62,14 @@
|
|||
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 void idOffset() throws Exception {
|
|||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
|
@ -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 FileTreeIterator(Repository repo) {
|
|||
* 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 FileTreeIterator(final File root, FS fs, WorkingTreeOptions options) {
|
|||
*
|
||||
* @param p
|
||||
* the parent iterator we were created from.
|
||||
* @param fs
|
||||
* the file system abstraction which will be necessary to perform
|
||||
* certain file system operations.
|
||||
* @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 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 @@ private Entry[] entries() {
|
|||
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 @@ static public class FileEntry extends Entry {
|
|||
* 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…
Reference in New Issue