+++ /dev/null
-!/notignored
+++ /dev/null
-notarealfile
+++ /dev/null
-/notarealfile2
+++ /dev/null
-/*
- * Copyright (C) 2010, Red Hat Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.eclipse.jgit.ignore;
-
-import java.io.BufferedWriter;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.IOException;
-import java.util.ArrayList;
-
-import org.eclipse.jgit.lib.RepositoryTestCase;
-import org.eclipse.jgit.util.JGitTestUtil;
-
-/**
- * Tests for the ignore cache
- */
-public class IgnoreCacheTest extends RepositoryTestCase {
-
- private File ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
- private SimpleIgnoreCache cache;
- private final ArrayList<File> toDelete = new ArrayList<File>();
-
- //TODO: Do not use OS dependent strings to encode file paths
-
- public void tearDown() throws Exception {
- super.tearDown();
- deleteIgnoreFiles();
- cache.clear();
- toDelete.clear();
- }
-
- public void setUp() throws Exception {
- super.setUp();
- ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
- assertTrue("Test resource directory is not a directory",ignoreTestDir.isDirectory());
-
- db = createWorkRepository();
- recursiveCopy(ignoreTestDir, db.getDirectory().getParentFile());
- cache = new SimpleIgnoreCache(db);
- initCache();
- }
-
- protected void recursiveCopy(File src, File parent) throws IOException {
- for (File file : src.listFiles()) {
- String rel = file.getName();
- File dst = new File(parent.toURI().resolve(rel));
- copyFileOrDirectory(file, dst);
- if (file.isDirectory())
- recursiveCopy(file, dst);
- }
- }
-
- protected static void copyFileOrDirectory(File src, File dst) throws IOException {
- if (src.isDirectory())
- dst.mkdir();
- else
- copyFile(src, dst);
- }
-
- public void testInitialization() {
- File test = new File(db.getDirectory().getParentFile() + "/new/a/b1/test.stp");
- assertTrue("Missing file " + test.getAbsolutePath(), test.exists());
-
- /*
- * Every folder along the path has a .gitignore file. Therefore every
- * folder should have been added and initialized
- */
- boolean result = isIgnored(getRelativePath(test));
- assertFalse("Unexpected match for " + test.toString(), result);
-
- /*
- * Check that every .gitignore along the path has been initialized
- */
- File folder = test.getParentFile();
- IgnoreNode rules = null;
- String fp = folder.getAbsolutePath();
- while (!folder.equals(db.getDirectory().getParentFile()) && fp.length() > 0) {
- rules = cache.getRules(getRelativePath(folder));
- assertNotNull("Ignore file not initialized for " + fp, rules);
- if (getRelativePath(folder).endsWith("new/a"))
- //The /new/a directory has an empty ignore file
- assertEquals("Ignore file not initialized for " + fp, 0, rules.getRules().size());
- else
- assertEquals("Ignore file not initialized for " + fp, 1, rules.getRules().size());
-
- folder = folder.getParentFile();
- fp = folder.getAbsolutePath();
- }
- if (rules != null)
- assertEquals(1, rules.getRules().size());
- else
- fail("Base directory not initialized");
-
- }
-
- public void testRules() {
- ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
- assertTrue("Test resource directory is not a directory", ignoreTestDir.isDirectory());
- createExcludeFile();
- initCache();
-
- File test = new File(db.getDirectory().getParentFile(), "test.stp");
- String path = test.getAbsolutePath();
- assertTrue("Could not find test file " + path, test.exists());
-
- IgnoreNode baseRules = cache.getRules("");
- assertNotNull("Could not find base rules", baseRules);
-
- /*
- * .git/info/excludes:
- * /test.stp
- * /notignored
- *
- * new/.gitignore:
- * notarealfile
- *
- * new/a/.gitignore:
- * <empty>
- *
- * new/a/b2/.gitignore:
- * <does not exist>
- *
- * new/a/b1/.gitignore:
- * /c
- *
- * new/a/b1/c/.gitignore:
- * !/shouldbeignored.txt
- *
- * .gitignore:
- * !/notignored
- * /commentNotIgnored.tx#t
- * /commentIgnored.txt#comment
- * /commentIgnored.txt #comment
- */
- boolean result = isIgnored(getRelativePath(test));
- assertEquals(3, baseRules.getRules().size());
- assertTrue(db.getDirectory().getParentFile().toURI().equals(baseRules.getBaseDir().toURI()));
- //Test basic exclude file
- assertTrue("Did not match file " + test.toString(), result);
- //Test exclude file priority
- assertNotIgnored("notignored");
- //Test that /src/test.stp is not matched by /test.stp in exclude file (Do not reinitialize)
- assertNotIgnored("/src/test.stp");
- //Test file that is not mentioned -- should just return unmatched
- assertNotIgnored("not/mentioned/file.txt");
-
- //Test adding nonexistent node
- test = new File(db.getDirectory().getParentFile(), "new/a/b2/d/test.stp");
- assertNotIgnored("new/a/b2/d/test.stp");
- assertNotIgnored("new/a/b2/d/");
- assertNotIgnored("new/a/b2/d");
-
- //Test folder
- test = new File(db.getDirectory().getParentFile(), "new/a/b1/c");
- assertIgnored("new/a/b1/c");
- assertIgnored("new/a/b1/c/anything.c");
- assertIgnored("new/a/b1/c/and.o");
- assertIgnored("new/a/b1/c/everything.d");
- assertIgnored("new/a/b1/c/everything.d");
- //Special case -- the normally higher priority negation in c/.gitignore is cancelled by the folder being ignored
- assertIgnored("new/a/b1/c/shouldbeignored.txt");
-
- //Test name-only (use non-existent folders)
- assertNotIgnored("notarealfile");
- assertNotIgnored("/notarealfile");
- assertIgnored("new/notarealfile");
- assertIgnored("new/notarealfile/fake");
- assertIgnored("new/a/notarealfile");
- assertIgnored("new/a/b1/notarealfile");
-
- //Test clearing node -- create empty .gitignore
- createIgnoreFile(db.getDirectory().getParentFile() + "/new/a/b2/.gitignore", new String[0]);
- test = new File(db.getDirectory().getParentFile(), "new/a/b2/c");
- initCache();
- baseRules = cache.getRules("new/a/b2");
- assertNotNull(baseRules);
- baseRules.clear();
- assertEquals(baseRules.getRules().size(), 0);
- try {
- assertFalse("Node not properly cleared", baseRules.isIgnored(getRelativePath(test)));
- } catch (IOException e) {
- e.printStackTrace();
- fail("IO exception when testing base rules");
- }
-
- //Test clearing entire cache, and isEmpty
- assertNotNull(cache.getRules(""));
- assertFalse(cache.isEmpty());
- cache.clear();
- assertNull(cache.getRules(""));
- assertTrue(cache.isEmpty());
- assertNotIgnored("/anything");
- assertNotIgnored("/new/anything");
- assertNotIgnored("/src/anything");
- }
-
- public void testPriorities() {
- ignoreTestDir = JGitTestUtil.getTestResourceFile("excludeTest");
- assertTrue("Test resource directory is not a directory",ignoreTestDir.isDirectory());
- createExcludeFile();
- initCache();
-
- File test = new File(db.getDirectory().getParentFile(), "/src/test.stp");
- assertTrue("Resource file " + test.getName() + " is missing", test.exists());
-
- //Test basic exclude file
- IgnoreNode node = cache.getRules("src");
- assertNotNull("Excludes file was not initialized", node);
-
- /*
- * src/.gitignore:
- * /*.st?
- * !/test.stp
- * !/a.c
- * /a.c
- *
- * ./.gitignore:
- * !/notignored
- *
- * .git/info/exclude:
- * /test.stp
- * /notignored
- */
- assertIgnored("src/a.c");
- assertIgnored("test.stp");
- assertIgnored("src/blank.stp");
- assertNotIgnored("notignored");
- assertNotIgnored("src/test.stp");
-
- assertEquals(4, node.getRules().size());
-
- /*
- * new/.gitignore:
- * notarealfile
- *
- * new/a/.gitignore:
- * <empty>
- *
- * new/a/b2/.gitignore:
- * <does not exist>
- *
- * new/a/b2/c/.gitignore:
- * /notarealfile2
- */
- assertIgnored("new/a/b2/c/notarealfile2");
- assertIgnored("new/notarealfile");
- assertIgnored("new/a/notarealfile");
- assertNotIgnored("new/a/b2/c/test.stp");
- assertNotIgnored("new/a/b2/c");
- assertNotIgnored("new/a/b2/nonexistent");
- }
-
- /**
- * Check if a file is not matched as ignored
- * @param relativePath
- * Path to file, relative to db.getDirectory. Use "/" as a separator,
- * this method will replace all instances of "/" with File.separator
- */
- private void assertNotIgnored(String relativePath) {
- File test = new File(db.getDirectory().getParentFile(), relativePath);
- assertFalse("Should not match " + test.toString(), isIgnored(getRelativePath(test)));
- }
-
- /**
- * Check if a file is matched as ignored
- * @param relativePath
- * Path to file, relative to db.getDirectory. Use "/" as a separator,
- * this method will replace all instances of "/" with File.separator.
- */
- private void assertIgnored(String relativePath) {
- File test = new File(db.getDirectory().getParentFile(), relativePath);
- assertTrue("Failed to match " + test.toString(), isIgnored(getRelativePath(test)));
- }
-
- /**
- * Attempt to write an ignore file at the given location
- * @param path
- * Will create file at this path
- * @param contents
- * Each entry in contents will be entered on its own line
- */
- private void createIgnoreFile(String path, String[] contents) {
- File ignoreFile = new File(path);
- ignoreFile.delete();
- ignoreFile.deleteOnExit(); //Hope to catch in the event of crash
- toDelete.add(ignoreFile); //For teardown purposes
-
- //Jump through some hoops to create the exclude file
- try {
- if (!ignoreFile.createNewFile())
- fail("Could not create ignore file" + ignoreFile.getAbsolutePath());
-
- BufferedWriter bw = new BufferedWriter(new FileWriter (ignoreFile));
- for (String s : contents)
- bw.write(s + System.getProperty("line.separator"));
- bw.flush();
- bw.close();
- } catch (IOException e1) {
- e1.printStackTrace();
- fail("Could not create exclude file");
- }
- }
-
- private void createExcludeFile() {
- String[] content = new String[2];
- content[0] = "/test.stp";
- content[1] = "/notignored";
-
- //We can do this because we explicitly delete parent directories later in deleteIgnoreFiles.
- File parent= new File(db.getDirectory().getParentFile(), ".git/info");
- if (!parent.exists())
- parent.mkdirs();
-
- createIgnoreFile(db.getDirectory().getParentFile() + "/.git/info/exclude", content);
- }
-
- private void deleteIgnoreFiles() {
- for (File f : toDelete)
- f.delete();
-
- //Systematically delete exclude parent dirs
- File f = new File(ignoreTestDir.getAbsoluteFile(), ".git/info");
- f.delete();
- f = new File(ignoreTestDir.getAbsoluteFile(), ".git");
- f.delete();
- }
-
- /**
- * @param path
- * Filepath relative to the git directory
- * @return
- * Results of cache.isIgnored(path) -- true if ignored, false if
- * a negation is encountered or if no rules apply
- */
- private boolean isIgnored(String path) {
- try {
- return cache.isIgnored(path);
- } catch (IOException e) {
- fail("IOException when attempting to check ignored status");
- }
- return false;
- }
-
- private String getRelativePath(File file) {
- String retVal = db.getDirectory().getParentFile().toURI().relativize(file.toURI()).getPath();
- if (retVal.length() == file.getAbsolutePath().length())
- fail("Not a child of the git directory");
- if (retVal.endsWith("/"))
- retVal = retVal.substring(0, retVal.length() - 1);
-
- return retVal;
- }
-
- private void initCache() {
- try {
- cache.initialize();
- } catch (IOException e) {
- e.printStackTrace();
- fail("Could not initialize cache");
- }
- }
-
-}
--- /dev/null
+/*
+ * Copyright (C) 2010, Red Hat Inc.
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.ignore;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.RepositoryTestCase;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+
+/**
+ * Tests ignore node behavior on the local filesystem.
+ */
+public class IgnoreNodeTest extends RepositoryTestCase {
+ private static final FileMode D = FileMode.TREE;
+
+ private static final FileMode F = FileMode.REGULAR_FILE;
+
+ private static final boolean ignored = true;
+
+ private static final boolean tracked = false;
+
+ private TreeWalk walk;
+
+ public void testRules() throws IOException {
+ writeIgnoreFile(".git/info/exclude", "*~", "/out");
+
+ writeIgnoreFile(".gitignore", "*.o", "/config");
+ writeTrashFile("config/secret", "");
+ writeTrashFile("mylib.c", "");
+ writeTrashFile("mylib.c~", "");
+ writeTrashFile("mylib.o", "");
+
+ writeTrashFile("out/object/foo.exe", "");
+ writeIgnoreFile("src/config/.gitignore", "lex.out");
+ writeTrashFile("src/config/lex.out", "");
+ writeTrashFile("src/config/config.c", "");
+ writeTrashFile("src/config/config.c~", "");
+ writeTrashFile("src/config/old/lex.out", "");
+
+ beginWalk();
+ assertEntry(F, tracked, ".gitignore");
+ assertEntry(D, ignored, "config");
+ assertEntry(F, ignored, "config/secret");
+ assertEntry(F, tracked, "mylib.c");
+ assertEntry(F, ignored, "mylib.c~");
+ assertEntry(F, ignored, "mylib.o");
+
+ assertEntry(D, ignored, "out");
+ assertEntry(D, ignored, "out/object");
+ assertEntry(F, ignored, "out/object/foo.exe");
+
+ assertEntry(D, tracked, "src");
+ assertEntry(D, tracked, "src/config");
+ assertEntry(F, tracked, "src/config/.gitignore");
+ assertEntry(F, tracked, "src/config/config.c");
+ assertEntry(F, ignored, "src/config/config.c~");
+ assertEntry(F, ignored, "src/config/lex.out");
+ assertEntry(D, tracked, "src/config/old");
+ assertEntry(F, ignored, "src/config/old/lex.out");
+ }
+
+ public void testNegation() throws IOException {
+ writeIgnoreFile(".gitignore", "*.o");
+ writeIgnoreFile("src/a/b/.gitignore", "!keep.o");
+ writeTrashFile("src/a/b/keep.o", "");
+ writeTrashFile("src/a/b/nothere.o", "");
+
+ beginWalk();
+ assertEntry(F, tracked, ".gitignore");
+ assertEntry(D, tracked, "src");
+ assertEntry(D, tracked, "src/a");
+ assertEntry(D, tracked, "src/a/b");
+ assertEntry(F, tracked, "src/a/b/.gitignore");
+ assertEntry(F, tracked, "src/a/b/keep.o");
+ assertEntry(F, ignored, "src/a/b/nothere.o");
+ }
+
+ public void testSlashOnlyMatchesDirectory() throws IOException {
+ writeIgnoreFile(".gitignore", "out/");
+ writeTrashFile("out", "");
+
+ beginWalk();
+ assertEntry(F, tracked, ".gitignore");
+ assertEntry(F, tracked, "out");
+
+ new File(trash, "out").delete();
+ writeTrashFile("out/foo", "");
+
+ beginWalk();
+ assertEntry(F, tracked, ".gitignore");
+ assertEntry(D, ignored, "out");
+ assertEntry(F, ignored, "out/foo");
+ }
+
+ public void testWithSlashDoesNotMatchInSubDirectory() throws IOException {
+ writeIgnoreFile(".gitignore", "a/b");
+ writeTrashFile("a/a", "");
+ writeTrashFile("a/b", "");
+ writeTrashFile("src/a/a", "");
+ writeTrashFile("src/a/b", "");
+
+ beginWalk();
+ assertEntry(F, tracked, ".gitignore");
+ assertEntry(D, tracked, "a");
+ assertEntry(F, tracked, "a/a");
+ assertEntry(F, ignored, "a/b");
+ assertEntry(D, tracked, "src");
+ assertEntry(D, tracked, "src/a");
+ assertEntry(F, tracked, "src/a/a");
+ assertEntry(F, tracked, "src/a/b");
+ }
+
+ private void beginWalk() throws CorruptObjectException {
+ walk = new TreeWalk(db);
+ walk.reset();
+ walk.addTree(new FileTreeIterator(db));
+ }
+
+ private void assertEntry(FileMode type, boolean entryIgnored,
+ String pathName) throws IOException {
+ assertTrue("walk has entry", walk.next());
+ assertEquals(pathName, walk.getPathString());
+ assertEquals(type, walk.getFileMode(0));
+
+ WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class);
+ assertNotNull("has tree", itr);
+ assertEquals("is ignored", entryIgnored, itr.isEntryIgnored());
+ if (D.equals(type))
+ walk.enterSubtree();
+ }
+
+ private void writeIgnoreFile(String name, String... rules)
+ throws IOException {
+ StringBuilder data = new StringBuilder();
+ for (String line : rules)
+ data.append(line + "\n");
+ writeTrashFile(name, data.toString());
+ }
+}
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.util.FS;
public abstract class ReadTreeTest extends RepositoryTestCase {
protected Tree theHead;
TreeWalk walk = new TreeWalk(db);
walk.reset();
walk.setRecursive(true);
- walk.addTree(new FileTreeIterator(db.getWorkDir(), FS.DETECTED));
+ walk.addTree(new FileTreeIterator(db));
String expectedValue;
String path;
int nrFiles = 0;
final TreeWalk tw = new TreeWalk(repo);
tw.reset();
tw.addTree(new DirCacheBuildIterator(builder));
- FileTreeIterator fileTreeIterator = new FileTreeIterator(
- repo.getWorkDir(), repo.getFS());
+ FileTreeIterator fileTreeIterator = new FileTreeIterator(repo);
tw.addTree(fileTreeIterator);
tw.setRecursive(true);
tw.setFilter(PathFilterGroup.createFromStrings(filepatterns));
package org.eclipse.jgit.ignore;
import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.eclipse.jgit.lib.Constants;
/**
* Represents a bundle of ignore rules inherited from a base directory.
- * Each IgnoreNode corresponds to one directory. Most IgnoreNodes will have
- * at most one source of ignore information -- its .gitignore file.
- * <br><br>
- * At the root of the repository, there may be an additional source of
- * ignore information (the exclude file)
- * <br><br>
- * It is recommended that implementers call the {@link #isIgnored(String)} method
- * rather than try to use the rules manually. The method will handle rule priority
- * automatically.
*
+ * This class is not thread safe, it maintains state about the last match.
*/
public class IgnoreNode {
- //The base directory will be used to find the .gitignore file
- private File baseDir;
- //Only used for root node.
- private File secondaryFile;
- private ArrayList<IgnoreRule> rules;
- //Indicates whether a match was made. Necessary to terminate early when a negation is encountered
- private boolean matched;
- //Indicates whether a match was made. Necessary to terminate early when a negation is encountered
- private long lastModified;
+ /** Result from {@link IgnoreNode#isIgnored(String, boolean)}. */
+ public static enum MatchResult {
+ /** The file is not ignored, due to a rule saying its not ignored. */
+ NOT_IGNORED,
- /**
- * Create a new ignore node based on the given directory. The node's
- * ignore file will be the .gitignore file in the directory (if any)
- * Rules contained within this node will only be applied to files
- * which are descendants of this directory.
- *
- * @param baseDir
- * base directory of this ignore node
- */
- public IgnoreNode(File baseDir) {
- this.baseDir = baseDir;
- rules = new ArrayList<IgnoreRule>();
- secondaryFile = null;
- lastModified = 0l;
- }
+ /** The file is ignored due to a rule in this node. */
+ IGNORED,
- /**
- * Parse files according to gitignore standards.
- *
- * @throws IOException
- * Error thrown when reading an ignore file.
- */
- private void parse() throws IOException {
- if (secondaryFile != null && secondaryFile.exists())
- parse(secondaryFile);
-
- parse(new File(baseDir.getAbsolutePath(), ".gitignore"));
+ /** The ignore status is unknown, check inherited rules. */
+ CHECK_PARENT;
}
- private void parse(File targetFile) throws IOException {
- if (!targetFile.exists())
- return;
-
- BufferedReader br = new BufferedReader(new FileReader(targetFile));
- String txt;
- try {
- while ((txt = br.readLine()) != null) {
- txt = txt.trim();
- if (txt.length() > 0 && !txt.startsWith("#"))
- rules.add(new IgnoreRule(txt));
- }
- } finally {
- br.close();
- }
- }
+ /** The rules that have been parsed into this node. */
+ private final List<IgnoreRule> rules;
- /**
- * @return
- * Base directory to which these rules apply, absolute path
- */
- public File getBaseDir() {
- return baseDir;
+ /** Create an empty ignore node with no rules. */
+ public IgnoreNode() {
+ rules = new ArrayList<IgnoreRule>();
}
-
/**
+ * Create an ignore node with given rules.
*
- * @return
- * List of all ignore rules held by this node
- */
- public ArrayList<IgnoreRule> getRules() {
- return rules;
+ * @param rules
+ * list of rules.
+ **/
+ public IgnoreNode(List<IgnoreRule> rules) {
+ this.rules = rules;
}
-
/**
+ * Parse files according to gitignore standards.
*
- * Returns whether or not a target is matched as being ignored by
- * any patterns in this directory.
- * <br>
- * Will return false if the file is not a descendant of this directory.
- * <br>
- *
- * @param target
- * Absolute path to the file. This makes stripping common path elements easier.
- * @return
- * true if target is ignored, false if the target is explicitly not
- * ignored or if no rules exist for the target.
+ * @param in
+ * input stream holding the standard ignore format. The caller is
+ * responsible for closing the stream.
* @throws IOException
- * Failed to parse rules
- *
+ * Error thrown when reading an ignore file.
*/
- public boolean isIgnored(String target) throws IOException {
- matched = false;
- File targetFile = new File(target);
- String tar = baseDir.toURI().relativize(targetFile.toURI()).getPath();
-
- if (tar.length() == target.length())
- //target is not a derivative of baseDir, this node has no jurisdiction
- return false;
-
- if (rules.isEmpty()) {
- //Either we haven't parsed yet, or the file is empty.
- //Empty file should be very fast to parse
- parse();
- }
- if (rules.isEmpty())
- return false;
-
- /*
- * Boolean matched is necessary because we may have encountered
- * a negation ("!/test.c").
- */
-
- int i;
- //Parse rules in the reverse order that they were read
- for (i = rules.size() -1; i > -1; i--) {
- matched = rules.get(i).isMatch(tar, targetFile.isDirectory());
- if (matched)
- break;
+ public void parse(InputStream in) throws IOException {
+ BufferedReader br = asReader(in);
+ String txt;
+ while ((txt = br.readLine()) != null) {
+ txt = txt.trim();
+ if (txt.length() > 0 && !txt.startsWith("#"))
+ rules.add(new IgnoreRule(txt));
}
-
- if (i > -1 && rules.get(i) != null)
- return rules.get(i).getResult();
-
- return false;
- }
-
- /**
- * @return
- * True if the previous call to isIgnored resulted in a match,
- * false otherwise.
- */
- public boolean wasMatched() {
- return matched;
}
- /**
- * Adds another file as a source of ignore rules for this file. The
- * secondary file will have a lower priority than the first file, and
- * the parent directory of this node will be regarded as firstFile.getParent()
- *
- * @param f
- * Secondary source of gitignore information for this node
- */
- public void addSecondarySource(File f) {
- secondaryFile = f;
+ private static BufferedReader asReader(InputStream in) {
+ return new BufferedReader(new InputStreamReader(in, Constants.CHARSET));
}
- /**
- * Clear all rules in this node.
- */
- public void clear() {
- rules.clear();
+ /** @return list of all ignore rules held by this node. */
+ public List<IgnoreRule> getRules() {
+ return Collections.unmodifiableList(rules);
}
/**
- * @param val
- * Set the last modified time of this node.
- */
- public void setLastModified(long val) {
- lastModified = val;
- }
-
- /**
- * @return
- * Last modified time of this node.
+ * Determine if an entry path matches an ignore rule.
+ *
+ * @param entryPath
+ * the path to test. The path must be relative to this ignore
+ * node's own repository path, and in repository path format
+ * (uses '/' and not '\').
+ * @param isDirectory
+ * true if the target item is a directory.
+ * @return status of the path.
*/
- public long getLastModified() {
- return lastModified;
+ public MatchResult isIgnored(String entryPath, boolean isDirectory) {
+ if (rules.isEmpty())
+ return MatchResult.CHECK_PARENT;
+
+ // Parse rules in the reverse order that they were read
+ for (int i = rules.size() - 1; i > -1; i--) {
+ IgnoreRule rule = rules.get(i);
+ if (rule.isMatch(entryPath, isDirectory)) {
+ if (rule.getResult())
+ return MatchResult.IGNORED;
+ else
+ return MatchResult.NOT_IGNORED;
+ }
+ }
+ return MatchResult.CHECK_PARENT;
}
}
endIndex --;
dirOnly = true;
}
+ boolean hasSlash = pattern.contains("/");
pattern = pattern.substring(startIndex, endIndex);
- if (!pattern.contains("/"))
+ if (!hasSlash)
nameOnly = true;
else if (!pattern.startsWith("/")) {
//Contains "/" but does not start with one
+++ /dev/null
-/*
- * Copyright (C) 2010, Red Hat Inc.
- * and other copyright owners as documented in the project's IP log.
- *
- * This program and the accompanying materials are made available
- * under the terms of the Eclipse Distribution License v1.0 which
- * accompanies this distribution, is reproduced below, and is
- * available at http://www.eclipse.org/org/documents/edl-v10.php
- *
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met:
- *
- * - Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- *
- * - Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following
- * disclaimer in the documentation and/or other materials provided
- * with the distribution.
- *
- * - Neither the name of the Eclipse Foundation, Inc. nor the
- * names of its contributors may be used to endorse or promote
- * products derived from this software without specific prior
- * written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
- * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package org.eclipse.jgit.ignore;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.util.HashMap;
-
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.treewalk.FileTreeIterator;
-import org.eclipse.jgit.treewalk.TreeWalk;
-import org.eclipse.jgit.treewalk.filter.PathSuffixFilter;
-import org.eclipse.jgit.util.FS;
-
-/**
- * A simple ignore cache. Stores ignore information on .gitignore and exclude files.
- * <br><br>
- * The cache can be initialized by calling {@link #initialize()} on a
- * target file.
- *
- * Inspiration from: Ferry Huberts
- */
-public class SimpleIgnoreCache {
-
- /**
- * Map of ignore nodes, indexed by base directory. By convention, the
- * base directory string should NOT start or end with a "/". Use
- * {@link #relativize(File)} before appending nodes to the ignoreMap
- * <br>
- * e.g: path/to/directory is a valid String
- */
- private HashMap<String, IgnoreNode> ignoreMap;
-
- //Repository associated with this cache
- private Repository repository;
-
- //Base directory of this cache
- private URI rootFileURI;
-
- /**
- * Creates a base implementation of an ignore cache. This default implementation
- * will search for all .gitignore files in all children of the base directory,
- * and grab the exclude file from baseDir/.git/info/exclude.
- * <br><br>
- * Call {@link #initialize()} to fetch the ignore information relevant
- * to a target file.
- * @param repository
- * Repository to associate this cache with. The cache's base directory will
- * be set to this repository's GIT_DIR
- *
- */
- public SimpleIgnoreCache(Repository repository) {
- ignoreMap = new HashMap<String, IgnoreNode>();
- this.repository = repository;
- this.rootFileURI = repository.getWorkDir().toURI();
- }
-
- /**
- * Initializes the ignore map for the target file and all parents.
- * This will delete existing ignore information for all folders
- * on the partial initialization path. Will only function for files
- * that are children of the cache's basePath.
- * <br><br>
- * Note that this does not initialize the ignore rules. Ignore rules will
- * be parsed when needed during a call to {@link #isIgnored(String)}
- *
- * @throws IOException
- * The tree could not be walked.
- */
- public void initialize() throws IOException {
- TreeWalk tw = new TreeWalk(repository);
- tw.reset();
- tw.addTree(new FileTreeIterator(repository.getWorkDir(), FS.DETECTED));
- tw.setFilter(PathSuffixFilter.create("/" + Constants.DOT_GIT_IGNORE));
- tw.setRecursive(true);
- while (tw.next())
- addNodeFromTree(tw.getTree(0, FileTreeIterator.class));
-
- //The base is special
- //TODO: Test alternate locations for GIT_DIR
- readRulesAtBase();
- }
-
- /**
- * Creates rules for .git/info/exclude and .gitignore to the base node.
- * It will overwrite the existing base ignore node. There will always
- * be a base ignore node, even if there is no .gitignore file
- */
- private void readRulesAtBase() {
- //Add .gitignore rules
- File f = new File(repository.getWorkDir(), Constants.DOT_GIT_IGNORE);
- String path = f.getAbsolutePath();
- IgnoreNode n = new IgnoreNode(f.getParentFile());
-
- //Add exclude rules
- //TODO: Get /info directory without string concat
- path = new File(repository.getDirectory(), "info/exclude").getAbsolutePath();
- f = new File(path);
- if (f.canRead())
- n.addSecondarySource(f);
-
- ignoreMap.put("", n);
- }
-
- /**
- * Adds a node located at the FileTreeIterator's current position.
- *
- * @param t
- * FileTreeIterator to check for ignore info. The name of the
- * entry should be ".gitignore".
- */
- protected void addNodeFromTree(FileTreeIterator t) {
- IgnoreNode n = ignoreMap.get(relativize(t.getDirectory()));
- long time = t.getEntryLastModified();
- if (n != null) {
- if (n.getLastModified() == time)
- //TODO: Test and optimize
- return;
- }
- n = addIgnoreNode(t.getDirectory());
- n.setLastModified(time);
- }
-
- /**
- * Maps the directory to an IgnoreNode, but does not initialize
- * the IgnoreNode. If a node already exists it will be emptied. Empty nodes
- * will be initialized when needed, see {@link #isIgnored(String)}
- *
- * @param dir
- * directory to load rules from
- * @return
- * true if set successfully, false if directory does not exist
- * or if directory does not contain a .gitignore file.
- */
- protected IgnoreNode addIgnoreNode(File dir) {
- String relativeDir = relativize(dir);
- IgnoreNode n = ignoreMap.get(relativeDir);
- if (n != null)
- n.clear();
- else {
- n = new IgnoreNode(dir);
- ignoreMap.put(relativeDir, n);
- }
- return n;
- }
-
- /**
- * Returns the ignored status of the file based on the current state
- * of the ignore nodes. Ignore nodes will not be updated and new ignore
- * nodes will not be created.
- * <br><br>
- * Traverses from highest to lowest priority and quits as soon as a match
- * is made. If no match is made anywhere, the file is assumed
- * to be not ignored.
- *
- * @param file
- * Path string relative to Repository.getWorkDir();
- * @return true
- * True if file is ignored, false if the file matches a negation statement
- * or if there are no rules pertaining to the file.
- * @throws IOException
- * Failed to check ignore status
- */
- public boolean isIgnored(String file) throws IOException{
- String currentPriority = file;
-
- boolean ignored = false;
- String target = rootFileURI.getPath() + file;
- while (currentPriority.length() > 1) {
- currentPriority = getParent(currentPriority);
- IgnoreNode n = ignoreMap.get(currentPriority);
-
- if (n != null) {
- ignored = n.isIgnored(target);
-
- if (n.wasMatched()) {
- if (ignored)
- return ignored;
- else
- target = getParent(target);
- }
- }
- }
-
- return false;
- }
-
- /**
- * String manipulation to get the parent directory of the given path.
- * It may be more efficient to make a file and call File.getParent().
- * This function is only called in {@link #initialize}
- *
- * @param filePath
- * Will seek parent directory for this path. Returns empty string
- * if the filePath does not contain a File.separator
- * @return
- * Parent of the filePath, or blank string if non-existent
- */
- private String getParent(String filePath) {
- int lastSlash = filePath.lastIndexOf("/");
- if (filePath.length() > 0 && lastSlash != -1)
- return filePath.substring(0, lastSlash);
- else
- //This line should be unreachable with the current partiallyInitialize
- return "";
- }
-
- /**
- * @param relativePath
- * Directory to find rules for, should be relative to the repository root
- * @return
- * Ignore rules for given base directory, contained in an IgnoreNode
- */
- public IgnoreNode getRules(String relativePath) {
- return ignoreMap.get(relativePath);
- }
-
- /**
- * @return
- * True if there are no ignore rules in this cache
- */
- public boolean isEmpty() {
- return ignoreMap.isEmpty();
- }
-
- /**
- * Clears the cache
- */
- public void clear() {
- ignoreMap.clear();
- }
-
- /**
- * Returns the relative path versus the repository root.
- *
- * @param directory
- * Directory to find relative path for.
- * @return
- * Relative path versus the repository root. This function will
- * strip the last trailing "/" from its return string
- */
- private String relativize(File directory) {
- String retVal = rootFileURI.relativize(directory.toURI()).getPath();
- if (retVal.endsWith("/"))
- retVal = retVal.substring(0, retVal.length() - 1);
- return retVal;
- }
-
-}
*/
protected final FS fs;
+ /**
+ * Create a new iterator to traverse the work tree and its children.
+ *
+ * @param repo
+ * the repository whose working tree will be scanned.
+ */
+ public FileTreeIterator(Repository repo) {
+ this(repo.getWorkDir(), repo.getFS());
+ initRootIterator(repo);
+ }
+
/**
* Create a new iterator to traverse the given directory and its children.
*
static String pathOf(final AbstractTreeIterator t) {
return RawParseUtils.decode(Constants.CHARSET, t.path, 0, t.pathLen);
}
+
+ static String pathOf(final byte[] buf, int pos, int end) {
+ return RawParseUtils.decode(Constants.CHARSET, buf, pos, end);
+ }
}
package org.eclipse.jgit.treewalk;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.text.MessageFormat;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Comparator;
import org.eclipse.jgit.JGitText;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.ignore.IgnoreNode;
+import org.eclipse.jgit.ignore.IgnoreRule;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.util.FS;
/**
/** Current position within {@link #entries}. */
private int ptr;
+ /** If there is a .gitignore file present, the parsed rules from it. */
+ private IgnoreNode ignoreNode;
+
/** Create a new iterator with no parent. */
protected WorkingTreeIterator() {
super();
nameEncoder = p.nameEncoder;
}
+ /**
+ * Initialize this iterator for the root level of a repository.
+ * <p>
+ * This method should only be invoked after calling {@link #init(Entry[])},
+ * and only for the root iterator.
+ *
+ * @param repo
+ * the repository.
+ */
+ protected void initRootIterator(Repository repo) {
+ Entry entry;
+ if (ignoreNode instanceof PerDirectoryIgnoreNode)
+ entry = ((PerDirectoryIgnoreNode) ignoreNode).entry;
+ else
+ entry = null;
+ ignoreNode = new RootIgnoreNode(entry, repo);
+ }
+
@Override
public byte[] idBuffer() {
if (contentIdFromPtr == ptr)
return current().getLastModified();
}
+ /**
+ * Determine if the current entry path is ignored by an ignore rule.
+ *
+ * @return true if the entry was ignored by an ignore rule file.
+ * @throws IOException
+ * a relevant ignore rule file exists but cannot be read.
+ */
+ public boolean isEntryIgnored() throws IOException {
+ return isEntryIgnored(pathLen);
+ }
+
+ /**
+ * Determine if the entry path is ignored by an ignore rule.
+ *
+ * @param pLen
+ * the length of the path in the path buffer.
+ * @return true if the entry is ignored by an ignore rule.
+ * @throws IOException
+ * a relevant ignore rule file exists but cannot be read.
+ */
+ protected boolean isEntryIgnored(final int pLen) throws IOException {
+ IgnoreNode rules = getIgnoreNode();
+ if (rules != null) {
+ // The ignore code wants path to start with a '/' if possible.
+ // If we have the '/' in our path buffer because we are inside
+ // a subdirectory include it in the range we convert to string.
+ //
+ int pOff = pathOffset;
+ if (0 < pOff)
+ pOff--;
+ String p = TreeWalk.pathOf(path, pOff, pLen);
+ switch (rules.isIgnored(p, FileMode.TREE.equals(mode))) {
+ case IGNORED:
+ return true;
+ case NOT_IGNORED:
+ return false;
+ case CHECK_PARENT:
+ break;
+ }
+ }
+ if (parent instanceof WorkingTreeIterator)
+ return ((WorkingTreeIterator) parent).isEntryIgnored(pLen);
+ return false;
+ }
+
+ private IgnoreNode getIgnoreNode() throws IOException {
+ if (ignoreNode instanceof PerDirectoryIgnoreNode)
+ ignoreNode = ((PerDirectoryIgnoreNode) ignoreNode).load();
+ return ignoreNode;
+ }
+
private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() {
public int compare(final Entry o1, final Entry o2) {
final byte[] a = o1.encodedName;
continue;
if (Constants.DOT_GIT.equals(name))
continue;
+ if (Constants.DOT_GIT_IGNORE.equals(name))
+ ignoreNode = new PerDirectoryIgnoreNode(e);
if (i != o)
entries[o] = e;
e.encodeName(nameEncoder);
*/
public abstract InputStream openInputStream() throws IOException;
}
+
+ /** Magic type indicating we know rules exist, but they aren't loaded. */
+ private static class PerDirectoryIgnoreNode extends IgnoreNode {
+ final Entry entry;
+
+ PerDirectoryIgnoreNode(Entry entry) {
+ super(Collections.<IgnoreRule> emptyList());
+ this.entry = entry;
+ }
+
+ IgnoreNode load() throws IOException {
+ IgnoreNode r = new IgnoreNode();
+ InputStream in = entry.openInputStream();
+ try {
+ r.parse(in);
+ } finally {
+ in.close();
+ }
+ return r.getRules().isEmpty() ? null : r;
+ }
+ }
+
+ /** Magic type indicating there may be rules for the top level. */
+ private static class RootIgnoreNode extends PerDirectoryIgnoreNode {
+ final Repository repository;
+
+ RootIgnoreNode(Entry entry, Repository repository) {
+ super(entry);
+ this.repository = repository;
+ }
+
+ @Override
+ IgnoreNode load() throws IOException {
+ IgnoreNode r;
+ if (entry != null) {
+ r = super.load();
+ if (r == null)
+ r = new IgnoreNode();
+ } else {
+ r = new IgnoreNode();
+ }
+
+ File exclude = new File(repository.getDirectory(), "info/exclude");
+ if (exclude.exists()) {
+ FileInputStream in = new FileInputStream(exclude);
+ try {
+ r.parse(in);
+ } finally {
+ in.close();
+ }
+ }
+
+ return r.getRules().isEmpty() ? null : r;
+ }
+ }
}