diff options
Diffstat (limited to 'org.eclipse.jgit/src/org/eclipse/jgit')
11 files changed, 818 insertions, 15 deletions
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java new file mode 100644 index 0000000000..d3ce685187 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2010, Marc Strapetz <marc.strapetz@syntevo.com> + * and other copyright owners as documented in the project's IP log. + * + * 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; + +/** + * Represents an attribute. + * <p> + * According to the man page, an attribute can have the following states: + * <ul> + * <li>Set - represented by {@link State#SET}</li> + * <li>Unset - represented by {@link State#UNSET}</li> + * <li>Set to a value - represented by {@link State#CUSTOM}</li> + * <li>Unspecified - <code>null</code> is used instead of an instance of this + * class</li> + * </ul> + * </p> + * + * @since 3.7 + */ +public final class Attribute { + + /** + * The attribute value state + */ + public static enum State { + /** the attribute is set */ + SET, + + /** the attribute is unset */ + UNSET, + + /** the attribute is set to a custom value */ + CUSTOM + } + + private final String key; + private final State state; + private final String value; + + /** + * Creates a new instance + * + * @param key + * the attribute key. Should not be <code>null</code>. + * @param state + * the attribute state. It should be either {@link State#SET} or + * {@link State#UNSET}. In order to create a custom value + * attribute prefer the use of {@link #Attribute(String, String)} + * constructor. + */ + public Attribute(String key, State state) { + this(key, state, null); + } + + private Attribute(String key, State state, String value) { + if (key == null) + throw new NullPointerException( + "The key of an attribute should not be null"); //$NON-NLS-1$ + if (state == null) + throw new NullPointerException( + "The state of an attribute should not be null"); //$NON-NLS-1$ + + this.key = key; + this.state = state; + this.value = value; + } + + /** + * Creates a new instance. + * + * @param key + * the attribute key. Should not be <code>null</code>. + * @param value + * the custom attribute value + */ + public Attribute(String key, String value) { + this(key, State.CUSTOM, value); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof Attribute)) + return false; + Attribute other = (Attribute) obj; + if (!key.equals(other.key)) + return false; + if (state != other.state) + return false; + if (value == null) { + if (other.value != null) + return false; + } else if (!value.equals(other.value)) + return false; + return true; + } + + /** + * @return the attribute key (never returns <code>null</code>) + */ + public String getKey() { + return key; + } + + /** + * Returns the state. + * + * @return the state (never returns <code>null</code>) + */ + public State getState() { + return state; + } + + /** + * @return the attribute value (may be <code>null</code>) + */ + public String getValue() { + return value; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + key.hashCode(); + result = prime * result + state.hashCode(); + result = prime * result + ((value == null) ? 0 : value.hashCode()); + return result; + } + + @Override + public String toString() { + switch (state) { + case SET: + return key; + case UNSET: + return "-" + key; //$NON-NLS-1$ + case CUSTOM: + default: + return key + "=" + value; //$NON-NLS-1$ + } + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java new file mode 100644 index 0000000000..70f56ff964 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2010, Red Hat Inc. + * and other copyright owners as documented in the project's IP log. + * + * 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.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; + +import org.eclipse.jgit.lib.Constants; + +/** + * Represents a bundle of attributes inherited from a base directory. + * + * This class is not thread safe, it maintains state about the last match. + * + * @since 3.7 + */ +public class AttributesNode { + /** The rules that have been parsed into this node. */ + private final List<AttributesRule> rules; + + /** Create an empty ignore node with no rules. */ + public AttributesNode() { + rules = new ArrayList<AttributesRule>(); + } + + /** + * Create an ignore node with given rules. + * + * @param rules + * list of rules. + **/ + public AttributesNode(List<AttributesRule> rules) { + this.rules = rules; + } + + /** + * Parse files according to gitattribute standards. + * + * @param in + * input stream holding the standard ignore format. The caller is + * responsible for closing the stream. + * @throws IOException + * Error thrown when reading an ignore file. + */ + public void parse(InputStream in) throws IOException { + BufferedReader br = asReader(in); + String txt; + while ((txt = br.readLine()) != null) { + txt = txt.trim(); + if (txt.length() > 0 && !txt.startsWith("#") /* Comments *///$NON-NLS-1$ + && !txt.startsWith("!") /* Negative pattern forbidden for attributes */) { //$NON-NLS-1$ + int patternEndSpace = txt.indexOf(' '); + int patternEndTab = txt.indexOf('\t'); + + final int patternEnd; + if (patternEndSpace == -1) + patternEnd = patternEndTab; + else if (patternEndTab == -1) + patternEnd = patternEndSpace; + else + patternEnd = Math.min(patternEndSpace, patternEndTab); + + if (patternEnd > -1) + rules.add(new AttributesRule(txt.substring(0, patternEnd), + txt.substring(patternEnd + 1).trim())); + } + } + } + + private static BufferedReader asReader(InputStream in) { + return new BufferedReader(new InputStreamReader(in, Constants.CHARSET)); + } + + /** @return list of all ignore rules held by this node. */ + public List<AttributesRule> getRules() { + 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. + */ + public void getAttributes(String entryPath, boolean isDirectory, + Map<String, Attribute> 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.getKey(), 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 new file mode 100644 index 0000000000..bcac14b5ff --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2010, Red Hat Inc. + * and other copyright owners as documented in the project's IP log. + * + * 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.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.errors.InvalidPatternException; +import org.eclipse.jgit.ignore.FastIgnoreRule; +import org.eclipse.jgit.ignore.internal.IMatcher; +import org.eclipse.jgit.ignore.internal.PathMatcher; + +/** + * A single attributes rule corresponding to one line in a .gitattributes file. + * + * Inspiration from: {@link FastIgnoreRule} + * + * @since 3.7 + */ +public class AttributesRule { + + /** + * regular expression for splitting attributes - space, tab and \r (the C + * implementation oddly enough allows \r between attributes) + * */ + private static final String ATTRIBUTES_SPLIT_REGEX = "[ \t\r]"; //$NON-NLS-1$ + + private static List<Attribute> parseAttributes(String attributesLine) { + // the C implementation oddly enough allows \r between attributes too. + ArrayList<Attribute> result = new ArrayList<Attribute>(); + for (String attribute : attributesLine.split(ATTRIBUTES_SPLIT_REGEX)) { + attribute = attribute.trim(); + if (attribute.length() == 0) + continue; + + if (attribute.startsWith("-")) {//$NON-NLS-1$ + if (attribute.length() > 1) + result.add(new Attribute(attribute.substring(1), + State.UNSET)); + continue; + } + + final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$ + if (equalsIndex == -1) + result.add(new Attribute(attribute, State.SET)); + else { + String attributeKey = attribute.substring(0, equalsIndex); + if (attributeKey.length() > 0) { + String attributeValue = attribute + .substring(equalsIndex + 1); + result.add(new Attribute(attributeKey, attributeValue)); + } + } + } + return result; + } + + private final String pattern; + private final List<Attribute> attributes; + + private boolean nameOnly; + private boolean dirOnly; + + private IMatcher matcher; + + /** + * Create a new attribute rule with the given pattern. Assumes that the + * pattern is already trimmed. + * + * @param pattern + * Base pattern for the attributes rule. This pattern will be + * parsed to generate rule parameters. It can not be + * <code>null</code>. + * @param attributes + * the rule attributes. This string will be parsed to read the + * attributes. + */ + 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; + } + + boolean hasSlash = pattern.contains("/"); //$NON-NLS-1$ + + if (!hasSlash) + nameOnly = true; + else if (!pattern.startsWith("/")) { //$NON-NLS-1$ + // Contains "/" but does not start with one + // Adding / to the start should not interfere with matching + pattern = "/" + pattern; //$NON-NLS-1$ + } + + try { + matcher = PathMatcher.createPathMatcher(pattern, + Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly); + } catch (InvalidPatternException e) { + matcher = NO_MATCH; + } + + this.pattern = pattern; + } + + /** + * @return True if the pattern should match directories only + */ + public boolean dirOnly() { + return dirOnly; + } + + /** + * Returns the attributes. + * + * @return an unmodifiable list of attributes (never returns + * <code>null</code>) + */ + public List<Attribute> getAttributes() { + return Collections.unmodifiableList(attributes); + } + + /** + * @return <code>true</code> if the pattern is just a file name and not a + * path + */ + public boolean isNameOnly() { + return nameOnly; + } + + /** + * @return The blob pattern to be used as a matcher (never returns + * <code>null</code>) + */ + public String getPattern() { + return pattern; + } + + /** + * Returns <code>true</code> if a match was made. + * + * @param relativeTarget + * Name pattern of the file, relative to the base directory of + * this rule + * @param isDirectory + * Whether the target file is a directory or not + * @return True if a match was made. + */ + public boolean isMatch(String relativeTarget, boolean isDirectory) { + if (relativeTarget == null) + return false; + if (relativeTarget.length() == 0) + return false; + boolean match = matcher.matches(relativeTarget, isDirectory); + return match; + } +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/package-info.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/package-info.java new file mode 100644 index 0000000000..5d133d828a --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/package-info.java @@ -0,0 +1,4 @@ +/** + * Support for reading .gitattributes. + */ +package org.eclipse.jgit.attributes; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java index 706e057480..354a07439a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java @@ -45,13 +45,20 @@ package org.eclipse.jgit.dircache; import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; +import org.eclipse.jgit.util.RawParseUtils; /** * Iterate a {@link DirCache} as part of a <code>TreeWalk</code>. @@ -65,6 +72,10 @@ import org.eclipse.jgit.treewalk.EmptyTreeIterator; * @see org.eclipse.jgit.treewalk.TreeWalk */ public class DirCacheIterator extends AbstractTreeIterator { + /** Byte array holding ".gitattributes" string */ + private static final byte[] DOT_GIT_ATTRIBUTES_BYTES = Constants.DOT_GIT_ATTRIBUTES + .getBytes(); + /** The cache this iterator was created to walk. */ protected final DirCache cache; @@ -92,6 +103,9 @@ public class DirCacheIterator extends AbstractTreeIterator { /** The subtree containing {@link #currentEntry} if this is first entry. */ protected DirCacheTree currentSubtree; + /** Holds an {@link AttributesNode} for the current entry */ + private AttributesNode attributesNode; + /** * Create a new iterator for an already loaded DirCache instance. * <p> @@ -254,6 +268,10 @@ public class DirCacheIterator extends AbstractTreeIterator { path = cep; pathLen = cep.length; currentSubtree = null; + // Checks if this entry is a .gitattributes file + if (RawParseUtils.match(path, pathOffset, DOT_GIT_ATTRIBUTES_BYTES) == path.length) + attributesNode = new LazyLoadingAttributesNode( + currentEntry.getObjectId()); } /** @@ -265,4 +283,50 @@ public class DirCacheIterator extends AbstractTreeIterator { public DirCacheEntry getDirCacheEntry() { return currentSubtree == null ? currentEntry : null; } + + /** + * Retrieves the {@link AttributesNode} for the current entry. + * + * @param reader + * {@link ObjectReader} used to parse the .gitattributes entry. + * @return {@link AttributesNode} for the current entry. + * @throws IOException + * @since 3.7 + */ + public AttributesNode getEntryAttributesNode(ObjectReader reader) + throws IOException { + if (attributesNode instanceof LazyLoadingAttributesNode) + attributesNode = ((LazyLoadingAttributesNode) attributesNode) + .load(reader); + return attributesNode; + } + + /** + * {@link AttributesNode} implementation that provides lazy loading + * facilities. + */ + private static class LazyLoadingAttributesNode extends AttributesNode { + final ObjectId objectId; + + LazyLoadingAttributesNode(ObjectId objectId) { + super(Collections.<AttributesRule> emptyList()); + this.objectId = objectId; + + } + + AttributesNode load(ObjectReader reader) throws IOException { + AttributesNode r = new AttributesNode(); + ObjectLoader loader = reader.open(objectId); + if (loader != null) { + InputStream in = loader.openStream(); + try { + r.parse(in); + } finally { + in.close(); + } + } + return r.getRules().isEmpty() ? null : r; + } + } + } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java index 02863bd16a..2303ffd6d6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java @@ -43,7 +43,7 @@ package org.eclipse.jgit.ignore; import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing; - +import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH; import org.eclipse.jgit.errors.InvalidPatternException; import org.eclipse.jgit.ignore.internal.IMatcher; import org.eclipse.jgit.ignore.internal.PathMatcher; @@ -63,8 +63,6 @@ public class FastIgnoreRule { */ public static final char PATH_SEPARATOR = '/'; - private static final NoResultMatcher NO_MATCH = new NoResultMatcher(); - private final IMatcher matcher; private final boolean inverse; @@ -214,16 +212,4 @@ public class FastIgnoreRule { return false; return matcher.equals(other.matcher); } - - static final class NoResultMatcher implements IMatcher { - - public boolean matches(String path, boolean assumeDirectory) { - return false; - } - - public boolean matches(String segment, int startIncl, int endExcl, - boolean assumeDirectory) { - return false; - } - } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java index 10b5e49e1f..8bb4dfb564 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java @@ -50,6 +50,20 @@ package org.eclipse.jgit.ignore.internal; public interface IMatcher { /** + * Matcher that does not match any pattern. + */ + public static final IMatcher NO_MATCH = new IMatcher() { + public boolean matches(String path, boolean assumeDirectory) { + return false; + } + + public boolean matches(String segment, int startIncl, int endExcl, + boolean assumeDirectory) { + return false; + } + }; + + /** * Matches entire given string * * @param path diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index ccbfed720a..8a2080bac8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -113,6 +113,13 @@ public class ConfigConstants { /** The "excludesfile" key */ public static final String CONFIG_KEY_EXCLUDESFILE = "excludesfile"; + /** + * The "attributesfile" key + * + * @since 3.7 + */ + public static final String CONFIG_KEY_ATTRIBUTESFILE = "attributesfile"; + /** The "filemode" key */ public static final String CONFIG_KEY_FILEMODE = "filemode"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index f149749843..705d54cfa3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -356,6 +356,13 @@ public final class Constants { /** A bare repository typically ends with this string */ public static final String DOT_GIT_EXT = ".git"; + /** + * Name of the attributes file + * + * @since 3.7 + */ + public static final String DOT_GIT_ATTRIBUTES = ".gitattributes"; + /** Name of the ignore file */ public static final String DOT_GIT_IGNORE = ".gitignore"; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java index 8f31d96de6..5a7634a6f1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java @@ -1,4 +1,5 @@ /* + * Copyright (C) 2013, Gunnar Wagenknecht * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> * Copyright (C) 2009, Christian Halstrick <christian.halstrick@sap.com> * Copyright (C) 2009, Google Inc. @@ -101,6 +102,8 @@ public class CoreConfig { private final String excludesfile; + private final String attributesfile; + /** * Options for symlink handling * @@ -136,6 +139,8 @@ public class CoreConfig { ConfigConstants.CONFIG_KEY_LOGALLREFUPDATES, true); excludesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_EXCLUDESFILE); + attributesfile = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, + null, ConfigConstants.CONFIG_KEY_ATTRIBUTESFILE); } /** @@ -165,4 +170,12 @@ public class CoreConfig { public String getExcludesFile() { return excludesfile; } + + /** + * @return path of attributesfile + * @since 3.7 + */ + public String getAttributesFile() { + return attributesfile; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 6311da6b68..3838149a4f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -63,6 +63,8 @@ import java.util.Collections; import java.util.Comparator; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -133,6 +135,9 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { /** If there is a .gitignore file present, the parsed rules from it. */ private IgnoreNode ignoreNode; + /** If there is a .gitattributes file present, the parsed rules from it. */ + private AttributesNode attributesNode; + /** Repository that is the root level being iterated over */ protected Repository repository; @@ -143,6 +148,19 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { private int contentIdOffset; /** + * Holds the {@link AttributesNode} that is stored in + * $GIT_DIR/info/attributes file. + */ + private AttributesNode infoAttributeNode; + + /** + * Holds the {@link AttributesNode} that is stored in global attribute file. + * + * @see CoreConfig#getAttributesFile() + */ + private AttributesNode globalAttributeNode; + + /** * Create a new iterator with no parent. * * @param options @@ -185,6 +203,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { protected WorkingTreeIterator(final WorkingTreeIterator p) { super(p); state = p.state; + infoAttributeNode = p.infoAttributeNode; + globalAttributeNode = p.globalAttributeNode; } /** @@ -204,6 +224,10 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { else entry = null; ignoreNode = new RootIgnoreNode(entry, repo); + + infoAttributeNode = new InfoAttributesNode(repo); + + globalAttributeNode = new GlobalAttributesNode(repo); } /** @@ -626,6 +650,56 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { return ignoreNode; } + /** + * Retrieves the {@link AttributesNode} for the current entry. + * + * @return {@link AttributesNode} for the current entry. + * @throws IOException + * if an error is raised while parsing the .gitattributes file + * @since 3.7 + */ + public AttributesNode getEntryAttributesNode() throws IOException { + if (attributesNode instanceof PerDirectoryAttributesNode) + attributesNode = ((PerDirectoryAttributesNode) attributesNode) + .load(); + return attributesNode; + } + + /** + * Retrieves the {@link AttributesNode} that holds the information located + * in $GIT_DIR/info/attributes file. + * + * @return the {@link AttributesNode} that holds the information located in + * $GIT_DIR/info/attributes file. + * @throws IOException + * if an error is raised while parsing the attributes file + * @since 3.7 + */ + public AttributesNode getInfoAttributesNode() throws IOException { + if (infoAttributeNode instanceof InfoAttributesNode) + infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load(); + return infoAttributeNode; + } + + /** + * Retrieves the {@link AttributesNode} that holds the information located + * in system-wide file. + * + * @return the {@link AttributesNode} that holds the information located in + * system-wide file. + * @throws IOException + * IOException if an error is raised while parsing the + * attributes file + * @see CoreConfig#getAttributesFile() + * @since 3.7 + */ + public AttributesNode getGlobalAttributesNode() throws IOException { + if (globalAttributeNode instanceof GlobalAttributesNode) + globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode) + .load(); + return globalAttributeNode; + } + private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() { public int compare(final Entry o1, final Entry o2) { final byte[] a = o1.encodedName; @@ -679,6 +753,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { continue; if (Constants.DOT_GIT_IGNORE.equals(name)) ignoreNode = new PerDirectoryIgnoreNode(e); + if (Constants.DOT_GIT_ATTRIBUTES.equals(name)) + attributesNode = new PerDirectoryAttributesNode(e); if (i != o) entries[o] = e; e.encodeName(nameEncoder); @@ -1223,6 +1299,90 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } + /** Magic type indicating we know rules exist, but they aren't loaded. */ + private static class PerDirectoryAttributesNode extends AttributesNode { + final Entry entry; + + PerDirectoryAttributesNode(Entry entry) { + super(Collections.<AttributesRule> emptyList()); + this.entry = entry; + } + + AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + InputStream in = entry.openInputStream(); + try { + r.parse(in); + } finally { + in.close(); + } + return r.getRules().isEmpty() ? null : r; + } + } + + /** + * Attributes node loaded from global system-wide file. + */ + private static class GlobalAttributesNode extends AttributesNode { + final Repository repository; + + GlobalAttributesNode(Repository repository) { + this.repository = repository; + } + + AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + String path = repository.getConfig().get(CoreConfig.KEY) + .getAttributesFile(); + if (path != null) { + File attributesFile; + if (path.startsWith("~/")) //$NON-NLS-1$ + attributesFile = fs.resolve(fs.userHome(), + path.substring(2)); + else + attributesFile = fs.resolve(null, path); + loadRulesFromFile(r, attributesFile); + } + return r.getRules().isEmpty() ? null : r; + } + } + + /** Magic type indicating there may be rules for the top level. */ + private static class InfoAttributesNode extends AttributesNode { + final Repository repository; + + InfoAttributesNode(Repository repository) { + this.repository = repository; + } + + AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + + File attributes = fs.resolve(repository.getDirectory(), + "info/attributes"); //$NON-NLS-1$ + loadRulesFromFile(r, attributes); + + return r.getRules().isEmpty() ? null : r; + } + + } + + private static void loadRulesFromFile(AttributesNode r, File attrs) + throws FileNotFoundException, IOException { + if (attrs.exists()) { + FileInputStream in = new FileInputStream(attrs); + try { + r.parse(in); + } finally { + in.close(); + } + } + } + private static final class IteratorState { /** Options used to process the working tree. */ final WorkingTreeOptions options; |