##// END OF EJS Templates
fileset: add "tracked()" to explicitly select files in the revision...
Yuya Nishihara -
r38708:131aae58 default
parent child Browse files
Show More
@@ -1,721 +1,728 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 match as matchmod,
16 match as matchmod,
17 merge,
17 merge,
18 parser,
18 parser,
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 elements = {
28 elements = {
29 # token-type: binding-strength, primary, prefix, infix, suffix
29 # token-type: binding-strength, primary, prefix, infix, suffix
30 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
30 "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
31 ":": (15, None, None, ("kindpat", 15), None),
31 ":": (15, None, None, ("kindpat", 15), None),
32 "-": (5, None, ("negate", 19), ("minus", 5), None),
32 "-": (5, None, ("negate", 19), ("minus", 5), None),
33 "not": (10, None, ("not", 10), None, None),
33 "not": (10, None, ("not", 10), None, None),
34 "!": (10, None, ("not", 10), None, None),
34 "!": (10, None, ("not", 10), None, None),
35 "and": (5, None, None, ("and", 5), None),
35 "and": (5, None, None, ("and", 5), None),
36 "&": (5, None, None, ("and", 5), None),
36 "&": (5, None, None, ("and", 5), None),
37 "or": (4, None, None, ("or", 4), None),
37 "or": (4, None, None, ("or", 4), None),
38 "|": (4, None, None, ("or", 4), None),
38 "|": (4, None, None, ("or", 4), None),
39 "+": (4, None, None, ("or", 4), None),
39 "+": (4, None, None, ("or", 4), None),
40 ",": (2, None, None, ("list", 2), None),
40 ",": (2, None, None, ("list", 2), None),
41 ")": (0, None, None, None, None),
41 ")": (0, None, None, None, None),
42 "symbol": (0, "symbol", None, None, None),
42 "symbol": (0, "symbol", None, None, None),
43 "string": (0, "string", None, None, None),
43 "string": (0, "string", None, None, None),
44 "end": (0, None, None, None, None),
44 "end": (0, None, None, None, None),
45 }
45 }
46
46
47 keywords = {'and', 'or', 'not'}
47 keywords = {'and', 'or', 'not'}
48
48
49 globchars = ".*{}[]?/\\_"
49 globchars = ".*{}[]?/\\_"
50
50
51 def tokenize(program):
51 def tokenize(program):
52 pos, l = 0, len(program)
52 pos, l = 0, len(program)
53 program = pycompat.bytestr(program)
53 program = pycompat.bytestr(program)
54 while pos < l:
54 while pos < l:
55 c = program[pos]
55 c = program[pos]
56 if c.isspace(): # skip inter-token whitespace
56 if c.isspace(): # skip inter-token whitespace
57 pass
57 pass
58 elif c in "(),-:|&+!": # handle simple operators
58 elif c in "(),-:|&+!": # handle simple operators
59 yield (c, None, pos)
59 yield (c, None, pos)
60 elif (c in '"\'' or c == 'r' and
60 elif (c in '"\'' or c == 'r' and
61 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
61 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
62 if c == 'r':
62 if c == 'r':
63 pos += 1
63 pos += 1
64 c = program[pos]
64 c = program[pos]
65 decode = lambda x: x
65 decode = lambda x: x
66 else:
66 else:
67 decode = parser.unescapestr
67 decode = parser.unescapestr
68 pos += 1
68 pos += 1
69 s = pos
69 s = pos
70 while pos < l: # find closing quote
70 while pos < l: # find closing quote
71 d = program[pos]
71 d = program[pos]
72 if d == '\\': # skip over escaped characters
72 if d == '\\': # skip over escaped characters
73 pos += 2
73 pos += 2
74 continue
74 continue
75 if d == c:
75 if d == c:
76 yield ('string', decode(program[s:pos]), s)
76 yield ('string', decode(program[s:pos]), s)
77 break
77 break
78 pos += 1
78 pos += 1
79 else:
79 else:
80 raise error.ParseError(_("unterminated string"), s)
80 raise error.ParseError(_("unterminated string"), s)
81 elif c.isalnum() or c in globchars or ord(c) > 127:
81 elif c.isalnum() or c in globchars or ord(c) > 127:
82 # gather up a symbol/keyword
82 # gather up a symbol/keyword
83 s = pos
83 s = pos
84 pos += 1
84 pos += 1
85 while pos < l: # find end of symbol
85 while pos < l: # find end of symbol
86 d = program[pos]
86 d = program[pos]
87 if not (d.isalnum() or d in globchars or ord(d) > 127):
87 if not (d.isalnum() or d in globchars or ord(d) > 127):
88 break
88 break
89 pos += 1
89 pos += 1
90 sym = program[s:pos]
90 sym = program[s:pos]
91 if sym in keywords: # operator keywords
91 if sym in keywords: # operator keywords
92 yield (sym, None, s)
92 yield (sym, None, s)
93 else:
93 else:
94 yield ('symbol', sym, s)
94 yield ('symbol', sym, s)
95 pos -= 1
95 pos -= 1
96 else:
96 else:
97 raise error.ParseError(_("syntax error"), pos)
97 raise error.ParseError(_("syntax error"), pos)
98 pos += 1
98 pos += 1
99 yield ('end', None, pos)
99 yield ('end', None, pos)
100
100
101 def parse(expr):
101 def parse(expr):
102 p = parser.parser(elements)
102 p = parser.parser(elements)
103 tree, pos = p.parse(tokenize(expr))
103 tree, pos = p.parse(tokenize(expr))
104 if pos != len(expr):
104 if pos != len(expr):
105 raise error.ParseError(_("invalid token"), pos)
105 raise error.ParseError(_("invalid token"), pos)
106 return tree
106 return tree
107
107
108 def getsymbol(x):
108 def getsymbol(x):
109 if x and x[0] == 'symbol':
109 if x and x[0] == 'symbol':
110 return x[1]
110 return x[1]
111 raise error.ParseError(_('not a symbol'))
111 raise error.ParseError(_('not a symbol'))
112
112
113 def getstring(x, err):
113 def getstring(x, err):
114 if x and (x[0] == 'string' or x[0] == 'symbol'):
114 if x and (x[0] == 'string' or x[0] == 'symbol'):
115 return x[1]
115 return x[1]
116 raise error.ParseError(err)
116 raise error.ParseError(err)
117
117
118 def _getkindpat(x, y, allkinds, err):
118 def _getkindpat(x, y, allkinds, err):
119 kind = getsymbol(x)
119 kind = getsymbol(x)
120 pat = getstring(y, err)
120 pat = getstring(y, err)
121 if kind not in allkinds:
121 if kind not in allkinds:
122 raise error.ParseError(_("invalid pattern kind: %s") % kind)
122 raise error.ParseError(_("invalid pattern kind: %s") % kind)
123 return '%s:%s' % (kind, pat)
123 return '%s:%s' % (kind, pat)
124
124
125 def getpattern(x, allkinds, err):
125 def getpattern(x, allkinds, err):
126 if x and x[0] == 'kindpat':
126 if x and x[0] == 'kindpat':
127 return _getkindpat(x[1], x[2], allkinds, err)
127 return _getkindpat(x[1], x[2], allkinds, err)
128 return getstring(x, err)
128 return getstring(x, err)
129
129
130 def getlist(x):
130 def getlist(x):
131 if not x:
131 if not x:
132 return []
132 return []
133 if x[0] == 'list':
133 if x[0] == 'list':
134 return getlist(x[1]) + [x[2]]
134 return getlist(x[1]) + [x[2]]
135 return [x]
135 return [x]
136
136
137 def getargs(x, min, max, err):
137 def getargs(x, min, max, err):
138 l = getlist(x)
138 l = getlist(x)
139 if len(l) < min or len(l) > max:
139 if len(l) < min or len(l) > max:
140 raise error.ParseError(err)
140 raise error.ParseError(err)
141 return l
141 return l
142
142
143 def getset(mctx, x):
143 def getset(mctx, x):
144 if not x:
144 if not x:
145 raise error.ParseError(_("missing argument"))
145 raise error.ParseError(_("missing argument"))
146 return methods[x[0]](mctx, *x[1:])
146 return methods[x[0]](mctx, *x[1:])
147
147
148 def stringset(mctx, x):
148 def stringset(mctx, x):
149 m = mctx.matcher([x])
149 m = mctx.matcher([x])
150 return [f for f in mctx.subset if m(f)]
150 return [f for f in mctx.subset if m(f)]
151
151
152 def kindpatset(mctx, x, y):
152 def kindpatset(mctx, x, y):
153 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
153 return stringset(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
154 _("pattern must be a string")))
154 _("pattern must be a string")))
155
155
156 def andset(mctx, x, y):
156 def andset(mctx, x, y):
157 xl = set(getset(mctx, x))
157 xl = set(getset(mctx, x))
158 yl = getset(mctx, y)
158 yl = getset(mctx, y)
159 return [f for f in yl if f in xl]
159 return [f for f in yl if f in xl]
160
160
161 def orset(mctx, x, y):
161 def orset(mctx, x, y):
162 # needs optimizing
162 # needs optimizing
163 xl = getset(mctx, x)
163 xl = getset(mctx, x)
164 yl = getset(mctx, y)
164 yl = getset(mctx, y)
165 return xl + [f for f in yl if f not in xl]
165 return xl + [f for f in yl if f not in xl]
166
166
167 def notset(mctx, x):
167 def notset(mctx, x):
168 s = set(getset(mctx, x))
168 s = set(getset(mctx, x))
169 return [r for r in mctx.subset if r not in s]
169 return [r for r in mctx.subset if r not in s]
170
170
171 def minusset(mctx, x, y):
171 def minusset(mctx, x, y):
172 xl = getset(mctx, x)
172 xl = getset(mctx, x)
173 yl = set(getset(mctx, y))
173 yl = set(getset(mctx, y))
174 return [f for f in xl if f not in yl]
174 return [f for f in xl if f not in yl]
175
175
176 def negateset(mctx, x):
176 def negateset(mctx, x):
177 raise error.ParseError(_("can't use negate operator in this context"))
177 raise error.ParseError(_("can't use negate operator in this context"))
178
178
179 def listset(mctx, a, b):
179 def listset(mctx, a, b):
180 raise error.ParseError(_("can't use a list in this context"),
180 raise error.ParseError(_("can't use a list in this context"),
181 hint=_('see hg help "filesets.x or y"'))
181 hint=_('see hg help "filesets.x or y"'))
182
182
183 def func(mctx, a, b):
183 def func(mctx, a, b):
184 funcname = getsymbol(a)
184 funcname = getsymbol(a)
185 if funcname in symbols:
185 if funcname in symbols:
186 enabled = mctx._existingenabled
186 enabled = mctx._existingenabled
187 mctx._existingenabled = funcname in _existingcallers
187 mctx._existingenabled = funcname in _existingcallers
188 try:
188 try:
189 return symbols[funcname](mctx, b)
189 return symbols[funcname](mctx, b)
190 finally:
190 finally:
191 mctx._existingenabled = enabled
191 mctx._existingenabled = enabled
192
192
193 keep = lambda fn: getattr(fn, '__doc__', None) is not None
193 keep = lambda fn: getattr(fn, '__doc__', None) is not None
194
194
195 syms = [s for (s, fn) in symbols.items() if keep(fn)]
195 syms = [s for (s, fn) in symbols.items() if keep(fn)]
196 raise error.UnknownIdentifier(funcname, syms)
196 raise error.UnknownIdentifier(funcname, syms)
197
197
198 # symbols are callable like:
198 # symbols are callable like:
199 # fun(mctx, x)
199 # fun(mctx, x)
200 # with:
200 # with:
201 # mctx - current matchctx instance
201 # mctx - current matchctx instance
202 # x - argument in tree form
202 # x - argument in tree form
203 symbols = {}
203 symbols = {}
204
204
205 # filesets using matchctx.status()
205 # filesets using matchctx.status()
206 _statuscallers = set()
206 _statuscallers = set()
207
207
208 # filesets using matchctx.existing()
208 # filesets using matchctx.existing()
209 _existingcallers = set()
209 _existingcallers = set()
210
210
211 predicate = registrar.filesetpredicate()
211 predicate = registrar.filesetpredicate()
212
212
213 @predicate('modified()', callstatus=True)
213 @predicate('modified()', callstatus=True)
214 def modified(mctx, x):
214 def modified(mctx, x):
215 """File that is modified according to :hg:`status`.
215 """File that is modified according to :hg:`status`.
216 """
216 """
217 # i18n: "modified" is a keyword
217 # i18n: "modified" is a keyword
218 getargs(x, 0, 0, _("modified takes no arguments"))
218 getargs(x, 0, 0, _("modified takes no arguments"))
219 s = set(mctx.status().modified)
219 s = set(mctx.status().modified)
220 return [f for f in mctx.subset if f in s]
220 return [f for f in mctx.subset if f in s]
221
221
222 @predicate('added()', callstatus=True)
222 @predicate('added()', callstatus=True)
223 def added(mctx, x):
223 def added(mctx, x):
224 """File that is added according to :hg:`status`.
224 """File that is added according to :hg:`status`.
225 """
225 """
226 # i18n: "added" is a keyword
226 # i18n: "added" is a keyword
227 getargs(x, 0, 0, _("added takes no arguments"))
227 getargs(x, 0, 0, _("added takes no arguments"))
228 s = set(mctx.status().added)
228 s = set(mctx.status().added)
229 return [f for f in mctx.subset if f in s]
229 return [f for f in mctx.subset if f in s]
230
230
231 @predicate('removed()', callstatus=True)
231 @predicate('removed()', callstatus=True)
232 def removed(mctx, x):
232 def removed(mctx, x):
233 """File that is removed according to :hg:`status`.
233 """File that is removed according to :hg:`status`.
234 """
234 """
235 # i18n: "removed" is a keyword
235 # i18n: "removed" is a keyword
236 getargs(x, 0, 0, _("removed takes no arguments"))
236 getargs(x, 0, 0, _("removed takes no arguments"))
237 s = set(mctx.status().removed)
237 s = set(mctx.status().removed)
238 return [f for f in mctx.subset if f in s]
238 return [f for f in mctx.subset if f in s]
239
239
240 @predicate('deleted()', callstatus=True)
240 @predicate('deleted()', callstatus=True)
241 def deleted(mctx, x):
241 def deleted(mctx, x):
242 """Alias for ``missing()``.
242 """Alias for ``missing()``.
243 """
243 """
244 # i18n: "deleted" is a keyword
244 # i18n: "deleted" is a keyword
245 getargs(x, 0, 0, _("deleted takes no arguments"))
245 getargs(x, 0, 0, _("deleted takes no arguments"))
246 s = set(mctx.status().deleted)
246 s = set(mctx.status().deleted)
247 return [f for f in mctx.subset if f in s]
247 return [f for f in mctx.subset if f in s]
248
248
249 @predicate('missing()', callstatus=True)
249 @predicate('missing()', callstatus=True)
250 def missing(mctx, x):
250 def missing(mctx, x):
251 """File that is missing according to :hg:`status`.
251 """File that is missing according to :hg:`status`.
252 """
252 """
253 # i18n: "missing" is a keyword
253 # i18n: "missing" is a keyword
254 getargs(x, 0, 0, _("missing takes no arguments"))
254 getargs(x, 0, 0, _("missing takes no arguments"))
255 s = set(mctx.status().deleted)
255 s = set(mctx.status().deleted)
256 return [f for f in mctx.subset if f in s]
256 return [f for f in mctx.subset if f in s]
257
257
258 @predicate('unknown()', callstatus=True)
258 @predicate('unknown()', callstatus=True)
259 def unknown(mctx, x):
259 def unknown(mctx, x):
260 """File that is unknown according to :hg:`status`. These files will only be
260 """File that is unknown according to :hg:`status`. These files will only be
261 considered if this predicate is used.
261 considered if this predicate is used.
262 """
262 """
263 # i18n: "unknown" is a keyword
263 # i18n: "unknown" is a keyword
264 getargs(x, 0, 0, _("unknown takes no arguments"))
264 getargs(x, 0, 0, _("unknown takes no arguments"))
265 s = set(mctx.status().unknown)
265 s = set(mctx.status().unknown)
266 return [f for f in mctx.subset if f in s]
266 return [f for f in mctx.subset if f in s]
267
267
268 @predicate('ignored()', callstatus=True)
268 @predicate('ignored()', callstatus=True)
269 def ignored(mctx, x):
269 def ignored(mctx, x):
270 """File that is ignored according to :hg:`status`. These files will only be
270 """File that is ignored according to :hg:`status`. These files will only be
271 considered if this predicate is used.
271 considered if this predicate is used.
272 """
272 """
273 # i18n: "ignored" is a keyword
273 # i18n: "ignored" is a keyword
274 getargs(x, 0, 0, _("ignored takes no arguments"))
274 getargs(x, 0, 0, _("ignored takes no arguments"))
275 s = set(mctx.status().ignored)
275 s = set(mctx.status().ignored)
276 return [f for f in mctx.subset if f in s]
276 return [f for f in mctx.subset if f in s]
277
277
278 @predicate('clean()', callstatus=True)
278 @predicate('clean()', callstatus=True)
279 def clean(mctx, x):
279 def clean(mctx, x):
280 """File that is clean according to :hg:`status`.
280 """File that is clean according to :hg:`status`.
281 """
281 """
282 # i18n: "clean" is a keyword
282 # i18n: "clean" is a keyword
283 getargs(x, 0, 0, _("clean takes no arguments"))
283 getargs(x, 0, 0, _("clean takes no arguments"))
284 s = set(mctx.status().clean)
284 s = set(mctx.status().clean)
285 return [f for f in mctx.subset if f in s]
285 return [f for f in mctx.subset if f in s]
286
286
287 @predicate('tracked()')
288 def tracked(mctx, x):
289 """File that is under Mercurial control."""
290 # i18n: "tracked" is a keyword
291 getargs(x, 0, 0, _("tracked takes no arguments"))
292 return [f for f in mctx.subset if f in mctx.ctx]
293
287 @predicate('binary()', callexisting=True)
294 @predicate('binary()', callexisting=True)
288 def binary(mctx, x):
295 def binary(mctx, x):
289 """File that appears to be binary (contains NUL bytes).
296 """File that appears to be binary (contains NUL bytes).
290 """
297 """
291 # i18n: "binary" is a keyword
298 # i18n: "binary" is a keyword
292 getargs(x, 0, 0, _("binary takes no arguments"))
299 getargs(x, 0, 0, _("binary takes no arguments"))
293 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
300 return [f for f in mctx.existing() if mctx.ctx[f].isbinary()]
294
301
295 @predicate('exec()', callexisting=True)
302 @predicate('exec()', callexisting=True)
296 def exec_(mctx, x):
303 def exec_(mctx, x):
297 """File that is marked as executable.
304 """File that is marked as executable.
298 """
305 """
299 # i18n: "exec" is a keyword
306 # i18n: "exec" is a keyword
300 getargs(x, 0, 0, _("exec takes no arguments"))
307 getargs(x, 0, 0, _("exec takes no arguments"))
301 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
308 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'x']
302
309
303 @predicate('symlink()', callexisting=True)
310 @predicate('symlink()', callexisting=True)
304 def symlink(mctx, x):
311 def symlink(mctx, x):
305 """File that is marked as a symlink.
312 """File that is marked as a symlink.
306 """
313 """
307 # i18n: "symlink" is a keyword
314 # i18n: "symlink" is a keyword
308 getargs(x, 0, 0, _("symlink takes no arguments"))
315 getargs(x, 0, 0, _("symlink takes no arguments"))
309 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
316 return [f for f in mctx.existing() if mctx.ctx.flags(f) == 'l']
310
317
311 @predicate('resolved()')
318 @predicate('resolved()')
312 def resolved(mctx, x):
319 def resolved(mctx, x):
313 """File that is marked resolved according to :hg:`resolve -l`.
320 """File that is marked resolved according to :hg:`resolve -l`.
314 """
321 """
315 # i18n: "resolved" is a keyword
322 # i18n: "resolved" is a keyword
316 getargs(x, 0, 0, _("resolved takes no arguments"))
323 getargs(x, 0, 0, _("resolved takes no arguments"))
317 if mctx.ctx.rev() is not None:
324 if mctx.ctx.rev() is not None:
318 return []
325 return []
319 ms = merge.mergestate.read(mctx.ctx.repo())
326 ms = merge.mergestate.read(mctx.ctx.repo())
320 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
327 return [f for f in mctx.subset if f in ms and ms[f] == 'r']
321
328
322 @predicate('unresolved()')
329 @predicate('unresolved()')
323 def unresolved(mctx, x):
330 def unresolved(mctx, x):
324 """File that is marked unresolved according to :hg:`resolve -l`.
331 """File that is marked unresolved according to :hg:`resolve -l`.
325 """
332 """
326 # i18n: "unresolved" is a keyword
333 # i18n: "unresolved" is a keyword
327 getargs(x, 0, 0, _("unresolved takes no arguments"))
334 getargs(x, 0, 0, _("unresolved takes no arguments"))
328 if mctx.ctx.rev() is not None:
335 if mctx.ctx.rev() is not None:
329 return []
336 return []
330 ms = merge.mergestate.read(mctx.ctx.repo())
337 ms = merge.mergestate.read(mctx.ctx.repo())
331 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
338 return [f for f in mctx.subset if f in ms and ms[f] == 'u']
332
339
333 @predicate('hgignore()')
340 @predicate('hgignore()')
334 def hgignore(mctx, x):
341 def hgignore(mctx, x):
335 """File that matches the active .hgignore pattern.
342 """File that matches the active .hgignore pattern.
336 """
343 """
337 # i18n: "hgignore" is a keyword
344 # i18n: "hgignore" is a keyword
338 getargs(x, 0, 0, _("hgignore takes no arguments"))
345 getargs(x, 0, 0, _("hgignore takes no arguments"))
339 ignore = mctx.ctx.repo().dirstate._ignore
346 ignore = mctx.ctx.repo().dirstate._ignore
340 return [f for f in mctx.subset if ignore(f)]
347 return [f for f in mctx.subset if ignore(f)]
341
348
342 @predicate('portable()')
349 @predicate('portable()')
343 def portable(mctx, x):
350 def portable(mctx, x):
344 """File that has a portable name. (This doesn't include filenames with case
351 """File that has a portable name. (This doesn't include filenames with case
345 collisions.)
352 collisions.)
346 """
353 """
347 # i18n: "portable" is a keyword
354 # i18n: "portable" is a keyword
348 getargs(x, 0, 0, _("portable takes no arguments"))
355 getargs(x, 0, 0, _("portable takes no arguments"))
349 checkwinfilename = util.checkwinfilename
356 checkwinfilename = util.checkwinfilename
350 return [f for f in mctx.subset if checkwinfilename(f) is None]
357 return [f for f in mctx.subset if checkwinfilename(f) is None]
351
358
352 @predicate('grep(regex)', callexisting=True)
359 @predicate('grep(regex)', callexisting=True)
353 def grep(mctx, x):
360 def grep(mctx, x):
354 """File contains the given regular expression.
361 """File contains the given regular expression.
355 """
362 """
356 try:
363 try:
357 # i18n: "grep" is a keyword
364 # i18n: "grep" is a keyword
358 r = re.compile(getstring(x, _("grep requires a pattern")))
365 r = re.compile(getstring(x, _("grep requires a pattern")))
359 except re.error as e:
366 except re.error as e:
360 raise error.ParseError(_('invalid match pattern: %s') %
367 raise error.ParseError(_('invalid match pattern: %s') %
361 stringutil.forcebytestr(e))
368 stringutil.forcebytestr(e))
362 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
369 return [f for f in mctx.existing() if r.search(mctx.ctx[f].data())]
363
370
364 def _sizetomax(s):
371 def _sizetomax(s):
365 try:
372 try:
366 s = s.strip().lower()
373 s = s.strip().lower()
367 for k, v in util._sizeunits:
374 for k, v in util._sizeunits:
368 if s.endswith(k):
375 if s.endswith(k):
369 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
376 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
370 n = s[:-len(k)]
377 n = s[:-len(k)]
371 inc = 1.0
378 inc = 1.0
372 if "." in n:
379 if "." in n:
373 inc /= 10 ** len(n.split(".")[1])
380 inc /= 10 ** len(n.split(".")[1])
374 return int((float(n) + inc) * v) - 1
381 return int((float(n) + inc) * v) - 1
375 # no extension, this is a precise value
382 # no extension, this is a precise value
376 return int(s)
383 return int(s)
377 except ValueError:
384 except ValueError:
378 raise error.ParseError(_("couldn't parse size: %s") % s)
385 raise error.ParseError(_("couldn't parse size: %s") % s)
379
386
380 def sizematcher(x):
387 def sizematcher(x):
381 """Return a function(size) -> bool from the ``size()`` expression"""
388 """Return a function(size) -> bool from the ``size()`` expression"""
382
389
383 # i18n: "size" is a keyword
390 # i18n: "size" is a keyword
384 expr = getstring(x, _("size requires an expression")).strip()
391 expr = getstring(x, _("size requires an expression")).strip()
385 if '-' in expr: # do we have a range?
392 if '-' in expr: # do we have a range?
386 a, b = expr.split('-', 1)
393 a, b = expr.split('-', 1)
387 a = util.sizetoint(a)
394 a = util.sizetoint(a)
388 b = util.sizetoint(b)
395 b = util.sizetoint(b)
389 return lambda x: x >= a and x <= b
396 return lambda x: x >= a and x <= b
390 elif expr.startswith("<="):
397 elif expr.startswith("<="):
391 a = util.sizetoint(expr[2:])
398 a = util.sizetoint(expr[2:])
392 return lambda x: x <= a
399 return lambda x: x <= a
393 elif expr.startswith("<"):
400 elif expr.startswith("<"):
394 a = util.sizetoint(expr[1:])
401 a = util.sizetoint(expr[1:])
395 return lambda x: x < a
402 return lambda x: x < a
396 elif expr.startswith(">="):
403 elif expr.startswith(">="):
397 a = util.sizetoint(expr[2:])
404 a = util.sizetoint(expr[2:])
398 return lambda x: x >= a
405 return lambda x: x >= a
399 elif expr.startswith(">"):
406 elif expr.startswith(">"):
400 a = util.sizetoint(expr[1:])
407 a = util.sizetoint(expr[1:])
401 return lambda x: x > a
408 return lambda x: x > a
402 else:
409 else:
403 a = util.sizetoint(expr)
410 a = util.sizetoint(expr)
404 b = _sizetomax(expr)
411 b = _sizetomax(expr)
405 return lambda x: x >= a and x <= b
412 return lambda x: x >= a and x <= b
406
413
407 @predicate('size(expression)', callexisting=True)
414 @predicate('size(expression)', callexisting=True)
408 def size(mctx, x):
415 def size(mctx, x):
409 """File size matches the given expression. Examples:
416 """File size matches the given expression. Examples:
410
417
411 - size('1k') - files from 1024 to 2047 bytes
418 - size('1k') - files from 1024 to 2047 bytes
412 - size('< 20k') - files less than 20480 bytes
419 - size('< 20k') - files less than 20480 bytes
413 - size('>= .5MB') - files at least 524288 bytes
420 - size('>= .5MB') - files at least 524288 bytes
414 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
421 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
415 """
422 """
416 m = sizematcher(x)
423 m = sizematcher(x)
417 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
424 return [f for f in mctx.existing() if m(mctx.ctx[f].size())]
418
425
419 @predicate('encoding(name)', callexisting=True)
426 @predicate('encoding(name)', callexisting=True)
420 def encoding(mctx, x):
427 def encoding(mctx, x):
421 """File can be successfully decoded with the given character
428 """File can be successfully decoded with the given character
422 encoding. May not be useful for encodings other than ASCII and
429 encoding. May not be useful for encodings other than ASCII and
423 UTF-8.
430 UTF-8.
424 """
431 """
425
432
426 # i18n: "encoding" is a keyword
433 # i18n: "encoding" is a keyword
427 enc = getstring(x, _("encoding requires an encoding name"))
434 enc = getstring(x, _("encoding requires an encoding name"))
428
435
429 s = []
436 s = []
430 for f in mctx.existing():
437 for f in mctx.existing():
431 d = mctx.ctx[f].data()
438 d = mctx.ctx[f].data()
432 try:
439 try:
433 d.decode(pycompat.sysstr(enc))
440 d.decode(pycompat.sysstr(enc))
434 except LookupError:
441 except LookupError:
435 raise error.Abort(_("unknown encoding '%s'") % enc)
442 raise error.Abort(_("unknown encoding '%s'") % enc)
436 except UnicodeDecodeError:
443 except UnicodeDecodeError:
437 continue
444 continue
438 s.append(f)
445 s.append(f)
439
446
440 return s
447 return s
441
448
442 @predicate('eol(style)', callexisting=True)
449 @predicate('eol(style)', callexisting=True)
443 def eol(mctx, x):
450 def eol(mctx, x):
444 """File contains newlines of the given style (dos, unix, mac). Binary
451 """File contains newlines of the given style (dos, unix, mac). Binary
445 files are excluded, files with mixed line endings match multiple
452 files are excluded, files with mixed line endings match multiple
446 styles.
453 styles.
447 """
454 """
448
455
449 # i18n: "eol" is a keyword
456 # i18n: "eol" is a keyword
450 enc = getstring(x, _("eol requires a style name"))
457 enc = getstring(x, _("eol requires a style name"))
451
458
452 s = []
459 s = []
453 for f in mctx.existing():
460 for f in mctx.existing():
454 fctx = mctx.ctx[f]
461 fctx = mctx.ctx[f]
455 if fctx.isbinary():
462 if fctx.isbinary():
456 continue
463 continue
457 d = fctx.data()
464 d = fctx.data()
458 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
465 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
459 s.append(f)
466 s.append(f)
460 elif enc == 'unix' and re.search('(?<!\r)\n', d):
467 elif enc == 'unix' and re.search('(?<!\r)\n', d):
461 s.append(f)
468 s.append(f)
462 elif enc == 'mac' and re.search('\r(?!\n)', d):
469 elif enc == 'mac' and re.search('\r(?!\n)', d):
463 s.append(f)
470 s.append(f)
464 return s
471 return s
465
472
466 @predicate('copied()')
473 @predicate('copied()')
467 def copied(mctx, x):
474 def copied(mctx, x):
468 """File that is recorded as being copied.
475 """File that is recorded as being copied.
469 """
476 """
470 # i18n: "copied" is a keyword
477 # i18n: "copied" is a keyword
471 getargs(x, 0, 0, _("copied takes no arguments"))
478 getargs(x, 0, 0, _("copied takes no arguments"))
472 s = []
479 s = []
473 for f in mctx.subset:
480 for f in mctx.subset:
474 if f in mctx.ctx:
481 if f in mctx.ctx:
475 p = mctx.ctx[f].parents()
482 p = mctx.ctx[f].parents()
476 if p and p[0].path() != f:
483 if p and p[0].path() != f:
477 s.append(f)
484 s.append(f)
478 return s
485 return s
479
486
480 @predicate('revs(revs, pattern)')
487 @predicate('revs(revs, pattern)')
481 def revs(mctx, x):
488 def revs(mctx, x):
482 """Evaluate set in the specified revisions. If the revset match multiple
489 """Evaluate set in the specified revisions. If the revset match multiple
483 revs, this will return file matching pattern in any of the revision.
490 revs, this will return file matching pattern in any of the revision.
484 """
491 """
485 # i18n: "revs" is a keyword
492 # i18n: "revs" is a keyword
486 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
493 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
487 # i18n: "revs" is a keyword
494 # i18n: "revs" is a keyword
488 revspec = getstring(r, _("first argument to revs must be a revision"))
495 revspec = getstring(r, _("first argument to revs must be a revision"))
489 repo = mctx.ctx.repo()
496 repo = mctx.ctx.repo()
490 revs = scmutil.revrange(repo, [revspec])
497 revs = scmutil.revrange(repo, [revspec])
491
498
492 found = set()
499 found = set()
493 result = []
500 result = []
494 for r in revs:
501 for r in revs:
495 ctx = repo[r]
502 ctx = repo[r]
496 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
503 for f in getset(mctx.switch(ctx, _buildstatus(ctx, x)), x):
497 if f not in found:
504 if f not in found:
498 found.add(f)
505 found.add(f)
499 result.append(f)
506 result.append(f)
500 return result
507 return result
501
508
502 @predicate('status(base, rev, pattern)')
509 @predicate('status(base, rev, pattern)')
503 def status(mctx, x):
510 def status(mctx, x):
504 """Evaluate predicate using status change between ``base`` and
511 """Evaluate predicate using status change between ``base`` and
505 ``rev``. Examples:
512 ``rev``. Examples:
506
513
507 - ``status(3, 7, added())`` - matches files added from "3" to "7"
514 - ``status(3, 7, added())`` - matches files added from "3" to "7"
508 """
515 """
509 repo = mctx.ctx.repo()
516 repo = mctx.ctx.repo()
510 # i18n: "status" is a keyword
517 # i18n: "status" is a keyword
511 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
518 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
512 # i18n: "status" is a keyword
519 # i18n: "status" is a keyword
513 baseerr = _("first argument to status must be a revision")
520 baseerr = _("first argument to status must be a revision")
514 baserevspec = getstring(b, baseerr)
521 baserevspec = getstring(b, baseerr)
515 if not baserevspec:
522 if not baserevspec:
516 raise error.ParseError(baseerr)
523 raise error.ParseError(baseerr)
517 reverr = _("second argument to status must be a revision")
524 reverr = _("second argument to status must be a revision")
518 revspec = getstring(r, reverr)
525 revspec = getstring(r, reverr)
519 if not revspec:
526 if not revspec:
520 raise error.ParseError(reverr)
527 raise error.ParseError(reverr)
521 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
528 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
522 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
529 return getset(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
523
530
524 @predicate('subrepo([pattern])')
531 @predicate('subrepo([pattern])')
525 def subrepo(mctx, x):
532 def subrepo(mctx, x):
526 """Subrepositories whose paths match the given pattern.
533 """Subrepositories whose paths match the given pattern.
527 """
534 """
528 # i18n: "subrepo" is a keyword
535 # i18n: "subrepo" is a keyword
529 getargs(x, 0, 1, _("subrepo takes at most one argument"))
536 getargs(x, 0, 1, _("subrepo takes at most one argument"))
530 ctx = mctx.ctx
537 ctx = mctx.ctx
531 sstate = sorted(ctx.substate)
538 sstate = sorted(ctx.substate)
532 if x:
539 if x:
533 pat = getpattern(x, matchmod.allpatternkinds,
540 pat = getpattern(x, matchmod.allpatternkinds,
534 # i18n: "subrepo" is a keyword
541 # i18n: "subrepo" is a keyword
535 _("subrepo requires a pattern or no arguments"))
542 _("subrepo requires a pattern or no arguments"))
536 fast = not matchmod.patkind(pat)
543 fast = not matchmod.patkind(pat)
537 if fast:
544 if fast:
538 def m(s):
545 def m(s):
539 return (s == pat)
546 return (s == pat)
540 else:
547 else:
541 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
548 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
542 return [sub for sub in sstate if m(sub)]
549 return [sub for sub in sstate if m(sub)]
543 else:
550 else:
544 return [sub for sub in sstate]
551 return [sub for sub in sstate]
545
552
546 methods = {
553 methods = {
547 'string': stringset,
554 'string': stringset,
548 'symbol': stringset,
555 'symbol': stringset,
549 'kindpat': kindpatset,
556 'kindpat': kindpatset,
550 'and': andset,
557 'and': andset,
551 'or': orset,
558 'or': orset,
552 'minus': minusset,
559 'minus': minusset,
553 'negate': negateset,
560 'negate': negateset,
554 'list': listset,
561 'list': listset,
555 'group': getset,
562 'group': getset,
556 'not': notset,
563 'not': notset,
557 'func': func,
564 'func': func,
558 }
565 }
559
566
560 class matchctx(object):
567 class matchctx(object):
561 def __init__(self, ctx, subset, status=None, badfn=None):
568 def __init__(self, ctx, subset, status=None, badfn=None):
562 self.ctx = ctx
569 self.ctx = ctx
563 self.subset = subset
570 self.subset = subset
564 self._status = status
571 self._status = status
565 self._badfn = badfn
572 self._badfn = badfn
566 self._existingenabled = False
573 self._existingenabled = False
567 def status(self):
574 def status(self):
568 return self._status
575 return self._status
569
576
570 def matcher(self, patterns):
577 def matcher(self, patterns):
571 return self.ctx.match(patterns, badfn=self._badfn)
578 return self.ctx.match(patterns, badfn=self._badfn)
572
579
573 def predicate(self, predfn, predrepr=None, cache=False):
580 def predicate(self, predfn, predrepr=None, cache=False):
574 """Create a matcher to select files by predfn(filename)"""
581 """Create a matcher to select files by predfn(filename)"""
575 if cache:
582 if cache:
576 predfn = util.cachefunc(predfn)
583 predfn = util.cachefunc(predfn)
577 repo = self.ctx.repo()
584 repo = self.ctx.repo()
578 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
585 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
579 predrepr=predrepr, badfn=self._badfn)
586 predrepr=predrepr, badfn=self._badfn)
580
587
581 def fpredicate(self, predfn, predrepr=None, cache=False):
588 def fpredicate(self, predfn, predrepr=None, cache=False):
582 """Create a matcher to select files by predfn(fctx) at the current
589 """Create a matcher to select files by predfn(fctx) at the current
583 revision
590 revision
584
591
585 Missing files are ignored.
592 Missing files are ignored.
586 """
593 """
587 ctx = self.ctx
594 ctx = self.ctx
588 if ctx.rev() is None:
595 if ctx.rev() is None:
589 def fctxpredfn(f):
596 def fctxpredfn(f):
590 try:
597 try:
591 fctx = ctx[f]
598 fctx = ctx[f]
592 except error.LookupError:
599 except error.LookupError:
593 return False
600 return False
594 try:
601 try:
595 fctx.audit()
602 fctx.audit()
596 except error.Abort:
603 except error.Abort:
597 return False
604 return False
598 try:
605 try:
599 return predfn(fctx)
606 return predfn(fctx)
600 except (IOError, OSError) as e:
607 except (IOError, OSError) as e:
601 if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EISDIR):
608 if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EISDIR):
602 return False
609 return False
603 raise
610 raise
604 else:
611 else:
605 def fctxpredfn(f):
612 def fctxpredfn(f):
606 try:
613 try:
607 fctx = ctx[f]
614 fctx = ctx[f]
608 except error.LookupError:
615 except error.LookupError:
609 return False
616 return False
610 return predfn(fctx)
617 return predfn(fctx)
611 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
618 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
612
619
613 def never(self):
620 def never(self):
614 """Create a matcher to select nothing"""
621 """Create a matcher to select nothing"""
615 repo = self.ctx.repo()
622 repo = self.ctx.repo()
616 return matchmod.nevermatcher(repo.root, repo.getcwd(),
623 return matchmod.nevermatcher(repo.root, repo.getcwd(),
617 badfn=self._badfn)
624 badfn=self._badfn)
618
625
619 def filter(self, files):
626 def filter(self, files):
620 return [f for f in files if f in self.subset]
627 return [f for f in files if f in self.subset]
621 def existing(self):
628 def existing(self):
622 if not self._existingenabled:
629 if not self._existingenabled:
623 raise error.ProgrammingError('unexpected existing() invocation')
630 raise error.ProgrammingError('unexpected existing() invocation')
624 if self._status is not None:
631 if self._status is not None:
625 removed = set(self._status[3])
632 removed = set(self._status[3])
626 unknown = set(self._status[4] + self._status[5])
633 unknown = set(self._status[4] + self._status[5])
627 else:
634 else:
628 removed = set()
635 removed = set()
629 unknown = set()
636 unknown = set()
630 return (f for f in self.subset
637 return (f for f in self.subset
631 if (f in self.ctx and f not in removed) or f in unknown)
638 if (f in self.ctx and f not in removed) or f in unknown)
632
639
633 def switch(self, ctx, status=None):
640 def switch(self, ctx, status=None):
634 subset = self.filter(_buildsubset(ctx, status))
641 subset = self.filter(_buildsubset(ctx, status))
635 return matchctx(ctx, subset, status, self._badfn)
642 return matchctx(ctx, subset, status, self._badfn)
636
643
637 class fullmatchctx(matchctx):
644 class fullmatchctx(matchctx):
638 """A match context where any files in any revisions should be valid"""
645 """A match context where any files in any revisions should be valid"""
639
646
640 def __init__(self, ctx, status=None, badfn=None):
647 def __init__(self, ctx, status=None, badfn=None):
641 subset = _buildsubset(ctx, status)
648 subset = _buildsubset(ctx, status)
642 super(fullmatchctx, self).__init__(ctx, subset, status, badfn)
649 super(fullmatchctx, self).__init__(ctx, subset, status, badfn)
643 def switch(self, ctx, status=None):
650 def switch(self, ctx, status=None):
644 return fullmatchctx(ctx, status, self._badfn)
651 return fullmatchctx(ctx, status, self._badfn)
645
652
646 # filesets using matchctx.switch()
653 # filesets using matchctx.switch()
647 _switchcallers = [
654 _switchcallers = [
648 'revs',
655 'revs',
649 'status',
656 'status',
650 ]
657 ]
651
658
652 def _intree(funcs, tree):
659 def _intree(funcs, tree):
653 if isinstance(tree, tuple):
660 if isinstance(tree, tuple):
654 if tree[0] == 'func' and tree[1][0] == 'symbol':
661 if tree[0] == 'func' and tree[1][0] == 'symbol':
655 if tree[1][1] in funcs:
662 if tree[1][1] in funcs:
656 return True
663 return True
657 if tree[1][1] in _switchcallers:
664 if tree[1][1] in _switchcallers:
658 # arguments won't be evaluated in the current context
665 # arguments won't be evaluated in the current context
659 return False
666 return False
660 for s in tree[1:]:
667 for s in tree[1:]:
661 if _intree(funcs, s):
668 if _intree(funcs, s):
662 return True
669 return True
663 return False
670 return False
664
671
665 def _buildsubset(ctx, status):
672 def _buildsubset(ctx, status):
666 if status:
673 if status:
667 subset = []
674 subset = []
668 for c in status:
675 for c in status:
669 subset.extend(c)
676 subset.extend(c)
670 return subset
677 return subset
671 else:
678 else:
672 return list(ctx.walk(ctx.match([])))
679 return list(ctx.walk(ctx.match([])))
673
680
674 def match(ctx, expr, badfn=None):
681 def match(ctx, expr, badfn=None):
675 """Create a matcher for a single fileset expression"""
682 """Create a matcher for a single fileset expression"""
676 repo = ctx.repo()
683 repo = ctx.repo()
677 tree = parse(expr)
684 tree = parse(expr)
678 fset = getset(fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn), tree)
685 fset = getset(fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn), tree)
679 return matchmod.predicatematcher(repo.root, repo.getcwd(),
686 return matchmod.predicatematcher(repo.root, repo.getcwd(),
680 fset.__contains__,
687 fset.__contains__,
681 predrepr='fileset', badfn=badfn)
688 predrepr='fileset', badfn=badfn)
682
689
683 def _buildstatus(ctx, tree, basectx=None):
690 def _buildstatus(ctx, tree, basectx=None):
684 # do we need status info?
691 # do we need status info?
685
692
686 # temporaty boolean to simplify the next conditional
693 # temporaty boolean to simplify the next conditional
687 purewdir = ctx.rev() is None and basectx is None
694 purewdir = ctx.rev() is None and basectx is None
688
695
689 if (_intree(_statuscallers, tree) or
696 if (_intree(_statuscallers, tree) or
690 # Using matchctx.existing() on a workingctx requires us to check
697 # Using matchctx.existing() on a workingctx requires us to check
691 # for deleted files.
698 # for deleted files.
692 (purewdir and _intree(_existingcallers, tree))):
699 (purewdir and _intree(_existingcallers, tree))):
693 unknown = _intree(['unknown'], tree)
700 unknown = _intree(['unknown'], tree)
694 ignored = _intree(['ignored'], tree)
701 ignored = _intree(['ignored'], tree)
695
702
696 r = ctx.repo()
703 r = ctx.repo()
697 if basectx is None:
704 if basectx is None:
698 basectx = ctx.p1()
705 basectx = ctx.p1()
699 return r.status(basectx, ctx,
706 return r.status(basectx, ctx,
700 unknown=unknown, ignored=ignored, clean=True)
707 unknown=unknown, ignored=ignored, clean=True)
701 else:
708 else:
702 return None
709 return None
703
710
704 def prettyformat(tree):
711 def prettyformat(tree):
705 return parser.prettyformat(tree, ('string', 'symbol'))
712 return parser.prettyformat(tree, ('string', 'symbol'))
706
713
707 def loadpredicate(ui, extname, registrarobj):
714 def loadpredicate(ui, extname, registrarobj):
708 """Load fileset predicates from specified registrarobj
715 """Load fileset predicates from specified registrarobj
709 """
716 """
710 for name, func in registrarobj._table.iteritems():
717 for name, func in registrarobj._table.iteritems():
711 symbols[name] = func
718 symbols[name] = func
712 if func._callstatus:
719 if func._callstatus:
713 _statuscallers.add(name)
720 _statuscallers.add(name)
714 if func._callexisting:
721 if func._callexisting:
715 _existingcallers.add(name)
722 _existingcallers.add(name)
716
723
717 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
724 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
718 loadpredicate(None, None, predicate)
725 loadpredicate(None, None, predicate)
719
726
720 # tell hggettext to extract docstrings from these functions:
727 # tell hggettext to extract docstrings from these functions:
721 i18nfunctions = symbols.values()
728 i18nfunctions = symbols.values()
@@ -1,676 +1,676 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 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 '!re:"a\d"'
30 $ fileset -v '!re:"a\d"'
31 (not
31 (not
32 (kindpat
32 (kindpat
33 (symbol 're')
33 (symbol 're')
34 (string 'a\\d')))
34 (string 'a\\d')))
35 b1
35 b1
36 b2
36 b2
37 $ fileset -v 'path:a1 or glob:b?'
37 $ fileset -v 'path:a1 or glob:b?'
38 (or
38 (or
39 (kindpat
39 (kindpat
40 (symbol 'path')
40 (symbol 'path')
41 (symbol 'a1'))
41 (symbol 'a1'))
42 (kindpat
42 (kindpat
43 (symbol 'glob')
43 (symbol 'glob')
44 (symbol 'b?')))
44 (symbol 'b?')))
45 a1
45 a1
46 b1
46 b1
47 b2
47 b2
48 $ fileset -v 'a1 or a2'
48 $ fileset -v 'a1 or a2'
49 (or
49 (or
50 (symbol 'a1')
50 (symbol 'a1')
51 (symbol 'a2'))
51 (symbol 'a2'))
52 a1
52 a1
53 a2
53 a2
54 $ fileset 'a1 | a2'
54 $ fileset 'a1 | a2'
55 a1
55 a1
56 a2
56 a2
57 $ fileset 'a* and "*1"'
57 $ fileset 'a* and "*1"'
58 a1
58 a1
59 $ fileset 'a* & "*1"'
59 $ fileset 'a* & "*1"'
60 a1
60 a1
61 $ fileset 'not (r"a*")'
61 $ fileset 'not (r"a*")'
62 b1
62 b1
63 b2
63 b2
64 $ fileset '! ("a*")'
64 $ fileset '! ("a*")'
65 b1
65 b1
66 b2
66 b2
67 $ fileset 'a* - a1'
67 $ fileset 'a* - a1'
68 a2
68 a2
69 $ fileset 'a_b'
69 $ fileset 'a_b'
70 $ fileset '"\xy"'
70 $ fileset '"\xy"'
71 hg: parse error: invalid \x escape* (glob)
71 hg: parse error: invalid \x escape* (glob)
72 [255]
72 [255]
73
73
74 Test invalid syntax
74 Test invalid syntax
75
75
76 $ fileset -v '"added"()'
76 $ fileset -v '"added"()'
77 (func
77 (func
78 (string 'added')
78 (string 'added')
79 None)
79 None)
80 hg: parse error: not a symbol
80 hg: parse error: not a symbol
81 [255]
81 [255]
82 $ fileset -v '()()'
82 $ fileset -v '()()'
83 (func
83 (func
84 (group
84 (group
85 None)
85 None)
86 None)
86 None)
87 hg: parse error: not a symbol
87 hg: parse error: not a symbol
88 [255]
88 [255]
89 $ fileset -v -- '-x'
89 $ fileset -v -- '-x'
90 (negate
90 (negate
91 (symbol 'x'))
91 (symbol 'x'))
92 hg: parse error: can't use negate operator in this context
92 hg: parse error: can't use negate operator in this context
93 [255]
93 [255]
94 $ fileset -v -- '-()'
94 $ fileset -v -- '-()'
95 (negate
95 (negate
96 (group
96 (group
97 None))
97 None))
98 hg: parse error: can't use negate operator in this context
98 hg: parse error: can't use negate operator in this context
99 [255]
99 [255]
100
100
101 $ fileset '"path":.'
101 $ fileset '"path":.'
102 hg: parse error: not a symbol
102 hg: parse error: not a symbol
103 [255]
103 [255]
104 $ fileset 'path:foo bar'
104 $ fileset 'path:foo bar'
105 hg: parse error at 9: invalid token
105 hg: parse error at 9: invalid token
106 [255]
106 [255]
107 $ fileset 'foo:bar:baz'
107 $ fileset 'foo:bar:baz'
108 hg: parse error: not a symbol
108 hg: parse error: not a symbol
109 [255]
109 [255]
110 $ fileset 'foo:bar()'
110 $ fileset 'foo:bar()'
111 hg: parse error: pattern must be a string
111 hg: parse error: pattern must be a string
112 [255]
112 [255]
113 $ fileset 'foo:bar'
113 $ fileset 'foo:bar'
114 hg: parse error: invalid pattern kind: foo
114 hg: parse error: invalid pattern kind: foo
115 [255]
115 [255]
116
116
117 Test files status
117 Test files status
118
118
119 $ rm a1
119 $ rm a1
120 $ hg rm a2
120 $ hg rm a2
121 $ echo b >> b2
121 $ echo b >> b2
122 $ hg cp b1 c1
122 $ hg cp b1 c1
123 $ echo c > c2
123 $ echo c > c2
124 $ echo c > c3
124 $ echo c > c3
125 $ cat > .hgignore <<EOF
125 $ cat > .hgignore <<EOF
126 > \.hgignore
126 > \.hgignore
127 > 2$
127 > 2$
128 > EOF
128 > EOF
129 $ fileset 'modified()'
129 $ fileset 'modified()'
130 b2
130 b2
131 $ fileset 'added()'
131 $ fileset 'added()'
132 c1
132 c1
133 $ fileset 'removed()'
133 $ fileset 'removed()'
134 a2
134 a2
135 $ fileset 'deleted()'
135 $ fileset 'deleted()'
136 a1
136 a1
137 $ fileset 'missing()'
137 $ fileset 'missing()'
138 a1
138 a1
139 $ fileset 'unknown()'
139 $ fileset 'unknown()'
140 c3
140 c3
141 $ fileset 'ignored()'
141 $ fileset 'ignored()'
142 .hgignore
142 .hgignore
143 c2
143 c2
144 $ fileset 'hgignore()'
144 $ fileset 'hgignore()'
145 a2
145 a2
146 b2
146 b2
147 $ fileset 'clean()'
147 $ fileset 'clean()'
148 b1
148 b1
149 $ fileset 'copied()'
149 $ fileset 'copied()'
150 c1
150 c1
151
151
152 Test files status in different revisions
152 Test files status in different revisions
153
153
154 $ hg status -m
154 $ hg status -m
155 M b2
155 M b2
156 $ fileset -r0 'revs("wdir()", modified())' --traceback
156 $ fileset -r0 'revs("wdir()", modified())' --traceback
157 b2
157 b2
158 $ hg status -a
158 $ hg status -a
159 A c1
159 A c1
160 $ fileset -r0 'revs("wdir()", added())'
160 $ fileset -r0 'revs("wdir()", added())'
161 c1
161 c1
162 $ hg status --change 0 -a
162 $ hg status --change 0 -a
163 A a1
163 A a1
164 A a2
164 A a2
165 A b1
165 A b1
166 A b2
166 A b2
167 $ hg status -mru
167 $ hg status -mru
168 M b2
168 M b2
169 R a2
169 R a2
170 ? c3
170 ? c3
171 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
171 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
172 a2
172 a2
173 b2
173 b2
174 $ fileset -r0 'added() or revs("wdir()", added())'
174 $ fileset -r0 'added() or revs("wdir()", added())'
175 a1
175 a1
176 a2
176 a2
177 b1
177 b1
178 b2
178 b2
179 c1
179 c1
180
180
181 Test files properties
181 Test files properties
182
182
183 >>> open('bin', 'wb').write(b'\0a') and None
183 >>> open('bin', 'wb').write(b'\0a') and None
184 $ fileset 'binary()'
184 $ fileset 'binary()'
185 $ fileset 'binary() and unknown()'
185 $ fileset 'binary() and unknown()'
186 bin
186 bin
187 $ echo '^bin$' >> .hgignore
187 $ echo '^bin$' >> .hgignore
188 $ fileset 'binary() and ignored()'
188 $ fileset 'binary() and ignored()'
189 bin
189 bin
190 $ hg add bin
190 $ hg add bin
191 $ fileset 'binary()'
191 $ fileset 'binary()'
192 bin
192 bin
193
193
194 $ fileset 'grep("b{1}")'
194 $ fileset 'grep("b{1}")'
195 b1
195 b1
196 b2
196 b2
197 c1
197 c1
198 $ fileset 'grep("missingparens(")'
198 $ fileset 'grep("missingparens(")'
199 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
199 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
200 [255]
200 [255]
201
201
202 #if execbit
202 #if execbit
203 $ chmod +x b2
203 $ chmod +x b2
204 $ fileset 'exec()'
204 $ fileset 'exec()'
205 b2
205 b2
206 #endif
206 #endif
207
207
208 #if symlink
208 #if symlink
209 $ ln -s b2 b2link
209 $ ln -s b2 b2link
210 $ fileset 'symlink() and unknown()'
210 $ fileset 'symlink() and unknown()'
211 b2link
211 b2link
212 $ hg add b2link
212 $ hg add b2link
213 #endif
213 #endif
214
214
215 #if no-windows
215 #if no-windows
216 $ echo foo > con.xml
216 $ echo foo > con.xml
217 $ fileset 'not portable()'
217 $ fileset 'not portable()'
218 con.xml
218 con.xml
219 $ hg --config ui.portablefilenames=ignore add con.xml
219 $ hg --config ui.portablefilenames=ignore add con.xml
220 #endif
220 #endif
221
221
222 >>> open('1k', 'wb').write(b' '*1024) and None
222 >>> open('1k', 'wb').write(b' '*1024) and None
223 >>> open('2k', 'wb').write(b' '*2048) and None
223 >>> open('2k', 'wb').write(b' '*2048) and None
224 $ hg add 1k 2k
224 $ hg add 1k 2k
225 $ fileset 'size("bar")'
225 $ fileset 'size("bar")'
226 hg: parse error: couldn't parse size: bar
226 hg: parse error: couldn't parse size: bar
227 [255]
227 [255]
228 $ fileset '(1k, 2k)'
228 $ fileset '(1k, 2k)'
229 hg: parse error: can't use a list in this context
229 hg: parse error: can't use a list in this context
230 (see hg help "filesets.x or y")
230 (see hg help "filesets.x or y")
231 [255]
231 [255]
232 $ fileset 'size(1k)'
232 $ fileset 'size(1k)'
233 1k
233 1k
234 $ fileset '(1k or 2k) and size("< 2k")'
234 $ fileset '(1k or 2k) and size("< 2k")'
235 1k
235 1k
236 $ fileset '(1k or 2k) and size("<=2k")'
236 $ fileset '(1k or 2k) and size("<=2k")'
237 1k
237 1k
238 2k
238 2k
239 $ fileset '(1k or 2k) and size("> 1k")'
239 $ fileset '(1k or 2k) and size("> 1k")'
240 2k
240 2k
241 $ fileset '(1k or 2k) and size(">=1K")'
241 $ fileset '(1k or 2k) and size(">=1K")'
242 1k
242 1k
243 2k
243 2k
244 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
244 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
245 1k
245 1k
246 $ fileset 'size("1M")'
246 $ fileset 'size("1M")'
247 $ fileset 'size("1 GB")'
247 $ fileset 'size("1 GB")'
248
248
249 Test merge states
249 Test merge states
250
250
251 $ hg ci -m manychanges
251 $ hg ci -m manychanges
252 $ hg file -r . 'set:copied() & modified()'
252 $ hg file -r . 'set:copied() & modified()'
253 [1]
253 [1]
254 $ hg up -C 0
254 $ hg up -C 0
255 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
255 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
256 $ echo c >> b2
256 $ echo c >> b2
257 $ hg ci -m diverging b2
257 $ hg ci -m diverging b2
258 created new head
258 created new head
259 $ fileset 'resolved()'
259 $ fileset 'resolved()'
260 $ fileset 'unresolved()'
260 $ fileset 'unresolved()'
261 $ hg merge
261 $ hg merge
262 merging b2
262 merging b2
263 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
263 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
264 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
264 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
265 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
265 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
266 [1]
266 [1]
267 $ fileset 'resolved()'
267 $ fileset 'resolved()'
268 $ fileset 'unresolved()'
268 $ fileset 'unresolved()'
269 b2
269 b2
270 $ echo e > b2
270 $ echo e > b2
271 $ hg resolve -m b2
271 $ hg resolve -m b2
272 (no more unresolved files)
272 (no more unresolved files)
273 $ fileset 'resolved()'
273 $ fileset 'resolved()'
274 b2
274 b2
275 $ fileset 'unresolved()'
275 $ fileset 'unresolved()'
276 $ hg ci -m merge
276 $ hg ci -m merge
277
277
278 Test subrepo predicate
278 Test subrepo predicate
279
279
280 $ hg init sub
280 $ hg init sub
281 $ echo a > sub/suba
281 $ echo a > sub/suba
282 $ hg -R sub add sub/suba
282 $ hg -R sub add sub/suba
283 $ hg -R sub ci -m sub
283 $ hg -R sub ci -m sub
284 $ echo 'sub = sub' > .hgsub
284 $ echo 'sub = sub' > .hgsub
285 $ hg init sub2
285 $ hg init sub2
286 $ echo b > sub2/b
286 $ echo b > sub2/b
287 $ hg -R sub2 ci -Am sub2
287 $ hg -R sub2 ci -Am sub2
288 adding b
288 adding b
289 $ echo 'sub2 = sub2' >> .hgsub
289 $ echo 'sub2 = sub2' >> .hgsub
290 $ fileset 'subrepo()'
290 $ fileset 'subrepo()'
291 $ hg add .hgsub
291 $ hg add .hgsub
292 $ fileset 'subrepo()'
292 $ fileset 'subrepo()'
293 sub
293 sub
294 sub2
294 sub2
295 $ fileset 'subrepo("sub")'
295 $ fileset 'subrepo("sub")'
296 sub
296 sub
297 $ fileset 'subrepo("glob:*")'
297 $ fileset 'subrepo("glob:*")'
298 sub
298 sub
299 sub2
299 sub2
300 $ hg ci -m subrepo
300 $ hg ci -m subrepo
301
301
302 Test that .hgsubstate is updated as appropriate during a conversion. The
302 Test that .hgsubstate is updated as appropriate during a conversion. The
303 saverev property is enough to alter the hashes of the subrepo.
303 saverev property is enough to alter the hashes of the subrepo.
304
304
305 $ hg init ../converted
305 $ hg init ../converted
306 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
306 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
307 > sub ../converted/sub
307 > sub ../converted/sub
308 initializing destination ../converted/sub repository
308 initializing destination ../converted/sub repository
309 scanning source...
309 scanning source...
310 sorting...
310 sorting...
311 converting...
311 converting...
312 0 sub
312 0 sub
313 $ hg clone -U sub2 ../converted/sub2
313 $ hg clone -U sub2 ../converted/sub2
314 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
314 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
315 > . ../converted
315 > . ../converted
316 scanning source...
316 scanning source...
317 sorting...
317 sorting...
318 converting...
318 converting...
319 4 addfiles
319 4 addfiles
320 3 manychanges
320 3 manychanges
321 2 diverging
321 2 diverging
322 1 merge
322 1 merge
323 0 subrepo
323 0 subrepo
324 no ".hgsubstate" updates will be made for "sub2"
324 no ".hgsubstate" updates will be made for "sub2"
325 $ hg up -q -R ../converted -r tip
325 $ hg up -q -R ../converted -r tip
326 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
326 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
327 a
327 a
328 b
328 b
329 $ oldnode=`hg log -r tip -T "{node}\n"`
329 $ oldnode=`hg log -r tip -T "{node}\n"`
330 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
330 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
331 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
331 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
332
332
333 Test with a revision
333 Test with a revision
334
334
335 $ hg log -G --template '{rev} {desc}\n'
335 $ hg log -G --template '{rev} {desc}\n'
336 @ 4 subrepo
336 @ 4 subrepo
337 |
337 |
338 o 3 merge
338 o 3 merge
339 |\
339 |\
340 | o 2 diverging
340 | o 2 diverging
341 | |
341 | |
342 o | 1 manychanges
342 o | 1 manychanges
343 |/
343 |/
344 o 0 addfiles
344 o 0 addfiles
345
345
346 $ echo unknown > unknown
346 $ echo unknown > unknown
347 $ fileset -r1 'modified()'
347 $ fileset -r1 'modified()'
348 b2
348 b2
349 $ fileset -r1 'added() and c1'
349 $ fileset -r1 'added() and c1'
350 c1
350 c1
351 $ fileset -r1 'removed()'
351 $ fileset -r1 'removed()'
352 a2
352 a2
353 $ fileset -r1 'deleted()'
353 $ fileset -r1 'deleted()'
354 $ fileset -r1 'unknown()'
354 $ fileset -r1 'unknown()'
355 $ fileset -r1 'ignored()'
355 $ fileset -r1 'ignored()'
356 $ fileset -r1 'hgignore()'
356 $ fileset -r1 'hgignore()'
357 b2
357 b2
358 bin
358 bin
359 $ fileset -r1 'binary()'
359 $ fileset -r1 'binary()'
360 bin
360 bin
361 $ fileset -r1 'size(1k)'
361 $ fileset -r1 'size(1k)'
362 1k
362 1k
363 $ fileset -r3 'resolved()'
363 $ fileset -r3 'resolved()'
364 $ fileset -r3 'unresolved()'
364 $ fileset -r3 'unresolved()'
365
365
366 #if execbit
366 #if execbit
367 $ fileset -r1 'exec()'
367 $ fileset -r1 'exec()'
368 b2
368 b2
369 #endif
369 #endif
370
370
371 #if symlink
371 #if symlink
372 $ fileset -r1 'symlink()'
372 $ fileset -r1 'symlink()'
373 b2link
373 b2link
374 #endif
374 #endif
375
375
376 #if no-windows
376 #if no-windows
377 $ fileset -r1 'not portable()'
377 $ fileset -r1 'not portable()'
378 con.xml
378 con.xml
379 $ hg forget 'con.xml'
379 $ hg forget 'con.xml'
380 #endif
380 #endif
381
381
382 $ fileset -r4 'subrepo("re:su.*")'
382 $ fileset -r4 'subrepo("re:su.*")'
383 sub
383 sub
384 sub2
384 sub2
385 $ fileset -r4 'subrepo(re:su.*)'
385 $ fileset -r4 'subrepo(re:su.*)'
386 sub
386 sub
387 sub2
387 sub2
388 $ fileset -r4 'subrepo("sub")'
388 $ fileset -r4 'subrepo("sub")'
389 sub
389 sub
390 $ fileset -r4 'b2 or c1'
390 $ fileset -r4 'b2 or c1'
391 b2
391 b2
392 c1
392 c1
393
393
394 >>> open('dos', 'wb').write(b"dos\r\n") and None
394 >>> open('dos', 'wb').write(b"dos\r\n") and None
395 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
395 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
396 >>> open('mac', 'wb').write(b"mac\r") and None
396 >>> open('mac', 'wb').write(b"mac\r") and None
397 $ hg add dos mixed mac
397 $ hg add dos mixed mac
398
398
399 (remove a1, to examine safety of 'eol' on removed files)
399 (remove a1, to examine safety of 'eol' on removed files)
400 $ rm a1
400 $ rm a1
401
401
402 $ fileset 'eol(dos)'
402 $ fileset 'eol(dos)'
403 dos
403 dos
404 mixed
404 mixed
405 $ fileset 'eol(unix)'
405 $ fileset 'eol(unix)'
406 .hgsub
406 .hgsub
407 .hgsubstate
407 .hgsubstate
408 b1
408 b1
409 b2
409 b2
410 c1
410 c1
411 mixed
411 mixed
412 $ fileset 'eol(mac)'
412 $ fileset 'eol(mac)'
413 mac
413 mac
414
414
415 Test safety of 'encoding' on removed files
415 Test safety of 'encoding' on removed files
416
416
417 $ fileset 'encoding("ascii")'
417 $ fileset 'encoding("ascii")'
418 .hgsub
418 .hgsub
419 .hgsubstate
419 .hgsubstate
420 1k
420 1k
421 2k
421 2k
422 b1
422 b1
423 b2
423 b2
424 b2link (symlink !)
424 b2link (symlink !)
425 bin
425 bin
426 c1
426 c1
427 dos
427 dos
428 mac
428 mac
429 mixed
429 mixed
430
430
431 Test detection of unintentional 'matchctx.existing()' invocation
431 Test detection of unintentional 'matchctx.existing()' invocation
432
432
433 $ cat > $TESTTMP/existingcaller.py <<EOF
433 $ cat > $TESTTMP/existingcaller.py <<EOF
434 > from mercurial import registrar
434 > from mercurial import registrar
435 >
435 >
436 > filesetpredicate = registrar.filesetpredicate()
436 > filesetpredicate = registrar.filesetpredicate()
437 > @filesetpredicate(b'existingcaller()', callexisting=False)
437 > @filesetpredicate(b'existingcaller()', callexisting=False)
438 > def existingcaller(mctx, x):
438 > def existingcaller(mctx, x):
439 > # this 'mctx.existing()' invocation is unintentional
439 > # this 'mctx.existing()' invocation is unintentional
440 > return [f for f in mctx.existing()]
440 > return [f for f in mctx.existing()]
441 > EOF
441 > EOF
442
442
443 $ cat >> .hg/hgrc <<EOF
443 $ cat >> .hg/hgrc <<EOF
444 > [extensions]
444 > [extensions]
445 > existingcaller = $TESTTMP/existingcaller.py
445 > existingcaller = $TESTTMP/existingcaller.py
446 > EOF
446 > EOF
447
447
448 $ fileset 'existingcaller()' 2>&1 | tail -1
448 $ fileset 'existingcaller()' 2>&1 | tail -1
449 *ProgrammingError: *unexpected existing() invocation* (glob)
449 *ProgrammingError: *unexpected existing() invocation* (glob)
450
450
451 Test 'revs(...)'
451 Test 'revs(...)'
452 ================
452 ================
453
453
454 small reminder of the repository state
454 small reminder of the repository state
455
455
456 $ hg log -G
456 $ hg log -G
457 @ changeset: 4:* (glob)
457 @ changeset: 4:* (glob)
458 | tag: tip
458 | tag: tip
459 | user: test
459 | user: test
460 | date: Thu Jan 01 00:00:00 1970 +0000
460 | date: Thu Jan 01 00:00:00 1970 +0000
461 | summary: subrepo
461 | summary: subrepo
462 |
462 |
463 o changeset: 3:* (glob)
463 o changeset: 3:* (glob)
464 |\ parent: 2:55b05bdebf36
464 |\ parent: 2:55b05bdebf36
465 | | parent: 1:* (glob)
465 | | parent: 1:* (glob)
466 | | user: test
466 | | user: test
467 | | date: Thu Jan 01 00:00:00 1970 +0000
467 | | date: Thu Jan 01 00:00:00 1970 +0000
468 | | summary: merge
468 | | summary: merge
469 | |
469 | |
470 | o changeset: 2:55b05bdebf36
470 | o changeset: 2:55b05bdebf36
471 | | parent: 0:8a9576c51c1f
471 | | parent: 0:8a9576c51c1f
472 | | user: test
472 | | user: test
473 | | date: Thu Jan 01 00:00:00 1970 +0000
473 | | date: Thu Jan 01 00:00:00 1970 +0000
474 | | summary: diverging
474 | | summary: diverging
475 | |
475 | |
476 o | changeset: 1:* (glob)
476 o | changeset: 1:* (glob)
477 |/ user: test
477 |/ user: test
478 | date: Thu Jan 01 00:00:00 1970 +0000
478 | date: Thu Jan 01 00:00:00 1970 +0000
479 | summary: manychanges
479 | summary: manychanges
480 |
480 |
481 o changeset: 0:8a9576c51c1f
481 o changeset: 0:8a9576c51c1f
482 user: test
482 user: test
483 date: Thu Jan 01 00:00:00 1970 +0000
483 date: Thu Jan 01 00:00:00 1970 +0000
484 summary: addfiles
484 summary: addfiles
485
485
486 $ hg status --change 0
486 $ hg status --change 0
487 A a1
487 A a1
488 A a2
488 A a2
489 A b1
489 A b1
490 A b2
490 A b2
491 $ hg status --change 1
491 $ hg status --change 1
492 M b2
492 M b2
493 A 1k
493 A 1k
494 A 2k
494 A 2k
495 A b2link (no-windows !)
495 A b2link (no-windows !)
496 A bin
496 A bin
497 A c1
497 A c1
498 A con.xml (no-windows !)
498 A con.xml (no-windows !)
499 R a2
499 R a2
500 $ hg status --change 2
500 $ hg status --change 2
501 M b2
501 M b2
502 $ hg status --change 3
502 $ hg status --change 3
503 M b2
503 M b2
504 A 1k
504 A 1k
505 A 2k
505 A 2k
506 A b2link (no-windows !)
506 A b2link (no-windows !)
507 A bin
507 A bin
508 A c1
508 A c1
509 A con.xml (no-windows !)
509 A con.xml (no-windows !)
510 R a2
510 R a2
511 $ hg status --change 4
511 $ hg status --change 4
512 A .hgsub
512 A .hgsub
513 A .hgsubstate
513 A .hgsubstate
514 $ hg status
514 $ hg status
515 A dos
515 A dos
516 A mac
516 A mac
517 A mixed
517 A mixed
518 R con.xml (no-windows !)
518 R con.xml (no-windows !)
519 ! a1
519 ! a1
520 ? b2.orig
520 ? b2.orig
521 ? c3
521 ? c3
522 ? unknown
522 ? unknown
523
523
524 Test files at -r0 should be filtered by files at wdir
524 Test files at -r0 should be filtered by files at wdir
525 -----------------------------------------------------
525 -----------------------------------------------------
526
526
527 $ fileset -r0 '* and revs("wdir()", *)'
527 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
528 a1
528 a1
529 b1
529 b1
530 b2
530 b2
531
531
532 Test that "revs()" work at all
532 Test that "revs()" work at all
533 ------------------------------
533 ------------------------------
534
534
535 $ fileset "revs('2', modified())"
535 $ fileset "revs('2', modified())"
536 b2
536 b2
537
537
538 Test that "revs()" work for file missing in the working copy/current context
538 Test that "revs()" work for file missing in the working copy/current context
539 ----------------------------------------------------------------------------
539 ----------------------------------------------------------------------------
540
540
541 (a2 not in working copy)
541 (a2 not in working copy)
542
542
543 $ fileset "revs('0', added())"
543 $ fileset "revs('0', added())"
544 a1
544 a1
545 a2
545 a2
546 b1
546 b1
547 b2
547 b2
548
548
549 (none of the file exist in "0")
549 (none of the file exist in "0")
550
550
551 $ fileset -r 0 "revs('4', added())"
551 $ fileset -r 0 "revs('4', added())"
552 .hgsub
552 .hgsub
553 .hgsubstate
553 .hgsubstate
554
554
555 Call with empty revset
555 Call with empty revset
556 --------------------------
556 --------------------------
557
557
558 $ fileset "revs('2-2', modified())"
558 $ fileset "revs('2-2', modified())"
559
559
560 Call with revset matching multiple revs
560 Call with revset matching multiple revs
561 ---------------------------------------
561 ---------------------------------------
562
562
563 $ fileset "revs('0+4', added())"
563 $ fileset "revs('0+4', added())"
564 .hgsub
564 .hgsub
565 .hgsubstate
565 .hgsubstate
566 a1
566 a1
567 a2
567 a2
568 b1
568 b1
569 b2
569 b2
570
570
571 overlapping set
571 overlapping set
572
572
573 $ fileset "revs('1+2', modified())"
573 $ fileset "revs('1+2', modified())"
574 b2
574 b2
575
575
576 test 'status(...)'
576 test 'status(...)'
577 =================
577 =================
578
578
579 Simple case
579 Simple case
580 -----------
580 -----------
581
581
582 $ fileset "status(3, 4, added())"
582 $ fileset "status(3, 4, added())"
583 .hgsub
583 .hgsub
584 .hgsubstate
584 .hgsubstate
585
585
586 use rev to restrict matched file
586 use rev to restrict matched file
587 -----------------------------------------
587 -----------------------------------------
588
588
589 $ hg status --removed --rev 0 --rev 1
589 $ hg status --removed --rev 0 --rev 1
590 R a2
590 R a2
591 $ fileset "status(0, 1, removed())"
591 $ fileset "status(0, 1, removed())"
592 a2
592 a2
593 $ fileset "* and status(0, 1, removed())"
593 $ fileset "tracked() and status(0, 1, removed())"
594 $ fileset -r 4 "status(0, 1, removed())"
594 $ fileset -r 4 "status(0, 1, removed())"
595 a2
595 a2
596 $ fileset -r 4 "* and status(0, 1, removed())"
596 $ fileset -r 4 "tracked() and status(0, 1, removed())"
597 $ fileset "revs('4', * and status(0, 1, removed()))"
597 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
598 $ fileset "revs('0', * and status(0, 1, removed()))"
598 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
599 a2
599 a2
600
600
601 check wdir()
601 check wdir()
602 ------------
602 ------------
603
603
604 $ hg status --removed --rev 4
604 $ hg status --removed --rev 4
605 R con.xml (no-windows !)
605 R con.xml (no-windows !)
606 $ fileset "status(4, 'wdir()', removed())"
606 $ fileset "status(4, 'wdir()', removed())"
607 con.xml (no-windows !)
607 con.xml (no-windows !)
608
608
609 $ hg status --removed --rev 2
609 $ hg status --removed --rev 2
610 R a2
610 R a2
611 $ fileset "status('2', 'wdir()', removed())"
611 $ fileset "status('2', 'wdir()', removed())"
612 a2
612 a2
613
613
614 test backward status
614 test backward status
615 --------------------
615 --------------------
616
616
617 $ hg status --removed --rev 0 --rev 4
617 $ hg status --removed --rev 0 --rev 4
618 R a2
618 R a2
619 $ hg status --added --rev 4 --rev 0
619 $ hg status --added --rev 4 --rev 0
620 A a2
620 A a2
621 $ fileset "status(4, 0, added())"
621 $ fileset "status(4, 0, added())"
622 a2
622 a2
623
623
624 test cross branch status
624 test cross branch status
625 ------------------------
625 ------------------------
626
626
627 $ hg status --added --rev 1 --rev 2
627 $ hg status --added --rev 1 --rev 2
628 A a2
628 A a2
629 $ fileset "status(1, 2, added())"
629 $ fileset "status(1, 2, added())"
630 a2
630 a2
631
631
632 test with multi revs revset
632 test with multi revs revset
633 ---------------------------
633 ---------------------------
634 $ hg status --added --rev 0:1 --rev 3:4
634 $ hg status --added --rev 0:1 --rev 3:4
635 A .hgsub
635 A .hgsub
636 A .hgsubstate
636 A .hgsubstate
637 A 1k
637 A 1k
638 A 2k
638 A 2k
639 A b2link (no-windows !)
639 A b2link (no-windows !)
640 A bin
640 A bin
641 A c1
641 A c1
642 A con.xml (no-windows !)
642 A con.xml (no-windows !)
643 $ fileset "status('0:1', '3:4', added())"
643 $ fileset "status('0:1', '3:4', added())"
644 .hgsub
644 .hgsub
645 .hgsubstate
645 .hgsubstate
646 1k
646 1k
647 2k
647 2k
648 b2link (no-windows !)
648 b2link (no-windows !)
649 bin
649 bin
650 c1
650 c1
651 con.xml (no-windows !)
651 con.xml (no-windows !)
652
652
653 tests with empty value
653 tests with empty value
654 ----------------------
654 ----------------------
655
655
656 Fully empty revset
656 Fully empty revset
657
657
658 $ fileset "status('', '4', added())"
658 $ fileset "status('', '4', added())"
659 hg: parse error: first argument to status must be a revision
659 hg: parse error: first argument to status must be a revision
660 [255]
660 [255]
661 $ fileset "status('2', '', added())"
661 $ fileset "status('2', '', added())"
662 hg: parse error: second argument to status must be a revision
662 hg: parse error: second argument to status must be a revision
663 [255]
663 [255]
664
664
665 Empty revset will error at the revset layer
665 Empty revset will error at the revset layer
666
666
667 $ fileset "status(' ', '4', added())"
667 $ fileset "status(' ', '4', added())"
668 hg: parse error at 1: not a prefix: end
668 hg: parse error at 1: not a prefix: end
669 (
669 (
670 ^ here)
670 ^ here)
671 [255]
671 [255]
672 $ fileset "status('2', ' ', added())"
672 $ fileset "status('2', ' ', added())"
673 hg: parse error at 1: not a prefix: end
673 hg: parse error at 1: not a prefix: end
674 (
674 (
675 ^ here)
675 ^ here)
676 [255]
676 [255]
General Comments 0
You need to be logged in to leave comments. Login now