##// END OF EJS Templates
revset: include the correct first ancestor change for follow(file)...
Matt Mackall -
r15966:610c4434 stable
parent child Browse files
Show More
@@ -1,1222 +1,1225
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 p = repo['.'].rev()
452 c = repo['.']
453 453 if l:
454 454 x = getstring(l[0], _("follow expected a filename"))
455 if x in repo['.']:
456 s = set(ctx.rev() for ctx in repo['.'][x].ancestors())
455 if x in c:
456 cx = c[x]
457 s = set(ctx.rev() for ctx in cx.ancestors())
458 # include the revision responsible for the most recent version
459 s.add(cx.linkrev())
457 460 else:
458 461 return []
459 462 else:
460 s = set(repo.changelog.ancestors(p))
463 s = set(repo.changelog.ancestors(c.rev()))
464 s.add(c.rev())
461 465
462 s |= set([p])
463 466 return [r for r in subset if r in s]
464 467
465 468 def getall(repo, subset, x):
466 469 """``all()``
467 470 All changesets, the same as ``0:tip``.
468 471 """
469 472 # i18n: "all" is a keyword
470 473 getargs(x, 0, 0, _("all takes no arguments"))
471 474 return subset
472 475
473 476 def grep(repo, subset, x):
474 477 """``grep(regex)``
475 478 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
476 479 to ensure special escape characters are handled correctly. Unlike
477 480 ``keyword(string)``, the match is case-sensitive.
478 481 """
479 482 try:
480 483 # i18n: "grep" is a keyword
481 484 gr = re.compile(getstring(x, _("grep requires a string")))
482 485 except re.error, e:
483 486 raise error.ParseError(_('invalid match pattern: %s') % e)
484 487 l = []
485 488 for r in subset:
486 489 c = repo[r]
487 490 for e in c.files() + [c.user(), c.description()]:
488 491 if gr.search(e):
489 492 l.append(r)
490 493 break
491 494 return l
492 495
493 496 def hasfile(repo, subset, x):
494 497 """``file(pattern)``
495 498 Changesets affecting files matched by pattern.
496 499 """
497 500 # i18n: "file" is a keyword
498 501 pat = getstring(x, _("file requires a pattern"))
499 502 m = None
500 503 s = []
501 504 for r in subset:
502 505 c = repo[r]
503 506 if not m or matchmod.patkind(pat) == 'set':
504 507 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
505 508 for f in c.files():
506 509 if m(f):
507 510 s.append(r)
508 511 break
509 512 return s
510 513
511 514 def head(repo, subset, x):
512 515 """``head()``
513 516 Changeset is a named branch head.
514 517 """
515 518 # i18n: "head" is a keyword
516 519 getargs(x, 0, 0, _("head takes no arguments"))
517 520 hs = set()
518 521 for b, ls in repo.branchmap().iteritems():
519 522 hs.update(repo[h].rev() for h in ls)
520 523 return [r for r in subset if r in hs]
521 524
522 525 def heads(repo, subset, x):
523 526 """``heads(set)``
524 527 Members of set with no children in set.
525 528 """
526 529 s = getset(repo, subset, x)
527 530 ps = set(parents(repo, subset, x))
528 531 return [r for r in s if r not in ps]
529 532
530 533 def keyword(repo, subset, x):
531 534 """``keyword(string)``
532 535 Search commit message, user name, and names of changed files for
533 536 string. The match is case-insensitive.
534 537 """
535 538 # i18n: "keyword" is a keyword
536 539 kw = encoding.lower(getstring(x, _("keyword requires a string")))
537 540 l = []
538 541 for r in subset:
539 542 c = repo[r]
540 543 t = " ".join(c.files() + [c.user(), c.description()])
541 544 if kw in encoding.lower(t):
542 545 l.append(r)
543 546 return l
544 547
545 548 def limit(repo, subset, x):
546 549 """``limit(set, [n])``
547 550 First n members of set, defaulting to 1.
548 551 """
549 552 # i18n: "limit" is a keyword
550 553 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
551 554 try:
552 555 lim = 1
553 556 if len(l) == 2:
554 557 # i18n: "limit" is a keyword
555 558 lim = int(getstring(l[1], _("limit requires a number")))
556 559 except (TypeError, ValueError):
557 560 # i18n: "limit" is a keyword
558 561 raise error.ParseError(_("limit expects a number"))
559 562 ss = set(subset)
560 563 os = getset(repo, range(len(repo)), l[0])[:lim]
561 564 return [r for r in os if r in ss]
562 565
563 566 def last(repo, subset, x):
564 567 """``last(set, [n])``
565 568 Last n members of set, defaulting to 1.
566 569 """
567 570 # i18n: "last" is a keyword
568 571 l = getargs(x, 1, 2, _("last requires one or two arguments"))
569 572 try:
570 573 lim = 1
571 574 if len(l) == 2:
572 575 # i18n: "last" is a keyword
573 576 lim = int(getstring(l[1], _("last requires a number")))
574 577 except (TypeError, ValueError):
575 578 # i18n: "last" is a keyword
576 579 raise error.ParseError(_("last expects a number"))
577 580 ss = set(subset)
578 581 os = getset(repo, range(len(repo)), l[0])[-lim:]
579 582 return [r for r in os if r in ss]
580 583
581 584 def maxrev(repo, subset, x):
582 585 """``max(set)``
583 586 Changeset with highest revision number in set.
584 587 """
585 588 os = getset(repo, range(len(repo)), x)
586 589 if os:
587 590 m = max(os)
588 591 if m in subset:
589 592 return [m]
590 593 return []
591 594
592 595 def merge(repo, subset, x):
593 596 """``merge()``
594 597 Changeset is a merge changeset.
595 598 """
596 599 # i18n: "merge" is a keyword
597 600 getargs(x, 0, 0, _("merge takes no arguments"))
598 601 cl = repo.changelog
599 602 return [r for r in subset if cl.parentrevs(r)[1] != -1]
600 603
601 604 def minrev(repo, subset, x):
602 605 """``min(set)``
603 606 Changeset with lowest revision number in set.
604 607 """
605 608 os = getset(repo, range(len(repo)), x)
606 609 if os:
607 610 m = min(os)
608 611 if m in subset:
609 612 return [m]
610 613 return []
611 614
612 615 def modifies(repo, subset, x):
613 616 """``modifies(pattern)``
614 617 Changesets modifying files matched by pattern.
615 618 """
616 619 # i18n: "modifies" is a keyword
617 620 pat = getstring(x, _("modifies requires a pattern"))
618 621 return checkstatus(repo, subset, pat, 0)
619 622
620 623 def node(repo, subset, x):
621 624 """``id(string)``
622 625 Revision non-ambiguously specified by the given hex string prefix.
623 626 """
624 627 # i18n: "id" is a keyword
625 628 l = getargs(x, 1, 1, _("id requires one argument"))
626 629 # i18n: "id" is a keyword
627 630 n = getstring(l[0], _("id requires a string"))
628 631 if len(n) == 40:
629 632 rn = repo[n].rev()
630 633 else:
631 634 rn = repo.changelog.rev(repo.changelog._partialmatch(n))
632 635 return [r for r in subset if r == rn]
633 636
634 637 def outgoing(repo, subset, x):
635 638 """``outgoing([path])``
636 639 Changesets not found in the specified destination repository, or the
637 640 default push location.
638 641 """
639 642 import hg # avoid start-up nasties
640 643 # i18n: "outgoing" is a keyword
641 644 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
642 645 # i18n: "outgoing" is a keyword
643 646 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
644 647 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
645 648 dest, branches = hg.parseurl(dest)
646 649 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
647 650 if revs:
648 651 revs = [repo.lookup(rev) for rev in revs]
649 652 other = hg.peer(repo, {}, dest)
650 653 repo.ui.pushbuffer()
651 654 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
652 655 repo.ui.popbuffer()
653 656 cl = repo.changelog
654 657 o = set([cl.rev(r) for r in outgoing.missing])
655 658 return [r for r in subset if r in o]
656 659
657 660 def p1(repo, subset, x):
658 661 """``p1([set])``
659 662 First parent of changesets in set, or the working directory.
660 663 """
661 664 if x is None:
662 665 p = repo[x].p1().rev()
663 666 return [r for r in subset if r == p]
664 667
665 668 ps = set()
666 669 cl = repo.changelog
667 670 for r in getset(repo, range(len(repo)), x):
668 671 ps.add(cl.parentrevs(r)[0])
669 672 return [r for r in subset if r in ps]
670 673
671 674 def p2(repo, subset, x):
672 675 """``p2([set])``
673 676 Second parent of changesets in set, or the working directory.
674 677 """
675 678 if x is None:
676 679 ps = repo[x].parents()
677 680 try:
678 681 p = ps[1].rev()
679 682 return [r for r in subset if r == p]
680 683 except IndexError:
681 684 return []
682 685
683 686 ps = set()
684 687 cl = repo.changelog
685 688 for r in getset(repo, range(len(repo)), x):
686 689 ps.add(cl.parentrevs(r)[1])
687 690 return [r for r in subset if r in ps]
688 691
689 692 def parents(repo, subset, x):
690 693 """``parents([set])``
691 694 The set of all parents for all changesets in set, or the working directory.
692 695 """
693 696 if x is None:
694 697 ps = tuple(p.rev() for p in repo[x].parents())
695 698 return [r for r in subset if r in ps]
696 699
697 700 ps = set()
698 701 cl = repo.changelog
699 702 for r in getset(repo, range(len(repo)), x):
700 703 ps.update(cl.parentrevs(r))
701 704 return [r for r in subset if r in ps]
702 705
703 706 def parentspec(repo, subset, x, n):
704 707 """``set^0``
705 708 The set.
706 709 ``set^1`` (or ``set^``), ``set^2``
707 710 First or second parent, respectively, of all changesets in set.
708 711 """
709 712 try:
710 713 n = int(n[1])
711 714 if n not in (0, 1, 2):
712 715 raise ValueError
713 716 except (TypeError, ValueError):
714 717 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
715 718 ps = set()
716 719 cl = repo.changelog
717 720 for r in getset(repo, subset, x):
718 721 if n == 0:
719 722 ps.add(r)
720 723 elif n == 1:
721 724 ps.add(cl.parentrevs(r)[0])
722 725 elif n == 2:
723 726 parents = cl.parentrevs(r)
724 727 if len(parents) > 1:
725 728 ps.add(parents[1])
726 729 return [r for r in subset if r in ps]
727 730
728 731 def present(repo, subset, x):
729 732 """``present(set)``
730 733 An empty set, if any revision in set isn't found; otherwise,
731 734 all revisions in set.
732 735 """
733 736 try:
734 737 return getset(repo, subset, x)
735 738 except error.RepoLookupError:
736 739 return []
737 740
738 741 def public(repo, subset, x):
739 742 """``public()``
740 743 Changeset in public phase."""
741 744 getargs(x, 0, 0, _("public takes no arguments"))
742 745 return [r for r in subset if repo._phaserev[r] == phases.public]
743 746
744 747 def remote(repo, subset, x):
745 748 """``remote([id], [path])``
746 749 Local revision that corresponds to the given identifier in a
747 750 remote repository, if present. Here, the '.' identifier is a
748 751 synonym for the current local branch.
749 752 """
750 753
751 754 import hg # avoid start-up nasties
752 755 # i18n: "remote" is a keyword
753 756 l = getargs(x, 0, 2, _("remote takes one or two arguments"))
754 757
755 758 q = '.'
756 759 if len(l) > 0:
757 760 # i18n: "remote" is a keyword
758 761 q = getstring(l[0], _("remote requires a string id"))
759 762 if q == '.':
760 763 q = repo['.'].branch()
761 764
762 765 dest = ''
763 766 if len(l) > 1:
764 767 # i18n: "remote" is a keyword
765 768 dest = getstring(l[1], _("remote requires a repository path"))
766 769 dest = repo.ui.expandpath(dest or 'default')
767 770 dest, branches = hg.parseurl(dest)
768 771 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
769 772 if revs:
770 773 revs = [repo.lookup(rev) for rev in revs]
771 774 other = hg.peer(repo, {}, dest)
772 775 n = other.lookup(q)
773 776 if n in repo:
774 777 r = repo[n].rev()
775 778 if r in subset:
776 779 return [r]
777 780 return []
778 781
779 782 def removes(repo, subset, x):
780 783 """``removes(pattern)``
781 784 Changesets which remove files matching pattern.
782 785 """
783 786 # i18n: "removes" is a keyword
784 787 pat = getstring(x, _("removes requires a pattern"))
785 788 return checkstatus(repo, subset, pat, 2)
786 789
787 790 def rev(repo, subset, x):
788 791 """``rev(number)``
789 792 Revision with the given numeric identifier.
790 793 """
791 794 # i18n: "rev" is a keyword
792 795 l = getargs(x, 1, 1, _("rev requires one argument"))
793 796 try:
794 797 # i18n: "rev" is a keyword
795 798 l = int(getstring(l[0], _("rev requires a number")))
796 799 except (TypeError, ValueError):
797 800 # i18n: "rev" is a keyword
798 801 raise error.ParseError(_("rev expects a number"))
799 802 return [r for r in subset if r == l]
800 803
801 804 def reverse(repo, subset, x):
802 805 """``reverse(set)``
803 806 Reverse order of set.
804 807 """
805 808 l = getset(repo, subset, x)
806 809 l.reverse()
807 810 return l
808 811
809 812 def roots(repo, subset, x):
810 813 """``roots(set)``
811 814 Changesets with no parent changeset in set.
812 815 """
813 816 s = getset(repo, xrange(len(repo)), x)
814 817 cs = _children(repo, s, s)
815 818 return [r for r in s if r not in cs]
816 819
817 820 def secret(repo, subset, x):
818 821 """``secret()``
819 822 Changeset in secret phase."""
820 823 getargs(x, 0, 0, _("secret takes no arguments"))
821 824 return [r for r in subset if repo._phaserev[r] == phases.secret]
822 825
823 826 def sort(repo, subset, x):
824 827 """``sort(set[, [-]key...])``
825 828 Sort set by keys. The default sort order is ascending, specify a key
826 829 as ``-key`` to sort in descending order.
827 830
828 831 The keys can be:
829 832
830 833 - ``rev`` for the revision number,
831 834 - ``branch`` for the branch name,
832 835 - ``desc`` for the commit message (description),
833 836 - ``user`` for user name (``author`` can be used as an alias),
834 837 - ``date`` for the commit date
835 838 """
836 839 # i18n: "sort" is a keyword
837 840 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
838 841 keys = "rev"
839 842 if len(l) == 2:
840 843 keys = getstring(l[1], _("sort spec must be a string"))
841 844
842 845 s = l[0]
843 846 keys = keys.split()
844 847 l = []
845 848 def invert(s):
846 849 return "".join(chr(255 - ord(c)) for c in s)
847 850 for r in getset(repo, subset, s):
848 851 c = repo[r]
849 852 e = []
850 853 for k in keys:
851 854 if k == 'rev':
852 855 e.append(r)
853 856 elif k == '-rev':
854 857 e.append(-r)
855 858 elif k == 'branch':
856 859 e.append(c.branch())
857 860 elif k == '-branch':
858 861 e.append(invert(c.branch()))
859 862 elif k == 'desc':
860 863 e.append(c.description())
861 864 elif k == '-desc':
862 865 e.append(invert(c.description()))
863 866 elif k in 'user author':
864 867 e.append(c.user())
865 868 elif k in '-user -author':
866 869 e.append(invert(c.user()))
867 870 elif k == 'date':
868 871 e.append(c.date()[0])
869 872 elif k == '-date':
870 873 e.append(-c.date()[0])
871 874 else:
872 875 raise error.ParseError(_("unknown sort key %r") % k)
873 876 e.append(r)
874 877 l.append(e)
875 878 l.sort()
876 879 return [e[-1] for e in l]
877 880
878 881 def tag(repo, subset, x):
879 882 """``tag([name])``
880 883 The specified tag by name, or all tagged revisions if no name is given.
881 884 """
882 885 # i18n: "tag" is a keyword
883 886 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
884 887 cl = repo.changelog
885 888 if args:
886 889 tn = getstring(args[0],
887 890 # i18n: "tag" is a keyword
888 891 _('the argument to tag must be a string'))
889 892 if not repo.tags().get(tn, None):
890 893 raise util.Abort(_("tag '%s' does not exist") % tn)
891 894 s = set([cl.rev(n) for t, n in repo.tagslist() if t == tn])
892 895 else:
893 896 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
894 897 return [r for r in subset if r in s]
895 898
896 899 def tagged(repo, subset, x):
897 900 return tag(repo, subset, x)
898 901
899 902 def user(repo, subset, x):
900 903 """``user(string)``
901 904 User name contains string. The match is case-insensitive.
902 905 """
903 906 return author(repo, subset, x)
904 907
905 908 # for internal use
906 909 def _list(repo, subset, x):
907 910 s = getstring(x, "internal error")
908 911 if not s:
909 912 return []
910 913 if not isinstance(subset, set):
911 914 subset = set(subset)
912 915 ls = [repo[r].rev() for r in s.split('\0')]
913 916 return [r for r in ls if r in subset]
914 917
915 918 symbols = {
916 919 "adds": adds,
917 920 "all": getall,
918 921 "ancestor": ancestor,
919 922 "ancestors": ancestors,
920 923 "author": author,
921 924 "bisect": bisect,
922 925 "bisected": bisected,
923 926 "bookmark": bookmark,
924 927 "branch": branch,
925 928 "children": children,
926 929 "closed": closed,
927 930 "contains": contains,
928 931 "date": date,
929 932 "desc": desc,
930 933 "descendants": descendants,
931 934 "draft": draft,
932 935 "file": hasfile,
933 936 "filelog": filelog,
934 937 "first": first,
935 938 "follow": follow,
936 939 "grep": grep,
937 940 "head": head,
938 941 "heads": heads,
939 942 "id": node,
940 943 "keyword": keyword,
941 944 "last": last,
942 945 "limit": limit,
943 946 "max": maxrev,
944 947 "merge": merge,
945 948 "min": minrev,
946 949 "modifies": modifies,
947 950 "outgoing": outgoing,
948 951 "p1": p1,
949 952 "p2": p2,
950 953 "parents": parents,
951 954 "present": present,
952 955 "public": public,
953 956 "remote": remote,
954 957 "removes": removes,
955 958 "rev": rev,
956 959 "reverse": reverse,
957 960 "roots": roots,
958 961 "sort": sort,
959 962 "secret": secret,
960 963 "tag": tag,
961 964 "tagged": tagged,
962 965 "user": user,
963 966 "_list": _list,
964 967 }
965 968
966 969 methods = {
967 970 "range": rangeset,
968 971 "string": stringset,
969 972 "symbol": symbolset,
970 973 "and": andset,
971 974 "or": orset,
972 975 "not": notset,
973 976 "list": listset,
974 977 "func": func,
975 978 "ancestor": ancestorspec,
976 979 "parent": parentspec,
977 980 "parentpost": p1,
978 981 }
979 982
980 983 def optimize(x, small):
981 984 if x is None:
982 985 return 0, x
983 986
984 987 smallbonus = 1
985 988 if small:
986 989 smallbonus = .5
987 990
988 991 op = x[0]
989 992 if op == 'minus':
990 993 return optimize(('and', x[1], ('not', x[2])), small)
991 994 elif op == 'dagrange':
992 995 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
993 996 ('func', ('symbol', 'ancestors'), x[2])), small)
994 997 elif op == 'dagrangepre':
995 998 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
996 999 elif op == 'dagrangepost':
997 1000 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
998 1001 elif op == 'rangepre':
999 1002 return optimize(('range', ('string', '0'), x[1]), small)
1000 1003 elif op == 'rangepost':
1001 1004 return optimize(('range', x[1], ('string', 'tip')), small)
1002 1005 elif op == 'negate':
1003 1006 return optimize(('string',
1004 1007 '-' + getstring(x[1], _("can't negate that"))), small)
1005 1008 elif op in 'string symbol negate':
1006 1009 return smallbonus, x # single revisions are small
1007 1010 elif op == 'and' or op == 'dagrange':
1008 1011 wa, ta = optimize(x[1], True)
1009 1012 wb, tb = optimize(x[2], True)
1010 1013 w = min(wa, wb)
1011 1014 if wa > wb:
1012 1015 return w, (op, tb, ta)
1013 1016 return w, (op, ta, tb)
1014 1017 elif op == 'or':
1015 1018 wa, ta = optimize(x[1], False)
1016 1019 wb, tb = optimize(x[2], False)
1017 1020 if wb < wa:
1018 1021 wb, wa = wa, wb
1019 1022 return max(wa, wb), (op, ta, tb)
1020 1023 elif op == 'not':
1021 1024 o = optimize(x[1], not small)
1022 1025 return o[0], (op, o[1])
1023 1026 elif op == 'parentpost':
1024 1027 o = optimize(x[1], small)
1025 1028 return o[0], (op, o[1])
1026 1029 elif op == 'group':
1027 1030 return optimize(x[1], small)
1028 1031 elif op in 'range list parent ancestorspec':
1029 1032 if op == 'parent':
1030 1033 # x^:y means (x^) : y, not x ^ (:y)
1031 1034 post = ('parentpost', x[1])
1032 1035 if x[2][0] == 'dagrangepre':
1033 1036 return optimize(('dagrange', post, x[2][1]), small)
1034 1037 elif x[2][0] == 'rangepre':
1035 1038 return optimize(('range', post, x[2][1]), small)
1036 1039
1037 1040 wa, ta = optimize(x[1], small)
1038 1041 wb, tb = optimize(x[2], small)
1039 1042 return wa + wb, (op, ta, tb)
1040 1043 elif op == 'func':
1041 1044 f = getstring(x[1], _("not a symbol"))
1042 1045 wa, ta = optimize(x[2], small)
1043 1046 if f in ("author branch closed date desc file grep keyword "
1044 1047 "outgoing user"):
1045 1048 w = 10 # slow
1046 1049 elif f in "modifies adds removes":
1047 1050 w = 30 # slower
1048 1051 elif f == "contains":
1049 1052 w = 100 # very slow
1050 1053 elif f == "ancestor":
1051 1054 w = 1 * smallbonus
1052 1055 elif f in "reverse limit first":
1053 1056 w = 0
1054 1057 elif f in "sort":
1055 1058 w = 10 # assume most sorts look at changelog
1056 1059 else:
1057 1060 w = 1
1058 1061 return w + wa, (op, x[1], ta)
1059 1062 return 1, x
1060 1063
1061 1064 class revsetalias(object):
1062 1065 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1063 1066 args = None
1064 1067
1065 1068 def __init__(self, name, value):
1066 1069 '''Aliases like:
1067 1070
1068 1071 h = heads(default)
1069 1072 b($1) = ancestors($1) - ancestors(default)
1070 1073 '''
1071 1074 if isinstance(name, tuple): # parameter substitution
1072 1075 self.tree = name
1073 1076 self.replacement = value
1074 1077 else: # alias definition
1075 1078 m = self.funcre.search(name)
1076 1079 if m:
1077 1080 self.tree = ('func', ('symbol', m.group(1)))
1078 1081 self.args = [x.strip() for x in m.group(2).split(',')]
1079 1082 for arg in self.args:
1080 1083 value = value.replace(arg, repr(arg))
1081 1084 else:
1082 1085 self.tree = ('symbol', name)
1083 1086
1084 1087 self.replacement, pos = parse(value)
1085 1088 if pos != len(value):
1086 1089 raise error.ParseError(_('invalid token'), pos)
1087 1090
1088 1091 def process(self, tree):
1089 1092 if isinstance(tree, tuple):
1090 1093 if self.args is None:
1091 1094 if tree == self.tree:
1092 1095 return self.replacement
1093 1096 elif tree[:2] == self.tree:
1094 1097 l = getlist(tree[2])
1095 1098 if len(l) != len(self.args):
1096 1099 raise error.ParseError(
1097 1100 _('invalid number of arguments: %s') % len(l))
1098 1101 result = self.replacement
1099 1102 for a, v in zip(self.args, l):
1100 1103 valalias = revsetalias(('string', a), v)
1101 1104 result = valalias.process(result)
1102 1105 return result
1103 1106 return tuple(map(self.process, tree))
1104 1107 return tree
1105 1108
1106 1109 def findaliases(ui, tree):
1107 1110 for k, v in ui.configitems('revsetalias'):
1108 1111 alias = revsetalias(k, v)
1109 1112 tree = alias.process(tree)
1110 1113 return tree
1111 1114
1112 1115 parse = parser.parser(tokenize, elements).parse
1113 1116
1114 1117 def match(ui, spec):
1115 1118 if not spec:
1116 1119 raise error.ParseError(_("empty query"))
1117 1120 tree, pos = parse(spec)
1118 1121 if (pos != len(spec)):
1119 1122 raise error.ParseError(_("invalid token"), pos)
1120 1123 if ui:
1121 1124 tree = findaliases(ui, tree)
1122 1125 weight, tree = optimize(tree, True)
1123 1126 def mfunc(repo, subset):
1124 1127 return getset(repo, subset, tree)
1125 1128 return mfunc
1126 1129
1127 1130 def formatspec(expr, *args):
1128 1131 '''
1129 1132 This is a convenience function for using revsets internally, and
1130 1133 escapes arguments appropriately. Aliases are intentionally ignored
1131 1134 so that intended expression behavior isn't accidentally subverted.
1132 1135
1133 1136 Supported arguments:
1134 1137
1135 1138 %r = revset expression, parenthesized
1136 1139 %d = int(arg), no quoting
1137 1140 %s = string(arg), escaped and single-quoted
1138 1141 %b = arg.branch(), escaped and single-quoted
1139 1142 %n = hex(arg), single-quoted
1140 1143 %% = a literal '%'
1141 1144
1142 1145 Prefixing the type with 'l' specifies a parenthesized list of that type.
1143 1146
1144 1147 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1145 1148 '(10 or 11):: and ((this()) or (that()))'
1146 1149 >>> formatspec('%d:: and not %d::', 10, 20)
1147 1150 '10:: and not 20::'
1148 1151 >>> formatspec('%ld or %ld', [], [1])
1149 1152 "_list('') or 1"
1150 1153 >>> formatspec('keyword(%s)', 'foo\\xe9')
1151 1154 "keyword('foo\\\\xe9')"
1152 1155 >>> b = lambda: 'default'
1153 1156 >>> b.branch = b
1154 1157 >>> formatspec('branch(%b)', b)
1155 1158 "branch('default')"
1156 1159 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1157 1160 "root(_list('a\\x00b\\x00c\\x00d'))"
1158 1161 '''
1159 1162
1160 1163 def quote(s):
1161 1164 return repr(str(s))
1162 1165
1163 1166 def argtype(c, arg):
1164 1167 if c == 'd':
1165 1168 return str(int(arg))
1166 1169 elif c == 's':
1167 1170 return quote(arg)
1168 1171 elif c == 'r':
1169 1172 parse(arg) # make sure syntax errors are confined
1170 1173 return '(%s)' % arg
1171 1174 elif c == 'n':
1172 1175 return quote(nodemod.hex(arg))
1173 1176 elif c == 'b':
1174 1177 return quote(arg.branch())
1175 1178
1176 1179 def listexp(s, t):
1177 1180 l = len(s)
1178 1181 if l == 0:
1179 1182 return "_list('')"
1180 1183 elif l == 1:
1181 1184 return argtype(t, s[0])
1182 1185 elif t == 'd':
1183 1186 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1184 1187 elif t == 's':
1185 1188 return "_list('%s')" % "\0".join(s)
1186 1189 elif t == 'n':
1187 1190 return "_list('%s')" % "\0".join(nodemod.hex(a) for a in s)
1188 1191 elif t == 'b':
1189 1192 return "_list('%s')" % "\0".join(a.branch() for a in s)
1190 1193
1191 1194 m = l // 2
1192 1195 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1193 1196
1194 1197 ret = ''
1195 1198 pos = 0
1196 1199 arg = 0
1197 1200 while pos < len(expr):
1198 1201 c = expr[pos]
1199 1202 if c == '%':
1200 1203 pos += 1
1201 1204 d = expr[pos]
1202 1205 if d == '%':
1203 1206 ret += d
1204 1207 elif d in 'dsnbr':
1205 1208 ret += argtype(d, args[arg])
1206 1209 arg += 1
1207 1210 elif d == 'l':
1208 1211 # a list of some type
1209 1212 pos += 1
1210 1213 d = expr[pos]
1211 1214 ret += listexp(list(args[arg]), d)
1212 1215 arg += 1
1213 1216 else:
1214 1217 raise util.Abort('unexpected revspec format character %s' % d)
1215 1218 else:
1216 1219 ret += c
1217 1220 pos += 1
1218 1221
1219 1222 return ret
1220 1223
1221 1224 # tell hggettext to extract docstrings from these functions:
1222 1225 i18nfunctions = symbols.values()
General Comments 0
You need to be logged in to leave comments. Login now