##// END OF EJS Templates
help: add quotes to a few commands we point to...
Martin von Zweigbergk -
r38846:4fe8d1f0 default
parent child Browse files
Show More
@@ -1,560 +1,560 b''
1 1 # fileset.py - file set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import re
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 error,
16 16 filesetlang,
17 17 match as matchmod,
18 18 merge,
19 19 pycompat,
20 20 registrar,
21 21 scmutil,
22 22 util,
23 23 )
24 24 from .utils import (
25 25 stringutil,
26 26 )
27 27
28 28 # helpers for processing parsed tree
29 29 getsymbol = filesetlang.getsymbol
30 30 getstring = filesetlang.getstring
31 31 _getkindpat = filesetlang.getkindpat
32 32 getpattern = filesetlang.getpattern
33 33 getargs = filesetlang.getargs
34 34
35 35 def getmatch(mctx, x):
36 36 if not x:
37 37 raise error.ParseError(_("missing argument"))
38 38 return methods[x[0]](mctx, *x[1:])
39 39
40 40 def stringmatch(mctx, x):
41 41 return mctx.matcher([x])
42 42
43 43 def kindpatmatch(mctx, x, y):
44 44 return stringmatch(mctx, _getkindpat(x, y, matchmod.allpatternkinds,
45 45 _("pattern must be a string")))
46 46
47 47 def andmatch(mctx, x, y):
48 48 xm = getmatch(mctx, x)
49 49 ym = getmatch(mctx, y)
50 50 return matchmod.intersectmatchers(xm, ym)
51 51
52 52 def ormatch(mctx, *xs):
53 53 ms = [getmatch(mctx, x) for x in xs]
54 54 return matchmod.unionmatcher(ms)
55 55
56 56 def notmatch(mctx, x):
57 57 m = getmatch(mctx, x)
58 58 return mctx.predicate(lambda f: not m(f), predrepr=('<not %r>', m))
59 59
60 60 def minusmatch(mctx, x, y):
61 61 xm = getmatch(mctx, x)
62 62 ym = getmatch(mctx, y)
63 63 return matchmod.differencematcher(xm, ym)
64 64
65 65 def negatematch(mctx, x):
66 66 raise error.ParseError(_("can't use negate operator in this context"))
67 67
68 68 def listmatch(mctx, *xs):
69 69 raise error.ParseError(_("can't use a list in this context"),
70 hint=_('see hg help "filesets.x or y"'))
70 hint=_('see \'hg help "filesets.x or y"\''))
71 71
72 72 def func(mctx, a, b):
73 73 funcname = getsymbol(a)
74 74 if funcname in symbols:
75 75 return symbols[funcname](mctx, b)
76 76
77 77 keep = lambda fn: getattr(fn, '__doc__', None) is not None
78 78
79 79 syms = [s for (s, fn) in symbols.items() if keep(fn)]
80 80 raise error.UnknownIdentifier(funcname, syms)
81 81
82 82 # symbols are callable like:
83 83 # fun(mctx, x)
84 84 # with:
85 85 # mctx - current matchctx instance
86 86 # x - argument in tree form
87 87 symbols = filesetlang.symbols
88 88
89 89 # filesets using matchctx.status()
90 90 _statuscallers = set()
91 91
92 92 predicate = registrar.filesetpredicate()
93 93
94 94 @predicate('modified()', callstatus=True)
95 95 def modified(mctx, x):
96 96 """File that is modified according to :hg:`status`.
97 97 """
98 98 # i18n: "modified" is a keyword
99 99 getargs(x, 0, 0, _("modified takes no arguments"))
100 100 s = set(mctx.status().modified)
101 101 return mctx.predicate(s.__contains__, predrepr='modified')
102 102
103 103 @predicate('added()', callstatus=True)
104 104 def added(mctx, x):
105 105 """File that is added according to :hg:`status`.
106 106 """
107 107 # i18n: "added" is a keyword
108 108 getargs(x, 0, 0, _("added takes no arguments"))
109 109 s = set(mctx.status().added)
110 110 return mctx.predicate(s.__contains__, predrepr='added')
111 111
112 112 @predicate('removed()', callstatus=True)
113 113 def removed(mctx, x):
114 114 """File that is removed according to :hg:`status`.
115 115 """
116 116 # i18n: "removed" is a keyword
117 117 getargs(x, 0, 0, _("removed takes no arguments"))
118 118 s = set(mctx.status().removed)
119 119 return mctx.predicate(s.__contains__, predrepr='removed')
120 120
121 121 @predicate('deleted()', callstatus=True)
122 122 def deleted(mctx, x):
123 123 """Alias for ``missing()``.
124 124 """
125 125 # i18n: "deleted" is a keyword
126 126 getargs(x, 0, 0, _("deleted takes no arguments"))
127 127 s = set(mctx.status().deleted)
128 128 return mctx.predicate(s.__contains__, predrepr='deleted')
129 129
130 130 @predicate('missing()', callstatus=True)
131 131 def missing(mctx, x):
132 132 """File that is missing according to :hg:`status`.
133 133 """
134 134 # i18n: "missing" is a keyword
135 135 getargs(x, 0, 0, _("missing takes no arguments"))
136 136 s = set(mctx.status().deleted)
137 137 return mctx.predicate(s.__contains__, predrepr='deleted')
138 138
139 139 @predicate('unknown()', callstatus=True)
140 140 def unknown(mctx, x):
141 141 """File that is unknown according to :hg:`status`."""
142 142 # i18n: "unknown" is a keyword
143 143 getargs(x, 0, 0, _("unknown takes no arguments"))
144 144 s = set(mctx.status().unknown)
145 145 return mctx.predicate(s.__contains__, predrepr='unknown')
146 146
147 147 @predicate('ignored()', callstatus=True)
148 148 def ignored(mctx, x):
149 149 """File that is ignored according to :hg:`status`."""
150 150 # i18n: "ignored" is a keyword
151 151 getargs(x, 0, 0, _("ignored takes no arguments"))
152 152 s = set(mctx.status().ignored)
153 153 return mctx.predicate(s.__contains__, predrepr='ignored')
154 154
155 155 @predicate('clean()', callstatus=True)
156 156 def clean(mctx, x):
157 157 """File that is clean according to :hg:`status`.
158 158 """
159 159 # i18n: "clean" is a keyword
160 160 getargs(x, 0, 0, _("clean takes no arguments"))
161 161 s = set(mctx.status().clean)
162 162 return mctx.predicate(s.__contains__, predrepr='clean')
163 163
164 164 @predicate('tracked()')
165 165 def tracked(mctx, x):
166 166 """File that is under Mercurial control."""
167 167 # i18n: "tracked" is a keyword
168 168 getargs(x, 0, 0, _("tracked takes no arguments"))
169 169 return mctx.predicate(mctx.ctx.__contains__, predrepr='tracked')
170 170
171 171 @predicate('binary()')
172 172 def binary(mctx, x):
173 173 """File that appears to be binary (contains NUL bytes).
174 174 """
175 175 # i18n: "binary" is a keyword
176 176 getargs(x, 0, 0, _("binary takes no arguments"))
177 177 return mctx.fpredicate(lambda fctx: fctx.isbinary(),
178 178 predrepr='binary', cache=True)
179 179
180 180 @predicate('exec()')
181 181 def exec_(mctx, x):
182 182 """File that is marked as executable.
183 183 """
184 184 # i18n: "exec" is a keyword
185 185 getargs(x, 0, 0, _("exec takes no arguments"))
186 186 ctx = mctx.ctx
187 187 return mctx.predicate(lambda f: ctx.flags(f) == 'x', predrepr='exec')
188 188
189 189 @predicate('symlink()')
190 190 def symlink(mctx, x):
191 191 """File that is marked as a symlink.
192 192 """
193 193 # i18n: "symlink" is a keyword
194 194 getargs(x, 0, 0, _("symlink takes no arguments"))
195 195 ctx = mctx.ctx
196 196 return mctx.predicate(lambda f: ctx.flags(f) == 'l', predrepr='symlink')
197 197
198 198 @predicate('resolved()')
199 199 def resolved(mctx, x):
200 200 """File that is marked resolved according to :hg:`resolve -l`.
201 201 """
202 202 # i18n: "resolved" is a keyword
203 203 getargs(x, 0, 0, _("resolved takes no arguments"))
204 204 if mctx.ctx.rev() is not None:
205 205 return mctx.never()
206 206 ms = merge.mergestate.read(mctx.ctx.repo())
207 207 return mctx.predicate(lambda f: f in ms and ms[f] == 'r',
208 208 predrepr='resolved')
209 209
210 210 @predicate('unresolved()')
211 211 def unresolved(mctx, x):
212 212 """File that is marked unresolved according to :hg:`resolve -l`.
213 213 """
214 214 # i18n: "unresolved" is a keyword
215 215 getargs(x, 0, 0, _("unresolved takes no arguments"))
216 216 if mctx.ctx.rev() is not None:
217 217 return mctx.never()
218 218 ms = merge.mergestate.read(mctx.ctx.repo())
219 219 return mctx.predicate(lambda f: f in ms and ms[f] == 'u',
220 220 predrepr='unresolved')
221 221
222 222 @predicate('hgignore()')
223 223 def hgignore(mctx, x):
224 224 """File that matches the active .hgignore pattern.
225 225 """
226 226 # i18n: "hgignore" is a keyword
227 227 getargs(x, 0, 0, _("hgignore takes no arguments"))
228 228 return mctx.ctx.repo().dirstate._ignore
229 229
230 230 @predicate('portable()')
231 231 def portable(mctx, x):
232 232 """File that has a portable name. (This doesn't include filenames with case
233 233 collisions.)
234 234 """
235 235 # i18n: "portable" is a keyword
236 236 getargs(x, 0, 0, _("portable takes no arguments"))
237 237 return mctx.predicate(lambda f: util.checkwinfilename(f) is None,
238 238 predrepr='portable')
239 239
240 240 @predicate('grep(regex)')
241 241 def grep(mctx, x):
242 242 """File contains the given regular expression.
243 243 """
244 244 try:
245 245 # i18n: "grep" is a keyword
246 246 r = re.compile(getstring(x, _("grep requires a pattern")))
247 247 except re.error as e:
248 248 raise error.ParseError(_('invalid match pattern: %s') %
249 249 stringutil.forcebytestr(e))
250 250 return mctx.fpredicate(lambda fctx: r.search(fctx.data()),
251 251 predrepr=('grep(%r)', r.pattern), cache=True)
252 252
253 253 def _sizetomax(s):
254 254 try:
255 255 s = s.strip().lower()
256 256 for k, v in util._sizeunits:
257 257 if s.endswith(k):
258 258 # max(4k) = 5k - 1, max(4.5k) = 4.6k - 1
259 259 n = s[:-len(k)]
260 260 inc = 1.0
261 261 if "." in n:
262 262 inc /= 10 ** len(n.split(".")[1])
263 263 return int((float(n) + inc) * v) - 1
264 264 # no extension, this is a precise value
265 265 return int(s)
266 266 except ValueError:
267 267 raise error.ParseError(_("couldn't parse size: %s") % s)
268 268
269 269 def sizematcher(expr):
270 270 """Return a function(size) -> bool from the ``size()`` expression"""
271 271 expr = expr.strip()
272 272 if '-' in expr: # do we have a range?
273 273 a, b = expr.split('-', 1)
274 274 a = util.sizetoint(a)
275 275 b = util.sizetoint(b)
276 276 return lambda x: x >= a and x <= b
277 277 elif expr.startswith("<="):
278 278 a = util.sizetoint(expr[2:])
279 279 return lambda x: x <= a
280 280 elif expr.startswith("<"):
281 281 a = util.sizetoint(expr[1:])
282 282 return lambda x: x < a
283 283 elif expr.startswith(">="):
284 284 a = util.sizetoint(expr[2:])
285 285 return lambda x: x >= a
286 286 elif expr.startswith(">"):
287 287 a = util.sizetoint(expr[1:])
288 288 return lambda x: x > a
289 289 else:
290 290 a = util.sizetoint(expr)
291 291 b = _sizetomax(expr)
292 292 return lambda x: x >= a and x <= b
293 293
294 294 @predicate('size(expression)')
295 295 def size(mctx, x):
296 296 """File size matches the given expression. Examples:
297 297
298 298 - size('1k') - files from 1024 to 2047 bytes
299 299 - size('< 20k') - files less than 20480 bytes
300 300 - size('>= .5MB') - files at least 524288 bytes
301 301 - size('4k - 1MB') - files from 4096 bytes to 1048576 bytes
302 302 """
303 303 # i18n: "size" is a keyword
304 304 expr = getstring(x, _("size requires an expression"))
305 305 m = sizematcher(expr)
306 306 return mctx.fpredicate(lambda fctx: m(fctx.size()),
307 307 predrepr=('size(%r)', expr), cache=True)
308 308
309 309 @predicate('encoding(name)')
310 310 def encoding(mctx, x):
311 311 """File can be successfully decoded with the given character
312 312 encoding. May not be useful for encodings other than ASCII and
313 313 UTF-8.
314 314 """
315 315
316 316 # i18n: "encoding" is a keyword
317 317 enc = getstring(x, _("encoding requires an encoding name"))
318 318
319 319 def encp(fctx):
320 320 d = fctx.data()
321 321 try:
322 322 d.decode(pycompat.sysstr(enc))
323 323 return True
324 324 except LookupError:
325 325 raise error.Abort(_("unknown encoding '%s'") % enc)
326 326 except UnicodeDecodeError:
327 327 return False
328 328
329 329 return mctx.fpredicate(encp, predrepr=('encoding(%r)', enc), cache=True)
330 330
331 331 @predicate('eol(style)')
332 332 def eol(mctx, x):
333 333 """File contains newlines of the given style (dos, unix, mac). Binary
334 334 files are excluded, files with mixed line endings match multiple
335 335 styles.
336 336 """
337 337
338 338 # i18n: "eol" is a keyword
339 339 enc = getstring(x, _("eol requires a style name"))
340 340
341 341 def eolp(fctx):
342 342 if fctx.isbinary():
343 343 return False
344 344 d = fctx.data()
345 345 if (enc == 'dos' or enc == 'win') and '\r\n' in d:
346 346 return True
347 347 elif enc == 'unix' and re.search('(?<!\r)\n', d):
348 348 return True
349 349 elif enc == 'mac' and re.search('\r(?!\n)', d):
350 350 return True
351 351 return False
352 352 return mctx.fpredicate(eolp, predrepr=('eol(%r)', enc), cache=True)
353 353
354 354 @predicate('copied()')
355 355 def copied(mctx, x):
356 356 """File that is recorded as being copied.
357 357 """
358 358 # i18n: "copied" is a keyword
359 359 getargs(x, 0, 0, _("copied takes no arguments"))
360 360 def copiedp(fctx):
361 361 p = fctx.parents()
362 362 return p and p[0].path() != fctx.path()
363 363 return mctx.fpredicate(copiedp, predrepr='copied', cache=True)
364 364
365 365 @predicate('revs(revs, pattern)')
366 366 def revs(mctx, x):
367 367 """Evaluate set in the specified revisions. If the revset match multiple
368 368 revs, this will return file matching pattern in any of the revision.
369 369 """
370 370 # i18n: "revs" is a keyword
371 371 r, x = getargs(x, 2, 2, _("revs takes two arguments"))
372 372 # i18n: "revs" is a keyword
373 373 revspec = getstring(r, _("first argument to revs must be a revision"))
374 374 repo = mctx.ctx.repo()
375 375 revs = scmutil.revrange(repo, [revspec])
376 376
377 377 matchers = []
378 378 for r in revs:
379 379 ctx = repo[r]
380 380 matchers.append(getmatch(mctx.switch(ctx, _buildstatus(ctx, x)), x))
381 381 if not matchers:
382 382 return mctx.never()
383 383 if len(matchers) == 1:
384 384 return matchers[0]
385 385 return matchmod.unionmatcher(matchers)
386 386
387 387 @predicate('status(base, rev, pattern)')
388 388 def status(mctx, x):
389 389 """Evaluate predicate using status change between ``base`` and
390 390 ``rev``. Examples:
391 391
392 392 - ``status(3, 7, added())`` - matches files added from "3" to "7"
393 393 """
394 394 repo = mctx.ctx.repo()
395 395 # i18n: "status" is a keyword
396 396 b, r, x = getargs(x, 3, 3, _("status takes three arguments"))
397 397 # i18n: "status" is a keyword
398 398 baseerr = _("first argument to status must be a revision")
399 399 baserevspec = getstring(b, baseerr)
400 400 if not baserevspec:
401 401 raise error.ParseError(baseerr)
402 402 reverr = _("second argument to status must be a revision")
403 403 revspec = getstring(r, reverr)
404 404 if not revspec:
405 405 raise error.ParseError(reverr)
406 406 basectx, ctx = scmutil.revpair(repo, [baserevspec, revspec])
407 407 return getmatch(mctx.switch(ctx, _buildstatus(ctx, x, basectx=basectx)), x)
408 408
409 409 @predicate('subrepo([pattern])')
410 410 def subrepo(mctx, x):
411 411 """Subrepositories whose paths match the given pattern.
412 412 """
413 413 # i18n: "subrepo" is a keyword
414 414 getargs(x, 0, 1, _("subrepo takes at most one argument"))
415 415 ctx = mctx.ctx
416 416 sstate = ctx.substate
417 417 if x:
418 418 pat = getpattern(x, matchmod.allpatternkinds,
419 419 # i18n: "subrepo" is a keyword
420 420 _("subrepo requires a pattern or no arguments"))
421 421 fast = not matchmod.patkind(pat)
422 422 if fast:
423 423 def m(s):
424 424 return (s == pat)
425 425 else:
426 426 m = matchmod.match(ctx.repo().root, '', [pat], ctx=ctx)
427 427 return mctx.predicate(lambda f: f in sstate and m(f),
428 428 predrepr=('subrepo(%r)', pat))
429 429 else:
430 430 return mctx.predicate(sstate.__contains__, predrepr='subrepo')
431 431
432 432 methods = {
433 433 'string': stringmatch,
434 434 'symbol': stringmatch,
435 435 'kindpat': kindpatmatch,
436 436 'and': andmatch,
437 437 'or': ormatch,
438 438 'minus': minusmatch,
439 439 'negate': negatematch,
440 440 'list': listmatch,
441 441 'group': getmatch,
442 442 'not': notmatch,
443 443 'func': func,
444 444 }
445 445
446 446 class matchctx(object):
447 447 def __init__(self, ctx, status=None, badfn=None):
448 448 self.ctx = ctx
449 449 self._status = status
450 450 self._badfn = badfn
451 451
452 452 def status(self):
453 453 return self._status
454 454
455 455 def matcher(self, patterns):
456 456 return self.ctx.match(patterns, badfn=self._badfn)
457 457
458 458 def predicate(self, predfn, predrepr=None, cache=False):
459 459 """Create a matcher to select files by predfn(filename)"""
460 460 if cache:
461 461 predfn = util.cachefunc(predfn)
462 462 repo = self.ctx.repo()
463 463 return matchmod.predicatematcher(repo.root, repo.getcwd(), predfn,
464 464 predrepr=predrepr, badfn=self._badfn)
465 465
466 466 def fpredicate(self, predfn, predrepr=None, cache=False):
467 467 """Create a matcher to select files by predfn(fctx) at the current
468 468 revision
469 469
470 470 Missing files are ignored.
471 471 """
472 472 ctx = self.ctx
473 473 if ctx.rev() is None:
474 474 def fctxpredfn(f):
475 475 try:
476 476 fctx = ctx[f]
477 477 except error.LookupError:
478 478 return False
479 479 try:
480 480 fctx.audit()
481 481 except error.Abort:
482 482 return False
483 483 try:
484 484 return predfn(fctx)
485 485 except (IOError, OSError) as e:
486 486 # open()-ing a directory fails with EACCES on Windows
487 487 if e.errno in (errno.ENOENT, errno.EACCES, errno.ENOTDIR,
488 488 errno.EISDIR):
489 489 return False
490 490 raise
491 491 else:
492 492 def fctxpredfn(f):
493 493 try:
494 494 fctx = ctx[f]
495 495 except error.LookupError:
496 496 return False
497 497 return predfn(fctx)
498 498 return self.predicate(fctxpredfn, predrepr=predrepr, cache=cache)
499 499
500 500 def never(self):
501 501 """Create a matcher to select nothing"""
502 502 repo = self.ctx.repo()
503 503 return matchmod.nevermatcher(repo.root, repo.getcwd(),
504 504 badfn=self._badfn)
505 505
506 506 def switch(self, ctx, status=None):
507 507 return matchctx(ctx, status, self._badfn)
508 508
509 509 # filesets using matchctx.switch()
510 510 _switchcallers = [
511 511 'revs',
512 512 'status',
513 513 ]
514 514
515 515 def _intree(funcs, tree):
516 516 if isinstance(tree, tuple):
517 517 if tree[0] == 'func' and tree[1][0] == 'symbol':
518 518 if tree[1][1] in funcs:
519 519 return True
520 520 if tree[1][1] in _switchcallers:
521 521 # arguments won't be evaluated in the current context
522 522 return False
523 523 for s in tree[1:]:
524 524 if _intree(funcs, s):
525 525 return True
526 526 return False
527 527
528 528 def match(ctx, expr, badfn=None):
529 529 """Create a matcher for a single fileset expression"""
530 530 tree = filesetlang.parse(expr)
531 531 mctx = matchctx(ctx, _buildstatus(ctx, tree), badfn=badfn)
532 532 return getmatch(mctx, tree)
533 533
534 534 def _buildstatus(ctx, tree, basectx=None):
535 535 # do we need status info?
536 536
537 537 if _intree(_statuscallers, tree):
538 538 unknown = _intree(['unknown'], tree)
539 539 ignored = _intree(['ignored'], tree)
540 540
541 541 if basectx is None:
542 542 basectx = ctx.p1()
543 543 return basectx.status(ctx, listunknown=unknown, listignored=ignored,
544 544 listclean=True)
545 545 else:
546 546 return None
547 547
548 548 def loadpredicate(ui, extname, registrarobj):
549 549 """Load fileset predicates from specified registrarobj
550 550 """
551 551 for name, func in registrarobj._table.iteritems():
552 552 symbols[name] = func
553 553 if func._callstatus:
554 554 _statuscallers.add(name)
555 555
556 556 # load built-in predicates explicitly to setup _statuscallers
557 557 loadpredicate(None, None, predicate)
558 558
559 559 # tell hggettext to extract docstrings from these functions:
560 560 i18nfunctions = symbols.values()
@@ -1,92 +1,92 b''
1 1 # minifileset.py - a simple language to select files
2 2 #
3 3 # Copyright 2017 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11 from . import (
12 12 error,
13 13 fileset,
14 14 filesetlang,
15 15 pycompat,
16 16 )
17 17
18 18 def _sizep(x):
19 19 # i18n: "size" is a keyword
20 20 expr = filesetlang.getstring(x, _("size requires an expression"))
21 21 return fileset.sizematcher(expr)
22 22
23 23 def _compile(tree):
24 24 if not tree:
25 25 raise error.ParseError(_("missing argument"))
26 26 op = tree[0]
27 27 if op in {'symbol', 'string', 'kindpat'}:
28 28 name = filesetlang.getpattern(tree, {'path'}, _('invalid file pattern'))
29 29 if name.startswith('**'): # file extension test, ex. "**.tar.gz"
30 30 ext = name[2:]
31 31 for c in pycompat.bytestr(ext):
32 32 if c in '*{}[]?/\\':
33 33 raise error.ParseError(_('reserved character: %s') % c)
34 34 return lambda n, s: n.endswith(ext)
35 35 elif name.startswith('path:'): # directory or full path test
36 36 p = name[5:] # prefix
37 37 pl = len(p)
38 38 f = lambda n, s: n.startswith(p) and (len(n) == pl
39 39 or n[pl:pl + 1] == '/')
40 40 return f
41 41 raise error.ParseError(_("unsupported file pattern: %s") % name,
42 42 hint=_('paths must be prefixed with "path:"'))
43 43 elif op == 'or':
44 44 funcs = [_compile(x) for x in tree[1:]]
45 45 return lambda n, s: any(f(n, s) for f in funcs)
46 46 elif op == 'and':
47 47 func1 = _compile(tree[1])
48 48 func2 = _compile(tree[2])
49 49 return lambda n, s: func1(n, s) and func2(n, s)
50 50 elif op == 'not':
51 51 return lambda n, s: not _compile(tree[1])(n, s)
52 52 elif op == 'group':
53 53 return _compile(tree[1])
54 54 elif op == 'func':
55 55 symbols = {
56 56 'all': lambda n, s: True,
57 57 'none': lambda n, s: False,
58 58 'size': lambda n, s: _sizep(tree[2])(s),
59 59 }
60 60
61 61 name = filesetlang.getsymbol(tree[1])
62 62 if name in symbols:
63 63 return symbols[name]
64 64
65 65 raise error.UnknownIdentifier(name, symbols.keys())
66 66 elif op == 'minus': # equivalent to 'x and not y'
67 67 func1 = _compile(tree[1])
68 68 func2 = _compile(tree[2])
69 69 return lambda n, s: func1(n, s) and not func2(n, s)
70 70 elif op == 'negate':
71 71 raise error.ParseError(_("can't use negate operator in this context"))
72 72 elif op == 'list':
73 73 raise error.ParseError(_("can't use a list in this context"),
74 hint=_('see hg help "filesets.x or y"'))
74 hint=_('see \'hg help "filesets.x or y"\''))
75 75 raise error.ProgrammingError('illegal tree: %r' % (tree,))
76 76
77 77 def compile(text):
78 78 """generate a function (path, size) -> bool from filter specification.
79 79
80 80 "text" could contain the operators defined by the fileset language for
81 81 common logic operations, and parenthesis for grouping. The supported path
82 82 tests are '**.extname' for file extension test, and '"path:dir/subdir"'
83 83 for prefix test. The ``size()`` predicate is borrowed from filesets to test
84 84 file size. The predicates ``all()`` and ``none()`` are also supported.
85 85
86 86 '(**.php & size(">10MB")) | **.zip | (path:bin & !path:bin/README)' for
87 87 example, will catch all php files whose size is greater than 10 MB, all
88 88 files whose name ends with ".zip", and all files under "bin" in the repo
89 89 root except for "bin/README".
90 90 """
91 91 tree = filesetlang.parse(text)
92 92 return _compile(tree)
@@ -1,2282 +1,2282 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import re
11 11
12 12 from .i18n import _
13 13 from . import (
14 14 dagop,
15 15 destutil,
16 16 diffutil,
17 17 encoding,
18 18 error,
19 19 hbisect,
20 20 match as matchmod,
21 21 node,
22 22 obsolete as obsmod,
23 23 obsutil,
24 24 pathutil,
25 25 phases,
26 26 pycompat,
27 27 registrar,
28 28 repoview,
29 29 revsetlang,
30 30 scmutil,
31 31 smartset,
32 32 stack as stackmod,
33 33 util,
34 34 )
35 35 from .utils import (
36 36 dateutil,
37 37 stringutil,
38 38 )
39 39
40 40 # helpers for processing parsed tree
41 41 getsymbol = revsetlang.getsymbol
42 42 getstring = revsetlang.getstring
43 43 getinteger = revsetlang.getinteger
44 44 getboolean = revsetlang.getboolean
45 45 getlist = revsetlang.getlist
46 46 getrange = revsetlang.getrange
47 47 getargs = revsetlang.getargs
48 48 getargsdict = revsetlang.getargsdict
49 49
50 50 baseset = smartset.baseset
51 51 generatorset = smartset.generatorset
52 52 spanset = smartset.spanset
53 53 fullreposet = smartset.fullreposet
54 54
55 55 # Constants for ordering requirement, used in getset():
56 56 #
57 57 # If 'define', any nested functions and operations MAY change the ordering of
58 58 # the entries in the set (but if changes the ordering, it MUST ALWAYS change
59 59 # it). If 'follow', any nested functions and operations MUST take the ordering
60 60 # specified by the first operand to the '&' operator.
61 61 #
62 62 # For instance,
63 63 #
64 64 # X & (Y | Z)
65 65 # ^ ^^^^^^^
66 66 # | follow
67 67 # define
68 68 #
69 69 # will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
70 70 # of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
71 71 #
72 72 # 'any' means the order doesn't matter. For instance,
73 73 #
74 74 # (X & !Y) | ancestors(Z)
75 75 # ^ ^
76 76 # any any
77 77 #
78 78 # For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
79 79 # order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
80 80 # since 'ancestors' does not care about the order of its argument.
81 81 #
82 82 # Currently, most revsets do not care about the order, so 'define' is
83 83 # equivalent to 'follow' for them, and the resulting order is based on the
84 84 # 'subset' parameter passed down to them:
85 85 #
86 86 # m = revset.match(...)
87 87 # m(repo, subset, order=defineorder)
88 88 # ^^^^^^
89 89 # For most revsets, 'define' means using the order this subset provides
90 90 #
91 91 # There are a few revsets that always redefine the order if 'define' is
92 92 # specified: 'sort(X)', 'reverse(X)', 'x:y'.
93 93 anyorder = 'any' # don't care the order, could be even random-shuffled
94 94 defineorder = 'define' # ALWAYS redefine, or ALWAYS follow the current order
95 95 followorder = 'follow' # MUST follow the current order
96 96
97 97 # helpers
98 98
99 99 def getset(repo, subset, x, order=defineorder):
100 100 if not x:
101 101 raise error.ParseError(_("missing argument"))
102 102 return methods[x[0]](repo, subset, *x[1:], order=order)
103 103
104 104 def _getrevsource(repo, r):
105 105 extra = repo[r].extra()
106 106 for label in ('source', 'transplant_source', 'rebase_source'):
107 107 if label in extra:
108 108 try:
109 109 return repo[extra[label]].rev()
110 110 except error.RepoLookupError:
111 111 pass
112 112 return None
113 113
114 114 def _sortedb(xs):
115 115 return sorted(pycompat.rapply(pycompat.maybebytestr, xs))
116 116
117 117 # operator methods
118 118
119 119 def stringset(repo, subset, x, order):
120 120 if not x:
121 121 raise error.ParseError(_("empty string is not a valid revision"))
122 122 x = scmutil.intrev(scmutil.revsymbol(repo, x))
123 123 if (x in subset
124 124 or x == node.nullrev and isinstance(subset, fullreposet)):
125 125 return baseset([x])
126 126 return baseset()
127 127
128 128 def rangeset(repo, subset, x, y, order):
129 129 m = getset(repo, fullreposet(repo), x)
130 130 n = getset(repo, fullreposet(repo), y)
131 131
132 132 if not m or not n:
133 133 return baseset()
134 134 return _makerangeset(repo, subset, m.first(), n.last(), order)
135 135
136 136 def rangeall(repo, subset, x, order):
137 137 assert x is None
138 138 return _makerangeset(repo, subset, 0, repo.changelog.tiprev(), order)
139 139
140 140 def rangepre(repo, subset, y, order):
141 141 # ':y' can't be rewritten to '0:y' since '0' may be hidden
142 142 n = getset(repo, fullreposet(repo), y)
143 143 if not n:
144 144 return baseset()
145 145 return _makerangeset(repo, subset, 0, n.last(), order)
146 146
147 147 def rangepost(repo, subset, x, order):
148 148 m = getset(repo, fullreposet(repo), x)
149 149 if not m:
150 150 return baseset()
151 151 return _makerangeset(repo, subset, m.first(), repo.changelog.tiprev(),
152 152 order)
153 153
154 154 def _makerangeset(repo, subset, m, n, order):
155 155 if m == n:
156 156 r = baseset([m])
157 157 elif n == node.wdirrev:
158 158 r = spanset(repo, m, len(repo)) + baseset([n])
159 159 elif m == node.wdirrev:
160 160 r = baseset([m]) + spanset(repo, repo.changelog.tiprev(), n - 1)
161 161 elif m < n:
162 162 r = spanset(repo, m, n + 1)
163 163 else:
164 164 r = spanset(repo, m, n - 1)
165 165
166 166 if order == defineorder:
167 167 return r & subset
168 168 else:
169 169 # carrying the sorting over when possible would be more efficient
170 170 return subset & r
171 171
172 172 def dagrange(repo, subset, x, y, order):
173 173 r = fullreposet(repo)
174 174 xs = dagop.reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
175 175 includepath=True)
176 176 return subset & xs
177 177
178 178 def andset(repo, subset, x, y, order):
179 179 if order == anyorder:
180 180 yorder = anyorder
181 181 else:
182 182 yorder = followorder
183 183 return getset(repo, getset(repo, subset, x, order), y, yorder)
184 184
185 185 def andsmallyset(repo, subset, x, y, order):
186 186 # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
187 187 if order == anyorder:
188 188 yorder = anyorder
189 189 else:
190 190 yorder = followorder
191 191 return getset(repo, getset(repo, subset, y, yorder), x, order)
192 192
193 193 def differenceset(repo, subset, x, y, order):
194 194 return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
195 195
196 196 def _orsetlist(repo, subset, xs, order):
197 197 assert xs
198 198 if len(xs) == 1:
199 199 return getset(repo, subset, xs[0], order)
200 200 p = len(xs) // 2
201 201 a = _orsetlist(repo, subset, xs[:p], order)
202 202 b = _orsetlist(repo, subset, xs[p:], order)
203 203 return a + b
204 204
205 205 def orset(repo, subset, x, order):
206 206 xs = getlist(x)
207 207 if not xs:
208 208 return baseset()
209 209 if order == followorder:
210 210 # slow path to take the subset order
211 211 return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
212 212 else:
213 213 return _orsetlist(repo, subset, xs, order)
214 214
215 215 def notset(repo, subset, x, order):
216 216 return subset - getset(repo, subset, x, anyorder)
217 217
218 218 def relationset(repo, subset, x, y, order):
219 219 raise error.ParseError(_("can't use a relation in this context"))
220 220
221 221 def relsubscriptset(repo, subset, x, y, z, order):
222 222 # this is pretty basic implementation of 'x#y[z]' operator, still
223 223 # experimental so undocumented. see the wiki for further ideas.
224 224 # https://www.mercurial-scm.org/wiki/RevsetOperatorPlan
225 225 rel = getsymbol(y)
226 226 n = getinteger(z, _("relation subscript must be an integer"))
227 227
228 228 # TODO: perhaps this should be a table of relation functions
229 229 if rel in ('g', 'generations'):
230 230 # TODO: support range, rewrite tests, and drop startdepth argument
231 231 # from ancestors() and descendants() predicates
232 232 if n <= 0:
233 233 n = -n
234 234 return _ancestors(repo, subset, x, startdepth=n, stopdepth=n + 1)
235 235 else:
236 236 return _descendants(repo, subset, x, startdepth=n, stopdepth=n + 1)
237 237
238 238 raise error.UnknownIdentifier(rel, ['generations'])
239 239
240 240 def subscriptset(repo, subset, x, y, order):
241 241 raise error.ParseError(_("can't use a subscript in this context"))
242 242
243 243 def listset(repo, subset, *xs, **opts):
244 244 raise error.ParseError(_("can't use a list in this context"),
245 hint=_('see hg help "revsets.x or y"'))
245 hint=_('see \'hg help "revsets.x or y"\''))
246 246
247 247 def keyvaluepair(repo, subset, k, v, order):
248 248 raise error.ParseError(_("can't use a key-value pair in this context"))
249 249
250 250 def func(repo, subset, a, b, order):
251 251 f = getsymbol(a)
252 252 if f in symbols:
253 253 func = symbols[f]
254 254 if getattr(func, '_takeorder', False):
255 255 return func(repo, subset, b, order)
256 256 return func(repo, subset, b)
257 257
258 258 keep = lambda fn: getattr(fn, '__doc__', None) is not None
259 259
260 260 syms = [s for (s, fn) in symbols.items() if keep(fn)]
261 261 raise error.UnknownIdentifier(f, syms)
262 262
263 263 # functions
264 264
265 265 # symbols are callables like:
266 266 # fn(repo, subset, x)
267 267 # with:
268 268 # repo - current repository instance
269 269 # subset - of revisions to be examined
270 270 # x - argument in tree form
271 271 symbols = revsetlang.symbols
272 272
273 273 # symbols which can't be used for a DoS attack for any given input
274 274 # (e.g. those which accept regexes as plain strings shouldn't be included)
275 275 # functions that just return a lot of changesets (like all) don't count here
276 276 safesymbols = set()
277 277
278 278 predicate = registrar.revsetpredicate()
279 279
280 280 @predicate('_destupdate')
281 281 def _destupdate(repo, subset, x):
282 282 # experimental revset for update destination
283 283 args = getargsdict(x, 'limit', 'clean')
284 284 return subset & baseset([destutil.destupdate(repo,
285 285 **pycompat.strkwargs(args))[0]])
286 286
287 287 @predicate('_destmerge')
288 288 def _destmerge(repo, subset, x):
289 289 # experimental revset for merge destination
290 290 sourceset = None
291 291 if x is not None:
292 292 sourceset = getset(repo, fullreposet(repo), x)
293 293 return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
294 294
295 295 @predicate('adds(pattern)', safe=True, weight=30)
296 296 def adds(repo, subset, x):
297 297 """Changesets that add a file matching pattern.
298 298
299 299 The pattern without explicit kind like ``glob:`` is expected to be
300 300 relative to the current directory and match against a file or a
301 301 directory.
302 302 """
303 303 # i18n: "adds" is a keyword
304 304 pat = getstring(x, _("adds requires a pattern"))
305 305 return checkstatus(repo, subset, pat, 1)
306 306
307 307 @predicate('ancestor(*changeset)', safe=True, weight=0.5)
308 308 def ancestor(repo, subset, x):
309 309 """A greatest common ancestor of the changesets.
310 310
311 311 Accepts 0 or more changesets.
312 312 Will return empty list when passed no args.
313 313 Greatest common ancestor of a single changeset is that changeset.
314 314 """
315 315 reviter = iter(orset(repo, fullreposet(repo), x, order=anyorder))
316 316 try:
317 317 anc = repo[next(reviter)]
318 318 except StopIteration:
319 319 return baseset()
320 320 for r in reviter:
321 321 anc = anc.ancestor(repo[r])
322 322
323 323 r = scmutil.intrev(anc)
324 324 if r in subset:
325 325 return baseset([r])
326 326 return baseset()
327 327
328 328 def _ancestors(repo, subset, x, followfirst=False, startdepth=None,
329 329 stopdepth=None):
330 330 heads = getset(repo, fullreposet(repo), x)
331 331 if not heads:
332 332 return baseset()
333 333 s = dagop.revancestors(repo, heads, followfirst, startdepth, stopdepth)
334 334 return subset & s
335 335
336 336 @predicate('ancestors(set[, depth])', safe=True)
337 337 def ancestors(repo, subset, x):
338 338 """Changesets that are ancestors of changesets in set, including the
339 339 given changesets themselves.
340 340
341 341 If depth is specified, the result only includes changesets up to
342 342 the specified generation.
343 343 """
344 344 # startdepth is for internal use only until we can decide the UI
345 345 args = getargsdict(x, 'ancestors', 'set depth startdepth')
346 346 if 'set' not in args:
347 347 # i18n: "ancestors" is a keyword
348 348 raise error.ParseError(_('ancestors takes at least 1 argument'))
349 349 startdepth = stopdepth = None
350 350 if 'startdepth' in args:
351 351 n = getinteger(args['startdepth'],
352 352 "ancestors expects an integer startdepth")
353 353 if n < 0:
354 354 raise error.ParseError("negative startdepth")
355 355 startdepth = n
356 356 if 'depth' in args:
357 357 # i18n: "ancestors" is a keyword
358 358 n = getinteger(args['depth'], _("ancestors expects an integer depth"))
359 359 if n < 0:
360 360 raise error.ParseError(_("negative depth"))
361 361 stopdepth = n + 1
362 362 return _ancestors(repo, subset, args['set'],
363 363 startdepth=startdepth, stopdepth=stopdepth)
364 364
365 365 @predicate('_firstancestors', safe=True)
366 366 def _firstancestors(repo, subset, x):
367 367 # ``_firstancestors(set)``
368 368 # Like ``ancestors(set)`` but follows only the first parents.
369 369 return _ancestors(repo, subset, x, followfirst=True)
370 370
371 371 def _childrenspec(repo, subset, x, n, order):
372 372 """Changesets that are the Nth child of a changeset
373 373 in set.
374 374 """
375 375 cs = set()
376 376 for r in getset(repo, fullreposet(repo), x):
377 377 for i in range(n):
378 378 c = repo[r].children()
379 379 if len(c) == 0:
380 380 break
381 381 if len(c) > 1:
382 382 raise error.RepoLookupError(
383 383 _("revision in set has more than one child"))
384 384 r = c[0].rev()
385 385 else:
386 386 cs.add(r)
387 387 return subset & cs
388 388
389 389 def ancestorspec(repo, subset, x, n, order):
390 390 """``set~n``
391 391 Changesets that are the Nth ancestor (first parents only) of a changeset
392 392 in set.
393 393 """
394 394 n = getinteger(n, _("~ expects a number"))
395 395 if n < 0:
396 396 # children lookup
397 397 return _childrenspec(repo, subset, x, -n, order)
398 398 ps = set()
399 399 cl = repo.changelog
400 400 for r in getset(repo, fullreposet(repo), x):
401 401 for i in range(n):
402 402 try:
403 403 r = cl.parentrevs(r)[0]
404 404 except error.WdirUnsupported:
405 405 r = repo[r].parents()[0].rev()
406 406 ps.add(r)
407 407 return subset & ps
408 408
409 409 @predicate('author(string)', safe=True, weight=10)
410 410 def author(repo, subset, x):
411 411 """Alias for ``user(string)``.
412 412 """
413 413 # i18n: "author" is a keyword
414 414 n = getstring(x, _("author requires a string"))
415 415 kind, pattern, matcher = _substringmatcher(n, casesensitive=False)
416 416 return subset.filter(lambda x: matcher(repo[x].user()),
417 417 condrepr=('<user %r>', n))
418 418
419 419 @predicate('bisect(string)', safe=True)
420 420 def bisect(repo, subset, x):
421 421 """Changesets marked in the specified bisect status:
422 422
423 423 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
424 424 - ``goods``, ``bads`` : csets topologically good/bad
425 425 - ``range`` : csets taking part in the bisection
426 426 - ``pruned`` : csets that are goods, bads or skipped
427 427 - ``untested`` : csets whose fate is yet unknown
428 428 - ``ignored`` : csets ignored due to DAG topology
429 429 - ``current`` : the cset currently being bisected
430 430 """
431 431 # i18n: "bisect" is a keyword
432 432 status = getstring(x, _("bisect requires a string")).lower()
433 433 state = set(hbisect.get(repo, status))
434 434 return subset & state
435 435
436 436 # Backward-compatibility
437 437 # - no help entry so that we do not advertise it any more
438 438 @predicate('bisected', safe=True)
439 439 def bisected(repo, subset, x):
440 440 return bisect(repo, subset, x)
441 441
442 442 @predicate('bookmark([name])', safe=True)
443 443 def bookmark(repo, subset, x):
444 444 """The named bookmark or all bookmarks.
445 445
446 446 Pattern matching is supported for `name`. See :hg:`help revisions.patterns`.
447 447 """
448 448 # i18n: "bookmark" is a keyword
449 449 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
450 450 if args:
451 451 bm = getstring(args[0],
452 452 # i18n: "bookmark" is a keyword
453 453 _('the argument to bookmark must be a string'))
454 454 kind, pattern, matcher = stringutil.stringmatcher(bm)
455 455 bms = set()
456 456 if kind == 'literal':
457 457 bmrev = repo._bookmarks.get(pattern, None)
458 458 if not bmrev:
459 459 raise error.RepoLookupError(_("bookmark '%s' does not exist")
460 460 % pattern)
461 461 bms.add(repo[bmrev].rev())
462 462 else:
463 463 matchrevs = set()
464 464 for name, bmrev in repo._bookmarks.iteritems():
465 465 if matcher(name):
466 466 matchrevs.add(bmrev)
467 467 if not matchrevs:
468 468 raise error.RepoLookupError(_("no bookmarks exist"
469 469 " that match '%s'") % pattern)
470 470 for bmrev in matchrevs:
471 471 bms.add(repo[bmrev].rev())
472 472 else:
473 473 bms = {repo[r].rev() for r in repo._bookmarks.values()}
474 474 bms -= {node.nullrev}
475 475 return subset & bms
476 476
477 477 @predicate('branch(string or set)', safe=True, weight=10)
478 478 def branch(repo, subset, x):
479 479 """
480 480 All changesets belonging to the given branch or the branches of the given
481 481 changesets.
482 482
483 483 Pattern matching is supported for `string`. See
484 484 :hg:`help revisions.patterns`.
485 485 """
486 486 getbi = repo.revbranchcache().branchinfo
487 487 def getbranch(r):
488 488 try:
489 489 return getbi(r)[0]
490 490 except error.WdirUnsupported:
491 491 return repo[r].branch()
492 492
493 493 try:
494 494 b = getstring(x, '')
495 495 except error.ParseError:
496 496 # not a string, but another revspec, e.g. tip()
497 497 pass
498 498 else:
499 499 kind, pattern, matcher = stringutil.stringmatcher(b)
500 500 if kind == 'literal':
501 501 # note: falls through to the revspec case if no branch with
502 502 # this name exists and pattern kind is not specified explicitly
503 503 if pattern in repo.branchmap():
504 504 return subset.filter(lambda r: matcher(getbranch(r)),
505 505 condrepr=('<branch %r>', b))
506 506 if b.startswith('literal:'):
507 507 raise error.RepoLookupError(_("branch '%s' does not exist")
508 508 % pattern)
509 509 else:
510 510 return subset.filter(lambda r: matcher(getbranch(r)),
511 511 condrepr=('<branch %r>', b))
512 512
513 513 s = getset(repo, fullreposet(repo), x)
514 514 b = set()
515 515 for r in s:
516 516 b.add(getbranch(r))
517 517 c = s.__contains__
518 518 return subset.filter(lambda r: c(r) or getbranch(r) in b,
519 519 condrepr=lambda: '<branch %r>' % _sortedb(b))
520 520
521 521 @predicate('phasedivergent()', safe=True)
522 522 def phasedivergent(repo, subset, x):
523 523 """Mutable changesets marked as successors of public changesets.
524 524
525 525 Only non-public and non-obsolete changesets can be `phasedivergent`.
526 526 (EXPERIMENTAL)
527 527 """
528 528 # i18n: "phasedivergent" is a keyword
529 529 getargs(x, 0, 0, _("phasedivergent takes no arguments"))
530 530 phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
531 531 return subset & phasedivergent
532 532
533 533 @predicate('bundle()', safe=True)
534 534 def bundle(repo, subset, x):
535 535 """Changesets in the bundle.
536 536
537 537 Bundle must be specified by the -R option."""
538 538
539 539 try:
540 540 bundlerevs = repo.changelog.bundlerevs
541 541 except AttributeError:
542 542 raise error.Abort(_("no bundle provided - specify with -R"))
543 543 return subset & bundlerevs
544 544
545 545 def checkstatus(repo, subset, pat, field):
546 546 hasset = matchmod.patkind(pat) == 'set'
547 547
548 548 mcache = [None]
549 549 def matches(x):
550 550 c = repo[x]
551 551 if not mcache[0] or hasset:
552 552 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
553 553 m = mcache[0]
554 554 fname = None
555 555 if not m.anypats() and len(m.files()) == 1:
556 556 fname = m.files()[0]
557 557 if fname is not None:
558 558 if fname not in c.files():
559 559 return False
560 560 else:
561 561 for f in c.files():
562 562 if m(f):
563 563 break
564 564 else:
565 565 return False
566 566 files = repo.status(c.p1().node(), c.node())[field]
567 567 if fname is not None:
568 568 if fname in files:
569 569 return True
570 570 else:
571 571 for f in files:
572 572 if m(f):
573 573 return True
574 574
575 575 return subset.filter(matches, condrepr=('<status[%r] %r>', field, pat))
576 576
577 577 def _children(repo, subset, parentset):
578 578 if not parentset:
579 579 return baseset()
580 580 cs = set()
581 581 pr = repo.changelog.parentrevs
582 582 minrev = parentset.min()
583 583 nullrev = node.nullrev
584 584 for r in subset:
585 585 if r <= minrev:
586 586 continue
587 587 p1, p2 = pr(r)
588 588 if p1 in parentset:
589 589 cs.add(r)
590 590 if p2 != nullrev and p2 in parentset:
591 591 cs.add(r)
592 592 return baseset(cs)
593 593
594 594 @predicate('children(set)', safe=True)
595 595 def children(repo, subset, x):
596 596 """Child changesets of changesets in set.
597 597 """
598 598 s = getset(repo, fullreposet(repo), x)
599 599 cs = _children(repo, subset, s)
600 600 return subset & cs
601 601
602 602 @predicate('closed()', safe=True, weight=10)
603 603 def closed(repo, subset, x):
604 604 """Changeset is closed.
605 605 """
606 606 # i18n: "closed" is a keyword
607 607 getargs(x, 0, 0, _("closed takes no arguments"))
608 608 return subset.filter(lambda r: repo[r].closesbranch(),
609 609 condrepr='<branch closed>')
610 610
611 611 # for internal use
612 612 @predicate('_commonancestorheads(set)', safe=True)
613 613 def _commonancestorheads(repo, subset, x):
614 614 # This is an internal method is for quickly calculating "heads(::x and
615 615 # ::y)"
616 616
617 617 # These greatest common ancestors are the same ones that the consesus bid
618 618 # merge will find.
619 619 h = heads(repo, fullreposet(repo), x, anyorder)
620 620
621 621 ancs = repo.changelog._commonancestorsheads(*list(h))
622 622 return subset & baseset(ancs)
623 623
624 624 @predicate('commonancestors(set)', safe=True)
625 625 def commonancestors(repo, subset, x):
626 626 """Returns all common ancestors of the set.
627 627
628 628 This method is for calculating "::x and ::y" (i.e. all the ancestors that
629 629 are common to both x and y) in an easy and optimized way. We can't quite
630 630 use "::head()" because that revset returns "::x + ::y + ..." for each head
631 631 in the repo (whereas we want "::x *and* ::y").
632 632
633 633 """
634 634 # only wants the heads of the set passed in
635 635 h = heads(repo, fullreposet(repo), x, anyorder)
636 636 if not h:
637 637 return baseset()
638 638 for r in h:
639 639 subset &= dagop.revancestors(repo, baseset([r]))
640 640
641 641 return subset
642 642
643 643 @predicate('contains(pattern)', weight=100)
644 644 def contains(repo, subset, x):
645 645 """The revision's manifest contains a file matching pattern (but might not
646 646 modify it). See :hg:`help patterns` for information about file patterns.
647 647
648 648 The pattern without explicit kind like ``glob:`` is expected to be
649 649 relative to the current directory and match against a file exactly
650 650 for efficiency.
651 651 """
652 652 # i18n: "contains" is a keyword
653 653 pat = getstring(x, _("contains requires a pattern"))
654 654
655 655 def matches(x):
656 656 if not matchmod.patkind(pat):
657 657 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
658 658 if pats in repo[x]:
659 659 return True
660 660 else:
661 661 c = repo[x]
662 662 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
663 663 for f in c.manifest():
664 664 if m(f):
665 665 return True
666 666 return False
667 667
668 668 return subset.filter(matches, condrepr=('<contains %r>', pat))
669 669
670 670 @predicate('converted([id])', safe=True)
671 671 def converted(repo, subset, x):
672 672 """Changesets converted from the given identifier in the old repository if
673 673 present, or all converted changesets if no identifier is specified.
674 674 """
675 675
676 676 # There is exactly no chance of resolving the revision, so do a simple
677 677 # string compare and hope for the best
678 678
679 679 rev = None
680 680 # i18n: "converted" is a keyword
681 681 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
682 682 if l:
683 683 # i18n: "converted" is a keyword
684 684 rev = getstring(l[0], _('converted requires a revision'))
685 685
686 686 def _matchvalue(r):
687 687 source = repo[r].extra().get('convert_revision', None)
688 688 return source is not None and (rev is None or source.startswith(rev))
689 689
690 690 return subset.filter(lambda r: _matchvalue(r),
691 691 condrepr=('<converted %r>', rev))
692 692
693 693 @predicate('date(interval)', safe=True, weight=10)
694 694 def date(repo, subset, x):
695 695 """Changesets within the interval, see :hg:`help dates`.
696 696 """
697 697 # i18n: "date" is a keyword
698 698 ds = getstring(x, _("date requires a string"))
699 699 dm = dateutil.matchdate(ds)
700 700 return subset.filter(lambda x: dm(repo[x].date()[0]),
701 701 condrepr=('<date %r>', ds))
702 702
703 703 @predicate('desc(string)', safe=True, weight=10)
704 704 def desc(repo, subset, x):
705 705 """Search commit message for string. The match is case-insensitive.
706 706
707 707 Pattern matching is supported for `string`. See
708 708 :hg:`help revisions.patterns`.
709 709 """
710 710 # i18n: "desc" is a keyword
711 711 ds = getstring(x, _("desc requires a string"))
712 712
713 713 kind, pattern, matcher = _substringmatcher(ds, casesensitive=False)
714 714
715 715 return subset.filter(lambda r: matcher(repo[r].description()),
716 716 condrepr=('<desc %r>', ds))
717 717
718 718 def _descendants(repo, subset, x, followfirst=False, startdepth=None,
719 719 stopdepth=None):
720 720 roots = getset(repo, fullreposet(repo), x)
721 721 if not roots:
722 722 return baseset()
723 723 s = dagop.revdescendants(repo, roots, followfirst, startdepth, stopdepth)
724 724 return subset & s
725 725
726 726 @predicate('descendants(set[, depth])', safe=True)
727 727 def descendants(repo, subset, x):
728 728 """Changesets which are descendants of changesets in set, including the
729 729 given changesets themselves.
730 730
731 731 If depth is specified, the result only includes changesets up to
732 732 the specified generation.
733 733 """
734 734 # startdepth is for internal use only until we can decide the UI
735 735 args = getargsdict(x, 'descendants', 'set depth startdepth')
736 736 if 'set' not in args:
737 737 # i18n: "descendants" is a keyword
738 738 raise error.ParseError(_('descendants takes at least 1 argument'))
739 739 startdepth = stopdepth = None
740 740 if 'startdepth' in args:
741 741 n = getinteger(args['startdepth'],
742 742 "descendants expects an integer startdepth")
743 743 if n < 0:
744 744 raise error.ParseError("negative startdepth")
745 745 startdepth = n
746 746 if 'depth' in args:
747 747 # i18n: "descendants" is a keyword
748 748 n = getinteger(args['depth'], _("descendants expects an integer depth"))
749 749 if n < 0:
750 750 raise error.ParseError(_("negative depth"))
751 751 stopdepth = n + 1
752 752 return _descendants(repo, subset, args['set'],
753 753 startdepth=startdepth, stopdepth=stopdepth)
754 754
755 755 @predicate('_firstdescendants', safe=True)
756 756 def _firstdescendants(repo, subset, x):
757 757 # ``_firstdescendants(set)``
758 758 # Like ``descendants(set)`` but follows only the first parents.
759 759 return _descendants(repo, subset, x, followfirst=True)
760 760
761 761 @predicate('destination([set])', safe=True, weight=10)
762 762 def destination(repo, subset, x):
763 763 """Changesets that were created by a graft, transplant or rebase operation,
764 764 with the given revisions specified as the source. Omitting the optional set
765 765 is the same as passing all().
766 766 """
767 767 if x is not None:
768 768 sources = getset(repo, fullreposet(repo), x)
769 769 else:
770 770 sources = fullreposet(repo)
771 771
772 772 dests = set()
773 773
774 774 # subset contains all of the possible destinations that can be returned, so
775 775 # iterate over them and see if their source(s) were provided in the arg set.
776 776 # Even if the immediate src of r is not in the arg set, src's source (or
777 777 # further back) may be. Scanning back further than the immediate src allows
778 778 # transitive transplants and rebases to yield the same results as transitive
779 779 # grafts.
780 780 for r in subset:
781 781 src = _getrevsource(repo, r)
782 782 lineage = None
783 783
784 784 while src is not None:
785 785 if lineage is None:
786 786 lineage = list()
787 787
788 788 lineage.append(r)
789 789
790 790 # The visited lineage is a match if the current source is in the arg
791 791 # set. Since every candidate dest is visited by way of iterating
792 792 # subset, any dests further back in the lineage will be tested by a
793 793 # different iteration over subset. Likewise, if the src was already
794 794 # selected, the current lineage can be selected without going back
795 795 # further.
796 796 if src in sources or src in dests:
797 797 dests.update(lineage)
798 798 break
799 799
800 800 r = src
801 801 src = _getrevsource(repo, r)
802 802
803 803 return subset.filter(dests.__contains__,
804 804 condrepr=lambda: '<destination %r>' % _sortedb(dests))
805 805
806 806 @predicate('contentdivergent()', safe=True)
807 807 def contentdivergent(repo, subset, x):
808 808 """
809 809 Final successors of changesets with an alternative set of final
810 810 successors. (EXPERIMENTAL)
811 811 """
812 812 # i18n: "contentdivergent" is a keyword
813 813 getargs(x, 0, 0, _("contentdivergent takes no arguments"))
814 814 contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
815 815 return subset & contentdivergent
816 816
817 817 @predicate('extdata(source)', safe=False, weight=100)
818 818 def extdata(repo, subset, x):
819 819 """Changesets in the specified extdata source. (EXPERIMENTAL)"""
820 820 # i18n: "extdata" is a keyword
821 821 args = getargsdict(x, 'extdata', 'source')
822 822 source = getstring(args.get('source'),
823 823 # i18n: "extdata" is a keyword
824 824 _('extdata takes at least 1 string argument'))
825 825 data = scmutil.extdatasource(repo, source)
826 826 return subset & baseset(data)
827 827
828 828 @predicate('extinct()', safe=True)
829 829 def extinct(repo, subset, x):
830 830 """Obsolete changesets with obsolete descendants only.
831 831 """
832 832 # i18n: "extinct" is a keyword
833 833 getargs(x, 0, 0, _("extinct takes no arguments"))
834 834 extincts = obsmod.getrevs(repo, 'extinct')
835 835 return subset & extincts
836 836
837 837 @predicate('extra(label, [value])', safe=True)
838 838 def extra(repo, subset, x):
839 839 """Changesets with the given label in the extra metadata, with the given
840 840 optional value.
841 841
842 842 Pattern matching is supported for `value`. See
843 843 :hg:`help revisions.patterns`.
844 844 """
845 845 args = getargsdict(x, 'extra', 'label value')
846 846 if 'label' not in args:
847 847 # i18n: "extra" is a keyword
848 848 raise error.ParseError(_('extra takes at least 1 argument'))
849 849 # i18n: "extra" is a keyword
850 850 label = getstring(args['label'], _('first argument to extra must be '
851 851 'a string'))
852 852 value = None
853 853
854 854 if 'value' in args:
855 855 # i18n: "extra" is a keyword
856 856 value = getstring(args['value'], _('second argument to extra must be '
857 857 'a string'))
858 858 kind, value, matcher = stringutil.stringmatcher(value)
859 859
860 860 def _matchvalue(r):
861 861 extra = repo[r].extra()
862 862 return label in extra and (value is None or matcher(extra[label]))
863 863
864 864 return subset.filter(lambda r: _matchvalue(r),
865 865 condrepr=('<extra[%r] %r>', label, value))
866 866
867 867 @predicate('filelog(pattern)', safe=True)
868 868 def filelog(repo, subset, x):
869 869 """Changesets connected to the specified filelog.
870 870
871 871 For performance reasons, visits only revisions mentioned in the file-level
872 872 filelog, rather than filtering through all changesets (much faster, but
873 873 doesn't include deletes or duplicate changes). For a slower, more accurate
874 874 result, use ``file()``.
875 875
876 876 The pattern without explicit kind like ``glob:`` is expected to be
877 877 relative to the current directory and match against a file exactly
878 878 for efficiency.
879 879
880 880 If some linkrev points to revisions filtered by the current repoview, we'll
881 881 work around it to return a non-filtered value.
882 882 """
883 883
884 884 # i18n: "filelog" is a keyword
885 885 pat = getstring(x, _("filelog requires a pattern"))
886 886 s = set()
887 887 cl = repo.changelog
888 888
889 889 if not matchmod.patkind(pat):
890 890 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
891 891 files = [f]
892 892 else:
893 893 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
894 894 files = (f for f in repo[None] if m(f))
895 895
896 896 for f in files:
897 897 fl = repo.file(f)
898 898 known = {}
899 899 scanpos = 0
900 900 for fr in list(fl):
901 901 fn = fl.node(fr)
902 902 if fn in known:
903 903 s.add(known[fn])
904 904 continue
905 905
906 906 lr = fl.linkrev(fr)
907 907 if lr in cl:
908 908 s.add(lr)
909 909 elif scanpos is not None:
910 910 # lowest matching changeset is filtered, scan further
911 911 # ahead in changelog
912 912 start = max(lr, scanpos) + 1
913 913 scanpos = None
914 914 for r in cl.revs(start):
915 915 # minimize parsing of non-matching entries
916 916 if f in cl.revision(r) and f in cl.readfiles(r):
917 917 try:
918 918 # try to use manifest delta fastpath
919 919 n = repo[r].filenode(f)
920 920 if n not in known:
921 921 if n == fn:
922 922 s.add(r)
923 923 scanpos = r
924 924 break
925 925 else:
926 926 known[n] = r
927 927 except error.ManifestLookupError:
928 928 # deletion in changelog
929 929 continue
930 930
931 931 return subset & s
932 932
933 933 @predicate('first(set, [n])', safe=True, takeorder=True, weight=0)
934 934 def first(repo, subset, x, order):
935 935 """An alias for limit().
936 936 """
937 937 return limit(repo, subset, x, order)
938 938
939 939 def _follow(repo, subset, x, name, followfirst=False):
940 940 args = getargsdict(x, name, 'file startrev')
941 941 revs = None
942 942 if 'startrev' in args:
943 943 revs = getset(repo, fullreposet(repo), args['startrev'])
944 944 if 'file' in args:
945 945 x = getstring(args['file'], _("%s expected a pattern") % name)
946 946 if revs is None:
947 947 revs = [None]
948 948 fctxs = []
949 949 for r in revs:
950 950 ctx = mctx = repo[r]
951 951 if r is None:
952 952 ctx = repo['.']
953 953 m = matchmod.match(repo.root, repo.getcwd(), [x],
954 954 ctx=mctx, default='path')
955 955 fctxs.extend(ctx[f].introfilectx() for f in ctx.manifest().walk(m))
956 956 s = dagop.filerevancestors(fctxs, followfirst)
957 957 else:
958 958 if revs is None:
959 959 revs = baseset([repo['.'].rev()])
960 960 s = dagop.revancestors(repo, revs, followfirst)
961 961
962 962 return subset & s
963 963
964 964 @predicate('follow([file[, startrev]])', safe=True)
965 965 def follow(repo, subset, x):
966 966 """
967 967 An alias for ``::.`` (ancestors of the working directory's first parent).
968 968 If file pattern is specified, the histories of files matching given
969 969 pattern in the revision given by startrev are followed, including copies.
970 970 """
971 971 return _follow(repo, subset, x, 'follow')
972 972
973 973 @predicate('_followfirst', safe=True)
974 974 def _followfirst(repo, subset, x):
975 975 # ``followfirst([file[, startrev]])``
976 976 # Like ``follow([file[, startrev]])`` but follows only the first parent
977 977 # of every revisions or files revisions.
978 978 return _follow(repo, subset, x, '_followfirst', followfirst=True)
979 979
980 980 @predicate('followlines(file, fromline:toline[, startrev=., descend=False])',
981 981 safe=True)
982 982 def followlines(repo, subset, x):
983 983 """Changesets modifying `file` in line range ('fromline', 'toline').
984 984
985 985 Line range corresponds to 'file' content at 'startrev' and should hence be
986 986 consistent with file size. If startrev is not specified, working directory's
987 987 parent is used.
988 988
989 989 By default, ancestors of 'startrev' are returned. If 'descend' is True,
990 990 descendants of 'startrev' are returned though renames are (currently) not
991 991 followed in this direction.
992 992 """
993 993 args = getargsdict(x, 'followlines', 'file *lines startrev descend')
994 994 if len(args['lines']) != 1:
995 995 raise error.ParseError(_("followlines requires a line range"))
996 996
997 997 rev = '.'
998 998 if 'startrev' in args:
999 999 revs = getset(repo, fullreposet(repo), args['startrev'])
1000 1000 if len(revs) != 1:
1001 1001 raise error.ParseError(
1002 1002 # i18n: "followlines" is a keyword
1003 1003 _("followlines expects exactly one revision"))
1004 1004 rev = revs.last()
1005 1005
1006 1006 pat = getstring(args['file'], _("followlines requires a pattern"))
1007 1007 # i18n: "followlines" is a keyword
1008 1008 msg = _("followlines expects exactly one file")
1009 1009 fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
1010 1010 # i18n: "followlines" is a keyword
1011 1011 lr = getrange(args['lines'][0], _("followlines expects a line range"))
1012 1012 fromline, toline = [getinteger(a, _("line range bounds must be integers"))
1013 1013 for a in lr]
1014 1014 fromline, toline = util.processlinerange(fromline, toline)
1015 1015
1016 1016 fctx = repo[rev].filectx(fname)
1017 1017 descend = False
1018 1018 if 'descend' in args:
1019 1019 descend = getboolean(args['descend'],
1020 1020 # i18n: "descend" is a keyword
1021 1021 _("descend argument must be a boolean"))
1022 1022 if descend:
1023 1023 rs = generatorset(
1024 1024 (c.rev() for c, _linerange
1025 1025 in dagop.blockdescendants(fctx, fromline, toline)),
1026 1026 iterasc=True)
1027 1027 else:
1028 1028 rs = generatorset(
1029 1029 (c.rev() for c, _linerange
1030 1030 in dagop.blockancestors(fctx, fromline, toline)),
1031 1031 iterasc=False)
1032 1032 return subset & rs
1033 1033
1034 1034 @predicate('all()', safe=True)
1035 1035 def getall(repo, subset, x):
1036 1036 """All changesets, the same as ``0:tip``.
1037 1037 """
1038 1038 # i18n: "all" is a keyword
1039 1039 getargs(x, 0, 0, _("all takes no arguments"))
1040 1040 return subset & spanset(repo) # drop "null" if any
1041 1041
1042 1042 @predicate('grep(regex)', weight=10)
1043 1043 def grep(repo, subset, x):
1044 1044 """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1045 1045 to ensure special escape characters are handled correctly. Unlike
1046 1046 ``keyword(string)``, the match is case-sensitive.
1047 1047 """
1048 1048 try:
1049 1049 # i18n: "grep" is a keyword
1050 1050 gr = re.compile(getstring(x, _("grep requires a string")))
1051 1051 except re.error as e:
1052 1052 raise error.ParseError(
1053 1053 _('invalid match pattern: %s') % stringutil.forcebytestr(e))
1054 1054
1055 1055 def matches(x):
1056 1056 c = repo[x]
1057 1057 for e in c.files() + [c.user(), c.description()]:
1058 1058 if gr.search(e):
1059 1059 return True
1060 1060 return False
1061 1061
1062 1062 return subset.filter(matches, condrepr=('<grep %r>', gr.pattern))
1063 1063
1064 1064 @predicate('_matchfiles', safe=True)
1065 1065 def _matchfiles(repo, subset, x):
1066 1066 # _matchfiles takes a revset list of prefixed arguments:
1067 1067 #
1068 1068 # [p:foo, i:bar, x:baz]
1069 1069 #
1070 1070 # builds a match object from them and filters subset. Allowed
1071 1071 # prefixes are 'p:' for regular patterns, 'i:' for include
1072 1072 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1073 1073 # a revision identifier, or the empty string to reference the
1074 1074 # working directory, from which the match object is
1075 1075 # initialized. Use 'd:' to set the default matching mode, default
1076 1076 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1077 1077
1078 1078 l = getargs(x, 1, -1, "_matchfiles requires at least one argument")
1079 1079 pats, inc, exc = [], [], []
1080 1080 rev, default = None, None
1081 1081 for arg in l:
1082 1082 s = getstring(arg, "_matchfiles requires string arguments")
1083 1083 prefix, value = s[:2], s[2:]
1084 1084 if prefix == 'p:':
1085 1085 pats.append(value)
1086 1086 elif prefix == 'i:':
1087 1087 inc.append(value)
1088 1088 elif prefix == 'x:':
1089 1089 exc.append(value)
1090 1090 elif prefix == 'r:':
1091 1091 if rev is not None:
1092 1092 raise error.ParseError('_matchfiles expected at most one '
1093 1093 'revision')
1094 1094 if value == '': # empty means working directory
1095 1095 rev = node.wdirrev
1096 1096 else:
1097 1097 rev = value
1098 1098 elif prefix == 'd:':
1099 1099 if default is not None:
1100 1100 raise error.ParseError('_matchfiles expected at most one '
1101 1101 'default mode')
1102 1102 default = value
1103 1103 else:
1104 1104 raise error.ParseError('invalid _matchfiles prefix: %s' % prefix)
1105 1105 if not default:
1106 1106 default = 'glob'
1107 1107 hasset = any(matchmod.patkind(p) == 'set' for p in pats + inc + exc)
1108 1108
1109 1109 mcache = [None]
1110 1110
1111 1111 # This directly read the changelog data as creating changectx for all
1112 1112 # revisions is quite expensive.
1113 1113 getfiles = repo.changelog.readfiles
1114 1114 wdirrev = node.wdirrev
1115 1115 def matches(x):
1116 1116 if x == wdirrev:
1117 1117 files = repo[x].files()
1118 1118 else:
1119 1119 files = getfiles(x)
1120 1120
1121 1121 if not mcache[0] or (hasset and rev is None):
1122 1122 r = x if rev is None else rev
1123 1123 mcache[0] = matchmod.match(repo.root, repo.getcwd(), pats,
1124 1124 include=inc, exclude=exc, ctx=repo[r],
1125 1125 default=default)
1126 1126 m = mcache[0]
1127 1127
1128 1128 for f in files:
1129 1129 if m(f):
1130 1130 return True
1131 1131 return False
1132 1132
1133 1133 return subset.filter(matches,
1134 1134 condrepr=('<matchfiles patterns=%r, include=%r '
1135 1135 'exclude=%r, default=%r, rev=%r>',
1136 1136 pats, inc, exc, default, rev))
1137 1137
1138 1138 @predicate('file(pattern)', safe=True, weight=10)
1139 1139 def hasfile(repo, subset, x):
1140 1140 """Changesets affecting files matched by pattern.
1141 1141
1142 1142 For a faster but less accurate result, consider using ``filelog()``
1143 1143 instead.
1144 1144
1145 1145 This predicate uses ``glob:`` as the default kind of pattern.
1146 1146 """
1147 1147 # i18n: "file" is a keyword
1148 1148 pat = getstring(x, _("file requires a pattern"))
1149 1149 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1150 1150
1151 1151 @predicate('head()', safe=True)
1152 1152 def head(repo, subset, x):
1153 1153 """Changeset is a named branch head.
1154 1154 """
1155 1155 # i18n: "head" is a keyword
1156 1156 getargs(x, 0, 0, _("head takes no arguments"))
1157 1157 hs = set()
1158 1158 cl = repo.changelog
1159 1159 for ls in repo.branchmap().itervalues():
1160 1160 hs.update(cl.rev(h) for h in ls)
1161 1161 return subset & baseset(hs)
1162 1162
1163 1163 @predicate('heads(set)', safe=True, takeorder=True)
1164 1164 def heads(repo, subset, x, order):
1165 1165 """Members of set with no children in set.
1166 1166 """
1167 1167 # argument set should never define order
1168 1168 if order == defineorder:
1169 1169 order = followorder
1170 1170 s = getset(repo, subset, x, order=order)
1171 1171 ps = parents(repo, subset, x)
1172 1172 return s - ps
1173 1173
1174 1174 @predicate('hidden()', safe=True)
1175 1175 def hidden(repo, subset, x):
1176 1176 """Hidden changesets.
1177 1177 """
1178 1178 # i18n: "hidden" is a keyword
1179 1179 getargs(x, 0, 0, _("hidden takes no arguments"))
1180 1180 hiddenrevs = repoview.filterrevs(repo, 'visible')
1181 1181 return subset & hiddenrevs
1182 1182
1183 1183 @predicate('keyword(string)', safe=True, weight=10)
1184 1184 def keyword(repo, subset, x):
1185 1185 """Search commit message, user name, and names of changed files for
1186 1186 string. The match is case-insensitive.
1187 1187
1188 1188 For a regular expression or case sensitive search of these fields, use
1189 1189 ``grep(regex)``.
1190 1190 """
1191 1191 # i18n: "keyword" is a keyword
1192 1192 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1193 1193
1194 1194 def matches(r):
1195 1195 c = repo[r]
1196 1196 return any(kw in encoding.lower(t)
1197 1197 for t in c.files() + [c.user(), c.description()])
1198 1198
1199 1199 return subset.filter(matches, condrepr=('<keyword %r>', kw))
1200 1200
1201 1201 @predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
1202 1202 def limit(repo, subset, x, order):
1203 1203 """First n members of set, defaulting to 1, starting from offset.
1204 1204 """
1205 1205 args = getargsdict(x, 'limit', 'set n offset')
1206 1206 if 'set' not in args:
1207 1207 # i18n: "limit" is a keyword
1208 1208 raise error.ParseError(_("limit requires one to three arguments"))
1209 1209 # i18n: "limit" is a keyword
1210 1210 lim = getinteger(args.get('n'), _("limit expects a number"), default=1)
1211 1211 if lim < 0:
1212 1212 raise error.ParseError(_("negative number to select"))
1213 1213 # i18n: "limit" is a keyword
1214 1214 ofs = getinteger(args.get('offset'), _("limit expects a number"), default=0)
1215 1215 if ofs < 0:
1216 1216 raise error.ParseError(_("negative offset"))
1217 1217 os = getset(repo, fullreposet(repo), args['set'])
1218 1218 ls = os.slice(ofs, ofs + lim)
1219 1219 if order == followorder and lim > 1:
1220 1220 return subset & ls
1221 1221 return ls & subset
1222 1222
1223 1223 @predicate('last(set, [n])', safe=True, takeorder=True)
1224 1224 def last(repo, subset, x, order):
1225 1225 """Last n members of set, defaulting to 1.
1226 1226 """
1227 1227 # i18n: "last" is a keyword
1228 1228 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1229 1229 lim = 1
1230 1230 if len(l) == 2:
1231 1231 # i18n: "last" is a keyword
1232 1232 lim = getinteger(l[1], _("last expects a number"))
1233 1233 if lim < 0:
1234 1234 raise error.ParseError(_("negative number to select"))
1235 1235 os = getset(repo, fullreposet(repo), l[0])
1236 1236 os.reverse()
1237 1237 ls = os.slice(0, lim)
1238 1238 if order == followorder and lim > 1:
1239 1239 return subset & ls
1240 1240 ls.reverse()
1241 1241 return ls & subset
1242 1242
1243 1243 @predicate('max(set)', safe=True)
1244 1244 def maxrev(repo, subset, x):
1245 1245 """Changeset with highest revision number in set.
1246 1246 """
1247 1247 os = getset(repo, fullreposet(repo), x)
1248 1248 try:
1249 1249 m = os.max()
1250 1250 if m in subset:
1251 1251 return baseset([m], datarepr=('<max %r, %r>', subset, os))
1252 1252 except ValueError:
1253 1253 # os.max() throws a ValueError when the collection is empty.
1254 1254 # Same as python's max().
1255 1255 pass
1256 1256 return baseset(datarepr=('<max %r, %r>', subset, os))
1257 1257
1258 1258 @predicate('merge()', safe=True)
1259 1259 def merge(repo, subset, x):
1260 1260 """Changeset is a merge changeset.
1261 1261 """
1262 1262 # i18n: "merge" is a keyword
1263 1263 getargs(x, 0, 0, _("merge takes no arguments"))
1264 1264 cl = repo.changelog
1265 1265 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1,
1266 1266 condrepr='<merge>')
1267 1267
1268 1268 @predicate('branchpoint()', safe=True)
1269 1269 def branchpoint(repo, subset, x):
1270 1270 """Changesets with more than one child.
1271 1271 """
1272 1272 # i18n: "branchpoint" is a keyword
1273 1273 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1274 1274 cl = repo.changelog
1275 1275 if not subset:
1276 1276 return baseset()
1277 1277 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1278 1278 # (and if it is not, it should.)
1279 1279 baserev = min(subset)
1280 1280 parentscount = [0]*(len(repo) - baserev)
1281 1281 for r in cl.revs(start=baserev + 1):
1282 1282 for p in cl.parentrevs(r):
1283 1283 if p >= baserev:
1284 1284 parentscount[p - baserev] += 1
1285 1285 return subset.filter(lambda r: parentscount[r - baserev] > 1,
1286 1286 condrepr='<branchpoint>')
1287 1287
1288 1288 @predicate('min(set)', safe=True)
1289 1289 def minrev(repo, subset, x):
1290 1290 """Changeset with lowest revision number in set.
1291 1291 """
1292 1292 os = getset(repo, fullreposet(repo), x)
1293 1293 try:
1294 1294 m = os.min()
1295 1295 if m in subset:
1296 1296 return baseset([m], datarepr=('<min %r, %r>', subset, os))
1297 1297 except ValueError:
1298 1298 # os.min() throws a ValueError when the collection is empty.
1299 1299 # Same as python's min().
1300 1300 pass
1301 1301 return baseset(datarepr=('<min %r, %r>', subset, os))
1302 1302
1303 1303 @predicate('modifies(pattern)', safe=True, weight=30)
1304 1304 def modifies(repo, subset, x):
1305 1305 """Changesets modifying files matched by pattern.
1306 1306
1307 1307 The pattern without explicit kind like ``glob:`` is expected to be
1308 1308 relative to the current directory and match against a file or a
1309 1309 directory.
1310 1310 """
1311 1311 # i18n: "modifies" is a keyword
1312 1312 pat = getstring(x, _("modifies requires a pattern"))
1313 1313 return checkstatus(repo, subset, pat, 0)
1314 1314
1315 1315 @predicate('named(namespace)')
1316 1316 def named(repo, subset, x):
1317 1317 """The changesets in a given namespace.
1318 1318
1319 1319 Pattern matching is supported for `namespace`. See
1320 1320 :hg:`help revisions.patterns`.
1321 1321 """
1322 1322 # i18n: "named" is a keyword
1323 1323 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1324 1324
1325 1325 ns = getstring(args[0],
1326 1326 # i18n: "named" is a keyword
1327 1327 _('the argument to named must be a string'))
1328 1328 kind, pattern, matcher = stringutil.stringmatcher(ns)
1329 1329 namespaces = set()
1330 1330 if kind == 'literal':
1331 1331 if pattern not in repo.names:
1332 1332 raise error.RepoLookupError(_("namespace '%s' does not exist")
1333 1333 % ns)
1334 1334 namespaces.add(repo.names[pattern])
1335 1335 else:
1336 1336 for name, ns in repo.names.iteritems():
1337 1337 if matcher(name):
1338 1338 namespaces.add(ns)
1339 1339 if not namespaces:
1340 1340 raise error.RepoLookupError(_("no namespace exists"
1341 1341 " that match '%s'") % pattern)
1342 1342
1343 1343 names = set()
1344 1344 for ns in namespaces:
1345 1345 for name in ns.listnames(repo):
1346 1346 if name not in ns.deprecated:
1347 1347 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1348 1348
1349 1349 names -= {node.nullrev}
1350 1350 return subset & names
1351 1351
1352 1352 @predicate('id(string)', safe=True)
1353 1353 def node_(repo, subset, x):
1354 1354 """Revision non-ambiguously specified by the given hex string prefix.
1355 1355 """
1356 1356 # i18n: "id" is a keyword
1357 1357 l = getargs(x, 1, 1, _("id requires one argument"))
1358 1358 # i18n: "id" is a keyword
1359 1359 n = getstring(l[0], _("id requires a string"))
1360 1360 if len(n) == 40:
1361 1361 try:
1362 1362 rn = repo.changelog.rev(node.bin(n))
1363 1363 except error.WdirUnsupported:
1364 1364 rn = node.wdirrev
1365 1365 except (LookupError, TypeError):
1366 1366 rn = None
1367 1367 else:
1368 1368 rn = None
1369 1369 try:
1370 1370 pm = scmutil.resolvehexnodeidprefix(repo, n)
1371 1371 if pm is not None:
1372 1372 rn = repo.changelog.rev(pm)
1373 1373 except LookupError:
1374 1374 pass
1375 1375 except error.WdirUnsupported:
1376 1376 rn = node.wdirrev
1377 1377
1378 1378 if rn is None:
1379 1379 return baseset()
1380 1380 result = baseset([rn])
1381 1381 return result & subset
1382 1382
1383 1383 @predicate('none()', safe=True)
1384 1384 def none(repo, subset, x):
1385 1385 """No changesets.
1386 1386 """
1387 1387 # i18n: "none" is a keyword
1388 1388 getargs(x, 0, 0, _("none takes no arguments"))
1389 1389 return baseset()
1390 1390
1391 1391 @predicate('obsolete()', safe=True)
1392 1392 def obsolete(repo, subset, x):
1393 1393 """Mutable changeset with a newer version."""
1394 1394 # i18n: "obsolete" is a keyword
1395 1395 getargs(x, 0, 0, _("obsolete takes no arguments"))
1396 1396 obsoletes = obsmod.getrevs(repo, 'obsolete')
1397 1397 return subset & obsoletes
1398 1398
1399 1399 @predicate('only(set, [set])', safe=True)
1400 1400 def only(repo, subset, x):
1401 1401 """Changesets that are ancestors of the first set that are not ancestors
1402 1402 of any other head in the repo. If a second set is specified, the result
1403 1403 is ancestors of the first set that are not ancestors of the second set
1404 1404 (i.e. ::<set1> - ::<set2>).
1405 1405 """
1406 1406 cl = repo.changelog
1407 1407 # i18n: "only" is a keyword
1408 1408 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1409 1409 include = getset(repo, fullreposet(repo), args[0])
1410 1410 if len(args) == 1:
1411 1411 if not include:
1412 1412 return baseset()
1413 1413
1414 1414 descendants = set(dagop.revdescendants(repo, include, False))
1415 1415 exclude = [rev for rev in cl.headrevs()
1416 1416 if not rev in descendants and not rev in include]
1417 1417 else:
1418 1418 exclude = getset(repo, fullreposet(repo), args[1])
1419 1419
1420 1420 results = set(cl.findmissingrevs(common=exclude, heads=include))
1421 1421 # XXX we should turn this into a baseset instead of a set, smartset may do
1422 1422 # some optimizations from the fact this is a baseset.
1423 1423 return subset & results
1424 1424
1425 1425 @predicate('origin([set])', safe=True)
1426 1426 def origin(repo, subset, x):
1427 1427 """
1428 1428 Changesets that were specified as a source for the grafts, transplants or
1429 1429 rebases that created the given revisions. Omitting the optional set is the
1430 1430 same as passing all(). If a changeset created by these operations is itself
1431 1431 specified as a source for one of these operations, only the source changeset
1432 1432 for the first operation is selected.
1433 1433 """
1434 1434 if x is not None:
1435 1435 dests = getset(repo, fullreposet(repo), x)
1436 1436 else:
1437 1437 dests = fullreposet(repo)
1438 1438
1439 1439 def _firstsrc(rev):
1440 1440 src = _getrevsource(repo, rev)
1441 1441 if src is None:
1442 1442 return None
1443 1443
1444 1444 while True:
1445 1445 prev = _getrevsource(repo, src)
1446 1446
1447 1447 if prev is None:
1448 1448 return src
1449 1449 src = prev
1450 1450
1451 1451 o = {_firstsrc(r) for r in dests}
1452 1452 o -= {None}
1453 1453 # XXX we should turn this into a baseset instead of a set, smartset may do
1454 1454 # some optimizations from the fact this is a baseset.
1455 1455 return subset & o
1456 1456
1457 1457 @predicate('outgoing([path])', safe=False, weight=10)
1458 1458 def outgoing(repo, subset, x):
1459 1459 """Changesets not found in the specified destination repository, or the
1460 1460 default push location.
1461 1461 """
1462 1462 # Avoid cycles.
1463 1463 from . import (
1464 1464 discovery,
1465 1465 hg,
1466 1466 )
1467 1467 # i18n: "outgoing" is a keyword
1468 1468 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1469 1469 # i18n: "outgoing" is a keyword
1470 1470 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1471 1471 if not dest:
1472 1472 # ui.paths.getpath() explicitly tests for None, not just a boolean
1473 1473 dest = None
1474 1474 path = repo.ui.paths.getpath(dest, default=('default-push', 'default'))
1475 1475 if not path:
1476 1476 raise error.Abort(_('default repository not configured!'),
1477 1477 hint=_("see 'hg help config.paths'"))
1478 1478 dest = path.pushloc or path.loc
1479 1479 branches = path.branch, []
1480 1480
1481 1481 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1482 1482 if revs:
1483 1483 revs = [repo.lookup(rev) for rev in revs]
1484 1484 other = hg.peer(repo, {}, dest)
1485 1485 repo.ui.pushbuffer()
1486 1486 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1487 1487 repo.ui.popbuffer()
1488 1488 cl = repo.changelog
1489 1489 o = {cl.rev(r) for r in outgoing.missing}
1490 1490 return subset & o
1491 1491
1492 1492 @predicate('p1([set])', safe=True)
1493 1493 def p1(repo, subset, x):
1494 1494 """First parent of changesets in set, or the working directory.
1495 1495 """
1496 1496 if x is None:
1497 1497 p = repo[x].p1().rev()
1498 1498 if p >= 0:
1499 1499 return subset & baseset([p])
1500 1500 return baseset()
1501 1501
1502 1502 ps = set()
1503 1503 cl = repo.changelog
1504 1504 for r in getset(repo, fullreposet(repo), x):
1505 1505 try:
1506 1506 ps.add(cl.parentrevs(r)[0])
1507 1507 except error.WdirUnsupported:
1508 1508 ps.add(repo[r].parents()[0].rev())
1509 1509 ps -= {node.nullrev}
1510 1510 # XXX we should turn this into a baseset instead of a set, smartset may do
1511 1511 # some optimizations from the fact this is a baseset.
1512 1512 return subset & ps
1513 1513
1514 1514 @predicate('p2([set])', safe=True)
1515 1515 def p2(repo, subset, x):
1516 1516 """Second parent of changesets in set, or the working directory.
1517 1517 """
1518 1518 if x is None:
1519 1519 ps = repo[x].parents()
1520 1520 try:
1521 1521 p = ps[1].rev()
1522 1522 if p >= 0:
1523 1523 return subset & baseset([p])
1524 1524 return baseset()
1525 1525 except IndexError:
1526 1526 return baseset()
1527 1527
1528 1528 ps = set()
1529 1529 cl = repo.changelog
1530 1530 for r in getset(repo, fullreposet(repo), x):
1531 1531 try:
1532 1532 ps.add(cl.parentrevs(r)[1])
1533 1533 except error.WdirUnsupported:
1534 1534 parents = repo[r].parents()
1535 1535 if len(parents) == 2:
1536 1536 ps.add(parents[1])
1537 1537 ps -= {node.nullrev}
1538 1538 # XXX we should turn this into a baseset instead of a set, smartset may do
1539 1539 # some optimizations from the fact this is a baseset.
1540 1540 return subset & ps
1541 1541
1542 1542 def parentpost(repo, subset, x, order):
1543 1543 return p1(repo, subset, x)
1544 1544
1545 1545 @predicate('parents([set])', safe=True)
1546 1546 def parents(repo, subset, x):
1547 1547 """
1548 1548 The set of all parents for all changesets in set, or the working directory.
1549 1549 """
1550 1550 if x is None:
1551 1551 ps = set(p.rev() for p in repo[x].parents())
1552 1552 else:
1553 1553 ps = set()
1554 1554 cl = repo.changelog
1555 1555 up = ps.update
1556 1556 parentrevs = cl.parentrevs
1557 1557 for r in getset(repo, fullreposet(repo), x):
1558 1558 try:
1559 1559 up(parentrevs(r))
1560 1560 except error.WdirUnsupported:
1561 1561 up(p.rev() for p in repo[r].parents())
1562 1562 ps -= {node.nullrev}
1563 1563 return subset & ps
1564 1564
1565 1565 def _phase(repo, subset, *targets):
1566 1566 """helper to select all rev in <targets> phases"""
1567 1567 return repo._phasecache.getrevset(repo, targets, subset)
1568 1568
1569 1569 @predicate('draft()', safe=True)
1570 1570 def draft(repo, subset, x):
1571 1571 """Changeset in draft phase."""
1572 1572 # i18n: "draft" is a keyword
1573 1573 getargs(x, 0, 0, _("draft takes no arguments"))
1574 1574 target = phases.draft
1575 1575 return _phase(repo, subset, target)
1576 1576
1577 1577 @predicate('secret()', safe=True)
1578 1578 def secret(repo, subset, x):
1579 1579 """Changeset in secret phase."""
1580 1580 # i18n: "secret" is a keyword
1581 1581 getargs(x, 0, 0, _("secret takes no arguments"))
1582 1582 target = phases.secret
1583 1583 return _phase(repo, subset, target)
1584 1584
1585 1585 @predicate('stack([revs])', safe=True)
1586 1586 def stack(repo, subset, x):
1587 1587 """Experimental revset for the stack of changesets or working directory
1588 1588 parent. (EXPERIMENTAL)
1589 1589 """
1590 1590 if x is None:
1591 1591 stacks = stackmod.getstack(repo, x)
1592 1592 else:
1593 1593 stacks = smartset.baseset([])
1594 1594 for revision in getset(repo, fullreposet(repo), x):
1595 1595 currentstack = stackmod.getstack(repo, revision)
1596 1596 stacks = stacks + currentstack
1597 1597
1598 1598 return subset & stacks
1599 1599
1600 1600 def parentspec(repo, subset, x, n, order):
1601 1601 """``set^0``
1602 1602 The set.
1603 1603 ``set^1`` (or ``set^``), ``set^2``
1604 1604 First or second parent, respectively, of all changesets in set.
1605 1605 """
1606 1606 try:
1607 1607 n = int(n[1])
1608 1608 if n not in (0, 1, 2):
1609 1609 raise ValueError
1610 1610 except (TypeError, ValueError):
1611 1611 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1612 1612 ps = set()
1613 1613 cl = repo.changelog
1614 1614 for r in getset(repo, fullreposet(repo), x):
1615 1615 if n == 0:
1616 1616 ps.add(r)
1617 1617 elif n == 1:
1618 1618 try:
1619 1619 ps.add(cl.parentrevs(r)[0])
1620 1620 except error.WdirUnsupported:
1621 1621 ps.add(repo[r].parents()[0].rev())
1622 1622 else:
1623 1623 try:
1624 1624 parents = cl.parentrevs(r)
1625 1625 if parents[1] != node.nullrev:
1626 1626 ps.add(parents[1])
1627 1627 except error.WdirUnsupported:
1628 1628 parents = repo[r].parents()
1629 1629 if len(parents) == 2:
1630 1630 ps.add(parents[1].rev())
1631 1631 return subset & ps
1632 1632
1633 1633 @predicate('present(set)', safe=True, takeorder=True)
1634 1634 def present(repo, subset, x, order):
1635 1635 """An empty set, if any revision in set isn't found; otherwise,
1636 1636 all revisions in set.
1637 1637
1638 1638 If any of specified revisions is not present in the local repository,
1639 1639 the query is normally aborted. But this predicate allows the query
1640 1640 to continue even in such cases.
1641 1641 """
1642 1642 try:
1643 1643 return getset(repo, subset, x, order)
1644 1644 except error.RepoLookupError:
1645 1645 return baseset()
1646 1646
1647 1647 # for internal use
1648 1648 @predicate('_notpublic', safe=True)
1649 1649 def _notpublic(repo, subset, x):
1650 1650 getargs(x, 0, 0, "_notpublic takes no arguments")
1651 1651 return _phase(repo, subset, phases.draft, phases.secret)
1652 1652
1653 1653 # for internal use
1654 1654 @predicate('_phaseandancestors(phasename, set)', safe=True)
1655 1655 def _phaseandancestors(repo, subset, x):
1656 1656 # equivalent to (phasename() & ancestors(set)) but more efficient
1657 1657 # phasename could be one of 'draft', 'secret', or '_notpublic'
1658 1658 args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
1659 1659 phasename = getsymbol(args[0])
1660 1660 s = getset(repo, fullreposet(repo), args[1])
1661 1661
1662 1662 draft = phases.draft
1663 1663 secret = phases.secret
1664 1664 phasenamemap = {
1665 1665 '_notpublic': draft,
1666 1666 'draft': draft, # follow secret's ancestors
1667 1667 'secret': secret,
1668 1668 }
1669 1669 if phasename not in phasenamemap:
1670 1670 raise error.ParseError('%r is not a valid phasename' % phasename)
1671 1671
1672 1672 minimalphase = phasenamemap[phasename]
1673 1673 getphase = repo._phasecache.phase
1674 1674
1675 1675 def cutfunc(rev):
1676 1676 return getphase(repo, rev) < minimalphase
1677 1677
1678 1678 revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
1679 1679
1680 1680 if phasename == 'draft': # need to remove secret changesets
1681 1681 revs = revs.filter(lambda r: getphase(repo, r) == draft)
1682 1682 return subset & revs
1683 1683
1684 1684 @predicate('public()', safe=True)
1685 1685 def public(repo, subset, x):
1686 1686 """Changeset in public phase."""
1687 1687 # i18n: "public" is a keyword
1688 1688 getargs(x, 0, 0, _("public takes no arguments"))
1689 1689 return _phase(repo, subset, phases.public)
1690 1690
1691 1691 @predicate('remote([id [,path]])', safe=False)
1692 1692 def remote(repo, subset, x):
1693 1693 """Local revision that corresponds to the given identifier in a
1694 1694 remote repository, if present. Here, the '.' identifier is a
1695 1695 synonym for the current local branch.
1696 1696 """
1697 1697
1698 1698 from . import hg # avoid start-up nasties
1699 1699 # i18n: "remote" is a keyword
1700 1700 l = getargs(x, 0, 2, _("remote takes zero, one, or two arguments"))
1701 1701
1702 1702 q = '.'
1703 1703 if len(l) > 0:
1704 1704 # i18n: "remote" is a keyword
1705 1705 q = getstring(l[0], _("remote requires a string id"))
1706 1706 if q == '.':
1707 1707 q = repo['.'].branch()
1708 1708
1709 1709 dest = ''
1710 1710 if len(l) > 1:
1711 1711 # i18n: "remote" is a keyword
1712 1712 dest = getstring(l[1], _("remote requires a repository path"))
1713 1713 dest = repo.ui.expandpath(dest or 'default')
1714 1714 dest, branches = hg.parseurl(dest)
1715 1715 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1716 1716 if revs:
1717 1717 revs = [repo.lookup(rev) for rev in revs]
1718 1718 other = hg.peer(repo, {}, dest)
1719 1719 n = other.lookup(q)
1720 1720 if n in repo:
1721 1721 r = repo[n].rev()
1722 1722 if r in subset:
1723 1723 return baseset([r])
1724 1724 return baseset()
1725 1725
1726 1726 @predicate('removes(pattern)', safe=True, weight=30)
1727 1727 def removes(repo, subset, x):
1728 1728 """Changesets which remove files matching pattern.
1729 1729
1730 1730 The pattern without explicit kind like ``glob:`` is expected to be
1731 1731 relative to the current directory and match against a file or a
1732 1732 directory.
1733 1733 """
1734 1734 # i18n: "removes" is a keyword
1735 1735 pat = getstring(x, _("removes requires a pattern"))
1736 1736 return checkstatus(repo, subset, pat, 2)
1737 1737
1738 1738 @predicate('rev(number)', safe=True)
1739 1739 def rev(repo, subset, x):
1740 1740 """Revision with the given numeric identifier.
1741 1741 """
1742 1742 # i18n: "rev" is a keyword
1743 1743 l = getargs(x, 1, 1, _("rev requires one argument"))
1744 1744 try:
1745 1745 # i18n: "rev" is a keyword
1746 1746 l = int(getstring(l[0], _("rev requires a number")))
1747 1747 except (TypeError, ValueError):
1748 1748 # i18n: "rev" is a keyword
1749 1749 raise error.ParseError(_("rev expects a number"))
1750 1750 if l not in repo.changelog and l not in (node.nullrev, node.wdirrev):
1751 1751 return baseset()
1752 1752 return subset & baseset([l])
1753 1753
1754 1754 @predicate('matching(revision [, field])', safe=True)
1755 1755 def matching(repo, subset, x):
1756 1756 """Changesets in which a given set of fields match the set of fields in the
1757 1757 selected revision or set.
1758 1758
1759 1759 To match more than one field pass the list of fields to match separated
1760 1760 by spaces (e.g. ``author description``).
1761 1761
1762 1762 Valid fields are most regular revision fields and some special fields.
1763 1763
1764 1764 Regular revision fields are ``description``, ``author``, ``branch``,
1765 1765 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1766 1766 and ``diff``.
1767 1767 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1768 1768 contents of the revision. Two revisions matching their ``diff`` will
1769 1769 also match their ``files``.
1770 1770
1771 1771 Special fields are ``summary`` and ``metadata``:
1772 1772 ``summary`` matches the first line of the description.
1773 1773 ``metadata`` is equivalent to matching ``description user date``
1774 1774 (i.e. it matches the main metadata fields).
1775 1775
1776 1776 ``metadata`` is the default field which is used when no fields are
1777 1777 specified. You can match more than one field at a time.
1778 1778 """
1779 1779 # i18n: "matching" is a keyword
1780 1780 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1781 1781
1782 1782 revs = getset(repo, fullreposet(repo), l[0])
1783 1783
1784 1784 fieldlist = ['metadata']
1785 1785 if len(l) > 1:
1786 1786 fieldlist = getstring(l[1],
1787 1787 # i18n: "matching" is a keyword
1788 1788 _("matching requires a string "
1789 1789 "as its second argument")).split()
1790 1790
1791 1791 # Make sure that there are no repeated fields,
1792 1792 # expand the 'special' 'metadata' field type
1793 1793 # and check the 'files' whenever we check the 'diff'
1794 1794 fields = []
1795 1795 for field in fieldlist:
1796 1796 if field == 'metadata':
1797 1797 fields += ['user', 'description', 'date']
1798 1798 elif field == 'diff':
1799 1799 # a revision matching the diff must also match the files
1800 1800 # since matching the diff is very costly, make sure to
1801 1801 # also match the files first
1802 1802 fields += ['files', 'diff']
1803 1803 else:
1804 1804 if field == 'author':
1805 1805 field = 'user'
1806 1806 fields.append(field)
1807 1807 fields = set(fields)
1808 1808 if 'summary' in fields and 'description' in fields:
1809 1809 # If a revision matches its description it also matches its summary
1810 1810 fields.discard('summary')
1811 1811
1812 1812 # We may want to match more than one field
1813 1813 # Not all fields take the same amount of time to be matched
1814 1814 # Sort the selected fields in order of increasing matching cost
1815 1815 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1816 1816 'files', 'description', 'substate', 'diff']
1817 1817 def fieldkeyfunc(f):
1818 1818 try:
1819 1819 return fieldorder.index(f)
1820 1820 except ValueError:
1821 1821 # assume an unknown field is very costly
1822 1822 return len(fieldorder)
1823 1823 fields = list(fields)
1824 1824 fields.sort(key=fieldkeyfunc)
1825 1825
1826 1826 # Each field will be matched with its own "getfield" function
1827 1827 # which will be added to the getfieldfuncs array of functions
1828 1828 getfieldfuncs = []
1829 1829 _funcs = {
1830 1830 'user': lambda r: repo[r].user(),
1831 1831 'branch': lambda r: repo[r].branch(),
1832 1832 'date': lambda r: repo[r].date(),
1833 1833 'description': lambda r: repo[r].description(),
1834 1834 'files': lambda r: repo[r].files(),
1835 1835 'parents': lambda r: repo[r].parents(),
1836 1836 'phase': lambda r: repo[r].phase(),
1837 1837 'substate': lambda r: repo[r].substate,
1838 1838 'summary': lambda r: repo[r].description().splitlines()[0],
1839 1839 'diff': lambda r: list(repo[r].diff(
1840 1840 opts=diffutil.diffallopts(repo.ui, {'git': True}))),
1841 1841 }
1842 1842 for info in fields:
1843 1843 getfield = _funcs.get(info, None)
1844 1844 if getfield is None:
1845 1845 raise error.ParseError(
1846 1846 # i18n: "matching" is a keyword
1847 1847 _("unexpected field name passed to matching: %s") % info)
1848 1848 getfieldfuncs.append(getfield)
1849 1849 # convert the getfield array of functions into a "getinfo" function
1850 1850 # which returns an array of field values (or a single value if there
1851 1851 # is only one field to match)
1852 1852 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1853 1853
1854 1854 def matches(x):
1855 1855 for rev in revs:
1856 1856 target = getinfo(rev)
1857 1857 match = True
1858 1858 for n, f in enumerate(getfieldfuncs):
1859 1859 if target[n] != f(x):
1860 1860 match = False
1861 1861 if match:
1862 1862 return True
1863 1863 return False
1864 1864
1865 1865 return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
1866 1866
1867 1867 @predicate('reverse(set)', safe=True, takeorder=True, weight=0)
1868 1868 def reverse(repo, subset, x, order):
1869 1869 """Reverse order of set.
1870 1870 """
1871 1871 l = getset(repo, subset, x, order)
1872 1872 if order == defineorder:
1873 1873 l.reverse()
1874 1874 return l
1875 1875
1876 1876 @predicate('roots(set)', safe=True)
1877 1877 def roots(repo, subset, x):
1878 1878 """Changesets in set with no parent changeset in set.
1879 1879 """
1880 1880 s = getset(repo, fullreposet(repo), x)
1881 1881 parents = repo.changelog.parentrevs
1882 1882 def filter(r):
1883 1883 for p in parents(r):
1884 1884 if 0 <= p and p in s:
1885 1885 return False
1886 1886 return True
1887 1887 return subset & s.filter(filter, condrepr='<roots>')
1888 1888
1889 1889 _sortkeyfuncs = {
1890 1890 'rev': lambda c: c.rev(),
1891 1891 'branch': lambda c: c.branch(),
1892 1892 'desc': lambda c: c.description(),
1893 1893 'user': lambda c: c.user(),
1894 1894 'author': lambda c: c.user(),
1895 1895 'date': lambda c: c.date()[0],
1896 1896 }
1897 1897
1898 1898 def _getsortargs(x):
1899 1899 """Parse sort options into (set, [(key, reverse)], opts)"""
1900 1900 args = getargsdict(x, 'sort', 'set keys topo.firstbranch')
1901 1901 if 'set' not in args:
1902 1902 # i18n: "sort" is a keyword
1903 1903 raise error.ParseError(_('sort requires one or two arguments'))
1904 1904 keys = "rev"
1905 1905 if 'keys' in args:
1906 1906 # i18n: "sort" is a keyword
1907 1907 keys = getstring(args['keys'], _("sort spec must be a string"))
1908 1908
1909 1909 keyflags = []
1910 1910 for k in keys.split():
1911 1911 fk = k
1912 1912 reverse = (k.startswith('-'))
1913 1913 if reverse:
1914 1914 k = k[1:]
1915 1915 if k not in _sortkeyfuncs and k != 'topo':
1916 1916 raise error.ParseError(
1917 1917 _("unknown sort key %r") % pycompat.bytestr(fk))
1918 1918 keyflags.append((k, reverse))
1919 1919
1920 1920 if len(keyflags) > 1 and any(k == 'topo' for k, reverse in keyflags):
1921 1921 # i18n: "topo" is a keyword
1922 1922 raise error.ParseError(_('topo sort order cannot be combined '
1923 1923 'with other sort keys'))
1924 1924
1925 1925 opts = {}
1926 1926 if 'topo.firstbranch' in args:
1927 1927 if any(k == 'topo' for k, reverse in keyflags):
1928 1928 opts['topo.firstbranch'] = args['topo.firstbranch']
1929 1929 else:
1930 1930 # i18n: "topo" and "topo.firstbranch" are keywords
1931 1931 raise error.ParseError(_('topo.firstbranch can only be used '
1932 1932 'when using the topo sort key'))
1933 1933
1934 1934 return args['set'], keyflags, opts
1935 1935
1936 1936 @predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True,
1937 1937 weight=10)
1938 1938 def sort(repo, subset, x, order):
1939 1939 """Sort set by keys. The default sort order is ascending, specify a key
1940 1940 as ``-key`` to sort in descending order.
1941 1941
1942 1942 The keys can be:
1943 1943
1944 1944 - ``rev`` for the revision number,
1945 1945 - ``branch`` for the branch name,
1946 1946 - ``desc`` for the commit message (description),
1947 1947 - ``user`` for user name (``author`` can be used as an alias),
1948 1948 - ``date`` for the commit date
1949 1949 - ``topo`` for a reverse topographical sort
1950 1950
1951 1951 The ``topo`` sort order cannot be combined with other sort keys. This sort
1952 1952 takes one optional argument, ``topo.firstbranch``, which takes a revset that
1953 1953 specifies what topographical branches to prioritize in the sort.
1954 1954
1955 1955 """
1956 1956 s, keyflags, opts = _getsortargs(x)
1957 1957 revs = getset(repo, subset, s, order)
1958 1958
1959 1959 if not keyflags or order != defineorder:
1960 1960 return revs
1961 1961 if len(keyflags) == 1 and keyflags[0][0] == "rev":
1962 1962 revs.sort(reverse=keyflags[0][1])
1963 1963 return revs
1964 1964 elif keyflags[0][0] == "topo":
1965 1965 firstbranch = ()
1966 1966 if 'topo.firstbranch' in opts:
1967 1967 firstbranch = getset(repo, subset, opts['topo.firstbranch'])
1968 1968 revs = baseset(dagop.toposort(revs, repo.changelog.parentrevs,
1969 1969 firstbranch),
1970 1970 istopo=True)
1971 1971 if keyflags[0][1]:
1972 1972 revs.reverse()
1973 1973 return revs
1974 1974
1975 1975 # sort() is guaranteed to be stable
1976 1976 ctxs = [repo[r] for r in revs]
1977 1977 for k, reverse in reversed(keyflags):
1978 1978 ctxs.sort(key=_sortkeyfuncs[k], reverse=reverse)
1979 1979 return baseset([c.rev() for c in ctxs])
1980 1980
1981 1981 @predicate('subrepo([pattern])')
1982 1982 def subrepo(repo, subset, x):
1983 1983 """Changesets that add, modify or remove the given subrepo. If no subrepo
1984 1984 pattern is named, any subrepo changes are returned.
1985 1985 """
1986 1986 # i18n: "subrepo" is a keyword
1987 1987 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
1988 1988 pat = None
1989 1989 if len(args) != 0:
1990 1990 pat = getstring(args[0], _("subrepo requires a pattern"))
1991 1991
1992 1992 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
1993 1993
1994 1994 def submatches(names):
1995 1995 k, p, m = stringutil.stringmatcher(pat)
1996 1996 for name in names:
1997 1997 if m(name):
1998 1998 yield name
1999 1999
2000 2000 def matches(x):
2001 2001 c = repo[x]
2002 2002 s = repo.status(c.p1().node(), c.node(), match=m)
2003 2003
2004 2004 if pat is None:
2005 2005 return s.added or s.modified or s.removed
2006 2006
2007 2007 if s.added:
2008 2008 return any(submatches(c.substate.keys()))
2009 2009
2010 2010 if s.modified:
2011 2011 subs = set(c.p1().substate.keys())
2012 2012 subs.update(c.substate.keys())
2013 2013
2014 2014 for path in submatches(subs):
2015 2015 if c.p1().substate.get(path) != c.substate.get(path):
2016 2016 return True
2017 2017
2018 2018 if s.removed:
2019 2019 return any(submatches(c.p1().substate.keys()))
2020 2020
2021 2021 return False
2022 2022
2023 2023 return subset.filter(matches, condrepr=('<subrepo %r>', pat))
2024 2024
2025 2025 def _mapbynodefunc(repo, s, f):
2026 2026 """(repo, smartset, [node] -> [node]) -> smartset
2027 2027
2028 2028 Helper method to map a smartset to another smartset given a function only
2029 2029 talking about nodes. Handles converting between rev numbers and nodes, and
2030 2030 filtering.
2031 2031 """
2032 2032 cl = repo.unfiltered().changelog
2033 2033 torev = cl.rev
2034 2034 tonode = cl.node
2035 2035 nodemap = cl.nodemap
2036 2036 result = set(torev(n) for n in f(tonode(r) for r in s) if n in nodemap)
2037 2037 return smartset.baseset(result - repo.changelog.filteredrevs)
2038 2038
2039 2039 @predicate('successors(set)', safe=True)
2040 2040 def successors(repo, subset, x):
2041 2041 """All successors for set, including the given set themselves"""
2042 2042 s = getset(repo, fullreposet(repo), x)
2043 2043 f = lambda nodes: obsutil.allsuccessors(repo.obsstore, nodes)
2044 2044 d = _mapbynodefunc(repo, s, f)
2045 2045 return subset & d
2046 2046
2047 2047 def _substringmatcher(pattern, casesensitive=True):
2048 2048 kind, pattern, matcher = stringutil.stringmatcher(
2049 2049 pattern, casesensitive=casesensitive)
2050 2050 if kind == 'literal':
2051 2051 if not casesensitive:
2052 2052 pattern = encoding.lower(pattern)
2053 2053 matcher = lambda s: pattern in encoding.lower(s)
2054 2054 else:
2055 2055 matcher = lambda s: pattern in s
2056 2056 return kind, pattern, matcher
2057 2057
2058 2058 @predicate('tag([name])', safe=True)
2059 2059 def tag(repo, subset, x):
2060 2060 """The specified tag by name, or all tagged revisions if no name is given.
2061 2061
2062 2062 Pattern matching is supported for `name`. See
2063 2063 :hg:`help revisions.patterns`.
2064 2064 """
2065 2065 # i18n: "tag" is a keyword
2066 2066 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2067 2067 cl = repo.changelog
2068 2068 if args:
2069 2069 pattern = getstring(args[0],
2070 2070 # i18n: "tag" is a keyword
2071 2071 _('the argument to tag must be a string'))
2072 2072 kind, pattern, matcher = stringutil.stringmatcher(pattern)
2073 2073 if kind == 'literal':
2074 2074 # avoid resolving all tags
2075 2075 tn = repo._tagscache.tags.get(pattern, None)
2076 2076 if tn is None:
2077 2077 raise error.RepoLookupError(_("tag '%s' does not exist")
2078 2078 % pattern)
2079 2079 s = {repo[tn].rev()}
2080 2080 else:
2081 2081 s = {cl.rev(n) for t, n in repo.tagslist() if matcher(t)}
2082 2082 else:
2083 2083 s = {cl.rev(n) for t, n in repo.tagslist() if t != 'tip'}
2084 2084 return subset & s
2085 2085
2086 2086 @predicate('tagged', safe=True)
2087 2087 def tagged(repo, subset, x):
2088 2088 return tag(repo, subset, x)
2089 2089
2090 2090 @predicate('orphan()', safe=True)
2091 2091 def orphan(repo, subset, x):
2092 2092 """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
2093 2093 """
2094 2094 # i18n: "orphan" is a keyword
2095 2095 getargs(x, 0, 0, _("orphan takes no arguments"))
2096 2096 orphan = obsmod.getrevs(repo, 'orphan')
2097 2097 return subset & orphan
2098 2098
2099 2099
2100 2100 @predicate('user(string)', safe=True, weight=10)
2101 2101 def user(repo, subset, x):
2102 2102 """User name contains string. The match is case-insensitive.
2103 2103
2104 2104 Pattern matching is supported for `string`. See
2105 2105 :hg:`help revisions.patterns`.
2106 2106 """
2107 2107 return author(repo, subset, x)
2108 2108
2109 2109 @predicate('wdir()', safe=True, weight=0)
2110 2110 def wdir(repo, subset, x):
2111 2111 """Working directory. (EXPERIMENTAL)"""
2112 2112 # i18n: "wdir" is a keyword
2113 2113 getargs(x, 0, 0, _("wdir takes no arguments"))
2114 2114 if node.wdirrev in subset or isinstance(subset, fullreposet):
2115 2115 return baseset([node.wdirrev])
2116 2116 return baseset()
2117 2117
2118 2118 def _orderedlist(repo, subset, x):
2119 2119 s = getstring(x, "internal error")
2120 2120 if not s:
2121 2121 return baseset()
2122 2122 # remove duplicates here. it's difficult for caller to deduplicate sets
2123 2123 # because different symbols can point to the same rev.
2124 2124 cl = repo.changelog
2125 2125 ls = []
2126 2126 seen = set()
2127 2127 for t in s.split('\0'):
2128 2128 try:
2129 2129 # fast path for integer revision
2130 2130 r = int(t)
2131 2131 if ('%d' % r) != t or r not in cl:
2132 2132 raise ValueError
2133 2133 revs = [r]
2134 2134 except ValueError:
2135 2135 revs = stringset(repo, subset, t, defineorder)
2136 2136
2137 2137 for r in revs:
2138 2138 if r in seen:
2139 2139 continue
2140 2140 if (r in subset
2141 2141 or r == node.nullrev and isinstance(subset, fullreposet)):
2142 2142 ls.append(r)
2143 2143 seen.add(r)
2144 2144 return baseset(ls)
2145 2145
2146 2146 # for internal use
2147 2147 @predicate('_list', safe=True, takeorder=True)
2148 2148 def _list(repo, subset, x, order):
2149 2149 if order == followorder:
2150 2150 # slow path to take the subset order
2151 2151 return subset & _orderedlist(repo, fullreposet(repo), x)
2152 2152 else:
2153 2153 return _orderedlist(repo, subset, x)
2154 2154
2155 2155 def _orderedintlist(repo, subset, x):
2156 2156 s = getstring(x, "internal error")
2157 2157 if not s:
2158 2158 return baseset()
2159 2159 ls = [int(r) for r in s.split('\0')]
2160 2160 s = subset
2161 2161 return baseset([r for r in ls if r in s])
2162 2162
2163 2163 # for internal use
2164 2164 @predicate('_intlist', safe=True, takeorder=True, weight=0)
2165 2165 def _intlist(repo, subset, x, order):
2166 2166 if order == followorder:
2167 2167 # slow path to take the subset order
2168 2168 return subset & _orderedintlist(repo, fullreposet(repo), x)
2169 2169 else:
2170 2170 return _orderedintlist(repo, subset, x)
2171 2171
2172 2172 def _orderedhexlist(repo, subset, x):
2173 2173 s = getstring(x, "internal error")
2174 2174 if not s:
2175 2175 return baseset()
2176 2176 cl = repo.changelog
2177 2177 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2178 2178 s = subset
2179 2179 return baseset([r for r in ls if r in s])
2180 2180
2181 2181 # for internal use
2182 2182 @predicate('_hexlist', safe=True, takeorder=True)
2183 2183 def _hexlist(repo, subset, x, order):
2184 2184 if order == followorder:
2185 2185 # slow path to take the subset order
2186 2186 return subset & _orderedhexlist(repo, fullreposet(repo), x)
2187 2187 else:
2188 2188 return _orderedhexlist(repo, subset, x)
2189 2189
2190 2190 methods = {
2191 2191 "range": rangeset,
2192 2192 "rangeall": rangeall,
2193 2193 "rangepre": rangepre,
2194 2194 "rangepost": rangepost,
2195 2195 "dagrange": dagrange,
2196 2196 "string": stringset,
2197 2197 "symbol": stringset,
2198 2198 "and": andset,
2199 2199 "andsmally": andsmallyset,
2200 2200 "or": orset,
2201 2201 "not": notset,
2202 2202 "difference": differenceset,
2203 2203 "relation": relationset,
2204 2204 "relsubscript": relsubscriptset,
2205 2205 "subscript": subscriptset,
2206 2206 "list": listset,
2207 2207 "keyvalue": keyvaluepair,
2208 2208 "func": func,
2209 2209 "ancestor": ancestorspec,
2210 2210 "parent": parentspec,
2211 2211 "parentpost": parentpost,
2212 2212 }
2213 2213
2214 2214 def lookupfn(repo):
2215 2215 return lambda symbol: scmutil.isrevsymbol(repo, symbol)
2216 2216
2217 2217 def match(ui, spec, lookup=None):
2218 2218 """Create a matcher for a single revision spec"""
2219 2219 return matchany(ui, [spec], lookup=lookup)
2220 2220
2221 2221 def matchany(ui, specs, lookup=None, localalias=None):
2222 2222 """Create a matcher that will include any revisions matching one of the
2223 2223 given specs
2224 2224
2225 2225 If lookup function is not None, the parser will first attempt to handle
2226 2226 old-style ranges, which may contain operator characters.
2227 2227
2228 2228 If localalias is not None, it is a dict {name: definitionstring}. It takes
2229 2229 precedence over [revsetalias] config section.
2230 2230 """
2231 2231 if not specs:
2232 2232 def mfunc(repo, subset=None):
2233 2233 return baseset()
2234 2234 return mfunc
2235 2235 if not all(specs):
2236 2236 raise error.ParseError(_("empty query"))
2237 2237 if len(specs) == 1:
2238 2238 tree = revsetlang.parse(specs[0], lookup)
2239 2239 else:
2240 2240 tree = ('or',
2241 2241 ('list',) + tuple(revsetlang.parse(s, lookup) for s in specs))
2242 2242
2243 2243 aliases = []
2244 2244 warn = None
2245 2245 if ui:
2246 2246 aliases.extend(ui.configitems('revsetalias'))
2247 2247 warn = ui.warn
2248 2248 if localalias:
2249 2249 aliases.extend(localalias.items())
2250 2250 if aliases:
2251 2251 tree = revsetlang.expandaliases(tree, aliases, warn=warn)
2252 2252 tree = revsetlang.foldconcat(tree)
2253 2253 tree = revsetlang.analyze(tree)
2254 2254 tree = revsetlang.optimize(tree)
2255 2255 return makematcher(tree)
2256 2256
2257 2257 def makematcher(tree):
2258 2258 """Create a matcher from an evaluatable tree"""
2259 2259 def mfunc(repo, subset=None, order=None):
2260 2260 if order is None:
2261 2261 if subset is None:
2262 2262 order = defineorder # 'x'
2263 2263 else:
2264 2264 order = followorder # 'subset & x'
2265 2265 if subset is None:
2266 2266 subset = fullreposet(repo)
2267 2267 return getset(repo, subset, tree, order)
2268 2268 return mfunc
2269 2269
2270 2270 def loadpredicate(ui, extname, registrarobj):
2271 2271 """Load revset predicates from specified registrarobj
2272 2272 """
2273 2273 for name, func in registrarobj._table.iteritems():
2274 2274 symbols[name] = func
2275 2275 if func._safe:
2276 2276 safesymbols.add(name)
2277 2277
2278 2278 # load built-in predicates explicitly to setup safesymbols
2279 2279 loadpredicate(None, None, predicate)
2280 2280
2281 2281 # tell hggettext to extract docstrings from these functions:
2282 2282 i18nfunctions = symbols.values()
@@ -1,743 +1,743 b''
1 1 $ fileset() {
2 2 > hg debugfileset --all-files "$@"
3 3 > }
4 4
5 5 $ hg init repo
6 6 $ cd repo
7 7 $ echo a > a1
8 8 $ echo a > a2
9 9 $ echo b > b1
10 10 $ echo b > b2
11 11 $ hg ci -Am addfiles
12 12 adding a1
13 13 adding a2
14 14 adding b1
15 15 adding b2
16 16
17 17 Test operators and basic patterns
18 18
19 19 $ fileset -v a1
20 20 (symbol 'a1')
21 21 * matcher:
22 22 <patternmatcher patterns='(?:a1$)'>
23 23 a1
24 24 $ fileset -v 'a*'
25 25 (symbol 'a*')
26 26 * matcher:
27 27 <patternmatcher patterns='(?:a[^/]*$)'>
28 28 a1
29 29 a2
30 30 $ fileset -v '"re:a\d"'
31 31 (string 're:a\\d')
32 32 * matcher:
33 33 <patternmatcher patterns='(?:a\\d)'>
34 34 a1
35 35 a2
36 36 $ fileset -v '!re:"a\d"'
37 37 (not
38 38 (kindpat
39 39 (symbol 're')
40 40 (string 'a\\d')))
41 41 * matcher:
42 42 <predicatenmatcher
43 43 pred=<not
44 44 <patternmatcher patterns='(?:a\\d)'>>>
45 45 b1
46 46 b2
47 47 $ fileset -v 'path:a1 or glob:b?'
48 48 (or
49 49 (kindpat
50 50 (symbol 'path')
51 51 (symbol 'a1'))
52 52 (kindpat
53 53 (symbol 'glob')
54 54 (symbol 'b?')))
55 55 * matcher:
56 56 <unionmatcher matchers=[
57 57 <patternmatcher patterns='(?:a1(?:/|$))'>,
58 58 <patternmatcher patterns='(?:b.$)'>]>
59 59 a1
60 60 b1
61 61 b2
62 62 $ fileset -v --no-show-matcher 'a1 or a2'
63 63 (or
64 64 (symbol 'a1')
65 65 (symbol 'a2'))
66 66 a1
67 67 a2
68 68 $ fileset 'a1 | a2'
69 69 a1
70 70 a2
71 71 $ fileset 'a* and "*1"'
72 72 a1
73 73 $ fileset 'a* & "*1"'
74 74 a1
75 75 $ fileset 'not (r"a*")'
76 76 b1
77 77 b2
78 78 $ fileset '! ("a*")'
79 79 b1
80 80 b2
81 81 $ fileset 'a* - a1'
82 82 a2
83 83 $ fileset 'a_b'
84 84 $ fileset '"\xy"'
85 85 hg: parse error: invalid \x escape* (glob)
86 86 [255]
87 87
88 88 Test invalid syntax
89 89
90 90 $ fileset -v '"added"()'
91 91 (func
92 92 (string 'added')
93 93 None)
94 94 hg: parse error: not a symbol
95 95 [255]
96 96 $ fileset -v '()()'
97 97 (func
98 98 (group
99 99 None)
100 100 None)
101 101 hg: parse error: not a symbol
102 102 [255]
103 103 $ fileset -v -- '-x'
104 104 (negate
105 105 (symbol 'x'))
106 106 hg: parse error: can't use negate operator in this context
107 107 [255]
108 108 $ fileset -v -- '-()'
109 109 (negate
110 110 (group
111 111 None))
112 112 hg: parse error: can't use negate operator in this context
113 113 [255]
114 114 $ fileset -p parsed 'a, b, c'
115 115 * parsed:
116 116 (list
117 117 (symbol 'a')
118 118 (symbol 'b')
119 119 (symbol 'c'))
120 120 hg: parse error: can't use a list in this context
121 (see hg help "filesets.x or y")
121 (see 'hg help "filesets.x or y"')
122 122 [255]
123 123
124 124 $ fileset '"path":.'
125 125 hg: parse error: not a symbol
126 126 [255]
127 127 $ fileset 'path:foo bar'
128 128 hg: parse error at 9: invalid token
129 129 [255]
130 130 $ fileset 'foo:bar:baz'
131 131 hg: parse error: not a symbol
132 132 [255]
133 133 $ fileset 'foo:bar()'
134 134 hg: parse error: pattern must be a string
135 135 [255]
136 136 $ fileset 'foo:bar'
137 137 hg: parse error: invalid pattern kind: foo
138 138 [255]
139 139
140 140 Show parsed tree at stages:
141 141
142 142 $ fileset -p unknown a
143 143 abort: invalid stage name: unknown
144 144 [255]
145 145
146 146 $ fileset -p parsed 'path:a1 or glob:b?'
147 147 * parsed:
148 148 (or
149 149 (kindpat
150 150 (symbol 'path')
151 151 (symbol 'a1'))
152 152 (kindpat
153 153 (symbol 'glob')
154 154 (symbol 'b?')))
155 155 a1
156 156 b1
157 157 b2
158 158
159 159 $ fileset -p all -s 'a1 or a2 or (grep("b") & clean())'
160 160 * parsed:
161 161 (or
162 162 (symbol 'a1')
163 163 (symbol 'a2')
164 164 (group
165 165 (and
166 166 (func
167 167 (symbol 'grep')
168 168 (string 'b'))
169 169 (func
170 170 (symbol 'clean')
171 171 None))))
172 172 * matcher:
173 173 <unionmatcher matchers=[
174 174 <patternmatcher patterns='(?:a1$)'>,
175 175 <patternmatcher patterns='(?:a2$)'>,
176 176 <intersectionmatcher
177 177 m1=<predicatenmatcher pred=grep('b')>,
178 178 m2=<predicatenmatcher pred=clean>>]>
179 179 a1
180 180 a2
181 181 b1
182 182 b2
183 183
184 184 Test files status
185 185
186 186 $ rm a1
187 187 $ hg rm a2
188 188 $ echo b >> b2
189 189 $ hg cp b1 c1
190 190 $ echo c > c2
191 191 $ echo c > c3
192 192 $ cat > .hgignore <<EOF
193 193 > \.hgignore
194 194 > 2$
195 195 > EOF
196 196 $ fileset 'modified()'
197 197 b2
198 198 $ fileset 'added()'
199 199 c1
200 200 $ fileset 'removed()'
201 201 a2
202 202 $ fileset 'deleted()'
203 203 a1
204 204 $ fileset 'missing()'
205 205 a1
206 206 $ fileset 'unknown()'
207 207 c3
208 208 $ fileset 'ignored()'
209 209 .hgignore
210 210 c2
211 211 $ fileset 'hgignore()'
212 212 .hgignore
213 213 a2
214 214 b2
215 215 c2
216 216 $ fileset 'clean()'
217 217 b1
218 218 $ fileset 'copied()'
219 219 c1
220 220
221 221 Test files status in different revisions
222 222
223 223 $ hg status -m
224 224 M b2
225 225 $ fileset -r0 'revs("wdir()", modified())' --traceback
226 226 b2
227 227 $ hg status -a
228 228 A c1
229 229 $ fileset -r0 'revs("wdir()", added())'
230 230 c1
231 231 $ hg status --change 0 -a
232 232 A a1
233 233 A a2
234 234 A b1
235 235 A b2
236 236 $ hg status -mru
237 237 M b2
238 238 R a2
239 239 ? c3
240 240 $ fileset -r0 'added() and revs("wdir()", modified() or removed() or unknown())'
241 241 a2
242 242 b2
243 243 $ fileset -r0 'added() or revs("wdir()", added())'
244 244 a1
245 245 a2
246 246 b1
247 247 b2
248 248 c1
249 249
250 250 Test files properties
251 251
252 252 >>> open('bin', 'wb').write(b'\0a') and None
253 253 $ fileset 'binary()'
254 254 bin
255 255 $ fileset 'binary() and unknown()'
256 256 bin
257 257 $ echo '^bin$' >> .hgignore
258 258 $ fileset 'binary() and ignored()'
259 259 bin
260 260 $ hg add bin
261 261 $ fileset 'binary()'
262 262 bin
263 263
264 264 $ fileset 'grep("b{1}")'
265 265 .hgignore
266 266 b1
267 267 b2
268 268 c1
269 269 $ fileset 'grep("missingparens(")'
270 270 hg: parse error: invalid match pattern: (unbalanced parenthesis|missing \)).* (re)
271 271 [255]
272 272
273 273 #if execbit
274 274 $ chmod +x b2
275 275 $ fileset 'exec()'
276 276 b2
277 277 #endif
278 278
279 279 #if symlink
280 280 $ ln -s b2 b2link
281 281 $ fileset 'symlink() and unknown()'
282 282 b2link
283 283 $ hg add b2link
284 284 #endif
285 285
286 286 #if no-windows
287 287 $ echo foo > con.xml
288 288 $ fileset 'not portable()'
289 289 con.xml
290 290 $ hg --config ui.portablefilenames=ignore add con.xml
291 291 #endif
292 292
293 293 >>> open('1k', 'wb').write(b' '*1024) and None
294 294 >>> open('2k', 'wb').write(b' '*2048) and None
295 295 $ hg add 1k 2k
296 296 $ fileset 'size("bar")'
297 297 hg: parse error: couldn't parse size: bar
298 298 [255]
299 299 $ fileset '(1k, 2k)'
300 300 hg: parse error: can't use a list in this context
301 (see hg help "filesets.x or y")
301 (see 'hg help "filesets.x or y"')
302 302 [255]
303 303 $ fileset 'size(1k)'
304 304 1k
305 305 $ fileset '(1k or 2k) and size("< 2k")'
306 306 1k
307 307 $ fileset '(1k or 2k) and size("<=2k")'
308 308 1k
309 309 2k
310 310 $ fileset '(1k or 2k) and size("> 1k")'
311 311 2k
312 312 $ fileset '(1k or 2k) and size(">=1K")'
313 313 1k
314 314 2k
315 315 $ fileset '(1k or 2k) and size(".5KB - 1.5kB")'
316 316 1k
317 317 $ fileset 'size("1M")'
318 318 $ fileset 'size("1 GB")'
319 319
320 320 Test merge states
321 321
322 322 $ hg ci -m manychanges
323 323 $ hg file -r . 'set:copied() & modified()'
324 324 [1]
325 325 $ hg up -C 0
326 326 * files updated, 0 files merged, * files removed, 0 files unresolved (glob)
327 327 $ echo c >> b2
328 328 $ hg ci -m diverging b2
329 329 created new head
330 330 $ fileset 'resolved()'
331 331 $ fileset 'unresolved()'
332 332 $ hg merge
333 333 merging b2
334 334 warning: conflicts while merging b2! (edit, then use 'hg resolve --mark')
335 335 * files updated, 0 files merged, 1 files removed, 1 files unresolved (glob)
336 336 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
337 337 [1]
338 338 $ fileset 'resolved()'
339 339 $ fileset 'unresolved()'
340 340 b2
341 341 $ echo e > b2
342 342 $ hg resolve -m b2
343 343 (no more unresolved files)
344 344 $ fileset 'resolved()'
345 345 b2
346 346 $ fileset 'unresolved()'
347 347 $ hg ci -m merge
348 348
349 349 Test subrepo predicate
350 350
351 351 $ hg init sub
352 352 $ echo a > sub/suba
353 353 $ hg -R sub add sub/suba
354 354 $ hg -R sub ci -m sub
355 355 $ echo 'sub = sub' > .hgsub
356 356 $ hg init sub2
357 357 $ echo b > sub2/b
358 358 $ hg -R sub2 ci -Am sub2
359 359 adding b
360 360 $ echo 'sub2 = sub2' >> .hgsub
361 361 $ fileset 'subrepo()'
362 362 $ hg add .hgsub
363 363 $ fileset 'subrepo()'
364 364 sub
365 365 sub2
366 366 $ fileset 'subrepo("sub")'
367 367 sub
368 368 $ fileset 'subrepo("glob:*")'
369 369 sub
370 370 sub2
371 371 $ hg ci -m subrepo
372 372
373 373 Test that .hgsubstate is updated as appropriate during a conversion. The
374 374 saverev property is enough to alter the hashes of the subrepo.
375 375
376 376 $ hg init ../converted
377 377 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
378 378 > sub ../converted/sub
379 379 initializing destination ../converted/sub repository
380 380 scanning source...
381 381 sorting...
382 382 converting...
383 383 0 sub
384 384 $ hg clone -U sub2 ../converted/sub2
385 385 $ hg --config extensions.convert= convert --config convert.hg.saverev=True \
386 386 > . ../converted
387 387 scanning source...
388 388 sorting...
389 389 converting...
390 390 4 addfiles
391 391 3 manychanges
392 392 2 diverging
393 393 1 merge
394 394 0 subrepo
395 395 no ".hgsubstate" updates will be made for "sub2"
396 396 $ hg up -q -R ../converted -r tip
397 397 $ hg --cwd ../converted cat sub/suba sub2/b -r tip
398 398 a
399 399 b
400 400 $ oldnode=`hg log -r tip -T "{node}\n"`
401 401 $ newnode=`hg log -R ../converted -r tip -T "{node}\n"`
402 402 $ [ "$oldnode" != "$newnode" ] || echo "nothing changed"
403 403
404 404 Test with a revision
405 405
406 406 $ hg log -G --template '{rev} {desc}\n'
407 407 @ 4 subrepo
408 408 |
409 409 o 3 merge
410 410 |\
411 411 | o 2 diverging
412 412 | |
413 413 o | 1 manychanges
414 414 |/
415 415 o 0 addfiles
416 416
417 417 $ echo unknown > unknown
418 418 $ fileset -r1 'modified()'
419 419 b2
420 420 $ fileset -r1 'added() and c1'
421 421 c1
422 422 $ fileset -r1 'removed()'
423 423 a2
424 424 $ fileset -r1 'deleted()'
425 425 $ fileset -r1 'unknown()'
426 426 $ fileset -r1 'ignored()'
427 427 $ fileset -r1 'hgignore()'
428 428 .hgignore
429 429 a2
430 430 b2
431 431 bin
432 432 c2
433 433 sub2
434 434 $ fileset -r1 'binary()'
435 435 bin
436 436 $ fileset -r1 'size(1k)'
437 437 1k
438 438 $ fileset -r3 'resolved()'
439 439 $ fileset -r3 'unresolved()'
440 440
441 441 #if execbit
442 442 $ fileset -r1 'exec()'
443 443 b2
444 444 #endif
445 445
446 446 #if symlink
447 447 $ fileset -r1 'symlink()'
448 448 b2link
449 449 #endif
450 450
451 451 #if no-windows
452 452 $ fileset -r1 'not portable()'
453 453 con.xml
454 454 $ hg forget 'con.xml'
455 455 #endif
456 456
457 457 $ fileset -r4 'subrepo("re:su.*")'
458 458 sub
459 459 sub2
460 460 $ fileset -r4 'subrepo(re:su.*)'
461 461 sub
462 462 sub2
463 463 $ fileset -r4 'subrepo("sub")'
464 464 sub
465 465 $ fileset -r4 'b2 or c1'
466 466 b2
467 467 c1
468 468
469 469 >>> open('dos', 'wb').write(b"dos\r\n") and None
470 470 >>> open('mixed', 'wb').write(b"dos\r\nunix\n") and None
471 471 >>> open('mac', 'wb').write(b"mac\r") and None
472 472 $ hg add dos mixed mac
473 473
474 474 (remove a1, to examine safety of 'eol' on removed files)
475 475 $ rm a1
476 476
477 477 $ fileset 'eol(dos)'
478 478 dos
479 479 mixed
480 480 $ fileset 'eol(unix)'
481 481 .hgignore
482 482 .hgsub
483 483 .hgsubstate
484 484 b1
485 485 b2
486 486 b2.orig
487 487 c1
488 488 c2
489 489 c3
490 490 con.xml (no-windows !)
491 491 mixed
492 492 unknown
493 493 $ fileset 'eol(mac)'
494 494 mac
495 495
496 496 Test safety of 'encoding' on removed files
497 497
498 498 $ fileset 'encoding("ascii")'
499 499 .hgignore
500 500 .hgsub
501 501 .hgsubstate
502 502 1k
503 503 2k
504 504 b1
505 505 b2
506 506 b2.orig
507 507 b2link (symlink !)
508 508 bin
509 509 c1
510 510 c2
511 511 c3
512 512 con.xml (no-windows !)
513 513 dos
514 514 mac
515 515 mixed
516 516 unknown
517 517
518 518 Test 'revs(...)'
519 519 ================
520 520
521 521 small reminder of the repository state
522 522
523 523 $ hg log -G
524 524 @ changeset: 4:* (glob)
525 525 | tag: tip
526 526 | user: test
527 527 | date: Thu Jan 01 00:00:00 1970 +0000
528 528 | summary: subrepo
529 529 |
530 530 o changeset: 3:* (glob)
531 531 |\ parent: 2:55b05bdebf36
532 532 | | parent: 1:* (glob)
533 533 | | user: test
534 534 | | date: Thu Jan 01 00:00:00 1970 +0000
535 535 | | summary: merge
536 536 | |
537 537 | o changeset: 2:55b05bdebf36
538 538 | | parent: 0:8a9576c51c1f
539 539 | | user: test
540 540 | | date: Thu Jan 01 00:00:00 1970 +0000
541 541 | | summary: diverging
542 542 | |
543 543 o | changeset: 1:* (glob)
544 544 |/ user: test
545 545 | date: Thu Jan 01 00:00:00 1970 +0000
546 546 | summary: manychanges
547 547 |
548 548 o changeset: 0:8a9576c51c1f
549 549 user: test
550 550 date: Thu Jan 01 00:00:00 1970 +0000
551 551 summary: addfiles
552 552
553 553 $ hg status --change 0
554 554 A a1
555 555 A a2
556 556 A b1
557 557 A b2
558 558 $ hg status --change 1
559 559 M b2
560 560 A 1k
561 561 A 2k
562 562 A b2link (no-windows !)
563 563 A bin
564 564 A c1
565 565 A con.xml (no-windows !)
566 566 R a2
567 567 $ hg status --change 2
568 568 M b2
569 569 $ hg status --change 3
570 570 M b2
571 571 A 1k
572 572 A 2k
573 573 A b2link (no-windows !)
574 574 A bin
575 575 A c1
576 576 A con.xml (no-windows !)
577 577 R a2
578 578 $ hg status --change 4
579 579 A .hgsub
580 580 A .hgsubstate
581 581 $ hg status
582 582 A dos
583 583 A mac
584 584 A mixed
585 585 R con.xml (no-windows !)
586 586 ! a1
587 587 ? b2.orig
588 588 ? c3
589 589 ? unknown
590 590
591 591 Test files at -r0 should be filtered by files at wdir
592 592 -----------------------------------------------------
593 593
594 594 $ fileset -r0 'tracked() and revs("wdir()", tracked())'
595 595 a1
596 596 b1
597 597 b2
598 598
599 599 Test that "revs()" work at all
600 600 ------------------------------
601 601
602 602 $ fileset "revs('2', modified())"
603 603 b2
604 604
605 605 Test that "revs()" work for file missing in the working copy/current context
606 606 ----------------------------------------------------------------------------
607 607
608 608 (a2 not in working copy)
609 609
610 610 $ fileset "revs('0', added())"
611 611 a1
612 612 a2
613 613 b1
614 614 b2
615 615
616 616 (none of the file exist in "0")
617 617
618 618 $ fileset -r 0 "revs('4', added())"
619 619 .hgsub
620 620 .hgsubstate
621 621
622 622 Call with empty revset
623 623 --------------------------
624 624
625 625 $ fileset "revs('2-2', modified())"
626 626
627 627 Call with revset matching multiple revs
628 628 ---------------------------------------
629 629
630 630 $ fileset "revs('0+4', added())"
631 631 .hgsub
632 632 .hgsubstate
633 633 a1
634 634 a2
635 635 b1
636 636 b2
637 637
638 638 overlapping set
639 639
640 640 $ fileset "revs('1+2', modified())"
641 641 b2
642 642
643 643 test 'status(...)'
644 644 =================
645 645
646 646 Simple case
647 647 -----------
648 648
649 649 $ fileset "status(3, 4, added())"
650 650 .hgsub
651 651 .hgsubstate
652 652
653 653 use rev to restrict matched file
654 654 -----------------------------------------
655 655
656 656 $ hg status --removed --rev 0 --rev 1
657 657 R a2
658 658 $ fileset "status(0, 1, removed())"
659 659 a2
660 660 $ fileset "tracked() and status(0, 1, removed())"
661 661 $ fileset -r 4 "status(0, 1, removed())"
662 662 a2
663 663 $ fileset -r 4 "tracked() and status(0, 1, removed())"
664 664 $ fileset "revs('4', tracked() and status(0, 1, removed()))"
665 665 $ fileset "revs('0', tracked() and status(0, 1, removed()))"
666 666 a2
667 667
668 668 check wdir()
669 669 ------------
670 670
671 671 $ hg status --removed --rev 4
672 672 R con.xml (no-windows !)
673 673 $ fileset "status(4, 'wdir()', removed())"
674 674 con.xml (no-windows !)
675 675
676 676 $ hg status --removed --rev 2
677 677 R a2
678 678 $ fileset "status('2', 'wdir()', removed())"
679 679 a2
680 680
681 681 test backward status
682 682 --------------------
683 683
684 684 $ hg status --removed --rev 0 --rev 4
685 685 R a2
686 686 $ hg status --added --rev 4 --rev 0
687 687 A a2
688 688 $ fileset "status(4, 0, added())"
689 689 a2
690 690
691 691 test cross branch status
692 692 ------------------------
693 693
694 694 $ hg status --added --rev 1 --rev 2
695 695 A a2
696 696 $ fileset "status(1, 2, added())"
697 697 a2
698 698
699 699 test with multi revs revset
700 700 ---------------------------
701 701 $ hg status --added --rev 0:1 --rev 3:4
702 702 A .hgsub
703 703 A .hgsubstate
704 704 A 1k
705 705 A 2k
706 706 A b2link (no-windows !)
707 707 A bin
708 708 A c1
709 709 A con.xml (no-windows !)
710 710 $ fileset "status('0:1', '3:4', added())"
711 711 .hgsub
712 712 .hgsubstate
713 713 1k
714 714 2k
715 715 b2link (no-windows !)
716 716 bin
717 717 c1
718 718 con.xml (no-windows !)
719 719
720 720 tests with empty value
721 721 ----------------------
722 722
723 723 Fully empty revset
724 724
725 725 $ fileset "status('', '4', added())"
726 726 hg: parse error: first argument to status must be a revision
727 727 [255]
728 728 $ fileset "status('2', '', added())"
729 729 hg: parse error: second argument to status must be a revision
730 730 [255]
731 731
732 732 Empty revset will error at the revset layer
733 733
734 734 $ fileset "status(' ', '4', added())"
735 735 hg: parse error at 1: not a prefix: end
736 736 (
737 737 ^ here)
738 738 [255]
739 739 $ fileset "status('2', ' ', added())"
740 740 hg: parse error at 1: not a prefix: end
741 741 (
742 742 ^ here)
743 743 [255]
@@ -1,1854 +1,1854 b''
1 1 $ HGENCODING=utf-8
2 2 $ export HGENCODING
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > drawdag=$TESTDIR/drawdag.py
6 6 > EOF
7 7
8 8 $ try() {
9 9 > hg debugrevspec --debug "$@"
10 10 > }
11 11
12 12 $ log() {
13 13 > hg log --template '{rev}\n' -r "$1"
14 14 > }
15 15
16 16 $ hg init repo
17 17 $ cd repo
18 18
19 19 $ echo a > a
20 20 $ hg branch a
21 21 marked working directory as branch a
22 22 (branches are permanent and global, did you want a bookmark?)
23 23 $ hg ci -Aqm0
24 24
25 25 $ echo b > b
26 26 $ hg branch b
27 27 marked working directory as branch b
28 28 $ hg ci -Aqm1
29 29
30 30 $ rm a
31 31 $ hg branch a-b-c-
32 32 marked working directory as branch a-b-c-
33 33 $ hg ci -Aqm2 -u Bob
34 34
35 35 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
36 36 2
37 37 $ hg log -r "extra('branch')" --template '{rev}\n'
38 38 0
39 39 1
40 40 2
41 41 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
42 42 0 a
43 43 2 a-b-c-
44 44
45 45 $ hg co 1
46 46 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
47 47 $ hg branch +a+b+c+
48 48 marked working directory as branch +a+b+c+
49 49 $ hg ci -Aqm3
50 50
51 51 $ hg co 2 # interleave
52 52 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
53 53 $ echo bb > b
54 54 $ hg branch -- -a-b-c-
55 55 marked working directory as branch -a-b-c-
56 56 $ hg ci -Aqm4 -d "May 12 2005"
57 57
58 58 $ hg co 3
59 59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 60 $ hg branch !a/b/c/
61 61 marked working directory as branch !a/b/c/
62 62 $ hg ci -Aqm"5 bug"
63 63
64 64 $ hg merge 4
65 65 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
66 66 (branch merge, don't forget to commit)
67 67 $ hg branch _a_b_c_
68 68 marked working directory as branch _a_b_c_
69 69 $ hg ci -Aqm"6 issue619"
70 70
71 71 $ hg branch .a.b.c.
72 72 marked working directory as branch .a.b.c.
73 73 $ hg ci -Aqm7
74 74
75 75 $ hg branch all
76 76 marked working directory as branch all
77 77
78 78 $ hg co 4
79 79 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
80 80 $ hg branch Γ©
81 81 marked working directory as branch \xc3\xa9 (esc)
82 82 $ hg ci -Aqm9
83 83
84 84 $ hg tag -r6 1.0
85 85 $ hg bookmark -r6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
86 86
87 87 $ hg clone --quiet -U -r 7 . ../remote1
88 88 $ hg clone --quiet -U -r 8 . ../remote2
89 89 $ echo "[paths]" >> .hg/hgrc
90 90 $ echo "default = ../remote1" >> .hg/hgrc
91 91
92 92 test subtracting something from an addset
93 93
94 94 $ log '(outgoing() or removes(a)) - removes(a)'
95 95 8
96 96 9
97 97
98 98 test intersecting something with an addset
99 99
100 100 $ log 'parents(outgoing() or removes(a))'
101 101 1
102 102 4
103 103 5
104 104 8
105 105
106 106 test that `or` operation combines elements in the right order:
107 107
108 108 $ log '3:4 or 2:5'
109 109 3
110 110 4
111 111 2
112 112 5
113 113 $ log '3:4 or 5:2'
114 114 3
115 115 4
116 116 5
117 117 2
118 118 $ log 'sort(3:4 or 2:5)'
119 119 2
120 120 3
121 121 4
122 122 5
123 123 $ log 'sort(3:4 or 5:2)'
124 124 2
125 125 3
126 126 4
127 127 5
128 128
129 129 test that more than one `-r`s are combined in the right order and deduplicated:
130 130
131 131 $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
132 132 3
133 133 4
134 134 5
135 135 2
136 136 0
137 137 1
138 138
139 139 test that `or` operation skips duplicated revisions from right-hand side
140 140
141 141 $ try 'reverse(1::5) or ancestors(4)'
142 142 (or
143 143 (list
144 144 (func
145 145 (symbol 'reverse')
146 146 (dagrange
147 147 (symbol '1')
148 148 (symbol '5')))
149 149 (func
150 150 (symbol 'ancestors')
151 151 (symbol '4'))))
152 152 * set:
153 153 <addset
154 154 <baseset- [1, 3, 5]>,
155 155 <generatorsetdesc+>>
156 156 5
157 157 3
158 158 1
159 159 0
160 160 2
161 161 4
162 162 $ try 'sort(ancestors(4) or reverse(1::5))'
163 163 (func
164 164 (symbol 'sort')
165 165 (or
166 166 (list
167 167 (func
168 168 (symbol 'ancestors')
169 169 (symbol '4'))
170 170 (func
171 171 (symbol 'reverse')
172 172 (dagrange
173 173 (symbol '1')
174 174 (symbol '5'))))))
175 175 * set:
176 176 <addset+
177 177 <generatorsetdesc+>,
178 178 <baseset- [1, 3, 5]>>
179 179 0
180 180 1
181 181 2
182 182 3
183 183 4
184 184 5
185 185
186 186 test optimization of trivial `or` operation
187 187
188 188 $ try --optimize '0|(1)|"2"|-2|tip|null'
189 189 (or
190 190 (list
191 191 (symbol '0')
192 192 (group
193 193 (symbol '1'))
194 194 (string '2')
195 195 (negate
196 196 (symbol '2'))
197 197 (symbol 'tip')
198 198 (symbol 'null')))
199 199 * optimized:
200 200 (func
201 201 (symbol '_list')
202 202 (string '0\x001\x002\x00-2\x00tip\x00null'))
203 203 * set:
204 204 <baseset [0, 1, 2, 8, 9, -1]>
205 205 0
206 206 1
207 207 2
208 208 8
209 209 9
210 210 -1
211 211
212 212 $ try --optimize '0|1|2:3'
213 213 (or
214 214 (list
215 215 (symbol '0')
216 216 (symbol '1')
217 217 (range
218 218 (symbol '2')
219 219 (symbol '3'))))
220 220 * optimized:
221 221 (or
222 222 (list
223 223 (func
224 224 (symbol '_list')
225 225 (string '0\x001'))
226 226 (range
227 227 (symbol '2')
228 228 (symbol '3'))))
229 229 * set:
230 230 <addset
231 231 <baseset [0, 1]>,
232 232 <spanset+ 2:4>>
233 233 0
234 234 1
235 235 2
236 236 3
237 237
238 238 $ try --optimize '0:1|2|3:4|5|6'
239 239 (or
240 240 (list
241 241 (range
242 242 (symbol '0')
243 243 (symbol '1'))
244 244 (symbol '2')
245 245 (range
246 246 (symbol '3')
247 247 (symbol '4'))
248 248 (symbol '5')
249 249 (symbol '6')))
250 250 * optimized:
251 251 (or
252 252 (list
253 253 (range
254 254 (symbol '0')
255 255 (symbol '1'))
256 256 (symbol '2')
257 257 (range
258 258 (symbol '3')
259 259 (symbol '4'))
260 260 (func
261 261 (symbol '_list')
262 262 (string '5\x006'))))
263 263 * set:
264 264 <addset
265 265 <addset
266 266 <spanset+ 0:2>,
267 267 <baseset [2]>>,
268 268 <addset
269 269 <spanset+ 3:5>,
270 270 <baseset [5, 6]>>>
271 271 0
272 272 1
273 273 2
274 274 3
275 275 4
276 276 5
277 277 6
278 278
279 279 unoptimized `or` looks like this
280 280
281 281 $ try --no-optimized -p analyzed '0|1|2|3|4'
282 282 * analyzed:
283 283 (or
284 284 (list
285 285 (symbol '0')
286 286 (symbol '1')
287 287 (symbol '2')
288 288 (symbol '3')
289 289 (symbol '4')))
290 290 * set:
291 291 <addset
292 292 <addset
293 293 <baseset [0]>,
294 294 <baseset [1]>>,
295 295 <addset
296 296 <baseset [2]>,
297 297 <addset
298 298 <baseset [3]>,
299 299 <baseset [4]>>>>
300 300 0
301 301 1
302 302 2
303 303 3
304 304 4
305 305
306 306 test that `_list` should be narrowed by provided `subset`
307 307
308 308 $ log '0:2 and (null|1|2|3)'
309 309 1
310 310 2
311 311
312 312 test that `_list` should remove duplicates
313 313
314 314 $ log '0|1|2|1|2|-1|tip'
315 315 0
316 316 1
317 317 2
318 318 9
319 319
320 320 test unknown revision in `_list`
321 321
322 322 $ log '0|unknown'
323 323 abort: unknown revision 'unknown'!
324 324 [255]
325 325
326 326 test integer range in `_list`
327 327
328 328 $ log '-1|-10'
329 329 9
330 330 0
331 331
332 332 $ log '-10|-11'
333 333 abort: unknown revision '-11'!
334 334 [255]
335 335
336 336 $ log '9|10'
337 337 abort: unknown revision '10'!
338 338 [255]
339 339
340 340 test '0000' != '0' in `_list`
341 341
342 342 $ log '0|0000'
343 343 0
344 344 -1
345 345
346 346 test ',' in `_list`
347 347 $ log '0,1'
348 348 hg: parse error: can't use a list in this context
349 (see hg help "revsets.x or y")
349 (see 'hg help "revsets.x or y"')
350 350 [255]
351 351 $ try '0,1,2'
352 352 (list
353 353 (symbol '0')
354 354 (symbol '1')
355 355 (symbol '2'))
356 356 hg: parse error: can't use a list in this context
357 (see hg help "revsets.x or y")
357 (see 'hg help "revsets.x or y"')
358 358 [255]
359 359
360 360 test that chained `or` operations make balanced addsets
361 361
362 362 $ try '0:1|1:2|2:3|3:4|4:5'
363 363 (or
364 364 (list
365 365 (range
366 366 (symbol '0')
367 367 (symbol '1'))
368 368 (range
369 369 (symbol '1')
370 370 (symbol '2'))
371 371 (range
372 372 (symbol '2')
373 373 (symbol '3'))
374 374 (range
375 375 (symbol '3')
376 376 (symbol '4'))
377 377 (range
378 378 (symbol '4')
379 379 (symbol '5'))))
380 380 * set:
381 381 <addset
382 382 <addset
383 383 <spanset+ 0:2>,
384 384 <spanset+ 1:3>>,
385 385 <addset
386 386 <spanset+ 2:4>,
387 387 <addset
388 388 <spanset+ 3:5>,
389 389 <spanset+ 4:6>>>>
390 390 0
391 391 1
392 392 2
393 393 3
394 394 4
395 395 5
396 396
397 397 no crash by empty group "()" while optimizing `or` operations
398 398
399 399 $ try --optimize '0|()'
400 400 (or
401 401 (list
402 402 (symbol '0')
403 403 (group
404 404 None)))
405 405 * optimized:
406 406 (or
407 407 (list
408 408 (symbol '0')
409 409 None))
410 410 hg: parse error: missing argument
411 411 [255]
412 412
413 413 test that chained `or` operations never eat up stack (issue4624)
414 414 (uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
415 415
416 416 $ hg log -T '{rev}\n' -r `$PYTHON -c "print '+'.join(['0:1'] * 500)"`
417 417 0
418 418 1
419 419
420 420 test that repeated `-r` options never eat up stack (issue4565)
421 421 (uses `-r 0::1` to avoid possible optimization at old-style parser)
422 422
423 423 $ hg log -T '{rev}\n' `$PYTHON -c "for i in range(500): print '-r 0::1 ',"`
424 424 0
425 425 1
426 426
427 427 check that conversion to only works
428 428 $ try --optimize '::3 - ::1'
429 429 (minus
430 430 (dagrangepre
431 431 (symbol '3'))
432 432 (dagrangepre
433 433 (symbol '1')))
434 434 * optimized:
435 435 (func
436 436 (symbol 'only')
437 437 (list
438 438 (symbol '3')
439 439 (symbol '1')))
440 440 * set:
441 441 <baseset+ [3]>
442 442 3
443 443 $ try --optimize 'ancestors(1) - ancestors(3)'
444 444 (minus
445 445 (func
446 446 (symbol 'ancestors')
447 447 (symbol '1'))
448 448 (func
449 449 (symbol 'ancestors')
450 450 (symbol '3')))
451 451 * optimized:
452 452 (func
453 453 (symbol 'only')
454 454 (list
455 455 (symbol '1')
456 456 (symbol '3')))
457 457 * set:
458 458 <baseset+ []>
459 459 $ try --optimize 'not ::2 and ::6'
460 460 (and
461 461 (not
462 462 (dagrangepre
463 463 (symbol '2')))
464 464 (dagrangepre
465 465 (symbol '6')))
466 466 * optimized:
467 467 (func
468 468 (symbol 'only')
469 469 (list
470 470 (symbol '6')
471 471 (symbol '2')))
472 472 * set:
473 473 <baseset+ [3, 4, 5, 6]>
474 474 3
475 475 4
476 476 5
477 477 6
478 478 $ try --optimize 'ancestors(6) and not ancestors(4)'
479 479 (and
480 480 (func
481 481 (symbol 'ancestors')
482 482 (symbol '6'))
483 483 (not
484 484 (func
485 485 (symbol 'ancestors')
486 486 (symbol '4'))))
487 487 * optimized:
488 488 (func
489 489 (symbol 'only')
490 490 (list
491 491 (symbol '6')
492 492 (symbol '4')))
493 493 * set:
494 494 <baseset+ [3, 5, 6]>
495 495 3
496 496 5
497 497 6
498 498
499 499 no crash by empty group "()" while optimizing to "only()"
500 500
501 501 $ try --optimize '::1 and ()'
502 502 (and
503 503 (dagrangepre
504 504 (symbol '1'))
505 505 (group
506 506 None))
507 507 * optimized:
508 508 (andsmally
509 509 (func
510 510 (symbol 'ancestors')
511 511 (symbol '1'))
512 512 None)
513 513 hg: parse error: missing argument
514 514 [255]
515 515
516 516 optimization to only() works only if ancestors() takes only one argument
517 517
518 518 $ hg debugrevspec -p optimized 'ancestors(6) - ancestors(4, 1)'
519 519 * optimized:
520 520 (difference
521 521 (func
522 522 (symbol 'ancestors')
523 523 (symbol '6'))
524 524 (func
525 525 (symbol 'ancestors')
526 526 (list
527 527 (symbol '4')
528 528 (symbol '1'))))
529 529 0
530 530 1
531 531 3
532 532 5
533 533 6
534 534 $ hg debugrevspec -p optimized 'ancestors(6, 1) - ancestors(4)'
535 535 * optimized:
536 536 (difference
537 537 (func
538 538 (symbol 'ancestors')
539 539 (list
540 540 (symbol '6')
541 541 (symbol '1')))
542 542 (func
543 543 (symbol 'ancestors')
544 544 (symbol '4')))
545 545 5
546 546 6
547 547
548 548 optimization disabled if keyword arguments passed (because we're too lazy
549 549 to support it)
550 550
551 551 $ hg debugrevspec -p optimized 'ancestors(set=6) - ancestors(set=4)'
552 552 * optimized:
553 553 (difference
554 554 (func
555 555 (symbol 'ancestors')
556 556 (keyvalue
557 557 (symbol 'set')
558 558 (symbol '6')))
559 559 (func
560 560 (symbol 'ancestors')
561 561 (keyvalue
562 562 (symbol 'set')
563 563 (symbol '4'))))
564 564 3
565 565 5
566 566 6
567 567
568 568 invalid function call should not be optimized to only()
569 569
570 570 $ log '"ancestors"(6) and not ancestors(4)'
571 571 hg: parse error: not a symbol
572 572 [255]
573 573
574 574 $ log 'ancestors(6) and not "ancestors"(4)'
575 575 hg: parse error: not a symbol
576 576 [255]
577 577
578 578 test empty string
579 579
580 580 $ log ''
581 581 hg: parse error: empty query
582 582 [255]
583 583 $ log 'parents("")'
584 584 hg: parse error: empty string is not a valid revision
585 585 [255]
586 586
587 587 test empty revset
588 588 $ hg log 'none()'
589 589
590 590 we can use patterns when searching for tags
591 591
592 592 $ log 'tag("1..*")'
593 593 abort: tag '1..*' does not exist!
594 594 [255]
595 595 $ log 'tag("re:1..*")'
596 596 6
597 597 $ log 'tag("re:[0-9].[0-9]")'
598 598 6
599 599 $ log 'tag("literal:1.0")'
600 600 6
601 601 $ log 'tag("re:0..*")'
602 602
603 603 $ log 'tag(unknown)'
604 604 abort: tag 'unknown' does not exist!
605 605 [255]
606 606 $ log 'tag("re:unknown")'
607 607 $ log 'present(tag("unknown"))'
608 608 $ log 'present(tag("re:unknown"))'
609 609 $ log 'branch(unknown)'
610 610 abort: unknown revision 'unknown'!
611 611 [255]
612 612 $ log 'branch("literal:unknown")'
613 613 abort: branch 'unknown' does not exist!
614 614 [255]
615 615 $ log 'branch("re:unknown")'
616 616 $ log 'present(branch("unknown"))'
617 617 $ log 'present(branch("re:unknown"))'
618 618 $ log 'user(bob)'
619 619 2
620 620
621 621 $ log '4::8'
622 622 4
623 623 8
624 624 $ log '4:8'
625 625 4
626 626 5
627 627 6
628 628 7
629 629 8
630 630
631 631 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
632 632 4
633 633 2
634 634 5
635 635
636 636 $ log 'not 0 and 0:2'
637 637 1
638 638 2
639 639 $ log 'not 1 and 0:2'
640 640 0
641 641 2
642 642 $ log 'not 2 and 0:2'
643 643 0
644 644 1
645 645 $ log '(1 and 2)::'
646 646 $ log '(1 and 2):'
647 647 $ log '(1 and 2):3'
648 648 $ log 'sort(head(), -rev)'
649 649 9
650 650 7
651 651 6
652 652 5
653 653 4
654 654 3
655 655 2
656 656 1
657 657 0
658 658 $ log '4::8 - 8'
659 659 4
660 660
661 661 matching() should preserve the order of the input set:
662 662
663 663 $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
664 664 2
665 665 3
666 666 1
667 667
668 668 $ log 'named("unknown")'
669 669 abort: namespace 'unknown' does not exist!
670 670 [255]
671 671 $ log 'named("re:unknown")'
672 672 abort: no namespace exists that match 'unknown'!
673 673 [255]
674 674 $ log 'present(named("unknown"))'
675 675 $ log 'present(named("re:unknown"))'
676 676
677 677 $ log 'tag()'
678 678 6
679 679 $ log 'named("tags")'
680 680 6
681 681
682 682 issue2437
683 683
684 684 $ log '3 and p1(5)'
685 685 3
686 686 $ log '4 and p2(6)'
687 687 4
688 688 $ log '1 and parents(:2)'
689 689 1
690 690 $ log '2 and children(1:)'
691 691 2
692 692 $ log 'roots(all()) or roots(all())'
693 693 0
694 694 $ hg debugrevspec 'roots(all()) or roots(all())'
695 695 0
696 696 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
697 697 9
698 698 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
699 699 4
700 700
701 701 issue2654: report a parse error if the revset was not completely parsed
702 702
703 703 $ log '1 OR 2'
704 704 hg: parse error at 2: invalid token
705 705 (1 OR 2
706 706 ^ here)
707 707 [255]
708 708
709 709 or operator should preserve ordering:
710 710 $ log 'reverse(2::4) or tip'
711 711 4
712 712 2
713 713 9
714 714
715 715 parentrevspec
716 716
717 717 $ log 'merge()^0'
718 718 6
719 719 $ log 'merge()^'
720 720 5
721 721 $ log 'merge()^1'
722 722 5
723 723 $ log 'merge()^2'
724 724 4
725 725 $ log '(not merge())^2'
726 726 $ log 'merge()^^'
727 727 3
728 728 $ log 'merge()^1^'
729 729 3
730 730 $ log 'merge()^^^'
731 731 1
732 732
733 733 $ hg debugrevspec -s '(merge() | 0)~-1'
734 734 * set:
735 735 <baseset+ [1, 7]>
736 736 1
737 737 7
738 738 $ log 'merge()~-1'
739 739 7
740 740 $ log 'tip~-1'
741 741 $ log '(tip | merge())~-1'
742 742 7
743 743 $ log 'merge()~0'
744 744 6
745 745 $ log 'merge()~1'
746 746 5
747 747 $ log 'merge()~2'
748 748 3
749 749 $ log 'merge()~2^1'
750 750 1
751 751 $ log 'merge()~3'
752 752 1
753 753
754 754 $ log '(-3:tip)^'
755 755 4
756 756 6
757 757 8
758 758
759 759 $ log 'tip^foo'
760 760 hg: parse error: ^ expects a number 0, 1, or 2
761 761 [255]
762 762
763 763 $ log 'branchpoint()~-1'
764 764 abort: revision in set has more than one child!
765 765 [255]
766 766
767 767 Bogus function gets suggestions
768 768 $ log 'add()'
769 769 hg: parse error: unknown identifier: add
770 770 (did you mean adds?)
771 771 [255]
772 772 $ log 'added()'
773 773 hg: parse error: unknown identifier: added
774 774 (did you mean adds?)
775 775 [255]
776 776 $ log 'remo()'
777 777 hg: parse error: unknown identifier: remo
778 778 (did you mean one of remote, removes?)
779 779 [255]
780 780 $ log 'babar()'
781 781 hg: parse error: unknown identifier: babar
782 782 [255]
783 783
784 784 Bogus function with a similar internal name doesn't suggest the internal name
785 785 $ log 'matches()'
786 786 hg: parse error: unknown identifier: matches
787 787 (did you mean matching?)
788 788 [255]
789 789
790 790 Undocumented functions aren't suggested as similar either
791 791 $ log 'tagged2()'
792 792 hg: parse error: unknown identifier: tagged2
793 793 [255]
794 794
795 795 multiple revspecs
796 796
797 797 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
798 798 8
799 799 9
800 800 4
801 801 5
802 802 6
803 803 7
804 804
805 805 test usage in revpair (with "+")
806 806
807 807 (real pair)
808 808
809 809 $ hg diff -r 'tip^^' -r 'tip'
810 810 diff -r 2326846efdab -r 24286f4ae135 .hgtags
811 811 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
812 812 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
813 813 @@ -0,0 +1,1 @@
814 814 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
815 815 $ hg diff -r 'tip^^::tip'
816 816 diff -r 2326846efdab -r 24286f4ae135 .hgtags
817 817 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
818 818 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
819 819 @@ -0,0 +1,1 @@
820 820 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
821 821
822 822 (single rev)
823 823
824 824 $ hg diff -r 'tip^' -r 'tip^'
825 825 $ hg diff -r 'tip^:tip^'
826 826
827 827 (single rev that does not looks like a range)
828 828
829 829 $ hg diff -r 'tip^::tip^ or tip^'
830 830 diff -r d5d0dcbdc4d9 .hgtags
831 831 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
832 832 +++ b/.hgtags * (glob)
833 833 @@ -0,0 +1,1 @@
834 834 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
835 835 $ hg diff -r 'tip^ or tip^'
836 836 diff -r d5d0dcbdc4d9 .hgtags
837 837 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
838 838 +++ b/.hgtags * (glob)
839 839 @@ -0,0 +1,1 @@
840 840 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
841 841
842 842 (no rev)
843 843
844 844 $ hg diff -r 'author("babar") or author("celeste")'
845 845 abort: empty revision range
846 846 [255]
847 847
848 848 aliases:
849 849
850 850 $ echo '[revsetalias]' >> .hg/hgrc
851 851 $ echo 'm = merge()' >> .hg/hgrc
852 852 (revset aliases can override builtin revsets)
853 853 $ echo 'p2($1) = p1($1)' >> .hg/hgrc
854 854 $ echo 'sincem = descendants(m)' >> .hg/hgrc
855 855 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
856 856 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
857 857 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
858 858
859 859 $ try m
860 860 (symbol 'm')
861 861 * expanded:
862 862 (func
863 863 (symbol 'merge')
864 864 None)
865 865 * set:
866 866 <filteredset
867 867 <fullreposet+ 0:10>,
868 868 <merge>>
869 869 6
870 870
871 871 $ HGPLAIN=1
872 872 $ export HGPLAIN
873 873 $ try m
874 874 (symbol 'm')
875 875 abort: unknown revision 'm'!
876 876 [255]
877 877
878 878 $ HGPLAINEXCEPT=revsetalias
879 879 $ export HGPLAINEXCEPT
880 880 $ try m
881 881 (symbol 'm')
882 882 * expanded:
883 883 (func
884 884 (symbol 'merge')
885 885 None)
886 886 * set:
887 887 <filteredset
888 888 <fullreposet+ 0:10>,
889 889 <merge>>
890 890 6
891 891
892 892 $ unset HGPLAIN
893 893 $ unset HGPLAINEXCEPT
894 894
895 895 $ try 'p2(.)'
896 896 (func
897 897 (symbol 'p2')
898 898 (symbol '.'))
899 899 * expanded:
900 900 (func
901 901 (symbol 'p1')
902 902 (symbol '.'))
903 903 * set:
904 904 <baseset+ [8]>
905 905 8
906 906
907 907 $ HGPLAIN=1
908 908 $ export HGPLAIN
909 909 $ try 'p2(.)'
910 910 (func
911 911 (symbol 'p2')
912 912 (symbol '.'))
913 913 * set:
914 914 <baseset+ []>
915 915
916 916 $ HGPLAINEXCEPT=revsetalias
917 917 $ export HGPLAINEXCEPT
918 918 $ try 'p2(.)'
919 919 (func
920 920 (symbol 'p2')
921 921 (symbol '.'))
922 922 * expanded:
923 923 (func
924 924 (symbol 'p1')
925 925 (symbol '.'))
926 926 * set:
927 927 <baseset+ [8]>
928 928 8
929 929
930 930 $ unset HGPLAIN
931 931 $ unset HGPLAINEXCEPT
932 932
933 933 test alias recursion
934 934
935 935 $ try sincem
936 936 (symbol 'sincem')
937 937 * expanded:
938 938 (func
939 939 (symbol 'descendants')
940 940 (func
941 941 (symbol 'merge')
942 942 None))
943 943 * set:
944 944 <generatorsetasc+>
945 945 6
946 946 7
947 947
948 948 test infinite recursion
949 949
950 950 $ echo 'recurse1 = recurse2' >> .hg/hgrc
951 951 $ echo 'recurse2 = recurse1' >> .hg/hgrc
952 952 $ try recurse1
953 953 (symbol 'recurse1')
954 954 hg: parse error: infinite expansion of revset alias "recurse1" detected
955 955 [255]
956 956
957 957 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
958 958 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
959 959 $ try "level2(level1(1, 2), 3)"
960 960 (func
961 961 (symbol 'level2')
962 962 (list
963 963 (func
964 964 (symbol 'level1')
965 965 (list
966 966 (symbol '1')
967 967 (symbol '2')))
968 968 (symbol '3')))
969 969 * expanded:
970 970 (or
971 971 (list
972 972 (symbol '3')
973 973 (or
974 974 (list
975 975 (symbol '1')
976 976 (symbol '2')))))
977 977 * set:
978 978 <addset
979 979 <baseset [3]>,
980 980 <baseset [1, 2]>>
981 981 3
982 982 1
983 983 2
984 984
985 985 test nesting and variable passing
986 986
987 987 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
988 988 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
989 989 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
990 990 $ try 'nested(2:5)'
991 991 (func
992 992 (symbol 'nested')
993 993 (range
994 994 (symbol '2')
995 995 (symbol '5')))
996 996 * expanded:
997 997 (func
998 998 (symbol 'max')
999 999 (range
1000 1000 (symbol '2')
1001 1001 (symbol '5')))
1002 1002 * set:
1003 1003 <baseset
1004 1004 <max
1005 1005 <fullreposet+ 0:10>,
1006 1006 <spanset+ 2:6>>>
1007 1007 5
1008 1008
1009 1009 test chained `or` operations are flattened at parsing phase
1010 1010
1011 1011 $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
1012 1012 $ try 'chainedorops(0:1, 1:2, 2:3)'
1013 1013 (func
1014 1014 (symbol 'chainedorops')
1015 1015 (list
1016 1016 (range
1017 1017 (symbol '0')
1018 1018 (symbol '1'))
1019 1019 (range
1020 1020 (symbol '1')
1021 1021 (symbol '2'))
1022 1022 (range
1023 1023 (symbol '2')
1024 1024 (symbol '3'))))
1025 1025 * expanded:
1026 1026 (or
1027 1027 (list
1028 1028 (range
1029 1029 (symbol '0')
1030 1030 (symbol '1'))
1031 1031 (range
1032 1032 (symbol '1')
1033 1033 (symbol '2'))
1034 1034 (range
1035 1035 (symbol '2')
1036 1036 (symbol '3'))))
1037 1037 * set:
1038 1038 <addset
1039 1039 <spanset+ 0:2>,
1040 1040 <addset
1041 1041 <spanset+ 1:3>,
1042 1042 <spanset+ 2:4>>>
1043 1043 0
1044 1044 1
1045 1045 2
1046 1046 3
1047 1047
1048 1048 test variable isolation, variable placeholders are rewritten as string
1049 1049 then parsed and matched again as string. Check they do not leak too
1050 1050 far away.
1051 1051
1052 1052 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
1053 1053 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
1054 1054 $ try 'callinjection(2:5)'
1055 1055 (func
1056 1056 (symbol 'callinjection')
1057 1057 (range
1058 1058 (symbol '2')
1059 1059 (symbol '5')))
1060 1060 * expanded:
1061 1061 (func
1062 1062 (symbol 'descendants')
1063 1063 (func
1064 1064 (symbol 'max')
1065 1065 (string '$1')))
1066 1066 abort: unknown revision '$1'!
1067 1067 [255]
1068 1068
1069 1069 test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
1070 1070 but 'all()' should never be substituted to '0()'.
1071 1071
1072 1072 $ echo 'universe = all()' >> .hg/hgrc
1073 1073 $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
1074 1074 $ try 'shadowall(0)'
1075 1075 (func
1076 1076 (symbol 'shadowall')
1077 1077 (symbol '0'))
1078 1078 * expanded:
1079 1079 (and
1080 1080 (symbol '0')
1081 1081 (func
1082 1082 (symbol 'all')
1083 1083 None))
1084 1084 * set:
1085 1085 <filteredset
1086 1086 <baseset [0]>,
1087 1087 <spanset+ 0:10>>
1088 1088 0
1089 1089
1090 1090 test unknown reference:
1091 1091
1092 1092 $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
1093 1093 (func
1094 1094 (symbol 'unknownref')
1095 1095 (symbol '0'))
1096 1096 abort: bad definition of revset alias "unknownref": invalid symbol '$2'
1097 1097 [255]
1098 1098
1099 1099 $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
1100 1100 (symbol 'tip')
1101 1101 warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
1102 1102 * set:
1103 1103 <baseset [9]>
1104 1104 9
1105 1105
1106 1106 $ try 'tip'
1107 1107 (symbol 'tip')
1108 1108 * set:
1109 1109 <baseset [9]>
1110 1110 9
1111 1111
1112 1112 $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
1113 1113 (symbol 'tip')
1114 1114 warning: bad declaration of revset alias "bad name": at 4: invalid token
1115 1115 * set:
1116 1116 <baseset [9]>
1117 1117 9
1118 1118 $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
1119 1119 $ try 'strictreplacing("foo", tip)'
1120 1120 (func
1121 1121 (symbol 'strictreplacing')
1122 1122 (list
1123 1123 (string 'foo')
1124 1124 (symbol 'tip')))
1125 1125 * expanded:
1126 1126 (or
1127 1127 (list
1128 1128 (symbol 'tip')
1129 1129 (func
1130 1130 (symbol 'desc')
1131 1131 (string '$1'))))
1132 1132 * set:
1133 1133 <addset
1134 1134 <baseset [9]>,
1135 1135 <filteredset
1136 1136 <fullreposet+ 0:10>,
1137 1137 <desc '$1'>>>
1138 1138 9
1139 1139
1140 1140 $ try 'd(2:5)'
1141 1141 (func
1142 1142 (symbol 'd')
1143 1143 (range
1144 1144 (symbol '2')
1145 1145 (symbol '5')))
1146 1146 * expanded:
1147 1147 (func
1148 1148 (symbol 'reverse')
1149 1149 (func
1150 1150 (symbol 'sort')
1151 1151 (list
1152 1152 (range
1153 1153 (symbol '2')
1154 1154 (symbol '5'))
1155 1155 (symbol 'date'))))
1156 1156 * set:
1157 1157 <baseset [4, 5, 3, 2]>
1158 1158 4
1159 1159 5
1160 1160 3
1161 1161 2
1162 1162 $ try 'rs(2 or 3, date)'
1163 1163 (func
1164 1164 (symbol 'rs')
1165 1165 (list
1166 1166 (or
1167 1167 (list
1168 1168 (symbol '2')
1169 1169 (symbol '3')))
1170 1170 (symbol 'date')))
1171 1171 * expanded:
1172 1172 (func
1173 1173 (symbol 'reverse')
1174 1174 (func
1175 1175 (symbol 'sort')
1176 1176 (list
1177 1177 (or
1178 1178 (list
1179 1179 (symbol '2')
1180 1180 (symbol '3')))
1181 1181 (symbol 'date'))))
1182 1182 * set:
1183 1183 <baseset [3, 2]>
1184 1184 3
1185 1185 2
1186 1186 $ try 'rs()'
1187 1187 (func
1188 1188 (symbol 'rs')
1189 1189 None)
1190 1190 hg: parse error: invalid number of arguments: 0
1191 1191 [255]
1192 1192 $ try 'rs(2)'
1193 1193 (func
1194 1194 (symbol 'rs')
1195 1195 (symbol '2'))
1196 1196 hg: parse error: invalid number of arguments: 1
1197 1197 [255]
1198 1198 $ try 'rs(2, data, 7)'
1199 1199 (func
1200 1200 (symbol 'rs')
1201 1201 (list
1202 1202 (symbol '2')
1203 1203 (symbol 'data')
1204 1204 (symbol '7')))
1205 1205 hg: parse error: invalid number of arguments: 3
1206 1206 [255]
1207 1207 $ try 'rs4(2 or 3, x, x, date)'
1208 1208 (func
1209 1209 (symbol 'rs4')
1210 1210 (list
1211 1211 (or
1212 1212 (list
1213 1213 (symbol '2')
1214 1214 (symbol '3')))
1215 1215 (symbol 'x')
1216 1216 (symbol 'x')
1217 1217 (symbol 'date')))
1218 1218 * expanded:
1219 1219 (func
1220 1220 (symbol 'reverse')
1221 1221 (func
1222 1222 (symbol 'sort')
1223 1223 (list
1224 1224 (or
1225 1225 (list
1226 1226 (symbol '2')
1227 1227 (symbol '3')))
1228 1228 (symbol 'date'))))
1229 1229 * set:
1230 1230 <baseset [3, 2]>
1231 1231 3
1232 1232 2
1233 1233
1234 1234 issue4553: check that revset aliases override existing hash prefix
1235 1235
1236 1236 $ hg log -qr e
1237 1237 6:e0cc66ef77e8
1238 1238
1239 1239 $ hg log -qr e --config revsetalias.e="all()"
1240 1240 0:2785f51eece5
1241 1241 1:d75937da8da0
1242 1242 2:5ed5505e9f1c
1243 1243 3:8528aa5637f2
1244 1244 4:2326846efdab
1245 1245 5:904fa392b941
1246 1246 6:e0cc66ef77e8
1247 1247 7:013af1973af4
1248 1248 8:d5d0dcbdc4d9
1249 1249 9:24286f4ae135
1250 1250
1251 1251 $ hg log -qr e: --config revsetalias.e="0"
1252 1252 0:2785f51eece5
1253 1253 1:d75937da8da0
1254 1254 2:5ed5505e9f1c
1255 1255 3:8528aa5637f2
1256 1256 4:2326846efdab
1257 1257 5:904fa392b941
1258 1258 6:e0cc66ef77e8
1259 1259 7:013af1973af4
1260 1260 8:d5d0dcbdc4d9
1261 1261 9:24286f4ae135
1262 1262
1263 1263 $ hg log -qr :e --config revsetalias.e="9"
1264 1264 0:2785f51eece5
1265 1265 1:d75937da8da0
1266 1266 2:5ed5505e9f1c
1267 1267 3:8528aa5637f2
1268 1268 4:2326846efdab
1269 1269 5:904fa392b941
1270 1270 6:e0cc66ef77e8
1271 1271 7:013af1973af4
1272 1272 8:d5d0dcbdc4d9
1273 1273 9:24286f4ae135
1274 1274
1275 1275 $ hg log -qr e:
1276 1276 6:e0cc66ef77e8
1277 1277 7:013af1973af4
1278 1278 8:d5d0dcbdc4d9
1279 1279 9:24286f4ae135
1280 1280
1281 1281 $ hg log -qr :e
1282 1282 0:2785f51eece5
1283 1283 1:d75937da8da0
1284 1284 2:5ed5505e9f1c
1285 1285 3:8528aa5637f2
1286 1286 4:2326846efdab
1287 1287 5:904fa392b941
1288 1288 6:e0cc66ef77e8
1289 1289
1290 1290 issue2549 - correct optimizations
1291 1291
1292 1292 $ try 'limit(1 or 2 or 3, 2) and not 2'
1293 1293 (and
1294 1294 (func
1295 1295 (symbol 'limit')
1296 1296 (list
1297 1297 (or
1298 1298 (list
1299 1299 (symbol '1')
1300 1300 (symbol '2')
1301 1301 (symbol '3')))
1302 1302 (symbol '2')))
1303 1303 (not
1304 1304 (symbol '2')))
1305 1305 * set:
1306 1306 <filteredset
1307 1307 <baseset [1, 2]>,
1308 1308 <not
1309 1309 <baseset [2]>>>
1310 1310 1
1311 1311 $ try 'max(1 or 2) and not 2'
1312 1312 (and
1313 1313 (func
1314 1314 (symbol 'max')
1315 1315 (or
1316 1316 (list
1317 1317 (symbol '1')
1318 1318 (symbol '2'))))
1319 1319 (not
1320 1320 (symbol '2')))
1321 1321 * set:
1322 1322 <filteredset
1323 1323 <baseset
1324 1324 <max
1325 1325 <fullreposet+ 0:10>,
1326 1326 <baseset [1, 2]>>>,
1327 1327 <not
1328 1328 <baseset [2]>>>
1329 1329 $ try 'min(1 or 2) and not 1'
1330 1330 (and
1331 1331 (func
1332 1332 (symbol 'min')
1333 1333 (or
1334 1334 (list
1335 1335 (symbol '1')
1336 1336 (symbol '2'))))
1337 1337 (not
1338 1338 (symbol '1')))
1339 1339 * set:
1340 1340 <filteredset
1341 1341 <baseset
1342 1342 <min
1343 1343 <fullreposet+ 0:10>,
1344 1344 <baseset [1, 2]>>>,
1345 1345 <not
1346 1346 <baseset [1]>>>
1347 1347 $ try 'last(1 or 2, 1) and not 2'
1348 1348 (and
1349 1349 (func
1350 1350 (symbol 'last')
1351 1351 (list
1352 1352 (or
1353 1353 (list
1354 1354 (symbol '1')
1355 1355 (symbol '2')))
1356 1356 (symbol '1')))
1357 1357 (not
1358 1358 (symbol '2')))
1359 1359 * set:
1360 1360 <filteredset
1361 1361 <baseset [2]>,
1362 1362 <not
1363 1363 <baseset [2]>>>
1364 1364
1365 1365 issue4289 - ordering of built-ins
1366 1366 $ hg log -M -q -r 3:2
1367 1367 3:8528aa5637f2
1368 1368 2:5ed5505e9f1c
1369 1369
1370 1370 test revsets started with 40-chars hash (issue3669)
1371 1371
1372 1372 $ ISSUE3669_TIP=`hg tip --template '{node}'`
1373 1373 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
1374 1374 9
1375 1375 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
1376 1376 8
1377 1377
1378 1378 test or-ed indirect predicates (issue3775)
1379 1379
1380 1380 $ log '6 or 6^1' | sort
1381 1381 5
1382 1382 6
1383 1383 $ log '6^1 or 6' | sort
1384 1384 5
1385 1385 6
1386 1386 $ log '4 or 4~1' | sort
1387 1387 2
1388 1388 4
1389 1389 $ log '4~1 or 4' | sort
1390 1390 2
1391 1391 4
1392 1392 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
1393 1393 0
1394 1394 1
1395 1395 2
1396 1396 3
1397 1397 4
1398 1398 5
1399 1399 6
1400 1400 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
1401 1401 0
1402 1402 1
1403 1403 2
1404 1404 3
1405 1405 4
1406 1406 5
1407 1407 6
1408 1408
1409 1409 tests for 'remote()' predicate:
1410 1410 #. (csets in remote) (id) (remote)
1411 1411 1. less than local current branch "default"
1412 1412 2. same with local specified "default"
1413 1413 3. more than local specified specified
1414 1414
1415 1415 $ hg clone --quiet -U . ../remote3
1416 1416 $ cd ../remote3
1417 1417 $ hg update -q 7
1418 1418 $ echo r > r
1419 1419 $ hg ci -Aqm 10
1420 1420 $ log 'remote()'
1421 1421 7
1422 1422 $ log 'remote("a-b-c-")'
1423 1423 2
1424 1424 $ cd ../repo
1425 1425 $ log 'remote(".a.b.c.", "../remote3")'
1426 1426
1427 1427 tests for concatenation of strings/symbols by "##"
1428 1428
1429 1429 $ try "278 ## '5f5' ## 1ee ## 'ce5'"
1430 1430 (_concat
1431 1431 (_concat
1432 1432 (_concat
1433 1433 (symbol '278')
1434 1434 (string '5f5'))
1435 1435 (symbol '1ee'))
1436 1436 (string 'ce5'))
1437 1437 * concatenated:
1438 1438 (string '2785f51eece5')
1439 1439 * set:
1440 1440 <baseset [0]>
1441 1441 0
1442 1442
1443 1443 $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
1444 1444 $ try "cat4(278, '5f5', 1ee, 'ce5')"
1445 1445 (func
1446 1446 (symbol 'cat4')
1447 1447 (list
1448 1448 (symbol '278')
1449 1449 (string '5f5')
1450 1450 (symbol '1ee')
1451 1451 (string 'ce5')))
1452 1452 * expanded:
1453 1453 (_concat
1454 1454 (_concat
1455 1455 (_concat
1456 1456 (symbol '278')
1457 1457 (string '5f5'))
1458 1458 (symbol '1ee'))
1459 1459 (string 'ce5'))
1460 1460 * concatenated:
1461 1461 (string '2785f51eece5')
1462 1462 * set:
1463 1463 <baseset [0]>
1464 1464 0
1465 1465
1466 1466 (check concatenation in alias nesting)
1467 1467
1468 1468 $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
1469 1469 $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
1470 1470 $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
1471 1471 0
1472 1472
1473 1473 (check operator priority)
1474 1474
1475 1475 $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
1476 1476 $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
1477 1477 0
1478 1478 4
1479 1479
1480 1480 $ cd ..
1481 1481
1482 1482 prepare repository that has "default" branches of multiple roots
1483 1483
1484 1484 $ hg init namedbranch
1485 1485 $ cd namedbranch
1486 1486
1487 1487 $ echo default0 >> a
1488 1488 $ hg ci -Aqm0
1489 1489 $ echo default1 >> a
1490 1490 $ hg ci -m1
1491 1491
1492 1492 $ hg branch -q stable
1493 1493 $ echo stable2 >> a
1494 1494 $ hg ci -m2
1495 1495 $ echo stable3 >> a
1496 1496 $ hg ci -m3
1497 1497
1498 1498 $ hg update -q null
1499 1499 $ echo default4 >> a
1500 1500 $ hg ci -Aqm4
1501 1501 $ echo default5 >> a
1502 1502 $ hg ci -m5
1503 1503
1504 1504 "null" revision belongs to "default" branch (issue4683)
1505 1505
1506 1506 $ log 'branch(null)'
1507 1507 0
1508 1508 1
1509 1509 4
1510 1510 5
1511 1511
1512 1512 "null" revision belongs to "default" branch, but it shouldn't appear in set
1513 1513 unless explicitly specified (issue4682)
1514 1514
1515 1515 $ log 'children(branch(default))'
1516 1516 1
1517 1517 2
1518 1518 5
1519 1519
1520 1520 $ cd ..
1521 1521
1522 1522 test author/desc/keyword in problematic encoding
1523 1523 # unicode: cp932:
1524 1524 # u30A2 0x83 0x41(= 'A')
1525 1525 # u30C2 0x83 0x61(= 'a')
1526 1526
1527 1527 $ hg init problematicencoding
1528 1528 $ cd problematicencoding
1529 1529
1530 1530 $ $PYTHON > setup.sh <<EOF
1531 1531 > print u'''
1532 1532 > echo a > text
1533 1533 > hg add text
1534 1534 > hg --encoding utf-8 commit -u '\u30A2' -m none
1535 1535 > echo b > text
1536 1536 > hg --encoding utf-8 commit -u '\u30C2' -m none
1537 1537 > echo c > text
1538 1538 > hg --encoding utf-8 commit -u none -m '\u30A2'
1539 1539 > echo d > text
1540 1540 > hg --encoding utf-8 commit -u none -m '\u30C2'
1541 1541 > '''.encode('utf-8')
1542 1542 > EOF
1543 1543 $ sh < setup.sh
1544 1544
1545 1545 test in problematic encoding
1546 1546 $ $PYTHON > test.sh <<EOF
1547 1547 > print u'''
1548 1548 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
1549 1549 > echo ====
1550 1550 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
1551 1551 > echo ====
1552 1552 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
1553 1553 > echo ====
1554 1554 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
1555 1555 > echo ====
1556 1556 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
1557 1557 > echo ====
1558 1558 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
1559 1559 > '''.encode('cp932')
1560 1560 > EOF
1561 1561 $ sh < test.sh
1562 1562 0
1563 1563 ====
1564 1564 1
1565 1565 ====
1566 1566 2
1567 1567 ====
1568 1568 3
1569 1569 ====
1570 1570 0
1571 1571 2
1572 1572 ====
1573 1573 1
1574 1574 3
1575 1575
1576 1576 test error message of bad revset
1577 1577 $ hg log -r 'foo\\'
1578 1578 hg: parse error at 3: syntax error in revset 'foo\\'
1579 1579 (foo\\
1580 1580 ^ here)
1581 1581 [255]
1582 1582
1583 1583 $ cd ..
1584 1584
1585 1585 Test that revset predicate of extension isn't loaded at failure of
1586 1586 loading it
1587 1587
1588 1588 $ cd repo
1589 1589
1590 1590 $ cat <<EOF > $TESTTMP/custompredicate.py
1591 1591 > from mercurial import error, registrar, revset
1592 1592 >
1593 1593 > revsetpredicate = registrar.revsetpredicate()
1594 1594 >
1595 1595 > @revsetpredicate(b'custom1()')
1596 1596 > def custom1(repo, subset, x):
1597 1597 > return revset.baseset([1])
1598 1598 >
1599 1599 > raise error.Abort(b'intentional failure of loading extension')
1600 1600 > EOF
1601 1601 $ cat <<EOF > .hg/hgrc
1602 1602 > [extensions]
1603 1603 > custompredicate = $TESTTMP/custompredicate.py
1604 1604 > EOF
1605 1605
1606 1606 $ hg debugrevspec "custom1()"
1607 1607 *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
1608 1608 hg: parse error: unknown identifier: custom1
1609 1609 [255]
1610 1610
1611 1611 Test repo.anyrevs with customized revset overrides
1612 1612
1613 1613 $ cat > $TESTTMP/printprevset.py <<EOF
1614 1614 > from mercurial import encoding, registrar
1615 1615 > cmdtable = {}
1616 1616 > command = registrar.command(cmdtable)
1617 1617 > @command(b'printprevset')
1618 1618 > def printprevset(ui, repo):
1619 1619 > alias = {}
1620 1620 > p = encoding.environ.get(b'P')
1621 1621 > if p:
1622 1622 > alias[b'P'] = p
1623 1623 > revs = repo.anyrevs([b'P'], user=True, localalias=alias)
1624 1624 > ui.write(b'P=%r\n' % list(revs))
1625 1625 > EOF
1626 1626
1627 1627 $ cat >> .hg/hgrc <<EOF
1628 1628 > custompredicate = !
1629 1629 > printprevset = $TESTTMP/printprevset.py
1630 1630 > EOF
1631 1631
1632 1632 $ hg --config revsetalias.P=1 printprevset
1633 1633 P=[1]
1634 1634 $ P=3 hg --config revsetalias.P=2 printprevset
1635 1635 P=[3]
1636 1636
1637 1637 $ cd ..
1638 1638
1639 1639 Test obsstore related revsets
1640 1640
1641 1641 $ hg init repo1
1642 1642 $ cd repo1
1643 1643 $ cat <<EOF >> .hg/hgrc
1644 1644 > [experimental]
1645 1645 > evolution.createmarkers=True
1646 1646 > EOF
1647 1647
1648 1648 $ hg debugdrawdag <<'EOS'
1649 1649 > F G
1650 1650 > |/ # split: B -> E, F
1651 1651 > B C D E # amend: B -> C -> D
1652 1652 > \|/ | # amend: F -> G
1653 1653 > A A Z # amend: A -> Z
1654 1654 > EOS
1655 1655 3 new orphan changesets
1656 1656 3 new content-divergent changesets
1657 1657
1658 1658 $ hg log -r 'successors(Z)' -T '{desc}\n'
1659 1659 Z
1660 1660
1661 1661 $ hg log -r 'successors(F)' -T '{desc}\n'
1662 1662 F
1663 1663 G
1664 1664
1665 1665 $ hg tag --remove --local C D E F G
1666 1666
1667 1667 $ hg log -r 'successors(B)' -T '{desc}\n'
1668 1668 B
1669 1669 D
1670 1670 E
1671 1671 G
1672 1672
1673 1673 $ hg log -r 'successors(B)' -T '{desc}\n' --hidden
1674 1674 B
1675 1675 C
1676 1676 D
1677 1677 E
1678 1678 F
1679 1679 G
1680 1680
1681 1681 $ hg log -r 'successors(B)-obsolete()' -T '{desc}\n' --hidden
1682 1682 D
1683 1683 E
1684 1684 G
1685 1685
1686 1686 $ hg log -r 'successors(B+A)-contentdivergent()' -T '{desc}\n'
1687 1687 A
1688 1688 Z
1689 1689 B
1690 1690
1691 1691 $ hg log -r 'successors(B+A)-contentdivergent()-obsolete()' -T '{desc}\n'
1692 1692 Z
1693 1693
1694 1694 Test `draft() & ::x` optimization
1695 1695
1696 1696 $ hg init $TESTTMP/repo2
1697 1697 $ cd $TESTTMP/repo2
1698 1698 $ hg debugdrawdag <<'EOS'
1699 1699 > P5 S1
1700 1700 > | |
1701 1701 > S2 | D3
1702 1702 > \|/
1703 1703 > P4
1704 1704 > |
1705 1705 > P3 D2
1706 1706 > | |
1707 1707 > P2 D1
1708 1708 > |/
1709 1709 > P1
1710 1710 > |
1711 1711 > P0
1712 1712 > EOS
1713 1713 $ hg phase --public -r P5
1714 1714 $ hg phase --force --secret -r S1+S2
1715 1715 $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo, topo.firstbranch=P5)'
1716 1716 o 8 P5 public
1717 1717 |
1718 1718 | o 10 S1 secret
1719 1719 | |
1720 1720 | o 7 D3 draft
1721 1721 |/
1722 1722 | o 9 S2 secret
1723 1723 |/
1724 1724 o 6 P4 public
1725 1725 |
1726 1726 o 5 P3 public
1727 1727 |
1728 1728 o 3 P2 public
1729 1729 |
1730 1730 | o 4 D2 draft
1731 1731 | |
1732 1732 | o 2 D1 draft
1733 1733 |/
1734 1734 o 1 P1 public
1735 1735 |
1736 1736 o 0 P0 public
1737 1737
1738 1738 $ hg debugrevspec --verify -p analyzed -p optimized 'draft() & ::(((S1+D1+P5)-D3)+S2)'
1739 1739 * analyzed:
1740 1740 (and
1741 1741 (func
1742 1742 (symbol 'draft')
1743 1743 None)
1744 1744 (func
1745 1745 (symbol 'ancestors')
1746 1746 (or
1747 1747 (list
1748 1748 (and
1749 1749 (or
1750 1750 (list
1751 1751 (symbol 'S1')
1752 1752 (symbol 'D1')
1753 1753 (symbol 'P5')))
1754 1754 (not
1755 1755 (symbol 'D3')))
1756 1756 (symbol 'S2')))))
1757 1757 * optimized:
1758 1758 (func
1759 1759 (symbol '_phaseandancestors')
1760 1760 (list
1761 1761 (symbol 'draft')
1762 1762 (or
1763 1763 (list
1764 1764 (difference
1765 1765 (func
1766 1766 (symbol '_list')
1767 1767 (string 'S1\x00D1\x00P5'))
1768 1768 (symbol 'D3'))
1769 1769 (symbol 'S2')))))
1770 1770 $ hg debugrevspec --verify -p analyzed -p optimized 'secret() & ::9'
1771 1771 * analyzed:
1772 1772 (and
1773 1773 (func
1774 1774 (symbol 'secret')
1775 1775 None)
1776 1776 (func
1777 1777 (symbol 'ancestors')
1778 1778 (symbol '9')))
1779 1779 * optimized:
1780 1780 (func
1781 1781 (symbol '_phaseandancestors')
1782 1782 (list
1783 1783 (symbol 'secret')
1784 1784 (symbol '9')))
1785 1785 $ hg debugrevspec --verify -p analyzed -p optimized '7 & ( (not public()) & ::(tag()) )'
1786 1786 * analyzed:
1787 1787 (and
1788 1788 (symbol '7')
1789 1789 (and
1790 1790 (not
1791 1791 (func
1792 1792 (symbol 'public')
1793 1793 None))
1794 1794 (func
1795 1795 (symbol 'ancestors')
1796 1796 (func
1797 1797 (symbol 'tag')
1798 1798 None))))
1799 1799 * optimized:
1800 1800 (and
1801 1801 (symbol '7')
1802 1802 (func
1803 1803 (symbol '_phaseandancestors')
1804 1804 (list
1805 1805 (symbol '_notpublic')
1806 1806 (func
1807 1807 (symbol 'tag')
1808 1808 None))))
1809 1809 $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, 1)'
1810 1810 * optimized:
1811 1811 (and
1812 1812 (func
1813 1813 (symbol '_notpublic')
1814 1814 None)
1815 1815 (func
1816 1816 (symbol 'ancestors')
1817 1817 (list
1818 1818 (func
1819 1819 (symbol '_list')
1820 1820 (string 'S1\x00D2\x00P5'))
1821 1821 (symbol '1'))))
1822 1822 $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, depth=1)'
1823 1823 * optimized:
1824 1824 (and
1825 1825 (func
1826 1826 (symbol '_notpublic')
1827 1827 None)
1828 1828 (func
1829 1829 (symbol 'ancestors')
1830 1830 (list
1831 1831 (func
1832 1832 (symbol '_list')
1833 1833 (string 'S1\x00D2\x00P5'))
1834 1834 (keyvalue
1835 1835 (symbol 'depth')
1836 1836 (symbol '1')))))
1837 1837
1838 1838 test commonancestors and its optimization
1839 1839
1840 1840 $ hg debugrevspec --verify -p analyzed -p optimized 'heads(commonancestors(head()))'
1841 1841 * analyzed:
1842 1842 (func
1843 1843 (symbol 'heads')
1844 1844 (func
1845 1845 (symbol 'commonancestors')
1846 1846 (func
1847 1847 (symbol 'head')
1848 1848 None)))
1849 1849 * optimized:
1850 1850 (func
1851 1851 (symbol '_commonancestorheads')
1852 1852 (func
1853 1853 (symbol 'head')
1854 1854 None))
General Comments 0
You need to be logged in to leave comments. Login now