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