View Javadoc
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  
12  import static org.eclipse.jgit.ignore.IMatcher.NO_MATCH;
13  import static org.eclipse.jgit.ignore.internal.Strings.isDirectoryPattern;
14  import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing;
15  import static org.eclipse.jgit.ignore.internal.Strings.stripTrailingWhitespace;
16  
17  import java.text.MessageFormat;
18  
19  import org.eclipse.jgit.errors.InvalidPatternException;
20  import org.eclipse.jgit.ignore.internal.PathMatcher;
21  import org.eclipse.jgit.internal.JGitText;
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  
25  /**
26   * "Fast" (compared with IgnoreRule) git ignore rule implementation supporting
27   * also double star {@code **} pattern.
28   * <p>
29   * This class is immutable and thread safe.
30   *
31   * @since 3.6
32   */
33  public class FastIgnoreRule {
34  	private static final Logger LOG = LoggerFactory
35  			.getLogger(FastIgnoreRule.class);
36  
37  	/**
38  	 * Character used as default path separator for ignore entries
39  	 */
40  	public static final char PATH_SEPARATOR = '/';
41  
42  	private IMatcher matcher;
43  
44  	private boolean inverse;
45  
46  	private boolean dirOnly;
47  
48  	/**
49  	 * Constructor for FastIgnoreRule
50  	 *
51  	 * @param pattern
52  	 *            ignore pattern as described in <a href=
53  	 *            "https://www.kernel.org/pub/software/scm/git/docs/gitignore.html"
54  	 *            >git manual</a>. If pattern is invalid or is not a pattern
55  	 *            (comment), this rule doesn't match anything.
56  	 */
57  	public FastIgnoreRule(String pattern) {
58  		this();
59  		try {
60  			parse(pattern);
61  		} catch (InvalidPatternException e) {
62  			LOG.error(MessageFormat.format(JGitText.get().badIgnorePattern,
63  					e.getPattern()), e);
64  		}
65  	}
66  
67  	FastIgnoreRule() {
68  		matcher = IMatcher.NO_MATCH;
69  	}
70  
71  	void parse(String pattern) throws InvalidPatternException {
72  		if (pattern == null) {
73  			throw new IllegalArgumentException("Pattern must not be null!"); //$NON-NLS-1$
74  		}
75  		if (pattern.length() == 0) {
76  			dirOnly = false;
77  			inverse = false;
78  			this.matcher = NO_MATCH;
79  			return;
80  		}
81  		inverse = pattern.charAt(0) == '!';
82  		if (inverse) {
83  			pattern = pattern.substring(1);
84  			if (pattern.length() == 0) {
85  				dirOnly = false;
86  				this.matcher = NO_MATCH;
87  				return;
88  			}
89  		}
90  		if (pattern.charAt(0) == '#') {
91  			this.matcher = NO_MATCH;
92  			dirOnly = false;
93  			return;
94  		}
95  		if (pattern.charAt(0) == '\\' && pattern.length() > 1) {
96  			char next = pattern.charAt(1);
97  			if (next == '!' || next == '#') {
98  				// remove backslash escaping first special characters
99  				pattern = pattern.substring(1);
100 			}
101 		}
102 		dirOnly = isDirectoryPattern(pattern);
103 		if (dirOnly) {
104 			pattern = stripTrailingWhitespace(pattern);
105 			pattern = stripTrailing(pattern, PATH_SEPARATOR);
106 			if (pattern.length() == 0) {
107 				this.matcher = NO_MATCH;
108 				return;
109 			}
110 		}
111 		this.matcher = PathMatcher.createPathMatcher(pattern,
112 				Character.valueOf(PATH_SEPARATOR), dirOnly);
113 	}
114 
115 	/**
116 	 * Returns true if a match was made. <br>
117 	 * This function does NOT return the actual ignore status of the target!
118 	 * Please consult {@link #getResult()} for the negation status. The actual
119 	 * ignore status may be true or false depending on whether this rule is an
120 	 * ignore rule or a negation rule.
121 	 *
122 	 * @param path
123 	 *            Name pattern of the file, relative to the base directory of
124 	 *            this rule
125 	 * @param directory
126 	 *            Whether the target file is a directory or not
127 	 * @return True if a match was made. This does not necessarily mean that the
128 	 *         target is ignored. Call {@link #getResult() getResult()} for the
129 	 *         result.
130 	 */
131 	public boolean isMatch(String path, boolean directory) {
132 		return isMatch(path, directory, false);
133 	}
134 
135 	/**
136 	 * Returns true if a match was made. <br>
137 	 * This function does NOT return the actual ignore status of the target!
138 	 * Please consult {@link #getResult()} for the negation status. The actual
139 	 * ignore status may be true or false depending on whether this rule is an
140 	 * ignore rule or a negation rule.
141 	 *
142 	 * @param path
143 	 *            Name pattern of the file, relative to the base directory of
144 	 *            this rule
145 	 * @param directory
146 	 *            Whether the target file is a directory or not
147 	 * @param pathMatch
148 	 *            {@code true} if the match is for the full path: see
149 	 *            {@link IMatcher#matches(String, int, int)}
150 	 * @return True if a match was made. This does not necessarily mean that the
151 	 *         target is ignored. Call {@link #getResult() getResult()} for the
152 	 *         result.
153 	 * @since 4.11
154 	 */
155 	public boolean isMatch(String path, boolean directory, boolean pathMatch) {
156 		if (path == null)
157 			return false;
158 		if (path.length() == 0)
159 			return false;
160 		boolean match = matcher.matches(path, directory, pathMatch);
161 		return match;
162 	}
163 
164 	/**
165 	 * Whether the pattern is just a file name and not a path
166 	 *
167 	 * @return {@code true} if the pattern is just a file name and not a path
168 	 */
169 	public boolean getNameOnly() {
170 		return !(matcher instanceof PathMatcher);
171 	}
172 
173 	/**
174 	 * Whether the pattern should match directories only
175 	 *
176 	 * @return {@code true} if the pattern should match directories only
177 	 */
178 	public boolean dirOnly() {
179 		return dirOnly;
180 	}
181 
182 	/**
183 	 * Indicates whether the rule is non-negation or negation.
184 	 *
185 	 * @return True if the pattern had a "!" in front of it
186 	 */
187 	public boolean getNegation() {
188 		return inverse;
189 	}
190 
191 	/**
192 	 * Indicates whether the rule is non-negation or negation.
193 	 *
194 	 * @return True if the target is to be ignored, false otherwise.
195 	 */
196 	public boolean getResult() {
197 		return !inverse;
198 	}
199 
200 	/**
201 	 * Whether the rule never matches
202 	 *
203 	 * @return {@code true} if the rule never matches (comment line or broken
204 	 *         pattern)
205 	 * @since 4.1
206 	 */
207 	public boolean isEmpty() {
208 		return matcher == NO_MATCH;
209 	}
210 
211 	/** {@inheritDoc} */
212 	@Override
213 	public String toString() {
214 		StringBuilder sb = new StringBuilder();
215 		if (inverse)
216 			sb.append('!');
217 		sb.append(matcher);
218 		if (dirOnly)
219 			sb.append(PATH_SEPARATOR);
220 		return sb.toString();
221 
222 	}
223 
224 	/** {@inheritDoc} */
225 	@Override
226 	public int hashCode() {
227 		final int prime = 31;
228 		int result = 1;
229 		result = prime * result + (inverse ? 1231 : 1237);
230 		result = prime * result + (dirOnly ? 1231 : 1237);
231 		result = prime * result + ((matcher == null) ? 0 : matcher.hashCode());
232 		return result;
233 	}
234 
235 	/** {@inheritDoc} */
236 	@Override
237 	public boolean equals(Object obj) {
238 		if (this == obj)
239 			return true;
240 		if (!(obj instanceof FastIgnoreRule))
241 			return false;
242 
243 		FastIgnoreRule other = (FastIgnoreRule) obj;
244 		if (inverse != other.inverse)
245 			return false;
246 		if (dirOnly != other.dirOnly)
247 			return false;
248 		return matcher.equals(other.matcher);
249 	}
250 }