##// END OF EJS Templates
revsets: let parents() return parents of working dir...
Kevin Bullock -
r12929:515c2786 default
parent child Browse files
Show More
@@ -1,807 +1,811 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
10 10 import match as matchmod
11 11 from i18n import _, gettext
12 12
13 13 elements = {
14 14 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
15 15 "-": (5, ("negate", 19), ("minus", 5)),
16 16 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
17 17 ("dagrangepost", 17)),
18 18 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
19 19 ("dagrangepost", 17)),
20 20 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
21 21 "not": (10, ("not", 10)),
22 22 "!": (10, ("not", 10)),
23 23 "and": (5, None, ("and", 5)),
24 24 "&": (5, None, ("and", 5)),
25 25 "or": (4, None, ("or", 4)),
26 26 "|": (4, None, ("or", 4)),
27 27 "+": (4, None, ("or", 4)),
28 28 ",": (2, None, ("list", 2)),
29 29 ")": (0, None, None),
30 30 "symbol": (0, ("symbol",), None),
31 31 "string": (0, ("string",), None),
32 32 "end": (0, None, None),
33 33 }
34 34
35 35 keywords = set(['and', 'or', 'not'])
36 36
37 37 def tokenize(program):
38 38 pos, l = 0, len(program)
39 39 while pos < l:
40 40 c = program[pos]
41 41 if c.isspace(): # skip inter-token whitespace
42 42 pass
43 43 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
44 44 yield ('::', None, pos)
45 45 pos += 1 # skip ahead
46 46 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
47 47 yield ('..', None, pos)
48 48 pos += 1 # skip ahead
49 49 elif c in "():,-|&+!": # handle simple operators
50 50 yield (c, None, pos)
51 51 elif (c in '"\'' or c == 'r' and
52 52 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
53 53 if c == 'r':
54 54 pos += 1
55 55 c = program[pos]
56 56 decode = lambda x: x
57 57 else:
58 58 decode = lambda x: x.decode('string-escape')
59 59 pos += 1
60 60 s = pos
61 61 while pos < l: # find closing quote
62 62 d = program[pos]
63 63 if d == '\\': # skip over escaped characters
64 64 pos += 2
65 65 continue
66 66 if d == c:
67 67 yield ('string', decode(program[s:pos]), s)
68 68 break
69 69 pos += 1
70 70 else:
71 71 raise error.ParseError(_("unterminated string"), s)
72 72 elif c.isalnum() or c in '._' or ord(c) > 127: # gather up a symbol/keyword
73 73 s = pos
74 74 pos += 1
75 75 while pos < l: # find end of symbol
76 76 d = program[pos]
77 77 if not (d.isalnum() or d in "._" or ord(d) > 127):
78 78 break
79 79 if d == '.' and program[pos - 1] == '.': # special case for ..
80 80 pos -= 1
81 81 break
82 82 pos += 1
83 83 sym = program[s:pos]
84 84 if sym in keywords: # operator keywords
85 85 yield (sym, None, s)
86 86 else:
87 87 yield ('symbol', sym, s)
88 88 pos -= 1
89 89 else:
90 90 raise error.ParseError(_("syntax error"), pos)
91 91 pos += 1
92 92 yield ('end', None, pos)
93 93
94 94 # helpers
95 95
96 96 def getstring(x, err):
97 97 if x and (x[0] == 'string' or x[0] == 'symbol'):
98 98 return x[1]
99 99 raise error.ParseError(err)
100 100
101 101 def getlist(x):
102 102 if not x:
103 103 return []
104 104 if x[0] == 'list':
105 105 return getlist(x[1]) + [x[2]]
106 106 return [x]
107 107
108 108 def getargs(x, min, max, err):
109 109 l = getlist(x)
110 110 if len(l) < min or len(l) > max:
111 111 raise error.ParseError(err)
112 112 return l
113 113
114 114 def getset(repo, subset, x):
115 115 if not x:
116 116 raise error.ParseError(_("missing argument"))
117 117 return methods[x[0]](repo, subset, *x[1:])
118 118
119 119 # operator methods
120 120
121 121 def stringset(repo, subset, x):
122 122 x = repo[x].rev()
123 123 if x == -1 and len(subset) == len(repo):
124 124 return [-1]
125 125 if x in subset:
126 126 return [x]
127 127 return []
128 128
129 129 def symbolset(repo, subset, x):
130 130 if x in symbols:
131 131 raise error.ParseError(_("can't use %s here") % x)
132 132 return stringset(repo, subset, x)
133 133
134 134 def rangeset(repo, subset, x, y):
135 135 m = getset(repo, subset, x)
136 136 if not m:
137 137 m = getset(repo, range(len(repo)), x)
138 138
139 139 n = getset(repo, subset, y)
140 140 if not n:
141 141 n = getset(repo, range(len(repo)), y)
142 142
143 143 if not m or not n:
144 144 return []
145 145 m, n = m[0], n[-1]
146 146
147 147 if m < n:
148 148 r = range(m, n + 1)
149 149 else:
150 150 r = range(m, n - 1, -1)
151 151 s = set(subset)
152 152 return [x for x in r if x in s]
153 153
154 154 def andset(repo, subset, x, y):
155 155 return getset(repo, getset(repo, subset, x), y)
156 156
157 157 def orset(repo, subset, x, y):
158 158 s = set(getset(repo, subset, x))
159 159 s |= set(getset(repo, [r for r in subset if r not in s], y))
160 160 return [r for r in subset if r in s]
161 161
162 162 def notset(repo, subset, x):
163 163 s = set(getset(repo, subset, x))
164 164 return [r for r in subset if r not in s]
165 165
166 166 def listset(repo, subset, a, b):
167 167 raise error.ParseError(_("can't use a list in this context"))
168 168
169 169 def func(repo, subset, a, b):
170 170 if a[0] == 'symbol' and a[1] in symbols:
171 171 return symbols[a[1]](repo, subset, b)
172 172 raise error.ParseError(_("not a function: %s") % a[1])
173 173
174 174 # functions
175 175
176 176 def node(repo, subset, x):
177 177 """``id(string)``
178 178 Revision non-ambiguously specified by the given hex string prefix.
179 179 """
180 180 # i18n: "id" is a keyword
181 181 l = getargs(x, 1, 1, _("id requires one argument"))
182 182 # i18n: "id" is a keyword
183 183 n = getstring(l[0], _("id requires a string"))
184 184 if len(n) == 40:
185 185 rn = repo[n].rev()
186 186 else:
187 187 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
188 188 return [r for r in subset if r == rn]
189 189
190 190 def rev(repo, subset, x):
191 191 """``rev(number)``
192 192 Revision with the given numeric identifier.
193 193 """
194 194 # i18n: "rev" is a keyword
195 195 l = getargs(x, 1, 1, _("rev requires one argument"))
196 196 try:
197 197 # i18n: "rev" is a keyword
198 198 l = int(getstring(l[0], _("rev requires a number")))
199 199 except ValueError:
200 200 # i18n: "rev" is a keyword
201 201 raise error.ParseError(_("rev expects a number"))
202 202 return [r for r in subset if r == l]
203 203
204 204 def p1(repo, subset, x):
205 205 """``p1([set])``
206 206 First parent of changesets in set, or the working directory.
207 207 """
208 208 if x is None:
209 209 return [repo[x].parents()[0].rev()]
210 210
211 211 ps = set()
212 212 cl = repo.changelog
213 213 for r in getset(repo, range(len(repo)), x):
214 214 ps.add(cl.parentrevs(r)[0])
215 215 return [r for r in subset if r in ps]
216 216
217 217 def p2(repo, subset, x):
218 218 """``p2([set])``
219 219 Second parent of changesets in set, or the working directory.
220 220 """
221 221 if x is None:
222 222 ps = repo[x].parents()
223 223 try:
224 224 return [ps[1].rev()]
225 225 except IndexError:
226 226 return []
227 227
228 228 ps = set()
229 229 cl = repo.changelog
230 230 for r in getset(repo, range(len(repo)), x):
231 231 ps.add(cl.parentrevs(r)[1])
232 232 return [r for r in subset if r in ps]
233 233
234 234 def parents(repo, subset, x):
235 """``parents(set)``
236 The set of all parents for all changesets in set.
235 """``parents([set])``
236 The set of all parents for all changesets in set, or the working directory.
237 237 """
238 repo.ui.debug(repr(x), '\n')
239 if x is None:
240 return [r.rev() for r in repo[x].parents()]
241
238 242 ps = set()
239 243 cl = repo.changelog
240 244 for r in getset(repo, range(len(repo)), x):
241 245 ps.update(cl.parentrevs(r))
242 246 return [r for r in subset if r in ps]
243 247
244 248 def maxrev(repo, subset, x):
245 249 """``max(set)``
246 250 Changeset with highest revision number in set.
247 251 """
248 252 s = getset(repo, subset, x)
249 253 if s:
250 254 m = max(s)
251 255 if m in subset:
252 256 return [m]
253 257 return []
254 258
255 259 def minrev(repo, subset, x):
256 260 """``min(set)``
257 261 Changeset with lowest revision number in set.
258 262 """
259 263 s = getset(repo, subset, x)
260 264 if s:
261 265 m = min(s)
262 266 if m in subset:
263 267 return [m]
264 268 return []
265 269
266 270 def limit(repo, subset, x):
267 271 """``limit(set, n)``
268 272 First n members of set.
269 273 """
270 274 # i18n: "limit" is a keyword
271 275 l = getargs(x, 2, 2, _("limit requires two arguments"))
272 276 try:
273 277 # i18n: "limit" is a keyword
274 278 lim = int(getstring(l[1], _("limit requires a number")))
275 279 except ValueError:
276 280 # i18n: "limit" is a keyword
277 281 raise error.ParseError(_("limit expects a number"))
278 282 return getset(repo, subset, l[0])[:lim]
279 283
280 284 def children(repo, subset, x):
281 285 """``children(set)``
282 286 Child changesets of changesets in set.
283 287 """
284 288 cs = set()
285 289 cl = repo.changelog
286 290 s = set(getset(repo, range(len(repo)), x))
287 291 for r in xrange(0, len(repo)):
288 292 for p in cl.parentrevs(r):
289 293 if p in s:
290 294 cs.add(r)
291 295 return [r for r in subset if r in cs]
292 296
293 297 def branch(repo, subset, x):
294 298 """``branch(set)``
295 299 All changesets belonging to the branches of changesets in set.
296 300 """
297 301 s = getset(repo, range(len(repo)), x)
298 302 b = set()
299 303 for r in s:
300 304 b.add(repo[r].branch())
301 305 s = set(s)
302 306 return [r for r in subset if r in s or repo[r].branch() in b]
303 307
304 308 def ancestor(repo, subset, x):
305 309 """``ancestor(single, single)``
306 310 Greatest common ancestor of the two changesets.
307 311 """
308 312 # i18n: "ancestor" is a keyword
309 313 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
310 314 r = range(len(repo))
311 315 a = getset(repo, r, l[0])
312 316 b = getset(repo, r, l[1])
313 317 if len(a) != 1 or len(b) != 1:
314 318 # i18n: "ancestor" is a keyword
315 319 raise error.ParseError(_("ancestor arguments must be single revisions"))
316 320 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
317 321
318 322 return [r for r in an if r in subset]
319 323
320 324 def ancestors(repo, subset, x):
321 325 """``ancestors(set)``
322 326 Changesets that are ancestors of a changeset in set.
323 327 """
324 328 args = getset(repo, range(len(repo)), x)
325 329 if not args:
326 330 return []
327 331 s = set(repo.changelog.ancestors(*args)) | set(args)
328 332 return [r for r in subset if r in s]
329 333
330 334 def descendants(repo, subset, x):
331 335 """``descendants(set)``
332 336 Changesets which are descendants of changesets in set.
333 337 """
334 338 args = getset(repo, range(len(repo)), x)
335 339 if not args:
336 340 return []
337 341 s = set(repo.changelog.descendants(*args)) | set(args)
338 342 return [r for r in subset if r in s]
339 343
340 344 def follow(repo, subset, x):
341 345 """``follow()``
342 346 An alias for ``::.`` (ancestors of the working copy's first parent).
343 347 """
344 348 # i18n: "follow" is a keyword
345 349 getargs(x, 0, 0, _("follow takes no arguments"))
346 350 p = repo['.'].rev()
347 351 s = set(repo.changelog.ancestors(p)) | set([p])
348 352 return [r for r in subset if r in s]
349 353
350 354 def date(repo, subset, x):
351 355 """``date(interval)``
352 356 Changesets within the interval, see :hg:`help dates`.
353 357 """
354 358 # i18n: "date" is a keyword
355 359 ds = getstring(x, _("date requires a string"))
356 360 dm = util.matchdate(ds)
357 361 return [r for r in subset if dm(repo[r].date()[0])]
358 362
359 363 def keyword(repo, subset, x):
360 364 """``keyword(string)``
361 365 Search commit message, user name, and names of changed files for
362 366 string.
363 367 """
364 368 # i18n: "keyword" is a keyword
365 369 kw = getstring(x, _("keyword requires a string")).lower()
366 370 l = []
367 371 for r in subset:
368 372 c = repo[r]
369 373 t = " ".join(c.files() + [c.user(), c.description()])
370 374 if kw in t.lower():
371 375 l.append(r)
372 376 return l
373 377
374 378 def grep(repo, subset, x):
375 379 """``grep(regex)``
376 380 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
377 381 to ensure special escape characters are handled correctly.
378 382 """
379 383 try:
380 384 # i18n: "grep" is a keyword
381 385 gr = re.compile(getstring(x, _("grep requires a string")))
382 386 except re.error, e:
383 387 raise error.ParseError(_('invalid match pattern: %s') % e)
384 388 l = []
385 389 for r in subset:
386 390 c = repo[r]
387 391 for e in c.files() + [c.user(), c.description()]:
388 392 if gr.search(e):
389 393 l.append(r)
390 394 continue
391 395 return l
392 396
393 397 def author(repo, subset, x):
394 398 """``author(string)``
395 399 Alias for ``user(string)``.
396 400 """
397 401 # i18n: "author" is a keyword
398 402 n = getstring(x, _("author requires a string")).lower()
399 403 return [r for r in subset if n in repo[r].user().lower()]
400 404
401 405 def user(repo, subset, x):
402 406 """``user(string)``
403 407 User name is string.
404 408 """
405 409 return author(repo, subset, x)
406 410
407 411 def hasfile(repo, subset, x):
408 412 """``file(pattern)``
409 413 Changesets affecting files matched by pattern.
410 414 """
411 415 # i18n: "file" is a keyword
412 416 pat = getstring(x, _("file requires a pattern"))
413 417 m = matchmod.match(repo.root, repo.getcwd(), [pat])
414 418 s = []
415 419 for r in subset:
416 420 for f in repo[r].files():
417 421 if m(f):
418 422 s.append(r)
419 423 continue
420 424 return s
421 425
422 426 def contains(repo, subset, x):
423 427 """``contains(pattern)``
424 428 Revision contains pattern.
425 429 """
426 430 # i18n: "contains" is a keyword
427 431 pat = getstring(x, _("contains requires a pattern"))
428 432 m = matchmod.match(repo.root, repo.getcwd(), [pat])
429 433 s = []
430 434 if m.files() == [pat]:
431 435 for r in subset:
432 436 if pat in repo[r]:
433 437 s.append(r)
434 438 continue
435 439 else:
436 440 for r in subset:
437 441 for f in repo[r].manifest():
438 442 if m(f):
439 443 s.append(r)
440 444 continue
441 445 return s
442 446
443 447 def checkstatus(repo, subset, pat, field):
444 448 m = matchmod.match(repo.root, repo.getcwd(), [pat])
445 449 s = []
446 450 fast = (m.files() == [pat])
447 451 for r in subset:
448 452 c = repo[r]
449 453 if fast:
450 454 if pat not in c.files():
451 455 continue
452 456 else:
453 457 for f in c.files():
454 458 if m(f):
455 459 break
456 460 else:
457 461 continue
458 462 files = repo.status(c.p1().node(), c.node())[field]
459 463 if fast:
460 464 if pat in files:
461 465 s.append(r)
462 466 continue
463 467 else:
464 468 for f in files:
465 469 if m(f):
466 470 s.append(r)
467 471 continue
468 472 return s
469 473
470 474 def modifies(repo, subset, x):
471 475 """``modifies(pattern)``
472 476 Changesets modifying files matched by pattern.
473 477 """
474 478 # i18n: "modifies" is a keyword
475 479 pat = getstring(x, _("modifies requires a pattern"))
476 480 return checkstatus(repo, subset, pat, 0)
477 481
478 482 def adds(repo, subset, x):
479 483 """``adds(pattern)``
480 484 Changesets that add a file matching pattern.
481 485 """
482 486 # i18n: "adds" is a keyword
483 487 pat = getstring(x, _("adds requires a pattern"))
484 488 return checkstatus(repo, subset, pat, 1)
485 489
486 490 def removes(repo, subset, x):
487 491 """``removes(pattern)``
488 492 Changesets which remove files matching pattern.
489 493 """
490 494 # i18n: "removes" is a keyword
491 495 pat = getstring(x, _("removes requires a pattern"))
492 496 return checkstatus(repo, subset, pat, 2)
493 497
494 498 def merge(repo, subset, x):
495 499 """``merge()``
496 500 Changeset is a merge changeset.
497 501 """
498 502 # i18n: "merge" is a keyword
499 503 getargs(x, 0, 0, _("merge takes no arguments"))
500 504 cl = repo.changelog
501 505 return [r for r in subset if cl.parentrevs(r)[1] != -1]
502 506
503 507 def closed(repo, subset, x):
504 508 """``closed()``
505 509 Changeset is closed.
506 510 """
507 511 # i18n: "closed" is a keyword
508 512 getargs(x, 0, 0, _("closed takes no arguments"))
509 513 return [r for r in subset if repo[r].extra().get('close')]
510 514
511 515 def head(repo, subset, x):
512 516 """``head()``
513 517 Changeset is a named branch head.
514 518 """
515 519 # i18n: "head" is a keyword
516 520 getargs(x, 0, 0, _("head takes no arguments"))
517 521 hs = set()
518 522 for b, ls in repo.branchmap().iteritems():
519 523 hs.update(repo[h].rev() for h in ls)
520 524 return [r for r in subset if r in hs]
521 525
522 526 def reverse(repo, subset, x):
523 527 """``reverse(set)``
524 528 Reverse order of set.
525 529 """
526 530 l = getset(repo, subset, x)
527 531 l.reverse()
528 532 return l
529 533
530 534 def present(repo, subset, x):
531 535 """``present(set)``
532 536 An empty set, if any revision in set isn't found; otherwise,
533 537 all revisions in set.
534 538 """
535 539 try:
536 540 return getset(repo, subset, x)
537 541 except error.RepoLookupError:
538 542 return []
539 543
540 544 def sort(repo, subset, x):
541 545 """``sort(set[, [-]key...])``
542 546 Sort set by keys. The default sort order is ascending, specify a key
543 547 as ``-key`` to sort in descending order.
544 548
545 549 The keys can be:
546 550
547 551 - ``rev`` for the revision number,
548 552 - ``branch`` for the branch name,
549 553 - ``desc`` for the commit message (description),
550 554 - ``user`` for user name (``author`` can be used as an alias),
551 555 - ``date`` for the commit date
552 556 """
553 557 # i18n: "sort" is a keyword
554 558 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
555 559 keys = "rev"
556 560 if len(l) == 2:
557 561 keys = getstring(l[1], _("sort spec must be a string"))
558 562
559 563 s = l[0]
560 564 keys = keys.split()
561 565 l = []
562 566 def invert(s):
563 567 return "".join(chr(255 - ord(c)) for c in s)
564 568 for r in getset(repo, subset, s):
565 569 c = repo[r]
566 570 e = []
567 571 for k in keys:
568 572 if k == 'rev':
569 573 e.append(r)
570 574 elif k == '-rev':
571 575 e.append(-r)
572 576 elif k == 'branch':
573 577 e.append(c.branch())
574 578 elif k == '-branch':
575 579 e.append(invert(c.branch()))
576 580 elif k == 'desc':
577 581 e.append(c.description())
578 582 elif k == '-desc':
579 583 e.append(invert(c.description()))
580 584 elif k in 'user author':
581 585 e.append(c.user())
582 586 elif k in '-user -author':
583 587 e.append(invert(c.user()))
584 588 elif k == 'date':
585 589 e.append(c.date()[0])
586 590 elif k == '-date':
587 591 e.append(-c.date()[0])
588 592 else:
589 593 raise error.ParseError(_("unknown sort key %r") % k)
590 594 e.append(r)
591 595 l.append(e)
592 596 l.sort()
593 597 return [e[-1] for e in l]
594 598
595 599 def getall(repo, subset, x):
596 600 """``all()``
597 601 All changesets, the same as ``0:tip``.
598 602 """
599 603 # i18n: "all" is a keyword
600 604 getargs(x, 0, 0, _("all takes no arguments"))
601 605 return subset
602 606
603 607 def heads(repo, subset, x):
604 608 """``heads(set)``
605 609 Members of set with no children in set.
606 610 """
607 611 s = getset(repo, subset, x)
608 612 ps = set(parents(repo, subset, x))
609 613 return [r for r in s if r not in ps]
610 614
611 615 def roots(repo, subset, x):
612 616 """``roots(set)``
613 617 Changesets with no parent changeset in set.
614 618 """
615 619 s = getset(repo, subset, x)
616 620 cs = set(children(repo, subset, x))
617 621 return [r for r in s if r not in cs]
618 622
619 623 def outgoing(repo, subset, x):
620 624 """``outgoing([path])``
621 625 Changesets not found in the specified destination repository, or the
622 626 default push location.
623 627 """
624 628 import hg # avoid start-up nasties
625 629 # i18n: "outgoing" is a keyword
626 630 l = getargs(x, 0, 1, _("outgoing requires a repository path"))
627 631 # i18n: "outgoing" is a keyword
628 632 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
629 633 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
630 634 dest, branches = hg.parseurl(dest)
631 635 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
632 636 if revs:
633 637 revs = [repo.lookup(rev) for rev in revs]
634 638 other = hg.repository(hg.remoteui(repo, {}), dest)
635 639 repo.ui.pushbuffer()
636 640 o = discovery.findoutgoing(repo, other)
637 641 repo.ui.popbuffer()
638 642 cl = repo.changelog
639 643 o = set([cl.rev(r) for r in repo.changelog.nodesbetween(o, revs)[0]])
640 644 return [r for r in subset if r in o]
641 645
642 646 def tag(repo, subset, x):
643 647 """``tag(name)``
644 648 The specified tag by name, or all tagged revisions if no name is given.
645 649 """
646 650 # i18n: "tag" is a keyword
647 651 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
648 652 cl = repo.changelog
649 653 if args:
650 654 tn = getstring(args[0],
651 655 # i18n: "tag" is a keyword
652 656 _('the argument to tag must be a string'))
653 657 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
654 658 else:
655 659 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
656 660 return [r for r in subset if r in s]
657 661
658 662 def tagged(repo, subset, x):
659 663 return tag(repo, subset, x)
660 664
661 665 symbols = {
662 666 "adds": adds,
663 667 "all": getall,
664 668 "ancestor": ancestor,
665 669 "ancestors": ancestors,
666 670 "author": author,
667 671 "branch": branch,
668 672 "children": children,
669 673 "closed": closed,
670 674 "contains": contains,
671 675 "date": date,
672 676 "descendants": descendants,
673 677 "file": hasfile,
674 678 "follow": follow,
675 679 "grep": grep,
676 680 "head": head,
677 681 "heads": heads,
678 682 "keyword": keyword,
679 683 "limit": limit,
680 684 "max": maxrev,
681 685 "min": minrev,
682 686 "merge": merge,
683 687 "modifies": modifies,
684 688 "id": node,
685 689 "outgoing": outgoing,
686 690 "p1": p1,
687 691 "p2": p2,
688 692 "parents": parents,
689 693 "present": present,
690 694 "removes": removes,
691 695 "reverse": reverse,
692 696 "rev": rev,
693 697 "roots": roots,
694 698 "sort": sort,
695 699 "tag": tag,
696 700 "tagged": tagged,
697 701 "user": user,
698 702 }
699 703
700 704 methods = {
701 705 "range": rangeset,
702 706 "string": stringset,
703 707 "symbol": symbolset,
704 708 "and": andset,
705 709 "or": orset,
706 710 "not": notset,
707 711 "list": listset,
708 712 "func": func,
709 713 }
710 714
711 715 def optimize(x, small):
712 716 if x == None:
713 717 return 0, x
714 718
715 719 smallbonus = 1
716 720 if small:
717 721 smallbonus = .5
718 722
719 723 op = x[0]
720 724 if op == 'minus':
721 725 return optimize(('and', x[1], ('not', x[2])), small)
722 726 elif op == 'dagrange':
723 727 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
724 728 ('func', ('symbol', 'ancestors'), x[2])), small)
725 729 elif op == 'dagrangepre':
726 730 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
727 731 elif op == 'dagrangepost':
728 732 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
729 733 elif op == 'rangepre':
730 734 return optimize(('range', ('string', '0'), x[1]), small)
731 735 elif op == 'rangepost':
732 736 return optimize(('range', x[1], ('string', 'tip')), small)
733 737 elif op == 'negate':
734 738 return optimize(('string',
735 739 '-' + getstring(x[1], _("can't negate that"))), small)
736 740 elif op in 'string symbol negate':
737 741 return smallbonus, x # single revisions are small
738 742 elif op == 'and' or op == 'dagrange':
739 743 wa, ta = optimize(x[1], True)
740 744 wb, tb = optimize(x[2], True)
741 745 w = min(wa, wb)
742 746 if wa > wb:
743 747 return w, (op, tb, ta)
744 748 return w, (op, ta, tb)
745 749 elif op == 'or':
746 750 wa, ta = optimize(x[1], False)
747 751 wb, tb = optimize(x[2], False)
748 752 if wb < wa:
749 753 wb, wa = wa, wb
750 754 return max(wa, wb), (op, ta, tb)
751 755 elif op == 'not':
752 756 o = optimize(x[1], not small)
753 757 return o[0], (op, o[1])
754 758 elif op == 'group':
755 759 return optimize(x[1], small)
756 760 elif op in 'range list':
757 761 wa, ta = optimize(x[1], small)
758 762 wb, tb = optimize(x[2], small)
759 763 return wa + wb, (op, ta, tb)
760 764 elif op == 'func':
761 765 f = getstring(x[1], _("not a symbol"))
762 766 wa, ta = optimize(x[2], small)
763 767 if f in "grep date user author keyword branch file outgoing":
764 768 w = 10 # slow
765 769 elif f in "modifies adds removes":
766 770 w = 30 # slower
767 771 elif f == "contains":
768 772 w = 100 # very slow
769 773 elif f == "ancestor":
770 774 w = 1 * smallbonus
771 775 elif f == "reverse limit":
772 776 w = 0
773 777 elif f in "sort":
774 778 w = 10 # assume most sorts look at changelog
775 779 else:
776 780 w = 1
777 781 return w + wa, (op, x[1], ta)
778 782 return 1, x
779 783
780 784 parse = parser.parser(tokenize, elements).parse
781 785
782 786 def match(spec):
783 787 if not spec:
784 788 raise error.ParseError(_("empty query"))
785 789 tree = parse(spec)
786 790 weight, tree = optimize(tree, True)
787 791 def mfunc(repo, subset):
788 792 return getset(repo, subset, tree)
789 793 return mfunc
790 794
791 795 def makedoc(topic, doc):
792 796 """Generate and include predicates help in revsets topic."""
793 797 predicates = []
794 798 for name in sorted(symbols):
795 799 text = symbols[name].__doc__
796 800 if not text:
797 801 continue
798 802 text = gettext(text.rstrip())
799 803 lines = text.splitlines()
800 804 lines[1:] = [(' ' + l.strip()) for l in lines[1:]]
801 805 predicates.append('\n'.join(lines))
802 806 predicates = '\n\n'.join(predicates)
803 807 doc = doc.replace('.. predicatesmarker', predicates)
804 808 return doc
805 809
806 810 # tell hggettext to extract docstrings from these functions:
807 811 i18nfunctions = symbols.values()
@@ -1,42 +1,48 b''
1 1 $ HGENCODING=utf-8
2 2 $ export HGENCODING
3 3
4 4 $ try() {
5 5 > hg debugrevspec --debug $@
6 6 > }
7 7
8 8 $ log() {
9 9 > hg log --template '{rev}\n' -r "$1"
10 10 > }
11 11
12 12 $ hg init repo
13 13 $ cd repo
14 14
15 15 $ try 'p1()'
16 16 ('func', ('symbol', 'p1'), None)
17 17 -1
18 18 $ try 'p2()'
19 19 ('func', ('symbol', 'p2'), None)
20 20
21 21 null revision
22 22 $ log 'p1()'
23 23 $ log 'p2()'
24 $ log 'parents()'
24 25
25 26 working dir with a single parent
26 27 $ echo a > a
27 28 $ hg ci -Aqm0
28 29 $ log 'p1()'
29 30 0
30 31 $ log 'p2()'
32 $ log 'parents()'
33 0
31 34
32 35 merge in progress
33 36 $ echo b > b
34 37 $ hg ci -Aqm1
35 38 $ hg up -q 0
36 39 $ echo c > c
37 40 $ hg ci -Aqm2
38 41 $ hg merge -q
39 42 $ log 'p1()'
40 43 2
41 44 $ log 'p2()'
42 45 1
46 $ log 'parents()'
47 2
48 1
General Comments 0
You need to be logged in to leave comments. Login now