diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 6ca3df17a..af40e883c 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -10,6 +10,7 @@ Bundle-RequiredExecutionEnvironment: J2SE-1.5 Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", org.eclipse.jgit.api;version="[3.7.0,3.8.0)", org.eclipse.jgit.api.errors;version="[3.7.0,3.8.0)", + org.eclipse.jgit.attributes;version="[3.7.0,3.8.0)", org.eclipse.jgit.awtui;version="[3.7.0,3.8.0)", org.eclipse.jgit.blame;version="[3.7.0,3.8.0)", org.eclipse.jgit.console;version="[3.7.0,3.8.0)", diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java new file mode 100644 index 000000000..ea250369a --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2014, Obeo. + * 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.attributes; + +import static org.eclipse.jgit.attributes.Attribute.State.SET; +import static org.eclipse.jgit.attributes.Attribute.State.UNSET; +import static org.junit.Assert.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Set; + +import org.junit.After; +import org.junit.Test; + +/** + * Test {@link AttributesNode} + */ +public class AttributeNodeTest { + + private static final Attribute A_SET_ATTR = new Attribute("A", SET); + + private static final Attribute A_UNSET_ATTR = new Attribute("A", UNSET); + + private static final Attribute B_SET_ATTR = new Attribute("B", SET); + + private static final Attribute B_UNSET_ATTR = new Attribute("B", UNSET); + + private static final Attribute C_VALUE_ATTR = new Attribute("C", "value"); + + private static final Attribute C_VALUE2_ATTR = new Attribute("C", "value2"); + + private InputStream is; + + @After + public void after() throws IOException { + if (is != null) + is.close(); + } + + @Test + public void testBasic() throws IOException { + String attributeFileContent = "*.type1 A -B C=value\n" + + "*.type2 -A B C=value2"; + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("file.type2", node, + asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); + } + + @Test + public void testNegativePattern() throws IOException { + String attributeFileContent = "!*.type1 A -B C=value\n" + + "!*.type2 -A B C=value2"; + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, Collections. emptySet()); + assertAttribute("file.type2", node, Collections. emptySet()); + } + + @Test + public void testEmptyNegativeAttributeKey() throws IOException { + String attributeFileContent = "*.type1 - \n" // + + "*.type2 - -A"; + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, Collections. emptySet()); + assertAttribute("file.type2", node, asSet(A_UNSET_ATTR)); + } + + @Test + public void testEmptyValueKey() throws IOException { + String attributeFileContent = "*.type1 = \n" // + + "*.type2 =value\n"// + + "*.type3 attr=\n"; + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, Collections. emptySet()); + assertAttribute("file.type2", node, Collections. emptySet()); + assertAttribute("file.type3", node, asSet(new Attribute("attr", ""))); + } + + @Test + public void testEmptyLine() throws IOException { + String attributeFileContent = "*.type1 A -B C=value\n" // + + "\n" // + + " \n" // + + "*.type2 -A B C=value2"; + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("file.type2", node, + asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); + } + + @Test + public void testTabSeparator() throws IOException { + String attributeFileContent = "*.type1 \tA -B\tC=value\n" + + "*.type2\t -A\tB C=value2\n" // + + "*.type3 \t\t B\n" // + + "*.type3\t-A";// + + is = new ByteArrayInputStream(attributeFileContent.getBytes()); + AttributesNode node = new AttributesNode(); + node.parse(is); + assertAttribute("file.type1", node, + asSet(A_SET_ATTR, B_UNSET_ATTR, C_VALUE_ATTR)); + assertAttribute("file.type2", node, + asSet(A_UNSET_ATTR, B_SET_ATTR, C_VALUE2_ATTR)); + assertAttribute("file.type3", node, asSet(A_UNSET_ATTR, B_SET_ATTR)); + } + + private void assertAttribute(String path, AttributesNode node, + Set attrs) { + HashMap attributes = new HashMap(); + node.getAttributes(path, false, attributes); + assertEquals(attrs, new HashSet(attributes.values())); + } + + static Set asSet(Attribute... attrs) { + Set result = new HashSet(); + for (Attribute attr : attrs) + result.add(attr); + return result; + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java new file mode 100644 index 000000000..93b954fa9 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010, Marc Strapetz + * Copyright (C) 2013, Gunnar Wagenknecht + * 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.attributes; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +import org.eclipse.jgit.attributes.Attribute.State; +import org.junit.Test; + +/** + * Tests {@link Attribute} + */ +public class AttributeTest { + + @Test + public void testBasic() { + Attribute a = new Attribute("delta", State.SET); + assertEquals(a.getKey(), "delta"); + assertEquals(a.getState(), State.SET); + assertNull(a.getValue()); + assertEquals(a.toString(), "delta"); + + a = new Attribute("delta", State.UNSET); + assertEquals(a.getKey(), "delta"); + assertEquals(a.getState(), State.UNSET); + assertNull(a.getValue()); + assertEquals(a.toString(), "-delta"); + + a = new Attribute("delta", "value"); + assertEquals(a.getKey(), "delta"); + assertEquals(a.getState(), State.CUSTOM); + assertEquals(a.getValue(), "value"); + assertEquals(a.toString(), "delta=value"); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java new file mode 100644 index 000000000..686540692 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java @@ -0,0 +1,412 @@ +/* + * 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.attributes; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +/** + * Tests git attributes pattern matches + *

+ * Inspired by {@link org.eclipse.jgit.ignore.IgnoreMatcherTest} + *

