IgnoreNode.java

  1. /*
  2.  * Copyright (C) 2010, 2021 Red Hat Inc. 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 java.nio.charset.StandardCharsets.UTF_8;

  12. import java.io.BufferedReader;
  13. import java.io.IOException;
  14. import java.io.InputStream;
  15. import java.io.InputStreamReader;
  16. import java.text.MessageFormat;
  17. import java.util.ArrayList;
  18. import java.util.Collections;
  19. import java.util.List;

  20. import org.eclipse.jgit.annotations.Nullable;
  21. import org.eclipse.jgit.errors.InvalidPatternException;
  22. import org.eclipse.jgit.internal.JGitText;
  23. import org.slf4j.Logger;
  24. import org.slf4j.LoggerFactory;

  25. /**
  26.  * Represents a bundle of ignore rules inherited from a base directory.
  27.  *
  28.  * This class is not thread safe, it maintains state about the last match.
  29.  */
  30. public class IgnoreNode {

  31.     private static final Logger LOG = LoggerFactory.getLogger(IgnoreNode.class);

  32.     /** Result from {@link IgnoreNode#isIgnored(String, boolean)}. */
  33.     public enum MatchResult {
  34.         /** The file is not ignored, due to a rule saying its not ignored. */
  35.         NOT_IGNORED,

  36.         /** The file is ignored due to a rule in this node. */
  37.         IGNORED,

  38.         /** The ignore status is unknown, check inherited rules. */
  39.         CHECK_PARENT,

  40.         /**
  41.          * The first previous (parent) ignore rule match (if any) should be
  42.          * negated, and then inherited rules applied.
  43.          *
  44.          * @since 3.6
  45.          */
  46.         CHECK_PARENT_NEGATE_FIRST_MATCH;
  47.     }

  48.     /** The rules that have been parsed into this node. */
  49.     private final List<FastIgnoreRule> rules;

  50.     /**
  51.      * Create an empty ignore node with no rules.
  52.      */
  53.     public IgnoreNode() {
  54.         this(new ArrayList<>());
  55.     }

  56.     /**
  57.      * Create an ignore node with given rules.
  58.      *
  59.      * @param rules
  60.      *            list of rules.
  61.      */
  62.     public IgnoreNode(List<FastIgnoreRule> rules) {
  63.         this.rules = rules;
  64.     }

  65.     /**
  66.      * Parse files according to gitignore standards.
  67.      *
  68.      * @param in
  69.      *            input stream holding the standard ignore format. The caller is
  70.      *            responsible for closing the stream.
  71.      * @throws java.io.IOException
  72.      *             Error thrown when reading an ignore file.
  73.      */
  74.     public void parse(InputStream in) throws IOException {
  75.         parse(null, in);
  76.     }

  77.     /**
  78.      * Parse files according to gitignore standards.
  79.      *
  80.      * @param sourceName
  81.      *            identifying the source of the stream
  82.      * @param in
  83.      *            input stream holding the standard ignore format. The caller is
  84.      *            responsible for closing the stream.
  85.      * @throws java.io.IOException
  86.      *             Error thrown when reading an ignore file.
  87.      * @since 5.11
  88.      */
  89.     public void parse(String sourceName, InputStream in) throws IOException {
  90.         BufferedReader br = asReader(in);
  91.         String txt;
  92.         int lineNumber = 1;
  93.         while ((txt = br.readLine()) != null) {
  94.             if (txt.length() > 0 && !txt.startsWith("#") && !txt.equals("/")) { //$NON-NLS-1$ //$NON-NLS-2$
  95.                 FastIgnoreRule rule = new FastIgnoreRule();
  96.                 try {
  97.                     rule.parse(txt);
  98.                 } catch (InvalidPatternException e) {
  99.                     if (sourceName != null) {
  100.                         LOG.error(MessageFormat.format(
  101.                                 JGitText.get().badIgnorePatternFull, sourceName,
  102.                                 Integer.toString(lineNumber), e.getPattern(),
  103.                                 e.getLocalizedMessage()), e);
  104.                     } else {
  105.                         LOG.error(MessageFormat.format(
  106.                                 JGitText.get().badIgnorePattern,
  107.                                 e.getPattern()), e);
  108.                     }
  109.                 }
  110.                 if (!rule.isEmpty()) {
  111.                     rules.add(rule);
  112.                 }
  113.             }
  114.             lineNumber++;
  115.         }
  116.     }

  117.     private static BufferedReader asReader(InputStream in) {
  118.         return new BufferedReader(new InputStreamReader(in, UTF_8));
  119.     }

  120.     /**
  121.      * Get list of all ignore rules held by this node
  122.      *
  123.      * @return list of all ignore rules held by this node
  124.      */
  125.     public List<FastIgnoreRule> getRules() {
  126.         return Collections.unmodifiableList(rules);
  127.     }

  128.     /**
  129.      * Determine if an entry path matches an ignore rule.
  130.      *
  131.      * @param entryPath
  132.      *            the path to test. The path must be relative to this ignore
  133.      *            node's own repository path, and in repository path format
  134.      *            (uses '/' and not '\').
  135.      * @param isDirectory
  136.      *            true if the target item is a directory.
  137.      * @return status of the path.
  138.      */
  139.     public MatchResult isIgnored(String entryPath, boolean isDirectory) {
  140.         final Boolean result = checkIgnored(entryPath, isDirectory);
  141.         if (result == null) {
  142.             return MatchResult.CHECK_PARENT;
  143.         }

  144.         return result.booleanValue() ? MatchResult.IGNORED
  145.                 : MatchResult.NOT_IGNORED;
  146.     }

  147.     /**
  148.      * Determine if an entry path matches an ignore rule.
  149.      *
  150.      * @param entryPath
  151.      *            the path to test. The path must be relative to this ignore
  152.      *            node's own repository path, and in repository path format
  153.      *            (uses '/' and not '\').
  154.      * @param isDirectory
  155.      *            true if the target item is a directory.
  156.      * @return Boolean.TRUE, if the entry is ignored; Boolean.FALSE, if the
  157.      *         entry is forced to be not ignored (negated match); or null, if
  158.      *         undetermined
  159.      * @since 4.11
  160.      */
  161.     public @Nullable Boolean checkIgnored(String entryPath,
  162.             boolean isDirectory) {
  163.         // Parse rules in the reverse order that they were read because later
  164.         // rules have higher priority
  165.         for (int i = rules.size() - 1; i > -1; i--) {
  166.             FastIgnoreRule rule = rules.get(i);
  167.             if (rule.isMatch(entryPath, isDirectory, true)) {
  168.                 return Boolean.valueOf(rule.getResult());
  169.             }
  170.         }
  171.         return null;
  172.     }

  173.     /** {@inheritDoc} */
  174.     @Override
  175.     public String toString() {
  176.         return rules.toString();
  177.     }
  178. }