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