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