FastIgnoreRule.java

  1. /*
  2.  * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
  3.  * and other copyright owners as documented in the project's IP log.
  4.  *
  5.  * This program and the accompanying materials are made available
  6.  * under the terms of the Eclipse Distribution License v1.0 which
  7.  * accompanies this distribution, is reproduced below, and is
  8.  * available at http://www.eclipse.org/org/documents/edl-v10.php
  9.  *
  10.  * All rights reserved.
  11.  *
  12.  * Redistribution and use in source and binary forms, with or
  13.  * without modification, are permitted provided that the following
  14.  * conditions are met:
  15.  *
  16.  * - Redistributions of source code must retain the above copyright
  17.  *   notice, this list of conditions and the following disclaimer.
  18.  *
  19.  * - Redistributions in binary form must reproduce the above
  20.  *   copyright notice, this list of conditions and the following
  21.  *   disclaimer in the documentation and/or other materials provided
  22.  *   with the distribution.
  23.  *
  24.  * - Neither the name of the Eclipse Foundation, Inc. nor the
  25.  *   names of its contributors may be used to endorse or promote
  26.  *   products derived from this software without specific prior
  27.  *   written permission.
  28.  *
  29.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30.  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31.  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32.  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35.  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36.  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37.  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38.  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39.  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41.  * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42.  */
  43. package org.eclipse.jgit.ignore;

  44. import static org.eclipse.jgit.ignore.internal.IMatcher.NO_MATCH;
  45. import static org.eclipse.jgit.ignore.internal.Strings.isDirectoryPattern;
  46. import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing;
  47. import static org.eclipse.jgit.ignore.internal.Strings.stripTrailingWhitespace;

  48. import org.eclipse.jgit.errors.InvalidPatternException;
  49. import org.eclipse.jgit.ignore.internal.IMatcher;
  50. import org.eclipse.jgit.ignore.internal.PathMatcher;
  51. import org.slf4j.Logger;
  52. import org.slf4j.LoggerFactory;

  53. /**
  54.  * "Fast" (compared with IgnoreRule) git ignore rule implementation supporting
  55.  * also double star {@code **} pattern.
  56.  * <p>
  57.  * This class is immutable and thread safe.
  58.  *
  59.  * @since 3.6
  60.  */
  61. public class FastIgnoreRule {
  62.     private final static Logger LOG = LoggerFactory
  63.             .getLogger(FastIgnoreRule.class);

  64.     /**
  65.      * Character used as default path separator for ignore entries
  66.      */
  67.     public static final char PATH_SEPARATOR = '/';

  68.     private final IMatcher matcher;

  69.     private final boolean inverse;

  70.     private final boolean dirOnly;

  71.     /**
  72.      * Constructor for FastIgnoreRule
  73.      *
  74.      * @param pattern
  75.      *            ignore pattern as described in <a href=
  76.      *            "https://www.kernel.org/pub/software/scm/git/docs/gitignore.html"
  77.      *            >git manual</a>. If pattern is invalid or is not a pattern
  78.      *            (comment), this rule doesn't match anything.
  79.      */
  80.     public FastIgnoreRule(String pattern) {
  81.         if (pattern == null)
  82.             throw new IllegalArgumentException("Pattern must not be null!"); //$NON-NLS-1$
  83.         if (pattern.length() == 0) {
  84.             dirOnly = false;
  85.             inverse = false;
  86.             this.matcher = NO_MATCH;
  87.             return;
  88.         }
  89.         inverse = pattern.charAt(0) == '!';
  90.         if (inverse) {
  91.             pattern = pattern.substring(1);
  92.             if (pattern.length() == 0) {
  93.                 dirOnly = false;
  94.                 this.matcher = NO_MATCH;
  95.                 return;
  96.             }
  97.         }
  98.         if (pattern.charAt(0) == '#') {
  99.             this.matcher = NO_MATCH;
  100.             dirOnly = false;
  101.             return;
  102.         }
  103.         if (pattern.charAt(0) == '\\' && pattern.length() > 1) {
  104.             char next = pattern.charAt(1);
  105.             if (next == '!' || next == '#') {
  106.                 // remove backslash escaping first special characters
  107.                 pattern = pattern.substring(1);
  108.             }
  109.         }
  110.         dirOnly = isDirectoryPattern(pattern);
  111.         if (dirOnly) {
  112.             pattern = stripTrailingWhitespace(pattern);
  113.             pattern = stripTrailing(pattern, PATH_SEPARATOR);
  114.             if (pattern.length() == 0) {
  115.                 this.matcher = NO_MATCH;
  116.                 return;
  117.             }
  118.         }
  119.         IMatcher m;
  120.         try {
  121.             m = PathMatcher.createPathMatcher(pattern,
  122.                     Character.valueOf(PATH_SEPARATOR), dirOnly);
  123.         } catch (InvalidPatternException e) {
  124.             m = NO_MATCH;
  125.             LOG.error(e.getMessage(), e);
  126.         }
  127.         this.matcher = m;
  128.     }

  129.     /**
  130.      * Returns true if a match was made. <br>
  131.      * This function does NOT return the actual ignore status of the target!
  132.      * Please consult {@link #getResult()} for the negation status. The actual
  133.      * ignore status may be true or false depending on whether this rule is an
  134.      * ignore rule or a negation rule.
  135.      *
  136.      * @param path
  137.      *            Name pattern of the file, relative to the base directory of
  138.      *            this rule
  139.      * @param directory
  140.      *            Whether the target file is a directory or not
  141.      * @return True if a match was made. This does not necessarily mean that the
  142.      *         target is ignored. Call {@link #getResult() getResult()} for the
  143.      *         result.
  144.      */
  145.     public boolean isMatch(String path, boolean directory) {
  146.         return isMatch(path, directory, false);
  147.     }

  148.     /**
  149.      * Returns true if a match was made. <br>
  150.      * This function does NOT return the actual ignore status of the target!
  151.      * Please consult {@link #getResult()} for the negation status. The actual
  152.      * ignore status may be true or false depending on whether this rule is an
  153.      * ignore rule or a negation rule.
  154.      *
  155.      * @param path
  156.      *            Name pattern of the file, relative to the base directory of
  157.      *            this rule
  158.      * @param directory
  159.      *            Whether the target file is a directory or not
  160.      * @param pathMatch
  161.      *            {@code true} if the match is for the full path: see
  162.      *            {@link IMatcher#matches(String, int, int)}
  163.      * @return True if a match was made. This does not necessarily mean that the
  164.      *         target is ignored. Call {@link #getResult() getResult()} for the
  165.      *         result.
  166.      * @since 4.11
  167.      */
  168.     public boolean isMatch(String path, boolean directory, boolean pathMatch) {
  169.         if (path == null)
  170.             return false;
  171.         if (path.length() == 0)
  172.             return false;
  173.         boolean match = matcher.matches(path, directory, pathMatch);
  174.         return match;
  175.     }

  176.     /**
  177.      * Whether the pattern is just a file name and not a path
  178.      *
  179.      * @return {@code true} if the pattern is just a file name and not a path
  180.      */
  181.     public boolean getNameOnly() {
  182.         return !(matcher instanceof PathMatcher);
  183.     }

  184.     /**
  185.      * Whether the pattern should match directories only
  186.      *
  187.      * @return {@code true} if the pattern should match directories only
  188.      */
  189.     public boolean dirOnly() {
  190.         return dirOnly;
  191.     }

  192.     /**
  193.      * Indicates whether the rule is non-negation or negation.
  194.      *
  195.      * @return True if the pattern had a "!" in front of it
  196.      */
  197.     public boolean getNegation() {
  198.         return inverse;
  199.     }

  200.     /**
  201.      * Indicates whether the rule is non-negation or negation.
  202.      *
  203.      * @return True if the target is to be ignored, false otherwise.
  204.      */
  205.     public boolean getResult() {
  206.         return !inverse;
  207.     }

  208.     /**
  209.      * Whether the rule never matches
  210.      *
  211.      * @return {@code true} if the rule never matches (comment line or broken
  212.      *         pattern)
  213.      * @since 4.1
  214.      */
  215.     public boolean isEmpty() {
  216.         return matcher == NO_MATCH;
  217.     }

  218.     /** {@inheritDoc} */
  219.     @Override
  220.     public String toString() {
  221.         StringBuilder sb = new StringBuilder();
  222.         if (inverse)
  223.             sb.append('!');
  224.         sb.append(matcher);
  225.         if (dirOnly)
  226.             sb.append(PATH_SEPARATOR);
  227.         return sb.toString();

  228.     }

  229.     /** {@inheritDoc} */
  230.     @Override
  231.     public int hashCode() {
  232.         final int prime = 31;
  233.         int result = 1;
  234.         result = prime * result + (inverse ? 1231 : 1237);
  235.         result = prime * result + (dirOnly ? 1231 : 1237);
  236.         result = prime * result + ((matcher == null) ? 0 : matcher.hashCode());
  237.         return result;
  238.     }

  239.     /** {@inheritDoc} */
  240.     @Override
  241.     public boolean equals(Object obj) {
  242.         if (this == obj)
  243.             return true;
  244.         if (!(obj instanceof FastIgnoreRule))
  245.             return false;

  246.         FastIgnoreRule other = (FastIgnoreRule) obj;
  247.         if (inverse != other.inverse)
  248.             return false;
  249.         if (dirOnly != other.dirOnly)
  250.             return false;
  251.         return matcher.equals(other.matcher);
  252.     }
  253. }