##// END OF EJS Templates
util: create bytecount array just once...
Matt Mackall -
r16397:f0f7f3fa default
parent child Browse files
Show More
@@ -1,1766 +1,1766
1 1 # util.py - Mercurial utility functions and platform specfic 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 specfic 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 i18n import _
17 17 import error, osutil, encoding
18 18 import errno, re, shutil, sys, tempfile, traceback
19 19 import os, time, datetime, calendar, textwrap, signal
20 20 import imp, socket, urllib
21 21
22 22 if os.name == 'nt':
23 23 import windows as platform
24 24 else:
25 25 import posix as platform
26 26
27 27 platform.encodinglower = encoding.lower
28 28 platform.encodingupper = encoding.upper
29 29
30 30 cachestat = platform.cachestat
31 31 checkexec = platform.checkexec
32 32 checklink = platform.checklink
33 33 copymode = platform.copymode
34 34 executablepath = platform.executablepath
35 35 expandglobs = platform.expandglobs
36 36 explainexit = platform.explainexit
37 37 findexe = platform.findexe
38 38 gethgcmd = platform.gethgcmd
39 39 getuser = platform.getuser
40 40 groupmembers = platform.groupmembers
41 41 groupname = platform.groupname
42 42 hidewindow = platform.hidewindow
43 43 isexec = platform.isexec
44 44 isowner = platform.isowner
45 45 localpath = platform.localpath
46 46 lookupreg = platform.lookupreg
47 47 makedir = platform.makedir
48 48 nlinks = platform.nlinks
49 49 normpath = platform.normpath
50 50 normcase = platform.normcase
51 51 nulldev = platform.nulldev
52 52 openhardlinks = platform.openhardlinks
53 53 oslink = platform.oslink
54 54 parsepatchoutput = platform.parsepatchoutput
55 55 pconvert = platform.pconvert
56 56 popen = platform.popen
57 57 posixfile = platform.posixfile
58 58 quotecommand = platform.quotecommand
59 59 realpath = platform.realpath
60 60 rename = platform.rename
61 61 samedevice = platform.samedevice
62 62 samefile = platform.samefile
63 63 samestat = platform.samestat
64 64 setbinary = platform.setbinary
65 65 setflags = platform.setflags
66 66 setsignalhandler = platform.setsignalhandler
67 67 shellquote = platform.shellquote
68 68 spawndetached = platform.spawndetached
69 69 sshargs = platform.sshargs
70 70 statfiles = platform.statfiles
71 71 termwidth = platform.termwidth
72 72 testpid = platform.testpid
73 73 umask = platform.umask
74 74 unlink = platform.unlink
75 75 unlinkpath = platform.unlinkpath
76 76 username = platform.username
77 77
78 78 # Python compatibility
79 79
80 80 _notset = object()
81 81
82 82 def safehasattr(thing, attr):
83 83 return getattr(thing, attr, _notset) is not _notset
84 84
85 85 def sha1(s=''):
86 86 '''
87 87 Low-overhead wrapper around Python's SHA support
88 88
89 89 >>> f = _fastsha1
90 90 >>> a = sha1()
91 91 >>> a = f()
92 92 >>> a.hexdigest()
93 93 'da39a3ee5e6b4b0d3255bfef95601890afd80709'
94 94 '''
95 95
96 96 return _fastsha1(s)
97 97
98 98 def _fastsha1(s=''):
99 99 # This function will import sha1 from hashlib or sha (whichever is
100 100 # available) and overwrite itself with it on the first call.
101 101 # Subsequent calls will go directly to the imported function.
102 102 if sys.version_info >= (2, 5):
103 103 from hashlib import sha1 as _sha1
104 104 else:
105 105 from sha import sha as _sha1
106 106 global _fastsha1, sha1
107 107 _fastsha1 = sha1 = _sha1
108 108 return _sha1(s)
109 109
110 110 try:
111 111 buffer = buffer
112 112 except NameError:
113 113 if sys.version_info[0] < 3:
114 114 def buffer(sliceable, offset=0):
115 115 return sliceable[offset:]
116 116 else:
117 117 def buffer(sliceable, offset=0):
118 118 return memoryview(sliceable)[offset:]
119 119
120 120 import subprocess
121 121 closefds = os.name == 'posix'
122 122
123 123 def popen2(cmd, env=None, newlines=False):
124 124 # Setting bufsize to -1 lets the system decide the buffer size.
125 125 # The default for bufsize is 0, meaning unbuffered. This leads to
126 126 # poor performance on Mac OS X: http://bugs.python.org/issue4194
127 127 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
128 128 close_fds=closefds,
129 129 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
130 130 universal_newlines=newlines,
131 131 env=env)
132 132 return p.stdin, p.stdout
133 133
134 134 def popen3(cmd, env=None, newlines=False):
135 135 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
136 136 close_fds=closefds,
137 137 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
138 138 stderr=subprocess.PIPE,
139 139 universal_newlines=newlines,
140 140 env=env)
141 141 return p.stdin, p.stdout, p.stderr
142 142
143 143 def version():
144 144 """Return version information if available."""
145 145 try:
146 146 import __version__
147 147 return __version__.version
148 148 except ImportError:
149 149 return 'unknown'
150 150
151 151 # used by parsedate
152 152 defaultdateformats = (
153 153 '%Y-%m-%d %H:%M:%S',
154 154 '%Y-%m-%d %I:%M:%S%p',
155 155 '%Y-%m-%d %H:%M',
156 156 '%Y-%m-%d %I:%M%p',
157 157 '%Y-%m-%d',
158 158 '%m-%d',
159 159 '%m/%d',
160 160 '%m/%d/%y',
161 161 '%m/%d/%Y',
162 162 '%a %b %d %H:%M:%S %Y',
163 163 '%a %b %d %I:%M:%S%p %Y',
164 164 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
165 165 '%b %d %H:%M:%S %Y',
166 166 '%b %d %I:%M:%S%p %Y',
167 167 '%b %d %H:%M:%S',
168 168 '%b %d %I:%M:%S%p',
169 169 '%b %d %H:%M',
170 170 '%b %d %I:%M%p',
171 171 '%b %d %Y',
172 172 '%b %d',
173 173 '%H:%M:%S',
174 174 '%I:%M:%S%p',
175 175 '%H:%M',
176 176 '%I:%M%p',
177 177 )
178 178
179 179 extendeddateformats = defaultdateformats + (
180 180 "%Y",
181 181 "%Y-%m",
182 182 "%b",
183 183 "%b %Y",
184 184 )
185 185
186 186 def cachefunc(func):
187 187 '''cache the result of function calls'''
188 188 # XXX doesn't handle keywords args
189 189 cache = {}
190 190 if func.func_code.co_argcount == 1:
191 191 # we gain a small amount of time because
192 192 # we don't need to pack/unpack the list
193 193 def f(arg):
194 194 if arg not in cache:
195 195 cache[arg] = func(arg)
196 196 return cache[arg]
197 197 else:
198 198 def f(*args):
199 199 if args not in cache:
200 200 cache[args] = func(*args)
201 201 return cache[args]
202 202
203 203 return f
204 204
205 205 def lrucachefunc(func):
206 206 '''cache most recent results of function calls'''
207 207 cache = {}
208 208 order = []
209 209 if func.func_code.co_argcount == 1:
210 210 def f(arg):
211 211 if arg not in cache:
212 212 if len(cache) > 20:
213 213 del cache[order.pop(0)]
214 214 cache[arg] = func(arg)
215 215 else:
216 216 order.remove(arg)
217 217 order.append(arg)
218 218 return cache[arg]
219 219 else:
220 220 def f(*args):
221 221 if args not in cache:
222 222 if len(cache) > 20:
223 223 del cache[order.pop(0)]
224 224 cache[args] = func(*args)
225 225 else:
226 226 order.remove(args)
227 227 order.append(args)
228 228 return cache[args]
229 229
230 230 return f
231 231
232 232 class propertycache(object):
233 233 def __init__(self, func):
234 234 self.func = func
235 235 self.name = func.__name__
236 236 def __get__(self, obj, type=None):
237 237 result = self.func(obj)
238 238 setattr(obj, self.name, result)
239 239 return result
240 240
241 241 def pipefilter(s, cmd):
242 242 '''filter string S through command CMD, returning its output'''
243 243 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
244 244 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
245 245 pout, perr = p.communicate(s)
246 246 return pout
247 247
248 248 def tempfilter(s, cmd):
249 249 '''filter string S through a pair of temporary files with CMD.
250 250 CMD is used as a template to create the real command to be run,
251 251 with the strings INFILE and OUTFILE replaced by the real names of
252 252 the temporary files generated.'''
253 253 inname, outname = None, None
254 254 try:
255 255 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
256 256 fp = os.fdopen(infd, 'wb')
257 257 fp.write(s)
258 258 fp.close()
259 259 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
260 260 os.close(outfd)
261 261 cmd = cmd.replace('INFILE', inname)
262 262 cmd = cmd.replace('OUTFILE', outname)
263 263 code = os.system(cmd)
264 264 if sys.platform == 'OpenVMS' and code & 1:
265 265 code = 0
266 266 if code:
267 267 raise Abort(_("command '%s' failed: %s") %
268 268 (cmd, explainexit(code)))
269 269 fp = open(outname, 'rb')
270 270 r = fp.read()
271 271 fp.close()
272 272 return r
273 273 finally:
274 274 try:
275 275 if inname:
276 276 os.unlink(inname)
277 277 except OSError:
278 278 pass
279 279 try:
280 280 if outname:
281 281 os.unlink(outname)
282 282 except OSError:
283 283 pass
284 284
285 285 filtertable = {
286 286 'tempfile:': tempfilter,
287 287 'pipe:': pipefilter,
288 288 }
289 289
290 290 def filter(s, cmd):
291 291 "filter a string through a command that transforms its input to its output"
292 292 for name, fn in filtertable.iteritems():
293 293 if cmd.startswith(name):
294 294 return fn(s, cmd[len(name):].lstrip())
295 295 return pipefilter(s, cmd)
296 296
297 297 def binary(s):
298 298 """return true if a string is binary data"""
299 299 return bool(s and '\0' in s)
300 300
301 301 def increasingchunks(source, min=1024, max=65536):
302 302 '''return no less than min bytes per chunk while data remains,
303 303 doubling min after each chunk until it reaches max'''
304 304 def log2(x):
305 305 if not x:
306 306 return 0
307 307 i = 0
308 308 while x:
309 309 x >>= 1
310 310 i += 1
311 311 return i - 1
312 312
313 313 buf = []
314 314 blen = 0
315 315 for chunk in source:
316 316 buf.append(chunk)
317 317 blen += len(chunk)
318 318 if blen >= min:
319 319 if min < max:
320 320 min = min << 1
321 321 nmin = 1 << log2(blen)
322 322 if nmin > min:
323 323 min = nmin
324 324 if min > max:
325 325 min = max
326 326 yield ''.join(buf)
327 327 blen = 0
328 328 buf = []
329 329 if buf:
330 330 yield ''.join(buf)
331 331
332 332 Abort = error.Abort
333 333
334 334 def always(fn):
335 335 return True
336 336
337 337 def never(fn):
338 338 return False
339 339
340 340 def pathto(root, n1, n2):
341 341 '''return the relative path from one place to another.
342 342 root should use os.sep to separate directories
343 343 n1 should use os.sep to separate directories
344 344 n2 should use "/" to separate directories
345 345 returns an os.sep-separated path.
346 346
347 347 If n1 is a relative path, it's assumed it's
348 348 relative to root.
349 349 n2 should always be relative to root.
350 350 '''
351 351 if not n1:
352 352 return localpath(n2)
353 353 if os.path.isabs(n1):
354 354 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
355 355 return os.path.join(root, localpath(n2))
356 356 n2 = '/'.join((pconvert(root), n2))
357 357 a, b = splitpath(n1), n2.split('/')
358 358 a.reverse()
359 359 b.reverse()
360 360 while a and b and a[-1] == b[-1]:
361 361 a.pop()
362 362 b.pop()
363 363 b.reverse()
364 364 return os.sep.join((['..'] * len(a)) + b) or '.'
365 365
366 366 _hgexecutable = None
367 367
368 368 def mainfrozen():
369 369 """return True if we are a frozen executable.
370 370
371 371 The code supports py2exe (most common, Windows only) and tools/freeze
372 372 (portable, not much used).
373 373 """
374 374 return (safehasattr(sys, "frozen") or # new py2exe
375 375 safehasattr(sys, "importers") or # old py2exe
376 376 imp.is_frozen("__main__")) # tools/freeze
377 377
378 378 def hgexecutable():
379 379 """return location of the 'hg' executable.
380 380
381 381 Defaults to $HG or 'hg' in the search path.
382 382 """
383 383 if _hgexecutable is None:
384 384 hg = os.environ.get('HG')
385 385 mainmod = sys.modules['__main__']
386 386 if hg:
387 387 _sethgexecutable(hg)
388 388 elif mainfrozen():
389 389 _sethgexecutable(sys.executable)
390 390 elif os.path.basename(getattr(mainmod, '__file__', '')) == 'hg':
391 391 _sethgexecutable(mainmod.__file__)
392 392 else:
393 393 exe = findexe('hg') or os.path.basename(sys.argv[0])
394 394 _sethgexecutable(exe)
395 395 return _hgexecutable
396 396
397 397 def _sethgexecutable(path):
398 398 """set location of the 'hg' executable"""
399 399 global _hgexecutable
400 400 _hgexecutable = path
401 401
402 402 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
403 403 '''enhanced shell command execution.
404 404 run with environment maybe modified, maybe in different dir.
405 405
406 406 if command fails and onerr is None, return status. if ui object,
407 407 print error message and return status, else raise onerr object as
408 408 exception.
409 409
410 410 if out is specified, it is assumed to be a file-like object that has a
411 411 write() method. stdout and stderr will be redirected to out.'''
412 412 try:
413 413 sys.stdout.flush()
414 414 except Exception:
415 415 pass
416 416 def py2shell(val):
417 417 'convert python object into string that is useful to shell'
418 418 if val is None or val is False:
419 419 return '0'
420 420 if val is True:
421 421 return '1'
422 422 return str(val)
423 423 origcmd = cmd
424 424 cmd = quotecommand(cmd)
425 425 if sys.platform == 'plan9':
426 426 # subprocess kludge to work around issues in half-baked Python
427 427 # ports, notably bichued/python:
428 428 if not cwd is None:
429 429 os.chdir(cwd)
430 430 rc = os.system(cmd)
431 431 else:
432 432 env = dict(os.environ)
433 433 env.update((k, py2shell(v)) for k, v in environ.iteritems())
434 434 env['HG'] = hgexecutable()
435 435 if out is None or out == sys.__stdout__:
436 436 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
437 437 env=env, cwd=cwd)
438 438 else:
439 439 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
440 440 env=env, cwd=cwd, stdout=subprocess.PIPE,
441 441 stderr=subprocess.STDOUT)
442 442 for line in proc.stdout:
443 443 out.write(line)
444 444 proc.wait()
445 445 rc = proc.returncode
446 446 if sys.platform == 'OpenVMS' and rc & 1:
447 447 rc = 0
448 448 if rc and onerr:
449 449 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
450 450 explainexit(rc)[0])
451 451 if errprefix:
452 452 errmsg = '%s: %s' % (errprefix, errmsg)
453 453 try:
454 454 onerr.warn(errmsg + '\n')
455 455 except AttributeError:
456 456 raise onerr(errmsg)
457 457 return rc
458 458
459 459 def checksignature(func):
460 460 '''wrap a function with code to check for calling errors'''
461 461 def check(*args, **kwargs):
462 462 try:
463 463 return func(*args, **kwargs)
464 464 except TypeError:
465 465 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
466 466 raise error.SignatureError
467 467 raise
468 468
469 469 return check
470 470
471 471 def copyfile(src, dest):
472 472 "copy a file, preserving mode and atime/mtime"
473 473 if os.path.islink(src):
474 474 try:
475 475 os.unlink(dest)
476 476 except OSError:
477 477 pass
478 478 os.symlink(os.readlink(src), dest)
479 479 else:
480 480 try:
481 481 shutil.copyfile(src, dest)
482 482 shutil.copymode(src, dest)
483 483 except shutil.Error, inst:
484 484 raise Abort(str(inst))
485 485
486 486 def copyfiles(src, dst, hardlink=None):
487 487 """Copy a directory tree using hardlinks if possible"""
488 488
489 489 if hardlink is None:
490 490 hardlink = (os.stat(src).st_dev ==
491 491 os.stat(os.path.dirname(dst)).st_dev)
492 492
493 493 num = 0
494 494 if os.path.isdir(src):
495 495 os.mkdir(dst)
496 496 for name, kind in osutil.listdir(src):
497 497 srcname = os.path.join(src, name)
498 498 dstname = os.path.join(dst, name)
499 499 hardlink, n = copyfiles(srcname, dstname, hardlink)
500 500 num += n
501 501 else:
502 502 if hardlink:
503 503 try:
504 504 oslink(src, dst)
505 505 except (IOError, OSError):
506 506 hardlink = False
507 507 shutil.copy(src, dst)
508 508 else:
509 509 shutil.copy(src, dst)
510 510 num += 1
511 511
512 512 return hardlink, num
513 513
514 514 _winreservednames = '''con prn aux nul
515 515 com1 com2 com3 com4 com5 com6 com7 com8 com9
516 516 lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
517 517 _winreservedchars = ':*?"<>|'
518 518 def checkwinfilename(path):
519 519 '''Check that the base-relative path is a valid filename on Windows.
520 520 Returns None if the path is ok, or a UI string describing the problem.
521 521
522 522 >>> checkwinfilename("just/a/normal/path")
523 523 >>> checkwinfilename("foo/bar/con.xml")
524 524 "filename contains 'con', which is reserved on Windows"
525 525 >>> checkwinfilename("foo/con.xml/bar")
526 526 "filename contains 'con', which is reserved on Windows"
527 527 >>> checkwinfilename("foo/bar/xml.con")
528 528 >>> checkwinfilename("foo/bar/AUX/bla.txt")
529 529 "filename contains 'AUX', which is reserved on Windows"
530 530 >>> checkwinfilename("foo/bar/bla:.txt")
531 531 "filename contains ':', which is reserved on Windows"
532 532 >>> checkwinfilename("foo/bar/b\07la.txt")
533 533 "filename contains '\\\\x07', which is invalid on Windows"
534 534 >>> checkwinfilename("foo/bar/bla ")
535 535 "filename ends with ' ', which is not allowed on Windows"
536 536 >>> checkwinfilename("../bar")
537 537 '''
538 538 for n in path.replace('\\', '/').split('/'):
539 539 if not n:
540 540 continue
541 541 for c in n:
542 542 if c in _winreservedchars:
543 543 return _("filename contains '%s', which is reserved "
544 544 "on Windows") % c
545 545 if ord(c) <= 31:
546 546 return _("filename contains %r, which is invalid "
547 547 "on Windows") % c
548 548 base = n.split('.')[0]
549 549 if base and base.lower() in _winreservednames:
550 550 return _("filename contains '%s', which is reserved "
551 551 "on Windows") % base
552 552 t = n[-1]
553 553 if t in '. ' and n not in '..':
554 554 return _("filename ends with '%s', which is not allowed "
555 555 "on Windows") % t
556 556
557 557 if os.name == 'nt':
558 558 checkosfilename = checkwinfilename
559 559 else:
560 560 checkosfilename = platform.checkosfilename
561 561
562 562 def makelock(info, pathname):
563 563 try:
564 564 return os.symlink(info, pathname)
565 565 except OSError, why:
566 566 if why.errno == errno.EEXIST:
567 567 raise
568 568 except AttributeError: # no symlink in os
569 569 pass
570 570
571 571 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
572 572 os.write(ld, info)
573 573 os.close(ld)
574 574
575 575 def readlock(pathname):
576 576 try:
577 577 return os.readlink(pathname)
578 578 except OSError, why:
579 579 if why.errno not in (errno.EINVAL, errno.ENOSYS):
580 580 raise
581 581 except AttributeError: # no symlink in os
582 582 pass
583 583 fp = posixfile(pathname)
584 584 r = fp.read()
585 585 fp.close()
586 586 return r
587 587
588 588 def fstat(fp):
589 589 '''stat file object that may not have fileno method.'''
590 590 try:
591 591 return os.fstat(fp.fileno())
592 592 except AttributeError:
593 593 return os.stat(fp.name)
594 594
595 595 # File system features
596 596
597 597 def checkcase(path):
598 598 """
599 599 Check whether the given path is on a case-sensitive filesystem
600 600
601 601 Requires a path (like /foo/.hg) ending with a foldable final
602 602 directory component.
603 603 """
604 604 s1 = os.stat(path)
605 605 d, b = os.path.split(path)
606 606 b2 = b.upper()
607 607 if b == b2:
608 608 b2 = b.lower()
609 609 if b == b2:
610 610 return True # no evidence against case sensitivity
611 611 p2 = os.path.join(d, b2)
612 612 try:
613 613 s2 = os.stat(p2)
614 614 if s2 == s1:
615 615 return False
616 616 return True
617 617 except OSError:
618 618 return True
619 619
620 620 _fspathcache = {}
621 621 def fspath(name, root):
622 622 '''Get name in the case stored in the filesystem
623 623
624 624 The name should be relative to root, and be normcase-ed for efficiency.
625 625
626 626 Note that this function is unnecessary, and should not be
627 627 called, for case-sensitive filesystems (simply because it's expensive).
628 628
629 629 The root should be normcase-ed, too.
630 630 '''
631 631 def find(p, contents):
632 632 for n in contents:
633 633 if normcase(n) == p:
634 634 return n
635 635 return None
636 636
637 637 seps = os.sep
638 638 if os.altsep:
639 639 seps = seps + os.altsep
640 640 # Protect backslashes. This gets silly very quickly.
641 641 seps.replace('\\','\\\\')
642 642 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
643 643 dir = os.path.normpath(root)
644 644 result = []
645 645 for part, sep in pattern.findall(name):
646 646 if sep:
647 647 result.append(sep)
648 648 continue
649 649
650 650 if dir not in _fspathcache:
651 651 _fspathcache[dir] = os.listdir(dir)
652 652 contents = _fspathcache[dir]
653 653
654 654 found = find(part, contents)
655 655 if not found:
656 656 # retry "once per directory" per "dirstate.walk" which
657 657 # may take place for each patches of "hg qpush", for example
658 658 contents = os.listdir(dir)
659 659 _fspathcache[dir] = contents
660 660 found = find(part, contents)
661 661
662 662 result.append(found or part)
663 663 dir = os.path.join(dir, part)
664 664
665 665 return ''.join(result)
666 666
667 667 def checknlink(testfile):
668 668 '''check whether hardlink count reporting works properly'''
669 669
670 670 # testfile may be open, so we need a separate file for checking to
671 671 # work around issue2543 (or testfile may get lost on Samba shares)
672 672 f1 = testfile + ".hgtmp1"
673 673 if os.path.lexists(f1):
674 674 return False
675 675 try:
676 676 posixfile(f1, 'w').close()
677 677 except IOError:
678 678 return False
679 679
680 680 f2 = testfile + ".hgtmp2"
681 681 fd = None
682 682 try:
683 683 try:
684 684 oslink(f1, f2)
685 685 except OSError:
686 686 return False
687 687
688 688 # nlinks() may behave differently for files on Windows shares if
689 689 # the file is open.
690 690 fd = posixfile(f2)
691 691 return nlinks(f2) > 1
692 692 finally:
693 693 if fd is not None:
694 694 fd.close()
695 695 for f in (f1, f2):
696 696 try:
697 697 os.unlink(f)
698 698 except OSError:
699 699 pass
700 700
701 701 return False
702 702
703 703 def endswithsep(path):
704 704 '''Check path ends with os.sep or os.altsep.'''
705 705 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
706 706
707 707 def splitpath(path):
708 708 '''Split path by os.sep.
709 709 Note that this function does not use os.altsep because this is
710 710 an alternative of simple "xxx.split(os.sep)".
711 711 It is recommended to use os.path.normpath() before using this
712 712 function if need.'''
713 713 return path.split(os.sep)
714 714
715 715 def gui():
716 716 '''Are we running in a GUI?'''
717 717 if sys.platform == 'darwin':
718 718 if 'SSH_CONNECTION' in os.environ:
719 719 # handle SSH access to a box where the user is logged in
720 720 return False
721 721 elif getattr(osutil, 'isgui', None):
722 722 # check if a CoreGraphics session is available
723 723 return osutil.isgui()
724 724 else:
725 725 # pure build; use a safe default
726 726 return True
727 727 else:
728 728 return os.name == "nt" or os.environ.get("DISPLAY")
729 729
730 730 def mktempcopy(name, emptyok=False, createmode=None):
731 731 """Create a temporary file with the same contents from name
732 732
733 733 The permission bits are copied from the original file.
734 734
735 735 If the temporary file is going to be truncated immediately, you
736 736 can use emptyok=True as an optimization.
737 737
738 738 Returns the name of the temporary file.
739 739 """
740 740 d, fn = os.path.split(name)
741 741 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
742 742 os.close(fd)
743 743 # Temporary files are created with mode 0600, which is usually not
744 744 # what we want. If the original file already exists, just copy
745 745 # its mode. Otherwise, manually obey umask.
746 746 copymode(name, temp, createmode)
747 747 if emptyok:
748 748 return temp
749 749 try:
750 750 try:
751 751 ifp = posixfile(name, "rb")
752 752 except IOError, inst:
753 753 if inst.errno == errno.ENOENT:
754 754 return temp
755 755 if not getattr(inst, 'filename', None):
756 756 inst.filename = name
757 757 raise
758 758 ofp = posixfile(temp, "wb")
759 759 for chunk in filechunkiter(ifp):
760 760 ofp.write(chunk)
761 761 ifp.close()
762 762 ofp.close()
763 763 except:
764 764 try: os.unlink(temp)
765 765 except: pass
766 766 raise
767 767 return temp
768 768
769 769 class atomictempfile(object):
770 770 '''writeable file object that atomically updates a file
771 771
772 772 All writes will go to a temporary copy of the original file. Call
773 773 close() when you are done writing, and atomictempfile will rename
774 774 the temporary copy to the original name, making the changes
775 775 visible. If the object is destroyed without being closed, all your
776 776 writes are discarded.
777 777 '''
778 778 def __init__(self, name, mode='w+b', createmode=None):
779 779 self.__name = name # permanent name
780 780 self._tempname = mktempcopy(name, emptyok=('w' in mode),
781 781 createmode=createmode)
782 782 self._fp = posixfile(self._tempname, mode)
783 783
784 784 # delegated methods
785 785 self.write = self._fp.write
786 786 self.fileno = self._fp.fileno
787 787
788 788 def close(self):
789 789 if not self._fp.closed:
790 790 self._fp.close()
791 791 rename(self._tempname, localpath(self.__name))
792 792
793 793 def discard(self):
794 794 if not self._fp.closed:
795 795 try:
796 796 os.unlink(self._tempname)
797 797 except OSError:
798 798 pass
799 799 self._fp.close()
800 800
801 801 def __del__(self):
802 802 if safehasattr(self, '_fp'): # constructor actually did something
803 803 self.discard()
804 804
805 805 def makedirs(name, mode=None):
806 806 """recursive directory creation with parent mode inheritance"""
807 807 try:
808 808 os.mkdir(name)
809 809 except OSError, err:
810 810 if err.errno == errno.EEXIST:
811 811 return
812 812 if err.errno != errno.ENOENT or not name:
813 813 raise
814 814 parent = os.path.dirname(os.path.abspath(name))
815 815 if parent == name:
816 816 raise
817 817 makedirs(parent, mode)
818 818 os.mkdir(name)
819 819 if mode is not None:
820 820 os.chmod(name, mode)
821 821
822 822 def readfile(path):
823 823 fp = open(path, 'rb')
824 824 try:
825 825 return fp.read()
826 826 finally:
827 827 fp.close()
828 828
829 829 def writefile(path, text):
830 830 fp = open(path, 'wb')
831 831 try:
832 832 fp.write(text)
833 833 finally:
834 834 fp.close()
835 835
836 836 def appendfile(path, text):
837 837 fp = open(path, 'ab')
838 838 try:
839 839 fp.write(text)
840 840 finally:
841 841 fp.close()
842 842
843 843 class chunkbuffer(object):
844 844 """Allow arbitrary sized chunks of data to be efficiently read from an
845 845 iterator over chunks of arbitrary size."""
846 846
847 847 def __init__(self, in_iter):
848 848 """in_iter is the iterator that's iterating over the input chunks.
849 849 targetsize is how big a buffer to try to maintain."""
850 850 def splitbig(chunks):
851 851 for chunk in chunks:
852 852 if len(chunk) > 2**20:
853 853 pos = 0
854 854 while pos < len(chunk):
855 855 end = pos + 2 ** 18
856 856 yield chunk[pos:end]
857 857 pos = end
858 858 else:
859 859 yield chunk
860 860 self.iter = splitbig(in_iter)
861 861 self._queue = []
862 862
863 863 def read(self, l):
864 864 """Read L bytes of data from the iterator of chunks of data.
865 865 Returns less than L bytes if the iterator runs dry."""
866 866 left = l
867 867 buf = ''
868 868 queue = self._queue
869 869 while left > 0:
870 870 # refill the queue
871 871 if not queue:
872 872 target = 2**18
873 873 for chunk in self.iter:
874 874 queue.append(chunk)
875 875 target -= len(chunk)
876 876 if target <= 0:
877 877 break
878 878 if not queue:
879 879 break
880 880
881 881 chunk = queue.pop(0)
882 882 left -= len(chunk)
883 883 if left < 0:
884 884 queue.insert(0, chunk[left:])
885 885 buf += chunk[:left]
886 886 else:
887 887 buf += chunk
888 888
889 889 return buf
890 890
891 891 def filechunkiter(f, size=65536, limit=None):
892 892 """Create a generator that produces the data in the file size
893 893 (default 65536) bytes at a time, up to optional limit (default is
894 894 to read all data). Chunks may be less than size bytes if the
895 895 chunk is the last chunk in the file, or the file is a socket or
896 896 some other type of file that sometimes reads less data than is
897 897 requested."""
898 898 assert size >= 0
899 899 assert limit is None or limit >= 0
900 900 while True:
901 901 if limit is None:
902 902 nbytes = size
903 903 else:
904 904 nbytes = min(limit, size)
905 905 s = nbytes and f.read(nbytes)
906 906 if not s:
907 907 break
908 908 if limit:
909 909 limit -= len(s)
910 910 yield s
911 911
912 912 def makedate():
913 913 ct = time.time()
914 914 if ct < 0:
915 915 hint = _("check your clock")
916 916 raise Abort(_("negative timestamp: %d") % ct, hint=hint)
917 917 delta = (datetime.datetime.utcfromtimestamp(ct) -
918 918 datetime.datetime.fromtimestamp(ct))
919 919 tz = delta.days * 86400 + delta.seconds
920 920 return ct, tz
921 921
922 922 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
923 923 """represent a (unixtime, offset) tuple as a localized time.
924 924 unixtime is seconds since the epoch, and offset is the time zone's
925 925 number of seconds away from UTC. if timezone is false, do not
926 926 append time zone to string."""
927 927 t, tz = date or makedate()
928 928 if t < 0:
929 929 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
930 930 tz = 0
931 931 if "%1" in format or "%2" in format:
932 932 sign = (tz > 0) and "-" or "+"
933 933 minutes = abs(tz) // 60
934 934 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
935 935 format = format.replace("%2", "%02d" % (minutes % 60))
936 936 try:
937 937 t = time.gmtime(float(t) - tz)
938 938 except ValueError:
939 939 # time was out of range
940 940 t = time.gmtime(sys.maxint)
941 941 s = time.strftime(format, t)
942 942 return s
943 943
944 944 def shortdate(date=None):
945 945 """turn (timestamp, tzoff) tuple into iso 8631 date."""
946 946 return datestr(date, format='%Y-%m-%d')
947 947
948 948 def strdate(string, format, defaults=[]):
949 949 """parse a localized time string and return a (unixtime, offset) tuple.
950 950 if the string cannot be parsed, ValueError is raised."""
951 951 def timezone(string):
952 952 tz = string.split()[-1]
953 953 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
954 954 sign = (tz[0] == "+") and 1 or -1
955 955 hours = int(tz[1:3])
956 956 minutes = int(tz[3:5])
957 957 return -sign * (hours * 60 + minutes) * 60
958 958 if tz == "GMT" or tz == "UTC":
959 959 return 0
960 960 return None
961 961
962 962 # NOTE: unixtime = localunixtime + offset
963 963 offset, date = timezone(string), string
964 964 if offset is not None:
965 965 date = " ".join(string.split()[:-1])
966 966
967 967 # add missing elements from defaults
968 968 usenow = False # default to using biased defaults
969 969 for part in ("S", "M", "HI", "d", "mb", "yY"): # decreasing specificity
970 970 found = [True for p in part if ("%"+p) in format]
971 971 if not found:
972 972 date += "@" + defaults[part][usenow]
973 973 format += "@%" + part[0]
974 974 else:
975 975 # We've found a specific time element, less specific time
976 976 # elements are relative to today
977 977 usenow = True
978 978
979 979 timetuple = time.strptime(date, format)
980 980 localunixtime = int(calendar.timegm(timetuple))
981 981 if offset is None:
982 982 # local timezone
983 983 unixtime = int(time.mktime(timetuple))
984 984 offset = unixtime - localunixtime
985 985 else:
986 986 unixtime = localunixtime + offset
987 987 return unixtime, offset
988 988
989 989 def parsedate(date, formats=None, bias={}):
990 990 """parse a localized date/time and return a (unixtime, offset) tuple.
991 991
992 992 The date may be a "unixtime offset" string or in one of the specified
993 993 formats. If the date already is a (unixtime, offset) tuple, it is returned.
994 994 """
995 995 if not date:
996 996 return 0, 0
997 997 if isinstance(date, tuple) and len(date) == 2:
998 998 return date
999 999 if not formats:
1000 1000 formats = defaultdateformats
1001 1001 date = date.strip()
1002 1002 try:
1003 1003 when, offset = map(int, date.split(' '))
1004 1004 except ValueError:
1005 1005 # fill out defaults
1006 1006 now = makedate()
1007 1007 defaults = {}
1008 1008 for part in ("d", "mb", "yY", "HI", "M", "S"):
1009 1009 # this piece is for rounding the specific end of unknowns
1010 1010 b = bias.get(part)
1011 1011 if b is None:
1012 1012 if part[0] in "HMS":
1013 1013 b = "00"
1014 1014 else:
1015 1015 b = "0"
1016 1016
1017 1017 # this piece is for matching the generic end to today's date
1018 1018 n = datestr(now, "%" + part[0])
1019 1019
1020 1020 defaults[part] = (b, n)
1021 1021
1022 1022 for format in formats:
1023 1023 try:
1024 1024 when, offset = strdate(date, format, defaults)
1025 1025 except (ValueError, OverflowError):
1026 1026 pass
1027 1027 else:
1028 1028 break
1029 1029 else:
1030 1030 raise Abort(_('invalid date: %r') % date)
1031 1031 # validate explicit (probably user-specified) date and
1032 1032 # time zone offset. values must fit in signed 32 bits for
1033 1033 # current 32-bit linux runtimes. timezones go from UTC-12
1034 1034 # to UTC+14
1035 1035 if abs(when) > 0x7fffffff:
1036 1036 raise Abort(_('date exceeds 32 bits: %d') % when)
1037 1037 if when < 0:
1038 1038 raise Abort(_('negative date value: %d') % when)
1039 1039 if offset < -50400 or offset > 43200:
1040 1040 raise Abort(_('impossible time zone offset: %d') % offset)
1041 1041 return when, offset
1042 1042
1043 1043 def matchdate(date):
1044 1044 """Return a function that matches a given date match specifier
1045 1045
1046 1046 Formats include:
1047 1047
1048 1048 '{date}' match a given date to the accuracy provided
1049 1049
1050 1050 '<{date}' on or before a given date
1051 1051
1052 1052 '>{date}' on or after a given date
1053 1053
1054 1054 >>> p1 = parsedate("10:29:59")
1055 1055 >>> p2 = parsedate("10:30:00")
1056 1056 >>> p3 = parsedate("10:30:59")
1057 1057 >>> p4 = parsedate("10:31:00")
1058 1058 >>> p5 = parsedate("Sep 15 10:30:00 1999")
1059 1059 >>> f = matchdate("10:30")
1060 1060 >>> f(p1[0])
1061 1061 False
1062 1062 >>> f(p2[0])
1063 1063 True
1064 1064 >>> f(p3[0])
1065 1065 True
1066 1066 >>> f(p4[0])
1067 1067 False
1068 1068 >>> f(p5[0])
1069 1069 False
1070 1070 """
1071 1071
1072 1072 def lower(date):
1073 1073 d = dict(mb="1", d="1")
1074 1074 return parsedate(date, extendeddateformats, d)[0]
1075 1075
1076 1076 def upper(date):
1077 1077 d = dict(mb="12", HI="23", M="59", S="59")
1078 1078 for days in ("31", "30", "29"):
1079 1079 try:
1080 1080 d["d"] = days
1081 1081 return parsedate(date, extendeddateformats, d)[0]
1082 1082 except:
1083 1083 pass
1084 1084 d["d"] = "28"
1085 1085 return parsedate(date, extendeddateformats, d)[0]
1086 1086
1087 1087 date = date.strip()
1088 1088
1089 1089 if not date:
1090 1090 raise Abort(_("dates cannot consist entirely of whitespace"))
1091 1091 elif date[0] == "<":
1092 1092 if not date[1:]:
1093 1093 raise Abort(_("invalid day spec, use '<DATE'"))
1094 1094 when = upper(date[1:])
1095 1095 return lambda x: x <= when
1096 1096 elif date[0] == ">":
1097 1097 if not date[1:]:
1098 1098 raise Abort(_("invalid day spec, use '>DATE'"))
1099 1099 when = lower(date[1:])
1100 1100 return lambda x: x >= when
1101 1101 elif date[0] == "-":
1102 1102 try:
1103 1103 days = int(date[1:])
1104 1104 except ValueError:
1105 1105 raise Abort(_("invalid day spec: %s") % date[1:])
1106 1106 if days < 0:
1107 1107 raise Abort(_("%s must be nonnegative (see 'hg help dates')")
1108 1108 % date[1:])
1109 1109 when = makedate()[0] - days * 3600 * 24
1110 1110 return lambda x: x >= when
1111 1111 elif " to " in date:
1112 1112 a, b = date.split(" to ")
1113 1113 start, stop = lower(a), upper(b)
1114 1114 return lambda x: x >= start and x <= stop
1115 1115 else:
1116 1116 start, stop = lower(date), upper(date)
1117 1117 return lambda x: x >= start and x <= stop
1118 1118
1119 1119 def shortuser(user):
1120 1120 """Return a short representation of a user name or email address."""
1121 1121 f = user.find('@')
1122 1122 if f >= 0:
1123 1123 user = user[:f]
1124 1124 f = user.find('<')
1125 1125 if f >= 0:
1126 1126 user = user[f + 1:]
1127 1127 f = user.find(' ')
1128 1128 if f >= 0:
1129 1129 user = user[:f]
1130 1130 f = user.find('.')
1131 1131 if f >= 0:
1132 1132 user = user[:f]
1133 1133 return user
1134 1134
1135 1135 def emailuser(user):
1136 1136 """Return the user portion of an email address."""
1137 1137 f = user.find('@')
1138 1138 if f >= 0:
1139 1139 user = user[:f]
1140 1140 f = user.find('<')
1141 1141 if f >= 0:
1142 1142 user = user[f + 1:]
1143 1143 return user
1144 1144
1145 1145 def email(author):
1146 1146 '''get email of author.'''
1147 1147 r = author.find('>')
1148 1148 if r == -1:
1149 1149 r = None
1150 1150 return author[author.find('<') + 1:r]
1151 1151
1152 1152 def _ellipsis(text, maxlength):
1153 1153 if len(text) <= maxlength:
1154 1154 return text, False
1155 1155 else:
1156 1156 return "%s..." % (text[:maxlength - 3]), True
1157 1157
1158 1158 def ellipsis(text, maxlength=400):
1159 1159 """Trim string to at most maxlength (default: 400) characters."""
1160 1160 try:
1161 1161 # use unicode not to split at intermediate multi-byte sequence
1162 1162 utext, truncated = _ellipsis(text.decode(encoding.encoding),
1163 1163 maxlength)
1164 1164 if not truncated:
1165 1165 return text
1166 1166 return utext.encode(encoding.encoding)
1167 1167 except (UnicodeDecodeError, UnicodeEncodeError):
1168 1168 return _ellipsis(text, maxlength)[0]
1169 1169
1170 def bytecount(nbytes):
1171 '''return byte count formatted as readable string, with units'''
1172
1173 units = (
1170 _byteunits = (
1174 1171 (100, 1 << 30, _('%.0f GB')),
1175 1172 (10, 1 << 30, _('%.1f GB')),
1176 1173 (1, 1 << 30, _('%.2f GB')),
1177 1174 (100, 1 << 20, _('%.0f MB')),
1178 1175 (10, 1 << 20, _('%.1f MB')),
1179 1176 (1, 1 << 20, _('%.2f MB')),
1180 1177 (100, 1 << 10, _('%.0f KB')),
1181 1178 (10, 1 << 10, _('%.1f KB')),
1182 1179 (1, 1 << 10, _('%.2f KB')),
1183 1180 (1, 1, _('%.0f bytes')),
1184 1181 )
1185 1182
1186 for multiplier, divisor, format in units:
1183 def bytecount(nbytes):
1184 '''return byte count formatted as readable string, with units'''
1185
1186 for multiplier, divisor, format in _byteunits:
1187 1187 if nbytes >= divisor * multiplier:
1188 1188 return format % (nbytes / float(divisor))
1189 1189 return units[-1][2] % nbytes
1190 1190
1191 1191 def uirepr(s):
1192 1192 # Avoid double backslash in Windows path repr()
1193 1193 return repr(s).replace('\\\\', '\\')
1194 1194
1195 1195 # delay import of textwrap
1196 1196 def MBTextWrapper(**kwargs):
1197 1197 class tw(textwrap.TextWrapper):
1198 1198 """
1199 1199 Extend TextWrapper for width-awareness.
1200 1200
1201 1201 Neither number of 'bytes' in any encoding nor 'characters' is
1202 1202 appropriate to calculate terminal columns for specified string.
1203 1203
1204 1204 Original TextWrapper implementation uses built-in 'len()' directly,
1205 1205 so overriding is needed to use width information of each characters.
1206 1206
1207 1207 In addition, characters classified into 'ambiguous' width are
1208 1208 treated as wide in east asian area, but as narrow in other.
1209 1209
1210 1210 This requires use decision to determine width of such characters.
1211 1211 """
1212 1212 def __init__(self, **kwargs):
1213 1213 textwrap.TextWrapper.__init__(self, **kwargs)
1214 1214
1215 1215 # for compatibility between 2.4 and 2.6
1216 1216 if getattr(self, 'drop_whitespace', None) is None:
1217 1217 self.drop_whitespace = kwargs.get('drop_whitespace', True)
1218 1218
1219 1219 def _cutdown(self, ucstr, space_left):
1220 1220 l = 0
1221 1221 colwidth = encoding.ucolwidth
1222 1222 for i in xrange(len(ucstr)):
1223 1223 l += colwidth(ucstr[i])
1224 1224 if space_left < l:
1225 1225 return (ucstr[:i], ucstr[i:])
1226 1226 return ucstr, ''
1227 1227
1228 1228 # overriding of base class
1229 1229 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1230 1230 space_left = max(width - cur_len, 1)
1231 1231
1232 1232 if self.break_long_words:
1233 1233 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1234 1234 cur_line.append(cut)
1235 1235 reversed_chunks[-1] = res
1236 1236 elif not cur_line:
1237 1237 cur_line.append(reversed_chunks.pop())
1238 1238
1239 1239 # this overriding code is imported from TextWrapper of python 2.6
1240 1240 # to calculate columns of string by 'encoding.ucolwidth()'
1241 1241 def _wrap_chunks(self, chunks):
1242 1242 colwidth = encoding.ucolwidth
1243 1243
1244 1244 lines = []
1245 1245 if self.width <= 0:
1246 1246 raise ValueError("invalid width %r (must be > 0)" % self.width)
1247 1247
1248 1248 # Arrange in reverse order so items can be efficiently popped
1249 1249 # from a stack of chucks.
1250 1250 chunks.reverse()
1251 1251
1252 1252 while chunks:
1253 1253
1254 1254 # Start the list of chunks that will make up the current line.
1255 1255 # cur_len is just the length of all the chunks in cur_line.
1256 1256 cur_line = []
1257 1257 cur_len = 0
1258 1258
1259 1259 # Figure out which static string will prefix this line.
1260 1260 if lines:
1261 1261 indent = self.subsequent_indent
1262 1262 else:
1263 1263 indent = self.initial_indent
1264 1264
1265 1265 # Maximum width for this line.
1266 1266 width = self.width - len(indent)
1267 1267
1268 1268 # First chunk on line is whitespace -- drop it, unless this
1269 1269 # is the very beginning of the text (ie. no lines started yet).
1270 1270 if self.drop_whitespace and chunks[-1].strip() == '' and lines:
1271 1271 del chunks[-1]
1272 1272
1273 1273 while chunks:
1274 1274 l = colwidth(chunks[-1])
1275 1275
1276 1276 # Can at least squeeze this chunk onto the current line.
1277 1277 if cur_len + l <= width:
1278 1278 cur_line.append(chunks.pop())
1279 1279 cur_len += l
1280 1280
1281 1281 # Nope, this line is full.
1282 1282 else:
1283 1283 break
1284 1284
1285 1285 # The current line is full, and the next chunk is too big to
1286 1286 # fit on *any* line (not just this one).
1287 1287 if chunks and colwidth(chunks[-1]) > width:
1288 1288 self._handle_long_word(chunks, cur_line, cur_len, width)
1289 1289
1290 1290 # If the last chunk on this line is all whitespace, drop it.
1291 1291 if (self.drop_whitespace and
1292 1292 cur_line and cur_line[-1].strip() == ''):
1293 1293 del cur_line[-1]
1294 1294
1295 1295 # Convert current line back to a string and store it in list
1296 1296 # of all lines (return value).
1297 1297 if cur_line:
1298 1298 lines.append(indent + ''.join(cur_line))
1299 1299
1300 1300 return lines
1301 1301
1302 1302 global MBTextWrapper
1303 1303 MBTextWrapper = tw
1304 1304 return tw(**kwargs)
1305 1305
1306 1306 def wrap(line, width, initindent='', hangindent=''):
1307 1307 maxindent = max(len(hangindent), len(initindent))
1308 1308 if width <= maxindent:
1309 1309 # adjust for weird terminal size
1310 1310 width = max(78, maxindent + 1)
1311 1311 line = line.decode(encoding.encoding, encoding.encodingmode)
1312 1312 initindent = initindent.decode(encoding.encoding, encoding.encodingmode)
1313 1313 hangindent = hangindent.decode(encoding.encoding, encoding.encodingmode)
1314 1314 wrapper = MBTextWrapper(width=width,
1315 1315 initial_indent=initindent,
1316 1316 subsequent_indent=hangindent)
1317 1317 return wrapper.fill(line).encode(encoding.encoding)
1318 1318
1319 1319 def iterlines(iterator):
1320 1320 for chunk in iterator:
1321 1321 for line in chunk.splitlines():
1322 1322 yield line
1323 1323
1324 1324 def expandpath(path):
1325 1325 return os.path.expanduser(os.path.expandvars(path))
1326 1326
1327 1327 def hgcmd():
1328 1328 """Return the command used to execute current hg
1329 1329
1330 1330 This is different from hgexecutable() because on Windows we want
1331 1331 to avoid things opening new shell windows like batch files, so we
1332 1332 get either the python call or current executable.
1333 1333 """
1334 1334 if mainfrozen():
1335 1335 return [sys.executable]
1336 1336 return gethgcmd()
1337 1337
1338 1338 def rundetached(args, condfn):
1339 1339 """Execute the argument list in a detached process.
1340 1340
1341 1341 condfn is a callable which is called repeatedly and should return
1342 1342 True once the child process is known to have started successfully.
1343 1343 At this point, the child process PID is returned. If the child
1344 1344 process fails to start or finishes before condfn() evaluates to
1345 1345 True, return -1.
1346 1346 """
1347 1347 # Windows case is easier because the child process is either
1348 1348 # successfully starting and validating the condition or exiting
1349 1349 # on failure. We just poll on its PID. On Unix, if the child
1350 1350 # process fails to start, it will be left in a zombie state until
1351 1351 # the parent wait on it, which we cannot do since we expect a long
1352 1352 # running process on success. Instead we listen for SIGCHLD telling
1353 1353 # us our child process terminated.
1354 1354 terminated = set()
1355 1355 def handler(signum, frame):
1356 1356 terminated.add(os.wait())
1357 1357 prevhandler = None
1358 1358 SIGCHLD = getattr(signal, 'SIGCHLD', None)
1359 1359 if SIGCHLD is not None:
1360 1360 prevhandler = signal.signal(SIGCHLD, handler)
1361 1361 try:
1362 1362 pid = spawndetached(args)
1363 1363 while not condfn():
1364 1364 if ((pid in terminated or not testpid(pid))
1365 1365 and not condfn()):
1366 1366 return -1
1367 1367 time.sleep(0.1)
1368 1368 return pid
1369 1369 finally:
1370 1370 if prevhandler is not None:
1371 1371 signal.signal(signal.SIGCHLD, prevhandler)
1372 1372
1373 1373 try:
1374 1374 any, all = any, all
1375 1375 except NameError:
1376 1376 def any(iterable):
1377 1377 for i in iterable:
1378 1378 if i:
1379 1379 return True
1380 1380 return False
1381 1381
1382 1382 def all(iterable):
1383 1383 for i in iterable:
1384 1384 if not i:
1385 1385 return False
1386 1386 return True
1387 1387
1388 1388 def interpolate(prefix, mapping, s, fn=None, escape_prefix=False):
1389 1389 """Return the result of interpolating items in the mapping into string s.
1390 1390
1391 1391 prefix is a single character string, or a two character string with
1392 1392 a backslash as the first character if the prefix needs to be escaped in
1393 1393 a regular expression.
1394 1394
1395 1395 fn is an optional function that will be applied to the replacement text
1396 1396 just before replacement.
1397 1397
1398 1398 escape_prefix is an optional flag that allows using doubled prefix for
1399 1399 its escaping.
1400 1400 """
1401 1401 fn = fn or (lambda s: s)
1402 1402 patterns = '|'.join(mapping.keys())
1403 1403 if escape_prefix:
1404 1404 patterns += '|' + prefix
1405 1405 if len(prefix) > 1:
1406 1406 prefix_char = prefix[1:]
1407 1407 else:
1408 1408 prefix_char = prefix
1409 1409 mapping[prefix_char] = prefix_char
1410 1410 r = re.compile(r'%s(%s)' % (prefix, patterns))
1411 1411 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1412 1412
1413 1413 def getport(port):
1414 1414 """Return the port for a given network service.
1415 1415
1416 1416 If port is an integer, it's returned as is. If it's a string, it's
1417 1417 looked up using socket.getservbyname(). If there's no matching
1418 1418 service, util.Abort is raised.
1419 1419 """
1420 1420 try:
1421 1421 return int(port)
1422 1422 except ValueError:
1423 1423 pass
1424 1424
1425 1425 try:
1426 1426 return socket.getservbyname(port)
1427 1427 except socket.error:
1428 1428 raise Abort(_("no port number associated with service '%s'") % port)
1429 1429
1430 1430 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1431 1431 '0': False, 'no': False, 'false': False, 'off': False,
1432 1432 'never': False}
1433 1433
1434 1434 def parsebool(s):
1435 1435 """Parse s into a boolean.
1436 1436
1437 1437 If s is not a valid boolean, returns None.
1438 1438 """
1439 1439 return _booleans.get(s.lower(), None)
1440 1440
1441 1441 _hexdig = '0123456789ABCDEFabcdef'
1442 1442 _hextochr = dict((a + b, chr(int(a + b, 16)))
1443 1443 for a in _hexdig for b in _hexdig)
1444 1444
1445 1445 def _urlunquote(s):
1446 1446 """unquote('abc%20def') -> 'abc def'."""
1447 1447 res = s.split('%')
1448 1448 # fastpath
1449 1449 if len(res) == 1:
1450 1450 return s
1451 1451 s = res[0]
1452 1452 for item in res[1:]:
1453 1453 try:
1454 1454 s += _hextochr[item[:2]] + item[2:]
1455 1455 except KeyError:
1456 1456 s += '%' + item
1457 1457 except UnicodeDecodeError:
1458 1458 s += unichr(int(item[:2], 16)) + item[2:]
1459 1459 return s
1460 1460
1461 1461 class url(object):
1462 1462 r"""Reliable URL parser.
1463 1463
1464 1464 This parses URLs and provides attributes for the following
1465 1465 components:
1466 1466
1467 1467 <scheme>://<user>:<passwd>@<host>:<port>/<path>?<query>#<fragment>
1468 1468
1469 1469 Missing components are set to None. The only exception is
1470 1470 fragment, which is set to '' if present but empty.
1471 1471
1472 1472 If parsefragment is False, fragment is included in query. If
1473 1473 parsequery is False, query is included in path. If both are
1474 1474 False, both fragment and query are included in path.
1475 1475
1476 1476 See http://www.ietf.org/rfc/rfc2396.txt for more information.
1477 1477
1478 1478 Note that for backward compatibility reasons, bundle URLs do not
1479 1479 take host names. That means 'bundle://../' has a path of '../'.
1480 1480
1481 1481 Examples:
1482 1482
1483 1483 >>> url('http://www.ietf.org/rfc/rfc2396.txt')
1484 1484 <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
1485 1485 >>> url('ssh://[::1]:2200//home/joe/repo')
1486 1486 <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
1487 1487 >>> url('file:///home/joe/repo')
1488 1488 <url scheme: 'file', path: '/home/joe/repo'>
1489 1489 >>> url('file:///c:/temp/foo/')
1490 1490 <url scheme: 'file', path: 'c:/temp/foo/'>
1491 1491 >>> url('bundle:foo')
1492 1492 <url scheme: 'bundle', path: 'foo'>
1493 1493 >>> url('bundle://../foo')
1494 1494 <url scheme: 'bundle', path: '../foo'>
1495 1495 >>> url(r'c:\foo\bar')
1496 1496 <url path: 'c:\\foo\\bar'>
1497 1497 >>> url(r'\\blah\blah\blah')
1498 1498 <url path: '\\\\blah\\blah\\blah'>
1499 1499 >>> url(r'\\blah\blah\blah#baz')
1500 1500 <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
1501 1501
1502 1502 Authentication credentials:
1503 1503
1504 1504 >>> url('ssh://joe:xyz@x/repo')
1505 1505 <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
1506 1506 >>> url('ssh://joe@x/repo')
1507 1507 <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
1508 1508
1509 1509 Query strings and fragments:
1510 1510
1511 1511 >>> url('http://host/a?b#c')
1512 1512 <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
1513 1513 >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
1514 1514 <url scheme: 'http', host: 'host', path: 'a?b#c'>
1515 1515 """
1516 1516
1517 1517 _safechars = "!~*'()+"
1518 1518 _safepchars = "/!~*'()+:"
1519 1519 _matchscheme = re.compile(r'^[a-zA-Z0-9+.\-]+:').match
1520 1520
1521 1521 def __init__(self, path, parsequery=True, parsefragment=True):
1522 1522 # We slowly chomp away at path until we have only the path left
1523 1523 self.scheme = self.user = self.passwd = self.host = None
1524 1524 self.port = self.path = self.query = self.fragment = None
1525 1525 self._localpath = True
1526 1526 self._hostport = ''
1527 1527 self._origpath = path
1528 1528
1529 1529 if parsefragment and '#' in path:
1530 1530 path, self.fragment = path.split('#', 1)
1531 1531 if not path:
1532 1532 path = None
1533 1533
1534 1534 # special case for Windows drive letters and UNC paths
1535 1535 if hasdriveletter(path) or path.startswith(r'\\'):
1536 1536 self.path = path
1537 1537 return
1538 1538
1539 1539 # For compatibility reasons, we can't handle bundle paths as
1540 1540 # normal URLS
1541 1541 if path.startswith('bundle:'):
1542 1542 self.scheme = 'bundle'
1543 1543 path = path[7:]
1544 1544 if path.startswith('//'):
1545 1545 path = path[2:]
1546 1546 self.path = path
1547 1547 return
1548 1548
1549 1549 if self._matchscheme(path):
1550 1550 parts = path.split(':', 1)
1551 1551 if parts[0]:
1552 1552 self.scheme, path = parts
1553 1553 self._localpath = False
1554 1554
1555 1555 if not path:
1556 1556 path = None
1557 1557 if self._localpath:
1558 1558 self.path = ''
1559 1559 return
1560 1560 else:
1561 1561 if self._localpath:
1562 1562 self.path = path
1563 1563 return
1564 1564
1565 1565 if parsequery and '?' in path:
1566 1566 path, self.query = path.split('?', 1)
1567 1567 if not path:
1568 1568 path = None
1569 1569 if not self.query:
1570 1570 self.query = None
1571 1571
1572 1572 # // is required to specify a host/authority
1573 1573 if path and path.startswith('//'):
1574 1574 parts = path[2:].split('/', 1)
1575 1575 if len(parts) > 1:
1576 1576 self.host, path = parts
1577 1577 path = path
1578 1578 else:
1579 1579 self.host = parts[0]
1580 1580 path = None
1581 1581 if not self.host:
1582 1582 self.host = None
1583 1583 # path of file:///d is /d
1584 1584 # path of file:///d:/ is d:/, not /d:/
1585 1585 if path and not hasdriveletter(path):
1586 1586 path = '/' + path
1587 1587
1588 1588 if self.host and '@' in self.host:
1589 1589 self.user, self.host = self.host.rsplit('@', 1)
1590 1590 if ':' in self.user:
1591 1591 self.user, self.passwd = self.user.split(':', 1)
1592 1592 if not self.host:
1593 1593 self.host = None
1594 1594
1595 1595 # Don't split on colons in IPv6 addresses without ports
1596 1596 if (self.host and ':' in self.host and
1597 1597 not (self.host.startswith('[') and self.host.endswith(']'))):
1598 1598 self._hostport = self.host
1599 1599 self.host, self.port = self.host.rsplit(':', 1)
1600 1600 if not self.host:
1601 1601 self.host = None
1602 1602
1603 1603 if (self.host and self.scheme == 'file' and
1604 1604 self.host not in ('localhost', '127.0.0.1', '[::1]')):
1605 1605 raise Abort(_('file:// URLs can only refer to localhost'))
1606 1606
1607 1607 self.path = path
1608 1608
1609 1609 # leave the query string escaped
1610 1610 for a in ('user', 'passwd', 'host', 'port',
1611 1611 'path', 'fragment'):
1612 1612 v = getattr(self, a)
1613 1613 if v is not None:
1614 1614 setattr(self, a, _urlunquote(v))
1615 1615
1616 1616 def __repr__(self):
1617 1617 attrs = []
1618 1618 for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
1619 1619 'query', 'fragment'):
1620 1620 v = getattr(self, a)
1621 1621 if v is not None:
1622 1622 attrs.append('%s: %r' % (a, v))
1623 1623 return '<url %s>' % ', '.join(attrs)
1624 1624
1625 1625 def __str__(self):
1626 1626 r"""Join the URL's components back into a URL string.
1627 1627
1628 1628 Examples:
1629 1629
1630 1630 >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
1631 1631 'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
1632 1632 >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
1633 1633 'http://user:pw@host:80/?foo=bar&baz=42'
1634 1634 >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
1635 1635 'http://user:pw@host:80/?foo=bar%3dbaz'
1636 1636 >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
1637 1637 'ssh://user:pw@[::1]:2200//home/joe#'
1638 1638 >>> str(url('http://localhost:80//'))
1639 1639 'http://localhost:80//'
1640 1640 >>> str(url('http://localhost:80/'))
1641 1641 'http://localhost:80/'
1642 1642 >>> str(url('http://localhost:80'))
1643 1643 'http://localhost:80/'
1644 1644 >>> str(url('bundle:foo'))
1645 1645 'bundle:foo'
1646 1646 >>> str(url('bundle://../foo'))
1647 1647 'bundle:../foo'
1648 1648 >>> str(url('path'))
1649 1649 'path'
1650 1650 >>> str(url('file:///tmp/foo/bar'))
1651 1651 'file:///tmp/foo/bar'
1652 1652 >>> str(url('file:///c:/tmp/foo/bar'))
1653 1653 'file:///c:/tmp/foo/bar'
1654 1654 >>> print url(r'bundle:foo\bar')
1655 1655 bundle:foo\bar
1656 1656 """
1657 1657 if self._localpath:
1658 1658 s = self.path
1659 1659 if self.scheme == 'bundle':
1660 1660 s = 'bundle:' + s
1661 1661 if self.fragment:
1662 1662 s += '#' + self.fragment
1663 1663 return s
1664 1664
1665 1665 s = self.scheme + ':'
1666 1666 if self.user or self.passwd or self.host:
1667 1667 s += '//'
1668 1668 elif self.scheme and (not self.path or self.path.startswith('/')
1669 1669 or hasdriveletter(self.path)):
1670 1670 s += '//'
1671 1671 if hasdriveletter(self.path):
1672 1672 s += '/'
1673 1673 if self.user:
1674 1674 s += urllib.quote(self.user, safe=self._safechars)
1675 1675 if self.passwd:
1676 1676 s += ':' + urllib.quote(self.passwd, safe=self._safechars)
1677 1677 if self.user or self.passwd:
1678 1678 s += '@'
1679 1679 if self.host:
1680 1680 if not (self.host.startswith('[') and self.host.endswith(']')):
1681 1681 s += urllib.quote(self.host)
1682 1682 else:
1683 1683 s += self.host
1684 1684 if self.port:
1685 1685 s += ':' + urllib.quote(self.port)
1686 1686 if self.host:
1687 1687 s += '/'
1688 1688 if self.path:
1689 1689 # TODO: similar to the query string, we should not unescape the
1690 1690 # path when we store it, the path might contain '%2f' = '/',
1691 1691 # which we should *not* escape.
1692 1692 s += urllib.quote(self.path, safe=self._safepchars)
1693 1693 if self.query:
1694 1694 # we store the query in escaped form.
1695 1695 s += '?' + self.query
1696 1696 if self.fragment is not None:
1697 1697 s += '#' + urllib.quote(self.fragment, safe=self._safepchars)
1698 1698 return s
1699 1699
1700 1700 def authinfo(self):
1701 1701 user, passwd = self.user, self.passwd
1702 1702 try:
1703 1703 self.user, self.passwd = None, None
1704 1704 s = str(self)
1705 1705 finally:
1706 1706 self.user, self.passwd = user, passwd
1707 1707 if not self.user:
1708 1708 return (s, None)
1709 1709 # authinfo[1] is passed to urllib2 password manager, and its
1710 1710 # URIs must not contain credentials. The host is passed in the
1711 1711 # URIs list because Python < 2.4.3 uses only that to search for
1712 1712 # a password.
1713 1713 return (s, (None, (s, self.host),
1714 1714 self.user, self.passwd or ''))
1715 1715
1716 1716 def isabs(self):
1717 1717 if self.scheme and self.scheme != 'file':
1718 1718 return True # remote URL
1719 1719 if hasdriveletter(self.path):
1720 1720 return True # absolute for our purposes - can't be joined()
1721 1721 if self.path.startswith(r'\\'):
1722 1722 return True # Windows UNC path
1723 1723 if self.path.startswith('/'):
1724 1724 return True # POSIX-style
1725 1725 return False
1726 1726
1727 1727 def localpath(self):
1728 1728 if self.scheme == 'file' or self.scheme == 'bundle':
1729 1729 path = self.path or '/'
1730 1730 # For Windows, we need to promote hosts containing drive
1731 1731 # letters to paths with drive letters.
1732 1732 if hasdriveletter(self._hostport):
1733 1733 path = self._hostport + '/' + self.path
1734 1734 elif (self.host is not None and self.path
1735 1735 and not hasdriveletter(path)):
1736 1736 path = '/' + path
1737 1737 return path
1738 1738 return self._origpath
1739 1739
1740 1740 def hasscheme(path):
1741 1741 return bool(url(path).scheme)
1742 1742
1743 1743 def hasdriveletter(path):
1744 1744 return path and path[1:2] == ':' and path[0:1].isalpha()
1745 1745
1746 1746 def urllocalpath(path):
1747 1747 return url(path, parsequery=False, parsefragment=False).localpath()
1748 1748
1749 1749 def hidepassword(u):
1750 1750 '''hide user credential in a url string'''
1751 1751 u = url(u)
1752 1752 if u.passwd:
1753 1753 u.passwd = '***'
1754 1754 return str(u)
1755 1755
1756 1756 def removeauth(u):
1757 1757 '''remove all authentication information from a url string'''
1758 1758 u = url(u)
1759 1759 u.user = u.passwd = None
1760 1760 return str(u)
1761 1761
1762 1762 def isatty(fd):
1763 1763 try:
1764 1764 return fd.isatty()
1765 1765 except AttributeError:
1766 1766 return False
General Comments 0
You need to be logged in to leave comments. Login now