##// END OF EJS Templates
revset: add pattern matching to 'bookmarks' revset expression
Simon King -
r16822:da55d8a7 default
parent child Browse files
Show More
@@ -1,1609 +1,1628 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, collections
9 9 import parser, util, error, discovery, hbisect, phases
10 10 import node
11 11 import bookmarks as bookmarksmod
12 12 import match as matchmod
13 13 from i18n import _
14 14 import encoding
15 15
16 16 def _revancestors(repo, revs, followfirst):
17 17 """Like revlog.ancestors(), but supports followfirst."""
18 18 cut = followfirst and 1 or None
19 19 cl = repo.changelog
20 20 visit = collections.deque(revs)
21 21 seen = set([node.nullrev])
22 22 while visit:
23 23 for parent in cl.parentrevs(visit.popleft())[:cut]:
24 24 if parent not in seen:
25 25 visit.append(parent)
26 26 seen.add(parent)
27 27 yield parent
28 28
29 29 def _revdescendants(repo, revs, followfirst):
30 30 """Like revlog.descendants() but supports followfirst."""
31 31 cut = followfirst and 1 or None
32 32 cl = repo.changelog
33 33 first = min(revs)
34 34 nullrev = node.nullrev
35 35 if first == nullrev:
36 36 # Are there nodes with a null first parent and a non-null
37 37 # second one? Maybe. Do we care? Probably not.
38 38 for i in cl:
39 39 yield i
40 40 return
41 41
42 42 seen = set(revs)
43 43 for i in xrange(first + 1, len(cl)):
44 44 for x in cl.parentrevs(i)[:cut]:
45 45 if x != nullrev and x in seen:
46 46 seen.add(i)
47 47 yield i
48 48 break
49 49
50 50 elements = {
51 51 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
52 52 "~": (18, None, ("ancestor", 18)),
53 53 "^": (18, None, ("parent", 18), ("parentpost", 18)),
54 54 "-": (5, ("negate", 19), ("minus", 5)),
55 55 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
56 56 ("dagrangepost", 17)),
57 57 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
58 58 ("dagrangepost", 17)),
59 59 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
60 60 "not": (10, ("not", 10)),
61 61 "!": (10, ("not", 10)),
62 62 "and": (5, None, ("and", 5)),
63 63 "&": (5, None, ("and", 5)),
64 64 "or": (4, None, ("or", 4)),
65 65 "|": (4, None, ("or", 4)),
66 66 "+": (4, None, ("or", 4)),
67 67 ",": (2, None, ("list", 2)),
68 68 ")": (0, None, None),
69 69 "symbol": (0, ("symbol",), None),
70 70 "string": (0, ("string",), None),
71 71 "end": (0, None, None),
72 72 }
73 73
74 74 keywords = set(['and', 'or', 'not'])
75 75
76 76 def tokenize(program):
77 77 pos, l = 0, len(program)
78 78 while pos < l:
79 79 c = program[pos]
80 80 if c.isspace(): # skip inter-token whitespace
81 81 pass
82 82 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
83 83 yield ('::', None, pos)
84 84 pos += 1 # skip ahead
85 85 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
86 86 yield ('..', None, pos)
87 87 pos += 1 # skip ahead
88 88 elif c in "():,-|&+!~^": # handle simple operators
89 89 yield (c, None, pos)
90 90 elif (c in '"\'' or c == 'r' and
91 91 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
92 92 if c == 'r':
93 93 pos += 1
94 94 c = program[pos]
95 95 decode = lambda x: x
96 96 else:
97 97 decode = lambda x: x.decode('string-escape')
98 98 pos += 1
99 99 s = pos
100 100 while pos < l: # find closing quote
101 101 d = program[pos]
102 102 if d == '\\': # skip over escaped characters
103 103 pos += 2
104 104 continue
105 105 if d == c:
106 106 yield ('string', decode(program[s:pos]), s)
107 107 break
108 108 pos += 1
109 109 else:
110 110 raise error.ParseError(_("unterminated string"), s)
111 111 # gather up a symbol/keyword
112 112 elif c.isalnum() or c in '._' or ord(c) > 127:
113 113 s = pos
114 114 pos += 1
115 115 while pos < l: # find end of symbol
116 116 d = program[pos]
117 117 if not (d.isalnum() or d in "._/" or ord(d) > 127):
118 118 break
119 119 if d == '.' and program[pos - 1] == '.': # special case for ..
120 120 pos -= 1
121 121 break
122 122 pos += 1
123 123 sym = program[s:pos]
124 124 if sym in keywords: # operator keywords
125 125 yield (sym, None, s)
126 126 else:
127 127 yield ('symbol', sym, s)
128 128 pos -= 1
129 129 else:
130 130 raise error.ParseError(_("syntax error"), pos)
131 131 pos += 1
132 132 yield ('end', None, pos)
133 133
134 134 # helpers
135 135
136 136 def getstring(x, err):
137 137 if x and (x[0] == 'string' or x[0] == 'symbol'):
138 138 return x[1]
139 139 raise error.ParseError(err)
140 140
141 141 def getlist(x):
142 142 if not x:
143 143 return []
144 144 if x[0] == 'list':
145 145 return getlist(x[1]) + [x[2]]
146 146 return [x]
147 147
148 148 def getargs(x, min, max, err):
149 149 l = getlist(x)
150 150 if len(l) < min or (max >= 0 and len(l) > max):
151 151 raise error.ParseError(err)
152 152 return l
153 153
154 154 def getset(repo, subset, x):
155 155 if not x:
156 156 raise error.ParseError(_("missing argument"))
157 157 return methods[x[0]](repo, subset, *x[1:])
158 158
159 159 # operator methods
160 160
161 161 def stringset(repo, subset, x):
162 162 x = repo[x].rev()
163 163 if x == -1 and len(subset) == len(repo):
164 164 return [-1]
165 165 if len(subset) == len(repo) or x in subset:
166 166 return [x]
167 167 return []
168 168
169 169 def symbolset(repo, subset, x):
170 170 if x in symbols:
171 171 raise error.ParseError(_("can't use %s here") % x)
172 172 return stringset(repo, subset, x)
173 173
174 174 def rangeset(repo, subset, x, y):
175 175 m = getset(repo, subset, x)
176 176 if not m:
177 177 m = getset(repo, range(len(repo)), x)
178 178
179 179 n = getset(repo, subset, y)
180 180 if not n:
181 181 n = getset(repo, range(len(repo)), y)
182 182
183 183 if not m or not n:
184 184 return []
185 185 m, n = m[0], n[-1]
186 186
187 187 if m < n:
188 188 r = range(m, n + 1)
189 189 else:
190 190 r = range(m, n - 1, -1)
191 191 s = set(subset)
192 192 return [x for x in r if x in s]
193 193
194 194 def andset(repo, subset, x, y):
195 195 return getset(repo, getset(repo, subset, x), y)
196 196
197 197 def orset(repo, subset, x, y):
198 198 xl = getset(repo, subset, x)
199 199 s = set(xl)
200 200 yl = getset(repo, [r for r in subset if r not in s], y)
201 201 return xl + yl
202 202
203 203 def notset(repo, subset, x):
204 204 s = set(getset(repo, subset, x))
205 205 return [r for r in subset if r not in s]
206 206
207 207 def listset(repo, subset, a, b):
208 208 raise error.ParseError(_("can't use a list in this context"))
209 209
210 210 def func(repo, subset, a, b):
211 211 if a[0] == 'symbol' and a[1] in symbols:
212 212 return symbols[a[1]](repo, subset, b)
213 213 raise error.ParseError(_("not a function: %s") % a[1])
214 214
215 215 # functions
216 216
217 217 def adds(repo, subset, x):
218 218 """``adds(pattern)``
219 219 Changesets that add a file matching pattern.
220 220 """
221 221 # i18n: "adds" is a keyword
222 222 pat = getstring(x, _("adds requires a pattern"))
223 223 return checkstatus(repo, subset, pat, 1)
224 224
225 225 def ancestor(repo, subset, x):
226 226 """``ancestor(single, single)``
227 227 Greatest common ancestor of the two changesets.
228 228 """
229 229 # i18n: "ancestor" is a keyword
230 230 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
231 231 r = range(len(repo))
232 232 a = getset(repo, r, l[0])
233 233 b = getset(repo, r, l[1])
234 234 if len(a) != 1 or len(b) != 1:
235 235 # i18n: "ancestor" is a keyword
236 236 raise error.ParseError(_("ancestor arguments must be single revisions"))
237 237 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
238 238
239 239 return [r for r in an if r in subset]
240 240
241 241 def _ancestors(repo, subset, x, followfirst=False):
242 242 args = getset(repo, range(len(repo)), x)
243 243 if not args:
244 244 return []
245 245 s = set(_revancestors(repo, args, followfirst)) | set(args)
246 246 return [r for r in subset if r in s]
247 247
248 248 def ancestors(repo, subset, x):
249 249 """``ancestors(set)``
250 250 Changesets that are ancestors of a changeset in set.
251 251 """
252 252 return _ancestors(repo, subset, x)
253 253
254 254 def _firstancestors(repo, subset, x):
255 255 # ``_firstancestors(set)``
256 256 # Like ``ancestors(set)`` but follows only the first parents.
257 257 return _ancestors(repo, subset, x, followfirst=True)
258 258
259 259 def ancestorspec(repo, subset, x, n):
260 260 """``set~n``
261 261 Changesets that are the Nth ancestor (first parents only) of a changeset
262 262 in set.
263 263 """
264 264 try:
265 265 n = int(n[1])
266 266 except (TypeError, ValueError):
267 267 raise error.ParseError(_("~ expects a number"))
268 268 ps = set()
269 269 cl = repo.changelog
270 270 for r in getset(repo, subset, x):
271 271 for i in range(n):
272 272 r = cl.parentrevs(r)[0]
273 273 ps.add(r)
274 274 return [r for r in subset if r in ps]
275 275
276 276 def author(repo, subset, x):
277 277 """``author(string)``
278 278 Alias for ``user(string)``.
279 279 """
280 280 # i18n: "author" is a keyword
281 281 n = encoding.lower(getstring(x, _("author requires a string")))
282 282 return [r for r in subset if n in encoding.lower(repo[r].user())]
283 283
284 284 def bisect(repo, subset, x):
285 285 """``bisect(string)``
286 286 Changesets marked in the specified bisect status:
287 287
288 288 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
289 289 - ``goods``, ``bads`` : csets topologicaly good/bad
290 290 - ``range`` : csets taking part in the bisection
291 291 - ``pruned`` : csets that are goods, bads or skipped
292 292 - ``untested`` : csets whose fate is yet unknown
293 293 - ``ignored`` : csets ignored due to DAG topology
294 294 - ``current`` : the cset currently being bisected
295 295 """
296 296 status = getstring(x, _("bisect requires a string")).lower()
297 297 state = set(hbisect.get(repo, status))
298 298 return [r for r in subset if r in state]
299 299
300 300 # Backward-compatibility
301 301 # - no help entry so that we do not advertise it any more
302 302 def bisected(repo, subset, x):
303 303 return bisect(repo, subset, x)
304 304
305 305 def bookmark(repo, subset, x):
306 306 """``bookmark([name])``
307 307 The named bookmark or all bookmarks.
308
309 If `name` starts with `re:`, the remainder of the name is treated as
310 a regular expression. To match a bookmark that actually starts with `re:`,
311 use the prefix `literal:`.
308 312 """
309 313 # i18n: "bookmark" is a keyword
310 314 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
311 315 if args:
312 316 bm = getstring(args[0],
313 317 # i18n: "bookmark" is a keyword
314 318 _('the argument to bookmark must be a string'))
315 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
316 if not bmrev:
317 raise util.Abort(_("bookmark '%s' does not exist") % bm)
318 bmrev = repo[bmrev].rev()
319 return [r for r in subset if r == bmrev]
319 kind, pattern, matcher = _stringmatcher(bm)
320 if kind == 'literal':
321 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
322 if not bmrev:
323 raise util.Abort(_("bookmark '%s' does not exist") % bm)
324 bmrev = repo[bmrev].rev()
325 return [r for r in subset if r == bmrev]
326 else:
327 matchrevs = set()
328 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
329 if matcher(name):
330 matchrevs.add(bmrev)
331 if not matchrevs:
332 raise util.Abort(_("no bookmarks exist that match '%s'")
333 % pattern)
334 bmrevs = set()
335 for bmrev in matchrevs:
336 bmrevs.add(repo[bmrev].rev())
337 return [r for r in subset if r in bmrevs]
338
320 339 bms = set([repo[r].rev()
321 340 for r in bookmarksmod.listbookmarks(repo).values()])
322 341 return [r for r in subset if r in bms]
323 342
324 343 def branch(repo, subset, x):
325 344 """``branch(string or set)``
326 345 All changesets belonging to the given branch or the branches of the given
327 346 changesets.
328 347
329 348 If `string` starts with `re:`, the remainder of the name is treated as
330 349 a regular expression. To match a branch that actually starts with `re:`,
331 350 use the prefix `literal:`.
332 351 """
333 352 try:
334 353 b = getstring(x, '')
335 354 except error.ParseError:
336 355 # not a string, but another revspec, e.g. tip()
337 356 pass
338 357 else:
339 358 kind, pattern, matcher = _stringmatcher(b)
340 359 if kind == 'literal':
341 360 # note: falls through to the revspec case if no branch with
342 361 # this name exists
343 362 if pattern in repo.branchmap():
344 363 return [r for r in subset if matcher(repo[r].branch())]
345 364 else:
346 365 return [r for r in subset if matcher(repo[r].branch())]
347 366
348 367 s = getset(repo, range(len(repo)), x)
349 368 b = set()
350 369 for r in s:
351 370 b.add(repo[r].branch())
352 371 s = set(s)
353 372 return [r for r in subset if r in s or repo[r].branch() in b]
354 373
355 374 def checkstatus(repo, subset, pat, field):
356 375 m = None
357 376 s = []
358 377 hasset = matchmod.patkind(pat) == 'set'
359 378 fname = None
360 379 for r in subset:
361 380 c = repo[r]
362 381 if not m or hasset:
363 382 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
364 383 if not m.anypats() and len(m.files()) == 1:
365 384 fname = m.files()[0]
366 385 if fname is not None:
367 386 if fname not in c.files():
368 387 continue
369 388 else:
370 389 for f in c.files():
371 390 if m(f):
372 391 break
373 392 else:
374 393 continue
375 394 files = repo.status(c.p1().node(), c.node())[field]
376 395 if fname is not None:
377 396 if fname in files:
378 397 s.append(r)
379 398 else:
380 399 for f in files:
381 400 if m(f):
382 401 s.append(r)
383 402 break
384 403 return s
385 404
386 405 def _children(repo, narrow, parentset):
387 406 cs = set()
388 407 pr = repo.changelog.parentrevs
389 408 for r in narrow:
390 409 for p in pr(r):
391 410 if p in parentset:
392 411 cs.add(r)
393 412 return cs
394 413
395 414 def children(repo, subset, x):
396 415 """``children(set)``
397 416 Child changesets of changesets in set.
398 417 """
399 418 s = set(getset(repo, range(len(repo)), x))
400 419 cs = _children(repo, subset, s)
401 420 return [r for r in subset if r in cs]
402 421
403 422 def closed(repo, subset, x):
404 423 """``closed()``
405 424 Changeset is closed.
406 425 """
407 426 # i18n: "closed" is a keyword
408 427 getargs(x, 0, 0, _("closed takes no arguments"))
409 428 return [r for r in subset if repo[r].closesbranch()]
410 429
411 430 def contains(repo, subset, x):
412 431 """``contains(pattern)``
413 432 Revision contains a file matching pattern. See :hg:`help patterns`
414 433 for information about file patterns.
415 434 """
416 435 # i18n: "contains" is a keyword
417 436 pat = getstring(x, _("contains requires a pattern"))
418 437 m = None
419 438 s = []
420 439 if not matchmod.patkind(pat):
421 440 for r in subset:
422 441 if pat in repo[r]:
423 442 s.append(r)
424 443 else:
425 444 for r in subset:
426 445 c = repo[r]
427 446 if not m or matchmod.patkind(pat) == 'set':
428 447 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
429 448 for f in c.manifest():
430 449 if m(f):
431 450 s.append(r)
432 451 break
433 452 return s
434 453
435 454 def date(repo, subset, x):
436 455 """``date(interval)``
437 456 Changesets within the interval, see :hg:`help dates`.
438 457 """
439 458 # i18n: "date" is a keyword
440 459 ds = getstring(x, _("date requires a string"))
441 460 dm = util.matchdate(ds)
442 461 return [r for r in subset if dm(repo[r].date()[0])]
443 462
444 463 def desc(repo, subset, x):
445 464 """``desc(string)``
446 465 Search commit message for string. The match is case-insensitive.
447 466 """
448 467 # i18n: "desc" is a keyword
449 468 ds = encoding.lower(getstring(x, _("desc requires a string")))
450 469 l = []
451 470 for r in subset:
452 471 c = repo[r]
453 472 if ds in encoding.lower(c.description()):
454 473 l.append(r)
455 474 return l
456 475
457 476 def _descendants(repo, subset, x, followfirst=False):
458 477 args = getset(repo, range(len(repo)), x)
459 478 if not args:
460 479 return []
461 480 s = set(_revdescendants(repo, args, followfirst)) | set(args)
462 481 return [r for r in subset if r in s]
463 482
464 483 def descendants(repo, subset, x):
465 484 """``descendants(set)``
466 485 Changesets which are descendants of changesets in set.
467 486 """
468 487 return _descendants(repo, subset, x)
469 488
470 489 def _firstdescendants(repo, subset, x):
471 490 # ``_firstdescendants(set)``
472 491 # Like ``descendants(set)`` but follows only the first parents.
473 492 return _descendants(repo, subset, x, followfirst=True)
474 493
475 494 def draft(repo, subset, x):
476 495 """``draft()``
477 496 Changeset in draft phase."""
478 497 getargs(x, 0, 0, _("draft takes no arguments"))
479 498 pc = repo._phasecache
480 499 return [r for r in subset if pc.phase(repo, r) == phases.draft]
481 500
482 501 def extra(repo, subset, x):
483 502 """``extra(label, [value])``
484 503 Changesets with the given label in the extra metadata, with the given
485 504 optional value."""
486 505
487 506 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
488 507 label = getstring(l[0], _('first argument to extra must be a string'))
489 508 value = None
490 509
491 510 if len(l) > 1:
492 511 value = getstring(l[1], _('second argument to extra must be a string'))
493 512
494 513 def _matchvalue(r):
495 514 extra = repo[r].extra()
496 515 return label in extra and (value is None or value == extra[label])
497 516
498 517 return [r for r in subset if _matchvalue(r)]
499 518
500 519 def filelog(repo, subset, x):
501 520 """``filelog(pattern)``
502 521 Changesets connected to the specified filelog.
503 522 """
504 523
505 524 pat = getstring(x, _("filelog requires a pattern"))
506 525 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
507 526 ctx=repo[None])
508 527 s = set()
509 528
510 529 if not matchmod.patkind(pat):
511 530 for f in m.files():
512 531 fl = repo.file(f)
513 532 for fr in fl:
514 533 s.add(fl.linkrev(fr))
515 534 else:
516 535 for f in repo[None]:
517 536 if m(f):
518 537 fl = repo.file(f)
519 538 for fr in fl:
520 539 s.add(fl.linkrev(fr))
521 540
522 541 return [r for r in subset if r in s]
523 542
524 543 def first(repo, subset, x):
525 544 """``first(set, [n])``
526 545 An alias for limit().
527 546 """
528 547 return limit(repo, subset, x)
529 548
530 549 def _follow(repo, subset, x, name, followfirst=False):
531 550 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
532 551 c = repo['.']
533 552 if l:
534 553 x = getstring(l[0], _("%s expected a filename") % name)
535 554 if x in c:
536 555 cx = c[x]
537 556 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
538 557 # include the revision responsible for the most recent version
539 558 s.add(cx.linkrev())
540 559 else:
541 560 return []
542 561 else:
543 562 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
544 563
545 564 return [r for r in subset if r in s]
546 565
547 566 def follow(repo, subset, x):
548 567 """``follow([file])``
549 568 An alias for ``::.`` (ancestors of the working copy's first parent).
550 569 If a filename is specified, the history of the given file is followed,
551 570 including copies.
552 571 """
553 572 return _follow(repo, subset, x, 'follow')
554 573
555 574 def _followfirst(repo, subset, x):
556 575 # ``followfirst([file])``
557 576 # Like ``follow([file])`` but follows only the first parent of
558 577 # every revision or file revision.
559 578 return _follow(repo, subset, x, '_followfirst', followfirst=True)
560 579
561 580 def getall(repo, subset, x):
562 581 """``all()``
563 582 All changesets, the same as ``0:tip``.
564 583 """
565 584 # i18n: "all" is a keyword
566 585 getargs(x, 0, 0, _("all takes no arguments"))
567 586 return subset
568 587
569 588 def grep(repo, subset, x):
570 589 """``grep(regex)``
571 590 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
572 591 to ensure special escape characters are handled correctly. Unlike
573 592 ``keyword(string)``, the match is case-sensitive.
574 593 """
575 594 try:
576 595 # i18n: "grep" is a keyword
577 596 gr = re.compile(getstring(x, _("grep requires a string")))
578 597 except re.error, e:
579 598 raise error.ParseError(_('invalid match pattern: %s') % e)
580 599 l = []
581 600 for r in subset:
582 601 c = repo[r]
583 602 for e in c.files() + [c.user(), c.description()]:
584 603 if gr.search(e):
585 604 l.append(r)
586 605 break
587 606 return l
588 607
589 608 def _matchfiles(repo, subset, x):
590 609 # _matchfiles takes a revset list of prefixed arguments:
591 610 #
592 611 # [p:foo, i:bar, x:baz]
593 612 #
594 613 # builds a match object from them and filters subset. Allowed
595 614 # prefixes are 'p:' for regular patterns, 'i:' for include
596 615 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
597 616 # a revision identifier, or the empty string to reference the
598 617 # working directory, from which the match object is
599 618 # initialized. Use 'd:' to set the default matching mode, default
600 619 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
601 620
602 621 # i18n: "_matchfiles" is a keyword
603 622 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
604 623 pats, inc, exc = [], [], []
605 624 hasset = False
606 625 rev, default = None, None
607 626 for arg in l:
608 627 s = getstring(arg, _("_matchfiles requires string arguments"))
609 628 prefix, value = s[:2], s[2:]
610 629 if prefix == 'p:':
611 630 pats.append(value)
612 631 elif prefix == 'i:':
613 632 inc.append(value)
614 633 elif prefix == 'x:':
615 634 exc.append(value)
616 635 elif prefix == 'r:':
617 636 if rev is not None:
618 637 raise error.ParseError(_('_matchfiles expected at most one '
619 638 'revision'))
620 639 rev = value
621 640 elif prefix == 'd:':
622 641 if default is not None:
623 642 raise error.ParseError(_('_matchfiles expected at most one '
624 643 'default mode'))
625 644 default = value
626 645 else:
627 646 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
628 647 if not hasset and matchmod.patkind(value) == 'set':
629 648 hasset = True
630 649 if not default:
631 650 default = 'glob'
632 651 m = None
633 652 s = []
634 653 for r in subset:
635 654 c = repo[r]
636 655 if not m or (hasset and rev is None):
637 656 ctx = c
638 657 if rev is not None:
639 658 ctx = repo[rev or None]
640 659 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
641 660 exclude=exc, ctx=ctx, default=default)
642 661 for f in c.files():
643 662 if m(f):
644 663 s.append(r)
645 664 break
646 665 return s
647 666
648 667 def hasfile(repo, subset, x):
649 668 """``file(pattern)``
650 669 Changesets affecting files matched by pattern.
651 670 """
652 671 # i18n: "file" is a keyword
653 672 pat = getstring(x, _("file requires a pattern"))
654 673 return _matchfiles(repo, subset, ('string', 'p:' + pat))
655 674
656 675 def head(repo, subset, x):
657 676 """``head()``
658 677 Changeset is a named branch head.
659 678 """
660 679 # i18n: "head" is a keyword
661 680 getargs(x, 0, 0, _("head takes no arguments"))
662 681 hs = set()
663 682 for b, ls in repo.branchmap().iteritems():
664 683 hs.update(repo[h].rev() for h in ls)
665 684 return [r for r in subset if r in hs]
666 685
667 686 def heads(repo, subset, x):
668 687 """``heads(set)``
669 688 Members of set with no children in set.
670 689 """
671 690 s = getset(repo, subset, x)
672 691 ps = set(parents(repo, subset, x))
673 692 return [r for r in s if r not in ps]
674 693
675 694 def keyword(repo, subset, x):
676 695 """``keyword(string)``
677 696 Search commit message, user name, and names of changed files for
678 697 string. The match is case-insensitive.
679 698 """
680 699 # i18n: "keyword" is a keyword
681 700 kw = encoding.lower(getstring(x, _("keyword requires a string")))
682 701 l = []
683 702 for r in subset:
684 703 c = repo[r]
685 704 t = " ".join(c.files() + [c.user(), c.description()])
686 705 if kw in encoding.lower(t):
687 706 l.append(r)
688 707 return l
689 708
690 709 def limit(repo, subset, x):
691 710 """``limit(set, [n])``
692 711 First n members of set, defaulting to 1.
693 712 """
694 713 # i18n: "limit" is a keyword
695 714 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
696 715 try:
697 716 lim = 1
698 717 if len(l) == 2:
699 718 # i18n: "limit" is a keyword
700 719 lim = int(getstring(l[1], _("limit requires a number")))
701 720 except (TypeError, ValueError):
702 721 # i18n: "limit" is a keyword
703 722 raise error.ParseError(_("limit expects a number"))
704 723 ss = set(subset)
705 724 os = getset(repo, range(len(repo)), l[0])[:lim]
706 725 return [r for r in os if r in ss]
707 726
708 727 def last(repo, subset, x):
709 728 """``last(set, [n])``
710 729 Last n members of set, defaulting to 1.
711 730 """
712 731 # i18n: "last" is a keyword
713 732 l = getargs(x, 1, 2, _("last requires one or two arguments"))
714 733 try:
715 734 lim = 1
716 735 if len(l) == 2:
717 736 # i18n: "last" is a keyword
718 737 lim = int(getstring(l[1], _("last requires a number")))
719 738 except (TypeError, ValueError):
720 739 # i18n: "last" is a keyword
721 740 raise error.ParseError(_("last expects a number"))
722 741 ss = set(subset)
723 742 os = getset(repo, range(len(repo)), l[0])[-lim:]
724 743 return [r for r in os if r in ss]
725 744
726 745 def maxrev(repo, subset, x):
727 746 """``max(set)``
728 747 Changeset with highest revision number in set.
729 748 """
730 749 os = getset(repo, range(len(repo)), x)
731 750 if os:
732 751 m = max(os)
733 752 if m in subset:
734 753 return [m]
735 754 return []
736 755
737 756 def merge(repo, subset, x):
738 757 """``merge()``
739 758 Changeset is a merge changeset.
740 759 """
741 760 # i18n: "merge" is a keyword
742 761 getargs(x, 0, 0, _("merge takes no arguments"))
743 762 cl = repo.changelog
744 763 return [r for r in subset if cl.parentrevs(r)[1] != -1]
745 764
746 765 def minrev(repo, subset, x):
747 766 """``min(set)``
748 767 Changeset with lowest revision number in set.
749 768 """
750 769 os = getset(repo, range(len(repo)), x)
751 770 if os:
752 771 m = min(os)
753 772 if m in subset:
754 773 return [m]
755 774 return []
756 775
757 776 def modifies(repo, subset, x):
758 777 """``modifies(pattern)``
759 778 Changesets modifying files matched by pattern.
760 779 """
761 780 # i18n: "modifies" is a keyword
762 781 pat = getstring(x, _("modifies requires a pattern"))
763 782 return checkstatus(repo, subset, pat, 0)
764 783
765 784 def node_(repo, subset, x):
766 785 """``id(string)``
767 786 Revision non-ambiguously specified by the given hex string prefix.
768 787 """
769 788 # i18n: "id" is a keyword
770 789 l = getargs(x, 1, 1, _("id requires one argument"))
771 790 # i18n: "id" is a keyword
772 791 n = getstring(l[0], _("id requires a string"))
773 792 if len(n) == 40:
774 793 rn = repo[n].rev()
775 794 else:
776 795 rn = None
777 796 pm = repo.changelog._partialmatch(n)
778 797 if pm is not None:
779 798 rn = repo.changelog.rev(pm)
780 799
781 800 return [r for r in subset if r == rn]
782 801
783 802 def outgoing(repo, subset, x):
784 803 """``outgoing([path])``
785 804 Changesets not found in the specified destination repository, or the
786 805 default push location.
787 806 """
788 807 import hg # avoid start-up nasties
789 808 # i18n: "outgoing" is a keyword
790 809 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
791 810 # i18n: "outgoing" is a keyword
792 811 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
793 812 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
794 813 dest, branches = hg.parseurl(dest)
795 814 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
796 815 if revs:
797 816 revs = [repo.lookup(rev) for rev in revs]
798 817 other = hg.peer(repo, {}, dest)
799 818 repo.ui.pushbuffer()
800 819 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
801 820 repo.ui.popbuffer()
802 821 cl = repo.changelog
803 822 o = set([cl.rev(r) for r in outgoing.missing])
804 823 return [r for r in subset if r in o]
805 824
806 825 def p1(repo, subset, x):
807 826 """``p1([set])``
808 827 First parent of changesets in set, or the working directory.
809 828 """
810 829 if x is None:
811 830 p = repo[x].p1().rev()
812 831 return [r for r in subset if r == p]
813 832
814 833 ps = set()
815 834 cl = repo.changelog
816 835 for r in getset(repo, range(len(repo)), x):
817 836 ps.add(cl.parentrevs(r)[0])
818 837 return [r for r in subset if r in ps]
819 838
820 839 def p2(repo, subset, x):
821 840 """``p2([set])``
822 841 Second parent of changesets in set, or the working directory.
823 842 """
824 843 if x is None:
825 844 ps = repo[x].parents()
826 845 try:
827 846 p = ps[1].rev()
828 847 return [r for r in subset if r == p]
829 848 except IndexError:
830 849 return []
831 850
832 851 ps = set()
833 852 cl = repo.changelog
834 853 for r in getset(repo, range(len(repo)), x):
835 854 ps.add(cl.parentrevs(r)[1])
836 855 return [r for r in subset if r in ps]
837 856
838 857 def parents(repo, subset, x):
839 858 """``parents([set])``
840 859 The set of all parents for all changesets in set, or the working directory.
841 860 """
842 861 if x is None:
843 862 ps = tuple(p.rev() for p in repo[x].parents())
844 863 return [r for r in subset if r in ps]
845 864
846 865 ps = set()
847 866 cl = repo.changelog
848 867 for r in getset(repo, range(len(repo)), x):
849 868 ps.update(cl.parentrevs(r))
850 869 return [r for r in subset if r in ps]
851 870
852 871 def parentspec(repo, subset, x, n):
853 872 """``set^0``
854 873 The set.
855 874 ``set^1`` (or ``set^``), ``set^2``
856 875 First or second parent, respectively, of all changesets in set.
857 876 """
858 877 try:
859 878 n = int(n[1])
860 879 if n not in (0, 1, 2):
861 880 raise ValueError
862 881 except (TypeError, ValueError):
863 882 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
864 883 ps = set()
865 884 cl = repo.changelog
866 885 for r in getset(repo, subset, x):
867 886 if n == 0:
868 887 ps.add(r)
869 888 elif n == 1:
870 889 ps.add(cl.parentrevs(r)[0])
871 890 elif n == 2:
872 891 parents = cl.parentrevs(r)
873 892 if len(parents) > 1:
874 893 ps.add(parents[1])
875 894 return [r for r in subset if r in ps]
876 895
877 896 def present(repo, subset, x):
878 897 """``present(set)``
879 898 An empty set, if any revision in set isn't found; otherwise,
880 899 all revisions in set.
881 900
882 901 If any of specified revisions is not present in the local repository,
883 902 the query is normally aborted. But this predicate allows the query
884 903 to continue even in such cases.
885 904 """
886 905 try:
887 906 return getset(repo, subset, x)
888 907 except error.RepoLookupError:
889 908 return []
890 909
891 910 def public(repo, subset, x):
892 911 """``public()``
893 912 Changeset in public phase."""
894 913 getargs(x, 0, 0, _("public takes no arguments"))
895 914 pc = repo._phasecache
896 915 return [r for r in subset if pc.phase(repo, r) == phases.public]
897 916
898 917 def remote(repo, subset, x):
899 918 """``remote([id [,path]])``
900 919 Local revision that corresponds to the given identifier in a
901 920 remote repository, if present. Here, the '.' identifier is a
902 921 synonym for the current local branch.
903 922 """
904 923
905 924 import hg # avoid start-up nasties
906 925 # i18n: "remote" is a keyword
907 926 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
908 927
909 928 q = '.'
910 929 if len(l) > 0:
911 930 # i18n: "remote" is a keyword
912 931 q = getstring(l[0], _("remote requires a string id"))
913 932 if q == '.':
914 933 q = repo['.'].branch()
915 934
916 935 dest = ''
917 936 if len(l) > 1:
918 937 # i18n: "remote" is a keyword
919 938 dest = getstring(l[1], _("remote requires a repository path"))
920 939 dest = repo.ui.expandpath(dest or 'default')
921 940 dest, branches = hg.parseurl(dest)
922 941 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
923 942 if revs:
924 943 revs = [repo.lookup(rev) for rev in revs]
925 944 other = hg.peer(repo, {}, dest)
926 945 n = other.lookup(q)
927 946 if n in repo:
928 947 r = repo[n].rev()
929 948 if r in subset:
930 949 return [r]
931 950 return []
932 951
933 952 def removes(repo, subset, x):
934 953 """``removes(pattern)``
935 954 Changesets which remove files matching pattern.
936 955 """
937 956 # i18n: "removes" is a keyword
938 957 pat = getstring(x, _("removes requires a pattern"))
939 958 return checkstatus(repo, subset, pat, 2)
940 959
941 960 def rev(repo, subset, x):
942 961 """``rev(number)``
943 962 Revision with the given numeric identifier.
944 963 """
945 964 # i18n: "rev" is a keyword
946 965 l = getargs(x, 1, 1, _("rev requires one argument"))
947 966 try:
948 967 # i18n: "rev" is a keyword
949 968 l = int(getstring(l[0], _("rev requires a number")))
950 969 except (TypeError, ValueError):
951 970 # i18n: "rev" is a keyword
952 971 raise error.ParseError(_("rev expects a number"))
953 972 return [r for r in subset if r == l]
954 973
955 974 def matching(repo, subset, x):
956 975 """``matching(revision [, field])``
957 976 Changesets in which a given set of fields match the set of fields in the
958 977 selected revision or set.
959 978
960 979 To match more than one field pass the list of fields to match separated
961 980 by spaces (e.g. ``author description``).
962 981
963 982 Valid fields are most regular revision fields and some special fields.
964 983
965 984 Regular revision fields are ``description``, ``author``, ``branch``,
966 985 ``date``, ``files``, ``phase``, ``parents``, ``substate`` and ``user``.
967 986 Note that ``author`` and ``user`` are synonyms.
968 987
969 988 Special fields are ``summary`` and ``metadata``:
970 989 ``summary`` matches the first line of the description.
971 990 ``metadata`` is equivalent to matching ``description user date``
972 991 (i.e. it matches the main metadata fields).
973 992
974 993 ``metadata`` is the default field which is used when no fields are
975 994 specified. You can match more than one field at a time.
976 995 """
977 996 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
978 997
979 998 revs = getset(repo, xrange(len(repo)), l[0])
980 999
981 1000 fieldlist = ['metadata']
982 1001 if len(l) > 1:
983 1002 fieldlist = getstring(l[1],
984 1003 _("matching requires a string "
985 1004 "as its second argument")).split()
986 1005
987 1006 # Make sure that there are no repeated fields, and expand the
988 1007 # 'special' 'metadata' field type
989 1008 fields = []
990 1009 for field in fieldlist:
991 1010 if field == 'metadata':
992 1011 fields += ['user', 'description', 'date']
993 1012 else:
994 1013 if field == 'author':
995 1014 field = 'user'
996 1015 fields.append(field)
997 1016 fields = set(fields)
998 1017 if 'summary' in fields and 'description' in fields:
999 1018 # If a revision matches its description it also matches its summary
1000 1019 fields.discard('summary')
1001 1020
1002 1021 # We may want to match more than one field
1003 1022 # Not all fields take the same amount of time to be matched
1004 1023 # Sort the selected fields in order of increasing matching cost
1005 1024 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1006 1025 'files', 'description', 'substate']
1007 1026 def fieldkeyfunc(f):
1008 1027 try:
1009 1028 return fieldorder.index(f)
1010 1029 except ValueError:
1011 1030 # assume an unknown field is very costly
1012 1031 return len(fieldorder)
1013 1032 fields = list(fields)
1014 1033 fields.sort(key=fieldkeyfunc)
1015 1034
1016 1035 # Each field will be matched with its own "getfield" function
1017 1036 # which will be added to the getfieldfuncs array of functions
1018 1037 getfieldfuncs = []
1019 1038 _funcs = {
1020 1039 'user': lambda r: repo[r].user(),
1021 1040 'branch': lambda r: repo[r].branch(),
1022 1041 'date': lambda r: repo[r].date(),
1023 1042 'description': lambda r: repo[r].description(),
1024 1043 'files': lambda r: repo[r].files(),
1025 1044 'parents': lambda r: repo[r].parents(),
1026 1045 'phase': lambda r: repo[r].phase(),
1027 1046 'substate': lambda r: repo[r].substate,
1028 1047 'summary': lambda r: repo[r].description().splitlines()[0],
1029 1048 }
1030 1049 for info in fields:
1031 1050 getfield = _funcs.get(info, None)
1032 1051 if getfield is None:
1033 1052 raise error.ParseError(
1034 1053 _("unexpected field name passed to matching: %s") % info)
1035 1054 getfieldfuncs.append(getfield)
1036 1055 # convert the getfield array of functions into a "getinfo" function
1037 1056 # which returns an array of field values (or a single value if there
1038 1057 # is only one field to match)
1039 1058 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1040 1059
1041 1060 matches = set()
1042 1061 for rev in revs:
1043 1062 target = getinfo(rev)
1044 1063 for r in subset:
1045 1064 match = True
1046 1065 for n, f in enumerate(getfieldfuncs):
1047 1066 if target[n] != f(r):
1048 1067 match = False
1049 1068 break
1050 1069 if match:
1051 1070 matches.add(r)
1052 1071 return [r for r in subset if r in matches]
1053 1072
1054 1073 def reverse(repo, subset, x):
1055 1074 """``reverse(set)``
1056 1075 Reverse order of set.
1057 1076 """
1058 1077 l = getset(repo, subset, x)
1059 1078 l.reverse()
1060 1079 return l
1061 1080
1062 1081 def roots(repo, subset, x):
1063 1082 """``roots(set)``
1064 1083 Changesets in set with no parent changeset in set.
1065 1084 """
1066 1085 s = set(getset(repo, xrange(len(repo)), x))
1067 1086 subset = [r for r in subset if r in s]
1068 1087 cs = _children(repo, subset, s)
1069 1088 return [r for r in subset if r not in cs]
1070 1089
1071 1090 def secret(repo, subset, x):
1072 1091 """``secret()``
1073 1092 Changeset in secret phase."""
1074 1093 getargs(x, 0, 0, _("secret takes no arguments"))
1075 1094 pc = repo._phasecache
1076 1095 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1077 1096
1078 1097 def sort(repo, subset, x):
1079 1098 """``sort(set[, [-]key...])``
1080 1099 Sort set by keys. The default sort order is ascending, specify a key
1081 1100 as ``-key`` to sort in descending order.
1082 1101
1083 1102 The keys can be:
1084 1103
1085 1104 - ``rev`` for the revision number,
1086 1105 - ``branch`` for the branch name,
1087 1106 - ``desc`` for the commit message (description),
1088 1107 - ``user`` for user name (``author`` can be used as an alias),
1089 1108 - ``date`` for the commit date
1090 1109 """
1091 1110 # i18n: "sort" is a keyword
1092 1111 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1093 1112 keys = "rev"
1094 1113 if len(l) == 2:
1095 1114 keys = getstring(l[1], _("sort spec must be a string"))
1096 1115
1097 1116 s = l[0]
1098 1117 keys = keys.split()
1099 1118 l = []
1100 1119 def invert(s):
1101 1120 return "".join(chr(255 - ord(c)) for c in s)
1102 1121 for r in getset(repo, subset, s):
1103 1122 c = repo[r]
1104 1123 e = []
1105 1124 for k in keys:
1106 1125 if k == 'rev':
1107 1126 e.append(r)
1108 1127 elif k == '-rev':
1109 1128 e.append(-r)
1110 1129 elif k == 'branch':
1111 1130 e.append(c.branch())
1112 1131 elif k == '-branch':
1113 1132 e.append(invert(c.branch()))
1114 1133 elif k == 'desc':
1115 1134 e.append(c.description())
1116 1135 elif k == '-desc':
1117 1136 e.append(invert(c.description()))
1118 1137 elif k in 'user author':
1119 1138 e.append(c.user())
1120 1139 elif k in '-user -author':
1121 1140 e.append(invert(c.user()))
1122 1141 elif k == 'date':
1123 1142 e.append(c.date()[0])
1124 1143 elif k == '-date':
1125 1144 e.append(-c.date()[0])
1126 1145 else:
1127 1146 raise error.ParseError(_("unknown sort key %r") % k)
1128 1147 e.append(r)
1129 1148 l.append(e)
1130 1149 l.sort()
1131 1150 return [e[-1] for e in l]
1132 1151
1133 1152 def _stringmatcher(pattern):
1134 1153 """
1135 1154 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1136 1155 returns the matcher name, pattern, and matcher function.
1137 1156 missing or unknown prefixes are treated as literal matches.
1138 1157
1139 1158 helper for tests:
1140 1159 >>> def test(pattern, *tests):
1141 1160 ... kind, pattern, matcher = _stringmatcher(pattern)
1142 1161 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1143 1162
1144 1163 exact matching (no prefix):
1145 1164 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1146 1165 ('literal', 'abcdefg', [False, False, True])
1147 1166
1148 1167 regex matching ('re:' prefix)
1149 1168 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1150 1169 ('re', 'a.+b', [False, False, True])
1151 1170
1152 1171 force exact matches ('literal:' prefix)
1153 1172 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1154 1173 ('literal', 're:foobar', [False, True])
1155 1174
1156 1175 unknown prefixes are ignored and treated as literals
1157 1176 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1158 1177 ('literal', 'foo:bar', [False, False, True])
1159 1178 """
1160 1179 if pattern.startswith('re:'):
1161 1180 pattern = pattern[3:]
1162 1181 try:
1163 1182 regex = re.compile(pattern)
1164 1183 except re.error, e:
1165 1184 raise error.ParseError(_('invalid regular expression: %s')
1166 1185 % e)
1167 1186 return 're', pattern, regex.search
1168 1187 elif pattern.startswith('literal:'):
1169 1188 pattern = pattern[8:]
1170 1189 return 'literal', pattern, pattern.__eq__
1171 1190
1172 1191
1173 1192 def tag(repo, subset, x):
1174 1193 """``tag([name])``
1175 1194 The specified tag by name, or all tagged revisions if no name is given.
1176 1195 """
1177 1196 # i18n: "tag" is a keyword
1178 1197 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1179 1198 cl = repo.changelog
1180 1199 if args:
1181 1200 pattern = getstring(args[0],
1182 1201 # i18n: "tag" is a keyword
1183 1202 _('the argument to tag must be a string'))
1184 1203 kind, pattern, matcher = _stringmatcher(pattern)
1185 1204 if kind == 'literal':
1186 1205 if not repo.tags().get(pattern, None):
1187 1206 raise util.Abort(_("tag '%s' does not exist") % pattern)
1188 1207 s = set([cl.rev(n) for t, n in repo.tagslist() if t == pattern])
1189 1208 else:
1190 1209 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1191 1210 if not s:
1192 1211 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1193 1212 else:
1194 1213 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1195 1214 return [r for r in subset if r in s]
1196 1215
1197 1216 def tagged(repo, subset, x):
1198 1217 return tag(repo, subset, x)
1199 1218
1200 1219 def user(repo, subset, x):
1201 1220 """``user(string)``
1202 1221 User name contains string. The match is case-insensitive.
1203 1222 """
1204 1223 return author(repo, subset, x)
1205 1224
1206 1225 # for internal use
1207 1226 def _list(repo, subset, x):
1208 1227 s = getstring(x, "internal error")
1209 1228 if not s:
1210 1229 return []
1211 1230 if not isinstance(subset, set):
1212 1231 subset = set(subset)
1213 1232 ls = [repo[r].rev() for r in s.split('\0')]
1214 1233 return [r for r in ls if r in subset]
1215 1234
1216 1235 symbols = {
1217 1236 "adds": adds,
1218 1237 "all": getall,
1219 1238 "ancestor": ancestor,
1220 1239 "ancestors": ancestors,
1221 1240 "_firstancestors": _firstancestors,
1222 1241 "author": author,
1223 1242 "bisect": bisect,
1224 1243 "bisected": bisected,
1225 1244 "bookmark": bookmark,
1226 1245 "branch": branch,
1227 1246 "children": children,
1228 1247 "closed": closed,
1229 1248 "contains": contains,
1230 1249 "date": date,
1231 1250 "desc": desc,
1232 1251 "descendants": descendants,
1233 1252 "_firstdescendants": _firstdescendants,
1234 1253 "draft": draft,
1235 1254 "extra": extra,
1236 1255 "file": hasfile,
1237 1256 "filelog": filelog,
1238 1257 "first": first,
1239 1258 "follow": follow,
1240 1259 "_followfirst": _followfirst,
1241 1260 "grep": grep,
1242 1261 "head": head,
1243 1262 "heads": heads,
1244 1263 "id": node_,
1245 1264 "keyword": keyword,
1246 1265 "last": last,
1247 1266 "limit": limit,
1248 1267 "_matchfiles": _matchfiles,
1249 1268 "max": maxrev,
1250 1269 "merge": merge,
1251 1270 "min": minrev,
1252 1271 "modifies": modifies,
1253 1272 "outgoing": outgoing,
1254 1273 "p1": p1,
1255 1274 "p2": p2,
1256 1275 "parents": parents,
1257 1276 "present": present,
1258 1277 "public": public,
1259 1278 "remote": remote,
1260 1279 "removes": removes,
1261 1280 "rev": rev,
1262 1281 "reverse": reverse,
1263 1282 "roots": roots,
1264 1283 "sort": sort,
1265 1284 "secret": secret,
1266 1285 "matching": matching,
1267 1286 "tag": tag,
1268 1287 "tagged": tagged,
1269 1288 "user": user,
1270 1289 "_list": _list,
1271 1290 }
1272 1291
1273 1292 methods = {
1274 1293 "range": rangeset,
1275 1294 "string": stringset,
1276 1295 "symbol": symbolset,
1277 1296 "and": andset,
1278 1297 "or": orset,
1279 1298 "not": notset,
1280 1299 "list": listset,
1281 1300 "func": func,
1282 1301 "ancestor": ancestorspec,
1283 1302 "parent": parentspec,
1284 1303 "parentpost": p1,
1285 1304 }
1286 1305
1287 1306 def optimize(x, small):
1288 1307 if x is None:
1289 1308 return 0, x
1290 1309
1291 1310 smallbonus = 1
1292 1311 if small:
1293 1312 smallbonus = .5
1294 1313
1295 1314 op = x[0]
1296 1315 if op == 'minus':
1297 1316 return optimize(('and', x[1], ('not', x[2])), small)
1298 1317 elif op == 'dagrange':
1299 1318 return optimize(('and', ('func', ('symbol', 'descendants'), x[1]),
1300 1319 ('func', ('symbol', 'ancestors'), x[2])), small)
1301 1320 elif op == 'dagrangepre':
1302 1321 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1303 1322 elif op == 'dagrangepost':
1304 1323 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1305 1324 elif op == 'rangepre':
1306 1325 return optimize(('range', ('string', '0'), x[1]), small)
1307 1326 elif op == 'rangepost':
1308 1327 return optimize(('range', x[1], ('string', 'tip')), small)
1309 1328 elif op == 'negate':
1310 1329 return optimize(('string',
1311 1330 '-' + getstring(x[1], _("can't negate that"))), small)
1312 1331 elif op in 'string symbol negate':
1313 1332 return smallbonus, x # single revisions are small
1314 1333 elif op == 'and' or op == 'dagrange':
1315 1334 wa, ta = optimize(x[1], True)
1316 1335 wb, tb = optimize(x[2], True)
1317 1336 w = min(wa, wb)
1318 1337 if wa > wb:
1319 1338 return w, (op, tb, ta)
1320 1339 return w, (op, ta, tb)
1321 1340 elif op == 'or':
1322 1341 wa, ta = optimize(x[1], False)
1323 1342 wb, tb = optimize(x[2], False)
1324 1343 if wb < wa:
1325 1344 wb, wa = wa, wb
1326 1345 return max(wa, wb), (op, ta, tb)
1327 1346 elif op == 'not':
1328 1347 o = optimize(x[1], not small)
1329 1348 return o[0], (op, o[1])
1330 1349 elif op == 'parentpost':
1331 1350 o = optimize(x[1], small)
1332 1351 return o[0], (op, o[1])
1333 1352 elif op == 'group':
1334 1353 return optimize(x[1], small)
1335 1354 elif op in 'range list parent ancestorspec':
1336 1355 if op == 'parent':
1337 1356 # x^:y means (x^) : y, not x ^ (:y)
1338 1357 post = ('parentpost', x[1])
1339 1358 if x[2][0] == 'dagrangepre':
1340 1359 return optimize(('dagrange', post, x[2][1]), small)
1341 1360 elif x[2][0] == 'rangepre':
1342 1361 return optimize(('range', post, x[2][1]), small)
1343 1362
1344 1363 wa, ta = optimize(x[1], small)
1345 1364 wb, tb = optimize(x[2], small)
1346 1365 return wa + wb, (op, ta, tb)
1347 1366 elif op == 'func':
1348 1367 f = getstring(x[1], _("not a symbol"))
1349 1368 wa, ta = optimize(x[2], small)
1350 1369 if f in ("author branch closed date desc file grep keyword "
1351 1370 "outgoing user"):
1352 1371 w = 10 # slow
1353 1372 elif f in "modifies adds removes":
1354 1373 w = 30 # slower
1355 1374 elif f == "contains":
1356 1375 w = 100 # very slow
1357 1376 elif f == "ancestor":
1358 1377 w = 1 * smallbonus
1359 1378 elif f in "reverse limit first":
1360 1379 w = 0
1361 1380 elif f in "sort":
1362 1381 w = 10 # assume most sorts look at changelog
1363 1382 else:
1364 1383 w = 1
1365 1384 return w + wa, (op, x[1], ta)
1366 1385 return 1, x
1367 1386
1368 1387 _aliasarg = ('func', ('symbol', '_aliasarg'))
1369 1388 def _getaliasarg(tree):
1370 1389 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1371 1390 return X, None otherwise.
1372 1391 """
1373 1392 if (len(tree) == 3 and tree[:2] == _aliasarg
1374 1393 and tree[2][0] == 'string'):
1375 1394 return tree[2][1]
1376 1395 return None
1377 1396
1378 1397 def _checkaliasarg(tree, known=None):
1379 1398 """Check tree contains no _aliasarg construct or only ones which
1380 1399 value is in known. Used to avoid alias placeholders injection.
1381 1400 """
1382 1401 if isinstance(tree, tuple):
1383 1402 arg = _getaliasarg(tree)
1384 1403 if arg is not None and (not known or arg not in known):
1385 1404 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1386 1405 for t in tree:
1387 1406 _checkaliasarg(t, known)
1388 1407
1389 1408 class revsetalias(object):
1390 1409 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1391 1410 args = None
1392 1411
1393 1412 def __init__(self, name, value):
1394 1413 '''Aliases like:
1395 1414
1396 1415 h = heads(default)
1397 1416 b($1) = ancestors($1) - ancestors(default)
1398 1417 '''
1399 1418 m = self.funcre.search(name)
1400 1419 if m:
1401 1420 self.name = m.group(1)
1402 1421 self.tree = ('func', ('symbol', m.group(1)))
1403 1422 self.args = [x.strip() for x in m.group(2).split(',')]
1404 1423 for arg in self.args:
1405 1424 # _aliasarg() is an unknown symbol only used separate
1406 1425 # alias argument placeholders from regular strings.
1407 1426 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1408 1427 else:
1409 1428 self.name = name
1410 1429 self.tree = ('symbol', name)
1411 1430
1412 1431 self.replacement, pos = parse(value)
1413 1432 if pos != len(value):
1414 1433 raise error.ParseError(_('invalid token'), pos)
1415 1434 # Check for placeholder injection
1416 1435 _checkaliasarg(self.replacement, self.args)
1417 1436
1418 1437 def _getalias(aliases, tree):
1419 1438 """If tree looks like an unexpanded alias, return it. Return None
1420 1439 otherwise.
1421 1440 """
1422 1441 if isinstance(tree, tuple) and tree:
1423 1442 if tree[0] == 'symbol' and len(tree) == 2:
1424 1443 name = tree[1]
1425 1444 alias = aliases.get(name)
1426 1445 if alias and alias.args is None and alias.tree == tree:
1427 1446 return alias
1428 1447 if tree[0] == 'func' and len(tree) > 1:
1429 1448 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1430 1449 name = tree[1][1]
1431 1450 alias = aliases.get(name)
1432 1451 if alias and alias.args is not None and alias.tree == tree[:2]:
1433 1452 return alias
1434 1453 return None
1435 1454
1436 1455 def _expandargs(tree, args):
1437 1456 """Replace _aliasarg instances with the substitution value of the
1438 1457 same name in args, recursively.
1439 1458 """
1440 1459 if not tree or not isinstance(tree, tuple):
1441 1460 return tree
1442 1461 arg = _getaliasarg(tree)
1443 1462 if arg is not None:
1444 1463 return args[arg]
1445 1464 return tuple(_expandargs(t, args) for t in tree)
1446 1465
1447 1466 def _expandaliases(aliases, tree, expanding):
1448 1467 """Expand aliases in tree, recursively.
1449 1468
1450 1469 'aliases' is a dictionary mapping user defined aliases to
1451 1470 revsetalias objects.
1452 1471 """
1453 1472 if not isinstance(tree, tuple):
1454 1473 # Do not expand raw strings
1455 1474 return tree
1456 1475 alias = _getalias(aliases, tree)
1457 1476 if alias is not None:
1458 1477 if alias in expanding:
1459 1478 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1460 1479 'detected') % alias.name)
1461 1480 expanding.append(alias)
1462 1481 result = _expandaliases(aliases, alias.replacement, expanding)
1463 1482 expanding.pop()
1464 1483 if alias.args is not None:
1465 1484 l = getlist(tree[2])
1466 1485 if len(l) != len(alias.args):
1467 1486 raise error.ParseError(
1468 1487 _('invalid number of arguments: %s') % len(l))
1469 1488 l = [_expandaliases(aliases, a, []) for a in l]
1470 1489 result = _expandargs(result, dict(zip(alias.args, l)))
1471 1490 else:
1472 1491 result = tuple(_expandaliases(aliases, t, expanding)
1473 1492 for t in tree)
1474 1493 return result
1475 1494
1476 1495 def findaliases(ui, tree):
1477 1496 _checkaliasarg(tree)
1478 1497 aliases = {}
1479 1498 for k, v in ui.configitems('revsetalias'):
1480 1499 alias = revsetalias(k, v)
1481 1500 aliases[alias.name] = alias
1482 1501 return _expandaliases(aliases, tree, [])
1483 1502
1484 1503 parse = parser.parser(tokenize, elements).parse
1485 1504
1486 1505 def match(ui, spec):
1487 1506 if not spec:
1488 1507 raise error.ParseError(_("empty query"))
1489 1508 tree, pos = parse(spec)
1490 1509 if (pos != len(spec)):
1491 1510 raise error.ParseError(_("invalid token"), pos)
1492 1511 if ui:
1493 1512 tree = findaliases(ui, tree)
1494 1513 weight, tree = optimize(tree, True)
1495 1514 def mfunc(repo, subset):
1496 1515 return getset(repo, subset, tree)
1497 1516 return mfunc
1498 1517
1499 1518 def formatspec(expr, *args):
1500 1519 '''
1501 1520 This is a convenience function for using revsets internally, and
1502 1521 escapes arguments appropriately. Aliases are intentionally ignored
1503 1522 so that intended expression behavior isn't accidentally subverted.
1504 1523
1505 1524 Supported arguments:
1506 1525
1507 1526 %r = revset expression, parenthesized
1508 1527 %d = int(arg), no quoting
1509 1528 %s = string(arg), escaped and single-quoted
1510 1529 %b = arg.branch(), escaped and single-quoted
1511 1530 %n = hex(arg), single-quoted
1512 1531 %% = a literal '%'
1513 1532
1514 1533 Prefixing the type with 'l' specifies a parenthesized list of that type.
1515 1534
1516 1535 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1517 1536 '(10 or 11):: and ((this()) or (that()))'
1518 1537 >>> formatspec('%d:: and not %d::', 10, 20)
1519 1538 '10:: and not 20::'
1520 1539 >>> formatspec('%ld or %ld', [], [1])
1521 1540 "_list('') or 1"
1522 1541 >>> formatspec('keyword(%s)', 'foo\\xe9')
1523 1542 "keyword('foo\\\\xe9')"
1524 1543 >>> b = lambda: 'default'
1525 1544 >>> b.branch = b
1526 1545 >>> formatspec('branch(%b)', b)
1527 1546 "branch('default')"
1528 1547 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1529 1548 "root(_list('a\\x00b\\x00c\\x00d'))"
1530 1549 '''
1531 1550
1532 1551 def quote(s):
1533 1552 return repr(str(s))
1534 1553
1535 1554 def argtype(c, arg):
1536 1555 if c == 'd':
1537 1556 return str(int(arg))
1538 1557 elif c == 's':
1539 1558 return quote(arg)
1540 1559 elif c == 'r':
1541 1560 parse(arg) # make sure syntax errors are confined
1542 1561 return '(%s)' % arg
1543 1562 elif c == 'n':
1544 1563 return quote(node.hex(arg))
1545 1564 elif c == 'b':
1546 1565 return quote(arg.branch())
1547 1566
1548 1567 def listexp(s, t):
1549 1568 l = len(s)
1550 1569 if l == 0:
1551 1570 return "_list('')"
1552 1571 elif l == 1:
1553 1572 return argtype(t, s[0])
1554 1573 elif t == 'd':
1555 1574 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1556 1575 elif t == 's':
1557 1576 return "_list('%s')" % "\0".join(s)
1558 1577 elif t == 'n':
1559 1578 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1560 1579 elif t == 'b':
1561 1580 return "_list('%s')" % "\0".join(a.branch() for a in s)
1562 1581
1563 1582 m = l // 2
1564 1583 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1565 1584
1566 1585 ret = ''
1567 1586 pos = 0
1568 1587 arg = 0
1569 1588 while pos < len(expr):
1570 1589 c = expr[pos]
1571 1590 if c == '%':
1572 1591 pos += 1
1573 1592 d = expr[pos]
1574 1593 if d == '%':
1575 1594 ret += d
1576 1595 elif d in 'dsnbr':
1577 1596 ret += argtype(d, args[arg])
1578 1597 arg += 1
1579 1598 elif d == 'l':
1580 1599 # a list of some type
1581 1600 pos += 1
1582 1601 d = expr[pos]
1583 1602 ret += listexp(list(args[arg]), d)
1584 1603 arg += 1
1585 1604 else:
1586 1605 raise util.Abort('unexpected revspec format character %s' % d)
1587 1606 else:
1588 1607 ret += c
1589 1608 pos += 1
1590 1609
1591 1610 return ret
1592 1611
1593 1612 def prettyformat(tree):
1594 1613 def _prettyformat(tree, level, lines):
1595 1614 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1596 1615 lines.append((level, str(tree)))
1597 1616 else:
1598 1617 lines.append((level, '(%s' % tree[0]))
1599 1618 for s in tree[1:]:
1600 1619 _prettyformat(s, level + 1, lines)
1601 1620 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1602 1621
1603 1622 lines = []
1604 1623 _prettyformat(tree, 0, lines)
1605 1624 output = '\n'.join((' '*l + s) for l, s in lines)
1606 1625 return output
1607 1626
1608 1627 # tell hggettext to extract docstrings from these functions:
1609 1628 i18nfunctions = symbols.values()
@@ -1,378 +1,392 b''
1 1 $ hg init
2 2
3 3 no bookmarks
4 4
5 5 $ hg bookmarks
6 6 no bookmarks set
7 7
8 8 bookmark rev -1
9 9
10 10 $ hg bookmark X
11 11
12 12 list bookmarks
13 13
14 14 $ hg bookmarks
15 15 * X -1:000000000000
16 16
17 17 list bookmarks with color
18 18
19 19 $ hg --config extensions.color= --config color.mode=ansi \
20 20 > bookmarks --color=always
21 21 \x1b[0;32m * X -1:000000000000\x1b[0m (esc)
22 22
23 23 $ echo a > a
24 24 $ hg add a
25 25 $ hg commit -m 0
26 26
27 27 bookmark X moved to rev 0
28 28
29 29 $ hg bookmarks
30 30 * X 0:f7b1eb17ad24
31 31
32 32 look up bookmark
33 33
34 34 $ hg log -r X
35 35 changeset: 0:f7b1eb17ad24
36 36 bookmark: X
37 37 tag: tip
38 38 user: test
39 39 date: Thu Jan 01 00:00:00 1970 +0000
40 40 summary: 0
41 41
42 42
43 43 second bookmark for rev 0
44 44
45 45 $ hg bookmark X2
46 46
47 47 bookmark rev -1 again
48 48
49 49 $ hg bookmark -r null Y
50 50
51 51 list bookmarks
52 52
53 53 $ hg bookmarks
54 54 X 0:f7b1eb17ad24
55 55 * X2 0:f7b1eb17ad24
56 56 Y -1:000000000000
57 57
58 58 $ echo b > b
59 59 $ hg add b
60 60 $ hg commit -m 1
61 61
62 62 bookmarks revset
63 63
64 64 $ hg log -r 'bookmark()'
65 65 changeset: 0:f7b1eb17ad24
66 66 bookmark: X
67 67 user: test
68 68 date: Thu Jan 01 00:00:00 1970 +0000
69 69 summary: 0
70 70
71 71 changeset: 1:925d80f479bb
72 72 bookmark: X2
73 73 tag: tip
74 74 user: test
75 75 date: Thu Jan 01 00:00:00 1970 +0000
76 76 summary: 1
77 77
78 78 $ hg log -r 'bookmark(Y)'
79 79 $ hg log -r 'bookmark(X2)'
80 80 changeset: 1:925d80f479bb
81 81 bookmark: X2
82 82 tag: tip
83 83 user: test
84 84 date: Thu Jan 01 00:00:00 1970 +0000
85 85 summary: 1
86 86
87 $ hg log -r 'bookmark("re:X")'
88 changeset: 0:f7b1eb17ad24
89 bookmark: X
90 user: test
91 date: Thu Jan 01 00:00:00 1970 +0000
92 summary: 0
93
94 changeset: 1:925d80f479bb
95 bookmark: X2
96 tag: tip
97 user: test
98 date: Thu Jan 01 00:00:00 1970 +0000
99 summary: 1
100
87 101 $ hg log -r 'bookmark(unknown)'
88 102 abort: bookmark 'unknown' does not exist
89 103 [255]
90 104
91 105 $ hg help revsets | grep 'bookmark('
92 106 "bookmark([name])"
93 107
94 108 bookmarks X and X2 moved to rev 1, Y at rev -1
95 109
96 110 $ hg bookmarks
97 111 X 0:f7b1eb17ad24
98 112 * X2 1:925d80f479bb
99 113 Y -1:000000000000
100 114
101 115 bookmark rev 0 again
102 116
103 117 $ hg bookmark -r 0 Z
104 118
105 119 $ hg update X
106 120 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
107 121 $ echo c > c
108 122 $ hg add c
109 123 $ hg commit -m 2
110 124 created new head
111 125
112 126 bookmarks X moved to rev 2, Y at rev -1, Z at rev 0
113 127
114 128 $ hg bookmarks
115 129 * X 2:db815d6d32e6
116 130 X2 1:925d80f479bb
117 131 Y -1:000000000000
118 132 Z 0:f7b1eb17ad24
119 133
120 134 rename nonexistent bookmark
121 135
122 136 $ hg bookmark -m A B
123 137 abort: bookmark 'A' does not exist
124 138 [255]
125 139
126 140 rename to existent bookmark
127 141
128 142 $ hg bookmark -m X Y
129 143 abort: bookmark 'Y' already exists (use -f to force)
130 144 [255]
131 145
132 146 force rename to existent bookmark
133 147
134 148 $ hg bookmark -f -m X Y
135 149
136 150 list bookmarks
137 151
138 152 $ hg bookmark
139 153 X2 1:925d80f479bb
140 154 * Y 2:db815d6d32e6
141 155 Z 0:f7b1eb17ad24
142 156
143 157 rename without new name
144 158
145 159 $ hg bookmark -m Y
146 160 abort: new bookmark name required
147 161 [255]
148 162
149 163 delete without name
150 164
151 165 $ hg bookmark -d
152 166 abort: bookmark name required
153 167 [255]
154 168
155 169 delete nonexistent bookmark
156 170
157 171 $ hg bookmark -d A
158 172 abort: bookmark 'A' does not exist
159 173 [255]
160 174
161 175 bookmark name with spaces should be stripped
162 176
163 177 $ hg bookmark ' x y '
164 178
165 179 list bookmarks
166 180
167 181 $ hg bookmarks
168 182 X2 1:925d80f479bb
169 183 Y 2:db815d6d32e6
170 184 Z 0:f7b1eb17ad24
171 185 * x y 2:db815d6d32e6
172 186
173 187 look up stripped bookmark name
174 188
175 189 $ hg log -r '"x y"'
176 190 changeset: 2:db815d6d32e6
177 191 bookmark: Y
178 192 bookmark: x y
179 193 tag: tip
180 194 parent: 0:f7b1eb17ad24
181 195 user: test
182 196 date: Thu Jan 01 00:00:00 1970 +0000
183 197 summary: 2
184 198
185 199
186 200 reject bookmark name with newline
187 201
188 202 $ hg bookmark '
189 203 > '
190 204 abort: bookmark name cannot contain newlines
191 205 [255]
192 206
193 207 bookmark with existing name
194 208
195 209 $ hg bookmark Z
196 210 abort: bookmark 'Z' already exists (use -f to force)
197 211 [255]
198 212
199 213 force bookmark with existing name
200 214
201 215 $ hg bookmark -f Z
202 216
203 217 list bookmarks
204 218
205 219 $ hg bookmark
206 220 X2 1:925d80f479bb
207 221 Y 2:db815d6d32e6
208 222 * Z 2:db815d6d32e6
209 223 x y 2:db815d6d32e6
210 224
211 225 revision but no bookmark name
212 226
213 227 $ hg bookmark -r .
214 228 abort: bookmark name required
215 229 [255]
216 230
217 231 bookmark name with whitespace only
218 232
219 233 $ hg bookmark ' '
220 234 abort: bookmark names cannot consist entirely of whitespace
221 235 [255]
222 236
223 237 invalid bookmark
224 238
225 239 $ hg bookmark 'foo:bar'
226 240 abort: bookmark 'foo:bar' contains illegal character
227 241 [255]
228 242
229 243 the bookmark extension should be ignored now that it is part of core
230 244
231 245 $ echo "[extensions]" >> $HGRCPATH
232 246 $ echo "bookmarks=" >> $HGRCPATH
233 247 $ hg bookmarks
234 248 X2 1:925d80f479bb
235 249 Y 2:db815d6d32e6
236 250 * Z 2:db815d6d32e6
237 251 x y 2:db815d6d32e6
238 252
239 253 test summary
240 254
241 255 $ hg summary
242 256 parent: 2:db815d6d32e6 tip
243 257 2
244 258 branch: default
245 259 bookmarks: *Z Y x y
246 260 commit: (clean)
247 261 update: 1 new changesets, 2 branch heads (merge)
248 262
249 263 test id
250 264
251 265 $ hg id
252 266 db815d6d32e6 tip Y/Z/x y
253 267
254 268 test rollback
255 269
256 270 $ echo foo > f1
257 271 $ hg ci -Amr
258 272 adding f1
259 273 $ hg bookmark -f Y -r 1
260 274 $ hg bookmark -f Z -r 1
261 275 $ hg rollback
262 276 repository tip rolled back to revision 2 (undo commit)
263 277 working directory now based on revision 2
264 278 $ hg bookmarks
265 279 X2 1:925d80f479bb
266 280 Y 2:db815d6d32e6
267 281 * Z 2:db815d6d32e6
268 282 x y 2:db815d6d32e6
269 283
270 284 test clone
271 285
272 286 $ hg bookmark -r 2 -i @
273 287 $ hg bookmark -r 2 -i a@
274 288 $ hg bookmarks
275 289 @ 2:db815d6d32e6
276 290 X2 1:925d80f479bb
277 291 Y 2:db815d6d32e6
278 292 * Z 2:db815d6d32e6
279 293 a@ 2:db815d6d32e6
280 294 x y 2:db815d6d32e6
281 295 $ hg clone . cloned-bookmarks
282 296 updating to branch default
283 297 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 298 $ hg -R cloned-bookmarks bookmarks
285 299 @ 2:db815d6d32e6
286 300 X2 1:925d80f479bb
287 301 Y 2:db815d6d32e6
288 302 Z 2:db815d6d32e6
289 303 a@ 2:db815d6d32e6
290 304 x y 2:db815d6d32e6
291 305
292 306 test clone with pull protocol
293 307
294 308 $ hg clone --pull . cloned-bookmarks-pull
295 309 requesting all changes
296 310 adding changesets
297 311 adding manifests
298 312 adding file changes
299 313 added 3 changesets with 3 changes to 3 files (+1 heads)
300 314 updating to branch default
301 315 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
302 316 $ hg -R cloned-bookmarks-pull bookmarks
303 317 @ 2:db815d6d32e6
304 318 X2 1:925d80f479bb
305 319 Y 2:db815d6d32e6
306 320 Z 2:db815d6d32e6
307 321 a@ 2:db815d6d32e6
308 322 x y 2:db815d6d32e6
309 323
310 324 $ hg bookmark -d @
311 325 $ hg bookmark -d a@
312 326
313 327 test clone with a specific revision
314 328
315 329 $ hg clone -r 925d80 . cloned-bookmarks-rev
316 330 adding changesets
317 331 adding manifests
318 332 adding file changes
319 333 added 2 changesets with 2 changes to 2 files
320 334 updating to branch default
321 335 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 336 $ hg -R cloned-bookmarks-rev bookmarks
323 337 X2 1:925d80f479bb
324 338
325 339 create bundle with two heads
326 340
327 341 $ hg clone . tobundle
328 342 updating to branch default
329 343 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
330 344 $ echo x > tobundle/x
331 345 $ hg -R tobundle add tobundle/x
332 346 $ hg -R tobundle commit -m'x'
333 347 $ hg -R tobundle update -r -2
334 348 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
335 349 $ echo y > tobundle/y
336 350 $ hg -R tobundle branch test
337 351 marked working directory as branch test
338 352 (branches are permanent and global, did you want a bookmark?)
339 353 $ hg -R tobundle add tobundle/y
340 354 $ hg -R tobundle commit -m'y'
341 355 $ hg -R tobundle bundle tobundle.hg
342 356 searching for changes
343 357 2 changesets found
344 358 $ hg unbundle tobundle.hg
345 359 adding changesets
346 360 adding manifests
347 361 adding file changes
348 362 added 2 changesets with 2 changes to 2 files (+1 heads)
349 363 (run 'hg heads' to see heads, 'hg merge' to merge)
350 364 $ hg update
351 365 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
352 366 $ hg bookmarks
353 367 X2 1:925d80f479bb
354 368 Y 2:db815d6d32e6
355 369 * Z 3:125c9a1d6df6
356 370 x y 2:db815d6d32e6
357 371
358 372 test wrongly formated bookmark
359 373
360 374 $ echo '' >> .hg/bookmarks
361 375 $ hg bookmarks
362 376 X2 1:925d80f479bb
363 377 Y 2:db815d6d32e6
364 378 * Z 3:125c9a1d6df6
365 379 x y 2:db815d6d32e6
366 380 $ echo "Ican'thasformatedlines" >> .hg/bookmarks
367 381 $ hg bookmarks
368 382 malformed line in .hg/bookmarks: "Ican'thasformatedlines"
369 383 X2 1:925d80f479bb
370 384 Y 2:db815d6d32e6
371 385 * Z 3:125c9a1d6df6
372 386 x y 2:db815d6d32e6
373 387
374 388 test missing revisions
375 389
376 390 $ echo "925d80f479bc z" > .hg/bookmarks
377 391 $ hg book
378 392 no bookmarks set
General Comments 0
You need to be logged in to leave comments. Login now