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