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