##// END OF EJS Templates
revset: add destination() predicate...
Matt Harbison -
r17186:a3da6f29 default
parent child Browse files
Show More
@@ -1,1782 +1,1827 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 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 = util.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 def _revsbetween(repo, roots, heads):
51 51 """Return all paths between roots and heads, inclusive of both endpoint
52 52 sets."""
53 53 if not roots:
54 54 return []
55 55 parentrevs = repo.changelog.parentrevs
56 56 visit = heads[:]
57 57 reachable = set()
58 58 seen = {}
59 59 minroot = min(roots)
60 60 roots = set(roots)
61 61 # open-code the post-order traversal due to the tiny size of
62 62 # sys.getrecursionlimit()
63 63 while visit:
64 64 rev = visit.pop()
65 65 if rev in roots:
66 66 reachable.add(rev)
67 67 parents = parentrevs(rev)
68 68 seen[rev] = parents
69 69 for parent in parents:
70 70 if parent >= minroot and parent not in seen:
71 71 visit.append(parent)
72 72 if not reachable:
73 73 return []
74 74 for rev in sorted(seen):
75 75 for parent in seen[rev]:
76 76 if parent in reachable:
77 77 reachable.add(rev)
78 78 return sorted(reachable)
79 79
80 80 elements = {
81 81 "(": (20, ("group", 1, ")"), ("func", 1, ")")),
82 82 "~": (18, None, ("ancestor", 18)),
83 83 "^": (18, None, ("parent", 18), ("parentpost", 18)),
84 84 "-": (5, ("negate", 19), ("minus", 5)),
85 85 "::": (17, ("dagrangepre", 17), ("dagrange", 17),
86 86 ("dagrangepost", 17)),
87 87 "..": (17, ("dagrangepre", 17), ("dagrange", 17),
88 88 ("dagrangepost", 17)),
89 89 ":": (15, ("rangepre", 15), ("range", 15), ("rangepost", 15)),
90 90 "not": (10, ("not", 10)),
91 91 "!": (10, ("not", 10)),
92 92 "and": (5, None, ("and", 5)),
93 93 "&": (5, None, ("and", 5)),
94 94 "or": (4, None, ("or", 4)),
95 95 "|": (4, None, ("or", 4)),
96 96 "+": (4, None, ("or", 4)),
97 97 ",": (2, None, ("list", 2)),
98 98 ")": (0, None, None),
99 99 "symbol": (0, ("symbol",), None),
100 100 "string": (0, ("string",), None),
101 101 "end": (0, None, None),
102 102 }
103 103
104 104 keywords = set(['and', 'or', 'not'])
105 105
106 106 def tokenize(program):
107 107 pos, l = 0, len(program)
108 108 while pos < l:
109 109 c = program[pos]
110 110 if c.isspace(): # skip inter-token whitespace
111 111 pass
112 112 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
113 113 yield ('::', None, pos)
114 114 pos += 1 # skip ahead
115 115 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
116 116 yield ('..', None, pos)
117 117 pos += 1 # skip ahead
118 118 elif c in "():,-|&+!~^": # handle simple operators
119 119 yield (c, None, pos)
120 120 elif (c in '"\'' or c == 'r' and
121 121 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
122 122 if c == 'r':
123 123 pos += 1
124 124 c = program[pos]
125 125 decode = lambda x: x
126 126 else:
127 127 decode = lambda x: x.decode('string-escape')
128 128 pos += 1
129 129 s = pos
130 130 while pos < l: # find closing quote
131 131 d = program[pos]
132 132 if d == '\\': # skip over escaped characters
133 133 pos += 2
134 134 continue
135 135 if d == c:
136 136 yield ('string', decode(program[s:pos]), s)
137 137 break
138 138 pos += 1
139 139 else:
140 140 raise error.ParseError(_("unterminated string"), s)
141 141 # gather up a symbol/keyword
142 142 elif c.isalnum() or c in '._' or ord(c) > 127:
143 143 s = pos
144 144 pos += 1
145 145 while pos < l: # find end of symbol
146 146 d = program[pos]
147 147 if not (d.isalnum() or d in "._/" or ord(d) > 127):
148 148 break
149 149 if d == '.' and program[pos - 1] == '.': # special case for ..
150 150 pos -= 1
151 151 break
152 152 pos += 1
153 153 sym = program[s:pos]
154 154 if sym in keywords: # operator keywords
155 155 yield (sym, None, s)
156 156 else:
157 157 yield ('symbol', sym, s)
158 158 pos -= 1
159 159 else:
160 160 raise error.ParseError(_("syntax error"), pos)
161 161 pos += 1
162 162 yield ('end', None, pos)
163 163
164 164 # helpers
165 165
166 166 def getstring(x, err):
167 167 if x and (x[0] == 'string' or x[0] == 'symbol'):
168 168 return x[1]
169 169 raise error.ParseError(err)
170 170
171 171 def getlist(x):
172 172 if not x:
173 173 return []
174 174 if x[0] == 'list':
175 175 return getlist(x[1]) + [x[2]]
176 176 return [x]
177 177
178 178 def getargs(x, min, max, err):
179 179 l = getlist(x)
180 180 if len(l) < min or (max >= 0 and len(l) > max):
181 181 raise error.ParseError(err)
182 182 return l
183 183
184 184 def getset(repo, subset, x):
185 185 if not x:
186 186 raise error.ParseError(_("missing argument"))
187 187 return methods[x[0]](repo, subset, *x[1:])
188 188
189 189 def _getrevsource(repo, r):
190 190 extra = repo[r].extra()
191 191 for label in ('source', 'transplant_source', 'rebase_source'):
192 192 if label in extra:
193 193 try:
194 194 return repo[extra[label]].rev()
195 195 except error.RepoLookupError:
196 196 pass
197 197 return None
198 198
199 199 # operator methods
200 200
201 201 def stringset(repo, subset, x):
202 202 x = repo[x].rev()
203 203 if x == -1 and len(subset) == len(repo):
204 204 return [-1]
205 205 if len(subset) == len(repo) or x in subset:
206 206 return [x]
207 207 return []
208 208
209 209 def symbolset(repo, subset, x):
210 210 if x in symbols:
211 211 raise error.ParseError(_("can't use %s here") % x)
212 212 return stringset(repo, subset, x)
213 213
214 214 def rangeset(repo, subset, x, y):
215 215 m = getset(repo, subset, x)
216 216 if not m:
217 217 m = getset(repo, range(len(repo)), x)
218 218
219 219 n = getset(repo, subset, y)
220 220 if not n:
221 221 n = getset(repo, range(len(repo)), y)
222 222
223 223 if not m or not n:
224 224 return []
225 225 m, n = m[0], n[-1]
226 226
227 227 if m < n:
228 228 r = range(m, n + 1)
229 229 else:
230 230 r = range(m, n - 1, -1)
231 231 s = set(subset)
232 232 return [x for x in r if x in s]
233 233
234 234 def dagrange(repo, subset, x, y):
235 235 if subset:
236 236 r = range(len(repo))
237 237 xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
238 238 s = set(subset)
239 239 return [r for r in xs if r in s]
240 240 return []
241 241
242 242 def andset(repo, subset, x, y):
243 243 return getset(repo, getset(repo, subset, x), y)
244 244
245 245 def orset(repo, subset, x, y):
246 246 xl = getset(repo, subset, x)
247 247 s = set(xl)
248 248 yl = getset(repo, [r for r in subset if r not in s], y)
249 249 return xl + yl
250 250
251 251 def notset(repo, subset, x):
252 252 s = set(getset(repo, subset, x))
253 253 return [r for r in subset if r not in s]
254 254
255 255 def listset(repo, subset, a, b):
256 256 raise error.ParseError(_("can't use a list in this context"))
257 257
258 258 def func(repo, subset, a, b):
259 259 if a[0] == 'symbol' and a[1] in symbols:
260 260 return symbols[a[1]](repo, subset, b)
261 261 raise error.ParseError(_("not a function: %s") % a[1])
262 262
263 263 # functions
264 264
265 265 def adds(repo, subset, x):
266 266 """``adds(pattern)``
267 267 Changesets that add a file matching pattern.
268 268 """
269 269 # i18n: "adds" is a keyword
270 270 pat = getstring(x, _("adds requires a pattern"))
271 271 return checkstatus(repo, subset, pat, 1)
272 272
273 273 def ancestor(repo, subset, x):
274 274 """``ancestor(single, single)``
275 275 Greatest common ancestor of the two changesets.
276 276 """
277 277 # i18n: "ancestor" is a keyword
278 278 l = getargs(x, 2, 2, _("ancestor requires two arguments"))
279 279 r = range(len(repo))
280 280 a = getset(repo, r, l[0])
281 281 b = getset(repo, r, l[1])
282 282 if len(a) != 1 or len(b) != 1:
283 283 # i18n: "ancestor" is a keyword
284 284 raise error.ParseError(_("ancestor arguments must be single revisions"))
285 285 an = [repo[a[0]].ancestor(repo[b[0]]).rev()]
286 286
287 287 return [r for r in an if r in subset]
288 288
289 289 def _ancestors(repo, subset, x, followfirst=False):
290 290 args = getset(repo, range(len(repo)), x)
291 291 if not args:
292 292 return []
293 293 s = set(_revancestors(repo, args, followfirst)) | set(args)
294 294 return [r for r in subset if r in s]
295 295
296 296 def ancestors(repo, subset, x):
297 297 """``ancestors(set)``
298 298 Changesets that are ancestors of a changeset in set.
299 299 """
300 300 return _ancestors(repo, subset, x)
301 301
302 302 def _firstancestors(repo, subset, x):
303 303 # ``_firstancestors(set)``
304 304 # Like ``ancestors(set)`` but follows only the first parents.
305 305 return _ancestors(repo, subset, x, followfirst=True)
306 306
307 307 def ancestorspec(repo, subset, x, n):
308 308 """``set~n``
309 309 Changesets that are the Nth ancestor (first parents only) of a changeset
310 310 in set.
311 311 """
312 312 try:
313 313 n = int(n[1])
314 314 except (TypeError, ValueError):
315 315 raise error.ParseError(_("~ expects a number"))
316 316 ps = set()
317 317 cl = repo.changelog
318 318 for r in getset(repo, subset, x):
319 319 for i in range(n):
320 320 r = cl.parentrevs(r)[0]
321 321 ps.add(r)
322 322 return [r for r in subset if r in ps]
323 323
324 324 def author(repo, subset, x):
325 325 """``author(string)``
326 326 Alias for ``user(string)``.
327 327 """
328 328 # i18n: "author" is a keyword
329 329 n = encoding.lower(getstring(x, _("author requires a string")))
330 330 kind, pattern, matcher = _substringmatcher(n)
331 331 return [r for r in subset if matcher(encoding.lower(repo[r].user()))]
332 332
333 333 def bisect(repo, subset, x):
334 334 """``bisect(string)``
335 335 Changesets marked in the specified bisect status:
336 336
337 337 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
338 338 - ``goods``, ``bads`` : csets topologicaly good/bad
339 339 - ``range`` : csets taking part in the bisection
340 340 - ``pruned`` : csets that are goods, bads or skipped
341 341 - ``untested`` : csets whose fate is yet unknown
342 342 - ``ignored`` : csets ignored due to DAG topology
343 343 - ``current`` : the cset currently being bisected
344 344 """
345 345 status = getstring(x, _("bisect requires a string")).lower()
346 346 state = set(hbisect.get(repo, status))
347 347 return [r for r in subset if r in state]
348 348
349 349 # Backward-compatibility
350 350 # - no help entry so that we do not advertise it any more
351 351 def bisected(repo, subset, x):
352 352 return bisect(repo, subset, x)
353 353
354 354 def bookmark(repo, subset, x):
355 355 """``bookmark([name])``
356 356 The named bookmark or all bookmarks.
357 357
358 358 If `name` starts with `re:`, the remainder of the name is treated as
359 359 a regular expression. To match a bookmark that actually starts with `re:`,
360 360 use the prefix `literal:`.
361 361 """
362 362 # i18n: "bookmark" is a keyword
363 363 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
364 364 if args:
365 365 bm = getstring(args[0],
366 366 # i18n: "bookmark" is a keyword
367 367 _('the argument to bookmark must be a string'))
368 368 kind, pattern, matcher = _stringmatcher(bm)
369 369 if kind == 'literal':
370 370 bmrev = bookmarksmod.listbookmarks(repo).get(bm, None)
371 371 if not bmrev:
372 372 raise util.Abort(_("bookmark '%s' does not exist") % bm)
373 373 bmrev = repo[bmrev].rev()
374 374 return [r for r in subset if r == bmrev]
375 375 else:
376 376 matchrevs = set()
377 377 for name, bmrev in bookmarksmod.listbookmarks(repo).iteritems():
378 378 if matcher(name):
379 379 matchrevs.add(bmrev)
380 380 if not matchrevs:
381 381 raise util.Abort(_("no bookmarks exist that match '%s'")
382 382 % pattern)
383 383 bmrevs = set()
384 384 for bmrev in matchrevs:
385 385 bmrevs.add(repo[bmrev].rev())
386 386 return [r for r in subset if r in bmrevs]
387 387
388 388 bms = set([repo[r].rev()
389 389 for r in bookmarksmod.listbookmarks(repo).values()])
390 390 return [r for r in subset if r in bms]
391 391
392 392 def branch(repo, subset, x):
393 393 """``branch(string or set)``
394 394 All changesets belonging to the given branch or the branches of the given
395 395 changesets.
396 396
397 397 If `string` starts with `re:`, the remainder of the name is treated as
398 398 a regular expression. To match a branch that actually starts with `re:`,
399 399 use the prefix `literal:`.
400 400 """
401 401 try:
402 402 b = getstring(x, '')
403 403 except error.ParseError:
404 404 # not a string, but another revspec, e.g. tip()
405 405 pass
406 406 else:
407 407 kind, pattern, matcher = _stringmatcher(b)
408 408 if kind == 'literal':
409 409 # note: falls through to the revspec case if no branch with
410 410 # this name exists
411 411 if pattern in repo.branchmap():
412 412 return [r for r in subset if matcher(repo[r].branch())]
413 413 else:
414 414 return [r for r in subset if matcher(repo[r].branch())]
415 415
416 416 s = getset(repo, range(len(repo)), x)
417 417 b = set()
418 418 for r in s:
419 419 b.add(repo[r].branch())
420 420 s = set(s)
421 421 return [r for r in subset if r in s or repo[r].branch() in b]
422 422
423 423 def checkstatus(repo, subset, pat, field):
424 424 m = None
425 425 s = []
426 426 hasset = matchmod.patkind(pat) == 'set'
427 427 fname = None
428 428 for r in subset:
429 429 c = repo[r]
430 430 if not m or hasset:
431 431 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
432 432 if not m.anypats() and len(m.files()) == 1:
433 433 fname = m.files()[0]
434 434 if fname is not None:
435 435 if fname not in c.files():
436 436 continue
437 437 else:
438 438 for f in c.files():
439 439 if m(f):
440 440 break
441 441 else:
442 442 continue
443 443 files = repo.status(c.p1().node(), c.node())[field]
444 444 if fname is not None:
445 445 if fname in files:
446 446 s.append(r)
447 447 else:
448 448 for f in files:
449 449 if m(f):
450 450 s.append(r)
451 451 break
452 452 return s
453 453
454 454 def _children(repo, narrow, parentset):
455 455 cs = set()
456 456 pr = repo.changelog.parentrevs
457 457 for r in narrow:
458 458 for p in pr(r):
459 459 if p in parentset:
460 460 cs.add(r)
461 461 return cs
462 462
463 463 def children(repo, subset, x):
464 464 """``children(set)``
465 465 Child changesets of changesets in set.
466 466 """
467 467 s = set(getset(repo, range(len(repo)), x))
468 468 cs = _children(repo, subset, s)
469 469 return [r for r in subset if r in cs]
470 470
471 471 def closed(repo, subset, x):
472 472 """``closed()``
473 473 Changeset is closed.
474 474 """
475 475 # i18n: "closed" is a keyword
476 476 getargs(x, 0, 0, _("closed takes no arguments"))
477 477 return [r for r in subset if repo[r].closesbranch()]
478 478
479 479 def contains(repo, subset, x):
480 480 """``contains(pattern)``
481 481 Revision contains a file matching pattern. See :hg:`help patterns`
482 482 for information about file patterns.
483 483 """
484 484 # i18n: "contains" is a keyword
485 485 pat = getstring(x, _("contains requires a pattern"))
486 486 m = None
487 487 s = []
488 488 if not matchmod.patkind(pat):
489 489 for r in subset:
490 490 if pat in repo[r]:
491 491 s.append(r)
492 492 else:
493 493 for r in subset:
494 494 c = repo[r]
495 495 if not m or matchmod.patkind(pat) == 'set':
496 496 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
497 497 for f in c.manifest():
498 498 if m(f):
499 499 s.append(r)
500 500 break
501 501 return s
502 502
503 503 def converted(repo, subset, x):
504 504 """``converted([id])``
505 505 Changesets converted from the given identifier in the old repository if
506 506 present, or all converted changesets if no identifier is specified.
507 507 """
508 508
509 509 # There is exactly no chance of resolving the revision, so do a simple
510 510 # string compare and hope for the best
511 511
512 512 # i18n: "converted" is a keyword
513 513 rev = None
514 514 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
515 515 if l:
516 516 rev = getstring(l[0], _('converted requires a revision'))
517 517
518 518 def _matchvalue(r):
519 519 source = repo[r].extra().get('convert_revision', None)
520 520 return source is not None and (rev is None or source.startswith(rev))
521 521
522 522 return [r for r in subset if _matchvalue(r)]
523 523
524 524 def date(repo, subset, x):
525 525 """``date(interval)``
526 526 Changesets within the interval, see :hg:`help dates`.
527 527 """
528 528 # i18n: "date" is a keyword
529 529 ds = getstring(x, _("date requires a string"))
530 530 dm = util.matchdate(ds)
531 531 return [r for r in subset if dm(repo[r].date()[0])]
532 532
533 533 def desc(repo, subset, x):
534 534 """``desc(string)``
535 535 Search commit message for string. The match is case-insensitive.
536 536 """
537 537 # i18n: "desc" is a keyword
538 538 ds = encoding.lower(getstring(x, _("desc requires a string")))
539 539 l = []
540 540 for r in subset:
541 541 c = repo[r]
542 542 if ds in encoding.lower(c.description()):
543 543 l.append(r)
544 544 return l
545 545
546 546 def _descendants(repo, subset, x, followfirst=False):
547 547 args = getset(repo, range(len(repo)), x)
548 548 if not args:
549 549 return []
550 550 s = set(_revdescendants(repo, args, followfirst)) | set(args)
551 551 return [r for r in subset if r in s]
552 552
553 553 def descendants(repo, subset, x):
554 554 """``descendants(set)``
555 555 Changesets which are descendants of changesets in set.
556 556 """
557 557 return _descendants(repo, subset, x)
558 558
559 559 def _firstdescendants(repo, subset, x):
560 560 # ``_firstdescendants(set)``
561 561 # Like ``descendants(set)`` but follows only the first parents.
562 562 return _descendants(repo, subset, x, followfirst=True)
563 563
564 def destination(repo, subset, x):
565 """``destination([set])``
566 Changesets that were created by a graft, transplant or rebase operation,
567 with the given revisions specified as the source. Omitting the optional set
568 is the same as passing all().
569 """
570 if x is not None:
571 args = set(getset(repo, range(len(repo)), x))
572 else:
573 args = set(getall(repo, range(len(repo)), x))
574
575 dests = set()
576
577 # subset contains all of the possible destinations that can be returned, so
578 # iterate over them and see if their source(s) were provided in the args.
579 # Even if the immediate src of r is not in the args, src's source (or
580 # further back) may be. Scanning back further than the immediate src allows
581 # transitive transplants and rebases to yield the same results as transitive
582 # grafts.
583 for r in subset:
584 src = _getrevsource(repo, r)
585 lineage = None
586
587 while src is not None:
588 if lineage is None:
589 lineage = list()
590
591 lineage.append(r)
592
593 # The visited lineage is a match if the current source is in the arg
594 # set. Since every candidate dest is visited by way of iterating
595 # subset, any dests futher back in the lineage will be tested by a
596 # different iteration over subset. Likewise, if the src was already
597 # selected, the current lineage can be selected without going back
598 # further.
599 if src in args or src in dests:
600 dests.update(lineage)
601 break
602
603 r = src
604 src = _getrevsource(repo, r)
605
606 return [r for r in subset if r in dests]
607
564 608 def draft(repo, subset, x):
565 609 """``draft()``
566 610 Changeset in draft phase."""
567 611 getargs(x, 0, 0, _("draft takes no arguments"))
568 612 pc = repo._phasecache
569 613 return [r for r in subset if pc.phase(repo, r) == phases.draft]
570 614
571 615 def extinct(repo, subset, x):
572 616 """``extinct()``
573 617 obsolete changeset with obsolete descendant only."""
574 618 getargs(x, 0, 0, _("obsolete takes no arguments"))
575 619 extinctset = set(repo.revs('(obsolete()::) - (::(not obsolete()))'))
576 620 return [r for r in subset if r in extinctset]
577 621
578 622 def extra(repo, subset, x):
579 623 """``extra(label, [value])``
580 624 Changesets with the given label in the extra metadata, with the given
581 625 optional value.
582 626
583 627 If `value` starts with `re:`, the remainder of the value is treated as
584 628 a regular expression. To match a value that actually starts with `re:`,
585 629 use the prefix `literal:`.
586 630 """
587 631
588 632 l = getargs(x, 1, 2, _('extra takes at least 1 and at most 2 arguments'))
589 633 label = getstring(l[0], _('first argument to extra must be a string'))
590 634 value = None
591 635
592 636 if len(l) > 1:
593 637 value = getstring(l[1], _('second argument to extra must be a string'))
594 638 kind, value, matcher = _stringmatcher(value)
595 639
596 640 def _matchvalue(r):
597 641 extra = repo[r].extra()
598 642 return label in extra and (value is None or matcher(extra[label]))
599 643
600 644 return [r for r in subset if _matchvalue(r)]
601 645
602 646 def filelog(repo, subset, x):
603 647 """``filelog(pattern)``
604 648 Changesets connected to the specified filelog.
605 649 """
606 650
607 651 pat = getstring(x, _("filelog requires a pattern"))
608 652 m = matchmod.match(repo.root, repo.getcwd(), [pat], default='relpath',
609 653 ctx=repo[None])
610 654 s = set()
611 655
612 656 if not matchmod.patkind(pat):
613 657 for f in m.files():
614 658 fl = repo.file(f)
615 659 for fr in fl:
616 660 s.add(fl.linkrev(fr))
617 661 else:
618 662 for f in repo[None]:
619 663 if m(f):
620 664 fl = repo.file(f)
621 665 for fr in fl:
622 666 s.add(fl.linkrev(fr))
623 667
624 668 return [r for r in subset if r in s]
625 669
626 670 def first(repo, subset, x):
627 671 """``first(set, [n])``
628 672 An alias for limit().
629 673 """
630 674 return limit(repo, subset, x)
631 675
632 676 def _follow(repo, subset, x, name, followfirst=False):
633 677 l = getargs(x, 0, 1, _("%s takes no arguments or a filename") % name)
634 678 c = repo['.']
635 679 if l:
636 680 x = getstring(l[0], _("%s expected a filename") % name)
637 681 if x in c:
638 682 cx = c[x]
639 683 s = set(ctx.rev() for ctx in cx.ancestors(followfirst=followfirst))
640 684 # include the revision responsible for the most recent version
641 685 s.add(cx.linkrev())
642 686 else:
643 687 return []
644 688 else:
645 689 s = set(_revancestors(repo, [c.rev()], followfirst)) | set([c.rev()])
646 690
647 691 return [r for r in subset if r in s]
648 692
649 693 def follow(repo, subset, x):
650 694 """``follow([file])``
651 695 An alias for ``::.`` (ancestors of the working copy's first parent).
652 696 If a filename is specified, the history of the given file is followed,
653 697 including copies.
654 698 """
655 699 return _follow(repo, subset, x, 'follow')
656 700
657 701 def _followfirst(repo, subset, x):
658 702 # ``followfirst([file])``
659 703 # Like ``follow([file])`` but follows only the first parent of
660 704 # every revision or file revision.
661 705 return _follow(repo, subset, x, '_followfirst', followfirst=True)
662 706
663 707 def getall(repo, subset, x):
664 708 """``all()``
665 709 All changesets, the same as ``0:tip``.
666 710 """
667 711 # i18n: "all" is a keyword
668 712 getargs(x, 0, 0, _("all takes no arguments"))
669 713 return subset
670 714
671 715 def grep(repo, subset, x):
672 716 """``grep(regex)``
673 717 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
674 718 to ensure special escape characters are handled correctly. Unlike
675 719 ``keyword(string)``, the match is case-sensitive.
676 720 """
677 721 try:
678 722 # i18n: "grep" is a keyword
679 723 gr = re.compile(getstring(x, _("grep requires a string")))
680 724 except re.error, e:
681 725 raise error.ParseError(_('invalid match pattern: %s') % e)
682 726 l = []
683 727 for r in subset:
684 728 c = repo[r]
685 729 for e in c.files() + [c.user(), c.description()]:
686 730 if gr.search(e):
687 731 l.append(r)
688 732 break
689 733 return l
690 734
691 735 def _matchfiles(repo, subset, x):
692 736 # _matchfiles takes a revset list of prefixed arguments:
693 737 #
694 738 # [p:foo, i:bar, x:baz]
695 739 #
696 740 # builds a match object from them and filters subset. Allowed
697 741 # prefixes are 'p:' for regular patterns, 'i:' for include
698 742 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
699 743 # a revision identifier, or the empty string to reference the
700 744 # working directory, from which the match object is
701 745 # initialized. Use 'd:' to set the default matching mode, default
702 746 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
703 747
704 748 # i18n: "_matchfiles" is a keyword
705 749 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
706 750 pats, inc, exc = [], [], []
707 751 hasset = False
708 752 rev, default = None, None
709 753 for arg in l:
710 754 s = getstring(arg, _("_matchfiles requires string arguments"))
711 755 prefix, value = s[:2], s[2:]
712 756 if prefix == 'p:':
713 757 pats.append(value)
714 758 elif prefix == 'i:':
715 759 inc.append(value)
716 760 elif prefix == 'x:':
717 761 exc.append(value)
718 762 elif prefix == 'r:':
719 763 if rev is not None:
720 764 raise error.ParseError(_('_matchfiles expected at most one '
721 765 'revision'))
722 766 rev = value
723 767 elif prefix == 'd:':
724 768 if default is not None:
725 769 raise error.ParseError(_('_matchfiles expected at most one '
726 770 'default mode'))
727 771 default = value
728 772 else:
729 773 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
730 774 if not hasset and matchmod.patkind(value) == 'set':
731 775 hasset = True
732 776 if not default:
733 777 default = 'glob'
734 778 m = None
735 779 s = []
736 780 for r in subset:
737 781 c = repo[r]
738 782 if not m or (hasset and rev is None):
739 783 ctx = c
740 784 if rev is not None:
741 785 ctx = repo[rev or None]
742 786 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
743 787 exclude=exc, ctx=ctx, default=default)
744 788 for f in c.files():
745 789 if m(f):
746 790 s.append(r)
747 791 break
748 792 return s
749 793
750 794 def hasfile(repo, subset, x):
751 795 """``file(pattern)``
752 796 Changesets affecting files matched by pattern.
753 797 """
754 798 # i18n: "file" is a keyword
755 799 pat = getstring(x, _("file requires a pattern"))
756 800 return _matchfiles(repo, subset, ('string', 'p:' + pat))
757 801
758 802 def head(repo, subset, x):
759 803 """``head()``
760 804 Changeset is a named branch head.
761 805 """
762 806 # i18n: "head" is a keyword
763 807 getargs(x, 0, 0, _("head takes no arguments"))
764 808 hs = set()
765 809 for b, ls in repo.branchmap().iteritems():
766 810 hs.update(repo[h].rev() for h in ls)
767 811 return [r for r in subset if r in hs]
768 812
769 813 def heads(repo, subset, x):
770 814 """``heads(set)``
771 815 Members of set with no children in set.
772 816 """
773 817 s = getset(repo, subset, x)
774 818 ps = set(parents(repo, subset, x))
775 819 return [r for r in s if r not in ps]
776 820
777 821 def keyword(repo, subset, x):
778 822 """``keyword(string)``
779 823 Search commit message, user name, and names of changed files for
780 824 string. The match is case-insensitive.
781 825 """
782 826 # i18n: "keyword" is a keyword
783 827 kw = encoding.lower(getstring(x, _("keyword requires a string")))
784 828 l = []
785 829 for r in subset:
786 830 c = repo[r]
787 831 t = " ".join(c.files() + [c.user(), c.description()])
788 832 if kw in encoding.lower(t):
789 833 l.append(r)
790 834 return l
791 835
792 836 def limit(repo, subset, x):
793 837 """``limit(set, [n])``
794 838 First n members of set, defaulting to 1.
795 839 """
796 840 # i18n: "limit" is a keyword
797 841 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
798 842 try:
799 843 lim = 1
800 844 if len(l) == 2:
801 845 # i18n: "limit" is a keyword
802 846 lim = int(getstring(l[1], _("limit requires a number")))
803 847 except (TypeError, ValueError):
804 848 # i18n: "limit" is a keyword
805 849 raise error.ParseError(_("limit expects a number"))
806 850 ss = set(subset)
807 851 os = getset(repo, range(len(repo)), l[0])[:lim]
808 852 return [r for r in os if r in ss]
809 853
810 854 def last(repo, subset, x):
811 855 """``last(set, [n])``
812 856 Last n members of set, defaulting to 1.
813 857 """
814 858 # i18n: "last" is a keyword
815 859 l = getargs(x, 1, 2, _("last requires one or two arguments"))
816 860 try:
817 861 lim = 1
818 862 if len(l) == 2:
819 863 # i18n: "last" is a keyword
820 864 lim = int(getstring(l[1], _("last requires a number")))
821 865 except (TypeError, ValueError):
822 866 # i18n: "last" is a keyword
823 867 raise error.ParseError(_("last expects a number"))
824 868 ss = set(subset)
825 869 os = getset(repo, range(len(repo)), l[0])[-lim:]
826 870 return [r for r in os if r in ss]
827 871
828 872 def maxrev(repo, subset, x):
829 873 """``max(set)``
830 874 Changeset with highest revision number in set.
831 875 """
832 876 os = getset(repo, range(len(repo)), x)
833 877 if os:
834 878 m = max(os)
835 879 if m in subset:
836 880 return [m]
837 881 return []
838 882
839 883 def merge(repo, subset, x):
840 884 """``merge()``
841 885 Changeset is a merge changeset.
842 886 """
843 887 # i18n: "merge" is a keyword
844 888 getargs(x, 0, 0, _("merge takes no arguments"))
845 889 cl = repo.changelog
846 890 return [r for r in subset if cl.parentrevs(r)[1] != -1]
847 891
848 892 def minrev(repo, subset, x):
849 893 """``min(set)``
850 894 Changeset with lowest revision number in set.
851 895 """
852 896 os = getset(repo, range(len(repo)), x)
853 897 if os:
854 898 m = min(os)
855 899 if m in subset:
856 900 return [m]
857 901 return []
858 902
859 903 def modifies(repo, subset, x):
860 904 """``modifies(pattern)``
861 905 Changesets modifying files matched by pattern.
862 906 """
863 907 # i18n: "modifies" is a keyword
864 908 pat = getstring(x, _("modifies requires a pattern"))
865 909 return checkstatus(repo, subset, pat, 0)
866 910
867 911 def node_(repo, subset, x):
868 912 """``id(string)``
869 913 Revision non-ambiguously specified by the given hex string prefix.
870 914 """
871 915 # i18n: "id" is a keyword
872 916 l = getargs(x, 1, 1, _("id requires one argument"))
873 917 # i18n: "id" is a keyword
874 918 n = getstring(l[0], _("id requires a string"))
875 919 if len(n) == 40:
876 920 rn = repo[n].rev()
877 921 else:
878 922 rn = None
879 923 pm = repo.changelog._partialmatch(n)
880 924 if pm is not None:
881 925 rn = repo.changelog.rev(pm)
882 926
883 927 return [r for r in subset if r == rn]
884 928
885 929 def obsolete(repo, subset, x):
886 930 """``obsolete()``
887 931 Mutable changeset with a newer version."""
888 932 getargs(x, 0, 0, _("obsolete takes no arguments"))
889 933 return [r for r in subset if repo[r].obsolete()]
890 934
891 935 def origin(repo, subset, x):
892 936 """``origin([set])``
893 937 Changesets that were specified as a source for the grafts, transplants or
894 938 rebases that created the given revisions. Omitting the optional set is the
895 939 same as passing all(). If a changeset created by these operations is itself
896 940 specified as a source for one of these operations, only the source changeset
897 941 for the first operation is selected.
898 942 """
899 943 if x is not None:
900 944 args = set(getset(repo, range(len(repo)), x))
901 945 else:
902 946 args = set(getall(repo, range(len(repo)), x))
903 947
904 948 def _firstsrc(rev):
905 949 src = _getrevsource(repo, rev)
906 950 if src is None:
907 951 return None
908 952
909 953 while True:
910 954 prev = _getrevsource(repo, src)
911 955
912 956 if prev is None:
913 957 return src
914 958 src = prev
915 959
916 960 o = set([_firstsrc(r) for r in args])
917 961 return [r for r in subset if r in o]
918 962
919 963 def outgoing(repo, subset, x):
920 964 """``outgoing([path])``
921 965 Changesets not found in the specified destination repository, or the
922 966 default push location.
923 967 """
924 968 import hg # avoid start-up nasties
925 969 # i18n: "outgoing" is a keyword
926 970 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
927 971 # i18n: "outgoing" is a keyword
928 972 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
929 973 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
930 974 dest, branches = hg.parseurl(dest)
931 975 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
932 976 if revs:
933 977 revs = [repo.lookup(rev) for rev in revs]
934 978 other = hg.peer(repo, {}, dest)
935 979 repo.ui.pushbuffer()
936 980 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
937 981 repo.ui.popbuffer()
938 982 cl = repo.changelog
939 983 o = set([cl.rev(r) for r in outgoing.missing])
940 984 return [r for r in subset if r in o]
941 985
942 986 def p1(repo, subset, x):
943 987 """``p1([set])``
944 988 First parent of changesets in set, or the working directory.
945 989 """
946 990 if x is None:
947 991 p = repo[x].p1().rev()
948 992 return [r for r in subset if r == p]
949 993
950 994 ps = set()
951 995 cl = repo.changelog
952 996 for r in getset(repo, range(len(repo)), x):
953 997 ps.add(cl.parentrevs(r)[0])
954 998 return [r for r in subset if r in ps]
955 999
956 1000 def p2(repo, subset, x):
957 1001 """``p2([set])``
958 1002 Second parent of changesets in set, or the working directory.
959 1003 """
960 1004 if x is None:
961 1005 ps = repo[x].parents()
962 1006 try:
963 1007 p = ps[1].rev()
964 1008 return [r for r in subset if r == p]
965 1009 except IndexError:
966 1010 return []
967 1011
968 1012 ps = set()
969 1013 cl = repo.changelog
970 1014 for r in getset(repo, range(len(repo)), x):
971 1015 ps.add(cl.parentrevs(r)[1])
972 1016 return [r for r in subset if r in ps]
973 1017
974 1018 def parents(repo, subset, x):
975 1019 """``parents([set])``
976 1020 The set of all parents for all changesets in set, or the working directory.
977 1021 """
978 1022 if x is None:
979 1023 ps = tuple(p.rev() for p in repo[x].parents())
980 1024 return [r for r in subset if r in ps]
981 1025
982 1026 ps = set()
983 1027 cl = repo.changelog
984 1028 for r in getset(repo, range(len(repo)), x):
985 1029 ps.update(cl.parentrevs(r))
986 1030 return [r for r in subset if r in ps]
987 1031
988 1032 def parentspec(repo, subset, x, n):
989 1033 """``set^0``
990 1034 The set.
991 1035 ``set^1`` (or ``set^``), ``set^2``
992 1036 First or second parent, respectively, of all changesets in set.
993 1037 """
994 1038 try:
995 1039 n = int(n[1])
996 1040 if n not in (0, 1, 2):
997 1041 raise ValueError
998 1042 except (TypeError, ValueError):
999 1043 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1000 1044 ps = set()
1001 1045 cl = repo.changelog
1002 1046 for r in getset(repo, subset, x):
1003 1047 if n == 0:
1004 1048 ps.add(r)
1005 1049 elif n == 1:
1006 1050 ps.add(cl.parentrevs(r)[0])
1007 1051 elif n == 2:
1008 1052 parents = cl.parentrevs(r)
1009 1053 if len(parents) > 1:
1010 1054 ps.add(parents[1])
1011 1055 return [r for r in subset if r in ps]
1012 1056
1013 1057 def present(repo, subset, x):
1014 1058 """``present(set)``
1015 1059 An empty set, if any revision in set isn't found; otherwise,
1016 1060 all revisions in set.
1017 1061
1018 1062 If any of specified revisions is not present in the local repository,
1019 1063 the query is normally aborted. But this predicate allows the query
1020 1064 to continue even in such cases.
1021 1065 """
1022 1066 try:
1023 1067 return getset(repo, subset, x)
1024 1068 except error.RepoLookupError:
1025 1069 return []
1026 1070
1027 1071 def public(repo, subset, x):
1028 1072 """``public()``
1029 1073 Changeset in public phase."""
1030 1074 getargs(x, 0, 0, _("public takes no arguments"))
1031 1075 pc = repo._phasecache
1032 1076 return [r for r in subset if pc.phase(repo, r) == phases.public]
1033 1077
1034 1078 def remote(repo, subset, x):
1035 1079 """``remote([id [,path]])``
1036 1080 Local revision that corresponds to the given identifier in a
1037 1081 remote repository, if present. Here, the '.' identifier is a
1038 1082 synonym for the current local branch.
1039 1083 """
1040 1084
1041 1085 import hg # avoid start-up nasties
1042 1086 # i18n: "remote" is a keyword
1043 1087 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1044 1088
1045 1089 q = '.'
1046 1090 if len(l) > 0:
1047 1091 # i18n: "remote" is a keyword
1048 1092 q = getstring(l[0], _("remote requires a string id"))
1049 1093 if q == '.':
1050 1094 q = repo['.'].branch()
1051 1095
1052 1096 dest = ''
1053 1097 if len(l) > 1:
1054 1098 # i18n: "remote" is a keyword
1055 1099 dest = getstring(l[1], _("remote requires a repository path"))
1056 1100 dest = repo.ui.expandpath(dest or 'default')
1057 1101 dest, branches = hg.parseurl(dest)
1058 1102 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1059 1103 if revs:
1060 1104 revs = [repo.lookup(rev) for rev in revs]
1061 1105 other = hg.peer(repo, {}, dest)
1062 1106 n = other.lookup(q)
1063 1107 if n in repo:
1064 1108 r = repo[n].rev()
1065 1109 if r in subset:
1066 1110 return [r]
1067 1111 return []
1068 1112
1069 1113 def removes(repo, subset, x):
1070 1114 """``removes(pattern)``
1071 1115 Changesets which remove files matching pattern.
1072 1116 """
1073 1117 # i18n: "removes" is a keyword
1074 1118 pat = getstring(x, _("removes requires a pattern"))
1075 1119 return checkstatus(repo, subset, pat, 2)
1076 1120
1077 1121 def rev(repo, subset, x):
1078 1122 """``rev(number)``
1079 1123 Revision with the given numeric identifier.
1080 1124 """
1081 1125 # i18n: "rev" is a keyword
1082 1126 l = getargs(x, 1, 1, _("rev requires one argument"))
1083 1127 try:
1084 1128 # i18n: "rev" is a keyword
1085 1129 l = int(getstring(l[0], _("rev requires a number")))
1086 1130 except (TypeError, ValueError):
1087 1131 # i18n: "rev" is a keyword
1088 1132 raise error.ParseError(_("rev expects a number"))
1089 1133 return [r for r in subset if r == l]
1090 1134
1091 1135 def matching(repo, subset, x):
1092 1136 """``matching(revision [, field])``
1093 1137 Changesets in which a given set of fields match the set of fields in the
1094 1138 selected revision or set.
1095 1139
1096 1140 To match more than one field pass the list of fields to match separated
1097 1141 by spaces (e.g. ``author description``).
1098 1142
1099 1143 Valid fields are most regular revision fields and some special fields.
1100 1144
1101 1145 Regular revision fields are ``description``, ``author``, ``branch``,
1102 1146 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1103 1147 and ``diff``.
1104 1148 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1105 1149 contents of the revision. Two revisions matching their ``diff`` will
1106 1150 also match their ``files``.
1107 1151
1108 1152 Special fields are ``summary`` and ``metadata``:
1109 1153 ``summary`` matches the first line of the description.
1110 1154 ``metadata`` is equivalent to matching ``description user date``
1111 1155 (i.e. it matches the main metadata fields).
1112 1156
1113 1157 ``metadata`` is the default field which is used when no fields are
1114 1158 specified. You can match more than one field at a time.
1115 1159 """
1116 1160 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1117 1161
1118 1162 revs = getset(repo, xrange(len(repo)), l[0])
1119 1163
1120 1164 fieldlist = ['metadata']
1121 1165 if len(l) > 1:
1122 1166 fieldlist = getstring(l[1],
1123 1167 _("matching requires a string "
1124 1168 "as its second argument")).split()
1125 1169
1126 1170 # Make sure that there are no repeated fields,
1127 1171 # expand the 'special' 'metadata' field type
1128 1172 # and check the 'files' whenever we check the 'diff'
1129 1173 fields = []
1130 1174 for field in fieldlist:
1131 1175 if field == 'metadata':
1132 1176 fields += ['user', 'description', 'date']
1133 1177 elif field == 'diff':
1134 1178 # a revision matching the diff must also match the files
1135 1179 # since matching the diff is very costly, make sure to
1136 1180 # also match the files first
1137 1181 fields += ['files', 'diff']
1138 1182 else:
1139 1183 if field == 'author':
1140 1184 field = 'user'
1141 1185 fields.append(field)
1142 1186 fields = set(fields)
1143 1187 if 'summary' in fields and 'description' in fields:
1144 1188 # If a revision matches its description it also matches its summary
1145 1189 fields.discard('summary')
1146 1190
1147 1191 # We may want to match more than one field
1148 1192 # Not all fields take the same amount of time to be matched
1149 1193 # Sort the selected fields in order of increasing matching cost
1150 1194 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1151 1195 'files', 'description', 'substate', 'diff']
1152 1196 def fieldkeyfunc(f):
1153 1197 try:
1154 1198 return fieldorder.index(f)
1155 1199 except ValueError:
1156 1200 # assume an unknown field is very costly
1157 1201 return len(fieldorder)
1158 1202 fields = list(fields)
1159 1203 fields.sort(key=fieldkeyfunc)
1160 1204
1161 1205 # Each field will be matched with its own "getfield" function
1162 1206 # which will be added to the getfieldfuncs array of functions
1163 1207 getfieldfuncs = []
1164 1208 _funcs = {
1165 1209 'user': lambda r: repo[r].user(),
1166 1210 'branch': lambda r: repo[r].branch(),
1167 1211 'date': lambda r: repo[r].date(),
1168 1212 'description': lambda r: repo[r].description(),
1169 1213 'files': lambda r: repo[r].files(),
1170 1214 'parents': lambda r: repo[r].parents(),
1171 1215 'phase': lambda r: repo[r].phase(),
1172 1216 'substate': lambda r: repo[r].substate,
1173 1217 'summary': lambda r: repo[r].description().splitlines()[0],
1174 1218 'diff': lambda r: list(repo[r].diff(git=True),)
1175 1219 }
1176 1220 for info in fields:
1177 1221 getfield = _funcs.get(info, None)
1178 1222 if getfield is None:
1179 1223 raise error.ParseError(
1180 1224 _("unexpected field name passed to matching: %s") % info)
1181 1225 getfieldfuncs.append(getfield)
1182 1226 # convert the getfield array of functions into a "getinfo" function
1183 1227 # which returns an array of field values (or a single value if there
1184 1228 # is only one field to match)
1185 1229 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1186 1230
1187 1231 matches = set()
1188 1232 for rev in revs:
1189 1233 target = getinfo(rev)
1190 1234 for r in subset:
1191 1235 match = True
1192 1236 for n, f in enumerate(getfieldfuncs):
1193 1237 if target[n] != f(r):
1194 1238 match = False
1195 1239 break
1196 1240 if match:
1197 1241 matches.add(r)
1198 1242 return [r for r in subset if r in matches]
1199 1243
1200 1244 def reverse(repo, subset, x):
1201 1245 """``reverse(set)``
1202 1246 Reverse order of set.
1203 1247 """
1204 1248 l = getset(repo, subset, x)
1205 1249 if not isinstance(l, list):
1206 1250 l = list(l)
1207 1251 l.reverse()
1208 1252 return l
1209 1253
1210 1254 def roots(repo, subset, x):
1211 1255 """``roots(set)``
1212 1256 Changesets in set with no parent changeset in set.
1213 1257 """
1214 1258 s = set(getset(repo, xrange(len(repo)), x))
1215 1259 subset = [r for r in subset if r in s]
1216 1260 cs = _children(repo, subset, s)
1217 1261 return [r for r in subset if r not in cs]
1218 1262
1219 1263 def secret(repo, subset, x):
1220 1264 """``secret()``
1221 1265 Changeset in secret phase."""
1222 1266 getargs(x, 0, 0, _("secret takes no arguments"))
1223 1267 pc = repo._phasecache
1224 1268 return [r for r in subset if pc.phase(repo, r) == phases.secret]
1225 1269
1226 1270 def sort(repo, subset, x):
1227 1271 """``sort(set[, [-]key...])``
1228 1272 Sort set by keys. The default sort order is ascending, specify a key
1229 1273 as ``-key`` to sort in descending order.
1230 1274
1231 1275 The keys can be:
1232 1276
1233 1277 - ``rev`` for the revision number,
1234 1278 - ``branch`` for the branch name,
1235 1279 - ``desc`` for the commit message (description),
1236 1280 - ``user`` for user name (``author`` can be used as an alias),
1237 1281 - ``date`` for the commit date
1238 1282 """
1239 1283 # i18n: "sort" is a keyword
1240 1284 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1241 1285 keys = "rev"
1242 1286 if len(l) == 2:
1243 1287 keys = getstring(l[1], _("sort spec must be a string"))
1244 1288
1245 1289 s = l[0]
1246 1290 keys = keys.split()
1247 1291 l = []
1248 1292 def invert(s):
1249 1293 return "".join(chr(255 - ord(c)) for c in s)
1250 1294 for r in getset(repo, subset, s):
1251 1295 c = repo[r]
1252 1296 e = []
1253 1297 for k in keys:
1254 1298 if k == 'rev':
1255 1299 e.append(r)
1256 1300 elif k == '-rev':
1257 1301 e.append(-r)
1258 1302 elif k == 'branch':
1259 1303 e.append(c.branch())
1260 1304 elif k == '-branch':
1261 1305 e.append(invert(c.branch()))
1262 1306 elif k == 'desc':
1263 1307 e.append(c.description())
1264 1308 elif k == '-desc':
1265 1309 e.append(invert(c.description()))
1266 1310 elif k in 'user author':
1267 1311 e.append(c.user())
1268 1312 elif k in '-user -author':
1269 1313 e.append(invert(c.user()))
1270 1314 elif k == 'date':
1271 1315 e.append(c.date()[0])
1272 1316 elif k == '-date':
1273 1317 e.append(-c.date()[0])
1274 1318 else:
1275 1319 raise error.ParseError(_("unknown sort key %r") % k)
1276 1320 e.append(r)
1277 1321 l.append(e)
1278 1322 l.sort()
1279 1323 return [e[-1] for e in l]
1280 1324
1281 1325 def _stringmatcher(pattern):
1282 1326 """
1283 1327 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1284 1328 returns the matcher name, pattern, and matcher function.
1285 1329 missing or unknown prefixes are treated as literal matches.
1286 1330
1287 1331 helper for tests:
1288 1332 >>> def test(pattern, *tests):
1289 1333 ... kind, pattern, matcher = _stringmatcher(pattern)
1290 1334 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1291 1335
1292 1336 exact matching (no prefix):
1293 1337 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1294 1338 ('literal', 'abcdefg', [False, False, True])
1295 1339
1296 1340 regex matching ('re:' prefix)
1297 1341 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1298 1342 ('re', 'a.+b', [False, False, True])
1299 1343
1300 1344 force exact matches ('literal:' prefix)
1301 1345 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1302 1346 ('literal', 're:foobar', [False, True])
1303 1347
1304 1348 unknown prefixes are ignored and treated as literals
1305 1349 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1306 1350 ('literal', 'foo:bar', [False, False, True])
1307 1351 """
1308 1352 if pattern.startswith('re:'):
1309 1353 pattern = pattern[3:]
1310 1354 try:
1311 1355 regex = re.compile(pattern)
1312 1356 except re.error, e:
1313 1357 raise error.ParseError(_('invalid regular expression: %s')
1314 1358 % e)
1315 1359 return 're', pattern, regex.search
1316 1360 elif pattern.startswith('literal:'):
1317 1361 pattern = pattern[8:]
1318 1362 return 'literal', pattern, pattern.__eq__
1319 1363
1320 1364 def _substringmatcher(pattern):
1321 1365 kind, pattern, matcher = _stringmatcher(pattern)
1322 1366 if kind == 'literal':
1323 1367 matcher = lambda s: pattern in s
1324 1368 return kind, pattern, matcher
1325 1369
1326 1370 def tag(repo, subset, x):
1327 1371 """``tag([name])``
1328 1372 The specified tag by name, or all tagged revisions if no name is given.
1329 1373 """
1330 1374 # i18n: "tag" is a keyword
1331 1375 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
1332 1376 cl = repo.changelog
1333 1377 if args:
1334 1378 pattern = getstring(args[0],
1335 1379 # i18n: "tag" is a keyword
1336 1380 _('the argument to tag must be a string'))
1337 1381 kind, pattern, matcher = _stringmatcher(pattern)
1338 1382 if kind == 'literal':
1339 1383 # avoid resolving all tags
1340 1384 tn = repo._tagscache.tags.get(pattern, None)
1341 1385 if tn is None:
1342 1386 raise util.Abort(_("tag '%s' does not exist") % pattern)
1343 1387 s = set([repo[tn].rev()])
1344 1388 else:
1345 1389 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
1346 1390 if not s:
1347 1391 raise util.Abort(_("no tags exist that match '%s'") % pattern)
1348 1392 else:
1349 1393 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
1350 1394 return [r for r in subset if r in s]
1351 1395
1352 1396 def tagged(repo, subset, x):
1353 1397 return tag(repo, subset, x)
1354 1398
1355 1399 def unstable(repo, subset, x):
1356 1400 """``unstable()``
1357 1401 Unstable changesets are non-obsolete with obsolete descendants."""
1358 1402 getargs(x, 0, 0, _("obsolete takes no arguments"))
1359 1403 unstableset = set(repo.revs('(obsolete()::) - obsolete()'))
1360 1404 return [r for r in subset if r in unstableset]
1361 1405
1362 1406
1363 1407 def user(repo, subset, x):
1364 1408 """``user(string)``
1365 1409 User name contains string. The match is case-insensitive.
1366 1410
1367 1411 If `string` starts with `re:`, the remainder of the string is treated as
1368 1412 a regular expression. To match a user that actually contains `re:`, use
1369 1413 the prefix `literal:`.
1370 1414 """
1371 1415 return author(repo, subset, x)
1372 1416
1373 1417 # for internal use
1374 1418 def _list(repo, subset, x):
1375 1419 s = getstring(x, "internal error")
1376 1420 if not s:
1377 1421 return []
1378 1422 if not isinstance(subset, set):
1379 1423 subset = set(subset)
1380 1424 ls = [repo[r].rev() for r in s.split('\0')]
1381 1425 return [r for r in ls if r in subset]
1382 1426
1383 1427 symbols = {
1384 1428 "adds": adds,
1385 1429 "all": getall,
1386 1430 "ancestor": ancestor,
1387 1431 "ancestors": ancestors,
1388 1432 "_firstancestors": _firstancestors,
1389 1433 "author": author,
1390 1434 "bisect": bisect,
1391 1435 "bisected": bisected,
1392 1436 "bookmark": bookmark,
1393 1437 "branch": branch,
1394 1438 "children": children,
1395 1439 "closed": closed,
1396 1440 "contains": contains,
1397 1441 "converted": converted,
1398 1442 "date": date,
1399 1443 "desc": desc,
1400 1444 "descendants": descendants,
1401 1445 "_firstdescendants": _firstdescendants,
1446 "destination": destination,
1402 1447 "draft": draft,
1403 1448 "extinct": extinct,
1404 1449 "extra": extra,
1405 1450 "file": hasfile,
1406 1451 "filelog": filelog,
1407 1452 "first": first,
1408 1453 "follow": follow,
1409 1454 "_followfirst": _followfirst,
1410 1455 "grep": grep,
1411 1456 "head": head,
1412 1457 "heads": heads,
1413 1458 "id": node_,
1414 1459 "keyword": keyword,
1415 1460 "last": last,
1416 1461 "limit": limit,
1417 1462 "_matchfiles": _matchfiles,
1418 1463 "max": maxrev,
1419 1464 "merge": merge,
1420 1465 "min": minrev,
1421 1466 "modifies": modifies,
1422 1467 "obsolete": obsolete,
1423 1468 "origin": origin,
1424 1469 "outgoing": outgoing,
1425 1470 "p1": p1,
1426 1471 "p2": p2,
1427 1472 "parents": parents,
1428 1473 "present": present,
1429 1474 "public": public,
1430 1475 "remote": remote,
1431 1476 "removes": removes,
1432 1477 "rev": rev,
1433 1478 "reverse": reverse,
1434 1479 "roots": roots,
1435 1480 "sort": sort,
1436 1481 "secret": secret,
1437 1482 "matching": matching,
1438 1483 "tag": tag,
1439 1484 "tagged": tagged,
1440 1485 "user": user,
1441 1486 "unstable": unstable,
1442 1487 "_list": _list,
1443 1488 }
1444 1489
1445 1490 methods = {
1446 1491 "range": rangeset,
1447 1492 "dagrange": dagrange,
1448 1493 "string": stringset,
1449 1494 "symbol": symbolset,
1450 1495 "and": andset,
1451 1496 "or": orset,
1452 1497 "not": notset,
1453 1498 "list": listset,
1454 1499 "func": func,
1455 1500 "ancestor": ancestorspec,
1456 1501 "parent": parentspec,
1457 1502 "parentpost": p1,
1458 1503 }
1459 1504
1460 1505 def optimize(x, small):
1461 1506 if x is None:
1462 1507 return 0, x
1463 1508
1464 1509 smallbonus = 1
1465 1510 if small:
1466 1511 smallbonus = .5
1467 1512
1468 1513 op = x[0]
1469 1514 if op == 'minus':
1470 1515 return optimize(('and', x[1], ('not', x[2])), small)
1471 1516 elif op == 'dagrangepre':
1472 1517 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
1473 1518 elif op == 'dagrangepost':
1474 1519 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
1475 1520 elif op == 'rangepre':
1476 1521 return optimize(('range', ('string', '0'), x[1]), small)
1477 1522 elif op == 'rangepost':
1478 1523 return optimize(('range', x[1], ('string', 'tip')), small)
1479 1524 elif op == 'negate':
1480 1525 return optimize(('string',
1481 1526 '-' + getstring(x[1], _("can't negate that"))), small)
1482 1527 elif op in 'string symbol negate':
1483 1528 return smallbonus, x # single revisions are small
1484 1529 elif op == 'and':
1485 1530 wa, ta = optimize(x[1], True)
1486 1531 wb, tb = optimize(x[2], True)
1487 1532 w = min(wa, wb)
1488 1533 if wa > wb:
1489 1534 return w, (op, tb, ta)
1490 1535 return w, (op, ta, tb)
1491 1536 elif op == 'or':
1492 1537 wa, ta = optimize(x[1], False)
1493 1538 wb, tb = optimize(x[2], False)
1494 1539 if wb < wa:
1495 1540 wb, wa = wa, wb
1496 1541 return max(wa, wb), (op, ta, tb)
1497 1542 elif op == 'not':
1498 1543 o = optimize(x[1], not small)
1499 1544 return o[0], (op, o[1])
1500 1545 elif op == 'parentpost':
1501 1546 o = optimize(x[1], small)
1502 1547 return o[0], (op, o[1])
1503 1548 elif op == 'group':
1504 1549 return optimize(x[1], small)
1505 1550 elif op in 'dagrange range list parent ancestorspec':
1506 1551 if op == 'parent':
1507 1552 # x^:y means (x^) : y, not x ^ (:y)
1508 1553 post = ('parentpost', x[1])
1509 1554 if x[2][0] == 'dagrangepre':
1510 1555 return optimize(('dagrange', post, x[2][1]), small)
1511 1556 elif x[2][0] == 'rangepre':
1512 1557 return optimize(('range', post, x[2][1]), small)
1513 1558
1514 1559 wa, ta = optimize(x[1], small)
1515 1560 wb, tb = optimize(x[2], small)
1516 1561 return wa + wb, (op, ta, tb)
1517 1562 elif op == 'func':
1518 1563 f = getstring(x[1], _("not a symbol"))
1519 1564 wa, ta = optimize(x[2], small)
1520 1565 if f in ("author branch closed date desc file grep keyword "
1521 1566 "outgoing user"):
1522 1567 w = 10 # slow
1523 1568 elif f in "modifies adds removes":
1524 1569 w = 30 # slower
1525 1570 elif f == "contains":
1526 1571 w = 100 # very slow
1527 1572 elif f == "ancestor":
1528 1573 w = 1 * smallbonus
1529 1574 elif f in "reverse limit first":
1530 1575 w = 0
1531 1576 elif f in "sort":
1532 1577 w = 10 # assume most sorts look at changelog
1533 1578 else:
1534 1579 w = 1
1535 1580 return w + wa, (op, x[1], ta)
1536 1581 return 1, x
1537 1582
1538 1583 _aliasarg = ('func', ('symbol', '_aliasarg'))
1539 1584 def _getaliasarg(tree):
1540 1585 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
1541 1586 return X, None otherwise.
1542 1587 """
1543 1588 if (len(tree) == 3 and tree[:2] == _aliasarg
1544 1589 and tree[2][0] == 'string'):
1545 1590 return tree[2][1]
1546 1591 return None
1547 1592
1548 1593 def _checkaliasarg(tree, known=None):
1549 1594 """Check tree contains no _aliasarg construct or only ones which
1550 1595 value is in known. Used to avoid alias placeholders injection.
1551 1596 """
1552 1597 if isinstance(tree, tuple):
1553 1598 arg = _getaliasarg(tree)
1554 1599 if arg is not None and (not known or arg not in known):
1555 1600 raise error.ParseError(_("not a function: %s") % '_aliasarg')
1556 1601 for t in tree:
1557 1602 _checkaliasarg(t, known)
1558 1603
1559 1604 class revsetalias(object):
1560 1605 funcre = re.compile('^([^(]+)\(([^)]+)\)$')
1561 1606 args = None
1562 1607
1563 1608 def __init__(self, name, value):
1564 1609 '''Aliases like:
1565 1610
1566 1611 h = heads(default)
1567 1612 b($1) = ancestors($1) - ancestors(default)
1568 1613 '''
1569 1614 m = self.funcre.search(name)
1570 1615 if m:
1571 1616 self.name = m.group(1)
1572 1617 self.tree = ('func', ('symbol', m.group(1)))
1573 1618 self.args = [x.strip() for x in m.group(2).split(',')]
1574 1619 for arg in self.args:
1575 1620 # _aliasarg() is an unknown symbol only used separate
1576 1621 # alias argument placeholders from regular strings.
1577 1622 value = value.replace(arg, '_aliasarg(%r)' % (arg,))
1578 1623 else:
1579 1624 self.name = name
1580 1625 self.tree = ('symbol', name)
1581 1626
1582 1627 self.replacement, pos = parse(value)
1583 1628 if pos != len(value):
1584 1629 raise error.ParseError(_('invalid token'), pos)
1585 1630 # Check for placeholder injection
1586 1631 _checkaliasarg(self.replacement, self.args)
1587 1632
1588 1633 def _getalias(aliases, tree):
1589 1634 """If tree looks like an unexpanded alias, return it. Return None
1590 1635 otherwise.
1591 1636 """
1592 1637 if isinstance(tree, tuple) and tree:
1593 1638 if tree[0] == 'symbol' and len(tree) == 2:
1594 1639 name = tree[1]
1595 1640 alias = aliases.get(name)
1596 1641 if alias and alias.args is None and alias.tree == tree:
1597 1642 return alias
1598 1643 if tree[0] == 'func' and len(tree) > 1:
1599 1644 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
1600 1645 name = tree[1][1]
1601 1646 alias = aliases.get(name)
1602 1647 if alias and alias.args is not None and alias.tree == tree[:2]:
1603 1648 return alias
1604 1649 return None
1605 1650
1606 1651 def _expandargs(tree, args):
1607 1652 """Replace _aliasarg instances with the substitution value of the
1608 1653 same name in args, recursively.
1609 1654 """
1610 1655 if not tree or not isinstance(tree, tuple):
1611 1656 return tree
1612 1657 arg = _getaliasarg(tree)
1613 1658 if arg is not None:
1614 1659 return args[arg]
1615 1660 return tuple(_expandargs(t, args) for t in tree)
1616 1661
1617 1662 def _expandaliases(aliases, tree, expanding, cache):
1618 1663 """Expand aliases in tree, recursively.
1619 1664
1620 1665 'aliases' is a dictionary mapping user defined aliases to
1621 1666 revsetalias objects.
1622 1667 """
1623 1668 if not isinstance(tree, tuple):
1624 1669 # Do not expand raw strings
1625 1670 return tree
1626 1671 alias = _getalias(aliases, tree)
1627 1672 if alias is not None:
1628 1673 if alias in expanding:
1629 1674 raise error.ParseError(_('infinite expansion of revset alias "%s" '
1630 1675 'detected') % alias.name)
1631 1676 expanding.append(alias)
1632 1677 if alias.name not in cache:
1633 1678 cache[alias.name] = _expandaliases(aliases, alias.replacement,
1634 1679 expanding, cache)
1635 1680 result = cache[alias.name]
1636 1681 expanding.pop()
1637 1682 if alias.args is not None:
1638 1683 l = getlist(tree[2])
1639 1684 if len(l) != len(alias.args):
1640 1685 raise error.ParseError(
1641 1686 _('invalid number of arguments: %s') % len(l))
1642 1687 l = [_expandaliases(aliases, a, [], cache) for a in l]
1643 1688 result = _expandargs(result, dict(zip(alias.args, l)))
1644 1689 else:
1645 1690 result = tuple(_expandaliases(aliases, t, expanding, cache)
1646 1691 for t in tree)
1647 1692 return result
1648 1693
1649 1694 def findaliases(ui, tree):
1650 1695 _checkaliasarg(tree)
1651 1696 aliases = {}
1652 1697 for k, v in ui.configitems('revsetalias'):
1653 1698 alias = revsetalias(k, v)
1654 1699 aliases[alias.name] = alias
1655 1700 return _expandaliases(aliases, tree, [], {})
1656 1701
1657 1702 parse = parser.parser(tokenize, elements).parse
1658 1703
1659 1704 def match(ui, spec):
1660 1705 if not spec:
1661 1706 raise error.ParseError(_("empty query"))
1662 1707 tree, pos = parse(spec)
1663 1708 if (pos != len(spec)):
1664 1709 raise error.ParseError(_("invalid token"), pos)
1665 1710 if ui:
1666 1711 tree = findaliases(ui, tree)
1667 1712 weight, tree = optimize(tree, True)
1668 1713 def mfunc(repo, subset):
1669 1714 return getset(repo, subset, tree)
1670 1715 return mfunc
1671 1716
1672 1717 def formatspec(expr, *args):
1673 1718 '''
1674 1719 This is a convenience function for using revsets internally, and
1675 1720 escapes arguments appropriately. Aliases are intentionally ignored
1676 1721 so that intended expression behavior isn't accidentally subverted.
1677 1722
1678 1723 Supported arguments:
1679 1724
1680 1725 %r = revset expression, parenthesized
1681 1726 %d = int(arg), no quoting
1682 1727 %s = string(arg), escaped and single-quoted
1683 1728 %b = arg.branch(), escaped and single-quoted
1684 1729 %n = hex(arg), single-quoted
1685 1730 %% = a literal '%'
1686 1731
1687 1732 Prefixing the type with 'l' specifies a parenthesized list of that type.
1688 1733
1689 1734 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
1690 1735 '(10 or 11):: and ((this()) or (that()))'
1691 1736 >>> formatspec('%d:: and not %d::', 10, 20)
1692 1737 '10:: and not 20::'
1693 1738 >>> formatspec('%ld or %ld', [], [1])
1694 1739 "_list('') or 1"
1695 1740 >>> formatspec('keyword(%s)', 'foo\\xe9')
1696 1741 "keyword('foo\\\\xe9')"
1697 1742 >>> b = lambda: 'default'
1698 1743 >>> b.branch = b
1699 1744 >>> formatspec('branch(%b)', b)
1700 1745 "branch('default')"
1701 1746 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
1702 1747 "root(_list('a\\x00b\\x00c\\x00d'))"
1703 1748 '''
1704 1749
1705 1750 def quote(s):
1706 1751 return repr(str(s))
1707 1752
1708 1753 def argtype(c, arg):
1709 1754 if c == 'd':
1710 1755 return str(int(arg))
1711 1756 elif c == 's':
1712 1757 return quote(arg)
1713 1758 elif c == 'r':
1714 1759 parse(arg) # make sure syntax errors are confined
1715 1760 return '(%s)' % arg
1716 1761 elif c == 'n':
1717 1762 return quote(node.hex(arg))
1718 1763 elif c == 'b':
1719 1764 return quote(arg.branch())
1720 1765
1721 1766 def listexp(s, t):
1722 1767 l = len(s)
1723 1768 if l == 0:
1724 1769 return "_list('')"
1725 1770 elif l == 1:
1726 1771 return argtype(t, s[0])
1727 1772 elif t == 'd':
1728 1773 return "_list('%s')" % "\0".join(str(int(a)) for a in s)
1729 1774 elif t == 's':
1730 1775 return "_list('%s')" % "\0".join(s)
1731 1776 elif t == 'n':
1732 1777 return "_list('%s')" % "\0".join(node.hex(a) for a in s)
1733 1778 elif t == 'b':
1734 1779 return "_list('%s')" % "\0".join(a.branch() for a in s)
1735 1780
1736 1781 m = l // 2
1737 1782 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
1738 1783
1739 1784 ret = ''
1740 1785 pos = 0
1741 1786 arg = 0
1742 1787 while pos < len(expr):
1743 1788 c = expr[pos]
1744 1789 if c == '%':
1745 1790 pos += 1
1746 1791 d = expr[pos]
1747 1792 if d == '%':
1748 1793 ret += d
1749 1794 elif d in 'dsnbr':
1750 1795 ret += argtype(d, args[arg])
1751 1796 arg += 1
1752 1797 elif d == 'l':
1753 1798 # a list of some type
1754 1799 pos += 1
1755 1800 d = expr[pos]
1756 1801 ret += listexp(list(args[arg]), d)
1757 1802 arg += 1
1758 1803 else:
1759 1804 raise util.Abort('unexpected revspec format character %s' % d)
1760 1805 else:
1761 1806 ret += c
1762 1807 pos += 1
1763 1808
1764 1809 return ret
1765 1810
1766 1811 def prettyformat(tree):
1767 1812 def _prettyformat(tree, level, lines):
1768 1813 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
1769 1814 lines.append((level, str(tree)))
1770 1815 else:
1771 1816 lines.append((level, '(%s' % tree[0]))
1772 1817 for s in tree[1:]:
1773 1818 _prettyformat(s, level + 1, lines)
1774 1819 lines[-1:] = [(lines[-1][0], lines[-1][1] + ')')]
1775 1820
1776 1821 lines = []
1777 1822 _prettyformat(tree, 0, lines)
1778 1823 output = '\n'.join((' '*l + s) for l, s in lines)
1779 1824 return output
1780 1825
1781 1826 # tell hggettext to extract docstrings from these functions:
1782 1827 i18nfunctions = symbols.values()
@@ -1,407 +1,535 b''
1 1 Create a repo with some stuff in it:
2 2
3 3 $ hg init a
4 4 $ cd a
5 5 $ echo a > a
6 6 $ echo a > d
7 7 $ echo a > e
8 8 $ hg ci -qAm0
9 9 $ echo b > a
10 10 $ hg ci -m1 -u bar
11 11 $ hg mv a b
12 12 $ hg ci -m2
13 13 $ hg cp b c
14 14 $ hg ci -m3 -u baz
15 15 $ echo b > d
16 16 $ echo f > e
17 17 $ hg ci -m4
18 18 $ hg up -q 3
19 19 $ echo b > e
20 20 $ hg branch -q stable
21 21 $ hg ci -m5
22 22 $ hg merge -q default --tool internal:local
23 23 $ hg branch -q default
24 24 $ hg ci -m6
25 25 $ hg phase --public 3
26 26 $ hg phase --force --secret 6
27 27
28 28 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
29 29 @ test@6.secret: 6
30 30 |\
31 31 | o test@5.draft: 5
32 32 | |
33 33 o | test@4.draft: 4
34 34 |/
35 35 o baz@3.public: 3
36 36 |
37 37 o test@2.public: 2
38 38 |
39 39 o bar@1.public: 1
40 40 |
41 41 o test@0.public: 0
42 42
43 43
44 44 Need to specify a rev:
45 45
46 46 $ hg graft
47 47 abort: no revisions specified
48 48 [255]
49 49
50 50 Can't graft ancestor:
51 51
52 52 $ hg graft 1 2
53 53 skipping ancestor revision 1
54 54 skipping ancestor revision 2
55 55 [255]
56 56
57 57 Specify revisions with -r:
58 58
59 59 $ hg graft -r 1 -r 2
60 60 skipping ancestor revision 1
61 61 skipping ancestor revision 2
62 62 [255]
63 63
64 64 $ hg graft -r 1 2
65 65 skipping ancestor revision 2
66 66 skipping ancestor revision 1
67 67 [255]
68 68
69 69 Can't graft with dirty wd:
70 70
71 71 $ hg up -q 0
72 72 $ echo foo > a
73 73 $ hg graft 1
74 74 abort: outstanding uncommitted changes
75 75 [255]
76 76 $ hg revert a
77 77
78 78 Graft a rename:
79 79
80 80 $ hg graft 2 -u foo
81 81 grafting revision 2
82 82 merging a and b to b
83 83 $ hg export tip --git
84 84 # HG changeset patch
85 85 # User foo
86 86 # Date 0 0
87 87 # Node ID ef0ef43d49e79e81ddafdc7997401ba0041efc82
88 88 # Parent 68795b066622ca79a25816a662041d8f78f3cd9e
89 89 2
90 90
91 91 diff --git a/a b/b
92 92 rename from a
93 93 rename to b
94 94
95 95 Look for extra:source
96 96
97 97 $ hg log --debug -r tip
98 98 changeset: 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
99 99 tag: tip
100 100 phase: draft
101 101 parent: 0:68795b066622ca79a25816a662041d8f78f3cd9e
102 102 parent: -1:0000000000000000000000000000000000000000
103 103 manifest: 7:e59b6b228f9cbf9903d5e9abf996e083a1f533eb
104 104 user: foo
105 105 date: Thu Jan 01 00:00:00 1970 +0000
106 106 files+: b
107 107 files-: a
108 108 extra: branch=default
109 109 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
110 110 description:
111 111 2
112 112
113 113
114 114
115 115 Graft out of order, skipping a merge and a duplicate
116 116
117 117 $ hg graft 1 5 4 3 'merge()' 2 -n
118 118 skipping ungraftable merge revision 6
119 119 skipping already grafted revision 2
120 120 grafting revision 1
121 121 grafting revision 5
122 122 grafting revision 4
123 123 grafting revision 3
124 124
125 125 $ hg graft 1 5 4 3 'merge()' 2 --debug
126 126 skipping ungraftable merge revision 6
127 127 scanning for duplicate grafts
128 128 skipping already grafted revision 2
129 129 grafting revision 1
130 130 searching for copies back to rev 1
131 131 unmatched files in local:
132 132 b
133 133 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
134 134 b -> a *
135 135 checking for directory renames
136 136 resolving manifests
137 137 overwrite: False, partial: False
138 138 ancestor: 68795b066622, local: ef0ef43d49e7+, remote: 5d205f8b35b6
139 139 b: local copied/moved to a -> m
140 140 preserving b for resolve of b
141 141 updating: b 1/1 files (100.00%)
142 142 picked tool 'internal:merge' for b (binary False symlink False)
143 143 merging b and a to b
144 144 my b@ef0ef43d49e7+ other a@5d205f8b35b6 ancestor a@68795b066622
145 145 premerge successful
146 146 b
147 147 grafting revision 5
148 148 searching for copies back to rev 1
149 149 resolving manifests
150 150 overwrite: False, partial: False
151 151 ancestor: 4c60f11aa304, local: 6b9e5368ca4e+, remote: 97f8bfe72746
152 152 e: remote is newer -> g
153 153 updating: e 1/1 files (100.00%)
154 154 getting e
155 155 e
156 156 grafting revision 4
157 157 searching for copies back to rev 1
158 158 resolving manifests
159 159 overwrite: False, partial: False
160 160 ancestor: 4c60f11aa304, local: 1905859650ec+, remote: 9c233e8e184d
161 161 e: versions differ -> m
162 162 d: remote is newer -> g
163 163 preserving e for resolve of e
164 164 updating: d 1/2 files (50.00%)
165 165 getting d
166 166 updating: e 2/2 files (100.00%)
167 167 picked tool 'internal:merge' for e (binary False symlink False)
168 168 merging e
169 169 my e@1905859650ec+ other e@9c233e8e184d ancestor e@68795b066622
170 170 warning: conflicts during merge.
171 171 merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
172 172 abort: unresolved conflicts, can't continue
173 173 (use hg resolve and hg graft --continue)
174 174 [255]
175 175
176 176 Continue without resolve should fail:
177 177
178 178 $ hg graft -c
179 179 grafting revision 4
180 180 abort: unresolved merge conflicts (see hg help resolve)
181 181 [255]
182 182
183 183 Fix up:
184 184
185 185 $ echo b > e
186 186 $ hg resolve -m e
187 187
188 188 Continue with a revision should fail:
189 189
190 190 $ hg graft -c 6
191 191 abort: can't specify --continue and revisions
192 192 [255]
193 193
194 194 $ hg graft -c -r 6
195 195 abort: can't specify --continue and revisions
196 196 [255]
197 197
198 198 Continue for real, clobber usernames
199 199
200 200 $ hg graft -c -U
201 201 grafting revision 4
202 202 grafting revision 3
203 203
204 204 Compare with original:
205 205
206 206 $ hg diff -r 6
207 207 $ hg status --rev 0:. -C
208 208 M d
209 209 M e
210 210 A b
211 211 a
212 212 A c
213 213 a
214 214 R a
215 215
216 216 View graph:
217 217
218 218 $ hg --config extensions.graphlog= log -G --template '{author}@{rev}.{phase}: {desc}\n'
219 219 @ test@11.draft: 3
220 220 |
221 221 o test@10.draft: 4
222 222 |
223 223 o test@9.draft: 5
224 224 |
225 225 o bar@8.draft: 1
226 226 |
227 227 o foo@7.draft: 2
228 228 |
229 229 | o test@6.secret: 6
230 230 | |\
231 231 | | o test@5.draft: 5
232 232 | | |
233 233 | o | test@4.draft: 4
234 234 | |/
235 235 | o baz@3.public: 3
236 236 | |
237 237 | o test@2.public: 2
238 238 | |
239 239 | o bar@1.public: 1
240 240 |/
241 241 o test@0.public: 0
242 242
243 243 Graft again onto another branch should preserve the original source
244 244 $ hg up -q 0
245 245 $ echo 'g'>g
246 246 $ hg add g
247 247 $ hg ci -m 7
248 248 created new head
249 249 $ hg graft 7
250 250 grafting revision 7
251 251
252 252 $ hg log -r 7 --template '{rev}:{node}\n'
253 253 7:ef0ef43d49e79e81ddafdc7997401ba0041efc82
254 254 $ hg log -r 2 --template '{rev}:{node}\n'
255 255 2:5c095ad7e90f871700f02dd1fa5012cb4498a2d4
256 256
257 257 $ hg log --debug -r tip
258 258 changeset: 13:9db0f28fd3747e92c57d015f53b5593aeec53c2d
259 259 tag: tip
260 260 phase: draft
261 261 parent: 12:b592ea63bb0c19a6c5c44685ee29a2284f9f1b8f
262 262 parent: -1:0000000000000000000000000000000000000000
263 263 manifest: 13:dc313617b8c32457c0d589e0dbbedfe71f3cd637
264 264 user: foo
265 265 date: Thu Jan 01 00:00:00 1970 +0000
266 266 files+: b
267 267 files-: a
268 268 extra: branch=default
269 269 extra: source=5c095ad7e90f871700f02dd1fa5012cb4498a2d4
270 270 description:
271 271 2
272 272
273 273
274 274 Disallow grafting an already grafted cset onto its original branch
275 275 $ hg up -q 6
276 276 $ hg graft 7
277 277 skipping already grafted revision 7 (was grafted from 2)
278 278 [255]
279 279
280 280 Disallow grafting already grafted csets with the same origin onto each other
281 281 $ hg up -q 13
282 282 $ hg graft 2
283 283 skipping already grafted revision 2
284 284 [255]
285 285 $ hg graft 7
286 286 skipping already grafted revision 7 (same origin 2)
287 287 [255]
288 288
289 289 $ hg up -q 7
290 290 $ hg graft 2
291 291 skipping already grafted revision 2
292 292 [255]
293 293 $ hg graft tip
294 294 skipping already grafted revision 13 (same origin 2)
295 295 [255]
296 296
297 297 Graft with --log
298 298
299 299 $ hg up -Cq 1
300 300 $ hg graft 3 --log -u foo
301 301 grafting revision 3
302 302 warning: can't find ancestor for 'c' copied from 'b'!
303 303 $ hg log --template '{rev} {parents} {desc}\n' -r tip
304 304 14 1:5d205f8b35b6 3
305 305 (grafted from 4c60f11aa304a54ae1c199feb94e7fc771e51ed8)
306 306
307 307 Resolve conflicted graft
308 308 $ hg up -q 0
309 309 $ echo b > a
310 310 $ hg ci -m 8
311 311 created new head
312 312 $ echo a > a
313 313 $ hg ci -m 9
314 314 $ hg graft 1 --tool internal:fail
315 315 grafting revision 1
316 316 abort: unresolved conflicts, can't continue
317 317 (use hg resolve and hg graft --continue)
318 318 [255]
319 319 $ hg resolve --all
320 320 merging a
321 321 $ hg graft -c
322 322 grafting revision 1
323 323 $ hg export tip --git
324 324 # HG changeset patch
325 325 # User bar
326 326 # Date 0 0
327 327 # Node ID 64ecd9071ce83c6e62f538d8ce7709d53f32ebf7
328 328 # Parent 4bdb9a9d0b84ffee1d30f0dfc7744cade17aa19c
329 329 1
330 330
331 331 diff --git a/a b/a
332 332 --- a/a
333 333 +++ b/a
334 334 @@ -1,1 +1,1 @@
335 335 -a
336 336 +b
337 337
338 338 Resolve conflicted graft with rename
339 339 $ echo c > a
340 340 $ hg ci -m 10
341 341 $ hg graft 2 --tool internal:fail
342 342 grafting revision 2
343 343 abort: unresolved conflicts, can't continue
344 344 (use hg resolve and hg graft --continue)
345 345 [255]
346 346 $ hg resolve --all
347 347 merging a and b to b
348 348 $ hg graft -c
349 349 grafting revision 2
350 350 $ hg export tip --git
351 351 # HG changeset patch
352 352 # User test
353 353 # Date 0 0
354 354 # Node ID 2e80e1351d6ed50302fe1e05f8bd1d4d412b6e11
355 355 # Parent e5a51ae854a8bbaaf25cc5c6a57ff46042dadbb4
356 356 2
357 357
358 358 diff --git a/a b/b
359 359 rename from a
360 360 rename to b
361 361
362 362 Test simple origin(), with and without args
363 363 $ hg log -r 'origin()'
364 364 changeset: 1:5d205f8b35b6
365 365 user: bar
366 366 date: Thu Jan 01 00:00:00 1970 +0000
367 367 summary: 1
368 368
369 369 changeset: 2:5c095ad7e90f
370 370 user: test
371 371 date: Thu Jan 01 00:00:00 1970 +0000
372 372 summary: 2
373 373
374 374 changeset: 3:4c60f11aa304
375 375 user: baz
376 376 date: Thu Jan 01 00:00:00 1970 +0000
377 377 summary: 3
378 378
379 379 changeset: 4:9c233e8e184d
380 380 user: test
381 381 date: Thu Jan 01 00:00:00 1970 +0000
382 382 summary: 4
383 383
384 384 changeset: 5:97f8bfe72746
385 385 branch: stable
386 386 parent: 3:4c60f11aa304
387 387 user: test
388 388 date: Thu Jan 01 00:00:00 1970 +0000
389 389 summary: 5
390 390
391 391 $ hg log -r 'origin(7)'
392 392 changeset: 2:5c095ad7e90f
393 393 user: test
394 394 date: Thu Jan 01 00:00:00 1970 +0000
395 395 summary: 2
396 396
397 397 Now transplant a graft to test following through copies
398 398 $ hg up -q 0
399 399 $ hg branch -q dev
400 400 $ hg ci -qm "dev branch"
401 401 $ hg --config extensions.transplant= transplant -q 7
402 402 $ hg log -r 'origin(.)'
403 403 changeset: 2:5c095ad7e90f
404 404 user: test
405 405 date: Thu Jan 01 00:00:00 1970 +0000
406 406 summary: 2
407 407
408 Test simple destination
409 $ hg log -r 'destination()'
410 changeset: 7:ef0ef43d49e7
411 parent: 0:68795b066622
412 user: foo
413 date: Thu Jan 01 00:00:00 1970 +0000
414 summary: 2
415
416 changeset: 8:6b9e5368ca4e
417 user: bar
418 date: Thu Jan 01 00:00:00 1970 +0000
419 summary: 1
420
421 changeset: 9:1905859650ec
422 user: test
423 date: Thu Jan 01 00:00:00 1970 +0000
424 summary: 5
425
426 changeset: 10:52dc0b4c6907
427 user: test
428 date: Thu Jan 01 00:00:00 1970 +0000
429 summary: 4
430
431 changeset: 11:882b35362a6b
432 user: test
433 date: Thu Jan 01 00:00:00 1970 +0000
434 summary: 3
435
436 changeset: 13:9db0f28fd374
437 user: foo
438 date: Thu Jan 01 00:00:00 1970 +0000
439 summary: 2
440
441 changeset: 14:f64defefacee
442 parent: 1:5d205f8b35b6
443 user: foo
444 date: Thu Jan 01 00:00:00 1970 +0000
445 summary: 3
446
447 changeset: 17:64ecd9071ce8
448 user: bar
449 date: Thu Jan 01 00:00:00 1970 +0000
450 summary: 1
451
452 changeset: 19:2e80e1351d6e
453 user: test
454 date: Thu Jan 01 00:00:00 1970 +0000
455 summary: 2
456
457 changeset: 21:7e61b508e709
458 branch: dev
459 tag: tip
460 user: foo
461 date: Thu Jan 01 00:00:00 1970 +0000
462 summary: 2
463
464 $ hg log -r 'destination(2)'
465 changeset: 7:ef0ef43d49e7
466 parent: 0:68795b066622
467 user: foo
468 date: Thu Jan 01 00:00:00 1970 +0000
469 summary: 2
470
471 changeset: 13:9db0f28fd374
472 user: foo
473 date: Thu Jan 01 00:00:00 1970 +0000
474 summary: 2
475
476 changeset: 19:2e80e1351d6e
477 user: test
478 date: Thu Jan 01 00:00:00 1970 +0000
479 summary: 2
480
481 changeset: 21:7e61b508e709
482 branch: dev
483 tag: tip
484 user: foo
485 date: Thu Jan 01 00:00:00 1970 +0000
486 summary: 2
487
488 Transplants of grafts can find a destination...
489 $ hg log -r 'destination(7)'
490 changeset: 21:7e61b508e709
491 branch: dev
492 tag: tip
493 user: foo
494 date: Thu Jan 01 00:00:00 1970 +0000
495 summary: 2
496
497 ... grafts of grafts unfortunately can't
498 $ hg graft -q 13
499 $ hg log -r 'destination(13)'
500 All copies of a cset
501 $ hg log -r 'origin(13) or destination(origin(13))'
502 changeset: 2:5c095ad7e90f
503 user: test
504 date: Thu Jan 01 00:00:00 1970 +0000
505 summary: 2
506
507 changeset: 7:ef0ef43d49e7
508 parent: 0:68795b066622
509 user: foo
510 date: Thu Jan 01 00:00:00 1970 +0000
511 summary: 2
512
513 changeset: 13:9db0f28fd374
514 user: foo
515 date: Thu Jan 01 00:00:00 1970 +0000
516 summary: 2
517
518 changeset: 19:2e80e1351d6e
519 user: test
520 date: Thu Jan 01 00:00:00 1970 +0000
521 summary: 2
522
523 changeset: 21:7e61b508e709
524 branch: dev
525 user: foo
526 date: Thu Jan 01 00:00:00 1970 +0000
527 summary: 2
528
529 changeset: 22:1313d0a825e2
530 branch: dev
531 tag: tip
532 user: foo
533 date: Thu Jan 01 00:00:00 1970 +0000
534 summary: 2
535
@@ -1,537 +1,605 b''
1 1 $ "$TESTDIR/hghave" serve || exit 80
2 2
3 3 $ cat <<EOF >> $HGRCPATH
4 4 > [extensions]
5 5 > transplant=
6 6 > EOF
7 7
8 8 $ hg init t
9 9 $ cd t
10 10 $ echo r1 > r1
11 11 $ hg ci -Amr1 -d'0 0'
12 12 adding r1
13 13 $ echo r2 > r2
14 14 $ hg ci -Amr2 -d'1 0'
15 15 adding r2
16 16 $ hg up 0
17 17 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
18 18
19 19 $ echo b1 > b1
20 20 $ hg ci -Amb1 -d '0 0'
21 21 adding b1
22 22 created new head
23 23 $ echo b2 > b2
24 24 $ hg ci -Amb2 -d '1 0'
25 25 adding b2
26 26 $ echo b3 > b3
27 27 $ hg ci -Amb3 -d '2 0'
28 28 adding b3
29 29
30 30 $ hg log --template '{rev} {parents} {desc}\n'
31 31 4 b3
32 32 3 b2
33 33 2 0:17ab29e464c6 b1
34 34 1 r2
35 35 0 r1
36 36
37 37 $ hg clone . ../rebase
38 38 updating to branch default
39 39 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 40 $ cd ../rebase
41 41
42 42 $ hg up -C 1
43 43 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
44 44
45 45 rebase b onto r1
46 46
47 47 $ hg transplant -a -b tip
48 48 applying 37a1297eb21b
49 49 37a1297eb21b transplanted to e234d668f844
50 50 applying 722f4667af76
51 51 722f4667af76 transplanted to 539f377d78df
52 52 applying a53251cdf717
53 53 a53251cdf717 transplanted to ffd6818a3975
54 54 $ hg log --template '{rev} {parents} {desc}\n'
55 55 7 b3
56 56 6 b2
57 57 5 1:d11e3596cc1a b1
58 58 4 b3
59 59 3 b2
60 60 2 0:17ab29e464c6 b1
61 61 1 r2
62 62 0 r1
63 63
64 64 test transplanted revset
65 65
66 66 $ hg log -r 'transplanted()' --template '{rev} {parents} {desc}\n'
67 67 5 1:d11e3596cc1a b1
68 68 6 b2
69 69 7 b3
70 70 $ hg help revsets | grep transplanted
71 71 "transplanted([set])"
72 72 Transplanted changesets in set, or all transplanted changesets.
73 73
74 74 test tranplanted keyword
75 75
76 76 $ hg log --template '{rev} {transplanted}\n'
77 77 7 a53251cdf717679d1907b289f991534be05c997a
78 78 6 722f4667af767100cb15b6a79324bf8abbfe1ef4
79 79 5 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21
80 80 4
81 81 3
82 82 2
83 83 1
84 84 0
85 85
86 test destination() revset predicate with a transplant of a transplant; new
87 clone so subsequent rollback isn't affected
88 $ hg clone -q . ../destination
89 $ cd ../destination
90 $ hg up -Cq 0
91 $ hg branch -q b4
92 $ hg ci -qm "b4"
93 $ hg transplant 7
94 applying ffd6818a3975
95 ffd6818a3975 transplanted to 502236fa76bb
96
97
98 $ hg log -r 'destination()'
99 changeset: 5:e234d668f844
100 parent: 1:d11e3596cc1a
101 user: test
102 date: Thu Jan 01 00:00:00 1970 +0000
103 summary: b1
104
105 changeset: 6:539f377d78df
106 user: test
107 date: Thu Jan 01 00:00:01 1970 +0000
108 summary: b2
109
110 changeset: 7:ffd6818a3975
111 user: test
112 date: Thu Jan 01 00:00:02 1970 +0000
113 summary: b3
114
115 changeset: 9:502236fa76bb
116 branch: b4
117 tag: tip
118 user: test
119 date: Thu Jan 01 00:00:02 1970 +0000
120 summary: b3
121
122 $ hg log -r 'destination(a53251cdf717)'
123 changeset: 7:ffd6818a3975
124 user: test
125 date: Thu Jan 01 00:00:02 1970 +0000
126 summary: b3
127
128 changeset: 9:502236fa76bb
129 branch: b4
130 tag: tip
131 user: test
132 date: Thu Jan 01 00:00:02 1970 +0000
133 summary: b3
134
135
136 test subset parameter in reverse order
137 $ hg log -r 'reverse(all()) and destination(a53251cdf717)'
138 changeset: 9:502236fa76bb
139 branch: b4
140 tag: tip
141 user: test
142 date: Thu Jan 01 00:00:02 1970 +0000
143 summary: b3
144
145 changeset: 7:ffd6818a3975
146 user: test
147 date: Thu Jan 01 00:00:02 1970 +0000
148 summary: b3
149
150
151 back to the original dir
152 $ cd ../rebase
153
86 154 rollback the transplant
87 155 $ hg rollback
88 156 repository tip rolled back to revision 4 (undo transplant)
89 157 working directory now based on revision 1
90 158 $ hg tip -q
91 159 4:a53251cdf717
92 160 $ hg parents -q
93 161 1:d11e3596cc1a
94 162 $ hg status
95 163 ? b1
96 164 ? b2
97 165 ? b3
98 166
99 167 $ hg clone ../t ../prune
100 168 updating to branch default
101 169 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 170 $ cd ../prune
103 171
104 172 $ hg up -C 1
105 173 1 files updated, 0 files merged, 3 files removed, 0 files unresolved
106 174
107 175 rebase b onto r1, skipping b2
108 176
109 177 $ hg transplant -a -b tip -p 3
110 178 applying 37a1297eb21b
111 179 37a1297eb21b transplanted to e234d668f844
112 180 applying a53251cdf717
113 181 a53251cdf717 transplanted to 7275fda4d04f
114 182 $ hg log --template '{rev} {parents} {desc}\n'
115 183 6 b3
116 184 5 1:d11e3596cc1a b1
117 185 4 b3
118 186 3 b2
119 187 2 0:17ab29e464c6 b1
120 188 1 r2
121 189 0 r1
122 190
123 191 test same-parent transplant with --log
124 192
125 193 $ hg clone -r 1 ../t ../sameparent
126 194 adding changesets
127 195 adding manifests
128 196 adding file changes
129 197 added 2 changesets with 2 changes to 2 files
130 198 updating to branch default
131 199 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
132 200 $ cd ../sameparent
133 201 $ hg transplant --log -s ../prune 5
134 202 searching for changes
135 203 applying e234d668f844
136 204 e234d668f844 transplanted to e07aea8ecf9c
137 205 $ hg log --template '{rev} {parents} {desc}\n'
138 206 2 b1
139 207 (transplanted from e234d668f844e1b1a765f01db83a32c0c7bfa170)
140 208 1 r2
141 209 0 r1
142 210 remote transplant
143 211
144 212 $ hg clone -r 1 ../t ../remote
145 213 adding changesets
146 214 adding manifests
147 215 adding file changes
148 216 added 2 changesets with 2 changes to 2 files
149 217 updating to branch default
150 218 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
151 219 $ cd ../remote
152 220 $ hg transplant --log -s ../t 2 4
153 221 searching for changes
154 222 applying 37a1297eb21b
155 223 37a1297eb21b transplanted to c19cf0ccb069
156 224 applying a53251cdf717
157 225 a53251cdf717 transplanted to f7fe5bf98525
158 226 $ hg log --template '{rev} {parents} {desc}\n'
159 227 3 b3
160 228 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
161 229 2 b1
162 230 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
163 231 1 r2
164 232 0 r1
165 233
166 234 skip previous transplants
167 235
168 236 $ hg transplant -s ../t -a -b 4
169 237 searching for changes
170 238 applying 722f4667af76
171 239 722f4667af76 transplanted to 47156cd86c0b
172 240 $ hg log --template '{rev} {parents} {desc}\n'
173 241 4 b2
174 242 3 b3
175 243 (transplanted from a53251cdf717679d1907b289f991534be05c997a)
176 244 2 b1
177 245 (transplanted from 37a1297eb21b3ef5c5d2ffac22121a0988ed9f21)
178 246 1 r2
179 247 0 r1
180 248
181 249 skip local changes transplanted to the source
182 250
183 251 $ echo b4 > b4
184 252 $ hg ci -Amb4 -d '3 0'
185 253 adding b4
186 254 $ hg clone ../t ../pullback
187 255 updating to branch default
188 256 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
189 257 $ cd ../pullback
190 258 $ hg transplant -s ../remote -a -b tip
191 259 searching for changes
192 260 applying 4333daefcb15
193 261 4333daefcb15 transplanted to 5f42c04e07cc
194 262
195 263
196 264 remote transplant with pull
197 265
198 266 $ hg -R ../t serve -p $HGPORT -d --pid-file=../t.pid
199 267 $ cat ../t.pid >> $DAEMON_PIDS
200 268
201 269 $ hg clone -r 0 ../t ../rp
202 270 adding changesets
203 271 adding manifests
204 272 adding file changes
205 273 added 1 changesets with 1 changes to 1 files
206 274 updating to branch default
207 275 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
208 276 $ cd ../rp
209 277 $ hg transplant -s http://localhost:$HGPORT/ 2 4
210 278 searching for changes
211 279 searching for changes
212 280 adding changesets
213 281 adding manifests
214 282 adding file changes
215 283 added 1 changesets with 1 changes to 1 files
216 284 applying a53251cdf717
217 285 a53251cdf717 transplanted to 8d9279348abb
218 286 $ hg log --template '{rev} {parents} {desc}\n'
219 287 2 b3
220 288 1 b1
221 289 0 r1
222 290
223 291 transplant --continue
224 292
225 293 $ hg init ../tc
226 294 $ cd ../tc
227 295 $ cat <<EOF > foo
228 296 > foo
229 297 > bar
230 298 > baz
231 299 > EOF
232 300 $ echo toremove > toremove
233 301 $ echo baz > baz
234 302 $ hg ci -Amfoo
235 303 adding baz
236 304 adding foo
237 305 adding toremove
238 306 $ cat <<EOF > foo
239 307 > foo2
240 308 > bar2
241 309 > baz2
242 310 > EOF
243 311 $ rm toremove
244 312 $ echo added > added
245 313 $ hg ci -Amfoo2
246 314 adding added
247 315 removing toremove
248 316 $ echo bar > bar
249 317 $ cat > baz <<EOF
250 318 > before baz
251 319 > baz
252 320 > after baz
253 321 > EOF
254 322 $ hg ci -Ambar
255 323 adding bar
256 324 $ echo bar2 >> bar
257 325 $ hg ci -mbar2
258 326 $ hg up 0
259 327 3 files updated, 0 files merged, 2 files removed, 0 files unresolved
260 328 $ echo foobar > foo
261 329 $ hg ci -mfoobar
262 330 created new head
263 331 $ hg transplant 1:3
264 332 applying 46ae92138f3c
265 333 patching file foo
266 334 Hunk #1 FAILED at 0
267 335 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
268 336 patch failed to apply
269 337 abort: fix up the merge and run hg transplant --continue
270 338 [255]
271 339
272 340 transplant -c shouldn't use an old changeset
273 341
274 342 $ hg up -C
275 343 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 344 $ rm added
277 345 $ hg transplant 1
278 346 applying 46ae92138f3c
279 347 patching file foo
280 348 Hunk #1 FAILED at 0
281 349 1 out of 1 hunks FAILED -- saving rejects to file foo.rej
282 350 patch failed to apply
283 351 abort: fix up the merge and run hg transplant --continue
284 352 [255]
285 353 $ hg transplant --continue
286 354 46ae92138f3c transplanted as 9159dada197d
287 355 $ hg transplant 1:3
288 356 skipping already applied revision 1:46ae92138f3c
289 357 applying 9d6d6b5a8275
290 358 9d6d6b5a8275 transplanted to 2d17a10c922f
291 359 applying 1dab759070cf
292 360 1dab759070cf transplanted to e06a69927eb0
293 361 $ hg locate
294 362 added
295 363 bar
296 364 baz
297 365 foo
298 366
299 367 test multiple revisions and --continue
300 368
301 369 $ hg up -qC 0
302 370 $ echo bazbaz > baz
303 371 $ hg ci -Am anotherbaz baz
304 372 created new head
305 373 $ hg transplant 1:3
306 374 applying 46ae92138f3c
307 375 46ae92138f3c transplanted to 1024233ea0ba
308 376 applying 9d6d6b5a8275
309 377 patching file baz
310 378 Hunk #1 FAILED at 0
311 379 1 out of 1 hunks FAILED -- saving rejects to file baz.rej
312 380 patch failed to apply
313 381 abort: fix up the merge and run hg transplant --continue
314 382 [255]
315 383 $ echo fixed > baz
316 384 $ hg transplant --continue
317 385 9d6d6b5a8275 transplanted as d80c49962290
318 386 applying 1dab759070cf
319 387 1dab759070cf transplanted to aa0ffe6bd5ae
320 388
321 389 $ cd ..
322 390
323 391 Issue1111: Test transplant --merge
324 392
325 393 $ hg init t1111
326 394 $ cd t1111
327 395 $ echo a > a
328 396 $ hg ci -Am adda
329 397 adding a
330 398 $ echo b >> a
331 399 $ hg ci -m appendb
332 400 $ echo c >> a
333 401 $ hg ci -m appendc
334 402 $ hg up -C 0
335 403 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
336 404 $ echo d >> a
337 405 $ hg ci -m appendd
338 406 created new head
339 407
340 408 tranplant
341 409
342 410 $ hg transplant -m 1
343 411 applying 42dc4432fd35
344 412 1:42dc4432fd35 merged at a9f4acbac129
345 413 $ cd ..
346 414
347 415 test transplant into empty repository
348 416
349 417 $ hg init empty
350 418 $ cd empty
351 419 $ hg transplant -s ../t -b tip -a
352 420 adding changesets
353 421 adding manifests
354 422 adding file changes
355 423 added 4 changesets with 4 changes to 4 files
356 424 $ cd ..
357 425
358 426
359 427 #if unix-permissions system-sh
360 428
361 429 test filter
362 430
363 431 $ hg init filter
364 432 $ cd filter
365 433 $ cat <<'EOF' >test-filter
366 434 > #!/bin/sh
367 435 > sed 's/r1/r2/' $1 > $1.new
368 436 > mv $1.new $1
369 437 > EOF
370 438 $ chmod +x test-filter
371 439 $ hg transplant -s ../t -b tip -a --filter ./test-filter
372 440 filtering * (glob)
373 441 applying 17ab29e464c6
374 442 17ab29e464c6 transplanted to e9ffc54ea104
375 443 filtering * (glob)
376 444 applying 37a1297eb21b
377 445 37a1297eb21b transplanted to 348b36d0b6a5
378 446 filtering * (glob)
379 447 applying 722f4667af76
380 448 722f4667af76 transplanted to 0aa6979afb95
381 449 filtering * (glob)
382 450 applying a53251cdf717
383 451 a53251cdf717 transplanted to 14f8512272b5
384 452 $ hg log --template '{rev} {parents} {desc}\n'
385 453 3 b3
386 454 2 b2
387 455 1 b1
388 456 0 r2
389 457 $ cd ..
390 458
391 459
392 460 test filter with failed patch
393 461
394 462 $ cd filter
395 463 $ hg up 0
396 464 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
397 465 $ echo foo > b1
398 466 $ hg ci -Am foo
399 467 adding b1
400 468 adding test-filter
401 469 created new head
402 470 $ hg transplant 1 --filter ./test-filter
403 471 filtering * (glob)
404 472 applying 348b36d0b6a5
405 473 file b1 already exists
406 474 1 out of 1 hunks FAILED -- saving rejects to file b1.rej
407 475 patch failed to apply
408 476 abort: fix up the merge and run hg transplant --continue
409 477 [255]
410 478 $ cd ..
411 479
412 480 test environment passed to filter
413 481
414 482 $ hg init filter-environment
415 483 $ cd filter-environment
416 484 $ cat <<'EOF' >test-filter-environment
417 485 > #!/bin/sh
418 486 > echo "Transplant by $HGUSER" >> $1
419 487 > echo "Transplant from rev $HGREVISION" >> $1
420 488 > EOF
421 489 $ chmod +x test-filter-environment
422 490 $ hg transplant -s ../t --filter ./test-filter-environment 0
423 491 filtering * (glob)
424 492 applying 17ab29e464c6
425 493 17ab29e464c6 transplanted to 5190e68026a0
426 494
427 495 $ hg log --template '{rev} {parents} {desc}\n'
428 496 0 r1
429 497 Transplant by test
430 498 Transplant from rev 17ab29e464c6ca53e329470efe2a9918ac617a6f
431 499 $ cd ..
432 500
433 501 test transplant with filter handles invalid changelog
434 502
435 503 $ hg init filter-invalid-log
436 504 $ cd filter-invalid-log
437 505 $ cat <<'EOF' >test-filter-invalid-log
438 506 > #!/bin/sh
439 507 > echo "" > $1
440 508 > EOF
441 509 $ chmod +x test-filter-invalid-log
442 510 $ hg transplant -s ../t --filter ./test-filter-invalid-log 0
443 511 filtering * (glob)
444 512 abort: filter corrupted changeset (no user or date)
445 513 [255]
446 514 $ cd ..
447 515
448 516 #endif
449 517
450 518
451 519 test with a win32ext like setup (differing EOLs)
452 520
453 521 $ hg init twin1
454 522 $ cd twin1
455 523 $ echo a > a
456 524 $ echo b > b
457 525 $ echo b >> b
458 526 $ hg ci -Am t
459 527 adding a
460 528 adding b
461 529 $ echo a > b
462 530 $ echo b >> b
463 531 $ hg ci -m changeb
464 532 $ cd ..
465 533
466 534 $ hg init twin2
467 535 $ cd twin2
468 536 $ echo '[patch]' >> .hg/hgrc
469 537 $ echo 'eol = crlf' >> .hg/hgrc
470 538 $ python -c "file('b', 'wb').write('b\r\nb\r\n')"
471 539 $ hg ci -Am addb
472 540 adding b
473 541 $ hg transplant -s ../twin1 tip
474 542 searching for changes
475 543 warning: repository is unrelated
476 544 applying 2e849d776c17
477 545 2e849d776c17 transplanted to 8e65bebc063e
478 546 $ cat b
479 547 a\r (esc)
480 548 b\r (esc)
481 549 $ cd ..
482 550
483 551 test transplant with merge changeset is skipped
484 552
485 553 $ hg init merge1a
486 554 $ cd merge1a
487 555 $ echo a > a
488 556 $ hg ci -Am a
489 557 adding a
490 558 $ hg branch b
491 559 marked working directory as branch b
492 560 (branches are permanent and global, did you want a bookmark?)
493 561 $ hg ci -m branchb
494 562 $ echo b > b
495 563 $ hg ci -Am b
496 564 adding b
497 565 $ hg update default
498 566 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
499 567 $ hg merge b
500 568 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
501 569 (branch merge, don't forget to commit)
502 570 $ hg ci -m mergeb
503 571 $ cd ..
504 572
505 573 $ hg init merge1b
506 574 $ cd merge1b
507 575 $ hg transplant -s ../merge1a tip
508 576 $ cd ..
509 577
510 578 test transplant with merge changeset accepts --parent
511 579
512 580 $ hg init merge2a
513 581 $ cd merge2a
514 582 $ echo a > a
515 583 $ hg ci -Am a
516 584 adding a
517 585 $ hg branch b
518 586 marked working directory as branch b
519 587 (branches are permanent and global, did you want a bookmark?)
520 588 $ hg ci -m branchb
521 589 $ echo b > b
522 590 $ hg ci -Am b
523 591 adding b
524 592 $ hg update default
525 593 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
526 594 $ hg merge b
527 595 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
528 596 (branch merge, don't forget to commit)
529 597 $ hg ci -m mergeb
530 598 $ cd ..
531 599
532 600 $ hg init merge2b
533 601 $ cd merge2b
534 602 $ hg transplant -s ../merge2a --parent 0 tip
535 603 applying be9f9b39483f
536 604 be9f9b39483f transplanted to 9959e51f94d1
537 605 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now