##// END OF EJS Templates
fileset: keep basectx by matchctx
Yuya Nishihara -
r38913:8d6780f0 default
parent child Browse files
Show More
@@ -1,570 +1,572 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 errno
10 import errno
11 import re
11 import re
12
12
13 from .i18n import _
13 from .i18n import _
14 from . import (
14 from . import (
15 error,
15 error,
16 filesetlang,
16 filesetlang,
17 match as matchmod,
17 match as matchmod,
18 merge,
18 merge,
19 pycompat,
19 pycompat,
20 registrar,
20 registrar,
21 scmutil,
21 scmutil,
22 util,
22 util,
23 )
23 )
24 from .utils import (
24 from .utils import (
25 stringutil,
25 stringutil,
26 )
26 )
27
27
28 # common weight constants
28 # common weight constants
29 _WEIGHT_CHECK_FILENAME = filesetlang.WEIGHT_CHECK_FILENAME
29 _WEIGHT_CHECK_FILENAME = filesetlang.WEIGHT_CHECK_FILENAME
30 _WEIGHT_READ_CONTENTS = filesetlang.WEIGHT_READ_CONTENTS
30 _WEIGHT_READ_CONTENTS = filesetlang.WEIGHT_READ_CONTENTS
31 _WEIGHT_STATUS = filesetlang.WEIGHT_STATUS
31 _WEIGHT_STATUS = filesetlang.WEIGHT_STATUS
32 _WEIGHT_STATUS_THOROUGH = filesetlang.WEIGHT_STATUS_THOROUGH
32 _WEIGHT_STATUS_THOROUGH = filesetlang.WEIGHT_STATUS_THOROUGH
33
33
34 # helpers for processing parsed tree
34 # helpers for processing parsed tree
35 getsymbol = filesetlang.getsymbol
35 getsymbol = filesetlang.getsymbol
36 getstring = filesetlang.getstring
36 getstring = filesetlang.getstring
37 _getkindpat = filesetlang.getkindpat
37 _getkindpat = filesetlang.getkindpat
38 getpattern = filesetlang.getpattern
38 getpattern = filesetlang.getpattern
39 getargs = filesetlang.getargs
39 getargs = filesetlang.getargs
40
40
41 def getmatch(mctx, x):
41 def getmatch(mctx, x):
42 if not x:
42 if not x:
43 raise error.ParseError(_("missing argument"))
43 raise error.ParseError(_("missing argument"))
44 return methods[x[0]](mctx, *x[1:])
44 return methods[x[0]](mctx, *x[1:])
45
45
46 def stringmatch(mctx, x):
46 def stringmatch(mctx, x):
47 return mctx.matcher([x])
47 return mctx.matcher([x])
48
48
49 def kindpatmatch(mctx, x, y):
49 def kindpatmatch(mctx, x, y):
50 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
50 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
51 _("pattern must be a string")))
51 _("pattern must be a string")))
52
52
53 def patternsmatch(mctx, *xs):
53 def patternsmatch(mctx, *xs):
54 allkinds = matchmod.allpatternkinds
54 allkinds = matchmod.allpatternkinds
55 patterns = [getpattern(x, allkinds, _("pattern must be a string"))
55 patterns = [getpattern(x, allkinds, _("pattern must be a string"))
56 for x in xs]
56 for x in xs]
57 return mctx.matcher(patterns)
57 return mctx.matcher(patterns)
58
58
59 def andmatch(mctx, x, y):
59 def andmatch(mctx, x, y):
60 xm = getmatch(mctx, x)
60 xm = getmatch(mctx, x)
61 ym = getmatch(mctx, y)
61 ym = getmatch(mctx, y)
62 return matchmod.intersectmatchers(xm, ym)
62 return matchmod.intersectmatchers(xm, ym)
63
63
64 def ormatch(mctx, *xs):
64 def ormatch(mctx, *xs):
65 ms = [getmatch(mctx, x) for x in xs]
65 ms = [getmatch(mctx, x) for x in xs]
66 return matchmod.unionmatcher(ms)
66 return matchmod.unionmatcher(ms)
67
67
68 def notmatch(mctx, x):
68 def notmatch(mctx, x):
69 m = getmatch(mctx, x)
69 m = getmatch(mctx, x)
70 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
70 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
71
71
72 def minusmatch(mctx, x, y):
72 def minusmatch(mctx, x, y):
73 xm = getmatch(mctx, x)
73 xm = getmatch(mctx, x)
74 ym = getmatch(mctx, y)
74 ym = getmatch(mctx, y)
75 return matchmod.differencematcher(xm, ym)
75 return matchmod.differencematcher(xm, ym)
76
76
77 def listmatch(mctx, *xs):
77 def listmatch(mctx, *xs):
78 raise error.ParseError(_("can't use a list in this context"),
78 raise error.ParseError(_("can't use a list in this context"),
79 hint=_('see \'hg help "filesets.x or y"\''))
79 hint=_('see \'hg help "filesets.x or y"\''))
80
80
81 def func(mctx, a, b):
81 def func(mctx, a, b):
82 funcname = getsymbol(a)
82 funcname = getsymbol(a)
83 if funcname in symbols:
83 if funcname in symbols:
84 return symbols[funcname](mctx, b)
84 return symbols[funcname](mctx, b)
85
85
86 keep = lambda fn: getattr(fn, '__doc__', None) is not None
86 keep = lambda fn: getattr(fn, '__doc__', None) is not None
87
87
88 syms = [s for (s, fn) in symbols.items() if keep(fn)]
88 syms = [s for (s, fn) in symbols.items() if keep(fn)]
89 raise error.UnknownIdentifier(funcname, syms)
89 raise error.UnknownIdentifier(funcname, syms)
90
90
91 # symbols are callable like:
91 # symbols are callable like:
92 # fun(mctx, x)
92 # fun(mctx, x)
93 # with:
93 # with:
94 # mctx - current matchctx instance
94 # mctx - current matchctx instance
95 # x - argument in tree form
95 # x - argument in tree form
96 symbols = filesetlang.symbols
96 symbols = filesetlang.symbols
97
97
98 # filesets using matchctx.status()
98 # filesets using matchctx.status()
99 _statuscallers = set()
99 _statuscallers = set()
100
100
101 predicate = registrar.filesetpredicate()
101 predicate = registrar.filesetpredicate()
102
102
103 @predicate('modified()', callstatus=True, weight=_WEIGHT_STATUS)
103 @predicate('modified()', callstatus=True, weight=_WEIGHT_STATUS)
104 def modified(mctx, x):
104 def modified(mctx, x):
105 """File that is modified according to :hg:`status`.
105 """File that is modified according to :hg:`status`.
106 """
106 """
107 # i18n: "modified" is a keyword
107 # i18n: "modified" is a keyword
108 getargs(x, 0, 0, _("modified takes no arguments"))
108 getargs(x, 0, 0, _("modified takes no arguments"))
109 s = set(mctx.status().modified)
109 s = set(mctx.status().modified)
110 return mctx.predicate(s.__contains__, predrepr='modified')
110 return mctx.predicate(s.__contains__, predrepr='modified')
111
111
112 @predicate('added()', callstatus=True, weight=_WEIGHT_STATUS)
112 @predicate('added()', callstatus=True, weight=_WEIGHT_STATUS)
113 def added(mctx, x):
113 def added(mctx, x):
114 """File that is added according to :hg:`status`.
114 """File that is added according to :hg:`status`.
115 """
115 """
116 # i18n: "added" is a keyword
116 # i18n: "added" is a keyword
117 getargs(x, 0, 0, _("added takes no arguments"))
117 getargs(x, 0, 0, _("added takes no arguments"))
118 s = set(mctx.status().added)
118 s = set(mctx.status().added)
119 return mctx.predicate(s.__contains__, predrepr='added')
119 return mctx.predicate(s.__contains__, predrepr='added')
120
120
121 @predicate('removed()', callstatus=True, weight=_WEIGHT_STATUS)
121 @predicate('removed()', callstatus=True, weight=_WEIGHT_STATUS)
122 def removed(mctx, x):
122 def removed(mctx, x):
123 """File that is removed according to :hg:`status`.
123 """File that is removed according to :hg:`status`.
124 """
124 """
125 # i18n: "removed" is a keyword
125 # i18n: "removed" is a keyword
126 getargs(x, 0, 0, _("removed takes no arguments"))
126 getargs(x, 0, 0, _("removed takes no arguments"))
127 s = set(mctx.status().removed)
127 s = set(mctx.status().removed)
128 return mctx.predicate(s.__contains__, predrepr='removed')
128 return mctx.predicate(s.__contains__, predrepr='removed')
129
129
130 @predicate('deleted()', callstatus=True, weight=_WEIGHT_STATUS)
130 @predicate('deleted()', callstatus=True, weight=_WEIGHT_STATUS)
131 def deleted(mctx, x):
131 def deleted(mctx, x):
132 """Alias for ``missing()``.
132 """Alias for ``missing()``.
133 """
133 """
134 # i18n: "deleted" is a keyword
134 # i18n: "deleted" is a keyword
135 getargs(x, 0, 0, _("deleted takes no arguments"))
135 getargs(x, 0, 0, _("deleted takes no arguments"))
136 s = set(mctx.status().deleted)
136 s = set(mctx.status().deleted)
137 return mctx.predicate(s.__contains__, predrepr='deleted')
137 return mctx.predicate(s.__contains__, predrepr='deleted')
138
138
139 @predicate('missing()', callstatus=True, weight=_WEIGHT_STATUS)
139 @predicate('missing()', callstatus=True, weight=_WEIGHT_STATUS)
140 def missing(mctx, x):
140 def missing(mctx, x):
141 """File that is missing according to :hg:`status`.
141 """File that is missing according to :hg:`status`.
142 """
142 """
143 # i18n: "missing" is a keyword
143 # i18n: "missing" is a keyword
144 getargs(x, 0, 0, _("missing takes no arguments"))
144 getargs(x, 0, 0, _("missing takes no arguments"))
145 s = set(mctx.status().deleted)
145 s = set(mctx.status().deleted)
146 return mctx.predicate(s.__contains__, predrepr='deleted')
146 return mctx.predicate(s.__contains__, predrepr='deleted')
147
147
148 @predicate('unknown()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
148 @predicate('unknown()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
149 def unknown(mctx, x):
149 def unknown(mctx, x):
150 """File that is unknown according to :hg:`status`."""
150 """File that is unknown according to :hg:`status`."""
151 # i18n: "unknown" is a keyword
151 # i18n: "unknown" is a keyword
152 getargs(x, 0, 0, _("unknown takes no arguments"))
152 getargs(x, 0, 0, _("unknown takes no arguments"))
153 s = set(mctx.status().unknown)
153 s = set(mctx.status().unknown)
154 return mctx.predicate(s.__contains__, predrepr='unknown')
154 return mctx.predicate(s.__contains__, predrepr='unknown')
155
155
156 @predicate('ignored()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
156 @predicate('ignored()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
157 def ignored(mctx, x):
157 def ignored(mctx, x):
158 """File that is ignored according to :hg:`status`."""
158 """File that is ignored according to :hg:`status`."""
159 # i18n: "ignored" is a keyword
159 # i18n: "ignored" is a keyword
160 getargs(x, 0, 0, _("ignored takes no arguments"))
160 getargs(x, 0, 0, _("ignored takes no arguments"))
161 s = set(mctx.status().ignored)
161 s = set(mctx.status().ignored)
162 return mctx.predicate(s.__contains__, predrepr='ignored')
162 return mctx.predicate(s.__contains__, predrepr='ignored')
163
163
164 @predicate('clean()', callstatus=True, weight=_WEIGHT_STATUS)
164 @predicate('clean()', callstatus=True, weight=_WEIGHT_STATUS)
165 def clean(mctx, x):
165 def clean(mctx, x):
166 """File that is clean according to :hg:`status`.
166 """File that is clean according to :hg:`status`.
167 """
167 """
168 # i18n: "clean" is a keyword
168 # i18n: "clean" is a keyword
169 getargs(x, 0, 0, _("clean takes no arguments"))
169 getargs(x, 0, 0, _("clean takes no arguments"))
170 s = set(mctx.status().clean)
170 s = set(mctx.status().clean)
171 return mctx.predicate(s.__contains__, predrepr='clean')
171 return mctx.predicate(s.__contains__, predrepr='clean')
172
172
173 @predicate('tracked()')
173 @predicate('tracked()')
174 def tracked(mctx, x):
174 def tracked(mctx, x):
175 """File that is under Mercurial control."""
175 """File that is under Mercurial control."""
176 # i18n: "tracked" is a keyword
176 # i18n: "tracked" is a keyword
177 getargs(x, 0, 0, _("tracked takes no arguments"))
177 getargs(x, 0, 0, _("tracked takes no arguments"))
178 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
178 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
179
179
180 @predicate('binary()', weight=_WEIGHT_READ_CONTENTS)
180 @predicate('binary()', weight=_WEIGHT_READ_CONTENTS)
181 def binary(mctx, x):
181 def binary(mctx, x):
182 """File that appears to be binary (contains NUL bytes).
182 """File that appears to be binary (contains NUL bytes).
183 """
183 """
184 # i18n: "binary" is a keyword
184 # i18n: "binary" is a keyword
185 getargs(x, 0, 0, _("binary takes no arguments"))
185 getargs(x, 0, 0, _("binary takes no arguments"))
186 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
186 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
187 predrepr='binary', cache=True)
187 predrepr='binary', cache=True)
188
188
189 @predicate('exec()')
189 @predicate('exec()')
190 def exec_(mctx, x):
190 def exec_(mctx, x):
191 """File that is marked as executable.
191 """File that is marked as executable.
192 """
192 """
193 # i18n: "exec" is a keyword
193 # i18n: "exec" is a keyword
194 getargs(x, 0, 0, _("exec takes no arguments"))
194 getargs(x, 0, 0, _("exec takes no arguments"))
195 ctx = mctx.ctx
195 ctx = mctx.ctx
196 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
196 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
197
197
198 @predicate('symlink()')
198 @predicate('symlink()')
199 def symlink(mctx, x):
199 def symlink(mctx, x):
200 """File that is marked as a symlink.
200 """File that is marked as a symlink.
201 """
201 """
202 # i18n: "symlink" is a keyword
202 # i18n: "symlink" is a keyword
203 getargs(x, 0, 0, _("symlink takes no arguments"))
203 getargs(x, 0, 0, _("symlink takes no arguments"))
204 ctx = mctx.ctx
204 ctx = mctx.ctx
205 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
205 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
206
206
207 @predicate('resolved()', weight=_WEIGHT_STATUS)
207 @predicate('resolved()', weight=_WEIGHT_STATUS)
208 def resolved(mctx, x):
208 def resolved(mctx, x):
209 """File that is marked resolved according to :hg:`resolve -l`.
209 """File that is marked resolved according to :hg:`resolve -l`.
210 """
210 """
211 # i18n: "resolved" is a keyword
211 # i18n: "resolved" is a keyword
212 getargs(x, 0, 0, _("resolved takes no arguments"))
212 getargs(x, 0, 0, _("resolved takes no arguments"))
213 if mctx.ctx.rev() is not None:
213 if mctx.ctx.rev() is not None:
214 return mctx.never()
214 return mctx.never()
215 ms = merge.mergestate.read(mctx.ctx.repo())
215 ms = merge.mergestate.read(mctx.ctx.repo())
216 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
216 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
217 predrepr='resolved')
217 predrepr='resolved')
218
218
219 @predicate('unresolved()', weight=_WEIGHT_STATUS)
219 @predicate('unresolved()', weight=_WEIGHT_STATUS)
220 def unresolved(mctx, x):
220 def unresolved(mctx, x):
221 """File that is marked unresolved according to :hg:`resolve -l`.
221 """File that is marked unresolved according to :hg:`resolve -l`.
222 """
222 """
223 # i18n: "unresolved" is a keyword
223 # i18n: "unresolved" is a keyword
224 getargs(x, 0, 0, _("unresolved takes no arguments"))
224 getargs(x, 0, 0, _("unresolved takes no arguments"))
225 if mctx.ctx.rev() is not None:
225 if mctx.ctx.rev() is not None:
226 return mctx.never()
226 return mctx.never()
227 ms = merge.mergestate.read(mctx.ctx.repo())
227 ms = merge.mergestate.read(mctx.ctx.repo())
228 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
228 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
229 predrepr='unresolved')
229 predrepr='unresolved')
230
230
231 @predicate('hgignore()', weight=_WEIGHT_STATUS)
231 @predicate('hgignore()', weight=_WEIGHT_STATUS)
232 def hgignore(mctx, x):
232 def hgignore(mctx, x):
233 """File that matches the active .hgignore pattern.
233 """File that matches the active .hgignore pattern.
234 """
234 """
235 # i18n: "hgignore" is a keyword
235 # i18n: "hgignore" is a keyword
236 getargs(x, 0, 0, _("hgignore takes no arguments"))
236 getargs(x, 0, 0, _("hgignore takes no arguments"))
237 return mctx.ctx.repo().dirstate._ignore
237 return mctx.ctx.repo().dirstate._ignore
238
238
239 @predicate('portable()', weight=_WEIGHT_CHECK_FILENAME)
239 @predicate('portable()', weight=_WEIGHT_CHECK_FILENAME)
240 def portable(mctx, x):
240 def portable(mctx, x):
241 """File that has a portable name. (This doesn't include filenames with case
241 """File that has a portable name. (This doesn't include filenames with case
242 collisions.)
242 collisions.)
243 """
243 """
244 # i18n: "portable" is a keyword
244 # i18n: "portable" is a keyword
245 getargs(x, 0, 0, _("portable takes no arguments"))
245 getargs(x, 0, 0, _("portable takes no arguments"))
246 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
246 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
247 predrepr='portable')
247 predrepr='portable')
248
248
249 @predicate('grep(regex)', weight=_WEIGHT_READ_CONTENTS)
249 @predicate('grep(regex)', weight=_WEIGHT_READ_CONTENTS)
250 def grep(mctx, x):
250 def grep(mctx, x):
251 """File contains the given regular expression.
251 """File contains the given regular expression.
252 """
252 """
253 try:
253 try:
254 # i18n: "grep" is a keyword
254 # i18n: "grep" is a keyword
255 r = re.compile(getstring(x, _("grep requires a pattern")))
255 r = re.compile(getstring(x, _("grep requires a pattern")))
256 except re.error as e:
256 except re.error as e:
257 raise error.ParseError(_('invalid match pattern: %s') %
257 raise error.ParseError(_('invalid match pattern: %s') %
258 stringutil.forcebytestr(e))
258 stringutil.forcebytestr(e))
259 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
259 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
260 predrepr=('grep(%r)', r.pattern), cache=True)
260 predrepr=('grep(%r)', r.pattern), cache=True)
261
261
262 def _sizetomax(s):
262 def _sizetomax(s):
263 try:
263 try:
264 s = s.strip().lower()
264 s = s.strip().lower()
265 for k, v in util._sizeunits:
265 for k, v in util._sizeunits:
266 if s.endswith(k):
266 if s.endswith(k):
267 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
267 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
268 n = s[:-len(k)]
268 n = s[:-len(k)]
269 inc = 1.0
269 inc = 1.0
270 if "." in n:
270 if "." in n:
271 inc /= 10 ** len(n.split(".")[1])
271 inc /= 10 ** len(n.split(".")[1])
272 return int((float(n) + inc) * v) - 1
272 return int((float(n) + inc) * v) - 1
273 # no extension, this is a precise value
273 # no extension, this is a precise value
274 return int(s)
274 return int(s)
275 except ValueError:
275 except ValueError:
276 raise error.ParseError(_("couldn't parse size: %s") % s)
276 raise error.ParseError(_("couldn't parse size: %s") % s)
277
277
278 def sizematcher(expr):
278 def sizematcher(expr):
279 """Return a function(size) -> bool from the ``size()`` expression"""
279 """Return a function(size) -> bool from the ``size()`` expression"""
280 expr = expr.strip()
280 expr = expr.strip()
281 if '-' in expr: # do we have a range?
281 if '-' in expr: # do we have a range?
282 a, b = expr.split('-', 1)
282 a, b = expr.split('-', 1)
283 a = util.sizetoint(a)
283 a = util.sizetoint(a)
284 b = util.sizetoint(b)
284 b = util.sizetoint(b)
285 return lambda x: x >= a and x <= b
285 return lambda x: x >= a and x <= b
286 elif expr.startswith("<="):
286 elif expr.startswith("<="):
287 a = util.sizetoint(expr[2:])
287 a = util.sizetoint(expr[2:])
288 return lambda x: x <= a
288 return lambda x: x <= a
289 elif expr.startswith("<"):
289 elif expr.startswith("<"):
290 a = util.sizetoint(expr[1:])
290 a = util.sizetoint(expr[1:])
291 return lambda x: x < a
291 return lambda x: x < a
292 elif expr.startswith(">="):
292 elif expr.startswith(">="):
293 a = util.sizetoint(expr[2:])
293 a = util.sizetoint(expr[2:])
294 return lambda x: x >= a
294 return lambda x: x >= a
295 elif expr.startswith(">"):
295 elif expr.startswith(">"):
296 a = util.sizetoint(expr[1:])
296 a = util.sizetoint(expr[1:])
297 return lambda x: x > a
297 return lambda x: x > a
298 else:
298 else:
299 a = util.sizetoint(expr)
299 a = util.sizetoint(expr)
300 b = _sizetomax(expr)
300 b = _sizetomax(expr)
301 return lambda x: x >= a and x <= b
301 return lambda x: x >= a and x <= b
302
302
303 @predicate('size(expression)', weight=_WEIGHT_STATUS)
303 @predicate('size(expression)', weight=_WEIGHT_STATUS)
304 def size(mctx, x):
304 def size(mctx, x):
305 """File size matches the given expression. Examples:
305 """File size matches the given expression. Examples:
306
306
307 - size('1k') - files from 1024 to 2047 bytes
307 - size('1k') - files from 1024 to 2047 bytes
308 - size('< 20k') - files less than 20480 bytes
308 - size('< 20k') - files less than 20480 bytes
309 - size('>= .5MB') - files at least 524288 bytes
309 - size('>= .5MB') - files at least 524288 bytes
310 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
310 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
311 """
311 """
312 # i18n: "size" is a keyword
312 # i18n: "size" is a keyword
313 expr = getstring(x, _("size requires an expression"))
313 expr = getstring(x, _("size requires an expression"))
314 m = sizematcher(expr)
314 m = sizematcher(expr)
315 return mctx.fpredicate(lambda fctx: m(fctx.size()),
315 return mctx.fpredicate(lambda fctx: m(fctx.size()),
316 predrepr=('size(%r)', expr), cache=True)
316 predrepr=('size(%r)', expr), cache=True)
317
317
318 @predicate('encoding(name)', weight=_WEIGHT_READ_CONTENTS)
318 @predicate('encoding(name)', weight=_WEIGHT_READ_CONTENTS)
319 def encoding(mctx, x):
319 def encoding(mctx, x):
320 """File can be successfully decoded with the given character
320 """File can be successfully decoded with the given character
321 encoding. May not be useful for encodings other than ASCII and
321 encoding. May not be useful for encodings other than ASCII and
322 UTF-8.
322 UTF-8.
323 """
323 """
324
324
325 # i18n: "encoding" is a keyword
325 # i18n: "encoding" is a keyword
326 enc = getstring(x, _("encoding requires an encoding name"))
326 enc = getstring(x, _("encoding requires an encoding name"))
327
327
328 def encp(fctx):
328 def encp(fctx):
329 d = fctx.data()
329 d = fctx.data()
330 try:
330 try:
331 d.decode(pycompat.sysstr(enc))
331 d.decode(pycompat.sysstr(enc))
332 return True
332 return True
333 except LookupError:
333 except LookupError:
334 raise error.Abort(_("unknown encoding '%s'") % enc)
334 raise error.Abort(_("unknown encoding '%s'") % enc)
335 except UnicodeDecodeError:
335 except UnicodeDecodeError:
336 return False
336 return False
337
337
338 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
338 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
339
339
340 @predicate('eol(style)', weight=_WEIGHT_READ_CONTENTS)
340 @predicate('eol(style)', weight=_WEIGHT_READ_CONTENTS)
341 def eol(mctx, x):
341 def eol(mctx, x):
342 """File contains newlines of the given style (dos, unix, mac). Binary
342 """File contains newlines of the given style (dos, unix, mac). Binary
343 files are excluded, files with mixed line endings match multiple
343 files are excluded, files with mixed line endings match multiple
344 styles.
344 styles.
345 """
345 """
346
346
347 # i18n: "eol" is a keyword
347 # i18n: "eol" is a keyword
348 enc = getstring(x, _("eol requires a style name"))
348 enc = getstring(x, _("eol requires a style name"))
349
349
350 def eolp(fctx):
350 def eolp(fctx):
351 if fctx.isbinary():
351 if fctx.isbinary():
352 return False
352 return False
353 d = fctx.data()
353 d = fctx.data()
354 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
354 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
355 return True
355 return True
356 elif enc == 'unix' and re.search('(?<!\r)\n', d):
356 elif enc == 'unix' and re.search('(?<!\r)\n', d):
357 return True
357 return True
358 elif enc == 'mac' and re.search('\r(?!\n)', d):
358 elif enc == 'mac' and re.search('\r(?!\n)', d):
359 return True
359 return True
360 return False
360 return False
361 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
361 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
362
362
363 @predicate('copied()')
363 @predicate('copied()')
364 def copied(mctx, x):
364 def copied(mctx, x):
365 """File that is recorded as being copied.
365 """File that is recorded as being copied.
366 """
366 """
367 # i18n: "copied" is a keyword
367 # i18n: "copied" is a keyword
368 getargs(x, 0, 0, _("copied takes no arguments"))
368 getargs(x, 0, 0, _("copied takes no arguments"))
369 def copiedp(fctx):
369 def copiedp(fctx):
370 p = fctx.parents()
370 p = fctx.parents()
371 return p and p[0].path() != fctx.path()
371 return p and p[0].path() != fctx.path()
372 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
372 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
373
373
374 @predicate('revs(revs, pattern)', weight=_WEIGHT_STATUS)
374 @predicate('revs(revs, pattern)', weight=_WEIGHT_STATUS)
375 def revs(mctx, x):
375 def revs(mctx, x):
376 """Evaluate set in the specified revisions. If the revset match multiple
376 """Evaluate set in the specified revisions. If the revset match multiple
377 revs, this will return file matching pattern in any of the revision.
377 revs, this will return file matching pattern in any of the revision.
378 """
378 """
379 # i18n: "revs" is a keyword
379 # i18n: "revs" is a keyword
380 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
380 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
381 # i18n: "revs" is a keyword
381 # i18n: "revs" is a keyword
382 revspec = getstring(r, _("first argument to revs must be a revision"))
382 revspec = getstring(r, _("first argument to revs must be a revision"))
383 repo = mctx.ctx.repo()
383 repo = mctx.ctx.repo()
384 revs = scmutil.revrange(repo, [revspec])
384 revs = scmutil.revrange(repo, [revspec])
385
385
386 matchers = []
386 matchers = []
387 for r in revs:
387 for r in revs:
388 ctx = repo[r]
388 ctx = repo[r]
389 mc = mctx.switch(ctx, _buildstatus(ctx.p1(), ctx, x))
389 mc = mctx.switch(ctx.p1(), ctx, _buildstatus(ctx.p1(), ctx, x))
390 matchers.append(getmatch(mc, x))
390 matchers.append(getmatch(mc, x))
391 if not matchers:
391 if not matchers:
392 return mctx.never()
392 return mctx.never()
393 if len(matchers) == 1:
393 if len(matchers) == 1:
394 return matchers[0]
394 return matchers[0]
395 return matchmod.unionmatcher(matchers)
395 return matchmod.unionmatcher(matchers)
396
396
397 @predicate('status(base, rev, pattern)', weight=_WEIGHT_STATUS)
397 @predicate('status(base, rev, pattern)', weight=_WEIGHT_STATUS)
398 def status(mctx, x):
398 def status(mctx, x):
399 """Evaluate predicate using status change between ``base`` and
399 """Evaluate predicate using status change between ``base`` and
400 ``rev``. Examples:
400 ``rev``. Examples:
401
401
402 - ``status(3, 7, added())`` - matches files added from "3" to "7"
402 - ``status(3, 7, added())`` - matches files added from "3" to "7"
403 """
403 """
404 repo = mctx.ctx.repo()
404 repo = mctx.ctx.repo()
405 # i18n: "status" is a keyword
405 # i18n: "status" is a keyword
406 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
406 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
407 # i18n: "status" is a keyword
407 # i18n: "status" is a keyword
408 baseerr = _("first argument to status must be a revision")
408 baseerr = _("first argument to status must be a revision")
409 baserevspec = getstring(b, baseerr)
409 baserevspec = getstring(b, baseerr)
410 if not baserevspec:
410 if not baserevspec:
411 raise error.ParseError(baseerr)
411 raise error.ParseError(baseerr)
412 reverr = _("second argument to status must be a revision")
412 reverr = _("second argument to status must be a revision")
413 revspec = getstring(r, reverr)
413 revspec = getstring(r, reverr)
414 if not revspec:
414 if not revspec:
415 raise error.ParseError(reverr)
415 raise error.ParseError(reverr)
416 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
416 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
417 mc = mctx.switch(ctx, _buildstatus(basectx, ctx, x))
417 mc = mctx.switch(basectx, ctx, _buildstatus(basectx, ctx, x))
418 return getmatch(mc, x)
418 return getmatch(mc, x)
419
419
420 @predicate('subrepo([pattern])')
420 @predicate('subrepo([pattern])')
421 def subrepo(mctx, x):
421 def subrepo(mctx, x):
422 """Subrepositories whose paths match the given pattern.
422 """Subrepositories whose paths match the given pattern.
423 """
423 """
424 # i18n: "subrepo" is a keyword
424 # i18n: "subrepo" is a keyword
425 getargs(x, 0, 1, _("subrepo takes at most one argument"))
425 getargs(x, 0, 1, _("subrepo takes at most one argument"))
426 ctx = mctx.ctx
426 ctx = mctx.ctx
427 sstate = ctx.substate
427 sstate = ctx.substate
428 if x:
428 if x:
429 pat = getpattern(x, matchmod.allpatternkinds,
429 pat = getpattern(x, matchmod.allpatternkinds,
430 # i18n: "subrepo" is a keyword
430 # i18n: "subrepo" is a keyword
431 _("subrepo requires a pattern or no arguments"))
431 _("subrepo requires a pattern or no arguments"))
432 fast = not matchmod.patkind(pat)
432 fast = not matchmod.patkind(pat)
433 if fast:
433 if fast:
434 def m(s):
434 def m(s):
435 return (s == pat)
435 return (s == pat)
436 else:
436 else:
437 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
437 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
438 return mctx.predicate(lambda f: f in sstate and m(f),
438 return mctx.predicate(lambda f: f in sstate and m(f),
439 predrepr=('subrepo(%r)', pat))
439 predrepr=('subrepo(%r)', pat))
440 else:
440 else:
441 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
441 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
442
442
443 methods = {
443 methods = {
444 'string': stringmatch,
444 'string': stringmatch,
445 'symbol': stringmatch,
445 'symbol': stringmatch,
446 'kindpat': kindpatmatch,
446 'kindpat': kindpatmatch,
447 'patterns': patternsmatch,
447 'patterns': patternsmatch,
448 'and': andmatch,
448 'and': andmatch,
449 'or': ormatch,
449 'or': ormatch,
450 'minus': minusmatch,
450 'minus': minusmatch,
451 'list': listmatch,
451 'list': listmatch,
452 'not': notmatch,
452 'not': notmatch,
453 'func': func,
453 'func': func,
454 }
454 }
455
455
456 class matchctx(object):
456 class matchctx(object):
457 def __init__(self, ctx, status=None, badfn=None):
457 def __init__(self, basectx, ctx, status=None, badfn=None):
458 self._basectx = basectx
458 self.ctx = ctx
459 self.ctx = ctx
459 self._status = status
460 self._status = status
460 self._badfn = badfn
461 self._badfn = badfn
461
462
462 def status(self):
463 def status(self):
463 return self._status
464 return self._status
464
465
465 def matcher(self, patterns):
466 def matcher(self, patterns):
466 return self.ctx.match(patterns, badfn=self._badfn)
467 return self.ctx.match(patterns, badfn=self._badfn)
467
468
468 def predicate(self, predfn, predrepr=None, cache=False):
469 def predicate(self, predfn, predrepr=None, cache=False):
469 """Create a matcher to select files by predfn(filename)"""
470 """Create a matcher to select files by predfn(filename)"""
470 if cache:
471 if cache:
471 predfn = util.cachefunc(predfn)
472 predfn = util.cachefunc(predfn)
472 repo = self.ctx.repo()
473 repo = self.ctx.repo()
473 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
474 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
474 predrepr=predrepr, badfn=self._badfn)
475 predrepr=predrepr, badfn=self._badfn)
475
476
476 def fpredicate(self, predfn, predrepr=None, cache=False):
477 def fpredicate(self, predfn, predrepr=None, cache=False):
477 """Create a matcher to select files by predfn(fctx) at the current
478 """Create a matcher to select files by predfn(fctx) at the current
478 revision
479 revision
479
480
480 Missing files are ignored.
481 Missing files are ignored.
481 """
482 """
482 ctx = self.ctx
483 ctx = self.ctx
483 if ctx.rev() is None:
484 if ctx.rev() is None:
484 def fctxpredfn(f):
485 def fctxpredfn(f):
485 try:
486 try:
486 fctx = ctx[f]
487 fctx = ctx[f]
487 except error.LookupError:
488 except error.LookupError:
488 return False
489 return False
489 try:
490 try:
490 fctx.audit()
491 fctx.audit()
491 except error.Abort:
492 except error.Abort:
492 return False
493 return False
493 try:
494 try:
494 return predfn(fctx)
495 return predfn(fctx)
495 except (IOError, OSError) as e:
496 except (IOError, OSError) as e:
496 # open()-ing a directory fails with EACCES on Windows
497 # open()-ing a directory fails with EACCES on Windows
497 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
498 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
498 errno.EISDIR):
499 errno.EISDIR):
499 return False
500 return False
500 raise
501 raise
501 else:
502 else:
502 def fctxpredfn(f):
503 def fctxpredfn(f):
503 try:
504 try:
504 fctx = ctx[f]
505 fctx = ctx[f]
505 except error.LookupError:
506 except error.LookupError:
506 return False
507 return False
507 return predfn(fctx)
508 return predfn(fctx)
508 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
509 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
509
510
510 def never(self):
511 def never(self):
511 """Create a matcher to select nothing"""
512 """Create a matcher to select nothing"""
512 repo = self.ctx.repo()
513 repo = self.ctx.repo()
513 return matchmod.nevermatcher(repo.root, repo.getcwd(),
514 return matchmod.nevermatcher(repo.root, repo.getcwd(),
514 badfn=self._badfn)
515 badfn=self._badfn)
515
516
516 def switch(self, ctx, status=None):
517 def switch(self, basectx, ctx, status=None):
517 return matchctx(ctx, status, self._badfn)
518 return matchctx(basectx, ctx, status, self._badfn)
518
519
519 # filesets using matchctx.switch()
520 # filesets using matchctx.switch()
520 _switchcallers = [
521 _switchcallers = [
521 'revs',
522 'revs',
522 'status',
523 'status',
523 ]
524 ]
524
525
525 def _intree(funcs, tree):
526 def _intree(funcs, tree):
526 if isinstance(tree, tuple):
527 if isinstance(tree, tuple):
527 if tree[0] == 'func' and tree[1][0] == 'symbol':
528 if tree[0] == 'func' and tree[1][0] == 'symbol':
528 if tree[1][1] in funcs:
529 if tree[1][1] in funcs:
529 return True
530 return True
530 if tree[1][1] in _switchcallers:
531 if tree[1][1] in _switchcallers:
531 # arguments won't be evaluated in the current context
532 # arguments won't be evaluated in the current context
532 return False
533 return False
533 for s in tree[1:]:
534 for s in tree[1:]:
534 if _intree(funcs, s):
535 if _intree(funcs, s):
535 return True
536 return True
536 return False
537 return False
537
538
538 def match(ctx, expr, badfn=None):
539 def match(ctx, expr, badfn=None):
539 """Create a matcher for a single fileset expression"""
540 """Create a matcher for a single fileset expression"""
540 tree = filesetlang.parse(expr)
541 tree = filesetlang.parse(expr)
541 tree = filesetlang.analyze(tree)
542 tree = filesetlang.analyze(tree)
542 tree = filesetlang.optimize(tree)
543 tree = filesetlang.optimize(tree)
543 mctx = matchctx(ctx, _buildstatus(ctx.p1(), ctx, tree), badfn=badfn)
544 mctx = matchctx(ctx.p1(), ctx, _buildstatus(ctx.p1(), ctx, tree),
545 badfn=badfn)
544 return getmatch(mctx, tree)
546 return getmatch(mctx, tree)
545
547
546 def _buildstatus(basectx, ctx, tree):
548 def _buildstatus(basectx, ctx, tree):
547 # do we need status info?
549 # do we need status info?
548
550
549 if _intree(_statuscallers, tree):
551 if _intree(_statuscallers, tree):
550 unknown = _intree(['unknown'], tree)
552 unknown = _intree(['unknown'], tree)
551 ignored = _intree(['ignored'], tree)
553 ignored = _intree(['ignored'], tree)
552
554
553 return basectx.status(ctx, listunknown=unknown, listignored=ignored,
555 return basectx.status(ctx, listunknown=unknown, listignored=ignored,
554 listclean=True)
556 listclean=True)
555 else:
557 else:
556 return None
558 return None
557
559
558 def loadpredicate(ui, extname, registrarobj):
560 def loadpredicate(ui, extname, registrarobj):
559 """Load fileset predicates from specified registrarobj
561 """Load fileset predicates from specified registrarobj
560 """
562 """
561 for name, func in registrarobj._table.iteritems():
563 for name, func in registrarobj._table.iteritems():
562 symbols[name] = func
564 symbols[name] = func
563 if func._callstatus:
565 if func._callstatus:
564 _statuscallers.add(name)
566 _statuscallers.add(name)
565
567
566 # load built-in predicates explicitly to setup _statuscallers
568 # load built-in predicates explicitly to setup _statuscallers
567 loadpredicate(None, None, predicate)
569 loadpredicate(None, None, predicate)
568
570
569 # tell hggettext to extract docstrings from these functions:
571 # tell hggettext to extract docstrings from these functions:
570 i18nfunctions = symbols.values()
572 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now