##// END OF EJS Templates
pycompat: add util.urlerr util.urlreq classes for py3 compat...
timeless -
r28882:800ec7c0 default
parent child Browse files
Show More
@@ -1,32 +1,123
1 1 # pycompat.py - portability shim for python 3
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 """Mercurial portability shim for python 3.
7 7
8 8 This contains aliases to hide python version-specific details from the core.
9 9 """
10 10
11 11 from __future__ import absolute_import
12 12
13 13 try:
14 14 import cStringIO as io
15 15 stringio = io.StringIO
16 16 except ImportError:
17 17 import io
18 18 stringio = io.StringIO
19 19
20 20 try:
21 21 import Queue as _queue
22 22 _queue.Queue
23 23 except ImportError:
24 24 import queue as _queue
25 25 empty = _queue.Empty
26 26 queue = _queue.Queue
27 27
28 class _pycompatstub(object):
29 pass
30
31 def _alias(alias, origin, items):
32 """ populate a _pycompatstub
33
34 copies items from origin to alias
35 """
36 def hgcase(item):
37 return item.replace('_', '').lower()
38 for item in items:
39 try:
40 setattr(alias, hgcase(item), getattr(origin, item))
41 except AttributeError:
42 pass
43
44 urlreq = _pycompatstub()
45 urlerr = _pycompatstub()
46 try:
47 import urllib2
48 import urllib
49 _alias(urlreq, urllib, (
50 "addclosehook",
51 "addinfourl",
52 "ftpwrapper",
53 "pathname2url",
54 "quote",
55 "splitattr",
56 "splitpasswd",
57 "splitport",
58 "splituser",
59 "unquote",
60 "url2pathname",
61 "urlencode",
62 "urlencode",
63 ))
64 _alias(urlreq, urllib2, (
65 "AbstractHTTPHandler",
66 "BaseHandler",
67 "build_opener",
68 "FileHandler",
69 "FTPHandler",
70 "HTTPBasicAuthHandler",
71 "HTTPDigestAuthHandler",
72 "HTTPHandler",
73 "HTTPPasswordMgrWithDefaultRealm",
74 "HTTPSHandler",
75 "install_opener",
76 "ProxyHandler",
77 "Request",
78 "urlopen",
79 ))
80 _alias(urlerr, urllib2, (
81 "HTTPError",
82 "URLError",
83 ))
84
85 except ImportError:
86 import urllib.request
87 _alias(urlreq, urllib.request, (
88 "AbstractHTTPHandler",
89 "addclosehook",
90 "addinfourl",
91 "BaseHandler",
92 "build_opener",
93 "FileHandler",
94 "FTPHandler",
95 "ftpwrapper",
96 "HTTPHandler",
97 "HTTPSHandler",
98 "install_opener",
99 "pathname2url",
100 "HTTPBasicAuthHandler",
101 "HTTPDigestAuthHandler",
102 "ProxyHandler",
103 "quote",
104 "Request",
105 "splitattr",
106 "splitpasswd",
107 "splitport",
108 "splituser",
109 "unquote",
110 "url2pathname",
111 "urlopen",
112 ))
113 import urllib.error
114 _alias(urlerr, urllib.error, (
115 "HTTPError",
116 "URLError",
117 ))
118
28 119 try:
29 120 xrange
30 121 except NameError:
31 122 import builtins
32 123 builtins.xrange = range
@@ -1,2753 +1,2755
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 collections
21 21 import datetime
22 22 import errno
23 23 import gc
24 24 import hashlib
25 25 import imp
26 26 import os
27 27 import re as remod
28 28 import shutil
29 29 import signal
30 30 import socket
31 31 import subprocess
32 32 import sys
33 33 import tempfile
34 34 import textwrap
35 35 import time
36 36 import traceback
37 37 import urllib
38 38 import zlib
39 39
40 40 from . import (
41 41 encoding,
42 42 error,
43 43 i18n,
44 44 osutil,
45 45 parsers,
46 46 pycompat,
47 47 )
48 48
49 49 for attr in (
50 50 'empty',
51 51 'queue',
52 'urlerr',
53 'urlreq',
52 54 'stringio',
53 55 ):
54 56 globals()[attr] = getattr(pycompat, attr)
55 57
56 58 if os.name == 'nt':
57 59 from . import windows as platform
58 60 else:
59 61 from . import posix as platform
60 62
61 63 md5 = hashlib.md5
62 64 sha1 = hashlib.sha1
63 65 sha512 = hashlib.sha512
64 66 _ = i18n._
65 67
66 68 cachestat = platform.cachestat
67 69 checkexec = platform.checkexec
68 70 checklink = platform.checklink
69 71 copymode = platform.copymode
70 72 executablepath = platform.executablepath
71 73 expandglobs = platform.expandglobs
72 74 explainexit = platform.explainexit
73 75 findexe = platform.findexe
74 76 gethgcmd = platform.gethgcmd
75 77 getuser = platform.getuser
76 78 getpid = os.getpid
77 79 groupmembers = platform.groupmembers
78 80 groupname = platform.groupname
79 81 hidewindow = platform.hidewindow
80 82 isexec = platform.isexec
81 83 isowner = platform.isowner
82 84 localpath = platform.localpath
83 85 lookupreg = platform.lookupreg
84 86 makedir = platform.makedir
85 87 nlinks = platform.nlinks
86 88 normpath = platform.normpath
87 89 normcase = platform.normcase
88 90 normcasespec = platform.normcasespec
89 91 normcasefallback = platform.normcasefallback
90 92 openhardlinks = platform.openhardlinks
91 93 oslink = platform.oslink
92 94 parsepatchoutput = platform.parsepatchoutput
93 95 pconvert = platform.pconvert
94 96 poll = platform.poll
95 97 popen = platform.popen
96 98 posixfile = platform.posixfile
97 99 quotecommand = platform.quotecommand
98 100 readpipe = platform.readpipe
99 101 rename = platform.rename
100 102 removedirs = platform.removedirs
101 103 samedevice = platform.samedevice
102 104 samefile = platform.samefile
103 105 samestat = platform.samestat
104 106 setbinary = platform.setbinary
105 107 setflags = platform.setflags
106 108 setsignalhandler = platform.setsignalhandler
107 109 shellquote = platform.shellquote
108 110 spawndetached = platform.spawndetached
109 111 split = platform.split
110 112 sshargs = platform.sshargs
111 113 statfiles = getattr(osutil, 'statfiles', platform.statfiles)
112 114 statisexec = platform.statisexec
113 115 statislink = platform.statislink
114 116 termwidth = platform.termwidth
115 117 testpid = platform.testpid
116 118 umask = platform.umask
117 119 unlink = platform.unlink
118 120 unlinkpath = platform.unlinkpath
119 121 username = platform.username
120 122
121 123 # Python compatibility
122 124
123 125 _notset = object()
124 126
125 127 # disable Python's problematic floating point timestamps (issue4836)
126 128 # (Python hypocritically says you shouldn't change this behavior in
127 129 # libraries, and sure enough Mercurial is not a library.)
128 130 os.stat_float_times(False)
129 131
130 132 def safehasattr(thing, attr):
131 133 return getattr(thing, attr, _notset) is not _notset
132 134
133 135 DIGESTS = {
134 136 'md5': md5,
135 137 'sha1': sha1,
136 138 'sha512': sha512,
137 139 }
138 140 # List of digest types from strongest to weakest
139 141 DIGESTS_BY_STRENGTH = ['sha512', 'sha1', 'md5']
140 142
141 143 for k in DIGESTS_BY_STRENGTH:
142 144 assert k in DIGESTS
143 145
144 146 class digester(object):
145 147 """helper to compute digests.
146 148
147 149 This helper can be used to compute one or more digests given their name.
148 150
149 151 >>> d = digester(['md5', 'sha1'])
150 152 >>> d.update('foo')
151 153 >>> [k for k in sorted(d)]
152 154 ['md5', 'sha1']
153 155 >>> d['md5']
154 156 'acbd18db4cc2f85cedef654fccc4a4d8'
155 157 >>> d['sha1']
156 158 '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
157 159 >>> digester.preferred(['md5', 'sha1'])
158 160 'sha1'
159 161 """
160 162
161 163 def __init__(self, digests, s=''):
162 164 self._hashes = {}
163 165 for k in digests:
164 166 if k not in DIGESTS:
165 167 raise Abort(_('unknown digest type: %s') % k)
166 168 self._hashes[k] = DIGESTS[k]()
167 169 if s:
168 170 self.update(s)
169 171
170 172 def update(self, data):
171 173 for h in self._hashes.values():
172 174 h.update(data)
173 175
174 176 def __getitem__(self, key):
175 177 if key not in DIGESTS:
176 178 raise Abort(_('unknown digest type: %s') % k)
177 179 return self._hashes[key].hexdigest()
178 180
179 181 def __iter__(self):
180 182 return iter(self._hashes)
181 183
182 184 @staticmethod
183 185 def preferred(supported):
184 186 """returns the strongest digest type in both supported and DIGESTS."""
185 187
186 188 for k in DIGESTS_BY_STRENGTH:
187 189 if k in supported:
188 190 return k
189 191 return None
190 192
191 193 class digestchecker(object):
192 194 """file handle wrapper that additionally checks content against a given
193 195 size and digests.
194 196
195 197 d = digestchecker(fh, size, {'md5': '...'})
196 198
197 199 When multiple digests are given, all of them are validated.
198 200 """
199 201
200 202 def __init__(self, fh, size, digests):
201 203 self._fh = fh
202 204 self._size = size
203 205 self._got = 0
204 206 self._digests = dict(digests)
205 207 self._digester = digester(self._digests.keys())
206 208
207 209 def read(self, length=-1):
208 210 content = self._fh.read(length)
209 211 self._digester.update(content)
210 212 self._got += len(content)
211 213 return content
212 214
213 215 def validate(self):
214 216 if self._size != self._got:
215 217 raise Abort(_('size mismatch: expected %d, got %d') %
216 218 (self._size, self._got))
217 219 for k, v in self._digests.items():
218 220 if v != self._digester[k]:
219 221 # i18n: first parameter is a digest name
220 222 raise Abort(_('%s mismatch: expected %s, got %s') %
221 223 (k, v, self._digester[k]))
222 224
223 225 try:
224 226 buffer = buffer
225 227 except NameError:
226 228 if sys.version_info[0] < 3:
227 229 def buffer(sliceable, offset=0):
228 230 return sliceable[offset:]
229 231 else:
230 232 def buffer(sliceable, offset=0):
231 233 return memoryview(sliceable)[offset:]
232 234
233 235 closefds = os.name == 'posix'
234 236
235 237 _chunksize = 4096
236 238
237 239 class bufferedinputpipe(object):
238 240 """a manually buffered input pipe
239 241
240 242 Python will not let us use buffered IO and lazy reading with 'polling' at
241 243 the same time. We cannot probe the buffer state and select will not detect
242 244 that data are ready to read if they are already buffered.
243 245
244 246 This class let us work around that by implementing its own buffering
245 247 (allowing efficient readline) while offering a way to know if the buffer is
246 248 empty from the output (allowing collaboration of the buffer with polling).
247 249
248 250 This class lives in the 'util' module because it makes use of the 'os'
249 251 module from the python stdlib.
250 252 """
251 253
252 254 def __init__(self, input):
253 255 self._input = input
254 256 self._buffer = []
255 257 self._eof = False
256 258 self._lenbuf = 0
257 259
258 260 @property
259 261 def hasbuffer(self):
260 262 """True is any data is currently buffered
261 263
262 264 This will be used externally a pre-step for polling IO. If there is
263 265 already data then no polling should be set in place."""
264 266 return bool(self._buffer)
265 267
266 268 @property
267 269 def closed(self):
268 270 return self._input.closed
269 271
270 272 def fileno(self):
271 273 return self._input.fileno()
272 274
273 275 def close(self):
274 276 return self._input.close()
275 277
276 278 def read(self, size):
277 279 while (not self._eof) and (self._lenbuf < size):
278 280 self._fillbuffer()
279 281 return self._frombuffer(size)
280 282
281 283 def readline(self, *args, **kwargs):
282 284 if 1 < len(self._buffer):
283 285 # this should not happen because both read and readline end with a
284 286 # _frombuffer call that collapse it.
285 287 self._buffer = [''.join(self._buffer)]
286 288 self._lenbuf = len(self._buffer[0])
287 289 lfi = -1
288 290 if self._buffer:
289 291 lfi = self._buffer[-1].find('\n')
290 292 while (not self._eof) and lfi < 0:
291 293 self._fillbuffer()
292 294 if self._buffer:
293 295 lfi = self._buffer[-1].find('\n')
294 296 size = lfi + 1
295 297 if lfi < 0: # end of file
296 298 size = self._lenbuf
297 299 elif 1 < len(self._buffer):
298 300 # we need to take previous chunks into account
299 301 size += self._lenbuf - len(self._buffer[-1])
300 302 return self._frombuffer(size)
301 303
302 304 def _frombuffer(self, size):
303 305 """return at most 'size' data from the buffer
304 306
305 307 The data are removed from the buffer."""
306 308 if size == 0 or not self._buffer:
307 309 return ''
308 310 buf = self._buffer[0]
309 311 if 1 < len(self._buffer):
310 312 buf = ''.join(self._buffer)
311 313
312 314 data = buf[:size]
313 315 buf = buf[len(data):]
314 316 if buf:
315 317 self._buffer = [buf]
316 318 self._lenbuf = len(buf)
317 319 else:
318 320 self._buffer = []
319 321 self._lenbuf = 0
320 322 return data
321 323
322 324 def _fillbuffer(self):
323 325 """read data to the buffer"""
324 326 data = os.read(self._input.fileno(), _chunksize)
325 327 if not data:
326 328 self._eof = True
327 329 else:
328 330 self._lenbuf += len(data)
329 331 self._buffer.append(data)
330 332
331 333 def popen2(cmd, env=None, newlines=False):
332 334 # Setting bufsize to -1 lets the system decide the buffer size.
333 335 # The default for bufsize is 0, meaning unbuffered. This leads to
334 336 # poor performance on Mac OS X: http://bugs.python.org/issue4194
335 337 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
336 338 close_fds=closefds,
337 339 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
338 340 universal_newlines=newlines,
339 341 env=env)
340 342 return p.stdin, p.stdout
341 343
342 344 def popen3(cmd, env=None, newlines=False):
343 345 stdin, stdout, stderr, p = popen4(cmd, env, newlines)
344 346 return stdin, stdout, stderr
345 347
346 348 def popen4(cmd, env=None, newlines=False, bufsize=-1):
347 349 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
348 350 close_fds=closefds,
349 351 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
350 352 stderr=subprocess.PIPE,
351 353 universal_newlines=newlines,
352 354 env=env)
353 355 return p.stdin, p.stdout, p.stderr, p
354 356
355 357 def version():
356 358 """Return version information if available."""
357 359 try:
358 360 from . import __version__
359 361 return __version__.version
360 362 except ImportError:
361 363 return 'unknown'
362 364
363 365 def versiontuple(v=None, n=4):
364 366 """Parses a Mercurial version string into an N-tuple.
365 367
366 368 The version string to be parsed is specified with the ``v`` argument.
367 369 If it isn't defined, the current Mercurial version string will be parsed.
368 370
369 371 ``n`` can be 2, 3, or 4. Here is how some version strings map to
370 372 returned values:
371 373
372 374 >>> v = '3.6.1+190-df9b73d2d444'
373 375 >>> versiontuple(v, 2)
374 376 (3, 6)
375 377 >>> versiontuple(v, 3)
376 378 (3, 6, 1)
377 379 >>> versiontuple(v, 4)
378 380 (3, 6, 1, '190-df9b73d2d444')
379 381
380 382 >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
381 383 (3, 6, 1, '190-df9b73d2d444+20151118')
382 384
383 385 >>> v = '3.6'
384 386 >>> versiontuple(v, 2)
385 387 (3, 6)
386 388 >>> versiontuple(v, 3)
387 389 (3, 6, None)
388 390 >>> versiontuple(v, 4)
389 391 (3, 6, None, None)
390 392 """
391 393 if not v:
392 394 v = version()
393 395 parts = v.split('+', 1)
394 396 if len(parts) == 1:
395 397 vparts, extra = parts[0], None
396 398 else:
397 399 vparts, extra = parts
398 400
399 401 vints = []
400 402 for i in vparts.split('.'):
401 403 try:
402 404 vints.append(int(i))
403 405 except ValueError:
404 406 break
405 407 # (3, 6) -> (3, 6, None)
406 408 while len(vints) < 3:
407 409 vints.append(None)
408 410
409 411 if n == 2:
410 412 return (vints[0], vints[1])
411 413 if n == 3:
412 414 return (vints[0], vints[1], vints[2])
413 415 if n == 4:
414 416 return (vints[0], vints[1], vints[2], extra)
415 417
416 418 # used by parsedate
417 419 defaultdateformats = (
418 420 '%Y-%m-%d %H:%M:%S',
419 421 '%Y-%m-%d %I:%M:%S%p',
420 422 '%Y-%m-%d %H:%M',
421 423 '%Y-%m-%d %I:%M%p',
422 424 '%Y-%m-%d',
423 425 '%m-%d',
424 426 '%m/%d',
425 427 '%m/%d/%y',
426 428 '%m/%d/%Y',
427 429 '%a %b %d %H:%M:%S %Y',
428 430 '%a %b %d %I:%M:%S%p %Y',
429 431 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
430 432 '%b %d %H:%M:%S %Y',
431 433 '%b %d %I:%M:%S%p %Y',
432 434 '%b %d %H:%M:%S',
433 435 '%b %d %I:%M:%S%p',
434 436 '%b %d %H:%M',
435 437 '%b %d %I:%M%p',
436 438 '%b %d %Y',
437 439 '%b %d',
438 440 '%H:%M:%S',
439 441 '%I:%M:%S%p',
440 442 '%H:%M',
441 443 '%I:%M%p',
442 444 )
443 445
444 446 extendeddateformats = defaultdateformats + (
445 447 "%Y",
446 448 "%Y-%m",
447 449 "%b",
448 450 "%b %Y",
449 451 )
450 452
451 453 def cachefunc(func):
452 454 '''cache the result of function calls'''
453 455 # XXX doesn't handle keywords args
454 456 if func.__code__.co_argcount == 0:
455 457 cache = []
456 458 def f():
457 459 if len(cache) == 0:
458 460 cache.append(func())
459 461 return cache[0]
460 462 return f
461 463 cache = {}
462 464 if func.__code__.co_argcount == 1:
463 465 # we gain a small amount of time because
464 466 # we don't need to pack/unpack the list
465 467 def f(arg):
466 468 if arg not in cache:
467 469 cache[arg] = func(arg)
468 470 return cache[arg]
469 471 else:
470 472 def f(*args):
471 473 if args not in cache:
472 474 cache[args] = func(*args)
473 475 return cache[args]
474 476
475 477 return f
476 478
477 479 class sortdict(dict):
478 480 '''a simple sorted dictionary'''
479 481 def __init__(self, data=None):
480 482 self._list = []
481 483 if data:
482 484 self.update(data)
483 485 def copy(self):
484 486 return sortdict(self)
485 487 def __setitem__(self, key, val):
486 488 if key in self:
487 489 self._list.remove(key)
488 490 self._list.append(key)
489 491 dict.__setitem__(self, key, val)
490 492 def __iter__(self):
491 493 return self._list.__iter__()
492 494 def update(self, src):
493 495 if isinstance(src, dict):
494 496 src = src.iteritems()
495 497 for k, v in src:
496 498 self[k] = v
497 499 def clear(self):
498 500 dict.clear(self)
499 501 self._list = []
500 502 def items(self):
501 503 return [(k, self[k]) for k in self._list]
502 504 def __delitem__(self, key):
503 505 dict.__delitem__(self, key)
504 506 self._list.remove(key)
505 507 def pop(self, key, *args, **kwargs):
506 508 dict.pop(self, key, *args, **kwargs)
507 509 try:
508 510 self._list.remove(key)
509 511 except ValueError:
510 512 pass
511 513 def keys(self):
512 514 return self._list
513 515 def iterkeys(self):
514 516 return self._list.__iter__()
515 517 def iteritems(self):
516 518 for k in self._list:
517 519 yield k, self[k]
518 520 def insert(self, index, key, val):
519 521 self._list.insert(index, key)
520 522 dict.__setitem__(self, key, val)
521 523
522 524 class _lrucachenode(object):
523 525 """A node in a doubly linked list.
524 526
525 527 Holds a reference to nodes on either side as well as a key-value
526 528 pair for the dictionary entry.
527 529 """
528 530 __slots__ = ('next', 'prev', 'key', 'value')
529 531
530 532 def __init__(self):
531 533 self.next = None
532 534 self.prev = None
533 535
534 536 self.key = _notset
535 537 self.value = None
536 538
537 539 def markempty(self):
538 540 """Mark the node as emptied."""
539 541 self.key = _notset
540 542
541 543 class lrucachedict(object):
542 544 """Dict that caches most recent accesses and sets.
543 545
544 546 The dict consists of an actual backing dict - indexed by original
545 547 key - and a doubly linked circular list defining the order of entries in
546 548 the cache.
547 549
548 550 The head node is the newest entry in the cache. If the cache is full,
549 551 we recycle head.prev and make it the new head. Cache accesses result in
550 552 the node being moved to before the existing head and being marked as the
551 553 new head node.
552 554 """
553 555 def __init__(self, max):
554 556 self._cache = {}
555 557
556 558 self._head = head = _lrucachenode()
557 559 head.prev = head
558 560 head.next = head
559 561 self._size = 1
560 562 self._capacity = max
561 563
562 564 def __len__(self):
563 565 return len(self._cache)
564 566
565 567 def __contains__(self, k):
566 568 return k in self._cache
567 569
568 570 def __iter__(self):
569 571 # We don't have to iterate in cache order, but why not.
570 572 n = self._head
571 573 for i in range(len(self._cache)):
572 574 yield n.key
573 575 n = n.next
574 576
575 577 def __getitem__(self, k):
576 578 node = self._cache[k]
577 579 self._movetohead(node)
578 580 return node.value
579 581
580 582 def __setitem__(self, k, v):
581 583 node = self._cache.get(k)
582 584 # Replace existing value and mark as newest.
583 585 if node is not None:
584 586 node.value = v
585 587 self._movetohead(node)
586 588 return
587 589
588 590 if self._size < self._capacity:
589 591 node = self._addcapacity()
590 592 else:
591 593 # Grab the last/oldest item.
592 594 node = self._head.prev
593 595
594 596 # At capacity. Kill the old entry.
595 597 if node.key is not _notset:
596 598 del self._cache[node.key]
597 599
598 600 node.key = k
599 601 node.value = v
600 602 self._cache[k] = node
601 603 # And mark it as newest entry. No need to adjust order since it
602 604 # is already self._head.prev.
603 605 self._head = node
604 606
605 607 def __delitem__(self, k):
606 608 node = self._cache.pop(k)
607 609 node.markempty()
608 610
609 611 # Temporarily mark as newest item before re-adjusting head to make
610 612 # this node the oldest item.
611 613 self._movetohead(node)
612 614 self._head = node.next
613 615
614 616 # Additional dict methods.
615 617
616 618 def get(self, k, default=None):
617 619 try:
618 620 return self._cache[k]
619 621 except KeyError:
620 622 return default
621 623
622 624 def clear(self):
623 625 n = self._head
624 626 while n.key is not _notset:
625 627 n.markempty()
626 628 n = n.next
627 629
628 630 self._cache.clear()
629 631
630 632 def copy(self):
631 633 result = lrucachedict(self._capacity)
632 634 n = self._head.prev
633 635 # Iterate in oldest-to-newest order, so the copy has the right ordering
634 636 for i in range(len(self._cache)):
635 637 result[n.key] = n.value
636 638 n = n.prev
637 639 return result
638 640
639 641 def _movetohead(self, node):
640 642 """Mark a node as the newest, making it the new head.
641 643
642 644 When a node is accessed, it becomes the freshest entry in the LRU
643 645 list, which is denoted by self._head.
644 646
645 647 Visually, let's make ``N`` the new head node (* denotes head):
646 648
647 649 previous/oldest <-> head <-> next/next newest
648 650
649 651 ----<->--- A* ---<->-----
650 652 | |
651 653 E <-> D <-> N <-> C <-> B
652 654
653 655 To:
654 656
655 657 ----<->--- N* ---<->-----
656 658 | |
657 659 E <-> D <-> C <-> B <-> A
658 660
659 661 This requires the following moves:
660 662
661 663 C.next = D (node.prev.next = node.next)
662 664 D.prev = C (node.next.prev = node.prev)
663 665 E.next = N (head.prev.next = node)
664 666 N.prev = E (node.prev = head.prev)
665 667 N.next = A (node.next = head)
666 668 A.prev = N (head.prev = node)
667 669 """
668 670 head = self._head
669 671 # C.next = D
670 672 node.prev.next = node.next
671 673 # D.prev = C
672 674 node.next.prev = node.prev
673 675 # N.prev = E
674 676 node.prev = head.prev
675 677 # N.next = A
676 678 # It is tempting to do just "head" here, however if node is
677 679 # adjacent to head, this will do bad things.
678 680 node.next = head.prev.next
679 681 # E.next = N
680 682 node.next.prev = node
681 683 # A.prev = N
682 684 node.prev.next = node
683 685
684 686 self._head = node
685 687
686 688 def _addcapacity(self):
687 689 """Add a node to the circular linked list.
688 690
689 691 The new node is inserted before the head node.
690 692 """
691 693 head = self._head
692 694 node = _lrucachenode()
693 695 head.prev.next = node
694 696 node.prev = head.prev
695 697 node.next = head
696 698 head.prev = node
697 699 self._size += 1
698 700 return node
699 701
700 702 def lrucachefunc(func):
701 703 '''cache most recent results of function calls'''
702 704 cache = {}
703 705 order = collections.deque()
704 706 if func.__code__.co_argcount == 1:
705 707 def f(arg):
706 708 if arg not in cache:
707 709 if len(cache) > 20:
708 710 del cache[order.popleft()]
709 711 cache[arg] = func(arg)
710 712 else:
711 713 order.remove(arg)
712 714 order.append(arg)
713 715 return cache[arg]
714 716 else:
715 717 def f(*args):
716 718 if args not in cache:
717 719 if len(cache) > 20:
718 720 del cache[order.popleft()]
719 721 cache[args] = func(*args)
720 722 else:
721 723 order.remove(args)
722 724 order.append(args)
723 725 return cache[args]
724 726
725 727 return f
726 728
727 729 class propertycache(object):
728 730 def __init__(self, func):
729 731 self.func = func
730 732 self.name = func.__name__
731 733 def __get__(self, obj, type=None):
732 734 result = self.func(obj)
733 735 self.cachevalue(obj, result)
734 736 return result
735 737
736 738 def cachevalue(self, obj, value):
737 739 # __dict__ assignment required to bypass __setattr__ (eg: repoview)
738 740 obj.__dict__[self.name] = value
739 741
740 742 def pipefilter(s, cmd):
741 743 '''filter string S through command CMD, returning its output'''
742 744 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
743 745 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
744 746 pout, perr = p.communicate(s)
745 747 return pout
746 748
747 749 def tempfilter(s, cmd):
748 750 '''filter string S through a pair of temporary files with CMD.
749 751 CMD is used as a template to create the real command to be run,
750 752 with the strings INFILE and OUTFILE replaced by the real names of
751 753 the temporary files generated.'''
752 754 inname, outname = None, None
753 755 try:
754 756 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
755 757 fp = os.fdopen(infd, 'wb')
756 758 fp.write(s)
757 759 fp.close()
758 760 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
759 761 os.close(outfd)
760 762 cmd = cmd.replace('INFILE', inname)
761 763 cmd = cmd.replace('OUTFILE', outname)
762 764 code = os.system(cmd)
763 765 if sys.platform == 'OpenVMS' and code & 1:
764 766 code = 0
765 767 if code:
766 768 raise Abort(_("command '%s' failed: %s") %
767 769 (cmd, explainexit(code)))
768 770 return readfile(outname)
769 771 finally:
770 772 try:
771 773 if inname:
772 774 os.unlink(inname)
773 775 except OSError:
774 776 pass
775 777 try:
776 778 if outname:
777 779 os.unlink(outname)
778 780 except OSError:
779 781 pass
780 782
781 783 filtertable = {
782 784 'tempfile:': tempfilter,
783 785 'pipe:': pipefilter,
784 786 }
785 787
786 788 def filter(s, cmd):
787 789 "filter a string through a command that transforms its input to its output"
788 790 for name, fn in filtertable.iteritems():
789 791 if cmd.startswith(name):
790 792 return fn(s, cmd[len(name):].lstrip())
791 793 return pipefilter(s, cmd)
792 794
793 795 def binary(s):
794 796 """return true if a string is binary data"""
795 797 return bool(s and '\0' in s)
796 798
797 799 def increasingchunks(source, min=1024, max=65536):
798 800 '''return no less than min bytes per chunk while data remains,
799 801 doubling min after each chunk until it reaches max'''
800 802 def log2(x):
801 803 if not x:
802 804 return 0
803 805 i = 0
804 806 while x:
805 807 x >>= 1
806 808 i += 1
807 809 return i - 1
808 810
809 811 buf = []
810 812 blen = 0
811 813 for chunk in source:
812 814 buf.append(chunk)
813 815 blen += len(chunk)
814 816 if blen >= min:
815 817 if min < max:
816 818 min = min << 1
817 819 nmin = 1 << log2(blen)
818 820 if nmin > min:
819 821 min = nmin
820 822 if min > max:
821 823 min = max
822 824 yield ''.join(buf)
823 825 blen = 0
824 826 buf = []
825 827 if buf:
826 828 yield ''.join(buf)
827 829
828 830 Abort = error.Abort
829 831
830 832 def always(fn):
831 833 return True
832 834
833 835 def never(fn):
834 836 return False
835 837
836 838 def nogc(func):
837 839 """disable garbage collector
838 840
839 841 Python's garbage collector triggers a GC each time a certain number of
840 842 container objects (the number being defined by gc.get_threshold()) are
841 843 allocated even when marked not to be tracked by the collector. Tracking has
842 844 no effect on when GCs are triggered, only on what objects the GC looks
843 845 into. As a workaround, disable GC while building complex (huge)
844 846 containers.
845 847
846 848 This garbage collector issue have been fixed in 2.7.
847 849 """
848 850 def wrapper(*args, **kwargs):
849 851 gcenabled = gc.isenabled()
850 852 gc.disable()
851 853 try:
852 854 return func(*args, **kwargs)
853 855 finally:
854 856 if gcenabled:
855 857 gc.enable()
856 858 return wrapper
857 859
858 860 def pathto(root, n1, n2):
859 861 '''return the relative path from one place to another.
860 862 root should use os.sep to separate directories
861 863 n1 should use os.sep to separate directories
862 864 n2 should use "/" to separate directories
863 865 returns an os.sep-separated path.
864 866
865 867 If n1 is a relative path, it's assumed it's
866 868 relative to root.
867 869 n2 should always be relative to root.
868 870 '''
869 871 if not n1:
870 872 return localpath(n2)
871 873 if os.path.isabs(n1):
872 874 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
873 875 return os.path.join(root, localpath(n2))
874 876 n2 = '/'.join((pconvert(root), n2))
875 877 a, b = splitpath(n1), n2.split('/')
876 878 a.reverse()
877 879 b.reverse()
878 880 while a and b and a[-1] == b[-1]:
879 881 a.pop()
880 882 b.pop()
881 883 b.reverse()
882 884 return os.sep.join((['..'] * len(a)) + b) or '.'
883 885
884 886 def mainfrozen():
885 887 """return True if we are a frozen executable.
886 888
887 889 The code supports py2exe (most common, Windows only) and tools/freeze
888 890 (portable, not much used).
889 891 """
890 892 return (safehasattr(sys, "frozen") or # new py2exe
891 893 safehasattr(sys, "importers") or # old py2exe
892 894 imp.is_frozen("__main__")) # tools/freeze
893 895
894 896 # the location of data files matching the source code
895 897 if mainfrozen() and getattr(sys, 'frozen', None) != 'macosx_app':
896 898 # executable version (py2exe) doesn't support __file__
897 899 datapath = os.path.dirname(sys.executable)
898 900 else:
899 901 datapath = os.path.dirname(__file__)
900 902
901 903 i18n.setdatapath(datapath)
902 904
903 905 _hgexecutable = None
904 906
905 907 def hgexecutable():
906 908 """return location of the 'hg' executable.
907 909
908 910 Defaults to $HG or 'hg' in the search path.
909 911 """
910 912 if _hgexecutable is None:
911 913 hg = os.environ.get('HG')
912 914 mainmod = sys.modules['__main__']
913 915 if hg:
914 916 _sethgexecutable(hg)
915 917 elif mainfrozen():
916 918 if getattr(sys, 'frozen', None) == 'macosx_app':
917 919 # Env variable set by py2app
918 920 _sethgexecutable(os.environ['EXECUTABLEPATH'])
919 921 else:
920 922 _sethgexecutable(sys.executable)
921 923 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
922 924 _sethgexecutable(mainmod.__file__)
923 925 else:
924 926 exe = findexe('hg') or os.path.basename(sys.argv[0])
925 927 _sethgexecutable(exe)
926 928 return _hgexecutable
927 929
928 930 def _sethgexecutable(path):
929 931 """set location of the 'hg' executable"""
930 932 global _hgexecutable
931 933 _hgexecutable = path
932 934
933 935 def _isstdout(f):
934 936 fileno = getattr(f, 'fileno', None)
935 937 return fileno and fileno() == sys.__stdout__.fileno()
936 938
937 939 def system(cmd, environ=None, cwd=None, onerr=None, errprefix=None, out=None):
938 940 '''enhanced shell command execution.
939 941 run with environment maybe modified, maybe in different dir.
940 942
941 943 if command fails and onerr is None, return status, else raise onerr
942 944 object as exception.
943 945
944 946 if out is specified, it is assumed to be a file-like object that has a
945 947 write() method. stdout and stderr will be redirected to out.'''
946 948 if environ is None:
947 949 environ = {}
948 950 try:
949 951 sys.stdout.flush()
950 952 except Exception:
951 953 pass
952 954 def py2shell(val):
953 955 'convert python object into string that is useful to shell'
954 956 if val is None or val is False:
955 957 return '0'
956 958 if val is True:
957 959 return '1'
958 960 return str(val)
959 961 origcmd = cmd
960 962 cmd = quotecommand(cmd)
961 963 if sys.platform == 'plan9' and (sys.version_info[0] == 2
962 964 and sys.version_info[1] < 7):
963 965 # subprocess kludge to work around issues in half-baked Python
964 966 # ports, notably bichued/python:
965 967 if not cwd is None:
966 968 os.chdir(cwd)
967 969 rc = os.system(cmd)
968 970 else:
969 971 env = dict(os.environ)
970 972 env.update((k, py2shell(v)) for k, v in environ.iteritems())
971 973 env['HG'] = hgexecutable()
972 974 if out is None or _isstdout(out):
973 975 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
974 976 env=env, cwd=cwd)
975 977 else:
976 978 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
977 979 env=env, cwd=cwd, stdout=subprocess.PIPE,
978 980 stderr=subprocess.STDOUT)
979 981 while True:
980 982 line = proc.stdout.readline()
981 983 if not line:
982 984 break
983 985 out.write(line)
984 986 proc.wait()
985 987 rc = proc.returncode
986 988 if sys.platform == 'OpenVMS' and rc & 1:
987 989 rc = 0
988 990 if rc and onerr:
989 991 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
990 992 explainexit(rc)[0])
991 993 if errprefix:
992 994 errmsg = '%s: %s' % (errprefix, errmsg)
993 995 raise onerr(errmsg)
994 996 return rc
995 997
996 998 def checksignature(func):
997 999 '''wrap a function with code to check for calling errors'''
998 1000 def check(*args, **kwargs):
999 1001 try:
1000 1002 return func(*args, **kwargs)
1001 1003 except TypeError:
1002 1004 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
1003 1005 raise error.SignatureError
1004 1006 raise
1005 1007
1006 1008 return check
1007 1009
1008 1010 def copyfile(src, dest, hardlink=False, copystat=False):
1009 1011 '''copy a file, preserving mode and optionally other stat info like
1010 1012 atime/mtime'''
1011 1013 if os.path.lexists(dest):
1012 1014 unlink(dest)
1013 1015 # hardlinks are problematic on CIFS, quietly ignore this flag
1014 1016 # until we find a way to work around it cleanly (issue4546)
1015 1017 if False and hardlink:
1016 1018 try:
1017 1019 oslink(src, dest)
1018 1020 return
1019 1021 except (IOError, OSError):
1020 1022 pass # fall back to normal copy
1021 1023 if os.path.islink(src):
1022 1024 os.symlink(os.readlink(src), dest)
1023 1025 # copytime is ignored for symlinks, but in general copytime isn't needed
1024 1026 # for them anyway
1025 1027 else:
1026 1028 try:
1027 1029 shutil.copyfile(src, dest)
1028 1030 if copystat:
1029 1031 # copystat also copies mode
1030 1032 shutil.copystat(src, dest)
1031 1033 else:
1032 1034 shutil.copymode(src, dest)
1033 1035 except shutil.Error as inst:
1034 1036 raise Abort(str(inst))
1035 1037
1036 1038 def copyfiles(src, dst, hardlink=None, progress=lambda t, pos: None):
1037 1039 """Copy a directory tree using hardlinks if possible."""
1038 1040 num = 0
1039 1041
1040 1042 if hardlink is None:
1041 1043 hardlink = (os.stat(src).st_dev ==
1042 1044 os.stat(os.path.dirname(dst)).st_dev)
1043 1045 if hardlink:
1044 1046 topic = _('linking')
1045 1047 else:
1046 1048 topic = _('copying')
1047 1049
1048 1050 if os.path.isdir(src):
1049 1051 os.mkdir(dst)
1050 1052 for name, kind in osutil.listdir(src):
1051 1053 srcname = os.path.join(src, name)
1052 1054 dstname = os.path.join(dst, name)
1053 1055 def nprog(t, pos):
1054 1056 if pos is not None:
1055 1057 return progress(t, pos + num)
1056 1058 hardlink, n = copyfiles(srcname, dstname, hardlink, progress=nprog)
1057 1059 num += n
1058 1060 else:
1059 1061 if hardlink:
1060 1062 try:
1061 1063 oslink(src, dst)
1062 1064 except (IOError, OSError):
1063 1065 hardlink = False
1064 1066 shutil.copy(src, dst)
1065 1067 else:
1066 1068 shutil.copy(src, dst)
1067 1069 num += 1
1068 1070 progress(topic, num)
1069 1071 progress(topic, None)
1070 1072
1071 1073 return hardlink, num
1072 1074
1073 1075 _winreservednames = '''con prn aux nul
1074 1076 com1 com2 com3 com4 com5 com6 com7 com8 com9
1075 1077 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
1076 1078 _winreservedchars = ':*?"<>|'
1077 1079 def checkwinfilename(path):
1078 1080 r'''Check that the base-relative path is a valid filename on Windows.
1079 1081 Returns None if the path is ok, or a UI string describing the problem.
1080 1082
1081 1083 >>> checkwinfilename("just/a/normal/path")
1082 1084 >>> checkwinfilename("foo/bar/con.xml")
1083 1085 "filename contains 'con', which is reserved on Windows"
1084 1086 >>> checkwinfilename("foo/con.xml/bar")
1085 1087 "filename contains 'con', which is reserved on Windows"
1086 1088 >>> checkwinfilename("foo/bar/xml.con")
1087 1089 >>> checkwinfilename("foo/bar/AUX/bla.txt")
1088 1090 "filename contains 'AUX', which is reserved on Windows"
1089 1091 >>> checkwinfilename("foo/bar/bla:.txt")
1090 1092 "filename contains ':', which is reserved on Windows"
1091 1093 >>> checkwinfilename("foo/bar/b\07la.txt")
1092 1094 "filename contains '\\x07', which is invalid on Windows"
1093 1095 >>> checkwinfilename("foo/bar/bla ")
1094 1096 "filename ends with ' ', which is not allowed on Windows"
1095 1097 >>> checkwinfilename("../bar")
1096 1098 >>> checkwinfilename("foo\\")
1097 1099 "filename ends with '\\', which is invalid on Windows"
1098 1100 >>> checkwinfilename("foo\\/bar")
1099 1101 "directory name ends with '\\', which is invalid on Windows"
1100 1102 '''
1101 1103 if path.endswith('\\'):
1102 1104 return _("filename ends with '\\', which is invalid on Windows")
1103 1105 if '\\/' in path:
1104 1106 return _("directory name ends with '\\', which is invalid on Windows")
1105 1107 for n in path.replace('\\', '/').split('/'):
1106 1108 if not n:
1107 1109 continue
1108 1110 for c in n:
1109 1111 if c in _winreservedchars:
1110 1112 return _("filename contains '%s', which is reserved "
1111 1113 "on Windows") % c
1112 1114 if ord(c) <= 31:
1113 1115 return _("filename contains %r, which is invalid "
1114 1116 "on Windows") % c
1115 1117 base = n.split('.')[0]
1116 1118 if base and base.lower() in _winreservednames:
1117 1119 return _("filename contains '%s', which is reserved "
1118 1120 "on Windows") % base
1119 1121 t = n[-1]
1120 1122 if t in '. ' and n not in '..':
1121 1123 return _("filename ends with '%s', which is not allowed "
1122 1124 "on Windows") % t
1123 1125
1124 1126 if os.name == 'nt':
1125 1127 checkosfilename = checkwinfilename
1126 1128 else:
1127 1129 checkosfilename = platform.checkosfilename
1128 1130
1129 1131 def makelock(info, pathname):
1130 1132 try:
1131 1133 return os.symlink(info, pathname)
1132 1134 except OSError as why:
1133 1135 if why.errno == errno.EEXIST:
1134 1136 raise
1135 1137 except AttributeError: # no symlink in os
1136 1138 pass
1137 1139
1138 1140 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
1139 1141 os.write(ld, info)
1140 1142 os.close(ld)
1141 1143
1142 1144 def readlock(pathname):
1143 1145 try:
1144 1146 return os.readlink(pathname)
1145 1147 except OSError as why:
1146 1148 if why.errno not in (errno.EINVAL, errno.ENOSYS):
1147 1149 raise
1148 1150 except AttributeError: # no symlink in os
1149 1151 pass
1150 1152 fp = posixfile(pathname)
1151 1153 r = fp.read()
1152 1154 fp.close()
1153 1155 return r
1154 1156
1155 1157 def fstat(fp):
1156 1158 '''stat file object that may not have fileno method.'''
1157 1159 try:
1158 1160 return os.fstat(fp.fileno())
1159 1161 except AttributeError:
1160 1162 return os.stat(fp.name)
1161 1163
1162 1164 # File system features
1163 1165
1164 1166 def checkcase(path):
1165 1167 """
1166 1168 Return true if the given path is on a case-sensitive filesystem
1167 1169
1168 1170 Requires a path (like /foo/.hg) ending with a foldable final
1169 1171 directory component.
1170 1172 """
1171 1173 s1 = os.lstat(path)
1172 1174 d, b = os.path.split(path)
1173 1175 b2 = b.upper()
1174 1176 if b == b2:
1175 1177 b2 = b.lower()
1176 1178 if b == b2:
1177 1179 return True # no evidence against case sensitivity
1178 1180 p2 = os.path.join(d, b2)
1179 1181 try:
1180 1182 s2 = os.lstat(p2)
1181 1183 if s2 == s1:
1182 1184 return False
1183 1185 return True
1184 1186 except OSError:
1185 1187 return True
1186 1188
1187 1189 try:
1188 1190 import re2
1189 1191 _re2 = None
1190 1192 except ImportError:
1191 1193 _re2 = False
1192 1194
1193 1195 class _re(object):
1194 1196 def _checkre2(self):
1195 1197 global _re2
1196 1198 try:
1197 1199 # check if match works, see issue3964
1198 1200 _re2 = bool(re2.match(r'\[([^\[]+)\]', '[ui]'))
1199 1201 except ImportError:
1200 1202 _re2 = False
1201 1203
1202 1204 def compile(self, pat, flags=0):
1203 1205 '''Compile a regular expression, using re2 if possible
1204 1206
1205 1207 For best performance, use only re2-compatible regexp features. The
1206 1208 only flags from the re module that are re2-compatible are
1207 1209 IGNORECASE and MULTILINE.'''
1208 1210 if _re2 is None:
1209 1211 self._checkre2()
1210 1212 if _re2 and (flags & ~(remod.IGNORECASE | remod.MULTILINE)) == 0:
1211 1213 if flags & remod.IGNORECASE:
1212 1214 pat = '(?i)' + pat
1213 1215 if flags & remod.MULTILINE:
1214 1216 pat = '(?m)' + pat
1215 1217 try:
1216 1218 return re2.compile(pat)
1217 1219 except re2.error:
1218 1220 pass
1219 1221 return remod.compile(pat, flags)
1220 1222
1221 1223 @propertycache
1222 1224 def escape(self):
1223 1225 '''Return the version of escape corresponding to self.compile.
1224 1226
1225 1227 This is imperfect because whether re2 or re is used for a particular
1226 1228 function depends on the flags, etc, but it's the best we can do.
1227 1229 '''
1228 1230 global _re2
1229 1231 if _re2 is None:
1230 1232 self._checkre2()
1231 1233 if _re2:
1232 1234 return re2.escape
1233 1235 else:
1234 1236 return remod.escape
1235 1237
1236 1238 re = _re()
1237 1239
1238 1240 _fspathcache = {}
1239 1241 def fspath(name, root):
1240 1242 '''Get name in the case stored in the filesystem
1241 1243
1242 1244 The name should be relative to root, and be normcase-ed for efficiency.
1243 1245
1244 1246 Note that this function is unnecessary, and should not be
1245 1247 called, for case-sensitive filesystems (simply because it's expensive).
1246 1248
1247 1249 The root should be normcase-ed, too.
1248 1250 '''
1249 1251 def _makefspathcacheentry(dir):
1250 1252 return dict((normcase(n), n) for n in os.listdir(dir))
1251 1253
1252 1254 seps = os.sep
1253 1255 if os.altsep:
1254 1256 seps = seps + os.altsep
1255 1257 # Protect backslashes. This gets silly very quickly.
1256 1258 seps.replace('\\','\\\\')
1257 1259 pattern = remod.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1258 1260 dir = os.path.normpath(root)
1259 1261 result = []
1260 1262 for part, sep in pattern.findall(name):
1261 1263 if sep:
1262 1264 result.append(sep)
1263 1265 continue
1264 1266
1265 1267 if dir not in _fspathcache:
1266 1268 _fspathcache[dir] = _makefspathcacheentry(dir)
1267 1269 contents = _fspathcache[dir]
1268 1270
1269 1271 found = contents.get(part)
1270 1272 if not found:
1271 1273 # retry "once per directory" per "dirstate.walk" which
1272 1274 # may take place for each patches of "hg qpush", for example
1273 1275 _fspathcache[dir] = contents = _makefspathcacheentry(dir)
1274 1276 found = contents.get(part)
1275 1277
1276 1278 result.append(found or part)
1277 1279 dir = os.path.join(dir, part)
1278 1280
1279 1281 return ''.join(result)
1280 1282
1281 1283 def checknlink(testfile):
1282 1284 '''check whether hardlink count reporting works properly'''
1283 1285
1284 1286 # testfile may be open, so we need a separate file for checking to
1285 1287 # work around issue2543 (or testfile may get lost on Samba shares)
1286 1288 f1 = testfile + ".hgtmp1"
1287 1289 if os.path.lexists(f1):
1288 1290 return False
1289 1291 try:
1290 1292 posixfile(f1, 'w').close()
1291 1293 except IOError:
1292 1294 return False
1293 1295
1294 1296 f2 = testfile + ".hgtmp2"
1295 1297 fd = None
1296 1298 try:
1297 1299 oslink(f1, f2)
1298 1300 # nlinks() may behave differently for files on Windows shares if
1299 1301 # the file is open.
1300 1302 fd = posixfile(f2)
1301 1303 return nlinks(f2) > 1
1302 1304 except OSError:
1303 1305 return False
1304 1306 finally:
1305 1307 if fd is not None:
1306 1308 fd.close()
1307 1309 for f in (f1, f2):
1308 1310 try:
1309 1311 os.unlink(f)
1310 1312 except OSError:
1311 1313 pass
1312 1314
1313 1315 def endswithsep(path):
1314 1316 '''Check path ends with os.sep or os.altsep.'''
1315 1317 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1316 1318
1317 1319 def splitpath(path):
1318 1320 '''Split path by os.sep.
1319 1321 Note that this function does not use os.altsep because this is
1320 1322 an alternative of simple "xxx.split(os.sep)".
1321 1323 It is recommended to use os.path.normpath() before using this
1322 1324 function if need.'''
1323 1325 return path.split(os.sep)
1324 1326
1325 1327 def gui():
1326 1328 '''Are we running in a GUI?'''
1327 1329 if sys.platform == 'darwin':
1328 1330 if 'SSH_CONNECTION' in os.environ:
1329 1331 # handle SSH access to a box where the user is logged in
1330 1332 return False
1331 1333 elif getattr(osutil, 'isgui', None):
1332 1334 # check if a CoreGraphics session is available
1333 1335 return osutil.isgui()
1334 1336 else:
1335 1337 # pure build; use a safe default
1336 1338 return True
1337 1339 else:
1338 1340 return os.name == "nt" or os.environ.get("DISPLAY")
1339 1341
1340 1342 def mktempcopy(name, emptyok=False, createmode=None):
1341 1343 """Create a temporary file with the same contents from name
1342 1344
1343 1345 The permission bits are copied from the original file.
1344 1346
1345 1347 If the temporary file is going to be truncated immediately, you
1346 1348 can use emptyok=True as an optimization.
1347 1349
1348 1350 Returns the name of the temporary file.
1349 1351 """
1350 1352 d, fn = os.path.split(name)
1351 1353 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1352 1354 os.close(fd)
1353 1355 # Temporary files are created with mode 0600, which is usually not
1354 1356 # what we want. If the original file already exists, just copy
1355 1357 # its mode. Otherwise, manually obey umask.
1356 1358 copymode(name, temp, createmode)
1357 1359 if emptyok:
1358 1360 return temp
1359 1361 try:
1360 1362 try:
1361 1363 ifp = posixfile(name, "rb")
1362 1364 except IOError as inst:
1363 1365 if inst.errno == errno.ENOENT:
1364 1366 return temp
1365 1367 if not getattr(inst, 'filename', None):
1366 1368 inst.filename = name
1367 1369 raise
1368 1370 ofp = posixfile(temp, "wb")
1369 1371 for chunk in filechunkiter(ifp):
1370 1372 ofp.write(chunk)
1371 1373 ifp.close()
1372 1374 ofp.close()
1373 1375 except: # re-raises
1374 1376 try: os.unlink(temp)
1375 1377 except OSError: pass
1376 1378 raise
1377 1379 return temp
1378 1380
1379 1381 class atomictempfile(object):
1380 1382 '''writable file object that atomically updates a file
1381 1383
1382 1384 All writes will go to a temporary copy of the original file. Call
1383 1385 close() when you are done writing, and atomictempfile will rename
1384 1386 the temporary copy to the original name, making the changes
1385 1387 visible. If the object is destroyed without being closed, all your
1386 1388 writes are discarded.
1387 1389 '''
1388 1390 def __init__(self, name, mode='w+b', createmode=None):
1389 1391 self.__name = name # permanent name
1390 1392 self._tempname = mktempcopy(name, emptyok=('w' in mode),
1391 1393 createmode=createmode)
1392 1394 self._fp = posixfile(self._tempname, mode)
1393 1395
1394 1396 # delegated methods
1395 1397 self.write = self._fp.write
1396 1398 self.seek = self._fp.seek
1397 1399 self.tell = self._fp.tell
1398 1400 self.fileno = self._fp.fileno
1399 1401
1400 1402 def close(self):
1401 1403 if not self._fp.closed:
1402 1404 self._fp.close()
1403 1405 rename(self._tempname, localpath(self.__name))
1404 1406
1405 1407 def discard(self):
1406 1408 if not self._fp.closed:
1407 1409 try:
1408 1410 os.unlink(self._tempname)
1409 1411 except OSError:
1410 1412 pass
1411 1413 self._fp.close()
1412 1414
1413 1415 def __del__(self):
1414 1416 if safehasattr(self, '_fp'): # constructor actually did something
1415 1417 self.discard()
1416 1418
1417 1419 def makedirs(name, mode=None, notindexed=False):
1418 1420 """recursive directory creation with parent mode inheritance"""
1419 1421 try:
1420 1422 makedir(name, notindexed)
1421 1423 except OSError as err:
1422 1424 if err.errno == errno.EEXIST:
1423 1425 return
1424 1426 if err.errno != errno.ENOENT or not name:
1425 1427 raise
1426 1428 parent = os.path.dirname(os.path.abspath(name))
1427 1429 if parent == name:
1428 1430 raise
1429 1431 makedirs(parent, mode, notindexed)
1430 1432 makedir(name, notindexed)
1431 1433 if mode is not None:
1432 1434 os.chmod(name, mode)
1433 1435
1434 1436 def ensuredirs(name, mode=None, notindexed=False):
1435 1437 """race-safe recursive directory creation
1436 1438
1437 1439 Newly created directories are marked as "not to be indexed by
1438 1440 the content indexing service", if ``notindexed`` is specified
1439 1441 for "write" mode access.
1440 1442 """
1441 1443 if os.path.isdir(name):
1442 1444 return
1443 1445 parent = os.path.dirname(os.path.abspath(name))
1444 1446 if parent != name:
1445 1447 ensuredirs(parent, mode, notindexed)
1446 1448 try:
1447 1449 makedir(name, notindexed)
1448 1450 except OSError as err:
1449 1451 if err.errno == errno.EEXIST and os.path.isdir(name):
1450 1452 # someone else seems to have won a directory creation race
1451 1453 return
1452 1454 raise
1453 1455 if mode is not None:
1454 1456 os.chmod(name, mode)
1455 1457
1456 1458 def readfile(path):
1457 1459 with open(path, 'rb') as fp:
1458 1460 return fp.read()
1459 1461
1460 1462 def writefile(path, text):
1461 1463 with open(path, 'wb') as fp:
1462 1464 fp.write(text)
1463 1465
1464 1466 def appendfile(path, text):
1465 1467 with open(path, 'ab') as fp:
1466 1468 fp.write(text)
1467 1469
1468 1470 class chunkbuffer(object):
1469 1471 """Allow arbitrary sized chunks of data to be efficiently read from an
1470 1472 iterator over chunks of arbitrary size."""
1471 1473
1472 1474 def __init__(self, in_iter):
1473 1475 """in_iter is the iterator that's iterating over the input chunks.
1474 1476 targetsize is how big a buffer to try to maintain."""
1475 1477 def splitbig(chunks):
1476 1478 for chunk in chunks:
1477 1479 if len(chunk) > 2**20:
1478 1480 pos = 0
1479 1481 while pos < len(chunk):
1480 1482 end = pos + 2 ** 18
1481 1483 yield chunk[pos:end]
1482 1484 pos = end
1483 1485 else:
1484 1486 yield chunk
1485 1487 self.iter = splitbig(in_iter)
1486 1488 self._queue = collections.deque()
1487 1489 self._chunkoffset = 0
1488 1490
1489 1491 def read(self, l=None):
1490 1492 """Read L bytes of data from the iterator of chunks of data.
1491 1493 Returns less than L bytes if the iterator runs dry.
1492 1494
1493 1495 If size parameter is omitted, read everything"""
1494 1496 if l is None:
1495 1497 return ''.join(self.iter)
1496 1498
1497 1499 left = l
1498 1500 buf = []
1499 1501 queue = self._queue
1500 1502 while left > 0:
1501 1503 # refill the queue
1502 1504 if not queue:
1503 1505 target = 2**18
1504 1506 for chunk in self.iter:
1505 1507 queue.append(chunk)
1506 1508 target -= len(chunk)
1507 1509 if target <= 0:
1508 1510 break
1509 1511 if not queue:
1510 1512 break
1511 1513
1512 1514 # The easy way to do this would be to queue.popleft(), modify the
1513 1515 # chunk (if necessary), then queue.appendleft(). However, for cases
1514 1516 # where we read partial chunk content, this incurs 2 dequeue
1515 1517 # mutations and creates a new str for the remaining chunk in the
1516 1518 # queue. Our code below avoids this overhead.
1517 1519
1518 1520 chunk = queue[0]
1519 1521 chunkl = len(chunk)
1520 1522 offset = self._chunkoffset
1521 1523
1522 1524 # Use full chunk.
1523 1525 if offset == 0 and left >= chunkl:
1524 1526 left -= chunkl
1525 1527 queue.popleft()
1526 1528 buf.append(chunk)
1527 1529 # self._chunkoffset remains at 0.
1528 1530 continue
1529 1531
1530 1532 chunkremaining = chunkl - offset
1531 1533
1532 1534 # Use all of unconsumed part of chunk.
1533 1535 if left >= chunkremaining:
1534 1536 left -= chunkremaining
1535 1537 queue.popleft()
1536 1538 # offset == 0 is enabled by block above, so this won't merely
1537 1539 # copy via ``chunk[0:]``.
1538 1540 buf.append(chunk[offset:])
1539 1541 self._chunkoffset = 0
1540 1542
1541 1543 # Partial chunk needed.
1542 1544 else:
1543 1545 buf.append(chunk[offset:offset + left])
1544 1546 self._chunkoffset += left
1545 1547 left -= chunkremaining
1546 1548
1547 1549 return ''.join(buf)
1548 1550
1549 1551 def filechunkiter(f, size=65536, limit=None):
1550 1552 """Create a generator that produces the data in the file size
1551 1553 (default 65536) bytes at a time, up to optional limit (default is
1552 1554 to read all data). Chunks may be less than size bytes if the
1553 1555 chunk is the last chunk in the file, or the file is a socket or
1554 1556 some other type of file that sometimes reads less data than is
1555 1557 requested."""
1556 1558 assert size >= 0
1557 1559 assert limit is None or limit >= 0
1558 1560 while True:
1559 1561 if limit is None:
1560 1562 nbytes = size
1561 1563 else:
1562 1564 nbytes = min(limit, size)
1563 1565 s = nbytes and f.read(nbytes)
1564 1566 if not s:
1565 1567 break
1566 1568 if limit:
1567 1569 limit -= len(s)
1568 1570 yield s
1569 1571
1570 1572 def makedate(timestamp=None):
1571 1573 '''Return a unix timestamp (or the current time) as a (unixtime,
1572 1574 offset) tuple based off the local timezone.'''
1573 1575 if timestamp is None:
1574 1576 timestamp = time.time()
1575 1577 if timestamp < 0:
1576 1578 hint = _("check your clock")
1577 1579 raise Abort(_("negative timestamp: %d") % timestamp, hint=hint)
1578 1580 delta = (datetime.datetime.utcfromtimestamp(timestamp) -
1579 1581 datetime.datetime.fromtimestamp(timestamp))
1580 1582 tz = delta.days * 86400 + delta.seconds
1581 1583 return timestamp, tz
1582 1584
1583 1585 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1584 1586 """represent a (unixtime, offset) tuple as a localized time.
1585 1587 unixtime is seconds since the epoch, and offset is the time zone's
1586 1588 number of seconds away from UTC.
1587 1589
1588 1590 >>> datestr((0, 0))
1589 1591 'Thu Jan 01 00:00:00 1970 +0000'
1590 1592 >>> datestr((42, 0))
1591 1593 'Thu Jan 01 00:00:42 1970 +0000'
1592 1594 >>> datestr((-42, 0))
1593 1595 'Wed Dec 31 23:59:18 1969 +0000'
1594 1596 >>> datestr((0x7fffffff, 0))
1595 1597 'Tue Jan 19 03:14:07 2038 +0000'
1596 1598 >>> datestr((-0x80000000, 0))
1597 1599 'Fri Dec 13 20:45:52 1901 +0000'
1598 1600 """
1599 1601 t, tz = date or makedate()
1600 1602 if "%1" in format or "%2" in format or "%z" in format:
1601 1603 sign = (tz > 0) and "-" or "+"
1602 1604 minutes = abs(tz) // 60
1603 1605 q, r = divmod(minutes, 60)
1604 1606 format = format.replace("%z", "%1%2")
1605 1607 format = format.replace("%1", "%c%02d" % (sign, q))
1606 1608 format = format.replace("%2", "%02d" % r)
1607 1609 d = t - tz
1608 1610 if d > 0x7fffffff:
1609 1611 d = 0x7fffffff
1610 1612 elif d < -0x80000000:
1611 1613 d = -0x80000000
1612 1614 # Never use time.gmtime() and datetime.datetime.fromtimestamp()
1613 1615 # because they use the gmtime() system call which is buggy on Windows
1614 1616 # for negative values.
1615 1617 t = datetime.datetime(1970, 1, 1) + datetime.timedelta(seconds=d)
1616 1618 s = t.strftime(format)
1617 1619 return s
1618 1620
1619 1621 def shortdate(date=None):
1620 1622 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1621 1623 return datestr(date, format='%Y-%m-%d')
1622 1624
1623 1625 def parsetimezone(tz):
1624 1626 """parse a timezone string and return an offset integer"""
1625 1627 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1626 1628 sign = (tz[0] == "+") and 1 or -1
1627 1629 hours = int(tz[1:3])
1628 1630 minutes = int(tz[3:5])
1629 1631 return -sign * (hours * 60 + minutes) * 60
1630 1632 if tz == "GMT" or tz == "UTC":
1631 1633 return 0
1632 1634 return None
1633 1635
1634 1636 def strdate(string, format, defaults=[]):
1635 1637 """parse a localized time string and return a (unixtime, offset) tuple.
1636 1638 if the string cannot be parsed, ValueError is raised."""
1637 1639 # NOTE: unixtime = localunixtime + offset
1638 1640 offset, date = parsetimezone(string.split()[-1]), string
1639 1641 if offset is not None:
1640 1642 date = " ".join(string.split()[:-1])
1641 1643
1642 1644 # add missing elements from defaults
1643 1645 usenow = False # default to using biased defaults
1644 1646 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
1645 1647 found = [True for p in part if ("%"+p) in format]
1646 1648 if not found:
1647 1649 date += "@" + defaults[part][usenow]
1648 1650 format += "@%" + part[0]
1649 1651 else:
1650 1652 # We've found a specific time element, less specific time
1651 1653 # elements are relative to today
1652 1654 usenow = True
1653 1655
1654 1656 timetuple = time.strptime(date, format)
1655 1657 localunixtime = int(calendar.timegm(timetuple))
1656 1658 if offset is None:
1657 1659 # local timezone
1658 1660 unixtime = int(time.mktime(timetuple))
1659 1661 offset = unixtime - localunixtime
1660 1662 else:
1661 1663 unixtime = localunixtime + offset
1662 1664 return unixtime, offset
1663 1665
1664 1666 def parsedate(date, formats=None, bias=None):
1665 1667 """parse a localized date/time and return a (unixtime, offset) tuple.
1666 1668
1667 1669 The date may be a "unixtime offset" string or in one of the specified
1668 1670 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1669 1671
1670 1672 >>> parsedate(' today ') == parsedate(\
1671 1673 datetime.date.today().strftime('%b %d'))
1672 1674 True
1673 1675 >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
1674 1676 datetime.timedelta(days=1)\
1675 1677 ).strftime('%b %d'))
1676 1678 True
1677 1679 >>> now, tz = makedate()
1678 1680 >>> strnow, strtz = parsedate('now')
1679 1681 >>> (strnow - now) < 1
1680 1682 True
1681 1683 >>> tz == strtz
1682 1684 True
1683 1685 """
1684 1686 if bias is None:
1685 1687 bias = {}
1686 1688 if not date:
1687 1689 return 0, 0
1688 1690 if isinstance(date, tuple) and len(date) == 2:
1689 1691 return date
1690 1692 if not formats:
1691 1693 formats = defaultdateformats
1692 1694 date = date.strip()
1693 1695
1694 1696 if date == 'now' or date == _('now'):
1695 1697 return makedate()
1696 1698 if date == 'today' or date == _('today'):
1697 1699 date = datetime.date.today().strftime('%b %d')
1698 1700 elif date == 'yesterday' or date == _('yesterday'):
1699 1701 date = (datetime.date.today() -
1700 1702 datetime.timedelta(days=1)).strftime('%b %d')
1701 1703
1702 1704 try:
1703 1705 when, offset = map(int, date.split(' '))
1704 1706 except ValueError:
1705 1707 # fill out defaults
1706 1708 now = makedate()
1707 1709 defaults = {}
1708 1710 for part in ("d", "mb", "yY", "HI", "M", "S"):
1709 1711 # this piece is for rounding the specific end of unknowns
1710 1712 b = bias.get(part)
1711 1713 if b is None:
1712 1714 if part[0] in "HMS":
1713 1715 b = "00"
1714 1716 else:
1715 1717 b = "0"
1716 1718
1717 1719 # this piece is for matching the generic end to today's date
1718 1720 n = datestr(now, "%" + part[0])
1719 1721
1720 1722 defaults[part] = (b, n)
1721 1723
1722 1724 for format in formats:
1723 1725 try:
1724 1726 when, offset = strdate(date, format, defaults)
1725 1727 except (ValueError, OverflowError):
1726 1728 pass
1727 1729 else:
1728 1730 break
1729 1731 else:
1730 1732 raise Abort(_('invalid date: %r') % date)
1731 1733 # validate explicit (probably user-specified) date and
1732 1734 # time zone offset. values must fit in signed 32 bits for
1733 1735 # current 32-bit linux runtimes. timezones go from UTC-12
1734 1736 # to UTC+14
1735 1737 if when < -0x80000000 or when > 0x7fffffff:
1736 1738 raise Abort(_('date exceeds 32 bits: %d') % when)
1737 1739 if offset < -50400 or offset > 43200:
1738 1740 raise Abort(_('impossible time zone offset: %d') % offset)
1739 1741 return when, offset
1740 1742
1741 1743 def matchdate(date):
1742 1744 """Return a function that matches a given date match specifier
1743 1745
1744 1746 Formats include:
1745 1747
1746 1748 '{date}' match a given date to the accuracy provided
1747 1749
1748 1750 '<{date}' on or before a given date
1749 1751
1750 1752 '>{date}' on or after a given date
1751 1753
1752 1754 >>> p1 = parsedate("10:29:59")
1753 1755 >>> p2 = parsedate("10:30:00")
1754 1756 >>> p3 = parsedate("10:30:59")
1755 1757 >>> p4 = parsedate("10:31:00")
1756 1758 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1757 1759 >>> f = matchdate("10:30")
1758 1760 >>> f(p1[0])
1759 1761 False
1760 1762 >>> f(p2[0])
1761 1763 True
1762 1764 >>> f(p3[0])
1763 1765 True
1764 1766 >>> f(p4[0])
1765 1767 False
1766 1768 >>> f(p5[0])
1767 1769 False
1768 1770 """
1769 1771
1770 1772 def lower(date):
1771 1773 d = {'mb': "1", 'd': "1"}
1772 1774 return parsedate(date, extendeddateformats, d)[0]
1773 1775
1774 1776 def upper(date):
1775 1777 d = {'mb': "12", 'HI': "23", 'M': "59", 'S': "59"}
1776 1778 for days in ("31", "30", "29"):
1777 1779 try:
1778 1780 d["d"] = days
1779 1781 return parsedate(date, extendeddateformats, d)[0]
1780 1782 except Abort:
1781 1783 pass
1782 1784 d["d"] = "28"
1783 1785 return parsedate(date, extendeddateformats, d)[0]
1784 1786
1785 1787 date = date.strip()
1786 1788
1787 1789 if not date:
1788 1790 raise Abort(_("dates cannot consist entirely of whitespace"))
1789 1791 elif date[0] == "<":
1790 1792 if not date[1:]:
1791 1793 raise Abort(_("invalid day spec, use '<DATE'"))
1792 1794 when = upper(date[1:])
1793 1795 return lambda x: x <= when
1794 1796 elif date[0] == ">":
1795 1797 if not date[1:]:
1796 1798 raise Abort(_("invalid day spec, use '>DATE'"))
1797 1799 when = lower(date[1:])
1798 1800 return lambda x: x >= when
1799 1801 elif date[0] == "-":
1800 1802 try:
1801 1803 days = int(date[1:])
1802 1804 except ValueError:
1803 1805 raise Abort(_("invalid day spec: %s") % date[1:])
1804 1806 if days < 0:
1805 1807 raise Abort(_('%s must be nonnegative (see "hg help dates")')
1806 1808 % date[1:])
1807 1809 when = makedate()[0] - days * 3600 * 24
1808 1810 return lambda x: x >= when
1809 1811 elif " to " in date:
1810 1812 a, b = date.split(" to ")
1811 1813 start, stop = lower(a), upper(b)
1812 1814 return lambda x: x >= start and x <= stop
1813 1815 else:
1814 1816 start, stop = lower(date), upper(date)
1815 1817 return lambda x: x >= start and x <= stop
1816 1818
1817 1819 def stringmatcher(pattern):
1818 1820 """
1819 1821 accepts a string, possibly starting with 're:' or 'literal:' prefix.
1820 1822 returns the matcher name, pattern, and matcher function.
1821 1823 missing or unknown prefixes are treated as literal matches.
1822 1824
1823 1825 helper for tests:
1824 1826 >>> def test(pattern, *tests):
1825 1827 ... kind, pattern, matcher = stringmatcher(pattern)
1826 1828 ... return (kind, pattern, [bool(matcher(t)) for t in tests])
1827 1829
1828 1830 exact matching (no prefix):
1829 1831 >>> test('abcdefg', 'abc', 'def', 'abcdefg')
1830 1832 ('literal', 'abcdefg', [False, False, True])
1831 1833
1832 1834 regex matching ('re:' prefix)
1833 1835 >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
1834 1836 ('re', 'a.+b', [False, False, True])
1835 1837
1836 1838 force exact matches ('literal:' prefix)
1837 1839 >>> test('literal:re:foobar', 'foobar', 're:foobar')
1838 1840 ('literal', 're:foobar', [False, True])
1839 1841
1840 1842 unknown prefixes are ignored and treated as literals
1841 1843 >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
1842 1844 ('literal', 'foo:bar', [False, False, True])
1843 1845 """
1844 1846 if pattern.startswith('re:'):
1845 1847 pattern = pattern[3:]
1846 1848 try:
1847 1849 regex = remod.compile(pattern)
1848 1850 except remod.error as e:
1849 1851 raise error.ParseError(_('invalid regular expression: %s')
1850 1852 % e)
1851 1853 return 're', pattern, regex.search
1852 1854 elif pattern.startswith('literal:'):
1853 1855 pattern = pattern[8:]
1854 1856 return 'literal', pattern, pattern.__eq__
1855 1857
1856 1858 def shortuser(user):
1857 1859 """Return a short representation of a user name or email address."""
1858 1860 f = user.find('@')
1859 1861 if f >= 0:
1860 1862 user = user[:f]
1861 1863 f = user.find('<')
1862 1864 if f >= 0:
1863 1865 user = user[f + 1:]
1864 1866 f = user.find(' ')
1865 1867 if f >= 0:
1866 1868 user = user[:f]
1867 1869 f = user.find('.')
1868 1870 if f >= 0:
1869 1871 user = user[:f]
1870 1872 return user
1871 1873
1872 1874 def emailuser(user):
1873 1875 """Return the user portion of an email address."""
1874 1876 f = user.find('@')
1875 1877 if f >= 0:
1876 1878 user = user[:f]
1877 1879 f = user.find('<')
1878 1880 if f >= 0:
1879 1881 user = user[f + 1:]
1880 1882 return user
1881 1883
1882 1884 def email(author):
1883 1885 '''get email of author.'''
1884 1886 r = author.find('>')
1885 1887 if r == -1:
1886 1888 r = None
1887 1889 return author[author.find('<') + 1:r]
1888 1890
1889 1891 def ellipsis(text, maxlength=400):
1890 1892 """Trim string to at most maxlength (default: 400) columns in display."""
1891 1893 return encoding.trim(text, maxlength, ellipsis='...')
1892 1894
1893 1895 def unitcountfn(*unittable):
1894 1896 '''return a function that renders a readable count of some quantity'''
1895 1897
1896 1898 def go(count):
1897 1899 for multiplier, divisor, format in unittable:
1898 1900 if count >= divisor * multiplier:
1899 1901 return format % (count / float(divisor))
1900 1902 return unittable[-1][2] % count
1901 1903
1902 1904 return go
1903 1905
1904 1906 bytecount = unitcountfn(
1905 1907 (100, 1 << 30, _('%.0f GB')),
1906 1908 (10, 1 << 30, _('%.1f GB')),
1907 1909 (1, 1 << 30, _('%.2f GB')),
1908 1910 (100, 1 << 20, _('%.0f MB')),
1909 1911 (10, 1 << 20, _('%.1f MB')),
1910 1912 (1, 1 << 20, _('%.2f MB')),
1911 1913 (100, 1 << 10, _('%.0f KB')),
1912 1914 (10, 1 << 10, _('%.1f KB')),
1913 1915 (1, 1 << 10, _('%.2f KB')),
1914 1916 (1, 1, _('%.0f bytes')),
1915 1917 )
1916 1918
1917 1919 def uirepr(s):
1918 1920 # Avoid double backslash in Windows path repr()
1919 1921 return repr(s).replace('\\\\', '\\')
1920 1922
1921 1923 # delay import of textwrap
1922 1924 def MBTextWrapper(**kwargs):
1923 1925 class tw(textwrap.TextWrapper):
1924 1926 """
1925 1927 Extend TextWrapper for width-awareness.
1926 1928
1927 1929 Neither number of 'bytes' in any encoding nor 'characters' is
1928 1930 appropriate to calculate terminal columns for specified string.
1929 1931
1930 1932 Original TextWrapper implementation uses built-in 'len()' directly,
1931 1933 so overriding is needed to use width information of each characters.
1932 1934
1933 1935 In addition, characters classified into 'ambiguous' width are
1934 1936 treated as wide in East Asian area, but as narrow in other.
1935 1937
1936 1938 This requires use decision to determine width of such characters.
1937 1939 """
1938 1940 def _cutdown(self, ucstr, space_left):
1939 1941 l = 0
1940 1942 colwidth = encoding.ucolwidth
1941 1943 for i in xrange(len(ucstr)):
1942 1944 l += colwidth(ucstr[i])
1943 1945 if space_left < l:
1944 1946 return (ucstr[:i], ucstr[i:])
1945 1947 return ucstr, ''
1946 1948
1947 1949 # overriding of base class
1948 1950 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1949 1951 space_left = max(width - cur_len, 1)
1950 1952
1951 1953 if self.break_long_words:
1952 1954 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1953 1955 cur_line.append(cut)
1954 1956 reversed_chunks[-1] = res
1955 1957 elif not cur_line:
1956 1958 cur_line.append(reversed_chunks.pop())
1957 1959
1958 1960 # this overriding code is imported from TextWrapper of Python 2.6
1959 1961 # to calculate columns of string by 'encoding.ucolwidth()'
1960 1962 def _wrap_chunks(self, chunks):
1961 1963 colwidth = encoding.ucolwidth
1962 1964
1963 1965 lines = []
1964 1966 if self.width <= 0:
1965 1967 raise ValueError("invalid width %r (must be > 0)" % self.width)
1966 1968
1967 1969 # Arrange in reverse order so items can be efficiently popped
1968 1970 # from a stack of chucks.
1969 1971 chunks.reverse()
1970 1972
1971 1973 while chunks:
1972 1974
1973 1975 # Start the list of chunks that will make up the current line.
1974 1976 # cur_len is just the length of all the chunks in cur_line.
1975 1977 cur_line = []
1976 1978 cur_len = 0
1977 1979
1978 1980 # Figure out which static string will prefix this line.
1979 1981 if lines:
1980 1982 indent = self.subsequent_indent
1981 1983 else:
1982 1984 indent = self.initial_indent
1983 1985
1984 1986 # Maximum width for this line.
1985 1987 width = self.width - len(indent)
1986 1988
1987 1989 # First chunk on line is whitespace -- drop it, unless this
1988 1990 # is the very beginning of the text (i.e. no lines started yet).
1989 1991 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1990 1992 del chunks[-1]
1991 1993
1992 1994 while chunks:
1993 1995 l = colwidth(chunks[-1])
1994 1996
1995 1997 # Can at least squeeze this chunk onto the current line.
1996 1998 if cur_len + l <= width:
1997 1999 cur_line.append(chunks.pop())
1998 2000 cur_len += l
1999 2001
2000 2002 # Nope, this line is full.
2001 2003 else:
2002 2004 break
2003 2005
2004 2006 # The current line is full, and the next chunk is too big to
2005 2007 # fit on *any* line (not just this one).
2006 2008 if chunks and colwidth(chunks[-1]) > width:
2007 2009 self._handle_long_word(chunks, cur_line, cur_len, width)
2008 2010
2009 2011 # If the last chunk on this line is all whitespace, drop it.
2010 2012 if (self.drop_whitespace and
2011 2013 cur_line and cur_line[-1].strip() == ''):
2012 2014 del cur_line[-1]
2013 2015
2014 2016 # Convert current line back to a string and store it in list
2015 2017 # of all lines (return value).
2016 2018 if cur_line:
2017 2019 lines.append(indent + ''.join(cur_line))
2018 2020
2019 2021 return lines
2020 2022
2021 2023 global MBTextWrapper
2022 2024 MBTextWrapper = tw
2023 2025 return tw(**kwargs)
2024 2026
2025 2027 def wrap(line, width, initindent='', hangindent=''):
2026 2028 maxindent = max(len(hangindent), len(initindent))
2027 2029 if width <= maxindent:
2028 2030 # adjust for weird terminal size
2029 2031 width = max(78, maxindent + 1)
2030 2032 line = line.decode(encoding.encoding, encoding.encodingmode)
2031 2033 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
2032 2034 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
2033 2035 wrapper = MBTextWrapper(width=width,
2034 2036 initial_indent=initindent,
2035 2037 subsequent_indent=hangindent)
2036 2038 return wrapper.fill(line).encode(encoding.encoding)
2037 2039
2038 2040 def iterlines(iterator):
2039 2041 for chunk in iterator:
2040 2042 for line in chunk.splitlines():
2041 2043 yield line
2042 2044
2043 2045 def expandpath(path):
2044 2046 return os.path.expanduser(os.path.expandvars(path))
2045 2047
2046 2048 def hgcmd():
2047 2049 """Return the command used to execute current hg
2048 2050
2049 2051 This is different from hgexecutable() because on Windows we want
2050 2052 to avoid things opening new shell windows like batch files, so we
2051 2053 get either the python call or current executable.
2052 2054 """
2053 2055 if mainfrozen():
2054 2056 if getattr(sys, 'frozen', None) == 'macosx_app':
2055 2057 # Env variable set by py2app
2056 2058 return [os.environ['EXECUTABLEPATH']]
2057 2059 else:
2058 2060 return [sys.executable]
2059 2061 return gethgcmd()
2060 2062
2061 2063 def rundetached(args, condfn):
2062 2064 """Execute the argument list in a detached process.
2063 2065
2064 2066 condfn is a callable which is called repeatedly and should return
2065 2067 True once the child process is known to have started successfully.
2066 2068 At this point, the child process PID is returned. If the child
2067 2069 process fails to start or finishes before condfn() evaluates to
2068 2070 True, return -1.
2069 2071 """
2070 2072 # Windows case is easier because the child process is either
2071 2073 # successfully starting and validating the condition or exiting
2072 2074 # on failure. We just poll on its PID. On Unix, if the child
2073 2075 # process fails to start, it will be left in a zombie state until
2074 2076 # the parent wait on it, which we cannot do since we expect a long
2075 2077 # running process on success. Instead we listen for SIGCHLD telling
2076 2078 # us our child process terminated.
2077 2079 terminated = set()
2078 2080 def handler(signum, frame):
2079 2081 terminated.add(os.wait())
2080 2082 prevhandler = None
2081 2083 SIGCHLD = getattr(signal, 'SIGCHLD', None)
2082 2084 if SIGCHLD is not None:
2083 2085 prevhandler = signal.signal(SIGCHLD, handler)
2084 2086 try:
2085 2087 pid = spawndetached(args)
2086 2088 while not condfn():
2087 2089 if ((pid in terminated or not testpid(pid))
2088 2090 and not condfn()):
2089 2091 return -1
2090 2092 time.sleep(0.1)
2091 2093 return pid
2092 2094 finally:
2093 2095 if prevhandler is not None:
2094 2096 signal.signal(signal.SIGCHLD, prevhandler)
2095 2097
2096 2098 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
2097 2099 """Return the result of interpolating items in the mapping into string s.
2098 2100
2099 2101 prefix is a single character string, or a two character string with
2100 2102 a backslash as the first character if the prefix needs to be escaped in
2101 2103 a regular expression.
2102 2104
2103 2105 fn is an optional function that will be applied to the replacement text
2104 2106 just before replacement.
2105 2107
2106 2108 escape_prefix is an optional flag that allows using doubled prefix for
2107 2109 its escaping.
2108 2110 """
2109 2111 fn = fn or (lambda s: s)
2110 2112 patterns = '|'.join(mapping.keys())
2111 2113 if escape_prefix:
2112 2114 patterns += '|' + prefix
2113 2115 if len(prefix) > 1:
2114 2116 prefix_char = prefix[1:]
2115 2117 else:
2116 2118 prefix_char = prefix
2117 2119 mapping[prefix_char] = prefix_char
2118 2120 r = remod.compile(r'%s(%s)' % (prefix, patterns))
2119 2121 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
2120 2122
2121 2123 def getport(port):
2122 2124 """Return the port for a given network service.
2123 2125
2124 2126 If port is an integer, it's returned as is. If it's a string, it's
2125 2127 looked up using socket.getservbyname(). If there's no matching
2126 2128 service, error.Abort is raised.
2127 2129 """
2128 2130 try:
2129 2131 return int(port)
2130 2132 except ValueError:
2131 2133 pass
2132 2134
2133 2135 try:
2134 2136 return socket.getservbyname(port)
2135 2137 except socket.error:
2136 2138 raise Abort(_("no port number associated with service '%s'") % port)
2137 2139
2138 2140 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
2139 2141 '0': False, 'no': False, 'false': False, 'off': False,
2140 2142 'never': False}
2141 2143
2142 2144 def parsebool(s):
2143 2145 """Parse s into a boolean.
2144 2146
2145 2147 If s is not a valid boolean, returns None.
2146 2148 """
2147 2149 return _booleans.get(s.lower(), None)
2148 2150
2149 2151 _hexdig = '0123456789ABCDEFabcdef'
2150 2152 _hextochr = dict((a + b, chr(int(a + b, 16)))
2151 2153 for a in _hexdig for b in _hexdig)
2152 2154
2153 2155 def _urlunquote(s):
2154 2156 """Decode HTTP/HTML % encoding.
2155 2157
2156 2158 >>> _urlunquote('abc%20def')
2157 2159 'abc def'
2158 2160 """
2159 2161 res = s.split('%')
2160 2162 # fastpath
2161 2163 if len(res) == 1:
2162 2164 return s
2163 2165 s = res[0]
2164 2166 for item in res[1:]:
2165 2167 try:
2166 2168 s += _hextochr[item[:2]] + item[2:]
2167 2169 except KeyError:
2168 2170 s += '%' + item
2169 2171 except UnicodeDecodeError:
2170 2172 s += unichr(int(item[:2], 16)) + item[2:]
2171 2173 return s
2172 2174
2173 2175 class url(object):
2174 2176 r"""Reliable URL parser.
2175 2177
2176 2178 This parses URLs and provides attributes for the following
2177 2179 components:
2178 2180
2179 2181 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
2180 2182
2181 2183 Missing components are set to None. The only exception is
2182 2184 fragment, which is set to '' if present but empty.
2183 2185
2184 2186 If parsefragment is False, fragment is included in query. If
2185 2187 parsequery is False, query is included in path. If both are
2186 2188 False, both fragment and query are included in path.
2187 2189
2188 2190 See http://www.ietf.org/rfc/rfc2396.txt for more information.
2189 2191
2190 2192 Note that for backward compatibility reasons, bundle URLs do not
2191 2193 take host names. That means 'bundle://../' has a path of '../'.
2192 2194
2193 2195 Examples:
2194 2196
2195 2197 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
2196 2198 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
2197 2199 >>> url('ssh://[::1]:2200//home/joe/repo')
2198 2200 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
2199 2201 >>> url('file:///home/joe/repo')
2200 2202 <url scheme: 'file', path: '/home/joe/repo'>
2201 2203 >>> url('file:///c:/temp/foo/')
2202 2204 <url scheme: 'file', path: 'c:/temp/foo/'>
2203 2205 >>> url('bundle:foo')
2204 2206 <url scheme: 'bundle', path: 'foo'>
2205 2207 >>> url('bundle://../foo')
2206 2208 <url scheme: 'bundle', path: '../foo'>
2207 2209 >>> url(r'c:\foo\bar')
2208 2210 <url path: 'c:\\foo\\bar'>
2209 2211 >>> url(r'\\blah\blah\blah')
2210 2212 <url path: '\\\\blah\\blah\\blah'>
2211 2213 >>> url(r'\\blah\blah\blah#baz')
2212 2214 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
2213 2215 >>> url(r'file:///C:\users\me')
2214 2216 <url scheme: 'file', path: 'C:\\users\\me'>
2215 2217
2216 2218 Authentication credentials:
2217 2219
2218 2220 >>> url('ssh://joe:xyz@x/repo')
2219 2221 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
2220 2222 >>> url('ssh://joe@x/repo')
2221 2223 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
2222 2224
2223 2225 Query strings and fragments:
2224 2226
2225 2227 >>> url('http://host/a?b#c')
2226 2228 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
2227 2229 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
2228 2230 <url scheme: 'http', host: 'host', path: 'a?b#c'>
2229 2231 """
2230 2232
2231 2233 _safechars = "!~*'()+"
2232 2234 _safepchars = "/!~*'()+:\\"
2233 2235 _matchscheme = remod.compile(r'^[a-zA-Z0-9+.\-]+:').match
2234 2236
2235 2237 def __init__(self, path, parsequery=True, parsefragment=True):
2236 2238 # We slowly chomp away at path until we have only the path left
2237 2239 self.scheme = self.user = self.passwd = self.host = None
2238 2240 self.port = self.path = self.query = self.fragment = None
2239 2241 self._localpath = True
2240 2242 self._hostport = ''
2241 2243 self._origpath = path
2242 2244
2243 2245 if parsefragment and '#' in path:
2244 2246 path, self.fragment = path.split('#', 1)
2245 2247 if not path:
2246 2248 path = None
2247 2249
2248 2250 # special case for Windows drive letters and UNC paths
2249 2251 if hasdriveletter(path) or path.startswith(r'\\'):
2250 2252 self.path = path
2251 2253 return
2252 2254
2253 2255 # For compatibility reasons, we can't handle bundle paths as
2254 2256 # normal URLS
2255 2257 if path.startswith('bundle:'):
2256 2258 self.scheme = 'bundle'
2257 2259 path = path[7:]
2258 2260 if path.startswith('//'):
2259 2261 path = path[2:]
2260 2262 self.path = path
2261 2263 return
2262 2264
2263 2265 if self._matchscheme(path):
2264 2266 parts = path.split(':', 1)
2265 2267 if parts[0]:
2266 2268 self.scheme, path = parts
2267 2269 self._localpath = False
2268 2270
2269 2271 if not path:
2270 2272 path = None
2271 2273 if self._localpath:
2272 2274 self.path = ''
2273 2275 return
2274 2276 else:
2275 2277 if self._localpath:
2276 2278 self.path = path
2277 2279 return
2278 2280
2279 2281 if parsequery and '?' in path:
2280 2282 path, self.query = path.split('?', 1)
2281 2283 if not path:
2282 2284 path = None
2283 2285 if not self.query:
2284 2286 self.query = None
2285 2287
2286 2288 # // is required to specify a host/authority
2287 2289 if path and path.startswith('//'):
2288 2290 parts = path[2:].split('/', 1)
2289 2291 if len(parts) > 1:
2290 2292 self.host, path = parts
2291 2293 else:
2292 2294 self.host = parts[0]
2293 2295 path = None
2294 2296 if not self.host:
2295 2297 self.host = None
2296 2298 # path of file:///d is /d
2297 2299 # path of file:///d:/ is d:/, not /d:/
2298 2300 if path and not hasdriveletter(path):
2299 2301 path = '/' + path
2300 2302
2301 2303 if self.host and '@' in self.host:
2302 2304 self.user, self.host = self.host.rsplit('@', 1)
2303 2305 if ':' in self.user:
2304 2306 self.user, self.passwd = self.user.split(':', 1)
2305 2307 if not self.host:
2306 2308 self.host = None
2307 2309
2308 2310 # Don't split on colons in IPv6 addresses without ports
2309 2311 if (self.host and ':' in self.host and
2310 2312 not (self.host.startswith('[') and self.host.endswith(']'))):
2311 2313 self._hostport = self.host
2312 2314 self.host, self.port = self.host.rsplit(':', 1)
2313 2315 if not self.host:
2314 2316 self.host = None
2315 2317
2316 2318 if (self.host and self.scheme == 'file' and
2317 2319 self.host not in ('localhost', '127.0.0.1', '[::1]')):
2318 2320 raise Abort(_('file:// URLs can only refer to localhost'))
2319 2321
2320 2322 self.path = path
2321 2323
2322 2324 # leave the query string escaped
2323 2325 for a in ('user', 'passwd', 'host', 'port',
2324 2326 'path', 'fragment'):
2325 2327 v = getattr(self, a)
2326 2328 if v is not None:
2327 2329 setattr(self, a, _urlunquote(v))
2328 2330
2329 2331 def __repr__(self):
2330 2332 attrs = []
2331 2333 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
2332 2334 'query', 'fragment'):
2333 2335 v = getattr(self, a)
2334 2336 if v is not None:
2335 2337 attrs.append('%s: %r' % (a, v))
2336 2338 return '<url %s>' % ', '.join(attrs)
2337 2339
2338 2340 def __str__(self):
2339 2341 r"""Join the URL's components back into a URL string.
2340 2342
2341 2343 Examples:
2342 2344
2343 2345 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
2344 2346 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
2345 2347 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
2346 2348 'http://user:pw@host:80/?foo=bar&baz=42'
2347 2349 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
2348 2350 'http://user:pw@host:80/?foo=bar%3dbaz'
2349 2351 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
2350 2352 'ssh://user:pw@[::1]:2200//home/joe#'
2351 2353 >>> str(url('http://localhost:80//'))
2352 2354 'http://localhost:80//'
2353 2355 >>> str(url('http://localhost:80/'))
2354 2356 'http://localhost:80/'
2355 2357 >>> str(url('http://localhost:80'))
2356 2358 'http://localhost:80/'
2357 2359 >>> str(url('bundle:foo'))
2358 2360 'bundle:foo'
2359 2361 >>> str(url('bundle://../foo'))
2360 2362 'bundle:../foo'
2361 2363 >>> str(url('path'))
2362 2364 'path'
2363 2365 >>> str(url('file:///tmp/foo/bar'))
2364 2366 'file:///tmp/foo/bar'
2365 2367 >>> str(url('file:///c:/tmp/foo/bar'))
2366 2368 'file:///c:/tmp/foo/bar'
2367 2369 >>> print url(r'bundle:foo\bar')
2368 2370 bundle:foo\bar
2369 2371 >>> print url(r'file:///D:\data\hg')
2370 2372 file:///D:\data\hg
2371 2373 """
2372 2374 if self._localpath:
2373 2375 s = self.path
2374 2376 if self.scheme == 'bundle':
2375 2377 s = 'bundle:' + s
2376 2378 if self.fragment:
2377 2379 s += '#' + self.fragment
2378 2380 return s
2379 2381
2380 2382 s = self.scheme + ':'
2381 2383 if self.user or self.passwd or self.host:
2382 2384 s += '//'
2383 2385 elif self.scheme and (not self.path or self.path.startswith('/')
2384 2386 or hasdriveletter(self.path)):
2385 2387 s += '//'
2386 2388 if hasdriveletter(self.path):
2387 2389 s += '/'
2388 2390 if self.user:
2389 2391 s += urllib.quote(self.user, safe=self._safechars)
2390 2392 if self.passwd:
2391 2393 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
2392 2394 if self.user or self.passwd:
2393 2395 s += '@'
2394 2396 if self.host:
2395 2397 if not (self.host.startswith('[') and self.host.endswith(']')):
2396 2398 s += urllib.quote(self.host)
2397 2399 else:
2398 2400 s += self.host
2399 2401 if self.port:
2400 2402 s += ':' + urllib.quote(self.port)
2401 2403 if self.host:
2402 2404 s += '/'
2403 2405 if self.path:
2404 2406 # TODO: similar to the query string, we should not unescape the
2405 2407 # path when we store it, the path might contain '%2f' = '/',
2406 2408 # which we should *not* escape.
2407 2409 s += urllib.quote(self.path, safe=self._safepchars)
2408 2410 if self.query:
2409 2411 # we store the query in escaped form.
2410 2412 s += '?' + self.query
2411 2413 if self.fragment is not None:
2412 2414 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
2413 2415 return s
2414 2416
2415 2417 def authinfo(self):
2416 2418 user, passwd = self.user, self.passwd
2417 2419 try:
2418 2420 self.user, self.passwd = None, None
2419 2421 s = str(self)
2420 2422 finally:
2421 2423 self.user, self.passwd = user, passwd
2422 2424 if not self.user:
2423 2425 return (s, None)
2424 2426 # authinfo[1] is passed to urllib2 password manager, and its
2425 2427 # URIs must not contain credentials. The host is passed in the
2426 2428 # URIs list because Python < 2.4.3 uses only that to search for
2427 2429 # a password.
2428 2430 return (s, (None, (s, self.host),
2429 2431 self.user, self.passwd or ''))
2430 2432
2431 2433 def isabs(self):
2432 2434 if self.scheme and self.scheme != 'file':
2433 2435 return True # remote URL
2434 2436 if hasdriveletter(self.path):
2435 2437 return True # absolute for our purposes - can't be joined()
2436 2438 if self.path.startswith(r'\\'):
2437 2439 return True # Windows UNC path
2438 2440 if self.path.startswith('/'):
2439 2441 return True # POSIX-style
2440 2442 return False
2441 2443
2442 2444 def localpath(self):
2443 2445 if self.scheme == 'file' or self.scheme == 'bundle':
2444 2446 path = self.path or '/'
2445 2447 # For Windows, we need to promote hosts containing drive
2446 2448 # letters to paths with drive letters.
2447 2449 if hasdriveletter(self._hostport):
2448 2450 path = self._hostport + '/' + self.path
2449 2451 elif (self.host is not None and self.path
2450 2452 and not hasdriveletter(path)):
2451 2453 path = '/' + path
2452 2454 return path
2453 2455 return self._origpath
2454 2456
2455 2457 def islocal(self):
2456 2458 '''whether localpath will return something that posixfile can open'''
2457 2459 return (not self.scheme or self.scheme == 'file'
2458 2460 or self.scheme == 'bundle')
2459 2461
2460 2462 def hasscheme(path):
2461 2463 return bool(url(path).scheme)
2462 2464
2463 2465 def hasdriveletter(path):
2464 2466 return path and path[1:2] == ':' and path[0:1].isalpha()
2465 2467
2466 2468 def urllocalpath(path):
2467 2469 return url(path, parsequery=False, parsefragment=False).localpath()
2468 2470
2469 2471 def hidepassword(u):
2470 2472 '''hide user credential in a url string'''
2471 2473 u = url(u)
2472 2474 if u.passwd:
2473 2475 u.passwd = '***'
2474 2476 return str(u)
2475 2477
2476 2478 def removeauth(u):
2477 2479 '''remove all authentication information from a url string'''
2478 2480 u = url(u)
2479 2481 u.user = u.passwd = None
2480 2482 return str(u)
2481 2483
2482 2484 def isatty(fp):
2483 2485 try:
2484 2486 return fp.isatty()
2485 2487 except AttributeError:
2486 2488 return False
2487 2489
2488 2490 timecount = unitcountfn(
2489 2491 (1, 1e3, _('%.0f s')),
2490 2492 (100, 1, _('%.1f s')),
2491 2493 (10, 1, _('%.2f s')),
2492 2494 (1, 1, _('%.3f s')),
2493 2495 (100, 0.001, _('%.1f ms')),
2494 2496 (10, 0.001, _('%.2f ms')),
2495 2497 (1, 0.001, _('%.3f ms')),
2496 2498 (100, 0.000001, _('%.1f us')),
2497 2499 (10, 0.000001, _('%.2f us')),
2498 2500 (1, 0.000001, _('%.3f us')),
2499 2501 (100, 0.000000001, _('%.1f ns')),
2500 2502 (10, 0.000000001, _('%.2f ns')),
2501 2503 (1, 0.000000001, _('%.3f ns')),
2502 2504 )
2503 2505
2504 2506 _timenesting = [0]
2505 2507
2506 2508 def timed(func):
2507 2509 '''Report the execution time of a function call to stderr.
2508 2510
2509 2511 During development, use as a decorator when you need to measure
2510 2512 the cost of a function, e.g. as follows:
2511 2513
2512 2514 @util.timed
2513 2515 def foo(a, b, c):
2514 2516 pass
2515 2517 '''
2516 2518
2517 2519 def wrapper(*args, **kwargs):
2518 2520 start = time.time()
2519 2521 indent = 2
2520 2522 _timenesting[0] += indent
2521 2523 try:
2522 2524 return func(*args, **kwargs)
2523 2525 finally:
2524 2526 elapsed = time.time() - start
2525 2527 _timenesting[0] -= indent
2526 2528 sys.stderr.write('%s%s: %s\n' %
2527 2529 (' ' * _timenesting[0], func.__name__,
2528 2530 timecount(elapsed)))
2529 2531 return wrapper
2530 2532
2531 2533 _sizeunits = (('m', 2**20), ('k', 2**10), ('g', 2**30),
2532 2534 ('kb', 2**10), ('mb', 2**20), ('gb', 2**30), ('b', 1))
2533 2535
2534 2536 def sizetoint(s):
2535 2537 '''Convert a space specifier to a byte count.
2536 2538
2537 2539 >>> sizetoint('30')
2538 2540 30
2539 2541 >>> sizetoint('2.2kb')
2540 2542 2252
2541 2543 >>> sizetoint('6M')
2542 2544 6291456
2543 2545 '''
2544 2546 t = s.strip().lower()
2545 2547 try:
2546 2548 for k, u in _sizeunits:
2547 2549 if t.endswith(k):
2548 2550 return int(float(t[:-len(k)]) * u)
2549 2551 return int(t)
2550 2552 except ValueError:
2551 2553 raise error.ParseError(_("couldn't parse size: %s") % s)
2552 2554
2553 2555 class hooks(object):
2554 2556 '''A collection of hook functions that can be used to extend a
2555 2557 function's behavior. Hooks are called in lexicographic order,
2556 2558 based on the names of their sources.'''
2557 2559
2558 2560 def __init__(self):
2559 2561 self._hooks = []
2560 2562
2561 2563 def add(self, source, hook):
2562 2564 self._hooks.append((source, hook))
2563 2565
2564 2566 def __call__(self, *args):
2565 2567 self._hooks.sort(key=lambda x: x[0])
2566 2568 results = []
2567 2569 for source, hook in self._hooks:
2568 2570 results.append(hook(*args))
2569 2571 return results
2570 2572
2571 2573 def getstackframes(skip=0, line=' %-*s in %s\n', fileline='%s:%s'):
2572 2574 '''Yields lines for a nicely formatted stacktrace.
2573 2575 Skips the 'skip' last entries.
2574 2576 Each file+linenumber is formatted according to fileline.
2575 2577 Each line is formatted according to line.
2576 2578 If line is None, it yields:
2577 2579 length of longest filepath+line number,
2578 2580 filepath+linenumber,
2579 2581 function
2580 2582
2581 2583 Not be used in production code but very convenient while developing.
2582 2584 '''
2583 2585 entries = [(fileline % (fn, ln), func)
2584 2586 for fn, ln, func, _text in traceback.extract_stack()[:-skip - 1]]
2585 2587 if entries:
2586 2588 fnmax = max(len(entry[0]) for entry in entries)
2587 2589 for fnln, func in entries:
2588 2590 if line is None:
2589 2591 yield (fnmax, fnln, func)
2590 2592 else:
2591 2593 yield line % (fnmax, fnln, func)
2592 2594
2593 2595 def debugstacktrace(msg='stacktrace', skip=0, f=sys.stderr, otherf=sys.stdout):
2594 2596 '''Writes a message to f (stderr) with a nicely formatted stacktrace.
2595 2597 Skips the 'skip' last entries. By default it will flush stdout first.
2596 2598 It can be used everywhere and intentionally does not require an ui object.
2597 2599 Not be used in production code but very convenient while developing.
2598 2600 '''
2599 2601 if otherf:
2600 2602 otherf.flush()
2601 2603 f.write('%s at:\n' % msg)
2602 2604 for line in getstackframes(skip + 1):
2603 2605 f.write(line)
2604 2606 f.flush()
2605 2607
2606 2608 class dirs(object):
2607 2609 '''a multiset of directory names from a dirstate or manifest'''
2608 2610
2609 2611 def __init__(self, map, skip=None):
2610 2612 self._dirs = {}
2611 2613 addpath = self.addpath
2612 2614 if safehasattr(map, 'iteritems') and skip is not None:
2613 2615 for f, s in map.iteritems():
2614 2616 if s[0] != skip:
2615 2617 addpath(f)
2616 2618 else:
2617 2619 for f in map:
2618 2620 addpath(f)
2619 2621
2620 2622 def addpath(self, path):
2621 2623 dirs = self._dirs
2622 2624 for base in finddirs(path):
2623 2625 if base in dirs:
2624 2626 dirs[base] += 1
2625 2627 return
2626 2628 dirs[base] = 1
2627 2629
2628 2630 def delpath(self, path):
2629 2631 dirs = self._dirs
2630 2632 for base in finddirs(path):
2631 2633 if dirs[base] > 1:
2632 2634 dirs[base] -= 1
2633 2635 return
2634 2636 del dirs[base]
2635 2637
2636 2638 def __iter__(self):
2637 2639 return self._dirs.iterkeys()
2638 2640
2639 2641 def __contains__(self, d):
2640 2642 return d in self._dirs
2641 2643
2642 2644 if safehasattr(parsers, 'dirs'):
2643 2645 dirs = parsers.dirs
2644 2646
2645 2647 def finddirs(path):
2646 2648 pos = path.rfind('/')
2647 2649 while pos != -1:
2648 2650 yield path[:pos]
2649 2651 pos = path.rfind('/', 0, pos)
2650 2652
2651 2653 # compression utility
2652 2654
2653 2655 class nocompress(object):
2654 2656 def compress(self, x):
2655 2657 return x
2656 2658 def flush(self):
2657 2659 return ""
2658 2660
2659 2661 compressors = {
2660 2662 None: nocompress,
2661 2663 # lambda to prevent early import
2662 2664 'BZ': lambda: bz2.BZ2Compressor(),
2663 2665 'GZ': lambda: zlib.compressobj(),
2664 2666 }
2665 2667 # also support the old form by courtesies
2666 2668 compressors['UN'] = compressors[None]
2667 2669
2668 2670 def _makedecompressor(decompcls):
2669 2671 def generator(f):
2670 2672 d = decompcls()
2671 2673 for chunk in filechunkiter(f):
2672 2674 yield d.decompress(chunk)
2673 2675 def func(fh):
2674 2676 return chunkbuffer(generator(fh))
2675 2677 return func
2676 2678
2677 2679 class ctxmanager(object):
2678 2680 '''A context manager for use in 'with' blocks to allow multiple
2679 2681 contexts to be entered at once. This is both safer and more
2680 2682 flexible than contextlib.nested.
2681 2683
2682 2684 Once Mercurial supports Python 2.7+, this will become mostly
2683 2685 unnecessary.
2684 2686 '''
2685 2687
2686 2688 def __init__(self, *args):
2687 2689 '''Accepts a list of no-argument functions that return context
2688 2690 managers. These will be invoked at __call__ time.'''
2689 2691 self._pending = args
2690 2692 self._atexit = []
2691 2693
2692 2694 def __enter__(self):
2693 2695 return self
2694 2696
2695 2697 def enter(self):
2696 2698 '''Create and enter context managers in the order in which they were
2697 2699 passed to the constructor.'''
2698 2700 values = []
2699 2701 for func in self._pending:
2700 2702 obj = func()
2701 2703 values.append(obj.__enter__())
2702 2704 self._atexit.append(obj.__exit__)
2703 2705 del self._pending
2704 2706 return values
2705 2707
2706 2708 def atexit(self, func, *args, **kwargs):
2707 2709 '''Add a function to call when this context manager exits. The
2708 2710 ordering of multiple atexit calls is unspecified, save that
2709 2711 they will happen before any __exit__ functions.'''
2710 2712 def wrapper(exc_type, exc_val, exc_tb):
2711 2713 func(*args, **kwargs)
2712 2714 self._atexit.append(wrapper)
2713 2715 return func
2714 2716
2715 2717 def __exit__(self, exc_type, exc_val, exc_tb):
2716 2718 '''Context managers are exited in the reverse order from which
2717 2719 they were created.'''
2718 2720 received = exc_type is not None
2719 2721 suppressed = False
2720 2722 pending = None
2721 2723 self._atexit.reverse()
2722 2724 for exitfunc in self._atexit:
2723 2725 try:
2724 2726 if exitfunc(exc_type, exc_val, exc_tb):
2725 2727 suppressed = True
2726 2728 exc_type = None
2727 2729 exc_val = None
2728 2730 exc_tb = None
2729 2731 except BaseException:
2730 2732 pending = sys.exc_info()
2731 2733 exc_type, exc_val, exc_tb = pending = sys.exc_info()
2732 2734 del self._atexit
2733 2735 if pending:
2734 2736 raise exc_val
2735 2737 return received and suppressed
2736 2738
2737 2739 def _bz2():
2738 2740 d = bz2.BZ2Decompressor()
2739 2741 # Bzip2 stream start with BZ, but we stripped it.
2740 2742 # we put it back for good measure.
2741 2743 d.decompress('BZ')
2742 2744 return d
2743 2745
2744 2746 decompressors = {None: lambda fh: fh,
2745 2747 '_truncatedBZ': _makedecompressor(_bz2),
2746 2748 'BZ': _makedecompressor(lambda: bz2.BZ2Decompressor()),
2747 2749 'GZ': _makedecompressor(lambda: zlib.decompressobj()),
2748 2750 }
2749 2751 # also support the old form by courtesies
2750 2752 decompressors['UN'] = decompressors[None]
2751 2753
2752 2754 # convenient shortcut
2753 2755 dst = debugstacktrace
General Comments 0
You need to be logged in to leave comments. Login now