##// END OF EJS Templates
fileset: add hint for list error to use or
timeless -
r27518:737ffdab default
parent child Browse files
Show More
@@ -1,563 +1,564
1 # fileset.py - file set queries for mercurial
1 # fileset.py - file set queries for mercurial
2 #
2 #
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import re
10 import re
11
11
12 from .i18n import _
12 from .i18n import _
13 from . import (
13 from . import (
14 error,
14 error,
15 merge,
15 merge,
16 parser,
16 parser,
17 util,
17 util,
18 )
18 )
19
19
20 elements = {
20 elements = {
21 # token-type: binding-strength, primary, prefix, infix, suffix
21 # token-type: binding-strength, primary, prefix, infix, suffix
22 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
22 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
23 "-": (5, None, ("negate", 19), ("minus", 5), None),
23 "-": (5, None, ("negate", 19), ("minus", 5), None),
24 "not": (10, None, ("not", 10), None, None),
24 "not": (10, None, ("not", 10), None, None),
25 "!": (10, None, ("not", 10), None, None),
25 "!": (10, None, ("not", 10), None, None),
26 "and": (5, None, None, ("and", 5), None),
26 "and": (5, None, None, ("and", 5), None),
27 "&": (5, None, None, ("and", 5), None),
27 "&": (5, None, None, ("and", 5), None),
28 "or": (4, None, None, ("or", 4), None),
28 "or": (4, None, None, ("or", 4), None),
29 "|": (4, None, None, ("or", 4), None),
29 "|": (4, None, None, ("or", 4), None),
30 "+": (4, None, None, ("or", 4), None),
30 "+": (4, None, None, ("or", 4), None),
31 ",": (2, None, None, ("list", 2), None),
31 ",": (2, None, None, ("list", 2), None),
32 ")": (0, None, None, None, None),
32 ")": (0, None, None, None, None),
33 "symbol": (0, "symbol", None, None, None),
33 "symbol": (0, "symbol", None, None, None),
34 "string": (0, "string", None, None, None),
34 "string": (0, "string", None, None, None),
35 "end": (0, None, None, None, None),
35 "end": (0, None, None, None, None),
36 }
36 }
37
37
38 keywords = set(['and', 'or', 'not'])
38 keywords = set(['and', 'or', 'not'])
39
39
40 globchars = ".*{}[]?/\\_"
40 globchars = ".*{}[]?/\\_"
41
41
42 def tokenize(program):
42 def tokenize(program):
43 pos, l = 0, len(program)
43 pos, l = 0, len(program)
44 while pos < l:
44 while pos < l:
45 c = program[pos]
45 c = program[pos]
46 if c.isspace(): # skip inter-token whitespace
46 if c.isspace(): # skip inter-token whitespace
47 pass
47 pass
48 elif c in "(),-|&+!": # handle simple operators
48 elif c in "(),-|&+!": # handle simple operators
49 yield (c, None, pos)
49 yield (c, None, pos)
50 elif (c in '"\'' or c == 'r' and
50 elif (c in '"\'' or c == 'r' and
51 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
51 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
52 if c == 'r':
52 if c == 'r':
53 pos += 1
53 pos += 1
54 c = program[pos]
54 c = program[pos]
55 decode = lambda x: x
55 decode = lambda x: x
56 else:
56 else:
57 decode = parser.unescapestr
57 decode = parser.unescapestr
58 pos += 1
58 pos += 1
59 s = pos
59 s = pos
60 while pos < l: # find closing quote
60 while pos < l: # find closing quote
61 d = program[pos]
61 d = program[pos]
62 if d == '\\': # skip over escaped characters
62 if d == '\\': # skip over escaped characters
63 pos += 2
63 pos += 2
64 continue
64 continue
65 if d == c:
65 if d == c:
66 yield ('string', decode(program[s:pos]), s)
66 yield ('string', decode(program[s:pos]), s)
67 break
67 break
68 pos += 1
68 pos += 1
69 else:
69 else:
70 raise error.ParseError(_("unterminated string"), s)
70 raise error.ParseError(_("unterminated string"), s)
71 elif c.isalnum() or c in globchars or ord(c) > 127:
71 elif c.isalnum() or c in globchars or ord(c) > 127:
72 # gather up a symbol/keyword
72 # gather up a symbol/keyword
73 s = pos
73 s = pos
74 pos += 1
74 pos += 1
75 while pos < l: # find end of symbol
75 while pos < l: # find end of symbol
76 d = program[pos]
76 d = program[pos]
77 if not (d.isalnum() or d in globchars or ord(d) > 127):
77 if not (d.isalnum() or d in globchars or ord(d) > 127):
78 break
78 break
79 pos += 1
79 pos += 1
80 sym = program[s:pos]
80 sym = program[s:pos]
81 if sym in keywords: # operator keywords
81 if sym in keywords: # operator keywords
82 yield (sym, None, s)
82 yield (sym, None, s)
83 else:
83 else:
84 yield ('symbol', sym, s)
84 yield ('symbol', sym, s)
85 pos -= 1
85 pos -= 1
86 else:
86 else:
87 raise error.ParseError(_("syntax error"), pos)
87 raise error.ParseError(_("syntax error"), pos)
88 pos += 1
88 pos += 1
89 yield ('end', None, pos)
89 yield ('end', None, pos)
90
90
91 def parse(expr):
91 def parse(expr):
92 p = parser.parser(elements)
92 p = parser.parser(elements)
93 tree, pos = p.parse(tokenize(expr))
93 tree, pos = p.parse(tokenize(expr))
94 if pos != len(expr):
94 if pos != len(expr):
95 raise error.ParseError(_("invalid token"), pos)
95 raise error.ParseError(_("invalid token"), pos)
96 return tree
96 return tree
97
97
98 def getstring(x, err):
98 def getstring(x, err):
99 if x and (x[0] == 'string' or x[0] == 'symbol'):
99 if x and (x[0] == 'string' or x[0] == 'symbol'):
100 return x[1]
100 return x[1]
101 raise error.ParseError(err)
101 raise error.ParseError(err)
102
102
103 def getset(mctx, x):
103 def getset(mctx, x):
104 if not x:
104 if not x:
105 raise error.ParseError(_("missing argument"))
105 raise error.ParseError(_("missing argument"))
106 return methods[x[0]](mctx, *x[1:])
106 return methods[x[0]](mctx, *x[1:])
107
107
108 def stringset(mctx, x):
108 def stringset(mctx, x):
109 m = mctx.matcher([x])
109 m = mctx.matcher([x])
110 return [f for f in mctx.subset if m(f)]
110 return [f for f in mctx.subset if m(f)]
111
111
112 def andset(mctx, x, y):
112 def andset(mctx, x, y):
113 return getset(mctx.narrow(getset(mctx, x)), y)
113 return getset(mctx.narrow(getset(mctx, x)), y)
114
114
115 def orset(mctx, x, y):
115 def orset(mctx, x, y):
116 # needs optimizing
116 # needs optimizing
117 xl = getset(mctx, x)
117 xl = getset(mctx, x)
118 yl = getset(mctx, y)
118 yl = getset(mctx, y)
119 return xl + [f for f in yl if f not in xl]
119 return xl + [f for f in yl if f not in xl]
120
120
121 def notset(mctx, x):
121 def notset(mctx, x):
122 s = set(getset(mctx, x))
122 s = set(getset(mctx, x))
123 return [r for r in mctx.subset if r not in s]
123 return [r for r in mctx.subset if r not in s]
124
124
125 def minusset(mctx, x, y):
125 def minusset(mctx, x, y):
126 xl = getset(mctx, x)
126 xl = getset(mctx, x)
127 yl = set(getset(mctx, y))
127 yl = set(getset(mctx, y))
128 return [f for f in xl if f not in yl]
128 return [f for f in xl if f not in yl]
129
129
130 def listset(mctx, a, b):
130 def listset(mctx, a, b):
131 raise error.ParseError(_("can't use a list in this context"))
131 raise error.ParseError(_("can't use a list in this context"),
132 hint=_('see hg help "filesets.x or y"'))
132
133
133 # symbols are callable like:
134 # symbols are callable like:
134 # fun(mctx, x)
135 # fun(mctx, x)
135 # with:
136 # with:
136 # mctx - current matchctx instance
137 # mctx - current matchctx instance
137 # x - argument in tree form
138 # x - argument in tree form
138 symbols = {}
139 symbols = {}
139
140
140 # filesets using matchctx.status()
141 # filesets using matchctx.status()
141 _statuscallers = set()
142 _statuscallers = set()
142
143
143 # filesets using matchctx.existing()
144 # filesets using matchctx.existing()
144 _existingcallers = set()
145 _existingcallers = set()
145
146
146 def predicate(decl, callstatus=False, callexisting=False):
147 def predicate(decl, callstatus=False, callexisting=False):
147 """Return a decorator for fileset predicate function
148 """Return a decorator for fileset predicate function
148
149
149 'decl' argument is the declaration (including argument list like
150 'decl' argument is the declaration (including argument list like
150 'adds(pattern)') or the name (for internal use only) of predicate.
151 'adds(pattern)') or the name (for internal use only) of predicate.
151
152
152 Optional 'callstatus' argument indicates whether predicate implies
153 Optional 'callstatus' argument indicates whether predicate implies
153 'matchctx.status()' at runtime or not (False, by default).
154 'matchctx.status()' at runtime or not (False, by default).
154
155
155 Optional 'callexisting' argument indicates whether predicate
156 Optional 'callexisting' argument indicates whether predicate
156 implies 'matchctx.existing()' at runtime or not (False, by
157 implies 'matchctx.existing()' at runtime or not (False, by
157 default).
158 default).
158 """
159 """
159 def decorator(func):
160 def decorator(func):
160 i = decl.find('(')
161 i = decl.find('(')
161 if i > 0:
162 if i > 0:
162 name = decl[:i]
163 name = decl[:i]
163 else:
164 else:
164 name = decl
165 name = decl
165 symbols[name] = func
166 symbols[name] = func
166 if callstatus:
167 if callstatus:
167 _statuscallers.add(name)
168 _statuscallers.add(name)
168 if callexisting:
169 if callexisting:
169 _existingcallers.add(name)
170 _existingcallers.add(name)
170 if func.__doc__:
171 if func.__doc__:
171 func.__doc__ = "``%s``\n %s" % (decl, func.__doc__.strip())
172 func.__doc__ = "``%s``\n %s" % (decl, func.__doc__.strip())
172 return func
173 return func
173 return decorator
174 return decorator
174
175
175 @predicate('modified()', callstatus=True)
176 @predicate('modified()', callstatus=True)
176 def modified(mctx, x):
177 def modified(mctx, x):
177 """File that is modified according to :hg:`status`.
178 """File that is modified according to :hg:`status`.
178 """
179 """
179 # i18n: "modified" is a keyword
180 # i18n: "modified" is a keyword
180 getargs(x, 0, 0, _("modified takes no arguments"))
181 getargs(x, 0, 0, _("modified takes no arguments"))
181 s = mctx.status().modified
182 s = mctx.status().modified
182 return [f for f in mctx.subset if f in s]
183 return [f for f in mctx.subset if f in s]
183
184
184 @predicate('added()', callstatus=True)
185 @predicate('added()', callstatus=True)
185 def added(mctx, x):
186 def added(mctx, x):
186 """File that is added according to :hg:`status`.
187 """File that is added according to :hg:`status`.
187 """
188 """
188 # i18n: "added" is a keyword
189 # i18n: "added" is a keyword
189 getargs(x, 0, 0, _("added takes no arguments"))
190 getargs(x, 0, 0, _("added takes no arguments"))
190 s = mctx.status().added
191 s = mctx.status().added
191 return [f for f in mctx.subset if f in s]
192 return [f for f in mctx.subset if f in s]
192
193
193 @predicate('removed()', callstatus=True)
194 @predicate('removed()', callstatus=True)
194 def removed(mctx, x):
195 def removed(mctx, x):
195 """File that is removed according to :hg:`status`.
196 """File that is removed according to :hg:`status`.
196 """
197 """
197 # i18n: "removed" is a keyword
198 # i18n: "removed" is a keyword
198 getargs(x, 0, 0, _("removed takes no arguments"))
199 getargs(x, 0, 0, _("removed takes no arguments"))
199 s = mctx.status().removed
200 s = mctx.status().removed
200 return [f for f in mctx.subset if f in s]
201 return [f for f in mctx.subset if f in s]
201
202
202 @predicate('deleted()', callstatus=True)
203 @predicate('deleted()', callstatus=True)
203 def deleted(mctx, x):
204 def deleted(mctx, x):
204 """Alias for ``missing()``.
205 """Alias for ``missing()``.
205 """
206 """
206 # i18n: "deleted" is a keyword
207 # i18n: "deleted" is a keyword
207 getargs(x, 0, 0, _("deleted takes no arguments"))
208 getargs(x, 0, 0, _("deleted takes no arguments"))
208 s = mctx.status().deleted
209 s = mctx.status().deleted
209 return [f for f in mctx.subset if f in s]
210 return [f for f in mctx.subset if f in s]
210
211
211 @predicate('missing()', callstatus=True)
212 @predicate('missing()', callstatus=True)
212 def missing(mctx, x):
213 def missing(mctx, x):
213 """File that is missing according to :hg:`status`.
214 """File that is missing according to :hg:`status`.
214 """
215 """
215 # i18n: "missing" is a keyword
216 # i18n: "missing" is a keyword
216 getargs(x, 0, 0, _("missing takes no arguments"))
217 getargs(x, 0, 0, _("missing takes no arguments"))
217 s = mctx.status().deleted
218 s = mctx.status().deleted
218 return [f for f in mctx.subset if f in s]
219 return [f for f in mctx.subset if f in s]
219
220
220 @predicate('unknown()', callstatus=True)
221 @predicate('unknown()', callstatus=True)
221 def unknown(mctx, x):
222 def unknown(mctx, x):
222 """File that is unknown according to :hg:`status`. These files will only be
223 """File that is unknown according to :hg:`status`. These files will only be
223 considered if this predicate is used.
224 considered if this predicate is used.
224 """
225 """
225 # i18n: "unknown" is a keyword
226 # i18n: "unknown" is a keyword
226 getargs(x, 0, 0, _("unknown takes no arguments"))
227 getargs(x, 0, 0, _("unknown takes no arguments"))
227 s = mctx.status().unknown
228 s = mctx.status().unknown
228 return [f for f in mctx.subset if f in s]
229 return [f for f in mctx.subset if f in s]
229
230
230 @predicate('ignored()', callstatus=True)
231 @predicate('ignored()', callstatus=True)
231 def ignored(mctx, x):
232 def ignored(mctx, x):
232 """File that is ignored according to :hg:`status`. These files will only be
233 """File that is ignored according to :hg:`status`. These files will only be
233 considered if this predicate is used.
234 considered if this predicate is used.
234 """
235 """
235 # i18n: "ignored" is a keyword
236 # i18n: "ignored" is a keyword
236 getargs(x, 0, 0, _("ignored takes no arguments"))
237 getargs(x, 0, 0, _("ignored takes no arguments"))
237 s = mctx.status().ignored
238 s = mctx.status().ignored
238 return [f for f in mctx.subset if f in s]
239 return [f for f in mctx.subset if f in s]
239
240
240 @predicate('clean()', callstatus=True)
241 @predicate('clean()', callstatus=True)
241 def clean(mctx, x):
242 def clean(mctx, x):
242 """File that is clean according to :hg:`status`.
243 """File that is clean according to :hg:`status`.
243 """
244 """
244 # i18n: "clean" is a keyword
245 # i18n: "clean" is a keyword
245 getargs(x, 0, 0, _("clean takes no arguments"))
246 getargs(x, 0, 0, _("clean takes no arguments"))
246 s = mctx.status().clean
247 s = mctx.status().clean
247 return [f for f in mctx.subset if f in s]
248 return [f for f in mctx.subset if f in s]
248
249
249 def func(mctx, a, b):
250 def func(mctx, a, b):
250 if a[0] == 'symbol' and a[1] in symbols:
251 if a[0] == 'symbol' and a[1] in symbols:
251 funcname = a[1]
252 funcname = a[1]
252 enabled = mctx._existingenabled
253 enabled = mctx._existingenabled
253 mctx._existingenabled = funcname in _existingcallers
254 mctx._existingenabled = funcname in _existingcallers
254 try:
255 try:
255 return symbols[funcname](mctx, b)
256 return symbols[funcname](mctx, b)
256 finally:
257 finally:
257 mctx._existingenabled = enabled
258 mctx._existingenabled = enabled
258
259
259 keep = lambda fn: getattr(fn, '__doc__', None) is not None
260 keep = lambda fn: getattr(fn, '__doc__', None) is not None
260
261
261 syms = [s for (s, fn) in symbols.items() if keep(fn)]
262 syms = [s for (s, fn) in symbols.items() if keep(fn)]
262 raise error.UnknownIdentifier(a[1], syms)
263 raise error.UnknownIdentifier(a[1], syms)
263
264
264 def getlist(x):
265 def getlist(x):
265 if not x:
266 if not x:
266 return []
267 return []
267 if x[0] == 'list':
268 if x[0] == 'list':
268 return getlist(x[1]) + [x[2]]
269 return getlist(x[1]) + [x[2]]
269 return [x]
270 return [x]
270
271
271 def getargs(x, min, max, err):
272 def getargs(x, min, max, err):
272 l = getlist(x)
273 l = getlist(x)
273 if len(l) < min or len(l) > max:
274 if len(l) < min or len(l) > max:
274 raise error.ParseError(err)
275 raise error.ParseError(err)
275 return l
276 return l
276
277
277 @predicate('binary()', callexisting=True)
278 @predicate('binary()', callexisting=True)
278 def binary(mctx, x):
279 def binary(mctx, x):
279 """File that appears to be binary (contains NUL bytes).
280 """File that appears to be binary (contains NUL bytes).
280 """
281 """
281 # i18n: "binary" is a keyword
282 # i18n: "binary" is a keyword
282 getargs(x, 0, 0, _("binary takes no arguments"))
283 getargs(x, 0, 0, _("binary takes no arguments"))
283 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
284 return [f for f in mctx.existing() if util.binary(mctx.ctx[f].data())]
284
285
285 @predicate('exec()', callexisting=True)
286 @predicate('exec()', callexisting=True)
286 def exec_(mctx, x):
287 def exec_(mctx, x):
287 """File that is marked as executable.
288 """File that is marked as executable.
288 """
289 """
289 # i18n: "exec" is a keyword
290 # i18n: "exec" is a keyword
290 getargs(x, 0, 0, _("exec takes no arguments"))
291 getargs(x, 0, 0, _("exec takes no arguments"))
291 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
292 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
292
293
293 @predicate('symlink()', callexisting=True)
294 @predicate('symlink()', callexisting=True)
294 def symlink(mctx, x):
295 def symlink(mctx, x):
295 """File that is marked as a symlink.
296 """File that is marked as a symlink.
296 """
297 """
297 # i18n: "symlink" is a keyword
298 # i18n: "symlink" is a keyword
298 getargs(x, 0, 0, _("symlink takes no arguments"))
299 getargs(x, 0, 0, _("symlink takes no arguments"))
299 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
300 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
300
301
301 @predicate('resolved()')
302 @predicate('resolved()')
302 def resolved(mctx, x):
303 def resolved(mctx, x):
303 """File that is marked resolved according to :hg:`resolve -l`.
304 """File that is marked resolved according to :hg:`resolve -l`.
304 """
305 """
305 # i18n: "resolved" is a keyword
306 # i18n: "resolved" is a keyword
306 getargs(x, 0, 0, _("resolved takes no arguments"))
307 getargs(x, 0, 0, _("resolved takes no arguments"))
307 if mctx.ctx.rev() is not None:
308 if mctx.ctx.rev() is not None:
308 return []
309 return []
309 ms = merge.mergestate.read(mctx.ctx.repo())
310 ms = merge.mergestate.read(mctx.ctx.repo())
310 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
311 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
311
312
312 @predicate('unresolved()')
313 @predicate('unresolved()')
313 def unresolved(mctx, x):
314 def unresolved(mctx, x):
314 """File that is marked unresolved according to :hg:`resolve -l`.
315 """File that is marked unresolved according to :hg:`resolve -l`.
315 """
316 """
316 # i18n: "unresolved" is a keyword
317 # i18n: "unresolved" is a keyword
317 getargs(x, 0, 0, _("unresolved takes no arguments"))
318 getargs(x, 0, 0, _("unresolved takes no arguments"))
318 if mctx.ctx.rev() is not None:
319 if mctx.ctx.rev() is not None:
319 return []
320 return []
320 ms = merge.mergestate.read(mctx.ctx.repo())
321 ms = merge.mergestate.read(mctx.ctx.repo())
321 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
322 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
322
323
323 @predicate('hgignore()')
324 @predicate('hgignore()')
324 def hgignore(mctx, x):
325 def hgignore(mctx, x):
325 """File that matches the active .hgignore pattern.
326 """File that matches the active .hgignore pattern.
326 """
327 """
327 # i18n: "hgignore" is a keyword
328 # i18n: "hgignore" is a keyword
328 getargs(x, 0, 0, _("hgignore takes no arguments"))
329 getargs(x, 0, 0, _("hgignore takes no arguments"))
329 ignore = mctx.ctx.repo().dirstate._ignore
330 ignore = mctx.ctx.repo().dirstate._ignore
330 return [f for f in mctx.subset if ignore(f)]
331 return [f for f in mctx.subset if ignore(f)]
331
332
332 @predicate('portable()')
333 @predicate('portable()')
333 def portable(mctx, x):
334 def portable(mctx, x):
334 """File that has a portable name. (This doesn't include filenames with case
335 """File that has a portable name. (This doesn't include filenames with case
335 collisions.)
336 collisions.)
336 """
337 """
337 # i18n: "portable" is a keyword
338 # i18n: "portable" is a keyword
338 getargs(x, 0, 0, _("portable takes no arguments"))
339 getargs(x, 0, 0, _("portable takes no arguments"))
339 checkwinfilename = util.checkwinfilename
340 checkwinfilename = util.checkwinfilename
340 return [f for f in mctx.subset if checkwinfilename(f) is None]
341 return [f for f in mctx.subset if checkwinfilename(f) is None]
341
342
342 @predicate('grep(regex)', callexisting=True)
343 @predicate('grep(regex)', callexisting=True)
343 def grep(mctx, x):
344 def grep(mctx, x):
344 """File contains the given regular expression.
345 """File contains the given regular expression.
345 """
346 """
346 try:
347 try:
347 # i18n: "grep" is a keyword
348 # i18n: "grep" is a keyword
348 r = re.compile(getstring(x, _("grep requires a pattern")))
349 r = re.compile(getstring(x, _("grep requires a pattern")))
349 except re.error as e:
350 except re.error as e:
350 raise error.ParseError(_('invalid match pattern: %s') % e)
351 raise error.ParseError(_('invalid match pattern: %s') % e)
351 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
352 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
352
353
353 def _sizetomax(s):
354 def _sizetomax(s):
354 try:
355 try:
355 s = s.strip().lower()
356 s = s.strip().lower()
356 for k, v in util._sizeunits:
357 for k, v in util._sizeunits:
357 if s.endswith(k):
358 if s.endswith(k):
358 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
359 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
359 n = s[:-len(k)]
360 n = s[:-len(k)]
360 inc = 1.0
361 inc = 1.0
361 if "." in n:
362 if "." in n:
362 inc /= 10 ** len(n.split(".")[1])
363 inc /= 10 ** len(n.split(".")[1])
363 return int((float(n) + inc) * v) - 1
364 return int((float(n) + inc) * v) - 1
364 # no extension, this is a precise value
365 # no extension, this is a precise value
365 return int(s)
366 return int(s)
366 except ValueError:
367 except ValueError:
367 raise error.ParseError(_("couldn't parse size: %s") % s)
368 raise error.ParseError(_("couldn't parse size: %s") % s)
368
369
369 @predicate('size(expression)', callexisting=True)
370 @predicate('size(expression)', callexisting=True)
370 def size(mctx, x):
371 def size(mctx, x):
371 """File size matches the given expression. Examples:
372 """File size matches the given expression. Examples:
372
373
373 - 1k (files from 1024 to 2047 bytes)
374 - 1k (files from 1024 to 2047 bytes)
374 - < 20k (files less than 20480 bytes)
375 - < 20k (files less than 20480 bytes)
375 - >= .5MB (files at least 524288 bytes)
376 - >= .5MB (files at least 524288 bytes)
376 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
377 - 4k - 1MB (files from 4096 bytes to 1048576 bytes)
377 """
378 """
378
379
379 # i18n: "size" is a keyword
380 # i18n: "size" is a keyword
380 expr = getstring(x, _("size requires an expression")).strip()
381 expr = getstring(x, _("size requires an expression")).strip()
381 if '-' in expr: # do we have a range?
382 if '-' in expr: # do we have a range?
382 a, b = expr.split('-', 1)
383 a, b = expr.split('-', 1)
383 a = util.sizetoint(a)
384 a = util.sizetoint(a)
384 b = util.sizetoint(b)
385 b = util.sizetoint(b)
385 m = lambda x: x >= a and x <= b
386 m = lambda x: x >= a and x <= b
386 elif expr.startswith("<="):
387 elif expr.startswith("<="):
387 a = util.sizetoint(expr[2:])
388 a = util.sizetoint(expr[2:])
388 m = lambda x: x <= a
389 m = lambda x: x <= a
389 elif expr.startswith("<"):
390 elif expr.startswith("<"):
390 a = util.sizetoint(expr[1:])
391 a = util.sizetoint(expr[1:])
391 m = lambda x: x < a
392 m = lambda x: x < a
392 elif expr.startswith(">="):
393 elif expr.startswith(">="):
393 a = util.sizetoint(expr[2:])
394 a = util.sizetoint(expr[2:])
394 m = lambda x: x >= a
395 m = lambda x: x >= a
395 elif expr.startswith(">"):
396 elif expr.startswith(">"):
396 a = util.sizetoint(expr[1:])
397 a = util.sizetoint(expr[1:])
397 m = lambda x: x > a
398 m = lambda x: x > a
398 elif expr[0].isdigit or expr[0] == '.':
399 elif expr[0].isdigit or expr[0] == '.':
399 a = util.sizetoint(expr)
400 a = util.sizetoint(expr)
400 b = _sizetomax(expr)
401 b = _sizetomax(expr)
401 m = lambda x: x >= a and x <= b
402 m = lambda x: x >= a and x <= b
402 else:
403 else:
403 raise error.ParseError(_("couldn't parse size: %s") % expr)
404 raise error.ParseError(_("couldn't parse size: %s") % expr)
404
405
405 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
406 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
406
407
407 @predicate('encoding(name)', callexisting=True)
408 @predicate('encoding(name)', callexisting=True)
408 def encoding(mctx, x):
409 def encoding(mctx, x):
409 """File can be successfully decoded with the given character
410 """File can be successfully decoded with the given character
410 encoding. May not be useful for encodings other than ASCII and
411 encoding. May not be useful for encodings other than ASCII and
411 UTF-8.
412 UTF-8.
412 """
413 """
413
414
414 # i18n: "encoding" is a keyword
415 # i18n: "encoding" is a keyword
415 enc = getstring(x, _("encoding requires an encoding name"))
416 enc = getstring(x, _("encoding requires an encoding name"))
416
417
417 s = []
418 s = []
418 for f in mctx.existing():
419 for f in mctx.existing():
419 d = mctx.ctx[f].data()
420 d = mctx.ctx[f].data()
420 try:
421 try:
421 d.decode(enc)
422 d.decode(enc)
422 except LookupError:
423 except LookupError:
423 raise error.Abort(_("unknown encoding '%s'") % enc)
424 raise error.Abort(_("unknown encoding '%s'") % enc)
424 except UnicodeDecodeError:
425 except UnicodeDecodeError:
425 continue
426 continue
426 s.append(f)
427 s.append(f)
427
428
428 return s
429 return s
429
430
430 @predicate('eol(style)', callexisting=True)
431 @predicate('eol(style)', callexisting=True)
431 def eol(mctx, x):
432 def eol(mctx, x):
432 """File contains newlines of the given style (dos, unix, mac). Binary
433 """File contains newlines of the given style (dos, unix, mac). Binary
433 files are excluded, files with mixed line endings match multiple
434 files are excluded, files with mixed line endings match multiple
434 styles.
435 styles.
435 """
436 """
436
437
437 # i18n: "encoding" is a keyword
438 # i18n: "encoding" is a keyword
438 enc = getstring(x, _("encoding requires an encoding name"))
439 enc = getstring(x, _("encoding requires an encoding name"))
439
440
440 s = []
441 s = []
441 for f in mctx.existing():
442 for f in mctx.existing():
442 d = mctx.ctx[f].data()
443 d = mctx.ctx[f].data()
443 if util.binary(d):
444 if util.binary(d):
444 continue
445 continue
445 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
446 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
446 s.append(f)
447 s.append(f)
447 elif enc == 'unix' and re.search('(?<!\r)\n', d):
448 elif enc == 'unix' and re.search('(?<!\r)\n', d):
448 s.append(f)
449 s.append(f)
449 elif enc == 'mac' and re.search('\r(?!\n)', d):
450 elif enc == 'mac' and re.search('\r(?!\n)', d):
450 s.append(f)
451 s.append(f)
451 return s
452 return s
452
453
453 @predicate('copied()')
454 @predicate('copied()')
454 def copied(mctx, x):
455 def copied(mctx, x):
455 """File that is recorded as being copied.
456 """File that is recorded as being copied.
456 """
457 """
457 # i18n: "copied" is a keyword
458 # i18n: "copied" is a keyword
458 getargs(x, 0, 0, _("copied takes no arguments"))
459 getargs(x, 0, 0, _("copied takes no arguments"))
459 s = []
460 s = []
460 for f in mctx.subset:
461 for f in mctx.subset:
461 p = mctx.ctx[f].parents()
462 p = mctx.ctx[f].parents()
462 if p and p[0].path() != f:
463 if p and p[0].path() != f:
463 s.append(f)
464 s.append(f)
464 return s
465 return s
465
466
466 @predicate('subrepo([pattern])')
467 @predicate('subrepo([pattern])')
467 def subrepo(mctx, x):
468 def subrepo(mctx, x):
468 """Subrepositories whose paths match the given pattern.
469 """Subrepositories whose paths match the given pattern.
469 """
470 """
470 # i18n: "subrepo" is a keyword
471 # i18n: "subrepo" is a keyword
471 getargs(x, 0, 1, _("subrepo takes at most one argument"))
472 getargs(x, 0, 1, _("subrepo takes at most one argument"))
472 ctx = mctx.ctx
473 ctx = mctx.ctx
473 sstate = sorted(ctx.substate)
474 sstate = sorted(ctx.substate)
474 if x:
475 if x:
475 # i18n: "subrepo" is a keyword
476 # i18n: "subrepo" is a keyword
476 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
477 pat = getstring(x, _("subrepo requires a pattern or no arguments"))
477
478
478 from . import match as matchmod # avoid circular import issues
479 from . import match as matchmod # avoid circular import issues
479 fast = not matchmod.patkind(pat)
480 fast = not matchmod.patkind(pat)
480 if fast:
481 if fast:
481 def m(s):
482 def m(s):
482 return (s == pat)
483 return (s == pat)
483 else:
484 else:
484 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
485 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
485 return [sub for sub in sstate if m(sub)]
486 return [sub for sub in sstate if m(sub)]
486 else:
487 else:
487 return [sub for sub in sstate]
488 return [sub for sub in sstate]
488
489
489 methods = {
490 methods = {
490 'string': stringset,
491 'string': stringset,
491 'symbol': stringset,
492 'symbol': stringset,
492 'and': andset,
493 'and': andset,
493 'or': orset,
494 'or': orset,
494 'minus': minusset,
495 'minus': minusset,
495 'list': listset,
496 'list': listset,
496 'group': getset,
497 'group': getset,
497 'not': notset,
498 'not': notset,
498 'func': func,
499 'func': func,
499 }
500 }
500
501
501 class matchctx(object):
502 class matchctx(object):
502 def __init__(self, ctx, subset=None, status=None):
503 def __init__(self, ctx, subset=None, status=None):
503 self.ctx = ctx
504 self.ctx = ctx
504 self.subset = subset
505 self.subset = subset
505 self._status = status
506 self._status = status
506 self._existingenabled = False
507 self._existingenabled = False
507 def status(self):
508 def status(self):
508 return self._status
509 return self._status
509 def matcher(self, patterns):
510 def matcher(self, patterns):
510 return self.ctx.match(patterns)
511 return self.ctx.match(patterns)
511 def filter(self, files):
512 def filter(self, files):
512 return [f for f in files if f in self.subset]
513 return [f for f in files if f in self.subset]
513 def existing(self):
514 def existing(self):
514 assert self._existingenabled, 'unexpected existing() invocation'
515 assert self._existingenabled, 'unexpected existing() invocation'
515 if self._status is not None:
516 if self._status is not None:
516 removed = set(self._status[3])
517 removed = set(self._status[3])
517 unknown = set(self._status[4] + self._status[5])
518 unknown = set(self._status[4] + self._status[5])
518 else:
519 else:
519 removed = set()
520 removed = set()
520 unknown = set()
521 unknown = set()
521 return (f for f in self.subset
522 return (f for f in self.subset
522 if (f in self.ctx and f not in removed) or f in unknown)
523 if (f in self.ctx and f not in removed) or f in unknown)
523 def narrow(self, files):
524 def narrow(self, files):
524 return matchctx(self.ctx, self.filter(files), self._status)
525 return matchctx(self.ctx, self.filter(files), self._status)
525
526
526 def _intree(funcs, tree):
527 def _intree(funcs, tree):
527 if isinstance(tree, tuple):
528 if isinstance(tree, tuple):
528 if tree[0] == 'func' and tree[1][0] == 'symbol':
529 if tree[0] == 'func' and tree[1][0] == 'symbol':
529 if tree[1][1] in funcs:
530 if tree[1][1] in funcs:
530 return True
531 return True
531 for s in tree[1:]:
532 for s in tree[1:]:
532 if _intree(funcs, s):
533 if _intree(funcs, s):
533 return True
534 return True
534 return False
535 return False
535
536
536 def getfileset(ctx, expr):
537 def getfileset(ctx, expr):
537 tree = parse(expr)
538 tree = parse(expr)
538
539
539 # do we need status info?
540 # do we need status info?
540 if (_intree(_statuscallers, tree) or
541 if (_intree(_statuscallers, tree) or
541 # Using matchctx.existing() on a workingctx requires us to check
542 # Using matchctx.existing() on a workingctx requires us to check
542 # for deleted files.
543 # for deleted files.
543 (ctx.rev() is None and _intree(_existingcallers, tree))):
544 (ctx.rev() is None and _intree(_existingcallers, tree))):
544 unknown = _intree(['unknown'], tree)
545 unknown = _intree(['unknown'], tree)
545 ignored = _intree(['ignored'], tree)
546 ignored = _intree(['ignored'], tree)
546
547
547 r = ctx.repo()
548 r = ctx.repo()
548 status = r.status(ctx.p1(), ctx,
549 status = r.status(ctx.p1(), ctx,
549 unknown=unknown, ignored=ignored, clean=True)
550 unknown=unknown, ignored=ignored, clean=True)
550 subset = []
551 subset = []
551 for c in status:
552 for c in status:
552 subset.extend(c)
553 subset.extend(c)
553 else:
554 else:
554 status = None
555 status = None
555 subset = list(ctx.walk(ctx.match([])))
556 subset = list(ctx.walk(ctx.match([])))
556
557
557 return getset(matchctx(ctx, subset, status), tree)
558 return getset(matchctx(ctx, subset, status), tree)
558
559
559 def prettyformat(tree):
560 def prettyformat(tree):
560 return parser.prettyformat(tree, ('string', 'symbol'))
561 return parser.prettyformat(tree, ('string', 'symbol'))
561
562
562 # tell hggettext to extract docstrings from these functions:
563 # tell hggettext to extract docstrings from these functions:
563 i18nfunctions = symbols.values()
564 i18nfunctions = symbols.values()
@@ -1,364 +1,368
1 $ fileset() {
1 $ fileset() {
2 > hg debugfileset "$@"
2 > hg debugfileset "$@"
3 > }
3 > }
4
4
5 $ hg init repo
5 $ hg init repo
6 $ cd repo
6 $ cd repo
7 $ echo a > a1
7 $ echo a > a1
8 $ echo a > a2
8 $ echo a > a2
9 $ echo b > b1
9 $ echo b > b1
10 $ 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 a1
21 a1
22 $ fileset -v 'a*'
22 $ fileset -v 'a*'
23 ('symbol', 'a*')
23 ('symbol', 'a*')
24 a1
24 a1
25 a2
25 a2
26 $ fileset -v '"re:a\d"'
26 $ fileset -v '"re:a\d"'
27 ('string', 're:a\\d')
27 ('string', 're:a\\d')
28 a1
28 a1
29 a2
29 a2
30 $ fileset -v 'a1 or a2'
30 $ fileset -v 'a1 or a2'
31 (or
31 (or
32 ('symbol', 'a1')
32 ('symbol', 'a1')
33 ('symbol', 'a2'))
33 ('symbol', 'a2'))
34 a1
34 a1
35 a2
35 a2
36 $ fileset 'a1 | a2'
36 $ fileset 'a1 | a2'
37 a1
37 a1
38 a2
38 a2
39 $ fileset 'a* and "*1"'
39 $ fileset 'a* and "*1"'
40 a1
40 a1
41 $ fileset 'a* & "*1"'
41 $ fileset 'a* & "*1"'
42 a1
42 a1
43 $ fileset 'not (r"a*")'
43 $ fileset 'not (r"a*")'
44 b1
44 b1
45 b2
45 b2
46 $ fileset '! ("a*")'
46 $ fileset '! ("a*")'
47 b1
47 b1
48 b2
48 b2
49 $ fileset 'a* - a1'
49 $ fileset 'a* - a1'
50 a2
50 a2
51 $ fileset 'a_b'
51 $ fileset 'a_b'
52 $ fileset '"\xy"'
52 $ fileset '"\xy"'
53 hg: parse error: invalid \x escape
53 hg: parse error: invalid \x escape
54 [255]
54 [255]
55
55
56 Test files status
56 Test files status
57
57
58 $ rm a1
58 $ rm a1
59 $ hg rm a2
59 $ hg rm a2
60 $ echo b >> b2
60 $ echo b >> b2
61 $ hg cp b1 c1
61 $ hg cp b1 c1
62 $ echo c > c2
62 $ echo c > c2
63 $ echo c > c3
63 $ echo c > c3
64 $ cat > .hgignore <<EOF
64 $ cat > .hgignore <<EOF
65 > \.hgignore
65 > \.hgignore
66 > 2$
66 > 2$
67 > EOF
67 > EOF
68 $ fileset 'modified()'
68 $ fileset 'modified()'
69 b2
69 b2
70 $ fileset 'added()'
70 $ fileset 'added()'
71 c1
71 c1
72 $ fileset 'removed()'
72 $ fileset 'removed()'
73 a2
73 a2
74 $ fileset 'deleted()'
74 $ fileset 'deleted()'
75 a1
75 a1
76 $ fileset 'missing()'
76 $ fileset 'missing()'
77 a1
77 a1
78 $ fileset 'unknown()'
78 $ fileset 'unknown()'
79 c3
79 c3
80 $ fileset 'ignored()'
80 $ fileset 'ignored()'
81 .hgignore
81 .hgignore
82 c2
82 c2
83 $ fileset 'hgignore()'
83 $ fileset 'hgignore()'
84 a2
84 a2
85 b2
85 b2
86 $ fileset 'clean()'
86 $ fileset 'clean()'
87 b1
87 b1
88 $ fileset 'copied()'
88 $ fileset 'copied()'
89 c1
89 c1
90
90
91 Test files properties
91 Test files properties
92
92
93 >>> file('bin', 'wb').write('\0a')
93 >>> file('bin', 'wb').write('\0a')
94 $ fileset 'binary()'
94 $ fileset 'binary()'
95 $ fileset 'binary() and unknown()'
95 $ fileset 'binary() and unknown()'
96 bin
96 bin
97 $ echo '^bin$' >> .hgignore
97 $ echo '^bin$' >> .hgignore
98 $ fileset 'binary() and ignored()'
98 $ fileset 'binary() and ignored()'
99 bin
99 bin
100 $ hg add bin
100 $ hg add bin
101 $ fileset 'binary()'
101 $ fileset 'binary()'
102 bin
102 bin
103
103
104 $ fileset 'grep("b{1}")'
104 $ fileset 'grep("b{1}")'
105 b2
105 b2
106 c1
106 c1
107 b1
107 b1
108 $ fileset 'grep("missingparens(")'
108 $ fileset 'grep("missingparens(")'
109 hg: parse error: invalid match pattern: unbalanced parenthesis
109 hg: parse error: invalid match pattern: unbalanced parenthesis
110 [255]
110 [255]
111
111
112 #if execbit
112 #if execbit
113 $ chmod +x b2
113 $ chmod +x b2
114 $ fileset 'exec()'
114 $ fileset 'exec()'
115 b2
115 b2
116 #endif
116 #endif
117
117
118 #if symlink
118 #if symlink
119 $ ln -s b2 b2link
119 $ ln -s b2 b2link
120 $ fileset 'symlink() and unknown()'
120 $ fileset 'symlink() and unknown()'
121 b2link
121 b2link
122 $ hg add b2link
122 $ hg add b2link
123 #endif
123 #endif
124
124
125 #if no-windows
125 #if no-windows
126 $ echo foo > con.xml
126 $ echo foo > con.xml
127 $ fileset 'not portable()'
127 $ fileset 'not portable()'
128 con.xml
128 con.xml
129 $ hg --config ui.portablefilenames=ignore add con.xml
129 $ hg --config ui.portablefilenames=ignore add con.xml
130 #endif
130 #endif
131
131
132 >>> file('1k', 'wb').write(' '*1024)
132 >>> file('1k', 'wb').write(' '*1024)
133 >>> file('2k', 'wb').write(' '*2048)
133 >>> file('2k', 'wb').write(' '*2048)
134 $ hg add 1k 2k
134 $ hg add 1k 2k
135 $ fileset 'size("bar")'
135 $ fileset 'size("bar")'
136 hg: parse error: couldn't parse size: bar
136 hg: parse error: couldn't parse size: bar
137 [255]
137 [255]
138 $ fileset '(1k, 2k)'
139 hg: parse error: can't use a list in this context
140 (see hg help "filesets.x or y")
141 [255]
138 $ fileset 'size(1k)'
142 $ fileset 'size(1k)'
139 1k
143 1k
140 $ fileset '(1k or 2k) and size("< 2k")'
144 $ fileset '(1k or 2k) and size("< 2k")'
141 1k
145 1k
142 $ fileset '(1k or 2k) and size("<=2k")'
146 $ fileset '(1k or 2k) and size("<=2k")'
143 1k
147 1k
144 2k
148 2k
145 $ fileset '(1k or 2k) and size("> 1k")'
149 $ fileset '(1k or 2k) and size("> 1k")'
146 2k
150 2k
147 $ fileset '(1k or 2k) and size(">=1K")'
151 $ fileset '(1k or 2k) and size(">=1K")'
148 1k
152 1k
149 2k
153 2k
150 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
154 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
151 1k
155 1k
152 $ fileset 'size("1M")'
156 $ fileset 'size("1M")'
153 $ fileset 'size("1 GB")'
157 $ fileset 'size("1 GB")'
154
158
155 Test merge states
159 Test merge states
156
160
157 $ hg ci -m manychanges
161 $ hg ci -m manychanges
158 $ hg up -C 0
162 $ hg up -C 0
159 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
163 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
160 $ echo c >> b2
164 $ echo c >> b2
161 $ hg ci -m diverging b2
165 $ hg ci -m diverging b2
162 created new head
166 created new head
163 $ fileset 'resolved()'
167 $ fileset 'resolved()'
164 $ fileset 'unresolved()'
168 $ fileset 'unresolved()'
165 $ hg merge
169 $ hg merge
166 merging b2
170 merging b2
167 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
171 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
168 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
172 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
169 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
173 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
170 [1]
174 [1]
171 $ fileset 'resolved()'
175 $ fileset 'resolved()'
172 $ fileset 'unresolved()'
176 $ fileset 'unresolved()'
173 b2
177 b2
174 $ echo e > b2
178 $ echo e > b2
175 $ hg resolve -m b2
179 $ hg resolve -m b2
176 (no more unresolved files)
180 (no more unresolved files)
177 $ fileset 'resolved()'
181 $ fileset 'resolved()'
178 b2
182 b2
179 $ fileset 'unresolved()'
183 $ fileset 'unresolved()'
180 $ hg ci -m merge
184 $ hg ci -m merge
181
185
182 Test subrepo predicate
186 Test subrepo predicate
183
187
184 $ hg init sub
188 $ hg init sub
185 $ echo a > sub/suba
189 $ echo a > sub/suba
186 $ hg -R sub add sub/suba
190 $ hg -R sub add sub/suba
187 $ hg -R sub ci -m sub
191 $ hg -R sub ci -m sub
188 $ echo 'sub = sub' > .hgsub
192 $ echo 'sub = sub' > .hgsub
189 $ hg init sub2
193 $ hg init sub2
190 $ echo b > sub2/b
194 $ echo b > sub2/b
191 $ hg -R sub2 ci -Am sub2
195 $ hg -R sub2 ci -Am sub2
192 adding b
196 adding b
193 $ echo 'sub2 = sub2' >> .hgsub
197 $ echo 'sub2 = sub2' >> .hgsub
194 $ fileset 'subrepo()'
198 $ fileset 'subrepo()'
195 $ hg add .hgsub
199 $ hg add .hgsub
196 $ fileset 'subrepo()'
200 $ fileset 'subrepo()'
197 sub
201 sub
198 sub2
202 sub2
199 $ fileset 'subrepo("sub")'
203 $ fileset 'subrepo("sub")'
200 sub
204 sub
201 $ fileset 'subrepo("glob:*")'
205 $ fileset 'subrepo("glob:*")'
202 sub
206 sub
203 sub2
207 sub2
204 $ hg ci -m subrepo
208 $ hg ci -m subrepo
205
209
206 Test that .hgsubstate is updated as appropriate during a conversion. The
210 Test that .hgsubstate is updated as appropriate during a conversion. The
207 saverev property is enough to alter the hashes of the subrepo.
211 saverev property is enough to alter the hashes of the subrepo.
208
212
209 $ hg init ../converted
213 $ hg init ../converted
210 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
214 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
211 > sub ../converted/sub
215 > sub ../converted/sub
212 initializing destination ../converted/sub repository
216 initializing destination ../converted/sub repository
213 scanning source...
217 scanning source...
214 sorting...
218 sorting...
215 converting...
219 converting...
216 0 sub
220 0 sub
217 $ hg clone -U sub2 ../converted/sub2
221 $ hg clone -U sub2 ../converted/sub2
218 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
222 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
219 > . ../converted
223 > . ../converted
220 scanning source...
224 scanning source...
221 sorting...
225 sorting...
222 converting...
226 converting...
223 4 addfiles
227 4 addfiles
224 3 manychanges
228 3 manychanges
225 2 diverging
229 2 diverging
226 1 merge
230 1 merge
227 0 subrepo
231 0 subrepo
228 no ".hgsubstate" updates will be made for "sub2"
232 no ".hgsubstate" updates will be made for "sub2"
229 $ hg up -q -R ../converted -r tip
233 $ hg up -q -R ../converted -r tip
230 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
234 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
231 a
235 a
232 b
236 b
233 $ oldnode=`hg log -r tip -T "{node}\n"`
237 $ oldnode=`hg log -r tip -T "{node}\n"`
234 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
238 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
235 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
239 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
236
240
237 Test with a revision
241 Test with a revision
238
242
239 $ hg log -G --template '{rev} {desc}\n'
243 $ hg log -G --template '{rev} {desc}\n'
240 @ 4 subrepo
244 @ 4 subrepo
241 |
245 |
242 o 3 merge
246 o 3 merge
243 |\
247 |\
244 | o 2 diverging
248 | o 2 diverging
245 | |
249 | |
246 o | 1 manychanges
250 o | 1 manychanges
247 |/
251 |/
248 o 0 addfiles
252 o 0 addfiles
249
253
250 $ echo unknown > unknown
254 $ echo unknown > unknown
251 $ fileset -r1 'modified()'
255 $ fileset -r1 'modified()'
252 b2
256 b2
253 $ fileset -r1 'added() and c1'
257 $ fileset -r1 'added() and c1'
254 c1
258 c1
255 $ fileset -r1 'removed()'
259 $ fileset -r1 'removed()'
256 a2
260 a2
257 $ fileset -r1 'deleted()'
261 $ fileset -r1 'deleted()'
258 $ fileset -r1 'unknown()'
262 $ fileset -r1 'unknown()'
259 $ fileset -r1 'ignored()'
263 $ fileset -r1 'ignored()'
260 $ fileset -r1 'hgignore()'
264 $ fileset -r1 'hgignore()'
261 b2
265 b2
262 bin
266 bin
263 $ fileset -r1 'binary()'
267 $ fileset -r1 'binary()'
264 bin
268 bin
265 $ fileset -r1 'size(1k)'
269 $ fileset -r1 'size(1k)'
266 1k
270 1k
267 $ fileset -r3 'resolved()'
271 $ fileset -r3 'resolved()'
268 $ fileset -r3 'unresolved()'
272 $ fileset -r3 'unresolved()'
269
273
270 #if execbit
274 #if execbit
271 $ fileset -r1 'exec()'
275 $ fileset -r1 'exec()'
272 b2
276 b2
273 #endif
277 #endif
274
278
275 #if symlink
279 #if symlink
276 $ fileset -r1 'symlink()'
280 $ fileset -r1 'symlink()'
277 b2link
281 b2link
278 #endif
282 #endif
279
283
280 #if no-windows
284 #if no-windows
281 $ fileset -r1 'not portable()'
285 $ fileset -r1 'not portable()'
282 con.xml
286 con.xml
283 $ hg forget 'con.xml'
287 $ hg forget 'con.xml'
284 #endif
288 #endif
285
289
286 $ fileset -r4 'subrepo("re:su.*")'
290 $ fileset -r4 'subrepo("re:su.*")'
287 sub
291 sub
288 sub2
292 sub2
289 $ fileset -r4 'subrepo("sub")'
293 $ fileset -r4 'subrepo("sub")'
290 sub
294 sub
291 $ fileset -r4 'b2 or c1'
295 $ fileset -r4 'b2 or c1'
292 b2
296 b2
293 c1
297 c1
294
298
295 >>> open('dos', 'wb').write("dos\r\n")
299 >>> open('dos', 'wb').write("dos\r\n")
296 >>> open('mixed', 'wb').write("dos\r\nunix\n")
300 >>> open('mixed', 'wb').write("dos\r\nunix\n")
297 >>> open('mac', 'wb').write("mac\r")
301 >>> open('mac', 'wb').write("mac\r")
298 $ hg add dos mixed mac
302 $ hg add dos mixed mac
299
303
300 (remove a1, to examine safety of 'eol' on removed files)
304 (remove a1, to examine safety of 'eol' on removed files)
301 $ rm a1
305 $ rm a1
302
306
303 $ fileset 'eol(dos)'
307 $ fileset 'eol(dos)'
304 dos
308 dos
305 mixed
309 mixed
306 $ fileset 'eol(unix)'
310 $ fileset 'eol(unix)'
307 mixed
311 mixed
308 .hgsub
312 .hgsub
309 .hgsubstate
313 .hgsubstate
310 b1
314 b1
311 b2
315 b2
312 c1
316 c1
313 $ fileset 'eol(mac)'
317 $ fileset 'eol(mac)'
314 mac
318 mac
315
319
316 Test safety of 'encoding' on removed files
320 Test safety of 'encoding' on removed files
317
321
318 #if symlink
322 #if symlink
319 $ fileset 'encoding("ascii")'
323 $ fileset 'encoding("ascii")'
320 dos
324 dos
321 mac
325 mac
322 mixed
326 mixed
323 .hgsub
327 .hgsub
324 .hgsubstate
328 .hgsubstate
325 1k
329 1k
326 2k
330 2k
327 b1
331 b1
328 b2
332 b2
329 b2link
333 b2link
330 bin
334 bin
331 c1
335 c1
332 #else
336 #else
333 $ fileset 'encoding("ascii")'
337 $ fileset 'encoding("ascii")'
334 dos
338 dos
335 mac
339 mac
336 mixed
340 mixed
337 .hgsub
341 .hgsub
338 .hgsubstate
342 .hgsubstate
339 1k
343 1k
340 2k
344 2k
341 b1
345 b1
342 b2
346 b2
343 bin
347 bin
344 c1
348 c1
345 #endif
349 #endif
346
350
347 Test detection of unintentional 'matchctx.existing()' invocation
351 Test detection of unintentional 'matchctx.existing()' invocation
348
352
349 $ cat > $TESTTMP/existingcaller.py <<EOF
353 $ cat > $TESTTMP/existingcaller.py <<EOF
350 > from mercurial import fileset
354 > from mercurial import fileset
351 >
355 >
352 > @fileset.predicate('existingcaller()', callexisting=False)
356 > @fileset.predicate('existingcaller()', callexisting=False)
353 > def existingcaller(mctx, x):
357 > def existingcaller(mctx, x):
354 > # this 'mctx.existing()' invocation is unintentional
358 > # this 'mctx.existing()' invocation is unintentional
355 > return [f for f in mctx.existing()]
359 > return [f for f in mctx.existing()]
356 > EOF
360 > EOF
357
361
358 $ cat >> .hg/hgrc <<EOF
362 $ cat >> .hg/hgrc <<EOF
359 > [extensions]
363 > [extensions]
360 > existingcaller = $TESTTMP/existingcaller.py
364 > existingcaller = $TESTTMP/existingcaller.py
361 > EOF
365 > EOF
362
366
363 $ fileset 'existingcaller()' 2>&1 | tail -1
367 $ fileset 'existingcaller()' 2>&1 | tail -1
364 AssertionError: unexpected existing() invocation
368 AssertionError: unexpected existing() invocation
General Comments 0
You need to be logged in to leave comments. Login now