View Javadoc
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  
12  import static java.nio.charset.StandardCharsets.UTF_8;
13  
14  import java.io.BufferedReader;
15  import java.io.IOException;
16  import java.io.InputStream;
17  import java.io.InputStreamReader;
18  import java.text.MessageFormat;
19  import java.util.ArrayList;
20  import java.util.Collections;
21  import java.util.List;
22  
23  import org.eclipse.jgit.annotations.Nullable;
24  import org.eclipse.jgit.errors.InvalidPatternException;
25  import org.eclipse.jgit.internal.JGitText;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  
29  /**
30   * Represents a bundle of ignore rules inherited from a base directory.
31   *
32   * This class is not thread safe, it maintains state about the last match.
33   */
34  public class IgnoreNode {
35  
36  	private static final Logger LOG = LoggerFactory.getLogger(IgnoreNode.class);
37  
38  	/** Result from {@link IgnoreNode#isIgnored(String, boolean)}. */
39  	public enum MatchResult {
40  		/** The file is not ignored, due to a rule saying its not ignored. */
41  		NOT_IGNORED,
42  
43  		/** The file is ignored due to a rule in this node. */
44  		IGNORED,
45  
46  		/** The ignore status is unknown, check inherited rules. */
47  		CHECK_PARENT,
48  
49  		/**
50  		 * The first previous (parent) ignore rule match (if any) should be
51  		 * negated, and then inherited rules applied.
52  		 *
53  		 * @since 3.6
54  		 */
55  		CHECK_PARENT_NEGATE_FIRST_MATCH;
56  	}
57  
58  	/** The rules that have been parsed into this node. */
59  	private final List<FastIgnoreRule> rules;
60  
61  	/**
62  	 * Create an empty ignore node with no rules.
63  	 */
64  	public IgnoreNode() {
65  		this(new ArrayList<>());
66  	}
67  
68  	/**
69  	 * Create an ignore node with given rules.
70  	 *
71  	 * @param rules
72  	 *            list of rules.
73  	 */
74  	public IgnoreNode(List<FastIgnoreRule> rules) {
75  		this.rules = rules;
76  	}
77  
78  	/**
79  	 * Parse files according to gitignore standards.
80  	 *
81  	 * @param in
82  	 *            input stream holding the standard ignore format. The caller is
83  	 *            responsible for closing the stream.
84  	 * @throws java.io.IOException
85  	 *             Error thrown when reading an ignore file.
86  	 */
87  	public void parse(InputStream in) throws IOException {
88  		parse(null, in);
89  	}
90  
91  	/**
92  	 * Parse files according to gitignore standards.
93  	 *
94  	 * @param sourceName
95  	 *            identifying the source of the stream
96  	 * @param in
97  	 *            input stream holding the standard ignore format. The caller is
98  	 *            responsible for closing the stream.
99  	 * @throws java.io.IOException
100 	 *             Error thrown when reading an ignore file.
101 	 * @since 5.11
102 	 */
103 	public void parse(String sourceName, InputStream in) throws IOException {
104 		BufferedReader br = asReader(in);
105 		String txt;
106 		int lineNumber = 1;
107 		while ((txt = br.readLine()) != null) {
108 			if (txt.length() > 0 && !txt.startsWith("#") && !txt.equals("/")) { //$NON-NLS-1$ //$NON-NLS-2$
109 				FastIgnoreRule rule = new FastIgnoreRule();
110 				try {
111 					rule.parse(txt);
112 				} catch (InvalidPatternException e) {
113 					if (sourceName != null) {
114 						LOG.error(MessageFormat.format(
115 								JGitText.get().badIgnorePatternFull, sourceName,
116 								Integer.toString(lineNumber), e.getPattern(),
117 								e.getLocalizedMessage()), e);
118 					} else {
119 						LOG.error(MessageFormat.format(
120 								JGitText.get().badIgnorePattern,
121 								e.getPattern()), e);
122 					}
123 				}
124 				if (!rule.isEmpty()) {
125 					rules.add(rule);
126 				}
127 			}
128 			lineNumber++;
129 		}
130 	}
131 
132 	private static BufferedReader asReader(InputStream in) {
133 		return new BufferedReader(new InputStreamReader(in, UTF_8));
134 	}
135 
136 	/**
137 	 * Get list of all ignore rules held by this node
138 	 *
139 	 * @return list of all ignore rules held by this node
140 	 */
141 	public List<FastIgnoreRule> getRules() {
142 		return Collections.unmodifiableList(rules);
143 	}
144 
145 	/**
146 	 * Determine if an entry path matches an ignore rule.
147 	 *
148 	 * @param entryPath
149 	 *            the path to test. The path must be relative to this ignore
150 	 *            node's own repository path, and in repository path format
151 	 *            (uses '/' and not '\').
152 	 * @param isDirectory
153 	 *            true if the target item is a directory.
154 	 * @return status of the path.
155 	 */
156 	public MatchResult isIgnored(String entryPath, boolean isDirectory) {
157 		final Boolean result = checkIgnored(entryPath, isDirectory);
158 		if (result == null) {
159 			return MatchResult.CHECK_PARENT;
160 		}
161 
162 		return result.booleanValue() ? MatchResult.IGNORED
163 				: MatchResult.NOT_IGNORED;
164 	}
165 
166 	/**
167 	 * Determine if an entry path matches an ignore rule.
168 	 *
169 	 * @param entryPath
170 	 *            the path to test. The path must be relative to this ignore
171 	 *            node's own repository path, and in repository path format
172 	 *            (uses '/' and not '\').
173 	 * @param isDirectory
174 	 *            true if the target item is a directory.
175 	 * @return Boolean.TRUE, if the entry is ignored; Boolean.FALSE, if the
176 	 *         entry is forced to be not ignored (negated match); or null, if
177 	 *         undetermined
178 	 * @since 4.11
179 	 */
180 	public @Nullable Boolean checkIgnored(String entryPath,
181 			boolean isDirectory) {
182 		// Parse rules in the reverse order that they were read because later
183 		// rules have higher priority
184 		for (int i = rules.size() - 1; i > -1; i--) {
185 			FastIgnoreRule rule = rules.get(i);
186 			if (rule.isMatch(entryPath, isDirectory, true)) {
187 				return Boolean.valueOf(rule.getResult());
188 			}
189 		}
190 		return null;
191 	}
192 
193 	/** {@inheritDoc} */
194 	@Override
195 	public String toString() {
196 		return rules.toString();
197 	}
198 }