+ */ +public class AttributesMatcherTest { + + @Test + public void testBasic() { + String pattern = "/test.stp"; + assertMatched(pattern, "/test.stp"); + + pattern = "#/test.stp"; + assertNotMatched(pattern, "/test.stp"); + } + + @Test + public void testFileNameWildcards() { + //Test basic * and ? for any pattern + any character + String pattern = "*.st?"; + assertMatched(pattern, "/test.stp"); + assertMatched(pattern, "/anothertest.stg"); + assertMatched(pattern, "/anothertest.st0"); + assertNotMatched(pattern, "/anothertest.sta1"); + //Check that asterisk does not expand to "/" + assertNotMatched(pattern, "/another/test.sta1"); + + //Same as above, with a leading slash to ensure that doesn't cause problems + pattern = "/*.st?"; + assertMatched(pattern, "/test.stp"); + assertMatched(pattern, "/anothertest.stg"); + assertMatched(pattern, "/anothertest.st0"); + assertNotMatched(pattern, "/anothertest.sta1"); + //Check that asterisk does not expand to "/" + assertNotMatched(pattern, "/another/test.sta1"); + + //Test for numbers + pattern = "*.sta[0-5]"; + assertMatched(pattern, "/test.sta5"); + assertMatched(pattern, "/test.sta4"); + assertMatched(pattern, "/test.sta3"); + assertMatched(pattern, "/test.sta2"); + assertMatched(pattern, "/test.sta1"); + assertMatched(pattern, "/test.sta0"); + assertMatched(pattern, "/anothertest.sta2"); + assertNotMatched(pattern, "test.stag"); + assertNotMatched(pattern, "test.sta6"); + + //Test for letters + pattern = "/[tv]est.sta[a-d]"; + assertMatched(pattern, "/test.staa"); + assertMatched(pattern, "/test.stab"); + assertMatched(pattern, "/test.stac"); + assertMatched(pattern, "/test.stad"); + assertMatched(pattern, "/vest.stac"); + assertNotMatched(pattern, "test.stae"); + assertNotMatched(pattern, "test.sta9"); + + //Test child directory/file is matched + pattern = "/src/ne?"; + assertMatched(pattern, "/src/new/"); + assertMatched(pattern, "/src/new"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/src/new/a/a.c"); + assertNotMatched(pattern, "/src/new.c"); + + //Test name-only fnmatcher matches + pattern = "ne?"; + assertMatched(pattern, "/src/new/"); + assertMatched(pattern, "/src/new"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/src/new/a/a.c"); + assertMatched(pattern, "/neb"); + assertNotMatched(pattern, "/src/new.c"); + } + + @Test + public void testTargetWithoutLeadingSlash() { + //Test basic * and ? for any pattern + any character + String pattern = "/*.st?"; + assertMatched(pattern, "test.stp"); + assertMatched(pattern, "anothertest.stg"); + assertMatched(pattern, "anothertest.st0"); + assertNotMatched(pattern, "anothertest.sta1"); + //Check that asterisk does not expand to "" + assertNotMatched(pattern, "another/test.sta1"); + + //Same as above, with a leading slash to ensure that doesn't cause problems + pattern = "/*.st?"; + assertMatched(pattern, "test.stp"); + assertMatched(pattern, "anothertest.stg"); + assertMatched(pattern, "anothertest.st0"); + assertNotMatched(pattern, "anothertest.sta1"); + //Check that asterisk does not expand to "" + assertNotMatched(pattern, "another/test.sta1"); + + //Test for numbers + pattern = "/*.sta[0-5]"; + assertMatched(pattern, "test.sta5"); + assertMatched(pattern, "test.sta4"); + assertMatched(pattern, "test.sta3"); + assertMatched(pattern, "test.sta2"); + assertMatched(pattern, "test.sta1"); + assertMatched(pattern, "test.sta0"); + assertMatched(pattern, "anothertest.sta2"); + assertNotMatched(pattern, "test.stag"); + assertNotMatched(pattern, "test.sta6"); + + //Test for letters + pattern = "/[tv]est.sta[a-d]"; + assertMatched(pattern, "test.staa"); + assertMatched(pattern, "test.stab"); + assertMatched(pattern, "test.stac"); + assertMatched(pattern, "test.stad"); + assertMatched(pattern, "vest.stac"); + assertNotMatched(pattern, "test.stae"); + assertNotMatched(pattern, "test.sta9"); + + //Test child directory/file is matched + pattern = "/src/ne?"; + assertMatched(pattern, "src/new/"); + assertMatched(pattern, "src/new"); + assertMatched(pattern, "src/new/a.c"); + assertMatched(pattern, "src/new/a/a.c"); + assertNotMatched(pattern, "src/new.c"); + + //Test name-only fnmatcher matches + pattern = "ne?"; + assertMatched(pattern, "src/new/"); + assertMatched(pattern, "src/new"); + assertMatched(pattern, "src/new/a.c"); + assertMatched(pattern, "src/new/a/a.c"); + assertMatched(pattern, "neb"); + assertNotMatched(pattern, "src/new.c"); + } + + @Test + public void testParentDirectoryGitAttributes() { + //Contains git attribute patterns such as might be seen in a parent directory + + //Test for wildcards + String pattern = "/*/*.c"; + assertMatched(pattern, "/file/a.c"); + assertMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src/new/a.c"); + + //Test child directory/file is matched + pattern = "/src/new"; + assertMatched(pattern, "/src/new/"); + assertMatched(pattern, "/src/new"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/src/new/a/a.c"); + assertNotMatched(pattern, "/src/new.c"); + + //Test child directory is matched, slash after name + pattern = "/src/new/"; + assertMatched(pattern, "/src/new/"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/src/new/a/a.c"); + assertNotMatched(pattern, "/src/new"); + assertNotMatched(pattern, "/src/new.c"); + + //Test directory is matched by name only + pattern = "b1"; + assertMatched(pattern, "/src/new/a/b1/a.c"); + assertNotMatched(pattern, "/src/new/a/b2/file.c"); + assertNotMatched(pattern, "/src/new/a/bb1/file.c"); + assertNotMatched(pattern, "/src/new/a/file.c"); + } + + @Test + public void testTrailingSlash() { + String pattern = "/src/"; + assertMatched(pattern, "/src/"); + assertMatched(pattern, "/src/new"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/src/a.c"); + assertNotMatched(pattern, "/src"); + assertNotMatched(pattern, "/srcA/"); + } + + @Test + public void testNameOnlyMatches() { + /* + * Name-only matches do not contain any path separators + */ + //Test matches for file extension + String pattern = "*.stp"; + assertMatched(pattern, "/test.stp"); + assertMatched(pattern, "/src/test.stp"); + assertNotMatched(pattern, "/test.stp1"); + assertNotMatched(pattern, "/test.astp"); + + //Test matches for name-only, applies to file name or folder name + pattern = "src"; + assertMatched(pattern, "/src"); + assertMatched(pattern, "/src/"); + assertMatched(pattern, "/src/a.c"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/new/src/a.c"); + assertMatched(pattern, "/file/src"); + + //Test matches for name-only, applies only to folder names + pattern = "src/"; + assertMatched(pattern, "/src/"); + assertMatched(pattern, "/src/a.c"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/new/src/a.c"); + assertNotMatched(pattern, "/src"); + assertNotMatched(pattern, "/file/src"); + + //Test matches for name-only, applies to file name or folder name + //With a small wildcard + pattern = "?rc"; + assertMatched(pattern, "/src/a.c"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/new/src/a.c"); + assertMatched(pattern, "/file/src"); + assertMatched(pattern, "/src/"); + + //Test matches for name-only, applies to file name or folder name + //With a small wildcard + pattern = "?r[a-c]"; + assertMatched(pattern, "/src/a.c"); + assertMatched(pattern, "/src/new/a.c"); + assertMatched(pattern, "/new/src/a.c"); + assertMatched(pattern, "/file/src"); + assertMatched(pattern, "/src/"); + assertMatched(pattern, "/srb/a.c"); + assertMatched(pattern, "/grb/new/a.c"); + assertMatched(pattern, "/new/crb/a.c"); + assertMatched(pattern, "/file/3rb"); + assertMatched(pattern, "/xrb/"); + assertMatched(pattern, "/3ra/a.c"); + assertMatched(pattern, "/5ra/new/a.c"); + assertMatched(pattern, "/new/1ra/a.c"); + assertMatched(pattern, "/file/dra"); + assertMatched(pattern, "/era/"); + assertNotMatched(pattern, "/crg"); + assertNotMatched(pattern, "/cr3"); + } + + @Test + public void testGetters() { + AttributesRule r = new AttributesRule("/pattern/", ""); + assertFalse(r.isNameOnly()); + assertTrue(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertTrue(r.getAttributes().isEmpty()); + assertEquals(r.getPattern(), "/pattern"); + + r = new AttributesRule("/patter?/", ""); + assertFalse(r.isNameOnly()); + assertTrue(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertTrue(r.getAttributes().isEmpty()); + assertEquals(r.getPattern(), "/patter?"); + + r = new AttributesRule("patt*", ""); + assertTrue(r.isNameOnly()); + assertFalse(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertTrue(r.getAttributes().isEmpty()); + assertEquals(r.getPattern(), "patt*"); + + r = new AttributesRule("pattern", "attribute1"); + assertTrue(r.isNameOnly()); + assertFalse(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertFalse(r.getAttributes().isEmpty()); + assertEquals(r.getAttributes().size(), 1); + assertEquals(r.getPattern(), "pattern"); + + r = new AttributesRule("pattern", "attribute1 -attribute2"); + assertTrue(r.isNameOnly()); + assertFalse(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertEquals(r.getAttributes().size(), 2); + assertEquals(r.getPattern(), "pattern"); + + r = new AttributesRule("pattern", "attribute1 \t-attribute2 \t"); + assertTrue(r.isNameOnly()); + assertFalse(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertEquals(r.getAttributes().size(), 2); + assertEquals(r.getPattern(), "pattern"); + + r = new AttributesRule("pattern", "attribute1\t-attribute2\t"); + assertTrue(r.isNameOnly()); + assertFalse(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertEquals(r.getAttributes().size(), 2); + assertEquals(r.getPattern(), "pattern"); + + r = new AttributesRule("pattern", "attribute1\t -attribute2\t "); + assertTrue(r.isNameOnly()); + assertFalse(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertEquals(r.getAttributes().size(), 2); + assertEquals(r.getPattern(), "pattern"); + + r = new AttributesRule("pattern", + "attribute1 -attribute2 attribute3=value "); + assertTrue(r.isNameOnly()); + assertFalse(r.dirOnly()); + assertNotNull(r.getAttributes()); + assertEquals(r.getAttributes().size(), 3); + assertEquals(r.getPattern(), "pattern"); + assertEquals(r.getAttributes().get(0).toString(), "attribute1"); + assertEquals(r.getAttributes().get(1).toString(), "-attribute2"); + assertEquals(r.getAttributes().get(2).toString(), "attribute3=value"); + } + + /** + * Check for a match. If target ends with "/", match will assume that the + * target is meant to be a directory. + * + * @param pattern + * Pattern as it would appear in a .gitattributes file + * @param target + * Target file path relative to repository's GIT_DIR + */ + public void assertMatched(String pattern, String target) { + boolean value = match(pattern, target); + assertTrue("Expected a match for: " + pattern + " with: " + target, + value); + } + + /** + * Check for a match. If target ends with "/", match will assume that the + * target is meant to be a directory. + * + * @param pattern + * Pattern as it would appear in a .gitattributes file + * @param target + * Target file path relative to repository's GIT_DIR + */ + public void assertNotMatched(String pattern, String target) { + boolean value = match(pattern, target); + assertFalse("Expected no match for: " + pattern + " with: " + target, + value); + } + + /** + * Check for a match. If target ends with "/", match will assume that the + * target is meant to be a directory. + * + * @param pattern + * Pattern as it would appear in a .gitattributes file + * @param target + * Target file path relative to repository's GIT_DIR + * @return Result of {@link AttributesRule#isMatch(String, boolean)} + */ + private static boolean match(String pattern, String target) { + AttributesRule r = new AttributesRule(pattern, ""); + //If speed of this test is ever an issue, we can use a presetRule field + //to avoid recompiling a pattern each time. + return r.isMatch(target, target.endsWith("/")); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java new file mode 100644 index 000000000..49279e6e5 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java @@ -0,0 +1,296 @@ +/* + * 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.attributes; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests attributes node behavior on the the index. + */ +public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase { + + private static final FileMode D = FileMode.TREE; + + private static final FileMode F = FileMode.REGULAR_FILE; + + private static Attribute EOL_LF = new Attribute("eol", "lf"); + + private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); + + private Git git; + + private TreeWalk walk; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + + } + + @Test + public void testRules() throws Exception { + writeAttributesFile(".git/info/attributes", "windows* eol=crlf"); + + writeAttributesFile(".gitattributes", "*.txt eol=lf"); + writeTrashFile("windows.file", ""); + writeTrashFile("windows.txt", ""); + writeTrashFile("readme.txt", ""); + + writeAttributesFile("src/config/.gitattributes", "*.txt -delta"); + writeTrashFile("src/config/readme.txt", ""); + writeTrashFile("src/config/windows.file", ""); + writeTrashFile("src/config/windows.txt", ""); + + // Adds file to index + git.add().addFilepattern(".").call(); + + walk = beginWalk(); + + assertIteration(F, ".gitattributes"); + assertIteration(F, "readme.txt", asList(EOL_LF)); + + assertIteration(D, "src"); + + assertIteration(D, "src/config"); + assertIteration(F, "src/config/.gitattributes"); + assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET)); + assertIteration(F, "src/config/windows.file", null); + assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET)); + + assertIteration(F, "windows.file", null); + assertIteration(F, "windows.txt", asList(EOL_LF)); + + endWalk(); + } + + /** + * Checks that if there is no .gitattributes file in the repository + * everything still work fine. + * + * @throws Exception + */ + @Test + public void testNoAttributes() throws Exception { + writeTrashFile("l0.txt", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + // Adds file to index + git.add().addFilepattern(".").call(); + walk = beginWalk(); + + assertIteration(F, "l0.txt"); + + assertIteration(D, "level1"); + assertIteration(F, "level1/l1.txt"); + + assertIteration(D, "level1/level2"); + assertIteration(F, "level1/level2/l2.txt"); + + endWalk(); + } + + /** + * Checks that empty .gitattribute files do not return incorrect value. + * + * @throws Exception + */ + @Test + public void testEmptyGitAttributeFile() throws Exception { + writeAttributesFile(".git/info/attributes", ""); + writeTrashFile("l0.txt", ""); + writeAttributesFile(".gitattributes", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + // Adds file to index + git.add().addFilepattern(".").call(); + walk = beginWalk(); + + assertIteration(F, ".gitattributes"); + assertIteration(F, "l0.txt"); + + assertIteration(D, "level1"); + assertIteration(F, "level1/l1.txt"); + + assertIteration(D, "level1/level2"); + assertIteration(F, "level1/level2/l2.txt"); + + endWalk(); + } + + @Test + public void testNoMatchingAttributes() throws Exception { + writeAttributesFile(".git/info/attributes", "*.java delta"); + writeAttributesFile(".gitattributes", "*.java -delta"); + writeAttributesFile("levelA/.gitattributes", "*.java eol=lf"); + writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf"); + + writeTrashFile("levelA/lA.txt", ""); + + // Adds file to index + git.add().addFilepattern(".").call(); + walk = beginWalk(); + + assertIteration(F, ".gitattributes"); + + assertIteration(D, "levelA"); + assertIteration(F, "levelA/.gitattributes"); + assertIteration(F, "levelA/lA.txt"); + + assertIteration(D, "levelB"); + assertIteration(F, "levelB/.gitattributes"); + + endWalk(); + } + + @Test + public void testIncorrectAttributeFileName() throws Exception { + writeAttributesFile("levelA/file.gitattributes", "*.txt -delta"); + writeAttributesFile("gitattributes", "*.txt eol=lf"); + + writeTrashFile("l0.txt", ""); + writeTrashFile("levelA/lA.txt", ""); + + // Adds file to index + git.add().addFilepattern(".").call(); + walk = beginWalk(); + + assertIteration(F, "gitattributes"); + + assertIteration(F, "l0.txt"); + + assertIteration(D, "levelA"); + assertIteration(F, "levelA/file.gitattributes"); + assertIteration(F, "levelA/lA.txt"); + + endWalk(); + } + + private void assertIteration(FileMode type, String pathName) + throws IOException { + assertIteration(type, pathName, Collections. emptyList()); + } + + private void assertIteration(FileMode type, String pathName, + List nodeAttrs) throws IOException { + assertTrue("walk has entry", walk.next()); + assertEquals(pathName, walk.getPathString()); + assertEquals(type, walk.getFileMode(0)); + DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class); + assertNotNull("has tree", itr); + + AttributesNode attributeNode = itr.getEntryAttributesNode(db + .newObjectReader()); + assertAttributeNode(pathName, attributeNode, nodeAttrs); + + if (D.equals(type)) + walk.enterSubtree(); + + } + + private void assertAttributeNode(String pathName, + AttributesNode attributeNode, List nodeAttrs) { + if (attributeNode == null) + assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); + else { + + Map entryAttributes = new LinkedHashMap(); + attributeNode.getAttributes(pathName, false, entryAttributes); + + if (nodeAttrs != null && !nodeAttrs.isEmpty()) { + for (Attribute attribute : nodeAttrs) { + assertThat(entryAttributes.values(), hasItem(attribute)); + } + } else { + assertTrue( + "The entry " + + pathName + + " should not have any attributes. Instead, the following attributes are applied to this file " + + entryAttributes.toString(), + entryAttributes.isEmpty()); + } + } + } + + private void writeAttributesFile(String name, String... rules) + throws IOException { + StringBuilder data = new StringBuilder(); + for (String line : rules) + data.append(line + "\n"); + writeTrashFile(name, data.toString()); + } + + private TreeWalk beginWalk() throws Exception { + TreeWalk newWalk = new TreeWalk(db); + newWalk.addTree(new DirCacheIterator(db.readDirCache())); + return newWalk; + } + + private void endWalk() throws IOException { + assertFalse("Not all files tested", walk.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java new file mode 100644 index 000000000..64b0535d6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2014, Obeo. + * 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.attributes; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.hasItem; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.treewalk.FileTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.junit.Test; + +/** + * Tests attributes node behavior on the local filesystem. + */ +public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { + + private static final FileMode D = FileMode.TREE; + + private static final FileMode F = FileMode.REGULAR_FILE; + + private static Attribute EOL_CRLF = new Attribute("eol", "crlf"); + + private static Attribute EOL_LF = new Attribute("eol", "lf"); + + private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); + + private static Attribute CUSTOM_VALUE = new Attribute("custom", "value"); + + private TreeWalk walk; + + @Test + public void testRules() throws Exception { + + File customAttributeFile = File.createTempFile("tmp_", + "customAttributeFile", null); + customAttributeFile.deleteOnExit(); + + JGitTestUtil.write(customAttributeFile, "*.txt custom=value"); + db.getConfig().setString("core", null, "attributesfile", + customAttributeFile.getAbsolutePath()); + writeAttributesFile(".git/info/attributes", "windows* eol=crlf"); + + writeAttributesFile(".gitattributes", "*.txt eol=lf"); + writeTrashFile("windows.file", ""); + writeTrashFile("windows.txt", ""); + writeTrashFile("global.txt", ""); + writeTrashFile("readme.txt", ""); + + writeAttributesFile("src/config/.gitattributes", "*.txt -delta"); + writeTrashFile("src/config/readme.txt", ""); + writeTrashFile("src/config/windows.file", ""); + writeTrashFile("src/config/windows.txt", ""); + + walk = beginWalk(); + + assertIteration(F, ".gitattributes"); + assertIteration(F, "global.txt", asList(EOL_LF), null, + asList(CUSTOM_VALUE)); + assertIteration(F, "readme.txt", asList(EOL_LF), null, + asList(CUSTOM_VALUE)); + + assertIteration(D, "src"); + + assertIteration(D, "src/config"); + assertIteration(F, "src/config/.gitattributes"); + assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null, + asList(CUSTOM_VALUE)); + assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF), + null); + assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET), + asList(EOL_CRLF), asList(CUSTOM_VALUE)); + + assertIteration(F, "windows.file", null, asList(EOL_CRLF), null); + assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF), + asList(CUSTOM_VALUE)); + + endWalk(); + } + + /** + * Checks that if there is no .gitattributes file in the repository + * everything still work fine. + * + * @throws Exception + */ + @Test + public void testNoAttributes() throws Exception { + writeTrashFile("l0.txt", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + walk = beginWalk(); + + assertIteration(F, "l0.txt"); + + assertIteration(D, "level1"); + assertIteration(F, "level1/l1.txt"); + + assertIteration(D, "level1/level2"); + assertIteration(F, "level1/level2/l2.txt"); + + endWalk(); + } + + /** + * Checks that empty .gitattribute files do not return incorrect value. + * + * @throws Exception + */ + @Test + public void testEmptyGitAttributeFile() throws Exception { + writeAttributesFile(".git/info/attributes", ""); + writeTrashFile("l0.txt", ""); + writeAttributesFile(".gitattributes", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + walk = beginWalk(); + + assertIteration(F, ".gitattributes"); + assertIteration(F, "l0.txt"); + + assertIteration(D, "level1"); + assertIteration(F, "level1/l1.txt"); + + assertIteration(D, "level1/level2"); + assertIteration(F, "level1/level2/l2.txt"); + + endWalk(); + } + + @Test + public void testNoMatchingAttributes() throws Exception { + writeAttributesFile(".git/info/attributes", "*.java delta"); + writeAttributesFile(".gitattributes", "*.java -delta"); + writeAttributesFile("levelA/.gitattributes", "*.java eol=lf"); + writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf"); + + writeTrashFile("levelA/lA.txt", ""); + + walk = beginWalk(); + + assertIteration(F, ".gitattributes"); + + assertIteration(D, "levelA"); + assertIteration(F, "levelA/.gitattributes"); + assertIteration(F, "levelA/lA.txt"); + + assertIteration(D, "levelB"); + assertIteration(F, "levelB/.gitattributes"); + + endWalk(); + } + + private void assertIteration(FileMode type, String pathName) + throws IOException { + assertIteration(type, pathName, Collections. emptyList(), + Collections. emptyList(), + Collections. emptyList()); + } + + private void assertIteration(FileMode type, String pathName, + List nodeAttrs, List infoAttrs, + List globalAttrs) + 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); + + AttributesNode attributeNode = itr.getEntryAttributesNode(); + assertAttributeNode(pathName, attributeNode, nodeAttrs); + AttributesNode infoAttributeNode = itr.getInfoAttributesNode(); + assertAttributeNode(pathName, infoAttributeNode, infoAttrs); + AttributesNode globalAttributeNode = itr.getGlobalAttributesNode(); + assertAttributeNode(pathName, globalAttributeNode, globalAttrs); + if (D.equals(type)) + walk.enterSubtree(); + + } + + private void assertAttributeNode(String pathName, + AttributesNode attributeNode, List nodeAttrs) { + if (attributeNode == null) + assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); + else { + + Map entryAttributes = new LinkedHashMap(); + attributeNode.getAttributes(pathName, false, entryAttributes); + + if (nodeAttrs != null && !nodeAttrs.isEmpty()) { + for (Attribute attribute : nodeAttrs) { + assertThat(entryAttributes.values(), hasItem(attribute)); + } + } else { + assertTrue( + "The entry " + + pathName + + " should not have any attributes. Instead, the following attributes are applied to this file " + + entryAttributes.toString(), + entryAttributes.isEmpty()); + } + } + } + + private void writeAttributesFile(String name, String... rules) + throws IOException { + StringBuilder data = new StringBuilder(); + for (String line : rules) + data.append(line + "\n"); + writeTrashFile(name, data.toString()); + } + + private TreeWalk beginWalk() throws CorruptObjectException { + TreeWalk newWalk = new TreeWalk(db); + newWalk.addTree(new FileTreeIterator(db)); + return newWalk; + } + + private void endWalk() throws IOException { + assertFalse("Not all files tested", walk.next()); + } +} diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index ae1136a36..eea13ec7b 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -16,10 +16,12 @@ Export-Package: org.eclipse.jgit.api;version="3.7.0"; org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.blame, + org.eclipse.jgit.submodule, org.eclipse.jgit.transport, org.eclipse.jgit.merge", org.eclipse.jgit.api.errors;version="3.7.0"; uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors", + org.eclipse.jgit.attributes;version="3.7.0", org.eclipse.jgit.blame;version="3.7.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, @@ -36,7 +38,8 @@ Export-Package: org.eclipse.jgit.api;version="3.7.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.util, - org.eclipse.jgit.events", + org.eclipse.jgit.events, + org.eclipse.jgit.attributes", org.eclipse.jgit.errors;version="3.7.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.internal.storage.pack, @@ -47,7 +50,8 @@ Export-Package: org.eclipse.jgit.api;version="3.7.0"; org.eclipse.jgit.fnmatch;version="3.7.0", org.eclipse.jgit.gitrepo;version="3.7.0"; uses:="org.eclipse.jgit.api, - org.eclipse.jgit.lib", + org.eclipse.jgit.lib, + org.eclipse.jgit.revwalk", org.eclipse.jgit.gitrepo.internal;version="3.7.0";x-internal:=true, org.eclipse.jgit.ignore;version="3.7.0", org.eclipse.jgit.ignore.internal;version="3.7.0";x-friends:="org.eclipse.jgit.test", @@ -69,13 +73,15 @@ Export-Package: org.eclipse.jgit.api;version="3.7.0"; org.eclipse.jgit.dircache, org.eclipse.jgit.internal.storage.file, org.eclipse.jgit.treewalk, - org.eclipse.jgit.transport", + org.eclipse.jgit.transport, + org.eclipse.jgit.submodule", org.eclipse.jgit.merge;version="3.7.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.diff, - org.eclipse.jgit.dircache", + org.eclipse.jgit.dircache, + org.eclipse.jgit.api", org.eclipse.jgit.nls;version="3.7.0", org.eclipse.jgit.notes;version="3.7.0"; uses:="org.eclipse.jgit.lib, @@ -85,7 +91,8 @@ Export-Package: org.eclipse.jgit.api;version="3.7.0"; org.eclipse.jgit.patch;version="3.7.0"; uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff", org.eclipse.jgit.revplot;version="3.7.0"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk", + uses:="org.eclipse.jgit.lib, + org.eclipse.jgit.revwalk", org.eclipse.jgit.revwalk;version="3.7.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.treewalk, @@ -99,7 +106,9 @@ Export-Package: org.eclipse.jgit.api;version="3.7.0"; org.eclipse.jgit.storage.pack;version="3.7.0"; uses:="org.eclipse.jgit.lib", org.eclipse.jgit.submodule;version="3.7.0"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk,org.eclipse.jgit.treewalk.filter", + uses:="org.eclipse.jgit.lib, + org.eclipse.jgit.treewalk.filter, + org.eclipse.jgit.treewalk", org.eclipse.jgit.transport;version="3.7.0"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.revwalk, @@ -115,17 +124,22 @@ Export-Package: org.eclipse.jgit.api;version="3.7.0"; org.eclipse.jgit.transport.http;version="3.7.0"; uses:="javax.net.ssl", org.eclipse.jgit.transport.resolver;version="3.7.0"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport", + uses:="org.eclipse.jgit.lib, + org.eclipse.jgit.transport", org.eclipse.jgit.treewalk;version="3.7.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, + org.eclipse.jgit.attributes, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, org.eclipse.jgit.dircache", org.eclipse.jgit.treewalk.filter;version="3.7.0"; uses:="org.eclipse.jgit.treewalk", org.eclipse.jgit.util;version="3.7.0"; - uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport.http,org.eclipse.jgit.storage.file", + uses:="org.eclipse.jgit.lib, + org.eclipse.jgit.transport.http, + org.eclipse.jgit.storage.file, + org.ietf.jgss", org.eclipse.jgit.util.io;version="3.7.0" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: J2SE-1.5 diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java new file mode 100644 index 000000000..d3ce68518 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2010, Marc Strapetz + * 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.attributes; + +/** + * Represents an attribute. + *

+ * According to the man page, an attribute can have the following states: + *

    + *
  • Set - represented by {@link State#SET}
  • + *
  • Unset - represented by {@link State#UNSET}
  • + *
  • Set to a value - represented by {@link State#CUSTOM}
  • + *
  • Unspecified - null is used instead of an instance of this + * class
  • + *
+ *

+ * + * @since 3.7 + */ +public final class Attribute { + + /** + * The attribute value state + */ + public static enum State { + /** the attribute is set */ + SET, + + /** the attribute is unset */ + UNSET, + + /** the attribute is set to a custom value */ + CUSTOM + } + + private final String key; + private final State state; + private final String value; + + /** + * Creates a new instance + * + * @param key + * the attribute key. Should not be null. + * @param state + * the attribute state. It should be either {@link State#SET} or + * {@link State#UNSET}. In order to create a custom value + * attribute prefer the use of {@link #Attribute(String, String)} + * constructor. + */ + public Attribute(String key, State state) { + this(key, state, null); + } + + private Attribute(String key, State state, String value) { + if (key == null) + throw new NullPointerException( + "The key of an attribute should not be null"); //$NON-NLS-1$ + if (state == null) + throw new NullPointerException( + "The state of an attribute should not be null"); //$NON-NLS-1$ + + this.key = key; + this.state = state; + this.value = value; + } + + /** + * Creates a new instance. + * + * @param key + * the attribute key. Should not be null. + * @param value + * the custom attribute value + */ + public Attribute(String key, String value) { + this(key, State.CUSTOM, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof Attribute)) + return false; + Attribute other = (Attribute) obj; + if (!key.equals(other.key)) + return false; + if (state != other.state) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + + /** + * @return the attribute key (never returns null) + */ + public String getKey() { + return key; + } + + /** + * Returns the state. + * + * @return the state (never returns null) + */ + public State getState() { + return state; + } + + /** + * @return the attribute value (may be null) + */ + public String getValue() { + return value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + key.hashCode(); + result = prime * result + state.hashCode(); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public String toString() { + switch (state) { + case SET: + return key; + case UNSET: + return "-" + key; //$NON-NLS-1$ + case CUSTOM: + default: + return key + "=" + value; //$NON-NLS-1$ + } + } +} \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java new file mode 100644 index 000000000..70f56ff96 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java @@ -0,0 +1,161 @@ +/* + * 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.attributes; + +import java.io.BufferedReader; +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 java.util.ListIterator; +import java.util.Map; + +import org.eclipse.jgit.lib.Constants; + +/** + * Represents a bundle of attributes inherited from a base directory. + * + * This class is not thread safe, it maintains state about the last match. + * + * @since 3.7 + */ +public class AttributesNode { + /** The rules that have been parsed into this node. */ + private final List rules; + + /** Create an empty ignore node with no rules. */ + public AttributesNode() { + rules = new ArrayList(); + } + + /** + * Create an ignore node with given rules. + * + * @param rules + * list of rules. + **/ + public AttributesNode(List rules) { + this.rules = rules; + } + + /** + * Parse files according to gitattribute standards. + * + * @param in + * input stream holding the standard ignore format. The caller is + * responsible for closing the stream. + * @throws IOException + * Error thrown when reading an ignore file. + */ + 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("#") /* Comments *///$NON-NLS-1$ + && !txt.startsWith("!") /* Negative pattern forbidden for attributes */) { //$NON-NLS-1$ + int patternEndSpace = txt.indexOf(' '); + int patternEndTab = txt.indexOf('\t'); + + final int patternEnd; + if (patternEndSpace == -1) + patternEnd = patternEndTab; + else if (patternEndTab == -1) + patternEnd = patternEndSpace; + else + patternEnd = Math.min(patternEndSpace, patternEndTab); + + if (patternEnd > -1) + rules.add(new AttributesRule(txt.substring(0, patternEnd), + txt.substring(patternEnd + 1).trim())); + } + } + } + + private static BufferedReader asReader(InputStream in) { + return new BufferedReader(new InputStreamReader(in, Constants.CHARSET)); + } + + /** @return list of all ignore rules held by this node. */ + public List getRules() { + return Collections.unmodifiableList(rules); + } + + /** + * Returns the matching attributes for an entry path. + * + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param attributes + * Map that will hold the attributes matching this entry path. If + * it is not empty, this method will NOT override any + * existing entry. + */ + public void getAttributes(String entryPath, boolean isDirectory, + Map attributes) { + // Parse rules in the reverse order that they were read since the last + // entry should be used + ListIterator ruleIterator = rules.listIterator(rules + .size()); + while (ruleIterator.hasPrevious()) { + AttributesRule rule = ruleIterator.previous(); + if (rule.isMatch(entryPath, isDirectory)) { + ListIterator attributeIte = rule.getAttributes() + .listIterator(rule.getAttributes().size()); + // Parses the attributes in the reverse order that they were + // read since the last entry should be used + while (attributeIte.hasPrevious()) { + Attribute attr = attributeIte.previous(); + if (!attributes.containsKey(attr.getKey())) + attributes.put(attr.getKey(), attr); + } + } + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java new file mode 100644 index 000000000..bcac14b5f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java @@ -0,0 +1,203 @@ +/* + * 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.attributes; + +import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.errors.InvalidPatternException; +import org.eclipse.jgit.ignore.FastIgnoreRule; +import org.eclipse.jgit.ignore.internal.IMatcher; +import org.eclipse.jgit.ignore.internal.PathMatcher; + +/** + * A single attributes rule corresponding to one line in a .gitattributes file. + * + * Inspiration from: {@link FastIgnoreRule} + * + * @since 3.7 + */ +public class AttributesRule { + + /** + * regular expression for splitting attributes - space, tab and \r (the C + * implementation oddly enough allows \r between attributes) + * */ + private static final String ATTRIBUTES_SPLIT_REGEX = "[ \t\r]"; //$NON-NLS-1$ + + private static List parseAttributes(String attributesLine) { + // the C implementation oddly enough allows \r between attributes too. + ArrayList result = new ArrayList(); + for (String attribute : attributesLine.split(ATTRIBUTES_SPLIT_REGEX)) { + attribute = attribute.trim(); + if (attribute.length() == 0) + continue; + + if (attribute.startsWith("-")) {//$NON-NLS-1$ + if (attribute.length() > 1) + result.add(new Attribute(attribute.substring(1), + State.UNSET)); + continue; + } + + final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$ + if (equalsIndex == -1) + result.add(new Attribute(attribute, State.SET)); + else { + String attributeKey = attribute.substring(0, equalsIndex); + if (attributeKey.length() > 0) { + String attributeValue = attribute + .substring(equalsIndex + 1); + result.add(new Attribute(attributeKey, attributeValue)); + } + } + } + return result; + } + + private final String pattern; + private final List attributes; + + private boolean nameOnly; + private boolean dirOnly; + + private IMatcher matcher; + + /** + * Create a new attribute rule with the given pattern. Assumes that the + * pattern is already trimmed. + * + * @param pattern + * Base pattern for the attributes rule. This pattern will be + * parsed to generate rule parameters. It can not be + * null. + * @param attributes + * the rule attributes. This string will be parsed to read the + * attributes. + */ + public AttributesRule(String pattern, String attributes) { + this.attributes = parseAttributes(attributes); + nameOnly = false; + dirOnly = false; + + if (pattern.endsWith("/")) { //$NON-NLS-1$ + pattern = pattern.substring(0, pattern.length() - 1); + dirOnly = true; + } + + boolean hasSlash = pattern.contains("/"); //$NON-NLS-1$ + + if (!hasSlash) + nameOnly = true; + else if (!pattern.startsWith("/")) { //$NON-NLS-1$ + // Contains "/" but does not start with one + // Adding / to the start should not interfere with matching + pattern = "/" + pattern; //$NON-NLS-1$ + } + + try { + matcher = PathMatcher.createPathMatcher(pattern, + Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly); + } catch (InvalidPatternException e) { + matcher = NO_MATCH; + } + + this.pattern = pattern; + } + + /** + * @return True if the pattern should match directories only + */ + public boolean dirOnly() { + return dirOnly; + } + + /** + * Returns the attributes. + * + * @return an unmodifiable list of attributes (never returns + * null) + */ + public List getAttributes() { + return Collections.unmodifiableList(attributes); + } + + /** + * @return true if the pattern is just a file name and not a + * path + */ + public boolean isNameOnly() { + return nameOnly; + } + + /** + * @return The blob pattern to be used as a matcher (never returns + * null) + */ + public String getPattern() { + return pattern; + } + + /** + * Returns true if a match was made. + * + * @param relativeTarget + * Name pattern of the file, relative to the base directory of + * this rule + * @param isDirectory + * Whether the target file is a directory or not + * @return True if a match was made. + */ + public boolean isMatch(String relativeTarget, boolean isDirectory) { + if (relativeTarget == null) + return false; + if (relativeTarget.length() == 0) + return false; + boolean match = matcher.matches(relativeTarget, isDirectory); + return match; + } +} \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/package-info.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/package-info.java new file mode 100644 index 000000000..5d133d828 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/package-info.java @@ -0,0 +1,4 @@ +/** + * Support for reading .gitattributes. + */ +package org.eclipse.jgit.attributes; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java index 706e05748..354a07439 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java @@ -45,13 +45,20 @@ package org.eclipse.jgit.dircache; import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; +import org.eclipse.jgit.util.RawParseUtils; /** * Iterate a {@link DirCache} as part of a TreeWalk. @@ -65,6 +72,10 @@ * @see org.eclipse.jgit.treewalk.TreeWalk */ public class DirCacheIterator extends AbstractTreeIterator { + /** Byte array holding ".gitattributes" string */ + private static final byte[] DOT_GIT_ATTRIBUTES_BYTES = Constants.DOT_GIT_ATTRIBUTES + .getBytes(); + /** The cache this iterator was created to walk. */ protected final DirCache cache; @@ -92,6 +103,9 @@ public class DirCacheIterator extends AbstractTreeIterator { /** The subtree containing {@link #currentEntry} if this is first entry. */ protected DirCacheTree currentSubtree; + /** Holds an {@link AttributesNode} for the current entry */ + private AttributesNode attributesNode; + /** * Create a new iterator for an already loaded DirCache instance. *

@@ -254,6 +268,10 @@ private void parseEntry(boolean forward) { path = cep; pathLen = cep.length; currentSubtree = null; + // Checks if this entry is a .gitattributes file + if (RawParseUtils.match(path, pathOffset, DOT_GIT_ATTRIBUTES_BYTES) == path.length) + attributesNode = new LazyLoadingAttributesNode( + currentEntry.getObjectId()); } /** @@ -265,4 +283,50 @@ private void parseEntry(boolean forward) { public DirCacheEntry getDirCacheEntry() { return currentSubtree == null ? currentEntry : null; } + + /** + * Retrieves the {@link AttributesNode} for the current entry. + * + * @param reader + * {@link ObjectReader} used to parse the .gitattributes entry. + * @return {@link AttributesNode} for the current entry. + * @throws IOException + * @since 3.7 + */ + public AttributesNode getEntryAttributesNode(ObjectReader reader) + throws IOException { + if (attributesNode instanceof LazyLoadingAttributesNode) + attributesNode = ((LazyLoadingAttributesNode) attributesNode) + .load(reader); + return attributesNode; + } + + /** + * {@link AttributesNode} implementation that provides lazy loading + * facilities. + */ + private static class LazyLoadingAttributesNode extends AttributesNode { + final ObjectId objectId; + + LazyLoadingAttributesNode(ObjectId objectId) { + super(Collections. emptyList()); + this.objectId = objectId; + + } + + AttributesNode load(ObjectReader reader) throws IOException { + AttributesNode r = new AttributesNode(); + ObjectLoader loader = reader.open(objectId); + if (loader != null) { + InputStream in = loader.openStream(); + try { + r.parse(in); + } finally { + in.close(); + } + } + return r.getRules().isEmpty() ? null : r; + } + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java index 02863bd16..2303ffd6d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java @@ -43,7 +43,7 @@ package org.eclipse.jgit.ignore; import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing; - +import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH; import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.ignore.internal.IMatcher; import org.eclipse.jgit.ignore.internal.PathMatcher; @@ -63,8 +63,6 @@ public class FastIgnoreRule { */ public static final char PATH_SEPARATOR = '/'; - private static final NoResultMatcher NO_MATCH = new NoResultMatcher(); - private final IMatcher matcher; private final boolean inverse; @@ -214,16 +212,4 @@ public boolean equals(Object obj) { return false; return matcher.equals(other.matcher); } - - static final class NoResultMatcher implements IMatcher { - - public boolean matches(String path, boolean assumeDirectory) { - return false; - } - - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { - return false; - } - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java index 10b5e49e1..8bb4dfb56 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java @@ -49,6 +49,20 @@ */ public interface IMatcher { + /** + * Matcher that does not match any pattern. + */ + public static final IMatcher NO_MATCH = new IMatcher() { + public boolean matches(String path, boolean assumeDirectory) { + return false; + } + + public boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory) { + return false; + } + }; + /** * Matches entire given string * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index ccbfed720..8a2080bac 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -113,6 +113,13 @@ public class ConfigConstants { /** The "excludesfile" key */ public static final String CONFIG_KEY_EXCLUDESFILE = "excludesfile"; + /** + * The "attributesfile" key + * + * @since 3.7 + */ + public static final String CONFIG_KEY_ATTRIBUTESFILE = "attributesfile"; + /** The "filemode" key */ public static final String CONFIG_KEY_FILEMODE = "filemode"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index f14974984..705d54cfa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -356,6 +356,13 @@ public final class Constants { /** A bare repository typically ends with this string */ public static final String DOT_GIT_EXT = ".git"; + /** + * Name of the attributes file + * + * @since 3.7 + */ + public static final String DOT_GIT_ATTRIBUTES = ".gitattributes"; + /** Name of the ignore file */ public static final String DOT_GIT_IGNORE = ".gitignore"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java index 8f31d96de..5a7634a6f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2013, Gunnar Wagenknecht * Copyright (C) 2010, Chris Aniszczyk * Copyright (C) 2009, Christian Halstrick * Copyright (C) 2009, Google Inc. @@ -101,6 +102,8 @@ public static enum CheckStat { private final String excludesfile; + private final String attributesfile; + /** * Options for symlink handling * @@ -136,6 +139,8 @@ private CoreConfig(final Config rc) { ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); excludesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_EXCLUDESFILE); + attributesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, + null, ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE); } /** @@ -165,4 +170,12 @@ public boolean isLogAllRefUpdates() { public String getExcludesFile() { return excludesfile; } + + /** + * @return path of attributesfile + * @since 3.7 + */ + public String getAttributesFile() { + return attributesfile; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 6311da6b6..3838149a4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -63,6 +63,8 @@ import java.util.Comparator; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -133,6 +135,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { /** If there is a .gitignore file present, the parsed rules from it. */ private IgnoreNode ignoreNode; + /** If there is a .gitattributes file present, the parsed rules from it. */ + private AttributesNode attributesNode; + /** Repository that is the root level being iterated over */ protected Repository repository; @@ -142,6 +147,19 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { /** The offset of the content id in {@link #idBuffer()} */ private int contentIdOffset; + /** + * Holds the {@link AttributesNode} that is stored in + * $GIT_DIR/info/attributes file. + */ + private AttributesNode infoAttributeNode; + + /** + * Holds the {@link AttributesNode} that is stored in global attribute file. + * + * @see CoreConfig#getAttributesFile() + */ + private AttributesNode globalAttributeNode; + /** * Create a new iterator with no parent. * @@ -185,6 +203,8 @@ protected WorkingTreeIterator(final String prefix, protected WorkingTreeIterator(final WorkingTreeIterator p) { super(p); state = p.state; + infoAttributeNode = p.infoAttributeNode; + globalAttributeNode = p.globalAttributeNode; } /** @@ -204,6 +224,10 @@ protected void initRootIterator(Repository repo) { else entry = null; ignoreNode = new RootIgnoreNode(entry, repo); + + infoAttributeNode = new InfoAttributesNode(repo); + + globalAttributeNode = new GlobalAttributesNode(repo); } /** @@ -626,6 +650,56 @@ private IgnoreNode getIgnoreNode() throws IOException { return ignoreNode; } + /** + * Retrieves the {@link AttributesNode} for the current entry. + * + * @return {@link AttributesNode} for the current entry. + * @throws IOException + * if an error is raised while parsing the .gitattributes file + * @since 3.7 + */ + public AttributesNode getEntryAttributesNode() throws IOException { + if (attributesNode instanceof PerDirectoryAttributesNode) + attributesNode = ((PerDirectoryAttributesNode) attributesNode) + .load(); + return attributesNode; + } + + /** + * Retrieves the {@link AttributesNode} that holds the information located + * in $GIT_DIR/info/attributes file. + * + * @return the {@link AttributesNode} that holds the information located in + * $GIT_DIR/info/attributes file. + * @throws IOException + * if an error is raised while parsing the attributes file + * @since 3.7 + */ + public AttributesNode getInfoAttributesNode() throws IOException { + if (infoAttributeNode instanceof InfoAttributesNode) + infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load(); + return infoAttributeNode; + } + + /** + * Retrieves the {@link AttributesNode} that holds the information located + * in system-wide file. + * + * @return the {@link AttributesNode} that holds the information located in + * system-wide file. + * @throws IOException + * IOException if an error is raised while parsing the + * attributes file + * @see CoreConfig#getAttributesFile() + * @since 3.7 + */ + public AttributesNode getGlobalAttributesNode() throws IOException { + if (globalAttributeNode instanceof GlobalAttributesNode) + globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode) + .load(); + return globalAttributeNode; + } + private static final Comparator ENTRY_CMP = new Comparator() { public int compare(final Entry o1, final Entry o2) { final byte[] a = o1.encodedName; @@ -679,6 +753,8 @@ protected void init(final Entry[] list) { continue; if (Constants.DOT_GIT_IGNORE.equals(name)) ignoreNode = new PerDirectoryIgnoreNode(e); + if (Constants.DOT_GIT_ATTRIBUTES.equals(name)) + attributesNode = new PerDirectoryAttributesNode(e); if (i != o) entries[o] = e; e.encodeName(nameEncoder); @@ -1223,6 +1299,90 @@ private static void loadRulesFromFile(IgnoreNode r, File exclude) } } + /** Magic type indicating we know rules exist, but they aren't loaded. */ + private static class PerDirectoryAttributesNode extends AttributesNode { + final Entry entry; + + PerDirectoryAttributesNode(Entry entry) { + super(Collections. emptyList()); + this.entry = entry; + } + + AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + InputStream in = entry.openInputStream(); + try { + r.parse(in); + } finally { + in.close(); + } + return r.getRules().isEmpty() ? null : r; + } + } + + /** + * Attributes node loaded from global system-wide file. + */ + private static class GlobalAttributesNode extends AttributesNode { + final Repository repository; + + GlobalAttributesNode(Repository repository) { + this.repository = repository; + } + + AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + String path = repository.getConfig().get(CoreConfig.KEY) + .getAttributesFile(); + if (path != null) { + File attributesFile; + if (path.startsWith("~/")) //$NON-NLS-1$ + attributesFile = fs.resolve(fs.userHome(), + path.substring(2)); + else + attributesFile = fs.resolve(null, path); + loadRulesFromFile(r, attributesFile); + } + return r.getRules().isEmpty() ? null : r; + } + } + + /** Magic type indicating there may be rules for the top level. */ + private static class InfoAttributesNode extends AttributesNode { + final Repository repository; + + InfoAttributesNode(Repository repository) { + this.repository = repository; + } + + AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + + File attributes = fs.resolve(repository.getDirectory(), + "info/attributes"); //$NON-NLS-1$ + loadRulesFromFile(r, attributes); + + return r.getRules().isEmpty() ? null : r; + } + + } + + private static void loadRulesFromFile(AttributesNode r, File attrs) + throws FileNotFoundException, IOException { + if (attrs.exists()) { + FileInputStream in = new FileInputStream(attrs); + try { + r.parse(in); + } finally { + in.close(); + } + } + } + private static final class IteratorState { /** Options used to process the working tree. */ final WorkingTreeOptions options;