##// END OF EJS Templates
Optimize return value of util._matcher for common command line case...
Alexis S. L. Carvalho -
r4198:9e312101 default
parent child Browse files
Show More
@@ -1,1375 +1,1379 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, 2006 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 gettext as _
16 16 from demandload import *
17 17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 18 demandload(globals(), "os threading time calendar ConfigParser locale glob")
19 19
20 20 try:
21 21 _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \
22 22 or "ascii"
23 23 except locale.Error:
24 24 _encoding = 'ascii'
25 25 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
26 26 _fallbackencoding = 'ISO-8859-1'
27 27
28 28 def tolocal(s):
29 29 """
30 30 Convert a string from internal UTF-8 to local encoding
31 31
32 32 All internal strings should be UTF-8 but some repos before the
33 33 implementation of locale support may contain latin1 or possibly
34 34 other character sets. We attempt to decode everything strictly
35 35 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
36 36 replace unknown characters.
37 37 """
38 38 for e in ('UTF-8', _fallbackencoding):
39 39 try:
40 40 u = s.decode(e) # attempt strict decoding
41 41 return u.encode(_encoding, "replace")
42 42 except LookupError, k:
43 43 raise Abort(_("%s, please check your locale settings") % k)
44 44 except UnicodeDecodeError:
45 45 pass
46 46 u = s.decode("utf-8", "replace") # last ditch
47 47 return u.encode(_encoding, "replace")
48 48
49 49 def fromlocal(s):
50 50 """
51 51 Convert a string from the local character encoding to UTF-8
52 52
53 53 We attempt to decode strings using the encoding mode set by
54 54 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
55 55 characters will cause an error message. Other modes include
56 56 'replace', which replaces unknown characters with a special
57 57 Unicode character, and 'ignore', which drops the character.
58 58 """
59 59 try:
60 60 return s.decode(_encoding, _encodingmode).encode("utf-8")
61 61 except UnicodeDecodeError, inst:
62 62 sub = s[max(0, inst.start-10):inst.start+10]
63 63 raise Abort("decoding near '%s': %s!" % (sub, inst))
64 64 except LookupError, k:
65 65 raise Abort(_("%s, please check your locale settings") % k)
66 66
67 67 def locallen(s):
68 68 """Find the length in characters of a local string"""
69 69 return len(s.decode(_encoding, "replace"))
70 70
71 71 def localsub(s, a, b=None):
72 72 try:
73 73 u = s.decode(_encoding, _encodingmode)
74 74 if b is not None:
75 75 u = u[a:b]
76 76 else:
77 77 u = u[:a]
78 78 return u.encode(_encoding, _encodingmode)
79 79 except UnicodeDecodeError, inst:
80 80 sub = s[max(0, inst.start-10), inst.start+10]
81 81 raise Abort(_("decoding near '%s': %s!\n") % (sub, inst))
82 82
83 83 # used by parsedate
84 84 defaultdateformats = (
85 85 '%Y-%m-%d %H:%M:%S',
86 86 '%Y-%m-%d %I:%M:%S%p',
87 87 '%Y-%m-%d %H:%M',
88 88 '%Y-%m-%d %I:%M%p',
89 89 '%Y-%m-%d',
90 90 '%m-%d',
91 91 '%m/%d',
92 92 '%m/%d/%y',
93 93 '%m/%d/%Y',
94 94 '%a %b %d %H:%M:%S %Y',
95 95 '%a %b %d %I:%M:%S%p %Y',
96 96 '%b %d %H:%M:%S %Y',
97 97 '%b %d %I:%M:%S%p %Y',
98 98 '%b %d %H:%M:%S',
99 99 '%b %d %I:%M:%S%p',
100 100 '%b %d %H:%M',
101 101 '%b %d %I:%M%p',
102 102 '%b %d %Y',
103 103 '%b %d',
104 104 '%H:%M:%S',
105 105 '%I:%M:%SP',
106 106 '%H:%M',
107 107 '%I:%M%p',
108 108 )
109 109
110 110 extendeddateformats = defaultdateformats + (
111 111 "%Y",
112 112 "%Y-%m",
113 113 "%b",
114 114 "%b %Y",
115 115 )
116 116
117 117 class SignalInterrupt(Exception):
118 118 """Exception raised on SIGTERM and SIGHUP."""
119 119
120 120 # like SafeConfigParser but with case-sensitive keys
121 121 class configparser(ConfigParser.SafeConfigParser):
122 122 def optionxform(self, optionstr):
123 123 return optionstr
124 124
125 125 def cachefunc(func):
126 126 '''cache the result of function calls'''
127 127 # XXX doesn't handle keywords args
128 128 cache = {}
129 129 if func.func_code.co_argcount == 1:
130 130 # we gain a small amount of time because
131 131 # we don't need to pack/unpack the list
132 132 def f(arg):
133 133 if arg not in cache:
134 134 cache[arg] = func(arg)
135 135 return cache[arg]
136 136 else:
137 137 def f(*args):
138 138 if args not in cache:
139 139 cache[args] = func(*args)
140 140 return cache[args]
141 141
142 142 return f
143 143
144 144 def pipefilter(s, cmd):
145 145 '''filter string S through command CMD, returning its output'''
146 146 (pout, pin) = popen2.popen2(cmd, -1, 'b')
147 147 def writer():
148 148 try:
149 149 pin.write(s)
150 150 pin.close()
151 151 except IOError, inst:
152 152 if inst.errno != errno.EPIPE:
153 153 raise
154 154
155 155 # we should use select instead on UNIX, but this will work on most
156 156 # systems, including Windows
157 157 w = threading.Thread(target=writer)
158 158 w.start()
159 159 f = pout.read()
160 160 pout.close()
161 161 w.join()
162 162 return f
163 163
164 164 def tempfilter(s, cmd):
165 165 '''filter string S through a pair of temporary files with CMD.
166 166 CMD is used as a template to create the real command to be run,
167 167 with the strings INFILE and OUTFILE replaced by the real names of
168 168 the temporary files generated.'''
169 169 inname, outname = None, None
170 170 try:
171 171 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
172 172 fp = os.fdopen(infd, 'wb')
173 173 fp.write(s)
174 174 fp.close()
175 175 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
176 176 os.close(outfd)
177 177 cmd = cmd.replace('INFILE', inname)
178 178 cmd = cmd.replace('OUTFILE', outname)
179 179 code = os.system(cmd)
180 180 if code: raise Abort(_("command '%s' failed: %s") %
181 181 (cmd, explain_exit(code)))
182 182 return open(outname, 'rb').read()
183 183 finally:
184 184 try:
185 185 if inname: os.unlink(inname)
186 186 except: pass
187 187 try:
188 188 if outname: os.unlink(outname)
189 189 except: pass
190 190
191 191 filtertable = {
192 192 'tempfile:': tempfilter,
193 193 'pipe:': pipefilter,
194 194 }
195 195
196 196 def filter(s, cmd):
197 197 "filter a string through a command that transforms its input to its output"
198 198 for name, fn in filtertable.iteritems():
199 199 if cmd.startswith(name):
200 200 return fn(s, cmd[len(name):].lstrip())
201 201 return pipefilter(s, cmd)
202 202
203 203 def find_in_path(name, path, default=None):
204 204 '''find name in search path. path can be string (will be split
205 205 with os.pathsep), or iterable thing that returns strings. if name
206 206 found, return path to name. else return default.'''
207 207 if isinstance(path, str):
208 208 path = path.split(os.pathsep)
209 209 for p in path:
210 210 p_name = os.path.join(p, name)
211 211 if os.path.exists(p_name):
212 212 return p_name
213 213 return default
214 214
215 215 def binary(s):
216 216 """return true if a string is binary data using diff's heuristic"""
217 217 if s and '\0' in s[:4096]:
218 218 return True
219 219 return False
220 220
221 221 def unique(g):
222 222 """return the uniq elements of iterable g"""
223 223 seen = {}
224 224 l = []
225 225 for f in g:
226 226 if f not in seen:
227 227 seen[f] = 1
228 228 l.append(f)
229 229 return l
230 230
231 231 class Abort(Exception):
232 232 """Raised if a command needs to print an error and exit."""
233 233
234 234 class UnexpectedOutput(Abort):
235 235 """Raised to print an error with part of output and exit."""
236 236
237 237 def always(fn): return True
238 238 def never(fn): return False
239 239
240 240 def expand_glob(pats):
241 241 '''On Windows, expand the implicit globs in a list of patterns'''
242 242 if os.name != 'nt':
243 243 return list(pats)
244 244 ret = []
245 245 for p in pats:
246 246 kind, name = patkind(p, None)
247 247 if kind is None:
248 248 globbed = glob.glob(name)
249 249 if globbed:
250 250 ret.extend(globbed)
251 251 continue
252 252 # if we couldn't expand the glob, just keep it around
253 253 ret.append(p)
254 254 return ret
255 255
256 256 def patkind(name, dflt_pat='glob'):
257 257 """Split a string into an optional pattern kind prefix and the
258 258 actual pattern."""
259 259 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
260 260 if name.startswith(prefix + ':'): return name.split(':', 1)
261 261 return dflt_pat, name
262 262
263 263 def globre(pat, head='^', tail='$'):
264 264 "convert a glob pattern into a regexp"
265 265 i, n = 0, len(pat)
266 266 res = ''
267 267 group = False
268 268 def peek(): return i < n and pat[i]
269 269 while i < n:
270 270 c = pat[i]
271 271 i = i+1
272 272 if c == '*':
273 273 if peek() == '*':
274 274 i += 1
275 275 res += '.*'
276 276 else:
277 277 res += '[^/]*'
278 278 elif c == '?':
279 279 res += '.'
280 280 elif c == '[':
281 281 j = i
282 282 if j < n and pat[j] in '!]':
283 283 j += 1
284 284 while j < n and pat[j] != ']':
285 285 j += 1
286 286 if j >= n:
287 287 res += '\\['
288 288 else:
289 289 stuff = pat[i:j].replace('\\','\\\\')
290 290 i = j + 1
291 291 if stuff[0] == '!':
292 292 stuff = '^' + stuff[1:]
293 293 elif stuff[0] == '^':
294 294 stuff = '\\' + stuff
295 295 res = '%s[%s]' % (res, stuff)
296 296 elif c == '{':
297 297 group = True
298 298 res += '(?:'
299 299 elif c == '}' and group:
300 300 res += ')'
301 301 group = False
302 302 elif c == ',' and group:
303 303 res += '|'
304 304 elif c == '\\':
305 305 p = peek()
306 306 if p:
307 307 i += 1
308 308 res += re.escape(p)
309 309 else:
310 310 res += re.escape(c)
311 311 else:
312 312 res += re.escape(c)
313 313 return head + res + tail
314 314
315 315 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
316 316
317 317 def pathto(n1, n2):
318 318 '''return the relative path from one place to another.
319 319 n1 should use os.sep to separate directories
320 320 n2 should use "/" to separate directories
321 321 returns an os.sep-separated path.
322 322 '''
323 323 if not n1: return localpath(n2)
324 324 a, b = n1.split(os.sep), n2.split('/')
325 325 a.reverse()
326 326 b.reverse()
327 327 while a and b and a[-1] == b[-1]:
328 328 a.pop()
329 329 b.pop()
330 330 b.reverse()
331 331 return os.sep.join((['..'] * len(a)) + b)
332 332
333 333 def canonpath(root, cwd, myname):
334 334 """return the canonical path of myname, given cwd and root"""
335 335 if root == os.sep:
336 336 rootsep = os.sep
337 337 elif root.endswith(os.sep):
338 338 rootsep = root
339 339 else:
340 340 rootsep = root + os.sep
341 341 name = myname
342 342 if not os.path.isabs(name):
343 343 name = os.path.join(root, cwd, name)
344 344 name = os.path.normpath(name)
345 345 if name != rootsep and name.startswith(rootsep):
346 346 name = name[len(rootsep):]
347 347 audit_path(name)
348 348 return pconvert(name)
349 349 elif name == root:
350 350 return ''
351 351 else:
352 352 # Determine whether `name' is in the hierarchy at or beneath `root',
353 353 # by iterating name=dirname(name) until that causes no change (can't
354 354 # check name == '/', because that doesn't work on windows). For each
355 355 # `name', compare dev/inode numbers. If they match, the list `rel'
356 356 # holds the reversed list of components making up the relative file
357 357 # name we want.
358 358 root_st = os.stat(root)
359 359 rel = []
360 360 while True:
361 361 try:
362 362 name_st = os.stat(name)
363 363 except OSError:
364 364 break
365 365 if samestat(name_st, root_st):
366 366 if not rel:
367 367 # name was actually the same as root (maybe a symlink)
368 368 return ''
369 369 rel.reverse()
370 370 name = os.path.join(*rel)
371 371 audit_path(name)
372 372 return pconvert(name)
373 373 dirname, basename = os.path.split(name)
374 374 rel.append(basename)
375 375 if dirname == name:
376 376 break
377 377 name = dirname
378 378
379 379 raise Abort('%s not under root' % myname)
380 380
381 381 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
382 382 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
383 383
384 384 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
385 385 globbed=False, default=None):
386 386 default = default or 'relpath'
387 387 if default == 'relpath' and not globbed:
388 388 names = expand_glob(names)
389 389 return _matcher(canonroot, cwd, names, inc, exc, default, src)
390 390
391 391 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
392 392 """build a function to match a set of file patterns
393 393
394 394 arguments:
395 395 canonroot - the canonical root of the tree you're matching against
396 396 cwd - the current working directory, if relevant
397 397 names - patterns to find
398 398 inc - patterns to include
399 399 exc - patterns to exclude
400 400 dflt_pat - if a pattern in names has no explicit type, assume this one
401 401 src - where these patterns came from (e.g. .hgignore)
402 402
403 403 a pattern is one of:
404 404 'glob:<glob>' - a glob relative to cwd
405 405 're:<regexp>' - a regular expression
406 406 'path:<path>' - a path relative to canonroot
407 407 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
408 408 'relpath:<path>' - a path relative to cwd
409 409 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
410 410 '<something>' - one of the cases above, selected by the dflt_pat argument
411 411
412 412 returns:
413 413 a 3-tuple containing
414 414 - list of roots (places where one should start a recursive walk of the fs);
415 415 this often matches the explicit non-pattern names passed in, but also
416 416 includes the initial part of glob: patterns that has no glob characters
417 417 - a bool match(filename) function
418 418 - a bool indicating if any patterns were passed in
419 419 """
420 420
421 # a common case: no patterns at all
422 if not names and not inc and not exc:
423 return [], always, False
424
421 425 def contains_glob(name):
422 426 for c in name:
423 427 if c in _globchars: return True
424 428 return False
425 429
426 430 def regex(kind, name, tail):
427 431 '''convert a pattern into a regular expression'''
428 432 if not name:
429 433 return ''
430 434 if kind == 're':
431 435 return name
432 436 elif kind == 'path':
433 437 return '^' + re.escape(name) + '(?:/|$)'
434 438 elif kind == 'relglob':
435 439 return globre(name, '(?:|.*/)', '(?:/|$)')
436 440 elif kind == 'relpath':
437 441 return re.escape(name) + '(?:/|$)'
438 442 elif kind == 'relre':
439 443 if name.startswith('^'):
440 444 return name
441 445 return '.*' + name
442 446 return globre(name, '', tail)
443 447
444 448 def matchfn(pats, tail):
445 449 """build a matching function from a set of patterns"""
446 450 if not pats:
447 451 return
448 452 matches = []
449 453 for k, p in pats:
450 454 try:
451 455 pat = '(?:%s)' % regex(k, p, tail)
452 456 matches.append(re.compile(pat).match)
453 457 except re.error:
454 458 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
455 459 else: raise Abort("invalid pattern (%s): %s" % (k, p))
456 460
457 461 def buildfn(text):
458 462 for m in matches:
459 463 r = m(text)
460 464 if r:
461 465 return r
462 466
463 467 return buildfn
464 468
465 469 def globprefix(pat):
466 470 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
467 471 root = []
468 472 for p in pat.split('/'):
469 473 if contains_glob(p): break
470 474 root.append(p)
471 475 return '/'.join(root) or '.'
472 476
473 477 def normalizepats(names, default):
474 478 pats = []
475 479 files = []
476 480 roots = []
477 481 anypats = False
478 482 for kind, name in [patkind(p, default) for p in names]:
479 483 if kind in ('glob', 'relpath'):
480 484 name = canonpath(canonroot, cwd, name)
481 485 elif kind in ('relglob', 'path'):
482 486 name = normpath(name)
483 487 if kind in ('glob', 're', 'relglob', 'relre'):
484 488 pats.append((kind, name))
485 489 anypats = True
486 490 if kind == 'glob':
487 491 root = globprefix(name)
488 492 roots.append(root)
489 493 elif kind in ('relpath', 'path'):
490 494 files.append((kind, name))
491 495 roots.append(name)
492 496 elif kind == 'relglob':
493 497 roots.append('.')
494 498 return roots, pats + files, anypats
495 499
496 500 roots, pats, anypats = normalizepats(names, dflt_pat)
497 501
498 502 patmatch = matchfn(pats, '$') or always
499 503 incmatch = always
500 504 if inc:
501 505 dummy, inckinds, dummy = normalizepats(inc, 'glob')
502 506 incmatch = matchfn(inckinds, '(?:/|$)')
503 507 excmatch = lambda fn: False
504 508 if exc:
505 509 dummy, exckinds, dummy = normalizepats(exc, 'glob')
506 510 excmatch = matchfn(exckinds, '(?:/|$)')
507 511
508 512 return (roots,
509 513 lambda fn: (incmatch(fn) and not excmatch(fn) and patmatch(fn)),
510 514 (inc or exc or anypats) and True)
511 515
512 516 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
513 517 '''enhanced shell command execution.
514 518 run with environment maybe modified, maybe in different dir.
515 519
516 520 if command fails and onerr is None, return status. if ui object,
517 521 print error message and return status, else raise onerr object as
518 522 exception.'''
519 523 def py2shell(val):
520 524 'convert python object into string that is useful to shell'
521 525 if val in (None, False):
522 526 return '0'
523 527 if val == True:
524 528 return '1'
525 529 return str(val)
526 530 oldenv = {}
527 531 for k in environ:
528 532 oldenv[k] = os.environ.get(k)
529 533 if cwd is not None:
530 534 oldcwd = os.getcwd()
531 535 origcmd = cmd
532 536 if os.name == 'nt':
533 537 cmd = '"%s"' % cmd
534 538 try:
535 539 for k, v in environ.iteritems():
536 540 os.environ[k] = py2shell(v)
537 541 if cwd is not None and oldcwd != cwd:
538 542 os.chdir(cwd)
539 543 rc = os.system(cmd)
540 544 if rc and onerr:
541 545 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
542 546 explain_exit(rc)[0])
543 547 if errprefix:
544 548 errmsg = '%s: %s' % (errprefix, errmsg)
545 549 try:
546 550 onerr.warn(errmsg + '\n')
547 551 except AttributeError:
548 552 raise onerr(errmsg)
549 553 return rc
550 554 finally:
551 555 for k, v in oldenv.iteritems():
552 556 if v is None:
553 557 del os.environ[k]
554 558 else:
555 559 os.environ[k] = v
556 560 if cwd is not None and oldcwd != cwd:
557 561 os.chdir(oldcwd)
558 562
559 563 def rename(src, dst):
560 564 """forcibly rename a file"""
561 565 try:
562 566 os.rename(src, dst)
563 567 except OSError, err:
564 568 # on windows, rename to existing file is not allowed, so we
565 569 # must delete destination first. but if file is open, unlink
566 570 # schedules it for delete but does not delete it. rename
567 571 # happens immediately even for open files, so we create
568 572 # temporary file, delete it, rename destination to that name,
569 573 # then delete that. then rename is safe to do.
570 574 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
571 575 os.close(fd)
572 576 os.unlink(temp)
573 577 os.rename(dst, temp)
574 578 os.unlink(temp)
575 579 os.rename(src, dst)
576 580
577 581 def unlink(f):
578 582 """unlink and remove the directory if it is empty"""
579 583 os.unlink(f)
580 584 # try removing directories that might now be empty
581 585 try:
582 586 os.removedirs(os.path.dirname(f))
583 587 except OSError:
584 588 pass
585 589
586 590 def copyfile(src, dest):
587 591 "copy a file, preserving mode"
588 592 try:
589 593 shutil.copyfile(src, dest)
590 594 shutil.copymode(src, dest)
591 595 except shutil.Error, inst:
592 596 raise Abort(str(inst))
593 597
594 598 def copyfiles(src, dst, hardlink=None):
595 599 """Copy a directory tree using hardlinks if possible"""
596 600
597 601 if hardlink is None:
598 602 hardlink = (os.stat(src).st_dev ==
599 603 os.stat(os.path.dirname(dst)).st_dev)
600 604
601 605 if os.path.isdir(src):
602 606 os.mkdir(dst)
603 607 for name in os.listdir(src):
604 608 srcname = os.path.join(src, name)
605 609 dstname = os.path.join(dst, name)
606 610 copyfiles(srcname, dstname, hardlink)
607 611 else:
608 612 if hardlink:
609 613 try:
610 614 os_link(src, dst)
611 615 except (IOError, OSError):
612 616 hardlink = False
613 617 shutil.copy(src, dst)
614 618 else:
615 619 shutil.copy(src, dst)
616 620
617 621 def audit_path(path):
618 622 """Abort if path contains dangerous components"""
619 623 parts = os.path.normcase(path).split(os.sep)
620 624 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
621 625 or os.pardir in parts):
622 626 raise Abort(_("path contains illegal component: %s\n") % path)
623 627
624 628 def _makelock_file(info, pathname):
625 629 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
626 630 os.write(ld, info)
627 631 os.close(ld)
628 632
629 633 def _readlock_file(pathname):
630 634 return posixfile(pathname).read()
631 635
632 636 def nlinks(pathname):
633 637 """Return number of hardlinks for the given file."""
634 638 return os.lstat(pathname).st_nlink
635 639
636 640 if hasattr(os, 'link'):
637 641 os_link = os.link
638 642 else:
639 643 def os_link(src, dst):
640 644 raise OSError(0, _("Hardlinks not supported"))
641 645
642 646 def fstat(fp):
643 647 '''stat file object that may not have fileno method.'''
644 648 try:
645 649 return os.fstat(fp.fileno())
646 650 except AttributeError:
647 651 return os.stat(fp.name)
648 652
649 653 posixfile = file
650 654
651 655 def is_win_9x():
652 656 '''return true if run on windows 95, 98 or me.'''
653 657 try:
654 658 return sys.getwindowsversion()[3] == 1
655 659 except AttributeError:
656 660 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
657 661
658 662 getuser_fallback = None
659 663
660 664 def getuser():
661 665 '''return name of current user'''
662 666 try:
663 667 return getpass.getuser()
664 668 except ImportError:
665 669 # import of pwd will fail on windows - try fallback
666 670 if getuser_fallback:
667 671 return getuser_fallback()
668 672 # raised if win32api not available
669 673 raise Abort(_('user name not available - set USERNAME '
670 674 'environment variable'))
671 675
672 676 def username(uid=None):
673 677 """Return the name of the user with the given uid.
674 678
675 679 If uid is None, return the name of the current user."""
676 680 try:
677 681 import pwd
678 682 if uid is None:
679 683 uid = os.getuid()
680 684 try:
681 685 return pwd.getpwuid(uid)[0]
682 686 except KeyError:
683 687 return str(uid)
684 688 except ImportError:
685 689 return None
686 690
687 691 def groupname(gid=None):
688 692 """Return the name of the group with the given gid.
689 693
690 694 If gid is None, return the name of the current group."""
691 695 try:
692 696 import grp
693 697 if gid is None:
694 698 gid = os.getgid()
695 699 try:
696 700 return grp.getgrgid(gid)[0]
697 701 except KeyError:
698 702 return str(gid)
699 703 except ImportError:
700 704 return None
701 705
702 706 # File system features
703 707
704 708 def checkfolding(path):
705 709 """
706 710 Check whether the given path is on a case-sensitive filesystem
707 711
708 712 Requires a path (like /foo/.hg) ending with a foldable final
709 713 directory component.
710 714 """
711 715 s1 = os.stat(path)
712 716 d, b = os.path.split(path)
713 717 p2 = os.path.join(d, b.upper())
714 718 if path == p2:
715 719 p2 = os.path.join(d, b.lower())
716 720 try:
717 721 s2 = os.stat(p2)
718 722 if s2 == s1:
719 723 return False
720 724 return True
721 725 except:
722 726 return True
723 727
724 728 # Platform specific variants
725 729 if os.name == 'nt':
726 730 demandload(globals(), "msvcrt")
727 731 nulldev = 'NUL:'
728 732
729 733 class winstdout:
730 734 '''stdout on windows misbehaves if sent through a pipe'''
731 735
732 736 def __init__(self, fp):
733 737 self.fp = fp
734 738
735 739 def __getattr__(self, key):
736 740 return getattr(self.fp, key)
737 741
738 742 def close(self):
739 743 try:
740 744 self.fp.close()
741 745 except: pass
742 746
743 747 def write(self, s):
744 748 try:
745 749 return self.fp.write(s)
746 750 except IOError, inst:
747 751 if inst.errno != 0: raise
748 752 self.close()
749 753 raise IOError(errno.EPIPE, 'Broken pipe')
750 754
751 755 def flush(self):
752 756 try:
753 757 return self.fp.flush()
754 758 except IOError, inst:
755 759 if inst.errno != errno.EINVAL: raise
756 760 self.close()
757 761 raise IOError(errno.EPIPE, 'Broken pipe')
758 762
759 763 sys.stdout = winstdout(sys.stdout)
760 764
761 765 def system_rcpath():
762 766 try:
763 767 return system_rcpath_win32()
764 768 except:
765 769 return [r'c:\mercurial\mercurial.ini']
766 770
767 771 def os_rcpath():
768 772 '''return default os-specific hgrc search path'''
769 773 path = system_rcpath()
770 774 path.append(user_rcpath())
771 775 userprofile = os.environ.get('USERPROFILE')
772 776 if userprofile:
773 777 path.append(os.path.join(userprofile, 'mercurial.ini'))
774 778 return path
775 779
776 780 def user_rcpath():
777 781 '''return os-specific hgrc search path to the user dir'''
778 782 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
779 783
780 784 def parse_patch_output(output_line):
781 785 """parses the output produced by patch and returns the file name"""
782 786 pf = output_line[14:]
783 787 if pf[0] == '`':
784 788 pf = pf[1:-1] # Remove the quotes
785 789 return pf
786 790
787 791 def testpid(pid):
788 792 '''return False if pid dead, True if running or not known'''
789 793 return True
790 794
791 795 def is_exec(f, last):
792 796 return last
793 797
794 798 def set_exec(f, mode):
795 799 pass
796 800
797 801 def set_binary(fd):
798 802 msvcrt.setmode(fd.fileno(), os.O_BINARY)
799 803
800 804 def pconvert(path):
801 805 return path.replace("\\", "/")
802 806
803 807 def localpath(path):
804 808 return path.replace('/', '\\')
805 809
806 810 def normpath(path):
807 811 return pconvert(os.path.normpath(path))
808 812
809 813 makelock = _makelock_file
810 814 readlock = _readlock_file
811 815
812 816 def samestat(s1, s2):
813 817 return False
814 818
815 819 # A sequence of backslashes is special iff it precedes a double quote:
816 820 # - if there's an even number of backslashes, the double quote is not
817 821 # quoted (i.e. it ends the quoted region)
818 822 # - if there's an odd number of backslashes, the double quote is quoted
819 823 # - in both cases, every pair of backslashes is unquoted into a single
820 824 # backslash
821 825 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
822 826 # So, to quote a string, we must surround it in double quotes, double
823 827 # the number of backslashes that preceed double quotes and add another
824 828 # backslash before every double quote (being careful with the double
825 829 # quote we've appended to the end)
826 830 _quotere = None
827 831 def shellquote(s):
828 832 global _quotere
829 833 if _quotere is None:
830 834 _quotere = re.compile(r'(\\*)("|\\$)')
831 835 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
832 836
833 837 def explain_exit(code):
834 838 return _("exited with status %d") % code, code
835 839
836 840 # if you change this stub into a real check, please try to implement the
837 841 # username and groupname functions above, too.
838 842 def isowner(fp, st=None):
839 843 return True
840 844
841 845 try:
842 846 # override functions with win32 versions if possible
843 847 from util_win32 import *
844 848 if not is_win_9x():
845 849 posixfile = posixfile_nt
846 850 except ImportError:
847 851 pass
848 852
849 853 else:
850 854 nulldev = '/dev/null'
851 855
852 856 def rcfiles(path):
853 857 rcs = [os.path.join(path, 'hgrc')]
854 858 rcdir = os.path.join(path, 'hgrc.d')
855 859 try:
856 860 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
857 861 if f.endswith(".rc")])
858 862 except OSError:
859 863 pass
860 864 return rcs
861 865
862 866 def os_rcpath():
863 867 '''return default os-specific hgrc search path'''
864 868 path = []
865 869 # old mod_python does not set sys.argv
866 870 if len(getattr(sys, 'argv', [])) > 0:
867 871 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
868 872 '/../etc/mercurial'))
869 873 path.extend(rcfiles('/etc/mercurial'))
870 874 path.append(os.path.expanduser('~/.hgrc'))
871 875 path = [os.path.normpath(f) for f in path]
872 876 return path
873 877
874 878 def parse_patch_output(output_line):
875 879 """parses the output produced by patch and returns the file name"""
876 880 pf = output_line[14:]
877 881 if pf.startswith("'") and pf.endswith("'") and " " in pf:
878 882 pf = pf[1:-1] # Remove the quotes
879 883 return pf
880 884
881 885 def is_exec(f, last):
882 886 """check whether a file is executable"""
883 887 return (os.lstat(f).st_mode & 0100 != 0)
884 888
885 889 def set_exec(f, mode):
886 890 s = os.lstat(f).st_mode
887 891 if (s & 0100 != 0) == mode:
888 892 return
889 893 if mode:
890 894 # Turn on +x for every +r bit when making a file executable
891 895 # and obey umask.
892 896 umask = os.umask(0)
893 897 os.umask(umask)
894 898 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
895 899 else:
896 900 os.chmod(f, s & 0666)
897 901
898 902 def set_binary(fd):
899 903 pass
900 904
901 905 def pconvert(path):
902 906 return path
903 907
904 908 def localpath(path):
905 909 return path
906 910
907 911 normpath = os.path.normpath
908 912 samestat = os.path.samestat
909 913
910 914 def makelock(info, pathname):
911 915 try:
912 916 os.symlink(info, pathname)
913 917 except OSError, why:
914 918 if why.errno == errno.EEXIST:
915 919 raise
916 920 else:
917 921 _makelock_file(info, pathname)
918 922
919 923 def readlock(pathname):
920 924 try:
921 925 return os.readlink(pathname)
922 926 except OSError, why:
923 927 if why.errno == errno.EINVAL:
924 928 return _readlock_file(pathname)
925 929 else:
926 930 raise
927 931
928 932 def shellquote(s):
929 933 return "'%s'" % s.replace("'", "'\\''")
930 934
931 935 def testpid(pid):
932 936 '''return False if pid dead, True if running or not sure'''
933 937 try:
934 938 os.kill(pid, 0)
935 939 return True
936 940 except OSError, inst:
937 941 return inst.errno != errno.ESRCH
938 942
939 943 def explain_exit(code):
940 944 """return a 2-tuple (desc, code) describing a process's status"""
941 945 if os.WIFEXITED(code):
942 946 val = os.WEXITSTATUS(code)
943 947 return _("exited with status %d") % val, val
944 948 elif os.WIFSIGNALED(code):
945 949 val = os.WTERMSIG(code)
946 950 return _("killed by signal %d") % val, val
947 951 elif os.WIFSTOPPED(code):
948 952 val = os.WSTOPSIG(code)
949 953 return _("stopped by signal %d") % val, val
950 954 raise ValueError(_("invalid exit code"))
951 955
952 956 def isowner(fp, st=None):
953 957 """Return True if the file object f belongs to the current user.
954 958
955 959 The return value of a util.fstat(f) may be passed as the st argument.
956 960 """
957 961 if st is None:
958 962 st = fstat(fp)
959 963 return st.st_uid == os.getuid()
960 964
961 965 def _buildencodefun():
962 966 e = '_'
963 967 win_reserved = [ord(x) for x in '\\:*?"<>|']
964 968 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
965 969 for x in (range(32) + range(126, 256) + win_reserved):
966 970 cmap[chr(x)] = "~%02x" % x
967 971 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
968 972 cmap[chr(x)] = e + chr(x).lower()
969 973 dmap = {}
970 974 for k, v in cmap.iteritems():
971 975 dmap[v] = k
972 976 def decode(s):
973 977 i = 0
974 978 while i < len(s):
975 979 for l in xrange(1, 4):
976 980 try:
977 981 yield dmap[s[i:i+l]]
978 982 i += l
979 983 break
980 984 except KeyError:
981 985 pass
982 986 else:
983 987 raise KeyError
984 988 return (lambda s: "".join([cmap[c] for c in s]),
985 989 lambda s: "".join(list(decode(s))))
986 990
987 991 encodefilename, decodefilename = _buildencodefun()
988 992
989 993 def encodedopener(openerfn, fn):
990 994 def o(path, *args, **kw):
991 995 return openerfn(fn(path), *args, **kw)
992 996 return o
993 997
994 998 def opener(base, audit=True):
995 999 """
996 1000 return a function that opens files relative to base
997 1001
998 1002 this function is used to hide the details of COW semantics and
999 1003 remote file access from higher level code.
1000 1004 """
1001 1005 p = base
1002 1006 audit_p = audit
1003 1007
1004 1008 def mktempcopy(name):
1005 1009 d, fn = os.path.split(name)
1006 1010 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1007 1011 os.close(fd)
1008 1012 ofp = posixfile(temp, "wb")
1009 1013 try:
1010 1014 try:
1011 1015 ifp = posixfile(name, "rb")
1012 1016 except IOError, inst:
1013 1017 if not getattr(inst, 'filename', None):
1014 1018 inst.filename = name
1015 1019 raise
1016 1020 for chunk in filechunkiter(ifp):
1017 1021 ofp.write(chunk)
1018 1022 ifp.close()
1019 1023 ofp.close()
1020 1024 except:
1021 1025 try: os.unlink(temp)
1022 1026 except: pass
1023 1027 raise
1024 1028 st = os.lstat(name)
1025 1029 os.chmod(temp, st.st_mode)
1026 1030 return temp
1027 1031
1028 1032 class atomictempfile(posixfile):
1029 1033 """the file will only be copied when rename is called"""
1030 1034 def __init__(self, name, mode):
1031 1035 self.__name = name
1032 1036 self.temp = mktempcopy(name)
1033 1037 posixfile.__init__(self, self.temp, mode)
1034 1038 def rename(self):
1035 1039 if not self.closed:
1036 1040 posixfile.close(self)
1037 1041 rename(self.temp, localpath(self.__name))
1038 1042 def __del__(self):
1039 1043 if not self.closed:
1040 1044 try:
1041 1045 os.unlink(self.temp)
1042 1046 except: pass
1043 1047 posixfile.close(self)
1044 1048
1045 1049 class atomicfile(atomictempfile):
1046 1050 """the file will only be copied on close"""
1047 1051 def __init__(self, name, mode):
1048 1052 atomictempfile.__init__(self, name, mode)
1049 1053 def close(self):
1050 1054 self.rename()
1051 1055 def __del__(self):
1052 1056 self.rename()
1053 1057
1054 1058 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1055 1059 if audit_p:
1056 1060 audit_path(path)
1057 1061 f = os.path.join(p, path)
1058 1062
1059 1063 if not text:
1060 1064 mode += "b" # for that other OS
1061 1065
1062 1066 if mode[0] != "r":
1063 1067 try:
1064 1068 nlink = nlinks(f)
1065 1069 except OSError:
1066 1070 d = os.path.dirname(f)
1067 1071 if not os.path.isdir(d):
1068 1072 os.makedirs(d)
1069 1073 else:
1070 1074 if atomic:
1071 1075 return atomicfile(f, mode)
1072 1076 elif atomictemp:
1073 1077 return atomictempfile(f, mode)
1074 1078 if nlink > 1:
1075 1079 rename(mktempcopy(f), f)
1076 1080 return posixfile(f, mode)
1077 1081
1078 1082 return o
1079 1083
1080 1084 class chunkbuffer(object):
1081 1085 """Allow arbitrary sized chunks of data to be efficiently read from an
1082 1086 iterator over chunks of arbitrary size."""
1083 1087
1084 1088 def __init__(self, in_iter, targetsize = 2**16):
1085 1089 """in_iter is the iterator that's iterating over the input chunks.
1086 1090 targetsize is how big a buffer to try to maintain."""
1087 1091 self.in_iter = iter(in_iter)
1088 1092 self.buf = ''
1089 1093 self.targetsize = int(targetsize)
1090 1094 if self.targetsize <= 0:
1091 1095 raise ValueError(_("targetsize must be greater than 0, was %d") %
1092 1096 targetsize)
1093 1097 self.iterempty = False
1094 1098
1095 1099 def fillbuf(self):
1096 1100 """Ignore target size; read every chunk from iterator until empty."""
1097 1101 if not self.iterempty:
1098 1102 collector = cStringIO.StringIO()
1099 1103 collector.write(self.buf)
1100 1104 for ch in self.in_iter:
1101 1105 collector.write(ch)
1102 1106 self.buf = collector.getvalue()
1103 1107 self.iterempty = True
1104 1108
1105 1109 def read(self, l):
1106 1110 """Read L bytes of data from the iterator of chunks of data.
1107 1111 Returns less than L bytes if the iterator runs dry."""
1108 1112 if l > len(self.buf) and not self.iterempty:
1109 1113 # Clamp to a multiple of self.targetsize
1110 1114 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1111 1115 collector = cStringIO.StringIO()
1112 1116 collector.write(self.buf)
1113 1117 collected = len(self.buf)
1114 1118 for chunk in self.in_iter:
1115 1119 collector.write(chunk)
1116 1120 collected += len(chunk)
1117 1121 if collected >= targetsize:
1118 1122 break
1119 1123 if collected < targetsize:
1120 1124 self.iterempty = True
1121 1125 self.buf = collector.getvalue()
1122 1126 s, self.buf = self.buf[:l], buffer(self.buf, l)
1123 1127 return s
1124 1128
1125 1129 def filechunkiter(f, size=65536, limit=None):
1126 1130 """Create a generator that produces the data in the file size
1127 1131 (default 65536) bytes at a time, up to optional limit (default is
1128 1132 to read all data). Chunks may be less than size bytes if the
1129 1133 chunk is the last chunk in the file, or the file is a socket or
1130 1134 some other type of file that sometimes reads less data than is
1131 1135 requested."""
1132 1136 assert size >= 0
1133 1137 assert limit is None or limit >= 0
1134 1138 while True:
1135 1139 if limit is None: nbytes = size
1136 1140 else: nbytes = min(limit, size)
1137 1141 s = nbytes and f.read(nbytes)
1138 1142 if not s: break
1139 1143 if limit: limit -= len(s)
1140 1144 yield s
1141 1145
1142 1146 def makedate():
1143 1147 lt = time.localtime()
1144 1148 if lt[8] == 1 and time.daylight:
1145 1149 tz = time.altzone
1146 1150 else:
1147 1151 tz = time.timezone
1148 1152 return time.mktime(lt), tz
1149 1153
1150 1154 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1151 1155 """represent a (unixtime, offset) tuple as a localized time.
1152 1156 unixtime is seconds since the epoch, and offset is the time zone's
1153 1157 number of seconds away from UTC. if timezone is false, do not
1154 1158 append time zone to string."""
1155 1159 t, tz = date or makedate()
1156 1160 s = time.strftime(format, time.gmtime(float(t) - tz))
1157 1161 if timezone:
1158 1162 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1159 1163 return s
1160 1164
1161 1165 def strdate(string, format, defaults):
1162 1166 """parse a localized time string and return a (unixtime, offset) tuple.
1163 1167 if the string cannot be parsed, ValueError is raised."""
1164 1168 def timezone(string):
1165 1169 tz = string.split()[-1]
1166 1170 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1167 1171 tz = int(tz)
1168 1172 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1169 1173 return offset
1170 1174 if tz == "GMT" or tz == "UTC":
1171 1175 return 0
1172 1176 return None
1173 1177
1174 1178 # NOTE: unixtime = localunixtime + offset
1175 1179 offset, date = timezone(string), string
1176 1180 if offset != None:
1177 1181 date = " ".join(string.split()[:-1])
1178 1182
1179 1183 # add missing elements from defaults
1180 1184 for part in defaults:
1181 1185 found = [True for p in part if ("%"+p) in format]
1182 1186 if not found:
1183 1187 date += "@" + defaults[part]
1184 1188 format += "@%" + part[0]
1185 1189
1186 1190 timetuple = time.strptime(date, format)
1187 1191 localunixtime = int(calendar.timegm(timetuple))
1188 1192 if offset is None:
1189 1193 # local timezone
1190 1194 unixtime = int(time.mktime(timetuple))
1191 1195 offset = unixtime - localunixtime
1192 1196 else:
1193 1197 unixtime = localunixtime + offset
1194 1198 return unixtime, offset
1195 1199
1196 1200 def parsedate(string, formats=None, defaults=None):
1197 1201 """parse a localized time string and return a (unixtime, offset) tuple.
1198 1202 The date may be a "unixtime offset" string or in one of the specified
1199 1203 formats."""
1200 1204 if not string:
1201 1205 return 0, 0
1202 1206 if not formats:
1203 1207 formats = defaultdateformats
1204 1208 string = string.strip()
1205 1209 try:
1206 1210 when, offset = map(int, string.split(' '))
1207 1211 except ValueError:
1208 1212 # fill out defaults
1209 1213 if not defaults:
1210 1214 defaults = {}
1211 1215 now = makedate()
1212 1216 for part in "d mb yY HI M S".split():
1213 1217 if part not in defaults:
1214 1218 if part[0] in "HMS":
1215 1219 defaults[part] = "00"
1216 1220 elif part[0] in "dm":
1217 1221 defaults[part] = "1"
1218 1222 else:
1219 1223 defaults[part] = datestr(now, "%" + part[0], False)
1220 1224
1221 1225 for format in formats:
1222 1226 try:
1223 1227 when, offset = strdate(string, format, defaults)
1224 1228 except ValueError:
1225 1229 pass
1226 1230 else:
1227 1231 break
1228 1232 else:
1229 1233 raise Abort(_('invalid date: %r ') % string)
1230 1234 # validate explicit (probably user-specified) date and
1231 1235 # time zone offset. values must fit in signed 32 bits for
1232 1236 # current 32-bit linux runtimes. timezones go from UTC-12
1233 1237 # to UTC+14
1234 1238 if abs(when) > 0x7fffffff:
1235 1239 raise Abort(_('date exceeds 32 bits: %d') % when)
1236 1240 if offset < -50400 or offset > 43200:
1237 1241 raise Abort(_('impossible time zone offset: %d') % offset)
1238 1242 return when, offset
1239 1243
1240 1244 def matchdate(date):
1241 1245 """Return a function that matches a given date match specifier
1242 1246
1243 1247 Formats include:
1244 1248
1245 1249 '{date}' match a given date to the accuracy provided
1246 1250
1247 1251 '<{date}' on or before a given date
1248 1252
1249 1253 '>{date}' on or after a given date
1250 1254
1251 1255 """
1252 1256
1253 1257 def lower(date):
1254 1258 return parsedate(date, extendeddateformats)[0]
1255 1259
1256 1260 def upper(date):
1257 1261 d = dict(mb="12", HI="23", M="59", S="59")
1258 1262 for days in "31 30 29".split():
1259 1263 try:
1260 1264 d["d"] = days
1261 1265 return parsedate(date, extendeddateformats, d)[0]
1262 1266 except:
1263 1267 pass
1264 1268 d["d"] = "28"
1265 1269 return parsedate(date, extendeddateformats, d)[0]
1266 1270
1267 1271 if date[0] == "<":
1268 1272 when = upper(date[1:])
1269 1273 return lambda x: x <= when
1270 1274 elif date[0] == ">":
1271 1275 when = lower(date[1:])
1272 1276 return lambda x: x >= when
1273 1277 elif date[0] == "-":
1274 1278 try:
1275 1279 days = int(date[1:])
1276 1280 except ValueError:
1277 1281 raise Abort(_("invalid day spec: %s") % date[1:])
1278 1282 when = makedate()[0] - days * 3600 * 24
1279 1283 return lambda x: x >= when
1280 1284 elif " to " in date:
1281 1285 a, b = date.split(" to ")
1282 1286 start, stop = lower(a), upper(b)
1283 1287 return lambda x: x >= start and x <= stop
1284 1288 else:
1285 1289 start, stop = lower(date), upper(date)
1286 1290 return lambda x: x >= start and x <= stop
1287 1291
1288 1292 def shortuser(user):
1289 1293 """Return a short representation of a user name or email address."""
1290 1294 f = user.find('@')
1291 1295 if f >= 0:
1292 1296 user = user[:f]
1293 1297 f = user.find('<')
1294 1298 if f >= 0:
1295 1299 user = user[f+1:]
1296 1300 f = user.find(' ')
1297 1301 if f >= 0:
1298 1302 user = user[:f]
1299 1303 f = user.find('.')
1300 1304 if f >= 0:
1301 1305 user = user[:f]
1302 1306 return user
1303 1307
1304 1308 def ellipsis(text, maxlength=400):
1305 1309 """Trim string to at most maxlength (default: 400) characters."""
1306 1310 if len(text) <= maxlength:
1307 1311 return text
1308 1312 else:
1309 1313 return "%s..." % (text[:maxlength-3])
1310 1314
1311 1315 def walkrepos(path):
1312 1316 '''yield every hg repository under path, recursively.'''
1313 1317 def errhandler(err):
1314 1318 if err.filename == path:
1315 1319 raise err
1316 1320
1317 1321 for root, dirs, files in os.walk(path, onerror=errhandler):
1318 1322 for d in dirs:
1319 1323 if d == '.hg':
1320 1324 yield root
1321 1325 dirs[:] = []
1322 1326 break
1323 1327
1324 1328 _rcpath = None
1325 1329
1326 1330 def rcpath():
1327 1331 '''return hgrc search path. if env var HGRCPATH is set, use it.
1328 1332 for each item in path, if directory, use files ending in .rc,
1329 1333 else use item.
1330 1334 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1331 1335 if no HGRCPATH, use default os-specific path.'''
1332 1336 global _rcpath
1333 1337 if _rcpath is None:
1334 1338 if 'HGRCPATH' in os.environ:
1335 1339 _rcpath = []
1336 1340 for p in os.environ['HGRCPATH'].split(os.pathsep):
1337 1341 if not p: continue
1338 1342 if os.path.isdir(p):
1339 1343 for f in os.listdir(p):
1340 1344 if f.endswith('.rc'):
1341 1345 _rcpath.append(os.path.join(p, f))
1342 1346 else:
1343 1347 _rcpath.append(p)
1344 1348 else:
1345 1349 _rcpath = os_rcpath()
1346 1350 return _rcpath
1347 1351
1348 1352 def bytecount(nbytes):
1349 1353 '''return byte count formatted as readable string, with units'''
1350 1354
1351 1355 units = (
1352 1356 (100, 1<<30, _('%.0f GB')),
1353 1357 (10, 1<<30, _('%.1f GB')),
1354 1358 (1, 1<<30, _('%.2f GB')),
1355 1359 (100, 1<<20, _('%.0f MB')),
1356 1360 (10, 1<<20, _('%.1f MB')),
1357 1361 (1, 1<<20, _('%.2f MB')),
1358 1362 (100, 1<<10, _('%.0f KB')),
1359 1363 (10, 1<<10, _('%.1f KB')),
1360 1364 (1, 1<<10, _('%.2f KB')),
1361 1365 (1, 1, _('%.0f bytes')),
1362 1366 )
1363 1367
1364 1368 for multiplier, divisor, format in units:
1365 1369 if nbytes >= divisor * multiplier:
1366 1370 return format % (nbytes / float(divisor))
1367 1371 return units[-1][2] % nbytes
1368 1372
1369 1373 def drop_scheme(scheme, path):
1370 1374 sc = scheme + ':'
1371 1375 if path.startswith(sc):
1372 1376 path = path[len(sc):]
1373 1377 if path.startswith('//'):
1374 1378 path = path[2:]
1375 1379 return path
General Comments 0
You need to be logged in to leave comments. Login now