]> source.dussan.org Git - jgit.git/commitdiff
Add Attribute Macro Expansion 17/60617/11
authorIvan Motsch <ivan.motsch@bsiag.com>
Tue, 17 Nov 2015 15:04:01 +0000 (16:04 +0100)
committerChristian Halstrick <christian.halstrick@sap.com>
Thu, 11 Feb 2016 16:08:49 +0000 (17:08 +0100)
Attributes MacroExpander implements macros used in git attributes. This
is implemented inside the TreeWalk using a lazy created MacroExpander.
In addition, the macro expander caches the global and info attributes
node in order to provide fast merge of attributes.

Change-Id: I2e69c9fc84e9d7fb8df0a05817d688fc456d8f00
Signed-off-by: Ivan Motsch <ivan.motsch@bsiag.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java [new file with mode: 0644]
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
org.eclipse.jgit/.settings/.api_filters [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java

diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
new file mode 100644 (file)
index 0000000..ca456b3
--- /dev/null
@@ -0,0 +1,339 @@
+/*
+ * 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.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Test;
+
+/**
+ * Tests {@link AttributesHandler}
+ */
+public class AttributesHandlerTest extends RepositoryTestCase {
+       private static final FileMode D = FileMode.TREE;
+
+       private static final FileMode F = FileMode.REGULAR_FILE;
+
+       private TreeWalk walk;
+
+       @Test
+       public void testExpandNonMacro1() throws Exception {
+               setupRepo(null, null, null, "*.txt text");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("text"));
+               endWalk();
+       }
+
+       @Test
+       public void testExpandNonMacro2() throws Exception {
+               setupRepo(null, null, null, "*.txt -text");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("-text"));
+               endWalk();
+       }
+
+       @Test
+       public void testExpandNonMacro3() throws Exception {
+               setupRepo(null, null, null, "*.txt !text");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs(""));
+               endWalk();
+       }
+
+       @Test
+       public void testExpandNonMacro4() throws Exception {
+               setupRepo(null, null, null, "*.txt text=auto");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("text=auto"));
+               endWalk();
+       }
+
+       @Test
+       public void testExpandBuiltInMacro1() throws Exception {
+               setupRepo(null, null, null, "*.txt binary");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("binary -diff -merge -text"));
+               endWalk();
+       }
+
+       @Test
+       public void testExpandBuiltInMacro2() throws Exception {
+               setupRepo(null, null, null, "*.txt -binary");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("-binary diff merge text"));
+               endWalk();
+       }
+
+       @Test
+       public void testExpandBuiltInMacro3() throws Exception {
+               setupRepo(null, null, null, "*.txt !binary");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs(""));
+               endWalk();
+       }
+
+       @Test
+       public void testCustomGlobalMacro1() throws Exception {
+               setupRepo(
+                               "[attr]foo a -b !c d=e", null, null, "*.txt foo");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("foo a -b d=e"));
+               endWalk();
+       }
+
+       @Test
+       public void testCustomGlobalMacro2() throws Exception {
+               setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt -foo");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("-foo -a b d=e"));
+               endWalk();
+       }
+
+       @Test
+       public void testCustomGlobalMacro3() throws Exception {
+               setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt !foo");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs(""));
+               endWalk();
+       }
+
+       @Test
+       public void testCustomGlobalMacro4() throws Exception {
+               setupRepo("[attr]foo a -b !c d=e", null, null, "*.txt foo=bar");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("foo=bar a -b d=bar"));
+               endWalk();
+       }
+
+       @Test
+       public void testInfoOverridesGlobal() throws Exception {
+               setupRepo("[attr]foo bar1",
+                               "[attr]foo bar2", null, "*.txt foo");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("foo bar2"));
+               endWalk();
+       }
+
+       @Test
+       public void testWorkDirRootOverridesGlobal() throws Exception {
+               setupRepo("[attr]foo bar1",
+                               null,
+                               "[attr]foo bar3", "*.txt foo");
+
+               walk = beginWalk();
+               assertIteration(F, ".gitattributes");
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("foo bar3"));
+               endWalk();
+       }
+
+       @Test
+       public void testInfoOverridesWorkDirRoot() throws Exception {
+               setupRepo("[attr]foo bar1",
+                               "[attr]foo bar2", "[attr]foo bar3", "*.txt foo");
+
+               walk = beginWalk();
+               assertIteration(F, ".gitattributes");
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("foo bar2"));
+               endWalk();
+       }
+
+       @Test
+       public void testRecursiveMacro() throws Exception {
+               setupRepo(
+                               "[attr]foo x bar -foo",
+                               null, null, "*.txt foo");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("foo x bar"));
+               endWalk();
+       }
+
+       @Test
+       public void testCyclicMacros() throws Exception {
+               setupRepo(
+                               "[attr]foo x -bar\n[attr]bar y -foo", null, null, "*.txt foo");
+
+               walk = beginWalk();
+               assertIteration(D, "sub");
+               assertIteration(F, "sub/.gitattributes");
+               assertIteration(F, "sub/a.txt", attrs("foo x -bar -y"));
+               endWalk();
+       }
+
+       private static Collection<Attribute> attrs(String s) {
+               return new AttributesRule("*", s).getAttributes();
+       }
+
+       private void assertIteration(FileMode type, String pathName)
+                       throws IOException {
+               assertIteration(type, pathName, Collections.<Attribute> emptyList());
+       }
+
+       private void assertIteration(FileMode type, String pathName,
+                       Collection<Attribute> expectedAttrs) throws IOException {
+               assertTrue("walk has entry", walk.next());
+               assertEquals(pathName, walk.getPathString());
+               assertEquals(type, walk.getFileMode(0));
+
+               if (expectedAttrs != null) {
+                       assertEquals(new ArrayList<>(expectedAttrs),
+                                       new ArrayList<>(walk.getAttributes().getAll()));
+               }
+
+               if (D.equals(type))
+                       walk.enterSubtree();
+       }
+
+       /**
+        * @param globalAttributesContent
+        * @param infoAttributesContent
+        * @param rootAttributesContent
+        * @param subDirAttributesContent
+        * @throws Exception
+        *             Setup a repo with .gitattributes files and a test file
+        *             sub/a.txt
+        */
+       private void setupRepo(
+                       String globalAttributesContent,
+                       String infoAttributesContent, String rootAttributesContent, String subDirAttributesContent)
+                                       throws Exception {
+               FileBasedConfig config = db.getConfig();
+               if (globalAttributesContent != null) {
+                       File f = new File(db.getDirectory(), "global/attributes");
+                       write(f, globalAttributesContent);
+                       config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+                                       ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE,
+                                       f.getAbsolutePath());
+
+               }
+               if (infoAttributesContent != null) {
+                       File f = new File(db.getDirectory(), Constants.INFO_ATTRIBUTES);
+                       write(f, infoAttributesContent);
+               }
+               config.save();
+
+               if (rootAttributesContent != null) {
+                       writeAttributesFile(Constants.DOT_GIT_ATTRIBUTES,
+                                       rootAttributesContent);
+               }
+
+               if (subDirAttributesContent != null) {
+                       writeAttributesFile("sub/" + Constants.DOT_GIT_ATTRIBUTES,
+                                       subDirAttributesContent);
+               }
+
+               writeTrashFile("sub/a.txt", "a");
+       }
+
+       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() {
+               TreeWalk newWalk = new TreeWalk(db);
+               newWalk.addTree(new FileTreeIterator(db));
+               return newWalk;
+       }
+
+       private void endWalk() throws IOException {
+               assertFalse("Not all files tested", walk.next());
+       }
+}
index 9f82b8a1e90090dc53a0fa1b4c66d65dd759b6dc..e8dd952324446089fc9cb9246694fecb062090ed 100644 (file)
@@ -293,28 +293,28 @@ public class AttributesMatcherTest {
        public void testGetters() {
                AttributesRule r = new AttributesRule("/pattern/", "");
                assertFalse(r.isNameOnly());
-               assertTrue(r.dirOnly());
+               assertTrue(r.isDirOnly());
                assertNotNull(r.getAttributes());
                assertTrue(r.getAttributes().isEmpty());
                assertEquals(r.getPattern(), "/pattern");
 
                r = new AttributesRule("/patter?/", "");
                assertFalse(r.isNameOnly());
-               assertTrue(r.dirOnly());
+               assertTrue(r.isDirOnly());
                assertNotNull(r.getAttributes());
                assertTrue(r.getAttributes().isEmpty());
                assertEquals(r.getPattern(), "/patter?");
 
                r = new AttributesRule("patt*", "");
                assertTrue(r.isNameOnly());
-               assertFalse(r.dirOnly());
+               assertFalse(r.isDirOnly());
                assertNotNull(r.getAttributes());
                assertTrue(r.getAttributes().isEmpty());
                assertEquals(r.getPattern(), "patt*");
 
                r = new AttributesRule("pattern", "attribute1");
                assertTrue(r.isNameOnly());
-               assertFalse(r.dirOnly());
+               assertFalse(r.isDirOnly());
                assertNotNull(r.getAttributes());
                assertFalse(r.getAttributes().isEmpty());
                assertEquals(r.getAttributes().size(), 1);
@@ -322,28 +322,28 @@ public class AttributesMatcherTest {
 
                r = new AttributesRule("pattern", "attribute1 -attribute2");
                assertTrue(r.isNameOnly());
-               assertFalse(r.dirOnly());
+               assertFalse(r.isDirOnly());
                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());
+               assertFalse(r.isDirOnly());
                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());
+               assertFalse(r.isDirOnly());
                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());
