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