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