Browse Source
The working tree iterator has perfect knowledge of the path structure as well as immediate information about whether or not an ignore file even exists at this level. We can exploit that to simplify the logic and running time for testing ignored file status by pushing all of the checks down into the iterator itself. Change-Id: I22ff534853e8c5672cc5c2d9444aeb14e294070e Signed-off-by: Shawn O. Pearce <spearce@spearce.org> CC: Charley Wang <chwang@redhat.com> CC: Chris Aniszczyk <caniszczyk@gmail.com> CC: Stefan Lay <stefan.lay@sap.com> CC: Matthias Sohn <matthias.sohn@sap.com>stable-0.9
Shawn O. Pearce
15 years ago
19 changed files with 405 additions and 860 deletions
@ -1 +0,0 @@
|
||||
!/notignored |
@ -1 +0,0 @@
|
||||
notarealfile |
@ -1 +0,0 @@
|
||||
/c |
@ -1 +0,0 @@
|
||||
/notarealfile2 |
@ -1,403 +0,0 @@
|
||||
/* |
||||
* 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"); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,184 @@
|
||||
/* |
||||
* 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()); |
||||
} |
||||
} |
@ -1,291 +0,0 @@
|
||||
/* |
||||
* 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; |
||||
} |
||||
|
||||
} |
Loading…
Reference in new issue