##// END OF EJS Templates
small globprefix fix
Alexis S. L. Carvalho -
r4183:6f947404 default
parent child Browse files
Show More
@@ -1,1369 +1,1369 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=[], head='', src=None):
382 382 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
383 383
384 384 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='',
385 385 src=None, globbed=False):
386 386 if not globbed:
387 387 names = expand_glob(names)
388 388 return _matcher(canonroot, cwd, names, inc, exc, head, 'relpath', src)
389 389
390 390 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
391 391 """build a function to match a set of file patterns
392 392
393 393 arguments:
394 394 canonroot - the canonical root of the tree you're matching against
395 395 cwd - the current working directory, if relevant
396 396 names - patterns to find
397 397 inc - patterns to include
398 398 exc - patterns to exclude
399 399 head - a regex to prepend to patterns to control whether a match is rooted
400 400
401 401 a pattern is one of:
402 402 'glob:<rooted glob>'
403 403 're:<rooted regexp>'
404 404 'path:<rooted path>'
405 405 'relglob:<relative glob>'
406 406 'relpath:<relative path>'
407 407 'relre:<relative regexp>'
408 408 '<rooted path or regexp>'
409 409
410 410 returns:
411 411 a 3-tuple containing
412 412 - list of explicit non-pattern names passed in
413 413 - a bool match(filename) function
414 414 - a bool indicating if any patterns were passed in
415 415
416 416 todo:
417 417 make head regex a rooted bool
418 418 """
419 419
420 420 def contains_glob(name):
421 421 for c in name:
422 422 if c in _globchars: return True
423 423 return False
424 424
425 425 def regex(kind, name, tail):
426 426 '''convert a pattern into a regular expression'''
427 427 if kind == 're':
428 428 return name
429 429 elif kind == 'path':
430 430 return '^' + re.escape(name) + '(?:/|$)'
431 431 elif kind == 'relglob':
432 432 return head + globre(name, '(?:|.*/)', tail)
433 433 elif kind == 'relpath':
434 434 return head + re.escape(name) + tail
435 435 elif kind == 'relre':
436 436 if name.startswith('^'):
437 437 return name
438 438 return '.*' + name
439 439 return head + globre(name, '', tail)
440 440
441 441 def matchfn(pats, tail):
442 442 """build a matching function from a set of patterns"""
443 443 if not pats:
444 444 return
445 445 matches = []
446 446 for k, p in pats:
447 447 try:
448 448 pat = '(?:%s)' % regex(k, p, tail)
449 449 matches.append(re.compile(pat).match)
450 450 except re.error:
451 451 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
452 452 else: raise Abort("invalid pattern (%s): %s" % (k, p))
453 453
454 454 def buildfn(text):
455 455 for m in matches:
456 456 r = m(text)
457 457 if r:
458 458 return r
459 459
460 460 return buildfn
461 461
462 462 def globprefix(pat):
463 463 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
464 464 root = []
465 for p in pat.split(os.sep):
465 for p in pat.split('/'):
466 466 if contains_glob(p): break
467 467 root.append(p)
468 468 return '/'.join(root)
469 469
470 470 pats = []
471 471 files = []
472 472 roots = []
473 473 for kind, name in [patkind(p, dflt_pat) for p in names]:
474 474 if kind in ('glob', 'relpath'):
475 475 name = canonpath(canonroot, cwd, name)
476 476 if name == '':
477 477 kind, name = 'glob', '**'
478 478 if kind in ('glob', 'path', 're'):
479 479 pats.append((kind, name))
480 480 if kind == 'glob':
481 481 root = globprefix(name)
482 482 if root: roots.append(root)
483 483 elif kind == 'relpath':
484 484 files.append((kind, name))
485 485 roots.append(name)
486 486
487 487 patmatch = matchfn(pats, '$') or always
488 488 filematch = matchfn(files, '(?:/|$)') or always
489 489 incmatch = always
490 490 if inc:
491 491 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
492 492 incmatch = matchfn(inckinds, '(?:/|$)')
493 493 excmatch = lambda fn: False
494 494 if exc:
495 495 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
496 496 excmatch = matchfn(exckinds, '(?:/|$)')
497 497
498 498 return (roots,
499 499 lambda fn: (incmatch(fn) and not excmatch(fn) and
500 500 (fn.endswith('/') or
501 501 (not pats and not files) or
502 502 (pats and patmatch(fn)) or
503 503 (files and filematch(fn)))),
504 504 (inc or exc or (pats and pats != [('glob', '**')])) and True)
505 505
506 506 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
507 507 '''enhanced shell command execution.
508 508 run with environment maybe modified, maybe in different dir.
509 509
510 510 if command fails and onerr is None, return status. if ui object,
511 511 print error message and return status, else raise onerr object as
512 512 exception.'''
513 513 def py2shell(val):
514 514 'convert python object into string that is useful to shell'
515 515 if val in (None, False):
516 516 return '0'
517 517 if val == True:
518 518 return '1'
519 519 return str(val)
520 520 oldenv = {}
521 521 for k in environ:
522 522 oldenv[k] = os.environ.get(k)
523 523 if cwd is not None:
524 524 oldcwd = os.getcwd()
525 525 origcmd = cmd
526 526 if os.name == 'nt':
527 527 cmd = '"%s"' % cmd
528 528 try:
529 529 for k, v in environ.iteritems():
530 530 os.environ[k] = py2shell(v)
531 531 if cwd is not None and oldcwd != cwd:
532 532 os.chdir(cwd)
533 533 rc = os.system(cmd)
534 534 if rc and onerr:
535 535 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
536 536 explain_exit(rc)[0])
537 537 if errprefix:
538 538 errmsg = '%s: %s' % (errprefix, errmsg)
539 539 try:
540 540 onerr.warn(errmsg + '\n')
541 541 except AttributeError:
542 542 raise onerr(errmsg)
543 543 return rc
544 544 finally:
545 545 for k, v in oldenv.iteritems():
546 546 if v is None:
547 547 del os.environ[k]
548 548 else:
549 549 os.environ[k] = v
550 550 if cwd is not None and oldcwd != cwd:
551 551 os.chdir(oldcwd)
552 552
553 553 def rename(src, dst):
554 554 """forcibly rename a file"""
555 555 try:
556 556 os.rename(src, dst)
557 557 except OSError, err:
558 558 # on windows, rename to existing file is not allowed, so we
559 559 # must delete destination first. but if file is open, unlink
560 560 # schedules it for delete but does not delete it. rename
561 561 # happens immediately even for open files, so we create
562 562 # temporary file, delete it, rename destination to that name,
563 563 # then delete that. then rename is safe to do.
564 564 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
565 565 os.close(fd)
566 566 os.unlink(temp)
567 567 os.rename(dst, temp)
568 568 os.unlink(temp)
569 569 os.rename(src, dst)
570 570
571 571 def unlink(f):
572 572 """unlink and remove the directory if it is empty"""
573 573 os.unlink(f)
574 574 # try removing directories that might now be empty
575 575 try:
576 576 os.removedirs(os.path.dirname(f))
577 577 except OSError:
578 578 pass
579 579
580 580 def copyfile(src, dest):
581 581 "copy a file, preserving mode"
582 582 try:
583 583 shutil.copyfile(src, dest)
584 584 shutil.copymode(src, dest)
585 585 except shutil.Error, inst:
586 586 raise Abort(str(inst))
587 587
588 588 def copyfiles(src, dst, hardlink=None):
589 589 """Copy a directory tree using hardlinks if possible"""
590 590
591 591 if hardlink is None:
592 592 hardlink = (os.stat(src).st_dev ==
593 593 os.stat(os.path.dirname(dst)).st_dev)
594 594
595 595 if os.path.isdir(src):
596 596 os.mkdir(dst)
597 597 for name in os.listdir(src):
598 598 srcname = os.path.join(src, name)
599 599 dstname = os.path.join(dst, name)
600 600 copyfiles(srcname, dstname, hardlink)
601 601 else:
602 602 if hardlink:
603 603 try:
604 604 os_link(src, dst)
605 605 except (IOError, OSError):
606 606 hardlink = False
607 607 shutil.copy(src, dst)
608 608 else:
609 609 shutil.copy(src, dst)
610 610
611 611 def audit_path(path):
612 612 """Abort if path contains dangerous components"""
613 613 parts = os.path.normcase(path).split(os.sep)
614 614 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
615 615 or os.pardir in parts):
616 616 raise Abort(_("path contains illegal component: %s\n") % path)
617 617
618 618 def _makelock_file(info, pathname):
619 619 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
620 620 os.write(ld, info)
621 621 os.close(ld)
622 622
623 623 def _readlock_file(pathname):
624 624 return posixfile(pathname).read()
625 625
626 626 def nlinks(pathname):
627 627 """Return number of hardlinks for the given file."""
628 628 return os.lstat(pathname).st_nlink
629 629
630 630 if hasattr(os, 'link'):
631 631 os_link = os.link
632 632 else:
633 633 def os_link(src, dst):
634 634 raise OSError(0, _("Hardlinks not supported"))
635 635
636 636 def fstat(fp):
637 637 '''stat file object that may not have fileno method.'''
638 638 try:
639 639 return os.fstat(fp.fileno())
640 640 except AttributeError:
641 641 return os.stat(fp.name)
642 642
643 643 posixfile = file
644 644
645 645 def is_win_9x():
646 646 '''return true if run on windows 95, 98 or me.'''
647 647 try:
648 648 return sys.getwindowsversion()[3] == 1
649 649 except AttributeError:
650 650 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
651 651
652 652 getuser_fallback = None
653 653
654 654 def getuser():
655 655 '''return name of current user'''
656 656 try:
657 657 return getpass.getuser()
658 658 except ImportError:
659 659 # import of pwd will fail on windows - try fallback
660 660 if getuser_fallback:
661 661 return getuser_fallback()
662 662 # raised if win32api not available
663 663 raise Abort(_('user name not available - set USERNAME '
664 664 'environment variable'))
665 665
666 666 def username(uid=None):
667 667 """Return the name of the user with the given uid.
668 668
669 669 If uid is None, return the name of the current user."""
670 670 try:
671 671 import pwd
672 672 if uid is None:
673 673 uid = os.getuid()
674 674 try:
675 675 return pwd.getpwuid(uid)[0]
676 676 except KeyError:
677 677 return str(uid)
678 678 except ImportError:
679 679 return None
680 680
681 681 def groupname(gid=None):
682 682 """Return the name of the group with the given gid.
683 683
684 684 If gid is None, return the name of the current group."""
685 685 try:
686 686 import grp
687 687 if gid is None:
688 688 gid = os.getgid()
689 689 try:
690 690 return grp.getgrgid(gid)[0]
691 691 except KeyError:
692 692 return str(gid)
693 693 except ImportError:
694 694 return None
695 695
696 696 # File system features
697 697
698 698 def checkfolding(path):
699 699 """
700 700 Check whether the given path is on a case-sensitive filesystem
701 701
702 702 Requires a path (like /foo/.hg) ending with a foldable final
703 703 directory component.
704 704 """
705 705 s1 = os.stat(path)
706 706 d, b = os.path.split(path)
707 707 p2 = os.path.join(d, b.upper())
708 708 if path == p2:
709 709 p2 = os.path.join(d, b.lower())
710 710 try:
711 711 s2 = os.stat(p2)
712 712 if s2 == s1:
713 713 return False
714 714 return True
715 715 except:
716 716 return True
717 717
718 718 # Platform specific variants
719 719 if os.name == 'nt':
720 720 demandload(globals(), "msvcrt")
721 721 nulldev = 'NUL:'
722 722
723 723 class winstdout:
724 724 '''stdout on windows misbehaves if sent through a pipe'''
725 725
726 726 def __init__(self, fp):
727 727 self.fp = fp
728 728
729 729 def __getattr__(self, key):
730 730 return getattr(self.fp, key)
731 731
732 732 def close(self):
733 733 try:
734 734 self.fp.close()
735 735 except: pass
736 736
737 737 def write(self, s):
738 738 try:
739 739 return self.fp.write(s)
740 740 except IOError, inst:
741 741 if inst.errno != 0: raise
742 742 self.close()
743 743 raise IOError(errno.EPIPE, 'Broken pipe')
744 744
745 745 def flush(self):
746 746 try:
747 747 return self.fp.flush()
748 748 except IOError, inst:
749 749 if inst.errno != errno.EINVAL: raise
750 750 self.close()
751 751 raise IOError(errno.EPIPE, 'Broken pipe')
752 752
753 753 sys.stdout = winstdout(sys.stdout)
754 754
755 755 def system_rcpath():
756 756 try:
757 757 return system_rcpath_win32()
758 758 except:
759 759 return [r'c:\mercurial\mercurial.ini']
760 760
761 761 def os_rcpath():
762 762 '''return default os-specific hgrc search path'''
763 763 path = system_rcpath()
764 764 path.append(user_rcpath())
765 765 userprofile = os.environ.get('USERPROFILE')
766 766 if userprofile:
767 767 path.append(os.path.join(userprofile, 'mercurial.ini'))
768 768 return path
769 769
770 770 def user_rcpath():
771 771 '''return os-specific hgrc search path to the user dir'''
772 772 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
773 773
774 774 def parse_patch_output(output_line):
775 775 """parses the output produced by patch and returns the file name"""
776 776 pf = output_line[14:]
777 777 if pf[0] == '`':
778 778 pf = pf[1:-1] # Remove the quotes
779 779 return pf
780 780
781 781 def testpid(pid):
782 782 '''return False if pid dead, True if running or not known'''
783 783 return True
784 784
785 785 def is_exec(f, last):
786 786 return last
787 787
788 788 def set_exec(f, mode):
789 789 pass
790 790
791 791 def set_binary(fd):
792 792 msvcrt.setmode(fd.fileno(), os.O_BINARY)
793 793
794 794 def pconvert(path):
795 795 return path.replace("\\", "/")
796 796
797 797 def localpath(path):
798 798 return path.replace('/', '\\')
799 799
800 800 def normpath(path):
801 801 return pconvert(os.path.normpath(path))
802 802
803 803 makelock = _makelock_file
804 804 readlock = _readlock_file
805 805
806 806 def samestat(s1, s2):
807 807 return False
808 808
809 809 # A sequence of backslashes is special iff it precedes a double quote:
810 810 # - if there's an even number of backslashes, the double quote is not
811 811 # quoted (i.e. it ends the quoted region)
812 812 # - if there's an odd number of backslashes, the double quote is quoted
813 813 # - in both cases, every pair of backslashes is unquoted into a single
814 814 # backslash
815 815 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
816 816 # So, to quote a string, we must surround it in double quotes, double
817 817 # the number of backslashes that preceed double quotes and add another
818 818 # backslash before every double quote (being careful with the double
819 819 # quote we've appended to the end)
820 820 _quotere = None
821 821 def shellquote(s):
822 822 global _quotere
823 823 if _quotere is None:
824 824 _quotere = re.compile(r'(\\*)("|\\$)')
825 825 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
826 826
827 827 def explain_exit(code):
828 828 return _("exited with status %d") % code, code
829 829
830 830 # if you change this stub into a real check, please try to implement the
831 831 # username and groupname functions above, too.
832 832 def isowner(fp, st=None):
833 833 return True
834 834
835 835 try:
836 836 # override functions with win32 versions if possible
837 837 from util_win32 import *
838 838 if not is_win_9x():
839 839 posixfile = posixfile_nt
840 840 except ImportError:
841 841 pass
842 842
843 843 else:
844 844 nulldev = '/dev/null'
845 845
846 846 def rcfiles(path):
847 847 rcs = [os.path.join(path, 'hgrc')]
848 848 rcdir = os.path.join(path, 'hgrc.d')
849 849 try:
850 850 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
851 851 if f.endswith(".rc")])
852 852 except OSError:
853 853 pass
854 854 return rcs
855 855
856 856 def os_rcpath():
857 857 '''return default os-specific hgrc search path'''
858 858 path = []
859 859 # old mod_python does not set sys.argv
860 860 if len(getattr(sys, 'argv', [])) > 0:
861 861 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
862 862 '/../etc/mercurial'))
863 863 path.extend(rcfiles('/etc/mercurial'))
864 864 path.append(os.path.expanduser('~/.hgrc'))
865 865 path = [os.path.normpath(f) for f in path]
866 866 return path
867 867
868 868 def parse_patch_output(output_line):
869 869 """parses the output produced by patch and returns the file name"""
870 870 pf = output_line[14:]
871 871 if pf.startswith("'") and pf.endswith("'") and " " in pf:
872 872 pf = pf[1:-1] # Remove the quotes
873 873 return pf
874 874
875 875 def is_exec(f, last):
876 876 """check whether a file is executable"""
877 877 return (os.lstat(f).st_mode & 0100 != 0)
878 878
879 879 def set_exec(f, mode):
880 880 s = os.lstat(f).st_mode
881 881 if (s & 0100 != 0) == mode:
882 882 return
883 883 if mode:
884 884 # Turn on +x for every +r bit when making a file executable
885 885 # and obey umask.
886 886 umask = os.umask(0)
887 887 os.umask(umask)
888 888 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
889 889 else:
890 890 os.chmod(f, s & 0666)
891 891
892 892 def set_binary(fd):
893 893 pass
894 894
895 895 def pconvert(path):
896 896 return path
897 897
898 898 def localpath(path):
899 899 return path
900 900
901 901 normpath = os.path.normpath
902 902 samestat = os.path.samestat
903 903
904 904 def makelock(info, pathname):
905 905 try:
906 906 os.symlink(info, pathname)
907 907 except OSError, why:
908 908 if why.errno == errno.EEXIST:
909 909 raise
910 910 else:
911 911 _makelock_file(info, pathname)
912 912
913 913 def readlock(pathname):
914 914 try:
915 915 return os.readlink(pathname)
916 916 except OSError, why:
917 917 if why.errno == errno.EINVAL:
918 918 return _readlock_file(pathname)
919 919 else:
920 920 raise
921 921
922 922 def shellquote(s):
923 923 return "'%s'" % s.replace("'", "'\\''")
924 924
925 925 def testpid(pid):
926 926 '''return False if pid dead, True if running or not sure'''
927 927 try:
928 928 os.kill(pid, 0)
929 929 return True
930 930 except OSError, inst:
931 931 return inst.errno != errno.ESRCH
932 932
933 933 def explain_exit(code):
934 934 """return a 2-tuple (desc, code) describing a process's status"""
935 935 if os.WIFEXITED(code):
936 936 val = os.WEXITSTATUS(code)
937 937 return _("exited with status %d") % val, val
938 938 elif os.WIFSIGNALED(code):
939 939 val = os.WTERMSIG(code)
940 940 return _("killed by signal %d") % val, val
941 941 elif os.WIFSTOPPED(code):
942 942 val = os.WSTOPSIG(code)
943 943 return _("stopped by signal %d") % val, val
944 944 raise ValueError(_("invalid exit code"))
945 945
946 946 def isowner(fp, st=None):
947 947 """Return True if the file object f belongs to the current user.
948 948
949 949 The return value of a util.fstat(f) may be passed as the st argument.
950 950 """
951 951 if st is None:
952 952 st = fstat(fp)
953 953 return st.st_uid == os.getuid()
954 954
955 955 def _buildencodefun():
956 956 e = '_'
957 957 win_reserved = [ord(x) for x in '\\:*?"<>|']
958 958 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
959 959 for x in (range(32) + range(126, 256) + win_reserved):
960 960 cmap[chr(x)] = "~%02x" % x
961 961 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
962 962 cmap[chr(x)] = e + chr(x).lower()
963 963 dmap = {}
964 964 for k, v in cmap.iteritems():
965 965 dmap[v] = k
966 966 def decode(s):
967 967 i = 0
968 968 while i < len(s):
969 969 for l in xrange(1, 4):
970 970 try:
971 971 yield dmap[s[i:i+l]]
972 972 i += l
973 973 break
974 974 except KeyError:
975 975 pass
976 976 else:
977 977 raise KeyError
978 978 return (lambda s: "".join([cmap[c] for c in s]),
979 979 lambda s: "".join(list(decode(s))))
980 980
981 981 encodefilename, decodefilename = _buildencodefun()
982 982
983 983 def encodedopener(openerfn, fn):
984 984 def o(path, *args, **kw):
985 985 return openerfn(fn(path), *args, **kw)
986 986 return o
987 987
988 988 def opener(base, audit=True):
989 989 """
990 990 return a function that opens files relative to base
991 991
992 992 this function is used to hide the details of COW semantics and
993 993 remote file access from higher level code.
994 994 """
995 995 p = base
996 996 audit_p = audit
997 997
998 998 def mktempcopy(name):
999 999 d, fn = os.path.split(name)
1000 1000 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1001 1001 os.close(fd)
1002 1002 ofp = posixfile(temp, "wb")
1003 1003 try:
1004 1004 try:
1005 1005 ifp = posixfile(name, "rb")
1006 1006 except IOError, inst:
1007 1007 if not getattr(inst, 'filename', None):
1008 1008 inst.filename = name
1009 1009 raise
1010 1010 for chunk in filechunkiter(ifp):
1011 1011 ofp.write(chunk)
1012 1012 ifp.close()
1013 1013 ofp.close()
1014 1014 except:
1015 1015 try: os.unlink(temp)
1016 1016 except: pass
1017 1017 raise
1018 1018 st = os.lstat(name)
1019 1019 os.chmod(temp, st.st_mode)
1020 1020 return temp
1021 1021
1022 1022 class atomictempfile(posixfile):
1023 1023 """the file will only be copied when rename is called"""
1024 1024 def __init__(self, name, mode):
1025 1025 self.__name = name
1026 1026 self.temp = mktempcopy(name)
1027 1027 posixfile.__init__(self, self.temp, mode)
1028 1028 def rename(self):
1029 1029 if not self.closed:
1030 1030 posixfile.close(self)
1031 1031 rename(self.temp, localpath(self.__name))
1032 1032 def __del__(self):
1033 1033 if not self.closed:
1034 1034 try:
1035 1035 os.unlink(self.temp)
1036 1036 except: pass
1037 1037 posixfile.close(self)
1038 1038
1039 1039 class atomicfile(atomictempfile):
1040 1040 """the file will only be copied on close"""
1041 1041 def __init__(self, name, mode):
1042 1042 atomictempfile.__init__(self, name, mode)
1043 1043 def close(self):
1044 1044 self.rename()
1045 1045 def __del__(self):
1046 1046 self.rename()
1047 1047
1048 1048 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
1049 1049 if audit_p:
1050 1050 audit_path(path)
1051 1051 f = os.path.join(p, path)
1052 1052
1053 1053 if not text:
1054 1054 mode += "b" # for that other OS
1055 1055
1056 1056 if mode[0] != "r":
1057 1057 try:
1058 1058 nlink = nlinks(f)
1059 1059 except OSError:
1060 1060 d = os.path.dirname(f)
1061 1061 if not os.path.isdir(d):
1062 1062 os.makedirs(d)
1063 1063 else:
1064 1064 if atomic:
1065 1065 return atomicfile(f, mode)
1066 1066 elif atomictemp:
1067 1067 return atomictempfile(f, mode)
1068 1068 if nlink > 1:
1069 1069 rename(mktempcopy(f), f)
1070 1070 return posixfile(f, mode)
1071 1071
1072 1072 return o
1073 1073
1074 1074 class chunkbuffer(object):
1075 1075 """Allow arbitrary sized chunks of data to be efficiently read from an
1076 1076 iterator over chunks of arbitrary size."""
1077 1077
1078 1078 def __init__(self, in_iter, targetsize = 2**16):
1079 1079 """in_iter is the iterator that's iterating over the input chunks.
1080 1080 targetsize is how big a buffer to try to maintain."""
1081 1081 self.in_iter = iter(in_iter)
1082 1082 self.buf = ''
1083 1083 self.targetsize = int(targetsize)
1084 1084 if self.targetsize <= 0:
1085 1085 raise ValueError(_("targetsize must be greater than 0, was %d") %
1086 1086 targetsize)
1087 1087 self.iterempty = False
1088 1088
1089 1089 def fillbuf(self):
1090 1090 """Ignore target size; read every chunk from iterator until empty."""
1091 1091 if not self.iterempty:
1092 1092 collector = cStringIO.StringIO()
1093 1093 collector.write(self.buf)
1094 1094 for ch in self.in_iter:
1095 1095 collector.write(ch)
1096 1096 self.buf = collector.getvalue()
1097 1097 self.iterempty = True
1098 1098
1099 1099 def read(self, l):
1100 1100 """Read L bytes of data from the iterator of chunks of data.
1101 1101 Returns less than L bytes if the iterator runs dry."""
1102 1102 if l > len(self.buf) and not self.iterempty:
1103 1103 # Clamp to a multiple of self.targetsize
1104 1104 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1105 1105 collector = cStringIO.StringIO()
1106 1106 collector.write(self.buf)
1107 1107 collected = len(self.buf)
1108 1108 for chunk in self.in_iter:
1109 1109 collector.write(chunk)
1110 1110 collected += len(chunk)
1111 1111 if collected >= targetsize:
1112 1112 break
1113 1113 if collected < targetsize:
1114 1114 self.iterempty = True
1115 1115 self.buf = collector.getvalue()
1116 1116 s, self.buf = self.buf[:l], buffer(self.buf, l)
1117 1117 return s
1118 1118
1119 1119 def filechunkiter(f, size=65536, limit=None):
1120 1120 """Create a generator that produces the data in the file size
1121 1121 (default 65536) bytes at a time, up to optional limit (default is
1122 1122 to read all data). Chunks may be less than size bytes if the
1123 1123 chunk is the last chunk in the file, or the file is a socket or
1124 1124 some other type of file that sometimes reads less data than is
1125 1125 requested."""
1126 1126 assert size >= 0
1127 1127 assert limit is None or limit >= 0
1128 1128 while True:
1129 1129 if limit is None: nbytes = size
1130 1130 else: nbytes = min(limit, size)
1131 1131 s = nbytes and f.read(nbytes)
1132 1132 if not s: break
1133 1133 if limit: limit -= len(s)
1134 1134 yield s
1135 1135
1136 1136 def makedate():
1137 1137 lt = time.localtime()
1138 1138 if lt[8] == 1 and time.daylight:
1139 1139 tz = time.altzone
1140 1140 else:
1141 1141 tz = time.timezone
1142 1142 return time.mktime(lt), tz
1143 1143
1144 1144 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1145 1145 """represent a (unixtime, offset) tuple as a localized time.
1146 1146 unixtime is seconds since the epoch, and offset is the time zone's
1147 1147 number of seconds away from UTC. if timezone is false, do not
1148 1148 append time zone to string."""
1149 1149 t, tz = date or makedate()
1150 1150 s = time.strftime(format, time.gmtime(float(t) - tz))
1151 1151 if timezone:
1152 1152 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1153 1153 return s
1154 1154
1155 1155 def strdate(string, format, defaults):
1156 1156 """parse a localized time string and return a (unixtime, offset) tuple.
1157 1157 if the string cannot be parsed, ValueError is raised."""
1158 1158 def timezone(string):
1159 1159 tz = string.split()[-1]
1160 1160 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1161 1161 tz = int(tz)
1162 1162 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1163 1163 return offset
1164 1164 if tz == "GMT" or tz == "UTC":
1165 1165 return 0
1166 1166 return None
1167 1167
1168 1168 # NOTE: unixtime = localunixtime + offset
1169 1169 offset, date = timezone(string), string
1170 1170 if offset != None:
1171 1171 date = " ".join(string.split()[:-1])
1172 1172
1173 1173 # add missing elements from defaults
1174 1174 for part in defaults:
1175 1175 found = [True for p in part if ("%"+p) in format]
1176 1176 if not found:
1177 1177 date += "@" + defaults[part]
1178 1178 format += "@%" + part[0]
1179 1179
1180 1180 timetuple = time.strptime(date, format)
1181 1181 localunixtime = int(calendar.timegm(timetuple))
1182 1182 if offset is None:
1183 1183 # local timezone
1184 1184 unixtime = int(time.mktime(timetuple))
1185 1185 offset = unixtime - localunixtime
1186 1186 else:
1187 1187 unixtime = localunixtime + offset
1188 1188 return unixtime, offset
1189 1189
1190 1190 def parsedate(string, formats=None, defaults=None):
1191 1191 """parse a localized time string and return a (unixtime, offset) tuple.
1192 1192 The date may be a "unixtime offset" string or in one of the specified
1193 1193 formats."""
1194 1194 if not string:
1195 1195 return 0, 0
1196 1196 if not formats:
1197 1197 formats = defaultdateformats
1198 1198 string = string.strip()
1199 1199 try:
1200 1200 when, offset = map(int, string.split(' '))
1201 1201 except ValueError:
1202 1202 # fill out defaults
1203 1203 if not defaults:
1204 1204 defaults = {}
1205 1205 now = makedate()
1206 1206 for part in "d mb yY HI M S".split():
1207 1207 if part not in defaults:
1208 1208 if part[0] in "HMS":
1209 1209 defaults[part] = "00"
1210 1210 elif part[0] in "dm":
1211 1211 defaults[part] = "1"
1212 1212 else:
1213 1213 defaults[part] = datestr(now, "%" + part[0], False)
1214 1214
1215 1215 for format in formats:
1216 1216 try:
1217 1217 when, offset = strdate(string, format, defaults)
1218 1218 except ValueError:
1219 1219 pass
1220 1220 else:
1221 1221 break
1222 1222 else:
1223 1223 raise Abort(_('invalid date: %r ') % string)
1224 1224 # validate explicit (probably user-specified) date and
1225 1225 # time zone offset. values must fit in signed 32 bits for
1226 1226 # current 32-bit linux runtimes. timezones go from UTC-12
1227 1227 # to UTC+14
1228 1228 if abs(when) > 0x7fffffff:
1229 1229 raise Abort(_('date exceeds 32 bits: %d') % when)
1230 1230 if offset < -50400 or offset > 43200:
1231 1231 raise Abort(_('impossible time zone offset: %d') % offset)
1232 1232 return when, offset
1233 1233
1234 1234 def matchdate(date):
1235 1235 """Return a function that matches a given date match specifier
1236 1236
1237 1237 Formats include:
1238 1238
1239 1239 '{date}' match a given date to the accuracy provided
1240 1240
1241 1241 '<{date}' on or before a given date
1242 1242
1243 1243 '>{date}' on or after a given date
1244 1244
1245 1245 """
1246 1246
1247 1247 def lower(date):
1248 1248 return parsedate(date, extendeddateformats)[0]
1249 1249
1250 1250 def upper(date):
1251 1251 d = dict(mb="12", HI="23", M="59", S="59")
1252 1252 for days in "31 30 29".split():
1253 1253 try:
1254 1254 d["d"] = days
1255 1255 return parsedate(date, extendeddateformats, d)[0]
1256 1256 except:
1257 1257 pass
1258 1258 d["d"] = "28"
1259 1259 return parsedate(date, extendeddateformats, d)[0]
1260 1260
1261 1261 if date[0] == "<":
1262 1262 when = upper(date[1:])
1263 1263 return lambda x: x <= when
1264 1264 elif date[0] == ">":
1265 1265 when = lower(date[1:])
1266 1266 return lambda x: x >= when
1267 1267 elif date[0] == "-":
1268 1268 try:
1269 1269 days = int(date[1:])
1270 1270 except ValueError:
1271 1271 raise Abort(_("invalid day spec: %s") % date[1:])
1272 1272 when = makedate()[0] - days * 3600 * 24
1273 1273 return lambda x: x >= when
1274 1274 elif " to " in date:
1275 1275 a, b = date.split(" to ")
1276 1276 start, stop = lower(a), upper(b)
1277 1277 return lambda x: x >= start and x <= stop
1278 1278 else:
1279 1279 start, stop = lower(date), upper(date)
1280 1280 return lambda x: x >= start and x <= stop
1281 1281
1282 1282 def shortuser(user):
1283 1283 """Return a short representation of a user name or email address."""
1284 1284 f = user.find('@')
1285 1285 if f >= 0:
1286 1286 user = user[:f]
1287 1287 f = user.find('<')
1288 1288 if f >= 0:
1289 1289 user = user[f+1:]
1290 1290 f = user.find(' ')
1291 1291 if f >= 0:
1292 1292 user = user[:f]
1293 1293 f = user.find('.')
1294 1294 if f >= 0:
1295 1295 user = user[:f]
1296 1296 return user
1297 1297
1298 1298 def ellipsis(text, maxlength=400):
1299 1299 """Trim string to at most maxlength (default: 400) characters."""
1300 1300 if len(text) <= maxlength:
1301 1301 return text
1302 1302 else:
1303 1303 return "%s..." % (text[:maxlength-3])
1304 1304
1305 1305 def walkrepos(path):
1306 1306 '''yield every hg repository under path, recursively.'''
1307 1307 def errhandler(err):
1308 1308 if err.filename == path:
1309 1309 raise err
1310 1310
1311 1311 for root, dirs, files in os.walk(path, onerror=errhandler):
1312 1312 for d in dirs:
1313 1313 if d == '.hg':
1314 1314 yield root
1315 1315 dirs[:] = []
1316 1316 break
1317 1317
1318 1318 _rcpath = None
1319 1319
1320 1320 def rcpath():
1321 1321 '''return hgrc search path. if env var HGRCPATH is set, use it.
1322 1322 for each item in path, if directory, use files ending in .rc,
1323 1323 else use item.
1324 1324 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1325 1325 if no HGRCPATH, use default os-specific path.'''
1326 1326 global _rcpath
1327 1327 if _rcpath is None:
1328 1328 if 'HGRCPATH' in os.environ:
1329 1329 _rcpath = []
1330 1330 for p in os.environ['HGRCPATH'].split(os.pathsep):
1331 1331 if not p: continue
1332 1332 if os.path.isdir(p):
1333 1333 for f in os.listdir(p):
1334 1334 if f.endswith('.rc'):
1335 1335 _rcpath.append(os.path.join(p, f))
1336 1336 else:
1337 1337 _rcpath.append(p)
1338 1338 else:
1339 1339 _rcpath = os_rcpath()
1340 1340 return _rcpath
1341 1341
1342 1342 def bytecount(nbytes):
1343 1343 '''return byte count formatted as readable string, with units'''
1344 1344
1345 1345 units = (
1346 1346 (100, 1<<30, _('%.0f GB')),
1347 1347 (10, 1<<30, _('%.1f GB')),
1348 1348 (1, 1<<30, _('%.2f GB')),
1349 1349 (100, 1<<20, _('%.0f MB')),
1350 1350 (10, 1<<20, _('%.1f MB')),
1351 1351 (1, 1<<20, _('%.2f MB')),
1352 1352 (100, 1<<10, _('%.0f KB')),
1353 1353 (10, 1<<10, _('%.1f KB')),
1354 1354 (1, 1<<10, _('%.2f KB')),
1355 1355 (1, 1, _('%.0f bytes')),
1356 1356 )
1357 1357
1358 1358 for multiplier, divisor, format in units:
1359 1359 if nbytes >= divisor * multiplier:
1360 1360 return format % (nbytes / float(divisor))
1361 1361 return units[-1][2] % nbytes
1362 1362
1363 1363 def drop_scheme(scheme, path):
1364 1364 sc = scheme + ':'
1365 1365 if path.startswith(sc):
1366 1366 path = path[len(sc):]
1367 1367 if path.startswith('//'):
1368 1368 path = path[2:]
1369 1369 return path
General Comments 0
You need to be logged in to leave comments. Login now