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