aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java339
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java18
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java9
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java10
-rw-r--r--org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java8
-rw-r--r--org.eclipse.jgit/.settings/.api_filters19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java434
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java322
11 files changed, 948 insertions, 289 deletions
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 0000000000..ca456b3c8a
--- /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<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());
+ }
+}
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 9f82b8a1e9..e8dd952324 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 0e595e61f8..7421e907dc 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<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.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
index d478a7cf08..ec2370e67f 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 4215ba23e5..b159cca0d5 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<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
index 0000000000..a5000dd6bd
--- /dev/null
+++ b/org.eclipse.jgit/.settings/.api_filters
@@ -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
index 0000000000..19e4afdf92
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
@@ -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;
+ }
+
+}
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 5c0aba2e0e..7196502112 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<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);
- }
- }
- }
- }
}
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 35d18c4b2a..0532250f9c 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<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;
}
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 58136355eb..dc835e4f36 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.
+ * <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;
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 5cd713da78..4775e96175 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;
/**
@@ -310,6 +311,14 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
/**
+ * @return the {@link OperationType}
+ * @since 4.3
+ */
+ public OperationType getOperationType() {
+ return operationType;
+ }
+
+ /**
* Release any resources used by this walker's reader.
* <p>
* A walker that has been released can be used again, but may need to be
@@ -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;
@@ -740,6 +823,16 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
/**
+ * 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>
* Using this method to compare ObjectId values between trees of this walker
@@ -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)) {
@@ -1269,76 +1219,6 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
}
/**
- * 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
*