/* * Copyright (C) 2010, 2017 Red Hat Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at * https://www.eclipse.org/org/documents/edl-v10.php. * * SPDX-License-Identifier: BSD-3-Clause */ package org.eclipse.jgit.attributes; import static org.eclipse.jgit.ignore.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.IMatcher; import org.eclipse.jgit.ignore.internal.PathMatcher; /** * A single attributes rule corresponding to one line in a .gitattributes file. * * Inspiration from: {@link org.eclipse.jgit.ignore.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 parseAttributes(String attributesLine) { // the C implementation oddly enough allows \r between attributes too. ArrayList result = new ArrayList<>(); 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; } if (attribute.startsWith("!")) {//$NON-NLS-1$ if (attribute.length() > 1) result.add(new Attribute(attribute.substring(1), State.UNSPECIFIED)); continue; } final int equalsIndex = attribute.indexOf('='); 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 attributes; private final boolean nameOnly; private final boolean dirOnly; private final 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 * null. * @param attributes * the rule attributes. This string will be parsed to read the * attributes. */ public AttributesRule(String pattern, String attributes) { this.attributes = parseAttributes(attributes); if (pattern.endsWith("/")) { //$NON-NLS-1$ pattern = pattern.substring(0, pattern.length() - 1); dirOnly = true; } else { dirOnly = false; } int slashIndex = pattern.indexOf('/'); if (slashIndex < 0) { nameOnly = true; } 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 { candidateMatcher = PathMatcher.createPathMatcher(pattern, Character.valueOf(FastIgnoreRule.PATH_SEPARATOR), dirOnly); } catch (InvalidPatternException e) { // ignore: invalid patterns are silently ignored } this.matcher = candidateMatcher; this.pattern = pattern; } /** * Whether to match directories only * * @return {@code true} if the pattern should match directories only * @since 4.3 */ public boolean isDirOnly() { return dirOnly; } /** * Return the attributes. * * @return an unmodifiable list of attributes (never returns * null) */ public List getAttributes() { return Collections.unmodifiableList(attributes); } /** * Whether the pattern is only a file name and not a path * * @return true if the pattern is just a file name and not a * path */ public boolean isNameOnly() { return nameOnly; } /** * Get the pattern * * @return The blob pattern to be used as a matcher (never returns * null) */ public String getPattern() { return pattern; } /** * Returns true 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, true); return match; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(pattern); for (Attribute a : attributes) { sb.append(" "); //$NON-NLS-1$ sb.append(a); } return sb.toString(); } }