##// END OF EJS Templates
revset: extend fullreposet to make "null" revision magically appears in set...
Yuya Nishihara -
r24204:d2de20e1 default
parent child Browse files
Show More
@@ -1,3345 +1,3344 b''
1 1 # revset.py - revision set queries for mercurial
2 2 #
3 3 # Copyright 2010 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import re
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import heapq
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15 import obsolete as obsmod
16 16 import pathutil
17 17 import repoview
18 18
19 19 def _revancestors(repo, revs, followfirst):
20 20 """Like revlog.ancestors(), but supports followfirst."""
21 21 cut = followfirst and 1 or None
22 22 cl = repo.changelog
23 23
24 24 def iterate():
25 25 revqueue, revsnode = None, None
26 26 h = []
27 27
28 28 revs.sort(reverse=True)
29 29 revqueue = util.deque(revs)
30 30 if revqueue:
31 31 revsnode = revqueue.popleft()
32 32 heapq.heappush(h, -revsnode)
33 33
34 34 seen = set()
35 35 while h:
36 36 current = -heapq.heappop(h)
37 37 if current not in seen:
38 38 if revsnode and current == revsnode:
39 39 if revqueue:
40 40 revsnode = revqueue.popleft()
41 41 heapq.heappush(h, -revsnode)
42 42 seen.add(current)
43 43 yield current
44 44 for parent in cl.parentrevs(current)[:cut]:
45 45 if parent != node.nullrev:
46 46 heapq.heappush(h, -parent)
47 47
48 48 return generatorset(iterate(), iterasc=False)
49 49
50 50 def _revdescendants(repo, revs, followfirst):
51 51 """Like revlog.descendants() but supports followfirst."""
52 52 cut = followfirst and 1 or None
53 53
54 54 def iterate():
55 55 cl = repo.changelog
56 56 first = min(revs)
57 57 nullrev = node.nullrev
58 58 if first == nullrev:
59 59 # Are there nodes with a null first parent and a non-null
60 60 # second one? Maybe. Do we care? Probably not.
61 61 for i in cl:
62 62 yield i
63 63 else:
64 64 seen = set(revs)
65 65 for i in cl.revs(first + 1):
66 66 for x in cl.parentrevs(i)[:cut]:
67 67 if x != nullrev and x in seen:
68 68 seen.add(i)
69 69 yield i
70 70 break
71 71
72 72 return generatorset(iterate(), iterasc=True)
73 73
74 74 def _revsbetween(repo, roots, heads):
75 75 """Return all paths between roots and heads, inclusive of both endpoint
76 76 sets."""
77 77 if not roots:
78 78 return baseset()
79 79 parentrevs = repo.changelog.parentrevs
80 80 visit = list(heads)
81 81 reachable = set()
82 82 seen = {}
83 83 minroot = min(roots)
84 84 roots = set(roots)
85 85 # open-code the post-order traversal due to the tiny size of
86 86 # sys.getrecursionlimit()
87 87 while visit:
88 88 rev = visit.pop()
89 89 if rev in roots:
90 90 reachable.add(rev)
91 91 parents = parentrevs(rev)
92 92 seen[rev] = parents
93 93 for parent in parents:
94 94 if parent >= minroot and parent not in seen:
95 95 visit.append(parent)
96 96 if not reachable:
97 97 return baseset()
98 98 for rev in sorted(seen):
99 99 for parent in seen[rev]:
100 100 if parent in reachable:
101 101 reachable.add(rev)
102 102 return baseset(sorted(reachable))
103 103
104 104 elements = {
105 105 "(": (21, ("group", 1, ")"), ("func", 1, ")")),
106 106 "##": (20, None, ("_concat", 20)),
107 107 "~": (18, None, ("ancestor", 18)),
108 108 "^": (18, None, ("parent", 18), ("parentpost", 18)),
109 109 "-": (5, ("negate", 19), ("minus", 5)),
110 110 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
111 111 ("dagrangepost", 17)),
112 112 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
113 113 ("dagrangepost", 17)),
114 114 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
115 115 "not": (10, ("not", 10)),
116 116 "!": (10, ("not", 10)),
117 117 "and": (5, None, ("and", 5)),
118 118 "&": (5, None, ("and", 5)),
119 119 "%": (5, None, ("only", 5), ("onlypost", 5)),
120 120 "or": (4, None, ("or", 4)),
121 121 "|": (4, None, ("or", 4)),
122 122 "+": (4, None, ("or", 4)),
123 123 ",": (2, None, ("list", 2)),
124 124 ")": (0, None, None),
125 125 "symbol": (0, ("symbol",), None),
126 126 "string": (0, ("string",), None),
127 127 "end": (0, None, None),
128 128 }
129 129
130 130 keywords = set(['and', 'or', 'not'])
131 131
132 132 # default set of valid characters for the initial letter of symbols
133 133 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
134 134 if c.isalnum() or c in '._@' or ord(c) > 127)
135 135
136 136 # default set of valid characters for non-initial letters of symbols
137 137 _symletters = set(c for c in [chr(i) for i in xrange(256)]
138 138 if c.isalnum() or c in '-._/@' or ord(c) > 127)
139 139
140 140 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
141 141 '''
142 142 Parse a revset statement into a stream of tokens
143 143
144 144 ``syminitletters`` is the set of valid characters for the initial
145 145 letter of symbols.
146 146
147 147 By default, character ``c`` is recognized as valid for initial
148 148 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
149 149
150 150 ``symletters`` is the set of valid characters for non-initial
151 151 letters of symbols.
152 152
153 153 By default, character ``c`` is recognized as valid for non-initial
154 154 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
155 155
156 156 Check that @ is a valid unquoted token character (issue3686):
157 157 >>> list(tokenize("@::"))
158 158 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
159 159
160 160 '''
161 161 if syminitletters is None:
162 162 syminitletters = _syminitletters
163 163 if symletters is None:
164 164 symletters = _symletters
165 165
166 166 pos, l = 0, len(program)
167 167 while pos < l:
168 168 c = program[pos]
169 169 if c.isspace(): # skip inter-token whitespace
170 170 pass
171 171 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
172 172 yield ('::', None, pos)
173 173 pos += 1 # skip ahead
174 174 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
175 175 yield ('..', None, pos)
176 176 pos += 1 # skip ahead
177 177 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
178 178 yield ('##', None, pos)
179 179 pos += 1 # skip ahead
180 180 elif c in "():,-|&+!~^%": # handle simple operators
181 181 yield (c, None, pos)
182 182 elif (c in '"\'' or c == 'r' and
183 183 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
184 184 if c == 'r':
185 185 pos += 1
186 186 c = program[pos]
187 187 decode = lambda x: x
188 188 else:
189 189 decode = lambda x: x.decode('string-escape')
190 190 pos += 1
191 191 s = pos
192 192 while pos < l: # find closing quote
193 193 d = program[pos]
194 194 if d == '\\': # skip over escaped characters
195 195 pos += 2
196 196 continue
197 197 if d == c:
198 198 yield ('string', decode(program[s:pos]), s)
199 199 break
200 200 pos += 1
201 201 else:
202 202 raise error.ParseError(_("unterminated string"), s)
203 203 # gather up a symbol/keyword
204 204 elif c in syminitletters:
205 205 s = pos
206 206 pos += 1
207 207 while pos < l: # find end of symbol
208 208 d = program[pos]
209 209 if d not in symletters:
210 210 break
211 211 if d == '.' and program[pos - 1] == '.': # special case for ..
212 212 pos -= 1
213 213 break
214 214 pos += 1
215 215 sym = program[s:pos]
216 216 if sym in keywords: # operator keywords
217 217 yield (sym, None, s)
218 218 elif '-' in sym:
219 219 # some jerk gave us foo-bar-baz, try to check if it's a symbol
220 220 if lookup and lookup(sym):
221 221 # looks like a real symbol
222 222 yield ('symbol', sym, s)
223 223 else:
224 224 # looks like an expression
225 225 parts = sym.split('-')
226 226 for p in parts[:-1]:
227 227 if p: # possible consecutive -
228 228 yield ('symbol', p, s)
229 229 s += len(p)
230 230 yield ('-', None, pos)
231 231 s += 1
232 232 if parts[-1]: # possible trailing -
233 233 yield ('symbol', parts[-1], s)
234 234 else:
235 235 yield ('symbol', sym, s)
236 236 pos -= 1
237 237 else:
238 238 raise error.ParseError(_("syntax error"), pos)
239 239 pos += 1
240 240 yield ('end', None, pos)
241 241
242 242 def parseerrordetail(inst):
243 243 """Compose error message from specified ParseError object
244 244 """
245 245 if len(inst.args) > 1:
246 246 return _('at %s: %s') % (inst.args[1], inst.args[0])
247 247 else:
248 248 return inst.args[0]
249 249
250 250 # helpers
251 251
252 252 def getstring(x, err):
253 253 if x and (x[0] == 'string' or x[0] == 'symbol'):
254 254 return x[1]
255 255 raise error.ParseError(err)
256 256
257 257 def getlist(x):
258 258 if not x:
259 259 return []
260 260 if x[0] == 'list':
261 261 return getlist(x[1]) + [x[2]]
262 262 return [x]
263 263
264 264 def getargs(x, min, max, err):
265 265 l = getlist(x)
266 266 if len(l) < min or (max >= 0 and len(l) > max):
267 267 raise error.ParseError(err)
268 268 return l
269 269
270 270 def isvalidsymbol(tree):
271 271 """Examine whether specified ``tree`` is valid ``symbol`` or not
272 272 """
273 273 return tree[0] == 'symbol' and len(tree) > 1
274 274
275 275 def getsymbol(tree):
276 276 """Get symbol name from valid ``symbol`` in ``tree``
277 277
278 278 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
279 279 """
280 280 return tree[1]
281 281
282 282 def isvalidfunc(tree):
283 283 """Examine whether specified ``tree`` is valid ``func`` or not
284 284 """
285 285 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
286 286
287 287 def getfuncname(tree):
288 288 """Get function name from valid ``func`` in ``tree``
289 289
290 290 This assumes that ``tree`` is already examined by ``isvalidfunc``.
291 291 """
292 292 return getsymbol(tree[1])
293 293
294 294 def getfuncargs(tree):
295 295 """Get list of function arguments from valid ``func`` in ``tree``
296 296
297 297 This assumes that ``tree`` is already examined by ``isvalidfunc``.
298 298 """
299 299 if len(tree) > 2:
300 300 return getlist(tree[2])
301 301 else:
302 302 return []
303 303
304 304 def getset(repo, subset, x):
305 305 if not x:
306 306 raise error.ParseError(_("missing argument"))
307 307 s = methods[x[0]](repo, subset, *x[1:])
308 308 if util.safehasattr(s, 'isascending'):
309 309 return s
310 310 return baseset(s)
311 311
312 312 def _getrevsource(repo, r):
313 313 extra = repo[r].extra()
314 314 for label in ('source', 'transplant_source', 'rebase_source'):
315 315 if label in extra:
316 316 try:
317 317 return repo[extra[label]].rev()
318 318 except error.RepoLookupError:
319 319 pass
320 320 return None
321 321
322 322 # operator methods
323 323
324 324 def stringset(repo, subset, x):
325 325 x = repo[x].rev()
326 if x == -1 and len(subset) == len(repo):
327 return baseset([-1])
328 326 if x in subset:
329 327 return baseset([x])
330 328 return baseset()
331 329
332 330 def symbolset(repo, subset, x):
333 331 if x in symbols:
334 332 raise error.ParseError(_("can't use %s here") % x)
335 333 return stringset(repo, subset, x)
336 334
337 335 def rangeset(repo, subset, x, y):
338 336 m = getset(repo, fullreposet(repo), x)
339 337 n = getset(repo, fullreposet(repo), y)
340 338
341 339 if not m or not n:
342 340 return baseset()
343 341 m, n = m.first(), n.last()
344 342
345 343 if m < n:
346 344 r = spanset(repo, m, n + 1)
347 345 else:
348 346 r = spanset(repo, m, n - 1)
349 347 return r & subset
350 348
351 349 def dagrange(repo, subset, x, y):
352 350 r = fullreposet(repo)
353 351 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
354 352 return xs & subset
355 353
356 354 def andset(repo, subset, x, y):
357 355 return getset(repo, getset(repo, subset, x), y)
358 356
359 357 def orset(repo, subset, x, y):
360 358 xl = getset(repo, subset, x)
361 359 yl = getset(repo, subset - xl, y)
362 360 return xl + yl
363 361
364 362 def notset(repo, subset, x):
365 363 return subset - getset(repo, subset, x)
366 364
367 365 def listset(repo, subset, a, b):
368 366 raise error.ParseError(_("can't use a list in this context"))
369 367
370 368 def func(repo, subset, a, b):
371 369 if a[0] == 'symbol' and a[1] in symbols:
372 370 return symbols[a[1]](repo, subset, b)
373 371 raise error.ParseError(_("not a function: %s") % a[1])
374 372
375 373 # functions
376 374
377 375 def adds(repo, subset, x):
378 376 """``adds(pattern)``
379 377 Changesets that add a file matching pattern.
380 378
381 379 The pattern without explicit kind like ``glob:`` is expected to be
382 380 relative to the current directory and match against a file or a
383 381 directory.
384 382 """
385 383 # i18n: "adds" is a keyword
386 384 pat = getstring(x, _("adds requires a pattern"))
387 385 return checkstatus(repo, subset, pat, 1)
388 386
389 387 def ancestor(repo, subset, x):
390 388 """``ancestor(*changeset)``
391 389 A greatest common ancestor of the changesets.
392 390
393 391 Accepts 0 or more changesets.
394 392 Will return empty list when passed no args.
395 393 Greatest common ancestor of a single changeset is that changeset.
396 394 """
397 395 # i18n: "ancestor" is a keyword
398 396 l = getlist(x)
399 397 rl = fullreposet(repo)
400 398 anc = None
401 399
402 400 # (getset(repo, rl, i) for i in l) generates a list of lists
403 401 for revs in (getset(repo, rl, i) for i in l):
404 402 for r in revs:
405 403 if anc is None:
406 404 anc = repo[r]
407 405 else:
408 406 anc = anc.ancestor(repo[r])
409 407
410 408 if anc is not None and anc.rev() in subset:
411 409 return baseset([anc.rev()])
412 410 return baseset()
413 411
414 412 def _ancestors(repo, subset, x, followfirst=False):
415 413 heads = getset(repo, fullreposet(repo), x)
416 414 if not heads:
417 415 return baseset()
418 416 s = _revancestors(repo, heads, followfirst)
419 417 return subset & s
420 418
421 419 def ancestors(repo, subset, x):
422 420 """``ancestors(set)``
423 421 Changesets that are ancestors of a changeset in set.
424 422 """
425 423 return _ancestors(repo, subset, x)
426 424
427 425 def _firstancestors(repo, subset, x):
428 426 # ``_firstancestors(set)``
429 427 # Like ``ancestors(set)`` but follows only the first parents.
430 428 return _ancestors(repo, subset, x, followfirst=True)
431 429
432 430 def ancestorspec(repo, subset, x, n):
433 431 """``set~n``
434 432 Changesets that are the Nth ancestor (first parents only) of a changeset
435 433 in set.
436 434 """
437 435 try:
438 436 n = int(n[1])
439 437 except (TypeError, ValueError):
440 438 raise error.ParseError(_("~ expects a number"))
441 439 ps = set()
442 440 cl = repo.changelog
443 441 for r in getset(repo, fullreposet(repo), x):
444 442 for i in range(n):
445 443 r = cl.parentrevs(r)[0]
446 444 ps.add(r)
447 445 return subset & ps
448 446
449 447 def author(repo, subset, x):
450 448 """``author(string)``
451 449 Alias for ``user(string)``.
452 450 """
453 451 # i18n: "author" is a keyword
454 452 n = encoding.lower(getstring(x, _("author requires a string")))
455 453 kind, pattern, matcher = _substringmatcher(n)
456 454 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
457 455
458 456 def bisect(repo, subset, x):
459 457 """``bisect(string)``
460 458 Changesets marked in the specified bisect status:
461 459
462 460 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
463 461 - ``goods``, ``bads`` : csets topologically good/bad
464 462 - ``range`` : csets taking part in the bisection
465 463 - ``pruned`` : csets that are goods, bads or skipped
466 464 - ``untested`` : csets whose fate is yet unknown
467 465 - ``ignored`` : csets ignored due to DAG topology
468 466 - ``current`` : the cset currently being bisected
469 467 """
470 468 # i18n: "bisect" is a keyword
471 469 status = getstring(x, _("bisect requires a string")).lower()
472 470 state = set(hbisect.get(repo, status))
473 471 return subset & state
474 472
475 473 # Backward-compatibility
476 474 # - no help entry so that we do not advertise it any more
477 475 def bisected(repo, subset, x):
478 476 return bisect(repo, subset, x)
479 477
480 478 def bookmark(repo, subset, x):
481 479 """``bookmark([name])``
482 480 The named bookmark or all bookmarks.
483 481
484 482 If `name` starts with `re:`, the remainder of the name is treated as
485 483 a regular expression. To match a bookmark that actually starts with `re:`,
486 484 use the prefix `literal:`.
487 485 """
488 486 # i18n: "bookmark" is a keyword
489 487 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
490 488 if args:
491 489 bm = getstring(args[0],
492 490 # i18n: "bookmark" is a keyword
493 491 _('the argument to bookmark must be a string'))
494 492 kind, pattern, matcher = _stringmatcher(bm)
495 493 bms = set()
496 494 if kind == 'literal':
497 495 bmrev = repo._bookmarks.get(pattern, None)
498 496 if not bmrev:
499 497 raise error.RepoLookupError(_("bookmark '%s' does not exist")
500 498 % bm)
501 499 bms.add(repo[bmrev].rev())
502 500 else:
503 501 matchrevs = set()
504 502 for name, bmrev in repo._bookmarks.iteritems():
505 503 if matcher(name):
506 504 matchrevs.add(bmrev)
507 505 if not matchrevs:
508 506 raise error.RepoLookupError(_("no bookmarks exist"
509 507 " that match '%s'") % pattern)
510 508 for bmrev in matchrevs:
511 509 bms.add(repo[bmrev].rev())
512 510 else:
513 511 bms = set([repo[r].rev()
514 512 for r in repo._bookmarks.values()])
515 513 bms -= set([node.nullrev])
516 514 return subset & bms
517 515
518 516 def branch(repo, subset, x):
519 517 """``branch(string or set)``
520 518 All changesets belonging to the given branch or the branches of the given
521 519 changesets.
522 520
523 521 If `string` starts with `re:`, the remainder of the name is treated as
524 522 a regular expression. To match a branch that actually starts with `re:`,
525 523 use the prefix `literal:`.
526 524 """
527 525 import branchmap
528 526 urepo = repo.unfiltered()
529 527 ucl = urepo.changelog
530 528 getbi = branchmap.revbranchcache(urepo, readonly=True).branchinfo
531 529
532 530 try:
533 531 b = getstring(x, '')
534 532 except error.ParseError:
535 533 # not a string, but another revspec, e.g. tip()
536 534 pass
537 535 else:
538 536 kind, pattern, matcher = _stringmatcher(b)
539 537 if kind == 'literal':
540 538 # note: falls through to the revspec case if no branch with
541 539 # this name exists
542 540 if pattern in repo.branchmap():
543 541 return subset.filter(lambda r: matcher(getbi(ucl, r)[0]))
544 542 else:
545 543 return subset.filter(lambda r: matcher(getbi(ucl, r)[0]))
546 544
547 545 s = getset(repo, fullreposet(repo), x)
548 546 b = set()
549 547 for r in s:
550 548 b.add(getbi(ucl, r)[0])
551 549 c = s.__contains__
552 550 return subset.filter(lambda r: c(r) or getbi(ucl, r)[0] in b)
553 551
554 552 def bumped(repo, subset, x):
555 553 """``bumped()``
556 554 Mutable changesets marked as successors of public changesets.
557 555
558 556 Only non-public and non-obsolete changesets can be `bumped`.
559 557 """
560 558 # i18n: "bumped" is a keyword
561 559 getargs(x, 0, 0, _("bumped takes no arguments"))
562 560 bumped = obsmod.getrevs(repo, 'bumped')
563 561 return subset & bumped
564 562
565 563 def bundle(repo, subset, x):
566 564 """``bundle()``
567 565 Changesets in the bundle.
568 566
569 567 Bundle must be specified by the -R option."""
570 568
571 569 try:
572 570 bundlerevs = repo.changelog.bundlerevs
573 571 except AttributeError:
574 572 raise util.Abort(_("no bundle provided - specify with -R"))
575 573 return subset & bundlerevs
576 574
577 575 def checkstatus(repo, subset, pat, field):
578 576 hasset = matchmod.patkind(pat) == 'set'
579 577
580 578 mcache = [None]
581 579 def matches(x):
582 580 c = repo[x]
583 581 if not mcache[0] or hasset:
584 582 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
585 583 m = mcache[0]
586 584 fname = None
587 585 if not m.anypats() and len(m.files()) == 1:
588 586 fname = m.files()[0]
589 587 if fname is not None:
590 588 if fname not in c.files():
591 589 return False
592 590 else:
593 591 for f in c.files():
594 592 if m(f):
595 593 break
596 594 else:
597 595 return False
598 596 files = repo.status(c.p1().node(), c.node())[field]
599 597 if fname is not None:
600 598 if fname in files:
601 599 return True
602 600 else:
603 601 for f in files:
604 602 if m(f):
605 603 return True
606 604
607 605 return subset.filter(matches)
608 606
609 607 def _children(repo, narrow, parentset):
610 608 cs = set()
611 609 if not parentset:
612 610 return baseset(cs)
613 611 pr = repo.changelog.parentrevs
614 612 minrev = min(parentset)
615 613 for r in narrow:
616 614 if r <= minrev:
617 615 continue
618 616 for p in pr(r):
619 617 if p in parentset:
620 618 cs.add(r)
621 619 return baseset(cs)
622 620
623 621 def children(repo, subset, x):
624 622 """``children(set)``
625 623 Child changesets of changesets in set.
626 624 """
627 625 s = getset(repo, fullreposet(repo), x)
628 626 cs = _children(repo, subset, s)
629 627 return subset & cs
630 628
631 629 def closed(repo, subset, x):
632 630 """``closed()``
633 631 Changeset is closed.
634 632 """
635 633 # i18n: "closed" is a keyword
636 634 getargs(x, 0, 0, _("closed takes no arguments"))
637 635 return subset.filter(lambda r: repo[r].closesbranch())
638 636
639 637 def contains(repo, subset, x):
640 638 """``contains(pattern)``
641 639 The revision's manifest contains a file matching pattern (but might not
642 640 modify it). See :hg:`help patterns` for information about file patterns.
643 641
644 642 The pattern without explicit kind like ``glob:`` is expected to be
645 643 relative to the current directory and match against a file exactly
646 644 for efficiency.
647 645 """
648 646 # i18n: "contains" is a keyword
649 647 pat = getstring(x, _("contains requires a pattern"))
650 648
651 649 def matches(x):
652 650 if not matchmod.patkind(pat):
653 651 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
654 652 if pats in repo[x]:
655 653 return True
656 654 else:
657 655 c = repo[x]
658 656 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
659 657 for f in c.manifest():
660 658 if m(f):
661 659 return True
662 660 return False
663 661
664 662 return subset.filter(matches)
665 663
666 664 def converted(repo, subset, x):
667 665 """``converted([id])``
668 666 Changesets converted from the given identifier in the old repository if
669 667 present, or all converted changesets if no identifier is specified.
670 668 """
671 669
672 670 # There is exactly no chance of resolving the revision, so do a simple
673 671 # string compare and hope for the best
674 672
675 673 rev = None
676 674 # i18n: "converted" is a keyword
677 675 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
678 676 if l:
679 677 # i18n: "converted" is a keyword
680 678 rev = getstring(l[0], _('converted requires a revision'))
681 679
682 680 def _matchvalue(r):
683 681 source = repo[r].extra().get('convert_revision', None)
684 682 return source is not None and (rev is None or source.startswith(rev))
685 683
686 684 return subset.filter(lambda r: _matchvalue(r))
687 685
688 686 def date(repo, subset, x):
689 687 """``date(interval)``
690 688 Changesets within the interval, see :hg:`help dates`.
691 689 """
692 690 # i18n: "date" is a keyword
693 691 ds = getstring(x, _("date requires a string"))
694 692 dm = util.matchdate(ds)
695 693 return subset.filter(lambda x: dm(repo[x].date()[0]))
696 694
697 695 def desc(repo, subset, x):
698 696 """``desc(string)``
699 697 Search commit message for string. The match is case-insensitive.
700 698 """
701 699 # i18n: "desc" is a keyword
702 700 ds = encoding.lower(getstring(x, _("desc requires a string")))
703 701
704 702 def matches(x):
705 703 c = repo[x]
706 704 return ds in encoding.lower(c.description())
707 705
708 706 return subset.filter(matches)
709 707
710 708 def _descendants(repo, subset, x, followfirst=False):
711 709 roots = getset(repo, fullreposet(repo), x)
712 710 if not roots:
713 711 return baseset()
714 712 s = _revdescendants(repo, roots, followfirst)
715 713
716 714 # Both sets need to be ascending in order to lazily return the union
717 715 # in the correct order.
718 716 base = subset & roots
719 717 desc = subset & s
720 718 result = base + desc
721 719 if subset.isascending():
722 720 result.sort()
723 721 elif subset.isdescending():
724 722 result.sort(reverse=True)
725 723 else:
726 724 result = subset & result
727 725 return result
728 726
729 727 def descendants(repo, subset, x):
730 728 """``descendants(set)``
731 729 Changesets which are descendants of changesets in set.
732 730 """
733 731 return _descendants(repo, subset, x)
734 732
735 733 def _firstdescendants(repo, subset, x):
736 734 # ``_firstdescendants(set)``
737 735 # Like ``descendants(set)`` but follows only the first parents.
738 736 return _descendants(repo, subset, x, followfirst=True)
739 737
740 738 def destination(repo, subset, x):
741 739 """``destination([set])``
742 740 Changesets that were created by a graft, transplant or rebase operation,
743 741 with the given revisions specified as the source. Omitting the optional set
744 742 is the same as passing all().
745 743 """
746 744 if x is not None:
747 745 sources = getset(repo, fullreposet(repo), x)
748 746 else:
749 747 sources = fullreposet(repo)
750 748
751 749 dests = set()
752 750
753 751 # subset contains all of the possible destinations that can be returned, so
754 752 # iterate over them and see if their source(s) were provided in the arg set.
755 753 # Even if the immediate src of r is not in the arg set, src's source (or
756 754 # further back) may be. Scanning back further than the immediate src allows
757 755 # transitive transplants and rebases to yield the same results as transitive
758 756 # grafts.
759 757 for r in subset:
760 758 src = _getrevsource(repo, r)
761 759 lineage = None
762 760
763 761 while src is not None:
764 762 if lineage is None:
765 763 lineage = list()
766 764
767 765 lineage.append(r)
768 766
769 767 # The visited lineage is a match if the current source is in the arg
770 768 # set. Since every candidate dest is visited by way of iterating
771 769 # subset, any dests further back in the lineage will be tested by a
772 770 # different iteration over subset. Likewise, if the src was already
773 771 # selected, the current lineage can be selected without going back
774 772 # further.
775 773 if src in sources or src in dests:
776 774 dests.update(lineage)
777 775 break
778 776
779 777 r = src
780 778 src = _getrevsource(repo, r)
781 779
782 780 return subset.filter(dests.__contains__)
783 781
784 782 def divergent(repo, subset, x):
785 783 """``divergent()``
786 784 Final successors of changesets with an alternative set of final successors.
787 785 """
788 786 # i18n: "divergent" is a keyword
789 787 getargs(x, 0, 0, _("divergent takes no arguments"))
790 788 divergent = obsmod.getrevs(repo, 'divergent')
791 789 return subset & divergent
792 790
793 791 def draft(repo, subset, x):
794 792 """``draft()``
795 793 Changeset in draft phase."""
796 794 # i18n: "draft" is a keyword
797 795 getargs(x, 0, 0, _("draft takes no arguments"))
798 796 phase = repo._phasecache.phase
799 797 target = phases.draft
800 798 condition = lambda r: phase(repo, r) == target
801 799 return subset.filter(condition, cache=False)
802 800
803 801 def extinct(repo, subset, x):
804 802 """``extinct()``
805 803 Obsolete changesets with obsolete descendants only.
806 804 """
807 805 # i18n: "extinct" is a keyword
808 806 getargs(x, 0, 0, _("extinct takes no arguments"))
809 807 extincts = obsmod.getrevs(repo, 'extinct')
810 808 return subset & extincts
811 809
812 810 def extra(repo, subset, x):
813 811 """``extra(label, [value])``
814 812 Changesets with the given label in the extra metadata, with the given
815 813 optional value.
816 814
817 815 If `value` starts with `re:`, the remainder of the value is treated as
818 816 a regular expression. To match a value that actually starts with `re:`,
819 817 use the prefix `literal:`.
820 818 """
821 819
822 820 # i18n: "extra" is a keyword
823 821 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
824 822 # i18n: "extra" is a keyword
825 823 label = getstring(l[0], _('first argument to extra must be a string'))
826 824 value = None
827 825
828 826 if len(l) > 1:
829 827 # i18n: "extra" is a keyword
830 828 value = getstring(l[1], _('second argument to extra must be a string'))
831 829 kind, value, matcher = _stringmatcher(value)
832 830
833 831 def _matchvalue(r):
834 832 extra = repo[r].extra()
835 833 return label in extra and (value is None or matcher(extra[label]))
836 834
837 835 return subset.filter(lambda r: _matchvalue(r))
838 836
839 837 def filelog(repo, subset, x):
840 838 """``filelog(pattern)``
841 839 Changesets connected to the specified filelog.
842 840
843 841 For performance reasons, visits only revisions mentioned in the file-level
844 842 filelog, rather than filtering through all changesets (much faster, but
845 843 doesn't include deletes or duplicate changes). For a slower, more accurate
846 844 result, use ``file()``.
847 845
848 846 The pattern without explicit kind like ``glob:`` is expected to be
849 847 relative to the current directory and match against a file exactly
850 848 for efficiency.
851 849
852 850 If some linkrev points to revisions filtered by the current repoview, we'll
853 851 work around it to return a non-filtered value.
854 852 """
855 853
856 854 # i18n: "filelog" is a keyword
857 855 pat = getstring(x, _("filelog requires a pattern"))
858 856 s = set()
859 857 cl = repo.changelog
860 858
861 859 if not matchmod.patkind(pat):
862 860 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
863 861 files = [f]
864 862 else:
865 863 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
866 864 files = (f for f in repo[None] if m(f))
867 865
868 866 for f in files:
869 867 backrevref = {} # final value for: filerev -> changerev
870 868 lowestchild = {} # lowest known filerev child of a filerev
871 869 delayed = [] # filerev with filtered linkrev, for post-processing
872 870 lowesthead = None # cache for manifest content of all head revisions
873 871 fl = repo.file(f)
874 872 for fr in list(fl):
875 873 rev = fl.linkrev(fr)
876 874 if rev not in cl:
877 875 # changerev pointed in linkrev is filtered
878 876 # record it for post processing.
879 877 delayed.append((fr, rev))
880 878 continue
881 879 for p in fl.parentrevs(fr):
882 880 if 0 <= p and p not in lowestchild:
883 881 lowestchild[p] = fr
884 882 backrevref[fr] = rev
885 883 s.add(rev)
886 884
887 885 # Post-processing of all filerevs we skipped because they were
888 886 # filtered. If such filerevs have known and unfiltered children, this
889 887 # means they have an unfiltered appearance out there. We'll use linkrev
890 888 # adjustment to find one of these appearances. The lowest known child
891 889 # will be used as a starting point because it is the best upper-bound we
892 890 # have.
893 891 #
894 892 # This approach will fail when an unfiltered but linkrev-shadowed
895 893 # appearance exists in a head changeset without unfiltered filerev
896 894 # children anywhere.
897 895 while delayed:
898 896 # must be a descending iteration. To slowly fill lowest child
899 897 # information that is of potential use by the next item.
900 898 fr, rev = delayed.pop()
901 899 lkr = rev
902 900
903 901 child = lowestchild.get(fr)
904 902
905 903 if child is None:
906 904 # search for existence of this file revision in a head revision.
907 905 # There are three possibilities:
908 906 # - the revision exists in a head and we can find an
909 907 # introduction from there,
910 908 # - the revision does not exist in a head because it has been
911 909 # changed since its introduction: we would have found a child
912 910 # and be in the other 'else' clause,
913 911 # - all versions of the revision are hidden.
914 912 if lowesthead is None:
915 913 lowesthead = {}
916 914 for h in repo.heads():
917 915 fnode = repo[h].manifest().get(f)
918 916 if fnode is not None:
919 917 lowesthead[fl.rev(fnode)] = h
920 918 headrev = lowesthead.get(fr)
921 919 if headrev is None:
922 920 # content is nowhere unfiltered
923 921 continue
924 922 rev = repo[headrev][f].introrev()
925 923 else:
926 924 # the lowest known child is a good upper bound
927 925 childcrev = backrevref[child]
928 926 # XXX this does not guarantee returning the lowest
929 927 # introduction of this revision, but this gives a
930 928 # result which is a good start and will fit in most
931 929 # cases. We probably need to fix the multiple
932 930 # introductions case properly (report each
933 931 # introduction, even for identical file revisions)
934 932 # once and for all at some point anyway.
935 933 for p in repo[childcrev][f].parents():
936 934 if p.filerev() == fr:
937 935 rev = p.rev()
938 936 break
939 937 if rev == lkr: # no shadowed entry found
940 938 # XXX This should never happen unless some manifest points
941 939 # to biggish file revisions (like a revision that uses a
942 940 # parent that never appears in the manifest ancestors)
943 941 continue
944 942
945 943 # Fill the data for the next iteration.
946 944 for p in fl.parentrevs(fr):
947 945 if 0 <= p and p not in lowestchild:
948 946 lowestchild[p] = fr
949 947 backrevref[fr] = rev
950 948 s.add(rev)
951 949
952 950 return subset & s
953 951
954 952 def first(repo, subset, x):
955 953 """``first(set, [n])``
956 954 An alias for limit().
957 955 """
958 956 return limit(repo, subset, x)
959 957
960 958 def _follow(repo, subset, x, name, followfirst=False):
961 959 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
962 960 c = repo['.']
963 961 if l:
964 962 x = getstring(l[0], _("%s expected a filename") % name)
965 963 if x in c:
966 964 cx = c[x]
967 965 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
968 966 # include the revision responsible for the most recent version
969 967 s.add(cx.introrev())
970 968 else:
971 969 return baseset()
972 970 else:
973 971 s = _revancestors(repo, baseset([c.rev()]), followfirst)
974 972
975 973 return subset & s
976 974
977 975 def follow(repo, subset, x):
978 976 """``follow([file])``
979 977 An alias for ``::.`` (ancestors of the working copy's first parent).
980 978 If a filename is specified, the history of the given file is followed,
981 979 including copies.
982 980 """
983 981 return _follow(repo, subset, x, 'follow')
984 982
985 983 def _followfirst(repo, subset, x):
986 984 # ``followfirst([file])``
987 985 # Like ``follow([file])`` but follows only the first parent of
988 986 # every revision or file revision.
989 987 return _follow(repo, subset, x, '_followfirst', followfirst=True)
990 988
991 989 def getall(repo, subset, x):
992 990 """``all()``
993 991 All changesets, the same as ``0:tip``.
994 992 """
995 993 # i18n: "all" is a keyword
996 994 getargs(x, 0, 0, _("all takes no arguments"))
997 995 return subset & spanset(repo) # drop "null" if any
998 996
999 997 def grep(repo, subset, x):
1000 998 """``grep(regex)``
1001 999 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1002 1000 to ensure special escape characters are handled correctly. Unlike
1003 1001 ``keyword(string)``, the match is case-sensitive.
1004 1002 """
1005 1003 try:
1006 1004 # i18n: "grep" is a keyword
1007 1005 gr = re.compile(getstring(x, _("grep requires a string")))
1008 1006 except re.error, e:
1009 1007 raise error.ParseError(_('invalid match pattern: %s') % e)
1010 1008
1011 1009 def matches(x):
1012 1010 c = repo[x]
1013 1011 for e in c.files() + [c.user(), c.description()]:
1014 1012 if gr.search(e):
1015 1013 return True
1016 1014 return False
1017 1015
1018 1016 return subset.filter(matches)
1019 1017
1020 1018 def _matchfiles(repo, subset, x):
1021 1019 # _matchfiles takes a revset list of prefixed arguments:
1022 1020 #
1023 1021 # [p:foo, i:bar, x:baz]
1024 1022 #
1025 1023 # builds a match object from them and filters subset. Allowed
1026 1024 # prefixes are 'p:' for regular patterns, 'i:' for include
1027 1025 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1028 1026 # a revision identifier, or the empty string to reference the
1029 1027 # working directory, from which the match object is
1030 1028 # initialized. Use 'd:' to set the default matching mode, default
1031 1029 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1032 1030
1033 1031 # i18n: "_matchfiles" is a keyword
1034 1032 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1035 1033 pats, inc, exc = [], [], []
1036 1034 rev, default = None, None
1037 1035 for arg in l:
1038 1036 # i18n: "_matchfiles" is a keyword
1039 1037 s = getstring(arg, _("_matchfiles requires string arguments"))
1040 1038 prefix, value = s[:2], s[2:]
1041 1039 if prefix == 'p:':
1042 1040 pats.append(value)
1043 1041 elif prefix == 'i:':
1044 1042 inc.append(value)
1045 1043 elif prefix == 'x:':
1046 1044 exc.append(value)
1047 1045 elif prefix == 'r:':
1048 1046 if rev is not None:
1049 1047 # i18n: "_matchfiles" is a keyword
1050 1048 raise error.ParseError(_('_matchfiles expected at most one '
1051 1049 'revision'))
1052 1050 if value != '': # empty means working directory; leave rev as None
1053 1051 rev = value
1054 1052 elif prefix == 'd:':
1055 1053 if default is not None:
1056 1054 # i18n: "_matchfiles" is a keyword
1057 1055 raise error.ParseError(_('_matchfiles expected at most one '
1058 1056 'default mode'))
1059 1057 default = value
1060 1058 else:
1061 1059 # i18n: "_matchfiles" is a keyword
1062 1060 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1063 1061 if not default:
1064 1062 default = 'glob'
1065 1063
1066 1064 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1067 1065 exclude=exc, ctx=repo[rev], default=default)
1068 1066
1069 1067 def matches(x):
1070 1068 for f in repo[x].files():
1071 1069 if m(f):
1072 1070 return True
1073 1071 return False
1074 1072
1075 1073 return subset.filter(matches)
1076 1074
1077 1075 def hasfile(repo, subset, x):
1078 1076 """``file(pattern)``
1079 1077 Changesets affecting files matched by pattern.
1080 1078
1081 1079 For a faster but less accurate result, consider using ``filelog()``
1082 1080 instead.
1083 1081
1084 1082 This predicate uses ``glob:`` as the default kind of pattern.
1085 1083 """
1086 1084 # i18n: "file" is a keyword
1087 1085 pat = getstring(x, _("file requires a pattern"))
1088 1086 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1089 1087
1090 1088 def head(repo, subset, x):
1091 1089 """``head()``
1092 1090 Changeset is a named branch head.
1093 1091 """
1094 1092 # i18n: "head" is a keyword
1095 1093 getargs(x, 0, 0, _("head takes no arguments"))
1096 1094 hs = set()
1097 1095 for b, ls in repo.branchmap().iteritems():
1098 1096 hs.update(repo[h].rev() for h in ls)
1099 1097 return baseset(hs).filter(subset.__contains__)
1100 1098
1101 1099 def heads(repo, subset, x):
1102 1100 """``heads(set)``
1103 1101 Members of set with no children in set.
1104 1102 """
1105 1103 s = getset(repo, subset, x)
1106 1104 ps = parents(repo, subset, x)
1107 1105 return s - ps
1108 1106
1109 1107 def hidden(repo, subset, x):
1110 1108 """``hidden()``
1111 1109 Hidden changesets.
1112 1110 """
1113 1111 # i18n: "hidden" is a keyword
1114 1112 getargs(x, 0, 0, _("hidden takes no arguments"))
1115 1113 hiddenrevs = repoview.filterrevs(repo, 'visible')
1116 1114 return subset & hiddenrevs
1117 1115
1118 1116 def keyword(repo, subset, x):
1119 1117 """``keyword(string)``
1120 1118 Search commit message, user name, and names of changed files for
1121 1119 string. The match is case-insensitive.
1122 1120 """
1123 1121 # i18n: "keyword" is a keyword
1124 1122 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1125 1123
1126 1124 def matches(r):
1127 1125 c = repo[r]
1128 1126 return util.any(kw in encoding.lower(t) for t in c.files() + [c.user(),
1129 1127 c.description()])
1130 1128
1131 1129 return subset.filter(matches)
1132 1130
1133 1131 def limit(repo, subset, x):
1134 1132 """``limit(set, [n])``
1135 1133 First n members of set, defaulting to 1.
1136 1134 """
1137 1135 # i18n: "limit" is a keyword
1138 1136 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1139 1137 try:
1140 1138 lim = 1
1141 1139 if len(l) == 2:
1142 1140 # i18n: "limit" is a keyword
1143 1141 lim = int(getstring(l[1], _("limit requires a number")))
1144 1142 except (TypeError, ValueError):
1145 1143 # i18n: "limit" is a keyword
1146 1144 raise error.ParseError(_("limit expects a number"))
1147 1145 ss = subset
1148 1146 os = getset(repo, fullreposet(repo), l[0])
1149 1147 result = []
1150 1148 it = iter(os)
1151 1149 for x in xrange(lim):
1152 1150 try:
1153 1151 y = it.next()
1154 1152 if y in ss:
1155 1153 result.append(y)
1156 1154 except (StopIteration):
1157 1155 break
1158 1156 return baseset(result)
1159 1157
1160 1158 def last(repo, subset, x):
1161 1159 """``last(set, [n])``
1162 1160 Last n members of set, defaulting to 1.
1163 1161 """
1164 1162 # i18n: "last" is a keyword
1165 1163 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1166 1164 try:
1167 1165 lim = 1
1168 1166 if len(l) == 2:
1169 1167 # i18n: "last" is a keyword
1170 1168 lim = int(getstring(l[1], _("last requires a number")))
1171 1169 except (TypeError, ValueError):
1172 1170 # i18n: "last" is a keyword
1173 1171 raise error.ParseError(_("last expects a number"))
1174 1172 ss = subset
1175 1173 os = getset(repo, fullreposet(repo), l[0])
1176 1174 os.reverse()
1177 1175 result = []
1178 1176 it = iter(os)
1179 1177 for x in xrange(lim):
1180 1178 try:
1181 1179 y = it.next()
1182 1180 if y in ss:
1183 1181 result.append(y)
1184 1182 except (StopIteration):
1185 1183 break
1186 1184 return baseset(result)
1187 1185
1188 1186 def maxrev(repo, subset, x):
1189 1187 """``max(set)``
1190 1188 Changeset with highest revision number in set.
1191 1189 """
1192 1190 os = getset(repo, fullreposet(repo), x)
1193 1191 if os:
1194 1192 m = os.max()
1195 1193 if m in subset:
1196 1194 return baseset([m])
1197 1195 return baseset()
1198 1196
1199 1197 def merge(repo, subset, x):
1200 1198 """``merge()``
1201 1199 Changeset is a merge changeset.
1202 1200 """
1203 1201 # i18n: "merge" is a keyword
1204 1202 getargs(x, 0, 0, _("merge takes no arguments"))
1205 1203 cl = repo.changelog
1206 1204 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1207 1205
1208 1206 def branchpoint(repo, subset, x):
1209 1207 """``branchpoint()``
1210 1208 Changesets with more than one child.
1211 1209 """
1212 1210 # i18n: "branchpoint" is a keyword
1213 1211 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1214 1212 cl = repo.changelog
1215 1213 if not subset:
1216 1214 return baseset()
1217 1215 baserev = min(subset)
1218 1216 parentscount = [0]*(len(repo) - baserev)
1219 1217 for r in cl.revs(start=baserev + 1):
1220 1218 for p in cl.parentrevs(r):
1221 1219 if p >= baserev:
1222 1220 parentscount[p - baserev] += 1
1223 1221 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1224 1222
1225 1223 def minrev(repo, subset, x):
1226 1224 """``min(set)``
1227 1225 Changeset with lowest revision number in set.
1228 1226 """
1229 1227 os = getset(repo, fullreposet(repo), x)
1230 1228 if os:
1231 1229 m = os.min()
1232 1230 if m in subset:
1233 1231 return baseset([m])
1234 1232 return baseset()
1235 1233
1236 1234 def modifies(repo, subset, x):
1237 1235 """``modifies(pattern)``
1238 1236 Changesets modifying files matched by pattern.
1239 1237
1240 1238 The pattern without explicit kind like ``glob:`` is expected to be
1241 1239 relative to the current directory and match against a file or a
1242 1240 directory.
1243 1241 """
1244 1242 # i18n: "modifies" is a keyword
1245 1243 pat = getstring(x, _("modifies requires a pattern"))
1246 1244 return checkstatus(repo, subset, pat, 0)
1247 1245
1248 1246 def named(repo, subset, x):
1249 1247 """``named(namespace)``
1250 1248 The changesets in a given namespace.
1251 1249
1252 1250 If `namespace` starts with `re:`, the remainder of the string is treated as
1253 1251 a regular expression. To match a namespace that actually starts with `re:`,
1254 1252 use the prefix `literal:`.
1255 1253 """
1256 1254 # i18n: "named" is a keyword
1257 1255 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1258 1256
1259 1257 ns = getstring(args[0],
1260 1258 # i18n: "named" is a keyword
1261 1259 _('the argument to named must be a string'))
1262 1260 kind, pattern, matcher = _stringmatcher(ns)
1263 1261 namespaces = set()
1264 1262 if kind == 'literal':
1265 1263 if pattern not in repo.names:
1266 1264 raise error.RepoLookupError(_("namespace '%s' does not exist")
1267 1265 % ns)
1268 1266 namespaces.add(repo.names[pattern])
1269 1267 else:
1270 1268 for name, ns in repo.names.iteritems():
1271 1269 if matcher(name):
1272 1270 namespaces.add(ns)
1273 1271 if not namespaces:
1274 1272 raise error.RepoLookupError(_("no namespace exists"
1275 1273 " that match '%s'") % pattern)
1276 1274
1277 1275 names = set()
1278 1276 for ns in namespaces:
1279 1277 for name in ns.listnames(repo):
1280 1278 if name not in ns.deprecated:
1281 1279 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1282 1280
1283 1281 names -= set([node.nullrev])
1284 1282 return subset & names
1285 1283
1286 1284 def node_(repo, subset, x):
1287 1285 """``id(string)``
1288 1286 Revision non-ambiguously specified by the given hex string prefix.
1289 1287 """
1290 1288 # i18n: "id" is a keyword
1291 1289 l = getargs(x, 1, 1, _("id requires one argument"))
1292 1290 # i18n: "id" is a keyword
1293 1291 n = getstring(l[0], _("id requires a string"))
1294 1292 if len(n) == 40:
1295 1293 rn = repo[n].rev()
1296 1294 else:
1297 1295 rn = None
1298 1296 pm = repo.changelog._partialmatch(n)
1299 1297 if pm is not None:
1300 1298 rn = repo.changelog.rev(pm)
1301 1299
1302 1300 if rn is None:
1303 1301 return baseset()
1304 1302 result = baseset([rn])
1305 1303 return result & subset
1306 1304
1307 1305 def obsolete(repo, subset, x):
1308 1306 """``obsolete()``
1309 1307 Mutable changeset with a newer version."""
1310 1308 # i18n: "obsolete" is a keyword
1311 1309 getargs(x, 0, 0, _("obsolete takes no arguments"))
1312 1310 obsoletes = obsmod.getrevs(repo, 'obsolete')
1313 1311 return subset & obsoletes
1314 1312
1315 1313 def only(repo, subset, x):
1316 1314 """``only(set, [set])``
1317 1315 Changesets that are ancestors of the first set that are not ancestors
1318 1316 of any other head in the repo. If a second set is specified, the result
1319 1317 is ancestors of the first set that are not ancestors of the second set
1320 1318 (i.e. ::<set1> - ::<set2>).
1321 1319 """
1322 1320 cl = repo.changelog
1323 1321 # i18n: "only" is a keyword
1324 1322 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1325 1323 include = getset(repo, fullreposet(repo), args[0])
1326 1324 if len(args) == 1:
1327 1325 if not include:
1328 1326 return baseset()
1329 1327
1330 1328 descendants = set(_revdescendants(repo, include, False))
1331 1329 exclude = [rev for rev in cl.headrevs()
1332 1330 if not rev in descendants and not rev in include]
1333 1331 else:
1334 1332 exclude = getset(repo, fullreposet(repo), args[1])
1335 1333
1336 1334 results = set(cl.findmissingrevs(common=exclude, heads=include))
1337 1335 return subset & results
1338 1336
1339 1337 def origin(repo, subset, x):
1340 1338 """``origin([set])``
1341 1339 Changesets that were specified as a source for the grafts, transplants or
1342 1340 rebases that created the given revisions. Omitting the optional set is the
1343 1341 same as passing all(). If a changeset created by these operations is itself
1344 1342 specified as a source for one of these operations, only the source changeset
1345 1343 for the first operation is selected.
1346 1344 """
1347 1345 if x is not None:
1348 1346 dests = getset(repo, fullreposet(repo), x)
1349 1347 else:
1350 1348 dests = fullreposet(repo)
1351 1349
1352 1350 def _firstsrc(rev):
1353 1351 src = _getrevsource(repo, rev)
1354 1352 if src is None:
1355 1353 return None
1356 1354
1357 1355 while True:
1358 1356 prev = _getrevsource(repo, src)
1359 1357
1360 1358 if prev is None:
1361 1359 return src
1362 1360 src = prev
1363 1361
1364 1362 o = set([_firstsrc(r) for r in dests])
1365 1363 o -= set([None])
1366 1364 return subset & o
1367 1365
1368 1366 def outgoing(repo, subset, x):
1369 1367 """``outgoing([path])``
1370 1368 Changesets not found in the specified destination repository, or the
1371 1369 default push location.
1372 1370 """
1373 1371 import hg # avoid start-up nasties
1374 1372 # i18n: "outgoing" is a keyword
1375 1373 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1376 1374 # i18n: "outgoing" is a keyword
1377 1375 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1378 1376 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1379 1377 dest, branches = hg.parseurl(dest)
1380 1378 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1381 1379 if revs:
1382 1380 revs = [repo.lookup(rev) for rev in revs]
1383 1381 other = hg.peer(repo, {}, dest)
1384 1382 repo.ui.pushbuffer()
1385 1383 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1386 1384 repo.ui.popbuffer()
1387 1385 cl = repo.changelog
1388 1386 o = set([cl.rev(r) for r in outgoing.missing])
1389 1387 return subset & o
1390 1388
1391 1389 def p1(repo, subset, x):
1392 1390 """``p1([set])``
1393 1391 First parent of changesets in set, or the working directory.
1394 1392 """
1395 1393 if x is None:
1396 1394 p = repo[x].p1().rev()
1397 1395 if p >= 0:
1398 1396 return subset & baseset([p])
1399 1397 return baseset()
1400 1398
1401 1399 ps = set()
1402 1400 cl = repo.changelog
1403 1401 for r in getset(repo, fullreposet(repo), x):
1404 1402 ps.add(cl.parentrevs(r)[0])
1405 1403 ps -= set([node.nullrev])
1406 1404 return subset & ps
1407 1405
1408 1406 def p2(repo, subset, x):
1409 1407 """``p2([set])``
1410 1408 Second parent of changesets in set, or the working directory.
1411 1409 """
1412 1410 if x is None:
1413 1411 ps = repo[x].parents()
1414 1412 try:
1415 1413 p = ps[1].rev()
1416 1414 if p >= 0:
1417 1415 return subset & baseset([p])
1418 1416 return baseset()
1419 1417 except IndexError:
1420 1418 return baseset()
1421 1419
1422 1420 ps = set()
1423 1421 cl = repo.changelog
1424 1422 for r in getset(repo, fullreposet(repo), x):
1425 1423 ps.add(cl.parentrevs(r)[1])
1426 1424 ps -= set([node.nullrev])
1427 1425 return subset & ps
1428 1426
1429 1427 def parents(repo, subset, x):
1430 1428 """``parents([set])``
1431 1429 The set of all parents for all changesets in set, or the working directory.
1432 1430 """
1433 1431 if x is None:
1434 1432 ps = set(p.rev() for p in repo[x].parents())
1435 1433 else:
1436 1434 ps = set()
1437 1435 cl = repo.changelog
1438 1436 for r in getset(repo, fullreposet(repo), x):
1439 1437 ps.update(cl.parentrevs(r))
1440 1438 ps -= set([node.nullrev])
1441 1439 return subset & ps
1442 1440
1443 1441 def parentspec(repo, subset, x, n):
1444 1442 """``set^0``
1445 1443 The set.
1446 1444 ``set^1`` (or ``set^``), ``set^2``
1447 1445 First or second parent, respectively, of all changesets in set.
1448 1446 """
1449 1447 try:
1450 1448 n = int(n[1])
1451 1449 if n not in (0, 1, 2):
1452 1450 raise ValueError
1453 1451 except (TypeError, ValueError):
1454 1452 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1455 1453 ps = set()
1456 1454 cl = repo.changelog
1457 1455 for r in getset(repo, fullreposet(repo), x):
1458 1456 if n == 0:
1459 1457 ps.add(r)
1460 1458 elif n == 1:
1461 1459 ps.add(cl.parentrevs(r)[0])
1462 1460 elif n == 2:
1463 1461 parents = cl.parentrevs(r)
1464 1462 if len(parents) > 1:
1465 1463 ps.add(parents[1])
1466 1464 return subset & ps
1467 1465
1468 1466 def present(repo, subset, x):
1469 1467 """``present(set)``
1470 1468 An empty set, if any revision in set isn't found; otherwise,
1471 1469 all revisions in set.
1472 1470
1473 1471 If any of specified revisions is not present in the local repository,
1474 1472 the query is normally aborted. But this predicate allows the query
1475 1473 to continue even in such cases.
1476 1474 """
1477 1475 try:
1478 1476 return getset(repo, subset, x)
1479 1477 except error.RepoLookupError:
1480 1478 return baseset()
1481 1479
1482 1480 def public(repo, subset, x):
1483 1481 """``public()``
1484 1482 Changeset in public phase."""
1485 1483 # i18n: "public" is a keyword
1486 1484 getargs(x, 0, 0, _("public takes no arguments"))
1487 1485 phase = repo._phasecache.phase
1488 1486 target = phases.public
1489 1487 condition = lambda r: phase(repo, r) == target
1490 1488 return subset.filter(condition, cache=False)
1491 1489
1492 1490 def remote(repo, subset, x):
1493 1491 """``remote([id [,path]])``
1494 1492 Local revision that corresponds to the given identifier in a
1495 1493 remote repository, if present. Here, the '.' identifier is a
1496 1494 synonym for the current local branch.
1497 1495 """
1498 1496
1499 1497 import hg # avoid start-up nasties
1500 1498 # i18n: "remote" is a keyword
1501 1499 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1502 1500
1503 1501 q = '.'
1504 1502 if len(l) > 0:
1505 1503 # i18n: "remote" is a keyword
1506 1504 q = getstring(l[0], _("remote requires a string id"))
1507 1505 if q == '.':
1508 1506 q = repo['.'].branch()
1509 1507
1510 1508 dest = ''
1511 1509 if len(l) > 1:
1512 1510 # i18n: "remote" is a keyword
1513 1511 dest = getstring(l[1], _("remote requires a repository path"))
1514 1512 dest = repo.ui.expandpath(dest or 'default')
1515 1513 dest, branches = hg.parseurl(dest)
1516 1514 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1517 1515 if revs:
1518 1516 revs = [repo.lookup(rev) for rev in revs]
1519 1517 other = hg.peer(repo, {}, dest)
1520 1518 n = other.lookup(q)
1521 1519 if n in repo:
1522 1520 r = repo[n].rev()
1523 1521 if r in subset:
1524 1522 return baseset([r])
1525 1523 return baseset()
1526 1524
1527 1525 def removes(repo, subset, x):
1528 1526 """``removes(pattern)``
1529 1527 Changesets which remove files matching pattern.
1530 1528
1531 1529 The pattern without explicit kind like ``glob:`` is expected to be
1532 1530 relative to the current directory and match against a file or a
1533 1531 directory.
1534 1532 """
1535 1533 # i18n: "removes" is a keyword
1536 1534 pat = getstring(x, _("removes requires a pattern"))
1537 1535 return checkstatus(repo, subset, pat, 2)
1538 1536
1539 1537 def rev(repo, subset, x):
1540 1538 """``rev(number)``
1541 1539 Revision with the given numeric identifier.
1542 1540 """
1543 1541 # i18n: "rev" is a keyword
1544 1542 l = getargs(x, 1, 1, _("rev requires one argument"))
1545 1543 try:
1546 1544 # i18n: "rev" is a keyword
1547 1545 l = int(getstring(l[0], _("rev requires a number")))
1548 1546 except (TypeError, ValueError):
1549 1547 # i18n: "rev" is a keyword
1550 1548 raise error.ParseError(_("rev expects a number"))
1551 1549 if l not in repo.changelog and l != node.nullrev:
1552 1550 return baseset()
1553 1551 return subset & baseset([l])
1554 1552
1555 1553 def matching(repo, subset, x):
1556 1554 """``matching(revision [, field])``
1557 1555 Changesets in which a given set of fields match the set of fields in the
1558 1556 selected revision or set.
1559 1557
1560 1558 To match more than one field pass the list of fields to match separated
1561 1559 by spaces (e.g. ``author description``).
1562 1560
1563 1561 Valid fields are most regular revision fields and some special fields.
1564 1562
1565 1563 Regular revision fields are ``description``, ``author``, ``branch``,
1566 1564 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1567 1565 and ``diff``.
1568 1566 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1569 1567 contents of the revision. Two revisions matching their ``diff`` will
1570 1568 also match their ``files``.
1571 1569
1572 1570 Special fields are ``summary`` and ``metadata``:
1573 1571 ``summary`` matches the first line of the description.
1574 1572 ``metadata`` is equivalent to matching ``description user date``
1575 1573 (i.e. it matches the main metadata fields).
1576 1574
1577 1575 ``metadata`` is the default field which is used when no fields are
1578 1576 specified. You can match more than one field at a time.
1579 1577 """
1580 1578 # i18n: "matching" is a keyword
1581 1579 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1582 1580
1583 1581 revs = getset(repo, fullreposet(repo), l[0])
1584 1582
1585 1583 fieldlist = ['metadata']
1586 1584 if len(l) > 1:
1587 1585 fieldlist = getstring(l[1],
1588 1586 # i18n: "matching" is a keyword
1589 1587 _("matching requires a string "
1590 1588 "as its second argument")).split()
1591 1589
1592 1590 # Make sure that there are no repeated fields,
1593 1591 # expand the 'special' 'metadata' field type
1594 1592 # and check the 'files' whenever we check the 'diff'
1595 1593 fields = []
1596 1594 for field in fieldlist:
1597 1595 if field == 'metadata':
1598 1596 fields += ['user', 'description', 'date']
1599 1597 elif field == 'diff':
1600 1598 # a revision matching the diff must also match the files
1601 1599 # since matching the diff is very costly, make sure to
1602 1600 # also match the files first
1603 1601 fields += ['files', 'diff']
1604 1602 else:
1605 1603 if field == 'author':
1606 1604 field = 'user'
1607 1605 fields.append(field)
1608 1606 fields = set(fields)
1609 1607 if 'summary' in fields and 'description' in fields:
1610 1608 # If a revision matches its description it also matches its summary
1611 1609 fields.discard('summary')
1612 1610
1613 1611 # We may want to match more than one field
1614 1612 # Not all fields take the same amount of time to be matched
1615 1613 # Sort the selected fields in order of increasing matching cost
1616 1614 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1617 1615 'files', 'description', 'substate', 'diff']
1618 1616 def fieldkeyfunc(f):
1619 1617 try:
1620 1618 return fieldorder.index(f)
1621 1619 except ValueError:
1622 1620 # assume an unknown field is very costly
1623 1621 return len(fieldorder)
1624 1622 fields = list(fields)
1625 1623 fields.sort(key=fieldkeyfunc)
1626 1624
1627 1625 # Each field will be matched with its own "getfield" function
1628 1626 # which will be added to the getfieldfuncs array of functions
1629 1627 getfieldfuncs = []
1630 1628 _funcs = {
1631 1629 'user': lambda r: repo[r].user(),
1632 1630 'branch': lambda r: repo[r].branch(),
1633 1631 'date': lambda r: repo[r].date(),
1634 1632 'description': lambda r: repo[r].description(),
1635 1633 'files': lambda r: repo[r].files(),
1636 1634 'parents': lambda r: repo[r].parents(),
1637 1635 'phase': lambda r: repo[r].phase(),
1638 1636 'substate': lambda r: repo[r].substate,
1639 1637 'summary': lambda r: repo[r].description().splitlines()[0],
1640 1638 'diff': lambda r: list(repo[r].diff(git=True),)
1641 1639 }
1642 1640 for info in fields:
1643 1641 getfield = _funcs.get(info, None)
1644 1642 if getfield is None:
1645 1643 raise error.ParseError(
1646 1644 # i18n: "matching" is a keyword
1647 1645 _("unexpected field name passed to matching: %s") % info)
1648 1646 getfieldfuncs.append(getfield)
1649 1647 # convert the getfield array of functions into a "getinfo" function
1650 1648 # which returns an array of field values (or a single value if there
1651 1649 # is only one field to match)
1652 1650 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1653 1651
1654 1652 def matches(x):
1655 1653 for rev in revs:
1656 1654 target = getinfo(rev)
1657 1655 match = True
1658 1656 for n, f in enumerate(getfieldfuncs):
1659 1657 if target[n] != f(x):
1660 1658 match = False
1661 1659 if match:
1662 1660 return True
1663 1661 return False
1664 1662
1665 1663 return subset.filter(matches)
1666 1664
1667 1665 def reverse(repo, subset, x):
1668 1666 """``reverse(set)``
1669 1667 Reverse order of set.
1670 1668 """
1671 1669 l = getset(repo, subset, x)
1672 1670 l.reverse()
1673 1671 return l
1674 1672
1675 1673 def roots(repo, subset, x):
1676 1674 """``roots(set)``
1677 1675 Changesets in set with no parent changeset in set.
1678 1676 """
1679 1677 s = getset(repo, fullreposet(repo), x)
1680 1678 subset = baseset([r for r in s if r in subset])
1681 1679 cs = _children(repo, subset, s)
1682 1680 return subset - cs
1683 1681
1684 1682 def secret(repo, subset, x):
1685 1683 """``secret()``
1686 1684 Changeset in secret phase."""
1687 1685 # i18n: "secret" is a keyword
1688 1686 getargs(x, 0, 0, _("secret takes no arguments"))
1689 1687 phase = repo._phasecache.phase
1690 1688 target = phases.secret
1691 1689 condition = lambda r: phase(repo, r) == target
1692 1690 return subset.filter(condition, cache=False)
1693 1691
1694 1692 def sort(repo, subset, x):
1695 1693 """``sort(set[, [-]key...])``
1696 1694 Sort set by keys. The default sort order is ascending, specify a key
1697 1695 as ``-key`` to sort in descending order.
1698 1696
1699 1697 The keys can be:
1700 1698
1701 1699 - ``rev`` for the revision number,
1702 1700 - ``branch`` for the branch name,
1703 1701 - ``desc`` for the commit message (description),
1704 1702 - ``user`` for user name (``author`` can be used as an alias),
1705 1703 - ``date`` for the commit date
1706 1704 """
1707 1705 # i18n: "sort" is a keyword
1708 1706 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1709 1707 keys = "rev"
1710 1708 if len(l) == 2:
1711 1709 # i18n: "sort" is a keyword
1712 1710 keys = getstring(l[1], _("sort spec must be a string"))
1713 1711
1714 1712 s = l[0]
1715 1713 keys = keys.split()
1716 1714 l = []
1717 1715 def invert(s):
1718 1716 return "".join(chr(255 - ord(c)) for c in s)
1719 1717 revs = getset(repo, subset, s)
1720 1718 if keys == ["rev"]:
1721 1719 revs.sort()
1722 1720 return revs
1723 1721 elif keys == ["-rev"]:
1724 1722 revs.sort(reverse=True)
1725 1723 return revs
1726 1724 for r in revs:
1727 1725 c = repo[r]
1728 1726 e = []
1729 1727 for k in keys:
1730 1728 if k == 'rev':
1731 1729 e.append(r)
1732 1730 elif k == '-rev':
1733 1731 e.append(-r)
1734 1732 elif k == 'branch':
1735 1733 e.append(c.branch())
1736 1734 elif k == '-branch':
1737 1735 e.append(invert(c.branch()))
1738 1736 elif k == 'desc':
1739 1737 e.append(c.description())
1740 1738 elif k == '-desc':
1741 1739 e.append(invert(c.description()))
1742 1740 elif k in 'user author':
1743 1741 e.append(c.user())
1744 1742 elif k in '-user -author':
1745 1743 e.append(invert(c.user()))
1746 1744 elif k == 'date':
1747 1745 e.append(c.date()[0])
1748 1746 elif k == '-date':
1749 1747 e.append(-c.date()[0])
1750 1748 else:
1751 1749 raise error.ParseError(_("unknown sort key %r") % k)
1752 1750 e.append(r)
1753 1751 l.append(e)
1754 1752 l.sort()
1755 1753 return baseset([e[-1] for e in l])
1756 1754
1757 1755 def _stringmatcher(pattern):
1758 1756 """
1759 1757 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1760 1758 returns the matcher name, pattern, and matcher function.
1761 1759 missing or unknown prefixes are treated as literal matches.
1762 1760
1763 1761 helper for tests:
1764 1762 >>> def test(pattern, *tests):
1765 1763 ... kind, pattern, matcher = _stringmatcher(pattern)
1766 1764 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1767 1765
1768 1766 exact matching (no prefix):
1769 1767 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1770 1768 ('literal', 'abcdefg', [False, False, True])
1771 1769
1772 1770 regex matching ('re:' prefix)
1773 1771 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1774 1772 ('re', 'a.+b', [False, False, True])
1775 1773
1776 1774 force exact matches ('literal:' prefix)
1777 1775 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1778 1776 ('literal', 're:foobar', [False, True])
1779 1777
1780 1778 unknown prefixes are ignored and treated as literals
1781 1779 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1782 1780 ('literal', 'foo:bar', [False, False, True])
1783 1781 """
1784 1782 if pattern.startswith('re:'):
1785 1783 pattern = pattern[3:]
1786 1784 try:
1787 1785 regex = re.compile(pattern)
1788 1786 except re.error, e:
1789 1787 raise error.ParseError(_('invalid regular expression: %s')
1790 1788 % e)
1791 1789 return 're', pattern, regex.search
1792 1790 elif pattern.startswith('literal:'):
1793 1791 pattern = pattern[8:]
1794 1792 return 'literal', pattern, pattern.__eq__
1795 1793
1796 1794 def _substringmatcher(pattern):
1797 1795 kind, pattern, matcher = _stringmatcher(pattern)
1798 1796 if kind == 'literal':
1799 1797 matcher = lambda s: pattern in s
1800 1798 return kind, pattern, matcher
1801 1799
1802 1800 def tag(repo, subset, x):
1803 1801 """``tag([name])``
1804 1802 The specified tag by name, or all tagged revisions if no name is given.
1805 1803
1806 1804 If `name` starts with `re:`, the remainder of the name is treated as
1807 1805 a regular expression. To match a tag that actually starts with `re:`,
1808 1806 use the prefix `literal:`.
1809 1807 """
1810 1808 # i18n: "tag" is a keyword
1811 1809 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1812 1810 cl = repo.changelog
1813 1811 if args:
1814 1812 pattern = getstring(args[0],
1815 1813 # i18n: "tag" is a keyword
1816 1814 _('the argument to tag must be a string'))
1817 1815 kind, pattern, matcher = _stringmatcher(pattern)
1818 1816 if kind == 'literal':
1819 1817 # avoid resolving all tags
1820 1818 tn = repo._tagscache.tags.get(pattern, None)
1821 1819 if tn is None:
1822 1820 raise error.RepoLookupError(_("tag '%s' does not exist")
1823 1821 % pattern)
1824 1822 s = set([repo[tn].rev()])
1825 1823 else:
1826 1824 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1827 1825 else:
1828 1826 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1829 1827 return subset & s
1830 1828
1831 1829 def tagged(repo, subset, x):
1832 1830 return tag(repo, subset, x)
1833 1831
1834 1832 def unstable(repo, subset, x):
1835 1833 """``unstable()``
1836 1834 Non-obsolete changesets with obsolete ancestors.
1837 1835 """
1838 1836 # i18n: "unstable" is a keyword
1839 1837 getargs(x, 0, 0, _("unstable takes no arguments"))
1840 1838 unstables = obsmod.getrevs(repo, 'unstable')
1841 1839 return subset & unstables
1842 1840
1843 1841
1844 1842 def user(repo, subset, x):
1845 1843 """``user(string)``
1846 1844 User name contains string. The match is case-insensitive.
1847 1845
1848 1846 If `string` starts with `re:`, the remainder of the string is treated as
1849 1847 a regular expression. To match a user that actually contains `re:`, use
1850 1848 the prefix `literal:`.
1851 1849 """
1852 1850 return author(repo, subset, x)
1853 1851
1854 1852 # for internal use
1855 1853 def _list(repo, subset, x):
1856 1854 s = getstring(x, "internal error")
1857 1855 if not s:
1858 1856 return baseset()
1859 1857 ls = [repo[r].rev() for r in s.split('\0')]
1860 1858 s = subset
1861 1859 return baseset([r for r in ls if r in s])
1862 1860
1863 1861 # for internal use
1864 1862 def _intlist(repo, subset, x):
1865 1863 s = getstring(x, "internal error")
1866 1864 if not s:
1867 1865 return baseset()
1868 1866 ls = [int(r) for r in s.split('\0')]
1869 1867 s = subset
1870 1868 return baseset([r for r in ls if r in s])
1871 1869
1872 1870 # for internal use
1873 1871 def _hexlist(repo, subset, x):
1874 1872 s = getstring(x, "internal error")
1875 1873 if not s:
1876 1874 return baseset()
1877 1875 cl = repo.changelog
1878 1876 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
1879 1877 s = subset
1880 1878 return baseset([r for r in ls if r in s])
1881 1879
1882 1880 symbols = {
1883 1881 "adds": adds,
1884 1882 "all": getall,
1885 1883 "ancestor": ancestor,
1886 1884 "ancestors": ancestors,
1887 1885 "_firstancestors": _firstancestors,
1888 1886 "author": author,
1889 1887 "bisect": bisect,
1890 1888 "bisected": bisected,
1891 1889 "bookmark": bookmark,
1892 1890 "branch": branch,
1893 1891 "branchpoint": branchpoint,
1894 1892 "bumped": bumped,
1895 1893 "bundle": bundle,
1896 1894 "children": children,
1897 1895 "closed": closed,
1898 1896 "contains": contains,
1899 1897 "converted": converted,
1900 1898 "date": date,
1901 1899 "desc": desc,
1902 1900 "descendants": descendants,
1903 1901 "_firstdescendants": _firstdescendants,
1904 1902 "destination": destination,
1905 1903 "divergent": divergent,
1906 1904 "draft": draft,
1907 1905 "extinct": extinct,
1908 1906 "extra": extra,
1909 1907 "file": hasfile,
1910 1908 "filelog": filelog,
1911 1909 "first": first,
1912 1910 "follow": follow,
1913 1911 "_followfirst": _followfirst,
1914 1912 "grep": grep,
1915 1913 "head": head,
1916 1914 "heads": heads,
1917 1915 "hidden": hidden,
1918 1916 "id": node_,
1919 1917 "keyword": keyword,
1920 1918 "last": last,
1921 1919 "limit": limit,
1922 1920 "_matchfiles": _matchfiles,
1923 1921 "max": maxrev,
1924 1922 "merge": merge,
1925 1923 "min": minrev,
1926 1924 "modifies": modifies,
1927 1925 "named": named,
1928 1926 "obsolete": obsolete,
1929 1927 "only": only,
1930 1928 "origin": origin,
1931 1929 "outgoing": outgoing,
1932 1930 "p1": p1,
1933 1931 "p2": p2,
1934 1932 "parents": parents,
1935 1933 "present": present,
1936 1934 "public": public,
1937 1935 "remote": remote,
1938 1936 "removes": removes,
1939 1937 "rev": rev,
1940 1938 "reverse": reverse,
1941 1939 "roots": roots,
1942 1940 "sort": sort,
1943 1941 "secret": secret,
1944 1942 "matching": matching,
1945 1943 "tag": tag,
1946 1944 "tagged": tagged,
1947 1945 "user": user,
1948 1946 "unstable": unstable,
1949 1947 "_list": _list,
1950 1948 "_intlist": _intlist,
1951 1949 "_hexlist": _hexlist,
1952 1950 }
1953 1951
1954 1952 # symbols which can't be used for a DoS attack for any given input
1955 1953 # (e.g. those which accept regexes as plain strings shouldn't be included)
1956 1954 # functions that just return a lot of changesets (like all) don't count here
1957 1955 safesymbols = set([
1958 1956 "adds",
1959 1957 "all",
1960 1958 "ancestor",
1961 1959 "ancestors",
1962 1960 "_firstancestors",
1963 1961 "author",
1964 1962 "bisect",
1965 1963 "bisected",
1966 1964 "bookmark",
1967 1965 "branch",
1968 1966 "branchpoint",
1969 1967 "bumped",
1970 1968 "bundle",
1971 1969 "children",
1972 1970 "closed",
1973 1971 "converted",
1974 1972 "date",
1975 1973 "desc",
1976 1974 "descendants",
1977 1975 "_firstdescendants",
1978 1976 "destination",
1979 1977 "divergent",
1980 1978 "draft",
1981 1979 "extinct",
1982 1980 "extra",
1983 1981 "file",
1984 1982 "filelog",
1985 1983 "first",
1986 1984 "follow",
1987 1985 "_followfirst",
1988 1986 "head",
1989 1987 "heads",
1990 1988 "hidden",
1991 1989 "id",
1992 1990 "keyword",
1993 1991 "last",
1994 1992 "limit",
1995 1993 "_matchfiles",
1996 1994 "max",
1997 1995 "merge",
1998 1996 "min",
1999 1997 "modifies",
2000 1998 "obsolete",
2001 1999 "only",
2002 2000 "origin",
2003 2001 "outgoing",
2004 2002 "p1",
2005 2003 "p2",
2006 2004 "parents",
2007 2005 "present",
2008 2006 "public",
2009 2007 "remote",
2010 2008 "removes",
2011 2009 "rev",
2012 2010 "reverse",
2013 2011 "roots",
2014 2012 "sort",
2015 2013 "secret",
2016 2014 "matching",
2017 2015 "tag",
2018 2016 "tagged",
2019 2017 "user",
2020 2018 "unstable",
2021 2019 "_list",
2022 2020 "_intlist",
2023 2021 "_hexlist",
2024 2022 ])
2025 2023
2026 2024 methods = {
2027 2025 "range": rangeset,
2028 2026 "dagrange": dagrange,
2029 2027 "string": stringset,
2030 2028 "symbol": symbolset,
2031 2029 "and": andset,
2032 2030 "or": orset,
2033 2031 "not": notset,
2034 2032 "list": listset,
2035 2033 "func": func,
2036 2034 "ancestor": ancestorspec,
2037 2035 "parent": parentspec,
2038 2036 "parentpost": p1,
2039 2037 "only": only,
2040 2038 "onlypost": only,
2041 2039 }
2042 2040
2043 2041 def optimize(x, small):
2044 2042 if x is None:
2045 2043 return 0, x
2046 2044
2047 2045 smallbonus = 1
2048 2046 if small:
2049 2047 smallbonus = .5
2050 2048
2051 2049 op = x[0]
2052 2050 if op == 'minus':
2053 2051 return optimize(('and', x[1], ('not', x[2])), small)
2054 2052 elif op == 'only':
2055 2053 return optimize(('func', ('symbol', 'only'),
2056 2054 ('list', x[1], x[2])), small)
2057 2055 elif op == 'dagrangepre':
2058 2056 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2059 2057 elif op == 'dagrangepost':
2060 2058 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2061 2059 elif op == 'rangepre':
2062 2060 return optimize(('range', ('string', '0'), x[1]), small)
2063 2061 elif op == 'rangepost':
2064 2062 return optimize(('range', x[1], ('string', 'tip')), small)
2065 2063 elif op == 'negate':
2066 2064 return optimize(('string',
2067 2065 '-' + getstring(x[1], _("can't negate that"))), small)
2068 2066 elif op in 'string symbol negate':
2069 2067 return smallbonus, x # single revisions are small
2070 2068 elif op == 'and':
2071 2069 wa, ta = optimize(x[1], True)
2072 2070 wb, tb = optimize(x[2], True)
2073 2071
2074 2072 # (::x and not ::y)/(not ::y and ::x) have a fast path
2075 2073 def isonly(revs, bases):
2076 2074 return (
2077 2075 revs[0] == 'func'
2078 2076 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2079 2077 and bases[0] == 'not'
2080 2078 and bases[1][0] == 'func'
2081 2079 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2082 2080
2083 2081 w = min(wa, wb)
2084 2082 if isonly(ta, tb):
2085 2083 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2086 2084 if isonly(tb, ta):
2087 2085 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2088 2086
2089 2087 if wa > wb:
2090 2088 return w, (op, tb, ta)
2091 2089 return w, (op, ta, tb)
2092 2090 elif op == 'or':
2093 2091 wa, ta = optimize(x[1], False)
2094 2092 wb, tb = optimize(x[2], False)
2095 2093 if wb < wa:
2096 2094 wb, wa = wa, wb
2097 2095 return max(wa, wb), (op, ta, tb)
2098 2096 elif op == 'not':
2099 2097 o = optimize(x[1], not small)
2100 2098 return o[0], (op, o[1])
2101 2099 elif op == 'parentpost':
2102 2100 o = optimize(x[1], small)
2103 2101 return o[0], (op, o[1])
2104 2102 elif op == 'group':
2105 2103 return optimize(x[1], small)
2106 2104 elif op in 'dagrange range list parent ancestorspec':
2107 2105 if op == 'parent':
2108 2106 # x^:y means (x^) : y, not x ^ (:y)
2109 2107 post = ('parentpost', x[1])
2110 2108 if x[2][0] == 'dagrangepre':
2111 2109 return optimize(('dagrange', post, x[2][1]), small)
2112 2110 elif x[2][0] == 'rangepre':
2113 2111 return optimize(('range', post, x[2][1]), small)
2114 2112
2115 2113 wa, ta = optimize(x[1], small)
2116 2114 wb, tb = optimize(x[2], small)
2117 2115 return wa + wb, (op, ta, tb)
2118 2116 elif op == 'func':
2119 2117 f = getstring(x[1], _("not a symbol"))
2120 2118 wa, ta = optimize(x[2], small)
2121 2119 if f in ("author branch closed date desc file grep keyword "
2122 2120 "outgoing user"):
2123 2121 w = 10 # slow
2124 2122 elif f in "modifies adds removes":
2125 2123 w = 30 # slower
2126 2124 elif f == "contains":
2127 2125 w = 100 # very slow
2128 2126 elif f == "ancestor":
2129 2127 w = 1 * smallbonus
2130 2128 elif f in "reverse limit first _intlist":
2131 2129 w = 0
2132 2130 elif f in "sort":
2133 2131 w = 10 # assume most sorts look at changelog
2134 2132 else:
2135 2133 w = 1
2136 2134 return w + wa, (op, x[1], ta)
2137 2135 return 1, x
2138 2136
2139 2137 _aliasarg = ('func', ('symbol', '_aliasarg'))
2140 2138 def _getaliasarg(tree):
2141 2139 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2142 2140 return X, None otherwise.
2143 2141 """
2144 2142 if (len(tree) == 3 and tree[:2] == _aliasarg
2145 2143 and tree[2][0] == 'string'):
2146 2144 return tree[2][1]
2147 2145 return None
2148 2146
2149 2147 def _checkaliasarg(tree, known=None):
2150 2148 """Check tree contains no _aliasarg construct or only ones which
2151 2149 value is in known. Used to avoid alias placeholders injection.
2152 2150 """
2153 2151 if isinstance(tree, tuple):
2154 2152 arg = _getaliasarg(tree)
2155 2153 if arg is not None and (not known or arg not in known):
2156 2154 raise error.ParseError(_("not a function: %s") % '_aliasarg')
2157 2155 for t in tree:
2158 2156 _checkaliasarg(t, known)
2159 2157
2160 2158 # the set of valid characters for the initial letter of symbols in
2161 2159 # alias declarations and definitions
2162 2160 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2163 2161 if c.isalnum() or c in '._@$' or ord(c) > 127)
2164 2162
2165 2163 def _tokenizealias(program, lookup=None):
2166 2164 """Parse alias declaration/definition into a stream of tokens
2167 2165
2168 2166 This allows symbol names to use also ``$`` as an initial letter
2169 2167 (for backward compatibility), and callers of this function should
2170 2168 examine whether ``$`` is used also for unexpected symbols or not.
2171 2169 """
2172 2170 return tokenize(program, lookup=lookup,
2173 2171 syminitletters=_aliassyminitletters)
2174 2172
2175 2173 def _parsealiasdecl(decl):
2176 2174 """Parse alias declaration ``decl``
2177 2175
2178 2176 This returns ``(name, tree, args, errorstr)`` tuple:
2179 2177
2180 2178 - ``name``: of declared alias (may be ``decl`` itself at error)
2181 2179 - ``tree``: parse result (or ``None`` at error)
2182 2180 - ``args``: list of alias argument names (or None for symbol declaration)
2183 2181 - ``errorstr``: detail about detected error (or None)
2184 2182
2185 2183 >>> _parsealiasdecl('foo')
2186 2184 ('foo', ('symbol', 'foo'), None, None)
2187 2185 >>> _parsealiasdecl('$foo')
2188 2186 ('$foo', None, None, "'$' not for alias arguments")
2189 2187 >>> _parsealiasdecl('foo::bar')
2190 2188 ('foo::bar', None, None, 'invalid format')
2191 2189 >>> _parsealiasdecl('foo bar')
2192 2190 ('foo bar', None, None, 'at 4: invalid token')
2193 2191 >>> _parsealiasdecl('foo()')
2194 2192 ('foo', ('func', ('symbol', 'foo')), [], None)
2195 2193 >>> _parsealiasdecl('$foo()')
2196 2194 ('$foo()', None, None, "'$' not for alias arguments")
2197 2195 >>> _parsealiasdecl('foo($1, $2)')
2198 2196 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2199 2197 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2200 2198 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2201 2199 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2202 2200 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2203 2201 >>> _parsealiasdecl('foo(bar($1, $2))')
2204 2202 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2205 2203 >>> _parsealiasdecl('foo("string")')
2206 2204 ('foo("string")', None, None, 'invalid argument list')
2207 2205 >>> _parsealiasdecl('foo($1, $2')
2208 2206 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2209 2207 >>> _parsealiasdecl('foo("string')
2210 2208 ('foo("string', None, None, 'at 5: unterminated string')
2211 2209 >>> _parsealiasdecl('foo($1, $2, $1)')
2212 2210 ('foo', None, None, 'argument names collide with each other')
2213 2211 """
2214 2212 p = parser.parser(_tokenizealias, elements)
2215 2213 try:
2216 2214 tree, pos = p.parse(decl)
2217 2215 if (pos != len(decl)):
2218 2216 raise error.ParseError(_('invalid token'), pos)
2219 2217
2220 2218 if isvalidsymbol(tree):
2221 2219 # "name = ...." style
2222 2220 name = getsymbol(tree)
2223 2221 if name.startswith('$'):
2224 2222 return (decl, None, None, _("'$' not for alias arguments"))
2225 2223 return (name, ('symbol', name), None, None)
2226 2224
2227 2225 if isvalidfunc(tree):
2228 2226 # "name(arg, ....) = ...." style
2229 2227 name = getfuncname(tree)
2230 2228 if name.startswith('$'):
2231 2229 return (decl, None, None, _("'$' not for alias arguments"))
2232 2230 args = []
2233 2231 for arg in getfuncargs(tree):
2234 2232 if not isvalidsymbol(arg):
2235 2233 return (decl, None, None, _("invalid argument list"))
2236 2234 args.append(getsymbol(arg))
2237 2235 if len(args) != len(set(args)):
2238 2236 return (name, None, None,
2239 2237 _("argument names collide with each other"))
2240 2238 return (name, ('func', ('symbol', name)), args, None)
2241 2239
2242 2240 return (decl, None, None, _("invalid format"))
2243 2241 except error.ParseError, inst:
2244 2242 return (decl, None, None, parseerrordetail(inst))
2245 2243
2246 2244 def _parsealiasdefn(defn, args):
2247 2245 """Parse alias definition ``defn``
2248 2246
2249 2247 This function also replaces alias argument references in the
2250 2248 specified definition by ``_aliasarg(ARGNAME)``.
2251 2249
2252 2250 ``args`` is a list of alias argument names, or None if the alias
2253 2251 is declared as a symbol.
2254 2252
2255 2253 This returns "tree" as parsing result.
2256 2254
2257 2255 >>> args = ['$1', '$2', 'foo']
2258 2256 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2259 2257 (or
2260 2258 (func
2261 2259 ('symbol', '_aliasarg')
2262 2260 ('string', '$1'))
2263 2261 (func
2264 2262 ('symbol', '_aliasarg')
2265 2263 ('string', 'foo')))
2266 2264 >>> try:
2267 2265 ... _parsealiasdefn('$1 or $bar', args)
2268 2266 ... except error.ParseError, inst:
2269 2267 ... print parseerrordetail(inst)
2270 2268 at 6: '$' not for alias arguments
2271 2269 >>> args = ['$1', '$10', 'foo']
2272 2270 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2273 2271 (or
2274 2272 (func
2275 2273 ('symbol', '_aliasarg')
2276 2274 ('string', '$10'))
2277 2275 ('symbol', 'foobar'))
2278 2276 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2279 2277 (or
2280 2278 ('string', '$1')
2281 2279 ('string', 'foo'))
2282 2280 """
2283 2281 def tokenizedefn(program, lookup=None):
2284 2282 if args:
2285 2283 argset = set(args)
2286 2284 else:
2287 2285 argset = set()
2288 2286
2289 2287 for t, value, pos in _tokenizealias(program, lookup=lookup):
2290 2288 if t == 'symbol':
2291 2289 if value in argset:
2292 2290 # emulate tokenization of "_aliasarg('ARGNAME')":
2293 2291 # "_aliasarg()" is an unknown symbol only used separate
2294 2292 # alias argument placeholders from regular strings.
2295 2293 yield ('symbol', '_aliasarg', pos)
2296 2294 yield ('(', None, pos)
2297 2295 yield ('string', value, pos)
2298 2296 yield (')', None, pos)
2299 2297 continue
2300 2298 elif value.startswith('$'):
2301 2299 raise error.ParseError(_("'$' not for alias arguments"),
2302 2300 pos)
2303 2301 yield (t, value, pos)
2304 2302
2305 2303 p = parser.parser(tokenizedefn, elements)
2306 2304 tree, pos = p.parse(defn)
2307 2305 if pos != len(defn):
2308 2306 raise error.ParseError(_('invalid token'), pos)
2309 2307 return tree
2310 2308
2311 2309 class revsetalias(object):
2312 2310 # whether own `error` information is already shown or not.
2313 2311 # this avoids showing same warning multiple times at each `findaliases`.
2314 2312 warned = False
2315 2313
2316 2314 def __init__(self, name, value):
2317 2315 '''Aliases like:
2318 2316
2319 2317 h = heads(default)
2320 2318 b($1) = ancestors($1) - ancestors(default)
2321 2319 '''
2322 2320 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2323 2321 if self.error:
2324 2322 self.error = _('failed to parse the declaration of revset alias'
2325 2323 ' "%s": %s') % (self.name, self.error)
2326 2324 return
2327 2325
2328 2326 try:
2329 2327 self.replacement = _parsealiasdefn(value, self.args)
2330 2328 # Check for placeholder injection
2331 2329 _checkaliasarg(self.replacement, self.args)
2332 2330 except error.ParseError, inst:
2333 2331 self.error = _('failed to parse the definition of revset alias'
2334 2332 ' "%s": %s') % (self.name, parseerrordetail(inst))
2335 2333
2336 2334 def _getalias(aliases, tree):
2337 2335 """If tree looks like an unexpanded alias, return it. Return None
2338 2336 otherwise.
2339 2337 """
2340 2338 if isinstance(tree, tuple) and tree:
2341 2339 if tree[0] == 'symbol' and len(tree) == 2:
2342 2340 name = tree[1]
2343 2341 alias = aliases.get(name)
2344 2342 if alias and alias.args is None and alias.tree == tree:
2345 2343 return alias
2346 2344 if tree[0] == 'func' and len(tree) > 1:
2347 2345 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2348 2346 name = tree[1][1]
2349 2347 alias = aliases.get(name)
2350 2348 if alias and alias.args is not None and alias.tree == tree[:2]:
2351 2349 return alias
2352 2350 return None
2353 2351
2354 2352 def _expandargs(tree, args):
2355 2353 """Replace _aliasarg instances with the substitution value of the
2356 2354 same name in args, recursively.
2357 2355 """
2358 2356 if not tree or not isinstance(tree, tuple):
2359 2357 return tree
2360 2358 arg = _getaliasarg(tree)
2361 2359 if arg is not None:
2362 2360 return args[arg]
2363 2361 return tuple(_expandargs(t, args) for t in tree)
2364 2362
2365 2363 def _expandaliases(aliases, tree, expanding, cache):
2366 2364 """Expand aliases in tree, recursively.
2367 2365
2368 2366 'aliases' is a dictionary mapping user defined aliases to
2369 2367 revsetalias objects.
2370 2368 """
2371 2369 if not isinstance(tree, tuple):
2372 2370 # Do not expand raw strings
2373 2371 return tree
2374 2372 alias = _getalias(aliases, tree)
2375 2373 if alias is not None:
2376 2374 if alias.error:
2377 2375 raise util.Abort(alias.error)
2378 2376 if alias in expanding:
2379 2377 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2380 2378 'detected') % alias.name)
2381 2379 expanding.append(alias)
2382 2380 if alias.name not in cache:
2383 2381 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2384 2382 expanding, cache)
2385 2383 result = cache[alias.name]
2386 2384 expanding.pop()
2387 2385 if alias.args is not None:
2388 2386 l = getlist(tree[2])
2389 2387 if len(l) != len(alias.args):
2390 2388 raise error.ParseError(
2391 2389 _('invalid number of arguments: %s') % len(l))
2392 2390 l = [_expandaliases(aliases, a, [], cache) for a in l]
2393 2391 result = _expandargs(result, dict(zip(alias.args, l)))
2394 2392 else:
2395 2393 result = tuple(_expandaliases(aliases, t, expanding, cache)
2396 2394 for t in tree)
2397 2395 return result
2398 2396
2399 2397 def findaliases(ui, tree, showwarning=None):
2400 2398 _checkaliasarg(tree)
2401 2399 aliases = {}
2402 2400 for k, v in ui.configitems('revsetalias'):
2403 2401 alias = revsetalias(k, v)
2404 2402 aliases[alias.name] = alias
2405 2403 tree = _expandaliases(aliases, tree, [], {})
2406 2404 if showwarning:
2407 2405 # warn about problematic (but not referred) aliases
2408 2406 for name, alias in sorted(aliases.iteritems()):
2409 2407 if alias.error and not alias.warned:
2410 2408 showwarning(_('warning: %s\n') % (alias.error))
2411 2409 alias.warned = True
2412 2410 return tree
2413 2411
2414 2412 def foldconcat(tree):
2415 2413 """Fold elements to be concatenated by `##`
2416 2414 """
2417 2415 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2418 2416 return tree
2419 2417 if tree[0] == '_concat':
2420 2418 pending = [tree]
2421 2419 l = []
2422 2420 while pending:
2423 2421 e = pending.pop()
2424 2422 if e[0] == '_concat':
2425 2423 pending.extend(reversed(e[1:]))
2426 2424 elif e[0] in ('string', 'symbol'):
2427 2425 l.append(e[1])
2428 2426 else:
2429 2427 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2430 2428 raise error.ParseError(msg)
2431 2429 return ('string', ''.join(l))
2432 2430 else:
2433 2431 return tuple(foldconcat(t) for t in tree)
2434 2432
2435 2433 def parse(spec, lookup=None):
2436 2434 p = parser.parser(tokenize, elements)
2437 2435 return p.parse(spec, lookup=lookup)
2438 2436
2439 2437 def match(ui, spec, repo=None):
2440 2438 if not spec:
2441 2439 raise error.ParseError(_("empty query"))
2442 2440 lookup = None
2443 2441 if repo:
2444 2442 lookup = repo.__contains__
2445 2443 tree, pos = parse(spec, lookup)
2446 2444 if (pos != len(spec)):
2447 2445 raise error.ParseError(_("invalid token"), pos)
2448 2446 if ui:
2449 2447 tree = findaliases(ui, tree, showwarning=ui.warn)
2450 2448 tree = foldconcat(tree)
2451 2449 weight, tree = optimize(tree, True)
2452 2450 def mfunc(repo, subset=None):
2453 2451 if subset is None:
2454 2452 subset = fullreposet(repo)
2455 2453 if util.safehasattr(subset, 'isascending'):
2456 2454 result = getset(repo, subset, tree)
2457 2455 else:
2458 2456 result = getset(repo, baseset(subset), tree)
2459 2457 return result
2460 2458 return mfunc
2461 2459
2462 2460 def formatspec(expr, *args):
2463 2461 '''
2464 2462 This is a convenience function for using revsets internally, and
2465 2463 escapes arguments appropriately. Aliases are intentionally ignored
2466 2464 so that intended expression behavior isn't accidentally subverted.
2467 2465
2468 2466 Supported arguments:
2469 2467
2470 2468 %r = revset expression, parenthesized
2471 2469 %d = int(arg), no quoting
2472 2470 %s = string(arg), escaped and single-quoted
2473 2471 %b = arg.branch(), escaped and single-quoted
2474 2472 %n = hex(arg), single-quoted
2475 2473 %% = a literal '%'
2476 2474
2477 2475 Prefixing the type with 'l' specifies a parenthesized list of that type.
2478 2476
2479 2477 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2480 2478 '(10 or 11):: and ((this()) or (that()))'
2481 2479 >>> formatspec('%d:: and not %d::', 10, 20)
2482 2480 '10:: and not 20::'
2483 2481 >>> formatspec('%ld or %ld', [], [1])
2484 2482 "_list('') or 1"
2485 2483 >>> formatspec('keyword(%s)', 'foo\\xe9')
2486 2484 "keyword('foo\\\\xe9')"
2487 2485 >>> b = lambda: 'default'
2488 2486 >>> b.branch = b
2489 2487 >>> formatspec('branch(%b)', b)
2490 2488 "branch('default')"
2491 2489 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2492 2490 "root(_list('a\\x00b\\x00c\\x00d'))"
2493 2491 '''
2494 2492
2495 2493 def quote(s):
2496 2494 return repr(str(s))
2497 2495
2498 2496 def argtype(c, arg):
2499 2497 if c == 'd':
2500 2498 return str(int(arg))
2501 2499 elif c == 's':
2502 2500 return quote(arg)
2503 2501 elif c == 'r':
2504 2502 parse(arg) # make sure syntax errors are confined
2505 2503 return '(%s)' % arg
2506 2504 elif c == 'n':
2507 2505 return quote(node.hex(arg))
2508 2506 elif c == 'b':
2509 2507 return quote(arg.branch())
2510 2508
2511 2509 def listexp(s, t):
2512 2510 l = len(s)
2513 2511 if l == 0:
2514 2512 return "_list('')"
2515 2513 elif l == 1:
2516 2514 return argtype(t, s[0])
2517 2515 elif t == 'd':
2518 2516 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2519 2517 elif t == 's':
2520 2518 return "_list('%s')" % "\0".join(s)
2521 2519 elif t == 'n':
2522 2520 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2523 2521 elif t == 'b':
2524 2522 return "_list('%s')" % "\0".join(a.branch() for a in s)
2525 2523
2526 2524 m = l // 2
2527 2525 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2528 2526
2529 2527 ret = ''
2530 2528 pos = 0
2531 2529 arg = 0
2532 2530 while pos < len(expr):
2533 2531 c = expr[pos]
2534 2532 if c == '%':
2535 2533 pos += 1
2536 2534 d = expr[pos]
2537 2535 if d == '%':
2538 2536 ret += d
2539 2537 elif d in 'dsnbr':
2540 2538 ret += argtype(d, args[arg])
2541 2539 arg += 1
2542 2540 elif d == 'l':
2543 2541 # a list of some type
2544 2542 pos += 1
2545 2543 d = expr[pos]
2546 2544 ret += listexp(list(args[arg]), d)
2547 2545 arg += 1
2548 2546 else:
2549 2547 raise util.Abort('unexpected revspec format character %s' % d)
2550 2548 else:
2551 2549 ret += c
2552 2550 pos += 1
2553 2551
2554 2552 return ret
2555 2553
2556 2554 def prettyformat(tree):
2557 2555 def _prettyformat(tree, level, lines):
2558 2556 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2559 2557 lines.append((level, str(tree)))
2560 2558 else:
2561 2559 lines.append((level, '(%s' % tree[0]))
2562 2560 for s in tree[1:]:
2563 2561 _prettyformat(s, level + 1, lines)
2564 2562 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
2565 2563
2566 2564 lines = []
2567 2565 _prettyformat(tree, 0, lines)
2568 2566 output = '\n'.join((' '*l + s) for l, s in lines)
2569 2567 return output
2570 2568
2571 2569 def depth(tree):
2572 2570 if isinstance(tree, tuple):
2573 2571 return max(map(depth, tree)) + 1
2574 2572 else:
2575 2573 return 0
2576 2574
2577 2575 def funcsused(tree):
2578 2576 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2579 2577 return set()
2580 2578 else:
2581 2579 funcs = set()
2582 2580 for s in tree[1:]:
2583 2581 funcs |= funcsused(s)
2584 2582 if tree[0] == 'func':
2585 2583 funcs.add(tree[1][1])
2586 2584 return funcs
2587 2585
2588 2586 class abstractsmartset(object):
2589 2587
2590 2588 def __nonzero__(self):
2591 2589 """True if the smartset is not empty"""
2592 2590 raise NotImplementedError()
2593 2591
2594 2592 def __contains__(self, rev):
2595 2593 """provide fast membership testing"""
2596 2594 raise NotImplementedError()
2597 2595
2598 2596 def __iter__(self):
2599 2597 """iterate the set in the order it is supposed to be iterated"""
2600 2598 raise NotImplementedError()
2601 2599
2602 2600 # Attributes containing a function to perform a fast iteration in a given
2603 2601 # direction. A smartset can have none, one, or both defined.
2604 2602 #
2605 2603 # Default value is None instead of a function returning None to avoid
2606 2604 # initializing an iterator just for testing if a fast method exists.
2607 2605 fastasc = None
2608 2606 fastdesc = None
2609 2607
2610 2608 def isascending(self):
2611 2609 """True if the set will iterate in ascending order"""
2612 2610 raise NotImplementedError()
2613 2611
2614 2612 def isdescending(self):
2615 2613 """True if the set will iterate in descending order"""
2616 2614 raise NotImplementedError()
2617 2615
2618 2616 def min(self):
2619 2617 """return the minimum element in the set"""
2620 2618 if self.fastasc is not None:
2621 2619 for r in self.fastasc():
2622 2620 return r
2623 2621 raise ValueError('arg is an empty sequence')
2624 2622 return min(self)
2625 2623
2626 2624 def max(self):
2627 2625 """return the maximum element in the set"""
2628 2626 if self.fastdesc is not None:
2629 2627 for r in self.fastdesc():
2630 2628 return r
2631 2629 raise ValueError('arg is an empty sequence')
2632 2630 return max(self)
2633 2631
2634 2632 def first(self):
2635 2633 """return the first element in the set (user iteration perspective)
2636 2634
2637 2635 Return None if the set is empty"""
2638 2636 raise NotImplementedError()
2639 2637
2640 2638 def last(self):
2641 2639 """return the last element in the set (user iteration perspective)
2642 2640
2643 2641 Return None if the set is empty"""
2644 2642 raise NotImplementedError()
2645 2643
2646 2644 def __len__(self):
2647 2645 """return the length of the smartsets
2648 2646
2649 2647 This can be expensive on smartset that could be lazy otherwise."""
2650 2648 raise NotImplementedError()
2651 2649
2652 2650 def reverse(self):
2653 2651 """reverse the expected iteration order"""
2654 2652 raise NotImplementedError()
2655 2653
2656 2654 def sort(self, reverse=True):
2657 2655 """get the set to iterate in an ascending or descending order"""
2658 2656 raise NotImplementedError()
2659 2657
2660 2658 def __and__(self, other):
2661 2659 """Returns a new object with the intersection of the two collections.
2662 2660
2663 2661 This is part of the mandatory API for smartset."""
2664 2662 return self.filter(other.__contains__, cache=False)
2665 2663
2666 2664 def __add__(self, other):
2667 2665 """Returns a new object with the union of the two collections.
2668 2666
2669 2667 This is part of the mandatory API for smartset."""
2670 2668 return addset(self, other)
2671 2669
2672 2670 def __sub__(self, other):
2673 2671 """Returns a new object with the substraction of the two collections.
2674 2672
2675 2673 This is part of the mandatory API for smartset."""
2676 2674 c = other.__contains__
2677 2675 return self.filter(lambda r: not c(r), cache=False)
2678 2676
2679 2677 def filter(self, condition, cache=True):
2680 2678 """Returns this smartset filtered by condition as a new smartset.
2681 2679
2682 2680 `condition` is a callable which takes a revision number and returns a
2683 2681 boolean.
2684 2682
2685 2683 This is part of the mandatory API for smartset."""
2686 2684 # builtin cannot be cached. but do not needs to
2687 2685 if cache and util.safehasattr(condition, 'func_code'):
2688 2686 condition = util.cachefunc(condition)
2689 2687 return filteredset(self, condition)
2690 2688
2691 2689 class baseset(abstractsmartset):
2692 2690 """Basic data structure that represents a revset and contains the basic
2693 2691 operation that it should be able to perform.
2694 2692
2695 2693 Every method in this class should be implemented by any smartset class.
2696 2694 """
2697 2695 def __init__(self, data=()):
2698 2696 if not isinstance(data, list):
2699 2697 data = list(data)
2700 2698 self._list = data
2701 2699 self._ascending = None
2702 2700
2703 2701 @util.propertycache
2704 2702 def _set(self):
2705 2703 return set(self._list)
2706 2704
2707 2705 @util.propertycache
2708 2706 def _asclist(self):
2709 2707 asclist = self._list[:]
2710 2708 asclist.sort()
2711 2709 return asclist
2712 2710
2713 2711 def __iter__(self):
2714 2712 if self._ascending is None:
2715 2713 return iter(self._list)
2716 2714 elif self._ascending:
2717 2715 return iter(self._asclist)
2718 2716 else:
2719 2717 return reversed(self._asclist)
2720 2718
2721 2719 def fastasc(self):
2722 2720 return iter(self._asclist)
2723 2721
2724 2722 def fastdesc(self):
2725 2723 return reversed(self._asclist)
2726 2724
2727 2725 @util.propertycache
2728 2726 def __contains__(self):
2729 2727 return self._set.__contains__
2730 2728
2731 2729 def __nonzero__(self):
2732 2730 return bool(self._list)
2733 2731
2734 2732 def sort(self, reverse=False):
2735 2733 self._ascending = not bool(reverse)
2736 2734
2737 2735 def reverse(self):
2738 2736 if self._ascending is None:
2739 2737 self._list.reverse()
2740 2738 else:
2741 2739 self._ascending = not self._ascending
2742 2740
2743 2741 def __len__(self):
2744 2742 return len(self._list)
2745 2743
2746 2744 def isascending(self):
2747 2745 """Returns True if the collection is ascending order, False if not.
2748 2746
2749 2747 This is part of the mandatory API for smartset."""
2750 2748 if len(self) <= 1:
2751 2749 return True
2752 2750 return self._ascending is not None and self._ascending
2753 2751
2754 2752 def isdescending(self):
2755 2753 """Returns True if the collection is descending order, False if not.
2756 2754
2757 2755 This is part of the mandatory API for smartset."""
2758 2756 if len(self) <= 1:
2759 2757 return True
2760 2758 return self._ascending is not None and not self._ascending
2761 2759
2762 2760 def first(self):
2763 2761 if self:
2764 2762 if self._ascending is None:
2765 2763 return self._list[0]
2766 2764 elif self._ascending:
2767 2765 return self._asclist[0]
2768 2766 else:
2769 2767 return self._asclist[-1]
2770 2768 return None
2771 2769
2772 2770 def last(self):
2773 2771 if self:
2774 2772 if self._ascending is None:
2775 2773 return self._list[-1]
2776 2774 elif self._ascending:
2777 2775 return self._asclist[-1]
2778 2776 else:
2779 2777 return self._asclist[0]
2780 2778 return None
2781 2779
2782 2780 class filteredset(abstractsmartset):
2783 2781 """Duck type for baseset class which iterates lazily over the revisions in
2784 2782 the subset and contains a function which tests for membership in the
2785 2783 revset
2786 2784 """
2787 2785 def __init__(self, subset, condition=lambda x: True):
2788 2786 """
2789 2787 condition: a function that decide whether a revision in the subset
2790 2788 belongs to the revset or not.
2791 2789 """
2792 2790 self._subset = subset
2793 2791 self._condition = condition
2794 2792 self._cache = {}
2795 2793
2796 2794 def __contains__(self, x):
2797 2795 c = self._cache
2798 2796 if x not in c:
2799 2797 v = c[x] = x in self._subset and self._condition(x)
2800 2798 return v
2801 2799 return c[x]
2802 2800
2803 2801 def __iter__(self):
2804 2802 return self._iterfilter(self._subset)
2805 2803
2806 2804 def _iterfilter(self, it):
2807 2805 cond = self._condition
2808 2806 for x in it:
2809 2807 if cond(x):
2810 2808 yield x
2811 2809
2812 2810 @property
2813 2811 def fastasc(self):
2814 2812 it = self._subset.fastasc
2815 2813 if it is None:
2816 2814 return None
2817 2815 return lambda: self._iterfilter(it())
2818 2816
2819 2817 @property
2820 2818 def fastdesc(self):
2821 2819 it = self._subset.fastdesc
2822 2820 if it is None:
2823 2821 return None
2824 2822 return lambda: self._iterfilter(it())
2825 2823
2826 2824 def __nonzero__(self):
2827 2825 for r in self:
2828 2826 return True
2829 2827 return False
2830 2828
2831 2829 def __len__(self):
2832 2830 # Basic implementation to be changed in future patches.
2833 2831 l = baseset([r for r in self])
2834 2832 return len(l)
2835 2833
2836 2834 def sort(self, reverse=False):
2837 2835 self._subset.sort(reverse=reverse)
2838 2836
2839 2837 def reverse(self):
2840 2838 self._subset.reverse()
2841 2839
2842 2840 def isascending(self):
2843 2841 return self._subset.isascending()
2844 2842
2845 2843 def isdescending(self):
2846 2844 return self._subset.isdescending()
2847 2845
2848 2846 def first(self):
2849 2847 for x in self:
2850 2848 return x
2851 2849 return None
2852 2850
2853 2851 def last(self):
2854 2852 it = None
2855 2853 if self._subset.isascending:
2856 2854 it = self.fastdesc
2857 2855 elif self._subset.isdescending:
2858 2856 it = self.fastdesc
2859 2857 if it is None:
2860 2858 # slowly consume everything. This needs improvement
2861 2859 it = lambda: reversed(list(self))
2862 2860 for x in it():
2863 2861 return x
2864 2862 return None
2865 2863
2866 2864 class addset(abstractsmartset):
2867 2865 """Represent the addition of two sets
2868 2866
2869 2867 Wrapper structure for lazily adding two structures without losing much
2870 2868 performance on the __contains__ method
2871 2869
2872 2870 If the ascending attribute is set, that means the two structures are
2873 2871 ordered in either an ascending or descending way. Therefore, we can add
2874 2872 them maintaining the order by iterating over both at the same time
2875 2873 """
2876 2874 def __init__(self, revs1, revs2, ascending=None):
2877 2875 self._r1 = revs1
2878 2876 self._r2 = revs2
2879 2877 self._iter = None
2880 2878 self._ascending = ascending
2881 2879 self._genlist = None
2882 2880 self._asclist = None
2883 2881
2884 2882 def __len__(self):
2885 2883 return len(self._list)
2886 2884
2887 2885 def __nonzero__(self):
2888 2886 return bool(self._r1) or bool(self._r2)
2889 2887
2890 2888 @util.propertycache
2891 2889 def _list(self):
2892 2890 if not self._genlist:
2893 2891 self._genlist = baseset(self._iterator())
2894 2892 return self._genlist
2895 2893
2896 2894 def _iterator(self):
2897 2895 """Iterate over both collections without repeating elements
2898 2896
2899 2897 If the ascending attribute is not set, iterate over the first one and
2900 2898 then over the second one checking for membership on the first one so we
2901 2899 dont yield any duplicates.
2902 2900
2903 2901 If the ascending attribute is set, iterate over both collections at the
2904 2902 same time, yielding only one value at a time in the given order.
2905 2903 """
2906 2904 if self._ascending is None:
2907 2905 def gen():
2908 2906 for r in self._r1:
2909 2907 yield r
2910 2908 inr1 = self._r1.__contains__
2911 2909 for r in self._r2:
2912 2910 if not inr1(r):
2913 2911 yield r
2914 2912 gen = gen()
2915 2913 else:
2916 2914 iter1 = iter(self._r1)
2917 2915 iter2 = iter(self._r2)
2918 2916 gen = self._iterordered(self._ascending, iter1, iter2)
2919 2917 return gen
2920 2918
2921 2919 def __iter__(self):
2922 2920 if self._ascending is None:
2923 2921 if self._genlist:
2924 2922 return iter(self._genlist)
2925 2923 return iter(self._iterator())
2926 2924 self._trysetasclist()
2927 2925 if self._ascending:
2928 2926 it = self.fastasc
2929 2927 else:
2930 2928 it = self.fastdesc
2931 2929 if it is None:
2932 2930 # consume the gen and try again
2933 2931 self._list
2934 2932 return iter(self)
2935 2933 return it()
2936 2934
2937 2935 def _trysetasclist(self):
2938 2936 """populate the _asclist attribute if possible and necessary"""
2939 2937 if self._genlist is not None and self._asclist is None:
2940 2938 self._asclist = sorted(self._genlist)
2941 2939
2942 2940 @property
2943 2941 def fastasc(self):
2944 2942 self._trysetasclist()
2945 2943 if self._asclist is not None:
2946 2944 return self._asclist.__iter__
2947 2945 iter1 = self._r1.fastasc
2948 2946 iter2 = self._r2.fastasc
2949 2947 if None in (iter1, iter2):
2950 2948 return None
2951 2949 return lambda: self._iterordered(True, iter1(), iter2())
2952 2950
2953 2951 @property
2954 2952 def fastdesc(self):
2955 2953 self._trysetasclist()
2956 2954 if self._asclist is not None:
2957 2955 return self._asclist.__reversed__
2958 2956 iter1 = self._r1.fastdesc
2959 2957 iter2 = self._r2.fastdesc
2960 2958 if None in (iter1, iter2):
2961 2959 return None
2962 2960 return lambda: self._iterordered(False, iter1(), iter2())
2963 2961
2964 2962 def _iterordered(self, ascending, iter1, iter2):
2965 2963 """produce an ordered iteration from two iterators with the same order
2966 2964
2967 2965 The ascending is used to indicated the iteration direction.
2968 2966 """
2969 2967 choice = max
2970 2968 if ascending:
2971 2969 choice = min
2972 2970
2973 2971 val1 = None
2974 2972 val2 = None
2975 2973
2976 2974 choice = max
2977 2975 if ascending:
2978 2976 choice = min
2979 2977 try:
2980 2978 # Consume both iterators in an ordered way until one is
2981 2979 # empty
2982 2980 while True:
2983 2981 if val1 is None:
2984 2982 val1 = iter1.next()
2985 2983 if val2 is None:
2986 2984 val2 = iter2.next()
2987 2985 next = choice(val1, val2)
2988 2986 yield next
2989 2987 if val1 == next:
2990 2988 val1 = None
2991 2989 if val2 == next:
2992 2990 val2 = None
2993 2991 except StopIteration:
2994 2992 # Flush any remaining values and consume the other one
2995 2993 it = iter2
2996 2994 if val1 is not None:
2997 2995 yield val1
2998 2996 it = iter1
2999 2997 elif val2 is not None:
3000 2998 # might have been equality and both are empty
3001 2999 yield val2
3002 3000 for val in it:
3003 3001 yield val
3004 3002
3005 3003 def __contains__(self, x):
3006 3004 return x in self._r1 or x in self._r2
3007 3005
3008 3006 def sort(self, reverse=False):
3009 3007 """Sort the added set
3010 3008
3011 3009 For this we use the cached list with all the generated values and if we
3012 3010 know they are ascending or descending we can sort them in a smart way.
3013 3011 """
3014 3012 self._ascending = not reverse
3015 3013
3016 3014 def isascending(self):
3017 3015 return self._ascending is not None and self._ascending
3018 3016
3019 3017 def isdescending(self):
3020 3018 return self._ascending is not None and not self._ascending
3021 3019
3022 3020 def reverse(self):
3023 3021 if self._ascending is None:
3024 3022 self._list.reverse()
3025 3023 else:
3026 3024 self._ascending = not self._ascending
3027 3025
3028 3026 def first(self):
3029 3027 for x in self:
3030 3028 return x
3031 3029 return None
3032 3030
3033 3031 def last(self):
3034 3032 self.reverse()
3035 3033 val = self.first()
3036 3034 self.reverse()
3037 3035 return val
3038 3036
3039 3037 class generatorset(abstractsmartset):
3040 3038 """Wrap a generator for lazy iteration
3041 3039
3042 3040 Wrapper structure for generators that provides lazy membership and can
3043 3041 be iterated more than once.
3044 3042 When asked for membership it generates values until either it finds the
3045 3043 requested one or has gone through all the elements in the generator
3046 3044 """
3047 3045 def __init__(self, gen, iterasc=None):
3048 3046 """
3049 3047 gen: a generator producing the values for the generatorset.
3050 3048 """
3051 3049 self._gen = gen
3052 3050 self._asclist = None
3053 3051 self._cache = {}
3054 3052 self._genlist = []
3055 3053 self._finished = False
3056 3054 self._ascending = True
3057 3055 if iterasc is not None:
3058 3056 if iterasc:
3059 3057 self.fastasc = self._iterator
3060 3058 self.__contains__ = self._asccontains
3061 3059 else:
3062 3060 self.fastdesc = self._iterator
3063 3061 self.__contains__ = self._desccontains
3064 3062
3065 3063 def __nonzero__(self):
3066 3064 for r in self:
3067 3065 return True
3068 3066 return False
3069 3067
3070 3068 def __contains__(self, x):
3071 3069 if x in self._cache:
3072 3070 return self._cache[x]
3073 3071
3074 3072 # Use new values only, as existing values would be cached.
3075 3073 for l in self._consumegen():
3076 3074 if l == x:
3077 3075 return True
3078 3076
3079 3077 self._cache[x] = False
3080 3078 return False
3081 3079
3082 3080 def _asccontains(self, x):
3083 3081 """version of contains optimised for ascending generator"""
3084 3082 if x in self._cache:
3085 3083 return self._cache[x]
3086 3084
3087 3085 # Use new values only, as existing values would be cached.
3088 3086 for l in self._consumegen():
3089 3087 if l == x:
3090 3088 return True
3091 3089 if l > x:
3092 3090 break
3093 3091
3094 3092 self._cache[x] = False
3095 3093 return False
3096 3094
3097 3095 def _desccontains(self, x):
3098 3096 """version of contains optimised for descending generator"""
3099 3097 if x in self._cache:
3100 3098 return self._cache[x]
3101 3099
3102 3100 # Use new values only, as existing values would be cached.
3103 3101 for l in self._consumegen():
3104 3102 if l == x:
3105 3103 return True
3106 3104 if l < x:
3107 3105 break
3108 3106
3109 3107 self._cache[x] = False
3110 3108 return False
3111 3109
3112 3110 def __iter__(self):
3113 3111 if self._ascending:
3114 3112 it = self.fastasc
3115 3113 else:
3116 3114 it = self.fastdesc
3117 3115 if it is not None:
3118 3116 return it()
3119 3117 # we need to consume the iterator
3120 3118 for x in self._consumegen():
3121 3119 pass
3122 3120 # recall the same code
3123 3121 return iter(self)
3124 3122
3125 3123 def _iterator(self):
3126 3124 if self._finished:
3127 3125 return iter(self._genlist)
3128 3126
3129 3127 # We have to use this complex iteration strategy to allow multiple
3130 3128 # iterations at the same time. We need to be able to catch revision
3131 3129 # removed from _consumegen and added to genlist in another instance.
3132 3130 #
3133 3131 # Getting rid of it would provide an about 15% speed up on this
3134 3132 # iteration.
3135 3133 genlist = self._genlist
3136 3134 nextrev = self._consumegen().next
3137 3135 _len = len # cache global lookup
3138 3136 def gen():
3139 3137 i = 0
3140 3138 while True:
3141 3139 if i < _len(genlist):
3142 3140 yield genlist[i]
3143 3141 else:
3144 3142 yield nextrev()
3145 3143 i += 1
3146 3144 return gen()
3147 3145
3148 3146 def _consumegen(self):
3149 3147 cache = self._cache
3150 3148 genlist = self._genlist.append
3151 3149 for item in self._gen:
3152 3150 cache[item] = True
3153 3151 genlist(item)
3154 3152 yield item
3155 3153 if not self._finished:
3156 3154 self._finished = True
3157 3155 asc = self._genlist[:]
3158 3156 asc.sort()
3159 3157 self._asclist = asc
3160 3158 self.fastasc = asc.__iter__
3161 3159 self.fastdesc = asc.__reversed__
3162 3160
3163 3161 def __len__(self):
3164 3162 for x in self._consumegen():
3165 3163 pass
3166 3164 return len(self._genlist)
3167 3165
3168 3166 def sort(self, reverse=False):
3169 3167 self._ascending = not reverse
3170 3168
3171 3169 def reverse(self):
3172 3170 self._ascending = not self._ascending
3173 3171
3174 3172 def isascending(self):
3175 3173 return self._ascending
3176 3174
3177 3175 def isdescending(self):
3178 3176 return not self._ascending
3179 3177
3180 3178 def first(self):
3181 3179 if self._ascending:
3182 3180 it = self.fastasc
3183 3181 else:
3184 3182 it = self.fastdesc
3185 3183 if it is None:
3186 3184 # we need to consume all and try again
3187 3185 for x in self._consumegen():
3188 3186 pass
3189 3187 return self.first()
3190 3188 if self:
3191 3189 return it().next()
3192 3190 return None
3193 3191
3194 3192 def last(self):
3195 3193 if self._ascending:
3196 3194 it = self.fastdesc
3197 3195 else:
3198 3196 it = self.fastasc
3199 3197 if it is None:
3200 3198 # we need to consume all and try again
3201 3199 for x in self._consumegen():
3202 3200 pass
3203 3201 return self.first()
3204 3202 if self:
3205 3203 return it().next()
3206 3204 return None
3207 3205
3208 3206 class spanset(abstractsmartset):
3209 3207 """Duck type for baseset class which represents a range of revisions and
3210 3208 can work lazily and without having all the range in memory
3211 3209
3212 3210 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3213 3211 notable points:
3214 3212 - when x < y it will be automatically descending,
3215 3213 - revision filtered with this repoview will be skipped.
3216 3214
3217 3215 """
3218 3216 def __init__(self, repo, start=0, end=None):
3219 3217 """
3220 3218 start: first revision included the set
3221 3219 (default to 0)
3222 3220 end: first revision excluded (last+1)
3223 3221 (default to len(repo)
3224 3222
3225 3223 Spanset will be descending if `end` < `start`.
3226 3224 """
3227 3225 if end is None:
3228 3226 end = len(repo)
3229 3227 self._ascending = start <= end
3230 3228 if not self._ascending:
3231 3229 start, end = end + 1, start +1
3232 3230 self._start = start
3233 3231 self._end = end
3234 3232 self._hiddenrevs = repo.changelog.filteredrevs
3235 3233
3236 3234 def sort(self, reverse=False):
3237 3235 self._ascending = not reverse
3238 3236
3239 3237 def reverse(self):
3240 3238 self._ascending = not self._ascending
3241 3239
3242 3240 def _iterfilter(self, iterrange):
3243 3241 s = self._hiddenrevs
3244 3242 for r in iterrange:
3245 3243 if r not in s:
3246 3244 yield r
3247 3245
3248 3246 def __iter__(self):
3249 3247 if self._ascending:
3250 3248 return self.fastasc()
3251 3249 else:
3252 3250 return self.fastdesc()
3253 3251
3254 3252 def fastasc(self):
3255 3253 iterrange = xrange(self._start, self._end)
3256 3254 if self._hiddenrevs:
3257 3255 return self._iterfilter(iterrange)
3258 3256 return iter(iterrange)
3259 3257
3260 3258 def fastdesc(self):
3261 3259 iterrange = xrange(self._end - 1, self._start - 1, -1)
3262 3260 if self._hiddenrevs:
3263 3261 return self._iterfilter(iterrange)
3264 3262 return iter(iterrange)
3265 3263
3266 3264 def __contains__(self, rev):
3267 3265 hidden = self._hiddenrevs
3268 3266 return ((self._start <= rev < self._end)
3269 3267 and not (hidden and rev in hidden))
3270 3268
3271 3269 def __nonzero__(self):
3272 3270 for r in self:
3273 3271 return True
3274 3272 return False
3275 3273
3276 3274 def __len__(self):
3277 3275 if not self._hiddenrevs:
3278 3276 return abs(self._end - self._start)
3279 3277 else:
3280 3278 count = 0
3281 3279 start = self._start
3282 3280 end = self._end
3283 3281 for rev in self._hiddenrevs:
3284 3282 if (end < rev <= start) or (start <= rev < end):
3285 3283 count += 1
3286 3284 return abs(self._end - self._start) - count
3287 3285
3288 3286 def isascending(self):
3289 3287 return self._ascending
3290 3288
3291 3289 def isdescending(self):
3292 3290 return not self._ascending
3293 3291
3294 3292 def first(self):
3295 3293 if self._ascending:
3296 3294 it = self.fastasc
3297 3295 else:
3298 3296 it = self.fastdesc
3299 3297 for x in it():
3300 3298 return x
3301 3299 return None
3302 3300
3303 3301 def last(self):
3304 3302 if self._ascending:
3305 3303 it = self.fastdesc
3306 3304 else:
3307 3305 it = self.fastasc
3308 3306 for x in it():
3309 3307 return x
3310 3308 return None
3311 3309
3312 3310 class fullreposet(spanset):
3313 3311 """a set containing all revisions in the repo
3314 3312
3315 This class exists to host special optimization.
3313 This class exists to host special optimization and magic to handle virtual
3314 revisions such as "null".
3316 3315 """
3317 3316
3318 3317 def __init__(self, repo):
3319 3318 super(fullreposet, self).__init__(repo)
3320 3319
3321 3320 def __contains__(self, rev):
3321 # assumes the given rev is valid
3322 3322 hidden = self._hiddenrevs
3323 return ((self._start <= rev < self._end)
3324 and not (hidden and rev in hidden))
3323 return not (hidden and rev in hidden)
3325 3324
3326 3325 def __and__(self, other):
3327 3326 """As self contains the whole repo, all of the other set should also be
3328 3327 in self. Therefore `self & other = other`.
3329 3328
3330 3329 This boldly assumes the other contains valid revs only.
3331 3330 """
3332 3331 # other not a smartset, make is so
3333 3332 if not util.safehasattr(other, 'isascending'):
3334 3333 # filter out hidden revision
3335 3334 # (this boldly assumes all smartset are pure)
3336 3335 #
3337 3336 # `other` was used with "&", let's assume this is a set like
3338 3337 # object.
3339 3338 other = baseset(other - self._hiddenrevs)
3340 3339
3341 3340 other.sort(reverse=self.isdescending())
3342 3341 return other
3343 3342
3344 3343 # tell hggettext to extract docstrings from these functions:
3345 3344 i18nfunctions = symbols.values()
@@ -1,836 +1,838 b''
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [phases]
3 3 > # public changeset are not obsolete
4 4 > publish=false
5 5 > [ui]
6 6 > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n"
7 7 > EOF
8 8 $ mkcommit() {
9 9 > echo "$1" > "$1"
10 10 > hg add "$1"
11 11 > hg ci -m "add $1"
12 12 > }
13 13 $ getid() {
14 14 > hg log -T "{node}\n" --hidden -r "desc('$1')"
15 15 > }
16 16
17 17 $ cat > debugkeys.py <<EOF
18 18 > def reposetup(ui, repo):
19 19 > class debugkeysrepo(repo.__class__):
20 20 > def listkeys(self, namespace):
21 21 > ui.write('listkeys %s\n' % (namespace,))
22 22 > return super(debugkeysrepo, self).listkeys(namespace)
23 23 >
24 24 > if repo.local():
25 25 > repo.__class__ = debugkeysrepo
26 26 > EOF
27 27
28 28 $ hg init tmpa
29 29 $ cd tmpa
30 30 $ mkcommit kill_me
31 31
32 32 Checking that the feature is properly disabled
33 33
34 34 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
35 35 abort: creating obsolete markers is not enabled on this repo
36 36 [255]
37 37
38 38 Enabling it
39 39
40 40 $ cat >> $HGRCPATH << EOF
41 41 > [experimental]
42 42 > evolution=createmarkers,exchange
43 43 > EOF
44 44
45 45 Killing a single changeset without replacement
46 46
47 47 $ hg debugobsolete 0
48 48 abort: changeset references must be full hexadecimal node identifiers
49 49 [255]
50 50 $ hg debugobsolete '00'
51 51 abort: changeset references must be full hexadecimal node identifiers
52 52 [255]
53 53 $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
54 54 $ hg debugobsolete
55 55 97b7c2d76b1845ed3eb988cd612611e72406cef0 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'babar'}
56 56
57 57 (test that mercurial is not confused)
58 58
59 59 $ hg up null --quiet # having 0 as parent prevents it to be hidden
60 60 $ hg tip
61 61 -1:000000000000 (public) [tip ]
62 62 $ hg up --hidden tip --quiet
63 63
64 64 Killing a single changeset with itself should fail
65 65 (simple local safeguard)
66 66
67 67 $ hg debugobsolete `getid kill_me` `getid kill_me`
68 68 abort: bad obsmarker input: in-marker cycle with 97b7c2d76b1845ed3eb988cd612611e72406cef0
69 69 [255]
70 70
71 71 $ cd ..
72 72
73 73 Killing a single changeset with replacement
74 74 (and testing the format option)
75 75
76 76 $ hg init tmpb
77 77 $ cd tmpb
78 78 $ mkcommit a
79 79 $ mkcommit b
80 80 $ mkcommit original_c
81 81 $ hg up "desc('b')"
82 82 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
83 83 $ mkcommit new_c
84 84 created new head
85 85 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
86 86 $ hg debugobsolete --config format.obsstore-version=0 --flag 12 `getid original_c` `getid new_c` -d '121 120'
87 87 $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
88 88 2:245bde4270cd add original_c
89 89 $ hg debugrevlog -cd
90 90 # rev p1rev p2rev start end deltastart base p1 p2 rawsize totalsize compression heads chainlen
91 91 0 -1 -1 0 59 0 0 0 0 58 58 0 1 0
92 92 1 0 -1 59 118 59 59 0 0 58 116 0 1 0
93 93 2 1 -1 118 193 118 118 59 0 76 192 0 1 0
94 94 3 1 -1 193 260 193 193 59 0 66 258 0 2 0
95 95 $ hg debugobsolete
96 96 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
97 97
98 98 (check for version number of the obsstore)
99 99
100 100 $ dd bs=1 count=1 if=.hg/store/obsstore 2>/dev/null
101 101 \x00 (no-eol) (esc)
102 102
103 103 do it again (it read the obsstore before adding new changeset)
104 104
105 105 $ hg up '.^'
106 106 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
107 107 $ mkcommit new_2_c
108 108 created new head
109 109 $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
110 110 $ hg debugobsolete
111 111 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
112 112 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
113 113
114 114 Register two markers with a missing node
115 115
116 116 $ hg up '.^'
117 117 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
118 118 $ mkcommit new_3_c
119 119 created new head
120 120 $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
121 121 $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
122 122 $ hg debugobsolete
123 123 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
124 124 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
125 125 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
126 126 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
127 127
128 128 Refuse pathological nullid successors
129 129 $ hg debugobsolete -d '9001 0' 1337133713371337133713371337133713371337 0000000000000000000000000000000000000000
130 130 transaction abort!
131 131 rollback completed
132 132 abort: bad obsolescence marker detected: invalid successors nullid
133 133 [255]
134 134
135 135 Check that graphlog detect that a changeset is obsolete:
136 136
137 137 $ hg log -G
138 138 @ 5:5601fb93a350 (draft) [tip ] add new_3_c
139 139 |
140 140 o 1:7c3bad9141dc (draft) [ ] add b
141 141 |
142 142 o 0:1f0dee641bb7 (draft) [ ] add a
143 143
144 144
145 145 check that heads does not report them
146 146
147 147 $ hg heads
148 148 5:5601fb93a350 (draft) [tip ] add new_3_c
149 149 $ hg heads --hidden
150 150 5:5601fb93a350 (draft) [tip ] add new_3_c
151 151 4:ca819180edb9 (draft) [ ] add new_2_c
152 152 3:cdbce2fbb163 (draft) [ ] add new_c
153 153 2:245bde4270cd (draft) [ ] add original_c
154 154
155 155
156 156 check that summary does not report them
157 157
158 158 $ hg init ../sink
159 159 $ echo '[paths]' >> .hg/hgrc
160 160 $ echo 'default=../sink' >> .hg/hgrc
161 161 $ hg summary --remote
162 162 parent: 5:5601fb93a350 tip
163 163 add new_3_c
164 164 branch: default
165 165 commit: (clean)
166 166 update: (current)
167 167 remote: 3 outgoing
168 168
169 169 $ hg summary --remote --hidden
170 170 parent: 5:5601fb93a350 tip
171 171 add new_3_c
172 172 branch: default
173 173 commit: (clean)
174 174 update: 3 new changesets, 4 branch heads (merge)
175 175 remote: 3 outgoing
176 176
177 177 check that various commands work well with filtering
178 178
179 179 $ hg tip
180 180 5:5601fb93a350 (draft) [tip ] add new_3_c
181 181 $ hg log -r 6
182 182 abort: unknown revision '6'!
183 183 [255]
184 184 $ hg log -r 4
185 185 abort: hidden revision '4'!
186 186 (use --hidden to access hidden revisions)
187 187 [255]
188 188 $ hg debugrevspec 'rev(6)'
189 189 $ hg debugrevspec 'rev(4)'
190 $ hg debugrevspec 'null'
191 -1
190 192
191 193 Check that public changeset are not accounted as obsolete:
192 194
193 195 $ hg --hidden phase --public 2
194 196 $ hg log -G
195 197 @ 5:5601fb93a350 (draft) [tip ] add new_3_c
196 198 |
197 199 | o 2:245bde4270cd (public) [ ] add original_c
198 200 |/
199 201 o 1:7c3bad9141dc (public) [ ] add b
200 202 |
201 203 o 0:1f0dee641bb7 (public) [ ] add a
202 204
203 205
204 206 And that bumped changeset are detected
205 207 --------------------------------------
206 208
207 209 If we didn't filtered obsolete changesets out, 3 and 4 would show up too. Also
208 210 note that the bumped changeset (5:5601fb93a350) is not a direct successor of
209 211 the public changeset
210 212
211 213 $ hg log --hidden -r 'bumped()'
212 214 5:5601fb93a350 (draft) [tip ] add new_3_c
213 215
214 216 And that we can't push bumped changeset
215 217
216 218 $ hg push ../tmpa -r 0 --force #(make repo related)
217 219 pushing to ../tmpa
218 220 searching for changes
219 221 warning: repository is unrelated
220 222 adding changesets
221 223 adding manifests
222 224 adding file changes
223 225 added 1 changesets with 1 changes to 1 files (+1 heads)
224 226 $ hg push ../tmpa
225 227 pushing to ../tmpa
226 228 searching for changes
227 229 abort: push includes bumped changeset: 5601fb93a350!
228 230 [255]
229 231
230 232 Fixing "bumped" situation
231 233 We need to create a clone of 5 and add a special marker with a flag
232 234
233 235 $ hg up '5^'
234 236 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
235 237 $ hg revert -ar 5
236 238 adding new_3_c
237 239 $ hg ci -m 'add n3w_3_c'
238 240 created new head
239 241 $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
240 242 $ hg log -r 'bumped()'
241 243 $ hg log -G
242 244 @ 6:6f9641995072 (draft) [tip ] add n3w_3_c
243 245 |
244 246 | o 2:245bde4270cd (public) [ ] add original_c
245 247 |/
246 248 o 1:7c3bad9141dc (public) [ ] add b
247 249 |
248 250 o 0:1f0dee641bb7 (public) [ ] add a
249 251
250 252
251 253
252 254
253 255 $ cd ..
254 256
255 257 Exchange Test
256 258 ============================
257 259
258 260 Destination repo does not have any data
259 261 ---------------------------------------
260 262
261 263 Simple incoming test
262 264
263 265 $ hg init tmpc
264 266 $ cd tmpc
265 267 $ hg incoming ../tmpb
266 268 comparing with ../tmpb
267 269 0:1f0dee641bb7 (public) [ ] add a
268 270 1:7c3bad9141dc (public) [ ] add b
269 271 2:245bde4270cd (public) [ ] add original_c
270 272 6:6f9641995072 (draft) [tip ] add n3w_3_c
271 273
272 274 Try to pull markers
273 275 (extinct changeset are excluded but marker are pushed)
274 276
275 277 $ hg pull ../tmpb
276 278 pulling from ../tmpb
277 279 requesting all changes
278 280 adding changesets
279 281 adding manifests
280 282 adding file changes
281 283 added 4 changesets with 4 changes to 4 files (+1 heads)
282 284 (run 'hg heads' to see heads, 'hg merge' to merge)
283 285 $ hg debugobsolete
284 286 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
285 287 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
286 288 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
287 289 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
288 290 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
289 291
290 292 Rollback//Transaction support
291 293
292 294 $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
293 295 $ hg debugobsolete
294 296 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
295 297 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
296 298 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
297 299 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
298 300 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
299 301 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 (Thu Jan 01 00:22:20 1970 +0000) {'user': 'test'}
300 302 $ hg rollback -n
301 303 repository tip rolled back to revision 3 (undo debugobsolete)
302 304 $ hg rollback
303 305 repository tip rolled back to revision 3 (undo debugobsolete)
304 306 $ hg debugobsolete
305 307 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
306 308 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
307 309 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
308 310 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
309 311 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
310 312
311 313 $ cd ..
312 314
313 315 Try to push markers
314 316
315 317 $ hg init tmpd
316 318 $ hg -R tmpb push tmpd
317 319 pushing to tmpd
318 320 searching for changes
319 321 adding changesets
320 322 adding manifests
321 323 adding file changes
322 324 added 4 changesets with 4 changes to 4 files (+1 heads)
323 325 $ hg -R tmpd debugobsolete | sort
324 326 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
325 327 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
326 328 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
327 329 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
328 330 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
329 331
330 332 Check obsolete keys are exchanged only if source has an obsolete store
331 333
332 334 $ hg init empty
333 335 $ hg --config extensions.debugkeys=debugkeys.py -R empty push tmpd
334 336 pushing to tmpd
335 337 listkeys phases
336 338 listkeys bookmarks
337 339 no changes found
338 340 listkeys phases
339 341 [1]
340 342
341 343 clone support
342 344 (markers are copied and extinct changesets are included to allow hardlinks)
343 345
344 346 $ hg clone tmpb clone-dest
345 347 updating to branch default
346 348 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
347 349 $ hg -R clone-dest log -G --hidden
348 350 @ 6:6f9641995072 (draft) [tip ] add n3w_3_c
349 351 |
350 352 | x 5:5601fb93a350 (draft) [ ] add new_3_c
351 353 |/
352 354 | x 4:ca819180edb9 (draft) [ ] add new_2_c
353 355 |/
354 356 | x 3:cdbce2fbb163 (draft) [ ] add new_c
355 357 |/
356 358 | o 2:245bde4270cd (public) [ ] add original_c
357 359 |/
358 360 o 1:7c3bad9141dc (public) [ ] add b
359 361 |
360 362 o 0:1f0dee641bb7 (public) [ ] add a
361 363
362 364 $ hg -R clone-dest debugobsolete
363 365 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
364 366 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
365 367 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
366 368 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
367 369 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
368 370
369 371
370 372 Destination repo have existing data
371 373 ---------------------------------------
372 374
373 375 On pull
374 376
375 377 $ hg init tmpe
376 378 $ cd tmpe
377 379 $ hg debugobsolete -d '1339 0' 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00
378 380 $ hg pull ../tmpb
379 381 pulling from ../tmpb
380 382 requesting all changes
381 383 adding changesets
382 384 adding manifests
383 385 adding file changes
384 386 added 4 changesets with 4 changes to 4 files (+1 heads)
385 387 (run 'hg heads' to see heads, 'hg merge' to merge)
386 388 $ hg debugobsolete
387 389 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
388 390 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
389 391 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
390 392 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
391 393 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
392 394 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
393 395
394 396
395 397 On push
396 398
397 399 $ hg push ../tmpc
398 400 pushing to ../tmpc
399 401 searching for changes
400 402 no changes found
401 403 [1]
402 404 $ hg -R ../tmpc debugobsolete
403 405 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
404 406 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
405 407 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
406 408 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
407 409 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
408 410 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
409 411
410 412 detect outgoing obsolete and unstable
411 413 ---------------------------------------
412 414
413 415
414 416 $ hg log -G
415 417 o 3:6f9641995072 (draft) [tip ] add n3w_3_c
416 418 |
417 419 | o 2:245bde4270cd (public) [ ] add original_c
418 420 |/
419 421 o 1:7c3bad9141dc (public) [ ] add b
420 422 |
421 423 o 0:1f0dee641bb7 (public) [ ] add a
422 424
423 425 $ hg up 'desc("n3w_3_c")'
424 426 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
425 427 $ mkcommit original_d
426 428 $ mkcommit original_e
427 429 $ hg debugobsolete --record-parents `getid original_d` -d '0 0'
428 430 $ hg debugobsolete | grep `getid original_d`
429 431 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
430 432 $ hg log -r 'obsolete()'
431 433 4:94b33453f93b (draft) [ ] add original_d
432 434 $ hg log -G -r '::unstable()'
433 435 @ 5:cda648ca50f5 (draft) [tip ] add original_e
434 436 |
435 437 x 4:94b33453f93b (draft) [ ] add original_d
436 438 |
437 439 o 3:6f9641995072 (draft) [ ] add n3w_3_c
438 440 |
439 441 o 1:7c3bad9141dc (public) [ ] add b
440 442 |
441 443 o 0:1f0dee641bb7 (public) [ ] add a
442 444
443 445
444 446 refuse to push obsolete changeset
445 447
446 448 $ hg push ../tmpc/ -r 'desc("original_d")'
447 449 pushing to ../tmpc/
448 450 searching for changes
449 451 abort: push includes obsolete changeset: 94b33453f93b!
450 452 [255]
451 453
452 454 refuse to push unstable changeset
453 455
454 456 $ hg push ../tmpc/
455 457 pushing to ../tmpc/
456 458 searching for changes
457 459 abort: push includes unstable changeset: cda648ca50f5!
458 460 [255]
459 461
460 462 Test that extinct changeset are properly detected
461 463
462 464 $ hg log -r 'extinct()'
463 465
464 466 Don't try to push extinct changeset
465 467
466 468 $ hg init ../tmpf
467 469 $ hg out ../tmpf
468 470 comparing with ../tmpf
469 471 searching for changes
470 472 0:1f0dee641bb7 (public) [ ] add a
471 473 1:7c3bad9141dc (public) [ ] add b
472 474 2:245bde4270cd (public) [ ] add original_c
473 475 3:6f9641995072 (draft) [ ] add n3w_3_c
474 476 4:94b33453f93b (draft) [ ] add original_d
475 477 5:cda648ca50f5 (draft) [tip ] add original_e
476 478 $ hg push ../tmpf -f # -f because be push unstable too
477 479 pushing to ../tmpf
478 480 searching for changes
479 481 adding changesets
480 482 adding manifests
481 483 adding file changes
482 484 added 6 changesets with 6 changes to 6 files (+1 heads)
483 485
484 486 no warning displayed
485 487
486 488 $ hg push ../tmpf
487 489 pushing to ../tmpf
488 490 searching for changes
489 491 no changes found
490 492 [1]
491 493
492 494 Do not warn about new head when the new head is a successors of a remote one
493 495
494 496 $ hg log -G
495 497 @ 5:cda648ca50f5 (draft) [tip ] add original_e
496 498 |
497 499 x 4:94b33453f93b (draft) [ ] add original_d
498 500 |
499 501 o 3:6f9641995072 (draft) [ ] add n3w_3_c
500 502 |
501 503 | o 2:245bde4270cd (public) [ ] add original_c
502 504 |/
503 505 o 1:7c3bad9141dc (public) [ ] add b
504 506 |
505 507 o 0:1f0dee641bb7 (public) [ ] add a
506 508
507 509 $ hg up -q 'desc(n3w_3_c)'
508 510 $ mkcommit obsolete_e
509 511 created new head
510 512 $ hg debugobsolete `getid 'original_e'` `getid 'obsolete_e'`
511 513 $ hg outgoing ../tmpf # parasite hg outgoing testin
512 514 comparing with ../tmpf
513 515 searching for changes
514 516 6:3de5eca88c00 (draft) [tip ] add obsolete_e
515 517 $ hg push ../tmpf
516 518 pushing to ../tmpf
517 519 searching for changes
518 520 adding changesets
519 521 adding manifests
520 522 adding file changes
521 523 added 1 changesets with 1 changes to 1 files (+1 heads)
522 524
523 525 test relevance computation
524 526 ---------------------------------------
525 527
526 528 Checking simple case of "marker relevance".
527 529
528 530
529 531 Reminder of the repo situation
530 532
531 533 $ hg log --hidden --graph
532 534 @ 6:3de5eca88c00 (draft) [tip ] add obsolete_e
533 535 |
534 536 | x 5:cda648ca50f5 (draft) [ ] add original_e
535 537 | |
536 538 | x 4:94b33453f93b (draft) [ ] add original_d
537 539 |/
538 540 o 3:6f9641995072 (draft) [ ] add n3w_3_c
539 541 |
540 542 | o 2:245bde4270cd (public) [ ] add original_c
541 543 |/
542 544 o 1:7c3bad9141dc (public) [ ] add b
543 545 |
544 546 o 0:1f0dee641bb7 (public) [ ] add a
545 547
546 548
547 549 List of all markers
548 550
549 551 $ hg debugobsolete
550 552 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
551 553 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
552 554 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
553 555 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
554 556 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
555 557 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
556 558 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
557 559 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
558 560
559 561 List of changesets with no chain
560 562
561 563 $ hg debugobsolete --hidden --rev ::2
562 564
563 565 List of changesets that are included on marker chain
564 566
565 567 $ hg debugobsolete --hidden --rev 6
566 568 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
567 569
568 570 List of changesets with a longer chain, (including a pruned children)
569 571
570 572 $ hg debugobsolete --hidden --rev 3
571 573 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
572 574 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
573 575 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
574 576 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
575 577 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
576 578 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
577 579 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
578 580
579 581 List of both
580 582
581 583 $ hg debugobsolete --hidden --rev 3::6
582 584 1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
583 585 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
584 586 245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Thu Jan 01 00:00:01 1970 -0002) {'user': 'test'}
585 587 5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
586 588 94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
587 589 ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
588 590 cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
589 591 cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
590 592
591 593 #if serve
592 594
593 595 check hgweb does not explode
594 596 ====================================
595 597
596 598 $ hg unbundle $TESTDIR/bundles/hgweb+obs.hg
597 599 adding changesets
598 600 adding manifests
599 601 adding file changes
600 602 added 62 changesets with 63 changes to 9 files (+60 heads)
601 603 (run 'hg heads .' to see heads, 'hg merge' to merge)
602 604 $ for node in `hg log -r 'desc(babar_)' --template '{node}\n'`;
603 605 > do
604 606 > hg debugobsolete $node
605 607 > done
606 608 $ hg up tip
607 609 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
608 610
609 611 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
610 612 $ cat hg.pid >> $DAEMON_PIDS
611 613
612 614 check changelog view
613 615
614 616 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'shortlog/'
615 617 200 Script output follows
616 618
617 619 check graph view
618 620
619 621 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'graph'
620 622 200 Script output follows
621 623
622 624 check filelog view
623 625
624 626 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'log/'`hg log -r . -T "{node}"`/'babar'
625 627 200 Script output follows
626 628
627 629 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/68'
628 630 200 Script output follows
629 631 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/67'
630 632 404 Not Found
631 633 [1]
632 634
633 635 check that web.view config option:
634 636
635 637 $ "$TESTDIR/killdaemons.py" hg.pid
636 638 $ cat >> .hg/hgrc << EOF
637 639 > [web]
638 640 > view=all
639 641 > EOF
640 642 $ wait
641 643 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
642 644 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/67'
643 645 200 Script output follows
644 646 $ "$TESTDIR/killdaemons.py" hg.pid
645 647
646 648 Checking _enable=False warning if obsolete marker exists
647 649
648 650 $ echo '[experimental]' >> $HGRCPATH
649 651 $ echo "evolution=" >> $HGRCPATH
650 652 $ hg log -r tip
651 653 obsolete feature not enabled but 68 markers found!
652 654 68:c15e9edfca13 (draft) [tip ] add celestine
653 655
654 656 reenable for later test
655 657
656 658 $ echo '[experimental]' >> $HGRCPATH
657 659 $ echo "evolution=createmarkers,exchange" >> $HGRCPATH
658 660
659 661 #endif
660 662
661 663 Test incoming/outcoming with changesets obsoleted remotely, known locally
662 664 ===============================================================================
663 665
664 666 This test issue 3805
665 667
666 668 $ hg init repo-issue3805
667 669 $ cd repo-issue3805
668 670 $ echo "foo" > foo
669 671 $ hg ci -Am "A"
670 672 adding foo
671 673 $ hg clone . ../other-issue3805
672 674 updating to branch default
673 675 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
674 676 $ echo "bar" >> foo
675 677 $ hg ci --amend
676 678 $ cd ../other-issue3805
677 679 $ hg log -G
678 680 @ 0:193e9254ce7e (draft) [tip ] A
679 681
680 682 $ hg log -G -R ../repo-issue3805
681 683 @ 2:3816541e5485 (draft) [tip ] A
682 684
683 685 $ hg incoming
684 686 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
685 687 searching for changes
686 688 2:3816541e5485 (draft) [tip ] A
687 689 $ hg incoming --bundle ../issue3805.hg
688 690 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
689 691 searching for changes
690 692 2:3816541e5485 (draft) [tip ] A
691 693 $ hg outgoing
692 694 comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
693 695 searching for changes
694 696 no changes found
695 697 [1]
696 698
697 699 #if serve
698 700
699 701 $ hg serve -R ../repo-issue3805 -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
700 702 $ cat hg.pid >> $DAEMON_PIDS
701 703
702 704 $ hg incoming http://localhost:$HGPORT
703 705 comparing with http://localhost:$HGPORT/
704 706 searching for changes
705 707 1:3816541e5485 (draft) [tip ] A
706 708 $ hg outgoing http://localhost:$HGPORT
707 709 comparing with http://localhost:$HGPORT/
708 710 searching for changes
709 711 no changes found
710 712 [1]
711 713
712 714 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
713 715
714 716 #endif
715 717
716 718 This test issue 3814
717 719
718 720 (nothing to push but locally hidden changeset)
719 721
720 722 $ cd ..
721 723 $ hg init repo-issue3814
722 724 $ cd repo-issue3805
723 725 $ hg push -r 3816541e5485 ../repo-issue3814
724 726 pushing to ../repo-issue3814
725 727 searching for changes
726 728 adding changesets
727 729 adding manifests
728 730 adding file changes
729 731 added 1 changesets with 1 changes to 1 files
730 732 $ hg out ../repo-issue3814
731 733 comparing with ../repo-issue3814
732 734 searching for changes
733 735 no changes found
734 736 [1]
735 737
736 738 Test that a local tag blocks a changeset from being hidden
737 739
738 740 $ hg tag -l visible -r 0 --hidden
739 741 $ hg log -G
740 742 @ 2:3816541e5485 (draft) [tip ] A
741 743
742 744 x 0:193e9254ce7e (draft) [visible ] A
743 745
744 746 Test that removing a local tag does not cause some commands to fail
745 747
746 748 $ hg tag -l -r tip tiptag
747 749 $ hg tags
748 750 tiptag 2:3816541e5485
749 751 tip 2:3816541e5485
750 752 visible 0:193e9254ce7e
751 753 $ hg --config extensions.strip= strip -r tip --no-backup
752 754 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
753 755 $ hg tags
754 756 visible 0:193e9254ce7e
755 757 tip 0:193e9254ce7e
756 758
757 759 #if serve
758 760
759 761 Test issue 4506
760 762
761 763 $ cd ..
762 764 $ hg init repo-issue4506
763 765 $ cd repo-issue4506
764 766 $ echo "0" > foo
765 767 $ hg add foo
766 768 $ hg ci -m "content-0"
767 769
768 770 $ hg up null
769 771 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
770 772 $ echo "1" > bar
771 773 $ hg add bar
772 774 $ hg ci -m "content-1"
773 775 created new head
774 776 $ hg up 0
775 777 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
776 778 $ hg graft 1
777 779 grafting 1:1c9eddb02162 "content-1" (tip)
778 780
779 781 $ hg debugobsolete `hg log -r1 -T'{node}'` `hg log -r2 -T'{node}'`
780 782
781 783 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
782 784 $ cat hg.pid >> $DAEMON_PIDS
783 785
784 786 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'rev/1'
785 787 404 Not Found
786 788 [1]
787 789 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'file/tip/bar'
788 790 200 Script output follows
789 791 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'annotate/tip/bar'
790 792 200 Script output follows
791 793
792 794 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
793 795
794 796 #endif
795 797
796 798 $ hg init a
797 799 $ cd a
798 800 $ touch foo
799 801 $ hg add foo
800 802 $ hg ci -mfoo
801 803 $ touch bar
802 804 $ hg add bar
803 805 $ hg ci -mbar
804 806 $ hg up 0
805 807 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
806 808 $ touch quux
807 809 $ hg add quux
808 810 $ hg ci -m quux
809 811 created new head
810 812 $ hg up 1
811 813 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
812 814 $ hg tag 1.0
813 815
814 816 $ hg up 2
815 817 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
816 818 $ hg log -G
817 819 o 3:bc47fc7e1c1d (draft) [tip ] Added tag 1.0 for changeset 50c889141114
818 820 |
819 821 | @ 2:3d7f255a0081 (draft) [ ] quux
820 822 | |
821 823 o | 1:50c889141114 (draft) [1.0 ] bar
822 824 |/
823 825 o 0:1f7b0de80e11 (draft) [ ] foo
824 826
825 827 $ hg debugobsolete `getid bar`
826 828 $ hg debugobsolete `getid 1.0`
827 829 $ hg tag 1.0
828 830 $ hg log -G
829 831 @ 4:f9f2ab71ffd5 (draft) [tip ] Added tag 1.0 for changeset 3d7f255a0081
830 832 |
831 833 o 2:3d7f255a0081 (draft) [1.0 ] quux
832 834 |
833 835 o 0:1f7b0de80e11 (draft) [ ] foo
834 836
835 837 $ cat .hgtags
836 838 3d7f255a008103380aeb2a7d581fe257f40969e7 1.0
@@ -1,1363 +1,1388 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 $ echo a > a
16 16 $ hg branch a
17 17 marked working directory as branch a
18 18 (branches are permanent and global, did you want a bookmark?)
19 19 $ hg ci -Aqm0
20 20
21 21 $ echo b > b
22 22 $ hg branch b
23 23 marked working directory as branch b
24 24 (branches are permanent and global, did you want a bookmark?)
25 25 $ hg ci -Aqm1
26 26
27 27 $ rm a
28 28 $ hg branch a-b-c-
29 29 marked working directory as branch a-b-c-
30 30 (branches are permanent and global, did you want a bookmark?)
31 31 $ hg ci -Aqm2 -u Bob
32 32
33 33 $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
34 34 2
35 35 $ hg log -r "extra('branch')" --template '{rev}\n'
36 36 0
37 37 1
38 38 2
39 39 $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
40 40 0 a
41 41 2 a-b-c-
42 42
43 43 $ hg co 1
44 44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
45 45 $ hg branch +a+b+c+
46 46 marked working directory as branch +a+b+c+
47 47 (branches are permanent and global, did you want a bookmark?)
48 48 $ hg ci -Aqm3
49 49
50 50 $ hg co 2 # interleave
51 51 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
52 52 $ echo bb > b
53 53 $ hg branch -- -a-b-c-
54 54 marked working directory as branch -a-b-c-
55 55 (branches are permanent and global, did you want a bookmark?)
56 56 $ hg ci -Aqm4 -d "May 12 2005"
57 57
58 58 $ hg co 3
59 59 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
60 60 $ hg branch !a/b/c/
61 61 marked working directory as branch !a/b/c/
62 62 (branches are permanent and global, did you want a bookmark?)
63 63 $ hg ci -Aqm"5 bug"
64 64
65 65 $ hg merge 4
66 66 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
67 67 (branch merge, don't forget to commit)
68 68 $ hg branch _a_b_c_
69 69 marked working directory as branch _a_b_c_
70 70 (branches are permanent and global, did you want a bookmark?)
71 71 $ hg ci -Aqm"6 issue619"
72 72
73 73 $ hg branch .a.b.c.
74 74 marked working directory as branch .a.b.c.
75 75 (branches are permanent and global, did you want a bookmark?)
76 76 $ hg ci -Aqm7
77 77
78 78 $ hg branch all
79 79 marked working directory as branch all
80 80 (branches are permanent and global, did you want a bookmark?)
81 81
82 82 $ hg co 4
83 83 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
84 84 $ hg branch Γ©
85 85 marked working directory as branch \xc3\xa9 (esc)
86 86 (branches are permanent and global, did you want a bookmark?)
87 87 $ hg ci -Aqm9
88 88
89 89 $ hg tag -r6 1.0
90 90
91 91 $ hg clone --quiet -U -r 7 . ../remote1
92 92 $ hg clone --quiet -U -r 8 . ../remote2
93 93 $ echo "[paths]" >> .hg/hgrc
94 94 $ echo "default = ../remote1" >> .hg/hgrc
95 95
96 96 names that should work without quoting
97 97
98 98 $ try a
99 99 ('symbol', 'a')
100 100 0
101 101 $ try b-a
102 102 (minus
103 103 ('symbol', 'b')
104 104 ('symbol', 'a'))
105 105 1
106 106 $ try _a_b_c_
107 107 ('symbol', '_a_b_c_')
108 108 6
109 109 $ try _a_b_c_-a
110 110 (minus
111 111 ('symbol', '_a_b_c_')
112 112 ('symbol', 'a'))
113 113 6
114 114 $ try .a.b.c.
115 115 ('symbol', '.a.b.c.')
116 116 7
117 117 $ try .a.b.c.-a
118 118 (minus
119 119 ('symbol', '.a.b.c.')
120 120 ('symbol', 'a'))
121 121 7
122 122 $ try -- '-a-b-c-' # complains
123 123 hg: parse error at 7: not a prefix: end
124 124 [255]
125 125 $ log -a-b-c- # succeeds with fallback
126 126 4
127 127
128 128 $ try -- -a-b-c--a # complains
129 129 (minus
130 130 (minus
131 131 (minus
132 132 (negate
133 133 ('symbol', 'a'))
134 134 ('symbol', 'b'))
135 135 ('symbol', 'c'))
136 136 (negate
137 137 ('symbol', 'a')))
138 138 abort: unknown revision '-a'!
139 139 [255]
140 140 $ try Γ©
141 141 ('symbol', '\xc3\xa9')
142 142 9
143 143
144 144 no quoting needed
145 145
146 146 $ log ::a-b-c-
147 147 0
148 148 1
149 149 2
150 150
151 151 quoting needed
152 152
153 153 $ try '"-a-b-c-"-a'
154 154 (minus
155 155 ('string', '-a-b-c-')
156 156 ('symbol', 'a'))
157 157 4
158 158
159 159 $ log '1 or 2'
160 160 1
161 161 2
162 162 $ log '1|2'
163 163 1
164 164 2
165 165 $ log '1 and 2'
166 166 $ log '1&2'
167 167 $ try '1&2|3' # precedence - and is higher
168 168 (or
169 169 (and
170 170 ('symbol', '1')
171 171 ('symbol', '2'))
172 172 ('symbol', '3'))
173 173 3
174 174 $ try '1|2&3'
175 175 (or
176 176 ('symbol', '1')
177 177 (and
178 178 ('symbol', '2')
179 179 ('symbol', '3')))
180 180 1
181 181 $ try '1&2&3' # associativity
182 182 (and
183 183 (and
184 184 ('symbol', '1')
185 185 ('symbol', '2'))
186 186 ('symbol', '3'))
187 187 $ try '1|(2|3)'
188 188 (or
189 189 ('symbol', '1')
190 190 (group
191 191 (or
192 192 ('symbol', '2')
193 193 ('symbol', '3'))))
194 194 1
195 195 2
196 196 3
197 197 $ log '1.0' # tag
198 198 6
199 199 $ log 'a' # branch
200 200 0
201 201 $ log '2785f51ee'
202 202 0
203 203 $ log 'date(2005)'
204 204 4
205 205 $ log 'date(this is a test)'
206 206 hg: parse error at 10: unexpected token: symbol
207 207 [255]
208 208 $ log 'date()'
209 209 hg: parse error: date requires a string
210 210 [255]
211 211 $ log 'date'
212 212 hg: parse error: can't use date here
213 213 [255]
214 214 $ log 'date('
215 215 hg: parse error at 5: not a prefix: end
216 216 [255]
217 217 $ log 'date(tip)'
218 218 abort: invalid date: 'tip'
219 219 [255]
220 220 $ log '"date"'
221 221 abort: unknown revision 'date'!
222 222 [255]
223 223 $ log 'date(2005) and 1::'
224 224 4
225 225
226 226 ancestor can accept 0 or more arguments
227 227
228 228 $ log 'ancestor()'
229 229 $ log 'ancestor(1)'
230 230 1
231 231 $ log 'ancestor(4,5)'
232 232 1
233 233 $ log 'ancestor(4,5) and 4'
234 234 $ log 'ancestor(0,0,1,3)'
235 235 0
236 236 $ log 'ancestor(3,1,5,3,5,1)'
237 237 1
238 238 $ log 'ancestor(0,1,3,5)'
239 239 0
240 240 $ log 'ancestor(1,2,3,4,5)'
241 241 1
242 242 $ log 'ancestors(5)'
243 243 0
244 244 1
245 245 3
246 246 5
247 247 $ log 'ancestor(ancestors(5))'
248 248 0
249 249 $ log 'author(bob)'
250 250 2
251 251 $ log 'author("re:bob|test")'
252 252 0
253 253 1
254 254 2
255 255 3
256 256 4
257 257 5
258 258 6
259 259 7
260 260 8
261 261 9
262 262 $ log 'branch(Γ©)'
263 263 8
264 264 9
265 265 $ log 'branch(a)'
266 266 0
267 267 $ hg log -r 'branch("re:a")' --template '{rev} {branch}\n'
268 268 0 a
269 269 2 a-b-c-
270 270 3 +a+b+c+
271 271 4 -a-b-c-
272 272 5 !a/b/c/
273 273 6 _a_b_c_
274 274 7 .a.b.c.
275 275 $ log 'children(ancestor(4,5))'
276 276 2
277 277 3
278 278 $ log 'closed()'
279 279 $ log 'contains(a)'
280 280 0
281 281 1
282 282 3
283 283 5
284 284 $ log 'contains("../repo/a")'
285 285 0
286 286 1
287 287 3
288 288 5
289 289 $ log 'desc(B)'
290 290 5
291 291 $ log 'descendants(2 or 3)'
292 292 2
293 293 3
294 294 4
295 295 5
296 296 6
297 297 7
298 298 8
299 299 9
300 300 $ log 'file("b*")'
301 301 1
302 302 4
303 303 $ log 'filelog("b")'
304 304 1
305 305 4
306 306 $ log 'filelog("../repo/b")'
307 307 1
308 308 4
309 309 $ log 'follow()'
310 310 0
311 311 1
312 312 2
313 313 4
314 314 8
315 315 9
316 316 $ log 'grep("issue\d+")'
317 317 6
318 318 $ try 'grep("(")' # invalid regular expression
319 319 (func
320 320 ('symbol', 'grep')
321 321 ('string', '('))
322 322 hg: parse error: invalid match pattern: unbalanced parenthesis
323 323 [255]
324 324 $ try 'grep("\bissue\d+")'
325 325 (func
326 326 ('symbol', 'grep')
327 327 ('string', '\x08issue\\d+'))
328 328 $ try 'grep(r"\bissue\d+")'
329 329 (func
330 330 ('symbol', 'grep')
331 331 ('string', '\\bissue\\d+'))
332 332 6
333 333 $ try 'grep(r"\")'
334 334 hg: parse error at 7: unterminated string
335 335 [255]
336 336 $ log 'head()'
337 337 0
338 338 1
339 339 2
340 340 3
341 341 4
342 342 5
343 343 6
344 344 7
345 345 9
346 346 $ log 'heads(6::)'
347 347 7
348 348 $ log 'keyword(issue)'
349 349 6
350 350 $ log 'keyword("test a")'
351 351 $ log 'limit(head(), 1)'
352 352 0
353 353 $ log 'matching(6)'
354 354 6
355 355 $ log 'matching(6:7, "phase parents user date branch summary files description substate")'
356 356 6
357 357 7
358 358
359 359 Testing min and max
360 360
361 361 max: simple
362 362
363 363 $ log 'max(contains(a))'
364 364 5
365 365
366 366 max: simple on unordered set)
367 367
368 368 $ log 'max((4+0+2+5+7) and contains(a))'
369 369 5
370 370
371 371 max: no result
372 372
373 373 $ log 'max(contains(stringthatdoesnotappearanywhere))'
374 374
375 375 max: no result on unordered set
376 376
377 377 $ log 'max((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
378 378
379 379 min: simple
380 380
381 381 $ log 'min(contains(a))'
382 382 0
383 383
384 384 min: simple on unordered set
385 385
386 386 $ log 'min((4+0+2+5+7) and contains(a))'
387 387 0
388 388
389 389 min: empty
390 390
391 391 $ log 'min(contains(stringthatdoesnotappearanywhere))'
392 392
393 393 min: empty on unordered set
394 394
395 395 $ log 'min((4+0+2+5+7) and contains(stringthatdoesnotappearanywhere))'
396 396
397 397
398 398 $ log 'merge()'
399 399 6
400 400 $ log 'branchpoint()'
401 401 1
402 402 4
403 403 $ log 'modifies(b)'
404 404 4
405 405 $ log 'modifies("path:b")'
406 406 4
407 407 $ log 'modifies("*")'
408 408 4
409 409 6
410 410 $ log 'modifies("set:modified()")'
411 411 4
412 412 $ log 'id(5)'
413 413 2
414 414 $ log 'only(9)'
415 415 8
416 416 9
417 417 $ log 'only(8)'
418 418 8
419 419 $ log 'only(9, 5)'
420 420 2
421 421 4
422 422 8
423 423 9
424 424 $ log 'only(7 + 9, 5 + 2)'
425 425 4
426 426 6
427 427 7
428 428 8
429 429 9
430 430
431 431 Test empty set input
432 432 $ log 'only(p2())'
433 433 $ log 'only(p1(), p2())'
434 434 0
435 435 1
436 436 2
437 437 4
438 438 8
439 439 9
440 440
441 441 Test '%' operator
442 442
443 443 $ log '9%'
444 444 8
445 445 9
446 446 $ log '9%5'
447 447 2
448 448 4
449 449 8
450 450 9
451 451 $ log '(7 + 9)%(5 + 2)'
452 452 4
453 453 6
454 454 7
455 455 8
456 456 9
457 457
458 458 Test the order of operations
459 459
460 460 $ log '7 + 9%5 + 2'
461 461 7
462 462 2
463 463 4
464 464 8
465 465 9
466 466
467 467 Test explicit numeric revision
468 468 $ log 'rev(-2)'
469 469 $ log 'rev(-1)'
470 470 -1
471 471 $ log 'rev(0)'
472 472 0
473 473 $ log 'rev(9)'
474 474 9
475 475 $ log 'rev(10)'
476 476 $ log 'rev(tip)'
477 477 hg: parse error: rev expects a number
478 478 [255]
479 479
480 480 Test null revision
481 $ log '(null)'
482 -1
483 $ log '(null:0)'
484 -1
485 0
486 $ log '(0:null)'
487 0
488 -1
489 $ log 'null::0'
490 -1
491 0
492 $ log 'null:tip - 0:'
493 -1
494 $ log 'null: and null::' | head -1
495 -1
496 $ log 'null: or 0:' | head -2
497 -1
498 0
481 499 $ log 'ancestors(null)'
482 500 -1
501 $ log 'reverse(null:)' | tail -2
502 0
503 -1
504 $ log 'first(null:)'
505 -1
506 $ log 'min(null:)'
507 -1
483 508 $ log 'tip:null and all()' | tail -2
484 509 1
485 510 0
486 511
487 512 $ log 'outgoing()'
488 513 8
489 514 9
490 515 $ log 'outgoing("../remote1")'
491 516 8
492 517 9
493 518 $ log 'outgoing("../remote2")'
494 519 3
495 520 5
496 521 6
497 522 7
498 523 9
499 524 $ log 'p1(merge())'
500 525 5
501 526 $ log 'p2(merge())'
502 527 4
503 528 $ log 'parents(merge())'
504 529 4
505 530 5
506 531 $ log 'p1(branchpoint())'
507 532 0
508 533 2
509 534 $ log 'p2(branchpoint())'
510 535 $ log 'parents(branchpoint())'
511 536 0
512 537 2
513 538 $ log 'removes(a)'
514 539 2
515 540 6
516 541 $ log 'roots(all())'
517 542 0
518 543 $ log 'reverse(2 or 3 or 4 or 5)'
519 544 5
520 545 4
521 546 3
522 547 2
523 548 $ log 'reverse(all())'
524 549 9
525 550 8
526 551 7
527 552 6
528 553 5
529 554 4
530 555 3
531 556 2
532 557 1
533 558 0
534 559 $ log 'reverse(all()) & filelog(b)'
535 560 4
536 561 1
537 562 $ log 'rev(5)'
538 563 5
539 564 $ log 'sort(limit(reverse(all()), 3))'
540 565 7
541 566 8
542 567 9
543 568 $ log 'sort(2 or 3 or 4 or 5, date)'
544 569 2
545 570 3
546 571 5
547 572 4
548 573 $ log 'tagged()'
549 574 6
550 575 $ log 'tag()'
551 576 6
552 577 $ log 'tag(1.0)'
553 578 6
554 579 $ log 'tag(tip)'
555 580 9
556 581
557 582 test sort revset
558 583 --------------------------------------------
559 584
560 585 test when adding two unordered revsets
561 586
562 587 $ log 'sort(keyword(issue) or modifies(b))'
563 588 4
564 589 6
565 590
566 591 test when sorting a reversed collection in the same way it is
567 592
568 593 $ log 'sort(reverse(all()), -rev)'
569 594 9
570 595 8
571 596 7
572 597 6
573 598 5
574 599 4
575 600 3
576 601 2
577 602 1
578 603 0
579 604
580 605 test when sorting a reversed collection
581 606
582 607 $ log 'sort(reverse(all()), rev)'
583 608 0
584 609 1
585 610 2
586 611 3
587 612 4
588 613 5
589 614 6
590 615 7
591 616 8
592 617 9
593 618
594 619
595 620 test sorting two sorted collections in different orders
596 621
597 622 $ log 'sort(outgoing() or reverse(removes(a)), rev)'
598 623 2
599 624 6
600 625 8
601 626 9
602 627
603 628 test sorting two sorted collections in different orders backwards
604 629
605 630 $ log 'sort(outgoing() or reverse(removes(a)), -rev)'
606 631 9
607 632 8
608 633 6
609 634 2
610 635
611 636 test subtracting something from an addset
612 637
613 638 $ log '(outgoing() or removes(a)) - removes(a)'
614 639 8
615 640 9
616 641
617 642 test intersecting something with an addset
618 643
619 644 $ log 'parents(outgoing() or removes(a))'
620 645 1
621 646 4
622 647 5
623 648 8
624 649
625 650 test that `or` operation combines elements in the right order:
626 651
627 652 $ log '3:4 or 2:5'
628 653 3
629 654 4
630 655 2
631 656 5
632 657 $ log '3:4 or 5:2'
633 658 3
634 659 4
635 660 5
636 661 2
637 662 $ log 'sort(3:4 or 2:5)'
638 663 2
639 664 3
640 665 4
641 666 5
642 667 $ log 'sort(3:4 or 5:2)'
643 668 2
644 669 3
645 670 4
646 671 5
647 672
648 673 check that conversion to only works
649 674 $ try --optimize '::3 - ::1'
650 675 (minus
651 676 (dagrangepre
652 677 ('symbol', '3'))
653 678 (dagrangepre
654 679 ('symbol', '1')))
655 680 * optimized:
656 681 (func
657 682 ('symbol', 'only')
658 683 (list
659 684 ('symbol', '3')
660 685 ('symbol', '1')))
661 686 3
662 687 $ try --optimize 'ancestors(1) - ancestors(3)'
663 688 (minus
664 689 (func
665 690 ('symbol', 'ancestors')
666 691 ('symbol', '1'))
667 692 (func
668 693 ('symbol', 'ancestors')
669 694 ('symbol', '3')))
670 695 * optimized:
671 696 (func
672 697 ('symbol', 'only')
673 698 (list
674 699 ('symbol', '1')
675 700 ('symbol', '3')))
676 701 $ try --optimize 'not ::2 and ::6'
677 702 (and
678 703 (not
679 704 (dagrangepre
680 705 ('symbol', '2')))
681 706 (dagrangepre
682 707 ('symbol', '6')))
683 708 * optimized:
684 709 (func
685 710 ('symbol', 'only')
686 711 (list
687 712 ('symbol', '6')
688 713 ('symbol', '2')))
689 714 3
690 715 4
691 716 5
692 717 6
693 718 $ try --optimize 'ancestors(6) and not ancestors(4)'
694 719 (and
695 720 (func
696 721 ('symbol', 'ancestors')
697 722 ('symbol', '6'))
698 723 (not
699 724 (func
700 725 ('symbol', 'ancestors')
701 726 ('symbol', '4'))))
702 727 * optimized:
703 728 (func
704 729 ('symbol', 'only')
705 730 (list
706 731 ('symbol', '6')
707 732 ('symbol', '4')))
708 733 3
709 734 5
710 735 6
711 736
712 737 we can use patterns when searching for tags
713 738
714 739 $ log 'tag("1..*")'
715 740 abort: tag '1..*' does not exist!
716 741 [255]
717 742 $ log 'tag("re:1..*")'
718 743 6
719 744 $ log 'tag("re:[0-9].[0-9]")'
720 745 6
721 746 $ log 'tag("literal:1.0")'
722 747 6
723 748 $ log 'tag("re:0..*")'
724 749
725 750 $ log 'tag(unknown)'
726 751 abort: tag 'unknown' does not exist!
727 752 [255]
728 753 $ log 'tag("re:unknown")'
729 754 $ log 'present(tag("unknown"))'
730 755 $ log 'present(tag("re:unknown"))'
731 756 $ log 'branch(unknown)'
732 757 abort: unknown revision 'unknown'!
733 758 [255]
734 759 $ log 'branch("re:unknown")'
735 760 $ log 'present(branch("unknown"))'
736 761 $ log 'present(branch("re:unknown"))'
737 762 $ log 'user(bob)'
738 763 2
739 764
740 765 $ log '4::8'
741 766 4
742 767 8
743 768 $ log '4:8'
744 769 4
745 770 5
746 771 6
747 772 7
748 773 8
749 774
750 775 $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
751 776 4
752 777 2
753 778 5
754 779
755 780 $ log 'not 0 and 0:2'
756 781 1
757 782 2
758 783 $ log 'not 1 and 0:2'
759 784 0
760 785 2
761 786 $ log 'not 2 and 0:2'
762 787 0
763 788 1
764 789 $ log '(1 and 2)::'
765 790 $ log '(1 and 2):'
766 791 $ log '(1 and 2):3'
767 792 $ log 'sort(head(), -rev)'
768 793 9
769 794 7
770 795 6
771 796 5
772 797 4
773 798 3
774 799 2
775 800 1
776 801 0
777 802 $ log '4::8 - 8'
778 803 4
779 804 $ log 'matching(1 or 2 or 3) and (2 or 3 or 1)'
780 805 2
781 806 3
782 807 1
783 808
784 809 $ log 'named("unknown")'
785 810 abort: namespace 'unknown' does not exist!
786 811 [255]
787 812 $ log 'named("re:unknown")'
788 813 abort: no namespace exists that match 'unknown'!
789 814 [255]
790 815 $ log 'present(named("unknown"))'
791 816 $ log 'present(named("re:unknown"))'
792 817
793 818 $ log 'tag()'
794 819 6
795 820 $ log 'named("tags")'
796 821 6
797 822
798 823 issue2437
799 824
800 825 $ log '3 and p1(5)'
801 826 3
802 827 $ log '4 and p2(6)'
803 828 4
804 829 $ log '1 and parents(:2)'
805 830 1
806 831 $ log '2 and children(1:)'
807 832 2
808 833 $ log 'roots(all()) or roots(all())'
809 834 0
810 835 $ hg debugrevspec 'roots(all()) or roots(all())'
811 836 0
812 837 $ log 'heads(branch(Γ©)) or heads(branch(Γ©))'
813 838 9
814 839 $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(Γ©)))'
815 840 4
816 841
817 842 issue2654: report a parse error if the revset was not completely parsed
818 843
819 844 $ log '1 OR 2'
820 845 hg: parse error at 2: invalid token
821 846 [255]
822 847
823 848 or operator should preserve ordering:
824 849 $ log 'reverse(2::4) or tip'
825 850 4
826 851 2
827 852 9
828 853
829 854 parentrevspec
830 855
831 856 $ log 'merge()^0'
832 857 6
833 858 $ log 'merge()^'
834 859 5
835 860 $ log 'merge()^1'
836 861 5
837 862 $ log 'merge()^2'
838 863 4
839 864 $ log 'merge()^^'
840 865 3
841 866 $ log 'merge()^1^'
842 867 3
843 868 $ log 'merge()^^^'
844 869 1
845 870
846 871 $ log 'merge()~0'
847 872 6
848 873 $ log 'merge()~1'
849 874 5
850 875 $ log 'merge()~2'
851 876 3
852 877 $ log 'merge()~2^1'
853 878 1
854 879 $ log 'merge()~3'
855 880 1
856 881
857 882 $ log '(-3:tip)^'
858 883 4
859 884 6
860 885 8
861 886
862 887 $ log 'tip^foo'
863 888 hg: parse error: ^ expects a number 0, 1, or 2
864 889 [255]
865 890
866 891 multiple revspecs
867 892
868 893 $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
869 894 8
870 895 9
871 896 4
872 897 5
873 898 6
874 899 7
875 900
876 901 test usage in revpair (with "+")
877 902
878 903 (real pair)
879 904
880 905 $ hg diff -r 'tip^^' -r 'tip'
881 906 diff -r 2326846efdab -r 24286f4ae135 .hgtags
882 907 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
883 908 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
884 909 @@ -0,0 +1,1 @@
885 910 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
886 911 $ hg diff -r 'tip^^::tip'
887 912 diff -r 2326846efdab -r 24286f4ae135 .hgtags
888 913 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
889 914 +++ b/.hgtags Thu Jan 01 00:00:00 1970 +0000
890 915 @@ -0,0 +1,1 @@
891 916 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
892 917
893 918 (single rev)
894 919
895 920 $ hg diff -r 'tip^' -r 'tip^'
896 921 $ hg diff -r 'tip^::tip^ or tip^'
897 922
898 923 (single rev that does not looks like a range)
899 924
900 925 $ hg diff -r 'tip^ or tip^'
901 926 diff -r d5d0dcbdc4d9 .hgtags
902 927 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
903 928 +++ b/.hgtags * (glob)
904 929 @@ -0,0 +1,1 @@
905 930 +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
906 931
907 932 (no rev)
908 933
909 934 $ hg diff -r 'author("babar") or author("celeste")'
910 935 abort: empty revision range
911 936 [255]
912 937
913 938 aliases:
914 939
915 940 $ echo '[revsetalias]' >> .hg/hgrc
916 941 $ echo 'm = merge()' >> .hg/hgrc
917 942 $ echo 'sincem = descendants(m)' >> .hg/hgrc
918 943 $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
919 944 $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
920 945 $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
921 946
922 947 $ try m
923 948 ('symbol', 'm')
924 949 (func
925 950 ('symbol', 'merge')
926 951 None)
927 952 6
928 953
929 954 test alias recursion
930 955
931 956 $ try sincem
932 957 ('symbol', 'sincem')
933 958 (func
934 959 ('symbol', 'descendants')
935 960 (func
936 961 ('symbol', 'merge')
937 962 None))
938 963 6
939 964 7
940 965
941 966 test infinite recursion
942 967
943 968 $ echo 'recurse1 = recurse2' >> .hg/hgrc
944 969 $ echo 'recurse2 = recurse1' >> .hg/hgrc
945 970 $ try recurse1
946 971 ('symbol', 'recurse1')
947 972 hg: parse error: infinite expansion of revset alias "recurse1" detected
948 973 [255]
949 974
950 975 $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
951 976 $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
952 977 $ try "level2(level1(1, 2), 3)"
953 978 (func
954 979 ('symbol', 'level2')
955 980 (list
956 981 (func
957 982 ('symbol', 'level1')
958 983 (list
959 984 ('symbol', '1')
960 985 ('symbol', '2')))
961 986 ('symbol', '3')))
962 987 (or
963 988 ('symbol', '3')
964 989 (or
965 990 ('symbol', '1')
966 991 ('symbol', '2')))
967 992 3
968 993 1
969 994 2
970 995
971 996 test nesting and variable passing
972 997
973 998 $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
974 999 $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
975 1000 $ echo 'nested3($1) = max($1)' >> .hg/hgrc
976 1001 $ try 'nested(2:5)'
977 1002 (func
978 1003 ('symbol', 'nested')
979 1004 (range
980 1005 ('symbol', '2')
981 1006 ('symbol', '5')))
982 1007 (func
983 1008 ('symbol', 'max')
984 1009 (range
985 1010 ('symbol', '2')
986 1011 ('symbol', '5')))
987 1012 5
988 1013
989 1014 test variable isolation, variable placeholders are rewritten as string
990 1015 then parsed and matched again as string. Check they do not leak too
991 1016 far away.
992 1017
993 1018 $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
994 1019 $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
995 1020 $ try 'callinjection(2:5)'
996 1021 (func
997 1022 ('symbol', 'callinjection')
998 1023 (range
999 1024 ('symbol', '2')
1000 1025 ('symbol', '5')))
1001 1026 (func
1002 1027 ('symbol', 'descendants')
1003 1028 (func
1004 1029 ('symbol', 'max')
1005 1030 ('string', '$1')))
1006 1031 abort: unknown revision '$1'!
1007 1032 [255]
1008 1033
1009 1034 $ echo 'injectparamasstring2 = max(_aliasarg("$1"))' >> .hg/hgrc
1010 1035 $ echo 'callinjection2($1) = descendants(injectparamasstring2)' >> .hg/hgrc
1011 1036 $ try 'callinjection2(2:5)'
1012 1037 (func
1013 1038 ('symbol', 'callinjection2')
1014 1039 (range
1015 1040 ('symbol', '2')
1016 1041 ('symbol', '5')))
1017 1042 abort: failed to parse the definition of revset alias "injectparamasstring2": not a function: _aliasarg
1018 1043 [255]
1019 1044 $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
1020 1045 ('symbol', 'tip')
1021 1046 warning: failed to parse the definition of revset alias "anotherbadone": at 7: not a prefix: end
1022 1047 warning: failed to parse the definition of revset alias "injectparamasstring2": not a function: _aliasarg
1023 1048 9
1024 1049 >>> data = file('.hg/hgrc', 'rb').read()
1025 1050 >>> file('.hg/hgrc', 'wb').write(data.replace('_aliasarg', ''))
1026 1051
1027 1052 $ try 'tip'
1028 1053 ('symbol', 'tip')
1029 1054 9
1030 1055
1031 1056 $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
1032 1057 ('symbol', 'tip')
1033 1058 warning: failed to parse the declaration of revset alias "bad name": at 4: invalid token
1034 1059 9
1035 1060 $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
1036 1061 $ try 'strictreplacing("foo", tip)'
1037 1062 (func
1038 1063 ('symbol', 'strictreplacing')
1039 1064 (list
1040 1065 ('string', 'foo')
1041 1066 ('symbol', 'tip')))
1042 1067 (or
1043 1068 ('symbol', 'tip')
1044 1069 (func
1045 1070 ('symbol', 'desc')
1046 1071 ('string', '$1')))
1047 1072 9
1048 1073
1049 1074 $ try 'd(2:5)'
1050 1075 (func
1051 1076 ('symbol', 'd')
1052 1077 (range
1053 1078 ('symbol', '2')
1054 1079 ('symbol', '5')))
1055 1080 (func
1056 1081 ('symbol', 'reverse')
1057 1082 (func
1058 1083 ('symbol', 'sort')
1059 1084 (list
1060 1085 (range
1061 1086 ('symbol', '2')
1062 1087 ('symbol', '5'))
1063 1088 ('symbol', 'date'))))
1064 1089 4
1065 1090 5
1066 1091 3
1067 1092 2
1068 1093 $ try 'rs(2 or 3, date)'
1069 1094 (func
1070 1095 ('symbol', 'rs')
1071 1096 (list
1072 1097 (or
1073 1098 ('symbol', '2')
1074 1099 ('symbol', '3'))
1075 1100 ('symbol', 'date')))
1076 1101 (func
1077 1102 ('symbol', 'reverse')
1078 1103 (func
1079 1104 ('symbol', 'sort')
1080 1105 (list
1081 1106 (or
1082 1107 ('symbol', '2')
1083 1108 ('symbol', '3'))
1084 1109 ('symbol', 'date'))))
1085 1110 3
1086 1111 2
1087 1112 $ try 'rs()'
1088 1113 (func
1089 1114 ('symbol', 'rs')
1090 1115 None)
1091 1116 hg: parse error: invalid number of arguments: 0
1092 1117 [255]
1093 1118 $ try 'rs(2)'
1094 1119 (func
1095 1120 ('symbol', 'rs')
1096 1121 ('symbol', '2'))
1097 1122 hg: parse error: invalid number of arguments: 1
1098 1123 [255]
1099 1124 $ try 'rs(2, data, 7)'
1100 1125 (func
1101 1126 ('symbol', 'rs')
1102 1127 (list
1103 1128 (list
1104 1129 ('symbol', '2')
1105 1130 ('symbol', 'data'))
1106 1131 ('symbol', '7')))
1107 1132 hg: parse error: invalid number of arguments: 3
1108 1133 [255]
1109 1134 $ try 'rs4(2 or 3, x, x, date)'
1110 1135 (func
1111 1136 ('symbol', 'rs4')
1112 1137 (list
1113 1138 (list
1114 1139 (list
1115 1140 (or
1116 1141 ('symbol', '2')
1117 1142 ('symbol', '3'))
1118 1143 ('symbol', 'x'))
1119 1144 ('symbol', 'x'))
1120 1145 ('symbol', 'date')))
1121 1146 (func
1122 1147 ('symbol', 'reverse')
1123 1148 (func
1124 1149 ('symbol', 'sort')
1125 1150 (list
1126 1151 (or
1127 1152 ('symbol', '2')
1128 1153 ('symbol', '3'))
1129 1154 ('symbol', 'date'))))
1130 1155 3
1131 1156 2
1132 1157
1133 1158 issue4553: check that revset aliases override existing hash prefix
1134 1159
1135 1160 $ hg log -qr e
1136 1161 6:e0cc66ef77e8
1137 1162
1138 1163 $ hg log -qr e --config revsetalias.e="all()"
1139 1164 0:2785f51eece5
1140 1165 1:d75937da8da0
1141 1166 2:5ed5505e9f1c
1142 1167 3:8528aa5637f2
1143 1168 4:2326846efdab
1144 1169 5:904fa392b941
1145 1170 6:e0cc66ef77e8
1146 1171 7:013af1973af4
1147 1172 8:d5d0dcbdc4d9
1148 1173 9:24286f4ae135
1149 1174
1150 1175 $ hg log -qr e: --config revsetalias.e="0"
1151 1176 0:2785f51eece5
1152 1177 1:d75937da8da0
1153 1178 2:5ed5505e9f1c
1154 1179 3:8528aa5637f2
1155 1180 4:2326846efdab
1156 1181 5:904fa392b941
1157 1182 6:e0cc66ef77e8
1158 1183 7:013af1973af4
1159 1184 8:d5d0dcbdc4d9
1160 1185 9:24286f4ae135
1161 1186
1162 1187 $ hg log -qr :e --config revsetalias.e="9"
1163 1188 0:2785f51eece5
1164 1189 1:d75937da8da0
1165 1190 2:5ed5505e9f1c
1166 1191 3:8528aa5637f2
1167 1192 4:2326846efdab
1168 1193 5:904fa392b941
1169 1194 6:e0cc66ef77e8
1170 1195 7:013af1973af4
1171 1196 8:d5d0dcbdc4d9
1172 1197 9:24286f4ae135
1173 1198
1174 1199 $ hg log -qr e:
1175 1200 6:e0cc66ef77e8
1176 1201 7:013af1973af4
1177 1202 8:d5d0dcbdc4d9
1178 1203 9:24286f4ae135
1179 1204
1180 1205 $ hg log -qr :e
1181 1206 0:2785f51eece5
1182 1207 1:d75937da8da0
1183 1208 2:5ed5505e9f1c
1184 1209 3:8528aa5637f2
1185 1210 4:2326846efdab
1186 1211 5:904fa392b941
1187 1212 6:e0cc66ef77e8
1188 1213
1189 1214 issue2549 - correct optimizations
1190 1215
1191 1216 $ log 'limit(1 or 2 or 3, 2) and not 2'
1192 1217 1
1193 1218 $ log 'max(1 or 2) and not 2'
1194 1219 $ log 'min(1 or 2) and not 1'
1195 1220 $ log 'last(1 or 2, 1) and not 2'
1196 1221
1197 1222 issue4289 - ordering of built-ins
1198 1223 $ hg log -M -q -r 3:2
1199 1224 3:8528aa5637f2
1200 1225 2:5ed5505e9f1c
1201 1226
1202 1227 test revsets started with 40-chars hash (issue3669)
1203 1228
1204 1229 $ ISSUE3669_TIP=`hg tip --template '{node}'`
1205 1230 $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
1206 1231 9
1207 1232 $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
1208 1233 8
1209 1234
1210 1235 test or-ed indirect predicates (issue3775)
1211 1236
1212 1237 $ log '6 or 6^1' | sort
1213 1238 5
1214 1239 6
1215 1240 $ log '6^1 or 6' | sort
1216 1241 5
1217 1242 6
1218 1243 $ log '4 or 4~1' | sort
1219 1244 2
1220 1245 4
1221 1246 $ log '4~1 or 4' | sort
1222 1247 2
1223 1248 4
1224 1249 $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
1225 1250 0
1226 1251 1
1227 1252 2
1228 1253 3
1229 1254 4
1230 1255 5
1231 1256 6
1232 1257 $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
1233 1258 0
1234 1259 1
1235 1260 2
1236 1261 3
1237 1262 4
1238 1263 5
1239 1264 6
1240 1265
1241 1266 tests for 'remote()' predicate:
1242 1267 #. (csets in remote) (id) (remote)
1243 1268 1. less than local current branch "default"
1244 1269 2. same with local specified "default"
1245 1270 3. more than local specified specified
1246 1271
1247 1272 $ hg clone --quiet -U . ../remote3
1248 1273 $ cd ../remote3
1249 1274 $ hg update -q 7
1250 1275 $ echo r > r
1251 1276 $ hg ci -Aqm 10
1252 1277 $ log 'remote()'
1253 1278 7
1254 1279 $ log 'remote("a-b-c-")'
1255 1280 2
1256 1281 $ cd ../repo
1257 1282 $ log 'remote(".a.b.c.", "../remote3")'
1258 1283
1259 1284 tests for concatenation of strings/symbols by "##"
1260 1285
1261 1286 $ try "278 ## '5f5' ## 1ee ## 'ce5'"
1262 1287 (_concat
1263 1288 (_concat
1264 1289 (_concat
1265 1290 ('symbol', '278')
1266 1291 ('string', '5f5'))
1267 1292 ('symbol', '1ee'))
1268 1293 ('string', 'ce5'))
1269 1294 ('string', '2785f51eece5')
1270 1295 0
1271 1296
1272 1297 $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
1273 1298 $ try "cat4(278, '5f5', 1ee, 'ce5')"
1274 1299 (func
1275 1300 ('symbol', 'cat4')
1276 1301 (list
1277 1302 (list
1278 1303 (list
1279 1304 ('symbol', '278')
1280 1305 ('string', '5f5'))
1281 1306 ('symbol', '1ee'))
1282 1307 ('string', 'ce5')))
1283 1308 (_concat
1284 1309 (_concat
1285 1310 (_concat
1286 1311 ('symbol', '278')
1287 1312 ('string', '5f5'))
1288 1313 ('symbol', '1ee'))
1289 1314 ('string', 'ce5'))
1290 1315 ('string', '2785f51eece5')
1291 1316 0
1292 1317
1293 1318 (check concatenation in alias nesting)
1294 1319
1295 1320 $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
1296 1321 $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
1297 1322 $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
1298 1323 0
1299 1324
1300 1325 (check operator priority)
1301 1326
1302 1327 $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
1303 1328 $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
1304 1329 0
1305 1330 4
1306 1331
1307 1332 $ cd ..
1308 1333
1309 1334 test author/desc/keyword in problematic encoding
1310 1335 # unicode: cp932:
1311 1336 # u30A2 0x83 0x41(= 'A')
1312 1337 # u30C2 0x83 0x61(= 'a')
1313 1338
1314 1339 $ hg init problematicencoding
1315 1340 $ cd problematicencoding
1316 1341
1317 1342 $ python > setup.sh <<EOF
1318 1343 > print u'''
1319 1344 > echo a > text
1320 1345 > hg add text
1321 1346 > hg --encoding utf-8 commit -u '\u30A2' -m none
1322 1347 > echo b > text
1323 1348 > hg --encoding utf-8 commit -u '\u30C2' -m none
1324 1349 > echo c > text
1325 1350 > hg --encoding utf-8 commit -u none -m '\u30A2'
1326 1351 > echo d > text
1327 1352 > hg --encoding utf-8 commit -u none -m '\u30C2'
1328 1353 > '''.encode('utf-8')
1329 1354 > EOF
1330 1355 $ sh < setup.sh
1331 1356
1332 1357 test in problematic encoding
1333 1358 $ python > test.sh <<EOF
1334 1359 > print u'''
1335 1360 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
1336 1361 > echo ====
1337 1362 > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
1338 1363 > echo ====
1339 1364 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
1340 1365 > echo ====
1341 1366 > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
1342 1367 > echo ====
1343 1368 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
1344 1369 > echo ====
1345 1370 > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
1346 1371 > '''.encode('cp932')
1347 1372 > EOF
1348 1373 $ sh < test.sh
1349 1374 0
1350 1375 ====
1351 1376 1
1352 1377 ====
1353 1378 2
1354 1379 ====
1355 1380 3
1356 1381 ====
1357 1382 0
1358 1383 2
1359 1384 ====
1360 1385 1
1361 1386 3
1362 1387
1363 1388 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now