Browse Source
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
11 changed files with 948 additions and 289 deletions
@ -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()); |
||||||
|
} |
||||||
|
} |
@ -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> |
@ -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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue