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