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