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