FastIgnoreRule.java

  1. /*
  2.  * Copyright (C) 2014, 2021 Andrey Loskutov <loskutov@gmx.de> and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */
  10. package org.eclipse.jgit.ignore;

  11. import static org.eclipse.jgit.ignore.IMatcher.NO_MATCH;
  12. import static org.eclipse.jgit.ignore.internal.Strings.isDirectoryPattern;
  13. import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing;
  14. import static org.eclipse.jgit.ignore.internal.Strings.stripTrailingWhitespace;

  15. import java.text.MessageFormat;

  16. import org.eclipse.jgit.errors.InvalidPatternException;
  17. import org.eclipse.jgit.ignore.internal.PathMatcher;
  18. import org.eclipse.jgit.internal.JGitText;
  19. import org.slf4j.Logger;
  20. import org.slf4j.LoggerFactory;

  21. /**
  22.  * "Fast" (compared with IgnoreRule) git ignore rule implementation supporting
  23.  * also double star {@code **} pattern.
  24.  * <p>
  25.  * This class is immutable and thread safe.
  26.  *
  27.  * @since 3.6
  28.  */
  29. public class FastIgnoreRule {
  30.     private static final Logger LOG = LoggerFactory
  31.             .getLogger(FastIgnoreRule.class);

  32.     /**
  33.      * Character used as default path separator for ignore entries
  34.      */
  35.     public static final char PATH_SEPARATOR = '/';

  36.     private IMatcher matcher;

  37.     private boolean inverse;

  38.     private boolean dirOnly;

  39.     /**
  40.      * Constructor for FastIgnoreRule
  41.      *
  42.      * @param pattern
  43.      *            ignore pattern as described in <a href=
  44.      *            "https://www.kernel.org/pub/software/scm/git/docs/gitignore.html"
  45.      *            >git manual</a>. If pattern is invalid or is not a pattern
  46.      *            (comment), this rule doesn't match anything.
  47.      */
  48.     public FastIgnoreRule(String pattern) {
  49.         this();
  50.         try {
  51.             parse(pattern);
  52.         } catch (InvalidPatternException e) {
  53.             LOG.error(MessageFormat.format(JGitText.get().badIgnorePattern,
  54.                     e.getPattern()), e);
  55.         }
  56.     }

  57.     FastIgnoreRule() {
  58.         matcher = IMatcher.NO_MATCH;
  59.     }

  60.     void parse(String pattern) throws InvalidPatternException {
  61.         if (pattern == null) {
  62.             throw new IllegalArgumentException("Pattern must not be null!"); //$NON-NLS-1$
  63.         }
  64.         if (pattern.length() == 0) {
  65.             dirOnly = false;
  66.             inverse = false;
  67.             this.matcher = NO_MATCH;
  68.             return;
  69.         }
  70.         inverse = pattern.charAt(0) == '!';
  71.         if (inverse) {
  72.             pattern = pattern.substring(1);
  73.             if (pattern.length() == 0) {
  74.                 dirOnly = false;
  75.                 this.matcher = NO_MATCH;
  76.                 return;
  77.             }
  78.         }
  79.         if (pattern.charAt(0) == '#') {
  80.             this.matcher = NO_MATCH;
  81.             dirOnly = false;
  82.             return;
  83.         }
  84.         if (pattern.charAt(0) == '\\' && pattern.length() > 1) {
  85.             char next = pattern.charAt(1);
  86.             if (next == '!' || next == '#') {
  87.                 // remove backslash escaping first special characters
  88.                 pattern = pattern.substring(1);
  89.             }
  90.         }
  91.         dirOnly = isDirectoryPattern(pattern);
  92.         if (dirOnly) {
  93.             pattern = stripTrailingWhitespace(pattern);
  94.             pattern = stripTrailing(pattern, PATH_SEPARATOR);
  95.             if (pattern.length() == 0) {
  96.                 this.matcher = NO_MATCH;
  97.                 return;
  98.             }
  99.         }
  100.         this.matcher = PathMatcher.createPathMatcher(pattern,
  101.                 Character.valueOf(PATH_SEPARATOR), dirOnly);
  102.     }

  103.     /**
  104.      * Returns true if a match was made. <br>
  105.      * This function does NOT return the actual ignore status of the target!
  106.      * Please consult {@link #getResult()} for the negation status. The actual
  107.      * ignore status may be true or false depending on whether this rule is an
  108.      * ignore rule or a negation rule.
  109.      *
  110.      * @param path
  111.      *            Name pattern of the file, relative to the base directory of
  112.      *            this rule
  113.      * @param directory
  114.      *            Whether the target file is a directory or not
  115.      * @return True if a match was made. This does not necessarily mean that the
  116.      *         target is ignored. Call {@link #getResult() getResult()} for the
  117.      *         result.
  118.      */
  119.     public boolean isMatch(String path, boolean directory) {
  120.         return isMatch(path, directory, false);
  121.     }

  122.     /**
  123.      * Returns true if a match was made. <br>
  124.      * This function does NOT return the actual ignore status of the target!
  125.      * Please consult {@link #getResult()} for the negation status. The actual
  126.      * ignore status may be true or false depending on whether this rule is an
  127.      * ignore rule or a negation rule.
  128.      *
  129.      * @param path
  130.      *            Name pattern of the file, relative to the base directory of
  131.      *            this rule
  132.      * @param directory
  133.      *            Whether the target file is a directory or not
  134.      * @param pathMatch
  135.      *            {@code true} if the match is for the full path: see
  136.      *            {@link IMatcher#matches(String, int, int)}
  137.      * @return True if a match was made. This does not necessarily mean that the
  138.      *         target is ignored. Call {@link #getResult() getResult()} for the
  139.      *         result.
  140.      * @since 4.11
  141.      */
  142.     public boolean isMatch(String path, boolean directory, boolean pathMatch) {
  143.         if (path == null)
  144.             return false;
  145.         if (path.length() == 0)
  146.             return false;
  147.         boolean match = matcher.matches(path, directory, pathMatch);
  148.         return match;
  149.     }

  150.     /**
  151.      * Whether the pattern is just a file name and not a path
  152.      *
  153.      * @return {@code true} if the pattern is just a file name and not a path
  154.      */
  155.     public boolean getNameOnly() {
  156.         return !(matcher instanceof PathMatcher);
  157.     }

  158.     /**
  159.      * Whether the pattern should match directories only
  160.      *
  161.      * @return {@code true} if the pattern should match directories only
  162.      */
  163.     public boolean dirOnly() {
  164.         return dirOnly;
  165.     }

  166.     /**
  167.      * Indicates whether the rule is non-negation or negation.
  168.      *
  169.      * @return True if the pattern had a "!" in front of it
  170.      */
  171.     public boolean getNegation() {
  172.         return inverse;
  173.     }

  174.     /**
  175.      * Indicates whether the rule is non-negation or negation.
  176.      *
  177.      * @return True if the target is to be ignored, false otherwise.
  178.      */
  179.     public boolean getResult() {
  180.         return !inverse;
  181.     }

  182.     /**
  183.      * Whether the rule never matches
  184.      *
  185.      * @return {@code true} if the rule never matches (comment line or broken
  186.      *         pattern)
  187.      * @since 4.1
  188.      */
  189.     public boolean isEmpty() {
  190.         return matcher == NO_MATCH;
  191.     }

  192.     /** {@inheritDoc} */
  193.     @Override
  194.     public String toString() {
  195.         StringBuilder sb = new StringBuilder();
  196.         if (inverse)
  197.             sb.append('!');
  198.         sb.append(matcher);
  199.         if (dirOnly)
  200.             sb.append(PATH_SEPARATOR);
  201.         return sb.toString();

  202.     }

  203.     /** {@inheritDoc} */
  204.     @Override
  205.     public int hashCode() {
  206.         final int prime = 31;
  207.         int result = 1;
  208.         result = prime * result + (inverse ? 1231 : 1237);
  209.         result = prime * result + (dirOnly ? 1231 : 1237);
  210.         result = prime * result + ((matcher == null) ? 0 : matcher.hashCode());
  211.         return result;
  212.     }

  213.     /** {@inheritDoc} */
  214.     @Override
  215.     public boolean equals(Object obj) {
  216.         if (this == obj)
  217.             return true;
  218.         if (!(obj instanceof FastIgnoreRule))
  219.             return false;

  220.         FastIgnoreRule other = (FastIgnoreRule) obj;
  221.         if (inverse != other.inverse)
  222.             return false;
  223.         if (dirOnly != other.dirOnly)
  224.             return false;
  225.         return matcher.equals(other.matcher);
  226.     }
  227. }