+               assertFalse(r.isDirOnly());
                assertNotNull(r.getAttributes());
                assertEquals(r.getAttributes().size(), 2);
                assertEquals(r.getPattern(), "pattern");
@@ -351,7 +351,7 @@ public class AttributesMatcherTest {
                r = new AttributesRule("pattern",
                                "attribute1 -attribute2  attribute3=value ");
                assertTrue(r.isNameOnly());
-               assertFalse(r.dirOnly());
+               assertFalse(r.isDirOnly());
                assertNotNull(r.getAttributes());
                assertEquals(r.getAttributes().size(), 3);
                assertEquals(r.getPattern(), "pattern");
index 0e595e61f85551c36e578e2b1653014fdc75dae8..7421e907dcbbe30add65259c89d458568dd87e3d 100644 (file)
@@ -251,14 +251,17 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase {
        }
 
        private void assertAttributesNode(String pathName,
-                       AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+                       AttributesNode attributesNode, List<Attribute> nodeAttrs)
+                                       throws IOException {
                if (attributesNode == null)
                        assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
                else {
 
                        Attributes entryAttributes = new Attributes();
-                       attributesNode.getAttributes(pathName,
-                                       false, entryAttributes);
+                       new AttributesHandler(walk).mergeAttributes(attributesNode,
+                                       pathName,
+                                       false,
+                                       entryAttributes);
 
                        if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
                                for (Attribute attribute : nodeAttrs) {
index d478a7cf087d4296cc44247336abecf31337b442..ec2370e67fa7301ee119a069a62122a29aff736a 100644 (file)
@@ -50,6 +50,9 @@ import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 
+import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
+import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
+import org.eclipse.jgit.treewalk.TreeWalk;
 import org.junit.After;
 import org.junit.Test;
 
@@ -57,6 +60,8 @@ import org.junit.Test;
  * Test {@link AttributesNode}
  */
 public class AttributesNodeTest {
+       private static final TreeWalk DUMMY_WALK = new TreeWalk(
+                       new InMemoryRepository(new DfsRepositoryDescription("FooBar")));
 
        private static final Attribute A_SET_ATTR = new Attribute("A", SET);
 
@@ -162,9 +167,10 @@ public class AttributesNodeTest {
        }
 
        private void assertAttribute(String path, AttributesNode node,
-                       Attributes attrs) {
+                       Attributes attrs) throws IOException {
                Attributes attributes = new Attributes();
-               node.getAttributes(path, false, attributes);
+               new AttributesHandler(DUMMY_WALK).mergeAttributes(node, path, false,
+                               attributes);
                assertEquals(attrs, attributes);
        }
 
index 4215ba23e58f6c2b70454d5d38b3403687e7a291..b159cca0d5f7002531ccd53998997f8e29b07f77 100644 (file)
@@ -219,14 +219,16 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase {
        }
 
        private void assertAttributesNode(String pathName,
-                       AttributesNode attributesNode, List<Attribute> nodeAttrs) {
+                       AttributesNode attributesNode, List<Attribute> nodeAttrs)
+                                       throws IOException {
                if (attributesNode == null)
                        assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
                else {
 
                        Attributes entryAttributes = new Attributes();
-                       attributesNode.getAttributes(pathName,
-                                       false, entryAttributes);
+                       new AttributesHandler(walk).mergeAttributes(attributesNode,
+                                       pathName, false,
+                                       entryAttributes);
 
                        if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
                                for (Attribute attribute : nodeAttrs) {
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters
new file mode 100644 (file)
index 0000000..a5000dd
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit" version="2">
+    <resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode">
+        <filter comment="moved to new AttributesManager" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.attributes.AttributesNode"/>
+                <message_argument value="getAttributes(String, boolean, Attributes)"/>
+            </message_arguments>
+        </filter>
+    </resource>
+    <resource path="src/org/eclipse/jgit/attributes/AttributesRule.java" type="org.eclipse.jgit.attributes.AttributesRule">
+        <filter comment="used only in tests: bean naming" id="338792546">
+            <message_arguments>
+                <message_argument value="org.eclipse.jgit.attributes.AttributesRule"/>
+                <message_argument value="dirOnly()"/>
+            </message_arguments>
+        </filter>
+    </resource>
+</component>
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
new file mode 100644 (file)
index 0000000..19e4afd
--- /dev/null
@@ -0,0 +1,434 @@
+/*
+ * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
+ *
+ * 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.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Map;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.attributes.Attribute.State;
+import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.treewalk.AbstractTreeIterator;
+import org.eclipse.jgit.treewalk.CanonicalTreeParser;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.WorkingTreeIterator;
+import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
+
+/**
+ * The attributes handler knows how to retrieve, parse and merge attributes from
+ * the various gitattributes files. Furthermore it collects and expands macro
+ * expressions. The method {@link #getAttributes()} yields the ready processed
+ * attributes for the current path represented by the {@link TreeWalk}
+ * <p>
+ * The implementation is based on the specifications in
+ * http://git-scm.com/docs/gitattributes
+ *
+ * @since 4.3
+ */
+public class AttributesHandler {
+       private static final String MACRO_PREFIX = "[attr]"; //$NON-NLS-1$
+
+       private static final String BINARY_RULE_KEY = "binary"; //$NON-NLS-1$
+
+       /**
+        * This is the default <b>binary</b> rule that is present in any git folder
+        * <code>[attr]binary -diff -merge -text</code>
+        */
+       private static final List<Attribute> BINARY_RULE_ATTRIBUTES = new AttributesRule(
+                       MACRO_PREFIX + BINARY_RULE_KEY, "-diff -merge -text") //$NON-NLS-1$
+                                       .getAttributes();
+
+       private final TreeWalk treeWalk;
+
+       private final AttributesNode globalNode;
+
+       private final AttributesNode infoNode;
+
+       private final Map<String, List<Attribute>> expansions = new HashMap<>();
+
+       /**
+        * Create an {@link AttributesHandler} with default rules as well as merged
+        * rules from global, info and worktree root attributes
+        *
+        * @param treeWalk
+        * @throws IOException
+        */
+       public AttributesHandler(TreeWalk treeWalk) throws IOException {
+               this.treeWalk = treeWalk;
+               AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider();
+               this.globalNode = attributesNodeProvider != null
+                               ? attributesNodeProvider.getGlobalAttributesNode() : null;
+               this.infoNode = attributesNodeProvider != null
+                               ? attributesNodeProvider.getInfoAttributesNode() : null;
+
+               AttributesNode rootNode = attributesNode(treeWalk,
+                               rootOf(
+                                               treeWalk.getTree(WorkingTreeIterator.class)),
+                               rootOf(
+                                               treeWalk.getTree(DirCacheIterator.class)),
+                               rootOf(treeWalk
+                                               .getTree(CanonicalTreeParser.class)));
+
+               expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
+               for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
+                               infoNode }) {
+                       if (node == null) {
+                               continue;
+                       }
+                       for (AttributesRule rule : node.getRules()) {
+                               if (rule.getPattern().startsWith(MACRO_PREFIX)) {
+                                       expansions.put(rule.getPattern()
+                                                       .substring(MACRO_PREFIX.length()).trim(),
+                                                       rule.getAttributes());
+                               }
+                       }
+               }
+       }
+
+       /**
+        * see {@link TreeWalk#getAttributes()}
+        *
+        * @return the {@link Attributes} for the current path represented by the
+        *         {@link TreeWalk}
+        * @throws IOException
+        */
+       public Attributes getAttributes() throws IOException {
+               String entryPath = treeWalk.getPathString();
+               boolean isDirectory = (treeWalk.getFileMode() == FileMode.TREE);
+               Attributes attributes = new Attributes();
+
+               // Gets the info attributes
+               mergeInfoAttributes(entryPath, isDirectory, attributes);
+
+               // Gets the attributes located on the current entry path
+               mergePerDirectoryEntryAttributes(entryPath, isDirectory,
+                               treeWalk.getTree(WorkingTreeIterator.class),
+                               treeWalk.getTree(DirCacheIterator.class),
+                               treeWalk.getTree(CanonicalTreeParser.class),
+                               attributes);
+
+               // Gets the attributes located in the global attribute file
+               mergeGlobalAttributes(entryPath, isDirectory, attributes);
+
+               // now after all attributes are collected - in the correct hierarchy
+               // order - remove all unspecified entries (the ! marker)
+               for (Attribute a : attributes.getAll()) {
+                       if (a.getState() == State.UNSPECIFIED)
+                               attributes.remove(a.getKey());
+               }
+
+               return attributes;
+       }
+
+       /**
+        * Merges the matching GLOBAL 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 result
+        *            that will hold the attributes matching this entry path. This
+        *            method will NOT override any existing entry in attributes.
+        */
+       private void mergeGlobalAttributes(String entryPath, boolean isDirectory,
+                       Attributes result) {
+               mergeAttributes(globalNode, entryPath, isDirectory, result);
+       }
+
+       /**
+        * Merges the matching INFO 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 result
+        *            that will hold the attributes matching this entry path. This
+        *            method will NOT override any existing entry in attributes.
+        */
+       private void mergeInfoAttributes(String entryPath, boolean isDirectory,
+                       Attributes result) {
+               mergeAttributes(infoNode, entryPath, isDirectory, result);
+       }
+
+       /**
+        * Merges the matching working directory 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 workingTreeIterator
+        * @param dirCacheIterator
+        * @param otherTree
+        * @param result
+        *            that will hold the attributes matching this entry path. This
+        *            method will NOT override any existing entry in attributes.
+        * @throws IOException
+        */
+       private void mergePerDirectoryEntryAttributes(String entryPath,
+                       boolean isDirectory,
+                       @Nullable WorkingTreeIterator workingTreeIterator,
+                       @Nullable DirCacheIterator dirCacheIterator,
+                       @Nullable CanonicalTreeParser otherTree, Attributes result)
+                                       throws IOException {
+               // Prevents infinite recurrence
+               if (workingTreeIterator != null || dirCacheIterator != null
+                               || otherTree != null) {
+                       AttributesNode attributesNode = attributesNode(
+                                       treeWalk, workingTreeIterator, dirCacheIterator, otherTree);
+                       if (attributesNode != null) {
+                               mergeAttributes(attributesNode, entryPath, isDirectory, result);
+                       }
+                       mergePerDirectoryEntryAttributes(entryPath, isDirectory,
+                                       parentOf(workingTreeIterator), parentOf(dirCacheIterator),
+                                       parentOf(otherTree), result);
+               }
+       }
+
+       /**
+        * Merges the matching node attributes for an entry path.
+        *
+        * @param node
+        *            the node to scan for matches to entryPath
+        * @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 result
+        *            that will hold the attributes matching this entry path. This
+        *            method will NOT override any existing entry in attributes.
+        */
+       protected void mergeAttributes(@Nullable AttributesNode node,
+                       String entryPath,
+                       boolean isDirectory, Attributes result) {
+               if (node == null)
+                       return;
+               List<AttributesRule> rules = node.getRules();
+               // 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()) {
+                                       expandMacro(attributeIte.previous(), result);
+                               }
+                       }
+               }
+       }
+
+       /**
+        * @param attr
+        * @param result
+        *            contains the (recursive) expanded and merged macro attributes
+        *            including the attribute iself
+        */
+       protected void expandMacro(Attribute attr, Attributes result) {
+               // loop detection = exists check
+               if (result.containsKey(attr.getKey()))
+                       return;
+
+               // also add macro to result set, same does native git
+               result.put(attr);
+
+               List<Attribute> expansion = expansions.get(attr.getKey());
+               if (expansion == null) {
+                       return;
+               }
+               switch (attr.getState()) {
+               case UNSET: {
+                       for (Attribute e : expansion) {
+                               switch (e.getState()) {
+                               case SET:
+                                       expandMacro(new Attribute(e.getKey(), State.UNSET), result);
+                                       break;
+                               case UNSET:
+                                       expandMacro(new Attribute(e.getKey(), State.SET), result);
+                                       break;
+                               case UNSPECIFIED:
+                                       expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED),
+                                                       result);
+                                       break;
+                               case CUSTOM:
+                               default:
+                                       expandMacro(e, result);
+                               }
+                       }
+                       break;
+               }
+               case CUSTOM: {
+                       for (Attribute e : expansion) {
+                               switch (e.getState()) {
+                               case SET:
+                               case UNSET:
+                               case UNSPECIFIED:
+                                       expandMacro(e, result);
+                                       break;
+                               case CUSTOM:
+                               default:
+                                       expandMacro(new Attribute(e.getKey(), attr.getValue()),
+                                                       result);
+                               }
+                       }
+                       break;
+               }
+               case UNSPECIFIED: {
+                       for (Attribute e : expansion) {
+                               expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED),
+                                               result);
+                       }
+                       break;
+               }
+               case SET:
+               default:
+                       for (Attribute e : expansion) {
+                               expandMacro(e, result);
+                       }
+                       break;
+               }
+       }
+
+       /**
+        * Get the {@link AttributesNode} for the current entry.
+        * <p>
+        * This method implements the fallback mechanism between the index and the
+        * working tree depending on the operation type
+        * </p>
+        *
+        * @param treeWalk
+        * @param workingTreeIterator
+        * @param dirCacheIterator
+        * @param otherTree
+        * @return a {@link AttributesNode} of the current entry,
+        *         {@link NullPointerException} otherwise.
+        * @throws IOException
+        *             It raises an {@link IOException} if a problem appears while
+        *             parsing one on the attributes file.
+        */
+       private static AttributesNode attributesNode(TreeWalk treeWalk,
+                       @Nullable WorkingTreeIterator workingTreeIterator,
+                       @Nullable DirCacheIterator dirCacheIterator,
+                       @Nullable CanonicalTreeParser otherTree) throws IOException {
+               AttributesNode attributesNode = null;
+               switch (treeWalk.getOperationType()) {
+               case CHECKIN_OP:
+                       if (workingTreeIterator != null) {
+                               attributesNode = workingTreeIterator.getEntryAttributesNode();
+                       }
+                       if (attributesNode == null && dirCacheIterator != null) {
+                               attributesNode = dirCacheIterator
+                                               .getEntryAttributesNode(treeWalk.getObjectReader());
+                       }
+                       if (attributesNode == null && otherTree != null) {
+                               attributesNode = otherTree
+                                               .getEntryAttributesNode(treeWalk.getObjectReader());
+                       }
+                       break;
+               case CHECKOUT_OP:
+                       if (otherTree != null) {
+                               attributesNode = otherTree
+                                               .getEntryAttributesNode(treeWalk.getObjectReader());
+                       }
+                       if (attributesNode == null && dirCacheIterator != null) {
+                               attributesNode = dirCacheIterator
+                                               .getEntryAttributesNode(treeWalk.getObjectReader());
+                       }
+                       if (attributesNode == null && workingTreeIterator != null) {
+                               attributesNode = workingTreeIterator.getEntryAttributesNode();
+                       }
+                       break;
+               default:
+                       throw new IllegalStateException(
+                                       "The only supported operation types are:" //$NON-NLS-1$
+                                                       + OperationType.CHECKIN_OP + "," //$NON-NLS-1$
+                                                       + OperationType.CHECKOUT_OP);
+               }
+
+               return attributesNode;
+       }
+
+       private static <T extends AbstractTreeIterator> T parentOf(@Nullable T node) {
+               if(node==null) return null;
+               @SuppressWarnings("unchecked")
+               Class<T> type = (Class<T>) node.getClass();
+               AbstractTreeIterator parent = node.parent;
+               if (type.isInstance(parent)) {
+                       return type.cast(parent);
+               }
+               return null;
+       }
+
+       private static <T extends AbstractTreeIterator> T rootOf(
+                       @Nullable T node) {
+               if(node==null) return null;
+               AbstractTreeIterator t=node;
+               while (t!= null && t.parent != null) {
+                       t= t.parent;
+               }
+               @SuppressWarnings("unchecked")
+               Class<T> type = (Class<T>) node.getClass();
+               if (type.isInstance(t)) {
+                       return type.cast(t);
+               }
+               return null;
+       }
+
+}
index 5c0aba2e0e91b8948d125aadd040cc946f06ee68..71965021121e450b3f3321bb345db28658fcb9e0 100644 (file)
@@ -49,7 +49,6 @@ import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.ListIterator;
 
 import org.eclipse.jgit.lib.Constants;
 
