##// END OF EJS Templates
fileset: remove callexisting flag and mctx.existing() (API)...
Yuya Nishihara -
r38712:5d9749c5 default
parent child Browse files
Show More
@@ -1,721 +1,694
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 getmatch(mctx, x):
143 def getmatch(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 stringmatch(mctx, x):
148 def stringmatch(mctx, x):
149 return mctx.matcher([x])
149 return mctx.matcher([x])
150
150
151 def kindpatmatch(mctx, x, y):
151 def kindpatmatch(mctx, x, y):
152 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
152 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
153 _("pattern must be a string")))
153 _("pattern must be a string")))
154
154
155 def andmatch(mctx, x, y):
155 def andmatch(mctx, x, y):
156 xm = getmatch(mctx, x)
156 xm = getmatch(mctx, x)
157 ym = getmatch(mctx, y)
157 ym = getmatch(mctx, y)
158 return matchmod.intersectmatchers(xm, ym)
158 return matchmod.intersectmatchers(xm, ym)
159
159
160 def ormatch(mctx, x, y):
160 def ormatch(mctx, x, y):
161 xm = getmatch(mctx, x)
161 xm = getmatch(mctx, x)
162 ym = getmatch(mctx, y)
162 ym = getmatch(mctx, y)
163 return matchmod.unionmatcher([xm, ym])
163 return matchmod.unionmatcher([xm, ym])
164
164
165 def notmatch(mctx, x):
165 def notmatch(mctx, x):
166 m = getmatch(mctx, x)
166 m = getmatch(mctx, x)
167 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
167 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
168
168
169 def minusmatch(mctx, x, y):
169 def minusmatch(mctx, x, y):
170 xm = getmatch(mctx, x)
170 xm = getmatch(mctx, x)
171 ym = getmatch(mctx, y)
171 ym = getmatch(mctx, y)
172 return matchmod.differencematcher(xm, ym)
172 return matchmod.differencematcher(xm, ym)
173
173
174 def negatematch(mctx, x):
174 def negatematch(mctx, x):
175 raise error.ParseError(_("can't use negate operator in this context"))
175 raise error.ParseError(_("can't use negate operator in this context"))
176
176
177 def listmatch(mctx, x, y):
177 def listmatch(mctx, x, y):
178 raise error.ParseError(_("can't use a list in this context"),
178 raise error.ParseError(_("can't use a list in this context"),
179 hint=_('see hg help "filesets.x or y"'))
179 hint=_('see hg help "filesets.x or y"'))
180
180
181 def func(mctx, a, b):
181 def func(mctx, a, b):
182 funcname = getsymbol(a)
182 funcname = getsymbol(a)
183 if funcname in symbols:
183 if funcname in symbols:
184 enabled = mctx._existingenabled
184 return symbols[funcname](mctx, b)
185 mctx._existingenabled = funcname in _existingcallers
186 try:
187 return symbols[funcname](mctx, b)
188 finally:
189 mctx._existingenabled = enabled
190
185
191 keep = lambda fn: getattr(fn, '__doc__', None) is not None
186 keep = lambda fn: getattr(fn, '__doc__', None) is not None
192
187
193 syms = [s for (s, fn) in symbols.items() if keep(fn)]
188 syms = [s for (s, fn) in symbols.items() if keep(fn)]
194 raise error.UnknownIdentifier(funcname, syms)
189 raise error.UnknownIdentifier(funcname, syms)
195
190
196 # symbols are callable like:
191 # symbols are callable like:
197 # fun(mctx, x)
192 # fun(mctx, x)
198 # with:
193 # with:
199 # mctx - current matchctx instance
194 # mctx - current matchctx instance
200 # x - argument in tree form
195 # x - argument in tree form
201 symbols = {}
196 symbols = {}
202
197
203 # filesets using matchctx.status()
198 # filesets using matchctx.status()
204 _statuscallers = set()
199 _statuscallers = set()
205
200
206 # filesets using matchctx.existing()
207 _existingcallers = set()
208
209 predicate = registrar.filesetpredicate()
201 predicate = registrar.filesetpredicate()
210
202
211 @predicate('modified()', callstatus=True)
203 @predicate('modified()', callstatus=True)
212 def modified(mctx, x):
204 def modified(mctx, x):
213 """File that is modified according to :hg:`status`.
205 """File that is modified according to :hg:`status`.
214 """
206 """
215 # i18n: "modified" is a keyword
207 # i18n: "modified" is a keyword
216 getargs(x, 0, 0, _("modified takes no arguments"))
208 getargs(x, 0, 0, _("modified takes no arguments"))
217 s = set(mctx.status().modified)
209 s = set(mctx.status().modified)
218 return mctx.predicate(s.__contains__, predrepr='modified')
210 return mctx.predicate(s.__contains__, predrepr='modified')
219
211
220 @predicate('added()', callstatus=True)
212 @predicate('added()', callstatus=True)
221 def added(mctx, x):
213 def added(mctx, x):
222 """File that is added according to :hg:`status`.
214 """File that is added according to :hg:`status`.
223 """
215 """
224 # i18n: "added" is a keyword
216 # i18n: "added" is a keyword
225 getargs(x, 0, 0, _("added takes no arguments"))
217 getargs(x, 0, 0, _("added takes no arguments"))
226 s = set(mctx.status().added)
218 s = set(mctx.status().added)
227 return mctx.predicate(s.__contains__, predrepr='added')
219 return mctx.predicate(s.__contains__, predrepr='added')
228
220
229 @predicate('removed()', callstatus=True)
221 @predicate('removed()', callstatus=True)
230 def removed(mctx, x):
222 def removed(mctx, x):
231 """File that is removed according to :hg:`status`.
223 """File that is removed according to :hg:`status`.
232 """
224 """
233 # i18n: "removed" is a keyword
225 # i18n: "removed" is a keyword
234 getargs(x, 0, 0, _("removed takes no arguments"))
226 getargs(x, 0, 0, _("removed takes no arguments"))
235 s = set(mctx.status().removed)
227 s = set(mctx.status().removed)
236 return mctx.predicate(s.__contains__, predrepr='removed')
228 return mctx.predicate(s.__contains__, predrepr='removed')
237
229
238 @predicate('deleted()', callstatus=True)
230 @predicate('deleted()', callstatus=True)
239 def deleted(mctx, x):
231 def deleted(mctx, x):
240 """Alias for ``missing()``.
232 """Alias for ``missing()``.
241 """
233 """
242 # i18n: "deleted" is a keyword
234 # i18n: "deleted" is a keyword
243 getargs(x, 0, 0, _("deleted takes no arguments"))
235 getargs(x, 0, 0, _("deleted takes no arguments"))
244 s = set(mctx.status().deleted)
236 s = set(mctx.status().deleted)
245 return mctx.predicate(s.__contains__, predrepr='deleted')
237 return mctx.predicate(s.__contains__, predrepr='deleted')
246
238
247 @predicate('missing()', callstatus=True)
239 @predicate('missing()', callstatus=True)
248 def missing(mctx, x):
240 def missing(mctx, x):
249 """File that is missing according to :hg:`status`.
241 """File that is missing according to :hg:`status`.
250 """
242 """
251 # i18n: "missing" is a keyword
243 # i18n: "missing" is a keyword
252 getargs(x, 0, 0, _("missing takes no arguments"))
244 getargs(x, 0, 0, _("missing takes no arguments"))
253 s = set(mctx.status().deleted)
245 s = set(mctx.status().deleted)
254 return mctx.predicate(s.__contains__, predrepr='deleted')
246 return mctx.predicate(s.__contains__, predrepr='deleted')
255
247
256 @predicate('unknown()', callstatus=True)
248 @predicate('unknown()', callstatus=True)
257 def unknown(mctx, x):
249 def unknown(mctx, x):
258 """File that is unknown according to :hg:`status`."""
250 """File that is unknown according to :hg:`status`."""
259 # i18n: "unknown" is a keyword
251 # i18n: "unknown" is a keyword
260 getargs(x, 0, 0, _("unknown takes no arguments"))
252 getargs(x, 0, 0, _("unknown takes no arguments"))
261 s = set(mctx.status().unknown)
253 s = set(mctx.status().unknown)
262 return mctx.predicate(s.__contains__, predrepr='unknown')
254 return mctx.predicate(s.__contains__, predrepr='unknown')
263
255
264 @predicate('ignored()', callstatus=True)
256 @predicate('ignored()', callstatus=True)
265 def ignored(mctx, x):
257 def ignored(mctx, x):
266 """File that is ignored according to :hg:`status`."""
258 """File that is ignored according to :hg:`status`."""
267 # i18n: "ignored" is a keyword
259 # i18n: "ignored" is a keyword
268 getargs(x, 0, 0, _("ignored takes no arguments"))
260 getargs(x, 0, 0, _("ignored takes no arguments"))
269 s = set(mctx.status().ignored)
261 s = set(mctx.status().ignored)
270 return mctx.predicate(s.__contains__, predrepr='ignored')
262 return mctx.predicate(s.__contains__, predrepr='ignored')
271
263
272 @predicate('clean()', callstatus=True)
264 @predicate('clean()', callstatus=True)
273 def clean(mctx, x):
265 def clean(mctx, x):
274 """File that is clean according to :hg:`status`.
266 """File that is clean according to :hg:`status`.
275 """
267 """
276 # i18n: "clean" is a keyword
268 # i18n: "clean" is a keyword
277 getargs(x, 0, 0, _("clean takes no arguments"))
269 getargs(x, 0, 0, _("clean takes no arguments"))
278 s = set(mctx.status().clean)
270 s = set(mctx.status().clean)
279 return mctx.predicate(s.__contains__, predrepr='clean')
271 return mctx.predicate(s.__contains__, predrepr='clean')
280
272
281 @predicate('tracked()')
273 @predicate('tracked()')
282 def tracked(mctx, x):
274 def tracked(mctx, x):
283 """File that is under Mercurial control."""
275 """File that is under Mercurial control."""
284 # i18n: "tracked" is a keyword
276 # i18n: "tracked" is a keyword
285 getargs(x, 0, 0, _("tracked takes no arguments"))
277 getargs(x, 0, 0, _("tracked takes no arguments"))
286 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
278 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
287
279
288 @predicate('binary()', callexisting=True)
280 @predicate('binary()')
289 def binary(mctx, x):
281 def binary(mctx, x):
290 """File that appears to be binary (contains NUL bytes).
282 """File that appears to be binary (contains NUL bytes).
291 """
283 """
292 # i18n: "binary" is a keyword
284 # i18n: "binary" is a keyword
293 getargs(x, 0, 0, _("binary takes no arguments"))
285 getargs(x, 0, 0, _("binary takes no arguments"))
294 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
286 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
295 predrepr='binary', cache=True)
287 predrepr='binary', cache=True)
296
288
297 @predicate('exec()', callexisting=True)
289 @predicate('exec()')
298 def exec_(mctx, x):
290 def exec_(mctx, x):
299 """File that is marked as executable.
291 """File that is marked as executable.
300 """
292 """
301 # i18n: "exec" is a keyword
293 # i18n: "exec" is a keyword
302 getargs(x, 0, 0, _("exec takes no arguments"))
294 getargs(x, 0, 0, _("exec takes no arguments"))
303 ctx = mctx.ctx
295 ctx = mctx.ctx
304 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
296 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
305
297
306 @predicate('symlink()', callexisting=True)
298 @predicate('symlink()')
307 def symlink(mctx, x):
299 def symlink(mctx, x):
308 """File that is marked as a symlink.
300 """File that is marked as a symlink.
309 """
301 """
310 # i18n: "symlink" is a keyword
302 # i18n: "symlink" is a keyword
311 getargs(x, 0, 0, _("symlink takes no arguments"))
303 getargs(x, 0, 0, _("symlink takes no arguments"))
312 ctx = mctx.ctx
304 ctx = mctx.ctx
313 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
305 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
314
306
315 @predicate('resolved()')
307 @predicate('resolved()')
316 def resolved(mctx, x):
308 def resolved(mctx, x):
317 """File that is marked resolved according to :hg:`resolve -l`.
309 """File that is marked resolved according to :hg:`resolve -l`.
318 """
310 """
319 # i18n: "resolved" is a keyword
311 # i18n: "resolved" is a keyword
320 getargs(x, 0, 0, _("resolved takes no arguments"))
312 getargs(x, 0, 0, _("resolved takes no arguments"))
321 if mctx.ctx.rev() is not None:
313 if mctx.ctx.rev() is not None:
322 return mctx.never()
314 return mctx.never()
323 ms = merge.mergestate.read(mctx.ctx.repo())
315 ms = merge.mergestate.read(mctx.ctx.repo())
324 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
316 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
325 predrepr='resolved')
317 predrepr='resolved')
326
318
327 @predicate('unresolved()')
319 @predicate('unresolved()')
328 def unresolved(mctx, x):
320 def unresolved(mctx, x):
329 """File that is marked unresolved according to :hg:`resolve -l`.
321 """File that is marked unresolved according to :hg:`resolve -l`.
330 """
322 """
331 # i18n: "unresolved" is a keyword
323 # i18n: "unresolved" is a keyword
332 getargs(x, 0, 0, _("unresolved takes no arguments"))
324 getargs(x, 0, 0, _("unresolved takes no arguments"))
333 if mctx.ctx.rev() is not None:
325 if mctx.ctx.rev() is not None:
334 return mctx.never()
326 return mctx.never()
335 ms = merge.mergestate.read(mctx.ctx.repo())
327 ms = merge.mergestate.read(mctx.ctx.repo())
336 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
328 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
337 predrepr='unresolved')
329 predrepr='unresolved')
338
330
339 @predicate('hgignore()')
331 @predicate('hgignore()')
340 def hgignore(mctx, x):
332 def hgignore(mctx, x):
341 """File that matches the active .hgignore pattern.
333 """File that matches the active .hgignore pattern.
342 """
334 """
343 # i18n: "hgignore" is a keyword
335 # i18n: "hgignore" is a keyword
344 getargs(x, 0, 0, _("hgignore takes no arguments"))
336 getargs(x, 0, 0, _("hgignore takes no arguments"))
345 return mctx.ctx.repo().dirstate._ignore
337 return mctx.ctx.repo().dirstate._ignore
346
338
347 @predicate('portable()')
339 @predicate('portable()')
348 def portable(mctx, x):
340 def portable(mctx, x):
349 """File that has a portable name. (This doesn't include filenames with case
341 """File that has a portable name. (This doesn't include filenames with case
350 collisions.)
342 collisions.)
351 """
343 """
352 # i18n: "portable" is a keyword
344 # i18n: "portable" is a keyword
353 getargs(x, 0, 0, _("portable takes no arguments"))
345 getargs(x, 0, 0, _("portable takes no arguments"))
354 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
346 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
355 predrepr='portable')
347 predrepr='portable')
356
348
357 @predicate('grep(regex)', callexisting=True)
349 @predicate('grep(regex)')
358 def grep(mctx, x):
350 def grep(mctx, x):
359 """File contains the given regular expression.
351 """File contains the given regular expression.
360 """
352 """
361 try:
353 try:
362 # i18n: "grep" is a keyword
354 # i18n: "grep" is a keyword
363 r = re.compile(getstring(x, _("grep requires a pattern")))
355 r = re.compile(getstring(x, _("grep requires a pattern")))
364 except re.error as e:
356 except re.error as e:
365 raise error.ParseError(_('invalid match pattern: %s') %
357 raise error.ParseError(_('invalid match pattern: %s') %
366 stringutil.forcebytestr(e))
358 stringutil.forcebytestr(e))
367 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
359 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
368 predrepr=('grep(%r)', r.pattern), cache=True)
360 predrepr=('grep(%r)', r.pattern), cache=True)
369
361
370 def _sizetomax(s):
362 def _sizetomax(s):
371 try:
363 try:
372 s = s.strip().lower()
364 s = s.strip().lower()
373 for k, v in util._sizeunits:
365 for k, v in util._sizeunits:
374 if s.endswith(k):
366 if s.endswith(k):
375 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
367 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
376 n = s[:-len(k)]
368 n = s[:-len(k)]
377 inc = 1.0
369 inc = 1.0
378 if "." in n:
370 if "." in n:
379 inc /= 10 ** len(n.split(".")[1])
371 inc /= 10 ** len(n.split(".")[1])
380 return int((float(n) + inc) * v) - 1
372 return int((float(n) + inc) * v) - 1
381 # no extension, this is a precise value
373 # no extension, this is a precise value
382 return int(s)
374 return int(s)
383 except ValueError:
375 except ValueError:
384 raise error.ParseError(_("couldn't parse size: %s") % s)
376 raise error.ParseError(_("couldn't parse size: %s") % s)
385
377
386 def sizematcher(expr):
378 def sizematcher(expr):
387 """Return a function(size) -> bool from the ``size()`` expression"""
379 """Return a function(size) -> bool from the ``size()`` expression"""
388 expr = expr.strip()
380 expr = expr.strip()
389 if '-' in expr: # do we have a range?
381 if '-' in expr: # do we have a range?
390 a, b = expr.split('-', 1)
382 a, b = expr.split('-', 1)
391 a = util.sizetoint(a)
383 a = util.sizetoint(a)
392 b = util.sizetoint(b)
384 b = util.sizetoint(b)
393 return lambda x: x >= a and x <= b
385 return lambda x: x >= a and x <= b
394 elif expr.startswith("<="):
386 elif expr.startswith("<="):
395 a = util.sizetoint(expr[2:])
387 a = util.sizetoint(expr[2:])
396 return lambda x: x <= a
388 return lambda x: x <= a
397 elif expr.startswith("<"):
389 elif expr.startswith("<"):
398 a = util.sizetoint(expr[1:])
390 a = util.sizetoint(expr[1:])
399 return lambda x: x < a
391 return lambda x: x < a
400 elif expr.startswith(">="):
392 elif expr.startswith(">="):
401 a = util.sizetoint(expr[2:])
393 a = util.sizetoint(expr[2:])
402 return lambda x: x >= a
394 return lambda x: x >= a
403 elif expr.startswith(">"):
395 elif expr.startswith(">"):
404 a = util.sizetoint(expr[1:])
396 a = util.sizetoint(expr[1:])
405 return lambda x: x > a
397 return lambda x: x > a
406 else:
398 else:
407 a = util.sizetoint(expr)
399 a = util.sizetoint(expr)
408 b = _sizetomax(expr)
400 b = _sizetomax(expr)
409 return lambda x: x >= a and x <= b
401 return lambda x: x >= a and x <= b
410
402
411 @predicate('size(expression)', callexisting=True)
403 @predicate('size(expression)')
412 def size(mctx, x):
404 def size(mctx, x):
413 """File size matches the given expression. Examples:
405 """File size matches the given expression. Examples:
414
406
415 - size('1k') - files from 1024 to 2047 bytes
407 - size('1k') - files from 1024 to 2047 bytes
416 - size('< 20k') - files less than 20480 bytes
408 - size('< 20k') - files less than 20480 bytes
417 - size('>= .5MB') - files at least 524288 bytes
409 - size('>= .5MB') - files at least 524288 bytes
418 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
410 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
419 """
411 """
420 # i18n: "size" is a keyword
412 # i18n: "size" is a keyword
421 expr = getstring(x, _("size requires an expression"))
413 expr = getstring(x, _("size requires an expression"))
422 m = sizematcher(expr)
414 m = sizematcher(expr)
423 return mctx.fpredicate(lambda fctx: m(fctx.size()),
415 return mctx.fpredicate(lambda fctx: m(fctx.size()),
424 predrepr=('size(%r)', expr), cache=True)
416 predrepr=('size(%r)', expr), cache=True)
425
417
426 @predicate('encoding(name)', callexisting=True)
418 @predicate('encoding(name)')
427 def encoding(mctx, x):
419 def encoding(mctx, x):
428 """File can be successfully decoded with the given character
420 """File can be successfully decoded with the given character
429 encoding. May not be useful for encodings other than ASCII and
421 encoding. May not be useful for encodings other than ASCII and
430 UTF-8.
422 UTF-8.
431 """
423 """
432
424
433 # i18n: "encoding" is a keyword
425 # i18n: "encoding" is a keyword
434 enc = getstring(x, _("encoding requires an encoding name"))
426 enc = getstring(x, _("encoding requires an encoding name"))
435
427
436 def encp(fctx):
428 def encp(fctx):
437 d = fctx.data()
429 d = fctx.data()
438 try:
430 try:
439 d.decode(pycompat.sysstr(enc))
431 d.decode(pycompat.sysstr(enc))
440 return True
432 return True
441 except LookupError:
433 except LookupError:
442 raise error.Abort(_("unknown encoding '%s'") % enc)
434 raise error.Abort(_("unknown encoding '%s'") % enc)
443 except UnicodeDecodeError:
435 except UnicodeDecodeError:
444 return False
436 return False
445
437
446 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
438 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
447
439
448 @predicate('eol(style)', callexisting=True)
440 @predicate('eol(style)')
449 def eol(mctx, x):
441 def eol(mctx, x):
450 """File contains newlines of the given style (dos, unix, mac). Binary
442 """File contains newlines of the given style (dos, unix, mac). Binary
451 files are excluded, files with mixed line endings match multiple
443 files are excluded, files with mixed line endings match multiple
452 styles.
444 styles.
453 """
445 """
454
446
455 # i18n: "eol" is a keyword
447 # i18n: "eol" is a keyword
456 enc = getstring(x, _("eol requires a style name"))
448 enc = getstring(x, _("eol requires a style name"))
457
449
458 def eolp(fctx):
450 def eolp(fctx):
459 if fctx.isbinary():
451 if fctx.isbinary():
460 return False
452 return False
461 d = fctx.data()
453 d = fctx.data()
462 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
454 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
463 return True
455 return True
464 elif enc == 'unix' and re.search('(?<!\r)\n', d):
456 elif enc == 'unix' and re.search('(?<!\r)\n', d):
465 return True
457 return True
466 elif enc == 'mac' and re.search('\r(?!\n)', d):
458 elif enc == 'mac' and re.search('\r(?!\n)', d):
467 return True
459 return True
468 return False
460 return False
469 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
461 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
470
462
471 @predicate('copied()')
463 @predicate('copied()')
472 def copied(mctx, x):
464 def copied(mctx, x):
473 """File that is recorded as being copied.
465 """File that is recorded as being copied.
474 """
466 """
475 # i18n: "copied" is a keyword
467 # i18n: "copied" is a keyword
476 getargs(x, 0, 0, _("copied takes no arguments"))
468 getargs(x, 0, 0, _("copied takes no arguments"))
477 def copiedp(fctx):
469 def copiedp(fctx):
478 p = fctx.parents()
470 p = fctx.parents()
479 return p and p[0].path() != fctx.path()
471 return p and p[0].path() != fctx.path()
480 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
472 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
481
473
482 @predicate('revs(revs, pattern)')
474 @predicate('revs(revs, pattern)')
483 def revs(mctx, x):
475 def revs(mctx, x):
484 """Evaluate set in the specified revisions. If the revset match multiple
476 """Evaluate set in the specified revisions. If the revset match multiple
485 revs, this will return file matching pattern in any of the revision.
477 revs, this will return file matching pattern in any of the revision.
486 """
478 """
487 # i18n: "revs" is a keyword
479 # i18n: "revs" is a keyword
488 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
480 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
489 # i18n: "revs" is a keyword
481 # i18n: "revs" is a keyword
490 revspec = getstring(r, _("first argument to revs must be a revision"))
482 revspec = getstring(r, _("first argument to revs must be a revision"))
491 repo = mctx.ctx.repo()
483 repo = mctx.ctx.repo()
492 revs = scmutil.revrange(repo, [revspec])
484 revs = scmutil.revrange(repo, [revspec])
493
485
494 matchers = []
486 matchers = []
495 for r in revs:
487 for r in revs:
496 ctx = repo[r]
488 ctx = repo[r]
497 matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
489 matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
498 if not matchers:
490 if not matchers:
499 return mctx.never()
491 return mctx.never()
500 if len(matchers) == 1:
492 if len(matchers) == 1:
501 return matchers[0]
493 return matchers[0]
502 return matchmod.unionmatcher(matchers)
494 return matchmod.unionmatcher(matchers)
503
495
504 @predicate('status(base, rev, pattern)')
496 @predicate('status(base, rev, pattern)')
505 def status(mctx, x):
497 def status(mctx, x):
506 """Evaluate predicate using status change between ``base`` and
498 """Evaluate predicate using status change between ``base`` and
507 ``rev``. Examples:
499 ``rev``. Examples:
508
500
509 - ``status(3, 7, added())`` - matches files added from "3" to "7"
501 - ``status(3, 7, added())`` - matches files added from "3" to "7"
510 """
502 """
511 repo = mctx.ctx.repo()
503 repo = mctx.ctx.repo()
512 # i18n: "status" is a keyword
504 # i18n: "status" is a keyword
513 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
505 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
514 # i18n: "status" is a keyword
506 # i18n: "status" is a keyword
515 baseerr = _("first argument to status must be a revision")
507 baseerr = _("first argument to status must be a revision")
516 baserevspec = getstring(b, baseerr)
508 baserevspec = getstring(b, baseerr)
517 if not baserevspec:
509 if not baserevspec:
518 raise error.ParseError(baseerr)
510 raise error.ParseError(baseerr)
519 reverr = _("second argument to status must be a revision")
511 reverr = _("second argument to status must be a revision")
520 revspec = getstring(r, reverr)
512 revspec = getstring(r, reverr)
521 if not revspec:
513 if not revspec:
522 raise error.ParseError(reverr)
514 raise error.ParseError(reverr)
523 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
515 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
524 return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
516 return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
525
517
526 @predicate('subrepo([pattern])')
518 @predicate('subrepo([pattern])')
527 def subrepo(mctx, x):
519 def subrepo(mctx, x):
528 """Subrepositories whose paths match the given pattern.
520 """Subrepositories whose paths match the given pattern.
529 """
521 """
530 # i18n: "subrepo" is a keyword
522 # i18n: "subrepo" is a keyword
531 getargs(x, 0, 1, _("subrepo takes at most one argument"))
523 getargs(x, 0, 1, _("subrepo takes at most one argument"))
532 ctx = mctx.ctx
524 ctx = mctx.ctx
533 sstate = ctx.substate
525 sstate = ctx.substate
534 if x:
526 if x:
535 pat = getpattern(x, matchmod.allpatternkinds,
527 pat = getpattern(x, matchmod.allpatternkinds,
536 # i18n: "subrepo" is a keyword
528 # i18n: "subrepo" is a keyword
537 _("subrepo requires a pattern or no arguments"))
529 _("subrepo requires a pattern or no arguments"))
538 fast = not matchmod.patkind(pat)
530 fast = not matchmod.patkind(pat)
539 if fast:
531 if fast:
540 def m(s):
532 def m(s):
541 return (s == pat)
533 return (s == pat)
542 else:
534 else:
543 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
535 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
544 return mctx.predicate(lambda f: f in sstate and m(f),
536 return mctx.predicate(lambda f: f in sstate and m(f),
545 predrepr=('subrepo(%r)', pat))
537 predrepr=('subrepo(%r)', pat))
546 else:
538 else:
547 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
539 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
548
540
549 methods = {
541 methods = {
550 'string': stringmatch,
542 'string': stringmatch,
551 'symbol': stringmatch,
543 'symbol': stringmatch,
552 'kindpat': kindpatmatch,
544 'kindpat': kindpatmatch,
553 'and': andmatch,
545 'and': andmatch,
554 'or': ormatch,
546 'or': ormatch,
555 'minus': minusmatch,
547 'minus': minusmatch,
556 'negate': negatematch,
548 'negate': negatematch,
557 'list': listmatch,
549 'list': listmatch,
558 'group': getmatch,
550 'group': getmatch,
559 'not': notmatch,
551 'not': notmatch,
560 'func': func,
552 'func': func,
561 }
553 }
562
554
563 class matchctx(object):
555 class matchctx(object):
564 def __init__(self, ctx, subset, status=None, badfn=None):
556 def __init__(self, ctx, subset, status=None, badfn=None):
565 self.ctx = ctx
557 self.ctx = ctx
566 self.subset = subset
558 self.subset = subset
567 self._status = status
559 self._status = status
568 self._badfn = badfn
560 self._badfn = badfn
569 self._existingenabled = False
561
570 def status(self):
562 def status(self):
571 return self._status
563 return self._status
572
564
573 def matcher(self, patterns):
565 def matcher(self, patterns):
574 return self.ctx.match(patterns, badfn=self._badfn)
566 return self.ctx.match(patterns, badfn=self._badfn)
575
567
576 def predicate(self, predfn, predrepr=None, cache=False):
568 def predicate(self, predfn, predrepr=None, cache=False):
577 """Create a matcher to select files by predfn(filename)"""
569 """Create a matcher to select files by predfn(filename)"""
578 if cache:
570 if cache:
579 predfn = util.cachefunc(predfn)
571 predfn = util.cachefunc(predfn)
580 repo = self.ctx.repo()
572 repo = self.ctx.repo()
581 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
573 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
582 predrepr=predrepr, badfn=self._badfn)
574 predrepr=predrepr, badfn=self._badfn)
583
575
584 def fpredicate(self, predfn, predrepr=None, cache=False):
576 def fpredicate(self, predfn, predrepr=None, cache=False):
585 """Create a matcher to select files by predfn(fctx) at the current
577 """Create a matcher to select files by predfn(fctx) at the current
586 revision
578 revision
587
579
588 Missing files are ignored.
580 Missing files are ignored.
589 """
581 """
590 ctx = self.ctx
582 ctx = self.ctx
591 if ctx.rev() is None:
583 if ctx.rev() is None:
592 def fctxpredfn(f):
584 def fctxpredfn(f):
593 try:
585 try:
594 fctx = ctx[f]
586 fctx = ctx[f]
595 except error.LookupError:
587 except error.LookupError:
596 return False
588 return False
597 try:
589 try:
598 fctx.audit()
590 fctx.audit()
599 except error.Abort:
591 except error.Abort:
600 return False
592 return False
601 try:
593 try:
602 return predfn(fctx)
594 return predfn(fctx)
603 except (IOError, OSError) as e:
595 except (IOError, OSError) as e:
604 if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EISDIR):
596 if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EISDIR):
605 return False
597 return False
606 raise
598 raise
607 else:
599 else:
608 def fctxpredfn(f):
600 def fctxpredfn(f):
609 try:
601 try:
610 fctx = ctx[f]
602 fctx = ctx[f]
611 except error.LookupError:
603 except error.LookupError:
612 return False
604 return False
613 return predfn(fctx)
605 return predfn(fctx)
614 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
606 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
615
607
616 def never(self):
608 def never(self):
617 """Create a matcher to select nothing"""
609 """Create a matcher to select nothing"""
618 repo = self.ctx.repo()
610 repo = self.ctx.repo()
619 return matchmod.nevermatcher(repo.root, repo.getcwd(),
611 return matchmod.nevermatcher(repo.root, repo.getcwd(),
620 badfn=self._badfn)
612 badfn=self._badfn)
621
613
622 def filter(self, files):
614 def filter(self, files):
623 return [f for f in files if f in self.subset]
615 return [f for f in files if f in self.subset]
624 def existing(self):
625 if not self._existingenabled:
626 raise error.ProgrammingError('unexpected existing() invocation')
627 if self._status is not None:
628 removed = set(self._status[3])
629 unknown = set(self._status[4] + self._status[5])
630 else:
631 removed = set()
632 unknown = set()
633 return (f for f in self.subset
634 if (f in self.ctx and f not in removed) or f in unknown)
635
616
636 def switch(self, ctx, status=None):
617 def switch(self, ctx, status=None):
637 subset = self.filter(_buildsubset(ctx, status))
618 subset = self.filter(_buildsubset(ctx, status))
638 return matchctx(ctx, subset, status, self._badfn)
619 return matchctx(ctx, subset, status, self._badfn)
639
620
640 class fullmatchctx(matchctx):
621 class fullmatchctx(matchctx):
641 """A match context where any files in any revisions should be valid"""
622 """A match context where any files in any revisions should be valid"""
642
623
643 def __init__(self, ctx, status=None, badfn=None):
624 def __init__(self, ctx, status=None, badfn=None):
644 subset = _buildsubset(ctx, status)
625 subset = _buildsubset(ctx, status)
645 super(fullmatchctx, self).__init__(ctx, subset, status, badfn)
626 super(fullmatchctx, self).__init__(ctx, subset, status, badfn)
646 def switch(self, ctx, status=None):
627 def switch(self, ctx, status=None):
647 return fullmatchctx(ctx, status, self._badfn)
628 return fullmatchctx(ctx, status, self._badfn)
648
629
649 # filesets using matchctx.switch()
630 # filesets using matchctx.switch()
650 _switchcallers = [
631 _switchcallers = [
651 'revs',
632 'revs',
652 'status',
633 'status',
653 ]
634 ]
654
635
655 def _intree(funcs, tree):
636 def _intree(funcs, tree):
656 if isinstance(tree, tuple):
637 if isinstance(tree, tuple):
657 if tree[0] == 'func' and tree[1][0] == 'symbol':
638 if tree[0] == 'func' and tree[1][0] == 'symbol':
658 if tree[1][1] in funcs:
639 if tree[1][1] in funcs:
659 return True
640 return True
660 if tree[1][1] in _switchcallers:
641 if tree[1][1] in _switchcallers:
661 # arguments won't be evaluated in the current context
642 # arguments won't be evaluated in the current context
662 return False
643 return False
663 for s in tree[1:]:
644 for s in tree[1:]:
664 if _intree(funcs, s):
645 if _intree(funcs, s):
665 return True
646 return True
666 return False
647 return False
667
648
668 def _buildsubset(ctx, status):
649 def _buildsubset(ctx, status):
669 if status:
650 if status:
670 subset = []
651 subset = []
671 for c in status:
652 for c in status:
672 subset.extend(c)
653 subset.extend(c)
673 return subset
654 return subset
674 else:
655 else:
675 return list(ctx.walk(ctx.match([])))
656 return list(ctx.walk(ctx.match([])))
676
657
677 def match(ctx, expr, badfn=None):
658 def match(ctx, expr, badfn=None):
678 """Create a matcher for a single fileset expression"""
659 """Create a matcher for a single fileset expression"""
679 tree = parse(expr)
660 tree = parse(expr)
680 mctx = fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
661 mctx = fullmatchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
681 return getmatch(mctx, tree)
662 return getmatch(mctx, tree)
682
663
683 def _buildstatus(ctx, tree, basectx=None):
664 def _buildstatus(ctx, tree, basectx=None):
684 # do we need status info?
665 # do we need status info?
685
666
686 # temporaty boolean to simplify the next conditional
667 if _intree(_statuscallers, tree):
687 purewdir = ctx.rev() is None and basectx is None
688
689 if (_intree(_statuscallers, tree) or
690 # Using matchctx.existing() on a workingctx requires us to check
691 # for deleted files.
692 (purewdir and _intree(_existingcallers, tree))):
693 unknown = _intree(['unknown'], tree)
668 unknown = _intree(['unknown'], tree)
694 ignored = _intree(['ignored'], tree)
669 ignored = _intree(['ignored'], tree)
695
670
696 r = ctx.repo()
671 r = ctx.repo()
697 if basectx is None:
672 if basectx is None:
698 basectx = ctx.p1()
673 basectx = ctx.p1()
699 return r.status(basectx, ctx,
674 return r.status(basectx, ctx,
700 unknown=unknown, ignored=ignored, clean=True)
675 unknown=unknown, ignored=ignored, clean=True)
701 else:
676 else:
702 return None
677 return None
703
678
704 def prettyformat(tree):
679 def prettyformat(tree):
705 return parser.prettyformat(tree, ('string', 'symbol'))
680 return parser.prettyformat(tree, ('string', 'symbol'))
706
681
707 def loadpredicate(ui, extname, registrarobj):
682 def loadpredicate(ui, extname, registrarobj):
708 """Load fileset predicates from specified registrarobj
683 """Load fileset predicates from specified registrarobj
709 """
684 """
710 for name, func in registrarobj._table.iteritems():
685 for name, func in registrarobj._table.iteritems():
711 symbols[name] = func
686 symbols[name] = func
712 if func._callstatus:
687 if func._callstatus:
713 _statuscallers.add(name)
688 _statuscallers.add(name)
714 if func._callexisting:
715 _existingcallers.add(name)
716
689
717 # load built-in predicates explicitly to setup _statuscallers/_existingcallers
690 # load built-in predicates explicitly to setup _statuscallers
718 loadpredicate(None, None, predicate)
691 loadpredicate(None, None, predicate)
719
692
720 # tell hggettext to extract docstrings from these functions:
693 # tell hggettext to extract docstrings from these functions:
721 i18nfunctions = symbols.values()
694 i18nfunctions = symbols.values()
@@ -1,444 +1,439
1 # registrar.py - utilities to register function for specific purpose
1 # registrar.py - utilities to register function for specific purpose
2 #
2 #
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
3 # Copyright FUJIWARA Katsunori <foozy@lares.dti.ne.jp> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 from . import (
10 from . import (
11 configitems,
11 configitems,
12 error,
12 error,
13 pycompat,
13 pycompat,
14 util,
14 util,
15 )
15 )
16
16
17 # unlike the other registered items, config options are neither functions or
17 # unlike the other registered items, config options are neither functions or
18 # classes. Registering the option is just small function call.
18 # classes. Registering the option is just small function call.
19 #
19 #
20 # We still add the official API to the registrar module for consistency with
20 # We still add the official API to the registrar module for consistency with
21 # the other items extensions want might to register.
21 # the other items extensions want might to register.
22 configitem = configitems.getitemregister
22 configitem = configitems.getitemregister
23
23
24 class _funcregistrarbase(object):
24 class _funcregistrarbase(object):
25 """Base of decorator to register a function for specific purpose
25 """Base of decorator to register a function for specific purpose
26
26
27 This decorator stores decorated functions into own dict 'table'.
27 This decorator stores decorated functions into own dict 'table'.
28
28
29 The least derived class can be defined by overriding 'formatdoc',
29 The least derived class can be defined by overriding 'formatdoc',
30 for example::
30 for example::
31
31
32 class keyword(_funcregistrarbase):
32 class keyword(_funcregistrarbase):
33 _docformat = ":%s: %s"
33 _docformat = ":%s: %s"
34
34
35 This should be used as below:
35 This should be used as below:
36
36
37 keyword = registrar.keyword()
37 keyword = registrar.keyword()
38
38
39 @keyword('bar')
39 @keyword('bar')
40 def barfunc(*args, **kwargs):
40 def barfunc(*args, **kwargs):
41 '''Explanation of bar keyword ....
41 '''Explanation of bar keyword ....
42 '''
42 '''
43 pass
43 pass
44
44
45 In this case:
45 In this case:
46
46
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
47 - 'barfunc' is stored as 'bar' in '_table' of an instance 'keyword' above
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
48 - 'barfunc.__doc__' becomes ":bar: Explanation of bar keyword"
49 """
49 """
50 def __init__(self, table=None):
50 def __init__(self, table=None):
51 if table is None:
51 if table is None:
52 self._table = {}
52 self._table = {}
53 else:
53 else:
54 self._table = table
54 self._table = table
55
55
56 def __call__(self, decl, *args, **kwargs):
56 def __call__(self, decl, *args, **kwargs):
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
57 return lambda func: self._doregister(func, decl, *args, **kwargs)
58
58
59 def _doregister(self, func, decl, *args, **kwargs):
59 def _doregister(self, func, decl, *args, **kwargs):
60 name = self._getname(decl)
60 name = self._getname(decl)
61
61
62 if name in self._table:
62 if name in self._table:
63 msg = 'duplicate registration for name: "%s"' % name
63 msg = 'duplicate registration for name: "%s"' % name
64 raise error.ProgrammingError(msg)
64 raise error.ProgrammingError(msg)
65
65
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
66 if func.__doc__ and not util.safehasattr(func, '_origdoc'):
67 doc = pycompat.sysbytes(func.__doc__).strip()
67 doc = pycompat.sysbytes(func.__doc__).strip()
68 func._origdoc = doc
68 func._origdoc = doc
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
69 func.__doc__ = pycompat.sysstr(self._formatdoc(decl, doc))
70
70
71 self._table[name] = func
71 self._table[name] = func
72 self._extrasetup(name, func, *args, **kwargs)
72 self._extrasetup(name, func, *args, **kwargs)
73
73
74 return func
74 return func
75
75
76 def _parsefuncdecl(self, decl):
76 def _parsefuncdecl(self, decl):
77 """Parse function declaration and return the name of function in it
77 """Parse function declaration and return the name of function in it
78 """
78 """
79 i = decl.find('(')
79 i = decl.find('(')
80 if i >= 0:
80 if i >= 0:
81 return decl[:i]
81 return decl[:i]
82 else:
82 else:
83 return decl
83 return decl
84
84
85 def _getname(self, decl):
85 def _getname(self, decl):
86 """Return the name of the registered function from decl
86 """Return the name of the registered function from decl
87
87
88 Derived class should override this, if it allows more
88 Derived class should override this, if it allows more
89 descriptive 'decl' string than just a name.
89 descriptive 'decl' string than just a name.
90 """
90 """
91 return decl
91 return decl
92
92
93 _docformat = None
93 _docformat = None
94
94
95 def _formatdoc(self, decl, doc):
95 def _formatdoc(self, decl, doc):
96 """Return formatted document of the registered function for help
96 """Return formatted document of the registered function for help
97
97
98 'doc' is '__doc__.strip()' of the registered function.
98 'doc' is '__doc__.strip()' of the registered function.
99 """
99 """
100 return self._docformat % (decl, doc)
100 return self._docformat % (decl, doc)
101
101
102 def _extrasetup(self, name, func):
102 def _extrasetup(self, name, func):
103 """Execute exra setup for registered function, if needed
103 """Execute exra setup for registered function, if needed
104 """
104 """
105
105
106 class command(_funcregistrarbase):
106 class command(_funcregistrarbase):
107 """Decorator to register a command function to table
107 """Decorator to register a command function to table
108
108
109 This class receives a command table as its argument. The table should
109 This class receives a command table as its argument. The table should
110 be a dict.
110 be a dict.
111
111
112 The created object can be used as a decorator for adding commands to
112 The created object can be used as a decorator for adding commands to
113 that command table. This accepts multiple arguments to define a command.
113 that command table. This accepts multiple arguments to define a command.
114
114
115 The first argument is the command name (as bytes).
115 The first argument is the command name (as bytes).
116
116
117 The `options` keyword argument is an iterable of tuples defining command
117 The `options` keyword argument is an iterable of tuples defining command
118 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
118 arguments. See ``mercurial.fancyopts.fancyopts()`` for the format of each
119 tuple.
119 tuple.
120
120
121 The `synopsis` argument defines a short, one line summary of how to use the
121 The `synopsis` argument defines a short, one line summary of how to use the
122 command. This shows up in the help output.
122 command. This shows up in the help output.
123
123
124 There are three arguments that control what repository (if any) is found
124 There are three arguments that control what repository (if any) is found
125 and passed to the decorated function: `norepo`, `optionalrepo`, and
125 and passed to the decorated function: `norepo`, `optionalrepo`, and
126 `inferrepo`.
126 `inferrepo`.
127
127
128 The `norepo` argument defines whether the command does not require a
128 The `norepo` argument defines whether the command does not require a
129 local repository. Most commands operate against a repository, thus the
129 local repository. Most commands operate against a repository, thus the
130 default is False. When True, no repository will be passed.
130 default is False. When True, no repository will be passed.
131
131
132 The `optionalrepo` argument defines whether the command optionally requires
132 The `optionalrepo` argument defines whether the command optionally requires
133 a local repository. If no repository can be found, None will be passed
133 a local repository. If no repository can be found, None will be passed
134 to the decorated function.
134 to the decorated function.
135
135
136 The `inferrepo` argument defines whether to try to find a repository from
136 The `inferrepo` argument defines whether to try to find a repository from
137 the command line arguments. If True, arguments will be examined for
137 the command line arguments. If True, arguments will be examined for
138 potential repository locations. See ``findrepo()``. If a repository is
138 potential repository locations. See ``findrepo()``. If a repository is
139 found, it will be used and passed to the decorated function.
139 found, it will be used and passed to the decorated function.
140
140
141 The `intents` argument defines a set of intended actions or capabilities
141 The `intents` argument defines a set of intended actions or capabilities
142 the command is taking. These intents can be used to affect the construction
142 the command is taking. These intents can be used to affect the construction
143 of the repository object passed to the command. For example, commands
143 of the repository object passed to the command. For example, commands
144 declaring that they are read-only could receive a repository that doesn't
144 declaring that they are read-only could receive a repository that doesn't
145 have any methods allowing repository mutation. Other intents could be used
145 have any methods allowing repository mutation. Other intents could be used
146 to prevent the command from running if the requested intent could not be
146 to prevent the command from running if the requested intent could not be
147 fulfilled.
147 fulfilled.
148
148
149 The following intents are defined:
149 The following intents are defined:
150
150
151 readonly
151 readonly
152 The command is read-only
152 The command is read-only
153
153
154 The signature of the decorated function looks like this:
154 The signature of the decorated function looks like this:
155 def cmd(ui[, repo] [, <args>] [, <options>])
155 def cmd(ui[, repo] [, <args>] [, <options>])
156
156
157 `repo` is required if `norepo` is False.
157 `repo` is required if `norepo` is False.
158 `<args>` are positional args (or `*args`) arguments, of non-option
158 `<args>` are positional args (or `*args`) arguments, of non-option
159 arguments from the command line.
159 arguments from the command line.
160 `<options>` are keyword arguments (or `**options`) of option arguments
160 `<options>` are keyword arguments (or `**options`) of option arguments
161 from the command line.
161 from the command line.
162
162
163 See the WritingExtensions and MercurialApi documentation for more exhaustive
163 See the WritingExtensions and MercurialApi documentation for more exhaustive
164 descriptions and examples.
164 descriptions and examples.
165 """
165 """
166
166
167 def _doregister(self, func, name, options=(), synopsis=None,
167 def _doregister(self, func, name, options=(), synopsis=None,
168 norepo=False, optionalrepo=False, inferrepo=False,
168 norepo=False, optionalrepo=False, inferrepo=False,
169 intents=None):
169 intents=None):
170
170
171 func.norepo = norepo
171 func.norepo = norepo
172 func.optionalrepo = optionalrepo
172 func.optionalrepo = optionalrepo
173 func.inferrepo = inferrepo
173 func.inferrepo = inferrepo
174 func.intents = intents or set()
174 func.intents = intents or set()
175 if synopsis:
175 if synopsis:
176 self._table[name] = func, list(options), synopsis
176 self._table[name] = func, list(options), synopsis
177 else:
177 else:
178 self._table[name] = func, list(options)
178 self._table[name] = func, list(options)
179 return func
179 return func
180
180
181 INTENT_READONLY = b'readonly'
181 INTENT_READONLY = b'readonly'
182
182
183 class revsetpredicate(_funcregistrarbase):
183 class revsetpredicate(_funcregistrarbase):
184 """Decorator to register revset predicate
184 """Decorator to register revset predicate
185
185
186 Usage::
186 Usage::
187
187
188 revsetpredicate = registrar.revsetpredicate()
188 revsetpredicate = registrar.revsetpredicate()
189
189
190 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
190 @revsetpredicate('mypredicate(arg1, arg2[, arg3])')
191 def mypredicatefunc(repo, subset, x):
191 def mypredicatefunc(repo, subset, x):
192 '''Explanation of this revset predicate ....
192 '''Explanation of this revset predicate ....
193 '''
193 '''
194 pass
194 pass
195
195
196 The first string argument is used also in online help.
196 The first string argument is used also in online help.
197
197
198 Optional argument 'safe' indicates whether a predicate is safe for
198 Optional argument 'safe' indicates whether a predicate is safe for
199 DoS attack (False by default).
199 DoS attack (False by default).
200
200
201 Optional argument 'takeorder' indicates whether a predicate function
201 Optional argument 'takeorder' indicates whether a predicate function
202 takes ordering policy as the last argument.
202 takes ordering policy as the last argument.
203
203
204 Optional argument 'weight' indicates the estimated run-time cost, useful
204 Optional argument 'weight' indicates the estimated run-time cost, useful
205 for static optimization, default is 1. Higher weight means more expensive.
205 for static optimization, default is 1. Higher weight means more expensive.
206 Usually, revsets that are fast and return only one revision has a weight of
206 Usually, revsets that are fast and return only one revision has a weight of
207 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
207 0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
208 changelog have weight 10 (ex. author); revsets reading manifest deltas have
208 changelog have weight 10 (ex. author); revsets reading manifest deltas have
209 weight 30 (ex. adds); revset reading manifest contents have weight 100
209 weight 30 (ex. adds); revset reading manifest contents have weight 100
210 (ex. contains). Note: those values are flexible. If the revset has a
210 (ex. contains). Note: those values are flexible. If the revset has a
211 same big-O time complexity as 'contains', but with a smaller constant, it
211 same big-O time complexity as 'contains', but with a smaller constant, it
212 might have a weight of 90.
212 might have a weight of 90.
213
213
214 'revsetpredicate' instance in example above can be used to
214 'revsetpredicate' instance in example above can be used to
215 decorate multiple functions.
215 decorate multiple functions.
216
216
217 Decorated functions are registered automatically at loading
217 Decorated functions are registered automatically at loading
218 extension, if an instance named as 'revsetpredicate' is used for
218 extension, if an instance named as 'revsetpredicate' is used for
219 decorating in extension.
219 decorating in extension.
220
220
221 Otherwise, explicit 'revset.loadpredicate()' is needed.
221 Otherwise, explicit 'revset.loadpredicate()' is needed.
222 """
222 """
223 _getname = _funcregistrarbase._parsefuncdecl
223 _getname = _funcregistrarbase._parsefuncdecl
224 _docformat = "``%s``\n %s"
224 _docformat = "``%s``\n %s"
225
225
226 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
226 def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
227 func._safe = safe
227 func._safe = safe
228 func._takeorder = takeorder
228 func._takeorder = takeorder
229 func._weight = weight
229 func._weight = weight
230
230
231 class filesetpredicate(_funcregistrarbase):
231 class filesetpredicate(_funcregistrarbase):
232 """Decorator to register fileset predicate
232 """Decorator to register fileset predicate
233
233
234 Usage::
234 Usage::
235
235
236 filesetpredicate = registrar.filesetpredicate()
236 filesetpredicate = registrar.filesetpredicate()
237
237
238 @filesetpredicate('mypredicate()')
238 @filesetpredicate('mypredicate()')
239 def mypredicatefunc(mctx, x):
239 def mypredicatefunc(mctx, x):
240 '''Explanation of this fileset predicate ....
240 '''Explanation of this fileset predicate ....
241 '''
241 '''
242 pass
242 pass
243
243
244 The first string argument is used also in online help.
244 The first string argument is used also in online help.
245
245
246 Optional argument 'callstatus' indicates whether a predicate
246 Optional argument 'callstatus' indicates whether a predicate
247 implies 'matchctx.status()' at runtime or not (False, by
247 implies 'matchctx.status()' at runtime or not (False, by
248 default).
248 default).
249
249
250 Optional argument 'callexisting' indicates whether a predicate
251 implies 'matchctx.existing()' at runtime or not (False, by
252 default).
253
254 'filesetpredicate' instance in example above can be used to
250 'filesetpredicate' instance in example above can be used to
255 decorate multiple functions.
251 decorate multiple functions.
256
252
257 Decorated functions are registered automatically at loading
253 Decorated functions are registered automatically at loading
258 extension, if an instance named as 'filesetpredicate' is used for
254 extension, if an instance named as 'filesetpredicate' is used for
259 decorating in extension.
255 decorating in extension.
260
256
261 Otherwise, explicit 'fileset.loadpredicate()' is needed.
257 Otherwise, explicit 'fileset.loadpredicate()' is needed.
262 """
258 """
263 _getname = _funcregistrarbase._parsefuncdecl
259 _getname = _funcregistrarbase._parsefuncdecl
264 _docformat = "``%s``\n %s"
260 _docformat = "``%s``\n %s"
265
261
266 def _extrasetup(self, name, func, callstatus=False, callexisting=False):
262 def _extrasetup(self, name, func, callstatus=False):
267 func._callstatus = callstatus
263 func._callstatus = callstatus
268 func._callexisting = callexisting
269
264
270 class _templateregistrarbase(_funcregistrarbase):
265 class _templateregistrarbase(_funcregistrarbase):
271 """Base of decorator to register functions as template specific one
266 """Base of decorator to register functions as template specific one
272 """
267 """
273 _docformat = ":%s: %s"
268 _docformat = ":%s: %s"
274
269
275 class templatekeyword(_templateregistrarbase):
270 class templatekeyword(_templateregistrarbase):
276 """Decorator to register template keyword
271 """Decorator to register template keyword
277
272
278 Usage::
273 Usage::
279
274
280 templatekeyword = registrar.templatekeyword()
275 templatekeyword = registrar.templatekeyword()
281
276
282 # new API (since Mercurial 4.6)
277 # new API (since Mercurial 4.6)
283 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
278 @templatekeyword('mykeyword', requires={'repo', 'ctx'})
284 def mykeywordfunc(context, mapping):
279 def mykeywordfunc(context, mapping):
285 '''Explanation of this template keyword ....
280 '''Explanation of this template keyword ....
286 '''
281 '''
287 pass
282 pass
288
283
289 # old API
284 # old API
290 @templatekeyword('mykeyword')
285 @templatekeyword('mykeyword')
291 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
286 def mykeywordfunc(repo, ctx, templ, cache, revcache, **args):
292 '''Explanation of this template keyword ....
287 '''Explanation of this template keyword ....
293 '''
288 '''
294 pass
289 pass
295
290
296 The first string argument is used also in online help.
291 The first string argument is used also in online help.
297
292
298 Optional argument 'requires' should be a collection of resource names
293 Optional argument 'requires' should be a collection of resource names
299 which the template keyword depends on. This also serves as a flag to
294 which the template keyword depends on. This also serves as a flag to
300 switch to the new API. If 'requires' is unspecified, all template
295 switch to the new API. If 'requires' is unspecified, all template
301 keywords and resources are expanded to the function arguments.
296 keywords and resources are expanded to the function arguments.
302
297
303 'templatekeyword' instance in example above can be used to
298 'templatekeyword' instance in example above can be used to
304 decorate multiple functions.
299 decorate multiple functions.
305
300
306 Decorated functions are registered automatically at loading
301 Decorated functions are registered automatically at loading
307 extension, if an instance named as 'templatekeyword' is used for
302 extension, if an instance named as 'templatekeyword' is used for
308 decorating in extension.
303 decorating in extension.
309
304
310 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
305 Otherwise, explicit 'templatekw.loadkeyword()' is needed.
311 """
306 """
312
307
313 def _extrasetup(self, name, func, requires=None):
308 def _extrasetup(self, name, func, requires=None):
314 func._requires = requires
309 func._requires = requires
315
310
316 class templatefilter(_templateregistrarbase):
311 class templatefilter(_templateregistrarbase):
317 """Decorator to register template filer
312 """Decorator to register template filer
318
313
319 Usage::
314 Usage::
320
315
321 templatefilter = registrar.templatefilter()
316 templatefilter = registrar.templatefilter()
322
317
323 @templatefilter('myfilter', intype=bytes)
318 @templatefilter('myfilter', intype=bytes)
324 def myfilterfunc(text):
319 def myfilterfunc(text):
325 '''Explanation of this template filter ....
320 '''Explanation of this template filter ....
326 '''
321 '''
327 pass
322 pass
328
323
329 The first string argument is used also in online help.
324 The first string argument is used also in online help.
330
325
331 Optional argument 'intype' defines the type of the input argument,
326 Optional argument 'intype' defines the type of the input argument,
332 which should be (bytes, int, templateutil.date, or None for any.)
327 which should be (bytes, int, templateutil.date, or None for any.)
333
328
334 'templatefilter' instance in example above can be used to
329 'templatefilter' instance in example above can be used to
335 decorate multiple functions.
330 decorate multiple functions.
336
331
337 Decorated functions are registered automatically at loading
332 Decorated functions are registered automatically at loading
338 extension, if an instance named as 'templatefilter' is used for
333 extension, if an instance named as 'templatefilter' is used for
339 decorating in extension.
334 decorating in extension.
340
335
341 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
336 Otherwise, explicit 'templatefilters.loadkeyword()' is needed.
342 """
337 """
343
338
344 def _extrasetup(self, name, func, intype=None):
339 def _extrasetup(self, name, func, intype=None):
345 func._intype = intype
340 func._intype = intype
346
341
347 class templatefunc(_templateregistrarbase):
342 class templatefunc(_templateregistrarbase):
348 """Decorator to register template function
343 """Decorator to register template function
349
344
350 Usage::
345 Usage::
351
346
352 templatefunc = registrar.templatefunc()
347 templatefunc = registrar.templatefunc()
353
348
354 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
349 @templatefunc('myfunc(arg1, arg2[, arg3])', argspec='arg1 arg2 arg3',
355 requires={'ctx'})
350 requires={'ctx'})
356 def myfuncfunc(context, mapping, args):
351 def myfuncfunc(context, mapping, args):
357 '''Explanation of this template function ....
352 '''Explanation of this template function ....
358 '''
353 '''
359 pass
354 pass
360
355
361 The first string argument is used also in online help.
356 The first string argument is used also in online help.
362
357
363 If optional 'argspec' is defined, the function will receive 'args' as
358 If optional 'argspec' is defined, the function will receive 'args' as
364 a dict of named arguments. Otherwise 'args' is a list of positional
359 a dict of named arguments. Otherwise 'args' is a list of positional
365 arguments.
360 arguments.
366
361
367 Optional argument 'requires' should be a collection of resource names
362 Optional argument 'requires' should be a collection of resource names
368 which the template function depends on.
363 which the template function depends on.
369
364
370 'templatefunc' instance in example above can be used to
365 'templatefunc' instance in example above can be used to
371 decorate multiple functions.
366 decorate multiple functions.
372
367
373 Decorated functions are registered automatically at loading
368 Decorated functions are registered automatically at loading
374 extension, if an instance named as 'templatefunc' is used for
369 extension, if an instance named as 'templatefunc' is used for
375 decorating in extension.
370 decorating in extension.
376
371
377 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
372 Otherwise, explicit 'templatefuncs.loadfunction()' is needed.
378 """
373 """
379 _getname = _funcregistrarbase._parsefuncdecl
374 _getname = _funcregistrarbase._parsefuncdecl
380
375
381 def _extrasetup(self, name, func, argspec=None, requires=()):
376 def _extrasetup(self, name, func, argspec=None, requires=()):
382 func._argspec = argspec
377 func._argspec = argspec
383 func._requires = requires
378 func._requires = requires
384
379
385 class internalmerge(_funcregistrarbase):
380 class internalmerge(_funcregistrarbase):
386 """Decorator to register in-process merge tool
381 """Decorator to register in-process merge tool
387
382
388 Usage::
383 Usage::
389
384
390 internalmerge = registrar.internalmerge()
385 internalmerge = registrar.internalmerge()
391
386
392 @internalmerge('mymerge', internalmerge.mergeonly,
387 @internalmerge('mymerge', internalmerge.mergeonly,
393 onfailure=None, precheck=None):
388 onfailure=None, precheck=None):
394 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
389 def mymergefunc(repo, mynode, orig, fcd, fco, fca,
395 toolconf, files, labels=None):
390 toolconf, files, labels=None):
396 '''Explanation of this internal merge tool ....
391 '''Explanation of this internal merge tool ....
397 '''
392 '''
398 return 1, False # means "conflicted", "no deletion needed"
393 return 1, False # means "conflicted", "no deletion needed"
399
394
400 The first string argument is used to compose actual merge tool name,
395 The first string argument is used to compose actual merge tool name,
401 ":name" and "internal:name" (the latter is historical one).
396 ":name" and "internal:name" (the latter is historical one).
402
397
403 The second argument is one of merge types below:
398 The second argument is one of merge types below:
404
399
405 ========== ======== ======== =========
400 ========== ======== ======== =========
406 merge type precheck premerge fullmerge
401 merge type precheck premerge fullmerge
407 ========== ======== ======== =========
402 ========== ======== ======== =========
408 nomerge x x x
403 nomerge x x x
409 mergeonly o x o
404 mergeonly o x o
410 fullmerge o o o
405 fullmerge o o o
411 ========== ======== ======== =========
406 ========== ======== ======== =========
412
407
413 Optional argument 'onfailure' is the format of warning message
408 Optional argument 'onfailure' is the format of warning message
414 to be used at failure of merging (target filename is specified
409 to be used at failure of merging (target filename is specified
415 at formatting). Or, None or so, if warning message should be
410 at formatting). Or, None or so, if warning message should be
416 suppressed.
411 suppressed.
417
412
418 Optional argument 'precheck' is the function to be used
413 Optional argument 'precheck' is the function to be used
419 before actual invocation of internal merge tool itself.
414 before actual invocation of internal merge tool itself.
420 It takes as same arguments as internal merge tool does, other than
415 It takes as same arguments as internal merge tool does, other than
421 'files' and 'labels'. If it returns false value, merging is aborted
416 'files' and 'labels'. If it returns false value, merging is aborted
422 immediately (and file is marked as "unresolved").
417 immediately (and file is marked as "unresolved").
423
418
424 'internalmerge' instance in example above can be used to
419 'internalmerge' instance in example above can be used to
425 decorate multiple functions.
420 decorate multiple functions.
426
421
427 Decorated functions are registered automatically at loading
422 Decorated functions are registered automatically at loading
428 extension, if an instance named as 'internalmerge' is used for
423 extension, if an instance named as 'internalmerge' is used for
429 decorating in extension.
424 decorating in extension.
430
425
431 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
426 Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
432 """
427 """
433 _docformat = "``:%s``\n %s"
428 _docformat = "``:%s``\n %s"
434
429
435 # merge type definitions:
430 # merge type definitions:
436 nomerge = None
431 nomerge = None
437 mergeonly = 'mergeonly' # just the full merge, no premerge
432 mergeonly = 'mergeonly' # just the full merge, no premerge
438 fullmerge = 'fullmerge' # both premerge and merge
433 fullmerge = 'fullmerge' # both premerge and merge
439
434
440 def _extrasetup(self, name, func, mergetype,
435 def _extrasetup(self, name, func, mergetype,
441 onfailure=None, precheck=None):
436 onfailure=None, precheck=None):
442 func.mergetype = mergetype
437 func.mergetype = mergetype
443 func.onfailure = onfailure
438 func.onfailure = onfailure
444 func.precheck = precheck
439 func.precheck = precheck
@@ -1,697 +1,676
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 .hgignore
145 .hgignore
146 a2
146 a2
147 b2
147 b2
148 c2
148 c2
149 $ fileset 'clean()'
149 $ fileset 'clean()'
150 b1
150 b1
151 $ fileset 'copied()'
151 $ fileset 'copied()'
152 c1
152 c1
153
153
154 Test files status in different revisions
154 Test files status in different revisions
155
155
156 $ hg status -m
156 $ hg status -m
157 M b2
157 M b2
158 $ fileset -r0 'revs("wdir()", modified())' --traceback
158 $ fileset -r0 'revs("wdir()", modified())' --traceback
159 b2
159 b2
160 $ hg status -a
160 $ hg status -a
161 A c1
161 A c1
162 $ fileset -r0 'revs("wdir()", added())'
162 $ fileset -r0 'revs("wdir()", added())'
163 c1
163 c1
164 $ hg status --change 0 -a
164 $ hg status --change 0 -a
165 A a1
165 A a1
166 A a2
166 A a2
167 A b1
167 A b1
168 A b2
168 A b2
169 $ hg status -mru
169 $ hg status -mru
170 M b2
170 M b2
171 R a2
171 R a2
172 ? c3
172 ? c3
173 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
173 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
174 a2
174 a2
175 b2
175 b2
176 $ fileset -r0 'added() or revs("wdir()", added())'
176 $ fileset -r0 'added() or revs("wdir()", added())'
177 a1
177 a1
178 a2
178 a2
179 b1
179 b1
180 b2
180 b2
181 c1
181 c1
182
182
183 Test files properties
183 Test files properties
184
184
185 >>> open('bin', 'wb').write(b'\0a') and None
185 >>> open('bin', 'wb').write(b'\0a') and None
186 $ fileset 'binary()'
186 $ fileset 'binary()'
187 bin
187 bin
188 $ fileset 'binary() and unknown()'
188 $ fileset 'binary() and unknown()'
189 bin
189 bin
190 $ echo '^bin$' >> .hgignore
190 $ echo '^bin$' >> .hgignore
191 $ fileset 'binary() and ignored()'
191 $ fileset 'binary() and ignored()'
192 bin
192 bin
193 $ hg add bin
193 $ hg add bin
194 $ fileset 'binary()'
194 $ fileset 'binary()'
195 bin
195 bin
196
196
197 $ fileset 'grep("b{1}")'
197 $ fileset 'grep("b{1}")'
198 .hgignore
198 .hgignore
199 b1
199 b1
200 b2
200 b2
201 c1
201 c1
202 $ fileset 'grep("missingparens(")'
202 $ fileset 'grep("missingparens(")'
203 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
203 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
204 [255]
204 [255]
205
205
206 #if execbit
206 #if execbit
207 $ chmod +x b2
207 $ chmod +x b2
208 $ fileset 'exec()'
208 $ fileset 'exec()'
209 b2
209 b2
210 #endif
210 #endif
211
211
212 #if symlink
212 #if symlink
213 $ ln -s b2 b2link
213 $ ln -s b2 b2link
214 $ fileset 'symlink() and unknown()'
214 $ fileset 'symlink() and unknown()'
215 b2link
215 b2link
216 $ hg add b2link
216 $ hg add b2link
217 #endif
217 #endif
218
218
219 #if no-windows
219 #if no-windows
220 $ echo foo > con.xml
220 $ echo foo > con.xml
221 $ fileset 'not portable()'
221 $ fileset 'not portable()'
222 con.xml
222 con.xml
223 $ hg --config ui.portablefilenames=ignore add con.xml
223 $ hg --config ui.portablefilenames=ignore add con.xml
224 #endif
224 #endif
225
225
226 >>> open('1k', 'wb').write(b' '*1024) and None
226 >>> open('1k', 'wb').write(b' '*1024) and None
227 >>> open('2k', 'wb').write(b' '*2048) and None
227 >>> open('2k', 'wb').write(b' '*2048) and None
228 $ hg add 1k 2k
228 $ hg add 1k 2k
229 $ fileset 'size("bar")'
229 $ fileset 'size("bar")'
230 hg: parse error: couldn't parse size: bar
230 hg: parse error: couldn't parse size: bar
231 [255]
231 [255]
232 $ fileset '(1k, 2k)'
232 $ fileset '(1k, 2k)'
233 hg: parse error: can't use a list in this context
233 hg: parse error: can't use a list in this context
234 (see hg help "filesets.x or y")
234 (see hg help "filesets.x or y")
235 [255]
235 [255]
236 $ fileset 'size(1k)'
236 $ fileset 'size(1k)'
237 1k
237 1k
238 $ fileset '(1k or 2k) and size("< 2k")'
238 $ fileset '(1k or 2k) and size("< 2k")'
239 1k
239 1k
240 $ fileset '(1k or 2k) and size("<=2k")'
240 $ fileset '(1k or 2k) and size("<=2k")'
241 1k
241 1k
242 2k
242 2k
243 $ fileset '(1k or 2k) and size("> 1k")'
243 $ fileset '(1k or 2k) and size("> 1k")'
244 2k
244 2k
245 $ fileset '(1k or 2k) and size(">=1K")'
245 $ fileset '(1k or 2k) and size(">=1K")'
246 1k
246 1k
247 2k
247 2k
248 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
248 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
249 1k
249 1k
250 $ fileset 'size("1M")'
250 $ fileset 'size("1M")'
251 $ fileset 'size("1 GB")'
251 $ fileset 'size("1 GB")'
252
252
253 Test merge states
253 Test merge states
254
254
255 $ hg ci -m manychanges
255 $ hg ci -m manychanges
256 $ hg file -r . 'set:copied() & modified()'
256 $ hg file -r . 'set:copied() & modified()'
257 [1]
257 [1]
258 $ hg up -C 0
258 $ hg up -C 0
259 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
259 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
260 $ echo c >> b2
260 $ echo c >> b2
261 $ hg ci -m diverging b2
261 $ hg ci -m diverging b2
262 created new head
262 created new head
263 $ fileset 'resolved()'
263 $ fileset 'resolved()'
264 $ fileset 'unresolved()'
264 $ fileset 'unresolved()'
265 $ hg merge
265 $ hg merge
266 merging b2
266 merging b2
267 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
267 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
268 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
268 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
269 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
269 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
270 [1]
270 [1]
271 $ fileset 'resolved()'
271 $ fileset 'resolved()'
272 $ fileset 'unresolved()'
272 $ fileset 'unresolved()'
273 b2
273 b2
274 $ echo e > b2
274 $ echo e > b2
275 $ hg resolve -m b2
275 $ hg resolve -m b2
276 (no more unresolved files)
276 (no more unresolved files)
277 $ fileset 'resolved()'
277 $ fileset 'resolved()'
278 b2
278 b2
279 $ fileset 'unresolved()'
279 $ fileset 'unresolved()'
280 $ hg ci -m merge
280 $ hg ci -m merge
281
281
282 Test subrepo predicate
282 Test subrepo predicate
283
283
284 $ hg init sub
284 $ hg init sub
285 $ echo a > sub/suba
285 $ echo a > sub/suba
286 $ hg -R sub add sub/suba
286 $ hg -R sub add sub/suba
287 $ hg -R sub ci -m sub
287 $ hg -R sub ci -m sub
288 $ echo 'sub = sub' > .hgsub
288 $ echo 'sub = sub' > .hgsub
289 $ hg init sub2
289 $ hg init sub2
290 $ echo b > sub2/b
290 $ echo b > sub2/b
291 $ hg -R sub2 ci -Am sub2
291 $ hg -R sub2 ci -Am sub2
292 adding b
292 adding b
293 $ echo 'sub2 = sub2' >> .hgsub
293 $ echo 'sub2 = sub2' >> .hgsub
294 $ fileset 'subrepo()'
294 $ fileset 'subrepo()'
295 $ hg add .hgsub
295 $ hg add .hgsub
296 $ fileset 'subrepo()'
296 $ fileset 'subrepo()'
297 sub
297 sub
298 sub2
298 sub2
299 $ fileset 'subrepo("sub")'
299 $ fileset 'subrepo("sub")'
300 sub
300 sub
301 $ fileset 'subrepo("glob:*")'
301 $ fileset 'subrepo("glob:*")'
302 sub
302 sub
303 sub2
303 sub2
304 $ hg ci -m subrepo
304 $ hg ci -m subrepo
305
305
306 Test that .hgsubstate is updated as appropriate during a conversion. The
306 Test that .hgsubstate is updated as appropriate during a conversion. The
307 saverev property is enough to alter the hashes of the subrepo.
307 saverev property is enough to alter the hashes of the subrepo.
308
308
309 $ hg init ../converted
309 $ hg init ../converted
310 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
310 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
311 > sub ../converted/sub
311 > sub ../converted/sub
312 initializing destination ../converted/sub repository
312 initializing destination ../converted/sub repository
313 scanning source...
313 scanning source...
314 sorting...
314 sorting...
315 converting...
315 converting...
316 0 sub
316 0 sub
317 $ hg clone -U sub2 ../converted/sub2
317 $ hg clone -U sub2 ../converted/sub2
318 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
318 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
319 > . ../converted
319 > . ../converted
320 scanning source...
320 scanning source...
321 sorting...
321 sorting...
322 converting...
322 converting...
323 4 addfiles
323 4 addfiles
324 3 manychanges
324 3 manychanges
325 2 diverging
325 2 diverging
326 1 merge
326 1 merge
327 0 subrepo
327 0 subrepo
328 no ".hgsubstate" updates will be made for "sub2"
328 no ".hgsubstate" updates will be made for "sub2"
329 $ hg up -q -R ../converted -r tip
329 $ hg up -q -R ../converted -r tip
330 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
330 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
331 a
331 a
332 b
332 b
333 $ oldnode=`hg log -r tip -T "{node}\n"`
333 $ oldnode=`hg log -r tip -T "{node}\n"`
334 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
334 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
335 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
335 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
336
336
337 Test with a revision
337 Test with a revision
338
338
339 $ hg log -G --template '{rev} {desc}\n'
339 $ hg log -G --template '{rev} {desc}\n'
340 @ 4 subrepo
340 @ 4 subrepo
341 |
341 |
342 o 3 merge
342 o 3 merge
343 |\
343 |\
344 | o 2 diverging
344 | o 2 diverging
345 | |
345 | |
346 o | 1 manychanges
346 o | 1 manychanges
347 |/
347 |/
348 o 0 addfiles
348 o 0 addfiles
349
349
350 $ echo unknown > unknown
350 $ echo unknown > unknown
351 $ fileset -r1 'modified()'
351 $ fileset -r1 'modified()'
352 b2
352 b2
353 $ fileset -r1 'added() and c1'
353 $ fileset -r1 'added() and c1'
354 c1
354 c1
355 $ fileset -r1 'removed()'
355 $ fileset -r1 'removed()'
356 a2
356 a2
357 $ fileset -r1 'deleted()'
357 $ fileset -r1 'deleted()'
358 $ fileset -r1 'unknown()'
358 $ fileset -r1 'unknown()'
359 $ fileset -r1 'ignored()'
359 $ fileset -r1 'ignored()'
360 $ fileset -r1 'hgignore()'
360 $ fileset -r1 'hgignore()'
361 .hgignore
361 .hgignore
362 a2
362 a2
363 b2
363 b2
364 bin
364 bin
365 c2
365 c2
366 sub2
366 sub2
367 $ fileset -r1 'binary()'
367 $ fileset -r1 'binary()'
368 bin
368 bin
369 $ fileset -r1 'size(1k)'
369 $ fileset -r1 'size(1k)'
370 1k
370 1k
371 $ fileset -r3 'resolved()'
371 $ fileset -r3 'resolved()'
372 $ fileset -r3 'unresolved()'
372 $ fileset -r3 'unresolved()'
373
373
374 #if execbit
374 #if execbit
375 $ fileset -r1 'exec()'
375 $ fileset -r1 'exec()'
376 b2
376 b2
377 #endif
377 #endif
378
378
379 #if symlink
379 #if symlink
380 $ fileset -r1 'symlink()'
380 $ fileset -r1 'symlink()'
381 b2link
381 b2link
382 #endif
382 #endif
383
383
384 #if no-windows
384 #if no-windows
385 $ fileset -r1 'not portable()'
385 $ fileset -r1 'not portable()'
386 con.xml
386 con.xml
387 $ hg forget 'con.xml'
387 $ hg forget 'con.xml'
388 #endif
388 #endif
389
389
390 $ fileset -r4 'subrepo("re:su.*")'
390 $ fileset -r4 'subrepo("re:su.*")'
391 sub
391 sub
392 sub2
392 sub2
393 $ fileset -r4 'subrepo(re:su.*)'
393 $ fileset -r4 'subrepo(re:su.*)'
394 sub
394 sub
395 sub2
395 sub2
396 $ fileset -r4 'subrepo("sub")'
396 $ fileset -r4 'subrepo("sub")'
397 sub
397 sub
398 $ fileset -r4 'b2 or c1'
398 $ fileset -r4 'b2 or c1'
399 b2
399 b2
400 c1
400 c1
401
401
402 >>> open('dos', 'wb').write(b"dos\r\n") and None
402 >>> open('dos', 'wb').write(b"dos\r\n") and None
403 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
403 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
404 >>> open('mac', 'wb').write(b"mac\r") and None
404 >>> open('mac', 'wb').write(b"mac\r") and None
405 $ hg add dos mixed mac
405 $ hg add dos mixed mac
406
406
407 (remove a1, to examine safety of 'eol' on removed files)
407 (remove a1, to examine safety of 'eol' on removed files)
408 $ rm a1
408 $ rm a1
409
409
410 $ fileset 'eol(dos)'
410 $ fileset 'eol(dos)'
411 dos
411 dos
412 mixed
412 mixed
413 $ fileset 'eol(unix)'
413 $ fileset 'eol(unix)'
414 .hgignore
414 .hgignore
415 .hgsub
415 .hgsub
416 .hgsubstate
416 .hgsubstate
417 b1
417 b1
418 b2
418 b2
419 b2.orig
419 b2.orig
420 c1
420 c1
421 c2
421 c2
422 c3
422 c3
423 con.xml
423 con.xml
424 mixed
424 mixed
425 unknown
425 unknown
426 $ fileset 'eol(mac)'
426 $ fileset 'eol(mac)'
427 mac
427 mac
428
428
429 Test safety of 'encoding' on removed files
429 Test safety of 'encoding' on removed files
430
430
431 $ fileset 'encoding("ascii")'
431 $ fileset 'encoding("ascii")'
432 .hgignore
432 .hgignore
433 .hgsub
433 .hgsub
434 .hgsubstate
434 .hgsubstate
435 1k
435 1k
436 2k
436 2k
437 b1
437 b1
438 b2
438 b2
439 b2.orig
439 b2.orig
440 b2link (symlink !)
440 b2link (symlink !)
441 bin
441 bin
442 c1
442 c1
443 c2
443 c2
444 c3
444 c3
445 con.xml
445 con.xml
446 dos
446 dos
447 mac
447 mac
448 mixed
448 mixed
449 unknown
449 unknown
450
450
451 Test detection of unintentional 'matchctx.existing()' invocation
452
453 $ cat > $TESTTMP/existingcaller.py <<EOF
454 > from mercurial import registrar
455 >
456 > filesetpredicate = registrar.filesetpredicate()
457 > @filesetpredicate(b'existingcaller()', callexisting=False)
458 > def existingcaller(mctx, x):
459 > # this 'mctx.existing()' invocation is unintentional
460 > existing = set(mctx.existing())
461 > return mctx.predicate(existing.__contains__, cache=False)
462 > EOF
463
464 $ cat >> .hg/hgrc <<EOF
465 > [extensions]
466 > existingcaller = $TESTTMP/existingcaller.py
467 > EOF
468
469 $ fileset 'existingcaller()' 2>&1 | tail -1
470 *ProgrammingError: *unexpected existing() invocation* (glob)
471
472 Test 'revs(...)'
451 Test 'revs(...)'
473 ================
452 ================
474
453
475 small reminder of the repository state
454 small reminder of the repository state
476
455
477 $ hg log -G
456 $ hg log -G
478 @ changeset: 4:* (glob)
457 @ changeset: 4:* (glob)
479 | tag: tip
458 | tag: tip
480 | user: test
459 | user: test
481 | date: Thu Jan 01 00:00:00 1970 +0000
460 | date: Thu Jan 01 00:00:00 1970 +0000
482 | summary: subrepo
461 | summary: subrepo
483 |
462 |
484 o changeset: 3:* (glob)
463 o changeset: 3:* (glob)
485 |\ parent: 2:55b05bdebf36
464 |\ parent: 2:55b05bdebf36
486 | | parent: 1:* (glob)
465 | | parent: 1:* (glob)
487 | | user: test
466 | | user: test
488 | | date: Thu Jan 01 00:00:00 1970 +0000
467 | | date: Thu Jan 01 00:00:00 1970 +0000
489 | | summary: merge
468 | | summary: merge
490 | |
469 | |
491 | o changeset: 2:55b05bdebf36
470 | o changeset: 2:55b05bdebf36
492 | | parent: 0:8a9576c51c1f
471 | | parent: 0:8a9576c51c1f
493 | | user: test
472 | | user: test
494 | | date: Thu Jan 01 00:00:00 1970 +0000
473 | | date: Thu Jan 01 00:00:00 1970 +0000
495 | | summary: diverging
474 | | summary: diverging
496 | |
475 | |
497 o | changeset: 1:* (glob)
476 o | changeset: 1:* (glob)
498 |/ user: test
477 |/ user: test
499 | date: Thu Jan 01 00:00:00 1970 +0000
478 | date: Thu Jan 01 00:00:00 1970 +0000
500 | summary: manychanges
479 | summary: manychanges
501 |
480 |
502 o changeset: 0:8a9576c51c1f
481 o changeset: 0:8a9576c51c1f
503 user: test
482 user: test
504 date: Thu Jan 01 00:00:00 1970 +0000
483 date: Thu Jan 01 00:00:00 1970 +0000
505 summary: addfiles
484 summary: addfiles
506
485
507 $ hg status --change 0
486 $ hg status --change 0
508 A a1
487 A a1
509 A a2
488 A a2
510 A b1
489 A b1
511 A b2
490 A b2
512 $ hg status --change 1
491 $ hg status --change 1
513 M b2
492 M b2
514 A 1k
493 A 1k
515 A 2k
494 A 2k
516 A b2link (no-windows !)
495 A b2link (no-windows !)
517 A bin
496 A bin
518 A c1
497 A c1
519 A con.xml (no-windows !)
498 A con.xml (no-windows !)
520 R a2
499 R a2
521 $ hg status --change 2
500 $ hg status --change 2
522 M b2
501 M b2
523 $ hg status --change 3
502 $ hg status --change 3
524 M b2
503 M b2
525 A 1k
504 A 1k
526 A 2k
505 A 2k
527 A b2link (no-windows !)
506 A b2link (no-windows !)
528 A bin
507 A bin
529 A c1
508 A c1
530 A con.xml (no-windows !)
509 A con.xml (no-windows !)
531 R a2
510 R a2
532 $ hg status --change 4
511 $ hg status --change 4
533 A .hgsub
512 A .hgsub
534 A .hgsubstate
513 A .hgsubstate
535 $ hg status
514 $ hg status
536 A dos
515 A dos
537 A mac
516 A mac
538 A mixed
517 A mixed
539 R con.xml (no-windows !)
518 R con.xml (no-windows !)
540 ! a1
519 ! a1
541 ? b2.orig
520 ? b2.orig
542 ? c3
521 ? c3
543 ? unknown
522 ? unknown
544
523
545 Test files at -r0 should be filtered by files at wdir
524 Test files at -r0 should be filtered by files at wdir
546 -----------------------------------------------------
525 -----------------------------------------------------
547
526
548 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
527 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
549 a1
528 a1
550 b1
529 b1
551 b2
530 b2
552
531
553 Test that "revs()" work at all
532 Test that "revs()" work at all
554 ------------------------------
533 ------------------------------
555
534
556 $ fileset "revs('2', modified())"
535 $ fileset "revs('2', modified())"
557 b2
536 b2
558
537
559 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
560 ----------------------------------------------------------------------------
539 ----------------------------------------------------------------------------
561
540
562 (a2 not in working copy)
541 (a2 not in working copy)
563
542
564 $ fileset "revs('0', added())"
543 $ fileset "revs('0', added())"
565 a1
544 a1
566 a2
545 a2
567 b1
546 b1
568 b2
547 b2
569
548
570 (none of the file exist in "0")
549 (none of the file exist in "0")
571
550
572 $ fileset -r 0 "revs('4', added())"
551 $ fileset -r 0 "revs('4', added())"
573 .hgsub
552 .hgsub
574 .hgsubstate
553 .hgsubstate
575
554
576 Call with empty revset
555 Call with empty revset
577 --------------------------
556 --------------------------
578
557
579 $ fileset "revs('2-2', modified())"
558 $ fileset "revs('2-2', modified())"
580
559
581 Call with revset matching multiple revs
560 Call with revset matching multiple revs
582 ---------------------------------------
561 ---------------------------------------
583
562
584 $ fileset "revs('0+4', added())"
563 $ fileset "revs('0+4', added())"
585 .hgsub
564 .hgsub
586 .hgsubstate
565 .hgsubstate
587 a1
566 a1
588 a2
567 a2
589 b1
568 b1
590 b2
569 b2
591
570
592 overlapping set
571 overlapping set
593
572
594 $ fileset "revs('1+2', modified())"
573 $ fileset "revs('1+2', modified())"
595 b2
574 b2
596
575
597 test 'status(...)'
576 test 'status(...)'
598 =================
577 =================
599
578
600 Simple case
579 Simple case
601 -----------
580 -----------
602
581
603 $ fileset "status(3, 4, added())"
582 $ fileset "status(3, 4, added())"
604 .hgsub
583 .hgsub
605 .hgsubstate
584 .hgsubstate
606
585
607 use rev to restrict matched file
586 use rev to restrict matched file
608 -----------------------------------------
587 -----------------------------------------
609
588
610 $ hg status --removed --rev 0 --rev 1
589 $ hg status --removed --rev 0 --rev 1
611 R a2
590 R a2
612 $ fileset "status(0, 1, removed())"
591 $ fileset "status(0, 1, removed())"
613 a2
592 a2
614 $ fileset "tracked() and status(0, 1, removed())"
593 $ fileset "tracked() and status(0, 1, removed())"
615 $ fileset -r 4 "status(0, 1, removed())"
594 $ fileset -r 4 "status(0, 1, removed())"
616 a2
595 a2
617 $ fileset -r 4 "tracked() and status(0, 1, removed())"
596 $ fileset -r 4 "tracked() and status(0, 1, removed())"
618 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
597 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
619 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
598 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
620 a2
599 a2
621
600
622 check wdir()
601 check wdir()
623 ------------
602 ------------
624
603
625 $ hg status --removed --rev 4
604 $ hg status --removed --rev 4
626 R con.xml (no-windows !)
605 R con.xml (no-windows !)
627 $ fileset "status(4, 'wdir()', removed())"
606 $ fileset "status(4, 'wdir()', removed())"
628 con.xml (no-windows !)
607 con.xml (no-windows !)
629
608
630 $ hg status --removed --rev 2
609 $ hg status --removed --rev 2
631 R a2
610 R a2
632 $ fileset "status('2', 'wdir()', removed())"
611 $ fileset "status('2', 'wdir()', removed())"
633 a2
612 a2
634
613
635 test backward status
614 test backward status
636 --------------------
615 --------------------
637
616
638 $ hg status --removed --rev 0 --rev 4
617 $ hg status --removed --rev 0 --rev 4
639 R a2
618 R a2
640 $ hg status --added --rev 4 --rev 0
619 $ hg status --added --rev 4 --rev 0
641 A a2
620 A a2
642 $ fileset "status(4, 0, added())"
621 $ fileset "status(4, 0, added())"
643 a2
622 a2
644
623
645 test cross branch status
624 test cross branch status
646 ------------------------
625 ------------------------
647
626
648 $ hg status --added --rev 1 --rev 2
627 $ hg status --added --rev 1 --rev 2
649 A a2
628 A a2
650 $ fileset "status(1, 2, added())"
629 $ fileset "status(1, 2, added())"
651 a2
630 a2
652
631
653 test with multi revs revset
632 test with multi revs revset
654 ---------------------------
633 ---------------------------
655 $ hg status --added --rev 0:1 --rev 3:4
634 $ hg status --added --rev 0:1 --rev 3:4
656 A .hgsub
635 A .hgsub
657 A .hgsubstate
636 A .hgsubstate
658 A 1k
637 A 1k
659 A 2k
638 A 2k
660 A b2link (no-windows !)
639 A b2link (no-windows !)
661 A bin
640 A bin
662 A c1
641 A c1
663 A con.xml (no-windows !)
642 A con.xml (no-windows !)
664 $ fileset "status('0:1', '3:4', added())"
643 $ fileset "status('0:1', '3:4', added())"
665 .hgsub
644 .hgsub
666 .hgsubstate
645 .hgsubstate
667 1k
646 1k
668 2k
647 2k
669 b2link (no-windows !)
648 b2link (no-windows !)
670 bin
649 bin
671 c1
650 c1
672 con.xml (no-windows !)
651 con.xml (no-windows !)
673
652
674 tests with empty value
653 tests with empty value
675 ----------------------
654 ----------------------
676
655
677 Fully empty revset
656 Fully empty revset
678
657
679 $ fileset "status('', '4', added())"
658 $ fileset "status('', '4', added())"
680 hg: parse error: first argument to status must be a revision
659 hg: parse error: first argument to status must be a revision
681 [255]
660 [255]
682 $ fileset "status('2', '', added())"
661 $ fileset "status('2', '', added())"
683 hg: parse error: second argument to status must be a revision
662 hg: parse error: second argument to status must be a revision
684 [255]
663 [255]
685
664
686 Empty revset will error at the revset layer
665 Empty revset will error at the revset layer
687
666
688 $ fileset "status(' ', '4', added())"
667 $ fileset "status(' ', '4', added())"
689 hg: parse error at 1: not a prefix: end
668 hg: parse error at 1: not a prefix: end
690 (
669 (
691 ^ here)
670 ^ here)
692 [255]
671 [255]
693 $ fileset "status('2', ' ', added())"
672 $ fileset "status('2', ' ', added())"
694 hg: parse error at 1: not a prefix: end
673 hg: parse error at 1: not a prefix: end
695 (
674 (
696 ^ here)
675 ^ here)
697 [255]
676 [255]
General Comments 0
You need to be logged in to leave comments. Login now