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