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