##// END OF EJS Templates
util: Fix date format for 12-hour time.
Carey Evans -
r9383:7116494c default
parent child Browse files
Show More
@@ -1,1284 +1,1284 b''
1 1 # util.py - Mercurial utility functions and platform specfic implementations
2 2 #
3 3 # Copyright 2005 K. Thananchayan <thananck@yahoo.com>
4 4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 5 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2, incorporated herein by reference.
9 9
10 10 """Mercurial utility functions and platform specfic implementations.
11 11
12 12 This contains helper routines that are independent of the SCM core and
13 13 hide platform-specific details from the core.
14 14 """
15 15
16 16 from i18n import _
17 17 import error, osutil
18 18 import cStringIO, errno, re, shutil, sys, tempfile, traceback
19 19 import os, stat, time, calendar, random, textwrap
20 20 import imp
21 21
22 22 # Python compatibility
23 23
24 24 def sha1(s):
25 25 return _fastsha1(s)
26 26
27 27 def _fastsha1(s):
28 28 # This function will import sha1 from hashlib or sha (whichever is
29 29 # available) and overwrite itself with it on the first call.
30 30 # Subsequent calls will go directly to the imported function.
31 31 try:
32 32 from hashlib import sha1 as _sha1
33 33 except ImportError:
34 34 from sha import sha as _sha1
35 35 global _fastsha1, sha1
36 36 _fastsha1 = sha1 = _sha1
37 37 return _sha1(s)
38 38
39 39 import subprocess
40 40 closefds = os.name == 'posix'
41 41 def popen2(cmd):
42 42 # Setting bufsize to -1 lets the system decide the buffer size.
43 43 # The default for bufsize is 0, meaning unbuffered. This leads to
44 44 # poor performance on Mac OS X: http://bugs.python.org/issue4194
45 45 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
46 46 close_fds=closefds,
47 47 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
48 48 return p.stdin, p.stdout
49 49 def popen3(cmd):
50 50 p = subprocess.Popen(cmd, shell=True, bufsize=-1,
51 51 close_fds=closefds,
52 52 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
53 53 stderr=subprocess.PIPE)
54 54 return p.stdin, p.stdout, p.stderr
55 55
56 56 def version():
57 57 """Return version information if available."""
58 58 try:
59 59 import __version__
60 60 return __version__.version
61 61 except ImportError:
62 62 return 'unknown'
63 63
64 64 # used by parsedate
65 65 defaultdateformats = (
66 66 '%Y-%m-%d %H:%M:%S',
67 67 '%Y-%m-%d %I:%M:%S%p',
68 68 '%Y-%m-%d %H:%M',
69 69 '%Y-%m-%d %I:%M%p',
70 70 '%Y-%m-%d',
71 71 '%m-%d',
72 72 '%m/%d',
73 73 '%m/%d/%y',
74 74 '%m/%d/%Y',
75 75 '%a %b %d %H:%M:%S %Y',
76 76 '%a %b %d %I:%M:%S%p %Y',
77 77 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
78 78 '%b %d %H:%M:%S %Y',
79 79 '%b %d %I:%M:%S%p %Y',
80 80 '%b %d %H:%M:%S',
81 81 '%b %d %I:%M:%S%p',
82 82 '%b %d %H:%M',
83 83 '%b %d %I:%M%p',
84 84 '%b %d %Y',
85 85 '%b %d',
86 86 '%H:%M:%S',
87 '%I:%M:%SP',
87 '%I:%M:%S%p',
88 88 '%H:%M',
89 89 '%I:%M%p',
90 90 )
91 91
92 92 extendeddateformats = defaultdateformats + (
93 93 "%Y",
94 94 "%Y-%m",
95 95 "%b",
96 96 "%b %Y",
97 97 )
98 98
99 99 def cachefunc(func):
100 100 '''cache the result of function calls'''
101 101 # XXX doesn't handle keywords args
102 102 cache = {}
103 103 if func.func_code.co_argcount == 1:
104 104 # we gain a small amount of time because
105 105 # we don't need to pack/unpack the list
106 106 def f(arg):
107 107 if arg not in cache:
108 108 cache[arg] = func(arg)
109 109 return cache[arg]
110 110 else:
111 111 def f(*args):
112 112 if args not in cache:
113 113 cache[args] = func(*args)
114 114 return cache[args]
115 115
116 116 return f
117 117
118 118 def lrucachefunc(func):
119 119 '''cache most recent results of function calls'''
120 120 cache = {}
121 121 order = []
122 122 if func.func_code.co_argcount == 1:
123 123 def f(arg):
124 124 if arg not in cache:
125 125 if len(cache) > 20:
126 126 del cache[order.pop(0)]
127 127 cache[arg] = func(arg)
128 128 else:
129 129 order.remove(arg)
130 130 order.append(arg)
131 131 return cache[arg]
132 132 else:
133 133 def f(*args):
134 134 if args not in cache:
135 135 if len(cache) > 20:
136 136 del cache[order.pop(0)]
137 137 cache[args] = func(*args)
138 138 else:
139 139 order.remove(args)
140 140 order.append(args)
141 141 return cache[args]
142 142
143 143 return f
144 144
145 145 class propertycache(object):
146 146 def __init__(self, func):
147 147 self.func = func
148 148 self.name = func.__name__
149 149 def __get__(self, obj, type=None):
150 150 result = self.func(obj)
151 151 setattr(obj, self.name, result)
152 152 return result
153 153
154 154 def pipefilter(s, cmd):
155 155 '''filter string S through command CMD, returning its output'''
156 156 p = subprocess.Popen(cmd, shell=True, close_fds=closefds,
157 157 stdin=subprocess.PIPE, stdout=subprocess.PIPE)
158 158 pout, perr = p.communicate(s)
159 159 return pout
160 160
161 161 def tempfilter(s, cmd):
162 162 '''filter string S through a pair of temporary files with CMD.
163 163 CMD is used as a template to create the real command to be run,
164 164 with the strings INFILE and OUTFILE replaced by the real names of
165 165 the temporary files generated.'''
166 166 inname, outname = None, None
167 167 try:
168 168 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
169 169 fp = os.fdopen(infd, 'wb')
170 170 fp.write(s)
171 171 fp.close()
172 172 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
173 173 os.close(outfd)
174 174 cmd = cmd.replace('INFILE', inname)
175 175 cmd = cmd.replace('OUTFILE', outname)
176 176 code = os.system(cmd)
177 177 if sys.platform == 'OpenVMS' and code & 1:
178 178 code = 0
179 179 if code: raise Abort(_("command '%s' failed: %s") %
180 180 (cmd, explain_exit(code)))
181 181 return open(outname, 'rb').read()
182 182 finally:
183 183 try:
184 184 if inname: os.unlink(inname)
185 185 except: pass
186 186 try:
187 187 if outname: os.unlink(outname)
188 188 except: pass
189 189
190 190 filtertable = {
191 191 'tempfile:': tempfilter,
192 192 'pipe:': pipefilter,
193 193 }
194 194
195 195 def filter(s, cmd):
196 196 "filter a string through a command that transforms its input to its output"
197 197 for name, fn in filtertable.iteritems():
198 198 if cmd.startswith(name):
199 199 return fn(s, cmd[len(name):].lstrip())
200 200 return pipefilter(s, cmd)
201 201
202 202 def binary(s):
203 203 """return true if a string is binary data"""
204 204 return bool(s and '\0' in s)
205 205
206 206 def increasingchunks(source, min=1024, max=65536):
207 207 '''return no less than min bytes per chunk while data remains,
208 208 doubling min after each chunk until it reaches max'''
209 209 def log2(x):
210 210 if not x:
211 211 return 0
212 212 i = 0
213 213 while x:
214 214 x >>= 1
215 215 i += 1
216 216 return i - 1
217 217
218 218 buf = []
219 219 blen = 0
220 220 for chunk in source:
221 221 buf.append(chunk)
222 222 blen += len(chunk)
223 223 if blen >= min:
224 224 if min < max:
225 225 min = min << 1
226 226 nmin = 1 << log2(blen)
227 227 if nmin > min:
228 228 min = nmin
229 229 if min > max:
230 230 min = max
231 231 yield ''.join(buf)
232 232 blen = 0
233 233 buf = []
234 234 if buf:
235 235 yield ''.join(buf)
236 236
237 237 Abort = error.Abort
238 238
239 239 def always(fn): return True
240 240 def never(fn): return False
241 241
242 242 def pathto(root, n1, n2):
243 243 '''return the relative path from one place to another.
244 244 root should use os.sep to separate directories
245 245 n1 should use os.sep to separate directories
246 246 n2 should use "/" to separate directories
247 247 returns an os.sep-separated path.
248 248
249 249 If n1 is a relative path, it's assumed it's
250 250 relative to root.
251 251 n2 should always be relative to root.
252 252 '''
253 253 if not n1: return localpath(n2)
254 254 if os.path.isabs(n1):
255 255 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
256 256 return os.path.join(root, localpath(n2))
257 257 n2 = '/'.join((pconvert(root), n2))
258 258 a, b = splitpath(n1), n2.split('/')
259 259 a.reverse()
260 260 b.reverse()
261 261 while a and b and a[-1] == b[-1]:
262 262 a.pop()
263 263 b.pop()
264 264 b.reverse()
265 265 return os.sep.join((['..'] * len(a)) + b) or '.'
266 266
267 267 def canonpath(root, cwd, myname):
268 268 """return the canonical path of myname, given cwd and root"""
269 269 if root == os.sep:
270 270 rootsep = os.sep
271 271 elif endswithsep(root):
272 272 rootsep = root
273 273 else:
274 274 rootsep = root + os.sep
275 275 name = myname
276 276 if not os.path.isabs(name):
277 277 name = os.path.join(root, cwd, name)
278 278 name = os.path.normpath(name)
279 279 audit_path = path_auditor(root)
280 280 if name != rootsep and name.startswith(rootsep):
281 281 name = name[len(rootsep):]
282 282 audit_path(name)
283 283 return pconvert(name)
284 284 elif name == root:
285 285 return ''
286 286 else:
287 287 # Determine whether `name' is in the hierarchy at or beneath `root',
288 288 # by iterating name=dirname(name) until that causes no change (can't
289 289 # check name == '/', because that doesn't work on windows). For each
290 290 # `name', compare dev/inode numbers. If they match, the list `rel'
291 291 # holds the reversed list of components making up the relative file
292 292 # name we want.
293 293 root_st = os.stat(root)
294 294 rel = []
295 295 while True:
296 296 try:
297 297 name_st = os.stat(name)
298 298 except OSError:
299 299 break
300 300 if samestat(name_st, root_st):
301 301 if not rel:
302 302 # name was actually the same as root (maybe a symlink)
303 303 return ''
304 304 rel.reverse()
305 305 name = os.path.join(*rel)
306 306 audit_path(name)
307 307 return pconvert(name)
308 308 dirname, basename = os.path.split(name)
309 309 rel.append(basename)
310 310 if dirname == name:
311 311 break
312 312 name = dirname
313 313
314 314 raise Abort('%s not under root' % myname)
315 315
316 316 _hgexecutable = None
317 317
318 318 def main_is_frozen():
319 319 """return True if we are a frozen executable.
320 320
321 321 The code supports py2exe (most common, Windows only) and tools/freeze
322 322 (portable, not much used).
323 323 """
324 324 return (hasattr(sys, "frozen") or # new py2exe
325 325 hasattr(sys, "importers") or # old py2exe
326 326 imp.is_frozen("__main__")) # tools/freeze
327 327
328 328 def hgexecutable():
329 329 """return location of the 'hg' executable.
330 330
331 331 Defaults to $HG or 'hg' in the search path.
332 332 """
333 333 if _hgexecutable is None:
334 334 hg = os.environ.get('HG')
335 335 if hg:
336 336 set_hgexecutable(hg)
337 337 elif main_is_frozen():
338 338 set_hgexecutable(sys.executable)
339 339 else:
340 340 set_hgexecutable(find_exe('hg') or 'hg')
341 341 return _hgexecutable
342 342
343 343 def set_hgexecutable(path):
344 344 """set location of the 'hg' executable"""
345 345 global _hgexecutable
346 346 _hgexecutable = path
347 347
348 348 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
349 349 '''enhanced shell command execution.
350 350 run with environment maybe modified, maybe in different dir.
351 351
352 352 if command fails and onerr is None, return status. if ui object,
353 353 print error message and return status, else raise onerr object as
354 354 exception.'''
355 355 def py2shell(val):
356 356 'convert python object into string that is useful to shell'
357 357 if val is None or val is False:
358 358 return '0'
359 359 if val is True:
360 360 return '1'
361 361 return str(val)
362 362 oldenv = {}
363 363 for k in environ:
364 364 oldenv[k] = os.environ.get(k)
365 365 if cwd is not None:
366 366 oldcwd = os.getcwd()
367 367 origcmd = cmd
368 368 if os.name == 'nt':
369 369 cmd = '"%s"' % cmd
370 370 try:
371 371 for k, v in environ.iteritems():
372 372 os.environ[k] = py2shell(v)
373 373 os.environ['HG'] = hgexecutable()
374 374 if cwd is not None and oldcwd != cwd:
375 375 os.chdir(cwd)
376 376 rc = os.system(cmd)
377 377 if sys.platform == 'OpenVMS' and rc & 1:
378 378 rc = 0
379 379 if rc and onerr:
380 380 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
381 381 explain_exit(rc)[0])
382 382 if errprefix:
383 383 errmsg = '%s: %s' % (errprefix, errmsg)
384 384 try:
385 385 onerr.warn(errmsg + '\n')
386 386 except AttributeError:
387 387 raise onerr(errmsg)
388 388 return rc
389 389 finally:
390 390 for k, v in oldenv.iteritems():
391 391 if v is None:
392 392 del os.environ[k]
393 393 else:
394 394 os.environ[k] = v
395 395 if cwd is not None and oldcwd != cwd:
396 396 os.chdir(oldcwd)
397 397
398 398 def checksignature(func):
399 399 '''wrap a function with code to check for calling errors'''
400 400 def check(*args, **kwargs):
401 401 try:
402 402 return func(*args, **kwargs)
403 403 except TypeError:
404 404 if len(traceback.extract_tb(sys.exc_info()[2])) == 1:
405 405 raise error.SignatureError
406 406 raise
407 407
408 408 return check
409 409
410 410 # os.path.lexists is not available on python2.3
411 411 def lexists(filename):
412 412 "test whether a file with this name exists. does not follow symlinks"
413 413 try:
414 414 os.lstat(filename)
415 415 except:
416 416 return False
417 417 return True
418 418
419 419 def rename(src, dst):
420 420 """forcibly rename a file"""
421 421 try:
422 422 os.rename(src, dst)
423 423 except OSError, err: # FIXME: check err (EEXIST ?)
424 424
425 425 # On windows, rename to existing file is not allowed, so we
426 426 # must delete destination first. But if a file is open, unlink
427 427 # schedules it for delete but does not delete it. Rename
428 428 # happens immediately even for open files, so we rename
429 429 # destination to a temporary name, then delete that. Then
430 430 # rename is safe to do.
431 431 # The temporary name is chosen at random to avoid the situation
432 432 # where a file is left lying around from a previous aborted run.
433 433 # The usual race condition this introduces can't be avoided as
434 434 # we need the name to rename into, and not the file itself. Due
435 435 # to the nature of the operation however, any races will at worst
436 436 # lead to the rename failing and the current operation aborting.
437 437
438 438 def tempname(prefix):
439 439 for tries in xrange(10):
440 440 temp = '%s-%08x' % (prefix, random.randint(0, 0xffffffff))
441 441 if not os.path.exists(temp):
442 442 return temp
443 443 raise IOError, (errno.EEXIST, "No usable temporary filename found")
444 444
445 445 temp = tempname(dst)
446 446 os.rename(dst, temp)
447 447 os.unlink(temp)
448 448 os.rename(src, dst)
449 449
450 450 def unlink(f):
451 451 """unlink and remove the directory if it is empty"""
452 452 os.unlink(f)
453 453 # try removing directories that might now be empty
454 454 try:
455 455 os.removedirs(os.path.dirname(f))
456 456 except OSError:
457 457 pass
458 458
459 459 def copyfile(src, dest):
460 460 "copy a file, preserving mode and atime/mtime"
461 461 if os.path.islink(src):
462 462 try:
463 463 os.unlink(dest)
464 464 except:
465 465 pass
466 466 os.symlink(os.readlink(src), dest)
467 467 else:
468 468 try:
469 469 shutil.copyfile(src, dest)
470 470 shutil.copystat(src, dest)
471 471 except shutil.Error, inst:
472 472 raise Abort(str(inst))
473 473
474 474 def copyfiles(src, dst, hardlink=None):
475 475 """Copy a directory tree using hardlinks if possible"""
476 476
477 477 if hardlink is None:
478 478 hardlink = (os.stat(src).st_dev ==
479 479 os.stat(os.path.dirname(dst)).st_dev)
480 480
481 481 if os.path.isdir(src):
482 482 os.mkdir(dst)
483 483 for name, kind in osutil.listdir(src):
484 484 srcname = os.path.join(src, name)
485 485 dstname = os.path.join(dst, name)
486 486 copyfiles(srcname, dstname, hardlink)
487 487 else:
488 488 if hardlink:
489 489 try:
490 490 os_link(src, dst)
491 491 except (IOError, OSError):
492 492 hardlink = False
493 493 shutil.copy(src, dst)
494 494 else:
495 495 shutil.copy(src, dst)
496 496
497 497 class path_auditor(object):
498 498 '''ensure that a filesystem path contains no banned components.
499 499 the following properties of a path are checked:
500 500
501 501 - under top-level .hg
502 502 - starts at the root of a windows drive
503 503 - contains ".."
504 504 - traverses a symlink (e.g. a/symlink_here/b)
505 505 - inside a nested repository'''
506 506
507 507 def __init__(self, root):
508 508 self.audited = set()
509 509 self.auditeddir = set()
510 510 self.root = root
511 511
512 512 def __call__(self, path):
513 513 if path in self.audited:
514 514 return
515 515 normpath = os.path.normcase(path)
516 516 parts = splitpath(normpath)
517 517 if (os.path.splitdrive(path)[0]
518 518 or parts[0].lower() in ('.hg', '.hg.', '')
519 519 or os.pardir in parts):
520 520 raise Abort(_("path contains illegal component: %s") % path)
521 521 if '.hg' in path.lower():
522 522 lparts = [p.lower() for p in parts]
523 523 for p in '.hg', '.hg.':
524 524 if p in lparts[1:]:
525 525 pos = lparts.index(p)
526 526 base = os.path.join(*parts[:pos])
527 527 raise Abort(_('path %r is inside repo %r') % (path, base))
528 528 def check(prefix):
529 529 curpath = os.path.join(self.root, prefix)
530 530 try:
531 531 st = os.lstat(curpath)
532 532 except OSError, err:
533 533 # EINVAL can be raised as invalid path syntax under win32.
534 534 # They must be ignored for patterns can be checked too.
535 535 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
536 536 raise
537 537 else:
538 538 if stat.S_ISLNK(st.st_mode):
539 539 raise Abort(_('path %r traverses symbolic link %r') %
540 540 (path, prefix))
541 541 elif (stat.S_ISDIR(st.st_mode) and
542 542 os.path.isdir(os.path.join(curpath, '.hg'))):
543 543 raise Abort(_('path %r is inside repo %r') %
544 544 (path, prefix))
545 545 parts.pop()
546 546 prefixes = []
547 547 while parts:
548 548 prefix = os.sep.join(parts)
549 549 if prefix in self.auditeddir:
550 550 break
551 551 check(prefix)
552 552 prefixes.append(prefix)
553 553 parts.pop()
554 554
555 555 self.audited.add(path)
556 556 # only add prefixes to the cache after checking everything: we don't
557 557 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
558 558 self.auditeddir.update(prefixes)
559 559
560 560 def nlinks(pathname):
561 561 """Return number of hardlinks for the given file."""
562 562 return os.lstat(pathname).st_nlink
563 563
564 564 if hasattr(os, 'link'):
565 565 os_link = os.link
566 566 else:
567 567 def os_link(src, dst):
568 568 raise OSError(0, _("Hardlinks not supported"))
569 569
570 570 def lookup_reg(key, name=None, scope=None):
571 571 return None
572 572
573 573 if os.name == 'nt':
574 574 from windows import *
575 575 else:
576 576 from posix import *
577 577
578 578 def makelock(info, pathname):
579 579 try:
580 580 return os.symlink(info, pathname)
581 581 except OSError, why:
582 582 if why.errno == errno.EEXIST:
583 583 raise
584 584 except AttributeError: # no symlink in os
585 585 pass
586 586
587 587 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
588 588 os.write(ld, info)
589 589 os.close(ld)
590 590
591 591 def readlock(pathname):
592 592 try:
593 593 return os.readlink(pathname)
594 594 except OSError, why:
595 595 if why.errno not in (errno.EINVAL, errno.ENOSYS):
596 596 raise
597 597 except AttributeError: # no symlink in os
598 598 pass
599 599 return posixfile(pathname).read()
600 600
601 601 def fstat(fp):
602 602 '''stat file object that may not have fileno method.'''
603 603 try:
604 604 return os.fstat(fp.fileno())
605 605 except AttributeError:
606 606 return os.stat(fp.name)
607 607
608 608 # File system features
609 609
610 610 def checkcase(path):
611 611 """
612 612 Check whether the given path is on a case-sensitive filesystem
613 613
614 614 Requires a path (like /foo/.hg) ending with a foldable final
615 615 directory component.
616 616 """
617 617 s1 = os.stat(path)
618 618 d, b = os.path.split(path)
619 619 p2 = os.path.join(d, b.upper())
620 620 if path == p2:
621 621 p2 = os.path.join(d, b.lower())
622 622 try:
623 623 s2 = os.stat(p2)
624 624 if s2 == s1:
625 625 return False
626 626 return True
627 627 except:
628 628 return True
629 629
630 630 _fspathcache = {}
631 631 def fspath(name, root):
632 632 '''Get name in the case stored in the filesystem
633 633
634 634 The name is either relative to root, or it is an absolute path starting
635 635 with root. Note that this function is unnecessary, and should not be
636 636 called, for case-sensitive filesystems (simply because it's expensive).
637 637 '''
638 638 # If name is absolute, make it relative
639 639 if name.lower().startswith(root.lower()):
640 640 l = len(root)
641 641 if name[l] == os.sep or name[l] == os.altsep:
642 642 l = l + 1
643 643 name = name[l:]
644 644
645 645 if not os.path.exists(os.path.join(root, name)):
646 646 return None
647 647
648 648 seps = os.sep
649 649 if os.altsep:
650 650 seps = seps + os.altsep
651 651 # Protect backslashes. This gets silly very quickly.
652 652 seps.replace('\\','\\\\')
653 653 pattern = re.compile(r'([^%s]+)|([%s]+)' % (seps, seps))
654 654 dir = os.path.normcase(os.path.normpath(root))
655 655 result = []
656 656 for part, sep in pattern.findall(name):
657 657 if sep:
658 658 result.append(sep)
659 659 continue
660 660
661 661 if dir not in _fspathcache:
662 662 _fspathcache[dir] = os.listdir(dir)
663 663 contents = _fspathcache[dir]
664 664
665 665 lpart = part.lower()
666 666 for n in contents:
667 667 if n.lower() == lpart:
668 668 result.append(n)
669 669 break
670 670 else:
671 671 # Cannot happen, as the file exists!
672 672 result.append(part)
673 673 dir = os.path.join(dir, lpart)
674 674
675 675 return ''.join(result)
676 676
677 677 def checkexec(path):
678 678 """
679 679 Check whether the given path is on a filesystem with UNIX-like exec flags
680 680
681 681 Requires a directory (like /foo/.hg)
682 682 """
683 683
684 684 # VFAT on some Linux versions can flip mode but it doesn't persist
685 685 # a FS remount. Frequently we can detect it if files are created
686 686 # with exec bit on.
687 687
688 688 try:
689 689 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
690 690 fh, fn = tempfile.mkstemp("", "", path)
691 691 try:
692 692 os.close(fh)
693 693 m = os.stat(fn).st_mode & 0777
694 694 new_file_has_exec = m & EXECFLAGS
695 695 os.chmod(fn, m ^ EXECFLAGS)
696 696 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
697 697 finally:
698 698 os.unlink(fn)
699 699 except (IOError, OSError):
700 700 # we don't care, the user probably won't be able to commit anyway
701 701 return False
702 702 return not (new_file_has_exec or exec_flags_cannot_flip)
703 703
704 704 def checklink(path):
705 705 """check whether the given path is on a symlink-capable filesystem"""
706 706 # mktemp is not racy because symlink creation will fail if the
707 707 # file already exists
708 708 name = tempfile.mktemp(dir=path)
709 709 try:
710 710 os.symlink(".", name)
711 711 os.unlink(name)
712 712 return True
713 713 except (OSError, AttributeError):
714 714 return False
715 715
716 716 def needbinarypatch():
717 717 """return True if patches should be applied in binary mode by default."""
718 718 return os.name == 'nt'
719 719
720 720 def endswithsep(path):
721 721 '''Check path ends with os.sep or os.altsep.'''
722 722 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
723 723
724 724 def splitpath(path):
725 725 '''Split path by os.sep.
726 726 Note that this function does not use os.altsep because this is
727 727 an alternative of simple "xxx.split(os.sep)".
728 728 It is recommended to use os.path.normpath() before using this
729 729 function if need.'''
730 730 return path.split(os.sep)
731 731
732 732 def gui():
733 733 '''Are we running in a GUI?'''
734 734 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
735 735
736 736 def mktempcopy(name, emptyok=False, createmode=None):
737 737 """Create a temporary file with the same contents from name
738 738
739 739 The permission bits are copied from the original file.
740 740
741 741 If the temporary file is going to be truncated immediately, you
742 742 can use emptyok=True as an optimization.
743 743
744 744 Returns the name of the temporary file.
745 745 """
746 746 d, fn = os.path.split(name)
747 747 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
748 748 os.close(fd)
749 749 # Temporary files are created with mode 0600, which is usually not
750 750 # what we want. If the original file already exists, just copy
751 751 # its mode. Otherwise, manually obey umask.
752 752 try:
753 753 st_mode = os.lstat(name).st_mode & 0777
754 754 except OSError, inst:
755 755 if inst.errno != errno.ENOENT:
756 756 raise
757 757 st_mode = createmode
758 758 if st_mode is None:
759 759 st_mode = ~umask
760 760 st_mode &= 0666
761 761 os.chmod(temp, st_mode)
762 762 if emptyok:
763 763 return temp
764 764 try:
765 765 try:
766 766 ifp = posixfile(name, "rb")
767 767 except IOError, inst:
768 768 if inst.errno == errno.ENOENT:
769 769 return temp
770 770 if not getattr(inst, 'filename', None):
771 771 inst.filename = name
772 772 raise
773 773 ofp = posixfile(temp, "wb")
774 774 for chunk in filechunkiter(ifp):
775 775 ofp.write(chunk)
776 776 ifp.close()
777 777 ofp.close()
778 778 except:
779 779 try: os.unlink(temp)
780 780 except: pass
781 781 raise
782 782 return temp
783 783
784 784 class atomictempfile(object):
785 785 """file-like object that atomically updates a file
786 786
787 787 All writes will be redirected to a temporary copy of the original
788 788 file. When rename is called, the copy is renamed to the original
789 789 name, making the changes visible.
790 790 """
791 791 def __init__(self, name, mode, createmode):
792 792 self.__name = name
793 793 self._fp = None
794 794 self.temp = mktempcopy(name, emptyok=('w' in mode),
795 795 createmode=createmode)
796 796 self._fp = posixfile(self.temp, mode)
797 797
798 798 def __getattr__(self, name):
799 799 return getattr(self._fp, name)
800 800
801 801 def rename(self):
802 802 if not self._fp.closed:
803 803 self._fp.close()
804 804 rename(self.temp, localpath(self.__name))
805 805
806 806 def __del__(self):
807 807 if not self._fp:
808 808 return
809 809 if not self._fp.closed:
810 810 try:
811 811 os.unlink(self.temp)
812 812 except: pass
813 813 self._fp.close()
814 814
815 815 def makedirs(name, mode=None):
816 816 """recursive directory creation with parent mode inheritance"""
817 817 try:
818 818 os.mkdir(name)
819 819 if mode is not None:
820 820 os.chmod(name, mode)
821 821 return
822 822 except OSError, err:
823 823 if err.errno == errno.EEXIST:
824 824 return
825 825 if err.errno != errno.ENOENT:
826 826 raise
827 827 parent = os.path.abspath(os.path.dirname(name))
828 828 makedirs(parent, mode)
829 829 makedirs(name, mode)
830 830
831 831 class opener(object):
832 832 """Open files relative to a base directory
833 833
834 834 This class is used to hide the details of COW semantics and
835 835 remote file access from higher level code.
836 836 """
837 837 def __init__(self, base, audit=True):
838 838 self.base = base
839 839 if audit:
840 840 self.audit_path = path_auditor(base)
841 841 else:
842 842 self.audit_path = always
843 843 self.createmode = None
844 844
845 845 def __getattr__(self, name):
846 846 if name == '_can_symlink':
847 847 self._can_symlink = checklink(self.base)
848 848 return self._can_symlink
849 849 raise AttributeError(name)
850 850
851 851 def _fixfilemode(self, name):
852 852 if self.createmode is None:
853 853 return
854 854 os.chmod(name, self.createmode & 0666)
855 855
856 856 def __call__(self, path, mode="r", text=False, atomictemp=False):
857 857 self.audit_path(path)
858 858 f = os.path.join(self.base, path)
859 859
860 860 if not text and "b" not in mode:
861 861 mode += "b" # for that other OS
862 862
863 863 nlink = -1
864 864 if mode not in ("r", "rb"):
865 865 try:
866 866 nlink = nlinks(f)
867 867 except OSError:
868 868 nlink = 0
869 869 d = os.path.dirname(f)
870 870 if not os.path.isdir(d):
871 871 makedirs(d, self.createmode)
872 872 if atomictemp:
873 873 return atomictempfile(f, mode, self.createmode)
874 874 if nlink > 1:
875 875 rename(mktempcopy(f), f)
876 876 fp = posixfile(f, mode)
877 877 if nlink == 0:
878 878 self._fixfilemode(f)
879 879 return fp
880 880
881 881 def symlink(self, src, dst):
882 882 self.audit_path(dst)
883 883 linkname = os.path.join(self.base, dst)
884 884 try:
885 885 os.unlink(linkname)
886 886 except OSError:
887 887 pass
888 888
889 889 dirname = os.path.dirname(linkname)
890 890 if not os.path.exists(dirname):
891 891 makedirs(dirname, self.createmode)
892 892
893 893 if self._can_symlink:
894 894 try:
895 895 os.symlink(src, linkname)
896 896 except OSError, err:
897 897 raise OSError(err.errno, _('could not symlink to %r: %s') %
898 898 (src, err.strerror), linkname)
899 899 else:
900 900 f = self(dst, "w")
901 901 f.write(src)
902 902 f.close()
903 903 self._fixfilemode(dst)
904 904
905 905 class chunkbuffer(object):
906 906 """Allow arbitrary sized chunks of data to be efficiently read from an
907 907 iterator over chunks of arbitrary size."""
908 908
909 909 def __init__(self, in_iter):
910 910 """in_iter is the iterator that's iterating over the input chunks.
911 911 targetsize is how big a buffer to try to maintain."""
912 912 self.iter = iter(in_iter)
913 913 self.buf = ''
914 914 self.targetsize = 2**16
915 915
916 916 def read(self, l):
917 917 """Read L bytes of data from the iterator of chunks of data.
918 918 Returns less than L bytes if the iterator runs dry."""
919 919 if l > len(self.buf) and self.iter:
920 920 # Clamp to a multiple of self.targetsize
921 921 targetsize = max(l, self.targetsize)
922 922 collector = cStringIO.StringIO()
923 923 collector.write(self.buf)
924 924 collected = len(self.buf)
925 925 for chunk in self.iter:
926 926 collector.write(chunk)
927 927 collected += len(chunk)
928 928 if collected >= targetsize:
929 929 break
930 930 if collected < targetsize:
931 931 self.iter = False
932 932 self.buf = collector.getvalue()
933 933 if len(self.buf) == l:
934 934 s, self.buf = str(self.buf), ''
935 935 else:
936 936 s, self.buf = self.buf[:l], buffer(self.buf, l)
937 937 return s
938 938
939 939 def filechunkiter(f, size=65536, limit=None):
940 940 """Create a generator that produces the data in the file size
941 941 (default 65536) bytes at a time, up to optional limit (default is
942 942 to read all data). Chunks may be less than size bytes if the
943 943 chunk is the last chunk in the file, or the file is a socket or
944 944 some other type of file that sometimes reads less data than is
945 945 requested."""
946 946 assert size >= 0
947 947 assert limit is None or limit >= 0
948 948 while True:
949 949 if limit is None: nbytes = size
950 950 else: nbytes = min(limit, size)
951 951 s = nbytes and f.read(nbytes)
952 952 if not s: break
953 953 if limit: limit -= len(s)
954 954 yield s
955 955
956 956 def makedate():
957 957 lt = time.localtime()
958 958 if lt[8] == 1 and time.daylight:
959 959 tz = time.altzone
960 960 else:
961 961 tz = time.timezone
962 962 return time.mktime(lt), tz
963 963
964 964 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
965 965 """represent a (unixtime, offset) tuple as a localized time.
966 966 unixtime is seconds since the epoch, and offset is the time zone's
967 967 number of seconds away from UTC. if timezone is false, do not
968 968 append time zone to string."""
969 969 t, tz = date or makedate()
970 970 if "%1" in format or "%2" in format:
971 971 sign = (tz > 0) and "-" or "+"
972 972 minutes = abs(tz) / 60
973 973 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
974 974 format = format.replace("%2", "%02d" % (minutes % 60))
975 975 s = time.strftime(format, time.gmtime(float(t) - tz))
976 976 return s
977 977
978 978 def shortdate(date=None):
979 979 """turn (timestamp, tzoff) tuple into iso 8631 date."""
980 980 return datestr(date, format='%Y-%m-%d')
981 981
982 982 def strdate(string, format, defaults=[]):
983 983 """parse a localized time string and return a (unixtime, offset) tuple.
984 984 if the string cannot be parsed, ValueError is raised."""
985 985 def timezone(string):
986 986 tz = string.split()[-1]
987 987 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
988 988 sign = (tz[0] == "+") and 1 or -1
989 989 hours = int(tz[1:3])
990 990 minutes = int(tz[3:5])
991 991 return -sign * (hours * 60 + minutes) * 60
992 992 if tz == "GMT" or tz == "UTC":
993 993 return 0
994 994 return None
995 995
996 996 # NOTE: unixtime = localunixtime + offset
997 997 offset, date = timezone(string), string
998 998 if offset != None:
999 999 date = " ".join(string.split()[:-1])
1000 1000
1001 1001 # add missing elements from defaults
1002 1002 for part in defaults:
1003 1003 found = [True for p in part if ("%"+p) in format]
1004 1004 if not found:
1005 1005 date += "@" + defaults[part]
1006 1006 format += "@%" + part[0]
1007 1007
1008 1008 timetuple = time.strptime(date, format)
1009 1009 localunixtime = int(calendar.timegm(timetuple))
1010 1010 if offset is None:
1011 1011 # local timezone
1012 1012 unixtime = int(time.mktime(timetuple))
1013 1013 offset = unixtime - localunixtime
1014 1014 else:
1015 1015 unixtime = localunixtime + offset
1016 1016 return unixtime, offset
1017 1017
1018 1018 def parsedate(date, formats=None, defaults=None):
1019 1019 """parse a localized date/time string and return a (unixtime, offset) tuple.
1020 1020
1021 1021 The date may be a "unixtime offset" string or in one of the specified
1022 1022 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1023 1023 """
1024 1024 if not date:
1025 1025 return 0, 0
1026 1026 if isinstance(date, tuple) and len(date) == 2:
1027 1027 return date
1028 1028 if not formats:
1029 1029 formats = defaultdateformats
1030 1030 date = date.strip()
1031 1031 try:
1032 1032 when, offset = map(int, date.split(' '))
1033 1033 except ValueError:
1034 1034 # fill out defaults
1035 1035 if not defaults:
1036 1036 defaults = {}
1037 1037 now = makedate()
1038 1038 for part in "d mb yY HI M S".split():
1039 1039 if part not in defaults:
1040 1040 if part[0] in "HMS":
1041 1041 defaults[part] = "00"
1042 1042 else:
1043 1043 defaults[part] = datestr(now, "%" + part[0])
1044 1044
1045 1045 for format in formats:
1046 1046 try:
1047 1047 when, offset = strdate(date, format, defaults)
1048 1048 except (ValueError, OverflowError):
1049 1049 pass
1050 1050 else:
1051 1051 break
1052 1052 else:
1053 1053 raise Abort(_('invalid date: %r ') % date)
1054 1054 # validate explicit (probably user-specified) date and
1055 1055 # time zone offset. values must fit in signed 32 bits for
1056 1056 # current 32-bit linux runtimes. timezones go from UTC-12
1057 1057 # to UTC+14
1058 1058 if abs(when) > 0x7fffffff:
1059 1059 raise Abort(_('date exceeds 32 bits: %d') % when)
1060 1060 if offset < -50400 or offset > 43200:
1061 1061 raise Abort(_('impossible time zone offset: %d') % offset)
1062 1062 return when, offset
1063 1063
1064 1064 def matchdate(date):
1065 1065 """Return a function that matches a given date match specifier
1066 1066
1067 1067 Formats include:
1068 1068
1069 1069 '{date}' match a given date to the accuracy provided
1070 1070
1071 1071 '<{date}' on or before a given date
1072 1072
1073 1073 '>{date}' on or after a given date
1074 1074
1075 1075 """
1076 1076
1077 1077 def lower(date):
1078 1078 d = dict(mb="1", d="1")
1079 1079 return parsedate(date, extendeddateformats, d)[0]
1080 1080
1081 1081 def upper(date):
1082 1082 d = dict(mb="12", HI="23", M="59", S="59")
1083 1083 for days in "31 30 29".split():
1084 1084 try:
1085 1085 d["d"] = days
1086 1086 return parsedate(date, extendeddateformats, d)[0]
1087 1087 except:
1088 1088 pass
1089 1089 d["d"] = "28"
1090 1090 return parsedate(date, extendeddateformats, d)[0]
1091 1091
1092 1092 date = date.strip()
1093 1093 if date[0] == "<":
1094 1094 when = upper(date[1:])
1095 1095 return lambda x: x <= when
1096 1096 elif date[0] == ">":
1097 1097 when = lower(date[1:])
1098 1098 return lambda x: x >= when
1099 1099 elif date[0] == "-":
1100 1100 try:
1101 1101 days = int(date[1:])
1102 1102 except ValueError:
1103 1103 raise Abort(_("invalid day spec: %s") % date[1:])
1104 1104 when = makedate()[0] - days * 3600 * 24
1105 1105 return lambda x: x >= when
1106 1106 elif " to " in date:
1107 1107 a, b = date.split(" to ")
1108 1108 start, stop = lower(a), upper(b)
1109 1109 return lambda x: x >= start and x <= stop
1110 1110 else:
1111 1111 start, stop = lower(date), upper(date)
1112 1112 return lambda x: x >= start and x <= stop
1113 1113
1114 1114 def shortuser(user):
1115 1115 """Return a short representation of a user name or email address."""
1116 1116 f = user.find('@')
1117 1117 if f >= 0:
1118 1118 user = user[:f]
1119 1119 f = user.find('<')
1120 1120 if f >= 0:
1121 1121 user = user[f+1:]
1122 1122 f = user.find(' ')
1123 1123 if f >= 0:
1124 1124 user = user[:f]
1125 1125 f = user.find('.')
1126 1126 if f >= 0:
1127 1127 user = user[:f]
1128 1128 return user
1129 1129
1130 1130 def email(author):
1131 1131 '''get email of author.'''
1132 1132 r = author.find('>')
1133 1133 if r == -1: r = None
1134 1134 return author[author.find('<')+1:r]
1135 1135
1136 1136 def ellipsis(text, maxlength=400):
1137 1137 """Trim string to at most maxlength (default: 400) characters."""
1138 1138 if len(text) <= maxlength:
1139 1139 return text
1140 1140 else:
1141 1141 return "%s..." % (text[:maxlength-3])
1142 1142
1143 1143 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1144 1144 '''yield every hg repository under path, recursively.'''
1145 1145 def errhandler(err):
1146 1146 if err.filename == path:
1147 1147 raise err
1148 1148 if followsym and hasattr(os.path, 'samestat'):
1149 1149 def _add_dir_if_not_there(dirlst, dirname):
1150 1150 match = False
1151 1151 samestat = os.path.samestat
1152 1152 dirstat = os.stat(dirname)
1153 1153 for lstdirstat in dirlst:
1154 1154 if samestat(dirstat, lstdirstat):
1155 1155 match = True
1156 1156 break
1157 1157 if not match:
1158 1158 dirlst.append(dirstat)
1159 1159 return not match
1160 1160 else:
1161 1161 followsym = False
1162 1162
1163 1163 if (seen_dirs is None) and followsym:
1164 1164 seen_dirs = []
1165 1165 _add_dir_if_not_there(seen_dirs, path)
1166 1166 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1167 1167 if '.hg' in dirs:
1168 1168 yield root # found a repository
1169 1169 qroot = os.path.join(root, '.hg', 'patches')
1170 1170 if os.path.isdir(os.path.join(qroot, '.hg')):
1171 1171 yield qroot # we have a patch queue repo here
1172 1172 if recurse:
1173 1173 # avoid recursing inside the .hg directory
1174 1174 dirs.remove('.hg')
1175 1175 else:
1176 1176 dirs[:] = [] # don't descend further
1177 1177 elif followsym:
1178 1178 newdirs = []
1179 1179 for d in dirs:
1180 1180 fname = os.path.join(root, d)
1181 1181 if _add_dir_if_not_there(seen_dirs, fname):
1182 1182 if os.path.islink(fname):
1183 1183 for hgname in walkrepos(fname, True, seen_dirs):
1184 1184 yield hgname
1185 1185 else:
1186 1186 newdirs.append(d)
1187 1187 dirs[:] = newdirs
1188 1188
1189 1189 _rcpath = None
1190 1190
1191 1191 def os_rcpath():
1192 1192 '''return default os-specific hgrc search path'''
1193 1193 path = system_rcpath()
1194 1194 path.extend(user_rcpath())
1195 1195 path = [os.path.normpath(f) for f in path]
1196 1196 return path
1197 1197
1198 1198 def rcpath():
1199 1199 '''return hgrc search path. if env var HGRCPATH is set, use it.
1200 1200 for each item in path, if directory, use files ending in .rc,
1201 1201 else use item.
1202 1202 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1203 1203 if no HGRCPATH, use default os-specific path.'''
1204 1204 global _rcpath
1205 1205 if _rcpath is None:
1206 1206 if 'HGRCPATH' in os.environ:
1207 1207 _rcpath = []
1208 1208 for p in os.environ['HGRCPATH'].split(os.pathsep):
1209 1209 if not p: continue
1210 1210 if os.path.isdir(p):
1211 1211 for f, kind in osutil.listdir(p):
1212 1212 if f.endswith('.rc'):
1213 1213 _rcpath.append(os.path.join(p, f))
1214 1214 else:
1215 1215 _rcpath.append(p)
1216 1216 else:
1217 1217 _rcpath = os_rcpath()
1218 1218 return _rcpath
1219 1219
1220 1220 def bytecount(nbytes):
1221 1221 '''return byte count formatted as readable string, with units'''
1222 1222
1223 1223 units = (
1224 1224 (100, 1<<30, _('%.0f GB')),
1225 1225 (10, 1<<30, _('%.1f GB')),
1226 1226 (1, 1<<30, _('%.2f GB')),
1227 1227 (100, 1<<20, _('%.0f MB')),
1228 1228 (10, 1<<20, _('%.1f MB')),
1229 1229 (1, 1<<20, _('%.2f MB')),
1230 1230 (100, 1<<10, _('%.0f KB')),
1231 1231 (10, 1<<10, _('%.1f KB')),
1232 1232 (1, 1<<10, _('%.2f KB')),
1233 1233 (1, 1, _('%.0f bytes')),
1234 1234 )
1235 1235
1236 1236 for multiplier, divisor, format in units:
1237 1237 if nbytes >= divisor * multiplier:
1238 1238 return format % (nbytes / float(divisor))
1239 1239 return units[-1][2] % nbytes
1240 1240
1241 1241 def drop_scheme(scheme, path):
1242 1242 sc = scheme + ':'
1243 1243 if path.startswith(sc):
1244 1244 path = path[len(sc):]
1245 1245 if path.startswith('//'):
1246 1246 path = path[2:]
1247 1247 return path
1248 1248
1249 1249 def uirepr(s):
1250 1250 # Avoid double backslash in Windows path repr()
1251 1251 return repr(s).replace('\\\\', '\\')
1252 1252
1253 1253 def termwidth():
1254 1254 if 'COLUMNS' in os.environ:
1255 1255 try:
1256 1256 return int(os.environ['COLUMNS'])
1257 1257 except ValueError:
1258 1258 pass
1259 1259 try:
1260 1260 import termios, array, fcntl
1261 1261 for dev in (sys.stdout, sys.stdin):
1262 1262 try:
1263 1263 try:
1264 1264 fd = dev.fileno()
1265 1265 except AttributeError:
1266 1266 continue
1267 1267 if not os.isatty(fd):
1268 1268 continue
1269 1269 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1270 1270 return array.array('h', arri)[1]
1271 1271 except ValueError:
1272 1272 pass
1273 1273 except ImportError:
1274 1274 pass
1275 1275 return 80
1276 1276
1277 1277 def wrap(line, hangindent, width=78):
1278 1278 padding = '\n' + ' ' * hangindent
1279 1279 return padding.join(textwrap.wrap(line, width=width - hangindent))
1280 1280
1281 1281 def iterlines(iterator):
1282 1282 for chunk in iterator:
1283 1283 for line in chunk.splitlines():
1284 1284 yield line
General Comments 0
You need to be logged in to leave comments. Login now