##// END OF EJS Templates
encoding: normalize some silly encoding names
Matt Mackall -
r7461:2a67430f 1.1 default
parent child Browse files
Show More
@@ -1,1980 +1,1983 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, re, shutil, sys, tempfile, traceback
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 18 import imp
19 19
20 20 # Python compatibility
21 21
22 22 try:
23 23 set = set
24 24 frozenset = frozenset
25 25 except NameError:
26 26 from sets import Set as set, ImmutableSet as frozenset
27 27
28 28 _md5 = None
29 29 def md5(s):
30 30 global _md5
31 31 if _md5 is None:
32 32 try:
33 33 import hashlib
34 34 _md5 = hashlib.md5
35 35 except ImportError:
36 36 import md5
37 37 _md5 = md5.md5
38 38 return _md5(s)
39 39
40 40 _sha1 = None
41 41 def sha1(s):
42 42 global _sha1
43 43 if _sha1 is None:
44 44 try:
45 45 import hashlib
46 46 _sha1 = hashlib.sha1
47 47 except ImportError:
48 48 import sha
49 49 _sha1 = sha.sha
50 50 return _sha1(s)
51 51
52 52 try:
53 53 import subprocess
54 54 subprocess.Popen # trigger ImportError early
55 55 closefds = os.name == 'posix'
56 56 def popen2(cmd, mode='t', bufsize=-1):
57 57 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
58 58 close_fds=closefds,
59 59 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
60 60 return p.stdin, p.stdout
61 61 def popen3(cmd, mode='t', bufsize=-1):
62 62 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
63 63 close_fds=closefds,
64 64 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
65 65 stderr=subprocess.PIPE)
66 66 return p.stdin, p.stdout, p.stderr
67 67 def Popen3(cmd, capturestderr=False, bufsize=-1):
68 68 stderr = capturestderr and subprocess.PIPE or None
69 69 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
70 70 close_fds=closefds,
71 71 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
72 72 stderr=stderr)
73 73 p.fromchild = p.stdout
74 74 p.tochild = p.stdin
75 75 p.childerr = p.stderr
76 76 return p
77 77 except ImportError:
78 78 subprocess = None
79 79 from popen2 import Popen3
80 80 popen2 = os.popen2
81 81 popen3 = os.popen3
82 82
83 83
84 _encodingfixup = {'646': 'ascii', 'ANSI_X3.4-1968': 'ascii'}
85
84 86 try:
85 87 _encoding = os.environ.get("HGENCODING")
86 88 if sys.platform == 'darwin' and not _encoding:
87 89 # On darwin, getpreferredencoding ignores the locale environment and
88 90 # always returns mac-roman. We override this if the environment is
89 91 # not C (has been customized by the user).
90 92 locale.setlocale(locale.LC_CTYPE, '')
91 93 _encoding = locale.getlocale()[1]
92 94 if not _encoding:
93 95 _encoding = locale.getpreferredencoding() or 'ascii'
96 _encoding = _encodingfixup.get(_encoding, _encoding)
94 97 except locale.Error:
95 98 _encoding = 'ascii'
96 99 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
97 100 _fallbackencoding = 'ISO-8859-1'
98 101
99 102 def tolocal(s):
100 103 """
101 104 Convert a string from internal UTF-8 to local encoding
102 105
103 106 All internal strings should be UTF-8 but some repos before the
104 107 implementation of locale support may contain latin1 or possibly
105 108 other character sets. We attempt to decode everything strictly
106 109 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
107 110 replace unknown characters.
108 111 """
109 112 for e in ('UTF-8', _fallbackencoding):
110 113 try:
111 114 u = s.decode(e) # attempt strict decoding
112 115 return u.encode(_encoding, "replace")
113 116 except LookupError, k:
114 117 raise Abort(_("%s, please check your locale settings") % k)
115 118 except UnicodeDecodeError:
116 119 pass
117 120 u = s.decode("utf-8", "replace") # last ditch
118 121 return u.encode(_encoding, "replace")
119 122
120 123 def fromlocal(s):
121 124 """
122 125 Convert a string from the local character encoding to UTF-8
123 126
124 127 We attempt to decode strings using the encoding mode set by
125 128 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
126 129 characters will cause an error message. Other modes include
127 130 'replace', which replaces unknown characters with a special
128 131 Unicode character, and 'ignore', which drops the character.
129 132 """
130 133 try:
131 134 return s.decode(_encoding, _encodingmode).encode("utf-8")
132 135 except UnicodeDecodeError, inst:
133 136 sub = s[max(0, inst.start-10):inst.start+10]
134 137 raise Abort("decoding near '%s': %s!" % (sub, inst))
135 138 except LookupError, k:
136 139 raise Abort(_("%s, please check your locale settings") % k)
137 140
138 141 def locallen(s):
139 142 """Find the length in characters of a local string"""
140 143 return len(s.decode(_encoding, "replace"))
141 144
142 145 # used by parsedate
143 146 defaultdateformats = (
144 147 '%Y-%m-%d %H:%M:%S',
145 148 '%Y-%m-%d %I:%M:%S%p',
146 149 '%Y-%m-%d %H:%M',
147 150 '%Y-%m-%d %I:%M%p',
148 151 '%Y-%m-%d',
149 152 '%m-%d',
150 153 '%m/%d',
151 154 '%m/%d/%y',
152 155 '%m/%d/%Y',
153 156 '%a %b %d %H:%M:%S %Y',
154 157 '%a %b %d %I:%M:%S%p %Y',
155 158 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
156 159 '%b %d %H:%M:%S %Y',
157 160 '%b %d %I:%M:%S%p %Y',
158 161 '%b %d %H:%M:%S',
159 162 '%b %d %I:%M:%S%p',
160 163 '%b %d %H:%M',
161 164 '%b %d %I:%M%p',
162 165 '%b %d %Y',
163 166 '%b %d',
164 167 '%H:%M:%S',
165 168 '%I:%M:%SP',
166 169 '%H:%M',
167 170 '%I:%M%p',
168 171 )
169 172
170 173 extendeddateformats = defaultdateformats + (
171 174 "%Y",
172 175 "%Y-%m",
173 176 "%b",
174 177 "%b %Y",
175 178 )
176 179
177 180 class SignalInterrupt(Exception):
178 181 """Exception raised on SIGTERM and SIGHUP."""
179 182
180 183 # differences from SafeConfigParser:
181 184 # - case-sensitive keys
182 185 # - allows values that are not strings (this means that you may not
183 186 # be able to save the configuration to a file)
184 187 class configparser(ConfigParser.SafeConfigParser):
185 188 def optionxform(self, optionstr):
186 189 return optionstr
187 190
188 191 def set(self, section, option, value):
189 192 return ConfigParser.ConfigParser.set(self, section, option, value)
190 193
191 194 def _interpolate(self, section, option, rawval, vars):
192 195 if not isinstance(rawval, basestring):
193 196 return rawval
194 197 return ConfigParser.SafeConfigParser._interpolate(self, section,
195 198 option, rawval, vars)
196 199
197 200 def cachefunc(func):
198 201 '''cache the result of function calls'''
199 202 # XXX doesn't handle keywords args
200 203 cache = {}
201 204 if func.func_code.co_argcount == 1:
202 205 # we gain a small amount of time because
203 206 # we don't need to pack/unpack the list
204 207 def f(arg):
205 208 if arg not in cache:
206 209 cache[arg] = func(arg)
207 210 return cache[arg]
208 211 else:
209 212 def f(*args):
210 213 if args not in cache:
211 214 cache[args] = func(*args)
212 215 return cache[args]
213 216
214 217 return f
215 218
216 219 def pipefilter(s, cmd):
217 220 '''filter string S through command CMD, returning its output'''
218 221 (pin, pout) = popen2(cmd, 'b')
219 222 def writer():
220 223 try:
221 224 pin.write(s)
222 225 pin.close()
223 226 except IOError, inst:
224 227 if inst.errno != errno.EPIPE:
225 228 raise
226 229
227 230 # we should use select instead on UNIX, but this will work on most
228 231 # systems, including Windows
229 232 w = threading.Thread(target=writer)
230 233 w.start()
231 234 f = pout.read()
232 235 pout.close()
233 236 w.join()
234 237 return f
235 238
236 239 def tempfilter(s, cmd):
237 240 '''filter string S through a pair of temporary files with CMD.
238 241 CMD is used as a template to create the real command to be run,
239 242 with the strings INFILE and OUTFILE replaced by the real names of
240 243 the temporary files generated.'''
241 244 inname, outname = None, None
242 245 try:
243 246 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
244 247 fp = os.fdopen(infd, 'wb')
245 248 fp.write(s)
246 249 fp.close()
247 250 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
248 251 os.close(outfd)
249 252 cmd = cmd.replace('INFILE', inname)
250 253 cmd = cmd.replace('OUTFILE', outname)
251 254 code = os.system(cmd)
252 255 if sys.platform == 'OpenVMS' and code & 1:
253 256 code = 0
254 257 if code: raise Abort(_("command '%s' failed: %s") %
255 258 (cmd, explain_exit(code)))
256 259 return open(outname, 'rb').read()
257 260 finally:
258 261 try:
259 262 if inname: os.unlink(inname)
260 263 except: pass
261 264 try:
262 265 if outname: os.unlink(outname)
263 266 except: pass
264 267
265 268 filtertable = {
266 269 'tempfile:': tempfilter,
267 270 'pipe:': pipefilter,
268 271 }
269 272
270 273 def filter(s, cmd):
271 274 "filter a string through a command that transforms its input to its output"
272 275 for name, fn in filtertable.iteritems():
273 276 if cmd.startswith(name):
274 277 return fn(s, cmd[len(name):].lstrip())
275 278 return pipefilter(s, cmd)
276 279
277 280 def binary(s):
278 281 """return true if a string is binary data"""
279 282 if s and '\0' in s:
280 283 return True
281 284 return False
282 285
283 286 def unique(g):
284 287 """return the uniq elements of iterable g"""
285 288 return dict.fromkeys(g).keys()
286 289
287 290 def sort(l):
288 291 if not isinstance(l, list):
289 292 l = list(l)
290 293 l.sort()
291 294 return l
292 295
293 296 def increasingchunks(source, min=1024, max=65536):
294 297 '''return no less than min bytes per chunk while data remains,
295 298 doubling min after each chunk until it reaches max'''
296 299 def log2(x):
297 300 if not x:
298 301 return 0
299 302 i = 0
300 303 while x:
301 304 x >>= 1
302 305 i += 1
303 306 return i - 1
304 307
305 308 buf = []
306 309 blen = 0
307 310 for chunk in source:
308 311 buf.append(chunk)
309 312 blen += len(chunk)
310 313 if blen >= min:
311 314 if min < max:
312 315 min = min << 1
313 316 nmin = 1 << log2(blen)
314 317 if nmin > min:
315 318 min = nmin
316 319 if min > max:
317 320 min = max
318 321 yield ''.join(buf)
319 322 blen = 0
320 323 buf = []
321 324 if buf:
322 325 yield ''.join(buf)
323 326
324 327 class Abort(Exception):
325 328 """Raised if a command needs to print an error and exit."""
326 329
327 330 class UnexpectedOutput(Abort):
328 331 """Raised to print an error with part of output and exit."""
329 332
330 333 def always(fn): return True
331 334 def never(fn): return False
332 335
333 336 def expand_glob(pats):
334 337 '''On Windows, expand the implicit globs in a list of patterns'''
335 338 if os.name != 'nt':
336 339 return list(pats)
337 340 ret = []
338 341 for p in pats:
339 342 kind, name = patkind(p, None)
340 343 if kind is None:
341 344 globbed = glob.glob(name)
342 345 if globbed:
343 346 ret.extend(globbed)
344 347 continue
345 348 # if we couldn't expand the glob, just keep it around
346 349 ret.append(p)
347 350 return ret
348 351
349 352 def patkind(name, default):
350 353 """Split a string into an optional pattern kind prefix and the
351 354 actual pattern."""
352 355 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
353 356 if name.startswith(prefix + ':'): return name.split(':', 1)
354 357 return default, name
355 358
356 359 def globre(pat, head='^', tail='$'):
357 360 "convert a glob pattern into a regexp"
358 361 i, n = 0, len(pat)
359 362 res = ''
360 363 group = 0
361 364 def peek(): return i < n and pat[i]
362 365 while i < n:
363 366 c = pat[i]
364 367 i = i+1
365 368 if c == '*':
366 369 if peek() == '*':
367 370 i += 1
368 371 res += '.*'
369 372 else:
370 373 res += '[^/]*'
371 374 elif c == '?':
372 375 res += '.'
373 376 elif c == '[':
374 377 j = i
375 378 if j < n and pat[j] in '!]':
376 379 j += 1
377 380 while j < n and pat[j] != ']':
378 381 j += 1
379 382 if j >= n:
380 383 res += '\\['
381 384 else:
382 385 stuff = pat[i:j].replace('\\','\\\\')
383 386 i = j + 1
384 387 if stuff[0] == '!':
385 388 stuff = '^' + stuff[1:]
386 389 elif stuff[0] == '^':
387 390 stuff = '\\' + stuff
388 391 res = '%s[%s]' % (res, stuff)
389 392 elif c == '{':
390 393 group += 1
391 394 res += '(?:'
392 395 elif c == '}' and group:
393 396 res += ')'
394 397 group -= 1
395 398 elif c == ',' and group:
396 399 res += '|'
397 400 elif c == '\\':
398 401 p = peek()
399 402 if p:
400 403 i += 1
401 404 res += re.escape(p)
402 405 else:
403 406 res += re.escape(c)
404 407 else:
405 408 res += re.escape(c)
406 409 return head + res + tail
407 410
408 411 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
409 412
410 413 def pathto(root, n1, n2):
411 414 '''return the relative path from one place to another.
412 415 root should use os.sep to separate directories
413 416 n1 should use os.sep to separate directories
414 417 n2 should use "/" to separate directories
415 418 returns an os.sep-separated path.
416 419
417 420 If n1 is a relative path, it's assumed it's
418 421 relative to root.
419 422 n2 should always be relative to root.
420 423 '''
421 424 if not n1: return localpath(n2)
422 425 if os.path.isabs(n1):
423 426 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
424 427 return os.path.join(root, localpath(n2))
425 428 n2 = '/'.join((pconvert(root), n2))
426 429 a, b = splitpath(n1), n2.split('/')
427 430 a.reverse()
428 431 b.reverse()
429 432 while a and b and a[-1] == b[-1]:
430 433 a.pop()
431 434 b.pop()
432 435 b.reverse()
433 436 return os.sep.join((['..'] * len(a)) + b) or '.'
434 437
435 438 def canonpath(root, cwd, myname):
436 439 """return the canonical path of myname, given cwd and root"""
437 440 if root == os.sep:
438 441 rootsep = os.sep
439 442 elif endswithsep(root):
440 443 rootsep = root
441 444 else:
442 445 rootsep = root + os.sep
443 446 name = myname
444 447 if not os.path.isabs(name):
445 448 name = os.path.join(root, cwd, name)
446 449 name = os.path.normpath(name)
447 450 audit_path = path_auditor(root)
448 451 if name != rootsep and name.startswith(rootsep):
449 452 name = name[len(rootsep):]
450 453 audit_path(name)
451 454 return pconvert(name)
452 455 elif name == root:
453 456 return ''
454 457 else:
455 458 # Determine whether `name' is in the hierarchy at or beneath `root',
456 459 # by iterating name=dirname(name) until that causes no change (can't
457 460 # check name == '/', because that doesn't work on windows). For each
458 461 # `name', compare dev/inode numbers. If they match, the list `rel'
459 462 # holds the reversed list of components making up the relative file
460 463 # name we want.
461 464 root_st = os.stat(root)
462 465 rel = []
463 466 while True:
464 467 try:
465 468 name_st = os.stat(name)
466 469 except OSError:
467 470 break
468 471 if samestat(name_st, root_st):
469 472 if not rel:
470 473 # name was actually the same as root (maybe a symlink)
471 474 return ''
472 475 rel.reverse()
473 476 name = os.path.join(*rel)
474 477 audit_path(name)
475 478 return pconvert(name)
476 479 dirname, basename = os.path.split(name)
477 480 rel.append(basename)
478 481 if dirname == name:
479 482 break
480 483 name = dirname
481 484
482 485 raise Abort('%s not under root' % myname)
483 486
484 487 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None, dflt_pat='glob'):
485 488 """build a function to match a set of file patterns
486 489
487 490 arguments:
488 491 canonroot - the canonical root of the tree you're matching against
489 492 cwd - the current working directory, if relevant
490 493 names - patterns to find
491 494 inc - patterns to include
492 495 exc - patterns to exclude
493 496 dflt_pat - if a pattern in names has no explicit type, assume this one
494 497 src - where these patterns came from (e.g. .hgignore)
495 498
496 499 a pattern is one of:
497 500 'glob:<glob>' - a glob relative to cwd
498 501 're:<regexp>' - a regular expression
499 502 'path:<path>' - a path relative to canonroot
500 503 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
501 504 'relpath:<path>' - a path relative to cwd
502 505 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
503 506 '<something>' - one of the cases above, selected by the dflt_pat argument
504 507
505 508 returns:
506 509 a 3-tuple containing
507 510 - list of roots (places where one should start a recursive walk of the fs);
508 511 this often matches the explicit non-pattern names passed in, but also
509 512 includes the initial part of glob: patterns that has no glob characters
510 513 - a bool match(filename) function
511 514 - a bool indicating if any patterns were passed in
512 515 """
513 516
514 517 # a common case: no patterns at all
515 518 if not names and not inc and not exc:
516 519 return [], always, False
517 520
518 521 def contains_glob(name):
519 522 for c in name:
520 523 if c in _globchars: return True
521 524 return False
522 525
523 526 def regex(kind, name, tail):
524 527 '''convert a pattern into a regular expression'''
525 528 if not name:
526 529 return ''
527 530 if kind == 're':
528 531 return name
529 532 elif kind == 'path':
530 533 return '^' + re.escape(name) + '(?:/|$)'
531 534 elif kind == 'relglob':
532 535 return globre(name, '(?:|.*/)', tail)
533 536 elif kind == 'relpath':
534 537 return re.escape(name) + '(?:/|$)'
535 538 elif kind == 'relre':
536 539 if name.startswith('^'):
537 540 return name
538 541 return '.*' + name
539 542 return globre(name, '', tail)
540 543
541 544 def matchfn(pats, tail):
542 545 """build a matching function from a set of patterns"""
543 546 if not pats:
544 547 return
545 548 try:
546 549 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
547 550 if len(pat) > 20000:
548 551 raise OverflowError()
549 552 return re.compile(pat).match
550 553 except OverflowError:
551 554 # We're using a Python with a tiny regex engine and we
552 555 # made it explode, so we'll divide the pattern list in two
553 556 # until it works
554 557 l = len(pats)
555 558 if l < 2:
556 559 raise
557 560 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
558 561 return lambda s: a(s) or b(s)
559 562 except re.error:
560 563 for k, p in pats:
561 564 try:
562 565 re.compile('(?:%s)' % regex(k, p, tail))
563 566 except re.error:
564 567 if src:
565 568 raise Abort("%s: invalid pattern (%s): %s" %
566 569 (src, k, p))
567 570 else:
568 571 raise Abort("invalid pattern (%s): %s" % (k, p))
569 572 raise Abort("invalid pattern")
570 573
571 574 def globprefix(pat):
572 575 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
573 576 root = []
574 577 for p in pat.split('/'):
575 578 if contains_glob(p): break
576 579 root.append(p)
577 580 return '/'.join(root) or '.'
578 581
579 582 def normalizepats(names, default):
580 583 pats = []
581 584 roots = []
582 585 anypats = False
583 586 for kind, name in [patkind(p, default) for p in names]:
584 587 if kind in ('glob', 'relpath'):
585 588 name = canonpath(canonroot, cwd, name)
586 589 elif kind in ('relglob', 'path'):
587 590 name = normpath(name)
588 591
589 592 pats.append((kind, name))
590 593
591 594 if kind in ('glob', 're', 'relglob', 'relre'):
592 595 anypats = True
593 596
594 597 if kind == 'glob':
595 598 root = globprefix(name)
596 599 roots.append(root)
597 600 elif kind in ('relpath', 'path'):
598 601 roots.append(name or '.')
599 602 elif kind == 'relglob':
600 603 roots.append('.')
601 604 return roots, pats, anypats
602 605
603 606 roots, pats, anypats = normalizepats(names, dflt_pat)
604 607
605 608 patmatch = matchfn(pats, '$') or always
606 609 incmatch = always
607 610 if inc:
608 611 dummy, inckinds, dummy = normalizepats(inc, 'glob')
609 612 incmatch = matchfn(inckinds, '(?:/|$)')
610 613 excmatch = never
611 614 if exc:
612 615 dummy, exckinds, dummy = normalizepats(exc, 'glob')
613 616 excmatch = matchfn(exckinds, '(?:/|$)')
614 617
615 618 if not names and inc and not exc:
616 619 # common case: hgignore patterns
617 620 match = incmatch
618 621 else:
619 622 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
620 623
621 624 return (roots, match, (inc or exc or anypats) and True)
622 625
623 626 _hgexecutable = None
624 627
625 628 def main_is_frozen():
626 629 """return True if we are a frozen executable.
627 630
628 631 The code supports py2exe (most common, Windows only) and tools/freeze
629 632 (portable, not much used).
630 633 """
631 634 return (hasattr(sys, "frozen") or # new py2exe
632 635 hasattr(sys, "importers") or # old py2exe
633 636 imp.is_frozen("__main__")) # tools/freeze
634 637
635 638 def hgexecutable():
636 639 """return location of the 'hg' executable.
637 640
638 641 Defaults to $HG or 'hg' in the search path.
639 642 """
640 643 if _hgexecutable is None:
641 644 hg = os.environ.get('HG')
642 645 if hg:
643 646 set_hgexecutable(hg)
644 647 elif main_is_frozen():
645 648 set_hgexecutable(sys.executable)
646 649 else:
647 650 set_hgexecutable(find_exe('hg', 'hg'))
648 651 return _hgexecutable
649 652
650 653 def set_hgexecutable(path):
651 654 """set location of the 'hg' executable"""
652 655 global _hgexecutable
653 656 _hgexecutable = path
654 657
655 658 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
656 659 '''enhanced shell command execution.
657 660 run with environment maybe modified, maybe in different dir.
658 661
659 662 if command fails and onerr is None, return status. if ui object,
660 663 print error message and return status, else raise onerr object as
661 664 exception.'''
662 665 def py2shell(val):
663 666 'convert python object into string that is useful to shell'
664 667 if val in (None, False):
665 668 return '0'
666 669 if val == True:
667 670 return '1'
668 671 return str(val)
669 672 oldenv = {}
670 673 for k in environ:
671 674 oldenv[k] = os.environ.get(k)
672 675 if cwd is not None:
673 676 oldcwd = os.getcwd()
674 677 origcmd = cmd
675 678 if os.name == 'nt':
676 679 cmd = '"%s"' % cmd
677 680 try:
678 681 for k, v in environ.iteritems():
679 682 os.environ[k] = py2shell(v)
680 683 os.environ['HG'] = hgexecutable()
681 684 if cwd is not None and oldcwd != cwd:
682 685 os.chdir(cwd)
683 686 rc = os.system(cmd)
684 687 if sys.platform == 'OpenVMS' and rc & 1:
685 688 rc = 0
686 689 if rc and onerr:
687 690 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
688 691 explain_exit(rc)[0])
689 692 if errprefix:
690 693 errmsg = '%s: %s' % (errprefix, errmsg)
691 694 try:
692 695 onerr.warn(errmsg + '\n')
693 696 except AttributeError:
694 697 raise onerr(errmsg)
695 698 return rc
696 699 finally:
697 700 for k, v in oldenv.iteritems():
698 701 if v is None:
699 702 del os.environ[k]
700 703 else:
701 704 os.environ[k] = v
702 705 if cwd is not None and oldcwd != cwd:
703 706 os.chdir(oldcwd)
704 707
705 708 class SignatureError:
706 709 pass
707 710
708 711 def checksignature(func):
709 712 '''wrap a function with code to check for calling errors'''
710 713 def check(*args, **kwargs):
711 714 try:
712 715 return func(*args, **kwargs)
713 716 except TypeError:
714 717 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
715 718 raise SignatureError
716 719 raise
717 720
718 721 return check
719 722
720 723 # os.path.lexists is not available on python2.3
721 724 def lexists(filename):
722 725 "test whether a file with this name exists. does not follow symlinks"
723 726 try:
724 727 os.lstat(filename)
725 728 except:
726 729 return False
727 730 return True
728 731
729 732 def rename(src, dst):
730 733 """forcibly rename a file"""
731 734 try:
732 735 os.rename(src, dst)
733 736 except OSError, err: # FIXME: check err (EEXIST ?)
734 737 # on windows, rename to existing file is not allowed, so we
735 738 # must delete destination first. but if file is open, unlink
736 739 # schedules it for delete but does not delete it. rename
737 740 # happens immediately even for open files, so we create
738 741 # temporary file, delete it, rename destination to that name,
739 742 # then delete that. then rename is safe to do.
740 743 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
741 744 os.close(fd)
742 745 os.unlink(temp)
743 746 os.rename(dst, temp)
744 747 os.unlink(temp)
745 748 os.rename(src, dst)
746 749
747 750 def unlink(f):
748 751 """unlink and remove the directory if it is empty"""
749 752 os.unlink(f)
750 753 # try removing directories that might now be empty
751 754 try:
752 755 os.removedirs(os.path.dirname(f))
753 756 except OSError:
754 757 pass
755 758
756 759 def copyfile(src, dest):
757 760 "copy a file, preserving mode"
758 761 if os.path.islink(src):
759 762 try:
760 763 os.unlink(dest)
761 764 except:
762 765 pass
763 766 os.symlink(os.readlink(src), dest)
764 767 else:
765 768 try:
766 769 shutil.copyfile(src, dest)
767 770 shutil.copymode(src, dest)
768 771 except shutil.Error, inst:
769 772 raise Abort(str(inst))
770 773
771 774 def copyfiles(src, dst, hardlink=None):
772 775 """Copy a directory tree using hardlinks if possible"""
773 776
774 777 if hardlink is None:
775 778 hardlink = (os.stat(src).st_dev ==
776 779 os.stat(os.path.dirname(dst)).st_dev)
777 780
778 781 if os.path.isdir(src):
779 782 os.mkdir(dst)
780 783 for name, kind in osutil.listdir(src):
781 784 srcname = os.path.join(src, name)
782 785 dstname = os.path.join(dst, name)
783 786 copyfiles(srcname, dstname, hardlink)
784 787 else:
785 788 if hardlink:
786 789 try:
787 790 os_link(src, dst)
788 791 except (IOError, OSError):
789 792 hardlink = False
790 793 shutil.copy(src, dst)
791 794 else:
792 795 shutil.copy(src, dst)
793 796
794 797 class path_auditor(object):
795 798 '''ensure that a filesystem path contains no banned components.
796 799 the following properties of a path are checked:
797 800
798 801 - under top-level .hg
799 802 - starts at the root of a windows drive
800 803 - contains ".."
801 804 - traverses a symlink (e.g. a/symlink_here/b)
802 805 - inside a nested repository'''
803 806
804 807 def __init__(self, root):
805 808 self.audited = set()
806 809 self.auditeddir = set()
807 810 self.root = root
808 811
809 812 def __call__(self, path):
810 813 if path in self.audited:
811 814 return
812 815 normpath = os.path.normcase(path)
813 816 parts = splitpath(normpath)
814 817 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
815 818 or os.pardir in parts):
816 819 raise Abort(_("path contains illegal component: %s") % path)
817 820 def check(prefix):
818 821 curpath = os.path.join(self.root, prefix)
819 822 try:
820 823 st = os.lstat(curpath)
821 824 except OSError, err:
822 825 # EINVAL can be raised as invalid path syntax under win32.
823 826 # They must be ignored for patterns can be checked too.
824 827 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
825 828 raise
826 829 else:
827 830 if stat.S_ISLNK(st.st_mode):
828 831 raise Abort(_('path %r traverses symbolic link %r') %
829 832 (path, prefix))
830 833 elif (stat.S_ISDIR(st.st_mode) and
831 834 os.path.isdir(os.path.join(curpath, '.hg'))):
832 835 raise Abort(_('path %r is inside repo %r') %
833 836 (path, prefix))
834 837 parts.pop()
835 838 prefixes = []
836 839 for n in range(len(parts)):
837 840 prefix = os.sep.join(parts)
838 841 if prefix in self.auditeddir:
839 842 break
840 843 check(prefix)
841 844 prefixes.append(prefix)
842 845 parts.pop()
843 846
844 847 self.audited.add(path)
845 848 # only add prefixes to the cache after checking everything: we don't
846 849 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
847 850 self.auditeddir.update(prefixes)
848 851
849 852 def _makelock_file(info, pathname):
850 853 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
851 854 os.write(ld, info)
852 855 os.close(ld)
853 856
854 857 def _readlock_file(pathname):
855 858 return posixfile(pathname).read()
856 859
857 860 def nlinks(pathname):
858 861 """Return number of hardlinks for the given file."""
859 862 return os.lstat(pathname).st_nlink
860 863
861 864 if hasattr(os, 'link'):
862 865 os_link = os.link
863 866 else:
864 867 def os_link(src, dst):
865 868 raise OSError(0, _("Hardlinks not supported"))
866 869
867 870 def fstat(fp):
868 871 '''stat file object that may not have fileno method.'''
869 872 try:
870 873 return os.fstat(fp.fileno())
871 874 except AttributeError:
872 875 return os.stat(fp.name)
873 876
874 877 posixfile = file
875 878
876 879 def openhardlinks():
877 880 '''return true if it is safe to hold open file handles to hardlinks'''
878 881 return True
879 882
880 883 def _statfiles(files):
881 884 'Stat each file in files and yield stat or None if file does not exist.'
882 885 lstat = os.lstat
883 886 for nf in files:
884 887 try:
885 888 st = lstat(nf)
886 889 except OSError, err:
887 890 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
888 891 raise
889 892 st = None
890 893 yield st
891 894
892 895 def _statfiles_clustered(files):
893 896 '''Stat each file in files and yield stat or None if file does not exist.
894 897 Cluster and cache stat per directory to minimize number of OS stat calls.'''
895 898 lstat = os.lstat
896 899 ncase = os.path.normcase
897 900 sep = os.sep
898 901 dircache = {} # dirname -> filename -> status | None if file does not exist
899 902 for nf in files:
900 903 nf = ncase(nf)
901 904 pos = nf.rfind(sep)
902 905 if pos == -1:
903 906 dir, base = '.', nf
904 907 else:
905 908 dir, base = nf[:pos+1], nf[pos+1:]
906 909 cache = dircache.get(dir, None)
907 910 if cache is None:
908 911 try:
909 912 dmap = dict([(ncase(n), s)
910 913 for n, k, s in osutil.listdir(dir, True)])
911 914 except OSError, err:
912 915 # handle directory not found in Python version prior to 2.5
913 916 # Python <= 2.4 returns native Windows code 3 in errno
914 917 # Python >= 2.5 returns ENOENT and adds winerror field
915 918 # EINVAL is raised if dir is not a directory.
916 919 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
917 920 errno.ENOTDIR):
918 921 raise
919 922 dmap = {}
920 923 cache = dircache.setdefault(dir, dmap)
921 924 yield cache.get(base, None)
922 925
923 926 if sys.platform == 'win32':
924 927 statfiles = _statfiles_clustered
925 928 else:
926 929 statfiles = _statfiles
927 930
928 931 getuser_fallback = None
929 932
930 933 def getuser():
931 934 '''return name of current user'''
932 935 try:
933 936 return getpass.getuser()
934 937 except ImportError:
935 938 # import of pwd will fail on windows - try fallback
936 939 if getuser_fallback:
937 940 return getuser_fallback()
938 941 # raised if win32api not available
939 942 raise Abort(_('user name not available - set USERNAME '
940 943 'environment variable'))
941 944
942 945 def username(uid=None):
943 946 """Return the name of the user with the given uid.
944 947
945 948 If uid is None, return the name of the current user."""
946 949 try:
947 950 import pwd
948 951 if uid is None:
949 952 uid = os.getuid()
950 953 try:
951 954 return pwd.getpwuid(uid)[0]
952 955 except KeyError:
953 956 return str(uid)
954 957 except ImportError:
955 958 return None
956 959
957 960 def groupname(gid=None):
958 961 """Return the name of the group with the given gid.
959 962
960 963 If gid is None, return the name of the current group."""
961 964 try:
962 965 import grp
963 966 if gid is None:
964 967 gid = os.getgid()
965 968 try:
966 969 return grp.getgrgid(gid)[0]
967 970 except KeyError:
968 971 return str(gid)
969 972 except ImportError:
970 973 return None
971 974
972 975 # File system features
973 976
974 977 def checkcase(path):
975 978 """
976 979 Check whether the given path is on a case-sensitive filesystem
977 980
978 981 Requires a path (like /foo/.hg) ending with a foldable final
979 982 directory component.
980 983 """
981 984 s1 = os.stat(path)
982 985 d, b = os.path.split(path)
983 986 p2 = os.path.join(d, b.upper())
984 987 if path == p2:
985 988 p2 = os.path.join(d, b.lower())
986 989 try:
987 990 s2 = os.stat(p2)
988 991 if s2 == s1:
989 992 return False
990 993 return True
991 994 except:
992 995 return True
993 996
994 997 _fspathcache = {}
995 998 def fspath(name, root):
996 999 '''Get name in the case stored in the filesystem
997 1000
998 1001 The name is either relative to root, or it is an absolute path starting
999 1002 with root. Note that this function is unnecessary, and should not be
1000 1003 called, for case-sensitive filesystems (simply because it's expensive).
1001 1004 '''
1002 1005 # If name is absolute, make it relative
1003 1006 if name.lower().startswith(root.lower()):
1004 1007 l = len(root)
1005 1008 if name[l] == os.sep or name[l] == os.altsep:
1006 1009 l = l + 1
1007 1010 name = name[l:]
1008 1011
1009 1012 if not os.path.exists(os.path.join(root, name)):
1010 1013 return None
1011 1014
1012 1015 seps = os.sep
1013 1016 if os.altsep:
1014 1017 seps = seps + os.altsep
1015 1018 # Protect backslashes. This gets silly very quickly.
1016 1019 seps.replace('\\','\\\\')
1017 1020 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
1018 1021 dir = os.path.normcase(os.path.normpath(root))
1019 1022 result = []
1020 1023 for part, sep in pattern.findall(name):
1021 1024 if sep:
1022 1025 result.append(sep)
1023 1026 continue
1024 1027
1025 1028 if dir not in _fspathcache:
1026 1029 _fspathcache[dir] = os.listdir(dir)
1027 1030 contents = _fspathcache[dir]
1028 1031
1029 1032 lpart = part.lower()
1030 1033 for n in contents:
1031 1034 if n.lower() == lpart:
1032 1035 result.append(n)
1033 1036 break
1034 1037 else:
1035 1038 # Cannot happen, as the file exists!
1036 1039 result.append(part)
1037 1040 dir = os.path.join(dir, lpart)
1038 1041
1039 1042 return ''.join(result)
1040 1043
1041 1044 def checkexec(path):
1042 1045 """
1043 1046 Check whether the given path is on a filesystem with UNIX-like exec flags
1044 1047
1045 1048 Requires a directory (like /foo/.hg)
1046 1049 """
1047 1050
1048 1051 # VFAT on some Linux versions can flip mode but it doesn't persist
1049 1052 # a FS remount. Frequently we can detect it if files are created
1050 1053 # with exec bit on.
1051 1054
1052 1055 try:
1053 1056 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
1054 1057 fh, fn = tempfile.mkstemp("", "", path)
1055 1058 try:
1056 1059 os.close(fh)
1057 1060 m = os.stat(fn).st_mode & 0777
1058 1061 new_file_has_exec = m & EXECFLAGS
1059 1062 os.chmod(fn, m ^ EXECFLAGS)
1060 1063 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
1061 1064 finally:
1062 1065 os.unlink(fn)
1063 1066 except (IOError, OSError):
1064 1067 # we don't care, the user probably won't be able to commit anyway
1065 1068 return False
1066 1069 return not (new_file_has_exec or exec_flags_cannot_flip)
1067 1070
1068 1071 def checklink(path):
1069 1072 """check whether the given path is on a symlink-capable filesystem"""
1070 1073 # mktemp is not racy because symlink creation will fail if the
1071 1074 # file already exists
1072 1075 name = tempfile.mktemp(dir=path)
1073 1076 try:
1074 1077 os.symlink(".", name)
1075 1078 os.unlink(name)
1076 1079 return True
1077 1080 except (OSError, AttributeError):
1078 1081 return False
1079 1082
1080 1083 _umask = os.umask(0)
1081 1084 os.umask(_umask)
1082 1085
1083 1086 def needbinarypatch():
1084 1087 """return True if patches should be applied in binary mode by default."""
1085 1088 return os.name == 'nt'
1086 1089
1087 1090 def endswithsep(path):
1088 1091 '''Check path ends with os.sep or os.altsep.'''
1089 1092 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
1090 1093
1091 1094 def splitpath(path):
1092 1095 '''Split path by os.sep.
1093 1096 Note that this function does not use os.altsep because this is
1094 1097 an alternative of simple "xxx.split(os.sep)".
1095 1098 It is recommended to use os.path.normpath() before using this
1096 1099 function if need.'''
1097 1100 return path.split(os.sep)
1098 1101
1099 1102 def gui():
1100 1103 '''Are we running in a GUI?'''
1101 1104 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
1102 1105
1103 1106 def lookup_reg(key, name=None, scope=None):
1104 1107 return None
1105 1108
1106 1109 # Platform specific variants
1107 1110 if os.name == 'nt':
1108 1111 import msvcrt
1109 1112 nulldev = 'NUL:'
1110 1113
1111 1114 class winstdout:
1112 1115 '''stdout on windows misbehaves if sent through a pipe'''
1113 1116
1114 1117 def __init__(self, fp):
1115 1118 self.fp = fp
1116 1119
1117 1120 def __getattr__(self, key):
1118 1121 return getattr(self.fp, key)
1119 1122
1120 1123 def close(self):
1121 1124 try:
1122 1125 self.fp.close()
1123 1126 except: pass
1124 1127
1125 1128 def write(self, s):
1126 1129 try:
1127 1130 # This is workaround for "Not enough space" error on
1128 1131 # writing large size of data to console.
1129 1132 limit = 16000
1130 1133 l = len(s)
1131 1134 start = 0
1132 1135 while start < l:
1133 1136 end = start + limit
1134 1137 self.fp.write(s[start:end])
1135 1138 start = end
1136 1139 except IOError, inst:
1137 1140 if inst.errno != 0: raise
1138 1141 self.close()
1139 1142 raise IOError(errno.EPIPE, 'Broken pipe')
1140 1143
1141 1144 def flush(self):
1142 1145 try:
1143 1146 return self.fp.flush()
1144 1147 except IOError, inst:
1145 1148 if inst.errno != errno.EINVAL: raise
1146 1149 self.close()
1147 1150 raise IOError(errno.EPIPE, 'Broken pipe')
1148 1151
1149 1152 sys.stdout = winstdout(sys.stdout)
1150 1153
1151 1154 def _is_win_9x():
1152 1155 '''return true if run on windows 95, 98 or me.'''
1153 1156 try:
1154 1157 return sys.getwindowsversion()[3] == 1
1155 1158 except AttributeError:
1156 1159 return 'command' in os.environ.get('comspec', '')
1157 1160
1158 1161 def openhardlinks():
1159 1162 return not _is_win_9x and "win32api" in locals()
1160 1163
1161 1164 def system_rcpath():
1162 1165 try:
1163 1166 return system_rcpath_win32()
1164 1167 except:
1165 1168 return [r'c:\mercurial\mercurial.ini']
1166 1169
1167 1170 def user_rcpath():
1168 1171 '''return os-specific hgrc search path to the user dir'''
1169 1172 try:
1170 1173 path = user_rcpath_win32()
1171 1174 except:
1172 1175 home = os.path.expanduser('~')
1173 1176 path = [os.path.join(home, 'mercurial.ini'),
1174 1177 os.path.join(home, '.hgrc')]
1175 1178 userprofile = os.environ.get('USERPROFILE')
1176 1179 if userprofile:
1177 1180 path.append(os.path.join(userprofile, 'mercurial.ini'))
1178 1181 path.append(os.path.join(userprofile, '.hgrc'))
1179 1182 return path
1180 1183
1181 1184 def parse_patch_output(output_line):
1182 1185 """parses the output produced by patch and returns the file name"""
1183 1186 pf = output_line[14:]
1184 1187 if pf[0] == '`':
1185 1188 pf = pf[1:-1] # Remove the quotes
1186 1189 return pf
1187 1190
1188 1191 def sshargs(sshcmd, host, user, port):
1189 1192 '''Build argument list for ssh or Plink'''
1190 1193 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
1191 1194 args = user and ("%s@%s" % (user, host)) or host
1192 1195 return port and ("%s %s %s" % (args, pflag, port)) or args
1193 1196
1194 1197 def testpid(pid):
1195 1198 '''return False if pid dead, True if running or not known'''
1196 1199 return True
1197 1200
1198 1201 def set_flags(f, l, x):
1199 1202 pass
1200 1203
1201 1204 def set_binary(fd):
1202 1205 # When run without console, pipes may expose invalid
1203 1206 # fileno(), usually set to -1.
1204 1207 if hasattr(fd, 'fileno') and fd.fileno() >= 0:
1205 1208 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1206 1209
1207 1210 def pconvert(path):
1208 1211 return '/'.join(splitpath(path))
1209 1212
1210 1213 def localpath(path):
1211 1214 return path.replace('/', '\\')
1212 1215
1213 1216 def normpath(path):
1214 1217 return pconvert(os.path.normpath(path))
1215 1218
1216 1219 makelock = _makelock_file
1217 1220 readlock = _readlock_file
1218 1221
1219 1222 def samestat(s1, s2):
1220 1223 return False
1221 1224
1222 1225 # A sequence of backslashes is special iff it precedes a double quote:
1223 1226 # - if there's an even number of backslashes, the double quote is not
1224 1227 # quoted (i.e. it ends the quoted region)
1225 1228 # - if there's an odd number of backslashes, the double quote is quoted
1226 1229 # - in both cases, every pair of backslashes is unquoted into a single
1227 1230 # backslash
1228 1231 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1229 1232 # So, to quote a string, we must surround it in double quotes, double
1230 1233 # the number of backslashes that preceed double quotes and add another
1231 1234 # backslash before every double quote (being careful with the double
1232 1235 # quote we've appended to the end)
1233 1236 _quotere = None
1234 1237 def shellquote(s):
1235 1238 global _quotere
1236 1239 if _quotere is None:
1237 1240 _quotere = re.compile(r'(\\*)("|\\$)')
1238 1241 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1239 1242
1240 1243 def quotecommand(cmd):
1241 1244 """Build a command string suitable for os.popen* calls."""
1242 1245 # The extra quotes are needed because popen* runs the command
1243 1246 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1244 1247 return '"' + cmd + '"'
1245 1248
1246 1249 def popen(command, mode='r'):
1247 1250 # Work around "popen spawned process may not write to stdout
1248 1251 # under windows"
1249 1252 # http://bugs.python.org/issue1366
1250 1253 command += " 2> %s" % nulldev
1251 1254 return os.popen(quotecommand(command), mode)
1252 1255
1253 1256 def explain_exit(code):
1254 1257 return _("exited with status %d") % code, code
1255 1258
1256 1259 # if you change this stub into a real check, please try to implement the
1257 1260 # username and groupname functions above, too.
1258 1261 def isowner(fp, st=None):
1259 1262 return True
1260 1263
1261 1264 def find_in_path(name, path, default=None):
1262 1265 '''find name in search path. path can be string (will be split
1263 1266 with os.pathsep), or iterable thing that returns strings. if name
1264 1267 found, return path to name. else return default. name is looked up
1265 1268 using cmd.exe rules, using PATHEXT.'''
1266 1269 if isinstance(path, str):
1267 1270 path = path.split(os.pathsep)
1268 1271
1269 1272 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1270 1273 pathext = pathext.lower().split(os.pathsep)
1271 1274 isexec = os.path.splitext(name)[1].lower() in pathext
1272 1275
1273 1276 for p in path:
1274 1277 p_name = os.path.join(p, name)
1275 1278
1276 1279 if isexec and os.path.exists(p_name):
1277 1280 return p_name
1278 1281
1279 1282 for ext in pathext:
1280 1283 p_name_ext = p_name + ext
1281 1284 if os.path.exists(p_name_ext):
1282 1285 return p_name_ext
1283 1286 return default
1284 1287
1285 1288 def set_signal_handler():
1286 1289 try:
1287 1290 set_signal_handler_win32()
1288 1291 except NameError:
1289 1292 pass
1290 1293
1291 1294 try:
1292 1295 # override functions with win32 versions if possible
1293 1296 from util_win32 import *
1294 1297 if not _is_win_9x():
1295 1298 posixfile = posixfile_nt
1296 1299 except ImportError:
1297 1300 pass
1298 1301
1299 1302 else:
1300 1303 nulldev = '/dev/null'
1301 1304
1302 1305 def rcfiles(path):
1303 1306 rcs = [os.path.join(path, 'hgrc')]
1304 1307 rcdir = os.path.join(path, 'hgrc.d')
1305 1308 try:
1306 1309 rcs.extend([os.path.join(rcdir, f)
1307 1310 for f, kind in osutil.listdir(rcdir)
1308 1311 if f.endswith(".rc")])
1309 1312 except OSError:
1310 1313 pass
1311 1314 return rcs
1312 1315
1313 1316 def system_rcpath():
1314 1317 path = []
1315 1318 # old mod_python does not set sys.argv
1316 1319 if len(getattr(sys, 'argv', [])) > 0:
1317 1320 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1318 1321 '/../etc/mercurial'))
1319 1322 path.extend(rcfiles('/etc/mercurial'))
1320 1323 return path
1321 1324
1322 1325 def user_rcpath():
1323 1326 return [os.path.expanduser('~/.hgrc')]
1324 1327
1325 1328 def parse_patch_output(output_line):
1326 1329 """parses the output produced by patch and returns the file name"""
1327 1330 pf = output_line[14:]
1328 1331 if os.sys.platform == 'OpenVMS':
1329 1332 if pf[0] == '`':
1330 1333 pf = pf[1:-1] # Remove the quotes
1331 1334 else:
1332 1335 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1333 1336 pf = pf[1:-1] # Remove the quotes
1334 1337 return pf
1335 1338
1336 1339 def sshargs(sshcmd, host, user, port):
1337 1340 '''Build argument list for ssh'''
1338 1341 args = user and ("%s@%s" % (user, host)) or host
1339 1342 return port and ("%s -p %s" % (args, port)) or args
1340 1343
1341 1344 def is_exec(f):
1342 1345 """check whether a file is executable"""
1343 1346 return (os.lstat(f).st_mode & 0100 != 0)
1344 1347
1345 1348 def set_flags(f, l, x):
1346 1349 s = os.lstat(f).st_mode
1347 1350 if l:
1348 1351 if not stat.S_ISLNK(s):
1349 1352 # switch file to link
1350 1353 data = file(f).read()
1351 1354 os.unlink(f)
1352 1355 try:
1353 1356 os.symlink(data, f)
1354 1357 except:
1355 1358 # failed to make a link, rewrite file
1356 1359 file(f, "w").write(data)
1357 1360 # no chmod needed at this point
1358 1361 return
1359 1362 if stat.S_ISLNK(s):
1360 1363 # switch link to file
1361 1364 data = os.readlink(f)
1362 1365 os.unlink(f)
1363 1366 file(f, "w").write(data)
1364 1367 s = 0666 & ~_umask # avoid restatting for chmod
1365 1368
1366 1369 sx = s & 0100
1367 1370 if x and not sx:
1368 1371 # Turn on +x for every +r bit when making a file executable
1369 1372 # and obey umask.
1370 1373 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1371 1374 elif not x and sx:
1372 1375 # Turn off all +x bits
1373 1376 os.chmod(f, s & 0666)
1374 1377
1375 1378 def set_binary(fd):
1376 1379 pass
1377 1380
1378 1381 def pconvert(path):
1379 1382 return path
1380 1383
1381 1384 def localpath(path):
1382 1385 return path
1383 1386
1384 1387 normpath = os.path.normpath
1385 1388 samestat = os.path.samestat
1386 1389
1387 1390 def makelock(info, pathname):
1388 1391 try:
1389 1392 os.symlink(info, pathname)
1390 1393 except OSError, why:
1391 1394 if why.errno == errno.EEXIST:
1392 1395 raise
1393 1396 else:
1394 1397 _makelock_file(info, pathname)
1395 1398
1396 1399 def readlock(pathname):
1397 1400 try:
1398 1401 return os.readlink(pathname)
1399 1402 except OSError, why:
1400 1403 if why.errno in (errno.EINVAL, errno.ENOSYS):
1401 1404 return _readlock_file(pathname)
1402 1405 else:
1403 1406 raise
1404 1407
1405 1408 def shellquote(s):
1406 1409 if os.sys.platform == 'OpenVMS':
1407 1410 return '"%s"' % s
1408 1411 else:
1409 1412 return "'%s'" % s.replace("'", "'\\''")
1410 1413
1411 1414 def quotecommand(cmd):
1412 1415 return cmd
1413 1416
1414 1417 def popen(command, mode='r'):
1415 1418 return os.popen(command, mode)
1416 1419
1417 1420 def testpid(pid):
1418 1421 '''return False if pid dead, True if running or not sure'''
1419 1422 if os.sys.platform == 'OpenVMS':
1420 1423 return True
1421 1424 try:
1422 1425 os.kill(pid, 0)
1423 1426 return True
1424 1427 except OSError, inst:
1425 1428 return inst.errno != errno.ESRCH
1426 1429
1427 1430 def explain_exit(code):
1428 1431 """return a 2-tuple (desc, code) describing a process's status"""
1429 1432 if os.WIFEXITED(code):
1430 1433 val = os.WEXITSTATUS(code)
1431 1434 return _("exited with status %d") % val, val
1432 1435 elif os.WIFSIGNALED(code):
1433 1436 val = os.WTERMSIG(code)
1434 1437 return _("killed by signal %d") % val, val
1435 1438 elif os.WIFSTOPPED(code):
1436 1439 val = os.WSTOPSIG(code)
1437 1440 return _("stopped by signal %d") % val, val
1438 1441 raise ValueError(_("invalid exit code"))
1439 1442
1440 1443 def isowner(fp, st=None):
1441 1444 """Return True if the file object f belongs to the current user.
1442 1445
1443 1446 The return value of a util.fstat(f) may be passed as the st argument.
1444 1447 """
1445 1448 if st is None:
1446 1449 st = fstat(fp)
1447 1450 return st.st_uid == os.getuid()
1448 1451
1449 1452 def find_in_path(name, path, default=None):
1450 1453 '''find name in search path. path can be string (will be split
1451 1454 with os.pathsep), or iterable thing that returns strings. if name
1452 1455 found, return path to name. else return default.'''
1453 1456 if isinstance(path, str):
1454 1457 path = path.split(os.pathsep)
1455 1458 for p in path:
1456 1459 p_name = os.path.join(p, name)
1457 1460 if os.path.exists(p_name):
1458 1461 return p_name
1459 1462 return default
1460 1463
1461 1464 def set_signal_handler():
1462 1465 pass
1463 1466
1464 1467 def find_exe(name, default=None):
1465 1468 '''find path of an executable.
1466 1469 if name contains a path component, return it as is. otherwise,
1467 1470 use normal executable search path.'''
1468 1471
1469 1472 if os.sep in name or sys.platform == 'OpenVMS':
1470 1473 # don't check the executable bit. if the file isn't
1471 1474 # executable, whoever tries to actually run it will give a
1472 1475 # much more useful error message.
1473 1476 return name
1474 1477 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1475 1478
1476 1479 def mktempcopy(name, emptyok=False, createmode=None):
1477 1480 """Create a temporary file with the same contents from name
1478 1481
1479 1482 The permission bits are copied from the original file.
1480 1483
1481 1484 If the temporary file is going to be truncated immediately, you
1482 1485 can use emptyok=True as an optimization.
1483 1486
1484 1487 Returns the name of the temporary file.
1485 1488 """
1486 1489 d, fn = os.path.split(name)
1487 1490 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1488 1491 os.close(fd)
1489 1492 # Temporary files are created with mode 0600, which is usually not
1490 1493 # what we want. If the original file already exists, just copy
1491 1494 # its mode. Otherwise, manually obey umask.
1492 1495 try:
1493 1496 st_mode = os.lstat(name).st_mode & 0777
1494 1497 except OSError, inst:
1495 1498 if inst.errno != errno.ENOENT:
1496 1499 raise
1497 1500 st_mode = createmode
1498 1501 if st_mode is None:
1499 1502 st_mode = ~_umask
1500 1503 st_mode &= 0666
1501 1504 os.chmod(temp, st_mode)
1502 1505 if emptyok:
1503 1506 return temp
1504 1507 try:
1505 1508 try:
1506 1509 ifp = posixfile(name, "rb")
1507 1510 except IOError, inst:
1508 1511 if inst.errno == errno.ENOENT:
1509 1512 return temp
1510 1513 if not getattr(inst, 'filename', None):
1511 1514 inst.filename = name
1512 1515 raise
1513 1516 ofp = posixfile(temp, "wb")
1514 1517 for chunk in filechunkiter(ifp):
1515 1518 ofp.write(chunk)
1516 1519 ifp.close()
1517 1520 ofp.close()
1518 1521 except:
1519 1522 try: os.unlink(temp)
1520 1523 except: pass
1521 1524 raise
1522 1525 return temp
1523 1526
1524 1527 class atomictempfile(posixfile):
1525 1528 """file-like object that atomically updates a file
1526 1529
1527 1530 All writes will be redirected to a temporary copy of the original
1528 1531 file. When rename is called, the copy is renamed to the original
1529 1532 name, making the changes visible.
1530 1533 """
1531 1534 def __init__(self, name, mode, createmode):
1532 1535 self.__name = name
1533 1536 self.temp = mktempcopy(name, emptyok=('w' in mode),
1534 1537 createmode=createmode)
1535 1538 posixfile.__init__(self, self.temp, mode)
1536 1539
1537 1540 def rename(self):
1538 1541 if not self.closed:
1539 1542 posixfile.close(self)
1540 1543 rename(self.temp, localpath(self.__name))
1541 1544
1542 1545 def __del__(self):
1543 1546 if not self.closed:
1544 1547 try:
1545 1548 os.unlink(self.temp)
1546 1549 except: pass
1547 1550 posixfile.close(self)
1548 1551
1549 1552 def makedirs(name, mode=None):
1550 1553 """recursive directory creation with parent mode inheritance"""
1551 1554 try:
1552 1555 os.mkdir(name)
1553 1556 if mode is not None:
1554 1557 os.chmod(name, mode)
1555 1558 return
1556 1559 except OSError, err:
1557 1560 if err.errno == errno.EEXIST:
1558 1561 return
1559 1562 if err.errno != errno.ENOENT:
1560 1563 raise
1561 1564 parent = os.path.abspath(os.path.dirname(name))
1562 1565 makedirs(parent, mode)
1563 1566 makedirs(name, mode)
1564 1567
1565 1568 class opener(object):
1566 1569 """Open files relative to a base directory
1567 1570
1568 1571 This class is used to hide the details of COW semantics and
1569 1572 remote file access from higher level code.
1570 1573 """
1571 1574 def __init__(self, base, audit=True):
1572 1575 self.base = base
1573 1576 if audit:
1574 1577 self.audit_path = path_auditor(base)
1575 1578 else:
1576 1579 self.audit_path = always
1577 1580 self.createmode = None
1578 1581
1579 1582 def __getattr__(self, name):
1580 1583 if name == '_can_symlink':
1581 1584 self._can_symlink = checklink(self.base)
1582 1585 return self._can_symlink
1583 1586 raise AttributeError(name)
1584 1587
1585 1588 def _fixfilemode(self, name):
1586 1589 if self.createmode is None:
1587 1590 return
1588 1591 os.chmod(name, self.createmode & 0666)
1589 1592
1590 1593 def __call__(self, path, mode="r", text=False, atomictemp=False):
1591 1594 self.audit_path(path)
1592 1595 f = os.path.join(self.base, path)
1593 1596
1594 1597 if not text and "b" not in mode:
1595 1598 mode += "b" # for that other OS
1596 1599
1597 1600 nlink = -1
1598 1601 if mode not in ("r", "rb"):
1599 1602 try:
1600 1603 nlink = nlinks(f)
1601 1604 except OSError:
1602 1605 nlink = 0
1603 1606 d = os.path.dirname(f)
1604 1607 if not os.path.isdir(d):
1605 1608 makedirs(d, self.createmode)
1606 1609 if atomictemp:
1607 1610 return atomictempfile(f, mode, self.createmode)
1608 1611 if nlink > 1:
1609 1612 rename(mktempcopy(f), f)
1610 1613 fp = posixfile(f, mode)
1611 1614 if nlink == 0:
1612 1615 self._fixfilemode(f)
1613 1616 return fp
1614 1617
1615 1618 def symlink(self, src, dst):
1616 1619 self.audit_path(dst)
1617 1620 linkname = os.path.join(self.base, dst)
1618 1621 try:
1619 1622 os.unlink(linkname)
1620 1623 except OSError:
1621 1624 pass
1622 1625
1623 1626 dirname = os.path.dirname(linkname)
1624 1627 if not os.path.exists(dirname):
1625 1628 makedirs(dirname, self.createmode)
1626 1629
1627 1630 if self._can_symlink:
1628 1631 try:
1629 1632 os.symlink(src, linkname)
1630 1633 except OSError, err:
1631 1634 raise OSError(err.errno, _('could not symlink to %r: %s') %
1632 1635 (src, err.strerror), linkname)
1633 1636 else:
1634 1637 f = self(dst, "w")
1635 1638 f.write(src)
1636 1639 f.close()
1637 1640 self._fixfilemode(dst)
1638 1641
1639 1642 class chunkbuffer(object):
1640 1643 """Allow arbitrary sized chunks of data to be efficiently read from an
1641 1644 iterator over chunks of arbitrary size."""
1642 1645
1643 1646 def __init__(self, in_iter):
1644 1647 """in_iter is the iterator that's iterating over the input chunks.
1645 1648 targetsize is how big a buffer to try to maintain."""
1646 1649 self.iter = iter(in_iter)
1647 1650 self.buf = ''
1648 1651 self.targetsize = 2**16
1649 1652
1650 1653 def read(self, l):
1651 1654 """Read L bytes of data from the iterator of chunks of data.
1652 1655 Returns less than L bytes if the iterator runs dry."""
1653 1656 if l > len(self.buf) and self.iter:
1654 1657 # Clamp to a multiple of self.targetsize
1655 1658 targetsize = max(l, self.targetsize)
1656 1659 collector = cStringIO.StringIO()
1657 1660 collector.write(self.buf)
1658 1661 collected = len(self.buf)
1659 1662 for chunk in self.iter:
1660 1663 collector.write(chunk)
1661 1664 collected += len(chunk)
1662 1665 if collected >= targetsize:
1663 1666 break
1664 1667 if collected < targetsize:
1665 1668 self.iter = False
1666 1669 self.buf = collector.getvalue()
1667 1670 if len(self.buf) == l:
1668 1671 s, self.buf = str(self.buf), ''
1669 1672 else:
1670 1673 s, self.buf = self.buf[:l], buffer(self.buf, l)
1671 1674 return s
1672 1675
1673 1676 def filechunkiter(f, size=65536, limit=None):
1674 1677 """Create a generator that produces the data in the file size
1675 1678 (default 65536) bytes at a time, up to optional limit (default is
1676 1679 to read all data). Chunks may be less than size bytes if the
1677 1680 chunk is the last chunk in the file, or the file is a socket or
1678 1681 some other type of file that sometimes reads less data than is
1679 1682 requested."""
1680 1683 assert size >= 0
1681 1684 assert limit is None or limit >= 0
1682 1685 while True:
1683 1686 if limit is None: nbytes = size
1684 1687 else: nbytes = min(limit, size)
1685 1688 s = nbytes and f.read(nbytes)
1686 1689 if not s: break
1687 1690 if limit: limit -= len(s)
1688 1691 yield s
1689 1692
1690 1693 def makedate():
1691 1694 lt = time.localtime()
1692 1695 if lt[8] == 1 and time.daylight:
1693 1696 tz = time.altzone
1694 1697 else:
1695 1698 tz = time.timezone
1696 1699 return time.mktime(lt), tz
1697 1700
1698 1701 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1699 1702 """represent a (unixtime, offset) tuple as a localized time.
1700 1703 unixtime is seconds since the epoch, and offset is the time zone's
1701 1704 number of seconds away from UTC. if timezone is false, do not
1702 1705 append time zone to string."""
1703 1706 t, tz = date or makedate()
1704 1707 if "%1" in format or "%2" in format:
1705 1708 sign = (tz > 0) and "-" or "+"
1706 1709 minutes = abs(tz) / 60
1707 1710 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1708 1711 format = format.replace("%2", "%02d" % (minutes % 60))
1709 1712 s = time.strftime(format, time.gmtime(float(t) - tz))
1710 1713 return s
1711 1714
1712 1715 def shortdate(date=None):
1713 1716 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1714 1717 return datestr(date, format='%Y-%m-%d')
1715 1718
1716 1719 def strdate(string, format, defaults=[]):
1717 1720 """parse a localized time string and return a (unixtime, offset) tuple.
1718 1721 if the string cannot be parsed, ValueError is raised."""
1719 1722 def timezone(string):
1720 1723 tz = string.split()[-1]
1721 1724 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1722 1725 sign = (tz[0] == "+") and 1 or -1
1723 1726 hours = int(tz[1:3])
1724 1727 minutes = int(tz[3:5])
1725 1728 return -sign * (hours * 60 + minutes) * 60
1726 1729 if tz == "GMT" or tz == "UTC":
1727 1730 return 0
1728 1731 return None
1729 1732
1730 1733 # NOTE: unixtime = localunixtime + offset
1731 1734 offset, date = timezone(string), string
1732 1735 if offset != None:
1733 1736 date = " ".join(string.split()[:-1])
1734 1737
1735 1738 # add missing elements from defaults
1736 1739 for part in defaults:
1737 1740 found = [True for p in part if ("%"+p) in format]
1738 1741 if not found:
1739 1742 date += "@" + defaults[part]
1740 1743 format += "@%" + part[0]
1741 1744
1742 1745 timetuple = time.strptime(date, format)
1743 1746 localunixtime = int(calendar.timegm(timetuple))
1744 1747 if offset is None:
1745 1748 # local timezone
1746 1749 unixtime = int(time.mktime(timetuple))
1747 1750 offset = unixtime - localunixtime
1748 1751 else:
1749 1752 unixtime = localunixtime + offset
1750 1753 return unixtime, offset
1751 1754
1752 1755 def parsedate(date, formats=None, defaults=None):
1753 1756 """parse a localized date/time string and return a (unixtime, offset) tuple.
1754 1757
1755 1758 The date may be a "unixtime offset" string or in one of the specified
1756 1759 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1757 1760 """
1758 1761 if not date:
1759 1762 return 0, 0
1760 1763 if isinstance(date, tuple) and len(date) == 2:
1761 1764 return date
1762 1765 if not formats:
1763 1766 formats = defaultdateformats
1764 1767 date = date.strip()
1765 1768 try:
1766 1769 when, offset = map(int, date.split(' '))
1767 1770 except ValueError:
1768 1771 # fill out defaults
1769 1772 if not defaults:
1770 1773 defaults = {}
1771 1774 now = makedate()
1772 1775 for part in "d mb yY HI M S".split():
1773 1776 if part not in defaults:
1774 1777 if part[0] in "HMS":
1775 1778 defaults[part] = "00"
1776 1779 else:
1777 1780 defaults[part] = datestr(now, "%" + part[0])
1778 1781
1779 1782 for format in formats:
1780 1783 try:
1781 1784 when, offset = strdate(date, format, defaults)
1782 1785 except (ValueError, OverflowError):
1783 1786 pass
1784 1787 else:
1785 1788 break
1786 1789 else:
1787 1790 raise Abort(_('invalid date: %r ') % date)
1788 1791 # validate explicit (probably user-specified) date and
1789 1792 # time zone offset. values must fit in signed 32 bits for
1790 1793 # current 32-bit linux runtimes. timezones go from UTC-12
1791 1794 # to UTC+14
1792 1795 if abs(when) > 0x7fffffff:
1793 1796 raise Abort(_('date exceeds 32 bits: %d') % when)
1794 1797 if offset < -50400 or offset > 43200:
1795 1798 raise Abort(_('impossible time zone offset: %d') % offset)
1796 1799 return when, offset
1797 1800
1798 1801 def matchdate(date):
1799 1802 """Return a function that matches a given date match specifier
1800 1803
1801 1804 Formats include:
1802 1805
1803 1806 '{date}' match a given date to the accuracy provided
1804 1807
1805 1808 '<{date}' on or before a given date
1806 1809
1807 1810 '>{date}' on or after a given date
1808 1811
1809 1812 """
1810 1813
1811 1814 def lower(date):
1812 1815 d = dict(mb="1", d="1")
1813 1816 return parsedate(date, extendeddateformats, d)[0]
1814 1817
1815 1818 def upper(date):
1816 1819 d = dict(mb="12", HI="23", M="59", S="59")
1817 1820 for days in "31 30 29".split():
1818 1821 try:
1819 1822 d["d"] = days
1820 1823 return parsedate(date, extendeddateformats, d)[0]
1821 1824 except:
1822 1825 pass
1823 1826 d["d"] = "28"
1824 1827 return parsedate(date, extendeddateformats, d)[0]
1825 1828
1826 1829 if date[0] == "<":
1827 1830 when = upper(date[1:])
1828 1831 return lambda x: x <= when
1829 1832 elif date[0] == ">":
1830 1833 when = lower(date[1:])
1831 1834 return lambda x: x >= when
1832 1835 elif date[0] == "-":
1833 1836 try:
1834 1837 days = int(date[1:])
1835 1838 except ValueError:
1836 1839 raise Abort(_("invalid day spec: %s") % date[1:])
1837 1840 when = makedate()[0] - days * 3600 * 24
1838 1841 return lambda x: x >= when
1839 1842 elif " to " in date:
1840 1843 a, b = date.split(" to ")
1841 1844 start, stop = lower(a), upper(b)
1842 1845 return lambda x: x >= start and x <= stop
1843 1846 else:
1844 1847 start, stop = lower(date), upper(date)
1845 1848 return lambda x: x >= start and x <= stop
1846 1849
1847 1850 def shortuser(user):
1848 1851 """Return a short representation of a user name or email address."""
1849 1852 f = user.find('@')
1850 1853 if f >= 0:
1851 1854 user = user[:f]
1852 1855 f = user.find('<')
1853 1856 if f >= 0:
1854 1857 user = user[f+1:]
1855 1858 f = user.find(' ')
1856 1859 if f >= 0:
1857 1860 user = user[:f]
1858 1861 f = user.find('.')
1859 1862 if f >= 0:
1860 1863 user = user[:f]
1861 1864 return user
1862 1865
1863 1866 def email(author):
1864 1867 '''get email of author.'''
1865 1868 r = author.find('>')
1866 1869 if r == -1: r = None
1867 1870 return author[author.find('<')+1:r]
1868 1871
1869 1872 def ellipsis(text, maxlength=400):
1870 1873 """Trim string to at most maxlength (default: 400) characters."""
1871 1874 if len(text) <= maxlength:
1872 1875 return text
1873 1876 else:
1874 1877 return "%s..." % (text[:maxlength-3])
1875 1878
1876 1879 def walkrepos(path, followsym=False, seen_dirs=None):
1877 1880 '''yield every hg repository under path, recursively.'''
1878 1881 def errhandler(err):
1879 1882 if err.filename == path:
1880 1883 raise err
1881 1884 if followsym and hasattr(os.path, 'samestat'):
1882 1885 def _add_dir_if_not_there(dirlst, dirname):
1883 1886 match = False
1884 1887 samestat = os.path.samestat
1885 1888 dirstat = os.stat(dirname)
1886 1889 for lstdirstat in dirlst:
1887 1890 if samestat(dirstat, lstdirstat):
1888 1891 match = True
1889 1892 break
1890 1893 if not match:
1891 1894 dirlst.append(dirstat)
1892 1895 return not match
1893 1896 else:
1894 1897 followsym = False
1895 1898
1896 1899 if (seen_dirs is None) and followsym:
1897 1900 seen_dirs = []
1898 1901 _add_dir_if_not_there(seen_dirs, path)
1899 1902 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1900 1903 if '.hg' in dirs:
1901 1904 dirs.remove('.hg') # don't recurse inside the .hg directory
1902 1905 yield root # found a repository
1903 1906 qroot = os.path.join(root, '.hg', 'patches')
1904 1907 if os.path.isdir(os.path.join(qroot, '.hg')):
1905 1908 yield qroot # we have a patch queue repo here
1906 1909 elif followsym:
1907 1910 newdirs = []
1908 1911 for d in dirs:
1909 1912 fname = os.path.join(root, d)
1910 1913 if _add_dir_if_not_there(seen_dirs, fname):
1911 1914 if os.path.islink(fname):
1912 1915 for hgname in walkrepos(fname, True, seen_dirs):
1913 1916 yield hgname
1914 1917 else:
1915 1918 newdirs.append(d)
1916 1919 dirs[:] = newdirs
1917 1920
1918 1921 _rcpath = None
1919 1922
1920 1923 def os_rcpath():
1921 1924 '''return default os-specific hgrc search path'''
1922 1925 path = system_rcpath()
1923 1926 path.extend(user_rcpath())
1924 1927 path = [os.path.normpath(f) for f in path]
1925 1928 return path
1926 1929
1927 1930 def rcpath():
1928 1931 '''return hgrc search path. if env var HGRCPATH is set, use it.
1929 1932 for each item in path, if directory, use files ending in .rc,
1930 1933 else use item.
1931 1934 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1932 1935 if no HGRCPATH, use default os-specific path.'''
1933 1936 global _rcpath
1934 1937 if _rcpath is None:
1935 1938 if 'HGRCPATH' in os.environ:
1936 1939 _rcpath = []
1937 1940 for p in os.environ['HGRCPATH'].split(os.pathsep):
1938 1941 if not p: continue
1939 1942 if os.path.isdir(p):
1940 1943 for f, kind in osutil.listdir(p):
1941 1944 if f.endswith('.rc'):
1942 1945 _rcpath.append(os.path.join(p, f))
1943 1946 else:
1944 1947 _rcpath.append(p)
1945 1948 else:
1946 1949 _rcpath = os_rcpath()
1947 1950 return _rcpath
1948 1951
1949 1952 def bytecount(nbytes):
1950 1953 '''return byte count formatted as readable string, with units'''
1951 1954
1952 1955 units = (
1953 1956 (100, 1<<30, _('%.0f GB')),
1954 1957 (10, 1<<30, _('%.1f GB')),
1955 1958 (1, 1<<30, _('%.2f GB')),
1956 1959 (100, 1<<20, _('%.0f MB')),
1957 1960 (10, 1<<20, _('%.1f MB')),
1958 1961 (1, 1<<20, _('%.2f MB')),
1959 1962 (100, 1<<10, _('%.0f KB')),
1960 1963 (10, 1<<10, _('%.1f KB')),
1961 1964 (1, 1<<10, _('%.2f KB')),
1962 1965 (1, 1, _('%.0f bytes')),
1963 1966 )
1964 1967
1965 1968 for multiplier, divisor, format in units:
1966 1969 if nbytes >= divisor * multiplier:
1967 1970 return format % (nbytes / float(divisor))
1968 1971 return units[-1][2] % nbytes
1969 1972
1970 1973 def drop_scheme(scheme, path):
1971 1974 sc = scheme + ':'
1972 1975 if path.startswith(sc):
1973 1976 path = path[len(sc):]
1974 1977 if path.startswith('//'):
1975 1978 path = path[2:]
1976 1979 return path
1977 1980
1978 1981 def uirepr(s):
1979 1982 # Avoid double backslash in Windows path repr()
1980 1983 return repr(s).replace('\\\\', '\\')
General Comments 0
You need to be logged in to leave comments. Login now