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