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