##// END OF EJS Templates
revset: fix documentation for 'remote()' predicate...
FUJIWARA Katsunori -
r16007:f06c53ca stable
parent child Browse files
Show More
@@ -1,1225 +1,1225 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 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node as nodemod
11 11 import bookmarks as bookmarksmod
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15
16 16 elements = {
17 17 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
18 18 "~": (18, None, ("ancestor", 18)),
19 19 "^": (18, None, ("parent", 18), ("parentpost", 18)),
20 20 "-": (5, ("negate", 19), ("minus", 5)),
21 21 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
22 22 ("dagrangepost", 17)),
23 23 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
24 24 ("dagrangepost", 17)),
25 25 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
26 26 "not": (10, ("not", 10)),
27 27 "!": (10, ("not", 10)),
28 28 "and": (5, None, ("and", 5)),
29 29 "&": (5, None, ("and", 5)),
30 30 "or": (4, None, ("or", 4)),
31 31 "|": (4, None, ("or", 4)),
32 32 "+": (4, None, ("or", 4)),
33 33 ",": (2, None, ("list", 2)),
34 34 ")": (0, None, None),
35 35 "symbol": (0, ("symbol",), None),
36 36 "string": (0, ("string",), None),
37 37 "end": (0, None, None),
38 38 }
39 39
40 40 keywords = set(['and', 'or', 'not'])
41 41
42 42 def tokenize(program):
43 43 pos, l = 0, len(program)
44 44 while pos < l:
45 45 c = program[pos]
46 46 if c.isspace(): # skip inter-token whitespace
47 47 pass
48 48 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
49 49 yield ('::', None, pos)
50 50 pos += 1 # skip ahead
51 51 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
52 52 yield ('..', None, pos)
53 53 pos += 1 # skip ahead
54 54 elif c in "():,-|&+!~^": # handle simple operators
55 55 yield (c, None, pos)
56 56 elif (c in '"\'' or c == 'r' and
57 57 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
58 58 if c == 'r':
59 59 pos += 1
60 60 c = program[pos]
61 61 decode = lambda x: x
62 62 else:
63 63 decode = lambda x: x.decode('string-escape')
64 64 pos += 1
65 65 s = pos
66 66 while pos < l: # find closing quote
67 67 d = program[pos]
68 68 if d == '\\': # skip over escaped characters
69 69 pos += 2
70 70 continue
71 71 if d == c:
72 72 yield ('string', decode(program[s:pos]), s)
73 73 break
74 74 pos += 1
75 75 else:
76 76 raise error.ParseError(_("unterminated string"), s)
77 77 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
78 78 s = pos
79 79 pos += 1
80 80 while pos < l: # find end of symbol
81 81 d = program[pos]
82 82 if not (d.isalnum() or d in "._/" or ord(d) > 127):
83 83 break
84 84 if d == '.' and program[pos - 1] == '.': # special case for ..
85 85 pos -= 1
86 86 break
87 87 pos += 1
88 88 sym = program[s:pos]
89 89 if sym in keywords: # operator keywords
90 90 yield (sym, None, s)
91 91 else:
92 92 yield ('symbol', sym, s)
93 93 pos -= 1
94 94 else:
95 95 raise error.ParseError(_("syntax error"), pos)
96 96 pos += 1
97 97 yield ('end', None, pos)
98 98
99 99 # helpers
100 100
101 101 def getstring(x, err):
102 102 if x and (x[0] == 'string' or x[0] == 'symbol'):
103 103 return x[1]
104 104 raise error.ParseError(err)
105 105
106 106 def getlist(x):
107 107 if not x:
108 108 return []
109 109 if x[0] == 'list':
110 110 return getlist(x[1]) + [x[2]]
111 111 return [x]
112 112
113 113 def getargs(x, min, max, err):
114 114 l = getlist(x)
115 115 if len(l) < min or len(l) > max:
116 116 raise error.ParseError(err)
117 117 return l
118 118
119 119 def getset(repo, subset, x):
120 120 if not x:
121 121 raise error.ParseError(_("missing argument"))
122 122 return methods[x[0]](repo, subset, *x[1:])
123 123
124 124 # operator methods
125 125
126 126 def stringset(repo, subset, x):
127 127 x = repo[x].rev()
128 128 if x == -1 and len(subset) == len(repo):
129 129 return [-1]
130 130 if len(subset) == len(repo) or x in subset:
131 131 return [x]
132 132 return []
133 133
134 134 def symbolset(repo, subset, x):
135 135 if x in symbols:
136 136 raise error.ParseError(_("can't use %s here") % x)
137 137 return stringset(repo, subset, x)
138 138
139 139 def rangeset(repo, subset, x, y):
140 140 m = getset(repo, subset, x)
141 141 if not m:
142 142 m = getset(repo, range(len(repo)), x)
143 143
144 144 n = getset(repo, subset, y)
145 145 if not n:
146 146 n = getset(repo, range(len(repo)), y)
147 147
148 148 if not m or not n:
149 149 return []
150 150 m, n = m[0], n[-1]
151 151
152 152 if m < n:
153 153 r = range(m, n + 1)
154 154 else:
155 155 r = range(m, n - 1, -1)
156 156 s = set(subset)
157 157 return [x for x in r if x in s]
158 158
159 159 def andset(repo, subset, x, y):
160 160 return getset(repo, getset(repo, subset, x), y)
161 161
162 162 def orset(repo, subset, x, y):
163 163 xl = getset(repo, subset, x)
164 164 s = set(xl)
165 165 yl = getset(repo, [r for r in subset if r not in s], y)
166 166 return xl + yl
167 167
168 168 def notset(repo, subset, x):
169 169 s = set(getset(repo, subset, x))
170 170 return [r for r in subset if r not in s]
171 171
172 172 def listset(repo, subset, a, b):
173 173 raise error.ParseError(_("can't use a list in this context"))
174 174
175 175 def func(repo, subset, a, b):
176 176 if a[0] == 'symbol' and a[1] in symbols:
177 177 return symbols[a[1]](repo, subset, b)
178 178 raise error.ParseError(_("not a function: %s") % a[1])
179 179
180 180 # functions
181 181
182 182 def adds(repo, subset, x):
183 183 """``adds(pattern)``
184 184 Changesets that add a file matching pattern.
185 185 """
186 186 # i18n: "adds" is a keyword
187 187 pat = getstring(x, _("adds requires a pattern"))
188 188 return checkstatus(repo, subset, pat, 1)
189 189
190 190 def ancestor(repo, subset, x):
191 191 """``ancestor(single, single)``
192 192 Greatest common ancestor of the two changesets.
193 193 """
194 194 # i18n: "ancestor" is a keyword
195 195 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
196 196 r = range(len(repo))
197 197 a = getset(repo, r, l[0])
198 198 b = getset(repo, r, l[1])
199 199 if len(a) != 1 or len(b) != 1:
200 200 # i18n: "ancestor" is a keyword
201 201 raise error.ParseError(_("ancestor arguments must be single revisions"))
202 202 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
203 203
204 204 return [r for r in an if r in subset]
205 205
206 206 def ancestors(repo, subset, x):
207 207 """``ancestors(set)``
208 208 Changesets that are ancestors of a changeset in set.
209 209 """
210 210 args = getset(repo, range(len(repo)), x)
211 211 if not args:
212 212 return []
213 213 s = set(repo.changelog.ancestors(*args)) | set(args)
214 214 return [r for r in subset if r in s]
215 215
216 216 def ancestorspec(repo, subset, x, n):
217 217 """``set~n``
218 218 Changesets that are the Nth ancestor (first parents only) of a changeset in set.
219 219 """
220 220 try:
221 221 n = int(n[1])
222 222 except (TypeError, ValueError):
223 223 raise error.ParseError(_("~ expects a number"))
224 224 ps = set()
225 225 cl = repo.changelog
226 226 for r in getset(repo, subset, x):
227 227 for i in range(n):
228 228 r = cl.parentrevs(r)[0]
229 229 ps.add(r)
230 230 return [r for r in subset if r in ps]
231 231
232 232 def author(repo, subset, x):
233 233 """``author(string)``
234 234 Alias for ``user(string)``.
235 235 """
236 236 # i18n: "author" is a keyword
237 237 n = encoding.lower(getstring(x, _("author requires a string")))
238 238 return [r for r in subset if n in encoding.lower(repo[r].user())]
239 239
240 240 def bisect(repo, subset, x):
241 241 """``bisect(string)``
242 242 Changesets marked in the specified bisect status:
243 243
244 244 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
245 245 - ``goods``, ``bads`` : csets topologicaly good/bad
246 246 - ``range`` : csets taking part in the bisection
247 247 - ``pruned`` : csets that are goods, bads or skipped
248 248 - ``untested`` : csets whose fate is yet unknown
249 249 - ``ignored`` : csets ignored due to DAG topology
250 250 """
251 251 status = getstring(x, _("bisect requires a string")).lower()
252 252 return [r for r in subset if r in hbisect.get(repo, status)]
253 253
254 254 # Backward-compatibility
255 255 # - no help entry so that we do not advertise it any more
256 256 def bisected(repo, subset, x):
257 257 return bisect(repo, subset, x)
258 258
259 259 def bookmark(repo, subset, x):
260 260 """``bookmark([name])``
261 261 The named bookmark or all bookmarks.
262 262 """
263 263 # i18n: "bookmark" is a keyword
264 264 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
265 265 if args:
266 266 bm = getstring(args[0],
267 267 # i18n: "bookmark" is a keyword
268 268 _('the argument to bookmark must be a string'))
269 269 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
270 270 if not bmrev:
271 271 raise util.Abort(_("bookmark '%s' does not exist") % bm)
272 272 bmrev = repo[bmrev].rev()
273 273 return [r for r in subset if r == bmrev]
274 274 bms = set([repo[r].rev()
275 275 for r in bookmarksmod.listbookmarks(repo).values()])
276 276 return [r for r in subset if r in bms]
277 277
278 278 def branch(repo, subset, x):
279 279 """``branch(string or set)``
280 280 All changesets belonging to the given branch or the branches of the given
281 281 changesets.
282 282 """
283 283 try:
284 284 b = getstring(x, '')
285 285 if b in repo.branchmap():
286 286 return [r for r in subset if repo[r].branch() == b]
287 287 except error.ParseError:
288 288 # not a string, but another revspec, e.g. tip()
289 289 pass
290 290
291 291 s = getset(repo, range(len(repo)), x)
292 292 b = set()
293 293 for r in s:
294 294 b.add(repo[r].branch())
295 295 s = set(s)
296 296 return [r for r in subset if r in s or repo[r].branch() in b]
297 297
298 298 def checkstatus(repo, subset, pat, field):
299 299 m = None
300 300 s = []
301 301 fast = not matchmod.patkind(pat)
302 302 for r in subset:
303 303 c = repo[r]
304 304 if fast:
305 305 if pat not in c.files():
306 306 continue
307 307 else:
308 308 if not m or matchmod.patkind(pat) == 'set':
309 309 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
310 310 for f in c.files():
311 311 if m(f):
312 312 break
313 313 else:
314 314 continue
315 315 files = repo.status(c.p1().node(), c.node())[field]
316 316 if fast:
317 317 if pat in files:
318 318 s.append(r)
319 319 else:
320 320 for f in files:
321 321 if m(f):
322 322 s.append(r)
323 323 break
324 324 return s
325 325
326 326 def _children(repo, narrow, s):
327 327 cs = set()
328 328 pr = repo.changelog.parentrevs
329 329 s = set(s)
330 330 for r in xrange(len(repo)):
331 331 for p in pr(r):
332 332 if p in s:
333 333 cs.add(r)
334 334 return cs
335 335
336 336 def children(repo, subset, x):
337 337 """``children(set)``
338 338 Child changesets of changesets in set.
339 339 """
340 340 s = getset(repo, range(len(repo)), x)
341 341 cs = _children(repo, subset, s)
342 342 return [r for r in subset if r in cs]
343 343
344 344 def closed(repo, subset, x):
345 345 """``closed()``
346 346 Changeset is closed.
347 347 """
348 348 # i18n: "closed" is a keyword
349 349 getargs(x, 0, 0, _("closed takes no arguments"))
350 350 return [r for r in subset if repo[r].extra().get('close')]
351 351
352 352 def contains(repo, subset, x):
353 353 """``contains(pattern)``
354 354 Revision contains a file matching pattern. See :hg:`help patterns`
355 355 for information about file patterns.
356 356 """
357 357 # i18n: "contains" is a keyword
358 358 pat = getstring(x, _("contains requires a pattern"))
359 359 m = None
360 360 s = []
361 361 if not matchmod.patkind(pat):
362 362 for r in subset:
363 363 if pat in repo[r]:
364 364 s.append(r)
365 365 else:
366 366 for r in subset:
367 367 c = repo[r]
368 368 if not m or matchmod.patkind(pat) == 'set':
369 369 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
370 370 for f in c.manifest():
371 371 if m(f):
372 372 s.append(r)
373 373 break
374 374 return s
375 375
376 376 def date(repo, subset, x):
377 377 """``date(interval)``
378 378 Changesets within the interval, see :hg:`help dates`.
379 379 """
380 380 # i18n: "date" is a keyword
381 381 ds = getstring(x, _("date requires a string"))
382 382 dm = util.matchdate(ds)
383 383 return [r for r in subset if dm(repo[r].date()[0])]
384 384
385 385 def desc(repo, subset, x):
386 386 """``desc(string)``
387 387 Search commit message for string. The match is case-insensitive.
388 388 """
389 389 # i18n: "desc" is a keyword
390 390 ds = encoding.lower(getstring(x, _("desc requires a string")))
391 391 l = []
392 392 for r in subset:
393 393 c = repo[r]
394 394 if ds in encoding.lower(c.description()):
395 395 l.append(r)
396 396 return l
397 397
398 398 def descendants(repo, subset, x):
399 399 """``descendants(set)``
400 400 Changesets which are descendants of changesets in set.
401 401 """
402 402 args = getset(repo, range(len(repo)), x)
403 403 if not args:
404 404 return []
405 405 s = set(repo.changelog.descendants(*args)) | set(args)
406 406 return [r for r in subset if r in s]
407 407
408 408 def draft(repo, subset, x):
409 409 """``draft()``
410 410 Changeset in draft phase."""
411 411 getargs(x, 0, 0, _("draft takes no arguments"))
412 412 return [r for r in subset if repo._phaserev[r] == phases.draft]
413 413
414 414 def filelog(repo, subset, x):
415 415 """``filelog(pattern)``
416 416 Changesets connected to the specified filelog.
417 417 """
418 418
419 419 pat = getstring(x, _("filelog requires a pattern"))
420 420 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
421 421 ctx=repo[None])
422 422 s = set()
423 423
424 424 if not matchmod.patkind(pat):
425 425 for f in m.files():
426 426 fl = repo.file(f)
427 427 for fr in fl:
428 428 s.add(fl.linkrev(fr))
429 429 else:
430 430 for f in repo[None]:
431 431 if m(f):
432 432 fl = repo.file(f)
433 433 for fr in fl:
434 434 s.add(fl.linkrev(fr))
435 435
436 436 return [r for r in subset if r in s]
437 437
438 438 def first(repo, subset, x):
439 439 """``first(set, [n])``
440 440 An alias for limit().
441 441 """
442 442 return limit(repo, subset, x)
443 443
444 444 def follow(repo, subset, x):
445 445 """``follow([file])``
446 446 An alias for ``::.`` (ancestors of the working copy's first parent).
447 447 If a filename is specified, the history of the given file is followed,
448 448 including copies.
449 449 """
450 450 # i18n: "follow" is a keyword
451 451 l = getargs(x, 0, 1, _("follow takes no arguments or a filename"))
452 452 c = repo['.']
453 453 if l:
454 454 x = getstring(l[0], _("follow expected a filename"))
455 455 if x in c:
456 456 cx = c[x]
457 457 s = set(ctx.rev() for ctx in cx.ancestors())
458 458 # include the revision responsible for the most recent version
459 459 s.add(cx.linkrev())
460 460 else:
461 461 return []
462 462 else:
463 463 s = set(repo.changelog.ancestors(c.rev()))
464 464 s.add(c.rev())
465 465
466 466 return [r for r in subset if r in s]
467 467
468 468 def getall(repo, subset, x):
469 469 """``all()``
470 470 All changesets, the same as ``0:tip``.
471 471 """
472 472 # i18n: "all" is a keyword
473 473 getargs(x, 0, 0, _("all takes no arguments"))
474 474 return subset
475 475
476 476 def grep(repo, subset, x):
477 477 """``grep(regex)``
478 478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
479 479 to ensure special escape characters are handled correctly. Unlike
480 480 ``keyword(string)``, the match is case-sensitive.
481 481 """
482 482 try:
483 483 # i18n: "grep" is a keyword
484 484 gr = re.compile(getstring(x, _("grep requires a string")))
485 485 except re.error, e:
486 486 raise error.ParseError(_('invalid match pattern: %s') % e)
487 487 l = []
488 488 for r in subset:
489 489 c = repo[r]
490 490 for e in c.files() + [c.user(), c.description()]:
491 491 if gr.search(e):
492 492 l.append(r)
493 493 break
494 494 return l
495 495
496 496 def hasfile(repo, subset, x):
497 497 """``file(pattern)``
498 498 Changesets affecting files matched by pattern.
499 499 """
500 500 # i18n: "file" is a keyword
501 501 pat = getstring(x, _("file requires a pattern"))
502 502 m = None
503 503 s = []
504 504 for r in subset:
505 505 c = repo[r]
506 506 if not m or matchmod.patkind(pat) == 'set':
507 507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
508 508 for f in c.files():
509 509 if m(f):
510 510 s.append(r)
511 511 break
512 512 return s
513 513
514 514 def head(repo, subset, x):
515 515 """``head()``
516 516 Changeset is a named branch head.
517 517 """
518 518 # i18n: "head" is a keyword
519 519 getargs(x, 0, 0, _("head takes no arguments"))
520 520 hs = set()
521 521 for b, ls in repo.branchmap().iteritems():
522 522 hs.update(repo[h].rev() for h in ls)
523 523 return [r for r in subset if r in hs]
524 524
525 525 def heads(repo, subset, x):
526 526 """``heads(set)``
527 527 Members of set with no children in set.
528 528 """
529 529 s = getset(repo, subset, x)
530 530 ps = set(parents(repo, subset, x))
531 531 return [r for r in s if r not in ps]
532 532
533 533 def keyword(repo, subset, x):
534 534 """``keyword(string)``
535 535 Search commit message, user name, and names of changed files for
536 536 string. The match is case-insensitive.
537 537 """
538 538 # i18n: "keyword" is a keyword
539 539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
540 540 l = []
541 541 for r in subset:
542 542 c = repo[r]
543 543 t = " ".join(c.files() + [c.user(), c.description()])
544 544 if kw in encoding.lower(t):
545 545 l.append(r)
546 546 return l
547 547
548 548 def limit(repo, subset, x):
549 549 """``limit(set, [n])``
550 550 First n members of set, defaulting to 1.
551 551 """
552 552 # i18n: "limit" is a keyword
553 553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
554 554 try:
555 555 lim = 1
556 556 if len(l) == 2:
557 557 # i18n: "limit" is a keyword
558 558 lim = int(getstring(l[1], _("limit requires a number")))
559 559 except (TypeError, ValueError):
560 560 # i18n: "limit" is a keyword
561 561 raise error.ParseError(_("limit expects a number"))
562 562 ss = set(subset)
563 563 os = getset(repo, range(len(repo)), l[0])[:lim]
564 564 return [r for r in os if r in ss]
565 565
566 566 def last(repo, subset, x):
567 567 """``last(set, [n])``
568 568 Last n members of set, defaulting to 1.
569 569 """
570 570 # i18n: "last" is a keyword
571 571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
572 572 try:
573 573 lim = 1
574 574 if len(l) == 2:
575 575 # i18n: "last" is a keyword
576 576 lim = int(getstring(l[1], _("last requires a number")))
577 577 except (TypeError, ValueError):
578 578 # i18n: "last" is a keyword
579 579 raise error.ParseError(_("last expects a number"))
580 580 ss = set(subset)
581 581 os = getset(repo, range(len(repo)), l[0])[-lim:]
582 582 return [r for r in os if r in ss]
583 583
584 584 def maxrev(repo, subset, x):
585 585 """``max(set)``
586 586 Changeset with highest revision number in set.
587 587 """
588 588 os = getset(repo, range(len(repo)), x)
589 589 if os:
590 590 m = max(os)
591 591 if m in subset:
592 592 return [m]
593 593 return []
594 594
595 595 def merge(repo, subset, x):
596 596 """``merge()``
597 597 Changeset is a merge changeset.
598 598 """
599 599 # i18n: "merge" is a keyword
600 600 getargs(x, 0, 0, _("merge takes no arguments"))
601 601 cl = repo.changelog
602 602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
603 603
604 604 def minrev(repo, subset, x):
605 605 """``min(set)``
606 606 Changeset with lowest revision number in set.
607 607 """
608 608 os = getset(repo, range(len(repo)), x)
609 609 if os:
610 610 m = min(os)
611 611 if m in subset:
612 612 return [m]
613 613 return []
614 614
615 615 def modifies(repo, subset, x):
616 616 """``modifies(pattern)``
617 617 Changesets modifying files matched by pattern.
618 618 """
619 619 # i18n: "modifies" is a keyword
620 620 pat = getstring(x, _("modifies requires a pattern"))
621 621 return checkstatus(repo, subset, pat, 0)
622 622
623 623 def node(repo, subset, x):
624 624 """``id(string)``
625 625 Revision non-ambiguously specified by the given hex string prefix.
626 626 """
627 627 # i18n: "id" is a keyword
628 628 l = getargs(x, 1, 1, _("id requires one argument"))
629 629 # i18n: "id" is a keyword
630 630 n = getstring(l[0], _("id requires a string"))
631 631 if len(n) == 40:
632 632 rn = repo[n].rev()
633 633 else:
634 634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
635 635 return [r for r in subset if r == rn]
636 636
637 637 def outgoing(repo, subset, x):
638 638 """``outgoing([path])``
639 639 Changesets not found in the specified destination repository, or the
640 640 default push location.
641 641 """
642 642 import hg # avoid start-up nasties
643 643 # i18n: "outgoing" is a keyword
644 644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
645 645 # i18n: "outgoing" is a keyword
646 646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
647 647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
648 648 dest, branches = hg.parseurl(dest)
649 649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
650 650 if revs:
651 651 revs = [repo.lookup(rev) for rev in revs]
652 652 other = hg.peer(repo, {}, dest)
653 653 repo.ui.pushbuffer()
654 654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
655 655 repo.ui.popbuffer()
656 656 cl = repo.changelog
657 657 o = set([cl.rev(r) for r in outgoing.missing])
658 658 return [r for r in subset if r in o]
659 659
660 660 def p1(repo, subset, x):
661 661 """``p1([set])``
662 662 First parent of changesets in set, or the working directory.
663 663 """
664 664 if x is None:
665 665 p = repo[x].p1().rev()
666 666 return [r for r in subset if r == p]
667 667
668 668 ps = set()
669 669 cl = repo.changelog
670 670 for r in getset(repo, range(len(repo)), x):
671 671 ps.add(cl.parentrevs(r)[0])
672 672 return [r for r in subset if r in ps]
673 673
674 674 def p2(repo, subset, x):
675 675 """``p2([set])``
676 676 Second parent of changesets in set, or the working directory.
677 677 """
678 678 if x is None:
679 679 ps = repo[x].parents()
680 680 try:
681 681 p = ps[1].rev()
682 682 return [r for r in subset if r == p]
683 683 except IndexError:
684 684 return []
685 685
686 686 ps = set()
687 687 cl = repo.changelog
688 688 for r in getset(repo, range(len(repo)), x):
689 689 ps.add(cl.parentrevs(r)[1])
690 690 return [r for r in subset if r in ps]
691 691
692 692 def parents(repo, subset, x):
693 693 """``parents([set])``
694 694 The set of all parents for all changesets in set, or the working directory.
695 695 """
696 696 if x is None:
697 697 ps = tuple(p.rev() for p in repo[x].parents())
698 698 return [r for r in subset if r in ps]
699 699
700 700 ps = set()
701 701 cl = repo.changelog
702 702 for r in getset(repo, range(len(repo)), x):
703 703 ps.update(cl.parentrevs(r))
704 704 return [r for r in subset if r in ps]
705 705
706 706 def parentspec(repo, subset, x, n):
707 707 """``set^0``
708 708 The set.
709 709 ``set^1`` (or ``set^``), ``set^2``
710 710 First or second parent, respectively, of all changesets in set.
711 711 """
712 712 try:
713 713 n = int(n[1])
714 714 if n not in (0, 1, 2):
715 715 raise ValueError
716 716 except (TypeError, ValueError):
717 717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
718 718 ps = set()
719 719 cl = repo.changelog
720 720 for r in getset(repo, subset, x):
721 721 if n == 0:
722 722 ps.add(r)
723 723 elif n == 1:
724 724 ps.add(cl.parentrevs(r)[0])
725 725 elif n == 2:
726 726 parents = cl.parentrevs(r)
727 727 if len(parents) > 1:
728 728 ps.add(parents[1])
729 729 return [r for r in subset if r in ps]
730 730
731 731 def present(repo, subset, x):
732 732 """``present(set)``
733 733 An empty set, if any revision in set isn't found; otherwise,
734 734 all revisions in set.
735 735 """
736 736 try:
737 737 return getset(repo, subset, x)
738 738 except error.RepoLookupError:
739 739 return []
740 740
741 741 def public(repo, subset, x):
742 742 """``public()``
743 743 Changeset in public phase."""
744 744 getargs(x, 0, 0, _("public takes no arguments"))
745 745 return [r for r in subset if repo._phaserev[r] == phases.public]
746 746
747 747 def remote(repo, subset, x):
748 """``remote([id], [path])``
748 """``remote([id [,path]])``
749 749 Local revision that corresponds to the given identifier in a
750 750 remote repository, if present. Here, the '.' identifier is a
751 751 synonym for the current local branch.
752 752 """
753 753
754 754 import hg # avoid start-up nasties
755 755 # i18n: "remote" is a keyword
756 l = getargs(x, 0, 2, _("remote takes one or two arguments"))
756 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
757 757
758 758 q = '.'
759 759 if len(l) > 0:
760 760 # i18n: "remote" is a keyword
761 761 q = getstring(l[0], _("remote requires a string id"))
762 762 if q == '.':
763 763 q = repo['.'].branch()
764 764
765 765 dest = ''
766 766 if len(l) > 1:
767 767 # i18n: "remote" is a keyword
768 768 dest = getstring(l[1], _("remote requires a repository path"))
769 769 dest = repo.ui.expandpath(dest or 'default')
770 770 dest, branches = hg.parseurl(dest)
771 771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
772 772 if revs:
773 773 revs = [repo.lookup(rev) for rev in revs]
774 774 other = hg.peer(repo, {}, dest)
775 775 n = other.lookup(q)
776 776 if n in repo:
777 777 r = repo[n].rev()
778 778 if r in subset:
779 779 return [r]
780 780 return []
781 781
782 782 def removes(repo, subset, x):
783 783 """``removes(pattern)``
784 784 Changesets which remove files matching pattern.
785 785 """
786 786 # i18n: "removes" is a keyword
787 787 pat = getstring(x, _("removes requires a pattern"))
788 788 return checkstatus(repo, subset, pat, 2)
789 789
790 790 def rev(repo, subset, x):
791 791 """``rev(number)``
792 792 Revision with the given numeric identifier.
793 793 """
794 794 # i18n: "rev" is a keyword
795 795 l = getargs(x, 1, 1, _("rev requires one argument"))
796 796 try:
797 797 # i18n: "rev" is a keyword
798 798 l = int(getstring(l[0], _("rev requires a number")))
799 799 except (TypeError, ValueError):
800 800 # i18n: "rev" is a keyword
801 801 raise error.ParseError(_("rev expects a number"))
802 802 return [r for r in subset if r == l]
803 803
804 804 def reverse(repo, subset, x):
805 805 """``reverse(set)``
806 806 Reverse order of set.
807 807 """
808 808 l = getset(repo, subset, x)
809 809 l.reverse()
810 810 return l
811 811
812 812 def roots(repo, subset, x):
813 813 """``roots(set)``
814 814 Changesets with no parent changeset in set.
815 815 """
816 816 s = getset(repo, xrange(len(repo)), x)
817 817 cs = _children(repo, s, s)
818 818 return [r for r in s if r not in cs]
819 819
820 820 def secret(repo, subset, x):
821 821 """``secret()``
822 822 Changeset in secret phase."""
823 823 getargs(x, 0, 0, _("secret takes no arguments"))
824 824 return [r for r in subset if repo._phaserev[r] == phases.secret]
825 825
826 826 def sort(repo, subset, x):
827 827 """``sort(set[, [-]key...])``
828 828 Sort set by keys. The default sort order is ascending, specify a key
829 829 as ``-key`` to sort in descending order.
830 830
831 831 The keys can be:
832 832
833 833 - ``rev`` for the revision number,
834 834 - ``branch`` for the branch name,
835 835 - ``desc`` for the commit message (description),
836 836 - ``user`` for user name (``author`` can be used as an alias),
837 837 - ``date`` for the commit date
838 838 """
839 839 # i18n: "sort" is a keyword
840 840 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
841 841 keys = "rev"
842 842 if len(l) == 2:
843 843 keys = getstring(l[1], _("sort spec must be a string"))
844 844
845 845 s = l[0]
846 846 keys = keys.split()
847 847 l = []
848 848 def invert(s):
849 849 return "".join(chr(255 - ord(c)) for c in s)
850 850 for r in getset(repo, subset, s):
851 851 c = repo[r]
852 852 e = []
853 853 for k in keys:
854 854 if k == 'rev':
855 855 e.append(r)
856 856 elif k == '-rev':
857 857 e.append(-r)
858 858 elif k == 'branch':
859 859 e.append(c.branch())
860 860 elif k == '-branch':
861 861 e.append(invert(c.branch()))
862 862 elif k == 'desc':
863 863 e.append(c.description())
864 864 elif k == '-desc':
865 865 e.append(invert(c.description()))
866 866 elif k in 'user author':
867 867 e.append(c.user())
868 868 elif k in '-user -author':
869 869 e.append(invert(c.user()))
870 870 elif k == 'date':
871 871 e.append(c.date()[0])
872 872 elif k == '-date':
873 873 e.append(-c.date()[0])
874 874 else:
875 875 raise error.ParseError(_("unknown sort key %r") % k)
876 876 e.append(r)
877 877 l.append(e)
878 878 l.sort()
879 879 return [e[-1] for e in l]
880 880
881 881 def tag(repo, subset, x):
882 882 """``tag([name])``
883 883 The specified tag by name, or all tagged revisions if no name is given.
884 884 """
885 885 # i18n: "tag" is a keyword
886 886 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
887 887 cl = repo.changelog
888 888 if args:
889 889 tn = getstring(args[0],
890 890 # i18n: "tag" is a keyword
891 891 _('the argument to tag must be a string'))
892 892 if not repo.tags().get(tn, None):
893 893 raise util.Abort(_("tag '%s' does not exist") % tn)
894 894 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
895 895 else:
896 896 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
897 897 return [r for r in subset if r in s]
898 898
899 899 def tagged(repo, subset, x):
900 900 return tag(repo, subset, x)
901 901
902 902 def user(repo, subset, x):
903 903 """``user(string)``
904 904 User name contains string. The match is case-insensitive.
905 905 """
906 906 return author(repo, subset, x)
907 907
908 908 # for internal use
909 909 def _list(repo, subset, x):
910 910 s = getstring(x, "internal error")
911 911 if not s:
912 912 return []
913 913 if not isinstance(subset, set):
914 914 subset = set(subset)
915 915 ls = [repo[r].rev() for r in s.split('\0')]
916 916 return [r for r in ls if r in subset]
917 917
918 918 symbols = {
919 919 "adds": adds,
920 920 "all": getall,
921 921 "ancestor": ancestor,
922 922 "ancestors": ancestors,
923 923 "author": author,
924 924 "bisect": bisect,
925 925 "bisected": bisected,
926 926 "bookmark": bookmark,
927 927 "branch": branch,
928 928 "children": children,
929 929 "closed": closed,
930 930 "contains": contains,
931 931 "date": date,
932 932 "desc": desc,
933 933 "descendants": descendants,
934 934 "draft": draft,
935 935 "file": hasfile,
936 936 "filelog": filelog,
937 937 "first": first,
938 938 "follow": follow,
939 939 "grep": grep,
940 940 "head": head,
941 941 "heads": heads,
942 942 "id": node,
943 943 "keyword": keyword,
944 944 "last": last,
945 945 "limit": limit,
946 946 "max": maxrev,
947 947 "merge": merge,
948 948 "min": minrev,
949 949 "modifies": modifies,
950 950 "outgoing": outgoing,
951 951 "p1": p1,
952 952 "p2": p2,
953 953 "parents": parents,
954 954 "present": present,
955 955 "public": public,
956 956 "remote": remote,
957 957 "removes": removes,
958 958 "rev": rev,
959 959 "reverse": reverse,
960 960 "roots": roots,
961 961 "sort": sort,
962 962 "secret": secret,
963 963 "tag": tag,
964 964 "tagged": tagged,
965 965 "user": user,
966 966 "_list": _list,
967 967 }
968 968
969 969 methods = {
970 970 "range": rangeset,
971 971 "string": stringset,
972 972 "symbol": symbolset,
973 973 "and": andset,
974 974 "or": orset,
975 975 "not": notset,
976 976 "list": listset,
977 977 "func": func,
978 978 "ancestor": ancestorspec,
979 979 "parent": parentspec,
980 980 "parentpost": p1,
981 981 }
982 982
983 983 def optimize(x, small):
984 984 if x is None:
985 985 return 0, x
986 986
987 987 smallbonus = 1
988 988 if small:
989 989 smallbonus = .5
990 990
991 991 op = x[0]
992 992 if op == 'minus':
993 993 return optimize(('and', x[1], ('not', x[2])), small)
994 994 elif op == 'dagrange':
995 995 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
996 996 ('func', ('symbol', 'ancestors'), x[2])), small)
997 997 elif op == 'dagrangepre':
998 998 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
999 999 elif op == 'dagrangepost':
1000 1000 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1001 1001 elif op == 'rangepre':
1002 1002 return optimize(('range', ('string', '0'), x[1]), small)
1003 1003 elif op == 'rangepost':
1004 1004 return optimize(('range', x[1], ('string', 'tip')), small)
1005 1005 elif op == 'negate':
1006 1006 return optimize(('string',
1007 1007 '-' + getstring(x[1], _("can't negate that"))), small)
1008 1008 elif op in 'string symbol negate':
1009 1009 return smallbonus, x # single revisions are small
1010 1010 elif op == 'and' or op == 'dagrange':
1011 1011 wa, ta = optimize(x[1], True)
1012 1012 wb, tb = optimize(x[2], True)
1013 1013 w = min(wa, wb)
1014 1014 if wa > wb:
1015 1015 return w, (op, tb, ta)
1016 1016 return w, (op, ta, tb)
1017 1017 elif op == 'or':
1018 1018 wa, ta = optimize(x[1], False)
1019 1019 wb, tb = optimize(x[2], False)
1020 1020 if wb < wa:
1021 1021 wb, wa = wa, wb
1022 1022 return max(wa, wb), (op, ta, tb)
1023 1023 elif op == 'not':
1024 1024 o = optimize(x[1], not small)
1025 1025 return o[0], (op, o[1])
1026 1026 elif op == 'parentpost':
1027 1027 o = optimize(x[1], small)
1028 1028 return o[0], (op, o[1])
1029 1029 elif op == 'group':
1030 1030 return optimize(x[1], small)
1031 1031 elif op in 'range list parent ancestorspec':
1032 1032 if op == 'parent':
1033 1033 # x^:y means (x^) : y, not x ^ (:y)
1034 1034 post = ('parentpost', x[1])
1035 1035 if x[2][0] == 'dagrangepre':
1036 1036 return optimize(('dagrange', post, x[2][1]), small)
1037 1037 elif x[2][0] == 'rangepre':
1038 1038 return optimize(('range', post, x[2][1]), small)
1039 1039
1040 1040 wa, ta = optimize(x[1], small)
1041 1041 wb, tb = optimize(x[2], small)
1042 1042 return wa + wb, (op, ta, tb)
1043 1043 elif op == 'func':
1044 1044 f = getstring(x[1], _("not a symbol"))
1045 1045 wa, ta = optimize(x[2], small)
1046 1046 if f in ("author branch closed date desc file grep keyword "
1047 1047 "outgoing user"):
1048 1048 w = 10 # slow
1049 1049 elif f in "modifies adds removes":
1050 1050 w = 30 # slower
1051 1051 elif f == "contains":
1052 1052 w = 100 # very slow
1053 1053 elif f == "ancestor":
1054 1054 w = 1 * smallbonus
1055 1055 elif f in "reverse limit first":
1056 1056 w = 0
1057 1057 elif f in "sort":
1058 1058 w = 10 # assume most sorts look at changelog
1059 1059 else:
1060 1060 w = 1
1061 1061 return w + wa, (op, x[1], ta)
1062 1062 return 1, x
1063 1063
1064 1064 class revsetalias(object):
1065 1065 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1066 1066 args = None
1067 1067
1068 1068 def __init__(self, name, value):
1069 1069 '''Aliases like:
1070 1070
1071 1071 h = heads(default)
1072 1072 b($1) = ancestors($1) - ancestors(default)
1073 1073 '''
1074 1074 if isinstance(name, tuple): # parameter substitution
1075 1075 self.tree = name
1076 1076 self.replacement = value
1077 1077 else: # alias definition
1078 1078 m = self.funcre.search(name)
1079 1079 if m:
1080 1080 self.tree = ('func', ('symbol', m.group(1)))
1081 1081 self.args = [x.strip() for x in m.group(2).split(',')]
1082 1082 for arg in self.args:
1083 1083 value = value.replace(arg, repr(arg))
1084 1084 else:
1085 1085 self.tree = ('symbol', name)
1086 1086
1087 1087 self.replacement, pos = parse(value)
1088 1088 if pos != len(value):
1089 1089 raise error.ParseError(_('invalid token'), pos)
1090 1090
1091 1091 def process(self, tree):
1092 1092 if isinstance(tree, tuple):
1093 1093 if self.args is None:
1094 1094 if tree == self.tree:
1095 1095 return self.replacement
1096 1096 elif tree[:2] == self.tree:
1097 1097 l = getlist(tree[2])
1098 1098 if len(l) != len(self.args):
1099 1099 raise error.ParseError(
1100 1100 _('invalid number of arguments: %s') % len(l))
1101 1101 result = self.replacement
1102 1102 for a, v in zip(self.args, l):
1103 1103 valalias = revsetalias(('string', a), v)
1104 1104 result = valalias.process(result)
1105 1105 return result
1106 1106 return tuple(map(self.process, tree))
1107 1107 return tree
1108 1108
1109 1109 def findaliases(ui, tree):
1110 1110 for k, v in ui.configitems('revsetalias'):
1111 1111 alias = revsetalias(k, v)
1112 1112 tree = alias.process(tree)
1113 1113 return tree
1114 1114
1115 1115 parse = parser.parser(tokenize, elements).parse
1116 1116
1117 1117 def match(ui, spec):
1118 1118 if not spec:
1119 1119 raise error.ParseError(_("empty query"))
1120 1120 tree, pos = parse(spec)
1121 1121 if (pos != len(spec)):
1122 1122 raise error.ParseError(_("invalid token"), pos)
1123 1123 if ui:
1124 1124 tree = findaliases(ui, tree)
1125 1125 weight, tree = optimize(tree, True)
1126 1126 def mfunc(repo, subset):
1127 1127 return getset(repo, subset, tree)
1128 1128 return mfunc
1129 1129
1130 1130 def formatspec(expr, *args):
1131 1131 '''
1132 1132 This is a convenience function for using revsets internally, and
1133 1133 escapes arguments appropriately. Aliases are intentionally ignored
1134 1134 so that intended expression behavior isn't accidentally subverted.
1135 1135
1136 1136 Supported arguments:
1137 1137
1138 1138 %r = revset expression, parenthesized
1139 1139 %d = int(arg), no quoting
1140 1140 %s = string(arg), escaped and single-quoted
1141 1141 %b = arg.branch(), escaped and single-quoted
1142 1142 %n = hex(arg), single-quoted
1143 1143 %% = a literal '%'
1144 1144
1145 1145 Prefixing the type with 'l' specifies a parenthesized list of that type.
1146 1146
1147 1147 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1148 1148 '(10 or 11):: and ((this()) or (that()))'
1149 1149 >>> formatspec('%d:: and not %d::', 10, 20)
1150 1150 '10:: and not 20::'
1151 1151 >>> formatspec('%ld or %ld', [], [1])
1152 1152 "_list('') or 1"
1153 1153 >>> formatspec('keyword(%s)', 'foo\\xe9')
1154 1154 "keyword('foo\\\\xe9')"
1155 1155 >>> b = lambda: 'default'
1156 1156 >>> b.branch = b
1157 1157 >>> formatspec('branch(%b)', b)
1158 1158 "branch('default')"
1159 1159 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1160 1160 "root(_list('a\\x00b\\x00c\\x00d'))"
1161 1161 '''
1162 1162
1163 1163 def quote(s):
1164 1164 return repr(str(s))
1165 1165
1166 1166 def argtype(c, arg):
1167 1167 if c == 'd':
1168 1168 return str(int(arg))
1169 1169 elif c == 's':
1170 1170 return quote(arg)
1171 1171 elif c == 'r':
1172 1172 parse(arg) # make sure syntax errors are confined
1173 1173 return '(%s)' % arg
1174 1174 elif c == 'n':
1175 1175 return quote(nodemod.hex(arg))
1176 1176 elif c == 'b':
1177 1177 return quote(arg.branch())
1178 1178
1179 1179 def listexp(s, t):
1180 1180 l = len(s)
1181 1181 if l == 0:
1182 1182 return "_list('')"
1183 1183 elif l == 1:
1184 1184 return argtype(t, s[0])
1185 1185 elif t == 'd':
1186 1186 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1187 1187 elif t == 's':
1188 1188 return "_list('%s')" % "\0".join(s)
1189 1189 elif t == 'n':
1190 1190 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1191 1191 elif t == 'b':
1192 1192 return "_list('%s')" % "\0".join(a.branch() for a in s)
1193 1193
1194 1194 m = l // 2
1195 1195 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1196 1196
1197 1197 ret = ''
1198 1198 pos = 0
1199 1199 arg = 0
1200 1200 while pos < len(expr):
1201 1201 c = expr[pos]
1202 1202 if c == '%':
1203 1203 pos += 1
1204 1204 d = expr[pos]
1205 1205 if d == '%':
1206 1206 ret += d
1207 1207 elif d in 'dsnbr':
1208 1208 ret += argtype(d, args[arg])
1209 1209 arg += 1
1210 1210 elif d == 'l':
1211 1211 # a list of some type
1212 1212 pos += 1
1213 1213 d = expr[pos]
1214 1214 ret += listexp(list(args[arg]), d)
1215 1215 arg += 1
1216 1216 else:
1217 1217 raise util.Abort('unexpected revspec format character %s' % d)
1218 1218 else:
1219 1219 ret += c
1220 1220 pos += 1
1221 1221
1222 1222 return ret
1223 1223
1224 1224 # tell hggettext to extract docstrings from these functions:
1225 1225 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now