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