@@ -122,40 +121,4 @@ public class AttributesNode {
                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.
-        * @since 4.2
-        */
-       public void getAttributes(String entryPath,
-                       boolean isDirectory, Attributes 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);
-                               }
-                       }
-               }
-       }
 }
index 35d18c4b2a9bcb672d6f438c4b1bbb2955d9c8de..0532250f9ceb65027d097beebae079bc272e08f5 100644 (file)
@@ -109,10 +109,11 @@ public class AttributesRule {
        private final String pattern;
        private final List<Attribute> attributes;
 
-       private boolean nameOnly;
-       private boolean dirOnly;
+       private final boolean nameOnly;
 
-       private IMatcher matcher;
+       private final boolean dirOnly;
+
+       private final IMatcher matcher;
 
        /**
         * Create a new attribute rule with the given pattern. Assumes that the
@@ -128,38 +129,43 @@ public class AttributesRule {
         */
        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;
+               } else {
+                       dirOnly = false;
                }
 
-               boolean hasSlash = pattern.contains("/"); //$NON-NLS-1$
+               int slashIndex = pattern.indexOf('/');
 
-               if (!hasSlash)
+               if (slashIndex < 0) {
                        nameOnly = true;
-               else if (!pattern.startsWith("/")) { //$NON-NLS-1$
+               } else if (slashIndex == 0) {
+                       nameOnly = false;
+               } else {
+                       nameOnly = false;
                        // Contains "/" but does not start with one
                        // Adding / to the start should not interfere with matching
                        pattern = "/" + pattern; //$NON-NLS-1$
                }
 
+               IMatcher candidateMatcher = NO_MATCH;
                try {
-                       matcher = PathMatcher.createPathMatcher(pattern,
+                       candidateMatcher = PathMatcher.createPathMatcher(pattern,
                                        Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly);
                } catch (InvalidPatternException e) {
-                       matcher = NO_MATCH;
+                       // ignore: invalid patterns are silently ignored
                }
-
+               this.matcher = candidateMatcher;
                this.pattern = pattern;
        }
 
        /**
         * @return True if the pattern should match directories only
+        * @since 4.3
         */
-       public boolean dirOnly() {
+       public boolean isDirOnly() {
                return dirOnly;
        }
 
index 58136355eb532e5fc5c7bfc2c857bb823e82c8a7..dc835e4f36cc6fa56f79febef7d7506d5dd4978c 100644 (file)
@@ -50,6 +50,7 @@ import java.nio.ByteBuffer;
 import java.nio.CharBuffer;
 
 import org.eclipse.jgit.attributes.AttributesNode;
+import org.eclipse.jgit.attributes.AttributesHandler;
 import org.eclipse.jgit.dircache.DirCacheCheckout;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -88,8 +89,14 @@ public abstract class AbstractTreeIterator {
        /** A dummy object id buffer that matches the zero ObjectId. */
        protected static final byte[] zeroid = new byte[Constants.OBJECT_ID_LENGTH];
 
-       /** Iterator for the parent tree; null if we are the root iterator. */
-       final AbstractTreeIterator parent;
+       /**
+        * Iterator for the parent tree; null if we are the root iterator.
+        * <p>
+        * Used by {@link TreeWalk} and {@link AttributesHandler}
+        *
+        * @since 4.3
+        */
+       public final AbstractTreeIterator parent;
 
        /** The iterator this current entry is path equal to. */
        AbstractTreeIterator matches;
index 5cd713da787f480dca3dc40f3a91db434c68112f..4775e9617576a58039d20aa85d22c36dd0bc306d 100644 (file)
@@ -49,15 +49,13 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Set;
 
-import org.eclipse.jgit.annotations.Nullable;
 import org.eclipse.jgit.api.errors.JGitInternalException;
 import org.eclipse.jgit.attributes.Attribute;
-import org.eclipse.jgit.attributes.Attribute.State;
 import org.eclipse.jgit.attributes.Attributes;
-import org.eclipse.jgit.attributes.AttributesNode;
 import org.eclipse.jgit.attributes.AttributesNodeProvider;
 import org.eclipse.jgit.attributes.AttributesProvider;
 import org.eclipse.jgit.dircache.DirCacheBuildIterator;
+import org.eclipse.jgit.attributes.AttributesHandler;
 import org.eclipse.jgit.dircache.DirCacheIterator;
 import org.eclipse.jgit.errors.CorruptObjectException;
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -270,6 +268,9 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
        /** Cached attribute for the current entry */
        private Attributes attrs = null;
 
+       /** Cached attributes handler */
+       private AttributesHandler attributesHandler;
+
        private Config config;
 
        /**
@@ -309,6 +310,14 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                return reader;
        }
 
+       /**
+        * @return the {@link OperationType}
+        * @since 4.3
+        */
+       public OperationType getOperationType() {
+               return operationType;
+       }
+
        /**
         * Release any resources used by this walker's reader.
         * <p>
@@ -435,9 +444,83 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                attributesNodeProvider = provider;
        }
 
+       /**
+        * @return the {@link AttributesNodeProvider} for this {@link TreeWalk}.
+        * @since 4.3
+        */
+       public AttributesNodeProvider getAttributesNodeProvider() {
+               return attributesNodeProvider;
+       }
+
+       /**
+        * Retrieve the git attributes for the current entry.
+        *
+        * <h4>Git attribute computation</h4>
+        *
+        * <ul>
+        * <li>Get the attributes matching the current path entry from the info file
+        * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li>
+        * <li>Completes the list of attributes using the .gitattributes files
+        * located on the current path (the further the directory that contains
+        * .gitattributes is from the path in question, the lower its precedence).
+        * For a checkin operation, it will look first on the working tree (if any).
+        * If there is no attributes file, it will fallback on the index. For a
+        * checkout operation, it will first use the index entry and then fallback
+        * on the working tree if none.</li>
+        * <li>In the end, completes the list of matching attributes using the
+        * global attribute file define in the configuration (see
+        * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li>
+        *
+        * </ul>
+        *
+        *
+        * <h4>Iterator constraints</h4>
+        *
+        * <p>
+        * In order to have a correct list of attributes for the current entry, this
+        * {@link TreeWalk} requires to have at least one
+        * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An
+        * {@link AttributesNodeProvider} is used to retrieve the attributes from
+        * the info attributes file and the global attributes file. The
+        * {@link DirCacheIterator} is used to retrieve the .gitattributes files
+        * stored in the index. A {@link WorkingTreeIterator} can also be provided
+        * to access the local version of the .gitattributes files. If none is
+        * provided it will fallback on the {@link DirCacheIterator}.
+        * </p>
+        *
+        * @return a {@link Set} of {@link Attribute}s that match the current entry.
+        * @since 4.2
+        */
+       public Attributes getAttributes() {
+               if (attrs != null)
+                       return attrs;
+
+               if (attributesNodeProvider == null) {
+                       // The work tree should have a AttributesNodeProvider to be able to
+                       // retrieve the info and global attributes node
+                       throw new IllegalStateException(
+                                       "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
+               }
+
+               try {
+                       // Lazy create the attributesHandler on the first access of
+                       // attributes. This requires the info, global and root
+                       // attributes nodes
+                       if (attributesHandler == null) {
+                               attributesHandler = new AttributesHandler(this);
+                       }
+                       attrs = attributesHandler.getAttributes();
+                       return attrs;
+               } catch (IOException e) {
+                       throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$
+                                       e);
+               }
+       }
+
        /** Reset this walker so new tree iterators can be added to it. */
        public void reset() {
                attrs = null;
+               attributesHandler = null;
                trees = NO_TREES;
                advance = false;
                depth = 0;
@@ -739,6 +822,16 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                return FileMode.fromBits(getRawMode(nth));
        }
 
+       /**
+        * Obtain the {@link FileMode} for the current entry on the currentHead tree
+        *
+        * @return mode for the current entry of the currentHead tree.
+        * @since 4.3
+        */
+       public FileMode getFileMode() {
+               return FileMode.fromBits(currentHead.mode);
+       }
+
        /**
         * Obtain the ObjectId for the current entry.
         * <p>
@@ -1109,156 +1202,13 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
        }
 
        /**
-        * Retrieve the git attributes for the current entry.
-        *
-        * <h4>Git attribute computation</h4>
-        *
-        * <ul>
-        * <li>Get the attributes matching the current path entry from the info file
-        * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li>
-        * <li>Completes the list of attributes using the .gitattributes files
-        * located on the current path (the further the directory that contains
-        * .gitattributes is from the path in question, the lower its precedence).
-        * For a checkin operation, it will look first on the working tree (if any).
-        * If there is no attributes file, it will fallback on the index. For a
-        * checkout operation, it will first use the index entry and then fallback
-        * on the working tree if none.</li>
-        * <li>In the end, completes the list of matching attributes using the
-        * global attribute file define in the configuration (see
-        * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li>
-        *
-        * </ul>
-        *
-        *
-        * <h4>Iterator constraints</h4>
-        *
-        * <p>
-        * In order to have a correct list of attributes for the current entry, this
-        * {@link TreeWalk} requires to have at least one
-        * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An
-        * {@link AttributesNodeProvider} is used to retrieve the attributes from
-        * the info attributes file and the global attributes file. The
-        * {@link DirCacheIterator} is used to retrieve the .gitattributes files
-        * stored in the index. A {@link WorkingTreeIterator} can also be provided
-        * to access the local version of the .gitattributes files. If none is
-        * provided it will fallback on the {@link DirCacheIterator}.
-        * </p>
-        *
-        * @return a {@link Set} of {@link Attribute}s that match the current entry.
-        * @since 4.2
-        */
-       public Attributes getAttributes() {
-               if (attrs != null)
-                       return attrs;
-
-               if (attributesNodeProvider == null) {
-                       // The work tree should have a AttributesNodeProvider to be able to
-                       // retrieve the info and global attributes node
-                       throw new IllegalStateException(
-                                       "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
-               }
-
-               WorkingTreeIterator workingTreeIterator = getTree(WorkingTreeIterator.class);
-               DirCacheIterator dirCacheIterator = getTree(DirCacheIterator.class);
-               CanonicalTreeParser other = getTree(CanonicalTreeParser.class);
-
-               if (workingTreeIterator == null && dirCacheIterator == null
-                               && other == null) {
-                       // Can not retrieve the attributes without at least one of the above
-                       // iterators.
-                       return new Attributes();
-               }
-
-               String path = currentHead.getEntryPathString();
-               final boolean isDir = FileMode.TREE.equals(currentHead.mode);
-               Attributes attributes = new Attributes();
-               try {
-                       // Gets the global attributes node
-                       AttributesNode globalNodeAttr = attributesNodeProvider
-                                       .getGlobalAttributesNode();
-                       // Gets the info attributes node
-                       AttributesNode infoNodeAttr = attributesNodeProvider
-                                       .getInfoAttributesNode();
-
-                       // Gets the info attributes
-                       if (infoNodeAttr != null) {
-                               infoNodeAttr.getAttributes(path, isDir, attributes);
-                       }
-
-                       // Gets the attributes located on the current entry path
-                       getPerDirectoryEntryAttributes(path, isDir, operationType,
-                                       workingTreeIterator, dirCacheIterator, other, attributes);
-
-                       // Gets the attributes located in the global attribute file
-                       if (globalNodeAttr != null) {
-                               globalNodeAttr.getAttributes(path, isDir, attributes);
-                       }
-               } catch (IOException e) {
-                       throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$
-               }
-               // now after all attributes are collected - in the correct hierarchy
-               // order - remove all unspecified entries (the ! marker)
-               for (Attribute a : attributes.getAll()) {
-                       if (a.getState() == State.UNSPECIFIED)
-                               attributes.remove(a.getKey());
-               }
-               return attributes;
-       }
-
-       /**
-        * Get the attributes located on the current entry path.
-        *
-        * @param path
-        *            current entry path
-        * @param isDir
-        *            holds true if the current entry is a directory
-        * @param opType
-        *            type of operation
-        * @param workingTreeIterator
-        *            a {@link WorkingTreeIterator} matching the current entry
-        * @param dirCacheIterator
-        *            a {@link DirCacheIterator} matching the current entry
-        * @param other
-        *            a {@link CanonicalTreeParser} matching the current entry
-        * @param attributes
-        *            Non null map holding the existing attributes. This map will be
-        *            augmented with new entry. None entry will be overrided.
-        * @throws IOException
-        *             It raises an {@link IOException} if a problem appears while
-        *             parsing one on the attributes file.
+        * @param type
+        *            of the tree to be queried
+        * @return the tree of that type or null if none is present
+        * @since 4.3
         */
-       private void getPerDirectoryEntryAttributes(String path, boolean isDir,
-                       OperationType opType, WorkingTreeIterator workingTreeIterator,
-                       DirCacheIterator dirCacheIterator, CanonicalTreeParser other,
-                       Attributes attributes)
-                       throws IOException {
-               // Prevents infinite recurrence
-               if (workingTreeIterator != null || dirCacheIterator != null
-                               || other != null) {
-                       AttributesNode currentAttributesNode = getCurrentAttributesNode(
-                                       opType, workingTreeIterator, dirCacheIterator, other);
-                       if (currentAttributesNode != null) {
-                               currentAttributesNode.getAttributes(path, isDir, attributes);
-                       }
-                       getPerDirectoryEntryAttributes(path, isDir, opType,
-                                       getParent(workingTreeIterator, WorkingTreeIterator.class),
-                                       getParent(dirCacheIterator, DirCacheIterator.class),
-                                       getParent(other, CanonicalTreeParser.class), attributes);
-               }
-       }
-
-       private static <T extends AbstractTreeIterator> T getParent(T current,
+       public <T extends AbstractTreeIterator> T getTree(
                        Class<T> type) {
-               if (current != null) {
-                       AbstractTreeIterator parent = current.parent;
-                       if (type.isInstance(parent)) {
-                               return type.cast(parent);
-                       }
-               }
-               return null;
-       }
-
-       private <T extends AbstractTreeIterator> T getTree(Class<T> type) {
                for (int i = 0; i < trees.length; i++) {
                        AbstractTreeIterator tree = trees[i];
                        if (type.isInstance(tree)) {
@@ -1268,76 +1218,6 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                return null;
        }
 
-       /**
-        * Get the {@link AttributesNode} for the current entry.
-        * <p>
-        * This method implements the fallback mechanism between the index and the
-        * working tree depending on the operation type
-        * </p>
-        *
-        * @param opType
-        * @param workingTreeIterator
-        * @param dirCacheIterator
-        * @param other
-        * @return a {@link AttributesNode} of the current entry,
-        *         {@link NullPointerException} otherwise.
-        * @throws IOException
-        *             It raises an {@link IOException} if a problem appears while
-        *             parsing one on the attributes file.
-        */
-       private AttributesNode getCurrentAttributesNode(OperationType opType,
-                       @Nullable WorkingTreeIterator workingTreeIterator,
-                       @Nullable DirCacheIterator dirCacheIterator,
-                       @Nullable CanonicalTreeParser other)
-                                       throws IOException {
-               AttributesNode attributesNode = null;
-               switch (opType) {
-               case CHECKIN_OP:
-                       if (workingTreeIterator != null) {
-                               attributesNode = workingTreeIterator.getEntryAttributesNode();
-                       }
-                       if (attributesNode == null && dirCacheIterator != null) {
-                               attributesNode = getAttributesNode(dirCacheIterator
-                                               .getEntryAttributesNode(getObjectReader()),
-                                               attributesNode);
-                       }
-                       if (attributesNode == null && other != null) {
-                               attributesNode = getAttributesNode(
-                                               other.getEntryAttributesNode(getObjectReader()),
-                                               attributesNode);
-                       }
-                       break;
-               case CHECKOUT_OP:
-                       if (other != null) {
-                               attributesNode = other
-                                               .getEntryAttributesNode(getObjectReader());
-                       }
-                       if (dirCacheIterator != null) {
-                               attributesNode = getAttributesNode(dirCacheIterator
-                                               .getEntryAttributesNode(getObjectReader()),
-                                               attributesNode);
-                       }
-                       if (attributesNode == null && workingTreeIterator != null) {
-                               attributesNode = getAttributesNode(
-                                               workingTreeIterator.getEntryAttributesNode(),
-                                               attributesNode);
-                       }
-                       break;
-               default:
-                       throw new IllegalStateException(
-                                       "The only supported operation types are:" //$NON-NLS-1$
-                                                       + OperationType.CHECKIN_OP + "," //$NON-NLS-1$
-                                                       + OperationType.CHECKOUT_OP);
-               }
-
-               return attributesNode;
-       }
-
-       private static AttributesNode getAttributesNode(AttributesNode value,
-                       AttributesNode defaultValue) {
-               return (value == null) ? defaultValue : value;
-       }
-
        /**
         * Inspect config and attributes to return a filtercommand applicable for
         * the current path