##// END OF EJS Templates
util: make atomictempfile saner if mktempcopy fails
Bryan O'Sullivan -
r8420:f53bc3e3 default
parent child Browse files
Show More
@@ -1,1458 +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 = {'[': 1, '{': 1, '*': 1, '?': 1}
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 525 if val in (None, False):
526 526 return '0'
527 527 if val == 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 self._fp = None
974 975 self.temp = mktempcopy(name, emptyok=('w' in mode),
975 976 createmode=createmode)
976 977 self._fp = posixfile(self.temp, mode)
977 978
978 979 def __getattr__(self, name):
979 980 return getattr(self._fp, name)
980 981
981 982 def rename(self):
982 983 if not self.closed:
983 984 self._fp.close()
984 985 rename(self.temp, localpath(self.__name))
985 986
986 987 def __del__(self):
987 988 if not self.closed:
988 989 try:
989 990 os.unlink(self.temp)
990 991 except: pass
992 if self._fp:
991 993 self._fp.close()
992 994
993 995 def makedirs(name, mode=None):
994 996 """recursive directory creation with parent mode inheritance"""
995 997 try:
996 998 os.mkdir(name)
997 999 if mode is not None:
998 1000 os.chmod(name, mode)
999 1001 return
1000 1002 except OSError, err:
1001 1003 if err.errno == errno.EEXIST:
1002 1004 return
1003 1005 if err.errno != errno.ENOENT:
1004 1006 raise
1005 1007 parent = os.path.abspath(os.path.dirname(name))
1006 1008 makedirs(parent, mode)
1007 1009 makedirs(name, mode)
1008 1010
1009 1011 class opener(object):
1010 1012 """Open files relative to a base directory
1011 1013
1012 1014 This class is used to hide the details of COW semantics and
1013 1015 remote file access from higher level code.
1014 1016 """
1015 1017 def __init__(self, base, audit=True):
1016 1018 self.base = base
1017 1019 if audit:
1018 1020 self.audit_path = path_auditor(base)
1019 1021 else:
1020 1022 self.audit_path = always
1021 1023 self.createmode = None
1022 1024
1023 1025 def __getattr__(self, name):
1024 1026 if name == '_can_symlink':
1025 1027 self._can_symlink = checklink(self.base)
1026 1028 return self._can_symlink
1027 1029 raise AttributeError(name)
1028 1030
1029 1031 def _fixfilemode(self, name):
1030 1032 if self.createmode is None:
1031 1033 return
1032 1034 os.chmod(name, self.createmode & 0666)
1033 1035
1034 1036 def __call__(self, path, mode="r", text=False, atomictemp=False):
1035 1037 self.audit_path(path)
1036 1038 f = os.path.join(self.base, path)
1037 1039
1038 1040 if not text and "b" not in mode:
1039 1041 mode += "b" # for that other OS
1040 1042
1041 1043 nlink = -1
1042 1044 if mode not in ("r", "rb"):
1043 1045 try:
1044 1046 nlink = nlinks(f)
1045 1047 except OSError:
1046 1048 nlink = 0
1047 1049 d = os.path.dirname(f)
1048 1050 if not os.path.isdir(d):
1049 1051 makedirs(d, self.createmode)
1050 1052 if atomictemp:
1051 1053 return atomictempfile(f, mode, self.createmode)
1052 1054 if nlink > 1:
1053 1055 rename(mktempcopy(f), f)
1054 1056 fp = posixfile(f, mode)
1055 1057 if nlink == 0:
1056 1058 self._fixfilemode(f)
1057 1059 return fp
1058 1060
1059 1061 def symlink(self, src, dst):
1060 1062 self.audit_path(dst)
1061 1063 linkname = os.path.join(self.base, dst)
1062 1064 try:
1063 1065 os.unlink(linkname)
1064 1066 except OSError:
1065 1067 pass
1066 1068
1067 1069 dirname = os.path.dirname(linkname)
1068 1070 if not os.path.exists(dirname):
1069 1071 makedirs(dirname, self.createmode)
1070 1072
1071 1073 if self._can_symlink:
1072 1074 try:
1073 1075 os.symlink(src, linkname)
1074 1076 except OSError, err:
1075 1077 raise OSError(err.errno, _('could not symlink to %r: %s') %
1076 1078 (src, err.strerror), linkname)
1077 1079 else:
1078 1080 f = self(dst, "w")
1079 1081 f.write(src)
1080 1082 f.close()
1081 1083 self._fixfilemode(dst)
1082 1084
1083 1085 class chunkbuffer(object):
1084 1086 """Allow arbitrary sized chunks of data to be efficiently read from an
1085 1087 iterator over chunks of arbitrary size."""
1086 1088
1087 1089 def __init__(self, in_iter):
1088 1090 """in_iter is the iterator that's iterating over the input chunks.
1089 1091 targetsize is how big a buffer to try to maintain."""
1090 1092 self.iter = iter(in_iter)
1091 1093 self.buf = ''
1092 1094 self.targetsize = 2**16
1093 1095
1094 1096 def read(self, l):
1095 1097 """Read L bytes of data from the iterator of chunks of data.
1096 1098 Returns less than L bytes if the iterator runs dry."""
1097 1099 if l > len(self.buf) and self.iter:
1098 1100 # Clamp to a multiple of self.targetsize
1099 1101 targetsize = max(l, self.targetsize)
1100 1102 collector = cStringIO.StringIO()
1101 1103 collector.write(self.buf)
1102 1104 collected = len(self.buf)
1103 1105 for chunk in self.iter:
1104 1106 collector.write(chunk)
1105 1107 collected += len(chunk)
1106 1108 if collected >= targetsize:
1107 1109 break
1108 1110 if collected < targetsize:
1109 1111 self.iter = False
1110 1112 self.buf = collector.getvalue()
1111 1113 if len(self.buf) == l:
1112 1114 s, self.buf = str(self.buf), ''
1113 1115 else:
1114 1116 s, self.buf = self.buf[:l], buffer(self.buf, l)
1115 1117 return s
1116 1118
1117 1119 def filechunkiter(f, size=65536, limit=None):
1118 1120 """Create a generator that produces the data in the file size
1119 1121 (default 65536) bytes at a time, up to optional limit (default is
1120 1122 to read all data). Chunks may be less than size bytes if the
1121 1123 chunk is the last chunk in the file, or the file is a socket or
1122 1124 some other type of file that sometimes reads less data than is
1123 1125 requested."""
1124 1126 assert size >= 0
1125 1127 assert limit is None or limit >= 0
1126 1128 while True:
1127 1129 if limit is None: nbytes = size
1128 1130 else: nbytes = min(limit, size)
1129 1131 s = nbytes and f.read(nbytes)
1130 1132 if not s: break
1131 1133 if limit: limit -= len(s)
1132 1134 yield s
1133 1135
1134 1136 def makedate():
1135 1137 lt = time.localtime()
1136 1138 if lt[8] == 1 and time.daylight:
1137 1139 tz = time.altzone
1138 1140 else:
1139 1141 tz = time.timezone
1140 1142 return time.mktime(lt), tz
1141 1143
1142 1144 def datestr(date=None, format='%a %b %d %H:%M:%S %Y %1%2'):
1143 1145 """represent a (unixtime, offset) tuple as a localized time.
1144 1146 unixtime is seconds since the epoch, and offset is the time zone's
1145 1147 number of seconds away from UTC. if timezone is false, do not
1146 1148 append time zone to string."""
1147 1149 t, tz = date or makedate()
1148 1150 if "%1" in format or "%2" in format:
1149 1151 sign = (tz > 0) and "-" or "+"
1150 1152 minutes = abs(tz) / 60
1151 1153 format = format.replace("%1", "%c%02d" % (sign, minutes / 60))
1152 1154 format = format.replace("%2", "%02d" % (minutes % 60))
1153 1155 s = time.strftime(format, time.gmtime(float(t) - tz))
1154 1156 return s
1155 1157
1156 1158 def shortdate(date=None):
1157 1159 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1158 1160 return datestr(date, format='%Y-%m-%d')
1159 1161
1160 1162 def strdate(string, format, defaults=[]):
1161 1163 """parse a localized time string and return a (unixtime, offset) tuple.
1162 1164 if the string cannot be parsed, ValueError is raised."""
1163 1165 def timezone(string):
1164 1166 tz = string.split()[-1]
1165 1167 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1166 1168 sign = (tz[0] == "+") and 1 or -1
1167 1169 hours = int(tz[1:3])
1168 1170 minutes = int(tz[3:5])
1169 1171 return -sign * (hours * 60 + minutes) * 60
1170 1172 if tz == "GMT" or tz == "UTC":
1171 1173 return 0
1172 1174 return None
1173 1175
1174 1176 # NOTE: unixtime = localunixtime + offset
1175 1177 offset, date = timezone(string), string
1176 1178 if offset != None:
1177 1179 date = " ".join(string.split()[:-1])
1178 1180
1179 1181 # add missing elements from defaults
1180 1182 for part in defaults:
1181 1183 found = [True for p in part if ("%"+p) in format]
1182 1184 if not found:
1183 1185 date += "@" + defaults[part]
1184 1186 format += "@%" + part[0]
1185 1187
1186 1188 timetuple = time.strptime(date, format)
1187 1189 localunixtime = int(calendar.timegm(timetuple))
1188 1190 if offset is None:
1189 1191 # local timezone
1190 1192 unixtime = int(time.mktime(timetuple))
1191 1193 offset = unixtime - localunixtime
1192 1194 else:
1193 1195 unixtime = localunixtime + offset
1194 1196 return unixtime, offset
1195 1197
1196 1198 def parsedate(date, formats=None, defaults=None):
1197 1199 """parse a localized date/time string and return a (unixtime, offset) tuple.
1198 1200
1199 1201 The date may be a "unixtime offset" string or in one of the specified
1200 1202 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1201 1203 """
1202 1204 if not date:
1203 1205 return 0, 0
1204 1206 if isinstance(date, tuple) and len(date) == 2:
1205 1207 return date
1206 1208 if not formats:
1207 1209 formats = defaultdateformats
1208 1210 date = date.strip()
1209 1211 try:
1210 1212 when, offset = map(int, date.split(' '))
1211 1213 except ValueError:
1212 1214 # fill out defaults
1213 1215 if not defaults:
1214 1216 defaults = {}
1215 1217 now = makedate()
1216 1218 for part in "d mb yY HI M S".split():
1217 1219 if part not in defaults:
1218 1220 if part[0] in "HMS":
1219 1221 defaults[part] = "00"
1220 1222 else:
1221 1223 defaults[part] = datestr(now, "%" + part[0])
1222 1224
1223 1225 for format in formats:
1224 1226 try:
1225 1227 when, offset = strdate(date, format, defaults)
1226 1228 except (ValueError, OverflowError):
1227 1229 pass
1228 1230 else:
1229 1231 break
1230 1232 else:
1231 1233 raise Abort(_('invalid date: %r ') % date)
1232 1234 # validate explicit (probably user-specified) date and
1233 1235 # time zone offset. values must fit in signed 32 bits for
1234 1236 # current 32-bit linux runtimes. timezones go from UTC-12
1235 1237 # to UTC+14
1236 1238 if abs(when) > 0x7fffffff:
1237 1239 raise Abort(_('date exceeds 32 bits: %d') % when)
1238 1240 if offset < -50400 or offset > 43200:
1239 1241 raise Abort(_('impossible time zone offset: %d') % offset)
1240 1242 return when, offset
1241 1243
1242 1244 def matchdate(date):
1243 1245 """Return a function that matches a given date match specifier
1244 1246
1245 1247 Formats include:
1246 1248
1247 1249 '{date}' match a given date to the accuracy provided
1248 1250
1249 1251 '<{date}' on or before a given date
1250 1252
1251 1253 '>{date}' on or after a given date
1252 1254
1253 1255 """
1254 1256
1255 1257 def lower(date):
1256 1258 d = dict(mb="1", d="1")
1257 1259 return parsedate(date, extendeddateformats, d)[0]
1258 1260
1259 1261 def upper(date):
1260 1262 d = dict(mb="12", HI="23", M="59", S="59")
1261 1263 for days in "31 30 29".split():
1262 1264 try:
1263 1265 d["d"] = days
1264 1266 return parsedate(date, extendeddateformats, d)[0]
1265 1267 except:
1266 1268 pass
1267 1269 d["d"] = "28"
1268 1270 return parsedate(date, extendeddateformats, d)[0]
1269 1271
1270 1272 date = date.strip()
1271 1273 if date[0] == "<":
1272 1274 when = upper(date[1:])
1273 1275 return lambda x: x <= when
1274 1276 elif date[0] == ">":
1275 1277 when = lower(date[1:])
1276 1278 return lambda x: x >= when
1277 1279 elif date[0] == "-":
1278 1280 try:
1279 1281 days = int(date[1:])
1280 1282 except ValueError:
1281 1283 raise Abort(_("invalid day spec: %s") % date[1:])
1282 1284 when = makedate()[0] - days * 3600 * 24
1283 1285 return lambda x: x >= when
1284 1286 elif " to " in date:
1285 1287 a, b = date.split(" to ")
1286 1288 start, stop = lower(a), upper(b)
1287 1289 return lambda x: x >= start and x <= stop
1288 1290 else:
1289 1291 start, stop = lower(date), upper(date)
1290 1292 return lambda x: x >= start and x <= stop
1291 1293
1292 1294 def shortuser(user):
1293 1295 """Return a short representation of a user name or email address."""
1294 1296 f = user.find('@')
1295 1297 if f >= 0:
1296 1298 user = user[:f]
1297 1299 f = user.find('<')
1298 1300 if f >= 0:
1299 1301 user = user[f+1:]
1300 1302 f = user.find(' ')
1301 1303 if f >= 0:
1302 1304 user = user[:f]
1303 1305 f = user.find('.')
1304 1306 if f >= 0:
1305 1307 user = user[:f]
1306 1308 return user
1307 1309
1308 1310 def email(author):
1309 1311 '''get email of author.'''
1310 1312 r = author.find('>')
1311 1313 if r == -1: r = None
1312 1314 return author[author.find('<')+1:r]
1313 1315
1314 1316 def ellipsis(text, maxlength=400):
1315 1317 """Trim string to at most maxlength (default: 400) characters."""
1316 1318 if len(text) <= maxlength:
1317 1319 return text
1318 1320 else:
1319 1321 return "%s..." % (text[:maxlength-3])
1320 1322
1321 1323 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
1322 1324 '''yield every hg repository under path, recursively.'''
1323 1325 def errhandler(err):
1324 1326 if err.filename == path:
1325 1327 raise err
1326 1328 if followsym and hasattr(os.path, 'samestat'):
1327 1329 def _add_dir_if_not_there(dirlst, dirname):
1328 1330 match = False
1329 1331 samestat = os.path.samestat
1330 1332 dirstat = os.stat(dirname)
1331 1333 for lstdirstat in dirlst:
1332 1334 if samestat(dirstat, lstdirstat):
1333 1335 match = True
1334 1336 break
1335 1337 if not match:
1336 1338 dirlst.append(dirstat)
1337 1339 return not match
1338 1340 else:
1339 1341 followsym = False
1340 1342
1341 1343 if (seen_dirs is None) and followsym:
1342 1344 seen_dirs = []
1343 1345 _add_dir_if_not_there(seen_dirs, path)
1344 1346 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
1345 1347 if '.hg' in dirs:
1346 1348 yield root # found a repository
1347 1349 qroot = os.path.join(root, '.hg', 'patches')
1348 1350 if os.path.isdir(os.path.join(qroot, '.hg')):
1349 1351 yield qroot # we have a patch queue repo here
1350 1352 if recurse:
1351 1353 # avoid recursing inside the .hg directory
1352 1354 dirs.remove('.hg')
1353 1355 else:
1354 1356 dirs[:] = [] # don't descend further
1355 1357 elif followsym:
1356 1358 newdirs = []
1357 1359 for d in dirs:
1358 1360 fname = os.path.join(root, d)
1359 1361 if _add_dir_if_not_there(seen_dirs, fname):
1360 1362 if os.path.islink(fname):
1361 1363 for hgname in walkrepos(fname, True, seen_dirs):
1362 1364 yield hgname
1363 1365 else:
1364 1366 newdirs.append(d)
1365 1367 dirs[:] = newdirs
1366 1368
1367 1369 _rcpath = None
1368 1370
1369 1371 def os_rcpath():
1370 1372 '''return default os-specific hgrc search path'''
1371 1373 path = system_rcpath()
1372 1374 path.extend(user_rcpath())
1373 1375 path = [os.path.normpath(f) for f in path]
1374 1376 return path
1375 1377
1376 1378 def rcpath():
1377 1379 '''return hgrc search path. if env var HGRCPATH is set, use it.
1378 1380 for each item in path, if directory, use files ending in .rc,
1379 1381 else use item.
1380 1382 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1381 1383 if no HGRCPATH, use default os-specific path.'''
1382 1384 global _rcpath
1383 1385 if _rcpath is None:
1384 1386 if 'HGRCPATH' in os.environ:
1385 1387 _rcpath = []
1386 1388 for p in os.environ['HGRCPATH'].split(os.pathsep):
1387 1389 if not p: continue
1388 1390 if os.path.isdir(p):
1389 1391 for f, kind in osutil.listdir(p):
1390 1392 if f.endswith('.rc'):
1391 1393 _rcpath.append(os.path.join(p, f))
1392 1394 else:
1393 1395 _rcpath.append(p)
1394 1396 else:
1395 1397 _rcpath = os_rcpath()
1396 1398 return _rcpath
1397 1399
1398 1400 def bytecount(nbytes):
1399 1401 '''return byte count formatted as readable string, with units'''
1400 1402
1401 1403 units = (
1402 1404 (100, 1<<30, _('%.0f GB')),
1403 1405 (10, 1<<30, _('%.1f GB')),
1404 1406 (1, 1<<30, _('%.2f GB')),
1405 1407 (100, 1<<20, _('%.0f MB')),
1406 1408 (10, 1<<20, _('%.1f MB')),
1407 1409 (1, 1<<20, _('%.2f MB')),
1408 1410 (100, 1<<10, _('%.0f KB')),
1409 1411 (10, 1<<10, _('%.1f KB')),
1410 1412 (1, 1<<10, _('%.2f KB')),
1411 1413 (1, 1, _('%.0f bytes')),
1412 1414 )
1413 1415
1414 1416 for multiplier, divisor, format in units:
1415 1417 if nbytes >= divisor * multiplier:
1416 1418 return format % (nbytes / float(divisor))
1417 1419 return units[-1][2] % nbytes
1418 1420
1419 1421 def drop_scheme(scheme, path):
1420 1422 sc = scheme + ':'
1421 1423 if path.startswith(sc):
1422 1424 path = path[len(sc):]
1423 1425 if path.startswith('//'):
1424 1426 path = path[2:]
1425 1427 return path
1426 1428
1427 1429 def uirepr(s):
1428 1430 # Avoid double backslash in Windows path repr()
1429 1431 return repr(s).replace('\\\\', '\\')
1430 1432
1431 1433 def termwidth():
1432 1434 if 'COLUMNS' in os.environ:
1433 1435 try:
1434 1436 return int(os.environ['COLUMNS'])
1435 1437 except ValueError:
1436 1438 pass
1437 1439 try:
1438 1440 import termios, array, fcntl
1439 1441 for dev in (sys.stdout, sys.stdin):
1440 1442 try:
1441 1443 try:
1442 1444 fd = dev.fileno()
1443 1445 except AttributeError:
1444 1446 continue
1445 1447 if not os.isatty(fd):
1446 1448 continue
1447 1449 arri = fcntl.ioctl(fd, termios.TIOCGWINSZ, '\0' * 8)
1448 1450 return array.array('h', arri)[1]
1449 1451 except ValueError:
1450 1452 pass
1451 1453 except ImportError:
1452 1454 pass
1453 1455 return 80
1454 1456
1455 1457 def iterlines(iterator):
1456 1458 for chunk in iterator:
1457 1459 for line in chunk.splitlines():
1458 1460 yield line
General Comments 0
You need to be logged in to leave comments. Login now