diff options
author | Ivan Motsch <ivan.motsch@bsiag.com> | 2015-11-17 16:04:01 +0100 |
---|---|---|
committer | Christian Halstrick <christian.halstrick@sap.com> | 2016-02-11 17:08:49 +0100 |
commit | 975aa8868591ea49e3d8033a0fe224c09e67aec9 (patch) | |
tree | dba3cd80359bd820a4dda4e334a7871befd3836a /org.eclipse.jgit | |
parent | 28e2fed761f0ddf01029c999b299ed0bccdf630e (diff) | |
download | jgit-975aa8868591ea49e3d8033a0fe224c09e67aec9.tar.gz jgit-975aa8868591ea49e3d8033a0fe224c09e67aec9.zip |
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>
Diffstat (limited to 'org.eclipse.jgit')
6 files changed, 581 insertions, 272 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters new file mode 100644 index 0000000000..a5000dd6bd --- /dev/null +++ b/org.eclipse.jgit/.settings/.api_filters @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<component id="org.eclipse.jgit" version="2"> + <resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode"> + <filter comment="moved to new AttributesManager" id="338792546"> + <message_arguments> + <message_argument value="org.eclipse.jgit.attributes.AttributesNode"/> + <message_argument value="getAttributes(String, boolean, Attributes)"/> + </message_arguments> + </filter> + </resource> + <resource path="src/org/eclipse/jgit/attributes/AttributesRule.java" type="org.eclipse.jgit.attributes.AttributesRule"> + <filter comment="used only in tests: bean naming" id="338792546"> + <message_arguments> + <message_argument value="org.eclipse.jgit.attributes.AttributesRule"/> + <message_argument value="dirOnly()"/> + </message_arguments> + </filter> + </resource> +</component> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java new file mode 100644 index 0000000000..19e4afdf92 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java @@ -0,0 +1,434 @@ +/* + * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.attributes; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.treewalk.AbstractTreeIterator; +import org.eclipse.jgit.treewalk.CanonicalTreeParser; +import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.WorkingTreeIterator; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; + +/** + * The attributes handler knows how to retrieve, parse and merge attributes from + * the various gitattributes files. Furthermore it collects and expands macro + * expressions. The method {@link #getAttributes()} yields the ready processed + * attributes for the current path represented by the {@link TreeWalk} + * <p> + * The implementation is based on the specifications in + * http://git-scm.com/docs/gitattributes + * + * @since 4.3 + */ +public class AttributesHandler { + private static final String MACRO_PREFIX = "[attr]"; //$NON-NLS-1$ + + private static final String BINARY_RULE_KEY = "binary"; //$NON-NLS-1$ + + /** + * This is the default <b>binary</b> rule that is present in any git folder + * <code>[attr]binary -diff -merge -text</code> + */ + private static final List<Attribute> BINARY_RULE_ATTRIBUTES = new AttributesRule( + MACRO_PREFIX + BINARY_RULE_KEY, "-diff -merge -text") //$NON-NLS-1$ + .getAttributes(); + + private final TreeWalk treeWalk; + + private final AttributesNode globalNode; + + private final AttributesNode infoNode; + + private final Map<String, List<Attribute>> expansions = new HashMap<>(); + + /** + * Create an {@link AttributesHandler} with default rules as well as merged + * rules from global, info and worktree root attributes + * + * @param treeWalk + * @throws IOException + */ + public AttributesHandler(TreeWalk treeWalk) throws IOException { + this.treeWalk = treeWalk; + AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider(); + this.globalNode = attributesNodeProvider != null + ? attributesNodeProvider.getGlobalAttributesNode() : null; + this.infoNode = attributesNodeProvider != null + ? attributesNodeProvider.getInfoAttributesNode() : null; + + AttributesNode rootNode = attributesNode(treeWalk, + rootOf( + treeWalk.getTree(WorkingTreeIterator.class)), + rootOf( + treeWalk.getTree(DirCacheIterator.class)), + rootOf(treeWalk + .getTree(CanonicalTreeParser.class))); + + expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES); + for (AttributesNode node : new AttributesNode[] { globalNode, rootNode, + infoNode }) { + if (node == null) { + continue; + } + for (AttributesRule rule : node.getRules()) { + if (rule.getPattern().startsWith(MACRO_PREFIX)) { + expansions.put(rule.getPattern() + .substring(MACRO_PREFIX.length()).trim(), + rule.getAttributes()); + } + } + } + } + + /** + * see {@link TreeWalk#getAttributes()} + * + * @return the {@link Attributes} for the current path represented by the + * {@link TreeWalk} + * @throws IOException + */ + public Attributes getAttributes() throws IOException { + String entryPath = treeWalk.getPathString(); + boolean isDirectory = (treeWalk.getFileMode() == FileMode.TREE); + Attributes attributes = new Attributes(); + + // Gets the info attributes + mergeInfoAttributes(entryPath, isDirectory, attributes); + + // Gets the attributes located on the current entry path + mergePerDirectoryEntryAttributes(entryPath, isDirectory, + treeWalk.getTree(WorkingTreeIterator.class), + treeWalk.getTree(DirCacheIterator.class), + treeWalk.getTree(CanonicalTreeParser.class), + attributes); + + // Gets the attributes located in the global attribute file + mergeGlobalAttributes(entryPath, isDirectory, attributes); + + // now after all attributes are collected - in the correct hierarchy + // order - remove all unspecified entries (the ! marker) + for (Attribute a : attributes.getAll()) { + if (a.getState() == State.UNSPECIFIED) + attributes.remove(a.getKey()); + } + + return attributes; + } + + /** + * Merges the matching GLOBAL attributes for an entry path. + * + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + */ + private void mergeGlobalAttributes(String entryPath, boolean isDirectory, + Attributes result) { + mergeAttributes(globalNode, entryPath, isDirectory, result); + } + + /** + * Merges the matching INFO attributes for an entry path. + * + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + */ + private void mergeInfoAttributes(String entryPath, boolean isDirectory, + Attributes result) { + mergeAttributes(infoNode, entryPath, isDirectory, result); + } + + /** + * Merges the matching working directory attributes for an entry path. + * + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param workingTreeIterator + * @param dirCacheIterator + * @param otherTree + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + * @throws IOException + */ + private void mergePerDirectoryEntryAttributes(String entryPath, + boolean isDirectory, + @Nullable WorkingTreeIterator workingTreeIterator, + @Nullable DirCacheIterator dirCacheIterator, + @Nullable CanonicalTreeParser otherTree, Attributes result) + throws IOException { + // Prevents infinite recurrence + if (workingTreeIterator != null || dirCacheIterator != null + || otherTree != null) { + AttributesNode attributesNode = attributesNode( + treeWalk, workingTreeIterator, dirCacheIterator, otherTree); + if (attributesNode != null) { + mergeAttributes(attributesNode, entryPath, isDirectory, result); + } + mergePerDirectoryEntryAttributes(entryPath, isDirectory, + parentOf(workingTreeIterator), parentOf(dirCacheIterator), + parentOf(otherTree), result); + } + } + + /** + * Merges the matching node attributes for an entry path. + * + * @param node + * the node to scan for matches to entryPath + * @param entryPath + * the path to test. The path must be relative to this attribute + * node's own repository path, and in repository path format + * (uses '/' and not '\'). + * @param isDirectory + * true if the target item is a directory. + * @param result + * that will hold the attributes matching this entry path. This + * method will NOT override any existing entry in attributes. + */ + protected void mergeAttributes(@Nullable AttributesNode node, + String entryPath, + boolean isDirectory, Attributes result) { + if (node == null) + return; + List<AttributesRule> rules = node.getRules(); + // Parse rules in the reverse order that they were read since the last + // entry should be used + ListIterator<AttributesRule> ruleIterator = rules + .listIterator(rules.size()); + while (ruleIterator.hasPrevious()) { + AttributesRule rule = ruleIterator.previous(); + if (rule.isMatch(entryPath, isDirectory)) { + ListIterator<Attribute> attributeIte = rule.getAttributes() + .listIterator(rule.getAttributes().size()); + // Parses the attributes in the reverse order that they were + // read since the last entry should be used + while (attributeIte.hasPrevious()) { + expandMacro(attributeIte.previous(), result); + } + } + } + } + + /** + * @param attr + * @param result + * contains the (recursive) expanded and merged macro attributes + * including the attribute iself + */ + protected void expandMacro(Attribute attr, Attributes result) { + // loop detection = exists check + if (result.containsKey(attr.getKey())) + return; + + // also add macro to result set, same does native git + result.put(attr); + + List<Attribute> expansion = expansions.get(attr.getKey()); + if (expansion == null) { + return; + } + switch (attr.getState()) { + case UNSET: { + for (Attribute e : expansion) { + switch (e.getState()) { + case SET: + expandMacro(new Attribute(e.getKey(), State.UNSET), result); + break; + case UNSET: + expandMacro(new Attribute(e.getKey(), State.SET), result); + break; + case UNSPECIFIED: + expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED), + result); + break; + case CUSTOM: + default: + expandMacro(e, result); + } + } + break; + } + case CUSTOM: { + for (Attribute e : expansion) { + switch (e.getState()) { + case SET: + case UNSET: + case UNSPECIFIED: + expandMacro(e, result); + break; + case CUSTOM: + default: + expandMacro(new Attribute(e.getKey(), attr.getValue()), + result); + } + } + break; + } + case UNSPECIFIED: { + for (Attribute e : expansion) { + expandMacro(new Attribute(e.getKey(), State.UNSPECIFIED), + result); + } + break; + } + case SET: + default: + for (Attribute e : expansion) { + expandMacro(e, result); + } + break; + } + } + + /** + * Get the {@link AttributesNode} for the current entry. + * <p> + * This method implements the fallback mechanism between the index and the + * working tree depending on the operation type + * </p> + * + * @param treeWalk + * @param workingTreeIterator + * @param dirCacheIterator + * @param otherTree + * @return a {@link AttributesNode} of the current entry, + * {@link NullPointerException} otherwise. + * @throws IOException + * It raises an {@link IOException} if a problem appears while + * parsing one on the attributes file. + */ + private static AttributesNode attributesNode(TreeWalk treeWalk, + @Nullable WorkingTreeIterator workingTreeIterator, + @Nullable DirCacheIterator dirCacheIterator, + @Nullable CanonicalTreeParser otherTree) throws IOException { + AttributesNode attributesNode = null; + switch (treeWalk.getOperationType()) { + case CHECKIN_OP: + if (workingTreeIterator != null) { + attributesNode = workingTreeIterator.getEntryAttributesNode(); + } + if (attributesNode == null && dirCacheIterator != null) { + attributesNode = dirCacheIterator + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + if (attributesNode == null && otherTree != null) { + attributesNode = otherTree + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + break; + case CHECKOUT_OP: + if (otherTree != null) { + attributesNode = otherTree + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + if (attributesNode == null && dirCacheIterator != null) { + attributesNode = dirCacheIterator + .getEntryAttributesNode(treeWalk.getObjectReader()); + } + if (attributesNode == null && workingTreeIterator != null) { + attributesNode = workingTreeIterator.getEntryAttributesNode(); + } + break; + default: + throw new IllegalStateException( + "The only supported operation types are:" //$NON-NLS-1$ + + OperationType.CHECKIN_OP + "," //$NON-NLS-1$ + + OperationType.CHECKOUT_OP); + } + + return attributesNode; + } + + private static <T extends AbstractTreeIterator> T parentOf(@Nullable T node) { + if(node==null) return null; + @SuppressWarnings("unchecked") + Class<T> type = (Class<T>) node.getClass(); + AbstractTreeIterator parent = node.parent; + if (type.isInstance(parent)) { + return type.cast(parent); + } + return null; + } + + private static <T extends AbstractTreeIterator> T rootOf( + @Nullable T node) { + if(node==null) return null; + AbstractTreeIterator t=node; + while (t!= null && t.parent != null) { + t= t.parent; + } + @SuppressWarnings("unchecked") + Class<T> type = (Class<T>) node.getClass(); + if (type.isInstance(t)) { + return type.cast(t); + } + return null; + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java index 5c0aba2e0e..7196502112 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java @@ -49,7 +49,6 @@ import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.ListIterator; import org.eclipse.jgit.lib.Constants; @@ -122,40 +121,4 @@ public class AttributesNode { return Collections.unmodifiableList(rules); } - /** - * Returns the matching attributes for an entry path. - * - * @param entryPath - * the path to test. The path must be relative to this attribute - * node's own repository path, and in repository path format - * (uses '/' and not '\'). - * @param isDirectory - * true if the target item is a directory. - * @param attributes - * Map that will hold the attributes matching this entry path. If - * it is not empty, this method will NOT override any existing - * entry. - * @since 4.2 - */ - public void getAttributes(String entryPath, - boolean isDirectory, Attributes attributes) { - // Parse rules in the reverse order that they were read since the last - // entry should be used - ListIterator<AttributesRule> ruleIterator = rules.listIterator(rules - .size()); - while (ruleIterator.hasPrevious()) { - AttributesRule rule = ruleIterator.previous(); - if (rule.isMatch(entryPath, isDirectory)) { - ListIterator<Attribute> attributeIte = rule.getAttributes() - .listIterator(rule.getAttributes().size()); - // Parses the attributes in the reverse order that they were - // read since the last entry should be used - while (attributeIte.hasPrevious()) { - Attribute attr = attributeIte.previous(); - if (!attributes.containsKey(attr.getKey())) - attributes.put(attr); - } - } - } - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java index 35d18c4b2a..0532250f9c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java @@ -109,10 +109,11 @@ public class AttributesRule { private final String pattern; private final List<Attribute> attributes; - private boolean nameOnly; - private boolean dirOnly; + private final boolean nameOnly; - private IMatcher matcher; + private final boolean dirOnly; + + private final IMatcher matcher; /** * Create a new attribute rule with the given pattern. Assumes that the @@ -128,38 +129,43 @@ public class AttributesRule { */ public AttributesRule(String pattern, String attributes) { this.attributes = parseAttributes(attributes); - nameOnly = false; - dirOnly = false; if (pattern.endsWith("/")) { //$NON-NLS-1$ pattern = pattern.substring(0, pattern.length() - 1); dirOnly = true; + } else { + dirOnly = false; } - boolean hasSlash = pattern.contains("/"); //$NON-NLS-1$ + int slashIndex = pattern.indexOf('/'); - if (!hasSlash) + if (slashIndex < 0) { nameOnly = true; - else if (!pattern.startsWith("/")) { //$NON-NLS-1$ + } else if (slashIndex == 0) { + nameOnly = false; + } else { + nameOnly = false; // Contains "/" but does not start with one // Adding / to the start should not interfere with matching pattern = "/" + pattern; //$NON-NLS-1$ } + IMatcher candidateMatcher = NO_MATCH; try { - matcher = PathMatcher.createPathMatcher(pattern, + candidateMatcher = PathMatcher.createPathMatcher(pattern, Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly); } catch (InvalidPatternException e) { - matcher = NO_MATCH; + // ignore: invalid patterns are silently ignored } - + this.matcher = candidateMatcher; this.pattern = pattern; } /** * @return True if the pattern should match directories only + * @since 4.3 */ - public boolean dirOnly() { + public boolean isDirOnly() { return dirOnly; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java index 58136355eb..dc835e4f36 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -50,6 +50,7 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesHandler; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -88,8 +89,14 @@ public abstract class AbstractTreeIterator { /** A dummy object id buffer that matches the zero ObjectId. */ protected static final byte[] zeroid = new byte[Constants.OBJECT_ID_LENGTH]; - /** Iterator for the parent tree; null if we are the root iterator. */ - final AbstractTreeIterator parent; + /** + * Iterator for the parent tree; null if we are the root iterator. + * <p> + * Used by {@link TreeWalk} and {@link AttributesHandler} + * + * @since 4.3 + */ + public final AbstractTreeIterator parent; /** The iterator this current entry is path equal to. */ AbstractTreeIterator matches; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java index 5cd713da78..4775e96175 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -49,15 +49,13 @@ import java.util.HashMap; import java.util.Map; import java.util.Set; -import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.attributes.Attribute; -import org.eclipse.jgit.attributes.Attribute.State; import org.eclipse.jgit.attributes.Attributes; -import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.attributes.AttributesProvider; import org.eclipse.jgit.dircache.DirCacheBuildIterator; +import org.eclipse.jgit.attributes.AttributesHandler; import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -270,6 +268,9 @@ public class TreeWalk implements AutoCloseable, AttributesProvider { /** Cached attribute for the current entry */ private Attributes attrs = null; + /** Cached attributes handler */ + private AttributesHandler attributesHandler; + private Config config; /** @@ -310,6 +311,14 @@ public class TreeWalk implements AutoCloseable, AttributesProvider { } /** + * @return the {@link OperationType} + * @since 4.3 + */ + public OperationType getOperationType() { + return operationType; + } + + /** * Release any resources used by this walker's reader. * <p> * A walker that has been released can be used again, but may need to be @@ -435,9 +444,83 @@ public class TreeWalk implements AutoCloseable, AttributesProvider { attributesNodeProvider = provider; } + /** + * @return the {@link AttributesNodeProvider} for this {@link TreeWalk}. + * @since 4.3 + */ + public AttributesNodeProvider getAttributesNodeProvider() { + return attributesNodeProvider; + } + + /** + * Retrieve the git attributes for the current entry. + * + * <h4>Git attribute computation</h4> + * + * <ul> + * <li>Get the attributes matching the current path entry from the info file + * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li> + * <li>Completes the list of attributes using the .gitattributes files + * located on the current path (the further the directory that contains + * .gitattributes is from the path in question, the lower its precedence). + * For a checkin operation, it will look first on the working tree (if any). + * If there is no attributes file, it will fallback on the index. For a + * checkout operation, it will first use the index entry and then fallback + * on the working tree if none.</li> + * <li>In the end, completes the list of matching attributes using the + * global attribute file define in the configuration (see + * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li> + * + * </ul> + * + * + * <h4>Iterator constraints</h4> + * + * <p> + * In order to have a correct list of attributes for the current entry, this + * {@link TreeWalk} requires to have at least one + * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An + * {@link AttributesNodeProvider} is used to retrieve the attributes from + * the info attributes file and the global attributes file. The + * {@link DirCacheIterator} is used to retrieve the .gitattributes files + * stored in the index. A {@link WorkingTreeIterator} can also be provided + * to access the local version of the .gitattributes files. If none is + * provided it will fallback on the {@link DirCacheIterator}. + * </p> + * + * @return a {@link Set} of {@link Attribute}s that match the current entry. + * @since 4.2 + */ + public Attributes getAttributes() { + if (attrs != null) + return attrs; + + if (attributesNodeProvider == null) { + // The work tree should have a AttributesNodeProvider to be able to + // retrieve the info and global attributes node + throw new IllegalStateException( + "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$ + } + + try { + // Lazy create the attributesHandler on the first access of + // attributes. This requires the info, global and root + // attributes nodes + if (attributesHandler == null) { + attributesHandler = new AttributesHandler(this); + } + attrs = attributesHandler.getAttributes(); + return attrs; + } catch (IOException e) { + throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$ + e); + } + } + /** Reset this walker so new tree iterators can be added to it. */ public void reset() { attrs = null; + attributesHandler = null; trees = NO_TREES; advance = false; depth = 0; @@ -740,6 +823,16 @@ public class TreeWalk implements AutoCloseable, AttributesProvider { } /** + * Obtain the {@link FileMode} for the current entry on the currentHead tree + * + * @return mode for the current entry of the currentHead tree. + * @since 4.3 + */ + public FileMode getFileMode() { + return FileMode.fromBits(currentHead.mode); + } + + /** * Obtain the ObjectId for the current entry. * <p> * Using this method to compare ObjectId values between trees of this walker @@ -1109,156 +1202,13 @@ public class TreeWalk implements AutoCloseable, AttributesProvider { } /** - * Retrieve the git attributes for the current entry. - * - * <h4>Git attribute computation</h4> - * - * <ul> - * <li>Get the attributes matching the current path entry from the info file - * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li> - * <li>Completes the list of attributes using the .gitattributes files - * located on the current path (the further the directory that contains - * .gitattributes is from the path in question, the lower its precedence). - * For a checkin operation, it will look first on the working tree (if any). - * If there is no attributes file, it will fallback on the index. For a - * checkout operation, it will first use the index entry and then fallback - * on the working tree if none.</li> - * <li>In the end, completes the list of matching attributes using the - * global attribute file define in the configuration (see - * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li> - * - * </ul> - * - * - * <h4>Iterator constraints</h4> - * - * <p> - * In order to have a correct list of attributes for the current entry, this - * {@link TreeWalk} requires to have at least one - * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An - * {@link AttributesNodeProvider} is used to retrieve the attributes from - * the info attributes file and the global attributes file. The - * {@link DirCacheIterator} is used to retrieve the .gitattributes files - * stored in the index. A {@link WorkingTreeIterator} can also be provided - * to access the local version of the .gitattributes files. If none is - * provided it will fallback on the {@link DirCacheIterator}. - * </p> - * - * @return a {@link Set} of {@link Attribute}s that match the current entry. - * @since 4.2 - */ - public Attributes getAttributes() { - if (attrs != null) - return attrs; - - if (attributesNodeProvider == null) { - // The work tree should have a AttributesNodeProvider to be able to - // retrieve the info and global attributes node - throw new IllegalStateException( - "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$ - } - - WorkingTreeIterator workingTreeIterator = getTree(WorkingTreeIterator.class); - DirCacheIterator dirCacheIterator = getTree(DirCacheIterator.class); - CanonicalTreeParser other = getTree(CanonicalTreeParser.class); - - if (workingTreeIterator == null && dirCacheIterator == null - && other == null) { - // Can not retrieve the attributes without at least one of the above - // iterators. - return new Attributes(); - } - - String path = currentHead.getEntryPathString(); - final boolean isDir = FileMode.TREE.equals(currentHead.mode); - Attributes attributes = new Attributes(); - try { - // Gets the global attributes node - AttributesNode globalNodeAttr = attributesNodeProvider - .getGlobalAttributesNode(); - // Gets the info attributes node - AttributesNode infoNodeAttr = attributesNodeProvider - .getInfoAttributesNode(); - - // Gets the info attributes - if (infoNodeAttr != null) { - infoNodeAttr.getAttributes(path, isDir, attributes); - } - - // Gets the attributes located on the current entry path - getPerDirectoryEntryAttributes(path, isDir, operationType, - workingTreeIterator, dirCacheIterator, other, attributes); - - // Gets the attributes located in the global attribute file - if (globalNodeAttr != null) { - globalNodeAttr.getAttributes(path, isDir, attributes); - } - } catch (IOException e) { - throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$ - } - // now after all attributes are collected - in the correct hierarchy - // order - remove all unspecified entries (the ! marker) - for (Attribute a : attributes.getAll()) { - if (a.getState() == State.UNSPECIFIED) - attributes.remove(a.getKey()); - } - return attributes; - } - - /** - * Get the attributes located on the current entry path. - * - * @param path - * current entry path - * @param isDir - * holds true if the current entry is a directory - * @param opType - * type of operation - * @param workingTreeIterator - * a {@link WorkingTreeIterator} matching the current entry - * @param dirCacheIterator - * a {@link DirCacheIterator} matching the current entry - * @param other - * a {@link CanonicalTreeParser} matching the current entry - * @param attributes - * Non null map holding the existing attributes. This map will be - * augmented with new entry. None entry will be overrided. - * @throws IOException - * It raises an {@link IOException} if a problem appears while - * parsing one on the attributes file. + * @param type + * of the tree to be queried + * @return the tree of that type or null if none is present + * @since 4.3 */ - private void getPerDirectoryEntryAttributes(String path, boolean isDir, - OperationType opType, WorkingTreeIterator workingTreeIterator, - DirCacheIterator dirCacheIterator, CanonicalTreeParser other, - Attributes attributes) - throws IOException { - // Prevents infinite recurrence - if (workingTreeIterator != null || dirCacheIterator != null - || other != null) { - AttributesNode currentAttributesNode = getCurrentAttributesNode( - opType, workingTreeIterator, dirCacheIterator, other); - if (currentAttributesNode != null) { - currentAttributesNode.getAttributes(path, isDir, attributes); - } - getPerDirectoryEntryAttributes(path, isDir, opType, - getParent(workingTreeIterator, WorkingTreeIterator.class), - getParent(dirCacheIterator, DirCacheIterator.class), - getParent(other, CanonicalTreeParser.class), attributes); - } - } - - private static <T extends AbstractTreeIterator> T getParent(T current, + public <T extends AbstractTreeIterator> T getTree( Class<T> type) { - if (current != null) { - AbstractTreeIterator parent = current.parent; - if (type.isInstance(parent)) { - return type.cast(parent); - } - } - return null; - } - - private <T extends AbstractTreeIterator> T getTree(Class<T> type) { for (int i = 0; i < trees.length; i++) { AbstractTreeIterator tree = trees[i]; if (type.isInstance(tree)) { @@ -1269,76 +1219,6 @@ public class TreeWalk implements AutoCloseable, AttributesProvider { } /** - * Get the {@link AttributesNode} for the current entry. - * <p> - * This method implements the fallback mechanism between the index and the - * working tree depending on the operation type - * </p> - * - * @param opType - * @param workingTreeIterator - * @param dirCacheIterator - * @param other - * @return a {@link AttributesNode} of the current entry, - * {@link NullPointerException} otherwise. - * @throws IOException - * It raises an {@link IOException} if a problem appears while - * parsing one on the attributes file. - */ - private AttributesNode getCurrentAttributesNode(OperationType opType, - @Nullable WorkingTreeIterator workingTreeIterator, - @Nullable DirCacheIterator dirCacheIterator, - @Nullable CanonicalTreeParser other) - throws IOException { - AttributesNode attributesNode = null; - switch (opType) { - case CHECKIN_OP: - if (workingTreeIterator != null) { - attributesNode = workingTreeIterator.getEntryAttributesNode(); - } - if (attributesNode == null && dirCacheIterator != null) { - attributesNode = getAttributesNode(dirCacheIterator - .getEntryAttributesNode(getObjectReader()), - attributesNode); - } - if (attributesNode == null && other != null) { - attributesNode = getAttributesNode( - other.getEntryAttributesNode(getObjectReader()), - attributesNode); - } - break; - case CHECKOUT_OP: - if (other != null) { - attributesNode = other - .getEntryAttributesNode(getObjectReader()); - } - if (dirCacheIterator != null) { - attributesNode = getAttributesNode(dirCacheIterator - .getEntryAttributesNode(getObjectReader()), - attributesNode); - } - if (attributesNode == null && workingTreeIterator != null) { - attributesNode = getAttributesNode( - workingTreeIterator.getEntryAttributesNode(), - attributesNode); - } - break; - default: - throw new IllegalStateException( - "The only supported operation types are:" //$NON-NLS-1$ - + OperationType.CHECKIN_OP + "," //$NON-NLS-1$ - + OperationType.CHECKOUT_OP); - } - - return attributesNode; - } - - private static AttributesNode getAttributesNode(AttributesNode value, - AttributesNode defaultValue) { - return (value == null) ? defaultValue : value; - } - - /** * Inspect config and attributes to return a filtercommand applicable for * the current path * |