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