##// END OF EJS Templates
util: use 'auditor' as consistent name for path auditors
Martin Geisler -
r12078:e03ca36b default
parent child Browse files
Show More
@@ -1,1433 +1,1433
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 def canonpath(root, cwd, myname, audit_path=None):
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 if audit_path is None:
306 audit_path = path_auditor(root)
305 if auditor is None:
306 auditor = path_auditor(root)
307 307 if name != rootsep and name.startswith(rootsep):
308 308 name = name[len(rootsep):]
309 audit_path(name)
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 audit_path(name)
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 394 if os.name == 'nt':
395 395 cmd = '"%s"' % cmd
396 396 env = dict(os.environ)
397 397 env.update((k, py2shell(v)) for k, v in environ.iteritems())
398 398 env['HG'] = hgexecutable()
399 399 if out is None:
400 400 rc = subprocess.call(cmd, shell=True, close_fds=closefds,
401 401 env=env, cwd=cwd)
402 402 else:
403 403 proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
404 404 env=env, cwd=cwd, stdout=subprocess.PIPE,
405 405 stderr=subprocess.STDOUT)
406 406 for line in proc.stdout:
407 407 out.write(line)
408 408 proc.wait()
409 409 rc = proc.returncode
410 410 if sys.platform == 'OpenVMS' and rc & 1:
411 411 rc = 0
412 412 if rc and onerr:
413 413 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
414 414 explain_exit(rc)[0])
415 415 if errprefix:
416 416 errmsg = '%s: %s' % (errprefix, errmsg)
417 417 try:
418 418 onerr.warn(errmsg + '\n')
419 419 except AttributeError:
420 420 raise onerr(errmsg)
421 421 return rc
422 422
423 423 def checksignature(func):
424 424 '''wrap a function with code to check for calling errors'''
425 425 def check(*args, **kwargs):
426 426 try:
427 427 return func(*args, **kwargs)
428 428 except TypeError:
429 429 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
430 430 raise error.SignatureError
431 431 raise
432 432
433 433 return check
434 434
435 435 def unlink(f):
436 436 """unlink and remove the directory if it is empty"""
437 437 os.unlink(f)
438 438 # try removing directories that might now be empty
439 439 try:
440 440 os.removedirs(os.path.dirname(f))
441 441 except OSError:
442 442 pass
443 443
444 444 def copyfile(src, dest):
445 445 "copy a file, preserving mode and atime/mtime"
446 446 if os.path.islink(src):
447 447 try:
448 448 os.unlink(dest)
449 449 except:
450 450 pass
451 451 os.symlink(os.readlink(src), dest)
452 452 else:
453 453 try:
454 454 shutil.copyfile(src, dest)
455 455 shutil.copystat(src, dest)
456 456 except shutil.Error, inst:
457 457 raise Abort(str(inst))
458 458
459 459 def copyfiles(src, dst, hardlink=None):
460 460 """Copy a directory tree using hardlinks if possible"""
461 461
462 462 if hardlink is None:
463 463 hardlink = (os.stat(src).st_dev ==
464 464 os.stat(os.path.dirname(dst)).st_dev)
465 465
466 466 num = 0
467 467 if os.path.isdir(src):
468 468 os.mkdir(dst)
469 469 for name, kind in osutil.listdir(src):
470 470 srcname = os.path.join(src, name)
471 471 dstname = os.path.join(dst, name)
472 472 hardlink, n = copyfiles(srcname, dstname, hardlink)
473 473 num += n
474 474 else:
475 475 if hardlink:
476 476 try:
477 477 os_link(src, dst)
478 478 except (IOError, OSError):
479 479 hardlink = False
480 480 shutil.copy(src, dst)
481 481 else:
482 482 shutil.copy(src, dst)
483 483 num += 1
484 484
485 485 return hardlink, num
486 486
487 487 class path_auditor(object):
488 488 '''ensure that a filesystem path contains no banned components.
489 489 the following properties of a path are checked:
490 490
491 491 - under top-level .hg
492 492 - starts at the root of a windows drive
493 493 - contains ".."
494 494 - traverses a symlink (e.g. a/symlink_here/b)
495 495 - inside a nested repository'''
496 496
497 497 def __init__(self, root):
498 498 self.audited = set()
499 499 self.auditeddir = set()
500 500 self.root = root
501 501
502 502 def __call__(self, path):
503 503 if path in self.audited:
504 504 return
505 505 normpath = os.path.normcase(path)
506 506 parts = splitpath(normpath)
507 507 if (os.path.splitdrive(path)[0]
508 508 or parts[0].lower() in ('.hg', '.hg.', '')
509 509 or os.pardir in parts):
510 510 raise Abort(_("path contains illegal component: %s") % path)
511 511 if '.hg' in path.lower():
512 512 lparts = [p.lower() for p in parts]
513 513 for p in '.hg', '.hg.':
514 514 if p in lparts[1:]:
515 515 pos = lparts.index(p)
516 516 base = os.path.join(*parts[:pos])
517 517 raise Abort(_('path %r is inside repo %r') % (path, base))
518 518 def check(prefix):
519 519 curpath = os.path.join(self.root, prefix)
520 520 try:
521 521 st = os.lstat(curpath)
522 522 except OSError, err:
523 523 # EINVAL can be raised as invalid path syntax under win32.
524 524 # They must be ignored for patterns can be checked too.
525 525 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
526 526 raise
527 527 else:
528 528 if stat.S_ISLNK(st.st_mode):
529 529 raise Abort(_('path %r traverses symbolic link %r') %
530 530 (path, prefix))
531 531 elif (stat.S_ISDIR(st.st_mode) and
532 532 os.path.isdir(os.path.join(curpath, '.hg'))):
533 533 raise Abort(_('path %r is inside repo %r') %
534 534 (path, prefix))
535 535 parts.pop()
536 536 prefixes = []
537 537 while parts:
538 538 prefix = os.sep.join(parts)
539 539 if prefix in self.auditeddir:
540 540 break
541 541 check(prefix)
542 542 prefixes.append(prefix)
543 543 parts.pop()
544 544
545 545 self.audited.add(path)
546 546 # only add prefixes to the cache after checking everything: we don't
547 547 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
548 548 self.auditeddir.update(prefixes)
549 549
550 550 def nlinks(pathname):
551 551 """Return number of hardlinks for the given file."""
552 552 return os.lstat(pathname).st_nlink
553 553
554 554 if hasattr(os, 'link'):
555 555 os_link = os.link
556 556 else:
557 557 def os_link(src, dst):
558 558 raise OSError(0, _("Hardlinks not supported"))
559 559
560 560 def lookup_reg(key, name=None, scope=None):
561 561 return None
562 562
563 563 def hidewindow():
564 564 """Hide current shell window.
565 565
566 566 Used to hide the window opened when starting asynchronous
567 567 child process under Windows, unneeded on other systems.
568 568 """
569 569 pass
570 570
571 571 if os.name == 'nt':
572 572 from windows import *
573 573 else:
574 574 from posix import *
575 575
576 576 def makelock(info, pathname):
577 577 try:
578 578 return os.symlink(info, pathname)
579 579 except OSError, why:
580 580 if why.errno == errno.EEXIST:
581 581 raise
582 582 except AttributeError: # no symlink in os
583 583 pass
584 584
585 585 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
586 586 os.write(ld, info)
587 587 os.close(ld)
588 588
589 589 def readlock(pathname):
590 590 try:
591 591 return os.readlink(pathname)
592 592 except OSError, why:
593 593 if why.errno not in (errno.EINVAL, errno.ENOSYS):
594 594 raise
595 595 except AttributeError: # no symlink in os
596 596 pass
597 597 return posixfile(pathname).read()
598 598
599 599 def fstat(fp):
600 600 '''stat file object that may not have fileno method.'''
601 601 try:
602 602 return os.fstat(fp.fileno())
603 603 except AttributeError:
604 604 return os.stat(fp.name)
605 605
606 606 # File system features
607 607
608 608 def checkcase(path):
609 609 """
610 610 Check whether the given path is on a case-sensitive filesystem
611 611
612 612 Requires a path (like /foo/.hg) ending with a foldable final
613 613 directory component.
614 614 """
615 615 s1 = os.stat(path)
616 616 d, b = os.path.split(path)
617 617 p2 = os.path.join(d, b.upper())
618 618 if path == p2:
619 619 p2 = os.path.join(d, b.lower())
620 620 try:
621 621 s2 = os.stat(p2)
622 622 if s2 == s1:
623 623 return False
624 624 return True
625 625 except:
626 626 return True
627 627
628 628 _fspathcache = {}
629 629 def fspath(name, root):
630 630 '''Get name in the case stored in the filesystem
631 631
632 632 The name is either relative to root, or it is an absolute path starting
633 633 with root. Note that this function is unnecessary, and should not be
634 634 called, for case-sensitive filesystems (simply because it's expensive).
635 635 '''
636 636 # If name is absolute, make it relative
637 637 if name.lower().startswith(root.lower()):
638 638 l = len(root)
639 639 if name[l] == os.sep or name[l] == os.altsep:
640 640 l = l + 1
641 641 name = name[l:]
642 642
643 643 if not os.path.exists(os.path.join(root, name)):
644 644 return None
645 645
646 646 seps = os.sep
647 647 if os.altsep:
648 648 seps = seps + os.altsep
649 649 # Protect backslashes. This gets silly very quickly.
650 650 seps.replace('\\','\\\\')
651 651 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
652 652 dir = os.path.normcase(os.path.normpath(root))
653 653 result = []
654 654 for part, sep in pattern.findall(name):
655 655 if sep:
656 656 result.append(sep)
657 657 continue
658 658
659 659 if dir not in _fspathcache:
660 660 _fspathcache[dir] = os.listdir(dir)
661 661 contents = _fspathcache[dir]
662 662
663 663 lpart = part.lower()
664 664 lenp = len(part)
665 665 for n in contents:
666 666 if lenp == len(n) and n.lower() == lpart:
667 667 result.append(n)
668 668 break
669 669 else:
670 670 # Cannot happen, as the file exists!
671 671 result.append(part)
672 672 dir = os.path.join(dir, lpart)
673 673
674 674 return ''.join(result)
675 675
676 676 def checkexec(path):
677 677 """
678 678 Check whether the given path is on a filesystem with UNIX-like exec flags
679 679
680 680 Requires a directory (like /foo/.hg)
681 681 """
682 682
683 683 # VFAT on some Linux versions can flip mode but it doesn't persist
684 684 # a FS remount. Frequently we can detect it if files are created
685 685 # with exec bit on.
686 686
687 687 try:
688 688 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
689 689 fh, fn = tempfile.mkstemp(dir=path, prefix='hg-checkexec-')
690 690 try:
691 691 os.close(fh)
692 692 m = os.stat(fn).st_mode & 0777
693 693 new_file_has_exec = m & EXECFLAGS
694 694 os.chmod(fn, m ^ EXECFLAGS)
695 695 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
696 696 finally:
697 697 os.unlink(fn)
698 698 except (IOError, OSError):
699 699 # we don't care, the user probably won't be able to commit anyway
700 700 return False
701 701 return not (new_file_has_exec or exec_flags_cannot_flip)
702 702
703 703 def checklink(path):
704 704 """check whether the given path is on a symlink-capable filesystem"""
705 705 # mktemp is not racy because symlink creation will fail if the
706 706 # file already exists
707 707 name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
708 708 try:
709 709 os.symlink(".", name)
710 710 os.unlink(name)
711 711 return True
712 712 except (OSError, AttributeError):
713 713 return False
714 714
715 715 def needbinarypatch():
716 716 """return True if patches should be applied in binary mode by default."""
717 717 return os.name == 'nt'
718 718
719 719 def endswithsep(path):
720 720 '''Check path ends with os.sep or os.altsep.'''
721 721 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
722 722
723 723 def splitpath(path):
724 724 '''Split path by os.sep.
725 725 Note that this function does not use os.altsep because this is
726 726 an alternative of simple "xxx.split(os.sep)".
727 727 It is recommended to use os.path.normpath() before using this
728 728 function if need.'''
729 729 return path.split(os.sep)
730 730
731 731 def gui():
732 732 '''Are we running in a GUI?'''
733 733 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
734 734
735 735 def mktempcopy(name, emptyok=False, createmode=None):
736 736 """Create a temporary file with the same contents from name
737 737
738 738 The permission bits are copied from the original file.
739 739
740 740 If the temporary file is going to be truncated immediately, you
741 741 can use emptyok=True as an optimization.
742 742
743 743 Returns the name of the temporary file.
744 744 """
745 745 d, fn = os.path.split(name)
746 746 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
747 747 os.close(fd)
748 748 # Temporary files are created with mode 0600, which is usually not
749 749 # what we want. If the original file already exists, just copy
750 750 # its mode. Otherwise, manually obey umask.
751 751 try:
752 752 st_mode = os.lstat(name).st_mode & 0777
753 753 except OSError, inst:
754 754 if inst.errno != errno.ENOENT:
755 755 raise
756 756 st_mode = createmode
757 757 if st_mode is None:
758 758 st_mode = ~umask
759 759 st_mode &= 0666
760 760 os.chmod(temp, st_mode)
761 761 if emptyok:
762 762 return temp
763 763 try:
764 764 try:
765 765 ifp = posixfile(name, "rb")
766 766 except IOError, inst:
767 767 if inst.errno == errno.ENOENT:
768 768 return temp
769 769 if not getattr(inst, 'filename', None):
770 770 inst.filename = name
771 771 raise
772 772 ofp = posixfile(temp, "wb")
773 773 for chunk in filechunkiter(ifp):
774 774 ofp.write(chunk)
775 775 ifp.close()
776 776 ofp.close()
777 777 except:
778 778 try: os.unlink(temp)
779 779 except: pass
780 780 raise
781 781 return temp
782 782
783 783 class atomictempfile(object):
784 784 """file-like object that atomically updates a file
785 785
786 786 All writes will be redirected to a temporary copy of the original
787 787 file. When rename is called, the copy is renamed to the original
788 788 name, making the changes visible.
789 789 """
790 790 def __init__(self, name, mode='w+b', createmode=None):
791 791 self.__name = name
792 792 self._fp = None
793 793 self.temp = mktempcopy(name, emptyok=('w' in mode),
794 794 createmode=createmode)
795 795 self._fp = posixfile(self.temp, mode)
796 796
797 797 def __getattr__(self, name):
798 798 return getattr(self._fp, name)
799 799
800 800 def rename(self):
801 801 if not self._fp.closed:
802 802 self._fp.close()
803 803 rename(self.temp, localpath(self.__name))
804 804
805 805 def __del__(self):
806 806 if not self._fp:
807 807 return
808 808 if not self._fp.closed:
809 809 try:
810 810 os.unlink(self.temp)
811 811 except: pass
812 812 self._fp.close()
813 813
814 814 def makedirs(name, mode=None):
815 815 """recursive directory creation with parent mode inheritance"""
816 816 try:
817 817 os.mkdir(name)
818 818 if mode is not None:
819 819 os.chmod(name, mode)
820 820 return
821 821 except OSError, err:
822 822 if err.errno == errno.EEXIST:
823 823 return
824 824 if err.errno != errno.ENOENT:
825 825 raise
826 826 parent = os.path.abspath(os.path.dirname(name))
827 827 makedirs(parent, mode)
828 828 makedirs(name, mode)
829 829
830 830 class opener(object):
831 831 """Open files relative to a base directory
832 832
833 833 This class is used to hide the details of COW semantics and
834 834 remote file access from higher level code.
835 835 """
836 836 def __init__(self, base, audit=True):
837 837 self.base = base
838 838 if audit:
839 self.audit_path = path_auditor(base)
839 self.auditor = path_auditor(base)
840 840 else:
841 self.audit_path = always
841 self.auditor = always
842 842 self.createmode = None
843 843
844 844 @propertycache
845 845 def _can_symlink(self):
846 846 return checklink(self.base)
847 847
848 848 def _fixfilemode(self, name):
849 849 if self.createmode is None:
850 850 return
851 851 os.chmod(name, self.createmode & 0666)
852 852
853 853 def __call__(self, path, mode="r", text=False, atomictemp=False):
854 self.audit_path(path)
854 self.auditor(path)
855 855 f = os.path.join(self.base, path)
856 856
857 857 if not text and "b" not in mode:
858 858 mode += "b" # for that other OS
859 859
860 860 nlink = -1
861 861 if mode not in ("r", "rb"):
862 862 try:
863 863 nlink = nlinks(f)
864 864 except OSError:
865 865 nlink = 0
866 866 d = os.path.dirname(f)
867 867 if not os.path.isdir(d):
868 868 makedirs(d, self.createmode)
869 869 if atomictemp:
870 870 return atomictempfile(f, mode, self.createmode)
871 871 if nlink > 1:
872 872 rename(mktempcopy(f), f)
873 873 fp = posixfile(f, mode)
874 874 if nlink == 0:
875 875 self._fixfilemode(f)
876 876 return fp
877 877
878 878 def symlink(self, src, dst):
879 self.audit_path(dst)
879 self.auditor(dst)
880 880 linkname = os.path.join(self.base, dst)
881 881 try:
882 882 os.unlink(linkname)
883 883 except OSError:
884 884 pass
885 885
886 886 dirname = os.path.dirname(linkname)
887 887 if not os.path.exists(dirname):
888 888 makedirs(dirname, self.createmode)
889 889
890 890 if self._can_symlink:
891 891 try:
892 892 os.symlink(src, linkname)
893 893 except OSError, err:
894 894 raise OSError(err.errno, _('could not symlink to %r: %s') %
895 895 (src, err.strerror), linkname)
896 896 else:
897 897 f = self(dst, "w")
898 898 f.write(src)
899 899 f.close()
900 900 self._fixfilemode(dst)
901 901
902 902 class chunkbuffer(object):
903 903 """Allow arbitrary sized chunks of data to be efficiently read from an
904 904 iterator over chunks of arbitrary size."""
905 905
906 906 def __init__(self, in_iter):
907 907 """in_iter is the iterator that's iterating over the input chunks.
908 908 targetsize is how big a buffer to try to maintain."""
909 909 def splitbig(chunks):
910 910 for chunk in chunks:
911 911 if len(chunk) > 2**20:
912 912 pos = 0
913 913 while pos < len(chunk):
914 914 end = pos + 2 ** 18
915 915 yield chunk[pos:end]
916 916 pos = end
917 917 else:
918 918 yield chunk
919 919 self.iter = splitbig(in_iter)
920 920 self._queue = []
921 921
922 922 def read(self, l):
923 923 """Read L bytes of data from the iterator of chunks of data.
924 924 Returns less than L bytes if the iterator runs dry."""
925 925 left = l
926 926 buf = ''
927 927 queue = self._queue
928 928 while left > 0:
929 929 # refill the queue
930 930 if not queue:
931 931 target = 2**18
932 932 for chunk in self.iter:
933 933 queue.append(chunk)
934 934 target -= len(chunk)
935 935 if target <= 0:
936 936 break
937 937 if not queue:
938 938 break
939 939
940 940 chunk = queue.pop(0)
941 941 left -= len(chunk)
942 942 if left < 0:
943 943 queue.insert(0, chunk[left:])
944 944 buf += chunk[:left]
945 945 else:
946 946 buf += chunk
947 947
948 948 return buf
949 949
950 950 def filechunkiter(f, size=65536, limit=None):
951 951 """Create a generator that produces the data in the file size
952 952 (default 65536) bytes at a time, up to optional limit (default is
953 953 to read all data). Chunks may be less than size bytes if the
954 954 chunk is the last chunk in the file, or the file is a socket or
955 955 some other type of file that sometimes reads less data than is
956 956 requested."""
957 957 assert size >= 0
958 958 assert limit is None or limit >= 0
959 959 while True:
960 960 if limit is None:
961 961 nbytes = size
962 962 else:
963 963 nbytes = min(limit, size)
964 964 s = nbytes and f.read(nbytes)
965 965 if not s:
966 966 break
967 967 if limit:
968 968 limit -= len(s)
969 969 yield s
970 970
971 971 def makedate():
972 972 lt = time.localtime()
973 973 if lt[8] == 1 and time.daylight:
974 974 tz = time.altzone
975 975 else:
976 976 tz = time.timezone
977 977 return time.mktime(lt), tz
978 978
979 979 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
980 980 """represent a (unixtime, offset) tuple as a localized time.
981 981 unixtime is seconds since the epoch, and offset is the time zone's
982 982 number of seconds away from UTC. if timezone is false, do not
983 983 append time zone to string."""
984 984 t, tz = date or makedate()
985 985 if "%1" in format or "%2" in format:
986 986 sign = (tz > 0) and "-" or "+"
987 987 minutes = abs(tz) // 60
988 988 format = format.replace("%1", "%c%02d" % (sign, minutes // 60))
989 989 format = format.replace("%2", "%02d" % (minutes % 60))
990 990 s = time.strftime(format, time.gmtime(float(t) - tz))
991 991 return s
992 992
993 993 def shortdate(date=None):
994 994 """turn (timestamp, tzoff) tuple into iso 8631 date."""
995 995 return datestr(date, format='%Y-%m-%d')
996 996
997 997 def strdate(string, format, defaults=[]):
998 998 """parse a localized time string and return a (unixtime, offset) tuple.
999 999 if the string cannot be parsed, ValueError is raised."""
1000 1000 def timezone(string):
1001 1001 tz = string.split()[-1]
1002 1002 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1003 1003 sign = (tz[0] == "+") and 1 or -1
1004 1004 hours = int(tz[1:3])
1005 1005 minutes = int(tz[3:5])
1006 1006 return -sign * (hours * 60 + minutes) * 60
1007 1007 if tz == "GMT" or tz == "UTC":
1008 1008 return 0
1009 1009 return None
1010 1010
1011 1011 # NOTE: unixtime = localunixtime + offset
1012 1012 offset, date = timezone(string), string
1013 1013 if offset != None:
1014 1014 date = " ".join(string.split()[:-1])
1015 1015
1016 1016 # add missing elements from defaults
1017 1017 for part in defaults:
1018 1018 found = [True for p in part if ("%"+p) in format]
1019 1019 if not found:
1020 1020 date += "@" + defaults[part]
1021 1021 format += "@%" + part[0]
1022 1022
1023 1023 timetuple = time.strptime(date, format)
1024 1024 localunixtime = int(calendar.timegm(timetuple))
1025 1025 if offset is None:
1026 1026 # local timezone
1027 1027 unixtime = int(time.mktime(timetuple))
1028 1028 offset = unixtime - localunixtime
1029 1029 else:
1030 1030 unixtime = localunixtime + offset
1031 1031 return unixtime, offset
1032 1032
1033 1033 def parsedate(date, formats=None, defaults=None):
1034 1034 """parse a localized date/time string and return a (unixtime, offset) tuple.
1035 1035
1036 1036 The date may be a "unixtime offset" string or in one of the specified
1037 1037 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1038 1038 """
1039 1039 if not date:
1040 1040 return 0, 0
1041 1041 if isinstance(date, tuple) and len(date) == 2:
1042 1042 return date
1043 1043 if not formats:
1044 1044 formats = defaultdateformats
1045 1045 date = date.strip()
1046 1046 try:
1047 1047 when, offset = map(int, date.split(' '))
1048 1048 except ValueError:
1049 1049 # fill out defaults
1050 1050 if not defaults:
1051 1051 defaults = {}
1052 1052 now = makedate()
1053 1053 for part in "d mb yY HI M S".split():
1054 1054 if part not in defaults:
1055 1055 if part[0] in "HMS":
1056 1056 defaults[part] = "00"
1057 1057 else:
1058 1058 defaults[part] = datestr(now, "%" + part[0])
1059 1059
1060 1060 for format in formats:
1061 1061 try:
1062 1062 when, offset = strdate(date, format, defaults)
1063 1063 except (ValueError, OverflowError):
1064 1064 pass
1065 1065 else:
1066 1066 break
1067 1067 else:
1068 1068 raise Abort(_('invalid date: %r ') % date)
1069 1069 # validate explicit (probably user-specified) date and
1070 1070 # time zone offset. values must fit in signed 32 bits for
1071 1071 # current 32-bit linux runtimes. timezones go from UTC-12
1072 1072 # to UTC+14
1073 1073 if abs(when) > 0x7fffffff:
1074 1074 raise Abort(_('date exceeds 32 bits: %d') % when)
1075 1075 if offset < -50400 or offset > 43200:
1076 1076 raise Abort(_('impossible time zone offset: %d') % offset)
1077 1077 return when, offset
1078 1078
1079 1079 def matchdate(date):
1080 1080 """Return a function that matches a given date match specifier
1081 1081
1082 1082 Formats include:
1083 1083
1084 1084 '{date}' match a given date to the accuracy provided
1085 1085
1086 1086 '<{date}' on or before a given date
1087 1087
1088 1088 '>{date}' on or after a given date
1089 1089
1090 1090 """
1091 1091
1092 1092 def lower(date):
1093 1093 d = dict(mb="1", d="1")
1094 1094 return parsedate(date, extendeddateformats, d)[0]
1095 1095
1096 1096 def upper(date):
1097 1097 d = dict(mb="12", HI="23", M="59", S="59")
1098 1098 for days in "31 30 29".split():
1099 1099 try:
1100 1100 d["d"] = days
1101 1101 return parsedate(date, extendeddateformats, d)[0]
1102 1102 except:
1103 1103 pass
1104 1104 d["d"] = "28"
1105 1105 return parsedate(date, extendeddateformats, d)[0]
1106 1106
1107 1107 date = date.strip()
1108 1108 if date[0] == "<":
1109 1109 when = upper(date[1:])
1110 1110 return lambda x: x <= when
1111 1111 elif date[0] == ">":
1112 1112 when = lower(date[1:])
1113 1113 return lambda x: x >= when
1114 1114 elif date[0] == "-":
1115 1115 try:
1116 1116 days = int(date[1:])
1117 1117 except ValueError:
1118 1118 raise Abort(_("invalid day spec: %s") % date[1:])
1119 1119 when = makedate()[0] - days * 3600 * 24
1120 1120 return lambda x: x >= when
1121 1121 elif " to " in date:
1122 1122 a, b = date.split(" to ")
1123 1123 start, stop = lower(a), upper(b)
1124 1124 return lambda x: x >= start and x <= stop
1125 1125 else:
1126 1126 start, stop = lower(date), upper(date)
1127 1127 return lambda x: x >= start and x <= stop
1128 1128
1129 1129 def shortuser(user):
1130 1130 """Return a short representation of a user name or email address."""
1131 1131 f = user.find('@')
1132 1132 if f >= 0:
1133 1133 user = user[:f]
1134 1134 f = user.find('<')
1135 1135 if f >= 0:
1136 1136 user = user[f + 1:]
1137 1137 f = user.find(' ')
1138 1138 if f >= 0:
1139 1139 user = user[:f]
1140 1140 f = user.find('.')
1141 1141 if f >= 0:
1142 1142 user = user[:f]
1143 1143 return user
1144 1144
1145 1145 def email(author):
1146 1146 '''get email of author.'''
1147 1147 r = author.find('>')
1148 1148 if r == -1:
1149 1149 r = None
1150 1150 return author[author.find('<') + 1:r]
1151 1151
1152 1152 def ellipsis(text, maxlength=400):
1153 1153 """Trim string to at most maxlength (default: 400) characters."""
1154 1154 if len(text) <= maxlength:
1155 1155 return text
1156 1156 else:
1157 1157 return "%s..." % (text[:maxlength - 3])
1158 1158
1159 1159 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1160 1160 '''yield every hg repository under path, recursively.'''
1161 1161 def errhandler(err):
1162 1162 if err.filename == path:
1163 1163 raise err
1164 1164 if followsym and hasattr(os.path, 'samestat'):
1165 1165 def _add_dir_if_not_there(dirlst, dirname):
1166 1166 match = False
1167 1167 samestat = os.path.samestat
1168 1168 dirstat = os.stat(dirname)
1169 1169 for lstdirstat in dirlst:
1170 1170 if samestat(dirstat, lstdirstat):
1171 1171 match = True
1172 1172 break
1173 1173 if not match:
1174 1174 dirlst.append(dirstat)
1175 1175 return not match
1176 1176 else:
1177 1177 followsym = False
1178 1178
1179 1179 if (seen_dirs is None) and followsym:
1180 1180 seen_dirs = []
1181 1181 _add_dir_if_not_there(seen_dirs, path)
1182 1182 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1183 1183 dirs.sort()
1184 1184 if '.hg' in dirs:
1185 1185 yield root # found a repository
1186 1186 qroot = os.path.join(root, '.hg', 'patches')
1187 1187 if os.path.isdir(os.path.join(qroot, '.hg')):
1188 1188 yield qroot # we have a patch queue repo here
1189 1189 if recurse:
1190 1190 # avoid recursing inside the .hg directory
1191 1191 dirs.remove('.hg')
1192 1192 else:
1193 1193 dirs[:] = [] # don't descend further
1194 1194 elif followsym:
1195 1195 newdirs = []
1196 1196 for d in dirs:
1197 1197 fname = os.path.join(root, d)
1198 1198 if _add_dir_if_not_there(seen_dirs, fname):
1199 1199 if os.path.islink(fname):
1200 1200 for hgname in walkrepos(fname, True, seen_dirs):
1201 1201 yield hgname
1202 1202 else:
1203 1203 newdirs.append(d)
1204 1204 dirs[:] = newdirs
1205 1205
1206 1206 _rcpath = None
1207 1207
1208 1208 def os_rcpath():
1209 1209 '''return default os-specific hgrc search path'''
1210 1210 path = system_rcpath()
1211 1211 path.extend(user_rcpath())
1212 1212 path = [os.path.normpath(f) for f in path]
1213 1213 return path
1214 1214
1215 1215 def rcpath():
1216 1216 '''return hgrc search path. if env var HGRCPATH is set, use it.
1217 1217 for each item in path, if directory, use files ending in .rc,
1218 1218 else use item.
1219 1219 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1220 1220 if no HGRCPATH, use default os-specific path.'''
1221 1221 global _rcpath
1222 1222 if _rcpath is None:
1223 1223 if 'HGRCPATH' in os.environ:
1224 1224 _rcpath = []
1225 1225 for p in os.environ['HGRCPATH'].split(os.pathsep):
1226 1226 if not p:
1227 1227 continue
1228 1228 p = expandpath(p)
1229 1229 if os.path.isdir(p):
1230 1230 for f, kind in osutil.listdir(p):
1231 1231 if f.endswith('.rc'):
1232 1232 _rcpath.append(os.path.join(p, f))
1233 1233 else:
1234 1234 _rcpath.append(p)
1235 1235 else:
1236 1236 _rcpath = os_rcpath()
1237 1237 return _rcpath
1238 1238
1239 1239 def bytecount(nbytes):
1240 1240 '''return byte count formatted as readable string, with units'''
1241 1241
1242 1242 units = (
1243 1243 (100, 1 << 30, _('%.0f GB')),
1244 1244 (10, 1 << 30, _('%.1f GB')),
1245 1245 (1, 1 << 30, _('%.2f GB')),
1246 1246 (100, 1 << 20, _('%.0f MB')),
1247 1247 (10, 1 << 20, _('%.1f MB')),
1248 1248 (1, 1 << 20, _('%.2f MB')),
1249 1249 (100, 1 << 10, _('%.0f KB')),
1250 1250 (10, 1 << 10, _('%.1f KB')),
1251 1251 (1, 1 << 10, _('%.2f KB')),
1252 1252 (1, 1, _('%.0f bytes')),
1253 1253 )
1254 1254
1255 1255 for multiplier, divisor, format in units:
1256 1256 if nbytes >= divisor * multiplier:
1257 1257 return format % (nbytes / float(divisor))
1258 1258 return units[-1][2] % nbytes
1259 1259
1260 1260 def drop_scheme(scheme, path):
1261 1261 sc = scheme + ':'
1262 1262 if path.startswith(sc):
1263 1263 path = path[len(sc):]
1264 1264 if path.startswith('//'):
1265 1265 if scheme == 'file':
1266 1266 i = path.find('/', 2)
1267 1267 if i == -1:
1268 1268 return ''
1269 1269 # On Windows, absolute paths are rooted at the current drive
1270 1270 # root. On POSIX they are rooted at the file system root.
1271 1271 if os.name == 'nt':
1272 1272 droot = os.path.splitdrive(os.getcwd())[0] + '/'
1273 1273 path = os.path.join(droot, path[i + 1:])
1274 1274 else:
1275 1275 path = path[i:]
1276 1276 else:
1277 1277 path = path[2:]
1278 1278 return path
1279 1279
1280 1280 def uirepr(s):
1281 1281 # Avoid double backslash in Windows path repr()
1282 1282 return repr(s).replace('\\\\', '\\')
1283 1283
1284 1284 #### naming convention of below implementation follows 'textwrap' module
1285 1285
1286 1286 class MBTextWrapper(textwrap.TextWrapper):
1287 1287 def __init__(self, **kwargs):
1288 1288 textwrap.TextWrapper.__init__(self, **kwargs)
1289 1289
1290 1290 def _cutdown(self, str, space_left):
1291 1291 l = 0
1292 1292 ucstr = unicode(str, encoding.encoding)
1293 1293 w = unicodedata.east_asian_width
1294 1294 for i in xrange(len(ucstr)):
1295 1295 l += w(ucstr[i]) in 'WFA' and 2 or 1
1296 1296 if space_left < l:
1297 1297 return (ucstr[:i].encode(encoding.encoding),
1298 1298 ucstr[i:].encode(encoding.encoding))
1299 1299 return str, ''
1300 1300
1301 1301 # ----------------------------------------
1302 1302 # overriding of base class
1303 1303
1304 1304 def _handle_long_word(self, reversed_chunks, cur_line, cur_len, width):
1305 1305 space_left = max(width - cur_len, 1)
1306 1306
1307 1307 if self.break_long_words:
1308 1308 cut, res = self._cutdown(reversed_chunks[-1], space_left)
1309 1309 cur_line.append(cut)
1310 1310 reversed_chunks[-1] = res
1311 1311 elif not cur_line:
1312 1312 cur_line.append(reversed_chunks.pop())
1313 1313
1314 1314 #### naming convention of above implementation follows 'textwrap' module
1315 1315
1316 1316 def wrap(line, width=None, initindent='', hangindent=''):
1317 1317 if width is None:
1318 1318 width = termwidth() - 2
1319 1319 maxindent = max(len(hangindent), len(initindent))
1320 1320 if width <= maxindent:
1321 1321 # adjust for weird terminal size
1322 1322 width = max(78, maxindent + 1)
1323 1323 wrapper = MBTextWrapper(width=width,
1324 1324 initial_indent=initindent,
1325 1325 subsequent_indent=hangindent)
1326 1326 return wrapper.fill(line)
1327 1327
1328 1328 def iterlines(iterator):
1329 1329 for chunk in iterator:
1330 1330 for line in chunk.splitlines():
1331 1331 yield line
1332 1332
1333 1333 def expandpath(path):
1334 1334 return os.path.expanduser(os.path.expandvars(path))
1335 1335
1336 1336 def hgcmd():
1337 1337 """Return the command used to execute current hg
1338 1338
1339 1339 This is different from hgexecutable() because on Windows we want
1340 1340 to avoid things opening new shell windows like batch files, so we
1341 1341 get either the python call or current executable.
1342 1342 """
1343 1343 if main_is_frozen():
1344 1344 return [sys.executable]
1345 1345 return gethgcmd()
1346 1346
1347 1347 def rundetached(args, condfn):
1348 1348 """Execute the argument list in a detached process.
1349 1349
1350 1350 condfn is a callable which is called repeatedly and should return
1351 1351 True once the child process is known to have started successfully.
1352 1352 At this point, the child process PID is returned. If the child
1353 1353 process fails to start or finishes before condfn() evaluates to
1354 1354 True, return -1.
1355 1355 """
1356 1356 # Windows case is easier because the child process is either
1357 1357 # successfully starting and validating the condition or exiting
1358 1358 # on failure. We just poll on its PID. On Unix, if the child
1359 1359 # process fails to start, it will be left in a zombie state until
1360 1360 # the parent wait on it, which we cannot do since we expect a long
1361 1361 # running process on success. Instead we listen for SIGCHLD telling
1362 1362 # us our child process terminated.
1363 1363 terminated = set()
1364 1364 def handler(signum, frame):
1365 1365 terminated.add(os.wait())
1366 1366 prevhandler = None
1367 1367 if hasattr(signal, 'SIGCHLD'):
1368 1368 prevhandler = signal.signal(signal.SIGCHLD, handler)
1369 1369 try:
1370 1370 pid = spawndetached(args)
1371 1371 while not condfn():
1372 1372 if ((pid in terminated or not testpid(pid))
1373 1373 and not condfn()):
1374 1374 return -1
1375 1375 time.sleep(0.1)
1376 1376 return pid
1377 1377 finally:
1378 1378 if prevhandler is not None:
1379 1379 signal.signal(signal.SIGCHLD, prevhandler)
1380 1380
1381 1381 try:
1382 1382 any, all = any, all
1383 1383 except NameError:
1384 1384 def any(iterable):
1385 1385 for i in iterable:
1386 1386 if i:
1387 1387 return True
1388 1388 return False
1389 1389
1390 1390 def all(iterable):
1391 1391 for i in iterable:
1392 1392 if not i:
1393 1393 return False
1394 1394 return True
1395 1395
1396 1396 def termwidth():
1397 1397 if 'COLUMNS' in os.environ:
1398 1398 try:
1399 1399 return int(os.environ['COLUMNS'])
1400 1400 except ValueError:
1401 1401 pass
1402 1402 return termwidth_()
1403 1403
1404 1404 def interpolate(prefix, mapping, s, fn=None):
1405 1405 """Return the result of interpolating items in the mapping into string s.
1406 1406
1407 1407 prefix is a single character string, or a two character string with
1408 1408 a backslash as the first character if the prefix needs to be escaped in
1409 1409 a regular expression.
1410 1410
1411 1411 fn is an optional function that will be applied to the replacement text
1412 1412 just before replacement.
1413 1413 """
1414 1414 fn = fn or (lambda s: s)
1415 1415 r = re.compile(r'%s(%s)' % (prefix, '|'.join(mapping.keys())))
1416 1416 return r.sub(lambda x: fn(mapping[x.group()[1:]]), s)
1417 1417
1418 1418 def getport(port):
1419 1419 """Return the port for a given network service.
1420 1420
1421 1421 If port is an integer, it's returned as is. If it's a string, it's
1422 1422 looked up using socket.getservbyname(). If there's no matching
1423 1423 service, util.Abort is raised.
1424 1424 """
1425 1425 try:
1426 1426 return int(port)
1427 1427 except ValueError:
1428 1428 pass
1429 1429
1430 1430 try:
1431 1431 return socket.getservbyname(port)
1432 1432 except socket.error:
1433 1433 raise Abort(_("no port number associated with service '%s'") % port)
General Comments 0
You need to be logged in to leave comments. Login now