##// END OF EJS Templates
util: work around behavior change in Python 2.7.1
Steve Borho -
r13128:dbc54681 stable
parent child Browse files
Show More
@@ -1,1484 +1,1485 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, stat, time, calendar, textwrap, unicodedata, signal
20 20 import imp, socket
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 if sys.version_info >= (2, 5):
32 32 from hashlib import sha1 as _sha1
33 33 else:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import __builtin__
40 40
41 41 if sys.version_info[0] < 3:
42 42 def fakebuffer(sliceable, offset=0):
43 43 return sliceable[offset:]
44 44 else:
45 45 def fakebuffer(sliceable, offset=0):
46 46 return memoryview(sliceable)[offset:]
47 47 try:
48 48 buffer
49 49 except NameError:
50 50 __builtin__.buffer = fakebuffer
51 51
52 52 import subprocess
53 53 closefds = os.name == 'posix'
54 54
55 55 def popen2(cmd, env=None, newlines=False):
56 56 # Setting bufsize to -1 lets the system decide the buffer size.
57 57 # The default for bufsize is 0, meaning unbuffered. This leads to
58 58 # poor performance on Mac OS X: http://bugs.python.org/issue4194
59 59 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
60 60 close_fds=closefds,
61 61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 62 universal_newlines=newlines,
63 63 env=env)
64 64 return p.stdin, p.stdout
65 65
66 66 def popen3(cmd, env=None, newlines=False):
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.PIPE,
71 71 universal_newlines=newlines,
72 72 env=env)
73 73 return p.stdin, p.stdout, p.stderr
74 74
75 75 def version():
76 76 """Return version information if available."""
77 77 try:
78 78 import __version__
79 79 return __version__.version
80 80 except ImportError:
81 81 return 'unknown'
82 82
83 83 # used by parsedate
84 84 defaultdateformats = (
85 85 '%Y-%m-%d %H:%M:%S',
86 86 '%Y-%m-%d %I:%M:%S%p',
87 87 '%Y-%m-%d %H:%M',
88 88 '%Y-%m-%d %I:%M%p',
89 89 '%Y-%m-%d',
90 90 '%m-%d',
91 91 '%m/%d',
92 92 '%m/%d/%y',
93 93 '%m/%d/%Y',
94 94 '%a %b %d %H:%M:%S %Y',
95 95 '%a %b %d %I:%M:%S%p %Y',
96 96 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
97 97 '%b %d %H:%M:%S %Y',
98 98 '%b %d %I:%M:%S%p %Y',
99 99 '%b %d %H:%M:%S',
100 100 '%b %d %I:%M:%S%p',
101 101 '%b %d %H:%M',
102 102 '%b %d %I:%M%p',
103 103 '%b %d %Y',
104 104 '%b %d',
105 105 '%H:%M:%S',
106 106 '%I:%M:%S%p',
107 107 '%H:%M',
108 108 '%I:%M%p',
109 109 )
110 110
111 111 extendeddateformats = defaultdateformats + (
112 112 "%Y",
113 113 "%Y-%m",
114 114 "%b",
115 115 "%b %Y",
116 116 )
117 117
118 118 def cachefunc(func):
119 119 '''cache the result of function calls'''
120 120 # XXX doesn't handle keywords args
121 121 cache = {}
122 122 if func.func_code.co_argcount == 1:
123 123 # we gain a small amount of time because
124 124 # we don't need to pack/unpack the list
125 125 def f(arg):
126 126 if arg not in cache:
127 127 cache[arg] = func(arg)
128 128 return cache[arg]
129 129 else:
130 130 def f(*args):
131 131 if args not in cache:
132 132 cache[args] = func(*args)
133 133 return cache[args]
134 134
135 135 return f
136 136
137 137 def lrucachefunc(func):
138 138 '''cache most recent results of function calls'''
139 139 cache = {}
140 140 order = []
141 141 if func.func_code.co_argcount == 1:
142 142 def f(arg):
143 143 if arg not in cache:
144 144 if len(cache) > 20:
145 145 del cache[order.pop(0)]
146 146 cache[arg] = func(arg)
147 147 else:
148 148 order.remove(arg)
149 149 order.append(arg)
150 150 return cache[arg]
151 151 else:
152 152 def f(*args):
153 153 if args not in cache:
154 154 if len(cache) > 20:
155 155 del cache[order.pop(0)]
156 156 cache[args] = func(*args)
157 157 else:
158 158 order.remove(args)
159 159 order.append(args)
160 160 return cache[args]
161 161
162 162 return f
163 163
164 164 class propertycache(object):
165 165 def __init__(self, func):
166 166 self.func = func
167 167 self.name = func.__name__
168 168 def __get__(self, obj, type=None):
169 169 result = self.func(obj)
170 170 setattr(obj, self.name, result)
171 171 return result
172 172
173 173 def pipefilter(s, cmd):
174 174 '''filter string S through command CMD, returning its output'''
175 175 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
176 176 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
177 177 pout, perr = p.communicate(s)
178 178 return pout
179 179
180 180 def tempfilter(s, cmd):
181 181 '''filter string S through a pair of temporary files with CMD.
182 182 CMD is used as a template to create the real command to be run,
183 183 with the strings INFILE and OUTFILE replaced by the real names of
184 184 the temporary files generated.'''
185 185 inname, outname = None, None
186 186 try:
187 187 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
188 188 fp = os.fdopen(infd, 'wb')
189 189 fp.write(s)
190 190 fp.close()
191 191 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
192 192 os.close(outfd)
193 193 cmd = cmd.replace('INFILE', inname)
194 194 cmd = cmd.replace('OUTFILE', outname)
195 195 code = os.system(cmd)
196 196 if sys.platform == 'OpenVMS' and code & 1:
197 197 code = 0
198 198 if code:
199 199 raise Abort(_("command '%s' failed: %s") %
200 200 (cmd, explain_exit(code)))
201 201 return open(outname, 'rb').read()
202 202 finally:
203 203 try:
204 204 if inname:
205 205 os.unlink(inname)
206 206 except:
207 207 pass
208 208 try:
209 209 if outname:
210 210 os.unlink(outname)
211 211 except:
212 212 pass
213 213
214 214 filtertable = {
215 215 'tempfile:': tempfilter,
216 216 'pipe:': pipefilter,
217 217 }
218 218
219 219 def filter(s, cmd):
220 220 "filter a string through a command that transforms its input to its output"
221 221 for name, fn in filtertable.iteritems():
222 222 if cmd.startswith(name):
223 223 return fn(s, cmd[len(name):].lstrip())
224 224 return pipefilter(s, cmd)
225 225
226 226 def binary(s):
227 227 """return true if a string is binary data"""
228 228 return bool(s and '\0' in s)
229 229
230 230 def increasingchunks(source, min=1024, max=65536):
231 231 '''return no less than min bytes per chunk while data remains,
232 232 doubling min after each chunk until it reaches max'''
233 233 def log2(x):
234 234 if not x:
235 235 return 0
236 236 i = 0
237 237 while x:
238 238 x >>= 1
239 239 i += 1
240 240 return i - 1
241 241
242 242 buf = []
243 243 blen = 0
244 244 for chunk in source:
245 245 buf.append(chunk)
246 246 blen += len(chunk)
247 247 if blen >= min:
248 248 if min < max:
249 249 min = min << 1
250 250 nmin = 1 << log2(blen)
251 251 if nmin > min:
252 252 min = nmin
253 253 if min > max:
254 254 min = max
255 255 yield ''.join(buf)
256 256 blen = 0
257 257 buf = []
258 258 if buf:
259 259 yield ''.join(buf)
260 260
261 261 Abort = error.Abort
262 262
263 263 def always(fn):
264 264 return True
265 265
266 266 def never(fn):
267 267 return False
268 268
269 269 def pathto(root, n1, n2):
270 270 '''return the relative path from one place to another.
271 271 root should use os.sep to separate directories
272 272 n1 should use os.sep to separate directories
273 273 n2 should use "/" to separate directories
274 274 returns an os.sep-separated path.
275 275
276 276 If n1 is a relative path, it's assumed it's
277 277 relative to root.
278 278 n2 should always be relative to root.
279 279 '''
280 280 if not n1:
281 281 return localpath(n2)
282 282 if os.path.isabs(n1):
283 283 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
284 284 return os.path.join(root, localpath(n2))
285 285 n2 = '/'.join((pconvert(root), n2))
286 286 a, b = splitpath(n1), n2.split('/')
287 287 a.reverse()
288 288 b.reverse()
289 289 while a and b and a[-1] == b[-1]:
290 290 a.pop()
291 291 b.pop()
292 292 b.reverse()
293 293 return os.sep.join((['..'] * len(a)) + b) or '.'
294 294
295 295 def canonpath(root, cwd, myname, auditor=None):
296 296 """return the canonical path of myname, given cwd and root"""
297 297 if endswithsep(root):
298 298 rootsep = root
299 299 else:
300 300 rootsep = root + os.sep
301 301 name = myname
302 302 if not os.path.isabs(name):
303 303 name = os.path.join(root, cwd, name)
304 304 name = os.path.normpath(name)
305 305 if auditor is None:
306 306 auditor = path_auditor(root)
307 307 if name != rootsep and name.startswith(rootsep):
308 308 name = name[len(rootsep):]
309 309 auditor(name)
310 310 return pconvert(name)
311 311 elif name == root:
312 312 return ''
313 313 else:
314 314 # Determine whether `name' is in the hierarchy at or beneath `root',
315 315 # by iterating name=dirname(name) until that causes no change (can't
316 316 # check name == '/', because that doesn't work on windows). For each
317 317 # `name', compare dev/inode numbers. If they match, the list `rel'
318 318 # holds the reversed list of components making up the relative file
319 319 # name we want.
320 320 root_st = os.stat(root)
321 321 rel = []
322 322 while True:
323 323 try:
324 324 name_st = os.stat(name)
325 325 except OSError:
326 326 break
327 327 if samestat(name_st, root_st):
328 328 if not rel:
329 329 # name was actually the same as root (maybe a symlink)
330 330 return ''
331 331 rel.reverse()
332 332 name = os.path.join(*rel)
333 333 auditor(name)
334 334 return pconvert(name)
335 335 dirname, basename = os.path.split(name)
336 336 rel.append(basename)
337 337 if dirname == name:
338 338 break
339 339 name = dirname
340 340
341 341 raise Abort('%s not under root' % myname)
342 342
343 343 _hgexecutable = None
344 344
345 345 def main_is_frozen():
346 346 """return True if we are a frozen executable.
347 347
348 348 The code supports py2exe (most common, Windows only) and tools/freeze
349 349 (portable, not much used).
350 350 """
351 351 return (hasattr(sys, "frozen") or # new py2exe
352 352 hasattr(sys, "importers") or # old py2exe
353 353 imp.is_frozen("__main__")) # tools/freeze
354 354
355 355 def hgexecutable():
356 356 """return location of the 'hg' executable.
357 357
358 358 Defaults to $HG or 'hg' in the search path.
359 359 """
360 360 if _hgexecutable is None:
361 361 hg = os.environ.get('HG')
362 362 if hg:
363 363 set_hgexecutable(hg)
364 364 elif main_is_frozen():
365 365 set_hgexecutable(sys.executable)
366 366 else:
367 367 exe = find_exe('hg') or os.path.basename(sys.argv[0])
368 368 set_hgexecutable(exe)
369 369 return _hgexecutable
370 370
371 371 def set_hgexecutable(path):
372 372 """set location of the 'hg' executable"""
373 373 global _hgexecutable
374 374 _hgexecutable = path
375 375
376 376 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None, out=None):
377 377 '''enhanced shell command execution.
378 378 run with environment maybe modified, maybe in different dir.
379 379
380 380 if command fails and onerr is None, return status. if ui object,
381 381 print error message and return status, else raise onerr object as
382 382 exception.
383 383
384 384 if out is specified, it is assumed to be a file-like object that has a
385 385 write() method. stdout and stderr will be redirected to out.'''
386 386 def py2shell(val):
387 387 'convert python object into string that is useful to shell'
388 388 if val is None or val is False:
389 389 return '0'
390 390 if val is True:
391 391 return '1'
392 392 return str(val)
393 393 origcmd = cmd
394 if os.name == 'nt':
394 if os.name == 'nt' and sys.version_info < (2, 7, 1):
395 # Python versions since 2.7.1 do this extra quoting themselves
395 396 cmd = '"%s"' % cmd
396 397 env = dict(os.environ)
397 398 env.update((k, py2shell(v)) for k, v in environ.iteritems())
398 399 env['HG'] = hgexecutable()
399 400 if out is None:
400 401 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
401 402 env=env, cwd=cwd)
402 403 else:
403 404 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
404 405 env=env, cwd=cwd, stdout=subprocess.PIPE,
405 406 stderr=subprocess.STDOUT)
406 407 for line in proc.stdout:
407 408 out.write(line)
408 409 proc.wait()
409 410 rc = proc.returncode
410 411 if sys.platform == 'OpenVMS' and rc & 1:
411 412 rc = 0
412 413 if rc and onerr:
413 414 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
414 415 explain_exit(rc)[0])
415 416 if errprefix:
416 417 errmsg = '%s: %s' % (errprefix, errmsg)
417 418 try:
418 419 onerr.warn(errmsg + '\n')
419 420 except AttributeError:
420 421 raise onerr(errmsg)
421 422 return rc
422 423
423 424 def checksignature(func):
424 425 '''wrap a function with code to check for calling errors'''
425 426 def check(*args, **kwargs):
426 427 try:
427 428 return func(*args, **kwargs)
428 429 except TypeError:
429 430 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
430 431 raise error.SignatureError
431 432 raise
432 433
433 434 return check
434 435
435 436 def unlink(f):
436 437 """unlink and remove the directory if it is empty"""
437 438 os.unlink(f)
438 439 # try removing directories that might now be empty
439 440 try:
440 441 os.removedirs(os.path.dirname(f))
441 442 except OSError:
442 443 pass
443 444
444 445 def copyfile(src, dest):
445 446 "copy a file, preserving mode and atime/mtime"
446 447 if os.path.islink(src):
447 448 try:
448 449 os.unlink(dest)
449 450 except:
450 451 pass
451 452 os.symlink(os.readlink(src), dest)
452 453 else:
453 454 try:
454 455 shutil.copyfile(src, dest)
455 456 shutil.copystat(src, dest)
456 457 except shutil.Error, inst:
457 458 raise Abort(str(inst))
458 459
459 460 def copyfiles(src, dst, hardlink=None):
460 461 """Copy a directory tree using hardlinks if possible"""
461 462
462 463 if hardlink is None:
463 464 hardlink = (os.stat(src).st_dev ==
464 465 os.stat(os.path.dirname(dst)).st_dev)
465 466
466 467 num = 0
467 468 if os.path.isdir(src):
468 469 os.mkdir(dst)
469 470 for name, kind in osutil.listdir(src):
470 471 srcname = os.path.join(src, name)
471 472 dstname = os.path.join(dst, name)
472 473 hardlink, n = copyfiles(srcname, dstname, hardlink)
473 474 num += n
474 475 else:
475 476 if hardlink:
476 477 try:
477 478 os_link(src, dst)
478 479 except (IOError, OSError):
479 480 hardlink = False
480 481 shutil.copy(src, dst)
481 482 else:
482 483 shutil.copy(src, dst)
483 484 num += 1
484 485
485 486 return hardlink, num
486 487
487 488 class path_auditor(object):
488 489 '''ensure that a filesystem path contains no banned components.
489 490 the following properties of a path are checked:
490 491
491 492 - under top-level .hg
492 493 - starts at the root of a windows drive
493 494 - contains ".."
494 495 - traverses a symlink (e.g. a/symlink_here/b)
495 496 - inside a nested repository (a callback can be used to approve
496 497 some nested repositories, e.g., subrepositories)
497 498 '''
498 499
499 500 def __init__(self, root, callback=None):
500 501 self.audited = set()
501 502 self.auditeddir = set()
502 503 self.root = root
503 504 self.callback = callback
504 505
505 506 def __call__(self, path):
506 507 if path in self.audited:
507 508 return
508 509 normpath = os.path.normcase(path)
509 510 parts = splitpath(normpath)
510 511 if (os.path.splitdrive(path)[0]
511 512 or parts[0].lower() in ('.hg', '.hg.', '')
512 513 or os.pardir in parts):
513 514 raise Abort(_("path contains illegal component: %s") % path)
514 515 if '.hg' in path.lower():
515 516 lparts = [p.lower() for p in parts]
516 517 for p in '.hg', '.hg.':
517 518 if p in lparts[1:]:
518 519 pos = lparts.index(p)
519 520 base = os.path.join(*parts[:pos])
520 521 raise Abort(_('path %r is inside repo %r') % (path, base))
521 522 def check(prefix):
522 523 curpath = os.path.join(self.root, prefix)
523 524 try:
524 525 st = os.lstat(curpath)
525 526 except OSError, err:
526 527 # EINVAL can be raised as invalid path syntax under win32.
527 528 # They must be ignored for patterns can be checked too.
528 529 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
529 530 raise
530 531 else:
531 532 if stat.S_ISLNK(st.st_mode):
532 533 raise Abort(_('path %r traverses symbolic link %r') %
533 534 (path, prefix))
534 535 elif (stat.S_ISDIR(st.st_mode) and
535 536 os.path.isdir(os.path.join(curpath, '.hg'))):
536 537 if not self.callback or not self.callback(curpath):
537 538 raise Abort(_('path %r is inside repo %r') %
538 539 (path, prefix))
539 540 parts.pop()
540 541 prefixes = []
541 542 while parts:
542 543 prefix = os.sep.join(parts)
543 544 if prefix in self.auditeddir:
544 545 break
545 546 check(prefix)
546 547 prefixes.append(prefix)
547 548 parts.pop()
548 549
549 550 self.audited.add(path)
550 551 # only add prefixes to the cache after checking everything: we don't
551 552 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
552 553 self.auditeddir.update(prefixes)
553 554
554 555 def nlinks(pathname):
555 556 """Return number of hardlinks for the given file."""
556 557 return os.lstat(pathname).st_nlink
557 558
558 559 if hasattr(os, 'link'):
559 560 os_link = os.link
560 561 else:
561 562 def os_link(src, dst):
562 563 raise OSError(0, _("Hardlinks not supported"))
563 564
564 565 def lookup_reg(key, name=None, scope=None):
565 566 return None
566 567
567 568 def hidewindow():
568 569 """Hide current shell window.
569 570
570 571 Used to hide the window opened when starting asynchronous
571 572 child process under Windows, unneeded on other systems.
572 573 """
573 574 pass
574 575
575 576 if os.name == 'nt':
576 577 from windows import *
577 578 else:
578 579 from posix import *
579 580
580 581 def makelock(info, pathname):
581 582 try:
582 583 return os.symlink(info, pathname)
583 584 except OSError, why:
584 585 if why.errno == errno.EEXIST:
585 586 raise
586 587 except AttributeError: # no symlink in os
587 588 pass
588 589
589 590 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
590 591 os.write(ld, info)
591 592 os.close(ld)
592 593
593 594 def readlock(pathname):
594 595 try:
595 596 return os.readlink(pathname)
596 597 except OSError, why:
597 598 if why.errno not in (errno.EINVAL, errno.ENOSYS):
598 599 raise
599 600 except AttributeError: # no symlink in os
600 601 pass
601 602 return posixfile(pathname).read()
602 603
603 604 def fstat(fp):
604 605 '''stat file object that may not have fileno method.'''
605 606 try:
606 607 return os.fstat(fp.fileno())
607 608 except AttributeError:
608 609 return os.stat(fp.name)
609 610
610 611 # File system features
611 612
612 613 def checkcase(path):
613 614 """
614 615 Check whether the given path is on a case-sensitive filesystem
615 616
616 617 Requires a path (like /foo/.hg) ending with a foldable final
617 618 directory component.
618 619 """
619 620 s1 = os.stat(path)
620 621 d, b = os.path.split(path)
621 622 p2 = os.path.join(d, b.upper())
622 623 if path == p2:
623 624 p2 = os.path.join(d, b.lower())
624 625 try:
625 626 s2 = os.stat(p2)
626 627 if s2 == s1:
627 628 return False
628 629 return True
629 630 except:
630 631 return True
631 632
632 633 _fspathcache = {}
633 634 def fspath(name, root):
634 635 '''Get name in the case stored in the filesystem
635 636
636 637 The name is either relative to root, or it is an absolute path starting
637 638 with root. Note that this function is unnecessary, and should not be
638 639 called, for case-sensitive filesystems (simply because it's expensive).
639 640 '''
640 641 # If name is absolute, make it relative
641 642 if name.lower().startswith(root.lower()):
642 643 l = len(root)
643 644 if name[l] == os.sep or name[l] == os.altsep:
644 645 l = l + 1
645 646 name = name[l:]
646 647
647 648 if not os.path.lexists(os.path.join(root, name)):
648 649 return None
649 650
650 651 seps = os.sep
651 652 if os.altsep:
652 653 seps = seps + os.altsep
653 654 # Protect backslashes. This gets silly very quickly.
654 655 seps.replace('\\','\\\\')
655 656 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
656 657 dir = os.path.normcase(os.path.normpath(root))
657 658 result = []
658 659 for part, sep in pattern.findall(name):
659 660 if sep:
660 661 result.append(sep)
661 662 continue
662 663
663 664 if dir not in _fspathcache:
664 665 _fspathcache[dir] = os.listdir(dir)
665 666 contents = _fspathcache[dir]
666 667
667 668 lpart = part.lower()
668 669 lenp = len(part)
669 670 for n in contents:
670 671 if lenp == len(n) and n.lower() == lpart:
671 672 result.append(n)
672 673 break
673 674 else:
674 675 # Cannot happen, as the file exists!
675 676 result.append(part)
676 677 dir = os.path.join(dir, lpart)
677 678
678 679 return ''.join(result)
679 680
680 681 def checkexec(path):
681 682 """
682 683 Check whether the given path is on a filesystem with UNIX-like exec flags
683 684
684 685 Requires a directory (like /foo/.hg)
685 686 """
686 687
687 688 # VFAT on some Linux versions can flip mode but it doesn't persist
688 689 # a FS remount. Frequently we can detect it if files are created
689 690 # with exec bit on.
690 691
691 692 try:
692 693 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
693 694 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
694 695 try:
695 696 os.close(fh)
696 697 m = os.stat(fn).st_mode & 0777
697 698 new_file_has_exec = m & EXECFLAGS
698 699 os.chmod(fn, m ^ EXECFLAGS)
699 700 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
700 701 finally:
701 702 os.unlink(fn)
702 703 except (IOError, OSError):
703 704 # we don't care, the user probably won't be able to commit anyway
704 705 return False
705 706 return not (new_file_has_exec or exec_flags_cannot_flip)
706 707
707 708 def checklink(path):
708 709 """check whether the given path is on a symlink-capable filesystem"""
709 710 # mktemp is not racy because symlink creation will fail if the
710 711 # file already exists
711 712 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
712 713 try:
713 714 os.symlink(".", name)
714 715 os.unlink(name)
715 716 return True
716 717 except (OSError, AttributeError):
717 718 return False
718 719
719 720 def checknlink(testfile):
720 721 '''check whether hardlink count reporting works properly'''
721 722 f = testfile + ".hgtmp"
722 723
723 724 try:
724 725 os_link(testfile, f)
725 726 except OSError:
726 727 return False
727 728
728 729 try:
729 730 # nlinks() may behave differently for files on Windows shares if
730 731 # the file is open.
731 732 fd = open(f)
732 733 return nlinks(f) > 1
733 734 finally:
734 735 fd.close()
735 736 os.unlink(f)
736 737
737 738 return False
738 739
739 740 def endswithsep(path):
740 741 '''Check path ends with os.sep or os.altsep.'''
741 742 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
742 743
743 744 def splitpath(path):
744 745 '''Split path by os.sep.
745 746 Note that this function does not use os.altsep because this is
746 747 an alternative of simple "xxx.split(os.sep)".
747 748 It is recommended to use os.path.normpath() before using this
748 749 function if need.'''
749 750 return path.split(os.sep)
750 751
751 752 def gui():
752 753 '''Are we running in a GUI?'''
753 754 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
754 755
755 756 def mktempcopy(name, emptyok=False, createmode=None):
756 757 """Create a temporary file with the same contents from name
757 758
758 759 The permission bits are copied from the original file.
759 760
760 761 If the temporary file is going to be truncated immediately, you
761 762 can use emptyok=True as an optimization.
762 763
763 764 Returns the name of the temporary file.
764 765 """
765 766 d, fn = os.path.split(name)
766 767 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
767 768 os.close(fd)
768 769 # Temporary files are created with mode 0600, which is usually not
769 770 # what we want. If the original file already exists, just copy
770 771 # its mode. Otherwise, manually obey umask.
771 772 try:
772 773 st_mode = os.lstat(name).st_mode & 0777
773 774 except OSError, inst:
774 775 if inst.errno != errno.ENOENT:
775 776 raise
776 777 st_mode = createmode
777 778 if st_mode is None:
778 779 st_mode = ~umask
779 780 st_mode &= 0666
780 781 os.chmod(temp, st_mode)
781 782 if emptyok:
782 783 return temp
783 784 try:
784 785 try:
785 786 ifp = posixfile(name, "rb")
786 787 except IOError, inst:
787 788 if inst.errno == errno.ENOENT:
788 789 return temp
789 790 if not getattr(inst, 'filename', None):
790 791 inst.filename = name
791 792 raise
792 793 ofp = posixfile(temp, "wb")
793 794 for chunk in filechunkiter(ifp):
794 795 ofp.write(chunk)
795 796 ifp.close()
796 797 ofp.close()
797 798 except:
798 799 try: os.unlink(temp)
799 800 except: pass
800 801 raise
801 802 return temp
802 803
803 804 class atomictempfile(object):
804 805 """file-like object that atomically updates a file
805 806
806 807 All writes will be redirected to a temporary copy of the original
807 808 file. When rename is called, the copy is renamed to the original
808 809 name, making the changes visible.
809 810 """
810 811 def __init__(self, name, mode='w+b', createmode=None):
811 812 self.__name = name
812 813 self._fp = None
813 814 self.temp = mktempcopy(name, emptyok=('w' in mode),
814 815 createmode=createmode)
815 816 self._fp = posixfile(self.temp, mode)
816 817
817 818 def __getattr__(self, name):
818 819 return getattr(self._fp, name)
819 820
820 821 def rename(self):
821 822 if not self._fp.closed:
822 823 self._fp.close()
823 824 rename(self.temp, localpath(self.__name))
824 825
825 826 def __del__(self):
826 827 if not self._fp:
827 828 return
828 829 if not self._fp.closed:
829 830 try:
830 831 os.unlink(self.temp)
831 832 except: pass
832 833 self._fp.close()
833 834
834 835 def makedirs(name, mode=None):
835 836 """recursive directory creation with parent mode inheritance"""
836 837 parent = os.path.abspath(os.path.dirname(name))
837 838 try:
838 839 os.mkdir(name)
839 840 if mode is not None:
840 841 os.chmod(name, mode)
841 842 return
842 843 except OSError, err:
843 844 if err.errno == errno.EEXIST:
844 845 return
845 846 if not name or parent == name or err.errno != errno.ENOENT:
846 847 raise
847 848 makedirs(parent, mode)
848 849 makedirs(name, mode)
849 850
850 851 class opener(object):
851 852 """Open files relative to a base directory
852 853
853 854 This class is used to hide the details of COW semantics and
854 855 remote file access from higher level code.
855 856 """
856 857 def __init__(self, base, audit=True):
857 858 self.base = base
858 859 if audit:
859 860 self.auditor = path_auditor(base)
860 861 else:
861 862 self.auditor = always
862 863 self.createmode = None
863 864 self._trustnlink = None
864 865
865 866 @propertycache
866 867 def _can_symlink(self):
867 868 return checklink(self.base)
868 869
869 870 def _fixfilemode(self, name):
870 871 if self.createmode is None:
871 872 return
872 873 os.chmod(name, self.createmode & 0666)
873 874
874 875 def __call__(self, path, mode="r", text=False, atomictemp=False):
875 876 self.auditor(path)
876 877 f = os.path.join(self.base, path)
877 878
878 879 if not text and "b" not in mode:
879 880 mode += "b" # for that other OS
880 881
881 882 nlink = -1
882 883 st_mode = None
883 884 dirname, basename = os.path.split(f)
884 885 # If basename is empty, then the path is malformed because it points
885 886 # to a directory. Let the posixfile() call below raise IOError.
886 887 if basename and mode not in ('r', 'rb'):
887 888 if atomictemp:
888 889 if not os.path.isdir(dirname):
889 890 makedirs(dirname, self.createmode)
890 891 return atomictempfile(f, mode, self.createmode)
891 892 try:
892 893 if 'w' in mode:
893 894 st_mode = os.lstat(f).st_mode & 0777
894 895 os.unlink(f)
895 896 nlink = 0
896 897 else:
897 898 # nlinks() may behave differently for files on Windows
898 899 # shares if the file is open.
899 900 fd = open(f)
900 901 nlink = nlinks(f)
901 902 fd.close()
902 903 except (OSError, IOError):
903 904 nlink = 0
904 905 if not os.path.isdir(dirname):
905 906 makedirs(dirname, self.createmode)
906 907 if nlink > 0:
907 908 if self._trustnlink is None:
908 909 self._trustnlink = nlink > 1 or checknlink(f)
909 910 if nlink > 1 or not self._trustnlink:
910 911 rename(mktempcopy(f), f)
911 912 fp = posixfile(f, mode)
912 913 if nlink == 0:
913 914 if st_mode is None:
914 915 self._fixfilemode(f)
915 916 else:
916 917 os.chmod(f, st_mode)
917 918 return fp
918 919
919 920 def symlink(self, src, dst):
920 921 self.auditor(dst)
921 922 linkname = os.path.join(self.base, dst)
922 923 try:
923 924 os.unlink(linkname)
924 925 except OSError:
925 926 pass
926 927
927 928 dirname = os.path.dirname(linkname)
928 929 if not os.path.exists(dirname):
929 930 makedirs(dirname, self.createmode)
930 931
931 932 if self._can_symlink:
932 933 try:
933 934 os.symlink(src, linkname)
934 935 except OSError, err:
935 936 raise OSError(err.errno, _('could not symlink to %r: %s') %
936 937 (src, err.strerror), linkname)
937 938 else:
938 939 f = self(dst, "w")
939 940 f.write(src)
940 941 f.close()
941 942 self._fixfilemode(dst)
942 943
943 944 class chunkbuffer(object):
944 945 """Allow arbitrary sized chunks of data to be efficiently read from an
945 946 iterator over chunks of arbitrary size."""
946 947
947 948 def __init__(self, in_iter):
948 949 """in_iter is the iterator that's iterating over the input chunks.
949 950 targetsize is how big a buffer to try to maintain."""
950 951 def splitbig(chunks):
951 952 for chunk in chunks:
952 953 if len(chunk) > 2**20:
953 954 pos = 0
954 955 while pos < len(chunk):
955 956 end = pos + 2 ** 18
956 957 yield chunk[pos:end]
957 958 pos = end
958 959 else:
959 960 yield chunk
960 961 self.iter = splitbig(in_iter)
961 962 self._queue = []
962 963
963 964 def read(self, l):
964 965 """Read L bytes of data from the iterator of chunks of data.
965 966 Returns less than L bytes if the iterator runs dry."""
966 967 left = l
967 968 buf = ''
968 969 queue = self._queue
969 970 while left > 0:
970 971 # refill the queue
971 972 if not queue:
972 973 target = 2**18
973 974 for chunk in self.iter:
974 975 queue.append(chunk)
975 976 target -= len(chunk)
976 977 if target <= 0:
977 978 break
978 979 if not queue:
979 980 break
980 981
981 982 chunk = queue.pop(0)
982 983 left -= len(chunk)
983 984 if left < 0:
984 985 queue.insert(0, chunk[left:])
985 986 buf += chunk[:left]
986 987 else:
987 988 buf += chunk
988 989
989 990 return buf
990 991
991 992 def filechunkiter(f, size=65536, limit=None):
992 993 """Create a generator that produces the data in the file size
993 994 (default 65536) bytes at a time, up to optional limit (default is
994 995 to read all data). Chunks may be less than size bytes if the
995 996 chunk is the last chunk in the file, or the file is a socket or
996 997 some other type of file that sometimes reads less data than is
997 998 requested."""
998 999 assert size >= 0
999 1000 assert limit is None or limit >= 0
1000 1001 while True:
1001 1002 if limit is None:
1002 1003 nbytes = size
1003 1004 else:
1004 1005 nbytes = min(limit, size)
1005 1006 s = nbytes and f.read(nbytes)
1006 1007 if not s:
1007 1008 break
1008 1009 if limit:
1009 1010 limit -= len(s)
1010 1011 yield s
1011 1012
1012 1013 def makedate():
1013 1014 lt = time.localtime()
1014 1015 if lt[8] == 1 and time.daylight:
1015 1016 tz = time.altzone
1016 1017 else:
1017 1018 tz = time.timezone
1018 1019 t = time.mktime(lt)
1019 1020 if t < 0:
1020 1021 hint = _("check your clock")
1021 1022 raise Abort(_("negative timestamp: %d") % t, hint=hint)
1022 1023 return t, tz
1023 1024
1024 1025 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1025 1026 """represent a (unixtime, offset) tuple as a localized time.
1026 1027 unixtime is seconds since the epoch, and offset is the time zone's
1027 1028 number of seconds away from UTC. if timezone is false, do not
1028 1029 append time zone to string."""
1029 1030 t, tz = date or makedate()
1030 1031 if t < 0:
1031 1032 t = 0 # time.gmtime(lt) fails on Windows for lt < -43200
1032 1033 tz = 0
1033 1034 if "%1" in format or "%2" in format:
1034 1035 sign = (tz > 0) and "-" or "+"
1035 1036 minutes = abs(tz) // 60
1036 1037 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
1037 1038 format = format.replace("%2", "%02d" % (minutes % 60))
1038 1039 s = time.strftime(format, time.gmtime(float(t) - tz))
1039 1040 return s
1040 1041
1041 1042 def shortdate(date=None):
1042 1043 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1043 1044 return datestr(date, format='%Y-%m-%d')
1044 1045
1045 1046 def strdate(string, format, defaults=[]):
1046 1047 """parse a localized time string and return a (unixtime, offset) tuple.
1047 1048 if the string cannot be parsed, ValueError is raised."""
1048 1049 def timezone(string):
1049 1050 tz = string.split()[-1]
1050 1051 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1051 1052 sign = (tz[0] == "+") and 1 or -1
1052 1053 hours = int(tz[1:3])
1053 1054 minutes = int(tz[3:5])
1054 1055 return -sign * (hours * 60 + minutes) * 60
1055 1056 if tz == "GMT" or tz == "UTC":
1056 1057 return 0
1057 1058 return None
1058 1059
1059 1060 # NOTE: unixtime = localunixtime + offset
1060 1061 offset, date = timezone(string), string
1061 1062 if offset != None:
1062 1063 date = " ".join(string.split()[:-1])
1063 1064
1064 1065 # add missing elements from defaults
1065 1066 for part in defaults:
1066 1067 found = [True for p in part if ("%"+p) in format]
1067 1068 if not found:
1068 1069 date += "@" + defaults[part]
1069 1070 format += "@%" + part[0]
1070 1071
1071 1072 timetuple = time.strptime(date, format)
1072 1073 localunixtime = int(calendar.timegm(timetuple))
1073 1074 if offset is None:
1074 1075 # local timezone
1075 1076 unixtime = int(time.mktime(timetuple))
1076 1077 offset = unixtime - localunixtime
1077 1078 else:
1078 1079 unixtime = localunixtime + offset
1079 1080 return unixtime, offset
1080 1081
1081 1082 def parsedate(date, formats=None, defaults=None):
1082 1083 """parse a localized date/time string and return a (unixtime, offset) tuple.
1083 1084
1084 1085 The date may be a "unixtime offset" string or in one of the specified
1085 1086 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1086 1087 """
1087 1088 if not date:
1088 1089 return 0, 0
1089 1090 if isinstance(date, tuple) and len(date) == 2:
1090 1091 return date
1091 1092 if not formats:
1092 1093 formats = defaultdateformats
1093 1094 date = date.strip()
1094 1095 try:
1095 1096 when, offset = map(int, date.split(' '))
1096 1097 except ValueError:
1097 1098 # fill out defaults
1098 1099 if not defaults:
1099 1100 defaults = {}
1100 1101 now = makedate()
1101 1102 for part in "d mb yY HI M S".split():
1102 1103 if part not in defaults:
1103 1104 if part[0] in "HMS":
1104 1105 defaults[part] = "00"
1105 1106 else:
1106 1107 defaults[part] = datestr(now, "%" + part[0])
1107 1108
1108 1109 for format in formats:
1109 1110 try:
1110 1111 when, offset = strdate(date, format, defaults)
1111 1112 except (ValueError, OverflowError):
1112 1113 pass
1113 1114 else:
1114 1115 break
1115 1116 else:
1116 1117 raise Abort(_('invalid date: %r') % date)
1117 1118 # validate explicit (probably user-specified) date and
1118 1119 # time zone offset. values must fit in signed 32 bits for
1119 1120 # current 32-bit linux runtimes. timezones go from UTC-12
1120 1121 # to UTC+14
1121 1122 if abs(when) > 0x7fffffff:
1122 1123 raise Abort(_('date exceeds 32 bits: %d') % when)
1123 1124 if when < 0:
1124 1125 raise Abort(_('negative date value: %d') % when)
1125 1126 if offset < -50400 or offset > 43200:
1126 1127 raise Abort(_('impossible time zone offset: %d') % offset)
1127 1128 return when, offset
1128 1129
1129 1130 def matchdate(date):
1130 1131 """Return a function that matches a given date match specifier
1131 1132
1132 1133 Formats include:
1133 1134
1134 1135 '{date}' match a given date to the accuracy provided
1135 1136
1136 1137 '<{date}' on or before a given date
1137 1138
1138 1139 '>{date}' on or after a given date
1139 1140
1140 1141 """
1141 1142
1142 1143 def lower(date):
1143 1144 d = dict(mb="1", d="1")
1144 1145 return parsedate(date, extendeddateformats, d)[0]
1145 1146
1146 1147 def upper(date):
1147 1148 d = dict(mb="12", HI="23", M="59", S="59")
1148 1149 for days in "31 30 29".split():
1149 1150 try:
1150 1151 d["d"] = days
1151 1152 return parsedate(date, extendeddateformats, d)[0]
1152 1153 except:
1153 1154 pass
1154 1155 d["d"] = "28"
1155 1156 return parsedate(date, extendeddateformats, d)[0]
1156 1157
1157 1158 date = date.strip()
1158 1159 if date[0] == "<":
1159 1160 when = upper(date[1:])
1160 1161 return lambda x: x <= when
1161 1162 elif date[0] == ">":
1162 1163 when = lower(date[1:])
1163 1164 return lambda x: x >= when
1164 1165 elif date[0] == "-":
1165 1166 try:
1166 1167 days = int(date[1:])
1167 1168 except ValueError:
1168 1169 raise Abort(_("invalid day spec: %s") % date[1:])
1169 1170 when = makedate()[0] - days * 3600 * 24
1170 1171 return lambda x: x >= when
1171 1172 elif " to " in date:
1172 1173 a, b = date.split(" to ")
1173 1174 start, stop = lower(a), upper(b)
1174 1175 return lambda x: x >= start and x <= stop
1175 1176 else:
1176 1177 start, stop = lower(date), upper(date)
1177 1178 return lambda x: x >= start and x <= stop
1178 1179
1179 1180 def shortuser(user):
1180 1181 """Return a short representation of a user name or email address."""
1181 1182 f = user.find('@')
1182 1183 if f >= 0:
1183 1184 user = user[:f]
1184 1185 f = user.find('<')
1185 1186 if f >= 0:
1186 1187 user = user[f + 1:]
1187 1188 f = user.find(' ')
1188 1189 if f >= 0:
1189 1190 user = user[:f]
1190 1191 f = user.find('.')
1191 1192 if f >= 0:
1192 1193 user = user[:f]
1193 1194 return user
1194 1195
1195 1196 def email(author):
1196 1197 '''get email of author.'''
1197 1198 r = author.find('>')
1198 1199 if r == -1:
1199 1200 r = None
1200 1201 return author[author.find('<') + 1:r]
1201 1202
1202 1203 def ellipsis(text, maxlength=400):
1203 1204 """Trim string to at most maxlength (default: 400) characters."""
1204 1205 if len(text) <= maxlength:
1205 1206 return text
1206 1207 else:
1207 1208 return "%s..." % (text[:maxlength - 3])
1208 1209
1209 1210 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1210 1211 '''yield every hg repository under path, recursively.'''
1211 1212 def errhandler(err):
1212 1213 if err.filename == path:
1213 1214 raise err
1214 1215 if followsym and hasattr(os.path, 'samestat'):
1215 1216 def _add_dir_if_not_there(dirlst, dirname):
1216 1217 match = False
1217 1218 samestat = os.path.samestat
1218 1219 dirstat = os.stat(dirname)
1219 1220 for lstdirstat in dirlst:
1220 1221 if samestat(dirstat, lstdirstat):
1221 1222 match = True
1222 1223 break
1223 1224 if not match:
1224 1225 dirlst.append(dirstat)
1225 1226 return not match
1226 1227 else:
1227 1228 followsym = False
1228 1229
1229 1230 if (seen_dirs is None) and followsym:
1230 1231 seen_dirs = []
1231 1232 _add_dir_if_not_there(seen_dirs, path)
1232 1233 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1233 1234 dirs.sort()
1234 1235 if '.hg' in dirs:
1235 1236 yield root # found a repository
1236 1237 qroot = os.path.join(root, '.hg', 'patches')
1237 1238 if os.path.isdir(os.path.join(qroot, '.hg')):
1238 1239 yield qroot # we have a patch queue repo here
1239 1240 if recurse:
1240 1241 # avoid recursing inside the .hg directory
1241 1242 dirs.remove('.hg')
1242 1243 else:
1243 1244 dirs[:] = [] # don't descend further
1244 1245 elif followsym:
1245 1246 newdirs = []
1246 1247 for d in dirs:
1247 1248 fname = os.path.join(root, d)
1248 1249 if _add_dir_if_not_there(seen_dirs, fname):
1249 1250 if os.path.islink(fname):
1250 1251 for hgname in walkrepos(fname, True, seen_dirs):
1251 1252 yield hgname
1252 1253 else:
1253 1254 newdirs.append(d)
1254 1255 dirs[:] = newdirs
1255 1256
1256 1257 _rcpath = None
1257 1258
1258 1259 def os_rcpath():
1259 1260 '''return default os-specific hgrc search path'''
1260 1261 path = system_rcpath()
1261 1262 path.extend(user_rcpath())
1262 1263 path = [os.path.normpath(f) for f in path]
1263 1264 return path
1264 1265
1265 1266 def rcpath():
1266 1267 '''return hgrc search path. if env var HGRCPATH is set, use it.
1267 1268 for each item in path, if directory, use files ending in .rc,
1268 1269 else use item.
1269 1270 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1270 1271 if no HGRCPATH, use default os-specific path.'''
1271 1272 global _rcpath
1272 1273 if _rcpath is None:
1273 1274 if 'HGRCPATH' in os.environ:
1274 1275 _rcpath = []
1275 1276 for p in os.environ['HGRCPATH'].split(os.pathsep):
1276 1277 if not p:
1277 1278 continue
1278 1279 p = expandpath(p)
1279 1280 if os.path.isdir(p):
1280 1281 for f, kind in osutil.listdir(p):
1281 1282 if f.endswith('.rc'):
1282 1283 _rcpath.append(os.path.join(p, f))
1283 1284 else:
1284 1285 _rcpath.append(p)
1285 1286 else:
1286 1287 _rcpath = os_rcpath()
1287 1288 return _rcpath
1288 1289
1289 1290 def bytecount(nbytes):
1290 1291 '''return byte count formatted as readable string, with units'''
1291 1292
1292 1293 units = (
1293 1294 (100, 1 << 30, _('%.0f GB')),
1294 1295 (10, 1 << 30, _('%.1f GB')),
1295 1296 (1, 1 << 30, _('%.2f GB')),
1296 1297 (100, 1 << 20, _('%.0f MB')),
1297 1298 (10, 1 << 20, _('%.1f MB')),
1298 1299 (1, 1 << 20, _('%.2f MB')),
1299 1300 (100, 1 << 10, _('%.0f KB')),
1300 1301 (10, 1 << 10, _('%.1f KB')),
1301 1302 (1, 1 << 10, _('%.2f KB')),
1302 1303 (1, 1, _('%.0f bytes')),
1303 1304 )
1304 1305
1305 1306 for multiplier, divisor, format in units:
1306 1307 if nbytes >= divisor * multiplier:
1307 1308 return format % (nbytes / float(divisor))
1308 1309 return units[-1][2] % nbytes
1309 1310
1310 1311 def drop_scheme(scheme, path):
1311 1312 sc = scheme + ':'
1312 1313 if path.startswith(sc):
1313 1314 path = path[len(sc):]
1314 1315 if path.startswith('//'):
1315 1316 if scheme == 'file':
1316 1317 i = path.find('/', 2)
1317 1318 if i == -1:
1318 1319 return ''
1319 1320 # On Windows, absolute paths are rooted at the current drive
1320 1321 # root. On POSIX they are rooted at the file system root.
1321 1322 if os.name == 'nt':
1322 1323 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1323 1324 path = os.path.join(droot, path[i + 1:])
1324 1325 else:
1325 1326 path = path[i:]
1326 1327 else:
1327 1328 path = path[2:]
1328 1329 return path
1329 1330
1330 1331 def uirepr(s):
1331 1332 # Avoid double backslash in Windows path repr()
1332 1333 return repr(s).replace('\\\\', '\\')
1333 1334
1334 1335 #### naming convention of below implementation follows 'textwrap' module
1335 1336
1336 1337 class MBTextWrapper(textwrap.TextWrapper):
1337 1338 def __init__(self, **kwargs):
1338 1339 textwrap.TextWrapper.__init__(self, **kwargs)
1339 1340
1340 1341 def _cutdown(self, str, space_left):
1341 1342 l = 0
1342 1343 ucstr = unicode(str, encoding.encoding)
1343 1344 w = unicodedata.east_asian_width
1344 1345 for i in xrange(len(ucstr)):
1345 1346 l += w(ucstr[i]) in 'WFA' and 2 or 1
1346 1347 if space_left < l:
1347 1348 return (ucstr[:i].encode(encoding.encoding),
1348 1349 ucstr[i:].encode(encoding.encoding))
1349 1350 return str, ''
1350 1351
1351 1352 # ----------------------------------------
1352 1353 # overriding of base class
1353 1354
1354 1355 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1355 1356 space_left = max(width - cur_len, 1)
1356 1357
1357 1358 if self.break_long_words:
1358 1359 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1359 1360 cur_line.append(cut)
1360 1361 reversed_chunks[-1] = res
1361 1362 elif not cur_line:
1362 1363 cur_line.append(reversed_chunks.pop())
1363 1364
1364 1365 #### naming convention of above implementation follows 'textwrap' module
1365 1366
1366 1367 def wrap(line, width, initindent='', hangindent=''):
1367 1368 maxindent = max(len(hangindent), len(initindent))
1368 1369 if width <= maxindent:
1369 1370 # adjust for weird terminal size
1370 1371 width = max(78, maxindent + 1)
1371 1372 wrapper = MBTextWrapper(width=width,
1372 1373 initial_indent=initindent,
1373 1374 subsequent_indent=hangindent)
1374 1375 return wrapper.fill(line)
1375 1376
1376 1377 def iterlines(iterator):
1377 1378 for chunk in iterator:
1378 1379 for line in chunk.splitlines():
1379 1380 yield line
1380 1381
1381 1382 def expandpath(path):
1382 1383 return os.path.expanduser(os.path.expandvars(path))
1383 1384
1384 1385 def hgcmd():
1385 1386 """Return the command used to execute current hg
1386 1387
1387 1388 This is different from hgexecutable() because on Windows we want
1388 1389 to avoid things opening new shell windows like batch files, so we
1389 1390 get either the python call or current executable.
1390 1391 """
1391 1392 if main_is_frozen():
1392 1393 return [sys.executable]
1393 1394 return gethgcmd()
1394 1395
1395 1396 def rundetached(args, condfn):
1396 1397 """Execute the argument list in a detached process.
1397 1398
1398 1399 condfn is a callable which is called repeatedly and should return
1399 1400 True once the child process is known to have started successfully.
1400 1401 At this point, the child process PID is returned. If the child
1401 1402 process fails to start or finishes before condfn() evaluates to
1402 1403 True, return -1.
1403 1404 """
1404 1405 # Windows case is easier because the child process is either
1405 1406 # successfully starting and validating the condition or exiting
1406 1407 # on failure. We just poll on its PID. On Unix, if the child
1407 1408 # process fails to start, it will be left in a zombie state until
1408 1409 # the parent wait on it, which we cannot do since we expect a long
1409 1410 # running process on success. Instead we listen for SIGCHLD telling
1410 1411 # us our child process terminated.
1411 1412 terminated = set()
1412 1413 def handler(signum, frame):
1413 1414 terminated.add(os.wait())
1414 1415 prevhandler = None
1415 1416 if hasattr(signal, 'SIGCHLD'):
1416 1417 prevhandler = signal.signal(signal.SIGCHLD, handler)
1417 1418 try:
1418 1419 pid = spawndetached(args)
1419 1420 while not condfn():
1420 1421 if ((pid in terminated or not testpid(pid))
1421 1422 and not condfn()):
1422 1423 return -1
1423 1424 time.sleep(0.1)
1424 1425 return pid
1425 1426 finally:
1426 1427 if prevhandler is not None:
1427 1428 signal.signal(signal.SIGCHLD, prevhandler)
1428 1429
1429 1430 try:
1430 1431 any, all = any, all
1431 1432 except NameError:
1432 1433 def any(iterable):
1433 1434 for i in iterable:
1434 1435 if i:
1435 1436 return True
1436 1437 return False
1437 1438
1438 1439 def all(iterable):
1439 1440 for i in iterable:
1440 1441 if not i:
1441 1442 return False
1442 1443 return True
1443 1444
1444 1445 def interpolate(prefix, mapping, s, fn=None):
1445 1446 """Return the result of interpolating items in the mapping into string s.
1446 1447
1447 1448 prefix is a single character string, or a two character string with
1448 1449 a backslash as the first character if the prefix needs to be escaped in
1449 1450 a regular expression.
1450 1451
1451 1452 fn is an optional function that will be applied to the replacement text
1452 1453 just before replacement.
1453 1454 """
1454 1455 fn = fn or (lambda s: s)
1455 1456 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1456 1457 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1457 1458
1458 1459 def getport(port):
1459 1460 """Return the port for a given network service.
1460 1461
1461 1462 If port is an integer, it's returned as is. If it's a string, it's
1462 1463 looked up using socket.getservbyname(). If there's no matching
1463 1464 service, util.Abort is raised.
1464 1465 """
1465 1466 try:
1466 1467 return int(port)
1467 1468 except ValueError:
1468 1469 pass
1469 1470
1470 1471 try:
1471 1472 return socket.getservbyname(port)
1472 1473 except socket.error:
1473 1474 raise Abort(_("no port number associated with service '%s'") % port)
1474 1475
1475 1476 _booleans = {'1': True, 'yes': True, 'true': True, 'on': True, 'always': True,
1476 1477 '0': False, 'no': False, 'false': False, 'off': False,
1477 1478 'never': False}
1478 1479
1479 1480 def parsebool(s):
1480 1481 """Parse s into a boolean.
1481 1482
1482 1483 If s is not a valid boolean, returns None.
1483 1484 """
1484 1485 return _booleans.get(s.lower(), None)
General Comments 0
You need to be logged in to leave comments. Login now