summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.test/META-INF/MANIFEST.MF1
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java182
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java77
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java412
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java296
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java282
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java184
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java161
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java203
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/package-info.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java64
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java16
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java13
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java160
18 files changed, 2090 insertions, 23 deletions
diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF
index 6ca3df17a3..af40e883cd 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 0000000000..ea250369a0
--- /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.<Attribute> emptySet());
+ assertAttribute("file.type2", node, Collections.<Attribute> 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.<Attribute> 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.<Attribute> emptySet());
+ assertAttribute("file.type2", node, Collections.<Attribute> 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<Attribute> attrs) {
+ HashMap<String, Attribute> attributes = new HashMap<String, Attribute>();
+ node.getAttributes(path, false, attributes);
+ assertEquals(attrs, new HashSet<Attribute>(attributes.values()));
+ }
+
+ static Set<Attribute> asSet(Attribute... attrs) {
+ Set<Attribute> result = new HashSet<Attribute>();
+ 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 0000000000..93b954fa9a
--- /dev/null
+++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010, Marc Strapetz <marc.strapetz@syntevo.com>
+ * 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 0000000000..6865406927
--- /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
+ * <p>
+ * Inspired by {@link org.eclipse.jgit.ignore.IgnoreMatcherTest}
+ * </p>
+ */
+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 0000000000..49279e6e5a
--- /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.<Attribute> emptyList());
+ }
+
+ private void assertIteration(FileMode type, String pathName,
+ List<Attribute> 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<Attribute> nodeAttrs) {
+ if (attributeNode == null)
+ assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
+ else {
+
+ Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
+ 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 0000000000..64b0535d6a
--- /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.<Attribute> emptyList(),
+ Collections.<Attribute> emptyList(),
+ Collections.<Attribute> emptyList());
+ }
+
+ private void assertIteration(FileMode type, String pathName,
+ List<Attribute> nodeAttrs, List<Attribute> infoAttrs,
+ List<Attribute> 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<Attribute> nodeAttrs) {
+ if (attributeNode == null)
+ assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
+ else {
+
+ Map<String, Attribute> entryAttributes = new LinkedHashMap<String, Attribute>();
+ 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 ae1136a361..eea13ec7be 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 0000000000..d3ce685187
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2010, Marc Strapetz <marc.strapetz@syntevo.com>
+ * 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.
+ * <p>
+ * According to the man page, an attribute can have the following states:
+ * <ul>
+ * <li>Set - represented by {@link State#SET}</li>
+ * <li>Unset - represented by {@link State#UNSET}</li>
+ * <li>Set to a value - represented by {@link State#CUSTOM}</li>
+ * <li>Unspecified - <code>null</code> is used instead of an instance of this
+ * class</li>
+ * </ul>
+ * </p>
+ *
+ * @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 <code>null</code>.
+ * @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 <code>null</code>.
+ * @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 <code>null</code>)
+ */
+ public String getKey() {
+ return key;
+ }
+
+ /**
+ * Returns the state.
+ *
+ * @return the state (never returns <code>null</code>)
+ */
+ public State getState() {
+ return state;
+ }
+
+ /**
+ * @return the attribute value (may be <code>null</code>)
+ */
+ 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 0000000000..70f56ff964
--- /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<AttributesRule> rules;
+
+ /** Create an empty ignore node with no rules. */
+ public AttributesNode() {
+ rules = new ArrayList<AttributesRule>();
+ }
+
+ /**
+ * Create an ignore node with given rules.
+ *
+ * @param rules
+ * list of rules.
+ **/
+ public AttributesNode(List<AttributesRule> 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<AttributesRule> 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<String, Attribute> attributes) {
+ // Parse rules in the reverse order that they were read since the last
+ // entry should be used
+ ListIterator<AttributesRule> ruleIterator = rules.listIterator(rules
+ .size());
+ while (ruleIterator.hasPrevious()) {
+ AttributesRule rule = ruleIterator.previous();
+ if (rule.isMatch(entryPath, isDirectory)) {
+ ListIterator<Attribute> 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 0000000000..bcac14b5ff
--- /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<Attribute> parseAttributes(String attributesLine) {
+ // the C implementation oddly enough allows \r between attributes too.
+ ArrayList<Attribute> result = new ArrayList<Attribute>();
+ 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<Attribute> 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
+ * <code>null</code>.
+ * @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
+ * <code>null</code>)
+ */
+ public List<Attribute> getAttributes() {
+ return Collections.unmodifiableList(attributes);
+ }
+
+ /**
+ * @return <code>true</code> 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
+ * <code>null</code>)
+ */
+ public String getPattern() {
+ return pattern;
+ }
+
+ /**
+ * Returns <code>true</code> 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 0000000000..5d133d828a
--- /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 706e057480..354a07439a 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 <code>TreeWalk</code>.
@@ -65,6 +72,10 @@ import org.eclipse.jgit.treewalk.EmptyTreeIterator;
* @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.
* <p>
@@ -254,6 +268,10 @@ public class DirCacheIterator extends AbstractTreeIterator {
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 @@ public class DirCacheIterator extends AbstractTreeIterator {
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.<AttributesRule> 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 02863bd16a..2303ffd6d6 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 class FastIgnoreRule {
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 10b5e49e1f..8bb4dfb564 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
@@ -50,6 +50,20 @@ package org.eclipse.jgit.ignore.internal;
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
*
* @param path
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 ccbfed720a..8a2080bac8 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 f149749843..705d54cfa3 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 8f31d96de6..5a7634a6f1 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 <caniszczyk@gmail.com>
* Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com>
* Copyright (C) 2009, Google Inc.
@@ -101,6 +102,8 @@ public class CoreConfig {
private final String excludesfile;
+ private final String attributesfile;
+
/**
* Options for symlink handling
*
@@ -136,6 +139,8 @@ public class CoreConfig {
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 class CoreConfig {
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 6311da6b68..3838149a4f 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.Collections;
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;
@@ -143,6 +148,19 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
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.
*
* @param options
@@ -185,6 +203,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
protected WorkingTreeIterator(final WorkingTreeIterator p) {
super(p);
state = p.state;
+ infoAttributeNode = p.infoAttributeNode;
+ globalAttributeNode = p.globalAttributeNode;
}
/**
@@ -204,6 +224,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
else
entry = null;
ignoreNode = new RootIgnoreNode(entry, repo);
+
+ infoAttributeNode = new InfoAttributesNode(repo);
+
+ globalAttributeNode = new GlobalAttributesNode(repo);
}
/**
@@ -626,6 +650,56 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
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> ENTRY_CMP = new Comparator<Entry>() {
public int compare(final Entry o1, final Entry o2) {
final byte[] a = o1.encodedName;
@@ -679,6 +753,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
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 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
}
}
+ /** 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.<AttributesRule> 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;