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