summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
authorIvan Motsch <ivan.motsch@bsiag.com>2015-11-17 16:04:01 +0100
committerChristian Halstrick <christian.halstrick@sap.com>2016-02-11 17:08:49 +0100
commit975aa8868591ea49e3d8033a0fe224c09e67aec9 (patch)
treedba3cd80359bd820a4dda4e334a7871befd3836a /org.eclipse.jgit
parent28e2fed761f0ddf01029c999b299ed0bccdf630e (diff)
downloadjgit-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')
-rw-r--r--org.eclipse.jgit/.settings/.api_filters19
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesHandler.java434
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java37
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java30
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java322
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
*