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