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