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