##// END OF EJS Templates
fileset: use context-returning revpair()...
Martin von Zweigbergk -
r37274:f290f130 default
parent child Browse files
Show More
@@ -1,664 +1,662 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 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 match as matchmod,
15 match as matchmod,
16 merge,
16 merge,
17 parser,
17 parser,
18 pycompat,
18 pycompat,
19 registrar,
19 registrar,
20 scmutil,
20 scmutil,
21 util,
21 util,
22 )
22 )
23 from .utils import (
23 from .utils import (
24 stringutil,
24 stringutil,
25 )
25 )
26
26
27 elements = {
27 elements = {
28 # token-type: binding-strength, primary, prefix, infix, suffix
28 # token-type: binding-strength, primary, prefix, infix, suffix
29 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
29 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
30 ":": (15, None, None, ("kindpat", 15), None),
30 ":": (15, None, None, ("kindpat", 15), None),
31 "-": (5, None, ("negate", 19), ("minus", 5), None),
31 "-": (5, None, ("negate", 19), ("minus", 5), None),
32 "not": (10, None, ("not", 10), None, None),
32 "not": (10, None, ("not", 10), None, None),
33 "!": (10, None, ("not", 10), None, None),
33 "!": (10, None, ("not", 10), None, None),
34 "and": (5, None, None, ("and", 5), None),
34 "and": (5, None, None, ("and", 5), None),
35 "&": (5, None, None, ("and", 5), None),
35 "&": (5, None, None, ("and", 5), None),
36 "or": (4, None, None, ("or", 4), None),
36 "or": (4, None, None, ("or", 4), None),
37 "|": (4, None, None, ("or", 4), None),
37 "|": (4, None, None, ("or", 4), None),
38 "+": (4, None, None, ("or", 4), None),
38 "+": (4, None, None, ("or", 4), None),
39 ",": (2, None, None, ("list", 2), None),
39 ",": (2, None, None, ("list", 2), None),
40 ")": (0, None, None, None, None),
40 ")": (0, None, None, None, None),
41 "symbol": (0, "symbol", None, None, None),
41 "symbol": (0, "symbol", None, None, None),
42 "string": (0, "string", None, None, None),
42 "string": (0, "string", None, None, None),
43 "end": (0, None, None, None, None),
43 "end": (0, None, None, None, None),
44 }
44 }
45
45
46 keywords = {'and', 'or', 'not'}
46 keywords = {'and', 'or', 'not'}
47
47
48 globchars = ".*{}[]?/\\_"
48 globchars = ".*{}[]?/\\_"
49
49
50 def tokenize(program):
50 def tokenize(program):
51 pos, l = 0, len(program)
51 pos, l = 0, len(program)
52 program = pycompat.bytestr(program)
52 program = pycompat.bytestr(program)
53 while pos < l:
53 while pos < l:
54 c = program[pos]
54 c = program[pos]
55 if c.isspace(): # skip inter-token whitespace
55 if c.isspace(): # skip inter-token whitespace
56 pass
56 pass
57 elif c in "(),-:|&+!": # handle simple operators
57 elif c in "(),-:|&+!": # handle simple operators
58 yield (c, None, pos)
58 yield (c, None, pos)
59 elif (c in '"\'' or c == 'r' and
59 elif (c in '"\'' or c == 'r' and
60 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
60 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
61 if c == 'r':
61 if c == 'r':
62 pos += 1
62 pos += 1
63 c = program[pos]
63 c = program[pos]
64 decode = lambda x: x
64 decode = lambda x: x
65 else:
65 else:
66 decode = parser.unescapestr
66 decode = parser.unescapestr
67 pos += 1
67 pos += 1
68 s = pos
68 s = pos
69 while pos < l: # find closing quote
69 while pos < l: # find closing quote
70 d = program[pos]
70 d = program[pos]
71 if d == '\\': # skip over escaped characters
71 if d == '\\': # skip over escaped characters
72 pos += 2
72 pos += 2
73 continue
73 continue
74 if d == c:
74 if d == c:
75 yield ('string', decode(program[s:pos]), s)
75 yield ('string', decode(program[s:pos]), s)
76 break
76 break
77 pos += 1
77 pos += 1
78 else:
78 else:
79 raise error.ParseError(_("unterminated string"), s)
79 raise error.ParseError(_("unterminated string"), s)
80 elif c.isalnum() or c in globchars or ord(c) > 127:
80 elif c.isalnum() or c in globchars or ord(c) > 127:
81 # gather up a symbol/keyword
81 # gather up a symbol/keyword
82 s = pos
82 s = pos
83 pos += 1
83 pos += 1
84 while pos < l: # find end of symbol
84 while pos < l: # find end of symbol
85 d = program[pos]
85 d = program[pos]
86 if not (d.isalnum() or d in globchars or ord(d) > 127):
86 if not (d.isalnum() or d in globchars or ord(d) > 127):
87 break
87 break
88 pos += 1
88 pos += 1
89 sym = program[s:pos]
89 sym = program[s:pos]
90 if sym in keywords: # operator keywords
90 if sym in keywords: # operator keywords
91 yield (sym, None, s)
91 yield (sym, None, s)
92 else:
92 else:
93 yield ('symbol', sym, s)
93 yield ('symbol', sym, s)
94 pos -= 1
94 pos -= 1
95 else:
95 else:
96 raise error.ParseError(_("syntax error"), pos)
96 raise error.ParseError(_("syntax error"), pos)
97 pos += 1
97 pos += 1
98 yield ('end', None, pos)
98 yield ('end', None, pos)
99
99
100 def parse(expr):
100 def parse(expr):
101 p = parser.parser(elements)
101 p = parser.parser(elements)
102 tree, pos = p.parse(tokenize(expr))
102 tree, pos = p.parse(tokenize(expr))
103 if pos != len(expr):
103 if pos != len(expr):
104 raise error.ParseError(_("invalid token"), pos)
104 raise error.ParseError(_("invalid token"), pos)
105 return tree
105 return tree
106
106
107 def getsymbol(x):
107 def getsymbol(x):
108 if x and x[0] == 'symbol':
108 if x and x[0] == 'symbol':
109 return x[1]
109 return x[1]
110 raise error.ParseError(_('not a symbol'))
110 raise error.ParseError(_('not a symbol'))
111
111
112 def getstring(x, err):
112 def getstring(x, err):
113 if x and (x[0] == 'string' or x[0] == 'symbol'):
113 if x and (x[0] == 'string' or x[0] == 'symbol'):
114 return x[1]
114 return x[1]
115 raise error.ParseError(err)
115 raise error.ParseError(err)
116
116
117 def _getkindpat(x, y, allkinds, err):
117 def _getkindpat(x, y, allkinds, err):
118 kind = getsymbol(x)
118 kind = getsymbol(x)
119 pat = getstring(y, err)
119 pat = getstring(y, err)
120 if kind not in allkinds:
120 if kind not in allkinds:
121 raise error.ParseError(_("invalid pattern kind: %s") % kind)
121 raise error.ParseError(_("invalid pattern kind: %s") % kind)
122 return '%s:%s' % (kind, pat)
122 return '%s:%s' % (kind, pat)
123
123
124 def getpattern(x, allkinds, err):
124 def getpattern(x, allkinds, err):
125 if x and x[0] == 'kindpat':
125 if x and x[0] == 'kindpat':
126 return _getkindpat(x[1], x[2], allkinds, err)
126 return _getkindpat(x[1], x[2], allkinds, err)
127 return getstring(x, err)
127 return getstring(x, err)
128
128
129 def getset(mctx, x):
129 def getset(mctx, x):
130 if not x:
130 if not x:
131 raise error.ParseError(_("missing argument"))
131 raise error.ParseError(_("missing argument"))
132 return methods[x[0]](mctx, *x[1:])
132 return methods[x[0]](mctx, *x[1:])
133
133
134 def stringset(mctx, x):
134 def stringset(mctx, x):
135 m = mctx.matcher([x])
135 m = mctx.matcher([x])
136 return [f for f in mctx.subset if m(f)]
136 return [f for f in mctx.subset if m(f)]
137
137
138 def kindpatset(mctx, x, y):
138 def kindpatset(mctx, x, y):
139 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
139 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
140 _("pattern must be a string")))
140 _("pattern must be a string")))
141
141
142 def andset(mctx, x, y):
142 def andset(mctx, x, y):
143 return getset(mctx.narrow(getset(mctx, x)), y)
143 return getset(mctx.narrow(getset(mctx, x)), y)
144
144
145 def orset(mctx, x, y):
145 def orset(mctx, x, y):
146 # needs optimizing
146 # needs optimizing
147 xl = getset(mctx, x)
147 xl = getset(mctx, x)
148 yl = getset(mctx, y)
148 yl = getset(mctx, y)
149 return xl + [f for f in yl if f not in xl]
149 return xl + [f for f in yl if f not in xl]
150
150
151 def notset(mctx, x):
151 def notset(mctx, x):
152 s = set(getset(mctx, x))
152 s = set(getset(mctx, x))
153 return [r for r in mctx.subset if r not in s]
153 return [r for r in mctx.subset if r not in s]
154
154
155 def minusset(mctx, x, y):
155 def minusset(mctx, x, y):
156 xl = getset(mctx, x)
156 xl = getset(mctx, x)
157 yl = set(getset(mctx, y))
157 yl = set(getset(mctx, y))
158 return [f for f in xl if f not in yl]
158 return [f for f in xl if f not in yl]
159
159
160 def negateset(mctx, x):
160 def negateset(mctx, x):
161 raise error.ParseError(_("can't use negate operator in this context"))
161 raise error.ParseError(_("can't use negate operator in this context"))
162
162
163 def listset(mctx, a, b):
163 def listset(mctx, a, b):
164 raise error.ParseError(_("can't use a list in this context"),
164 raise error.ParseError(_("can't use a list in this context"),
165 hint=_('see hg help "filesets.x or y"'))
165 hint=_('see hg help "filesets.x or y"'))
166
166
167 # symbols are callable like:
167 # symbols are callable like:
168 # fun(mctx, x)
168 # fun(mctx, x)
169 # with:
169 # with:
170 # mctx - current matchctx instance
170 # mctx - current matchctx instance
171 # x - argument in tree form
171 # x - argument in tree form
172 symbols = {}
172 symbols = {}
173
173
174 # filesets using matchctx.status()
174 # filesets using matchctx.status()
175 _statuscallers = set()
175 _statuscallers = set()
176
176
177 # filesets using matchctx.existing()
177 # filesets using matchctx.existing()
178 _existingcallers = set()
178 _existingcallers = set()
179
179
180 predicate = registrar.filesetpredicate()
180 predicate = registrar.filesetpredicate()
181
181
182 @predicate('modified()', callstatus=True)
182 @predicate('modified()', callstatus=True)
183 def modified(mctx, x):
183 def modified(mctx, x):
184 """File that is modified according to :hg:`status`.
184 """File that is modified according to :hg:`status`.
185 """
185 """
186 # i18n: "modified" is a keyword
186 # i18n: "modified" is a keyword
187 getargs(x, 0, 0, _("modified takes no arguments"))
187 getargs(x, 0, 0, _("modified takes no arguments"))
188 s = set(mctx.status().modified)
188 s = set(mctx.status().modified)
189 return [f for f in mctx.subset if f in s]
189 return [f for f in mctx.subset if f in s]
190
190
191 @predicate('added()', callstatus=True)
191 @predicate('added()', callstatus=True)
192 def added(mctx, x):
192 def added(mctx, x):
193 """File that is added according to :hg:`status`.
193 """File that is added according to :hg:`status`.
194 """
194 """
195 # i18n: "added" is a keyword
195 # i18n: "added" is a keyword
196 getargs(x, 0, 0, _("added takes no arguments"))
196 getargs(x, 0, 0, _("added takes no arguments"))
197 s = set(mctx.status().added)
197 s = set(mctx.status().added)
198 return [f for f in mctx.subset if f in s]
198 return [f for f in mctx.subset if f in s]
199
199
200 @predicate('removed()', callstatus=True)
200 @predicate('removed()', callstatus=True)
201 def removed(mctx, x):
201 def removed(mctx, x):
202 """File that is removed according to :hg:`status`.
202 """File that is removed according to :hg:`status`.
203 """
203 """
204 # i18n: "removed" is a keyword
204 # i18n: "removed" is a keyword
205 getargs(x, 0, 0, _("removed takes no arguments"))
205 getargs(x, 0, 0, _("removed takes no arguments"))
206 s = set(mctx.status().removed)
206 s = set(mctx.status().removed)
207 return [f for f in mctx.subset if f in s]
207 return [f for f in mctx.subset if f in s]
208
208
209 @predicate('deleted()', callstatus=True)
209 @predicate('deleted()', callstatus=True)
210 def deleted(mctx, x):
210 def deleted(mctx, x):
211 """Alias for ``missing()``.
211 """Alias for ``missing()``.
212 """
212 """
213 # i18n: "deleted" is a keyword
213 # i18n: "deleted" is a keyword
214 getargs(x, 0, 0, _("deleted takes no arguments"))
214 getargs(x, 0, 0, _("deleted takes no arguments"))
215 s = set(mctx.status().deleted)
215 s = set(mctx.status().deleted)
216 return [f for f in mctx.subset if f in s]
216 return [f for f in mctx.subset if f in s]
217
217
218 @predicate('missing()', callstatus=True)
218 @predicate('missing()', callstatus=True)
219 def missing(mctx, x):
219 def missing(mctx, x):
220 """File that is missing according to :hg:`status`.
220 """File that is missing according to :hg:`status`.
221 """
221 """
222 # i18n: "missing" is a keyword
222 # i18n: "missing" is a keyword
223 getargs(x, 0, 0, _("missing takes no arguments"))
223 getargs(x, 0, 0, _("missing takes no arguments"))
224 s = set(mctx.status().deleted)
224 s = set(mctx.status().deleted)
225 return [f for f in mctx.subset if f in s]
225 return [f for f in mctx.subset if f in s]
226
226
227 @predicate('unknown()', callstatus=True)
227 @predicate('unknown()', callstatus=True)
228 def unknown(mctx, x):
228 def unknown(mctx, x):
229 """File that is unknown according to :hg:`status`. These files will only be
229 """File that is unknown according to :hg:`status`. These files will only be
230 considered if this predicate is used.
230 considered if this predicate is used.
231 """
231 """
232 # i18n: "unknown" is a keyword
232 # i18n: "unknown" is a keyword
233 getargs(x, 0, 0, _("unknown takes no arguments"))
233 getargs(x, 0, 0, _("unknown takes no arguments"))
234 s = set(mctx.status().unknown)
234 s = set(mctx.status().unknown)
235 return [f for f in mctx.subset if f in s]
235 return [f for f in mctx.subset if f in s]
236
236
237 @predicate('ignored()', callstatus=True)
237 @predicate('ignored()', callstatus=True)
238 def ignored(mctx, x):
238 def ignored(mctx, x):
239 """File that is ignored according to :hg:`status`. These files will only be
239 """File that is ignored according to :hg:`status`. These files will only be
240 considered if this predicate is used.
240 considered if this predicate is used.
241 """
241 """
242 # i18n: "ignored" is a keyword
242 # i18n: "ignored" is a keyword
243 getargs(x, 0, 0, _("ignored takes no arguments"))
243 getargs(x, 0, 0, _("ignored takes no arguments"))
244 s = set(mctx.status().ignored)
244 s = set(mctx.status().ignored)
245 return [f for f in mctx.subset if f in s]
245 return [f for f in mctx.subset if f in s]
246
246
247 @predicate('clean()', callstatus=True)
247 @predicate('clean()', callstatus=True)
248 def clean(mctx, x):
248 def clean(mctx, x):
249 """File that is clean according to :hg:`status`.
249 """File that is clean according to :hg:`status`.
250 """
250 """
251 # i18n: "clean" is a keyword
251 # i18n: "clean" is a keyword
252 getargs(x, 0, 0, _("clean takes no arguments"))
252 getargs(x, 0, 0, _("clean takes no arguments"))
253 s = set(mctx.status().clean)
253 s = set(mctx.status().clean)
254 return [f for f in mctx.subset if f in s]
254 return [f for f in mctx.subset if f in s]
255
255
256 def func(mctx, a, b):
256 def func(mctx, a, b):
257 funcname = getsymbol(a)
257 funcname = getsymbol(a)
258 if funcname in symbols:
258 if funcname in symbols:
259 enabled = mctx._existingenabled
259 enabled = mctx._existingenabled
260 mctx._existingenabled = funcname in _existingcallers
260 mctx._existingenabled = funcname in _existingcallers
261 try:
261 try:
262 return symbols[funcname](mctx, b)
262 return symbols[funcname](mctx, b)
263 finally:
263 finally:
264 mctx._existingenabled = enabled
264 mctx._existingenabled = enabled
265
265
266 keep = lambda fn: getattr(fn, '__doc__', None) is not None
266 keep = lambda fn: getattr(fn, '__doc__', None) is not None
267
267
268 syms = [s for (s, fn) in symbols.items() if keep(fn)]
268 syms = [s for (s, fn) in symbols.items() if keep(fn)]
269 raise error.UnknownIdentifier(funcname, syms)
269 raise error.UnknownIdentifier(funcname, syms)
270
270
271 def getlist(x):
271 def getlist(x):
272 if not x:
272 if not x:
273 return []
273 return []
274 if x[0] == 'list':
274 if x[0] == 'list':
275 return getlist(x[1]) + [x[2]]
275 return getlist(x[1]) + [x[2]]
276 return [x]
276 return [x]
277
277
278 def getargs(x, min, max, err):
278 def getargs(x, min, max, err):
279 l = getlist(x)
279 l = getlist(x)
280 if len(l) < min or len(l) > max:
280 if len(l) < min or len(l) > max:
281 raise error.ParseError(err)
281 raise error.ParseError(err)
282 return l
282 return l
283
283
284 @predicate('binary()', callexisting=True)
284 @predicate('binary()', callexisting=True)
285 def binary(mctx, x):
285 def binary(mctx, x):
286 """File that appears to be binary (contains NUL bytes).
286 """File that appears to be binary (contains NUL bytes).
287 """
287 """
288 # i18n: "binary" is a keyword
288 # i18n: "binary" is a keyword
289 getargs(x, 0, 0, _("binary takes no arguments"))
289 getargs(x, 0, 0, _("binary takes no arguments"))
290 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
290 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
291
291
292 @predicate('exec()', callexisting=True)
292 @predicate('exec()', callexisting=True)
293 def exec_(mctx, x):
293 def exec_(mctx, x):
294 """File that is marked as executable.
294 """File that is marked as executable.
295 """
295 """
296 # i18n: "exec" is a keyword
296 # i18n: "exec" is a keyword
297 getargs(x, 0, 0, _("exec takes no arguments"))
297 getargs(x, 0, 0, _("exec takes no arguments"))
298 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
298 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
299
299
300 @predicate('symlink()', callexisting=True)
300 @predicate('symlink()', callexisting=True)
301 def symlink(mctx, x):
301 def symlink(mctx, x):
302 """File that is marked as a symlink.
302 """File that is marked as a symlink.
303 """
303 """
304 # i18n: "symlink" is a keyword
304 # i18n: "symlink" is a keyword
305 getargs(x, 0, 0, _("symlink takes no arguments"))
305 getargs(x, 0, 0, _("symlink takes no arguments"))
306 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
306 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
307
307
308 @predicate('resolved()')
308 @predicate('resolved()')
309 def resolved(mctx, x):
309 def resolved(mctx, x):
310 """File that is marked resolved according to :hg:`resolve -l`.
310 """File that is marked resolved according to :hg:`resolve -l`.
311 """
311 """
312 # i18n: "resolved" is a keyword
312 # i18n: "resolved" is a keyword
313 getargs(x, 0, 0, _("resolved takes no arguments"))
313 getargs(x, 0, 0, _("resolved takes no arguments"))
314 if mctx.ctx.rev() is not None:
314 if mctx.ctx.rev() is not None:
315 return []
315 return []
316 ms = merge.mergestate.read(mctx.ctx.repo())
316 ms = merge.mergestate.read(mctx.ctx.repo())
317 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
317 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
318
318
319 @predicate('unresolved()')
319 @predicate('unresolved()')
320 def unresolved(mctx, x):
320 def unresolved(mctx, x):
321 """File that is marked unresolved according to :hg:`resolve -l`.
321 """File that is marked unresolved according to :hg:`resolve -l`.
322 """
322 """
323 # i18n: "unresolved" is a keyword
323 # i18n: "unresolved" is a keyword
324 getargs(x, 0, 0, _("unresolved takes no arguments"))
324 getargs(x, 0, 0, _("unresolved takes no arguments"))
325 if mctx.ctx.rev() is not None:
325 if mctx.ctx.rev() is not None:
326 return []
326 return []
327 ms = merge.mergestate.read(mctx.ctx.repo())
327 ms = merge.mergestate.read(mctx.ctx.repo())
328 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
328 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
329
329
330 @predicate('hgignore()')
330 @predicate('hgignore()')
331 def hgignore(mctx, x):
331 def hgignore(mctx, x):
332 """File that matches the active .hgignore pattern.
332 """File that matches the active .hgignore pattern.
333 """
333 """
334 # i18n: "hgignore" is a keyword
334 # i18n: "hgignore" is a keyword
335 getargs(x, 0, 0, _("hgignore takes no arguments"))
335 getargs(x, 0, 0, _("hgignore takes no arguments"))
336 ignore = mctx.ctx.repo().dirstate._ignore
336 ignore = mctx.ctx.repo().dirstate._ignore
337 return [f for f in mctx.subset if ignore(f)]
337 return [f for f in mctx.subset if ignore(f)]
338
338
339 @predicate('portable()')
339 @predicate('portable()')
340 def portable(mctx, x):
340 def portable(mctx, x):
341 """File that has a portable name. (This doesn't include filenames with case
341 """File that has a portable name. (This doesn't include filenames with case
342 collisions.)
342 collisions.)
343 """
343 """
344 # i18n: "portable" is a keyword
344 # i18n: "portable" is a keyword
345 getargs(x, 0, 0, _("portable takes no arguments"))
345 getargs(x, 0, 0, _("portable takes no arguments"))
346 checkwinfilename = util.checkwinfilename
346 checkwinfilename = util.checkwinfilename
347 return [f for f in mctx.subset if checkwinfilename(f) is None]
347 return [f for f in mctx.subset if checkwinfilename(f) is None]
348
348
349 @predicate('grep(regex)', callexisting=True)
349 @predicate('grep(regex)', callexisting=True)
350 def grep(mctx, x):
350 def grep(mctx, x):
351 """File contains the given regular expression.
351 """File contains the given regular expression.
352 """
352 """
353 try:
353 try:
354 # i18n: "grep" is a keyword
354 # i18n: "grep" is a keyword
355 r = re.compile(getstring(x, _("grep requires a pattern")))
355 r = re.compile(getstring(x, _("grep requires a pattern")))
356 except re.error as e:
356 except re.error as e:
357 raise error.ParseError(_('invalid match pattern: %s') % e)
357 raise error.ParseError(_('invalid match pattern: %s') % e)
358 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
358 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
359
359
360 def _sizetomax(s):
360 def _sizetomax(s):
361 try:
361 try:
362 s = s.strip().lower()
362 s = s.strip().lower()
363 for k, v in util._sizeunits:
363 for k, v in util._sizeunits:
364 if s.endswith(k):
364 if s.endswith(k):
365 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
365 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
366 n = s[:-len(k)]
366 n = s[:-len(k)]
367 inc = 1.0
367 inc = 1.0
368 if "." in n:
368 if "." in n:
369 inc /= 10 ** len(n.split(".")[1])
369 inc /= 10 ** len(n.split(".")[1])
370 return int((float(n) + inc) * v) - 1
370 return int((float(n) + inc) * v) - 1
371 # no extension, this is a precise value
371 # no extension, this is a precise value
372 return int(s)
372 return int(s)
373 except ValueError:
373 except ValueError:
374 raise error.ParseError(_("couldn't parse size: %s") % s)
374 raise error.ParseError(_("couldn't parse size: %s") % s)
375
375
376 def sizematcher(x):
376 def sizematcher(x):
377 """Return a function(size) -> bool from the ``size()`` expression"""
377 """Return a function(size) -> bool from the ``size()`` expression"""
378
378
379 # i18n: "size" is a keyword
379 # i18n: "size" is a keyword
380 expr = getstring(x, _("size requires an expression")).strip()
380 expr = getstring(x, _("size requires an expression")).strip()
381 if '-' in expr: # do we have a range?
381 if '-' in expr: # do we have a range?
382 a, b = expr.split('-', 1)
382 a, b = expr.split('-', 1)
383 a = util.sizetoint(a)
383 a = util.sizetoint(a)
384 b = util.sizetoint(b)
384 b = util.sizetoint(b)
385 return lambda x: x >= a and x <= b
385 return lambda x: x >= a and x <= b
386 elif expr.startswith("<="):
386 elif expr.startswith("<="):
387 a = util.sizetoint(expr[2:])
387 a = util.sizetoint(expr[2:])
388 return lambda x: x <= a
388 return lambda x: x <= a
389 elif expr.startswith("<"):
389 elif expr.startswith("<"):
390 a = util.sizetoint(expr[1:])
390 a = util.sizetoint(expr[1:])
391 return lambda x: x < a
391 return lambda x: x < a
392 elif expr.startswith(">="):
392 elif expr.startswith(">="):
393 a = util.sizetoint(expr[2:])
393 a = util.sizetoint(expr[2:])
394 return lambda x: x >= a
394 return lambda x: x >= a
395 elif expr.startswith(">"):
395 elif expr.startswith(">"):
396 a = util.sizetoint(expr[1:])
396 a = util.sizetoint(expr[1:])
397 return lambda x: x > a
397 return lambda x: x > a
398 else:
398 else:
399 a = util.sizetoint(expr)
399 a = util.sizetoint(expr)
400 b = _sizetomax(expr)
400 b = _sizetomax(expr)
401 return lambda x: x >= a and x <= b
401 return lambda x: x >= a and x <= b
402
402
403 @predicate('size(expression)', callexisting=True)
403 @predicate('size(expression)', callexisting=True)
404 def size(mctx, x):
404 def size(mctx, x):
405 """File size matches the given expression. Examples:
405 """File size matches the given expression. Examples:
406
406
407 - size('1k') - files from 1024 to 2047 bytes
407 - size('1k') - files from 1024 to 2047 bytes
408 - size('< 20k') - files less than 20480 bytes
408 - size('< 20k') - files less than 20480 bytes
409 - size('>= .5MB') - files at least 524288 bytes
409 - size('>= .5MB') - files at least 524288 bytes
410 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
410 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
411 """
411 """
412 m = sizematcher(x)
412 m = sizematcher(x)
413 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
413 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
414
414
415 @predicate('encoding(name)', callexisting=True)
415 @predicate('encoding(name)', callexisting=True)
416 def encoding(mctx, x):
416 def encoding(mctx, x):
417 """File can be successfully decoded with the given character
417 """File can be successfully decoded with the given character
418 encoding. May not be useful for encodings other than ASCII and
418 encoding. May not be useful for encodings other than ASCII and
419 UTF-8.
419 UTF-8.
420 """
420 """
421
421
422 # i18n: "encoding" is a keyword
422 # i18n: "encoding" is a keyword
423 enc = getstring(x, _("encoding requires an encoding name"))
423 enc = getstring(x, _("encoding requires an encoding name"))
424
424
425 s = []
425 s = []
426 for f in mctx.existing():
426 for f in mctx.existing():
427 d = mctx.ctx[f].data()
427 d = mctx.ctx[f].data()
428 try:
428 try:
429 d.decode(enc)
429 d.decode(enc)
430 except LookupError:
430 except LookupError:
431 raise error.Abort(_("unknown encoding '%s'") % enc)
431 raise error.Abort(_("unknown encoding '%s'") % enc)
432 except UnicodeDecodeError:
432 except UnicodeDecodeError:
433 continue
433 continue
434 s.append(f)
434 s.append(f)
435
435
436 return s
436 return s
437
437
438 @predicate('eol(style)', callexisting=True)
438 @predicate('eol(style)', callexisting=True)
439 def eol(mctx, x):
439 def eol(mctx, x):
440 """File contains newlines of the given style (dos, unix, mac). Binary
440 """File contains newlines of the given style (dos, unix, mac). Binary
441 files are excluded, files with mixed line endings match multiple
441 files are excluded, files with mixed line endings match multiple
442 styles.
442 styles.
443 """
443 """
444
444
445 # i18n: "eol" is a keyword
445 # i18n: "eol" is a keyword
446 enc = getstring(x, _("eol requires a style name"))
446 enc = getstring(x, _("eol requires a style name"))
447
447
448 s = []
448 s = []
449 for f in mctx.existing():
449 for f in mctx.existing():
450 d = mctx.ctx[f].data()
450 d = mctx.ctx[f].data()
451 if stringutil.binary(d):
451 if stringutil.binary(d):
452 continue
452 continue
453 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
453 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
454 s.append(f)
454 s.append(f)
455 elif enc == 'unix' and re.search('(?<!\r)\n', d):
455 elif enc == 'unix' and re.search('(?<!\r)\n', d):
456 s.append(f)
456 s.append(f)
457 elif enc == 'mac' and re.search('\r(?!\n)', d):
457 elif enc == 'mac' and re.search('\r(?!\n)', d):
458 s.append(f)
458 s.append(f)
459 return s
459 return s
460
460
461 @predicate('copied()')
461 @predicate('copied()')
462 def copied(mctx, x):
462 def copied(mctx, x):
463 """File that is recorded as being copied.
463 """File that is recorded as being copied.
464 """
464 """
465 # i18n: "copied" is a keyword
465 # i18n: "copied" is a keyword
466 getargs(x, 0, 0, _("copied takes no arguments"))
466 getargs(x, 0, 0, _("copied takes no arguments"))
467 s = []
467 s = []
468 for f in mctx.subset:
468 for f in mctx.subset:
469 if f in mctx.ctx:
469 if f in mctx.ctx:
470 p = mctx.ctx[f].parents()
470 p = mctx.ctx[f].parents()
471 if p and p[0].path() != f:
471 if p and p[0].path() != f:
472 s.append(f)
472 s.append(f)
473 return s
473 return s
474
474
475 @predicate('revs(revs, pattern)')
475 @predicate('revs(revs, pattern)')
476 def revs(mctx, x):
476 def revs(mctx, x):
477 """Evaluate set in the specified revisions. If the revset match multiple
477 """Evaluate set in the specified revisions. If the revset match multiple
478 revs, this will return file matching pattern in any of the revision.
478 revs, this will return file matching pattern in any of the revision.
479 """
479 """
480 # i18n: "revs" is a keyword
480 # i18n: "revs" is a keyword
481 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
481 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
482 # i18n: "revs" is a keyword
482 # i18n: "revs" is a keyword
483 revspec = getstring(r, _("first argument to revs must be a revision"))
483 revspec = getstring(r, _("first argument to revs must be a revision"))
484 repo = mctx.ctx.repo()
484 repo = mctx.ctx.repo()
485 revs = scmutil.revrange(repo, [revspec])
485 revs = scmutil.revrange(repo, [revspec])
486
486
487 found = set()
487 found = set()
488 result = []
488 result = []
489 for r in revs:
489 for r in revs:
490 ctx = repo[r]
490 ctx = repo[r]
491 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
491 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
492 if f not in found:
492 if f not in found:
493 found.add(f)
493 found.add(f)
494 result.append(f)
494 result.append(f)
495 return result
495 return result
496
496
497 @predicate('status(base, rev, pattern)')
497 @predicate('status(base, rev, pattern)')
498 def status(mctx, x):
498 def status(mctx, x):
499 """Evaluate predicate using status change between ``base`` and
499 """Evaluate predicate using status change between ``base`` and
500 ``rev``. Examples:
500 ``rev``. Examples:
501
501
502 - ``status(3, 7, added())`` - matches files added from "3" to "7"
502 - ``status(3, 7, added())`` - matches files added from "3" to "7"
503 """
503 """
504 repo = mctx.ctx.repo()
504 repo = mctx.ctx.repo()
505 # i18n: "status" is a keyword
505 # i18n: "status" is a keyword
506 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
506 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
507 # i18n: "status" is a keyword
507 # i18n: "status" is a keyword
508 baseerr = _("first argument to status must be a revision")
508 baseerr = _("first argument to status must be a revision")
509 baserevspec = getstring(b, baseerr)
509 baserevspec = getstring(b, baseerr)
510 if not baserevspec:
510 if not baserevspec:
511 raise error.ParseError(baseerr)
511 raise error.ParseError(baseerr)
512 reverr = _("second argument to status must be a revision")
512 reverr = _("second argument to status must be a revision")
513 revspec = getstring(r, reverr)
513 revspec = getstring(r, reverr)
514 if not revspec:
514 if not revspec:
515 raise error.ParseError(reverr)
515 raise error.ParseError(reverr)
516 basenode, node = scmutil.revpairnodes(repo, [baserevspec, revspec])
516 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
517 basectx = repo[basenode]
518 ctx = repo[node]
519 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
517 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
520
518
521 @predicate('subrepo([pattern])')
519 @predicate('subrepo([pattern])')
522 def subrepo(mctx, x):
520 def subrepo(mctx, x):
523 """Subrepositories whose paths match the given pattern.
521 """Subrepositories whose paths match the given pattern.
524 """
522 """
525 # i18n: "subrepo" is a keyword
523 # i18n: "subrepo" is a keyword
526 getargs(x, 0, 1, _("subrepo takes at most one argument"))
524 getargs(x, 0, 1, _("subrepo takes at most one argument"))
527 ctx = mctx.ctx
525 ctx = mctx.ctx
528 sstate = sorted(ctx.substate)
526 sstate = sorted(ctx.substate)
529 if x:
527 if x:
530 pat = getpattern(x, matchmod.allpatternkinds,
528 pat = getpattern(x, matchmod.allpatternkinds,
531 # i18n: "subrepo" is a keyword
529 # i18n: "subrepo" is a keyword
532 _("subrepo requires a pattern or no arguments"))
530 _("subrepo requires a pattern or no arguments"))
533 fast = not matchmod.patkind(pat)
531 fast = not matchmod.patkind(pat)
534 if fast:
532 if fast:
535 def m(s):
533 def m(s):
536 return (s == pat)
534 return (s == pat)
537 else:
535 else:
538 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
536 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
539 return [sub for sub in sstate if m(sub)]
537 return [sub for sub in sstate if m(sub)]
540 else:
538 else:
541 return [sub for sub in sstate]
539 return [sub for sub in sstate]
542
540
543 methods = {
541 methods = {
544 'string': stringset,
542 'string': stringset,
545 'symbol': stringset,
543 'symbol': stringset,
546 'kindpat': kindpatset,
544 'kindpat': kindpatset,
547 'and': andset,
545 'and': andset,
548 'or': orset,
546 'or': orset,
549 'minus': minusset,
547 'minus': minusset,
550 'negate': negateset,
548 'negate': negateset,
551 'list': listset,
549 'list': listset,
552 'group': getset,
550 'group': getset,
553 'not': notset,
551 'not': notset,
554 'func': func,
552 'func': func,
555 }
553 }
556
554
557 class matchctx(object):
555 class matchctx(object):
558 def __init__(self, ctx, subset, status=None):
556 def __init__(self, ctx, subset, status=None):
559 self.ctx = ctx
557 self.ctx = ctx
560 self.subset = subset
558 self.subset = subset
561 self._status = status
559 self._status = status
562 self._existingenabled = False
560 self._existingenabled = False
563 def status(self):
561 def status(self):
564 return self._status
562 return self._status
565 def matcher(self, patterns):
563 def matcher(self, patterns):
566 return self.ctx.match(patterns)
564 return self.ctx.match(patterns)
567 def filter(self, files):
565 def filter(self, files):
568 return [f for f in files if f in self.subset]
566 return [f for f in files if f in self.subset]
569 def existing(self):
567 def existing(self):
570 assert self._existingenabled, 'unexpected existing() invocation'
568 assert self._existingenabled, 'unexpected existing() invocation'
571 if self._status is not None:
569 if self._status is not None:
572 removed = set(self._status[3])
570 removed = set(self._status[3])
573 unknown = set(self._status[4] + self._status[5])
571 unknown = set(self._status[4] + self._status[5])
574 else:
572 else:
575 removed = set()
573 removed = set()
576 unknown = set()
574 unknown = set()
577 return (f for f in self.subset
575 return (f for f in self.subset
578 if (f in self.ctx and f not in removed) or f in unknown)
576 if (f in self.ctx and f not in removed) or f in unknown)
579 def narrow(self, files):
577 def narrow(self, files):
580 return matchctx(self.ctx, self.filter(files), self._status)
578 return matchctx(self.ctx, self.filter(files), self._status)
581 def switch(self, ctx, status=None):
579 def switch(self, ctx, status=None):
582 subset = self.filter(_buildsubset(ctx, status))
580 subset = self.filter(_buildsubset(ctx, status))
583 return matchctx(ctx, subset, status)
581 return matchctx(ctx, subset, status)
584
582
585 class fullmatchctx(matchctx):
583 class fullmatchctx(matchctx):
586 """A match context where any files in any revisions should be valid"""
584 """A match context where any files in any revisions should be valid"""
587
585
588 def __init__(self, ctx, status=None):
586 def __init__(self, ctx, status=None):
589 subset = _buildsubset(ctx, status)
587 subset = _buildsubset(ctx, status)
590 super(fullmatchctx, self).__init__(ctx, subset, status)
588 super(fullmatchctx, self).__init__(ctx, subset, status)
591 def switch(self, ctx, status=None):
589 def switch(self, ctx, status=None):
592 return fullmatchctx(ctx, status)
590 return fullmatchctx(ctx, status)
593
591
594 # filesets using matchctx.switch()
592 # filesets using matchctx.switch()
595 _switchcallers = [
593 _switchcallers = [
596 'revs',
594 'revs',
597 'status',
595 'status',
598 ]
596 ]
599
597
600 def _intree(funcs, tree):
598 def _intree(funcs, tree):
601 if isinstance(tree, tuple):
599 if isinstance(tree, tuple):
602 if tree[0] == 'func' and tree[1][0] == 'symbol':
600 if tree[0] == 'func' and tree[1][0] == 'symbol':
603 if tree[1][1] in funcs:
601 if tree[1][1] in funcs:
604 return True
602 return True
605 if tree[1][1] in _switchcallers:
603 if tree[1][1] in _switchcallers:
606 # arguments won't be evaluated in the current context
604 # arguments won't be evaluated in the current context
607 return False
605 return False
608 for s in tree[1:]:
606 for s in tree[1:]:
609 if _intree(funcs, s):
607 if _intree(funcs, s):
610 return True
608 return True
611 return False
609 return False
612
610
613 def _buildsubset(ctx, status):
611 def _buildsubset(ctx, status):
614 if status:
612 if status:
615 subset = []
613 subset = []
616 for c in status:
614 for c in status:
617 subset.extend(c)
615 subset.extend(c)
618 return subset
616 return subset
619 else:
617 else:
620 return list(ctx.walk(ctx.match([])))
618 return list(ctx.walk(ctx.match([])))
621
619
622 def getfileset(ctx, expr):
620 def getfileset(ctx, expr):
623 tree = parse(expr)
621 tree = parse(expr)
624 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
622 return getset(fullmatchctx(ctx, _buildstatus(ctx, tree)), tree)
625
623
626 def _buildstatus(ctx, tree, basectx=None):
624 def _buildstatus(ctx, tree, basectx=None):
627 # do we need status info?
625 # do we need status info?
628
626
629 # temporaty boolean to simplify the next conditional
627 # temporaty boolean to simplify the next conditional
630 purewdir = ctx.rev() is None and basectx is None
628 purewdir = ctx.rev() is None and basectx is None
631
629
632 if (_intree(_statuscallers, tree) or
630 if (_intree(_statuscallers, tree) or
633 # Using matchctx.existing() on a workingctx requires us to check
631 # Using matchctx.existing() on a workingctx requires us to check
634 # for deleted files.
632 # for deleted files.
635 (purewdir and _intree(_existingcallers, tree))):
633 (purewdir and _intree(_existingcallers, tree))):
636 unknown = _intree(['unknown'], tree)
634 unknown = _intree(['unknown'], tree)
637 ignored = _intree(['ignored'], tree)
635 ignored = _intree(['ignored'], tree)
638
636
639 r = ctx.repo()
637 r = ctx.repo()
640 if basectx is None:
638 if basectx is None:
641 basectx = ctx.p1()
639 basectx = ctx.p1()
642 return r.status(basectx, ctx,
640 return r.status(basectx, ctx,
643 unknown=unknown, ignored=ignored, clean=True)
641 unknown=unknown, ignored=ignored, clean=True)
644 else:
642 else:
645 return None
643 return None
646
644
647 def prettyformat(tree):
645 def prettyformat(tree):
648 return parser.prettyformat(tree, ('string', 'symbol'))
646 return parser.prettyformat(tree, ('string', 'symbol'))
649
647
650 def loadpredicate(ui, extname, registrarobj):
648 def loadpredicate(ui, extname, registrarobj):
651 """Load fileset predicates from specified registrarobj
649 """Load fileset predicates from specified registrarobj
652 """
650 """
653 for name, func in registrarobj._table.iteritems():
651 for name, func in registrarobj._table.iteritems():
654 symbols[name] = func
652 symbols[name] = func
655 if func._callstatus:
653 if func._callstatus:
656 _statuscallers.add(name)
654 _statuscallers.add(name)
657 if func._callexisting:
655 if func._callexisting:
658 _existingcallers.add(name)
656 _existingcallers.add(name)
659
657
660 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
658 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
661 loadpredicate(None, None, predicate)
659 loadpredicate(None, None, predicate)
662
660
663 # tell hggettext to extract docstrings from these functions:
661 # tell hggettext to extract docstrings from these functions:
664 i18nfunctions = symbols.values()
662 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now