Browse Source

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 <ivan.motsch@bsiag.com>
stable-4.3
Ivan Motsch 9 years ago committed by Christian Halstrick
parent
commit
975aa88685
  1. 339
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesHandlerTest.java
  2. 18
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java
  3. 9
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java
  4. 10
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java
  5. 8
      org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java
  6. 19
      org.eclipse.jgit/.settings/.api_filters
  7. 434
      org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java
  8. 37
      org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java
  9. 30
      org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java
  10. 11
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java
  11. 322
      org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java

339
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());
}
}

18
org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesMatcherTest.java

@ -293,28 +293,28 @@ public class AttributesMatcherTest {
public void testGetters() { public void testGetters() {
AttributesRule r = new AttributesRule("/pattern/", ""); AttributesRule r = new AttributesRule("/pattern/", "");
assertFalse(r.isNameOnly()); assertFalse(r.isNameOnly());
assertTrue(r.dirOnly()); assertTrue(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertTrue(r.getAttributes().isEmpty()); assertTrue(r.getAttributes().isEmpty());
assertEquals(r.getPattern(), "/pattern"); assertEquals(r.getPattern(), "/pattern");
r = new AttributesRule("/patter?/", ""); r = new AttributesRule("/patter?/", "");
assertFalse(r.isNameOnly()); assertFalse(r.isNameOnly());
assertTrue(r.dirOnly()); assertTrue(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertTrue(r.getAttributes().isEmpty()); assertTrue(r.getAttributes().isEmpty());
assertEquals(r.getPattern(), "/patter?"); assertEquals(r.getPattern(), "/patter?");
r = new AttributesRule("patt*", ""); r = new AttributesRule("patt*", "");
assertTrue(r.isNameOnly()); assertTrue(r.isNameOnly());
assertFalse(r.dirOnly()); assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertTrue(r.getAttributes().isEmpty()); assertTrue(r.getAttributes().isEmpty());
assertEquals(r.getPattern(), "patt*"); assertEquals(r.getPattern(), "patt*");
r = new AttributesRule("pattern", "attribute1"); r = new AttributesRule("pattern", "attribute1");
assertTrue(r.isNameOnly()); assertTrue(r.isNameOnly());
assertFalse(r.dirOnly()); assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertFalse(r.getAttributes().isEmpty()); assertFalse(r.getAttributes().isEmpty());
assertEquals(r.getAttributes().size(), 1); assertEquals(r.getAttributes().size(), 1);
@ -322,28 +322,28 @@ public class AttributesMatcherTest {
r = new AttributesRule("pattern", "attribute1 -attribute2"); r = new AttributesRule("pattern", "attribute1 -attribute2");
assertTrue(r.isNameOnly()); assertTrue(r.isNameOnly());
assertFalse(r.dirOnly()); assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2); assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern"); assertEquals(r.getPattern(), "pattern");
r = new AttributesRule("pattern", "attribute1 \t-attribute2 \t"); r = new AttributesRule("pattern", "attribute1 \t-attribute2 \t");
assertTrue(r.isNameOnly()); assertTrue(r.isNameOnly());
assertFalse(r.dirOnly()); assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2); assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern"); assertEquals(r.getPattern(), "pattern");
r = new AttributesRule("pattern", "attribute1\t-attribute2\t"); r = new AttributesRule("pattern", "attribute1\t-attribute2\t");
assertTrue(r.isNameOnly()); assertTrue(r.isNameOnly());
assertFalse(r.dirOnly()); assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2); assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern"); assertEquals(r.getPattern(), "pattern");
r = new AttributesRule("pattern", "attribute1\t -attribute2\t "); r = new AttributesRule("pattern", "attribute1\t -attribute2\t ");
assertTrue(r.isNameOnly()); assertTrue(r.isNameOnly());
assertFalse(r.dirOnly()); assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 2); assertEquals(r.getAttributes().size(), 2);
assertEquals(r.getPattern(), "pattern"); assertEquals(r.getPattern(), "pattern");
@ -351,7 +351,7 @@ public class AttributesMatcherTest {
r = new AttributesRule("pattern", r = new AttributesRule("pattern",
"attribute1 -attribute2 attribute3=value "); "attribute1 -attribute2 attribute3=value ");
assertTrue(r.isNameOnly()); assertTrue(r.isNameOnly());
assertFalse(r.dirOnly()); assertFalse(r.isDirOnly());
assertNotNull(r.getAttributes()); assertNotNull(r.getAttributes());
assertEquals(r.getAttributes().size(), 3); assertEquals(r.getAttributes().size(), 3);
assertEquals(r.getPattern(), "pattern"); assertEquals(r.getPattern(), "pattern");

9
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, private void assertAttributesNode(String pathName,
AttributesNode attributesNode, List<Attribute> nodeAttrs) { AttributesNode attributesNode, List<Attribute> nodeAttrs)
throws IOException {
if (attributesNode == null) if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else { else {
Attributes entryAttributes = new Attributes(); Attributes entryAttributes = new Attributes();
attributesNode.getAttributes(pathName, new AttributesHandler(walk).mergeAttributes(attributesNode,
false, entryAttributes); pathName,
false,
entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) { if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) { for (Attribute attribute : nodeAttrs) {

10
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.IOException;
import java.io.InputStream; 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.After;
import org.junit.Test; import org.junit.Test;
@ -57,6 +60,8 @@ import org.junit.Test;
* Test {@link AttributesNode} * Test {@link AttributesNode}
*/ */
public class AttributesNodeTest { 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); 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, private void assertAttribute(String path, AttributesNode node,
Attributes attrs) { Attributes attrs) throws IOException {
Attributes attributes = new Attributes(); Attributes attributes = new Attributes();
node.getAttributes(path, false, attributes); new AttributesHandler(DUMMY_WALK).mergeAttributes(node, path, false,
attributes);
assertEquals(attrs, attributes); assertEquals(attrs, attributes);
} }

8
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, private void assertAttributesNode(String pathName,
AttributesNode attributesNode, List<Attribute> nodeAttrs) { AttributesNode attributesNode, List<Attribute> nodeAttrs)
throws IOException {
if (attributesNode == null) if (attributesNode == null)
assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); assertTrue(nodeAttrs == null || nodeAttrs.isEmpty());
else { else {
Attributes entryAttributes = new Attributes(); Attributes entryAttributes = new Attributes();
attributesNode.getAttributes(pathName, new AttributesHandler(walk).mergeAttributes(attributesNode,
false, entryAttributes); pathName, false,
entryAttributes);
if (nodeAttrs != null && !nodeAttrs.isEmpty()) { if (nodeAttrs != null && !nodeAttrs.isEmpty()) {
for (Attribute attribute : nodeAttrs) { for (Attribute attribute : nodeAttrs) {

19
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>

434
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;
}
}

37
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.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
@ -122,40 +121,4 @@ public class AttributesNode {
return Collections.unmodifiableList(rules); 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);
}
}
}
}
} }

30
org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java

@ -109,10 +109,11 @@ public class AttributesRule {
private final String pattern; private final String pattern;
private final List<Attribute> attributes; private final List<Attribute> attributes;
private boolean nameOnly; private final boolean nameOnly;
private boolean dirOnly;
private IMatcher matcher; private final boolean dirOnly;
private final IMatcher matcher;
/** /**
* Create a new attribute rule with the given pattern. Assumes that the * 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) { public AttributesRule(String pattern, String attributes) {
this.attributes = parseAttributes(attributes); this.attributes = parseAttributes(attributes);
nameOnly = false;
dirOnly = false;
if (pattern.endsWith("/")) { //$NON-NLS-1$ if (pattern.endsWith("/")) { //$NON-NLS-1$
pattern = pattern.substring(0, pattern.length() - 1); pattern = pattern.substring(0, pattern.length() - 1);
dirOnly = true; dirOnly = true;
} else {
dirOnly = false;
} }
boolean hasSlash = pattern.contains("/"); //$NON-NLS-1$ int slashIndex = pattern.indexOf('/');
if (!hasSlash) if (slashIndex < 0) {
nameOnly = true; 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 // Contains "/" but does not start with one
// Adding / to the start should not interfere with matching // Adding / to the start should not interfere with matching
pattern = "/" + pattern; //$NON-NLS-1$ pattern = "/" + pattern; //$NON-NLS-1$
} }
IMatcher candidateMatcher = NO_MATCH;
try { try {
matcher = PathMatcher.createPathMatcher(pattern, candidateMatcher = PathMatcher.createPathMatcher(pattern,
Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly); Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly);
} catch (InvalidPatternException e) { } catch (InvalidPatternException e) {
matcher = NO_MATCH; // ignore: invalid patterns are silently ignored
} }
this.matcher = candidateMatcher;
this.pattern = pattern; this.pattern = pattern;
} }
/** /**
* @return True if the pattern should match directories only * @return True if the pattern should match directories only
* @since 4.3
*/ */
public boolean dirOnly() { public boolean isDirOnly() {
return dirOnly; return dirOnly;
} }

11
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java

@ -50,6 +50,7 @@ import java.nio.ByteBuffer;
import java.nio.CharBuffer; import java.nio.CharBuffer;
import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesHandler;
import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.dircache.DirCacheCheckout;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@ -88,8 +89,14 @@ public abstract class AbstractTreeIterator {
/** A dummy object id buffer that matches the zero ObjectId. */ /** A dummy object id buffer that matches the zero ObjectId. */
protected static final byte[] zeroid = new byte[Constants.OBJECT_ID_LENGTH]; 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. */ /** The iterator this current entry is path equal to. */
AbstractTreeIterator matches; AbstractTreeIterator matches;

322
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.Map;
import java.util.Set; import java.util.Set;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.attributes.Attribute; import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attribute.State;
import org.eclipse.jgit.attributes.Attributes; import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.attributes.AttributesNode;
import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.attributes.AttributesNodeProvider;
import org.eclipse.jgit.attributes.AttributesProvider; import org.eclipse.jgit.attributes.AttributesProvider;
import org.eclipse.jgit.dircache.DirCacheBuildIterator; import org.eclipse.jgit.dircache.DirCacheBuildIterator;
import org.eclipse.jgit.attributes.AttributesHandler;
import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@ -270,6 +268,9 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
/** Cached attribute for the current entry */ /** Cached attribute for the current entry */
private Attributes attrs = null; private Attributes attrs = null;
/** Cached attributes handler */
private AttributesHandler attributesHandler;
private Config config; private Config config;
/** /**
@ -309,6 +310,14 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
return reader; return reader;
} }
/**
* @return the {@link OperationType}
* @since 4.3
*/
public OperationType getOperationType() {
return operationType;
}
/** /**
* Release any resources used by this walker's reader. * Release any resources used by this walker's reader.
* <p> * <p>
@ -435,9 +444,83 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
attributesNodeProvider = provider; 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. */ /** Reset this walker so new tree iterators can be added to it. */
public void reset() { public void reset() {
attrs = null; attrs = null;
attributesHandler = null;
trees = NO_TREES; trees = NO_TREES;
advance = false; advance = false;
depth = 0; depth = 0;
@ -739,6 +822,16 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
return FileMode.fromBits(getRawMode(nth)); 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. * Obtain the ObjectId for the current entry.
* <p> * <p>
@ -1109,156 +1202,13 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
} }
/** /**
* Retrieve the git attributes for the current entry. * @param type
* * of the tree to be queried
* <h4>Git attribute computation</h4> * @return the tree of that type or null if none is present
* * @since 4.3
* <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.
*/ */
private void getPerDirectoryEntryAttributes(String path, boolean isDir, public <T extends AbstractTreeIterator> T getTree(
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,
Class<T> type) { 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++) { for (int i = 0; i < trees.length; i++) {
AbstractTreeIterator tree = trees[i]; AbstractTreeIterator tree = trees[i];
if (type.isInstance(tree)) { if (type.isInstance(tree)) {
@ -1268,76 +1218,6 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
return null; return null;
} }
/**
* Get the {@link AttributesNode} for the current entry.
* <p>
* This method implements the fallback mechanism between the index and the
* working tree depending on the operation type
* </p>
*
* @param opType
* @param workingTreeIterator
* @param dirCacheIterator
* @param other
* @return a {@link AttributesNode} of the current entry,
* {@link NullPointerException} otherwise.
* @throws IOException
* It raises an {@link IOException} if a problem appears while
* parsing one on the attributes file.
*/
private AttributesNode getCurrentAttributesNode(OperationType opType,
@Nullable WorkingTreeIterator workingTreeIterator,
@Nullable DirCacheIterator dirCacheIterator,
@Nullable CanonicalTreeParser other)
throws IOException {
AttributesNode attributesNode = null;
switch (opType) {
case CHECKIN_OP:
if (workingTreeIterator != null) {
attributesNode = workingTreeIterator.getEntryAttributesNode();
}
if (attributesNode == null && dirCacheIterator != null) {
attributesNode = getAttributesNode(dirCacheIterator
.getEntryAttributesNode(getObjectReader()),
attributesNode);
}
if (attributesNode == null && other != null) {
attributesNode = getAttributesNode(
other.getEntryAttributesNode(getObjectReader()),
attributesNode);
}
break;
case CHECKOUT_OP:
if (other != null) {
attributesNode = other
.getEntryAttributesNode(getObjectReader());
}
if (dirCacheIterator != null) {
attributesNode = getAttributesNode(dirCacheIterator
.getEntryAttributesNode(getObjectReader()),
attributesNode);
}
if (attributesNode == null && workingTreeIterator != null) {
attributesNode = getAttributesNode(
workingTreeIterator.getEntryAttributesNode(),
attributesNode);
}
break;
default:
throw new IllegalStateException(
"The only supported operation types are:" //$NON-NLS-1$
+ OperationType.CHECKIN_OP + "," //$NON-NLS-1$
+ OperationType.CHECKOUT_OP);
}
return attributesNode;
}
private static AttributesNode getAttributesNode(AttributesNode value,
AttributesNode defaultValue) {
return (value == null) ? defaultValue : value;
}
/** /**
* Inspect config and attributes to return a filtercommand applicable for * Inspect config and attributes to return a filtercommand applicable for
* the current path * the current path

Loading…
Cancel
Save