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