##// END OF EJS Templates
fileset: actually implement 'minusset'...
Patrick Mezard -
r17363:5d9e2031 stable
parent child Browse files
Show More
@@ -1,465 +1,471 b''
1 # fileset.py - file set queries for mercurial
1 # fileset.py - file set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 import parser, error, util, merge, re
8 import parser, error, util, merge, re
9 from i18n import _
9 from i18n import _
10
10
11 elements = {
11 elements = {
12 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
12 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
13 "-": (5, ("negate", 19), ("minus", 5)),
13 "-": (5, ("negate", 19), ("minus", 5)),
14 "not": (10, ("not", 10)),
14 "not": (10, ("not", 10)),
15 "!": (10, ("not", 10)),
15 "!": (10, ("not", 10)),
16 "and": (5, None, ("and", 5)),
16 "and": (5, None, ("and", 5)),
17 "&": (5, None, ("and", 5)),
17 "&": (5, None, ("and", 5)),
18 "or": (4, None, ("or", 4)),
18 "or": (4, None, ("or", 4)),
19 "|": (4, None, ("or", 4)),
19 "|": (4, None, ("or", 4)),
20 "+": (4, None, ("or", 4)),
20 "+": (4, None, ("or", 4)),
21 ",": (2, None, ("list", 2)),
21 ",": (2, None, ("list", 2)),
22 ")": (0, None, None),
22 ")": (0, None, None),
23 "symbol": (0, ("symbol",), None),
23 "symbol": (0, ("symbol",), None),
24 "string": (0, ("string",), None),
24 "string": (0, ("string",), None),
25 "end": (0, None, None),
25 "end": (0, None, None),
26 }
26 }
27
27
28 keywords = set(['and', 'or', 'not'])
28 keywords = set(['and', 'or', 'not'])
29
29
30 globchars = ".*{}[]?/\\"
30 globchars = ".*{}[]?/\\"
31
31
32 def tokenize(program):
32 def tokenize(program):
33 pos, l = 0, len(program)
33 pos, l = 0, len(program)
34 while pos < l:
34 while pos < l:
35 c = program[pos]
35 c = program[pos]
36 if c.isspace(): # skip inter-token whitespace
36 if c.isspace(): # skip inter-token whitespace
37 pass
37 pass
38 elif c in "(),-|&+!": # handle simple operators
38 elif c in "(),-|&+!": # handle simple operators
39 yield (c, None, pos)
39 yield (c, None, pos)
40 elif (c in '"\'' or c == 'r' and
40 elif (c in '"\'' or c == 'r' and
41 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
41 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
42 if c == 'r':
42 if c == 'r':
43 pos += 1
43 pos += 1
44 c = program[pos]
44 c = program[pos]
45 decode = lambda x: x
45 decode = lambda x: x
46 else:
46 else:
47 decode = lambda x: x.decode('string-escape')
47 decode = lambda x: x.decode('string-escape')
48 pos += 1
48 pos += 1
49 s = pos
49 s = pos
50 while pos < l: # find closing quote
50 while pos < l: # find closing quote
51 d = program[pos]
51 d = program[pos]
52 if d == '\\': # skip over escaped characters
52 if d == '\\': # skip over escaped characters
53 pos += 2
53 pos += 2
54 continue
54 continue
55 if d == c:
55 if d == c:
56 yield ('string', decode(program[s:pos]), s)
56 yield ('string', decode(program[s:pos]), s)
57 break
57 break
58 pos += 1
58 pos += 1
59 else:
59 else:
60 raise error.ParseError(_("unterminated string"), s)
60 raise error.ParseError(_("unterminated string"), s)
61 elif c.isalnum() or c in globchars or ord(c) > 127:
61 elif c.isalnum() or c in globchars or ord(c) > 127:
62 # gather up a symbol/keyword
62 # gather up a symbol/keyword
63 s = pos
63 s = pos
64 pos += 1
64 pos += 1
65 while pos < l: # find end of symbol
65 while pos < l: # find end of symbol
66 d = program[pos]
66 d = program[pos]
67 if not (d.isalnum() or d in globchars or ord(d) > 127):
67 if not (d.isalnum() or d in globchars or ord(d) > 127):
68 break
68 break
69 pos += 1
69 pos += 1
70 sym = program[s:pos]
70 sym = program[s:pos]
71 if sym in keywords: # operator keywords
71 if sym in keywords: # operator keywords
72 yield (sym, None, s)
72 yield (sym, None, s)
73 else:
73 else:
74 yield ('symbol', sym, s)
74 yield ('symbol', sym, s)
75 pos -= 1
75 pos -= 1
76 else:
76 else:
77 raise error.ParseError(_("syntax error"), pos)
77 raise error.ParseError(_("syntax error"), pos)
78 pos += 1
78 pos += 1
79 yield ('end', None, pos)
79 yield ('end', None, pos)
80
80
81 parse = parser.parser(tokenize, elements).parse
81 parse = parser.parser(tokenize, elements).parse
82
82
83 def getstring(x, err):
83 def getstring(x, err):
84 if x and (x[0] == 'string' or x[0] == 'symbol'):
84 if x and (x[0] == 'string' or x[0] == 'symbol'):
85 return x[1]
85 return x[1]
86 raise error.ParseError(err)
86 raise error.ParseError(err)
87
87
88 def getset(mctx, x):
88 def getset(mctx, x):
89 if not x:
89 if not x:
90 raise error.ParseError(_("missing argument"))
90 raise error.ParseError(_("missing argument"))
91 return methods[x[0]](mctx, *x[1:])
91 return methods[x[0]](mctx, *x[1:])
92
92
93 def stringset(mctx, x):
93 def stringset(mctx, x):
94 m = mctx.matcher([x])
94 m = mctx.matcher([x])
95 return [f for f in mctx.subset if m(f)]
95 return [f for f in mctx.subset if m(f)]
96
96
97 def andset(mctx, x, y):
97 def andset(mctx, x, y):
98 return getset(mctx.narrow(getset(mctx, x)), y)
98 return getset(mctx.narrow(getset(mctx, x)), y)
99
99
100 def orset(mctx, x, y):
100 def orset(mctx, x, y):
101 # needs optimizing
101 # needs optimizing
102 xl = getset(mctx, x)
102 xl = getset(mctx, x)
103 yl = getset(mctx, y)
103 yl = getset(mctx, y)
104 return xl + [f for f in yl if f not in xl]
104 return xl + [f for f in yl if f not in xl]
105
105
106 def notset(mctx, x):
106 def notset(mctx, x):
107 s = set(getset(mctx, x))
107 s = set(getset(mctx, x))
108 return [r for r in mctx.subset if r not in s]
108 return [r for r in mctx.subset if r not in s]
109
109
110 def minusset(mctx, x, y):
111 xl = getset(mctx, x)
112 yl = set(getset(mctx, y))
113 return [f for f in xl if f not in yl]
114
110 def listset(mctx, a, b):
115 def listset(mctx, a, b):
111 raise error.ParseError(_("can't use a list in this context"))
116 raise error.ParseError(_("can't use a list in this context"))
112
117
113 def modified(mctx, x):
118 def modified(mctx, x):
114 """``modified()``
119 """``modified()``
115 File that is modified according to status.
120 File that is modified according to status.
116 """
121 """
117 # i18n: "modified" is a keyword
122 # i18n: "modified" is a keyword
118 getargs(x, 0, 0, _("modified takes no arguments"))
123 getargs(x, 0, 0, _("modified takes no arguments"))
119 s = mctx.status()[0]
124 s = mctx.status()[0]
120 return [f for f in mctx.subset if f in s]
125 return [f for f in mctx.subset if f in s]
121
126
122 def added(mctx, x):
127 def added(mctx, x):
123 """``added()``
128 """``added()``
124 File that is added according to status.
129 File that is added according to status.
125 """
130 """
126 # i18n: "added" is a keyword
131 # i18n: "added" is a keyword
127 getargs(x, 0, 0, _("added takes no arguments"))
132 getargs(x, 0, 0, _("added takes no arguments"))
128 s = mctx.status()[1]
133 s = mctx.status()[1]
129 return [f for f in mctx.subset if f in s]
134 return [f for f in mctx.subset if f in s]
130
135
131 def removed(mctx, x):
136 def removed(mctx, x):
132 """``removed()``
137 """``removed()``
133 File that is removed according to status.
138 File that is removed according to status.
134 """
139 """
135 # i18n: "removed" is a keyword
140 # i18n: "removed" is a keyword
136 getargs(x, 0, 0, _("removed takes no arguments"))
141 getargs(x, 0, 0, _("removed takes no arguments"))
137 s = mctx.status()[2]
142 s = mctx.status()[2]
138 return [f for f in mctx.subset if f in s]
143 return [f for f in mctx.subset if f in s]
139
144
140 def deleted(mctx, x):
145 def deleted(mctx, x):
141 """``deleted()``
146 """``deleted()``
142 File that is deleted according to status.
147 File that is deleted according to status.
143 """
148 """
144 # i18n: "deleted" is a keyword
149 # i18n: "deleted" is a keyword
145 getargs(x, 0, 0, _("deleted takes no arguments"))
150 getargs(x, 0, 0, _("deleted takes no arguments"))
146 s = mctx.status()[3]
151 s = mctx.status()[3]
147 return [f for f in mctx.subset if f in s]
152 return [f for f in mctx.subset if f in s]
148
153
149 def unknown(mctx, x):
154 def unknown(mctx, x):
150 """``unknown()``
155 """``unknown()``
151 File that is unknown according to status. These files will only be
156 File that is unknown according to status. These files will only be
152 considered if this predicate is used.
157 considered if this predicate is used.
153 """
158 """
154 # i18n: "unknown" is a keyword
159 # i18n: "unknown" is a keyword
155 getargs(x, 0, 0, _("unknown takes no arguments"))
160 getargs(x, 0, 0, _("unknown takes no arguments"))
156 s = mctx.status()[4]
161 s = mctx.status()[4]
157 return [f for f in mctx.subset if f in s]
162 return [f for f in mctx.subset if f in s]
158
163
159 def ignored(mctx, x):
164 def ignored(mctx, x):
160 """``ignored()``
165 """``ignored()``
161 File that is ignored according to status. These files will only be
166 File that is ignored according to status. These files will only be
162 considered if this predicate is used.
167 considered if this predicate is used.
163 """
168 """
164 # i18n: "ignored" is a keyword
169 # i18n: "ignored" is a keyword
165 getargs(x, 0, 0, _("ignored takes no arguments"))
170 getargs(x, 0, 0, _("ignored takes no arguments"))
166 s = mctx.status()[5]
171 s = mctx.status()[5]
167 return [f for f in mctx.subset if f in s]
172 return [f for f in mctx.subset if f in s]
168
173
169 def clean(mctx, x):
174 def clean(mctx, x):
170 """``clean()``
175 """``clean()``
171 File that is clean according to status.
176 File that is clean according to status.
172 """
177 """
173 # i18n: "clean" is a keyword
178 # i18n: "clean" is a keyword
174 getargs(x, 0, 0, _("clean takes no arguments"))
179 getargs(x, 0, 0, _("clean takes no arguments"))
175 s = mctx.status()[6]
180 s = mctx.status()[6]
176 return [f for f in mctx.subset if f in s]
181 return [f for f in mctx.subset if f in s]
177
182
178 def func(mctx, a, b):
183 def func(mctx, a, b):
179 if a[0] == 'symbol' and a[1] in symbols:
184 if a[0] == 'symbol' and a[1] in symbols:
180 return symbols[a[1]](mctx, b)
185 return symbols[a[1]](mctx, b)
181 raise error.ParseError(_("not a function: %s") % a[1])
186 raise error.ParseError(_("not a function: %s") % a[1])
182
187
183 def getlist(x):
188 def getlist(x):
184 if not x:
189 if not x:
185 return []
190 return []
186 if x[0] == 'list':
191 if x[0] == 'list':
187 return getlist(x[1]) + [x[2]]
192 return getlist(x[1]) + [x[2]]
188 return [x]
193 return [x]
189
194
190 def getargs(x, min, max, err):
195 def getargs(x, min, max, err):
191 l = getlist(x)
196 l = getlist(x)
192 if len(l) < min or len(l) > max:
197 if len(l) < min or len(l) > max:
193 raise error.ParseError(err)
198 raise error.ParseError(err)
194 return l
199 return l
195
200
196 def binary(mctx, x):
201 def binary(mctx, x):
197 """``binary()``
202 """``binary()``
198 File that appears to be binary (contains NUL bytes).
203 File that appears to be binary (contains NUL bytes).
199 """
204 """
200 # i18n: "binary" is a keyword
205 # i18n: "binary" is a keyword
201 getargs(x, 0, 0, _("binary takes no arguments"))
206 getargs(x, 0, 0, _("binary takes no arguments"))
202 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
207 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
203
208
204 def exec_(mctx, x):
209 def exec_(mctx, x):
205 """``exec()``
210 """``exec()``
206 File that is marked as executable.
211 File that is marked as executable.
207 """
212 """
208 # i18n: "exec" is a keyword
213 # i18n: "exec" is a keyword
209 getargs(x, 0, 0, _("exec takes no arguments"))
214 getargs(x, 0, 0, _("exec takes no arguments"))
210 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
215 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
211
216
212 def symlink(mctx, x):
217 def symlink(mctx, x):
213 """``symlink()``
218 """``symlink()``
214 File that is marked as a symlink.
219 File that is marked as a symlink.
215 """
220 """
216 # i18n: "symlink" is a keyword
221 # i18n: "symlink" is a keyword
217 getargs(x, 0, 0, _("symlink takes no arguments"))
222 getargs(x, 0, 0, _("symlink takes no arguments"))
218 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
223 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
219
224
220 def resolved(mctx, x):
225 def resolved(mctx, x):
221 """``resolved()``
226 """``resolved()``
222 File that is marked resolved according to the resolve state.
227 File that is marked resolved according to the resolve state.
223 """
228 """
224 # i18n: "resolved" is a keyword
229 # i18n: "resolved" is a keyword
225 getargs(x, 0, 0, _("resolved takes no arguments"))
230 getargs(x, 0, 0, _("resolved takes no arguments"))
226 if mctx.ctx.rev() is not None:
231 if mctx.ctx.rev() is not None:
227 return []
232 return []
228 ms = merge.mergestate(mctx.ctx._repo)
233 ms = merge.mergestate(mctx.ctx._repo)
229 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
234 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
230
235
231 def unresolved(mctx, x):
236 def unresolved(mctx, x):
232 """``unresolved()``
237 """``unresolved()``
233 File that is marked unresolved according to the resolve state.
238 File that is marked unresolved according to the resolve state.
234 """
239 """
235 # i18n: "unresolved" is a keyword
240 # i18n: "unresolved" is a keyword
236 getargs(x, 0, 0, _("unresolved takes no arguments"))
241 getargs(x, 0, 0, _("unresolved takes no arguments"))
237 if mctx.ctx.rev() is not None:
242 if mctx.ctx.rev() is not None:
238 return []
243 return []
239 ms = merge.mergestate(mctx.ctx._repo)
244 ms = merge.mergestate(mctx.ctx._repo)
240 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
245 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
241
246
242 def hgignore(mctx, x):
247 def hgignore(mctx, x):
243 """``hgignore()``
248 """``hgignore()``
244 File that matches the active .hgignore pattern.
249 File that matches the active .hgignore pattern.
245 """
250 """
246 getargs(x, 0, 0, _("hgignore takes no arguments"))
251 getargs(x, 0, 0, _("hgignore takes no arguments"))
247 ignore = mctx.ctx._repo.dirstate._ignore
252 ignore = mctx.ctx._repo.dirstate._ignore
248 return [f for f in mctx.subset if ignore(f)]
253 return [f for f in mctx.subset if ignore(f)]
249
254
250 def grep(mctx, x):
255 def grep(mctx, x):
251 """``grep(regex)``
256 """``grep(regex)``
252 File contains the given regular expression.
257 File contains the given regular expression.
253 """
258 """
254 pat = getstring(x, _("grep requires a pattern"))
259 pat = getstring(x, _("grep requires a pattern"))
255 r = re.compile(pat)
260 r = re.compile(pat)
256 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
261 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
257
262
258 _units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
263 _units = dict(k=2**10, K=2**10, kB=2**10, KB=2**10,
259 M=2**20, MB=2**20, G=2**30, GB=2**30)
264 M=2**20, MB=2**20, G=2**30, GB=2**30)
260
265
261 def _sizetoint(s):
266 def _sizetoint(s):
262 try:
267 try:
263 s = s.strip()
268 s = s.strip()
264 for k, v in _units.items():
269 for k, v in _units.items():
265 if s.endswith(k):
270 if s.endswith(k):
266 return int(float(s[:-len(k)]) * v)
271 return int(float(s[:-len(k)]) * v)
267 return int(s)
272 return int(s)
268 except ValueError:
273 except ValueError:
269 raise error.ParseError(_("couldn't parse size: %s") % s)
274 raise error.ParseError(_("couldn't parse size: %s") % s)
270
275
271 def _sizetomax(s):
276 def _sizetomax(s):
272 try:
277 try:
273 s = s.strip()
278 s = s.strip()
274 for k, v in _units.items():
279 for k, v in _units.items():
275 if s.endswith(k):
280 if s.endswith(k):
276 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
281 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
277 n = s[:-len(k)]
282 n = s[:-len(k)]
278 inc = 1.0
283 inc = 1.0
279 if "." in n:
284 if "." in n:
280 inc /= 10 ** len(n.split(".")[1])
285 inc /= 10 ** len(n.split(".")[1])
281 return int((float(n) + inc) * v) - 1
286 return int((float(n) + inc) * v) - 1
282 # no extension, this is a precise value
287 # no extension, this is a precise value
283 return int(s)
288 return int(s)
284 except ValueError:
289 except ValueError:
285 raise error.ParseError(_("couldn't parse size: %s") % s)
290 raise error.ParseError(_("couldn't parse size: %s") % s)
286
291
287 def size(mctx, x):
292 def size(mctx, x):
288 """``size(expression)``
293 """``size(expression)``
289 File size matches the given expression. Examples:
294 File size matches the given expression. Examples:
290
295
291 - 1k (files from 1024 to 2047 bytes)
296 - 1k (files from 1024 to 2047 bytes)
292 - < 20k (files less than 20480 bytes)
297 - < 20k (files less than 20480 bytes)
293 - >= .5MB (files at least 524288 bytes)
298 - >= .5MB (files at least 524288 bytes)
294 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
299 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
295 """
300 """
296
301
297 # i18n: "size" is a keyword
302 # i18n: "size" is a keyword
298 expr = getstring(x, _("size requires an expression")).strip()
303 expr = getstring(x, _("size requires an expression")).strip()
299 if '-' in expr: # do we have a range?
304 if '-' in expr: # do we have a range?
300 a, b = expr.split('-', 1)
305 a, b = expr.split('-', 1)
301 a = _sizetoint(a)
306 a = _sizetoint(a)
302 b = _sizetoint(b)
307 b = _sizetoint(b)
303 m = lambda x: x >= a and x <= b
308 m = lambda x: x >= a and x <= b
304 elif expr.startswith("<="):
309 elif expr.startswith("<="):
305 a = _sizetoint(expr[2:])
310 a = _sizetoint(expr[2:])
306 m = lambda x: x <= a
311 m = lambda x: x <= a
307 elif expr.startswith("<"):
312 elif expr.startswith("<"):
308 a = _sizetoint(expr[1:])
313 a = _sizetoint(expr[1:])
309 m = lambda x: x < a
314 m = lambda x: x < a
310 elif expr.startswith(">="):
315 elif expr.startswith(">="):
311 a = _sizetoint(expr[2:])
316 a = _sizetoint(expr[2:])
312 m = lambda x: x >= a
317 m = lambda x: x >= a
313 elif expr.startswith(">"):
318 elif expr.startswith(">"):
314 a = _sizetoint(expr[1:])
319 a = _sizetoint(expr[1:])
315 m = lambda x: x > a
320 m = lambda x: x > a
316 elif expr[0].isdigit or expr[0] == '.':
321 elif expr[0].isdigit or expr[0] == '.':
317 a = _sizetoint(expr)
322 a = _sizetoint(expr)
318 b = _sizetomax(expr)
323 b = _sizetomax(expr)
319 m = lambda x: x >= a and x <= b
324 m = lambda x: x >= a and x <= b
320 else:
325 else:
321 raise error.ParseError(_("couldn't parse size: %s") % expr)
326 raise error.ParseError(_("couldn't parse size: %s") % expr)
322
327
323 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
328 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
324
329
325 def encoding(mctx, x):
330 def encoding(mctx, x):
326 """``encoding(name)``
331 """``encoding(name)``
327 File can be successfully decoded with the given character
332 File can be successfully decoded with the given character
328 encoding. May not be useful for encodings other than ASCII and
333 encoding. May not be useful for encodings other than ASCII and
329 UTF-8.
334 UTF-8.
330 """
335 """
331
336
332 # i18n: "encoding" is a keyword
337 # i18n: "encoding" is a keyword
333 enc = getstring(x, _("encoding requires an encoding name"))
338 enc = getstring(x, _("encoding requires an encoding name"))
334
339
335 s = []
340 s = []
336 for f in mctx.existing():
341 for f in mctx.existing():
337 d = mctx.ctx[f].data()
342 d = mctx.ctx[f].data()
338 try:
343 try:
339 d.decode(enc)
344 d.decode(enc)
340 except LookupError:
345 except LookupError:
341 raise util.Abort(_("unknown encoding '%s'") % enc)
346 raise util.Abort(_("unknown encoding '%s'") % enc)
342 except UnicodeDecodeError:
347 except UnicodeDecodeError:
343 continue
348 continue
344 s.append(f)
349 s.append(f)
345
350
346 return s
351 return s
347
352
348 def copied(mctx, x):
353 def copied(mctx, x):
349 """``copied()``
354 """``copied()``
350 File that is recorded as being copied.
355 File that is recorded as being copied.
351 """
356 """
352 # i18n: "copied" is a keyword
357 # i18n: "copied" is a keyword
353 getargs(x, 0, 0, _("copied takes no arguments"))
358 getargs(x, 0, 0, _("copied takes no arguments"))
354 s = []
359 s = []
355 for f in mctx.subset:
360 for f in mctx.subset:
356 p = mctx.ctx[f].parents()
361 p = mctx.ctx[f].parents()
357 if p and p[0].path() != f:
362 if p and p[0].path() != f:
358 s.append(f)
363 s.append(f)
359 return s
364 return s
360
365
361 def subrepo(mctx, x):
366 def subrepo(mctx, x):
362 """``subrepo([pattern])``
367 """``subrepo([pattern])``
363 Subrepositories whose paths match the given pattern.
368 Subrepositories whose paths match the given pattern.
364 """
369 """
365 # i18n: "subrepo" is a keyword
370 # i18n: "subrepo" is a keyword
366 getargs(x, 0, 1, _("subrepo takes at most one argument"))
371 getargs(x, 0, 1, _("subrepo takes at most one argument"))
367 ctx = mctx.ctx
372 ctx = mctx.ctx
368 sstate = ctx.substate
373 sstate = ctx.substate
369 if x:
374 if x:
370 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
375 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
371
376
372 import match as matchmod # avoid circular import issues
377 import match as matchmod # avoid circular import issues
373 fast = not matchmod.patkind(pat)
378 fast = not matchmod.patkind(pat)
374 if fast:
379 if fast:
375 def m(s):
380 def m(s):
376 return (s == pat)
381 return (s == pat)
377 else:
382 else:
378 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
383 m = matchmod.match(ctx._repo.root, '', [pat], ctx=ctx)
379 return [sub for sub in sstate if m(sub)]
384 return [sub for sub in sstate if m(sub)]
380 else:
385 else:
381 return [sub for sub in sstate]
386 return [sub for sub in sstate]
382
387
383 symbols = {
388 symbols = {
384 'added': added,
389 'added': added,
385 'binary': binary,
390 'binary': binary,
386 'clean': clean,
391 'clean': clean,
387 'copied': copied,
392 'copied': copied,
388 'deleted': deleted,
393 'deleted': deleted,
389 'encoding': encoding,
394 'encoding': encoding,
390 'exec': exec_,
395 'exec': exec_,
391 'grep': grep,
396 'grep': grep,
392 'ignored': ignored,
397 'ignored': ignored,
393 'hgignore': hgignore,
398 'hgignore': hgignore,
394 'modified': modified,
399 'modified': modified,
395 'removed': removed,
400 'removed': removed,
396 'resolved': resolved,
401 'resolved': resolved,
397 'size': size,
402 'size': size,
398 'symlink': symlink,
403 'symlink': symlink,
399 'unknown': unknown,
404 'unknown': unknown,
400 'unresolved': unresolved,
405 'unresolved': unresolved,
401 'subrepo': subrepo,
406 'subrepo': subrepo,
402 }
407 }
403
408
404 methods = {
409 methods = {
405 'string': stringset,
410 'string': stringset,
406 'symbol': stringset,
411 'symbol': stringset,
407 'and': andset,
412 'and': andset,
408 'or': orset,
413 'or': orset,
414 'minus': minusset,
409 'list': listset,
415 'list': listset,
410 'group': getset,
416 'group': getset,
411 'not': notset,
417 'not': notset,
412 'func': func,
418 'func': func,
413 }
419 }
414
420
415 class matchctx(object):
421 class matchctx(object):
416 def __init__(self, ctx, subset=None, status=None):
422 def __init__(self, ctx, subset=None, status=None):
417 self.ctx = ctx
423 self.ctx = ctx
418 self.subset = subset
424 self.subset = subset
419 self._status = status
425 self._status = status
420 def status(self):
426 def status(self):
421 return self._status
427 return self._status
422 def matcher(self, patterns):
428 def matcher(self, patterns):
423 return self.ctx.match(patterns)
429 return self.ctx.match(patterns)
424 def filter(self, files):
430 def filter(self, files):
425 return [f for f in files if f in self.subset]
431 return [f for f in files if f in self.subset]
426 def existing(self):
432 def existing(self):
427 return (f for f in self.subset if f in self.ctx)
433 return (f for f in self.subset if f in self.ctx)
428 def narrow(self, files):
434 def narrow(self, files):
429 return matchctx(self.ctx, self.filter(files), self._status)
435 return matchctx(self.ctx, self.filter(files), self._status)
430
436
431 def _intree(funcs, tree):
437 def _intree(funcs, tree):
432 if isinstance(tree, tuple):
438 if isinstance(tree, tuple):
433 if tree[0] == 'func' and tree[1][0] == 'symbol':
439 if tree[0] == 'func' and tree[1][0] == 'symbol':
434 if tree[1][1] in funcs:
440 if tree[1][1] in funcs:
435 return True
441 return True
436 for s in tree[1:]:
442 for s in tree[1:]:
437 if _intree(funcs, s):
443 if _intree(funcs, s):
438 return True
444 return True
439 return False
445 return False
440
446
441 def getfileset(ctx, expr):
447 def getfileset(ctx, expr):
442 tree, pos = parse(expr)
448 tree, pos = parse(expr)
443 if (pos != len(expr)):
449 if (pos != len(expr)):
444 raise error.ParseError(_("invalid token"), pos)
450 raise error.ParseError(_("invalid token"), pos)
445
451
446 # do we need status info?
452 # do we need status info?
447 if _intree(['modified', 'added', 'removed', 'deleted',
453 if _intree(['modified', 'added', 'removed', 'deleted',
448 'unknown', 'ignored', 'clean'], tree):
454 'unknown', 'ignored', 'clean'], tree):
449 unknown = _intree(['unknown'], tree)
455 unknown = _intree(['unknown'], tree)
450 ignored = _intree(['ignored'], tree)
456 ignored = _intree(['ignored'], tree)
451
457
452 r = ctx._repo
458 r = ctx._repo
453 status = r.status(ctx.p1(), ctx,
459 status = r.status(ctx.p1(), ctx,
454 unknown=unknown, ignored=ignored, clean=True)
460 unknown=unknown, ignored=ignored, clean=True)
455 subset = []
461 subset = []
456 for c in status:
462 for c in status:
457 subset.extend(c)
463 subset.extend(c)
458 else:
464 else:
459 status = None
465 status = None
460 subset = ctx.walk(ctx.match([]))
466 subset = ctx.walk(ctx.match([]))
461
467
462 return getset(matchctx(ctx, subset, status), tree)
468 return getset(matchctx(ctx, subset, status), tree)
463
469
464 # tell hggettext to extract docstrings from these functions:
470 # tell hggettext to extract docstrings from these functions:
465 i18nfunctions = symbols.values()
471 i18nfunctions = symbols.values()
@@ -1,39 +1,41 b''
1 $ fileset() {
1 $ fileset() {
2 > hg debugfileset "$@"
2 > hg debugfileset "$@"
3 > }
3 > }
4
4
5 $ hg init repo
5 $ hg init repo
6 $ cd repo
6 $ cd repo
7 $ echo a > a1
7 $ echo a > a1
8 $ echo a > a2
8 $ echo a > a2
9 $ echo b > b1
9 $ echo b > b1
10 $ hg ci -Am addfiles
10 $ hg ci -Am addfiles
11 adding a1
11 adding a1
12 adding a2
12 adding a2
13 adding b1
13 adding b1
14
14
15 Test operators and basic patterns
15 Test operators and basic patterns
16
16
17 $ fileset a1
17 $ fileset a1
18 a1
18 a1
19 $ fileset 'a*'
19 $ fileset 'a*'
20 a1
20 a1
21 a2
21 a2
22 $ fileset '"re:a\d"'
22 $ fileset '"re:a\d"'
23 a1
23 a1
24 a2
24 a2
25 $ fileset 'a1 or a2'
25 $ fileset 'a1 or a2'
26 a1
26 a1
27 a2
27 a2
28 $ fileset 'a1 | a2'
28 $ fileset 'a1 | a2'
29 a1
29 a1
30 a2
30 a2
31 $ fileset 'a* and "*1"'
31 $ fileset 'a* and "*1"'
32 a1
32 a1
33 $ fileset 'a* & "*1"'
33 $ fileset 'a* & "*1"'
34 a1
34 a1
35 $ fileset 'not (r"a*")'
35 $ fileset 'not (r"a*")'
36 b1
36 b1
37 $ fileset '! ("a*")'
37 $ fileset '! ("a*")'
38 b1
38 b1
39 $ fileset 'a* - a1'
40 a2
39
41
General Comments 0
You need to be logged in to leave comments. Login now