From 975aa8868591ea49e3d8033a0fe224c09e67aec9 Mon Sep 17 00:00:00 2001 From: Ivan Motsch Date: Tue, 17 Nov 2015 16:04:01 +0100 Subject: [PATCH] Add Attribute Macro Expansion 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 --- .../attributes/AttributesHandlerTest.java | 339 ++++++++++++++ .../attributes/AttributesMatcherTest.java | 18 +- .../AttributesNodeDirCacheIteratorTest.java | 9 +- .../jgit/attributes/AttributesNodeTest.java | 10 +- ...AttributesNodeWorkingTreeIteratorTest.java | 8 +- org.eclipse.jgit/.settings/.api_filters | 19 + .../jgit/attributes/AttributesHandler.java | 434 ++++++++++++++++++ .../jgit/attributes/AttributesNode.java | 37 -- .../jgit/attributes/AttributesRule.java | 30 +- .../jgit/treewalk/AbstractTreeIterator.java | 11 +- .../org/eclipse/jgit/treewalk/TreeWalk.java | 322 ++++--------- 11 files changed, 948 insertions(+), 289 deletions(-) create mode 100644 org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java create mode 100644 org.eclipse.jgit/.settings/.api_filters create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.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 index 000000000..ca456b3c8 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java @@ -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 attrs(String s) { + return new AttributesRule("*", s).getAttributes(); + } + + private void assertIteration(FileMode type, String pathName) + throws IOException { + assertIteration(type, pathName, Collections. emptyList()); + } + + private void assertIteration(FileMode type, String pathName, + Collection 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()); + } +} 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 index 9f82b8a1e..e8dd95232 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java @@ -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"); 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 index 0e595e61f..7421e907d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java @@ -251,14 +251,17 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase { } private void assertAttributesNode(String pathName, - AttributesNode attributesNode, List nodeAttrs) { + AttributesNode attributesNode, List 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.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java index d478a7cf0..ec2370e67 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java @@ -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); } 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 index 4215ba23e..b159cca0d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java @@ -219,14 +219,16 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { } private void assertAttributesNode(String pathName, - AttributesNode attributesNode, List nodeAttrs) { + AttributesNode attributesNode, List 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 index 000000000..a5000dd6b --- /dev/null +++ b/org.eclipse.jgit/.settings/.api_filters @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + 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 index 000000000..19e4afdf9 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * 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} + *

+ * 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 binary rule that is present in any git folder + * [attr]binary -diff -merge -text + */ + private static final List 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> 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 rules = node.getRules(); + // Parse rules in the reverse order that they were read since the last + // entry should be used + ListIterator ruleIterator = rules + .listIterator(rules.size()); + while (ruleIterator.hasPrevious()) { + AttributesRule rule = ruleIterator.previous(); + if (rule.isMatch(entryPath, isDirectory)) { + ListIterator attributeIte = rule.getAttributes() + .listIterator(rule.getAttributes().size()); + // Parses the attributes in the reverse order that they were + // read since the last entry should be used + while (attributeIte.hasPrevious()) { + 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 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. + *

+ * This method implements the fallback mechanism between the index and the + * working tree depending on the operation type + *

+ * + * @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 parentOf(@Nullable T node) { + if(node==null) return null; + @SuppressWarnings("unchecked") + Class type = (Class) node.getClass(); + AbstractTreeIterator parent = node.parent; + if (type.isInstance(parent)) { + return type.cast(parent); + } + return null; + } + + private static 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 type = (Class) node.getClass(); + if (type.isInstance(t)) { + return type.cast(t); + } + return null; + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java index 5c0aba2e0..719650211 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java @@ -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 ruleIterator = rules.listIterator(rules - .size()); - while (ruleIterator.hasPrevious()) { - AttributesRule rule = ruleIterator.previous(); - if (rule.isMatch(entryPath, isDirectory)) { - ListIterator attributeIte = rule.getAttributes() - .listIterator(rule.getAttributes().size()); - // Parses the attributes in the reverse order that they were - // read since the last entry should be used - while (attributeIte.hasPrevious()) { - Attribute attr = attributeIte.previous(); - if (!attributes.containsKey(attr.getKey())) - attributes.put(attr); - } - } - } - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java index 35d18c4b2..0532250f9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java @@ -109,10 +109,11 @@ public class AttributesRule { private final String pattern; private final List 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; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java index 58136355e..dc835e4f3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -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. + *

+ * 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java index 5cd713da7..4775e9617 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -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. *

@@ -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. + * + *

Git attribute computation

+ * + *
    + *
  • Get the attributes matching the current path entry from the info file + * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).
  • + *
  • 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.
  • + *
  • In the end, completes the list of matching attributes using the + * global attribute file define in the configuration (see + * {@link AttributesNodeProvider#getGlobalAttributesNode()})
  • + * + *
+ * + * + *

Iterator constraints

+ * + *

+ * 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}. + *

+ * + * @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. *

@@ -1109,156 +1202,13 @@ public class TreeWalk implements AutoCloseable, AttributesProvider { } /** - * Retrieve the git attributes for the current entry. - * - *

Git attribute computation

- * - *
    - *
  • Get the attributes matching the current path entry from the info file - * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).
  • - *
  • 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.
  • - *
  • In the end, completes the list of matching attributes using the - * global attribute file define in the configuration (see - * {@link AttributesNodeProvider#getGlobalAttributesNode()})
  • - * - *
- * - * - *

Iterator constraints

- * - *

- * 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}. - *

- * - * @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 getParent(T current, + public T getTree( Class type) { - if (current != null) { - AbstractTreeIterator parent = current.parent; - if (type.isInstance(parent)) { - return type.cast(parent); - } - } - return null; - } - - private T getTree(Class 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. - *

- * This method implements the fallback mechanism between the index and the - * working tree depending on the operation type - *

- * - * @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