##// END OF EJS Templates
util: extract stringmatcher() from revset...
Matt Harbison -
r26481:7d132557 default
parent child Browse files
Show More
@@ -1,3854 +1,3815
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 from __future__ import absolute_import
9 9
10 10 import heapq
11 11 import re
12 12
13 13 from .i18n import _
14 14 from . import (
15 15 encoding,
16 16 error,
17 17 hbisect,
18 18 match as matchmod,
19 19 node,
20 20 obsolete as obsmod,
21 21 parser,
22 22 pathutil,
23 23 phases,
24 24 repoview,
25 25 util,
26 26 )
27 27
28 28 def _revancestors(repo, revs, followfirst):
29 29 """Like revlog.ancestors(), but supports followfirst."""
30 30 if followfirst:
31 31 cut = 1
32 32 else:
33 33 cut = None
34 34 cl = repo.changelog
35 35
36 36 def iterate():
37 37 revs.sort(reverse=True)
38 38 irevs = iter(revs)
39 39 h = []
40 40
41 41 inputrev = next(irevs, None)
42 42 if inputrev is not None:
43 43 heapq.heappush(h, -inputrev)
44 44
45 45 seen = set()
46 46 while h:
47 47 current = -heapq.heappop(h)
48 48 if current == inputrev:
49 49 inputrev = next(irevs, None)
50 50 if inputrev is not None:
51 51 heapq.heappush(h, -inputrev)
52 52 if current not in seen:
53 53 seen.add(current)
54 54 yield current
55 55 for parent in cl.parentrevs(current)[:cut]:
56 56 if parent != node.nullrev:
57 57 heapq.heappush(h, -parent)
58 58
59 59 return generatorset(iterate(), iterasc=False)
60 60
61 61 def _revdescendants(repo, revs, followfirst):
62 62 """Like revlog.descendants() but supports followfirst."""
63 63 if followfirst:
64 64 cut = 1
65 65 else:
66 66 cut = None
67 67
68 68 def iterate():
69 69 cl = repo.changelog
70 70 # XXX this should be 'parentset.min()' assuming 'parentset' is a
71 71 # smartset (and if it is not, it should.)
72 72 first = min(revs)
73 73 nullrev = node.nullrev
74 74 if first == nullrev:
75 75 # Are there nodes with a null first parent and a non-null
76 76 # second one? Maybe. Do we care? Probably not.
77 77 for i in cl:
78 78 yield i
79 79 else:
80 80 seen = set(revs)
81 81 for i in cl.revs(first + 1):
82 82 for x in cl.parentrevs(i)[:cut]:
83 83 if x != nullrev and x in seen:
84 84 seen.add(i)
85 85 yield i
86 86 break
87 87
88 88 return generatorset(iterate(), iterasc=True)
89 89
90 90 def _reachablerootspure(repo, minroot, roots, heads, includepath):
91 91 """return (heads(::<roots> and ::<heads>))
92 92
93 93 If includepath is True, return (<roots>::<heads>)."""
94 94 if not roots:
95 95 return []
96 96 parentrevs = repo.changelog.parentrevs
97 97 roots = set(roots)
98 98 visit = list(heads)
99 99 reachable = set()
100 100 seen = {}
101 101 # prefetch all the things! (because python is slow)
102 102 reached = reachable.add
103 103 dovisit = visit.append
104 104 nextvisit = visit.pop
105 105 # open-code the post-order traversal due to the tiny size of
106 106 # sys.getrecursionlimit()
107 107 while visit:
108 108 rev = nextvisit()
109 109 if rev in roots:
110 110 reached(rev)
111 111 if not includepath:
112 112 continue
113 113 parents = parentrevs(rev)
114 114 seen[rev] = parents
115 115 for parent in parents:
116 116 if parent >= minroot and parent not in seen:
117 117 dovisit(parent)
118 118 if not reachable:
119 119 return baseset()
120 120 if not includepath:
121 121 return reachable
122 122 for rev in sorted(seen):
123 123 for parent in seen[rev]:
124 124 if parent in reachable:
125 125 reached(rev)
126 126 return reachable
127 127
128 128 def reachableroots(repo, roots, heads, includepath=False):
129 129 """return (heads(::<roots> and ::<heads>))
130 130
131 131 If includepath is True, return (<roots>::<heads>)."""
132 132 if not roots:
133 133 return baseset()
134 134 minroot = roots.min()
135 135 roots = list(roots)
136 136 heads = list(heads)
137 137 try:
138 138 revs = repo.changelog.reachableroots(minroot, heads, roots, includepath)
139 139 except AttributeError:
140 140 revs = _reachablerootspure(repo, minroot, roots, heads, includepath)
141 141 revs = baseset(revs)
142 142 revs.sort()
143 143 return revs
144 144
145 145 elements = {
146 146 # token-type: binding-strength, primary, prefix, infix, suffix
147 147 "(": (21, None, ("group", 1, ")"), ("func", 1, ")"), None),
148 148 "##": (20, None, None, ("_concat", 20), None),
149 149 "~": (18, None, None, ("ancestor", 18), None),
150 150 "^": (18, None, None, ("parent", 18), ("parentpost", 18)),
151 151 "-": (5, None, ("negate", 19), ("minus", 5), None),
152 152 "::": (17, None, ("dagrangepre", 17), ("dagrange", 17),
153 153 ("dagrangepost", 17)),
154 154 "..": (17, None, ("dagrangepre", 17), ("dagrange", 17),
155 155 ("dagrangepost", 17)),
156 156 ":": (15, "rangeall", ("rangepre", 15), ("range", 15), ("rangepost", 15)),
157 157 "not": (10, None, ("not", 10), None, None),
158 158 "!": (10, None, ("not", 10), None, None),
159 159 "and": (5, None, None, ("and", 5), None),
160 160 "&": (5, None, None, ("and", 5), None),
161 161 "%": (5, None, None, ("only", 5), ("onlypost", 5)),
162 162 "or": (4, None, None, ("or", 4), None),
163 163 "|": (4, None, None, ("or", 4), None),
164 164 "+": (4, None, None, ("or", 4), None),
165 165 "=": (3, None, None, ("keyvalue", 3), None),
166 166 ",": (2, None, None, ("list", 2), None),
167 167 ")": (0, None, None, None, None),
168 168 "symbol": (0, "symbol", None, None, None),
169 169 "string": (0, "string", None, None, None),
170 170 "end": (0, None, None, None, None),
171 171 }
172 172
173 173 keywords = set(['and', 'or', 'not'])
174 174
175 175 # default set of valid characters for the initial letter of symbols
176 176 _syminitletters = set(c for c in [chr(i) for i in xrange(256)]
177 177 if c.isalnum() or c in '._@' or ord(c) > 127)
178 178
179 179 # default set of valid characters for non-initial letters of symbols
180 180 _symletters = set(c for c in [chr(i) for i in xrange(256)]
181 181 if c.isalnum() or c in '-._/@' or ord(c) > 127)
182 182
183 183 def tokenize(program, lookup=None, syminitletters=None, symletters=None):
184 184 '''
185 185 Parse a revset statement into a stream of tokens
186 186
187 187 ``syminitletters`` is the set of valid characters for the initial
188 188 letter of symbols.
189 189
190 190 By default, character ``c`` is recognized as valid for initial
191 191 letter of symbols, if ``c.isalnum() or c in '._@' or ord(c) > 127``.
192 192
193 193 ``symletters`` is the set of valid characters for non-initial
194 194 letters of symbols.
195 195
196 196 By default, character ``c`` is recognized as valid for non-initial
197 197 letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
198 198
199 199 Check that @ is a valid unquoted token character (issue3686):
200 200 >>> list(tokenize("@::"))
201 201 [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
202 202
203 203 '''
204 204 if syminitletters is None:
205 205 syminitletters = _syminitletters
206 206 if symletters is None:
207 207 symletters = _symletters
208 208
209 209 if program and lookup:
210 210 # attempt to parse old-style ranges first to deal with
211 211 # things like old-tag which contain query metacharacters
212 212 parts = program.split(':', 1)
213 213 if all(lookup(sym) for sym in parts if sym):
214 214 if parts[0]:
215 215 yield ('symbol', parts[0], 0)
216 216 if len(parts) > 1:
217 217 s = len(parts[0])
218 218 yield (':', None, s)
219 219 if parts[1]:
220 220 yield ('symbol', parts[1], s + 1)
221 221 yield ('end', None, len(program))
222 222 return
223 223
224 224 pos, l = 0, len(program)
225 225 while pos < l:
226 226 c = program[pos]
227 227 if c.isspace(): # skip inter-token whitespace
228 228 pass
229 229 elif c == ':' and program[pos:pos + 2] == '::': # look ahead carefully
230 230 yield ('::', None, pos)
231 231 pos += 1 # skip ahead
232 232 elif c == '.' and program[pos:pos + 2] == '..': # look ahead carefully
233 233 yield ('..', None, pos)
234 234 pos += 1 # skip ahead
235 235 elif c == '#' and program[pos:pos + 2] == '##': # look ahead carefully
236 236 yield ('##', None, pos)
237 237 pos += 1 # skip ahead
238 238 elif c in "():=,-|&+!~^%": # handle simple operators
239 239 yield (c, None, pos)
240 240 elif (c in '"\'' or c == 'r' and
241 241 program[pos:pos + 2] in ("r'", 'r"')): # handle quoted strings
242 242 if c == 'r':
243 243 pos += 1
244 244 c = program[pos]
245 245 decode = lambda x: x
246 246 else:
247 247 decode = parser.unescapestr
248 248 pos += 1
249 249 s = pos
250 250 while pos < l: # find closing quote
251 251 d = program[pos]
252 252 if d == '\\': # skip over escaped characters
253 253 pos += 2
254 254 continue
255 255 if d == c:
256 256 yield ('string', decode(program[s:pos]), s)
257 257 break
258 258 pos += 1
259 259 else:
260 260 raise error.ParseError(_("unterminated string"), s)
261 261 # gather up a symbol/keyword
262 262 elif c in syminitletters:
263 263 s = pos
264 264 pos += 1
265 265 while pos < l: # find end of symbol
266 266 d = program[pos]
267 267 if d not in symletters:
268 268 break
269 269 if d == '.' and program[pos - 1] == '.': # special case for ..
270 270 pos -= 1
271 271 break
272 272 pos += 1
273 273 sym = program[s:pos]
274 274 if sym in keywords: # operator keywords
275 275 yield (sym, None, s)
276 276 elif '-' in sym:
277 277 # some jerk gave us foo-bar-baz, try to check if it's a symbol
278 278 if lookup and lookup(sym):
279 279 # looks like a real symbol
280 280 yield ('symbol', sym, s)
281 281 else:
282 282 # looks like an expression
283 283 parts = sym.split('-')
284 284 for p in parts[:-1]:
285 285 if p: # possible consecutive -
286 286 yield ('symbol', p, s)
287 287 s += len(p)
288 288 yield ('-', None, pos)
289 289 s += 1
290 290 if parts[-1]: # possible trailing -
291 291 yield ('symbol', parts[-1], s)
292 292 else:
293 293 yield ('symbol', sym, s)
294 294 pos -= 1
295 295 else:
296 296 raise error.ParseError(_("syntax error in revset '%s'") %
297 297 program, pos)
298 298 pos += 1
299 299 yield ('end', None, pos)
300 300
301 301 def parseerrordetail(inst):
302 302 """Compose error message from specified ParseError object
303 303 """
304 304 if len(inst.args) > 1:
305 305 return _('at %s: %s') % (inst.args[1], inst.args[0])
306 306 else:
307 307 return inst.args[0]
308 308
309 309 # helpers
310 310
311 311 def getstring(x, err):
312 312 if x and (x[0] == 'string' or x[0] == 'symbol'):
313 313 return x[1]
314 314 raise error.ParseError(err)
315 315
316 316 def getlist(x):
317 317 if not x:
318 318 return []
319 319 if x[0] == 'list':
320 320 return getlist(x[1]) + [x[2]]
321 321 return [x]
322 322
323 323 def getargs(x, min, max, err):
324 324 l = getlist(x)
325 325 if len(l) < min or (max >= 0 and len(l) > max):
326 326 raise error.ParseError(err)
327 327 return l
328 328
329 329 def getargsdict(x, funcname, keys):
330 330 return parser.buildargsdict(getlist(x), funcname, keys.split(),
331 331 keyvaluenode='keyvalue', keynode='symbol')
332 332
333 333 def isvalidsymbol(tree):
334 334 """Examine whether specified ``tree`` is valid ``symbol`` or not
335 335 """
336 336 return tree[0] == 'symbol' and len(tree) > 1
337 337
338 338 def getsymbol(tree):
339 339 """Get symbol name from valid ``symbol`` in ``tree``
340 340
341 341 This assumes that ``tree`` is already examined by ``isvalidsymbol``.
342 342 """
343 343 return tree[1]
344 344
345 345 def isvalidfunc(tree):
346 346 """Examine whether specified ``tree`` is valid ``func`` or not
347 347 """
348 348 return tree[0] == 'func' and len(tree) > 1 and isvalidsymbol(tree[1])
349 349
350 350 def getfuncname(tree):
351 351 """Get function name from valid ``func`` in ``tree``
352 352
353 353 This assumes that ``tree`` is already examined by ``isvalidfunc``.
354 354 """
355 355 return getsymbol(tree[1])
356 356
357 357 def getfuncargs(tree):
358 358 """Get list of function arguments from valid ``func`` in ``tree``
359 359
360 360 This assumes that ``tree`` is already examined by ``isvalidfunc``.
361 361 """
362 362 if len(tree) > 2:
363 363 return getlist(tree[2])
364 364 else:
365 365 return []
366 366
367 367 def getset(repo, subset, x):
368 368 if not x:
369 369 raise error.ParseError(_("missing argument"))
370 370 s = methods[x[0]](repo, subset, *x[1:])
371 371 if util.safehasattr(s, 'isascending'):
372 372 return s
373 373 if (repo.ui.configbool('devel', 'all-warnings')
374 374 or repo.ui.configbool('devel', 'old-revset')):
375 375 # else case should not happen, because all non-func are internal,
376 376 # ignoring for now.
377 377 if x[0] == 'func' and x[1][0] == 'symbol' and x[1][1] in symbols:
378 378 repo.ui.develwarn('revset "%s" use list instead of smartset, '
379 379 '(upgrade your code)' % x[1][1])
380 380 return baseset(s)
381 381
382 382 def _getrevsource(repo, r):
383 383 extra = repo[r].extra()
384 384 for label in ('source', 'transplant_source', 'rebase_source'):
385 385 if label in extra:
386 386 try:
387 387 return repo[extra[label]].rev()
388 388 except error.RepoLookupError:
389 389 pass
390 390 return None
391 391
392 392 # operator methods
393 393
394 394 def stringset(repo, subset, x):
395 395 x = repo[x].rev()
396 396 if (x in subset
397 397 or x == node.nullrev and isinstance(subset, fullreposet)):
398 398 return baseset([x])
399 399 return baseset()
400 400
401 401 def rangeset(repo, subset, x, y):
402 402 m = getset(repo, fullreposet(repo), x)
403 403 n = getset(repo, fullreposet(repo), y)
404 404
405 405 if not m or not n:
406 406 return baseset()
407 407 m, n = m.first(), n.last()
408 408
409 409 if m == n:
410 410 r = baseset([m])
411 411 elif n == node.wdirrev:
412 412 r = spanset(repo, m, len(repo)) + baseset([n])
413 413 elif m == node.wdirrev:
414 414 r = baseset([m]) + spanset(repo, len(repo) - 1, n - 1)
415 415 elif m < n:
416 416 r = spanset(repo, m, n + 1)
417 417 else:
418 418 r = spanset(repo, m, n - 1)
419 419 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
420 420 # necessary to ensure we preserve the order in subset.
421 421 #
422 422 # This has performance implication, carrying the sorting over when possible
423 423 # would be more efficient.
424 424 return r & subset
425 425
426 426 def dagrange(repo, subset, x, y):
427 427 r = fullreposet(repo)
428 428 xs = reachableroots(repo, getset(repo, r, x), getset(repo, r, y),
429 429 includepath=True)
430 430 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
431 431 # necessary to ensure we preserve the order in subset.
432 432 return xs & subset
433 433
434 434 def andset(repo, subset, x, y):
435 435 return getset(repo, getset(repo, subset, x), y)
436 436
437 437 def orset(repo, subset, *xs):
438 438 assert xs
439 439 if len(xs) == 1:
440 440 return getset(repo, subset, xs[0])
441 441 p = len(xs) // 2
442 442 a = orset(repo, subset, *xs[:p])
443 443 b = orset(repo, subset, *xs[p:])
444 444 return a + b
445 445
446 446 def notset(repo, subset, x):
447 447 return subset - getset(repo, subset, x)
448 448
449 449 def listset(repo, subset, a, b):
450 450 raise error.ParseError(_("can't use a list in this context"))
451 451
452 452 def keyvaluepair(repo, subset, k, v):
453 453 raise error.ParseError(_("can't use a key-value pair in this context"))
454 454
455 455 def func(repo, subset, a, b):
456 456 if a[0] == 'symbol' and a[1] in symbols:
457 457 return symbols[a[1]](repo, subset, b)
458 458
459 459 keep = lambda fn: getattr(fn, '__doc__', None) is not None
460 460
461 461 syms = [s for (s, fn) in symbols.items() if keep(fn)]
462 462 raise error.UnknownIdentifier(a[1], syms)
463 463
464 464 # functions
465 465
466 466 def _mergedefaultdest(repo, subset, x):
467 467 # ``_mergedefaultdest()``
468 468
469 469 # default destination for merge.
470 470 # # XXX: Currently private because I expect the signature to change.
471 471 # # XXX: - taking rev as arguments,
472 472 # # XXX: - bailing out in case of ambiguity vs returning all data.
473 473 getargs(x, 0, 0, _("_mergedefaultdest takes no arguments"))
474 474 if repo._activebookmark:
475 475 bmheads = repo.bookmarkheads(repo._activebookmark)
476 476 curhead = repo[repo._activebookmark].node()
477 477 if len(bmheads) == 2:
478 478 if curhead == bmheads[0]:
479 479 node = bmheads[1]
480 480 else:
481 481 node = bmheads[0]
482 482 elif len(bmheads) > 2:
483 483 raise util.Abort(_("multiple matching bookmarks to merge - "
484 484 "please merge with an explicit rev or bookmark"),
485 485 hint=_("run 'hg heads' to see all heads"))
486 486 elif len(bmheads) <= 1:
487 487 raise util.Abort(_("no matching bookmark to merge - "
488 488 "please merge with an explicit rev or bookmark"),
489 489 hint=_("run 'hg heads' to see all heads"))
490 490 else:
491 491 branch = repo[None].branch()
492 492 bheads = repo.branchheads(branch)
493 493 nbhs = [bh for bh in bheads if not repo[bh].bookmarks()]
494 494
495 495 if len(nbhs) > 2:
496 496 raise util.Abort(_("branch '%s' has %d heads - "
497 497 "please merge with an explicit rev")
498 498 % (branch, len(bheads)),
499 499 hint=_("run 'hg heads .' to see heads"))
500 500
501 501 parent = repo.dirstate.p1()
502 502 if len(nbhs) <= 1:
503 503 if len(bheads) > 1:
504 504 raise util.Abort(_("heads are bookmarked - "
505 505 "please merge with an explicit rev"),
506 506 hint=_("run 'hg heads' to see all heads"))
507 507 if len(repo.heads()) > 1:
508 508 raise util.Abort(_("branch '%s' has one head - "
509 509 "please merge with an explicit rev")
510 510 % branch,
511 511 hint=_("run 'hg heads' to see all heads"))
512 512 msg, hint = _('nothing to merge'), None
513 513 if parent != repo.lookup(branch):
514 514 hint = _("use 'hg update' instead")
515 515 raise util.Abort(msg, hint=hint)
516 516
517 517 if parent not in bheads:
518 518 raise util.Abort(_('working directory not at a head revision'),
519 519 hint=_("use 'hg update' or merge with an "
520 520 "explicit revision"))
521 521 if parent == nbhs[0]:
522 522 node = nbhs[-1]
523 523 else:
524 524 node = nbhs[0]
525 525 return subset & baseset([repo[node].rev()])
526 526
527 527 def _updatedefaultdest(repo, subset, x):
528 528 # ``_updatedefaultdest()``
529 529
530 530 # default destination for update.
531 531 # # XXX: Currently private because I expect the signature to change.
532 532 # # XXX: - taking rev as arguments,
533 533 # # XXX: - bailing out in case of ambiguity vs returning all data.
534 534 getargs(x, 0, 0, _("_updatedefaultdest takes no arguments"))
535 535 # Here is where we should consider bookmarks, divergent bookmarks,
536 536 # foreground changesets (successors), and tip of current branch;
537 537 # but currently we are only checking the branch tips.
538 538 node = None
539 539 wc = repo[None]
540 540 p1 = wc.p1()
541 541 try:
542 542 node = repo.branchtip(wc.branch())
543 543 except error.RepoLookupError:
544 544 if wc.branch() == 'default': # no default branch!
545 545 node = repo.lookup('tip') # update to tip
546 546 else:
547 547 raise util.Abort(_("branch %s not found") % wc.branch())
548 548
549 549 if p1.obsolete() and not p1.children():
550 550 # allow updating to successors
551 551 successors = obsmod.successorssets(repo, p1.node())
552 552
553 553 # behavior of certain cases is as follows,
554 554 #
555 555 # divergent changesets: update to highest rev, similar to what
556 556 # is currently done when there are more than one head
557 557 # (i.e. 'tip')
558 558 #
559 559 # replaced changesets: same as divergent except we know there
560 560 # is no conflict
561 561 #
562 562 # pruned changeset: no update is done; though, we could
563 563 # consider updating to the first non-obsolete parent,
564 564 # similar to what is current done for 'hg prune'
565 565
566 566 if successors:
567 567 # flatten the list here handles both divergent (len > 1)
568 568 # and the usual case (len = 1)
569 569 successors = [n for sub in successors for n in sub]
570 570
571 571 # get the max revision for the given successors set,
572 572 # i.e. the 'tip' of a set
573 573 node = repo.revs('max(%ln)', successors).first()
574 574 return subset & baseset([repo[node].rev()])
575 575
576 576 def adds(repo, subset, x):
577 577 """``adds(pattern)``
578 578 Changesets that add a file matching pattern.
579 579
580 580 The pattern without explicit kind like ``glob:`` is expected to be
581 581 relative to the current directory and match against a file or a
582 582 directory.
583 583 """
584 584 # i18n: "adds" is a keyword
585 585 pat = getstring(x, _("adds requires a pattern"))
586 586 return checkstatus(repo, subset, pat, 1)
587 587
588 588 def ancestor(repo, subset, x):
589 589 """``ancestor(*changeset)``
590 590 A greatest common ancestor of the changesets.
591 591
592 592 Accepts 0 or more changesets.
593 593 Will return empty list when passed no args.
594 594 Greatest common ancestor of a single changeset is that changeset.
595 595 """
596 596 # i18n: "ancestor" is a keyword
597 597 l = getlist(x)
598 598 rl = fullreposet(repo)
599 599 anc = None
600 600
601 601 # (getset(repo, rl, i) for i in l) generates a list of lists
602 602 for revs in (getset(repo, rl, i) for i in l):
603 603 for r in revs:
604 604 if anc is None:
605 605 anc = repo[r]
606 606 else:
607 607 anc = anc.ancestor(repo[r])
608 608
609 609 if anc is not None and anc.rev() in subset:
610 610 return baseset([anc.rev()])
611 611 return baseset()
612 612
613 613 def _ancestors(repo, subset, x, followfirst=False):
614 614 heads = getset(repo, fullreposet(repo), x)
615 615 if not heads:
616 616 return baseset()
617 617 s = _revancestors(repo, heads, followfirst)
618 618 return subset & s
619 619
620 620 def ancestors(repo, subset, x):
621 621 """``ancestors(set)``
622 622 Changesets that are ancestors of a changeset in set.
623 623 """
624 624 return _ancestors(repo, subset, x)
625 625
626 626 def _firstancestors(repo, subset, x):
627 627 # ``_firstancestors(set)``
628 628 # Like ``ancestors(set)`` but follows only the first parents.
629 629 return _ancestors(repo, subset, x, followfirst=True)
630 630
631 631 def ancestorspec(repo, subset, x, n):
632 632 """``set~n``
633 633 Changesets that are the Nth ancestor (first parents only) of a changeset
634 634 in set.
635 635 """
636 636 try:
637 637 n = int(n[1])
638 638 except (TypeError, ValueError):
639 639 raise error.ParseError(_("~ expects a number"))
640 640 ps = set()
641 641 cl = repo.changelog
642 642 for r in getset(repo, fullreposet(repo), x):
643 643 for i in range(n):
644 644 r = cl.parentrevs(r)[0]
645 645 ps.add(r)
646 646 return subset & ps
647 647
648 648 def author(repo, subset, x):
649 649 """``author(string)``
650 650 Alias for ``user(string)``.
651 651 """
652 652 # i18n: "author" is a keyword
653 653 n = encoding.lower(getstring(x, _("author requires a string")))
654 654 kind, pattern, matcher = _substringmatcher(n)
655 655 return subset.filter(lambda x: matcher(encoding.lower(repo[x].user())))
656 656
657 657 def bisect(repo, subset, x):
658 658 """``bisect(string)``
659 659 Changesets marked in the specified bisect status:
660 660
661 661 - ``good``, ``bad``, ``skip``: csets explicitly marked as good/bad/skip
662 662 - ``goods``, ``bads`` : csets topologically good/bad
663 663 - ``range`` : csets taking part in the bisection
664 664 - ``pruned`` : csets that are goods, bads or skipped
665 665 - ``untested`` : csets whose fate is yet unknown
666 666 - ``ignored`` : csets ignored due to DAG topology
667 667 - ``current`` : the cset currently being bisected
668 668 """
669 669 # i18n: "bisect" is a keyword
670 670 status = getstring(x, _("bisect requires a string")).lower()
671 671 state = set(hbisect.get(repo, status))
672 672 return subset & state
673 673
674 674 # Backward-compatibility
675 675 # - no help entry so that we do not advertise it any more
676 676 def bisected(repo, subset, x):
677 677 return bisect(repo, subset, x)
678 678
679 679 def bookmark(repo, subset, x):
680 680 """``bookmark([name])``
681 681 The named bookmark or all bookmarks.
682 682
683 683 If `name` starts with `re:`, the remainder of the name is treated as
684 684 a regular expression. To match a bookmark that actually starts with `re:`,
685 685 use the prefix `literal:`.
686 686 """
687 687 # i18n: "bookmark" is a keyword
688 688 args = getargs(x, 0, 1, _('bookmark takes one or no arguments'))
689 689 if args:
690 690 bm = getstring(args[0],
691 691 # i18n: "bookmark" is a keyword
692 692 _('the argument to bookmark must be a string'))
693 kind, pattern, matcher = _stringmatcher(bm)
693 kind, pattern, matcher = util.stringmatcher(bm)
694 694 bms = set()
695 695 if kind == 'literal':
696 696 bmrev = repo._bookmarks.get(pattern, None)
697 697 if not bmrev:
698 698 raise error.RepoLookupError(_("bookmark '%s' does not exist")
699 699 % bm)
700 700 bms.add(repo[bmrev].rev())
701 701 else:
702 702 matchrevs = set()
703 703 for name, bmrev in repo._bookmarks.iteritems():
704 704 if matcher(name):
705 705 matchrevs.add(bmrev)
706 706 if not matchrevs:
707 707 raise error.RepoLookupError(_("no bookmarks exist"
708 708 " that match '%s'") % pattern)
709 709 for bmrev in matchrevs:
710 710 bms.add(repo[bmrev].rev())
711 711 else:
712 712 bms = set([repo[r].rev()
713 713 for r in repo._bookmarks.values()])
714 714 bms -= set([node.nullrev])
715 715 return subset & bms
716 716
717 717 def branch(repo, subset, x):
718 718 """``branch(string or set)``
719 719 All changesets belonging to the given branch or the branches of the given
720 720 changesets.
721 721
722 722 If `string` starts with `re:`, the remainder of the name is treated as
723 723 a regular expression. To match a branch that actually starts with `re:`,
724 724 use the prefix `literal:`.
725 725 """
726 726 getbi = repo.revbranchcache().branchinfo
727 727
728 728 try:
729 729 b = getstring(x, '')
730 730 except error.ParseError:
731 731 # not a string, but another revspec, e.g. tip()
732 732 pass
733 733 else:
734 kind, pattern, matcher = _stringmatcher(b)
734 kind, pattern, matcher = util.stringmatcher(b)
735 735 if kind == 'literal':
736 736 # note: falls through to the revspec case if no branch with
737 737 # this name exists
738 738 if pattern in repo.branchmap():
739 739 return subset.filter(lambda r: matcher(getbi(r)[0]))
740 740 else:
741 741 return subset.filter(lambda r: matcher(getbi(r)[0]))
742 742
743 743 s = getset(repo, fullreposet(repo), x)
744 744 b = set()
745 745 for r in s:
746 746 b.add(getbi(r)[0])
747 747 c = s.__contains__
748 748 return subset.filter(lambda r: c(r) or getbi(r)[0] in b)
749 749
750 750 def bumped(repo, subset, x):
751 751 """``bumped()``
752 752 Mutable changesets marked as successors of public changesets.
753 753
754 754 Only non-public and non-obsolete changesets can be `bumped`.
755 755 """
756 756 # i18n: "bumped" is a keyword
757 757 getargs(x, 0, 0, _("bumped takes no arguments"))
758 758 bumped = obsmod.getrevs(repo, 'bumped')
759 759 return subset & bumped
760 760
761 761 def bundle(repo, subset, x):
762 762 """``bundle()``
763 763 Changesets in the bundle.
764 764
765 765 Bundle must be specified by the -R option."""
766 766
767 767 try:
768 768 bundlerevs = repo.changelog.bundlerevs
769 769 except AttributeError:
770 770 raise util.Abort(_("no bundle provided - specify with -R"))
771 771 return subset & bundlerevs
772 772
773 773 def checkstatus(repo, subset, pat, field):
774 774 hasset = matchmod.patkind(pat) == 'set'
775 775
776 776 mcache = [None]
777 777 def matches(x):
778 778 c = repo[x]
779 779 if not mcache[0] or hasset:
780 780 mcache[0] = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
781 781 m = mcache[0]
782 782 fname = None
783 783 if not m.anypats() and len(m.files()) == 1:
784 784 fname = m.files()[0]
785 785 if fname is not None:
786 786 if fname not in c.files():
787 787 return False
788 788 else:
789 789 for f in c.files():
790 790 if m(f):
791 791 break
792 792 else:
793 793 return False
794 794 files = repo.status(c.p1().node(), c.node())[field]
795 795 if fname is not None:
796 796 if fname in files:
797 797 return True
798 798 else:
799 799 for f in files:
800 800 if m(f):
801 801 return True
802 802
803 803 return subset.filter(matches)
804 804
805 805 def _children(repo, narrow, parentset):
806 806 if not parentset:
807 807 return baseset()
808 808 cs = set()
809 809 pr = repo.changelog.parentrevs
810 810 minrev = parentset.min()
811 811 for r in narrow:
812 812 if r <= minrev:
813 813 continue
814 814 for p in pr(r):
815 815 if p in parentset:
816 816 cs.add(r)
817 817 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
818 818 # This does not break because of other fullreposet misbehavior.
819 819 return baseset(cs)
820 820
821 821 def children(repo, subset, x):
822 822 """``children(set)``
823 823 Child changesets of changesets in set.
824 824 """
825 825 s = getset(repo, fullreposet(repo), x)
826 826 cs = _children(repo, subset, s)
827 827 return subset & cs
828 828
829 829 def closed(repo, subset, x):
830 830 """``closed()``
831 831 Changeset is closed.
832 832 """
833 833 # i18n: "closed" is a keyword
834 834 getargs(x, 0, 0, _("closed takes no arguments"))
835 835 return subset.filter(lambda r: repo[r].closesbranch())
836 836
837 837 def contains(repo, subset, x):
838 838 """``contains(pattern)``
839 839 The revision's manifest contains a file matching pattern (but might not
840 840 modify it). See :hg:`help patterns` for information about file patterns.
841 841
842 842 The pattern without explicit kind like ``glob:`` is expected to be
843 843 relative to the current directory and match against a file exactly
844 844 for efficiency.
845 845 """
846 846 # i18n: "contains" is a keyword
847 847 pat = getstring(x, _("contains requires a pattern"))
848 848
849 849 def matches(x):
850 850 if not matchmod.patkind(pat):
851 851 pats = pathutil.canonpath(repo.root, repo.getcwd(), pat)
852 852 if pats in repo[x]:
853 853 return True
854 854 else:
855 855 c = repo[x]
856 856 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=c)
857 857 for f in c.manifest():
858 858 if m(f):
859 859 return True
860 860 return False
861 861
862 862 return subset.filter(matches)
863 863
864 864 def converted(repo, subset, x):
865 865 """``converted([id])``
866 866 Changesets converted from the given identifier in the old repository if
867 867 present, or all converted changesets if no identifier is specified.
868 868 """
869 869
870 870 # There is exactly no chance of resolving the revision, so do a simple
871 871 # string compare and hope for the best
872 872
873 873 rev = None
874 874 # i18n: "converted" is a keyword
875 875 l = getargs(x, 0, 1, _('converted takes one or no arguments'))
876 876 if l:
877 877 # i18n: "converted" is a keyword
878 878 rev = getstring(l[0], _('converted requires a revision'))
879 879
880 880 def _matchvalue(r):
881 881 source = repo[r].extra().get('convert_revision', None)
882 882 return source is not None and (rev is None or source.startswith(rev))
883 883
884 884 return subset.filter(lambda r: _matchvalue(r))
885 885
886 886 def date(repo, subset, x):
887 887 """``date(interval)``
888 888 Changesets within the interval, see :hg:`help dates`.
889 889 """
890 890 # i18n: "date" is a keyword
891 891 ds = getstring(x, _("date requires a string"))
892 892 dm = util.matchdate(ds)
893 893 return subset.filter(lambda x: dm(repo[x].date()[0]))
894 894
895 895 def desc(repo, subset, x):
896 896 """``desc(string)``
897 897 Search commit message for string. The match is case-insensitive.
898 898 """
899 899 # i18n: "desc" is a keyword
900 900 ds = encoding.lower(getstring(x, _("desc requires a string")))
901 901
902 902 def matches(x):
903 903 c = repo[x]
904 904 return ds in encoding.lower(c.description())
905 905
906 906 return subset.filter(matches)
907 907
908 908 def _descendants(repo, subset, x, followfirst=False):
909 909 roots = getset(repo, fullreposet(repo), x)
910 910 if not roots:
911 911 return baseset()
912 912 s = _revdescendants(repo, roots, followfirst)
913 913
914 914 # Both sets need to be ascending in order to lazily return the union
915 915 # in the correct order.
916 916 base = subset & roots
917 917 desc = subset & s
918 918 result = base + desc
919 919 if subset.isascending():
920 920 result.sort()
921 921 elif subset.isdescending():
922 922 result.sort(reverse=True)
923 923 else:
924 924 result = subset & result
925 925 return result
926 926
927 927 def descendants(repo, subset, x):
928 928 """``descendants(set)``
929 929 Changesets which are descendants of changesets in set.
930 930 """
931 931 return _descendants(repo, subset, x)
932 932
933 933 def _firstdescendants(repo, subset, x):
934 934 # ``_firstdescendants(set)``
935 935 # Like ``descendants(set)`` but follows only the first parents.
936 936 return _descendants(repo, subset, x, followfirst=True)
937 937
938 938 def destination(repo, subset, x):
939 939 """``destination([set])``
940 940 Changesets that were created by a graft, transplant or rebase operation,
941 941 with the given revisions specified as the source. Omitting the optional set
942 942 is the same as passing all().
943 943 """
944 944 if x is not None:
945 945 sources = getset(repo, fullreposet(repo), x)
946 946 else:
947 947 sources = fullreposet(repo)
948 948
949 949 dests = set()
950 950
951 951 # subset contains all of the possible destinations that can be returned, so
952 952 # iterate over them and see if their source(s) were provided in the arg set.
953 953 # Even if the immediate src of r is not in the arg set, src's source (or
954 954 # further back) may be. Scanning back further than the immediate src allows
955 955 # transitive transplants and rebases to yield the same results as transitive
956 956 # grafts.
957 957 for r in subset:
958 958 src = _getrevsource(repo, r)
959 959 lineage = None
960 960
961 961 while src is not None:
962 962 if lineage is None:
963 963 lineage = list()
964 964
965 965 lineage.append(r)
966 966
967 967 # The visited lineage is a match if the current source is in the arg
968 968 # set. Since every candidate dest is visited by way of iterating
969 969 # subset, any dests further back in the lineage will be tested by a
970 970 # different iteration over subset. Likewise, if the src was already
971 971 # selected, the current lineage can be selected without going back
972 972 # further.
973 973 if src in sources or src in dests:
974 974 dests.update(lineage)
975 975 break
976 976
977 977 r = src
978 978 src = _getrevsource(repo, r)
979 979
980 980 return subset.filter(dests.__contains__)
981 981
982 982 def divergent(repo, subset, x):
983 983 """``divergent()``
984 984 Final successors of changesets with an alternative set of final successors.
985 985 """
986 986 # i18n: "divergent" is a keyword
987 987 getargs(x, 0, 0, _("divergent takes no arguments"))
988 988 divergent = obsmod.getrevs(repo, 'divergent')
989 989 return subset & divergent
990 990
991 991 def extinct(repo, subset, x):
992 992 """``extinct()``
993 993 Obsolete changesets with obsolete descendants only.
994 994 """
995 995 # i18n: "extinct" is a keyword
996 996 getargs(x, 0, 0, _("extinct takes no arguments"))
997 997 extincts = obsmod.getrevs(repo, 'extinct')
998 998 return subset & extincts
999 999
1000 1000 def extra(repo, subset, x):
1001 1001 """``extra(label, [value])``
1002 1002 Changesets with the given label in the extra metadata, with the given
1003 1003 optional value.
1004 1004
1005 1005 If `value` starts with `re:`, the remainder of the value is treated as
1006 1006 a regular expression. To match a value that actually starts with `re:`,
1007 1007 use the prefix `literal:`.
1008 1008 """
1009 1009 args = getargsdict(x, 'extra', 'label value')
1010 1010 if 'label' not in args:
1011 1011 # i18n: "extra" is a keyword
1012 1012 raise error.ParseError(_('extra takes at least 1 argument'))
1013 1013 # i18n: "extra" is a keyword
1014 1014 label = getstring(args['label'], _('first argument to extra must be '
1015 1015 'a string'))
1016 1016 value = None
1017 1017
1018 1018 if 'value' in args:
1019 1019 # i18n: "extra" is a keyword
1020 1020 value = getstring(args['value'], _('second argument to extra must be '
1021 1021 'a string'))
1022 kind, value, matcher = _stringmatcher(value)
1022 kind, value, matcher = util.stringmatcher(value)
1023 1023
1024 1024 def _matchvalue(r):
1025 1025 extra = repo[r].extra()
1026 1026 return label in extra and (value is None or matcher(extra[label]))
1027 1027
1028 1028 return subset.filter(lambda r: _matchvalue(r))
1029 1029
1030 1030 def filelog(repo, subset, x):
1031 1031 """``filelog(pattern)``
1032 1032 Changesets connected to the specified filelog.
1033 1033
1034 1034 For performance reasons, visits only revisions mentioned in the file-level
1035 1035 filelog, rather than filtering through all changesets (much faster, but
1036 1036 doesn't include deletes or duplicate changes). For a slower, more accurate
1037 1037 result, use ``file()``.
1038 1038
1039 1039 The pattern without explicit kind like ``glob:`` is expected to be
1040 1040 relative to the current directory and match against a file exactly
1041 1041 for efficiency.
1042 1042
1043 1043 If some linkrev points to revisions filtered by the current repoview, we'll
1044 1044 work around it to return a non-filtered value.
1045 1045 """
1046 1046
1047 1047 # i18n: "filelog" is a keyword
1048 1048 pat = getstring(x, _("filelog requires a pattern"))
1049 1049 s = set()
1050 1050 cl = repo.changelog
1051 1051
1052 1052 if not matchmod.patkind(pat):
1053 1053 f = pathutil.canonpath(repo.root, repo.getcwd(), pat)
1054 1054 files = [f]
1055 1055 else:
1056 1056 m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[None])
1057 1057 files = (f for f in repo[None] if m(f))
1058 1058
1059 1059 for f in files:
1060 1060 backrevref = {} # final value for: filerev -> changerev
1061 1061 lowestchild = {} # lowest known filerev child of a filerev
1062 1062 delayed = [] # filerev with filtered linkrev, for post-processing
1063 1063 lowesthead = None # cache for manifest content of all head revisions
1064 1064 fl = repo.file(f)
1065 1065 for fr in list(fl):
1066 1066 rev = fl.linkrev(fr)
1067 1067 if rev not in cl:
1068 1068 # changerev pointed in linkrev is filtered
1069 1069 # record it for post processing.
1070 1070 delayed.append((fr, rev))
1071 1071 continue
1072 1072 for p in fl.parentrevs(fr):
1073 1073 if 0 <= p and p not in lowestchild:
1074 1074 lowestchild[p] = fr
1075 1075 backrevref[fr] = rev
1076 1076 s.add(rev)
1077 1077
1078 1078 # Post-processing of all filerevs we skipped because they were
1079 1079 # filtered. If such filerevs have known and unfiltered children, this
1080 1080 # means they have an unfiltered appearance out there. We'll use linkrev
1081 1081 # adjustment to find one of these appearances. The lowest known child
1082 1082 # will be used as a starting point because it is the best upper-bound we
1083 1083 # have.
1084 1084 #
1085 1085 # This approach will fail when an unfiltered but linkrev-shadowed
1086 1086 # appearance exists in a head changeset without unfiltered filerev
1087 1087 # children anywhere.
1088 1088 while delayed:
1089 1089 # must be a descending iteration. To slowly fill lowest child
1090 1090 # information that is of potential use by the next item.
1091 1091 fr, rev = delayed.pop()
1092 1092 lkr = rev
1093 1093
1094 1094 child = lowestchild.get(fr)
1095 1095
1096 1096 if child is None:
1097 1097 # search for existence of this file revision in a head revision.
1098 1098 # There are three possibilities:
1099 1099 # - the revision exists in a head and we can find an
1100 1100 # introduction from there,
1101 1101 # - the revision does not exist in a head because it has been
1102 1102 # changed since its introduction: we would have found a child
1103 1103 # and be in the other 'else' clause,
1104 1104 # - all versions of the revision are hidden.
1105 1105 if lowesthead is None:
1106 1106 lowesthead = {}
1107 1107 for h in repo.heads():
1108 1108 fnode = repo[h].manifest().get(f)
1109 1109 if fnode is not None:
1110 1110 lowesthead[fl.rev(fnode)] = h
1111 1111 headrev = lowesthead.get(fr)
1112 1112 if headrev is None:
1113 1113 # content is nowhere unfiltered
1114 1114 continue
1115 1115 rev = repo[headrev][f].introrev()
1116 1116 else:
1117 1117 # the lowest known child is a good upper bound
1118 1118 childcrev = backrevref[child]
1119 1119 # XXX this does not guarantee returning the lowest
1120 1120 # introduction of this revision, but this gives a
1121 1121 # result which is a good start and will fit in most
1122 1122 # cases. We probably need to fix the multiple
1123 1123 # introductions case properly (report each
1124 1124 # introduction, even for identical file revisions)
1125 1125 # once and for all at some point anyway.
1126 1126 for p in repo[childcrev][f].parents():
1127 1127 if p.filerev() == fr:
1128 1128 rev = p.rev()
1129 1129 break
1130 1130 if rev == lkr: # no shadowed entry found
1131 1131 # XXX This should never happen unless some manifest points
1132 1132 # to biggish file revisions (like a revision that uses a
1133 1133 # parent that never appears in the manifest ancestors)
1134 1134 continue
1135 1135
1136 1136 # Fill the data for the next iteration.
1137 1137 for p in fl.parentrevs(fr):
1138 1138 if 0 <= p and p not in lowestchild:
1139 1139 lowestchild[p] = fr
1140 1140 backrevref[fr] = rev
1141 1141 s.add(rev)
1142 1142
1143 1143 return subset & s
1144 1144
1145 1145 def first(repo, subset, x):
1146 1146 """``first(set, [n])``
1147 1147 An alias for limit().
1148 1148 """
1149 1149 return limit(repo, subset, x)
1150 1150
1151 1151 def _follow(repo, subset, x, name, followfirst=False):
1152 1152 l = getargs(x, 0, 1, _("%s takes no arguments or a pattern") % name)
1153 1153 c = repo['.']
1154 1154 if l:
1155 1155 x = getstring(l[0], _("%s expected a pattern") % name)
1156 1156 matcher = matchmod.match(repo.root, repo.getcwd(), [x],
1157 1157 ctx=repo[None], default='path')
1158 1158
1159 1159 s = set()
1160 1160 for fname in c:
1161 1161 if matcher(fname):
1162 1162 fctx = c[fname]
1163 1163 s = s.union(set(c.rev() for c in fctx.ancestors(followfirst)))
1164 1164 # include the revision responsible for the most recent version
1165 1165 s.add(fctx.introrev())
1166 1166 else:
1167 1167 s = _revancestors(repo, baseset([c.rev()]), followfirst)
1168 1168
1169 1169 return subset & s
1170 1170
1171 1171 def follow(repo, subset, x):
1172 1172 """``follow([pattern])``
1173 1173 An alias for ``::.`` (ancestors of the working directory's first parent).
1174 1174 If pattern is specified, the histories of files matching given
1175 1175 pattern is followed, including copies.
1176 1176 """
1177 1177 return _follow(repo, subset, x, 'follow')
1178 1178
1179 1179 def _followfirst(repo, subset, x):
1180 1180 # ``followfirst([pattern])``
1181 1181 # Like ``follow([pattern])`` but follows only the first parent of
1182 1182 # every revisions or files revisions.
1183 1183 return _follow(repo, subset, x, '_followfirst', followfirst=True)
1184 1184
1185 1185 def getall(repo, subset, x):
1186 1186 """``all()``
1187 1187 All changesets, the same as ``0:tip``.
1188 1188 """
1189 1189 # i18n: "all" is a keyword
1190 1190 getargs(x, 0, 0, _("all takes no arguments"))
1191 1191 return subset & spanset(repo) # drop "null" if any
1192 1192
1193 1193 def grep(repo, subset, x):
1194 1194 """``grep(regex)``
1195 1195 Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
1196 1196 to ensure special escape characters are handled correctly. Unlike
1197 1197 ``keyword(string)``, the match is case-sensitive.
1198 1198 """
1199 1199 try:
1200 1200 # i18n: "grep" is a keyword
1201 1201 gr = re.compile(getstring(x, _("grep requires a string")))
1202 1202 except re.error as e:
1203 1203 raise error.ParseError(_('invalid match pattern: %s') % e)
1204 1204
1205 1205 def matches(x):
1206 1206 c = repo[x]
1207 1207 for e in c.files() + [c.user(), c.description()]:
1208 1208 if gr.search(e):
1209 1209 return True
1210 1210 return False
1211 1211
1212 1212 return subset.filter(matches)
1213 1213
1214 1214 def _matchfiles(repo, subset, x):
1215 1215 # _matchfiles takes a revset list of prefixed arguments:
1216 1216 #
1217 1217 # [p:foo, i:bar, x:baz]
1218 1218 #
1219 1219 # builds a match object from them and filters subset. Allowed
1220 1220 # prefixes are 'p:' for regular patterns, 'i:' for include
1221 1221 # patterns and 'x:' for exclude patterns. Use 'r:' prefix to pass
1222 1222 # a revision identifier, or the empty string to reference the
1223 1223 # working directory, from which the match object is
1224 1224 # initialized. Use 'd:' to set the default matching mode, default
1225 1225 # to 'glob'. At most one 'r:' and 'd:' argument can be passed.
1226 1226
1227 1227 # i18n: "_matchfiles" is a keyword
1228 1228 l = getargs(x, 1, -1, _("_matchfiles requires at least one argument"))
1229 1229 pats, inc, exc = [], [], []
1230 1230 rev, default = None, None
1231 1231 for arg in l:
1232 1232 # i18n: "_matchfiles" is a keyword
1233 1233 s = getstring(arg, _("_matchfiles requires string arguments"))
1234 1234 prefix, value = s[:2], s[2:]
1235 1235 if prefix == 'p:':
1236 1236 pats.append(value)
1237 1237 elif prefix == 'i:':
1238 1238 inc.append(value)
1239 1239 elif prefix == 'x:':
1240 1240 exc.append(value)
1241 1241 elif prefix == 'r:':
1242 1242 if rev is not None:
1243 1243 # i18n: "_matchfiles" is a keyword
1244 1244 raise error.ParseError(_('_matchfiles expected at most one '
1245 1245 'revision'))
1246 1246 if value != '': # empty means working directory; leave rev as None
1247 1247 rev = value
1248 1248 elif prefix == 'd:':
1249 1249 if default is not None:
1250 1250 # i18n: "_matchfiles" is a keyword
1251 1251 raise error.ParseError(_('_matchfiles expected at most one '
1252 1252 'default mode'))
1253 1253 default = value
1254 1254 else:
1255 1255 # i18n: "_matchfiles" is a keyword
1256 1256 raise error.ParseError(_('invalid _matchfiles prefix: %s') % prefix)
1257 1257 if not default:
1258 1258 default = 'glob'
1259 1259
1260 1260 m = matchmod.match(repo.root, repo.getcwd(), pats, include=inc,
1261 1261 exclude=exc, ctx=repo[rev], default=default)
1262 1262
1263 1263 def matches(x):
1264 1264 for f in repo[x].files():
1265 1265 if m(f):
1266 1266 return True
1267 1267 return False
1268 1268
1269 1269 return subset.filter(matches)
1270 1270
1271 1271 def hasfile(repo, subset, x):
1272 1272 """``file(pattern)``
1273 1273 Changesets affecting files matched by pattern.
1274 1274
1275 1275 For a faster but less accurate result, consider using ``filelog()``
1276 1276 instead.
1277 1277
1278 1278 This predicate uses ``glob:`` as the default kind of pattern.
1279 1279 """
1280 1280 # i18n: "file" is a keyword
1281 1281 pat = getstring(x, _("file requires a pattern"))
1282 1282 return _matchfiles(repo, subset, ('string', 'p:' + pat))
1283 1283
1284 1284 def head(repo, subset, x):
1285 1285 """``head()``
1286 1286 Changeset is a named branch head.
1287 1287 """
1288 1288 # i18n: "head" is a keyword
1289 1289 getargs(x, 0, 0, _("head takes no arguments"))
1290 1290 hs = set()
1291 1291 cl = repo.changelog
1292 1292 for b, ls in repo.branchmap().iteritems():
1293 1293 hs.update(cl.rev(h) for h in ls)
1294 1294 # XXX using a set to feed the baseset is wrong. Sets are not ordered.
1295 1295 # This does not break because of other fullreposet misbehavior.
1296 1296 # XXX We should combine with subset first: 'subset & baseset(...)'. This is
1297 1297 # necessary to ensure we preserve the order in subset.
1298 1298 return baseset(hs) & subset
1299 1299
1300 1300 def heads(repo, subset, x):
1301 1301 """``heads(set)``
1302 1302 Members of set with no children in set.
1303 1303 """
1304 1304 s = getset(repo, subset, x)
1305 1305 ps = parents(repo, subset, x)
1306 1306 return s - ps
1307 1307
1308 1308 def hidden(repo, subset, x):
1309 1309 """``hidden()``
1310 1310 Hidden changesets.
1311 1311 """
1312 1312 # i18n: "hidden" is a keyword
1313 1313 getargs(x, 0, 0, _("hidden takes no arguments"))
1314 1314 hiddenrevs = repoview.filterrevs(repo, 'visible')
1315 1315 return subset & hiddenrevs
1316 1316
1317 1317 def keyword(repo, subset, x):
1318 1318 """``keyword(string)``
1319 1319 Search commit message, user name, and names of changed files for
1320 1320 string. The match is case-insensitive.
1321 1321 """
1322 1322 # i18n: "keyword" is a keyword
1323 1323 kw = encoding.lower(getstring(x, _("keyword requires a string")))
1324 1324
1325 1325 def matches(r):
1326 1326 c = repo[r]
1327 1327 return any(kw in encoding.lower(t)
1328 1328 for t in c.files() + [c.user(), c.description()])
1329 1329
1330 1330 return subset.filter(matches)
1331 1331
1332 1332 def limit(repo, subset, x):
1333 1333 """``limit(set, [n])``
1334 1334 First n members of set, defaulting to 1.
1335 1335 """
1336 1336 # i18n: "limit" is a keyword
1337 1337 l = getargs(x, 1, 2, _("limit requires one or two arguments"))
1338 1338 try:
1339 1339 lim = 1
1340 1340 if len(l) == 2:
1341 1341 # i18n: "limit" is a keyword
1342 1342 lim = int(getstring(l[1], _("limit requires a number")))
1343 1343 except (TypeError, ValueError):
1344 1344 # i18n: "limit" is a keyword
1345 1345 raise error.ParseError(_("limit expects a number"))
1346 1346 ss = subset
1347 1347 os = getset(repo, fullreposet(repo), l[0])
1348 1348 result = []
1349 1349 it = iter(os)
1350 1350 for x in xrange(lim):
1351 1351 y = next(it, None)
1352 1352 if y is None:
1353 1353 break
1354 1354 elif y in ss:
1355 1355 result.append(y)
1356 1356 return baseset(result)
1357 1357
1358 1358 def last(repo, subset, x):
1359 1359 """``last(set, [n])``
1360 1360 Last n members of set, defaulting to 1.
1361 1361 """
1362 1362 # i18n: "last" is a keyword
1363 1363 l = getargs(x, 1, 2, _("last requires one or two arguments"))
1364 1364 try:
1365 1365 lim = 1
1366 1366 if len(l) == 2:
1367 1367 # i18n: "last" is a keyword
1368 1368 lim = int(getstring(l[1], _("last requires a number")))
1369 1369 except (TypeError, ValueError):
1370 1370 # i18n: "last" is a keyword
1371 1371 raise error.ParseError(_("last expects a number"))
1372 1372 ss = subset
1373 1373 os = getset(repo, fullreposet(repo), l[0])
1374 1374 os.reverse()
1375 1375 result = []
1376 1376 it = iter(os)
1377 1377 for x in xrange(lim):
1378 1378 y = next(it, None)
1379 1379 if y is None:
1380 1380 break
1381 1381 elif y in ss:
1382 1382 result.append(y)
1383 1383 return baseset(result)
1384 1384
1385 1385 def maxrev(repo, subset, x):
1386 1386 """``max(set)``
1387 1387 Changeset with highest revision number in set.
1388 1388 """
1389 1389 os = getset(repo, fullreposet(repo), x)
1390 1390 try:
1391 1391 m = os.max()
1392 1392 if m in subset:
1393 1393 return baseset([m])
1394 1394 except ValueError:
1395 1395 # os.max() throws a ValueError when the collection is empty.
1396 1396 # Same as python's max().
1397 1397 pass
1398 1398 return baseset()
1399 1399
1400 1400 def merge(repo, subset, x):
1401 1401 """``merge()``
1402 1402 Changeset is a merge changeset.
1403 1403 """
1404 1404 # i18n: "merge" is a keyword
1405 1405 getargs(x, 0, 0, _("merge takes no arguments"))
1406 1406 cl = repo.changelog
1407 1407 return subset.filter(lambda r: cl.parentrevs(r)[1] != -1)
1408 1408
1409 1409 def branchpoint(repo, subset, x):
1410 1410 """``branchpoint()``
1411 1411 Changesets with more than one child.
1412 1412 """
1413 1413 # i18n: "branchpoint" is a keyword
1414 1414 getargs(x, 0, 0, _("branchpoint takes no arguments"))
1415 1415 cl = repo.changelog
1416 1416 if not subset:
1417 1417 return baseset()
1418 1418 # XXX this should be 'parentset.min()' assuming 'parentset' is a smartset
1419 1419 # (and if it is not, it should.)
1420 1420 baserev = min(subset)
1421 1421 parentscount = [0]*(len(repo) - baserev)
1422 1422 for r in cl.revs(start=baserev + 1):
1423 1423 for p in cl.parentrevs(r):
1424 1424 if p >= baserev:
1425 1425 parentscount[p - baserev] += 1
1426 1426 return subset.filter(lambda r: parentscount[r - baserev] > 1)
1427 1427
1428 1428 def minrev(repo, subset, x):
1429 1429 """``min(set)``
1430 1430 Changeset with lowest revision number in set.
1431 1431 """
1432 1432 os = getset(repo, fullreposet(repo), x)
1433 1433 try:
1434 1434 m = os.min()
1435 1435 if m in subset:
1436 1436 return baseset([m])
1437 1437 except ValueError:
1438 1438 # os.min() throws a ValueError when the collection is empty.
1439 1439 # Same as python's min().
1440 1440 pass
1441 1441 return baseset()
1442 1442
1443 1443 def modifies(repo, subset, x):
1444 1444 """``modifies(pattern)``
1445 1445 Changesets modifying files matched by pattern.
1446 1446
1447 1447 The pattern without explicit kind like ``glob:`` is expected to be
1448 1448 relative to the current directory and match against a file or a
1449 1449 directory.
1450 1450 """
1451 1451 # i18n: "modifies" is a keyword
1452 1452 pat = getstring(x, _("modifies requires a pattern"))
1453 1453 return checkstatus(repo, subset, pat, 0)
1454 1454
1455 1455 def named(repo, subset, x):
1456 1456 """``named(namespace)``
1457 1457 The changesets in a given namespace.
1458 1458
1459 1459 If `namespace` starts with `re:`, the remainder of the string is treated as
1460 1460 a regular expression. To match a namespace that actually starts with `re:`,
1461 1461 use the prefix `literal:`.
1462 1462 """
1463 1463 # i18n: "named" is a keyword
1464 1464 args = getargs(x, 1, 1, _('named requires a namespace argument'))
1465 1465
1466 1466 ns = getstring(args[0],
1467 1467 # i18n: "named" is a keyword
1468 1468 _('the argument to named must be a string'))
1469 kind, pattern, matcher = _stringmatcher(ns)
1469 kind, pattern, matcher = util.stringmatcher(ns)
1470 1470 namespaces = set()
1471 1471 if kind == 'literal':
1472 1472 if pattern not in repo.names:
1473 1473 raise error.RepoLookupError(_("namespace '%s' does not exist")
1474 1474 % ns)
1475 1475 namespaces.add(repo.names[pattern])
1476 1476 else:
1477 1477 for name, ns in repo.names.iteritems():
1478 1478 if matcher(name):
1479 1479 namespaces.add(ns)
1480 1480 if not namespaces:
1481 1481 raise error.RepoLookupError(_("no namespace exists"
1482 1482 " that match '%s'") % pattern)
1483 1483
1484 1484 names = set()
1485 1485 for ns in namespaces:
1486 1486 for name in ns.listnames(repo):
1487 1487 if name not in ns.deprecated:
1488 1488 names.update(repo[n].rev() for n in ns.nodes(repo, name))
1489 1489
1490 1490 names -= set([node.nullrev])
1491 1491 return subset & names
1492 1492
1493 1493 def node_(repo, subset, x):
1494 1494 """``id(string)``
1495 1495 Revision non-ambiguously specified by the given hex string prefix.
1496 1496 """
1497 1497 # i18n: "id" is a keyword
1498 1498 l = getargs(x, 1, 1, _("id requires one argument"))
1499 1499 # i18n: "id" is a keyword
1500 1500 n = getstring(l[0], _("id requires a string"))
1501 1501 if len(n) == 40:
1502 1502 try:
1503 1503 rn = repo.changelog.rev(node.bin(n))
1504 1504 except (LookupError, TypeError):
1505 1505 rn = None
1506 1506 else:
1507 1507 rn = None
1508 1508 pm = repo.changelog._partialmatch(n)
1509 1509 if pm is not None:
1510 1510 rn = repo.changelog.rev(pm)
1511 1511
1512 1512 if rn is None:
1513 1513 return baseset()
1514 1514 result = baseset([rn])
1515 1515 return result & subset
1516 1516
1517 1517 def obsolete(repo, subset, x):
1518 1518 """``obsolete()``
1519 1519 Mutable changeset with a newer version."""
1520 1520 # i18n: "obsolete" is a keyword
1521 1521 getargs(x, 0, 0, _("obsolete takes no arguments"))
1522 1522 obsoletes = obsmod.getrevs(repo, 'obsolete')
1523 1523 return subset & obsoletes
1524 1524
1525 1525 def only(repo, subset, x):
1526 1526 """``only(set, [set])``
1527 1527 Changesets that are ancestors of the first set that are not ancestors
1528 1528 of any other head in the repo. If a second set is specified, the result
1529 1529 is ancestors of the first set that are not ancestors of the second set
1530 1530 (i.e. ::<set1> - ::<set2>).
1531 1531 """
1532 1532 cl = repo.changelog
1533 1533 # i18n: "only" is a keyword
1534 1534 args = getargs(x, 1, 2, _('only takes one or two arguments'))
1535 1535 include = getset(repo, fullreposet(repo), args[0])
1536 1536 if len(args) == 1:
1537 1537 if not include:
1538 1538 return baseset()
1539 1539
1540 1540 descendants = set(_revdescendants(repo, include, False))
1541 1541 exclude = [rev for rev in cl.headrevs()
1542 1542 if not rev in descendants and not rev in include]
1543 1543 else:
1544 1544 exclude = getset(repo, fullreposet(repo), args[1])
1545 1545
1546 1546 results = set(cl.findmissingrevs(common=exclude, heads=include))
1547 1547 # XXX we should turn this into a baseset instead of a set, smartset may do
1548 1548 # some optimisations from the fact this is a baseset.
1549 1549 return subset & results
1550 1550
1551 1551 def origin(repo, subset, x):
1552 1552 """``origin([set])``
1553 1553 Changesets that were specified as a source for the grafts, transplants or
1554 1554 rebases that created the given revisions. Omitting the optional set is the
1555 1555 same as passing all(). If a changeset created by these operations is itself
1556 1556 specified as a source for one of these operations, only the source changeset
1557 1557 for the first operation is selected.
1558 1558 """
1559 1559 if x is not None:
1560 1560 dests = getset(repo, fullreposet(repo), x)
1561 1561 else:
1562 1562 dests = fullreposet(repo)
1563 1563
1564 1564 def _firstsrc(rev):
1565 1565 src = _getrevsource(repo, rev)
1566 1566 if src is None:
1567 1567 return None
1568 1568
1569 1569 while True:
1570 1570 prev = _getrevsource(repo, src)
1571 1571
1572 1572 if prev is None:
1573 1573 return src
1574 1574 src = prev
1575 1575
1576 1576 o = set([_firstsrc(r) for r in dests])
1577 1577 o -= set([None])
1578 1578 # XXX we should turn this into a baseset instead of a set, smartset may do
1579 1579 # some optimisations from the fact this is a baseset.
1580 1580 return subset & o
1581 1581
1582 1582 def outgoing(repo, subset, x):
1583 1583 """``outgoing([path])``
1584 1584 Changesets not found in the specified destination repository, or the
1585 1585 default push location.
1586 1586 """
1587 1587 # Avoid cycles.
1588 1588 from . import (
1589 1589 discovery,
1590 1590 hg,
1591 1591 )
1592 1592 # i18n: "outgoing" is a keyword
1593 1593 l = getargs(x, 0, 1, _("outgoing takes one or no arguments"))
1594 1594 # i18n: "outgoing" is a keyword
1595 1595 dest = l and getstring(l[0], _("outgoing requires a repository path")) or ''
1596 1596 dest = repo.ui.expandpath(dest or 'default-push', dest or 'default')
1597 1597 dest, branches = hg.parseurl(dest)
1598 1598 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1599 1599 if revs:
1600 1600 revs = [repo.lookup(rev) for rev in revs]
1601 1601 other = hg.peer(repo, {}, dest)
1602 1602 repo.ui.pushbuffer()
1603 1603 outgoing = discovery.findcommonoutgoing(repo, other, onlyheads=revs)
1604 1604 repo.ui.popbuffer()
1605 1605 cl = repo.changelog
1606 1606 o = set([cl.rev(r) for r in outgoing.missing])
1607 1607 return subset & o
1608 1608
1609 1609 def p1(repo, subset, x):
1610 1610 """``p1([set])``
1611 1611 First parent of changesets in set, or the working directory.
1612 1612 """
1613 1613 if x is None:
1614 1614 p = repo[x].p1().rev()
1615 1615 if p >= 0:
1616 1616 return subset & baseset([p])
1617 1617 return baseset()
1618 1618
1619 1619 ps = set()
1620 1620 cl = repo.changelog
1621 1621 for r in getset(repo, fullreposet(repo), x):
1622 1622 ps.add(cl.parentrevs(r)[0])
1623 1623 ps -= set([node.nullrev])
1624 1624 # XXX we should turn this into a baseset instead of a set, smartset may do
1625 1625 # some optimisations from the fact this is a baseset.
1626 1626 return subset & ps
1627 1627
1628 1628 def p2(repo, subset, x):
1629 1629 """``p2([set])``
1630 1630 Second parent of changesets in set, or the working directory.
1631 1631 """
1632 1632 if x is None:
1633 1633 ps = repo[x].parents()
1634 1634 try:
1635 1635 p = ps[1].rev()
1636 1636 if p >= 0:
1637 1637 return subset & baseset([p])
1638 1638 return baseset()
1639 1639 except IndexError:
1640 1640 return baseset()
1641 1641
1642 1642 ps = set()
1643 1643 cl = repo.changelog
1644 1644 for r in getset(repo, fullreposet(repo), x):
1645 1645 ps.add(cl.parentrevs(r)[1])
1646 1646 ps -= set([node.nullrev])
1647 1647 # XXX we should turn this into a baseset instead of a set, smartset may do
1648 1648 # some optimisations from the fact this is a baseset.
1649 1649 return subset & ps
1650 1650
1651 1651 def parents(repo, subset, x):
1652 1652 """``parents([set])``
1653 1653 The set of all parents for all changesets in set, or the working directory.
1654 1654 """
1655 1655 if x is None:
1656 1656 ps = set(p.rev() for p in repo[x].parents())
1657 1657 else:
1658 1658 ps = set()
1659 1659 cl = repo.changelog
1660 1660 up = ps.update
1661 1661 parentrevs = cl.parentrevs
1662 1662 for r in getset(repo, fullreposet(repo), x):
1663 1663 if r == node.wdirrev:
1664 1664 up(p.rev() for p in repo[r].parents())
1665 1665 else:
1666 1666 up(parentrevs(r))
1667 1667 ps -= set([node.nullrev])
1668 1668 return subset & ps
1669 1669
1670 1670 def _phase(repo, subset, target):
1671 1671 """helper to select all rev in phase <target>"""
1672 1672 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1673 1673 if repo._phasecache._phasesets:
1674 1674 s = repo._phasecache._phasesets[target] - repo.changelog.filteredrevs
1675 1675 s = baseset(s)
1676 1676 s.sort() # set are non ordered, so we enforce ascending
1677 1677 return subset & s
1678 1678 else:
1679 1679 phase = repo._phasecache.phase
1680 1680 condition = lambda r: phase(repo, r) == target
1681 1681 return subset.filter(condition, cache=False)
1682 1682
1683 1683 def draft(repo, subset, x):
1684 1684 """``draft()``
1685 1685 Changeset in draft phase."""
1686 1686 # i18n: "draft" is a keyword
1687 1687 getargs(x, 0, 0, _("draft takes no arguments"))
1688 1688 target = phases.draft
1689 1689 return _phase(repo, subset, target)
1690 1690
1691 1691 def secret(repo, subset, x):
1692 1692 """``secret()``
1693 1693 Changeset in secret phase."""
1694 1694 # i18n: "secret" is a keyword
1695 1695 getargs(x, 0, 0, _("secret takes no arguments"))
1696 1696 target = phases.secret
1697 1697 return _phase(repo, subset, target)
1698 1698
1699 1699 def parentspec(repo, subset, x, n):
1700 1700 """``set^0``
1701 1701 The set.
1702 1702 ``set^1`` (or ``set^``), ``set^2``
1703 1703 First or second parent, respectively, of all changesets in set.
1704 1704 """
1705 1705 try:
1706 1706 n = int(n[1])
1707 1707 if n not in (0, 1, 2):
1708 1708 raise ValueError
1709 1709 except (TypeError, ValueError):
1710 1710 raise error.ParseError(_("^ expects a number 0, 1, or 2"))
1711 1711 ps = set()
1712 1712 cl = repo.changelog
1713 1713 for r in getset(repo, fullreposet(repo), x):
1714 1714 if n == 0:
1715 1715 ps.add(r)
1716 1716 elif n == 1:
1717 1717 ps.add(cl.parentrevs(r)[0])
1718 1718 elif n == 2:
1719 1719 parents = cl.parentrevs(r)
1720 1720 if len(parents) > 1:
1721 1721 ps.add(parents[1])
1722 1722 return subset & ps
1723 1723
1724 1724 def present(repo, subset, x):
1725 1725 """``present(set)``
1726 1726 An empty set, if any revision in set isn't found; otherwise,
1727 1727 all revisions in set.
1728 1728
1729 1729 If any of specified revisions is not present in the local repository,
1730 1730 the query is normally aborted. But this predicate allows the query
1731 1731 to continue even in such cases.
1732 1732 """
1733 1733 try:
1734 1734 return getset(repo, subset, x)
1735 1735 except error.RepoLookupError:
1736 1736 return baseset()
1737 1737
1738 1738 # for internal use
1739 1739 def _notpublic(repo, subset, x):
1740 1740 getargs(x, 0, 0, "_notpublic takes no arguments")
1741 1741 repo._phasecache.loadphaserevs(repo) # ensure phase's sets are loaded
1742 1742 if repo._phasecache._phasesets:
1743 1743 s = set()
1744 1744 for u in repo._phasecache._phasesets[1:]:
1745 1745 s.update(u)
1746 1746 s = baseset(s - repo.changelog.filteredrevs)
1747 1747 s.sort()
1748 1748 return subset & s
1749 1749 else:
1750 1750 phase = repo._phasecache.phase
1751 1751 target = phases.public
1752 1752 condition = lambda r: phase(repo, r) != target
1753 1753 return subset.filter(condition, cache=False)
1754 1754
1755 1755 def public(repo, subset, x):
1756 1756 """``public()``
1757 1757 Changeset in public phase."""
1758 1758 # i18n: "public" is a keyword
1759 1759 getargs(x, 0, 0, _("public takes no arguments"))
1760 1760 phase = repo._phasecache.phase
1761 1761 target = phases.public
1762 1762 condition = lambda r: phase(repo, r) == target
1763 1763 return subset.filter(condition, cache=False)
1764 1764
1765 1765 def remote(repo, subset, x):
1766 1766 """``remote([id [,path]])``
1767 1767 Local revision that corresponds to the given identifier in a
1768 1768 remote repository, if present. Here, the '.' identifier is a
1769 1769 synonym for the current local branch.
1770 1770 """
1771 1771
1772 1772 from . import hg # avoid start-up nasties
1773 1773 # i18n: "remote" is a keyword
1774 1774 l = getargs(x, 0, 2, _("remote takes one, two or no arguments"))
1775 1775
1776 1776 q = '.'
1777 1777 if len(l) > 0:
1778 1778 # i18n: "remote" is a keyword
1779 1779 q = getstring(l[0], _("remote requires a string id"))
1780 1780 if q == '.':
1781 1781 q = repo['.'].branch()
1782 1782
1783 1783 dest = ''
1784 1784 if len(l) > 1:
1785 1785 # i18n: "remote" is a keyword
1786 1786 dest = getstring(l[1], _("remote requires a repository path"))
1787 1787 dest = repo.ui.expandpath(dest or 'default')
1788 1788 dest, branches = hg.parseurl(dest)
1789 1789 revs, checkout = hg.addbranchrevs(repo, repo, branches, [])
1790 1790 if revs:
1791 1791 revs = [repo.lookup(rev) for rev in revs]
1792 1792 other = hg.peer(repo, {}, dest)
1793 1793 n = other.lookup(q)
1794 1794 if n in repo:
1795 1795 r = repo[n].rev()
1796 1796 if r in subset:
1797 1797 return baseset([r])
1798 1798 return baseset()
1799 1799
1800 1800 def removes(repo, subset, x):
1801 1801 """``removes(pattern)``
1802 1802 Changesets which remove files matching pattern.
1803 1803
1804 1804 The pattern without explicit kind like ``glob:`` is expected to be
1805 1805 relative to the current directory and match against a file or a
1806 1806 directory.
1807 1807 """
1808 1808 # i18n: "removes" is a keyword
1809 1809 pat = getstring(x, _("removes requires a pattern"))
1810 1810 return checkstatus(repo, subset, pat, 2)
1811 1811
1812 1812 def rev(repo, subset, x):
1813 1813 """``rev(number)``
1814 1814 Revision with the given numeric identifier.
1815 1815 """
1816 1816 # i18n: "rev" is a keyword
1817 1817 l = getargs(x, 1, 1, _("rev requires one argument"))
1818 1818 try:
1819 1819 # i18n: "rev" is a keyword
1820 1820 l = int(getstring(l[0], _("rev requires a number")))
1821 1821 except (TypeError, ValueError):
1822 1822 # i18n: "rev" is a keyword
1823 1823 raise error.ParseError(_("rev expects a number"))
1824 1824 if l not in repo.changelog and l != node.nullrev:
1825 1825 return baseset()
1826 1826 return subset & baseset([l])
1827 1827
1828 1828 def matching(repo, subset, x):
1829 1829 """``matching(revision [, field])``
1830 1830 Changesets in which a given set of fields match the set of fields in the
1831 1831 selected revision or set.
1832 1832
1833 1833 To match more than one field pass the list of fields to match separated
1834 1834 by spaces (e.g. ``author description``).
1835 1835
1836 1836 Valid fields are most regular revision fields and some special fields.
1837 1837
1838 1838 Regular revision fields are ``description``, ``author``, ``branch``,
1839 1839 ``date``, ``files``, ``phase``, ``parents``, ``substate``, ``user``
1840 1840 and ``diff``.
1841 1841 Note that ``author`` and ``user`` are synonyms. ``diff`` refers to the
1842 1842 contents of the revision. Two revisions matching their ``diff`` will
1843 1843 also match their ``files``.
1844 1844
1845 1845 Special fields are ``summary`` and ``metadata``:
1846 1846 ``summary`` matches the first line of the description.
1847 1847 ``metadata`` is equivalent to matching ``description user date``
1848 1848 (i.e. it matches the main metadata fields).
1849 1849
1850 1850 ``metadata`` is the default field which is used when no fields are
1851 1851 specified. You can match more than one field at a time.
1852 1852 """
1853 1853 # i18n: "matching" is a keyword
1854 1854 l = getargs(x, 1, 2, _("matching takes 1 or 2 arguments"))
1855 1855
1856 1856 revs = getset(repo, fullreposet(repo), l[0])
1857 1857
1858 1858 fieldlist = ['metadata']
1859 1859 if len(l) > 1:
1860 1860 fieldlist = getstring(l[1],
1861 1861 # i18n: "matching" is a keyword
1862 1862 _("matching requires a string "
1863 1863 "as its second argument")).split()
1864 1864
1865 1865 # Make sure that there are no repeated fields,
1866 1866 # expand the 'special' 'metadata' field type
1867 1867 # and check the 'files' whenever we check the 'diff'
1868 1868 fields = []
1869 1869 for field in fieldlist:
1870 1870 if field == 'metadata':
1871 1871 fields += ['user', 'description', 'date']
1872 1872 elif field == 'diff':
1873 1873 # a revision matching the diff must also match the files
1874 1874 # since matching the diff is very costly, make sure to
1875 1875 # also match the files first
1876 1876 fields += ['files', 'diff']
1877 1877 else:
1878 1878 if field == 'author':
1879 1879 field = 'user'
1880 1880 fields.append(field)
1881 1881 fields = set(fields)
1882 1882 if 'summary' in fields and 'description' in fields:
1883 1883 # If a revision matches its description it also matches its summary
1884 1884 fields.discard('summary')
1885 1885
1886 1886 # We may want to match more than one field
1887 1887 # Not all fields take the same amount of time to be matched
1888 1888 # Sort the selected fields in order of increasing matching cost
1889 1889 fieldorder = ['phase', 'parents', 'user', 'date', 'branch', 'summary',
1890 1890 'files', 'description', 'substate', 'diff']
1891 1891 def fieldkeyfunc(f):
1892 1892 try:
1893 1893 return fieldorder.index(f)
1894 1894 except ValueError:
1895 1895 # assume an unknown field is very costly
1896 1896 return len(fieldorder)
1897 1897 fields = list(fields)
1898 1898 fields.sort(key=fieldkeyfunc)
1899 1899
1900 1900 # Each field will be matched with its own "getfield" function
1901 1901 # which will be added to the getfieldfuncs array of functions
1902 1902 getfieldfuncs = []
1903 1903 _funcs = {
1904 1904 'user': lambda r: repo[r].user(),
1905 1905 'branch': lambda r: repo[r].branch(),
1906 1906 'date': lambda r: repo[r].date(),
1907 1907 'description': lambda r: repo[r].description(),
1908 1908 'files': lambda r: repo[r].files(),
1909 1909 'parents': lambda r: repo[r].parents(),
1910 1910 'phase': lambda r: repo[r].phase(),
1911 1911 'substate': lambda r: repo[r].substate,
1912 1912 'summary': lambda r: repo[r].description().splitlines()[0],
1913 1913 'diff': lambda r: list(repo[r].diff(git=True),)
1914 1914 }
1915 1915 for info in fields:
1916 1916 getfield = _funcs.get(info, None)
1917 1917 if getfield is None:
1918 1918 raise error.ParseError(
1919 1919 # i18n: "matching" is a keyword
1920 1920 _("unexpected field name passed to matching: %s") % info)
1921 1921 getfieldfuncs.append(getfield)
1922 1922 # convert the getfield array of functions into a "getinfo" function
1923 1923 # which returns an array of field values (or a single value if there
1924 1924 # is only one field to match)
1925 1925 getinfo = lambda r: [f(r) for f in getfieldfuncs]
1926 1926
1927 1927 def matches(x):
1928 1928 for rev in revs:
1929 1929 target = getinfo(rev)
1930 1930 match = True
1931 1931 for n, f in enumerate(getfieldfuncs):
1932 1932 if target[n] != f(x):
1933 1933 match = False
1934 1934 if match:
1935 1935 return True
1936 1936 return False
1937 1937
1938 1938 return subset.filter(matches)
1939 1939
1940 1940 def reverse(repo, subset, x):
1941 1941 """``reverse(set)``
1942 1942 Reverse order of set.
1943 1943 """
1944 1944 l = getset(repo, subset, x)
1945 1945 l.reverse()
1946 1946 return l
1947 1947
1948 1948 def roots(repo, subset, x):
1949 1949 """``roots(set)``
1950 1950 Changesets in set with no parent changeset in set.
1951 1951 """
1952 1952 s = getset(repo, fullreposet(repo), x)
1953 1953 parents = repo.changelog.parentrevs
1954 1954 def filter(r):
1955 1955 for p in parents(r):
1956 1956 if 0 <= p and p in s:
1957 1957 return False
1958 1958 return True
1959 1959 return subset & s.filter(filter)
1960 1960
1961 1961 def sort(repo, subset, x):
1962 1962 """``sort(set[, [-]key...])``
1963 1963 Sort set by keys. The default sort order is ascending, specify a key
1964 1964 as ``-key`` to sort in descending order.
1965 1965
1966 1966 The keys can be:
1967 1967
1968 1968 - ``rev`` for the revision number,
1969 1969 - ``branch`` for the branch name,
1970 1970 - ``desc`` for the commit message (description),
1971 1971 - ``user`` for user name (``author`` can be used as an alias),
1972 1972 - ``date`` for the commit date
1973 1973 """
1974 1974 # i18n: "sort" is a keyword
1975 1975 l = getargs(x, 1, 2, _("sort requires one or two arguments"))
1976 1976 keys = "rev"
1977 1977 if len(l) == 2:
1978 1978 # i18n: "sort" is a keyword
1979 1979 keys = getstring(l[1], _("sort spec must be a string"))
1980 1980
1981 1981 s = l[0]
1982 1982 keys = keys.split()
1983 1983 l = []
1984 1984 def invert(s):
1985 1985 return "".join(chr(255 - ord(c)) for c in s)
1986 1986 revs = getset(repo, subset, s)
1987 1987 if keys == ["rev"]:
1988 1988 revs.sort()
1989 1989 return revs
1990 1990 elif keys == ["-rev"]:
1991 1991 revs.sort(reverse=True)
1992 1992 return revs
1993 1993 for r in revs:
1994 1994 c = repo[r]
1995 1995 e = []
1996 1996 for k in keys:
1997 1997 if k == 'rev':
1998 1998 e.append(r)
1999 1999 elif k == '-rev':
2000 2000 e.append(-r)
2001 2001 elif k == 'branch':
2002 2002 e.append(c.branch())
2003 2003 elif k == '-branch':
2004 2004 e.append(invert(c.branch()))
2005 2005 elif k == 'desc':
2006 2006 e.append(c.description())
2007 2007 elif k == '-desc':
2008 2008 e.append(invert(c.description()))
2009 2009 elif k in 'user author':
2010 2010 e.append(c.user())
2011 2011 elif k in '-user -author':
2012 2012 e.append(invert(c.user()))
2013 2013 elif k == 'date':
2014 2014 e.append(c.date()[0])
2015 2015 elif k == '-date':
2016 2016 e.append(-c.date()[0])
2017 2017 else:
2018 2018 raise error.ParseError(_("unknown sort key %r") % k)
2019 2019 e.append(r)
2020 2020 l.append(e)
2021 2021 l.sort()
2022 2022 return baseset([e[-1] for e in l])
2023 2023
2024 2024 def subrepo(repo, subset, x):
2025 2025 """``subrepo([pattern])``
2026 2026 Changesets that add, modify or remove the given subrepo. If no subrepo
2027 2027 pattern is named, any subrepo changes are returned.
2028 2028 """
2029 2029 # i18n: "subrepo" is a keyword
2030 2030 args = getargs(x, 0, 1, _('subrepo takes at most one argument'))
2031 2031 if len(args) != 0:
2032 2032 pat = getstring(args[0], _("subrepo requires a pattern"))
2033 2033
2034 2034 m = matchmod.exact(repo.root, repo.root, ['.hgsubstate'])
2035 2035
2036 2036 def submatches(names):
2037 k, p, m = _stringmatcher(pat)
2037 k, p, m = util.stringmatcher(pat)
2038 2038 for name in names:
2039 2039 if m(name):
2040 2040 yield name
2041 2041
2042 2042 def matches(x):
2043 2043 c = repo[x]
2044 2044 s = repo.status(c.p1().node(), c.node(), match=m)
2045 2045
2046 2046 if len(args) == 0:
2047 2047 return s.added or s.modified or s.removed
2048 2048
2049 2049 if s.added:
2050 2050 return any(submatches(c.substate.keys()))
2051 2051
2052 2052 if s.modified:
2053 2053 subs = set(c.p1().substate.keys())
2054 2054 subs.update(c.substate.keys())
2055 2055
2056 2056 for path in submatches(subs):
2057 2057 if c.p1().substate.get(path) != c.substate.get(path):
2058 2058 return True
2059 2059
2060 2060 if s.removed:
2061 2061 return any(submatches(c.p1().substate.keys()))
2062 2062
2063 2063 return False
2064 2064
2065 2065 return subset.filter(matches)
2066 2066
2067 def _stringmatcher(pattern):
2068 """
2069 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2070 returns the matcher name, pattern, and matcher function.
2071 missing or unknown prefixes are treated as literal matches.
2072
2073 helper for tests:
2074 >>> def test(pattern, *tests):
2075 ... kind, pattern, matcher = _stringmatcher(pattern)
2076 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2077
2078 exact matching (no prefix):
2079 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2080 ('literal', 'abcdefg', [False, False, True])
2081
2082 regex matching ('re:' prefix)
2083 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2084 ('re', 'a.+b', [False, False, True])
2085
2086 force exact matches ('literal:' prefix)
2087 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2088 ('literal', 're:foobar', [False, True])
2089
2090 unknown prefixes are ignored and treated as literals
2091 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2092 ('literal', 'foo:bar', [False, False, True])
2093 """
2094 if pattern.startswith('re:'):
2095 pattern = pattern[3:]
2096 try:
2097 regex = re.compile(pattern)
2098 except re.error as e:
2099 raise error.ParseError(_('invalid regular expression: %s')
2100 % e)
2101 return 're', pattern, regex.search
2102 elif pattern.startswith('literal:'):
2103 pattern = pattern[8:]
2104 return 'literal', pattern, pattern.__eq__
2105
2106 2067 def _substringmatcher(pattern):
2107 kind, pattern, matcher = _stringmatcher(pattern)
2068 kind, pattern, matcher = util.stringmatcher(pattern)
2108 2069 if kind == 'literal':
2109 2070 matcher = lambda s: pattern in s
2110 2071 return kind, pattern, matcher
2111 2072
2112 2073 def tag(repo, subset, x):
2113 2074 """``tag([name])``
2114 2075 The specified tag by name, or all tagged revisions if no name is given.
2115 2076
2116 2077 If `name` starts with `re:`, the remainder of the name is treated as
2117 2078 a regular expression. To match a tag that actually starts with `re:`,
2118 2079 use the prefix `literal:`.
2119 2080 """
2120 2081 # i18n: "tag" is a keyword
2121 2082 args = getargs(x, 0, 1, _("tag takes one or no arguments"))
2122 2083 cl = repo.changelog
2123 2084 if args:
2124 2085 pattern = getstring(args[0],
2125 2086 # i18n: "tag" is a keyword
2126 2087 _('the argument to tag must be a string'))
2127 kind, pattern, matcher = _stringmatcher(pattern)
2088 kind, pattern, matcher = util.stringmatcher(pattern)
2128 2089 if kind == 'literal':
2129 2090 # avoid resolving all tags
2130 2091 tn = repo._tagscache.tags.get(pattern, None)
2131 2092 if tn is None:
2132 2093 raise error.RepoLookupError(_("tag '%s' does not exist")
2133 2094 % pattern)
2134 2095 s = set([repo[tn].rev()])
2135 2096 else:
2136 2097 s = set([cl.rev(n) for t, n in repo.tagslist() if matcher(t)])
2137 2098 else:
2138 2099 s = set([cl.rev(n) for t, n in repo.tagslist() if t != 'tip'])
2139 2100 return subset & s
2140 2101
2141 2102 def tagged(repo, subset, x):
2142 2103 return tag(repo, subset, x)
2143 2104
2144 2105 def unstable(repo, subset, x):
2145 2106 """``unstable()``
2146 2107 Non-obsolete changesets with obsolete ancestors.
2147 2108 """
2148 2109 # i18n: "unstable" is a keyword
2149 2110 getargs(x, 0, 0, _("unstable takes no arguments"))
2150 2111 unstables = obsmod.getrevs(repo, 'unstable')
2151 2112 return subset & unstables
2152 2113
2153 2114
2154 2115 def user(repo, subset, x):
2155 2116 """``user(string)``
2156 2117 User name contains string. The match is case-insensitive.
2157 2118
2158 2119 If `string` starts with `re:`, the remainder of the string is treated as
2159 2120 a regular expression. To match a user that actually contains `re:`, use
2160 2121 the prefix `literal:`.
2161 2122 """
2162 2123 return author(repo, subset, x)
2163 2124
2164 2125 # experimental
2165 2126 def wdir(repo, subset, x):
2166 2127 # i18n: "wdir" is a keyword
2167 2128 getargs(x, 0, 0, _("wdir takes no arguments"))
2168 2129 if node.wdirrev in subset or isinstance(subset, fullreposet):
2169 2130 return baseset([node.wdirrev])
2170 2131 return baseset()
2171 2132
2172 2133 # for internal use
2173 2134 def _list(repo, subset, x):
2174 2135 s = getstring(x, "internal error")
2175 2136 if not s:
2176 2137 return baseset()
2177 2138 # remove duplicates here. it's difficult for caller to deduplicate sets
2178 2139 # because different symbols can point to the same rev.
2179 2140 cl = repo.changelog
2180 2141 ls = []
2181 2142 seen = set()
2182 2143 for t in s.split('\0'):
2183 2144 try:
2184 2145 # fast path for integer revision
2185 2146 r = int(t)
2186 2147 if str(r) != t or r not in cl:
2187 2148 raise ValueError
2188 2149 revs = [r]
2189 2150 except ValueError:
2190 2151 revs = stringset(repo, subset, t)
2191 2152
2192 2153 for r in revs:
2193 2154 if r in seen:
2194 2155 continue
2195 2156 if (r in subset
2196 2157 or r == node.nullrev and isinstance(subset, fullreposet)):
2197 2158 ls.append(r)
2198 2159 seen.add(r)
2199 2160 return baseset(ls)
2200 2161
2201 2162 # for internal use
2202 2163 def _intlist(repo, subset, x):
2203 2164 s = getstring(x, "internal error")
2204 2165 if not s:
2205 2166 return baseset()
2206 2167 ls = [int(r) for r in s.split('\0')]
2207 2168 s = subset
2208 2169 return baseset([r for r in ls if r in s])
2209 2170
2210 2171 # for internal use
2211 2172 def _hexlist(repo, subset, x):
2212 2173 s = getstring(x, "internal error")
2213 2174 if not s:
2214 2175 return baseset()
2215 2176 cl = repo.changelog
2216 2177 ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
2217 2178 s = subset
2218 2179 return baseset([r for r in ls if r in s])
2219 2180
2220 2181 symbols = {
2221 2182 "_mergedefaultdest": _mergedefaultdest,
2222 2183 "_updatedefaultdest": _updatedefaultdest,
2223 2184 "adds": adds,
2224 2185 "all": getall,
2225 2186 "ancestor": ancestor,
2226 2187 "ancestors": ancestors,
2227 2188 "_firstancestors": _firstancestors,
2228 2189 "author": author,
2229 2190 "bisect": bisect,
2230 2191 "bisected": bisected,
2231 2192 "bookmark": bookmark,
2232 2193 "branch": branch,
2233 2194 "branchpoint": branchpoint,
2234 2195 "bumped": bumped,
2235 2196 "bundle": bundle,
2236 2197 "children": children,
2237 2198 "closed": closed,
2238 2199 "contains": contains,
2239 2200 "converted": converted,
2240 2201 "date": date,
2241 2202 "desc": desc,
2242 2203 "descendants": descendants,
2243 2204 "_firstdescendants": _firstdescendants,
2244 2205 "destination": destination,
2245 2206 "divergent": divergent,
2246 2207 "draft": draft,
2247 2208 "extinct": extinct,
2248 2209 "extra": extra,
2249 2210 "file": hasfile,
2250 2211 "filelog": filelog,
2251 2212 "first": first,
2252 2213 "follow": follow,
2253 2214 "_followfirst": _followfirst,
2254 2215 "grep": grep,
2255 2216 "head": head,
2256 2217 "heads": heads,
2257 2218 "hidden": hidden,
2258 2219 "id": node_,
2259 2220 "keyword": keyword,
2260 2221 "last": last,
2261 2222 "limit": limit,
2262 2223 "_matchfiles": _matchfiles,
2263 2224 "max": maxrev,
2264 2225 "merge": merge,
2265 2226 "min": minrev,
2266 2227 "modifies": modifies,
2267 2228 "named": named,
2268 2229 "obsolete": obsolete,
2269 2230 "only": only,
2270 2231 "origin": origin,
2271 2232 "outgoing": outgoing,
2272 2233 "p1": p1,
2273 2234 "p2": p2,
2274 2235 "parents": parents,
2275 2236 "present": present,
2276 2237 "public": public,
2277 2238 "_notpublic": _notpublic,
2278 2239 "remote": remote,
2279 2240 "removes": removes,
2280 2241 "rev": rev,
2281 2242 "reverse": reverse,
2282 2243 "roots": roots,
2283 2244 "sort": sort,
2284 2245 "secret": secret,
2285 2246 "subrepo": subrepo,
2286 2247 "matching": matching,
2287 2248 "tag": tag,
2288 2249 "tagged": tagged,
2289 2250 "user": user,
2290 2251 "unstable": unstable,
2291 2252 "wdir": wdir,
2292 2253 "_list": _list,
2293 2254 "_intlist": _intlist,
2294 2255 "_hexlist": _hexlist,
2295 2256 }
2296 2257
2297 2258 # symbols which can't be used for a DoS attack for any given input
2298 2259 # (e.g. those which accept regexes as plain strings shouldn't be included)
2299 2260 # functions that just return a lot of changesets (like all) don't count here
2300 2261 safesymbols = set([
2301 2262 "adds",
2302 2263 "all",
2303 2264 "ancestor",
2304 2265 "ancestors",
2305 2266 "_firstancestors",
2306 2267 "author",
2307 2268 "bisect",
2308 2269 "bisected",
2309 2270 "bookmark",
2310 2271 "branch",
2311 2272 "branchpoint",
2312 2273 "bumped",
2313 2274 "bundle",
2314 2275 "children",
2315 2276 "closed",
2316 2277 "converted",
2317 2278 "date",
2318 2279 "desc",
2319 2280 "descendants",
2320 2281 "_firstdescendants",
2321 2282 "destination",
2322 2283 "divergent",
2323 2284 "draft",
2324 2285 "extinct",
2325 2286 "extra",
2326 2287 "file",
2327 2288 "filelog",
2328 2289 "first",
2329 2290 "follow",
2330 2291 "_followfirst",
2331 2292 "head",
2332 2293 "heads",
2333 2294 "hidden",
2334 2295 "id",
2335 2296 "keyword",
2336 2297 "last",
2337 2298 "limit",
2338 2299 "_matchfiles",
2339 2300 "max",
2340 2301 "merge",
2341 2302 "min",
2342 2303 "modifies",
2343 2304 "obsolete",
2344 2305 "only",
2345 2306 "origin",
2346 2307 "outgoing",
2347 2308 "p1",
2348 2309 "p2",
2349 2310 "parents",
2350 2311 "present",
2351 2312 "public",
2352 2313 "_notpublic",
2353 2314 "remote",
2354 2315 "removes",
2355 2316 "rev",
2356 2317 "reverse",
2357 2318 "roots",
2358 2319 "sort",
2359 2320 "secret",
2360 2321 "matching",
2361 2322 "tag",
2362 2323 "tagged",
2363 2324 "user",
2364 2325 "unstable",
2365 2326 "wdir",
2366 2327 "_list",
2367 2328 "_intlist",
2368 2329 "_hexlist",
2369 2330 ])
2370 2331
2371 2332 methods = {
2372 2333 "range": rangeset,
2373 2334 "dagrange": dagrange,
2374 2335 "string": stringset,
2375 2336 "symbol": stringset,
2376 2337 "and": andset,
2377 2338 "or": orset,
2378 2339 "not": notset,
2379 2340 "list": listset,
2380 2341 "keyvalue": keyvaluepair,
2381 2342 "func": func,
2382 2343 "ancestor": ancestorspec,
2383 2344 "parent": parentspec,
2384 2345 "parentpost": p1,
2385 2346 }
2386 2347
2387 2348 def optimize(x, small):
2388 2349 if x is None:
2389 2350 return 0, x
2390 2351
2391 2352 smallbonus = 1
2392 2353 if small:
2393 2354 smallbonus = .5
2394 2355
2395 2356 op = x[0]
2396 2357 if op == 'minus':
2397 2358 return optimize(('and', x[1], ('not', x[2])), small)
2398 2359 elif op == 'only':
2399 2360 return optimize(('func', ('symbol', 'only'),
2400 2361 ('list', x[1], x[2])), small)
2401 2362 elif op == 'onlypost':
2402 2363 return optimize(('func', ('symbol', 'only'), x[1]), small)
2403 2364 elif op == 'dagrangepre':
2404 2365 return optimize(('func', ('symbol', 'ancestors'), x[1]), small)
2405 2366 elif op == 'dagrangepost':
2406 2367 return optimize(('func', ('symbol', 'descendants'), x[1]), small)
2407 2368 elif op == 'rangeall':
2408 2369 return optimize(('range', ('string', '0'), ('string', 'tip')), small)
2409 2370 elif op == 'rangepre':
2410 2371 return optimize(('range', ('string', '0'), x[1]), small)
2411 2372 elif op == 'rangepost':
2412 2373 return optimize(('range', x[1], ('string', 'tip')), small)
2413 2374 elif op == 'negate':
2414 2375 return optimize(('string',
2415 2376 '-' + getstring(x[1], _("can't negate that"))), small)
2416 2377 elif op in 'string symbol negate':
2417 2378 return smallbonus, x # single revisions are small
2418 2379 elif op == 'and':
2419 2380 wa, ta = optimize(x[1], True)
2420 2381 wb, tb = optimize(x[2], True)
2421 2382
2422 2383 # (::x and not ::y)/(not ::y and ::x) have a fast path
2423 2384 def isonly(revs, bases):
2424 2385 return (
2425 2386 revs is not None
2426 2387 and revs[0] == 'func'
2427 2388 and getstring(revs[1], _('not a symbol')) == 'ancestors'
2428 2389 and bases is not None
2429 2390 and bases[0] == 'not'
2430 2391 and bases[1][0] == 'func'
2431 2392 and getstring(bases[1][1], _('not a symbol')) == 'ancestors')
2432 2393
2433 2394 w = min(wa, wb)
2434 2395 if isonly(ta, tb):
2435 2396 return w, ('func', ('symbol', 'only'), ('list', ta[2], tb[1][2]))
2436 2397 if isonly(tb, ta):
2437 2398 return w, ('func', ('symbol', 'only'), ('list', tb[2], ta[1][2]))
2438 2399
2439 2400 if wa > wb:
2440 2401 return w, (op, tb, ta)
2441 2402 return w, (op, ta, tb)
2442 2403 elif op == 'or':
2443 2404 # fast path for machine-generated expression, that is likely to have
2444 2405 # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
2445 2406 ws, ts, ss = [], [], []
2446 2407 def flushss():
2447 2408 if not ss:
2448 2409 return
2449 2410 if len(ss) == 1:
2450 2411 w, t = ss[0]
2451 2412 else:
2452 2413 s = '\0'.join(t[1] for w, t in ss)
2453 2414 y = ('func', ('symbol', '_list'), ('string', s))
2454 2415 w, t = optimize(y, False)
2455 2416 ws.append(w)
2456 2417 ts.append(t)
2457 2418 del ss[:]
2458 2419 for y in x[1:]:
2459 2420 w, t = optimize(y, False)
2460 2421 if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
2461 2422 ss.append((w, t))
2462 2423 continue
2463 2424 flushss()
2464 2425 ws.append(w)
2465 2426 ts.append(t)
2466 2427 flushss()
2467 2428 if len(ts) == 1:
2468 2429 return ws[0], ts[0] # 'or' operation is fully optimized out
2469 2430 # we can't reorder trees by weight because it would change the order.
2470 2431 # ("sort(a + b)" == "sort(b + a)", but "a + b" != "b + a")
2471 2432 # ts = tuple(t for w, t in sorted(zip(ws, ts), key=lambda wt: wt[0]))
2472 2433 return max(ws), (op,) + tuple(ts)
2473 2434 elif op == 'not':
2474 2435 # Optimize not public() to _notpublic() because we have a fast version
2475 2436 if x[1] == ('func', ('symbol', 'public'), None):
2476 2437 newsym = ('func', ('symbol', '_notpublic'), None)
2477 2438 o = optimize(newsym, not small)
2478 2439 return o[0], o[1]
2479 2440 else:
2480 2441 o = optimize(x[1], not small)
2481 2442 return o[0], (op, o[1])
2482 2443 elif op == 'parentpost':
2483 2444 o = optimize(x[1], small)
2484 2445 return o[0], (op, o[1])
2485 2446 elif op == 'group':
2486 2447 return optimize(x[1], small)
2487 2448 elif op in 'dagrange range list parent ancestorspec':
2488 2449 if op == 'parent':
2489 2450 # x^:y means (x^) : y, not x ^ (:y)
2490 2451 post = ('parentpost', x[1])
2491 2452 if x[2][0] == 'dagrangepre':
2492 2453 return optimize(('dagrange', post, x[2][1]), small)
2493 2454 elif x[2][0] == 'rangepre':
2494 2455 return optimize(('range', post, x[2][1]), small)
2495 2456
2496 2457 wa, ta = optimize(x[1], small)
2497 2458 wb, tb = optimize(x[2], small)
2498 2459 return wa + wb, (op, ta, tb)
2499 2460 elif op == 'func':
2500 2461 f = getstring(x[1], _("not a symbol"))
2501 2462 wa, ta = optimize(x[2], small)
2502 2463 if f in ("author branch closed date desc file grep keyword "
2503 2464 "outgoing user"):
2504 2465 w = 10 # slow
2505 2466 elif f in "modifies adds removes":
2506 2467 w = 30 # slower
2507 2468 elif f == "contains":
2508 2469 w = 100 # very slow
2509 2470 elif f == "ancestor":
2510 2471 w = 1 * smallbonus
2511 2472 elif f in "reverse limit first _intlist":
2512 2473 w = 0
2513 2474 elif f in "sort":
2514 2475 w = 10 # assume most sorts look at changelog
2515 2476 else:
2516 2477 w = 1
2517 2478 return w + wa, (op, x[1], ta)
2518 2479 return 1, x
2519 2480
2520 2481 _aliasarg = ('func', ('symbol', '_aliasarg'))
2521 2482 def _getaliasarg(tree):
2522 2483 """If tree matches ('func', ('symbol', '_aliasarg'), ('string', X))
2523 2484 return X, None otherwise.
2524 2485 """
2525 2486 if (len(tree) == 3 and tree[:2] == _aliasarg
2526 2487 and tree[2][0] == 'string'):
2527 2488 return tree[2][1]
2528 2489 return None
2529 2490
2530 2491 def _checkaliasarg(tree, known=None):
2531 2492 """Check tree contains no _aliasarg construct or only ones which
2532 2493 value is in known. Used to avoid alias placeholders injection.
2533 2494 """
2534 2495 if isinstance(tree, tuple):
2535 2496 arg = _getaliasarg(tree)
2536 2497 if arg is not None and (not known or arg not in known):
2537 2498 raise error.UnknownIdentifier('_aliasarg', [])
2538 2499 for t in tree:
2539 2500 _checkaliasarg(t, known)
2540 2501
2541 2502 # the set of valid characters for the initial letter of symbols in
2542 2503 # alias declarations and definitions
2543 2504 _aliassyminitletters = set(c for c in [chr(i) for i in xrange(256)]
2544 2505 if c.isalnum() or c in '._@$' or ord(c) > 127)
2545 2506
2546 2507 def _tokenizealias(program, lookup=None):
2547 2508 """Parse alias declaration/definition into a stream of tokens
2548 2509
2549 2510 This allows symbol names to use also ``$`` as an initial letter
2550 2511 (for backward compatibility), and callers of this function should
2551 2512 examine whether ``$`` is used also for unexpected symbols or not.
2552 2513 """
2553 2514 return tokenize(program, lookup=lookup,
2554 2515 syminitletters=_aliassyminitletters)
2555 2516
2556 2517 def _parsealiasdecl(decl):
2557 2518 """Parse alias declaration ``decl``
2558 2519
2559 2520 This returns ``(name, tree, args, errorstr)`` tuple:
2560 2521
2561 2522 - ``name``: of declared alias (may be ``decl`` itself at error)
2562 2523 - ``tree``: parse result (or ``None`` at error)
2563 2524 - ``args``: list of alias argument names (or None for symbol declaration)
2564 2525 - ``errorstr``: detail about detected error (or None)
2565 2526
2566 2527 >>> _parsealiasdecl('foo')
2567 2528 ('foo', ('symbol', 'foo'), None, None)
2568 2529 >>> _parsealiasdecl('$foo')
2569 2530 ('$foo', None, None, "'$' not for alias arguments")
2570 2531 >>> _parsealiasdecl('foo::bar')
2571 2532 ('foo::bar', None, None, 'invalid format')
2572 2533 >>> _parsealiasdecl('foo bar')
2573 2534 ('foo bar', None, None, 'at 4: invalid token')
2574 2535 >>> _parsealiasdecl('foo()')
2575 2536 ('foo', ('func', ('symbol', 'foo')), [], None)
2576 2537 >>> _parsealiasdecl('$foo()')
2577 2538 ('$foo()', None, None, "'$' not for alias arguments")
2578 2539 >>> _parsealiasdecl('foo($1, $2)')
2579 2540 ('foo', ('func', ('symbol', 'foo')), ['$1', '$2'], None)
2580 2541 >>> _parsealiasdecl('foo(bar_bar, baz.baz)')
2581 2542 ('foo', ('func', ('symbol', 'foo')), ['bar_bar', 'baz.baz'], None)
2582 2543 >>> _parsealiasdecl('foo($1, $2, nested($1, $2))')
2583 2544 ('foo($1, $2, nested($1, $2))', None, None, 'invalid argument list')
2584 2545 >>> _parsealiasdecl('foo(bar($1, $2))')
2585 2546 ('foo(bar($1, $2))', None, None, 'invalid argument list')
2586 2547 >>> _parsealiasdecl('foo("string")')
2587 2548 ('foo("string")', None, None, 'invalid argument list')
2588 2549 >>> _parsealiasdecl('foo($1, $2')
2589 2550 ('foo($1, $2', None, None, 'at 10: unexpected token: end')
2590 2551 >>> _parsealiasdecl('foo("string')
2591 2552 ('foo("string', None, None, 'at 5: unterminated string')
2592 2553 >>> _parsealiasdecl('foo($1, $2, $1)')
2593 2554 ('foo', None, None, 'argument names collide with each other')
2594 2555 """
2595 2556 p = parser.parser(elements)
2596 2557 try:
2597 2558 tree, pos = p.parse(_tokenizealias(decl))
2598 2559 if (pos != len(decl)):
2599 2560 raise error.ParseError(_('invalid token'), pos)
2600 2561
2601 2562 if isvalidsymbol(tree):
2602 2563 # "name = ...." style
2603 2564 name = getsymbol(tree)
2604 2565 if name.startswith('$'):
2605 2566 return (decl, None, None, _("'$' not for alias arguments"))
2606 2567 return (name, ('symbol', name), None, None)
2607 2568
2608 2569 if isvalidfunc(tree):
2609 2570 # "name(arg, ....) = ...." style
2610 2571 name = getfuncname(tree)
2611 2572 if name.startswith('$'):
2612 2573 return (decl, None, None, _("'$' not for alias arguments"))
2613 2574 args = []
2614 2575 for arg in getfuncargs(tree):
2615 2576 if not isvalidsymbol(arg):
2616 2577 return (decl, None, None, _("invalid argument list"))
2617 2578 args.append(getsymbol(arg))
2618 2579 if len(args) != len(set(args)):
2619 2580 return (name, None, None,
2620 2581 _("argument names collide with each other"))
2621 2582 return (name, ('func', ('symbol', name)), args, None)
2622 2583
2623 2584 return (decl, None, None, _("invalid format"))
2624 2585 except error.ParseError as inst:
2625 2586 return (decl, None, None, parseerrordetail(inst))
2626 2587
2627 2588 def _parsealiasdefn(defn, args):
2628 2589 """Parse alias definition ``defn``
2629 2590
2630 2591 This function also replaces alias argument references in the
2631 2592 specified definition by ``_aliasarg(ARGNAME)``.
2632 2593
2633 2594 ``args`` is a list of alias argument names, or None if the alias
2634 2595 is declared as a symbol.
2635 2596
2636 2597 This returns "tree" as parsing result.
2637 2598
2638 2599 >>> args = ['$1', '$2', 'foo']
2639 2600 >>> print prettyformat(_parsealiasdefn('$1 or foo', args))
2640 2601 (or
2641 2602 (func
2642 2603 ('symbol', '_aliasarg')
2643 2604 ('string', '$1'))
2644 2605 (func
2645 2606 ('symbol', '_aliasarg')
2646 2607 ('string', 'foo')))
2647 2608 >>> try:
2648 2609 ... _parsealiasdefn('$1 or $bar', args)
2649 2610 ... except error.ParseError, inst:
2650 2611 ... print parseerrordetail(inst)
2651 2612 at 6: '$' not for alias arguments
2652 2613 >>> args = ['$1', '$10', 'foo']
2653 2614 >>> print prettyformat(_parsealiasdefn('$10 or foobar', args))
2654 2615 (or
2655 2616 (func
2656 2617 ('symbol', '_aliasarg')
2657 2618 ('string', '$10'))
2658 2619 ('symbol', 'foobar'))
2659 2620 >>> print prettyformat(_parsealiasdefn('"$1" or "foo"', args))
2660 2621 (or
2661 2622 ('string', '$1')
2662 2623 ('string', 'foo'))
2663 2624 """
2664 2625 def tokenizedefn(program, lookup=None):
2665 2626 if args:
2666 2627 argset = set(args)
2667 2628 else:
2668 2629 argset = set()
2669 2630
2670 2631 for t, value, pos in _tokenizealias(program, lookup=lookup):
2671 2632 if t == 'symbol':
2672 2633 if value in argset:
2673 2634 # emulate tokenization of "_aliasarg('ARGNAME')":
2674 2635 # "_aliasarg()" is an unknown symbol only used separate
2675 2636 # alias argument placeholders from regular strings.
2676 2637 yield ('symbol', '_aliasarg', pos)
2677 2638 yield ('(', None, pos)
2678 2639 yield ('string', value, pos)
2679 2640 yield (')', None, pos)
2680 2641 continue
2681 2642 elif value.startswith('$'):
2682 2643 raise error.ParseError(_("'$' not for alias arguments"),
2683 2644 pos)
2684 2645 yield (t, value, pos)
2685 2646
2686 2647 p = parser.parser(elements)
2687 2648 tree, pos = p.parse(tokenizedefn(defn))
2688 2649 if pos != len(defn):
2689 2650 raise error.ParseError(_('invalid token'), pos)
2690 2651 return parser.simplifyinfixops(tree, ('or',))
2691 2652
2692 2653 class revsetalias(object):
2693 2654 # whether own `error` information is already shown or not.
2694 2655 # this avoids showing same warning multiple times at each `findaliases`.
2695 2656 warned = False
2696 2657
2697 2658 def __init__(self, name, value):
2698 2659 '''Aliases like:
2699 2660
2700 2661 h = heads(default)
2701 2662 b($1) = ancestors($1) - ancestors(default)
2702 2663 '''
2703 2664 self.name, self.tree, self.args, self.error = _parsealiasdecl(name)
2704 2665 if self.error:
2705 2666 self.error = _('failed to parse the declaration of revset alias'
2706 2667 ' "%s": %s') % (self.name, self.error)
2707 2668 return
2708 2669
2709 2670 try:
2710 2671 self.replacement = _parsealiasdefn(value, self.args)
2711 2672 # Check for placeholder injection
2712 2673 _checkaliasarg(self.replacement, self.args)
2713 2674 except error.ParseError as inst:
2714 2675 self.error = _('failed to parse the definition of revset alias'
2715 2676 ' "%s": %s') % (self.name, parseerrordetail(inst))
2716 2677
2717 2678 def _getalias(aliases, tree):
2718 2679 """If tree looks like an unexpanded alias, return it. Return None
2719 2680 otherwise.
2720 2681 """
2721 2682 if isinstance(tree, tuple) and tree:
2722 2683 if tree[0] == 'symbol' and len(tree) == 2:
2723 2684 name = tree[1]
2724 2685 alias = aliases.get(name)
2725 2686 if alias and alias.args is None and alias.tree == tree:
2726 2687 return alias
2727 2688 if tree[0] == 'func' and len(tree) > 1:
2728 2689 if tree[1][0] == 'symbol' and len(tree[1]) == 2:
2729 2690 name = tree[1][1]
2730 2691 alias = aliases.get(name)
2731 2692 if alias and alias.args is not None and alias.tree == tree[:2]:
2732 2693 return alias
2733 2694 return None
2734 2695
2735 2696 def _expandargs(tree, args):
2736 2697 """Replace _aliasarg instances with the substitution value of the
2737 2698 same name in args, recursively.
2738 2699 """
2739 2700 if not tree or not isinstance(tree, tuple):
2740 2701 return tree
2741 2702 arg = _getaliasarg(tree)
2742 2703 if arg is not None:
2743 2704 return args[arg]
2744 2705 return tuple(_expandargs(t, args) for t in tree)
2745 2706
2746 2707 def _expandaliases(aliases, tree, expanding, cache):
2747 2708 """Expand aliases in tree, recursively.
2748 2709
2749 2710 'aliases' is a dictionary mapping user defined aliases to
2750 2711 revsetalias objects.
2751 2712 """
2752 2713 if not isinstance(tree, tuple):
2753 2714 # Do not expand raw strings
2754 2715 return tree
2755 2716 alias = _getalias(aliases, tree)
2756 2717 if alias is not None:
2757 2718 if alias.error:
2758 2719 raise util.Abort(alias.error)
2759 2720 if alias in expanding:
2760 2721 raise error.ParseError(_('infinite expansion of revset alias "%s" '
2761 2722 'detected') % alias.name)
2762 2723 expanding.append(alias)
2763 2724 if alias.name not in cache:
2764 2725 cache[alias.name] = _expandaliases(aliases, alias.replacement,
2765 2726 expanding, cache)
2766 2727 result = cache[alias.name]
2767 2728 expanding.pop()
2768 2729 if alias.args is not None:
2769 2730 l = getlist(tree[2])
2770 2731 if len(l) != len(alias.args):
2771 2732 raise error.ParseError(
2772 2733 _('invalid number of arguments: %s') % len(l))
2773 2734 l = [_expandaliases(aliases, a, [], cache) for a in l]
2774 2735 result = _expandargs(result, dict(zip(alias.args, l)))
2775 2736 else:
2776 2737 result = tuple(_expandaliases(aliases, t, expanding, cache)
2777 2738 for t in tree)
2778 2739 return result
2779 2740
2780 2741 def findaliases(ui, tree, showwarning=None):
2781 2742 _checkaliasarg(tree)
2782 2743 aliases = {}
2783 2744 for k, v in ui.configitems('revsetalias'):
2784 2745 alias = revsetalias(k, v)
2785 2746 aliases[alias.name] = alias
2786 2747 tree = _expandaliases(aliases, tree, [], {})
2787 2748 if showwarning:
2788 2749 # warn about problematic (but not referred) aliases
2789 2750 for name, alias in sorted(aliases.iteritems()):
2790 2751 if alias.error and not alias.warned:
2791 2752 showwarning(_('warning: %s\n') % (alias.error))
2792 2753 alias.warned = True
2793 2754 return tree
2794 2755
2795 2756 def foldconcat(tree):
2796 2757 """Fold elements to be concatenated by `##`
2797 2758 """
2798 2759 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2799 2760 return tree
2800 2761 if tree[0] == '_concat':
2801 2762 pending = [tree]
2802 2763 l = []
2803 2764 while pending:
2804 2765 e = pending.pop()
2805 2766 if e[0] == '_concat':
2806 2767 pending.extend(reversed(e[1:]))
2807 2768 elif e[0] in ('string', 'symbol'):
2808 2769 l.append(e[1])
2809 2770 else:
2810 2771 msg = _("\"##\" can't concatenate \"%s\" element") % (e[0])
2811 2772 raise error.ParseError(msg)
2812 2773 return ('string', ''.join(l))
2813 2774 else:
2814 2775 return tuple(foldconcat(t) for t in tree)
2815 2776
2816 2777 def parse(spec, lookup=None):
2817 2778 p = parser.parser(elements)
2818 2779 tree, pos = p.parse(tokenize(spec, lookup=lookup))
2819 2780 if pos != len(spec):
2820 2781 raise error.ParseError(_("invalid token"), pos)
2821 2782 return parser.simplifyinfixops(tree, ('or',))
2822 2783
2823 2784 def posttreebuilthook(tree, repo):
2824 2785 # hook for extensions to execute code on the optimized tree
2825 2786 pass
2826 2787
2827 2788 def match(ui, spec, repo=None):
2828 2789 if not spec:
2829 2790 raise error.ParseError(_("empty query"))
2830 2791 lookup = None
2831 2792 if repo:
2832 2793 lookup = repo.__contains__
2833 2794 tree = parse(spec, lookup)
2834 2795 return _makematcher(ui, tree, repo)
2835 2796
2836 2797 def matchany(ui, specs, repo=None):
2837 2798 """Create a matcher that will include any revisions matching one of the
2838 2799 given specs"""
2839 2800 if not specs:
2840 2801 def mfunc(repo, subset=None):
2841 2802 return baseset()
2842 2803 return mfunc
2843 2804 if not all(specs):
2844 2805 raise error.ParseError(_("empty query"))
2845 2806 lookup = None
2846 2807 if repo:
2847 2808 lookup = repo.__contains__
2848 2809 if len(specs) == 1:
2849 2810 tree = parse(specs[0], lookup)
2850 2811 else:
2851 2812 tree = ('or',) + tuple(parse(s, lookup) for s in specs)
2852 2813 return _makematcher(ui, tree, repo)
2853 2814
2854 2815 def _makematcher(ui, tree, repo):
2855 2816 if ui:
2856 2817 tree = findaliases(ui, tree, showwarning=ui.warn)
2857 2818 tree = foldconcat(tree)
2858 2819 weight, tree = optimize(tree, True)
2859 2820 posttreebuilthook(tree, repo)
2860 2821 def mfunc(repo, subset=None):
2861 2822 if subset is None:
2862 2823 subset = fullreposet(repo)
2863 2824 if util.safehasattr(subset, 'isascending'):
2864 2825 result = getset(repo, subset, tree)
2865 2826 else:
2866 2827 result = getset(repo, baseset(subset), tree)
2867 2828 return result
2868 2829 return mfunc
2869 2830
2870 2831 def formatspec(expr, *args):
2871 2832 '''
2872 2833 This is a convenience function for using revsets internally, and
2873 2834 escapes arguments appropriately. Aliases are intentionally ignored
2874 2835 so that intended expression behavior isn't accidentally subverted.
2875 2836
2876 2837 Supported arguments:
2877 2838
2878 2839 %r = revset expression, parenthesized
2879 2840 %d = int(arg), no quoting
2880 2841 %s = string(arg), escaped and single-quoted
2881 2842 %b = arg.branch(), escaped and single-quoted
2882 2843 %n = hex(arg), single-quoted
2883 2844 %% = a literal '%'
2884 2845
2885 2846 Prefixing the type with 'l' specifies a parenthesized list of that type.
2886 2847
2887 2848 >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
2888 2849 '(10 or 11):: and ((this()) or (that()))'
2889 2850 >>> formatspec('%d:: and not %d::', 10, 20)
2890 2851 '10:: and not 20::'
2891 2852 >>> formatspec('%ld or %ld', [], [1])
2892 2853 "_list('') or 1"
2893 2854 >>> formatspec('keyword(%s)', 'foo\\xe9')
2894 2855 "keyword('foo\\\\xe9')"
2895 2856 >>> b = lambda: 'default'
2896 2857 >>> b.branch = b
2897 2858 >>> formatspec('branch(%b)', b)
2898 2859 "branch('default')"
2899 2860 >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
2900 2861 "root(_list('a\\x00b\\x00c\\x00d'))"
2901 2862 '''
2902 2863
2903 2864 def quote(s):
2904 2865 return repr(str(s))
2905 2866
2906 2867 def argtype(c, arg):
2907 2868 if c == 'd':
2908 2869 return str(int(arg))
2909 2870 elif c == 's':
2910 2871 return quote(arg)
2911 2872 elif c == 'r':
2912 2873 parse(arg) # make sure syntax errors are confined
2913 2874 return '(%s)' % arg
2914 2875 elif c == 'n':
2915 2876 return quote(node.hex(arg))
2916 2877 elif c == 'b':
2917 2878 return quote(arg.branch())
2918 2879
2919 2880 def listexp(s, t):
2920 2881 l = len(s)
2921 2882 if l == 0:
2922 2883 return "_list('')"
2923 2884 elif l == 1:
2924 2885 return argtype(t, s[0])
2925 2886 elif t == 'd':
2926 2887 return "_intlist('%s')" % "\0".join(str(int(a)) for a in s)
2927 2888 elif t == 's':
2928 2889 return "_list('%s')" % "\0".join(s)
2929 2890 elif t == 'n':
2930 2891 return "_hexlist('%s')" % "\0".join(node.hex(a) for a in s)
2931 2892 elif t == 'b':
2932 2893 return "_list('%s')" % "\0".join(a.branch() for a in s)
2933 2894
2934 2895 m = l // 2
2935 2896 return '(%s or %s)' % (listexp(s[:m], t), listexp(s[m:], t))
2936 2897
2937 2898 ret = ''
2938 2899 pos = 0
2939 2900 arg = 0
2940 2901 while pos < len(expr):
2941 2902 c = expr[pos]
2942 2903 if c == '%':
2943 2904 pos += 1
2944 2905 d = expr[pos]
2945 2906 if d == '%':
2946 2907 ret += d
2947 2908 elif d in 'dsnbr':
2948 2909 ret += argtype(d, args[arg])
2949 2910 arg += 1
2950 2911 elif d == 'l':
2951 2912 # a list of some type
2952 2913 pos += 1
2953 2914 d = expr[pos]
2954 2915 ret += listexp(list(args[arg]), d)
2955 2916 arg += 1
2956 2917 else:
2957 2918 raise util.Abort('unexpected revspec format character %s' % d)
2958 2919 else:
2959 2920 ret += c
2960 2921 pos += 1
2961 2922
2962 2923 return ret
2963 2924
2964 2925 def prettyformat(tree):
2965 2926 return parser.prettyformat(tree, ('string', 'symbol'))
2966 2927
2967 2928 def depth(tree):
2968 2929 if isinstance(tree, tuple):
2969 2930 return max(map(depth, tree)) + 1
2970 2931 else:
2971 2932 return 0
2972 2933
2973 2934 def funcsused(tree):
2974 2935 if not isinstance(tree, tuple) or tree[0] in ('string', 'symbol'):
2975 2936 return set()
2976 2937 else:
2977 2938 funcs = set()
2978 2939 for s in tree[1:]:
2979 2940 funcs |= funcsused(s)
2980 2941 if tree[0] == 'func':
2981 2942 funcs.add(tree[1][1])
2982 2943 return funcs
2983 2944
2984 2945 class abstractsmartset(object):
2985 2946
2986 2947 def __nonzero__(self):
2987 2948 """True if the smartset is not empty"""
2988 2949 raise NotImplementedError()
2989 2950
2990 2951 def __contains__(self, rev):
2991 2952 """provide fast membership testing"""
2992 2953 raise NotImplementedError()
2993 2954
2994 2955 def __iter__(self):
2995 2956 """iterate the set in the order it is supposed to be iterated"""
2996 2957 raise NotImplementedError()
2997 2958
2998 2959 # Attributes containing a function to perform a fast iteration in a given
2999 2960 # direction. A smartset can have none, one, or both defined.
3000 2961 #
3001 2962 # Default value is None instead of a function returning None to avoid
3002 2963 # initializing an iterator just for testing if a fast method exists.
3003 2964 fastasc = None
3004 2965 fastdesc = None
3005 2966
3006 2967 def isascending(self):
3007 2968 """True if the set will iterate in ascending order"""
3008 2969 raise NotImplementedError()
3009 2970
3010 2971 def isdescending(self):
3011 2972 """True if the set will iterate in descending order"""
3012 2973 raise NotImplementedError()
3013 2974
3014 2975 @util.cachefunc
3015 2976 def min(self):
3016 2977 """return the minimum element in the set"""
3017 2978 if self.fastasc is not None:
3018 2979 for r in self.fastasc():
3019 2980 return r
3020 2981 raise ValueError('arg is an empty sequence')
3021 2982 return min(self)
3022 2983
3023 2984 @util.cachefunc
3024 2985 def max(self):
3025 2986 """return the maximum element in the set"""
3026 2987 if self.fastdesc is not None:
3027 2988 for r in self.fastdesc():
3028 2989 return r
3029 2990 raise ValueError('arg is an empty sequence')
3030 2991 return max(self)
3031 2992
3032 2993 def first(self):
3033 2994 """return the first element in the set (user iteration perspective)
3034 2995
3035 2996 Return None if the set is empty"""
3036 2997 raise NotImplementedError()
3037 2998
3038 2999 def last(self):
3039 3000 """return the last element in the set (user iteration perspective)
3040 3001
3041 3002 Return None if the set is empty"""
3042 3003 raise NotImplementedError()
3043 3004
3044 3005 def __len__(self):
3045 3006 """return the length of the smartsets
3046 3007
3047 3008 This can be expensive on smartset that could be lazy otherwise."""
3048 3009 raise NotImplementedError()
3049 3010
3050 3011 def reverse(self):
3051 3012 """reverse the expected iteration order"""
3052 3013 raise NotImplementedError()
3053 3014
3054 3015 def sort(self, reverse=True):
3055 3016 """get the set to iterate in an ascending or descending order"""
3056 3017 raise NotImplementedError()
3057 3018
3058 3019 def __and__(self, other):
3059 3020 """Returns a new object with the intersection of the two collections.
3060 3021
3061 3022 This is part of the mandatory API for smartset."""
3062 3023 if isinstance(other, fullreposet):
3063 3024 return self
3064 3025 return self.filter(other.__contains__, cache=False)
3065 3026
3066 3027 def __add__(self, other):
3067 3028 """Returns a new object with the union of the two collections.
3068 3029
3069 3030 This is part of the mandatory API for smartset."""
3070 3031 return addset(self, other)
3071 3032
3072 3033 def __sub__(self, other):
3073 3034 """Returns a new object with the substraction of the two collections.
3074 3035
3075 3036 This is part of the mandatory API for smartset."""
3076 3037 c = other.__contains__
3077 3038 return self.filter(lambda r: not c(r), cache=False)
3078 3039
3079 3040 def filter(self, condition, cache=True):
3080 3041 """Returns this smartset filtered by condition as a new smartset.
3081 3042
3082 3043 `condition` is a callable which takes a revision number and returns a
3083 3044 boolean.
3084 3045
3085 3046 This is part of the mandatory API for smartset."""
3086 3047 # builtin cannot be cached. but do not needs to
3087 3048 if cache and util.safehasattr(condition, 'func_code'):
3088 3049 condition = util.cachefunc(condition)
3089 3050 return filteredset(self, condition)
3090 3051
3091 3052 class baseset(abstractsmartset):
3092 3053 """Basic data structure that represents a revset and contains the basic
3093 3054 operation that it should be able to perform.
3094 3055
3095 3056 Every method in this class should be implemented by any smartset class.
3096 3057 """
3097 3058 def __init__(self, data=()):
3098 3059 if not isinstance(data, list):
3099 3060 if isinstance(data, set):
3100 3061 self._set = data
3101 3062 data = list(data)
3102 3063 self._list = data
3103 3064 self._ascending = None
3104 3065
3105 3066 @util.propertycache
3106 3067 def _set(self):
3107 3068 return set(self._list)
3108 3069
3109 3070 @util.propertycache
3110 3071 def _asclist(self):
3111 3072 asclist = self._list[:]
3112 3073 asclist.sort()
3113 3074 return asclist
3114 3075
3115 3076 def __iter__(self):
3116 3077 if self._ascending is None:
3117 3078 return iter(self._list)
3118 3079 elif self._ascending:
3119 3080 return iter(self._asclist)
3120 3081 else:
3121 3082 return reversed(self._asclist)
3122 3083
3123 3084 def fastasc(self):
3124 3085 return iter(self._asclist)
3125 3086
3126 3087 def fastdesc(self):
3127 3088 return reversed(self._asclist)
3128 3089
3129 3090 @util.propertycache
3130 3091 def __contains__(self):
3131 3092 return self._set.__contains__
3132 3093
3133 3094 def __nonzero__(self):
3134 3095 return bool(self._list)
3135 3096
3136 3097 def sort(self, reverse=False):
3137 3098 self._ascending = not bool(reverse)
3138 3099
3139 3100 def reverse(self):
3140 3101 if self._ascending is None:
3141 3102 self._list.reverse()
3142 3103 else:
3143 3104 self._ascending = not self._ascending
3144 3105
3145 3106 def __len__(self):
3146 3107 return len(self._list)
3147 3108
3148 3109 def isascending(self):
3149 3110 """Returns True if the collection is ascending order, False if not.
3150 3111
3151 3112 This is part of the mandatory API for smartset."""
3152 3113 if len(self) <= 1:
3153 3114 return True
3154 3115 return self._ascending is not None and self._ascending
3155 3116
3156 3117 def isdescending(self):
3157 3118 """Returns True if the collection is descending order, False if not.
3158 3119
3159 3120 This is part of the mandatory API for smartset."""
3160 3121 if len(self) <= 1:
3161 3122 return True
3162 3123 return self._ascending is not None and not self._ascending
3163 3124
3164 3125 def first(self):
3165 3126 if self:
3166 3127 if self._ascending is None:
3167 3128 return self._list[0]
3168 3129 elif self._ascending:
3169 3130 return self._asclist[0]
3170 3131 else:
3171 3132 return self._asclist[-1]
3172 3133 return None
3173 3134
3174 3135 def last(self):
3175 3136 if self:
3176 3137 if self._ascending is None:
3177 3138 return self._list[-1]
3178 3139 elif self._ascending:
3179 3140 return self._asclist[-1]
3180 3141 else:
3181 3142 return self._asclist[0]
3182 3143 return None
3183 3144
3184 3145 def __repr__(self):
3185 3146 d = {None: '', False: '-', True: '+'}[self._ascending]
3186 3147 return '<%s%s %r>' % (type(self).__name__, d, self._list)
3187 3148
3188 3149 class filteredset(abstractsmartset):
3189 3150 """Duck type for baseset class which iterates lazily over the revisions in
3190 3151 the subset and contains a function which tests for membership in the
3191 3152 revset
3192 3153 """
3193 3154 def __init__(self, subset, condition=lambda x: True):
3194 3155 """
3195 3156 condition: a function that decide whether a revision in the subset
3196 3157 belongs to the revset or not.
3197 3158 """
3198 3159 self._subset = subset
3199 3160 self._condition = condition
3200 3161
3201 3162 def __contains__(self, x):
3202 3163 return x in self._subset and self._condition(x)
3203 3164
3204 3165 def __iter__(self):
3205 3166 return self._iterfilter(self._subset)
3206 3167
3207 3168 def _iterfilter(self, it):
3208 3169 cond = self._condition
3209 3170 for x in it:
3210 3171 if cond(x):
3211 3172 yield x
3212 3173
3213 3174 @property
3214 3175 def fastasc(self):
3215 3176 it = self._subset.fastasc
3216 3177 if it is None:
3217 3178 return None
3218 3179 return lambda: self._iterfilter(it())
3219 3180
3220 3181 @property
3221 3182 def fastdesc(self):
3222 3183 it = self._subset.fastdesc
3223 3184 if it is None:
3224 3185 return None
3225 3186 return lambda: self._iterfilter(it())
3226 3187
3227 3188 def __nonzero__(self):
3228 3189 fast = self.fastasc
3229 3190 if fast is None:
3230 3191 fast = self.fastdesc
3231 3192 if fast is not None:
3232 3193 it = fast()
3233 3194 else:
3234 3195 it = self
3235 3196
3236 3197 for r in it:
3237 3198 return True
3238 3199 return False
3239 3200
3240 3201 def __len__(self):
3241 3202 # Basic implementation to be changed in future patches.
3242 3203 l = baseset([r for r in self])
3243 3204 return len(l)
3244 3205
3245 3206 def sort(self, reverse=False):
3246 3207 self._subset.sort(reverse=reverse)
3247 3208
3248 3209 def reverse(self):
3249 3210 self._subset.reverse()
3250 3211
3251 3212 def isascending(self):
3252 3213 return self._subset.isascending()
3253 3214
3254 3215 def isdescending(self):
3255 3216 return self._subset.isdescending()
3256 3217
3257 3218 def first(self):
3258 3219 for x in self:
3259 3220 return x
3260 3221 return None
3261 3222
3262 3223 def last(self):
3263 3224 it = None
3264 3225 if self.isascending():
3265 3226 it = self.fastdesc
3266 3227 elif self.isdescending():
3267 3228 it = self.fastasc
3268 3229 if it is not None:
3269 3230 for x in it():
3270 3231 return x
3271 3232 return None #empty case
3272 3233 else:
3273 3234 x = None
3274 3235 for x in self:
3275 3236 pass
3276 3237 return x
3277 3238
3278 3239 def __repr__(self):
3279 3240 return '<%s %r>' % (type(self).__name__, self._subset)
3280 3241
3281 3242 def _iterordered(ascending, iter1, iter2):
3282 3243 """produce an ordered iteration from two iterators with the same order
3283 3244
3284 3245 The ascending is used to indicated the iteration direction.
3285 3246 """
3286 3247 choice = max
3287 3248 if ascending:
3288 3249 choice = min
3289 3250
3290 3251 val1 = None
3291 3252 val2 = None
3292 3253 try:
3293 3254 # Consume both iterators in an ordered way until one is empty
3294 3255 while True:
3295 3256 if val1 is None:
3296 3257 val1 = iter1.next()
3297 3258 if val2 is None:
3298 3259 val2 = iter2.next()
3299 3260 next = choice(val1, val2)
3300 3261 yield next
3301 3262 if val1 == next:
3302 3263 val1 = None
3303 3264 if val2 == next:
3304 3265 val2 = None
3305 3266 except StopIteration:
3306 3267 # Flush any remaining values and consume the other one
3307 3268 it = iter2
3308 3269 if val1 is not None:
3309 3270 yield val1
3310 3271 it = iter1
3311 3272 elif val2 is not None:
3312 3273 # might have been equality and both are empty
3313 3274 yield val2
3314 3275 for val in it:
3315 3276 yield val
3316 3277
3317 3278 class addset(abstractsmartset):
3318 3279 """Represent the addition of two sets
3319 3280
3320 3281 Wrapper structure for lazily adding two structures without losing much
3321 3282 performance on the __contains__ method
3322 3283
3323 3284 If the ascending attribute is set, that means the two structures are
3324 3285 ordered in either an ascending or descending way. Therefore, we can add
3325 3286 them maintaining the order by iterating over both at the same time
3326 3287
3327 3288 >>> xs = baseset([0, 3, 2])
3328 3289 >>> ys = baseset([5, 2, 4])
3329 3290
3330 3291 >>> rs = addset(xs, ys)
3331 3292 >>> bool(rs), 0 in rs, 1 in rs, 5 in rs, rs.first(), rs.last()
3332 3293 (True, True, False, True, 0, 4)
3333 3294 >>> rs = addset(xs, baseset([]))
3334 3295 >>> bool(rs), 0 in rs, 1 in rs, rs.first(), rs.last()
3335 3296 (True, True, False, 0, 2)
3336 3297 >>> rs = addset(baseset([]), baseset([]))
3337 3298 >>> bool(rs), 0 in rs, rs.first(), rs.last()
3338 3299 (False, False, None, None)
3339 3300
3340 3301 iterate unsorted:
3341 3302 >>> rs = addset(xs, ys)
3342 3303 >>> [x for x in rs] # without _genlist
3343 3304 [0, 3, 2, 5, 4]
3344 3305 >>> assert not rs._genlist
3345 3306 >>> len(rs)
3346 3307 5
3347 3308 >>> [x for x in rs] # with _genlist
3348 3309 [0, 3, 2, 5, 4]
3349 3310 >>> assert rs._genlist
3350 3311
3351 3312 iterate ascending:
3352 3313 >>> rs = addset(xs, ys, ascending=True)
3353 3314 >>> [x for x in rs], [x for x in rs.fastasc()] # without _asclist
3354 3315 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3355 3316 >>> assert not rs._asclist
3356 3317 >>> len(rs)
3357 3318 5
3358 3319 >>> [x for x in rs], [x for x in rs.fastasc()]
3359 3320 ([0, 2, 3, 4, 5], [0, 2, 3, 4, 5])
3360 3321 >>> assert rs._asclist
3361 3322
3362 3323 iterate descending:
3363 3324 >>> rs = addset(xs, ys, ascending=False)
3364 3325 >>> [x for x in rs], [x for x in rs.fastdesc()] # without _asclist
3365 3326 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3366 3327 >>> assert not rs._asclist
3367 3328 >>> len(rs)
3368 3329 5
3369 3330 >>> [x for x in rs], [x for x in rs.fastdesc()]
3370 3331 ([5, 4, 3, 2, 0], [5, 4, 3, 2, 0])
3371 3332 >>> assert rs._asclist
3372 3333
3373 3334 iterate ascending without fastasc:
3374 3335 >>> rs = addset(xs, generatorset(ys), ascending=True)
3375 3336 >>> assert rs.fastasc is None
3376 3337 >>> [x for x in rs]
3377 3338 [0, 2, 3, 4, 5]
3378 3339
3379 3340 iterate descending without fastdesc:
3380 3341 >>> rs = addset(generatorset(xs), ys, ascending=False)
3381 3342 >>> assert rs.fastdesc is None
3382 3343 >>> [x for x in rs]
3383 3344 [5, 4, 3, 2, 0]
3384 3345 """
3385 3346 def __init__(self, revs1, revs2, ascending=None):
3386 3347 self._r1 = revs1
3387 3348 self._r2 = revs2
3388 3349 self._iter = None
3389 3350 self._ascending = ascending
3390 3351 self._genlist = None
3391 3352 self._asclist = None
3392 3353
3393 3354 def __len__(self):
3394 3355 return len(self._list)
3395 3356
3396 3357 def __nonzero__(self):
3397 3358 return bool(self._r1) or bool(self._r2)
3398 3359
3399 3360 @util.propertycache
3400 3361 def _list(self):
3401 3362 if not self._genlist:
3402 3363 self._genlist = baseset(iter(self))
3403 3364 return self._genlist
3404 3365
3405 3366 def __iter__(self):
3406 3367 """Iterate over both collections without repeating elements
3407 3368
3408 3369 If the ascending attribute is not set, iterate over the first one and
3409 3370 then over the second one checking for membership on the first one so we
3410 3371 dont yield any duplicates.
3411 3372
3412 3373 If the ascending attribute is set, iterate over both collections at the
3413 3374 same time, yielding only one value at a time in the given order.
3414 3375 """
3415 3376 if self._ascending is None:
3416 3377 if self._genlist:
3417 3378 return iter(self._genlist)
3418 3379 def arbitraryordergen():
3419 3380 for r in self._r1:
3420 3381 yield r
3421 3382 inr1 = self._r1.__contains__
3422 3383 for r in self._r2:
3423 3384 if not inr1(r):
3424 3385 yield r
3425 3386 return arbitraryordergen()
3426 3387 # try to use our own fast iterator if it exists
3427 3388 self._trysetasclist()
3428 3389 if self._ascending:
3429 3390 attr = 'fastasc'
3430 3391 else:
3431 3392 attr = 'fastdesc'
3432 3393 it = getattr(self, attr)
3433 3394 if it is not None:
3434 3395 return it()
3435 3396 # maybe half of the component supports fast
3436 3397 # get iterator for _r1
3437 3398 iter1 = getattr(self._r1, attr)
3438 3399 if iter1 is None:
3439 3400 # let's avoid side effect (not sure it matters)
3440 3401 iter1 = iter(sorted(self._r1, reverse=not self._ascending))
3441 3402 else:
3442 3403 iter1 = iter1()
3443 3404 # get iterator for _r2
3444 3405 iter2 = getattr(self._r2, attr)
3445 3406 if iter2 is None:
3446 3407 # let's avoid side effect (not sure it matters)
3447 3408 iter2 = iter(sorted(self._r2, reverse=not self._ascending))
3448 3409 else:
3449 3410 iter2 = iter2()
3450 3411 return _iterordered(self._ascending, iter1, iter2)
3451 3412
3452 3413 def _trysetasclist(self):
3453 3414 """populate the _asclist attribute if possible and necessary"""
3454 3415 if self._genlist is not None and self._asclist is None:
3455 3416 self._asclist = sorted(self._genlist)
3456 3417
3457 3418 @property
3458 3419 def fastasc(self):
3459 3420 self._trysetasclist()
3460 3421 if self._asclist is not None:
3461 3422 return self._asclist.__iter__
3462 3423 iter1 = self._r1.fastasc
3463 3424 iter2 = self._r2.fastasc
3464 3425 if None in (iter1, iter2):
3465 3426 return None
3466 3427 return lambda: _iterordered(True, iter1(), iter2())
3467 3428
3468 3429 @property
3469 3430 def fastdesc(self):
3470 3431 self._trysetasclist()
3471 3432 if self._asclist is not None:
3472 3433 return self._asclist.__reversed__
3473 3434 iter1 = self._r1.fastdesc
3474 3435 iter2 = self._r2.fastdesc
3475 3436 if None in (iter1, iter2):
3476 3437 return None
3477 3438 return lambda: _iterordered(False, iter1(), iter2())
3478 3439
3479 3440 def __contains__(self, x):
3480 3441 return x in self._r1 or x in self._r2
3481 3442
3482 3443 def sort(self, reverse=False):
3483 3444 """Sort the added set
3484 3445
3485 3446 For this we use the cached list with all the generated values and if we
3486 3447 know they are ascending or descending we can sort them in a smart way.
3487 3448 """
3488 3449 self._ascending = not reverse
3489 3450
3490 3451 def isascending(self):
3491 3452 return self._ascending is not None and self._ascending
3492 3453
3493 3454 def isdescending(self):
3494 3455 return self._ascending is not None and not self._ascending
3495 3456
3496 3457 def reverse(self):
3497 3458 if self._ascending is None:
3498 3459 self._list.reverse()
3499 3460 else:
3500 3461 self._ascending = not self._ascending
3501 3462
3502 3463 def first(self):
3503 3464 for x in self:
3504 3465 return x
3505 3466 return None
3506 3467
3507 3468 def last(self):
3508 3469 self.reverse()
3509 3470 val = self.first()
3510 3471 self.reverse()
3511 3472 return val
3512 3473
3513 3474 def __repr__(self):
3514 3475 d = {None: '', False: '-', True: '+'}[self._ascending]
3515 3476 return '<%s%s %r, %r>' % (type(self).__name__, d, self._r1, self._r2)
3516 3477
3517 3478 class generatorset(abstractsmartset):
3518 3479 """Wrap a generator for lazy iteration
3519 3480
3520 3481 Wrapper structure for generators that provides lazy membership and can
3521 3482 be iterated more than once.
3522 3483 When asked for membership it generates values until either it finds the
3523 3484 requested one or has gone through all the elements in the generator
3524 3485 """
3525 3486 def __init__(self, gen, iterasc=None):
3526 3487 """
3527 3488 gen: a generator producing the values for the generatorset.
3528 3489 """
3529 3490 self._gen = gen
3530 3491 self._asclist = None
3531 3492 self._cache = {}
3532 3493 self._genlist = []
3533 3494 self._finished = False
3534 3495 self._ascending = True
3535 3496 if iterasc is not None:
3536 3497 if iterasc:
3537 3498 self.fastasc = self._iterator
3538 3499 self.__contains__ = self._asccontains
3539 3500 else:
3540 3501 self.fastdesc = self._iterator
3541 3502 self.__contains__ = self._desccontains
3542 3503
3543 3504 def __nonzero__(self):
3544 3505 # Do not use 'for r in self' because it will enforce the iteration
3545 3506 # order (default ascending), possibly unrolling a whole descending
3546 3507 # iterator.
3547 3508 if self._genlist:
3548 3509 return True
3549 3510 for r in self._consumegen():
3550 3511 return True
3551 3512 return False
3552 3513
3553 3514 def __contains__(self, x):
3554 3515 if x in self._cache:
3555 3516 return self._cache[x]
3556 3517
3557 3518 # Use new values only, as existing values would be cached.
3558 3519 for l in self._consumegen():
3559 3520 if l == x:
3560 3521 return True
3561 3522
3562 3523 self._cache[x] = False
3563 3524 return False
3564 3525
3565 3526 def _asccontains(self, x):
3566 3527 """version of contains optimised for ascending generator"""
3567 3528 if x in self._cache:
3568 3529 return self._cache[x]
3569 3530
3570 3531 # Use new values only, as existing values would be cached.
3571 3532 for l in self._consumegen():
3572 3533 if l == x:
3573 3534 return True
3574 3535 if l > x:
3575 3536 break
3576 3537
3577 3538 self._cache[x] = False
3578 3539 return False
3579 3540
3580 3541 def _desccontains(self, x):
3581 3542 """version of contains optimised for descending generator"""
3582 3543 if x in self._cache:
3583 3544 return self._cache[x]
3584 3545
3585 3546 # Use new values only, as existing values would be cached.
3586 3547 for l in self._consumegen():
3587 3548 if l == x:
3588 3549 return True
3589 3550 if l < x:
3590 3551 break
3591 3552
3592 3553 self._cache[x] = False
3593 3554 return False
3594 3555
3595 3556 def __iter__(self):
3596 3557 if self._ascending:
3597 3558 it = self.fastasc
3598 3559 else:
3599 3560 it = self.fastdesc
3600 3561 if it is not None:
3601 3562 return it()
3602 3563 # we need to consume the iterator
3603 3564 for x in self._consumegen():
3604 3565 pass
3605 3566 # recall the same code
3606 3567 return iter(self)
3607 3568
3608 3569 def _iterator(self):
3609 3570 if self._finished:
3610 3571 return iter(self._genlist)
3611 3572
3612 3573 # We have to use this complex iteration strategy to allow multiple
3613 3574 # iterations at the same time. We need to be able to catch revision
3614 3575 # removed from _consumegen and added to genlist in another instance.
3615 3576 #
3616 3577 # Getting rid of it would provide an about 15% speed up on this
3617 3578 # iteration.
3618 3579 genlist = self._genlist
3619 3580 nextrev = self._consumegen().next
3620 3581 _len = len # cache global lookup
3621 3582 def gen():
3622 3583 i = 0
3623 3584 while True:
3624 3585 if i < _len(genlist):
3625 3586 yield genlist[i]
3626 3587 else:
3627 3588 yield nextrev()
3628 3589 i += 1
3629 3590 return gen()
3630 3591
3631 3592 def _consumegen(self):
3632 3593 cache = self._cache
3633 3594 genlist = self._genlist.append
3634 3595 for item in self._gen:
3635 3596 cache[item] = True
3636 3597 genlist(item)
3637 3598 yield item
3638 3599 if not self._finished:
3639 3600 self._finished = True
3640 3601 asc = self._genlist[:]
3641 3602 asc.sort()
3642 3603 self._asclist = asc
3643 3604 self.fastasc = asc.__iter__
3644 3605 self.fastdesc = asc.__reversed__
3645 3606
3646 3607 def __len__(self):
3647 3608 for x in self._consumegen():
3648 3609 pass
3649 3610 return len(self._genlist)
3650 3611
3651 3612 def sort(self, reverse=False):
3652 3613 self._ascending = not reverse
3653 3614
3654 3615 def reverse(self):
3655 3616 self._ascending = not self._ascending
3656 3617
3657 3618 def isascending(self):
3658 3619 return self._ascending
3659 3620
3660 3621 def isdescending(self):
3661 3622 return not self._ascending
3662 3623
3663 3624 def first(self):
3664 3625 if self._ascending:
3665 3626 it = self.fastasc
3666 3627 else:
3667 3628 it = self.fastdesc
3668 3629 if it is None:
3669 3630 # we need to consume all and try again
3670 3631 for x in self._consumegen():
3671 3632 pass
3672 3633 return self.first()
3673 3634 return next(it(), None)
3674 3635
3675 3636 def last(self):
3676 3637 if self._ascending:
3677 3638 it = self.fastdesc
3678 3639 else:
3679 3640 it = self.fastasc
3680 3641 if it is None:
3681 3642 # we need to consume all and try again
3682 3643 for x in self._consumegen():
3683 3644 pass
3684 3645 return self.first()
3685 3646 return next(it(), None)
3686 3647
3687 3648 def __repr__(self):
3688 3649 d = {False: '-', True: '+'}[self._ascending]
3689 3650 return '<%s%s>' % (type(self).__name__, d)
3690 3651
3691 3652 class spanset(abstractsmartset):
3692 3653 """Duck type for baseset class which represents a range of revisions and
3693 3654 can work lazily and without having all the range in memory
3694 3655
3695 3656 Note that spanset(x, y) behave almost like xrange(x, y) except for two
3696 3657 notable points:
3697 3658 - when x < y it will be automatically descending,
3698 3659 - revision filtered with this repoview will be skipped.
3699 3660
3700 3661 """
3701 3662 def __init__(self, repo, start=0, end=None):
3702 3663 """
3703 3664 start: first revision included the set
3704 3665 (default to 0)
3705 3666 end: first revision excluded (last+1)
3706 3667 (default to len(repo)
3707 3668
3708 3669 Spanset will be descending if `end` < `start`.
3709 3670 """
3710 3671 if end is None:
3711 3672 end = len(repo)
3712 3673 self._ascending = start <= end
3713 3674 if not self._ascending:
3714 3675 start, end = end + 1, start +1
3715 3676 self._start = start
3716 3677 self._end = end
3717 3678 self._hiddenrevs = repo.changelog.filteredrevs
3718 3679
3719 3680 def sort(self, reverse=False):
3720 3681 self._ascending = not reverse
3721 3682
3722 3683 def reverse(self):
3723 3684 self._ascending = not self._ascending
3724 3685
3725 3686 def _iterfilter(self, iterrange):
3726 3687 s = self._hiddenrevs
3727 3688 for r in iterrange:
3728 3689 if r not in s:
3729 3690 yield r
3730 3691
3731 3692 def __iter__(self):
3732 3693 if self._ascending:
3733 3694 return self.fastasc()
3734 3695 else:
3735 3696 return self.fastdesc()
3736 3697
3737 3698 def fastasc(self):
3738 3699 iterrange = xrange(self._start, self._end)
3739 3700 if self._hiddenrevs:
3740 3701 return self._iterfilter(iterrange)
3741 3702 return iter(iterrange)
3742 3703
3743 3704 def fastdesc(self):
3744 3705 iterrange = xrange(self._end - 1, self._start - 1, -1)
3745 3706 if self._hiddenrevs:
3746 3707 return self._iterfilter(iterrange)
3747 3708 return iter(iterrange)
3748 3709
3749 3710 def __contains__(self, rev):
3750 3711 hidden = self._hiddenrevs
3751 3712 return ((self._start <= rev < self._end)
3752 3713 and not (hidden and rev in hidden))
3753 3714
3754 3715 def __nonzero__(self):
3755 3716 for r in self:
3756 3717 return True
3757 3718 return False
3758 3719
3759 3720 def __len__(self):
3760 3721 if not self._hiddenrevs:
3761 3722 return abs(self._end - self._start)
3762 3723 else:
3763 3724 count = 0
3764 3725 start = self._start
3765 3726 end = self._end
3766 3727 for rev in self._hiddenrevs:
3767 3728 if (end < rev <= start) or (start <= rev < end):
3768 3729 count += 1
3769 3730 return abs(self._end - self._start) - count
3770 3731
3771 3732 def isascending(self):
3772 3733 return self._ascending
3773 3734
3774 3735 def isdescending(self):
3775 3736 return not self._ascending
3776 3737
3777 3738 def first(self):
3778 3739 if self._ascending:
3779 3740 it = self.fastasc
3780 3741 else:
3781 3742 it = self.fastdesc
3782 3743 for x in it():
3783 3744 return x
3784 3745 return None
3785 3746
3786 3747 def last(self):
3787 3748 if self._ascending:
3788 3749 it = self.fastdesc
3789 3750 else:
3790 3751 it = self.fastasc
3791 3752 for x in it():
3792 3753 return x
3793 3754 return None
3794 3755
3795 3756 def __repr__(self):
3796 3757 d = {False: '-', True: '+'}[self._ascending]
3797 3758 return '<%s%s %d:%d>' % (type(self).__name__, d,
3798 3759 self._start, self._end - 1)
3799 3760
3800 3761 class fullreposet(spanset):
3801 3762 """a set containing all revisions in the repo
3802 3763
3803 3764 This class exists to host special optimization and magic to handle virtual
3804 3765 revisions such as "null".
3805 3766 """
3806 3767
3807 3768 def __init__(self, repo):
3808 3769 super(fullreposet, self).__init__(repo)
3809 3770
3810 3771 def __and__(self, other):
3811 3772 """As self contains the whole repo, all of the other set should also be
3812 3773 in self. Therefore `self & other = other`.
3813 3774
3814 3775 This boldly assumes the other contains valid revs only.
3815 3776 """
3816 3777 # other not a smartset, make is so
3817 3778 if not util.safehasattr(other, 'isascending'):
3818 3779 # filter out hidden revision
3819 3780 # (this boldly assumes all smartset are pure)
3820 3781 #
3821 3782 # `other` was used with "&", let's assume this is a set like
3822 3783 # object.
3823 3784 other = baseset(other - self._hiddenrevs)
3824 3785
3825 3786 # XXX As fullreposet is also used as bootstrap, this is wrong.
3826 3787 #
3827 3788 # With a giveme312() revset returning [3,1,2], this makes
3828 3789 # 'hg log -r "giveme312()"' -> 1, 2, 3 (wrong)
3829 3790 # We cannot just drop it because other usage still need to sort it:
3830 3791 # 'hg log -r "all() and giveme312()"' -> 1, 2, 3 (right)
3831 3792 #
3832 3793 # There is also some faulty revset implementations that rely on it
3833 3794 # (eg: children as of its state in e8075329c5fb)
3834 3795 #
3835 3796 # When we fix the two points above we can move this into the if clause
3836 3797 other.sort(reverse=self.isdescending())
3837 3798 return other
3838 3799
3839 3800 def prettyformatset(revs):
3840 3801 lines = []
3841 3802 rs = repr(revs)
3842 3803 p = 0
3843 3804 while p < len(rs):
3844 3805 q = rs.find('<', p + 1)
3845 3806 if q < 0:
3846 3807 q = len(rs)
3847 3808 l = rs.count('<', 0, p) - rs.count('>', 0, p)
3848 3809 assert l >= 0
3849 3810 lines.append((l, rs[p:q].rstrip()))
3850 3811 p = q
3851 3812 return '\n'.join(' ' * l + s for l, s in lines)
3852 3813
3853 3814 # tell hggettext to extract docstrings from these functions:
3854 3815 i18nfunctions = symbols.values()
@@ -1,2423 +1,2462
1 1 # util.py - Mercurial utility functions and platform specific implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 """Mercurial utility functions and platform specific implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 import i18n
17 17 _ = i18n._
18 18 import error, osutil, encoding, parsers
19 19 import errno, shutil, sys, tempfile, traceback
20 20 import re as remod
21 21 import os, time, datetime, calendar, textwrap, signal, collections
22 22 import imp, socket, urllib
23 23 import gc
24 24 import bz2
25 25 import zlib
26 26
27 27 if os.name == 'nt':
28 28 import windows as platform
29 29 else:
30 30 import posix as platform
31 31
32 32 cachestat = platform.cachestat
33 33 checkexec = platform.checkexec
34 34 checklink = platform.checklink
35 35 copymode = platform.copymode
36 36 executablepath = platform.executablepath
37 37 expandglobs = platform.expandglobs
38 38 explainexit = platform.explainexit
39 39 findexe = platform.findexe
40 40 gethgcmd = platform.gethgcmd
41 41 getuser = platform.getuser
42 42 groupmembers = platform.groupmembers
43 43 groupname = platform.groupname
44 44 hidewindow = platform.hidewindow
45 45 isexec = platform.isexec
46 46 isowner = platform.isowner
47 47 localpath = platform.localpath
48 48 lookupreg = platform.lookupreg
49 49 makedir = platform.makedir
50 50 nlinks = platform.nlinks
51 51 normpath = platform.normpath
52 52 normcase = platform.normcase
53 53 normcasespec = platform.normcasespec
54 54 normcasefallback = platform.normcasefallback
55 55 openhardlinks = platform.openhardlinks
56 56 oslink = platform.oslink
57 57 parsepatchoutput = platform.parsepatchoutput
58 58 pconvert = platform.pconvert
59 59 poll = platform.poll
60 60 popen = platform.popen
61 61 posixfile = platform.posixfile
62 62 quotecommand = platform.quotecommand
63 63 readpipe = platform.readpipe
64 64 rename = platform.rename
65 65 removedirs = platform.removedirs
66 66 samedevice = platform.samedevice
67 67 samefile = platform.samefile
68 68 samestat = platform.samestat
69 69 setbinary = platform.setbinary
70 70 setflags = platform.setflags
71 71 setsignalhandler = platform.setsignalhandler
72 72 shellquote = platform.shellquote
73 73 spawndetached = platform.spawndetached
74 74 split = platform.split
75 75 sshargs = platform.sshargs
76 76 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
77 77 statisexec = platform.statisexec
78 78 statislink = platform.statislink
79 79 termwidth = platform.termwidth
80 80 testpid = platform.testpid
81 81 umask = platform.umask
82 82 unlink = platform.unlink
83 83 unlinkpath = platform.unlinkpath
84 84 username = platform.username
85 85
86 86 # Python compatibility
87 87
88 88 _notset = object()
89 89
90 90 def safehasattr(thing, attr):
91 91 return getattr(thing, attr, _notset) is not _notset
92 92
93 93 def sha1(s=''):
94 94 '''
95 95 Low-overhead wrapper around Python's SHA support
96 96
97 97 >>> f = _fastsha1
98 98 >>> a = sha1()
99 99 >>> a = f()
100 100 >>> a.hexdigest()
101 101 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
102 102 '''
103 103
104 104 return _fastsha1(s)
105 105
106 106 def _fastsha1(s=''):
107 107 # This function will import sha1 from hashlib or sha (whichever is
108 108 # available) and overwrite itself with it on the first call.
109 109 # Subsequent calls will go directly to the imported function.
110 110 if sys.version_info >= (2, 5):
111 111 from hashlib import sha1 as _sha1
112 112 else:
113 113 from sha import sha as _sha1
114 114 global _fastsha1, sha1
115 115 _fastsha1 = sha1 = _sha1
116 116 return _sha1(s)
117 117
118 118 def md5(s=''):
119 119 try:
120 120 from hashlib import md5 as _md5
121 121 except ImportError:
122 122 from md5 import md5 as _md5
123 123 global md5
124 124 md5 = _md5
125 125 return _md5(s)
126 126
127 127 DIGESTS = {
128 128 'md5': md5,
129 129 'sha1': sha1,
130 130 }
131 131 # List of digest types from strongest to weakest
132 132 DIGESTS_BY_STRENGTH = ['sha1', 'md5']
133 133
134 134 try:
135 135 import hashlib
136 136 DIGESTS.update({
137 137 'sha512': hashlib.sha512,
138 138 })
139 139 DIGESTS_BY_STRENGTH.insert(0, 'sha512')
140 140 except ImportError:
141 141 pass
142 142
143 143 for k in DIGESTS_BY_STRENGTH:
144 144 assert k in DIGESTS
145 145
146 146 class digester(object):
147 147 """helper to compute digests.
148 148
149 149 This helper can be used to compute one or more digests given their name.
150 150
151 151 >>> d = digester(['md5', 'sha1'])
152 152 >>> d.update('foo')
153 153 >>> [k for k in sorted(d)]
154 154 ['md5', 'sha1']
155 155 >>> d['md5']
156 156 'acbd18db4cc2f85cedef654fccc4a4d8'
157 157 >>> d['sha1']
158 158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
159 159 >>> digester.preferred(['md5', 'sha1'])
160 160 'sha1'
161 161 """
162 162
163 163 def __init__(self, digests, s=''):
164 164 self._hashes = {}
165 165 for k in digests:
166 166 if k not in DIGESTS:
167 167 raise Abort(_('unknown digest type: %s') % k)
168 168 self._hashes[k] = DIGESTS[k]()
169 169 if s:
170 170 self.update(s)
171 171
172 172 def update(self, data):
173 173 for h in self._hashes.values():
174 174 h.update(data)
175 175
176 176 def __getitem__(self, key):
177 177 if key not in DIGESTS:
178 178 raise Abort(_('unknown digest type: %s') % k)
179 179 return self._hashes[key].hexdigest()
180 180
181 181 def __iter__(self):
182 182 return iter(self._hashes)
183 183
184 184 @staticmethod
185 185 def preferred(supported):
186 186 """returns the strongest digest type in both supported and DIGESTS."""
187 187
188 188 for k in DIGESTS_BY_STRENGTH:
189 189 if k in supported:
190 190 return k
191 191 return None
192 192
193 193 class digestchecker(object):
194 194 """file handle wrapper that additionally checks content against a given
195 195 size and digests.
196 196
197 197 d = digestchecker(fh, size, {'md5': '...'})
198 198
199 199 When multiple digests are given, all of them are validated.
200 200 """
201 201
202 202 def __init__(self, fh, size, digests):
203 203 self._fh = fh
204 204 self._size = size
205 205 self._got = 0
206 206 self._digests = dict(digests)
207 207 self._digester = digester(self._digests.keys())
208 208
209 209 def read(self, length=-1):
210 210 content = self._fh.read(length)
211 211 self._digester.update(content)
212 212 self._got += len(content)
213 213 return content
214 214
215 215 def validate(self):
216 216 if self._size != self._got:
217 217 raise Abort(_('size mismatch: expected %d, got %d') %
218 218 (self._size, self._got))
219 219 for k, v in self._digests.items():
220 220 if v != self._digester[k]:
221 221 # i18n: first parameter is a digest name
222 222 raise Abort(_('%s mismatch: expected %s, got %s') %
223 223 (k, v, self._digester[k]))
224 224
225 225 try:
226 226 buffer = buffer
227 227 except NameError:
228 228 if sys.version_info[0] < 3:
229 229 def buffer(sliceable, offset=0):
230 230 return sliceable[offset:]
231 231 else:
232 232 def buffer(sliceable, offset=0):
233 233 return memoryview(sliceable)[offset:]
234 234
235 235 import subprocess
236 236 closefds = os.name == 'posix'
237 237
238 238 _chunksize = 4096
239 239
240 240 class bufferedinputpipe(object):
241 241 """a manually buffered input pipe
242 242
243 243 Python will not let us use buffered IO and lazy reading with 'polling' at
244 244 the same time. We cannot probe the buffer state and select will not detect
245 245 that data are ready to read if they are already buffered.
246 246
247 247 This class let us work around that by implementing its own buffering
248 248 (allowing efficient readline) while offering a way to know if the buffer is
249 249 empty from the output (allowing collaboration of the buffer with polling).
250 250
251 251 This class lives in the 'util' module because it makes use of the 'os'
252 252 module from the python stdlib.
253 253 """
254 254
255 255 def __init__(self, input):
256 256 self._input = input
257 257 self._buffer = []
258 258 self._eof = False
259 259 self._lenbuf = 0
260 260
261 261 @property
262 262 def hasbuffer(self):
263 263 """True is any data is currently buffered
264 264
265 265 This will be used externally a pre-step for polling IO. If there is
266 266 already data then no polling should be set in place."""
267 267 return bool(self._buffer)
268 268
269 269 @property
270 270 def closed(self):
271 271 return self._input.closed
272 272
273 273 def fileno(self):
274 274 return self._input.fileno()
275 275
276 276 def close(self):
277 277 return self._input.close()
278 278
279 279 def read(self, size):
280 280 while (not self._eof) and (self._lenbuf < size):
281 281 self._fillbuffer()
282 282 return self._frombuffer(size)
283 283
284 284 def readline(self, *args, **kwargs):
285 285 if 1 < len(self._buffer):
286 286 # this should not happen because both read and readline end with a
287 287 # _frombuffer call that collapse it.
288 288 self._buffer = [''.join(self._buffer)]
289 289 self._lenbuf = len(self._buffer[0])
290 290 lfi = -1
291 291 if self._buffer:
292 292 lfi = self._buffer[-1].find('\n')
293 293 while (not self._eof) and lfi < 0:
294 294 self._fillbuffer()
295 295 if self._buffer:
296 296 lfi = self._buffer[-1].find('\n')
297 297 size = lfi + 1
298 298 if lfi < 0: # end of file
299 299 size = self._lenbuf
300 300 elif 1 < len(self._buffer):
301 301 # we need to take previous chunks into account
302 302 size += self._lenbuf - len(self._buffer[-1])
303 303 return self._frombuffer(size)
304 304
305 305 def _frombuffer(self, size):
306 306 """return at most 'size' data from the buffer
307 307
308 308 The data are removed from the buffer."""
309 309 if size == 0 or not self._buffer:
310 310 return ''
311 311 buf = self._buffer[0]
312 312 if 1 < len(self._buffer):
313 313 buf = ''.join(self._buffer)
314 314
315 315 data = buf[:size]
316 316 buf = buf[len(data):]
317 317 if buf:
318 318 self._buffer = [buf]
319 319 self._lenbuf = len(buf)
320 320 else:
321 321 self._buffer = []
322 322 self._lenbuf = 0
323 323 return data
324 324
325 325 def _fillbuffer(self):
326 326 """read data to the buffer"""
327 327 data = os.read(self._input.fileno(), _chunksize)
328 328 if not data:
329 329 self._eof = True
330 330 else:
331 331 self._lenbuf += len(data)
332 332 self._buffer.append(data)
333 333
334 334 def popen2(cmd, env=None, newlines=False):
335 335 # Setting bufsize to -1 lets the system decide the buffer size.
336 336 # The default for bufsize is 0, meaning unbuffered. This leads to
337 337 # poor performance on Mac OS X: http://bugs.python.org/issue4194
338 338 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
339 339 close_fds=closefds,
340 340 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
341 341 universal_newlines=newlines,
342 342 env=env)
343 343 return p.stdin, p.stdout
344 344
345 345 def popen3(cmd, env=None, newlines=False):
346 346 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
347 347 return stdin, stdout, stderr
348 348
349 349 def popen4(cmd, env=None, newlines=False, bufsize=-1):
350 350 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
351 351 close_fds=closefds,
352 352 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
353 353 stderr=subprocess.PIPE,
354 354 universal_newlines=newlines,
355 355 env=env)
356 356 return p.stdin, p.stdout, p.stderr, p
357 357
358 358 def version():
359 359 """Return version information if available."""
360 360 try:
361 361 import __version__
362 362 return __version__.version
363 363 except ImportError:
364 364 return 'unknown'
365 365
366 366 # used by parsedate
367 367 defaultdateformats = (
368 368 '%Y-%m-%d %H:%M:%S',
369 369 '%Y-%m-%d %I:%M:%S%p',
370 370 '%Y-%m-%d %H:%M',
371 371 '%Y-%m-%d %I:%M%p',
372 372 '%Y-%m-%d',
373 373 '%m-%d',
374 374 '%m/%d',
375 375 '%m/%d/%y',
376 376 '%m/%d/%Y',
377 377 '%a %b %d %H:%M:%S %Y',
378 378 '%a %b %d %I:%M:%S%p %Y',
379 379 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
380 380 '%b %d %H:%M:%S %Y',
381 381 '%b %d %I:%M:%S%p %Y',
382 382 '%b %d %H:%M:%S',
383 383 '%b %d %I:%M:%S%p',
384 384 '%b %d %H:%M',
385 385 '%b %d %I:%M%p',
386 386 '%b %d %Y',
387 387 '%b %d',
388 388 '%H:%M:%S',
389 389 '%I:%M:%S%p',
390 390 '%H:%M',
391 391 '%I:%M%p',
392 392 )
393 393
394 394 extendeddateformats = defaultdateformats + (
395 395 "%Y",
396 396 "%Y-%m",
397 397 "%b",
398 398 "%b %Y",
399 399 )
400 400
401 401 def cachefunc(func):
402 402 '''cache the result of function calls'''
403 403 # XXX doesn't handle keywords args
404 404 if func.func_code.co_argcount == 0:
405 405 cache = []
406 406 def f():
407 407 if len(cache) == 0:
408 408 cache.append(func())
409 409 return cache[0]
410 410 return f
411 411 cache = {}
412 412 if func.func_code.co_argcount == 1:
413 413 # we gain a small amount of time because
414 414 # we don't need to pack/unpack the list
415 415 def f(arg):
416 416 if arg not in cache:
417 417 cache[arg] = func(arg)
418 418 return cache[arg]
419 419 else:
420 420 def f(*args):
421 421 if args not in cache:
422 422 cache[args] = func(*args)
423 423 return cache[args]
424 424
425 425 return f
426 426
427 427 class sortdict(dict):
428 428 '''a simple sorted dictionary'''
429 429 def __init__(self, data=None):
430 430 self._list = []
431 431 if data:
432 432 self.update(data)
433 433 def copy(self):
434 434 return sortdict(self)
435 435 def __setitem__(self, key, val):
436 436 if key in self:
437 437 self._list.remove(key)
438 438 self._list.append(key)
439 439 dict.__setitem__(self, key, val)
440 440 def __iter__(self):
441 441 return self._list.__iter__()
442 442 def update(self, src):
443 443 if isinstance(src, dict):
444 444 src = src.iteritems()
445 445 for k, v in src:
446 446 self[k] = v
447 447 def clear(self):
448 448 dict.clear(self)
449 449 self._list = []
450 450 def items(self):
451 451 return [(k, self[k]) for k in self._list]
452 452 def __delitem__(self, key):
453 453 dict.__delitem__(self, key)
454 454 self._list.remove(key)
455 455 def pop(self, key, *args, **kwargs):
456 456 dict.pop(self, key, *args, **kwargs)
457 457 try:
458 458 self._list.remove(key)
459 459 except ValueError:
460 460 pass
461 461 def keys(self):
462 462 return self._list
463 463 def iterkeys(self):
464 464 return self._list.__iter__()
465 465 def iteritems(self):
466 466 for k in self._list:
467 467 yield k, self[k]
468 468 def insert(self, index, key, val):
469 469 self._list.insert(index, key)
470 470 dict.__setitem__(self, key, val)
471 471
472 472 class lrucachedict(object):
473 473 '''cache most recent gets from or sets to this dictionary'''
474 474 def __init__(self, maxsize):
475 475 self._cache = {}
476 476 self._maxsize = maxsize
477 477 self._order = collections.deque()
478 478
479 479 def __getitem__(self, key):
480 480 value = self._cache[key]
481 481 self._order.remove(key)
482 482 self._order.append(key)
483 483 return value
484 484
485 485 def __setitem__(self, key, value):
486 486 if key not in self._cache:
487 487 if len(self._cache) >= self._maxsize:
488 488 del self._cache[self._order.popleft()]
489 489 else:
490 490 self._order.remove(key)
491 491 self._cache[key] = value
492 492 self._order.append(key)
493 493
494 494 def __contains__(self, key):
495 495 return key in self._cache
496 496
497 497 def clear(self):
498 498 self._cache.clear()
499 499 self._order = collections.deque()
500 500
501 501 def lrucachefunc(func):
502 502 '''cache most recent results of function calls'''
503 503 cache = {}
504 504 order = collections.deque()
505 505 if func.func_code.co_argcount == 1:
506 506 def f(arg):
507 507 if arg not in cache:
508 508 if len(cache) > 20:
509 509 del cache[order.popleft()]
510 510 cache[arg] = func(arg)
511 511 else:
512 512 order.remove(arg)
513 513 order.append(arg)
514 514 return cache[arg]
515 515 else:
516 516 def f(*args):
517 517 if args not in cache:
518 518 if len(cache) > 20:
519 519 del cache[order.popleft()]
520 520 cache[args] = func(*args)
521 521 else:
522 522 order.remove(args)
523 523 order.append(args)
524 524 return cache[args]
525 525
526 526 return f
527 527
528 528 class propertycache(object):
529 529 def __init__(self, func):
530 530 self.func = func
531 531 self.name = func.__name__
532 532 def __get__(self, obj, type=None):
533 533 result = self.func(obj)
534 534 self.cachevalue(obj, result)
535 535 return result
536 536
537 537 def cachevalue(self, obj, value):
538 538 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
539 539 obj.__dict__[self.name] = value
540 540
541 541 def pipefilter(s, cmd):
542 542 '''filter string S through command CMD, returning its output'''
543 543 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
544 544 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
545 545 pout, perr = p.communicate(s)
546 546 return pout
547 547
548 548 def tempfilter(s, cmd):
549 549 '''filter string S through a pair of temporary files with CMD.
550 550 CMD is used as a template to create the real command to be run,
551 551 with the strings INFILE and OUTFILE replaced by the real names of
552 552 the temporary files generated.'''
553 553 inname, outname = None, None
554 554 try:
555 555 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
556 556 fp = os.fdopen(infd, 'wb')
557 557 fp.write(s)
558 558 fp.close()
559 559 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
560 560 os.close(outfd)
561 561 cmd = cmd.replace('INFILE', inname)
562 562 cmd = cmd.replace('OUTFILE', outname)
563 563 code = os.system(cmd)
564 564 if sys.platform == 'OpenVMS' and code & 1:
565 565 code = 0
566 566 if code:
567 567 raise Abort(_("command '%s' failed: %s") %
568 568 (cmd, explainexit(code)))
569 569 fp = open(outname, 'rb')
570 570 r = fp.read()
571 571 fp.close()
572 572 return r
573 573 finally:
574 574 try:
575 575 if inname:
576 576 os.unlink(inname)
577 577 except OSError:
578 578 pass
579 579 try:
580 580 if outname:
581 581 os.unlink(outname)
582 582 except OSError:
583 583 pass
584 584
585 585 filtertable = {
586 586 'tempfile:': tempfilter,
587 587 'pipe:': pipefilter,
588 588 }
589 589
590 590 def filter(s, cmd):
591 591 "filter a string through a command that transforms its input to its output"
592 592 for name, fn in filtertable.iteritems():
593 593 if cmd.startswith(name):
594 594 return fn(s, cmd[len(name):].lstrip())
595 595 return pipefilter(s, cmd)
596 596
597 597 def binary(s):
598 598 """return true if a string is binary data"""
599 599 return bool(s and '\0' in s)
600 600
601 601 def increasingchunks(source, min=1024, max=65536):
602 602 '''return no less than min bytes per chunk while data remains,
603 603 doubling min after each chunk until it reaches max'''
604 604 def log2(x):
605 605 if not x:
606 606 return 0
607 607 i = 0
608 608 while x:
609 609 x >>= 1
610 610 i += 1
611 611 return i - 1
612 612
613 613 buf = []
614 614 blen = 0
615 615 for chunk in source:
616 616 buf.append(chunk)
617 617 blen += len(chunk)
618 618 if blen >= min:
619 619 if min < max:
620 620 min = min << 1
621 621 nmin = 1 << log2(blen)
622 622 if nmin > min:
623 623 min = nmin
624 624 if min > max:
625 625 min = max
626 626 yield ''.join(buf)
627 627 blen = 0
628 628 buf = []
629 629 if buf:
630 630 yield ''.join(buf)
631 631
632 632 Abort = error.Abort
633 633
634 634 def always(fn):
635 635 return True
636 636
637 637 def never(fn):
638 638 return False
639 639
640 640 def nogc(func):
641 641 """disable garbage collector
642 642
643 643 Python's garbage collector triggers a GC each time a certain number of
644 644 container objects (the number being defined by gc.get_threshold()) are
645 645 allocated even when marked not to be tracked by the collector. Tracking has
646 646 no effect on when GCs are triggered, only on what objects the GC looks
647 647 into. As a workaround, disable GC while building complex (huge)
648 648 containers.
649 649
650 650 This garbage collector issue have been fixed in 2.7.
651 651 """
652 652 def wrapper(*args, **kwargs):
653 653 gcenabled = gc.isenabled()
654 654 gc.disable()
655 655 try:
656 656 return func(*args, **kwargs)
657 657 finally:
658 658 if gcenabled:
659 659 gc.enable()
660 660 return wrapper
661 661
662 662 def pathto(root, n1, n2):
663 663 '''return the relative path from one place to another.
664 664 root should use os.sep to separate directories
665 665 n1 should use os.sep to separate directories
666 666 n2 should use "/" to separate directories
667 667 returns an os.sep-separated path.
668 668
669 669 If n1 is a relative path, it's assumed it's
670 670 relative to root.
671 671 n2 should always be relative to root.
672 672 '''
673 673 if not n1:
674 674 return localpath(n2)
675 675 if os.path.isabs(n1):
676 676 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
677 677 return os.path.join(root, localpath(n2))
678 678 n2 = '/'.join((pconvert(root), n2))
679 679 a, b = splitpath(n1), n2.split('/')
680 680 a.reverse()
681 681 b.reverse()
682 682 while a and b and a[-1] == b[-1]:
683 683 a.pop()
684 684 b.pop()
685 685 b.reverse()
686 686 return os.sep.join((['..'] * len(a)) + b) or '.'
687 687
688 688 def mainfrozen():
689 689 """return True if we are a frozen executable.
690 690
691 691 The code supports py2exe (most common, Windows only) and tools/freeze
692 692 (portable, not much used).
693 693 """
694 694 return (safehasattr(sys, "frozen") or # new py2exe
695 695 safehasattr(sys, "importers") or # old py2exe
696 696 imp.is_frozen("__main__")) # tools/freeze
697 697
698 698 # the location of data files matching the source code
699 699 if mainfrozen():
700 700 # executable version (py2exe) doesn't support __file__
701 701 datapath = os.path.dirname(sys.executable)
702 702 else:
703 703 datapath = os.path.dirname(__file__)
704 704
705 705 i18n.setdatapath(datapath)
706 706
707 707 _hgexecutable = None
708 708
709 709 def hgexecutable():
710 710 """return location of the 'hg' executable.
711 711
712 712 Defaults to $HG or 'hg' in the search path.
713 713 """
714 714 if _hgexecutable is None:
715 715 hg = os.environ.get('HG')
716 716 mainmod = sys.modules['__main__']
717 717 if hg:
718 718 _sethgexecutable(hg)
719 719 elif mainfrozen():
720 720 _sethgexecutable(sys.executable)
721 721 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
722 722 _sethgexecutable(mainmod.__file__)
723 723 else:
724 724 exe = findexe('hg') or os.path.basename(sys.argv[0])
725 725 _sethgexecutable(exe)
726 726 return _hgexecutable
727 727
728 728 def _sethgexecutable(path):
729 729 """set location of the 'hg' executable"""
730 730 global _hgexecutable
731 731 _hgexecutable = path
732 732
733 733 def _isstdout(f):
734 734 fileno = getattr(f, 'fileno', None)
735 735 return fileno and fileno() == sys.__stdout__.fileno()
736 736
737 737 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
738 738 '''enhanced shell command execution.
739 739 run with environment maybe modified, maybe in different dir.
740 740
741 741 if command fails and onerr is None, return status, else raise onerr
742 742 object as exception.
743 743
744 744 if out is specified, it is assumed to be a file-like object that has a
745 745 write() method. stdout and stderr will be redirected to out.'''
746 746 if environ is None:
747 747 environ = {}
748 748 try:
749 749 sys.stdout.flush()
750 750 except Exception:
751 751 pass
752 752 def py2shell(val):
753 753 'convert python object into string that is useful to shell'
754 754 if val is None or val is False:
755 755 return '0'
756 756 if val is True:
757 757 return '1'
758 758 return str(val)
759 759 origcmd = cmd
760 760 cmd = quotecommand(cmd)
761 761 if sys.platform == 'plan9' and (sys.version_info[0] == 2
762 762 and sys.version_info[1] < 7):
763 763 # subprocess kludge to work around issues in half-baked Python
764 764 # ports, notably bichued/python:
765 765 if not cwd is None:
766 766 os.chdir(cwd)
767 767 rc = os.system(cmd)
768 768 else:
769 769 env = dict(os.environ)
770 770 env.update((k, py2shell(v)) for k, v in environ.iteritems())
771 771 env['HG'] = hgexecutable()
772 772 if out is None or _isstdout(out):
773 773 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
774 774 env=env, cwd=cwd)
775 775 else:
776 776 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
777 777 env=env, cwd=cwd, stdout=subprocess.PIPE,
778 778 stderr=subprocess.STDOUT)
779 779 while True:
780 780 line = proc.stdout.readline()
781 781 if not line:
782 782 break
783 783 out.write(line)
784 784 proc.wait()
785 785 rc = proc.returncode
786 786 if sys.platform == 'OpenVMS' and rc & 1:
787 787 rc = 0
788 788 if rc and onerr:
789 789 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
790 790 explainexit(rc)[0])
791 791 if errprefix:
792 792 errmsg = '%s: %s' % (errprefix, errmsg)
793 793 raise onerr(errmsg)
794 794 return rc
795 795
796 796 def checksignature(func):
797 797 '''wrap a function with code to check for calling errors'''
798 798 def check(*args, **kwargs):
799 799 try:
800 800 return func(*args, **kwargs)
801 801 except TypeError:
802 802 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
803 803 raise error.SignatureError
804 804 raise
805 805
806 806 return check
807 807
808 808 def copyfile(src, dest, hardlink=False):
809 809 "copy a file, preserving mode and atime/mtime"
810 810 if os.path.lexists(dest):
811 811 unlink(dest)
812 812 # hardlinks are problematic on CIFS, quietly ignore this flag
813 813 # until we find a way to work around it cleanly (issue4546)
814 814 if False and hardlink:
815 815 try:
816 816 oslink(src, dest)
817 817 return
818 818 except (IOError, OSError):
819 819 pass # fall back to normal copy
820 820 if os.path.islink(src):
821 821 os.symlink(os.readlink(src), dest)
822 822 else:
823 823 try:
824 824 shutil.copyfile(src, dest)
825 825 shutil.copymode(src, dest)
826 826 except shutil.Error as inst:
827 827 raise Abort(str(inst))
828 828
829 829 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
830 830 """Copy a directory tree using hardlinks if possible."""
831 831 num = 0
832 832
833 833 if hardlink is None:
834 834 hardlink = (os.stat(src).st_dev ==
835 835 os.stat(os.path.dirname(dst)).st_dev)
836 836 if hardlink:
837 837 topic = _('linking')
838 838 else:
839 839 topic = _('copying')
840 840
841 841 if os.path.isdir(src):
842 842 os.mkdir(dst)
843 843 for name, kind in osutil.listdir(src):
844 844 srcname = os.path.join(src, name)
845 845 dstname = os.path.join(dst, name)
846 846 def nprog(t, pos):
847 847 if pos is not None:
848 848 return progress(t, pos + num)
849 849 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
850 850 num += n
851 851 else:
852 852 if hardlink:
853 853 try:
854 854 oslink(src, dst)
855 855 except (IOError, OSError):
856 856 hardlink = False
857 857 shutil.copy(src, dst)
858 858 else:
859 859 shutil.copy(src, dst)
860 860 num += 1
861 861 progress(topic, num)
862 862 progress(topic, None)
863 863
864 864 return hardlink, num
865 865
866 866 _winreservednames = '''con prn aux nul
867 867 com1 com2 com3 com4 com5 com6 com7 com8 com9
868 868 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
869 869 _winreservedchars = ':*?"<>|'
870 870 def checkwinfilename(path):
871 871 r'''Check that the base-relative path is a valid filename on Windows.
872 872 Returns None if the path is ok, or a UI string describing the problem.
873 873
874 874 >>> checkwinfilename("just/a/normal/path")
875 875 >>> checkwinfilename("foo/bar/con.xml")
876 876 "filename contains 'con', which is reserved on Windows"
877 877 >>> checkwinfilename("foo/con.xml/bar")
878 878 "filename contains 'con', which is reserved on Windows"
879 879 >>> checkwinfilename("foo/bar/xml.con")
880 880 >>> checkwinfilename("foo/bar/AUX/bla.txt")
881 881 "filename contains 'AUX', which is reserved on Windows"
882 882 >>> checkwinfilename("foo/bar/bla:.txt")
883 883 "filename contains ':', which is reserved on Windows"
884 884 >>> checkwinfilename("foo/bar/b\07la.txt")
885 885 "filename contains '\\x07', which is invalid on Windows"
886 886 >>> checkwinfilename("foo/bar/bla ")
887 887 "filename ends with ' ', which is not allowed on Windows"
888 888 >>> checkwinfilename("../bar")
889 889 >>> checkwinfilename("foo\\")
890 890 "filename ends with '\\', which is invalid on Windows"
891 891 >>> checkwinfilename("foo\\/bar")
892 892 "directory name ends with '\\', which is invalid on Windows"
893 893 '''
894 894 if path.endswith('\\'):
895 895 return _("filename ends with '\\', which is invalid on Windows")
896 896 if '\\/' in path:
897 897 return _("directory name ends with '\\', which is invalid on Windows")
898 898 for n in path.replace('\\', '/').split('/'):
899 899 if not n:
900 900 continue
901 901 for c in n:
902 902 if c in _winreservedchars:
903 903 return _("filename contains '%s', which is reserved "
904 904 "on Windows") % c
905 905 if ord(c) <= 31:
906 906 return _("filename contains %r, which is invalid "
907 907 "on Windows") % c
908 908 base = n.split('.')[0]
909 909 if base and base.lower() in _winreservednames:
910 910 return _("filename contains '%s', which is reserved "
911 911 "on Windows") % base
912 912 t = n[-1]
913 913 if t in '. ' and n not in '..':
914 914 return _("filename ends with '%s', which is not allowed "
915 915 "on Windows") % t
916 916
917 917 if os.name == 'nt':
918 918 checkosfilename = checkwinfilename
919 919 else:
920 920 checkosfilename = platform.checkosfilename
921 921
922 922 def makelock(info, pathname):
923 923 try:
924 924 return os.symlink(info, pathname)
925 925 except OSError as why:
926 926 if why.errno == errno.EEXIST:
927 927 raise
928 928 except AttributeError: # no symlink in os
929 929 pass
930 930
931 931 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
932 932 os.write(ld, info)
933 933 os.close(ld)
934 934
935 935 def readlock(pathname):
936 936 try:
937 937 return os.readlink(pathname)
938 938 except OSError as why:
939 939 if why.errno not in (errno.EINVAL, errno.ENOSYS):
940 940 raise
941 941 except AttributeError: # no symlink in os
942 942 pass
943 943 fp = posixfile(pathname)
944 944 r = fp.read()
945 945 fp.close()
946 946 return r
947 947
948 948 def fstat(fp):
949 949 '''stat file object that may not have fileno method.'''
950 950 try:
951 951 return os.fstat(fp.fileno())
952 952 except AttributeError:
953 953 return os.stat(fp.name)
954 954
955 955 # File system features
956 956
957 957 def checkcase(path):
958 958 """
959 959 Return true if the given path is on a case-sensitive filesystem
960 960
961 961 Requires a path (like /foo/.hg) ending with a foldable final
962 962 directory component.
963 963 """
964 964 s1 = os.lstat(path)
965 965 d, b = os.path.split(path)
966 966 b2 = b.upper()
967 967 if b == b2:
968 968 b2 = b.lower()
969 969 if b == b2:
970 970 return True # no evidence against case sensitivity
971 971 p2 = os.path.join(d, b2)
972 972 try:
973 973 s2 = os.lstat(p2)
974 974 if s2 == s1:
975 975 return False
976 976 return True
977 977 except OSError:
978 978 return True
979 979
980 980 try:
981 981 import re2
982 982 _re2 = None
983 983 except ImportError:
984 984 _re2 = False
985 985
986 986 class _re(object):
987 987 def _checkre2(self):
988 988 global _re2
989 989 try:
990 990 # check if match works, see issue3964
991 991 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
992 992 except ImportError:
993 993 _re2 = False
994 994
995 995 def compile(self, pat, flags=0):
996 996 '''Compile a regular expression, using re2 if possible
997 997
998 998 For best performance, use only re2-compatible regexp features. The
999 999 only flags from the re module that are re2-compatible are
1000 1000 IGNORECASE and MULTILINE.'''
1001 1001 if _re2 is None:
1002 1002 self._checkre2()
1003 1003 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1004 1004 if flags & remod.IGNORECASE:
1005 1005 pat = '(?i)' + pat
1006 1006 if flags & remod.MULTILINE:
1007 1007 pat = '(?m)' + pat
1008 1008 try:
1009 1009 return re2.compile(pat)
1010 1010 except re2.error:
1011 1011 pass
1012 1012 return remod.compile(pat, flags)
1013 1013
1014 1014 @propertycache
1015 1015 def escape(self):
1016 1016 '''Return the version of escape corresponding to self.compile.
1017 1017
1018 1018 This is imperfect because whether re2 or re is used for a particular
1019 1019 function depends on the flags, etc, but it's the best we can do.
1020 1020 '''
1021 1021 global _re2
1022 1022 if _re2 is None:
1023 1023 self._checkre2()
1024 1024 if _re2:
1025 1025 return re2.escape
1026 1026 else:
1027 1027 return remod.escape
1028 1028
1029 1029 re = _re()
1030 1030
1031 1031 _fspathcache = {}
1032 1032 def fspath(name, root):
1033 1033 '''Get name in the case stored in the filesystem
1034 1034
1035 1035 The name should be relative to root, and be normcase-ed for efficiency.
1036 1036
1037 1037 Note that this function is unnecessary, and should not be
1038 1038 called, for case-sensitive filesystems (simply because it's expensive).
1039 1039
1040 1040 The root should be normcase-ed, too.
1041 1041 '''
1042 1042 def _makefspathcacheentry(dir):
1043 1043 return dict((normcase(n), n) for n in os.listdir(dir))
1044 1044
1045 1045 seps = os.sep
1046 1046 if os.altsep:
1047 1047 seps = seps + os.altsep
1048 1048 # Protect backslashes. This gets silly very quickly.
1049 1049 seps.replace('\\','\\\\')
1050 1050 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1051 1051 dir = os.path.normpath(root)
1052 1052 result = []
1053 1053 for part, sep in pattern.findall(name):
1054 1054 if sep:
1055 1055 result.append(sep)
1056 1056 continue
1057 1057
1058 1058 if dir not in _fspathcache:
1059 1059 _fspathcache[dir] = _makefspathcacheentry(dir)
1060 1060 contents = _fspathcache[dir]
1061 1061
1062 1062 found = contents.get(part)
1063 1063 if not found:
1064 1064 # retry "once per directory" per "dirstate.walk" which
1065 1065 # may take place for each patches of "hg qpush", for example
1066 1066 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1067 1067 found = contents.get(part)
1068 1068
1069 1069 result.append(found or part)
1070 1070 dir = os.path.join(dir, part)
1071 1071
1072 1072 return ''.join(result)
1073 1073
1074 1074 def checknlink(testfile):
1075 1075 '''check whether hardlink count reporting works properly'''
1076 1076
1077 1077 # testfile may be open, so we need a separate file for checking to
1078 1078 # work around issue2543 (or testfile may get lost on Samba shares)
1079 1079 f1 = testfile + ".hgtmp1"
1080 1080 if os.path.lexists(f1):
1081 1081 return False
1082 1082 try:
1083 1083 posixfile(f1, 'w').close()
1084 1084 except IOError:
1085 1085 return False
1086 1086
1087 1087 f2 = testfile + ".hgtmp2"
1088 1088 fd = None
1089 1089 try:
1090 1090 oslink(f1, f2)
1091 1091 # nlinks() may behave differently for files on Windows shares if
1092 1092 # the file is open.
1093 1093 fd = posixfile(f2)
1094 1094 return nlinks(f2) > 1
1095 1095 except OSError:
1096 1096 return False
1097 1097 finally:
1098 1098 if fd is not None:
1099 1099 fd.close()
1100 1100 for f in (f1, f2):
1101 1101 try:
1102 1102 os.unlink(f)
1103 1103 except OSError:
1104 1104 pass
1105 1105
1106 1106 def endswithsep(path):
1107 1107 '''Check path ends with os.sep or os.altsep.'''
1108 1108 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1109 1109
1110 1110 def splitpath(path):
1111 1111 '''Split path by os.sep.
1112 1112 Note that this function does not use os.altsep because this is
1113 1113 an alternative of simple "xxx.split(os.sep)".
1114 1114 It is recommended to use os.path.normpath() before using this
1115 1115 function if need.'''
1116 1116 return path.split(os.sep)
1117 1117
1118 1118 def gui():
1119 1119 '''Are we running in a GUI?'''
1120 1120 if sys.platform == 'darwin':
1121 1121 if 'SSH_CONNECTION' in os.environ:
1122 1122 # handle SSH access to a box where the user is logged in
1123 1123 return False
1124 1124 elif getattr(osutil, 'isgui', None):
1125 1125 # check if a CoreGraphics session is available
1126 1126 return osutil.isgui()
1127 1127 else:
1128 1128 # pure build; use a safe default
1129 1129 return True
1130 1130 else:
1131 1131 return os.name == "nt" or os.environ.get("DISPLAY")
1132 1132
1133 1133 def mktempcopy(name, emptyok=False, createmode=None):
1134 1134 """Create a temporary file with the same contents from name
1135 1135
1136 1136 The permission bits are copied from the original file.
1137 1137
1138 1138 If the temporary file is going to be truncated immediately, you
1139 1139 can use emptyok=True as an optimization.
1140 1140
1141 1141 Returns the name of the temporary file.
1142 1142 """
1143 1143 d, fn = os.path.split(name)
1144 1144 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1145 1145 os.close(fd)
1146 1146 # Temporary files are created with mode 0600, which is usually not
1147 1147 # what we want. If the original file already exists, just copy
1148 1148 # its mode. Otherwise, manually obey umask.
1149 1149 copymode(name, temp, createmode)
1150 1150 if emptyok:
1151 1151 return temp
1152 1152 try:
1153 1153 try:
1154 1154 ifp = posixfile(name, "rb")
1155 1155 except IOError as inst:
1156 1156 if inst.errno == errno.ENOENT:
1157 1157 return temp
1158 1158 if not getattr(inst, 'filename', None):
1159 1159 inst.filename = name
1160 1160 raise
1161 1161 ofp = posixfile(temp, "wb")
1162 1162 for chunk in filechunkiter(ifp):
1163 1163 ofp.write(chunk)
1164 1164 ifp.close()
1165 1165 ofp.close()
1166 1166 except: # re-raises
1167 1167 try: os.unlink(temp)
1168 1168 except OSError: pass
1169 1169 raise
1170 1170 return temp
1171 1171
1172 1172 class atomictempfile(object):
1173 1173 '''writable file object that atomically updates a file
1174 1174
1175 1175 All writes will go to a temporary copy of the original file. Call
1176 1176 close() when you are done writing, and atomictempfile will rename
1177 1177 the temporary copy to the original name, making the changes
1178 1178 visible. If the object is destroyed without being closed, all your
1179 1179 writes are discarded.
1180 1180 '''
1181 1181 def __init__(self, name, mode='w+b', createmode=None):
1182 1182 self.__name = name # permanent name
1183 1183 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1184 1184 createmode=createmode)
1185 1185 self._fp = posixfile(self._tempname, mode)
1186 1186
1187 1187 # delegated methods
1188 1188 self.write = self._fp.write
1189 1189 self.seek = self._fp.seek
1190 1190 self.tell = self._fp.tell
1191 1191 self.fileno = self._fp.fileno
1192 1192
1193 1193 def close(self):
1194 1194 if not self._fp.closed:
1195 1195 self._fp.close()
1196 1196 rename(self._tempname, localpath(self.__name))
1197 1197
1198 1198 def discard(self):
1199 1199 if not self._fp.closed:
1200 1200 try:
1201 1201 os.unlink(self._tempname)
1202 1202 except OSError:
1203 1203 pass
1204 1204 self._fp.close()
1205 1205
1206 1206 def __del__(self):
1207 1207 if safehasattr(self, '_fp'): # constructor actually did something
1208 1208 self.discard()
1209 1209
1210 1210 def makedirs(name, mode=None, notindexed=False):
1211 1211 """recursive directory creation with parent mode inheritance"""
1212 1212 try:
1213 1213 makedir(name, notindexed)
1214 1214 except OSError as err:
1215 1215 if err.errno == errno.EEXIST:
1216 1216 return
1217 1217 if err.errno != errno.ENOENT or not name:
1218 1218 raise
1219 1219 parent = os.path.dirname(os.path.abspath(name))
1220 1220 if parent == name:
1221 1221 raise
1222 1222 makedirs(parent, mode, notindexed)
1223 1223 makedir(name, notindexed)
1224 1224 if mode is not None:
1225 1225 os.chmod(name, mode)
1226 1226
1227 1227 def ensuredirs(name, mode=None, notindexed=False):
1228 1228 """race-safe recursive directory creation
1229 1229
1230 1230 Newly created directories are marked as "not to be indexed by
1231 1231 the content indexing service", if ``notindexed`` is specified
1232 1232 for "write" mode access.
1233 1233 """
1234 1234 if os.path.isdir(name):
1235 1235 return
1236 1236 parent = os.path.dirname(os.path.abspath(name))
1237 1237 if parent != name:
1238 1238 ensuredirs(parent, mode, notindexed)
1239 1239 try:
1240 1240 makedir(name, notindexed)
1241 1241 except OSError as err:
1242 1242 if err.errno == errno.EEXIST and os.path.isdir(name):
1243 1243 # someone else seems to have won a directory creation race
1244 1244 return
1245 1245 raise
1246 1246 if mode is not None:
1247 1247 os.chmod(name, mode)
1248 1248
1249 1249 def readfile(path):
1250 1250 fp = open(path, 'rb')
1251 1251 try:
1252 1252 return fp.read()
1253 1253 finally:
1254 1254 fp.close()
1255 1255
1256 1256 def writefile(path, text):
1257 1257 fp = open(path, 'wb')
1258 1258 try:
1259 1259 fp.write(text)
1260 1260 finally:
1261 1261 fp.close()
1262 1262
1263 1263 def appendfile(path, text):
1264 1264 fp = open(path, 'ab')
1265 1265 try:
1266 1266 fp.write(text)
1267 1267 finally:
1268 1268 fp.close()
1269 1269
1270 1270 class chunkbuffer(object):
1271 1271 """Allow arbitrary sized chunks of data to be efficiently read from an
1272 1272 iterator over chunks of arbitrary size."""
1273 1273
1274 1274 def __init__(self, in_iter):
1275 1275 """in_iter is the iterator that's iterating over the input chunks.
1276 1276 targetsize is how big a buffer to try to maintain."""
1277 1277 def splitbig(chunks):
1278 1278 for chunk in chunks:
1279 1279 if len(chunk) > 2**20:
1280 1280 pos = 0
1281 1281 while pos < len(chunk):
1282 1282 end = pos + 2 ** 18
1283 1283 yield chunk[pos:end]
1284 1284 pos = end
1285 1285 else:
1286 1286 yield chunk
1287 1287 self.iter = splitbig(in_iter)
1288 1288 self._queue = collections.deque()
1289 1289 self._chunkoffset = 0
1290 1290
1291 1291 def read(self, l=None):
1292 1292 """Read L bytes of data from the iterator of chunks of data.
1293 1293 Returns less than L bytes if the iterator runs dry.
1294 1294
1295 1295 If size parameter is omitted, read everything"""
1296 1296 if l is None:
1297 1297 return ''.join(self.iter)
1298 1298
1299 1299 left = l
1300 1300 buf = []
1301 1301 queue = self._queue
1302 1302 while left > 0:
1303 1303 # refill the queue
1304 1304 if not queue:
1305 1305 target = 2**18
1306 1306 for chunk in self.iter:
1307 1307 queue.append(chunk)
1308 1308 target -= len(chunk)
1309 1309 if target <= 0:
1310 1310 break
1311 1311 if not queue:
1312 1312 break
1313 1313
1314 1314 # The easy way to do this would be to queue.popleft(), modify the
1315 1315 # chunk (if necessary), then queue.appendleft(). However, for cases
1316 1316 # where we read partial chunk content, this incurs 2 dequeue
1317 1317 # mutations and creates a new str for the remaining chunk in the
1318 1318 # queue. Our code below avoids this overhead.
1319 1319
1320 1320 chunk = queue[0]
1321 1321 chunkl = len(chunk)
1322 1322 offset = self._chunkoffset
1323 1323
1324 1324 # Use full chunk.
1325 1325 if offset == 0 and left >= chunkl:
1326 1326 left -= chunkl
1327 1327 queue.popleft()
1328 1328 buf.append(chunk)
1329 1329 # self._chunkoffset remains at 0.
1330 1330 continue
1331 1331
1332 1332 chunkremaining = chunkl - offset
1333 1333
1334 1334 # Use all of unconsumed part of chunk.
1335 1335 if left >= chunkremaining:
1336 1336 left -= chunkremaining
1337 1337 queue.popleft()
1338 1338 # offset == 0 is enabled by block above, so this won't merely
1339 1339 # copy via ``chunk[0:]``.
1340 1340 buf.append(chunk[offset:])
1341 1341 self._chunkoffset = 0
1342 1342
1343 1343 # Partial chunk needed.
1344 1344 else:
1345 1345 buf.append(chunk[offset:offset + left])
1346 1346 self._chunkoffset += left
1347 1347 left -= chunkremaining
1348 1348
1349 1349 return ''.join(buf)
1350 1350
1351 1351 def filechunkiter(f, size=65536, limit=None):
1352 1352 """Create a generator that produces the data in the file size
1353 1353 (default 65536) bytes at a time, up to optional limit (default is
1354 1354 to read all data). Chunks may be less than size bytes if the
1355 1355 chunk is the last chunk in the file, or the file is a socket or
1356 1356 some other type of file that sometimes reads less data than is
1357 1357 requested."""
1358 1358 assert size >= 0
1359 1359 assert limit is None or limit >= 0
1360 1360 while True:
1361 1361 if limit is None:
1362 1362 nbytes = size
1363 1363 else:
1364 1364 nbytes = min(limit, size)
1365 1365 s = nbytes and f.read(nbytes)
1366 1366 if not s:
1367 1367 break
1368 1368 if limit:
1369 1369 limit -= len(s)
1370 1370 yield s
1371 1371
1372 1372 def makedate(timestamp=None):
1373 1373 '''Return a unix timestamp (or the current time) as a (unixtime,
1374 1374 offset) tuple based off the local timezone.'''
1375 1375 if timestamp is None:
1376 1376 timestamp = time.time()
1377 1377 if timestamp < 0:
1378 1378 hint = _("check your clock")
1379 1379 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1380 1380 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1381 1381 datetime.datetime.fromtimestamp(timestamp))
1382 1382 tz = delta.days * 86400 + delta.seconds
1383 1383 return timestamp, tz
1384 1384
1385 1385 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1386 1386 """represent a (unixtime, offset) tuple as a localized time.
1387 1387 unixtime is seconds since the epoch, and offset is the time zone's
1388 1388 number of seconds away from UTC. if timezone is false, do not
1389 1389 append time zone to string."""
1390 1390 t, tz = date or makedate()
1391 1391 if t < 0:
1392 1392 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1393 1393 tz = 0
1394 1394 if "%1" in format or "%2" in format or "%z" in format:
1395 1395 sign = (tz > 0) and "-" or "+"
1396 1396 minutes = abs(tz) // 60
1397 1397 format = format.replace("%z", "%1%2")
1398 1398 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1399 1399 format = format.replace("%2", "%02d" % (minutes % 60))
1400 1400 try:
1401 1401 t = time.gmtime(float(t) - tz)
1402 1402 except ValueError:
1403 1403 # time was out of range
1404 1404 t = time.gmtime(sys.maxint)
1405 1405 s = time.strftime(format, t)
1406 1406 return s
1407 1407
1408 1408 def shortdate(date=None):
1409 1409 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1410 1410 return datestr(date, format='%Y-%m-%d')
1411 1411
1412 1412 def parsetimezone(tz):
1413 1413 """parse a timezone string and return an offset integer"""
1414 1414 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1415 1415 sign = (tz[0] == "+") and 1 or -1
1416 1416 hours = int(tz[1:3])
1417 1417 minutes = int(tz[3:5])
1418 1418 return -sign * (hours * 60 + minutes) * 60
1419 1419 if tz == "GMT" or tz == "UTC":
1420 1420 return 0
1421 1421 return None
1422 1422
1423 1423 def strdate(string, format, defaults=[]):
1424 1424 """parse a localized time string and return a (unixtime, offset) tuple.
1425 1425 if the string cannot be parsed, ValueError is raised."""
1426 1426 # NOTE: unixtime = localunixtime + offset
1427 1427 offset, date = parsetimezone(string.split()[-1]), string
1428 1428 if offset is not None:
1429 1429 date = " ".join(string.split()[:-1])
1430 1430
1431 1431 # add missing elements from defaults
1432 1432 usenow = False # default to using biased defaults
1433 1433 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1434 1434 found = [True for p in part if ("%"+p) in format]
1435 1435 if not found:
1436 1436 date += "@" + defaults[part][usenow]
1437 1437 format += "@%" + part[0]
1438 1438 else:
1439 1439 # We've found a specific time element, less specific time
1440 1440 # elements are relative to today
1441 1441 usenow = True
1442 1442
1443 1443 timetuple = time.strptime(date, format)
1444 1444 localunixtime = int(calendar.timegm(timetuple))
1445 1445 if offset is None:
1446 1446 # local timezone
1447 1447 unixtime = int(time.mktime(timetuple))
1448 1448 offset = unixtime - localunixtime
1449 1449 else:
1450 1450 unixtime = localunixtime + offset
1451 1451 return unixtime, offset
1452 1452
1453 1453 def parsedate(date, formats=None, bias=None):
1454 1454 """parse a localized date/time and return a (unixtime, offset) tuple.
1455 1455
1456 1456 The date may be a "unixtime offset" string or in one of the specified
1457 1457 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1458 1458
1459 1459 >>> parsedate(' today ') == parsedate(\
1460 1460 datetime.date.today().strftime('%b %d'))
1461 1461 True
1462 1462 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1463 1463 datetime.timedelta(days=1)\
1464 1464 ).strftime('%b %d'))
1465 1465 True
1466 1466 >>> now, tz = makedate()
1467 1467 >>> strnow, strtz = parsedate('now')
1468 1468 >>> (strnow - now) < 1
1469 1469 True
1470 1470 >>> tz == strtz
1471 1471 True
1472 1472 """
1473 1473 if bias is None:
1474 1474 bias = {}
1475 1475 if not date:
1476 1476 return 0, 0
1477 1477 if isinstance(date, tuple) and len(date) == 2:
1478 1478 return date
1479 1479 if not formats:
1480 1480 formats = defaultdateformats
1481 1481 date = date.strip()
1482 1482
1483 1483 if date == 'now' or date == _('now'):
1484 1484 return makedate()
1485 1485 if date == 'today' or date == _('today'):
1486 1486 date = datetime.date.today().strftime('%b %d')
1487 1487 elif date == 'yesterday' or date == _('yesterday'):
1488 1488 date = (datetime.date.today() -
1489 1489 datetime.timedelta(days=1)).strftime('%b %d')
1490 1490
1491 1491 try:
1492 1492 when, offset = map(int, date.split(' '))
1493 1493 except ValueError:
1494 1494 # fill out defaults
1495 1495 now = makedate()
1496 1496 defaults = {}
1497 1497 for part in ("d", "mb", "yY", "HI", "M", "S"):
1498 1498 # this piece is for rounding the specific end of unknowns
1499 1499 b = bias.get(part)
1500 1500 if b is None:
1501 1501 if part[0] in "HMS":
1502 1502 b = "00"
1503 1503 else:
1504 1504 b = "0"
1505 1505
1506 1506 # this piece is for matching the generic end to today's date
1507 1507 n = datestr(now, "%" + part[0])
1508 1508
1509 1509 defaults[part] = (b, n)
1510 1510
1511 1511 for format in formats:
1512 1512 try:
1513 1513 when, offset = strdate(date, format, defaults)
1514 1514 except (ValueError, OverflowError):
1515 1515 pass
1516 1516 else:
1517 1517 break
1518 1518 else:
1519 1519 raise Abort(_('invalid date: %r') % date)
1520 1520 # validate explicit (probably user-specified) date and
1521 1521 # time zone offset. values must fit in signed 32 bits for
1522 1522 # current 32-bit linux runtimes. timezones go from UTC-12
1523 1523 # to UTC+14
1524 1524 if abs(when) > 0x7fffffff:
1525 1525 raise Abort(_('date exceeds 32 bits: %d') % when)
1526 1526 if when < 0:
1527 1527 raise Abort(_('negative date value: %d') % when)
1528 1528 if offset < -50400 or offset > 43200:
1529 1529 raise Abort(_('impossible time zone offset: %d') % offset)
1530 1530 return when, offset
1531 1531
1532 1532 def matchdate(date):
1533 1533 """Return a function that matches a given date match specifier
1534 1534
1535 1535 Formats include:
1536 1536
1537 1537 '{date}' match a given date to the accuracy provided
1538 1538
1539 1539 '<{date}' on or before a given date
1540 1540
1541 1541 '>{date}' on or after a given date
1542 1542
1543 1543 >>> p1 = parsedate("10:29:59")
1544 1544 >>> p2 = parsedate("10:30:00")
1545 1545 >>> p3 = parsedate("10:30:59")
1546 1546 >>> p4 = parsedate("10:31:00")
1547 1547 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1548 1548 >>> f = matchdate("10:30")
1549 1549 >>> f(p1[0])
1550 1550 False
1551 1551 >>> f(p2[0])
1552 1552 True
1553 1553 >>> f(p3[0])
1554 1554 True
1555 1555 >>> f(p4[0])
1556 1556 False
1557 1557 >>> f(p5[0])
1558 1558 False
1559 1559 """
1560 1560
1561 1561 def lower(date):
1562 1562 d = {'mb': "1", 'd': "1"}
1563 1563 return parsedate(date, extendeddateformats, d)[0]
1564 1564
1565 1565 def upper(date):
1566 1566 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1567 1567 for days in ("31", "30", "29"):
1568 1568 try:
1569 1569 d["d"] = days
1570 1570 return parsedate(date, extendeddateformats, d)[0]
1571 1571 except Abort:
1572 1572 pass
1573 1573 d["d"] = "28"
1574 1574 return parsedate(date, extendeddateformats, d)[0]
1575 1575
1576 1576 date = date.strip()
1577 1577
1578 1578 if not date:
1579 1579 raise Abort(_("dates cannot consist entirely of whitespace"))
1580 1580 elif date[0] == "<":
1581 1581 if not date[1:]:
1582 1582 raise Abort(_("invalid day spec, use '<DATE'"))
1583 1583 when = upper(date[1:])
1584 1584 return lambda x: x <= when
1585 1585 elif date[0] == ">":
1586 1586 if not date[1:]:
1587 1587 raise Abort(_("invalid day spec, use '>DATE'"))
1588 1588 when = lower(date[1:])
1589 1589 return lambda x: x >= when
1590 1590 elif date[0] == "-":
1591 1591 try:
1592 1592 days = int(date[1:])
1593 1593 except ValueError:
1594 1594 raise Abort(_("invalid day spec: %s") % date[1:])
1595 1595 if days < 0:
1596 1596 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1597 1597 % date[1:])
1598 1598 when = makedate()[0] - days * 3600 * 24
1599 1599 return lambda x: x >= when
1600 1600 elif " to " in date:
1601 1601 a, b = date.split(" to ")
1602 1602 start, stop = lower(a), upper(b)
1603 1603 return lambda x: x >= start and x <= stop
1604 1604 else:
1605 1605 start, stop = lower(date), upper(date)
1606 1606 return lambda x: x >= start and x <= stop
1607 1607
1608 def stringmatcher(pattern):
1609 """
1610 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1611 returns the matcher name, pattern, and matcher function.
1612 missing or unknown prefixes are treated as literal matches.
1613
1614 helper for tests:
1615 >>> def test(pattern, *tests):
1616 ... kind, pattern, matcher = stringmatcher(pattern)
1617 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1618
1619 exact matching (no prefix):
1620 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1621 ('literal', 'abcdefg', [False, False, True])
1622
1623 regex matching ('re:' prefix)
1624 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1625 ('re', 'a.+b', [False, False, True])
1626
1627 force exact matches ('literal:' prefix)
1628 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1629 ('literal', 're:foobar', [False, True])
1630
1631 unknown prefixes are ignored and treated as literals
1632 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1633 ('literal', 'foo:bar', [False, False, True])
1634 """
1635 if pattern.startswith('re:'):
1636 pattern = pattern[3:]
1637 try:
1638 regex = remod.compile(pattern)
1639 except remod.error as e:
1640 raise error.ParseError(_('invalid regular expression: %s')
1641 % e)
1642 return 're', pattern, regex.search
1643 elif pattern.startswith('literal:'):
1644 pattern = pattern[8:]
1645 return 'literal', pattern, pattern.__eq__
1646
1608 1647 def shortuser(user):
1609 1648 """Return a short representation of a user name or email address."""
1610 1649 f = user.find('@')
1611 1650 if f >= 0:
1612 1651 user = user[:f]
1613 1652 f = user.find('<')
1614 1653 if f >= 0:
1615 1654 user = user[f + 1:]
1616 1655 f = user.find(' ')
1617 1656 if f >= 0:
1618 1657 user = user[:f]
1619 1658 f = user.find('.')
1620 1659 if f >= 0:
1621 1660 user = user[:f]
1622 1661 return user
1623 1662
1624 1663 def emailuser(user):
1625 1664 """Return the user portion of an email address."""
1626 1665 f = user.find('@')
1627 1666 if f >= 0:
1628 1667 user = user[:f]
1629 1668 f = user.find('<')
1630 1669 if f >= 0:
1631 1670 user = user[f + 1:]
1632 1671 return user
1633 1672
1634 1673 def email(author):
1635 1674 '''get email of author.'''
1636 1675 r = author.find('>')
1637 1676 if r == -1:
1638 1677 r = None
1639 1678 return author[author.find('<') + 1:r]
1640 1679
1641 1680 def ellipsis(text, maxlength=400):
1642 1681 """Trim string to at most maxlength (default: 400) columns in display."""
1643 1682 return encoding.trim(text, maxlength, ellipsis='...')
1644 1683
1645 1684 def unitcountfn(*unittable):
1646 1685 '''return a function that renders a readable count of some quantity'''
1647 1686
1648 1687 def go(count):
1649 1688 for multiplier, divisor, format in unittable:
1650 1689 if count >= divisor * multiplier:
1651 1690 return format % (count / float(divisor))
1652 1691 return unittable[-1][2] % count
1653 1692
1654 1693 return go
1655 1694
1656 1695 bytecount = unitcountfn(
1657 1696 (100, 1 << 30, _('%.0f GB')),
1658 1697 (10, 1 << 30, _('%.1f GB')),
1659 1698 (1, 1 << 30, _('%.2f GB')),
1660 1699 (100, 1 << 20, _('%.0f MB')),
1661 1700 (10, 1 << 20, _('%.1f MB')),
1662 1701 (1, 1 << 20, _('%.2f MB')),
1663 1702 (100, 1 << 10, _('%.0f KB')),
1664 1703 (10, 1 << 10, _('%.1f KB')),
1665 1704 (1, 1 << 10, _('%.2f KB')),
1666 1705 (1, 1, _('%.0f bytes')),
1667 1706 )
1668 1707
1669 1708 def uirepr(s):
1670 1709 # Avoid double backslash in Windows path repr()
1671 1710 return repr(s).replace('\\\\', '\\')
1672 1711
1673 1712 # delay import of textwrap
1674 1713 def MBTextWrapper(**kwargs):
1675 1714 class tw(textwrap.TextWrapper):
1676 1715 """
1677 1716 Extend TextWrapper for width-awareness.
1678 1717
1679 1718 Neither number of 'bytes' in any encoding nor 'characters' is
1680 1719 appropriate to calculate terminal columns for specified string.
1681 1720
1682 1721 Original TextWrapper implementation uses built-in 'len()' directly,
1683 1722 so overriding is needed to use width information of each characters.
1684 1723
1685 1724 In addition, characters classified into 'ambiguous' width are
1686 1725 treated as wide in East Asian area, but as narrow in other.
1687 1726
1688 1727 This requires use decision to determine width of such characters.
1689 1728 """
1690 1729 def _cutdown(self, ucstr, space_left):
1691 1730 l = 0
1692 1731 colwidth = encoding.ucolwidth
1693 1732 for i in xrange(len(ucstr)):
1694 1733 l += colwidth(ucstr[i])
1695 1734 if space_left < l:
1696 1735 return (ucstr[:i], ucstr[i:])
1697 1736 return ucstr, ''
1698 1737
1699 1738 # overriding of base class
1700 1739 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1701 1740 space_left = max(width - cur_len, 1)
1702 1741
1703 1742 if self.break_long_words:
1704 1743 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1705 1744 cur_line.append(cut)
1706 1745 reversed_chunks[-1] = res
1707 1746 elif not cur_line:
1708 1747 cur_line.append(reversed_chunks.pop())
1709 1748
1710 1749 # this overriding code is imported from TextWrapper of Python 2.6
1711 1750 # to calculate columns of string by 'encoding.ucolwidth()'
1712 1751 def _wrap_chunks(self, chunks):
1713 1752 colwidth = encoding.ucolwidth
1714 1753
1715 1754 lines = []
1716 1755 if self.width <= 0:
1717 1756 raise ValueError("invalid width %r (must be > 0)" % self.width)
1718 1757
1719 1758 # Arrange in reverse order so items can be efficiently popped
1720 1759 # from a stack of chucks.
1721 1760 chunks.reverse()
1722 1761
1723 1762 while chunks:
1724 1763
1725 1764 # Start the list of chunks that will make up the current line.
1726 1765 # cur_len is just the length of all the chunks in cur_line.
1727 1766 cur_line = []
1728 1767 cur_len = 0
1729 1768
1730 1769 # Figure out which static string will prefix this line.
1731 1770 if lines:
1732 1771 indent = self.subsequent_indent
1733 1772 else:
1734 1773 indent = self.initial_indent
1735 1774
1736 1775 # Maximum width for this line.
1737 1776 width = self.width - len(indent)
1738 1777
1739 1778 # First chunk on line is whitespace -- drop it, unless this
1740 1779 # is the very beginning of the text (i.e. no lines started yet).
1741 1780 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1742 1781 del chunks[-1]
1743 1782
1744 1783 while chunks:
1745 1784 l = colwidth(chunks[-1])
1746 1785
1747 1786 # Can at least squeeze this chunk onto the current line.
1748 1787 if cur_len + l <= width:
1749 1788 cur_line.append(chunks.pop())
1750 1789 cur_len += l
1751 1790
1752 1791 # Nope, this line is full.
1753 1792 else:
1754 1793 break
1755 1794
1756 1795 # The current line is full, and the next chunk is too big to
1757 1796 # fit on *any* line (not just this one).
1758 1797 if chunks and colwidth(chunks[-1]) > width:
1759 1798 self._handle_long_word(chunks, cur_line, cur_len, width)
1760 1799
1761 1800 # If the last chunk on this line is all whitespace, drop it.
1762 1801 if (self.drop_whitespace and
1763 1802 cur_line and cur_line[-1].strip() == ''):
1764 1803 del cur_line[-1]
1765 1804
1766 1805 # Convert current line back to a string and store it in list
1767 1806 # of all lines (return value).
1768 1807 if cur_line:
1769 1808 lines.append(indent + ''.join(cur_line))
1770 1809
1771 1810 return lines
1772 1811
1773 1812 global MBTextWrapper
1774 1813 MBTextWrapper = tw
1775 1814 return tw(**kwargs)
1776 1815
1777 1816 def wrap(line, width, initindent='', hangindent=''):
1778 1817 maxindent = max(len(hangindent), len(initindent))
1779 1818 if width <= maxindent:
1780 1819 # adjust for weird terminal size
1781 1820 width = max(78, maxindent + 1)
1782 1821 line = line.decode(encoding.encoding, encoding.encodingmode)
1783 1822 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1784 1823 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1785 1824 wrapper = MBTextWrapper(width=width,
1786 1825 initial_indent=initindent,
1787 1826 subsequent_indent=hangindent)
1788 1827 return wrapper.fill(line).encode(encoding.encoding)
1789 1828
1790 1829 def iterlines(iterator):
1791 1830 for chunk in iterator:
1792 1831 for line in chunk.splitlines():
1793 1832 yield line
1794 1833
1795 1834 def expandpath(path):
1796 1835 return os.path.expanduser(os.path.expandvars(path))
1797 1836
1798 1837 def hgcmd():
1799 1838 """Return the command used to execute current hg
1800 1839
1801 1840 This is different from hgexecutable() because on Windows we want
1802 1841 to avoid things opening new shell windows like batch files, so we
1803 1842 get either the python call or current executable.
1804 1843 """
1805 1844 if mainfrozen():
1806 1845 return [sys.executable]
1807 1846 return gethgcmd()
1808 1847
1809 1848 def rundetached(args, condfn):
1810 1849 """Execute the argument list in a detached process.
1811 1850
1812 1851 condfn is a callable which is called repeatedly and should return
1813 1852 True once the child process is known to have started successfully.
1814 1853 At this point, the child process PID is returned. If the child
1815 1854 process fails to start or finishes before condfn() evaluates to
1816 1855 True, return -1.
1817 1856 """
1818 1857 # Windows case is easier because the child process is either
1819 1858 # successfully starting and validating the condition or exiting
1820 1859 # on failure. We just poll on its PID. On Unix, if the child
1821 1860 # process fails to start, it will be left in a zombie state until
1822 1861 # the parent wait on it, which we cannot do since we expect a long
1823 1862 # running process on success. Instead we listen for SIGCHLD telling
1824 1863 # us our child process terminated.
1825 1864 terminated = set()
1826 1865 def handler(signum, frame):
1827 1866 terminated.add(os.wait())
1828 1867 prevhandler = None
1829 1868 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1830 1869 if SIGCHLD is not None:
1831 1870 prevhandler = signal.signal(SIGCHLD, handler)
1832 1871 try:
1833 1872 pid = spawndetached(args)
1834 1873 while not condfn():
1835 1874 if ((pid in terminated or not testpid(pid))
1836 1875 and not condfn()):
1837 1876 return -1
1838 1877 time.sleep(0.1)
1839 1878 return pid
1840 1879 finally:
1841 1880 if prevhandler is not None:
1842 1881 signal.signal(signal.SIGCHLD, prevhandler)
1843 1882
1844 1883 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1845 1884 """Return the result of interpolating items in the mapping into string s.
1846 1885
1847 1886 prefix is a single character string, or a two character string with
1848 1887 a backslash as the first character if the prefix needs to be escaped in
1849 1888 a regular expression.
1850 1889
1851 1890 fn is an optional function that will be applied to the replacement text
1852 1891 just before replacement.
1853 1892
1854 1893 escape_prefix is an optional flag that allows using doubled prefix for
1855 1894 its escaping.
1856 1895 """
1857 1896 fn = fn or (lambda s: s)
1858 1897 patterns = '|'.join(mapping.keys())
1859 1898 if escape_prefix:
1860 1899 patterns += '|' + prefix
1861 1900 if len(prefix) > 1:
1862 1901 prefix_char = prefix[1:]
1863 1902 else:
1864 1903 prefix_char = prefix
1865 1904 mapping[prefix_char] = prefix_char
1866 1905 r = remod.compile(r'%s(%s)' % (prefix, patterns))
1867 1906 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1868 1907
1869 1908 def getport(port):
1870 1909 """Return the port for a given network service.
1871 1910
1872 1911 If port is an integer, it's returned as is. If it's a string, it's
1873 1912 looked up using socket.getservbyname(). If there's no matching
1874 1913 service, util.Abort is raised.
1875 1914 """
1876 1915 try:
1877 1916 return int(port)
1878 1917 except ValueError:
1879 1918 pass
1880 1919
1881 1920 try:
1882 1921 return socket.getservbyname(port)
1883 1922 except socket.error:
1884 1923 raise Abort(_("no port number associated with service '%s'") % port)
1885 1924
1886 1925 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1887 1926 '0': False, 'no': False, 'false': False, 'off': False,
1888 1927 'never': False}
1889 1928
1890 1929 def parsebool(s):
1891 1930 """Parse s into a boolean.
1892 1931
1893 1932 If s is not a valid boolean, returns None.
1894 1933 """
1895 1934 return _booleans.get(s.lower(), None)
1896 1935
1897 1936 _hexdig = '0123456789ABCDEFabcdef'
1898 1937 _hextochr = dict((a + b, chr(int(a + b, 16)))
1899 1938 for a in _hexdig for b in _hexdig)
1900 1939
1901 1940 def _urlunquote(s):
1902 1941 """Decode HTTP/HTML % encoding.
1903 1942
1904 1943 >>> _urlunquote('abc%20def')
1905 1944 'abc def'
1906 1945 """
1907 1946 res = s.split('%')
1908 1947 # fastpath
1909 1948 if len(res) == 1:
1910 1949 return s
1911 1950 s = res[0]
1912 1951 for item in res[1:]:
1913 1952 try:
1914 1953 s += _hextochr[item[:2]] + item[2:]
1915 1954 except KeyError:
1916 1955 s += '%' + item
1917 1956 except UnicodeDecodeError:
1918 1957 s += unichr(int(item[:2], 16)) + item[2:]
1919 1958 return s
1920 1959
1921 1960 class url(object):
1922 1961 r"""Reliable URL parser.
1923 1962
1924 1963 This parses URLs and provides attributes for the following
1925 1964 components:
1926 1965
1927 1966 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1928 1967
1929 1968 Missing components are set to None. The only exception is
1930 1969 fragment, which is set to '' if present but empty.
1931 1970
1932 1971 If parsefragment is False, fragment is included in query. If
1933 1972 parsequery is False, query is included in path. If both are
1934 1973 False, both fragment and query are included in path.
1935 1974
1936 1975 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1937 1976
1938 1977 Note that for backward compatibility reasons, bundle URLs do not
1939 1978 take host names. That means 'bundle://../' has a path of '../'.
1940 1979
1941 1980 Examples:
1942 1981
1943 1982 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1944 1983 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1945 1984 >>> url('ssh://[::1]:2200//home/joe/repo')
1946 1985 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1947 1986 >>> url('file:///home/joe/repo')
1948 1987 <url scheme: 'file', path: '/home/joe/repo'>
1949 1988 >>> url('file:///c:/temp/foo/')
1950 1989 <url scheme: 'file', path: 'c:/temp/foo/'>
1951 1990 >>> url('bundle:foo')
1952 1991 <url scheme: 'bundle', path: 'foo'>
1953 1992 >>> url('bundle://../foo')
1954 1993 <url scheme: 'bundle', path: '../foo'>
1955 1994 >>> url(r'c:\foo\bar')
1956 1995 <url path: 'c:\\foo\\bar'>
1957 1996 >>> url(r'\\blah\blah\blah')
1958 1997 <url path: '\\\\blah\\blah\\blah'>
1959 1998 >>> url(r'\\blah\blah\blah#baz')
1960 1999 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1961 2000 >>> url(r'file:///C:\users\me')
1962 2001 <url scheme: 'file', path: 'C:\\users\\me'>
1963 2002
1964 2003 Authentication credentials:
1965 2004
1966 2005 >>> url('ssh://joe:xyz@x/repo')
1967 2006 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1968 2007 >>> url('ssh://joe@x/repo')
1969 2008 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1970 2009
1971 2010 Query strings and fragments:
1972 2011
1973 2012 >>> url('http://host/a?b#c')
1974 2013 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1975 2014 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1976 2015 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1977 2016 """
1978 2017
1979 2018 _safechars = "!~*'()+"
1980 2019 _safepchars = "/!~*'()+:\\"
1981 2020 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
1982 2021
1983 2022 def __init__(self, path, parsequery=True, parsefragment=True):
1984 2023 # We slowly chomp away at path until we have only the path left
1985 2024 self.scheme = self.user = self.passwd = self.host = None
1986 2025 self.port = self.path = self.query = self.fragment = None
1987 2026 self._localpath = True
1988 2027 self._hostport = ''
1989 2028 self._origpath = path
1990 2029
1991 2030 if parsefragment and '#' in path:
1992 2031 path, self.fragment = path.split('#', 1)
1993 2032 if not path:
1994 2033 path = None
1995 2034
1996 2035 # special case for Windows drive letters and UNC paths
1997 2036 if hasdriveletter(path) or path.startswith(r'\\'):
1998 2037 self.path = path
1999 2038 return
2000 2039
2001 2040 # For compatibility reasons, we can't handle bundle paths as
2002 2041 # normal URLS
2003 2042 if path.startswith('bundle:'):
2004 2043 self.scheme = 'bundle'
2005 2044 path = path[7:]
2006 2045 if path.startswith('//'):
2007 2046 path = path[2:]
2008 2047 self.path = path
2009 2048 return
2010 2049
2011 2050 if self._matchscheme(path):
2012 2051 parts = path.split(':', 1)
2013 2052 if parts[0]:
2014 2053 self.scheme, path = parts
2015 2054 self._localpath = False
2016 2055
2017 2056 if not path:
2018 2057 path = None
2019 2058 if self._localpath:
2020 2059 self.path = ''
2021 2060 return
2022 2061 else:
2023 2062 if self._localpath:
2024 2063 self.path = path
2025 2064 return
2026 2065
2027 2066 if parsequery and '?' in path:
2028 2067 path, self.query = path.split('?', 1)
2029 2068 if not path:
2030 2069 path = None
2031 2070 if not self.query:
2032 2071 self.query = None
2033 2072
2034 2073 # // is required to specify a host/authority
2035 2074 if path and path.startswith('//'):
2036 2075 parts = path[2:].split('/', 1)
2037 2076 if len(parts) > 1:
2038 2077 self.host, path = parts
2039 2078 else:
2040 2079 self.host = parts[0]
2041 2080 path = None
2042 2081 if not self.host:
2043 2082 self.host = None
2044 2083 # path of file:///d is /d
2045 2084 # path of file:///d:/ is d:/, not /d:/
2046 2085 if path and not hasdriveletter(path):
2047 2086 path = '/' + path
2048 2087
2049 2088 if self.host and '@' in self.host:
2050 2089 self.user, self.host = self.host.rsplit('@', 1)
2051 2090 if ':' in self.user:
2052 2091 self.user, self.passwd = self.user.split(':', 1)
2053 2092 if not self.host:
2054 2093 self.host = None
2055 2094
2056 2095 # Don't split on colons in IPv6 addresses without ports
2057 2096 if (self.host and ':' in self.host and
2058 2097 not (self.host.startswith('[') and self.host.endswith(']'))):
2059 2098 self._hostport = self.host
2060 2099 self.host, self.port = self.host.rsplit(':', 1)
2061 2100 if not self.host:
2062 2101 self.host = None
2063 2102
2064 2103 if (self.host and self.scheme == 'file' and
2065 2104 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2066 2105 raise Abort(_('file:// URLs can only refer to localhost'))
2067 2106
2068 2107 self.path = path
2069 2108
2070 2109 # leave the query string escaped
2071 2110 for a in ('user', 'passwd', 'host', 'port',
2072 2111 'path', 'fragment'):
2073 2112 v = getattr(self, a)
2074 2113 if v is not None:
2075 2114 setattr(self, a, _urlunquote(v))
2076 2115
2077 2116 def __repr__(self):
2078 2117 attrs = []
2079 2118 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2080 2119 'query', 'fragment'):
2081 2120 v = getattr(self, a)
2082 2121 if v is not None:
2083 2122 attrs.append('%s: %r' % (a, v))
2084 2123 return '<url %s>' % ', '.join(attrs)
2085 2124
2086 2125 def __str__(self):
2087 2126 r"""Join the URL's components back into a URL string.
2088 2127
2089 2128 Examples:
2090 2129
2091 2130 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2092 2131 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2093 2132 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2094 2133 'http://user:pw@host:80/?foo=bar&baz=42'
2095 2134 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2096 2135 'http://user:pw@host:80/?foo=bar%3dbaz'
2097 2136 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2098 2137 'ssh://user:pw@[::1]:2200//home/joe#'
2099 2138 >>> str(url('http://localhost:80//'))
2100 2139 'http://localhost:80//'
2101 2140 >>> str(url('http://localhost:80/'))
2102 2141 'http://localhost:80/'
2103 2142 >>> str(url('http://localhost:80'))
2104 2143 'http://localhost:80/'
2105 2144 >>> str(url('bundle:foo'))
2106 2145 'bundle:foo'
2107 2146 >>> str(url('bundle://../foo'))
2108 2147 'bundle:../foo'
2109 2148 >>> str(url('path'))
2110 2149 'path'
2111 2150 >>> str(url('file:///tmp/foo/bar'))
2112 2151 'file:///tmp/foo/bar'
2113 2152 >>> str(url('file:///c:/tmp/foo/bar'))
2114 2153 'file:///c:/tmp/foo/bar'
2115 2154 >>> print url(r'bundle:foo\bar')
2116 2155 bundle:foo\bar
2117 2156 >>> print url(r'file:///D:\data\hg')
2118 2157 file:///D:\data\hg
2119 2158 """
2120 2159 if self._localpath:
2121 2160 s = self.path
2122 2161 if self.scheme == 'bundle':
2123 2162 s = 'bundle:' + s
2124 2163 if self.fragment:
2125 2164 s += '#' + self.fragment
2126 2165 return s
2127 2166
2128 2167 s = self.scheme + ':'
2129 2168 if self.user or self.passwd or self.host:
2130 2169 s += '//'
2131 2170 elif self.scheme and (not self.path or self.path.startswith('/')
2132 2171 or hasdriveletter(self.path)):
2133 2172 s += '//'
2134 2173 if hasdriveletter(self.path):
2135 2174 s += '/'
2136 2175 if self.user:
2137 2176 s += urllib.quote(self.user, safe=self._safechars)
2138 2177 if self.passwd:
2139 2178 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2140 2179 if self.user or self.passwd:
2141 2180 s += '@'
2142 2181 if self.host:
2143 2182 if not (self.host.startswith('[') and self.host.endswith(']')):
2144 2183 s += urllib.quote(self.host)
2145 2184 else:
2146 2185 s += self.host
2147 2186 if self.port:
2148 2187 s += ':' + urllib.quote(self.port)
2149 2188 if self.host:
2150 2189 s += '/'
2151 2190 if self.path:
2152 2191 # TODO: similar to the query string, we should not unescape the
2153 2192 # path when we store it, the path might contain '%2f' = '/',
2154 2193 # which we should *not* escape.
2155 2194 s += urllib.quote(self.path, safe=self._safepchars)
2156 2195 if self.query:
2157 2196 # we store the query in escaped form.
2158 2197 s += '?' + self.query
2159 2198 if self.fragment is not None:
2160 2199 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2161 2200 return s
2162 2201
2163 2202 def authinfo(self):
2164 2203 user, passwd = self.user, self.passwd
2165 2204 try:
2166 2205 self.user, self.passwd = None, None
2167 2206 s = str(self)
2168 2207 finally:
2169 2208 self.user, self.passwd = user, passwd
2170 2209 if not self.user:
2171 2210 return (s, None)
2172 2211 # authinfo[1] is passed to urllib2 password manager, and its
2173 2212 # URIs must not contain credentials. The host is passed in the
2174 2213 # URIs list because Python < 2.4.3 uses only that to search for
2175 2214 # a password.
2176 2215 return (s, (None, (s, self.host),
2177 2216 self.user, self.passwd or ''))
2178 2217
2179 2218 def isabs(self):
2180 2219 if self.scheme and self.scheme != 'file':
2181 2220 return True # remote URL
2182 2221 if hasdriveletter(self.path):
2183 2222 return True # absolute for our purposes - can't be joined()
2184 2223 if self.path.startswith(r'\\'):
2185 2224 return True # Windows UNC path
2186 2225 if self.path.startswith('/'):
2187 2226 return True # POSIX-style
2188 2227 return False
2189 2228
2190 2229 def localpath(self):
2191 2230 if self.scheme == 'file' or self.scheme == 'bundle':
2192 2231 path = self.path or '/'
2193 2232 # For Windows, we need to promote hosts containing drive
2194 2233 # letters to paths with drive letters.
2195 2234 if hasdriveletter(self._hostport):
2196 2235 path = self._hostport + '/' + self.path
2197 2236 elif (self.host is not None and self.path
2198 2237 and not hasdriveletter(path)):
2199 2238 path = '/' + path
2200 2239 return path
2201 2240 return self._origpath
2202 2241
2203 2242 def islocal(self):
2204 2243 '''whether localpath will return something that posixfile can open'''
2205 2244 return (not self.scheme or self.scheme == 'file'
2206 2245 or self.scheme == 'bundle')
2207 2246
2208 2247 def hasscheme(path):
2209 2248 return bool(url(path).scheme)
2210 2249
2211 2250 def hasdriveletter(path):
2212 2251 return path and path[1:2] == ':' and path[0:1].isalpha()
2213 2252
2214 2253 def urllocalpath(path):
2215 2254 return url(path, parsequery=False, parsefragment=False).localpath()
2216 2255
2217 2256 def hidepassword(u):
2218 2257 '''hide user credential in a url string'''
2219 2258 u = url(u)
2220 2259 if u.passwd:
2221 2260 u.passwd = '***'
2222 2261 return str(u)
2223 2262
2224 2263 def removeauth(u):
2225 2264 '''remove all authentication information from a url string'''
2226 2265 u = url(u)
2227 2266 u.user = u.passwd = None
2228 2267 return str(u)
2229 2268
2230 2269 def isatty(fd):
2231 2270 try:
2232 2271 return fd.isatty()
2233 2272 except AttributeError:
2234 2273 return False
2235 2274
2236 2275 timecount = unitcountfn(
2237 2276 (1, 1e3, _('%.0f s')),
2238 2277 (100, 1, _('%.1f s')),
2239 2278 (10, 1, _('%.2f s')),
2240 2279 (1, 1, _('%.3f s')),
2241 2280 (100, 0.001, _('%.1f ms')),
2242 2281 (10, 0.001, _('%.2f ms')),
2243 2282 (1, 0.001, _('%.3f ms')),
2244 2283 (100, 0.000001, _('%.1f us')),
2245 2284 (10, 0.000001, _('%.2f us')),
2246 2285 (1, 0.000001, _('%.3f us')),
2247 2286 (100, 0.000000001, _('%.1f ns')),
2248 2287 (10, 0.000000001, _('%.2f ns')),
2249 2288 (1, 0.000000001, _('%.3f ns')),
2250 2289 )
2251 2290
2252 2291 _timenesting = [0]
2253 2292
2254 2293 def timed(func):
2255 2294 '''Report the execution time of a function call to stderr.
2256 2295
2257 2296 During development, use as a decorator when you need to measure
2258 2297 the cost of a function, e.g. as follows:
2259 2298
2260 2299 @util.timed
2261 2300 def foo(a, b, c):
2262 2301 pass
2263 2302 '''
2264 2303
2265 2304 def wrapper(*args, **kwargs):
2266 2305 start = time.time()
2267 2306 indent = 2
2268 2307 _timenesting[0] += indent
2269 2308 try:
2270 2309 return func(*args, **kwargs)
2271 2310 finally:
2272 2311 elapsed = time.time() - start
2273 2312 _timenesting[0] -= indent
2274 2313 sys.stderr.write('%s%s: %s\n' %
2275 2314 (' ' * _timenesting[0], func.__name__,
2276 2315 timecount(elapsed)))
2277 2316 return wrapper
2278 2317
2279 2318 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2280 2319 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2281 2320
2282 2321 def sizetoint(s):
2283 2322 '''Convert a space specifier to a byte count.
2284 2323
2285 2324 >>> sizetoint('30')
2286 2325 30
2287 2326 >>> sizetoint('2.2kb')
2288 2327 2252
2289 2328 >>> sizetoint('6M')
2290 2329 6291456
2291 2330 '''
2292 2331 t = s.strip().lower()
2293 2332 try:
2294 2333 for k, u in _sizeunits:
2295 2334 if t.endswith(k):
2296 2335 return int(float(t[:-len(k)]) * u)
2297 2336 return int(t)
2298 2337 except ValueError:
2299 2338 raise error.ParseError(_("couldn't parse size: %s") % s)
2300 2339
2301 2340 class hooks(object):
2302 2341 '''A collection of hook functions that can be used to extend a
2303 2342 function's behavior. Hooks are called in lexicographic order,
2304 2343 based on the names of their sources.'''
2305 2344
2306 2345 def __init__(self):
2307 2346 self._hooks = []
2308 2347
2309 2348 def add(self, source, hook):
2310 2349 self._hooks.append((source, hook))
2311 2350
2312 2351 def __call__(self, *args):
2313 2352 self._hooks.sort(key=lambda x: x[0])
2314 2353 results = []
2315 2354 for source, hook in self._hooks:
2316 2355 results.append(hook(*args))
2317 2356 return results
2318 2357
2319 2358 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2320 2359 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2321 2360 Skips the 'skip' last entries. By default it will flush stdout first.
2322 2361 It can be used everywhere and do intentionally not require an ui object.
2323 2362 Not be used in production code but very convenient while developing.
2324 2363 '''
2325 2364 if otherf:
2326 2365 otherf.flush()
2327 2366 f.write('%s at:\n' % msg)
2328 2367 entries = [('%s:%s' % (fn, ln), func)
2329 2368 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2330 2369 if entries:
2331 2370 fnmax = max(len(entry[0]) for entry in entries)
2332 2371 for fnln, func in entries:
2333 2372 f.write(' %-*s in %s\n' % (fnmax, fnln, func))
2334 2373 f.flush()
2335 2374
2336 2375 class dirs(object):
2337 2376 '''a multiset of directory names from a dirstate or manifest'''
2338 2377
2339 2378 def __init__(self, map, skip=None):
2340 2379 self._dirs = {}
2341 2380 addpath = self.addpath
2342 2381 if safehasattr(map, 'iteritems') and skip is not None:
2343 2382 for f, s in map.iteritems():
2344 2383 if s[0] != skip:
2345 2384 addpath(f)
2346 2385 else:
2347 2386 for f in map:
2348 2387 addpath(f)
2349 2388
2350 2389 def addpath(self, path):
2351 2390 dirs = self._dirs
2352 2391 for base in finddirs(path):
2353 2392 if base in dirs:
2354 2393 dirs[base] += 1
2355 2394 return
2356 2395 dirs[base] = 1
2357 2396
2358 2397 def delpath(self, path):
2359 2398 dirs = self._dirs
2360 2399 for base in finddirs(path):
2361 2400 if dirs[base] > 1:
2362 2401 dirs[base] -= 1
2363 2402 return
2364 2403 del dirs[base]
2365 2404
2366 2405 def __iter__(self):
2367 2406 return self._dirs.iterkeys()
2368 2407
2369 2408 def __contains__(self, d):
2370 2409 return d in self._dirs
2371 2410
2372 2411 if safehasattr(parsers, 'dirs'):
2373 2412 dirs = parsers.dirs
2374 2413
2375 2414 def finddirs(path):
2376 2415 pos = path.rfind('/')
2377 2416 while pos != -1:
2378 2417 yield path[:pos]
2379 2418 pos = path.rfind('/', 0, pos)
2380 2419
2381 2420 # compression utility
2382 2421
2383 2422 class nocompress(object):
2384 2423 def compress(self, x):
2385 2424 return x
2386 2425 def flush(self):
2387 2426 return ""
2388 2427
2389 2428 compressors = {
2390 2429 None: nocompress,
2391 2430 # lambda to prevent early import
2392 2431 'BZ': lambda: bz2.BZ2Compressor(),
2393 2432 'GZ': lambda: zlib.compressobj(),
2394 2433 }
2395 2434 # also support the old form by courtesies
2396 2435 compressors['UN'] = compressors[None]
2397 2436
2398 2437 def _makedecompressor(decompcls):
2399 2438 def generator(f):
2400 2439 d = decompcls()
2401 2440 for chunk in filechunkiter(f):
2402 2441 yield d.decompress(chunk)
2403 2442 def func(fh):
2404 2443 return chunkbuffer(generator(fh))
2405 2444 return func
2406 2445
2407 2446 def _bz2():
2408 2447 d = bz2.BZ2Decompressor()
2409 2448 # Bzip2 stream start with BZ, but we stripped it.
2410 2449 # we put it back for good measure.
2411 2450 d.decompress('BZ')
2412 2451 return d
2413 2452
2414 2453 decompressors = {None: lambda fh: fh,
2415 2454 '_truncatedBZ': _makedecompressor(_bz2),
2416 2455 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2417 2456 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2418 2457 }
2419 2458 # also support the old form by courtesies
2420 2459 decompressors['UN'] = decompressors[None]
2421 2460
2422 2461 # convenient shortcut
2423 2462 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now