##// END OF EJS Templates
fileset: insert hints where status should be computed...
Yuya Nishihara -
r38915:e79a69af default
parent child Browse files
Show More
@@ -1,573 +1,577 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):
47 return getmatch(mctx, x)
48
46 def stringmatch(mctx, x):
49 def stringmatch(mctx, x):
47 return mctx.matcher([x])
50 return mctx.matcher([x])
48
51
49 def kindpatmatch(mctx, x, y):
52 def kindpatmatch(mctx, x, y):
50 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
53 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
51 _("pattern must be a string")))
54 _("pattern must be a string")))
52
55
53 def patternsmatch(mctx, *xs):
56 def patternsmatch(mctx, *xs):
54 allkinds = matchmod.allpatternkinds
57 allkinds = matchmod.allpatternkinds
55 patterns = [getpattern(x, allkinds, _("pattern must be a string"))
58 patterns = [getpattern(x, allkinds, _("pattern must be a string"))
56 for x in xs]
59 for x in xs]
57 return mctx.matcher(patterns)
60 return mctx.matcher(patterns)
58
61
59 def andmatch(mctx, x, y):
62 def andmatch(mctx, x, y):
60 xm = getmatch(mctx, x)
63 xm = getmatch(mctx, x)
61 ym = getmatch(mctx, y)
64 ym = getmatch(mctx, y)
62 return matchmod.intersectmatchers(xm, ym)
65 return matchmod.intersectmatchers(xm, ym)
63
66
64 def ormatch(mctx, *xs):
67 def ormatch(mctx, *xs):
65 ms = [getmatch(mctx, x) for x in xs]
68 ms = [getmatch(mctx, x) for x in xs]
66 return matchmod.unionmatcher(ms)
69 return matchmod.unionmatcher(ms)
67
70
68 def notmatch(mctx, x):
71 def notmatch(mctx, x):
69 m = getmatch(mctx, x)
72 m = getmatch(mctx, x)
70 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
73 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
71
74
72 def minusmatch(mctx, x, y):
75 def minusmatch(mctx, x, y):
73 xm = getmatch(mctx, x)
76 xm = getmatch(mctx, x)
74 ym = getmatch(mctx, y)
77 ym = getmatch(mctx, y)
75 return matchmod.differencematcher(xm, ym)
78 return matchmod.differencematcher(xm, ym)
76
79
77 def listmatch(mctx, *xs):
80 def listmatch(mctx, *xs):
78 raise error.ParseError(_("can't use a list in this context"),
81 raise error.ParseError(_("can't use a list in this context"),
79 hint=_('see \'hg help "filesets.x or y"\''))
82 hint=_('see \'hg help "filesets.x or y"\''))
80
83
81 def func(mctx, a, b):
84 def func(mctx, a, b):
82 funcname = getsymbol(a)
85 funcname = getsymbol(a)
83 if funcname in symbols:
86 if funcname in symbols:
84 return symbols[funcname](mctx, b)
87 return symbols[funcname](mctx, b)
85
88
86 keep = lambda fn: getattr(fn, '__doc__', None) is not None
89 keep = lambda fn: getattr(fn, '__doc__', None) is not None
87
90
88 syms = [s for (s, fn) in symbols.items() if keep(fn)]
91 syms = [s for (s, fn) in symbols.items() if keep(fn)]
89 raise error.UnknownIdentifier(funcname, syms)
92 raise error.UnknownIdentifier(funcname, syms)
90
93
91 # symbols are callable like:
94 # symbols are callable like:
92 # fun(mctx, x)
95 # fun(mctx, x)
93 # with:
96 # with:
94 # mctx - current matchctx instance
97 # mctx - current matchctx instance
95 # x - argument in tree form
98 # x - argument in tree form
96 symbols = filesetlang.symbols
99 symbols = filesetlang.symbols
97
100
98 # filesets using matchctx.status()
101 # filesets using matchctx.status()
99 _statuscallers = set()
102 _statuscallers = set()
100
103
101 predicate = registrar.filesetpredicate()
104 predicate = registrar.filesetpredicate()
102
105
103 @predicate('modified()', callstatus=True, weight=_WEIGHT_STATUS)
106 @predicate('modified()', callstatus=True, weight=_WEIGHT_STATUS)
104 def modified(mctx, x):
107 def modified(mctx, x):
105 """File that is modified according to :hg:`status`.
108 """File that is modified according to :hg:`status`.
106 """
109 """
107 # i18n: "modified" is a keyword
110 # i18n: "modified" is a keyword
108 getargs(x, 0, 0, _("modified takes no arguments"))
111 getargs(x, 0, 0, _("modified takes no arguments"))
109 s = set(mctx.status().modified)
112 s = set(mctx.status().modified)
110 return mctx.predicate(s.__contains__, predrepr='modified')
113 return mctx.predicate(s.__contains__, predrepr='modified')
111
114
112 @predicate('added()', callstatus=True, weight=_WEIGHT_STATUS)
115 @predicate('added()', callstatus=True, weight=_WEIGHT_STATUS)
113 def added(mctx, x):
116 def added(mctx, x):
114 """File that is added according to :hg:`status`.
117 """File that is added according to :hg:`status`.
115 """
118 """
116 # i18n: "added" is a keyword
119 # i18n: "added" is a keyword
117 getargs(x, 0, 0, _("added takes no arguments"))
120 getargs(x, 0, 0, _("added takes no arguments"))
118 s = set(mctx.status().added)
121 s = set(mctx.status().added)
119 return mctx.predicate(s.__contains__, predrepr='added')
122 return mctx.predicate(s.__contains__, predrepr='added')
120
123
121 @predicate('removed()', callstatus=True, weight=_WEIGHT_STATUS)
124 @predicate('removed()', callstatus=True, weight=_WEIGHT_STATUS)
122 def removed(mctx, x):
125 def removed(mctx, x):
123 """File that is removed according to :hg:`status`.
126 """File that is removed according to :hg:`status`.
124 """
127 """
125 # i18n: "removed" is a keyword
128 # i18n: "removed" is a keyword
126 getargs(x, 0, 0, _("removed takes no arguments"))
129 getargs(x, 0, 0, _("removed takes no arguments"))
127 s = set(mctx.status().removed)
130 s = set(mctx.status().removed)
128 return mctx.predicate(s.__contains__, predrepr='removed')
131 return mctx.predicate(s.__contains__, predrepr='removed')
129
132
130 @predicate('deleted()', callstatus=True, weight=_WEIGHT_STATUS)
133 @predicate('deleted()', callstatus=True, weight=_WEIGHT_STATUS)
131 def deleted(mctx, x):
134 def deleted(mctx, x):
132 """Alias for ``missing()``.
135 """Alias for ``missing()``.
133 """
136 """
134 # i18n: "deleted" is a keyword
137 # i18n: "deleted" is a keyword
135 getargs(x, 0, 0, _("deleted takes no arguments"))
138 getargs(x, 0, 0, _("deleted takes no arguments"))
136 s = set(mctx.status().deleted)
139 s = set(mctx.status().deleted)
137 return mctx.predicate(s.__contains__, predrepr='deleted')
140 return mctx.predicate(s.__contains__, predrepr='deleted')
138
141
139 @predicate('missing()', callstatus=True, weight=_WEIGHT_STATUS)
142 @predicate('missing()', callstatus=True, weight=_WEIGHT_STATUS)
140 def missing(mctx, x):
143 def missing(mctx, x):
141 """File that is missing according to :hg:`status`.
144 """File that is missing according to :hg:`status`.
142 """
145 """
143 # i18n: "missing" is a keyword
146 # i18n: "missing" is a keyword
144 getargs(x, 0, 0, _("missing takes no arguments"))
147 getargs(x, 0, 0, _("missing takes no arguments"))
145 s = set(mctx.status().deleted)
148 s = set(mctx.status().deleted)
146 return mctx.predicate(s.__contains__, predrepr='deleted')
149 return mctx.predicate(s.__contains__, predrepr='deleted')
147
150
148 @predicate('unknown()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
151 @predicate('unknown()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
149 def unknown(mctx, x):
152 def unknown(mctx, x):
150 """File that is unknown according to :hg:`status`."""
153 """File that is unknown according to :hg:`status`."""
151 # i18n: "unknown" is a keyword
154 # i18n: "unknown" is a keyword
152 getargs(x, 0, 0, _("unknown takes no arguments"))
155 getargs(x, 0, 0, _("unknown takes no arguments"))
153 s = set(mctx.status().unknown)
156 s = set(mctx.status().unknown)
154 return mctx.predicate(s.__contains__, predrepr='unknown')
157 return mctx.predicate(s.__contains__, predrepr='unknown')
155
158
156 @predicate('ignored()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
159 @predicate('ignored()', callstatus=True, weight=_WEIGHT_STATUS_THOROUGH)
157 def ignored(mctx, x):
160 def ignored(mctx, x):
158 """File that is ignored according to :hg:`status`."""
161 """File that is ignored according to :hg:`status`."""
159 # i18n: "ignored" is a keyword
162 # i18n: "ignored" is a keyword
160 getargs(x, 0, 0, _("ignored takes no arguments"))
163 getargs(x, 0, 0, _("ignored takes no arguments"))
161 s = set(mctx.status().ignored)
164 s = set(mctx.status().ignored)
162 return mctx.predicate(s.__contains__, predrepr='ignored')
165 return mctx.predicate(s.__contains__, predrepr='ignored')
163
166
164 @predicate('clean()', callstatus=True, weight=_WEIGHT_STATUS)
167 @predicate('clean()', callstatus=True, weight=_WEIGHT_STATUS)
165 def clean(mctx, x):
168 def clean(mctx, x):
166 """File that is clean according to :hg:`status`.
169 """File that is clean according to :hg:`status`.
167 """
170 """
168 # i18n: "clean" is a keyword
171 # i18n: "clean" is a keyword
169 getargs(x, 0, 0, _("clean takes no arguments"))
172 getargs(x, 0, 0, _("clean takes no arguments"))
170 s = set(mctx.status().clean)
173 s = set(mctx.status().clean)
171 return mctx.predicate(s.__contains__, predrepr='clean')
174 return mctx.predicate(s.__contains__, predrepr='clean')
172
175
173 @predicate('tracked()')
176 @predicate('tracked()')
174 def tracked(mctx, x):
177 def tracked(mctx, x):
175 """File that is under Mercurial control."""
178 """File that is under Mercurial control."""
176 # i18n: "tracked" is a keyword
179 # i18n: "tracked" is a keyword
177 getargs(x, 0, 0, _("tracked takes no arguments"))
180 getargs(x, 0, 0, _("tracked takes no arguments"))
178 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
181 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
179
182
180 @predicate('binary()', weight=_WEIGHT_READ_CONTENTS)
183 @predicate('binary()', weight=_WEIGHT_READ_CONTENTS)
181 def binary(mctx, x):
184 def binary(mctx, x):
182 """File that appears to be binary (contains NUL bytes).
185 """File that appears to be binary (contains NUL bytes).
183 """
186 """
184 # i18n: "binary" is a keyword
187 # i18n: "binary" is a keyword
185 getargs(x, 0, 0, _("binary takes no arguments"))
188 getargs(x, 0, 0, _("binary takes no arguments"))
186 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
189 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
187 predrepr='binary', cache=True)
190 predrepr='binary', cache=True)
188
191
189 @predicate('exec()')
192 @predicate('exec()')
190 def exec_(mctx, x):
193 def exec_(mctx, x):
191 """File that is marked as executable.
194 """File that is marked as executable.
192 """
195 """
193 # i18n: "exec" is a keyword
196 # i18n: "exec" is a keyword
194 getargs(x, 0, 0, _("exec takes no arguments"))
197 getargs(x, 0, 0, _("exec takes no arguments"))
195 ctx = mctx.ctx
198 ctx = mctx.ctx
196 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
199 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
197
200
198 @predicate('symlink()')
201 @predicate('symlink()')
199 def symlink(mctx, x):
202 def symlink(mctx, x):
200 """File that is marked as a symlink.
203 """File that is marked as a symlink.
201 """
204 """
202 # i18n: "symlink" is a keyword
205 # i18n: "symlink" is a keyword
203 getargs(x, 0, 0, _("symlink takes no arguments"))
206 getargs(x, 0, 0, _("symlink takes no arguments"))
204 ctx = mctx.ctx
207 ctx = mctx.ctx
205 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
208 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
206
209
207 @predicate('resolved()', weight=_WEIGHT_STATUS)
210 @predicate('resolved()', weight=_WEIGHT_STATUS)
208 def resolved(mctx, x):
211 def resolved(mctx, x):
209 """File that is marked resolved according to :hg:`resolve -l`.
212 """File that is marked resolved according to :hg:`resolve -l`.
210 """
213 """
211 # i18n: "resolved" is a keyword
214 # i18n: "resolved" is a keyword
212 getargs(x, 0, 0, _("resolved takes no arguments"))
215 getargs(x, 0, 0, _("resolved takes no arguments"))
213 if mctx.ctx.rev() is not None:
216 if mctx.ctx.rev() is not None:
214 return mctx.never()
217 return mctx.never()
215 ms = merge.mergestate.read(mctx.ctx.repo())
218 ms = merge.mergestate.read(mctx.ctx.repo())
216 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
219 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
217 predrepr='resolved')
220 predrepr='resolved')
218
221
219 @predicate('unresolved()', weight=_WEIGHT_STATUS)
222 @predicate('unresolved()', weight=_WEIGHT_STATUS)
220 def unresolved(mctx, x):
223 def unresolved(mctx, x):
221 """File that is marked unresolved according to :hg:`resolve -l`.
224 """File that is marked unresolved according to :hg:`resolve -l`.
222 """
225 """
223 # i18n: "unresolved" is a keyword
226 # i18n: "unresolved" is a keyword
224 getargs(x, 0, 0, _("unresolved takes no arguments"))
227 getargs(x, 0, 0, _("unresolved takes no arguments"))
225 if mctx.ctx.rev() is not None:
228 if mctx.ctx.rev() is not None:
226 return mctx.never()
229 return mctx.never()
227 ms = merge.mergestate.read(mctx.ctx.repo())
230 ms = merge.mergestate.read(mctx.ctx.repo())
228 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
231 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
229 predrepr='unresolved')
232 predrepr='unresolved')
230
233
231 @predicate('hgignore()', weight=_WEIGHT_STATUS)
234 @predicate('hgignore()', weight=_WEIGHT_STATUS)
232 def hgignore(mctx, x):
235 def hgignore(mctx, x):
233 """File that matches the active .hgignore pattern.
236 """File that matches the active .hgignore pattern.
234 """
237 """
235 # i18n: "hgignore" is a keyword
238 # i18n: "hgignore" is a keyword
236 getargs(x, 0, 0, _("hgignore takes no arguments"))
239 getargs(x, 0, 0, _("hgignore takes no arguments"))
237 return mctx.ctx.repo().dirstate._ignore
240 return mctx.ctx.repo().dirstate._ignore
238
241
239 @predicate('portable()', weight=_WEIGHT_CHECK_FILENAME)
242 @predicate('portable()', weight=_WEIGHT_CHECK_FILENAME)
240 def portable(mctx, x):
243 def portable(mctx, x):
241 """File that has a portable name. (This doesn't include filenames with case
244 """File that has a portable name. (This doesn't include filenames with case
242 collisions.)
245 collisions.)
243 """
246 """
244 # i18n: "portable" is a keyword
247 # i18n: "portable" is a keyword
245 getargs(x, 0, 0, _("portable takes no arguments"))
248 getargs(x, 0, 0, _("portable takes no arguments"))
246 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
249 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
247 predrepr='portable')
250 predrepr='portable')
248
251
249 @predicate('grep(regex)', weight=_WEIGHT_READ_CONTENTS)
252 @predicate('grep(regex)', weight=_WEIGHT_READ_CONTENTS)
250 def grep(mctx, x):
253 def grep(mctx, x):
251 """File contains the given regular expression.
254 """File contains the given regular expression.
252 """
255 """
253 try:
256 try:
254 # i18n: "grep" is a keyword
257 # i18n: "grep" is a keyword
255 r = re.compile(getstring(x, _("grep requires a pattern")))
258 r = re.compile(getstring(x, _("grep requires a pattern")))
256 except re.error as e:
259 except re.error as e:
257 raise error.ParseError(_('invalid match pattern: %s') %
260 raise error.ParseError(_('invalid match pattern: %s') %
258 stringutil.forcebytestr(e))
261 stringutil.forcebytestr(e))
259 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
262 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
260 predrepr=('grep(%r)', r.pattern), cache=True)
263 predrepr=('grep(%r)', r.pattern), cache=True)
261
264
262 def _sizetomax(s):
265 def _sizetomax(s):
263 try:
266 try:
264 s = s.strip().lower()
267 s = s.strip().lower()
265 for k, v in util._sizeunits:
268 for k, v in util._sizeunits:
266 if s.endswith(k):
269 if s.endswith(k):
267 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
270 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
268 n = s[:-len(k)]
271 n = s[:-len(k)]
269 inc = 1.0
272 inc = 1.0
270 if "." in n:
273 if "." in n:
271 inc /= 10 ** len(n.split(".")[1])
274 inc /= 10 ** len(n.split(".")[1])
272 return int((float(n) + inc) * v) - 1
275 return int((float(n) + inc) * v) - 1
273 # no extension, this is a precise value
276 # no extension, this is a precise value
274 return int(s)
277 return int(s)
275 except ValueError:
278 except ValueError:
276 raise error.ParseError(_("couldn't parse size: %s") % s)
279 raise error.ParseError(_("couldn't parse size: %s") % s)
277
280
278 def sizematcher(expr):
281 def sizematcher(expr):
279 """Return a function(size) -> bool from the ``size()`` expression"""
282 """Return a function(size) -> bool from the ``size()`` expression"""
280 expr = expr.strip()
283 expr = expr.strip()
281 if '-' in expr: # do we have a range?
284 if '-' in expr: # do we have a range?
282 a, b = expr.split('-', 1)
285 a, b = expr.split('-', 1)
283 a = util.sizetoint(a)
286 a = util.sizetoint(a)
284 b = util.sizetoint(b)
287 b = util.sizetoint(b)
285 return lambda x: x >= a and x <= b
288 return lambda x: x >= a and x <= b
286 elif expr.startswith("<="):
289 elif expr.startswith("<="):
287 a = util.sizetoint(expr[2:])
290 a = util.sizetoint(expr[2:])
288 return lambda x: x <= a
291 return lambda x: x <= a
289 elif expr.startswith("<"):
292 elif expr.startswith("<"):
290 a = util.sizetoint(expr[1:])
293 a = util.sizetoint(expr[1:])
291 return lambda x: x < a
294 return lambda x: x < a
292 elif expr.startswith(">="):
295 elif expr.startswith(">="):
293 a = util.sizetoint(expr[2:])
296 a = util.sizetoint(expr[2:])
294 return lambda x: x >= a
297 return lambda x: x >= a
295 elif expr.startswith(">"):
298 elif expr.startswith(">"):
296 a = util.sizetoint(expr[1:])
299 a = util.sizetoint(expr[1:])
297 return lambda x: x > a
300 return lambda x: x > a
298 else:
301 else:
299 a = util.sizetoint(expr)
302 a = util.sizetoint(expr)
300 b = _sizetomax(expr)
303 b = _sizetomax(expr)
301 return lambda x: x >= a and x <= b
304 return lambda x: x >= a and x <= b
302
305
303 @predicate('size(expression)', weight=_WEIGHT_STATUS)
306 @predicate('size(expression)', weight=_WEIGHT_STATUS)
304 def size(mctx, x):
307 def size(mctx, x):
305 """File size matches the given expression. Examples:
308 """File size matches the given expression. Examples:
306
309
307 - size('1k') - files from 1024 to 2047 bytes
310 - size('1k') - files from 1024 to 2047 bytes
308 - size('< 20k') - files less than 20480 bytes
311 - size('< 20k') - files less than 20480 bytes
309 - size('>= .5MB') - files at least 524288 bytes
312 - size('>= .5MB') - files at least 524288 bytes
310 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
313 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
311 """
314 """
312 # i18n: "size" is a keyword
315 # i18n: "size" is a keyword
313 expr = getstring(x, _("size requires an expression"))
316 expr = getstring(x, _("size requires an expression"))
314 m = sizematcher(expr)
317 m = sizematcher(expr)
315 return mctx.fpredicate(lambda fctx: m(fctx.size()),
318 return mctx.fpredicate(lambda fctx: m(fctx.size()),
316 predrepr=('size(%r)', expr), cache=True)
319 predrepr=('size(%r)', expr), cache=True)
317
320
318 @predicate('encoding(name)', weight=_WEIGHT_READ_CONTENTS)
321 @predicate('encoding(name)', weight=_WEIGHT_READ_CONTENTS)
319 def encoding(mctx, x):
322 def encoding(mctx, x):
320 """File can be successfully decoded with the given character
323 """File can be successfully decoded with the given character
321 encoding. May not be useful for encodings other than ASCII and
324 encoding. May not be useful for encodings other than ASCII and
322 UTF-8.
325 UTF-8.
323 """
326 """
324
327
325 # i18n: "encoding" is a keyword
328 # i18n: "encoding" is a keyword
326 enc = getstring(x, _("encoding requires an encoding name"))
329 enc = getstring(x, _("encoding requires an encoding name"))
327
330
328 def encp(fctx):
331 def encp(fctx):
329 d = fctx.data()
332 d = fctx.data()
330 try:
333 try:
331 d.decode(pycompat.sysstr(enc))
334 d.decode(pycompat.sysstr(enc))
332 return True
335 return True
333 except LookupError:
336 except LookupError:
334 raise error.Abort(_("unknown encoding '%s'") % enc)
337 raise error.Abort(_("unknown encoding '%s'") % enc)
335 except UnicodeDecodeError:
338 except UnicodeDecodeError:
336 return False
339 return False
337
340
338 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
341 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
339
342
340 @predicate('eol(style)', weight=_WEIGHT_READ_CONTENTS)
343 @predicate('eol(style)', weight=_WEIGHT_READ_CONTENTS)
341 def eol(mctx, x):
344 def eol(mctx, x):
342 """File contains newlines of the given style (dos, unix, mac). Binary
345 """File contains newlines of the given style (dos, unix, mac). Binary
343 files are excluded, files with mixed line endings match multiple
346 files are excluded, files with mixed line endings match multiple
344 styles.
347 styles.
345 """
348 """
346
349
347 # i18n: "eol" is a keyword
350 # i18n: "eol" is a keyword
348 enc = getstring(x, _("eol requires a style name"))
351 enc = getstring(x, _("eol requires a style name"))
349
352
350 def eolp(fctx):
353 def eolp(fctx):
351 if fctx.isbinary():
354 if fctx.isbinary():
352 return False
355 return False
353 d = fctx.data()
356 d = fctx.data()
354 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
357 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
355 return True
358 return True
356 elif enc == 'unix' and re.search('(?<!\r)\n', d):
359 elif enc == 'unix' and re.search('(?<!\r)\n', d):
357 return True
360 return True
358 elif enc == 'mac' and re.search('\r(?!\n)', d):
361 elif enc == 'mac' and re.search('\r(?!\n)', d):
359 return True
362 return True
360 return False
363 return False
361 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
364 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
362
365
363 @predicate('copied()')
366 @predicate('copied()')
364 def copied(mctx, x):
367 def copied(mctx, x):
365 """File that is recorded as being copied.
368 """File that is recorded as being copied.
366 """
369 """
367 # i18n: "copied" is a keyword
370 # i18n: "copied" is a keyword
368 getargs(x, 0, 0, _("copied takes no arguments"))
371 getargs(x, 0, 0, _("copied takes no arguments"))
369 def copiedp(fctx):
372 def copiedp(fctx):
370 p = fctx.parents()
373 p = fctx.parents()
371 return p and p[0].path() != fctx.path()
374 return p and p[0].path() != fctx.path()
372 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
375 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
373
376
374 @predicate('revs(revs, pattern)', weight=_WEIGHT_STATUS)
377 @predicate('revs(revs, pattern)', weight=_WEIGHT_STATUS)
375 def revs(mctx, x):
378 def revs(mctx, x):
376 """Evaluate set in the specified revisions. If the revset match multiple
379 """Evaluate set in the specified revisions. If the revset match multiple
377 revs, this will return file matching pattern in any of the revision.
380 revs, this will return file matching pattern in any of the revision.
378 """
381 """
379 # i18n: "revs" is a keyword
382 # i18n: "revs" is a keyword
380 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
383 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
381 # i18n: "revs" is a keyword
384 # i18n: "revs" is a keyword
382 revspec = getstring(r, _("first argument to revs must be a revision"))
385 revspec = getstring(r, _("first argument to revs must be a revision"))
383 repo = mctx.ctx.repo()
386 repo = mctx.ctx.repo()
384 revs = scmutil.revrange(repo, [revspec])
387 revs = scmutil.revrange(repo, [revspec])
385
388
386 matchers = []
389 matchers = []
387 for r in revs:
390 for r in revs:
388 ctx = repo[r]
391 ctx = repo[r]
389 mc = mctx.switch(ctx.p1(), ctx)
392 mc = mctx.switch(ctx.p1(), ctx)
390 mc.buildstatus(x)
393 mc.buildstatus(x)
391 matchers.append(getmatch(mc, x))
394 matchers.append(getmatch(mc, x))
392 if not matchers:
395 if not matchers:
393 return mctx.never()
396 return mctx.never()
394 if len(matchers) == 1:
397 if len(matchers) == 1:
395 return matchers[0]
398 return matchers[0]
396 return matchmod.unionmatcher(matchers)
399 return matchmod.unionmatcher(matchers)
397
400
398 @predicate('status(base, rev, pattern)', weight=_WEIGHT_STATUS)
401 @predicate('status(base, rev, pattern)', weight=_WEIGHT_STATUS)
399 def status(mctx, x):
402 def status(mctx, x):
400 """Evaluate predicate using status change between ``base`` and
403 """Evaluate predicate using status change between ``base`` and
401 ``rev``. Examples:
404 ``rev``. Examples:
402
405
403 - ``status(3, 7, added())`` - matches files added from "3" to "7"
406 - ``status(3, 7, added())`` - matches files added from "3" to "7"
404 """
407 """
405 repo = mctx.ctx.repo()
408 repo = mctx.ctx.repo()
406 # i18n: "status" is a keyword
409 # i18n: "status" is a keyword
407 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
410 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
408 # i18n: "status" is a keyword
411 # i18n: "status" is a keyword
409 baseerr = _("first argument to status must be a revision")
412 baseerr = _("first argument to status must be a revision")
410 baserevspec = getstring(b, baseerr)
413 baserevspec = getstring(b, baseerr)
411 if not baserevspec:
414 if not baserevspec:
412 raise error.ParseError(baseerr)
415 raise error.ParseError(baseerr)
413 reverr = _("second argument to status must be a revision")
416 reverr = _("second argument to status must be a revision")
414 revspec = getstring(r, reverr)
417 revspec = getstring(r, reverr)
415 if not revspec:
418 if not revspec:
416 raise error.ParseError(reverr)
419 raise error.ParseError(reverr)
417 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
420 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
418 mc = mctx.switch(basectx, ctx)
421 mc = mctx.switch(basectx, ctx)
419 mc.buildstatus(x)
422 mc.buildstatus(x)
420 return getmatch(mc, x)
423 return getmatch(mc, x)
421
424
422 @predicate('subrepo([pattern])')
425 @predicate('subrepo([pattern])')
423 def subrepo(mctx, x):
426 def subrepo(mctx, x):
424 """Subrepositories whose paths match the given pattern.
427 """Subrepositories whose paths match the given pattern.
425 """
428 """
426 # i18n: "subrepo" is a keyword
429 # i18n: "subrepo" is a keyword
427 getargs(x, 0, 1, _("subrepo takes at most one argument"))
430 getargs(x, 0, 1, _("subrepo takes at most one argument"))
428 ctx = mctx.ctx
431 ctx = mctx.ctx
429 sstate = ctx.substate
432 sstate = ctx.substate
430 if x:
433 if x:
431 pat = getpattern(x, matchmod.allpatternkinds,
434 pat = getpattern(x, matchmod.allpatternkinds,
432 # i18n: "subrepo" is a keyword
435 # i18n: "subrepo" is a keyword
433 _("subrepo requires a pattern or no arguments"))
436 _("subrepo requires a pattern or no arguments"))
434 fast = not matchmod.patkind(pat)
437 fast = not matchmod.patkind(pat)
435 if fast:
438 if fast:
436 def m(s):
439 def m(s):
437 return (s == pat)
440 return (s == pat)
438 else:
441 else:
439 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
442 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
440 return mctx.predicate(lambda f: f in sstate and m(f),
443 return mctx.predicate(lambda f: f in sstate and m(f),
441 predrepr=('subrepo(%r)', pat))
444 predrepr=('subrepo(%r)', pat))
442 else:
445 else:
443 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
446 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
444
447
445 methods = {
448 methods = {
449 'withstatus': getmatchwithstatus,
446 'string': stringmatch,
450 'string': stringmatch,
447 'symbol': stringmatch,
451 'symbol': stringmatch,
448 'kindpat': kindpatmatch,
452 'kindpat': kindpatmatch,
449 'patterns': patternsmatch,
453 'patterns': patternsmatch,
450 'and': andmatch,
454 'and': andmatch,
451 'or': ormatch,
455 'or': ormatch,
452 'minus': minusmatch,
456 'minus': minusmatch,
453 'list': listmatch,
457 'list': listmatch,
454 'not': notmatch,
458 'not': notmatch,
455 'func': func,
459 'func': func,
456 }
460 }
457
461
458 class matchctx(object):
462 class matchctx(object):
459 def __init__(self, basectx, ctx, badfn=None):
463 def __init__(self, basectx, ctx, badfn=None):
460 self._basectx = basectx
464 self._basectx = basectx
461 self.ctx = ctx
465 self.ctx = ctx
462 self._badfn = badfn
466 self._badfn = badfn
463 self._status = None
467 self._status = None
464
468
465 def buildstatus(self, tree):
469 def buildstatus(self, tree):
466 if not _intree(_statuscallers, tree):
470 if not _intree(_statuscallers, tree):
467 return
471 return
468 unknown = _intree(['unknown'], tree)
472 unknown = _intree(['unknown'], tree)
469 ignored = _intree(['ignored'], tree)
473 ignored = _intree(['ignored'], tree)
470 self._status = self._basectx.status(self.ctx,
474 self._status = self._basectx.status(self.ctx,
471 listignored=ignored,
475 listignored=ignored,
472 listclean=True,
476 listclean=True,
473 listunknown=unknown)
477 listunknown=unknown)
474
478
475 def status(self):
479 def status(self):
476 return self._status
480 return self._status
477
481
478 def matcher(self, patterns):
482 def matcher(self, patterns):
479 return self.ctx.match(patterns, badfn=self._badfn)
483 return self.ctx.match(patterns, badfn=self._badfn)
480
484
481 def predicate(self, predfn, predrepr=None, cache=False):
485 def predicate(self, predfn, predrepr=None, cache=False):
482 """Create a matcher to select files by predfn(filename)"""
486 """Create a matcher to select files by predfn(filename)"""
483 if cache:
487 if cache:
484 predfn = util.cachefunc(predfn)
488 predfn = util.cachefunc(predfn)
485 repo = self.ctx.repo()
489 repo = self.ctx.repo()
486 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
490 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
487 predrepr=predrepr, badfn=self._badfn)
491 predrepr=predrepr, badfn=self._badfn)
488
492
489 def fpredicate(self, predfn, predrepr=None, cache=False):
493 def fpredicate(self, predfn, predrepr=None, cache=False):
490 """Create a matcher to select files by predfn(fctx) at the current
494 """Create a matcher to select files by predfn(fctx) at the current
491 revision
495 revision
492
496
493 Missing files are ignored.
497 Missing files are ignored.
494 """
498 """
495 ctx = self.ctx
499 ctx = self.ctx
496 if ctx.rev() is None:
500 if ctx.rev() is None:
497 def fctxpredfn(f):
501 def fctxpredfn(f):
498 try:
502 try:
499 fctx = ctx[f]
503 fctx = ctx[f]
500 except error.LookupError:
504 except error.LookupError:
501 return False
505 return False
502 try:
506 try:
503 fctx.audit()
507 fctx.audit()
504 except error.Abort:
508 except error.Abort:
505 return False
509 return False
506 try:
510 try:
507 return predfn(fctx)
511 return predfn(fctx)
508 except (IOError, OSError) as e:
512 except (IOError, OSError) as e:
509 # open()-ing a directory fails with EACCES on Windows
513 # open()-ing a directory fails with EACCES on Windows
510 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
514 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
511 errno.EISDIR):
515 errno.EISDIR):
512 return False
516 return False
513 raise
517 raise
514 else:
518 else:
515 def fctxpredfn(f):
519 def fctxpredfn(f):
516 try:
520 try:
517 fctx = ctx[f]
521 fctx = ctx[f]
518 except error.LookupError:
522 except error.LookupError:
519 return False
523 return False
520 return predfn(fctx)
524 return predfn(fctx)
521 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
525 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
522
526
523 def never(self):
527 def never(self):
524 """Create a matcher to select nothing"""
528 """Create a matcher to select nothing"""
525 repo = self.ctx.repo()
529 repo = self.ctx.repo()
526 return matchmod.nevermatcher(repo.root, repo.getcwd(),
530 return matchmod.nevermatcher(repo.root, repo.getcwd(),
527 badfn=self._badfn)
531 badfn=self._badfn)
528
532
529 def switch(self, basectx, ctx):
533 def switch(self, basectx, ctx):
530 return matchctx(basectx, ctx, self._badfn)
534 return matchctx(basectx, ctx, self._badfn)
531
535
532 # filesets using matchctx.switch()
536 # filesets using matchctx.switch()
533 _switchcallers = [
537 _switchcallers = [
534 'revs',
538 'revs',
535 'status',
539 'status',
536 ]
540 ]
537
541
538 def _intree(funcs, tree):
542 def _intree(funcs, tree):
539 if isinstance(tree, tuple):
543 if isinstance(tree, tuple):
540 if tree[0] == 'func' and tree[1][0] == 'symbol':
544 if tree[0] == 'func' and tree[1][0] == 'symbol':
541 if tree[1][1] in funcs:
545 if tree[1][1] in funcs:
542 return True
546 return True
543 if tree[1][1] in _switchcallers:
547 if tree[1][1] in _switchcallers:
544 # arguments won't be evaluated in the current context
548 # arguments won't be evaluated in the current context
545 return False
549 return False
546 for s in tree[1:]:
550 for s in tree[1:]:
547 if _intree(funcs, s):
551 if _intree(funcs, s):
548 return True
552 return True
549 return False
553 return False
550
554
551 def match(ctx, expr, badfn=None):
555 def match(ctx, expr, badfn=None):
552 """Create a matcher for a single fileset expression"""
556 """Create a matcher for a single fileset expression"""
553 tree = filesetlang.parse(expr)
557 tree = filesetlang.parse(expr)
554 tree = filesetlang.analyze(tree)
558 tree = filesetlang.analyze(tree)
555 tree = filesetlang.optimize(tree)
559 tree = filesetlang.optimize(tree)
556 mctx = matchctx(ctx.p1(), ctx, badfn=badfn)
560 mctx = matchctx(ctx.p1(), ctx, badfn=badfn)
557 mctx.buildstatus(tree)
561 mctx.buildstatus(tree)
558 return getmatch(mctx, tree)
562 return getmatch(mctx, tree)
559
563
560
564
561 def loadpredicate(ui, extname, registrarobj):
565 def loadpredicate(ui, extname, registrarobj):
562 """Load fileset predicates from specified registrarobj
566 """Load fileset predicates from specified registrarobj
563 """
567 """
564 for name, func in registrarobj._table.iteritems():
568 for name, func in registrarobj._table.iteritems():
565 symbols[name] = func
569 symbols[name] = func
566 if func._callstatus:
570 if func._callstatus:
567 _statuscallers.add(name)
571 _statuscallers.add(name)
568
572
569 # load built-in predicates explicitly to setup _statuscallers
573 # load built-in predicates explicitly to setup _statuscallers
570 loadpredicate(None, None, predicate)
574 loadpredicate(None, None, predicate)
571
575
572 # tell hggettext to extract docstrings from these functions:
576 # tell hggettext to extract docstrings from these functions:
573 i18nfunctions = symbols.values()
577 i18nfunctions = symbols.values()
@@ -1,249 +1,330 b''
1 # filesetlang.py - parser, tokenizer and utility for file set language
1 # filesetlang.py - parser, tokenizer and utility for file set language
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 from .i18n import _
10 from .i18n import _
11 from . import (
11 from . import (
12 error,
12 error,
13 parser,
13 parser,
14 pycompat,
14 pycompat,
15 )
15 )
16
16
17 # common weight constants for static optimization
17 # common weight constants for static optimization
18 # (see registrar.filesetpredicate for details)
18 # (see registrar.filesetpredicate for details)
19 WEIGHT_CHECK_FILENAME = 0.5
19 WEIGHT_CHECK_FILENAME = 0.5
20 WEIGHT_READ_CONTENTS = 30
20 WEIGHT_READ_CONTENTS = 30
21 WEIGHT_STATUS = 10
21 WEIGHT_STATUS = 10
22 WEIGHT_STATUS_THOROUGH = 50
22 WEIGHT_STATUS_THOROUGH = 50
23
23
24 elements = {
24 elements = {
25 # token-type: binding-strength, primary, prefix, infix, suffix
25 # token-type: binding-strength, primary, prefix, infix, suffix
26 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
26 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
27 ":": (15, None, None, ("kindpat", 15), None),
27 ":": (15, None, None, ("kindpat", 15), None),
28 "-": (5, None, ("negate", 19), ("minus", 5), None),
28 "-": (5, None, ("negate", 19), ("minus", 5), None),
29 "not": (10, None, ("not", 10), None, None),
29 "not": (10, None, ("not", 10), None, None),
30 "!": (10, None, ("not", 10), None, None),
30 "!": (10, None, ("not", 10), None, None),
31 "and": (5, None, None, ("and", 5), None),
31 "and": (5, None, None, ("and", 5), None),
32 "&": (5, None, None, ("and", 5), None),
32 "&": (5, None, None, ("and", 5), None),
33 "or": (4, None, None, ("or", 4), None),
33 "or": (4, None, None, ("or", 4), None),
34 "|": (4, None, None, ("or", 4), None),
34 "|": (4, None, None, ("or", 4), None),
35 "+": (4, None, None, ("or", 4), None),
35 "+": (4, None, None, ("or", 4), None),
36 ",": (2, None, None, ("list", 2), None),
36 ",": (2, None, None, ("list", 2), None),
37 ")": (0, None, None, None, None),
37 ")": (0, None, None, None, None),
38 "symbol": (0, "symbol", None, None, None),
38 "symbol": (0, "symbol", None, None, None),
39 "string": (0, "string", None, None, None),
39 "string": (0, "string", None, None, None),
40 "end": (0, None, None, None, None),
40 "end": (0, None, None, None, None),
41 }
41 }
42
42
43 keywords = {'and', 'or', 'not'}
43 keywords = {'and', 'or', 'not'}
44
44
45 symbols = {}
45 symbols = {}
46
46
47 globchars = ".*{}[]?/\\_"
47 globchars = ".*{}[]?/\\_"
48
48
49 def tokenize(program):
49 def tokenize(program):
50 pos, l = 0, len(program)
50 pos, l = 0, len(program)
51 program = pycompat.bytestr(program)
51 program = pycompat.bytestr(program)
52 while pos < l:
52 while pos < l:
53 c = program[pos]
53 c = program[pos]
54 if c.isspace(): # skip inter-token whitespace
54 if c.isspace(): # skip inter-token whitespace
55 pass
55 pass
56 elif c in "(),-:|&+!": # handle simple operators
56 elif c in "(),-:|&+!": # handle simple operators
57 yield (c, None, pos)
57 yield (c, None, pos)
58 elif (c in '"\'' or c == 'r' and
58 elif (c in '"\'' or c == 'r' and
59 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
59 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
60 if c == 'r':
60 if c == 'r':
61 pos += 1
61 pos += 1
62 c = program[pos]
62 c = program[pos]
63 decode = lambda x: x
63 decode = lambda x: x
64 else:
64 else:
65 decode = parser.unescapestr
65 decode = parser.unescapestr
66 pos += 1
66 pos += 1
67 s = pos
67 s = pos
68 while pos < l: # find closing quote
68 while pos < l: # find closing quote
69 d = program[pos]
69 d = program[pos]
70 if d == '\\': # skip over escaped characters
70 if d == '\\': # skip over escaped characters
71 pos += 2
71 pos += 2
72 continue
72 continue
73 if d == c:
73 if d == c:
74 yield ('string', decode(program[s:pos]), s)
74 yield ('string', decode(program[s:pos]), s)
75 break
75 break
76 pos += 1
76 pos += 1
77 else:
77 else:
78 raise error.ParseError(_("unterminated string"), s)
78 raise error.ParseError(_("unterminated string"), s)
79 elif c.isalnum() or c in globchars or ord(c) > 127:
79 elif c.isalnum() or c in globchars or ord(c) > 127:
80 # gather up a symbol/keyword
80 # gather up a symbol/keyword
81 s = pos
81 s = pos
82 pos += 1
82 pos += 1
83 while pos < l: # find end of symbol
83 while pos < l: # find end of symbol
84 d = program[pos]
84 d = program[pos]
85 if not (d.isalnum() or d in globchars or ord(d) > 127):
85 if not (d.isalnum() or d in globchars or ord(d) > 127):
86 break
86 break
87 pos += 1
87 pos += 1
88 sym = program[s:pos]
88 sym = program[s:pos]
89 if sym in keywords: # operator keywords
89 if sym in keywords: # operator keywords
90 yield (sym, None, s)
90 yield (sym, None, s)
91 else:
91 else:
92 yield ('symbol', sym, s)
92 yield ('symbol', sym, s)
93 pos -= 1
93 pos -= 1
94 else:
94 else:
95 raise error.ParseError(_("syntax error"), pos)
95 raise error.ParseError(_("syntax error"), pos)
96 pos += 1
96 pos += 1
97 yield ('end', None, pos)
97 yield ('end', None, pos)
98
98
99 def parse(expr):
99 def parse(expr):
100 p = parser.parser(elements)
100 p = parser.parser(elements)
101 tree, pos = p.parse(tokenize(expr))
101 tree, pos = p.parse(tokenize(expr))
102 if pos != len(expr):
102 if pos != len(expr):
103 raise error.ParseError(_("invalid token"), pos)
103 raise error.ParseError(_("invalid token"), pos)
104 return parser.simplifyinfixops(tree, {'list', 'or'})
104 return parser.simplifyinfixops(tree, {'list', 'or'})
105
105
106 def getsymbol(x):
106 def getsymbol(x):
107 if x and x[0] == 'symbol':
107 if x and x[0] == 'symbol':
108 return x[1]
108 return x[1]
109 raise error.ParseError(_('not a symbol'))
109 raise error.ParseError(_('not a symbol'))
110
110
111 def getstring(x, err):
111 def getstring(x, err):
112 if x and (x[0] == 'string' or x[0] == 'symbol'):
112 if x and (x[0] == 'string' or x[0] == 'symbol'):
113 return x[1]
113 return x[1]
114 raise error.ParseError(err)
114 raise error.ParseError(err)
115
115
116 def getkindpat(x, y, allkinds, err):
116 def getkindpat(x, y, allkinds, err):
117 kind = getsymbol(x)
117 kind = getsymbol(x)
118 pat = getstring(y, err)
118 pat = getstring(y, err)
119 if kind not in allkinds:
119 if kind not in allkinds:
120 raise error.ParseError(_("invalid pattern kind: %s") % kind)
120 raise error.ParseError(_("invalid pattern kind: %s") % kind)
121 return '%s:%s' % (kind, pat)
121 return '%s:%s' % (kind, pat)
122
122
123 def getpattern(x, allkinds, err):
123 def getpattern(x, allkinds, err):
124 if x and x[0] == 'kindpat':
124 if x and x[0] == 'kindpat':
125 return getkindpat(x[1], x[2], allkinds, err)
125 return getkindpat(x[1], x[2], allkinds, err)
126 return getstring(x, err)
126 return getstring(x, err)
127
127
128 def getlist(x):
128 def getlist(x):
129 if not x:
129 if not x:
130 return []
130 return []
131 if x[0] == 'list':
131 if x[0] == 'list':
132 return list(x[1:])
132 return list(x[1:])
133 return [x]
133 return [x]
134
134
135 def getargs(x, min, max, err):
135 def getargs(x, min, max, err):
136 l = getlist(x)
136 l = getlist(x)
137 if len(l) < min or len(l) > max:
137 if len(l) < min or len(l) > max:
138 raise error.ParseError(err)
138 raise error.ParseError(err)
139 return l
139 return l
140
140
141 def _analyze(x):
141 def _analyze(x):
142 if x is None:
142 if x is None:
143 return x
143 return x
144
144
145 op = x[0]
145 op = x[0]
146 if op in {'string', 'symbol'}:
146 if op in {'string', 'symbol'}:
147 return x
147 return x
148 if op == 'kindpat':
148 if op == 'kindpat':
149 getsymbol(x[1]) # kind must be a symbol
149 getsymbol(x[1]) # kind must be a symbol
150 t = _analyze(x[2])
150 t = _analyze(x[2])
151 return (op, x[1], t)
151 return (op, x[1], t)
152 if op == 'group':
152 if op == 'group':
153 return _analyze(x[1])
153 return _analyze(x[1])
154 if op == 'negate':
154 if op == 'negate':
155 raise error.ParseError(_("can't use negate operator in this context"))
155 raise error.ParseError(_("can't use negate operator in this context"))
156 if op == 'not':
156 if op == 'not':
157 t = _analyze(x[1])
157 t = _analyze(x[1])
158 return (op, t)
158 return (op, t)
159 if op == 'and':
159 if op == 'and':
160 ta = _analyze(x[1])
160 ta = _analyze(x[1])
161 tb = _analyze(x[2])
161 tb = _analyze(x[2])
162 return (op, ta, tb)
162 return (op, ta, tb)
163 if op == 'minus':
163 if op == 'minus':
164 return _analyze(('and', x[1], ('not', x[2])))
164 return _analyze(('and', x[1], ('not', x[2])))
165 if op in {'list', 'or'}:
165 if op in {'list', 'or'}:
166 ts = tuple(_analyze(y) for y in x[1:])
166 ts = tuple(_analyze(y) for y in x[1:])
167 return (op,) + ts
167 return (op,) + ts
168 if op == 'func':
168 if op == 'func':
169 getsymbol(x[1]) # function name must be a symbol
169 getsymbol(x[1]) # function name must be a symbol
170 ta = _analyze(x[2])
170 ta = _analyze(x[2])
171 return (op, x[1], ta)
171 return (op, x[1], ta)
172 raise error.ProgrammingError('invalid operator %r' % op)
172 raise error.ProgrammingError('invalid operator %r' % op)
173
173
174 def _insertstatushints(x):
175 """Insert hint nodes where status should be calculated (first path)
176
177 This works in bottom-up way, summing up status names and inserting hint
178 nodes at 'and' and 'or' as needed. Thus redundant hint nodes may be left.
179
180 Returns (status-names, new-tree) at the given subtree, where status-names
181 is a sum of status names referenced in the given subtree.
182 """
183 if x is None:
184 return (), x
185
186 op = x[0]
187 if op in {'string', 'symbol', 'kindpat'}:
188 return (), x
189 if op == 'not':
190 h, t = _insertstatushints(x[1])
191 return h, (op, t)
192 if op == 'and':
193 ha, ta = _insertstatushints(x[1])
194 hb, tb = _insertstatushints(x[2])
195 hr = ha + hb
196 if ha and hb:
197 return hr, ('withstatus', (op, ta, tb), ('string', ' '.join(hr)))
198 return hr, (op, ta, tb)
199 if op == 'or':
200 hs, ts = zip(*(_insertstatushints(y) for y in x[1:]))
201 hr = sum(hs, ())
202 if sum(bool(h) for h in hs) > 1:
203 return hr, ('withstatus', (op,) + ts, ('string', ' '.join(hr)))
204 return hr, (op,) + ts
205 if op == 'list':
206 hs, ts = zip(*(_insertstatushints(y) for y in x[1:]))
207 return sum(hs, ()), (op,) + ts
208 if op == 'func':
209 f = getsymbol(x[1])
210 # don't propagate 'ha' crossing a function boundary
211 ha, ta = _insertstatushints(x[2])
212 if getattr(symbols.get(f), '_callstatus', False):
213 return (f,), ('withstatus', (op, x[1], ta), ('string', f))
214 return (), (op, x[1], ta)
215 raise error.ProgrammingError('invalid operator %r' % op)
216
217 def _mergestatushints(x, instatus):
218 """Remove redundant status hint nodes (second path)
219
220 This is the top-down path to eliminate inner hint nodes.
221 """
222 if x is None:
223 return x
224
225 op = x[0]
226 if op == 'withstatus':
227 if instatus:
228 # drop redundant hint node
229 return _mergestatushints(x[1], instatus)
230 t = _mergestatushints(x[1], instatus=True)
231 return (op, t, x[2])
232 if op in {'string', 'symbol', 'kindpat'}:
233 return x
234 if op == 'not':
235 t = _mergestatushints(x[1], instatus)
236 return (op, t)
237 if op == 'and':
238 ta = _mergestatushints(x[1], instatus)
239 tb = _mergestatushints(x[2], instatus)
240 return (op, ta, tb)
241 if op in {'list', 'or'}:
242 ts = tuple(_mergestatushints(y, instatus) for y in x[1:])
243 return (op,) + ts
244 if op == 'func':
245 # don't propagate 'instatus' crossing a function boundary
246 ta = _mergestatushints(x[2], instatus=False)
247 return (op, x[1], ta)
248 raise error.ProgrammingError('invalid operator %r' % op)
249
174 def analyze(x):
250 def analyze(x):
175 """Transform raw parsed tree to evaluatable tree which can be fed to
251 """Transform raw parsed tree to evaluatable tree which can be fed to
176 optimize() or getmatch()
252 optimize() or getmatch()
177
253
178 All pseudo operations should be mapped to real operations or functions
254 All pseudo operations should be mapped to real operations or functions
179 defined in methods or symbols table respectively.
255 defined in methods or symbols table respectively.
180 """
256 """
181 return _analyze(x)
257 t = _analyze(x)
258 _h, t = _insertstatushints(t)
259 return _mergestatushints(t, instatus=False)
182
260
183 def _optimizeandops(op, ta, tb):
261 def _optimizeandops(op, ta, tb):
184 if tb is not None and tb[0] == 'not':
262 if tb is not None and tb[0] == 'not':
185 return ('minus', ta, tb[1])
263 return ('minus', ta, tb[1])
186 return (op, ta, tb)
264 return (op, ta, tb)
187
265
188 def _optimizeunion(xs):
266 def _optimizeunion(xs):
189 # collect string patterns so they can be compiled into a single regexp
267 # collect string patterns so they can be compiled into a single regexp
190 ws, ts, ss = [], [], []
268 ws, ts, ss = [], [], []
191 for x in xs:
269 for x in xs:
192 w, t = _optimize(x)
270 w, t = _optimize(x)
193 if t is not None and t[0] in {'string', 'symbol', 'kindpat'}:
271 if t is not None and t[0] in {'string', 'symbol', 'kindpat'}:
194 ss.append(t)
272 ss.append(t)
195 continue
273 continue
196 ws.append(w)
274 ws.append(w)
197 ts.append(t)
275 ts.append(t)
198 if ss:
276 if ss:
199 ws.append(WEIGHT_CHECK_FILENAME)
277 ws.append(WEIGHT_CHECK_FILENAME)
200 ts.append(('patterns',) + tuple(ss))
278 ts.append(('patterns',) + tuple(ss))
201 return ws, ts
279 return ws, ts
202
280
203 def _optimize(x):
281 def _optimize(x):
204 if x is None:
282 if x is None:
205 return 0, x
283 return 0, x
206
284
207 op = x[0]
285 op = x[0]
286 if op == 'withstatus':
287 w, t = _optimize(x[1])
288 return w, (op, t, x[2])
208 if op in {'string', 'symbol'}:
289 if op in {'string', 'symbol'}:
209 return WEIGHT_CHECK_FILENAME, x
290 return WEIGHT_CHECK_FILENAME, x
210 if op == 'kindpat':
291 if op == 'kindpat':
211 w, t = _optimize(x[2])
292 w, t = _optimize(x[2])
212 return w, (op, x[1], t)
293 return w, (op, x[1], t)
213 if op == 'not':
294 if op == 'not':
214 w, t = _optimize(x[1])
295 w, t = _optimize(x[1])
215 return w, (op, t)
296 return w, (op, t)
216 if op == 'and':
297 if op == 'and':
217 wa, ta = _optimize(x[1])
298 wa, ta = _optimize(x[1])
218 wb, tb = _optimize(x[2])
299 wb, tb = _optimize(x[2])
219 if wa <= wb:
300 if wa <= wb:
220 return wa, _optimizeandops(op, ta, tb)
301 return wa, _optimizeandops(op, ta, tb)
221 else:
302 else:
222 return wb, _optimizeandops(op, tb, ta)
303 return wb, _optimizeandops(op, tb, ta)
223 if op == 'or':
304 if op == 'or':
224 ws, ts = _optimizeunion(x[1:])
305 ws, ts = _optimizeunion(x[1:])
225 if len(ts) == 1:
306 if len(ts) == 1:
226 return ws[0], ts[0] # 'or' operation is fully optimized out
307 return ws[0], ts[0] # 'or' operation is fully optimized out
227 ts = tuple(it[1] for it in sorted(enumerate(ts),
308 ts = tuple(it[1] for it in sorted(enumerate(ts),
228 key=lambda it: ws[it[0]]))
309 key=lambda it: ws[it[0]]))
229 return max(ws), (op,) + ts
310 return max(ws), (op,) + ts
230 if op == 'list':
311 if op == 'list':
231 ws, ts = zip(*(_optimize(y) for y in x[1:]))
312 ws, ts = zip(*(_optimize(y) for y in x[1:]))
232 return sum(ws), (op,) + ts
313 return sum(ws), (op,) + ts
233 if op == 'func':
314 if op == 'func':
234 f = getsymbol(x[1])
315 f = getsymbol(x[1])
235 w = getattr(symbols.get(f), '_weight', 1)
316 w = getattr(symbols.get(f), '_weight', 1)
236 wa, ta = _optimize(x[2])
317 wa, ta = _optimize(x[2])
237 return w + wa, (op, x[1], ta)
318 return w + wa, (op, x[1], ta)
238 raise error.ProgrammingError('invalid operator %r' % op)
319 raise error.ProgrammingError('invalid operator %r' % op)
239
320
240 def optimize(x):
321 def optimize(x):
241 """Reorder/rewrite evaluatable tree for optimization
322 """Reorder/rewrite evaluatable tree for optimization
242
323
243 All pseudo operations should be transformed beforehand.
324 All pseudo operations should be transformed beforehand.
244 """
325 """
245 _w, t = _optimize(x)
326 _w, t = _optimize(x)
246 return t
327 return t
247
328
248 def prettyformat(tree):
329 def prettyformat(tree):
249 return parser.prettyformat(tree, ('string', 'symbol'))
330 return parser.prettyformat(tree, ('string', 'symbol'))
@@ -1,90 +1,92 b''
1 # minifileset.py - a simple language to select files
1 # minifileset.py - a simple language to select files
2 #
2 #
3 # Copyright 2017 Facebook, Inc.
3 # Copyright 2017 Facebook, Inc.
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 from .i18n import _
10 from .i18n import _
11 from . import (
11 from . import (
12 error,
12 error,
13 fileset,
13 fileset,
14 filesetlang,
14 filesetlang,
15 pycompat,
15 pycompat,
16 )
16 )
17
17
18 def _sizep(x):
18 def _sizep(x):
19 # i18n: "size" is a keyword
19 # i18n: "size" is a keyword
20 expr = filesetlang.getstring(x, _("size requires an expression"))
20 expr = filesetlang.getstring(x, _("size requires an expression"))
21 return fileset.sizematcher(expr)
21 return fileset.sizematcher(expr)
22
22
23 def _compile(tree):
23 def _compile(tree):
24 if not tree:
24 if not tree:
25 raise error.ParseError(_("missing argument"))
25 raise error.ParseError(_("missing argument"))
26 op = tree[0]
26 op = tree[0]
27 if op in {'symbol', 'string', 'kindpat'}:
27 if op == 'withstatus':
28 return _compile(tree[1])
29 elif op in {'symbol', 'string', 'kindpat'}:
28 name = filesetlang.getpattern(tree, {'path'}, _('invalid file pattern'))
30 name = filesetlang.getpattern(tree, {'path'}, _('invalid file pattern'))
29 if name.startswith('**'): # file extension test, ex. "**.tar.gz"
31 if name.startswith('**'): # file extension test, ex. "**.tar.gz"
30 ext = name[2:]
32 ext = name[2:]
31 for c in pycompat.bytestr(ext):
33 for c in pycompat.bytestr(ext):
32 if c in '*{}[]?/\\':
34 if c in '*{}[]?/\\':
33 raise error.ParseError(_('reserved character: %s') % c)
35 raise error.ParseError(_('reserved character: %s') % c)
34 return lambda n, s: n.endswith(ext)
36 return lambda n, s: n.endswith(ext)
35 elif name.startswith('path:'): # directory or full path test
37 elif name.startswith('path:'): # directory or full path test
36 p = name[5:] # prefix
38 p = name[5:] # prefix
37 pl = len(p)
39 pl = len(p)
38 f = lambda n, s: n.startswith(p) and (len(n) == pl
40 f = lambda n, s: n.startswith(p) and (len(n) == pl
39 or n[pl:pl + 1] == '/')
41 or n[pl:pl + 1] == '/')
40 return f
42 return f
41 raise error.ParseError(_("unsupported file pattern: %s") % name,
43 raise error.ParseError(_("unsupported file pattern: %s") % name,
42 hint=_('paths must be prefixed with "path:"'))
44 hint=_('paths must be prefixed with "path:"'))
43 elif op in {'or', 'patterns'}:
45 elif op in {'or', 'patterns'}:
44 funcs = [_compile(x) for x in tree[1:]]
46 funcs = [_compile(x) for x in tree[1:]]
45 return lambda n, s: any(f(n, s) for f in funcs)
47 return lambda n, s: any(f(n, s) for f in funcs)
46 elif op == 'and':
48 elif op == 'and':
47 func1 = _compile(tree[1])
49 func1 = _compile(tree[1])
48 func2 = _compile(tree[2])
50 func2 = _compile(tree[2])
49 return lambda n, s: func1(n, s) and func2(n, s)
51 return lambda n, s: func1(n, s) and func2(n, s)
50 elif op == 'not':
52 elif op == 'not':
51 return lambda n, s: not _compile(tree[1])(n, s)
53 return lambda n, s: not _compile(tree[1])(n, s)
52 elif op == 'func':
54 elif op == 'func':
53 symbols = {
55 symbols = {
54 'all': lambda n, s: True,
56 'all': lambda n, s: True,
55 'none': lambda n, s: False,
57 'none': lambda n, s: False,
56 'size': lambda n, s: _sizep(tree[2])(s),
58 'size': lambda n, s: _sizep(tree[2])(s),
57 }
59 }
58
60
59 name = filesetlang.getsymbol(tree[1])
61 name = filesetlang.getsymbol(tree[1])
60 if name in symbols:
62 if name in symbols:
61 return symbols[name]
63 return symbols[name]
62
64
63 raise error.UnknownIdentifier(name, symbols.keys())
65 raise error.UnknownIdentifier(name, symbols.keys())
64 elif op == 'minus': # equivalent to 'x and not y'
66 elif op == 'minus': # equivalent to 'x and not y'
65 func1 = _compile(tree[1])
67 func1 = _compile(tree[1])
66 func2 = _compile(tree[2])
68 func2 = _compile(tree[2])
67 return lambda n, s: func1(n, s) and not func2(n, s)
69 return lambda n, s: func1(n, s) and not func2(n, s)
68 elif op == 'list':
70 elif op == 'list':
69 raise error.ParseError(_("can't use a list in this context"),
71 raise error.ParseError(_("can't use a list in this context"),
70 hint=_('see \'hg help "filesets.x or y"\''))
72 hint=_('see \'hg help "filesets.x or y"\''))
71 raise error.ProgrammingError('illegal tree: %r' % (tree,))
73 raise error.ProgrammingError('illegal tree: %r' % (tree,))
72
74
73 def compile(text):
75 def compile(text):
74 """generate a function (path, size) -> bool from filter specification.
76 """generate a function (path, size) -> bool from filter specification.
75
77
76 "text" could contain the operators defined by the fileset language for
78 "text" could contain the operators defined by the fileset language for
77 common logic operations, and parenthesis for grouping. The supported path
79 common logic operations, and parenthesis for grouping. The supported path
78 tests are '**.extname' for file extension test, and '"path:dir/subdir"'
80 tests are '**.extname' for file extension test, and '"path:dir/subdir"'
79 for prefix test. The ``size()`` predicate is borrowed from filesets to test
81 for prefix test. The ``size()`` predicate is borrowed from filesets to test
80 file size. The predicates ``all()`` and ``none()`` are also supported.
82 file size. The predicates ``all()`` and ``none()`` are also supported.
81
83
82 '(**.php & size(">10MB")) | **.zip | (path:bin & !path:bin/README)' for
84 '(**.php & size(">10MB")) | **.zip | (path:bin & !path:bin/README)' for
83 example, will catch all php files whose size is greater than 10 MB, all
85 example, will catch all php files whose size is greater than 10 MB, all
84 files whose name ends with ".zip", and all files under "bin" in the repo
86 files whose name ends with ".zip", and all files under "bin" in the repo
85 root except for "bin/README".
87 root except for "bin/README".
86 """
88 """
87 tree = filesetlang.parse(text)
89 tree = filesetlang.parse(text)
88 tree = filesetlang.analyze(tree)
90 tree = filesetlang.analyze(tree)
89 tree = filesetlang.optimize(tree)
91 tree = filesetlang.optimize(tree)
90 return _compile(tree)
92 return _compile(tree)
@@ -1,883 +1,1037 b''
1 $ fileset() {
1 $ fileset() {
2 > hg debugfileset --all-files "$@"
2 > hg debugfileset --all-files "$@"
3 > }
3 > }
4
4
5 $ hg init repo
5 $ hg init repo
6 $ cd repo
6 $ cd repo
7 $ echo a > a1
7 $ echo a > a1
8 $ echo a > a2
8 $ echo a > a2
9 $ echo b > b1
9 $ echo b > b1
10 $ echo b > b2
10 $ echo b > b2
11 $ hg ci -Am addfiles
11 $ hg ci -Am addfiles
12 adding a1
12 adding a1
13 adding a2
13 adding a2
14 adding b1
14 adding b1
15 adding b2
15 adding b2
16
16
17 Test operators and basic patterns
17 Test operators and basic patterns
18
18
19 $ fileset -v a1
19 $ fileset -v a1
20 (symbol 'a1')
20 (symbol 'a1')
21 * matcher:
21 * matcher:
22 <patternmatcher patterns='(?:a1$)'>
22 <patternmatcher patterns='(?:a1$)'>
23 a1
23 a1
24 $ fileset -v 'a*'
24 $ fileset -v 'a*'
25 (symbol 'a*')
25 (symbol 'a*')
26 * matcher:
26 * matcher:
27 <patternmatcher patterns='(?:a[^/]*$)'>
27 <patternmatcher patterns='(?:a[^/]*$)'>
28 a1
28 a1
29 a2
29 a2
30 $ fileset -v '"re:a\d"'
30 $ fileset -v '"re:a\d"'
31 (string 're:a\\d')
31 (string 're:a\\d')
32 * matcher:
32 * matcher:
33 <patternmatcher patterns='(?:a\\d)'>
33 <patternmatcher patterns='(?:a\\d)'>
34 a1
34 a1
35 a2
35 a2
36 $ fileset -v '!re:"a\d"'
36 $ fileset -v '!re:"a\d"'
37 (not
37 (not
38 (kindpat
38 (kindpat
39 (symbol 're')
39 (symbol 're')
40 (string 'a\\d')))
40 (string 'a\\d')))
41 * matcher:
41 * matcher:
42 <predicatenmatcher
42 <predicatenmatcher
43 pred=<not
43 pred=<not
44 <patternmatcher patterns='(?:a\\d)'>>>
44 <patternmatcher patterns='(?:a\\d)'>>>
45 b1
45 b1
46 b2
46 b2
47 $ fileset -v 'path:a1 or glob:b?'
47 $ fileset -v 'path:a1 or glob:b?'
48 (or
48 (or
49 (kindpat
49 (kindpat
50 (symbol 'path')
50 (symbol 'path')
51 (symbol 'a1'))
51 (symbol 'a1'))
52 (kindpat
52 (kindpat
53 (symbol 'glob')
53 (symbol 'glob')
54 (symbol 'b?')))
54 (symbol 'b?')))
55 * matcher:
55 * matcher:
56 <patternmatcher patterns='(?:a1(?:/|$)|b.$)'>
56 <patternmatcher patterns='(?:a1(?:/|$)|b.$)'>
57 a1
57 a1
58 b1
58 b1
59 b2
59 b2
60 $ fileset -v --no-show-matcher 'a1 or a2'
60 $ fileset -v --no-show-matcher 'a1 or a2'
61 (or
61 (or
62 (symbol 'a1')
62 (symbol 'a1')
63 (symbol 'a2'))
63 (symbol 'a2'))
64 a1
64 a1
65 a2
65 a2
66 $ fileset 'a1 | a2'
66 $ fileset 'a1 | a2'
67 a1
67 a1
68 a2
68 a2
69 $ fileset 'a* and "*1"'
69 $ fileset 'a* and "*1"'
70 a1
70 a1
71 $ fileset 'a* & "*1"'
71 $ fileset 'a* & "*1"'
72 a1
72 a1
73 $ fileset 'not (r"a*")'
73 $ fileset 'not (r"a*")'
74 b1
74 b1
75 b2
75 b2
76 $ fileset '! ("a*")'
76 $ fileset '! ("a*")'
77 b1
77 b1
78 b2
78 b2
79 $ fileset 'a* - a1'
79 $ fileset 'a* - a1'
80 a2
80 a2
81 $ fileset 'a_b'
81 $ fileset 'a_b'
82 $ fileset '"\xy"'
82 $ fileset '"\xy"'
83 hg: parse error: invalid \x escape* (glob)
83 hg: parse error: invalid \x escape* (glob)
84 [255]
84 [255]
85
85
86 Test invalid syntax
86 Test invalid syntax
87
87
88 $ fileset -v '"added"()'
88 $ fileset -v '"added"()'
89 (func
89 (func
90 (string 'added')
90 (string 'added')
91 None)
91 None)
92 hg: parse error: not a symbol
92 hg: parse error: not a symbol
93 [255]
93 [255]
94 $ fileset -v '()()'
94 $ fileset -v '()()'
95 (func
95 (func
96 (group
96 (group
97 None)
97 None)
98 None)
98 None)
99 hg: parse error: not a symbol
99 hg: parse error: not a symbol
100 [255]
100 [255]
101 $ fileset -v -- '-x'
101 $ fileset -v -- '-x'
102 (negate
102 (negate
103 (symbol 'x'))
103 (symbol 'x'))
104 hg: parse error: can't use negate operator in this context
104 hg: parse error: can't use negate operator in this context
105 [255]
105 [255]
106 $ fileset -v -- '-()'
106 $ fileset -v -- '-()'
107 (negate
107 (negate
108 (group
108 (group
109 None))
109 None))
110 hg: parse error: can't use negate operator in this context
110 hg: parse error: can't use negate operator in this context
111 [255]
111 [255]
112 $ fileset -p parsed 'a, b, c'
112 $ fileset -p parsed 'a, b, c'
113 * parsed:
113 * parsed:
114 (list
114 (list
115 (symbol 'a')
115 (symbol 'a')
116 (symbol 'b')
116 (symbol 'b')
117 (symbol 'c'))
117 (symbol 'c'))
118 hg: parse error: can't use a list in this context
118 hg: parse error: can't use a list in this context
119 (see 'hg help "filesets.x or y"')
119 (see 'hg help "filesets.x or y"')
120 [255]
120 [255]
121
121
122 $ fileset '"path":.'
122 $ fileset '"path":.'
123 hg: parse error: not a symbol
123 hg: parse error: not a symbol
124 [255]
124 [255]
125 $ fileset 'path:foo bar'
125 $ fileset 'path:foo bar'
126 hg: parse error at 9: invalid token
126 hg: parse error at 9: invalid token
127 [255]
127 [255]
128 $ fileset 'foo:bar:baz'
128 $ fileset 'foo:bar:baz'
129 hg: parse error: not a symbol
129 hg: parse error: not a symbol
130 [255]
130 [255]
131 $ fileset 'foo:bar()'
131 $ fileset 'foo:bar()'
132 hg: parse error: pattern must be a string
132 hg: parse error: pattern must be a string
133 [255]
133 [255]
134 $ fileset 'foo:bar'
134 $ fileset 'foo:bar'
135 hg: parse error: invalid pattern kind: foo
135 hg: parse error: invalid pattern kind: foo
136 [255]
136 [255]
137
137
138 Show parsed tree at stages:
138 Show parsed tree at stages:
139
139
140 $ fileset -p unknown a
140 $ fileset -p unknown a
141 abort: invalid stage name: unknown
141 abort: invalid stage name: unknown
142 [255]
142 [255]
143
143
144 $ fileset -p parsed 'path:a1 or glob:b?'
144 $ fileset -p parsed 'path:a1 or glob:b?'
145 * parsed:
145 * parsed:
146 (or
146 (or
147 (kindpat
147 (kindpat
148 (symbol 'path')
148 (symbol 'path')
149 (symbol 'a1'))
149 (symbol 'a1'))
150 (kindpat
150 (kindpat
151 (symbol 'glob')
151 (symbol 'glob')
152 (symbol 'b?')))
152 (symbol 'b?')))
153 a1
153 a1
154 b1
154 b1
155 b2
155 b2
156
156
157 $ fileset -p all -s 'a1 or a2 or (grep("b") & clean())'
157 $ fileset -p all -s 'a1 or a2 or (grep("b") & clean())'
158 * parsed:
158 * parsed:
159 (or
159 (or
160 (symbol 'a1')
160 (symbol 'a1')
161 (symbol 'a2')
161 (symbol 'a2')
162 (group
162 (group
163 (and
163 (and
164 (func
164 (func
165 (symbol 'grep')
165 (symbol 'grep')
166 (string 'b'))
166 (string 'b'))
167 (func
167 (func
168 (symbol 'clean')
168 (symbol 'clean')
169 None))))
169 None))))
170 * analyzed:
170 * analyzed:
171 (or
171 (or
172 (symbol 'a1')
172 (symbol 'a1')
173 (symbol 'a2')
173 (symbol 'a2')
174 (and
174 (and
175 (func
175 (func
176 (symbol 'grep')
176 (symbol 'grep')
177 (string 'b'))
177 (string 'b'))
178 (func
178 (withstatus
179 (symbol 'clean')
179 (func
180 None)))
180 (symbol 'clean')
181 None)
182 (string 'clean'))))
181 * optimized:
183 * optimized:
182 (or
184 (or
183 (patterns
185 (patterns
184 (symbol 'a1')
186 (symbol 'a1')
185 (symbol 'a2'))
187 (symbol 'a2'))
186 (and
188 (and
187 (func
189 (withstatus
188 (symbol 'clean')
190 (func
189 None)
191 (symbol 'clean')
192 None)
193 (string 'clean'))
190 (func
194 (func
191 (symbol 'grep')
195 (symbol 'grep')
192 (string 'b'))))
196 (string 'b'))))
193 * matcher:
197 * matcher:
194 <unionmatcher matchers=[
198 <unionmatcher matchers=[
195 <patternmatcher patterns='(?:a1$|a2$)'>,
199 <patternmatcher patterns='(?:a1$|a2$)'>,
196 <intersectionmatcher
200 <intersectionmatcher
197 m1=<predicatenmatcher pred=clean>,
201 m1=<predicatenmatcher pred=clean>,
198 m2=<predicatenmatcher pred=grep('b')>>]>
202 m2=<predicatenmatcher pred=grep('b')>>]>
199 a1
203 a1
200 a2
204 a2
201 b1
205 b1
202 b2
206 b2
203
207
204 Union of basic patterns:
208 Union of basic patterns:
205
209
206 $ fileset -p optimized -s -r. 'a1 or a2 or path:b1'
210 $ fileset -p optimized -s -r. 'a1 or a2 or path:b1'
207 * optimized:
211 * optimized:
208 (patterns
212 (patterns
209 (symbol 'a1')
213 (symbol 'a1')
210 (symbol 'a2')
214 (symbol 'a2')
211 (kindpat
215 (kindpat
212 (symbol 'path')
216 (symbol 'path')
213 (symbol 'b1')))
217 (symbol 'b1')))
214 * matcher:
218 * matcher:
215 <patternmatcher patterns='(?:a1$|a2$|b1(?:/|$))'>
219 <patternmatcher patterns='(?:a1$|a2$|b1(?:/|$))'>
216 a1
220 a1
217 a2
221 a2
218 b1
222 b1
219
223
220 OR expression should be reordered by weight:
224 OR expression should be reordered by weight:
221
225
222 $ fileset -p optimized -s -r. 'grep("a") or a1 or grep("b") or b2'
226 $ fileset -p optimized -s -r. 'grep("a") or a1 or grep("b") or b2'
223 * optimized:
227 * optimized:
224 (or
228 (or
225 (patterns
229 (patterns
226 (symbol 'a1')
230 (symbol 'a1')
227 (symbol 'b2'))
231 (symbol 'b2'))
228 (func
232 (func
229 (symbol 'grep')
233 (symbol 'grep')
230 (string 'a'))
234 (string 'a'))
231 (func
235 (func
232 (symbol 'grep')
236 (symbol 'grep')
233 (string 'b')))
237 (string 'b')))
234 * matcher:
238 * matcher:
235 <unionmatcher matchers=[
239 <unionmatcher matchers=[
236 <patternmatcher patterns='(?:a1$|b2$)'>,
240 <patternmatcher patterns='(?:a1$|b2$)'>,
237 <predicatenmatcher pred=grep('a')>,
241 <predicatenmatcher pred=grep('a')>,
238 <predicatenmatcher pred=grep('b')>]>
242 <predicatenmatcher pred=grep('b')>]>
239 a1
243 a1
240 a2
244 a2
241 b1
245 b1
242 b2
246 b2
243
247
244 Use differencematcher for 'x and not y':
248 Use differencematcher for 'x and not y':
245
249
246 $ fileset -p optimized -s 'a* and not a1'
250 $ fileset -p optimized -s 'a* and not a1'
247 * optimized:
251 * optimized:
248 (minus
252 (minus
249 (symbol 'a*')
253 (symbol 'a*')
250 (symbol 'a1'))
254 (symbol 'a1'))
251 * matcher:
255 * matcher:
252 <differencematcher
256 <differencematcher
253 m1=<patternmatcher patterns='(?:a[^/]*$)'>,
257 m1=<patternmatcher patterns='(?:a[^/]*$)'>,
254 m2=<patternmatcher patterns='(?:a1$)'>>
258 m2=<patternmatcher patterns='(?:a1$)'>>
255 a2
259 a2
256
260
257 $ fileset -p optimized -s '!binary() and a*'
261 $ fileset -p optimized -s '!binary() and a*'
258 * optimized:
262 * optimized:
259 (minus
263 (minus
260 (symbol 'a*')
264 (symbol 'a*')
261 (func
265 (func
262 (symbol 'binary')
266 (symbol 'binary')
263 None))
267 None))
264 * matcher:
268 * matcher:
265 <differencematcher
269 <differencematcher
266 m1=<patternmatcher patterns='(?:a[^/]*$)'>,
270 m1=<patternmatcher patterns='(?:a[^/]*$)'>,
267 m2=<predicatenmatcher pred=binary>>
271 m2=<predicatenmatcher pred=binary>>
268 a1
272 a1
269 a2
273 a2
270
274
271 'x - y' is rewritten to 'x and not y' first so the operands can be reordered:
275 'x - y' is rewritten to 'x and not y' first so the operands can be reordered:
272
276
273 $ fileset -p analyzed -p optimized -s 'a* - a1'
277 $ fileset -p analyzed -p optimized -s 'a* - a1'
274 * analyzed:
278 * analyzed:
275 (and
279 (and
276 (symbol 'a*')
280 (symbol 'a*')
277 (not
281 (not
278 (symbol 'a1')))
282 (symbol 'a1')))
279 * optimized:
283 * optimized:
280 (minus
284 (minus
281 (symbol 'a*')
285 (symbol 'a*')
282 (symbol 'a1'))
286 (symbol 'a1'))
283 * matcher:
287 * matcher:
284 <differencematcher
288 <differencematcher
285 m1=<patternmatcher patterns='(?:a[^/]*$)'>,
289 m1=<patternmatcher patterns='(?:a[^/]*$)'>,
286 m2=<patternmatcher patterns='(?:a1$)'>>
290 m2=<patternmatcher patterns='(?:a1$)'>>
287 a2
291 a2
288
292
289 $ fileset -p analyzed -p optimized -s 'binary() - a*'
293 $ fileset -p analyzed -p optimized -s 'binary() - a*'
290 * analyzed:
294 * analyzed:
291 (and
295 (and
292 (func
296 (func
293 (symbol 'binary')
297 (symbol 'binary')
294 None)
298 None)
295 (not
299 (not
296 (symbol 'a*')))
300 (symbol 'a*')))
297 * optimized:
301 * optimized:
298 (and
302 (and
299 (not
303 (not
300 (symbol 'a*'))
304 (symbol 'a*'))
301 (func
305 (func
302 (symbol 'binary')
306 (symbol 'binary')
303 None))
307 None))
304 * matcher:
308 * matcher:
305 <intersectionmatcher
309 <intersectionmatcher
306 m1=<predicatenmatcher
310 m1=<predicatenmatcher
307 pred=<not
311 pred=<not
308 <patternmatcher patterns='(?:a[^/]*$)'>>>,
312 <patternmatcher patterns='(?:a[^/]*$)'>>>,
309 m2=<predicatenmatcher pred=binary>>
313 m2=<predicatenmatcher pred=binary>>
310
314
311 Test files status
315 Test files status
312
316
313 $ rm a1
317 $ rm a1
314 $ hg rm a2
318 $ hg rm a2
315 $ echo b >> b2
319 $ echo b >> b2
316 $ hg cp b1 c1
320 $ hg cp b1 c1
317 $ echo c > c2
321 $ echo c > c2
318 $ echo c > c3
322 $ echo c > c3
319 $ cat > .hgignore <<EOF
323 $ cat > .hgignore <<EOF
320 > \.hgignore
324 > \.hgignore
321 > 2$
325 > 2$
322 > EOF
326 > EOF
323 $ fileset 'modified()'
327 $ fileset 'modified()'
324 b2
328 b2
325 $ fileset 'added()'
329 $ fileset 'added()'
326 c1
330 c1
327 $ fileset 'removed()'
331 $ fileset 'removed()'
328 a2
332 a2
329 $ fileset 'deleted()'
333 $ fileset 'deleted()'
330 a1
334 a1
331 $ fileset 'missing()'
335 $ fileset 'missing()'
332 a1
336 a1
333 $ fileset 'unknown()'
337 $ fileset 'unknown()'
334 c3
338 c3
335 $ fileset 'ignored()'
339 $ fileset 'ignored()'
336 .hgignore
340 .hgignore
337 c2
341 c2
338 $ fileset 'hgignore()'
342 $ fileset 'hgignore()'
339 .hgignore
343 .hgignore
340 a2
344 a2
341 b2
345 b2
342 c2
346 c2
343 $ fileset 'clean()'
347 $ fileset 'clean()'
344 b1
348 b1
345 $ fileset 'copied()'
349 $ fileset 'copied()'
346 c1
350 c1
347
351
348 Test files status in different revisions
352 Test files status in different revisions
349
353
350 $ hg status -m
354 $ hg status -m
351 M b2
355 M b2
352 $ fileset -r0 'revs("wdir()", modified())' --traceback
356 $ fileset -r0 'revs("wdir()", modified())' --traceback
353 b2
357 b2
354 $ hg status -a
358 $ hg status -a
355 A c1
359 A c1
356 $ fileset -r0 'revs("wdir()", added())'
360 $ fileset -r0 'revs("wdir()", added())'
357 c1
361 c1
358 $ hg status --change 0 -a
362 $ hg status --change 0 -a
359 A a1
363 A a1
360 A a2
364 A a2
361 A b1
365 A b1
362 A b2
366 A b2
363 $ hg status -mru
367 $ hg status -mru
364 M b2
368 M b2
365 R a2
369 R a2
366 ? c3
370 ? c3
367 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
371 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
368 a2
372 a2
369 b2
373 b2
370 $ fileset -r0 'added() or revs("wdir()", added())'
374 $ fileset -r0 'added() or revs("wdir()", added())'
371 a1
375 a1
372 a2
376 a2
373 b1
377 b1
374 b2
378 b2
375 c1
379 c1
376
380
381 Test insertion of status hints
382
383 $ fileset -p optimized 'added()'
384 * optimized:
385 (withstatus
386 (func
387 (symbol 'added')
388 None)
389 (string 'added'))
390 c1
391
392 $ fileset -p optimized 'a* & removed()'
393 * optimized:
394 (and
395 (symbol 'a*')
396 (withstatus
397 (func
398 (symbol 'removed')
399 None)
400 (string 'removed')))
401 a2
402
403 $ fileset -p optimized 'a* - removed()'
404 * optimized:
405 (minus
406 (symbol 'a*')
407 (withstatus
408 (func
409 (symbol 'removed')
410 None)
411 (string 'removed')))
412 a1
413
414 $ fileset -p analyzed -p optimized '(added() + removed()) - a*'
415 * analyzed:
416 (and
417 (withstatus
418 (or
419 (func
420 (symbol 'added')
421 None)
422 (func
423 (symbol 'removed')
424 None))
425 (string 'added removed'))
426 (not
427 (symbol 'a*')))
428 * optimized:
429 (and
430 (not
431 (symbol 'a*'))
432 (withstatus
433 (or
434 (func
435 (symbol 'added')
436 None)
437 (func
438 (symbol 'removed')
439 None))
440 (string 'added removed')))
441 c1
442
443 $ fileset -p optimized 'a* + b* + added() + unknown()'
444 * optimized:
445 (withstatus
446 (or
447 (patterns
448 (symbol 'a*')
449 (symbol 'b*'))
450 (func
451 (symbol 'added')
452 None)
453 (func
454 (symbol 'unknown')
455 None))
456 (string 'added unknown'))
457 a1
458 a2
459 b1
460 b2
461 c1
462 c3
463
464 $ fileset -p analyzed -p optimized 'removed() & missing() & a*'
465 * analyzed:
466 (and
467 (withstatus
468 (and
469 (func
470 (symbol 'removed')
471 None)
472 (func
473 (symbol 'missing')
474 None))
475 (string 'removed missing'))
476 (symbol 'a*'))
477 * optimized:
478 (and
479 (symbol 'a*')
480 (withstatus
481 (and
482 (func
483 (symbol 'removed')
484 None)
485 (func
486 (symbol 'missing')
487 None))
488 (string 'removed missing')))
489
490 $ fileset -p optimized 'clean() & revs(0, added())'
491 * optimized:
492 (and
493 (withstatus
494 (func
495 (symbol 'clean')
496 None)
497 (string 'clean'))
498 (func
499 (symbol 'revs')
500 (list
501 (symbol '0')
502 (withstatus
503 (func
504 (symbol 'added')
505 None)
506 (string 'added')))))
507 b1
508
509 $ fileset -p optimized 'clean() & status(null, 0, b* & added())'
510 * optimized:
511 (and
512 (withstatus
513 (func
514 (symbol 'clean')
515 None)
516 (string 'clean'))
517 (func
518 (symbol 'status')
519 (list
520 (symbol 'null')
521 (symbol '0')
522 (and
523 (symbol 'b*')
524 (withstatus
525 (func
526 (symbol 'added')
527 None)
528 (string 'added'))))))
529 b1
530
377 Test files properties
531 Test files properties
378
532
379 >>> open('bin', 'wb').write(b'\0a') and None
533 >>> open('bin', 'wb').write(b'\0a') and None
380 $ fileset 'binary()'
534 $ fileset 'binary()'
381 bin
535 bin
382 $ fileset 'binary() and unknown()'
536 $ fileset 'binary() and unknown()'
383 bin
537 bin
384 $ echo '^bin$' >> .hgignore
538 $ echo '^bin$' >> .hgignore
385 $ fileset 'binary() and ignored()'
539 $ fileset 'binary() and ignored()'
386 bin
540 bin
387 $ hg add bin
541 $ hg add bin
388 $ fileset 'binary()'
542 $ fileset 'binary()'
389 bin
543 bin
390
544
391 $ fileset -p optimized -s 'binary() and b*'
545 $ fileset -p optimized -s 'binary() and b*'
392 * optimized:
546 * optimized:
393 (and
547 (and
394 (symbol 'b*')
548 (symbol 'b*')
395 (func
549 (func
396 (symbol 'binary')
550 (symbol 'binary')
397 None))
551 None))
398 * matcher:
552 * matcher:
399 <intersectionmatcher
553 <intersectionmatcher
400 m1=<patternmatcher patterns='(?:b[^/]*$)'>,
554 m1=<patternmatcher patterns='(?:b[^/]*$)'>,
401 m2=<predicatenmatcher pred=binary>>
555 m2=<predicatenmatcher pred=binary>>
402 bin
556 bin
403
557
404 $ fileset 'grep("b{1}")'
558 $ fileset 'grep("b{1}")'
405 .hgignore
559 .hgignore
406 b1
560 b1
407 b2
561 b2
408 c1
562 c1
409 $ fileset 'grep("missingparens(")'
563 $ fileset 'grep("missingparens(")'
410 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
564 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
411 [255]
565 [255]
412
566
413 #if execbit
567 #if execbit
414 $ chmod +x b2
568 $ chmod +x b2
415 $ fileset 'exec()'
569 $ fileset 'exec()'
416 b2
570 b2
417 #endif
571 #endif
418
572
419 #if symlink
573 #if symlink
420 $ ln -s b2 b2link
574 $ ln -s b2 b2link
421 $ fileset 'symlink() and unknown()'
575 $ fileset 'symlink() and unknown()'
422 b2link
576 b2link
423 $ hg add b2link
577 $ hg add b2link
424 #endif
578 #endif
425
579
426 #if no-windows
580 #if no-windows
427 $ echo foo > con.xml
581 $ echo foo > con.xml
428 $ fileset 'not portable()'
582 $ fileset 'not portable()'
429 con.xml
583 con.xml
430 $ hg --config ui.portablefilenames=ignore add con.xml
584 $ hg --config ui.portablefilenames=ignore add con.xml
431 #endif
585 #endif
432
586
433 >>> open('1k', 'wb').write(b' '*1024) and None
587 >>> open('1k', 'wb').write(b' '*1024) and None
434 >>> open('2k', 'wb').write(b' '*2048) and None
588 >>> open('2k', 'wb').write(b' '*2048) and None
435 $ hg add 1k 2k
589 $ hg add 1k 2k
436 $ fileset 'size("bar")'
590 $ fileset 'size("bar")'
437 hg: parse error: couldn't parse size: bar
591 hg: parse error: couldn't parse size: bar
438 [255]
592 [255]
439 $ fileset '(1k, 2k)'
593 $ fileset '(1k, 2k)'
440 hg: parse error: can't use a list in this context
594 hg: parse error: can't use a list in this context
441 (see 'hg help "filesets.x or y"')
595 (see 'hg help "filesets.x or y"')
442 [255]
596 [255]
443 $ fileset 'size(1k)'
597 $ fileset 'size(1k)'
444 1k
598 1k
445 $ fileset '(1k or 2k) and size("< 2k")'
599 $ fileset '(1k or 2k) and size("< 2k")'
446 1k
600 1k
447 $ fileset '(1k or 2k) and size("<=2k")'
601 $ fileset '(1k or 2k) and size("<=2k")'
448 1k
602 1k
449 2k
603 2k
450 $ fileset '(1k or 2k) and size("> 1k")'
604 $ fileset '(1k or 2k) and size("> 1k")'
451 2k
605 2k
452 $ fileset '(1k or 2k) and size(">=1K")'
606 $ fileset '(1k or 2k) and size(">=1K")'
453 1k
607 1k
454 2k
608 2k
455 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
609 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
456 1k
610 1k
457 $ fileset 'size("1M")'
611 $ fileset 'size("1M")'
458 $ fileset 'size("1 GB")'
612 $ fileset 'size("1 GB")'
459
613
460 Test merge states
614 Test merge states
461
615
462 $ hg ci -m manychanges
616 $ hg ci -m manychanges
463 $ hg file -r . 'set:copied() & modified()'
617 $ hg file -r . 'set:copied() & modified()'
464 [1]
618 [1]
465 $ hg up -C 0
619 $ hg up -C 0
466 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
620 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
467 $ echo c >> b2
621 $ echo c >> b2
468 $ hg ci -m diverging b2
622 $ hg ci -m diverging b2
469 created new head
623 created new head
470 $ fileset 'resolved()'
624 $ fileset 'resolved()'
471 $ fileset 'unresolved()'
625 $ fileset 'unresolved()'
472 $ hg merge
626 $ hg merge
473 merging b2
627 merging b2
474 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
628 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
475 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
629 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
476 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
630 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
477 [1]
631 [1]
478 $ fileset 'resolved()'
632 $ fileset 'resolved()'
479 $ fileset 'unresolved()'
633 $ fileset 'unresolved()'
480 b2
634 b2
481 $ echo e > b2
635 $ echo e > b2
482 $ hg resolve -m b2
636 $ hg resolve -m b2
483 (no more unresolved files)
637 (no more unresolved files)
484 $ fileset 'resolved()'
638 $ fileset 'resolved()'
485 b2
639 b2
486 $ fileset 'unresolved()'
640 $ fileset 'unresolved()'
487 $ hg ci -m merge
641 $ hg ci -m merge
488
642
489 Test subrepo predicate
643 Test subrepo predicate
490
644
491 $ hg init sub
645 $ hg init sub
492 $ echo a > sub/suba
646 $ echo a > sub/suba
493 $ hg -R sub add sub/suba
647 $ hg -R sub add sub/suba
494 $ hg -R sub ci -m sub
648 $ hg -R sub ci -m sub
495 $ echo 'sub = sub' > .hgsub
649 $ echo 'sub = sub' > .hgsub
496 $ hg init sub2
650 $ hg init sub2
497 $ echo b > sub2/b
651 $ echo b > sub2/b
498 $ hg -R sub2 ci -Am sub2
652 $ hg -R sub2 ci -Am sub2
499 adding b
653 adding b
500 $ echo 'sub2 = sub2' >> .hgsub
654 $ echo 'sub2 = sub2' >> .hgsub
501 $ fileset 'subrepo()'
655 $ fileset 'subrepo()'
502 $ hg add .hgsub
656 $ hg add .hgsub
503 $ fileset 'subrepo()'
657 $ fileset 'subrepo()'
504 sub
658 sub
505 sub2
659 sub2
506 $ fileset 'subrepo("sub")'
660 $ fileset 'subrepo("sub")'
507 sub
661 sub
508 $ fileset 'subrepo("glob:*")'
662 $ fileset 'subrepo("glob:*")'
509 sub
663 sub
510 sub2
664 sub2
511 $ hg ci -m subrepo
665 $ hg ci -m subrepo
512
666
513 Test that .hgsubstate is updated as appropriate during a conversion. The
667 Test that .hgsubstate is updated as appropriate during a conversion. The
514 saverev property is enough to alter the hashes of the subrepo.
668 saverev property is enough to alter the hashes of the subrepo.
515
669
516 $ hg init ../converted
670 $ hg init ../converted
517 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
671 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
518 > sub ../converted/sub
672 > sub ../converted/sub
519 initializing destination ../converted/sub repository
673 initializing destination ../converted/sub repository
520 scanning source...
674 scanning source...
521 sorting...
675 sorting...
522 converting...
676 converting...
523 0 sub
677 0 sub
524 $ hg clone -U sub2 ../converted/sub2
678 $ hg clone -U sub2 ../converted/sub2
525 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
679 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
526 > . ../converted
680 > . ../converted
527 scanning source...
681 scanning source...
528 sorting...
682 sorting...
529 converting...
683 converting...
530 4 addfiles
684 4 addfiles
531 3 manychanges
685 3 manychanges
532 2 diverging
686 2 diverging
533 1 merge
687 1 merge
534 0 subrepo
688 0 subrepo
535 no ".hgsubstate" updates will be made for "sub2"
689 no ".hgsubstate" updates will be made for "sub2"
536 $ hg up -q -R ../converted -r tip
690 $ hg up -q -R ../converted -r tip
537 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
691 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
538 a
692 a
539 b
693 b
540 $ oldnode=`hg log -r tip -T "{node}\n"`
694 $ oldnode=`hg log -r tip -T "{node}\n"`
541 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
695 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
542 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
696 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
543
697
544 Test with a revision
698 Test with a revision
545
699
546 $ hg log -G --template '{rev} {desc}\n'
700 $ hg log -G --template '{rev} {desc}\n'
547 @ 4 subrepo
701 @ 4 subrepo
548 |
702 |
549 o 3 merge
703 o 3 merge
550 |\
704 |\
551 | o 2 diverging
705 | o 2 diverging
552 | |
706 | |
553 o | 1 manychanges
707 o | 1 manychanges
554 |/
708 |/
555 o 0 addfiles
709 o 0 addfiles
556
710
557 $ echo unknown > unknown
711 $ echo unknown > unknown
558 $ fileset -r1 'modified()'
712 $ fileset -r1 'modified()'
559 b2
713 b2
560 $ fileset -r1 'added() and c1'
714 $ fileset -r1 'added() and c1'
561 c1
715 c1
562 $ fileset -r1 'removed()'
716 $ fileset -r1 'removed()'
563 a2
717 a2
564 $ fileset -r1 'deleted()'
718 $ fileset -r1 'deleted()'
565 $ fileset -r1 'unknown()'
719 $ fileset -r1 'unknown()'
566 $ fileset -r1 'ignored()'
720 $ fileset -r1 'ignored()'
567 $ fileset -r1 'hgignore()'
721 $ fileset -r1 'hgignore()'
568 .hgignore
722 .hgignore
569 a2
723 a2
570 b2
724 b2
571 bin
725 bin
572 c2
726 c2
573 sub2
727 sub2
574 $ fileset -r1 'binary()'
728 $ fileset -r1 'binary()'
575 bin
729 bin
576 $ fileset -r1 'size(1k)'
730 $ fileset -r1 'size(1k)'
577 1k
731 1k
578 $ fileset -r3 'resolved()'
732 $ fileset -r3 'resolved()'
579 $ fileset -r3 'unresolved()'
733 $ fileset -r3 'unresolved()'
580
734
581 #if execbit
735 #if execbit
582 $ fileset -r1 'exec()'
736 $ fileset -r1 'exec()'
583 b2
737 b2
584 #endif
738 #endif
585
739
586 #if symlink
740 #if symlink
587 $ fileset -r1 'symlink()'
741 $ fileset -r1 'symlink()'
588 b2link
742 b2link
589 #endif
743 #endif
590
744
591 #if no-windows
745 #if no-windows
592 $ fileset -r1 'not portable()'
746 $ fileset -r1 'not portable()'
593 con.xml
747 con.xml
594 $ hg forget 'con.xml'
748 $ hg forget 'con.xml'
595 #endif
749 #endif
596
750
597 $ fileset -r4 'subrepo("re:su.*")'
751 $ fileset -r4 'subrepo("re:su.*")'
598 sub
752 sub
599 sub2
753 sub2
600 $ fileset -r4 'subrepo(re:su.*)'
754 $ fileset -r4 'subrepo(re:su.*)'
601 sub
755 sub
602 sub2
756 sub2
603 $ fileset -r4 'subrepo("sub")'
757 $ fileset -r4 'subrepo("sub")'
604 sub
758 sub
605 $ fileset -r4 'b2 or c1'
759 $ fileset -r4 'b2 or c1'
606 b2
760 b2
607 c1
761 c1
608
762
609 >>> open('dos', 'wb').write(b"dos\r\n") and None
763 >>> open('dos', 'wb').write(b"dos\r\n") and None
610 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
764 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
611 >>> open('mac', 'wb').write(b"mac\r") and None
765 >>> open('mac', 'wb').write(b"mac\r") and None
612 $ hg add dos mixed mac
766 $ hg add dos mixed mac
613
767
614 (remove a1, to examine safety of 'eol' on removed files)
768 (remove a1, to examine safety of 'eol' on removed files)
615 $ rm a1
769 $ rm a1
616
770
617 $ fileset 'eol(dos)'
771 $ fileset 'eol(dos)'
618 dos
772 dos
619 mixed
773 mixed
620 $ fileset 'eol(unix)'
774 $ fileset 'eol(unix)'
621 .hgignore
775 .hgignore
622 .hgsub
776 .hgsub
623 .hgsubstate
777 .hgsubstate
624 b1
778 b1
625 b2
779 b2
626 b2.orig
780 b2.orig
627 c1
781 c1
628 c2
782 c2
629 c3
783 c3
630 con.xml (no-windows !)
784 con.xml (no-windows !)
631 mixed
785 mixed
632 unknown
786 unknown
633 $ fileset 'eol(mac)'
787 $ fileset 'eol(mac)'
634 mac
788 mac
635
789
636 Test safety of 'encoding' on removed files
790 Test safety of 'encoding' on removed files
637
791
638 $ fileset 'encoding("ascii")'
792 $ fileset 'encoding("ascii")'
639 .hgignore
793 .hgignore
640 .hgsub
794 .hgsub
641 .hgsubstate
795 .hgsubstate
642 1k
796 1k
643 2k
797 2k
644 b1
798 b1
645 b2
799 b2
646 b2.orig
800 b2.orig
647 b2link (symlink !)
801 b2link (symlink !)
648 bin
802 bin
649 c1
803 c1
650 c2
804 c2
651 c3
805 c3
652 con.xml (no-windows !)
806 con.xml (no-windows !)
653 dos
807 dos
654 mac
808 mac
655 mixed
809 mixed
656 unknown
810 unknown
657
811
658 Test 'revs(...)'
812 Test 'revs(...)'
659 ================
813 ================
660
814
661 small reminder of the repository state
815 small reminder of the repository state
662
816
663 $ hg log -G
817 $ hg log -G
664 @ changeset: 4:* (glob)
818 @ changeset: 4:* (glob)
665 | tag: tip
819 | tag: tip
666 | user: test
820 | user: test
667 | date: Thu Jan 01 00:00:00 1970 +0000
821 | date: Thu Jan 01 00:00:00 1970 +0000
668 | summary: subrepo
822 | summary: subrepo
669 |
823 |
670 o changeset: 3:* (glob)
824 o changeset: 3:* (glob)
671 |\ parent: 2:55b05bdebf36
825 |\ parent: 2:55b05bdebf36
672 | | parent: 1:* (glob)
826 | | parent: 1:* (glob)
673 | | user: test
827 | | user: test
674 | | date: Thu Jan 01 00:00:00 1970 +0000
828 | | date: Thu Jan 01 00:00:00 1970 +0000
675 | | summary: merge
829 | | summary: merge
676 | |
830 | |
677 | o changeset: 2:55b05bdebf36
831 | o changeset: 2:55b05bdebf36
678 | | parent: 0:8a9576c51c1f
832 | | parent: 0:8a9576c51c1f
679 | | user: test
833 | | user: test
680 | | date: Thu Jan 01 00:00:00 1970 +0000
834 | | date: Thu Jan 01 00:00:00 1970 +0000
681 | | summary: diverging
835 | | summary: diverging
682 | |
836 | |
683 o | changeset: 1:* (glob)
837 o | changeset: 1:* (glob)
684 |/ user: test
838 |/ user: test
685 | date: Thu Jan 01 00:00:00 1970 +0000
839 | date: Thu Jan 01 00:00:00 1970 +0000
686 | summary: manychanges
840 | summary: manychanges
687 |
841 |
688 o changeset: 0:8a9576c51c1f
842 o changeset: 0:8a9576c51c1f
689 user: test
843 user: test
690 date: Thu Jan 01 00:00:00 1970 +0000
844 date: Thu Jan 01 00:00:00 1970 +0000
691 summary: addfiles
845 summary: addfiles
692
846
693 $ hg status --change 0
847 $ hg status --change 0
694 A a1
848 A a1
695 A a2
849 A a2
696 A b1
850 A b1
697 A b2
851 A b2
698 $ hg status --change 1
852 $ hg status --change 1
699 M b2
853 M b2
700 A 1k
854 A 1k
701 A 2k
855 A 2k
702 A b2link (no-windows !)
856 A b2link (no-windows !)
703 A bin
857 A bin
704 A c1
858 A c1
705 A con.xml (no-windows !)
859 A con.xml (no-windows !)
706 R a2
860 R a2
707 $ hg status --change 2
861 $ hg status --change 2
708 M b2
862 M b2
709 $ hg status --change 3
863 $ hg status --change 3
710 M b2
864 M b2
711 A 1k
865 A 1k
712 A 2k
866 A 2k
713 A b2link (no-windows !)
867 A b2link (no-windows !)
714 A bin
868 A bin
715 A c1
869 A c1
716 A con.xml (no-windows !)
870 A con.xml (no-windows !)
717 R a2
871 R a2
718 $ hg status --change 4
872 $ hg status --change 4
719 A .hgsub
873 A .hgsub
720 A .hgsubstate
874 A .hgsubstate
721 $ hg status
875 $ hg status
722 A dos
876 A dos
723 A mac
877 A mac
724 A mixed
878 A mixed
725 R con.xml (no-windows !)
879 R con.xml (no-windows !)
726 ! a1
880 ! a1
727 ? b2.orig
881 ? b2.orig
728 ? c3
882 ? c3
729 ? unknown
883 ? unknown
730
884
731 Test files at -r0 should be filtered by files at wdir
885 Test files at -r0 should be filtered by files at wdir
732 -----------------------------------------------------
886 -----------------------------------------------------
733
887
734 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
888 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
735 a1
889 a1
736 b1
890 b1
737 b2
891 b2
738
892
739 Test that "revs()" work at all
893 Test that "revs()" work at all
740 ------------------------------
894 ------------------------------
741
895
742 $ fileset "revs('2', modified())"
896 $ fileset "revs('2', modified())"
743 b2
897 b2
744
898
745 Test that "revs()" work for file missing in the working copy/current context
899 Test that "revs()" work for file missing in the working copy/current context
746 ----------------------------------------------------------------------------
900 ----------------------------------------------------------------------------
747
901
748 (a2 not in working copy)
902 (a2 not in working copy)
749
903
750 $ fileset "revs('0', added())"
904 $ fileset "revs('0', added())"
751 a1
905 a1
752 a2
906 a2
753 b1
907 b1
754 b2
908 b2
755
909
756 (none of the file exist in "0")
910 (none of the file exist in "0")
757
911
758 $ fileset -r 0 "revs('4', added())"
912 $ fileset -r 0 "revs('4', added())"
759 .hgsub
913 .hgsub
760 .hgsubstate
914 .hgsubstate
761
915
762 Call with empty revset
916 Call with empty revset
763 --------------------------
917 --------------------------
764
918
765 $ fileset "revs('2-2', modified())"
919 $ fileset "revs('2-2', modified())"
766
920
767 Call with revset matching multiple revs
921 Call with revset matching multiple revs
768 ---------------------------------------
922 ---------------------------------------
769
923
770 $ fileset "revs('0+4', added())"
924 $ fileset "revs('0+4', added())"
771 .hgsub
925 .hgsub
772 .hgsubstate
926 .hgsubstate
773 a1
927 a1
774 a2
928 a2
775 b1
929 b1
776 b2
930 b2
777
931
778 overlapping set
932 overlapping set
779
933
780 $ fileset "revs('1+2', modified())"
934 $ fileset "revs('1+2', modified())"
781 b2
935 b2
782
936
783 test 'status(...)'
937 test 'status(...)'
784 =================
938 =================
785
939
786 Simple case
940 Simple case
787 -----------
941 -----------
788
942
789 $ fileset "status(3, 4, added())"
943 $ fileset "status(3, 4, added())"
790 .hgsub
944 .hgsub
791 .hgsubstate
945 .hgsubstate
792
946
793 use rev to restrict matched file
947 use rev to restrict matched file
794 -----------------------------------------
948 -----------------------------------------
795
949
796 $ hg status --removed --rev 0 --rev 1
950 $ hg status --removed --rev 0 --rev 1
797 R a2
951 R a2
798 $ fileset "status(0, 1, removed())"
952 $ fileset "status(0, 1, removed())"
799 a2
953 a2
800 $ fileset "tracked() and status(0, 1, removed())"
954 $ fileset "tracked() and status(0, 1, removed())"
801 $ fileset -r 4 "status(0, 1, removed())"
955 $ fileset -r 4 "status(0, 1, removed())"
802 a2
956 a2
803 $ fileset -r 4 "tracked() and status(0, 1, removed())"
957 $ fileset -r 4 "tracked() and status(0, 1, removed())"
804 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
958 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
805 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
959 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
806 a2
960 a2
807
961
808 check wdir()
962 check wdir()
809 ------------
963 ------------
810
964
811 $ hg status --removed --rev 4
965 $ hg status --removed --rev 4
812 R con.xml (no-windows !)
966 R con.xml (no-windows !)
813 $ fileset "status(4, 'wdir()', removed())"
967 $ fileset "status(4, 'wdir()', removed())"
814 con.xml (no-windows !)
968 con.xml (no-windows !)
815
969
816 $ hg status --removed --rev 2
970 $ hg status --removed --rev 2
817 R a2
971 R a2
818 $ fileset "status('2', 'wdir()', removed())"
972 $ fileset "status('2', 'wdir()', removed())"
819 a2
973 a2
820
974
821 test backward status
975 test backward status
822 --------------------
976 --------------------
823
977
824 $ hg status --removed --rev 0 --rev 4
978 $ hg status --removed --rev 0 --rev 4
825 R a2
979 R a2
826 $ hg status --added --rev 4 --rev 0
980 $ hg status --added --rev 4 --rev 0
827 A a2
981 A a2
828 $ fileset "status(4, 0, added())"
982 $ fileset "status(4, 0, added())"
829 a2
983 a2
830
984
831 test cross branch status
985 test cross branch status
832 ------------------------
986 ------------------------
833
987
834 $ hg status --added --rev 1 --rev 2
988 $ hg status --added --rev 1 --rev 2
835 A a2
989 A a2
836 $ fileset "status(1, 2, added())"
990 $ fileset "status(1, 2, added())"
837 a2
991 a2
838
992
839 test with multi revs revset
993 test with multi revs revset
840 ---------------------------
994 ---------------------------
841 $ hg status --added --rev 0:1 --rev 3:4
995 $ hg status --added --rev 0:1 --rev 3:4
842 A .hgsub
996 A .hgsub
843 A .hgsubstate
997 A .hgsubstate
844 A 1k
998 A 1k
845 A 2k
999 A 2k
846 A b2link (no-windows !)
1000 A b2link (no-windows !)
847 A bin
1001 A bin
848 A c1
1002 A c1
849 A con.xml (no-windows !)
1003 A con.xml (no-windows !)
850 $ fileset "status('0:1', '3:4', added())"
1004 $ fileset "status('0:1', '3:4', added())"
851 .hgsub
1005 .hgsub
852 .hgsubstate
1006 .hgsubstate
853 1k
1007 1k
854 2k
1008 2k
855 b2link (no-windows !)
1009 b2link (no-windows !)
856 bin
1010 bin
857 c1
1011 c1
858 con.xml (no-windows !)
1012 con.xml (no-windows !)
859
1013
860 tests with empty value
1014 tests with empty value
861 ----------------------
1015 ----------------------
862
1016
863 Fully empty revset
1017 Fully empty revset
864
1018
865 $ fileset "status('', '4', added())"
1019 $ fileset "status('', '4', added())"
866 hg: parse error: first argument to status must be a revision
1020 hg: parse error: first argument to status must be a revision
867 [255]
1021 [255]
868 $ fileset "status('2', '', added())"
1022 $ fileset "status('2', '', added())"
869 hg: parse error: second argument to status must be a revision
1023 hg: parse error: second argument to status must be a revision
870 [255]
1024 [255]
871
1025
872 Empty revset will error at the revset layer
1026 Empty revset will error at the revset layer
873
1027
874 $ fileset "status(' ', '4', added())"
1028 $ fileset "status(' ', '4', added())"
875 hg: parse error at 1: not a prefix: end
1029 hg: parse error at 1: not a prefix: end
876 (
1030 (
877 ^ here)
1031 ^ here)
878 [255]
1032 [255]
879 $ fileset "status('2', ' ', added())"
1033 $ fileset "status('2', ' ', added())"
880 hg: parse error at 1: not a prefix: end
1034 hg: parse error at 1: not a prefix: end
881 (
1035 (
882 ^ here)
1036 ^ here)
883 [255]
1037 [255]
General Comments 0
You need to be logged in to leave comments. Login now