##// END OF EJS Templates
Simplify utils.walkrepos().
Walter Doerwald -
r6140:47e6d5d5 default
parent child Browse files
Show More
@@ -1,1787 +1,1785 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
18 18 import urlparse
19 19
20 20 try:
21 21 set = set
22 22 frozenset = frozenset
23 23 except NameError:
24 24 from sets import Set as set, ImmutableSet as frozenset
25 25
26 26 try:
27 27 _encoding = os.environ.get("HGENCODING")
28 28 if sys.platform == 'darwin' and not _encoding:
29 29 # On darwin, getpreferredencoding ignores the locale environment and
30 30 # always returns mac-roman. We override this if the environment is
31 31 # not C (has been customized by the user).
32 32 locale.setlocale(locale.LC_CTYPE, '')
33 33 _encoding = locale.getlocale()[1]
34 34 if not _encoding:
35 35 _encoding = locale.getpreferredencoding() or 'ascii'
36 36 except locale.Error:
37 37 _encoding = 'ascii'
38 38 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
39 39 _fallbackencoding = 'ISO-8859-1'
40 40
41 41 def tolocal(s):
42 42 """
43 43 Convert a string from internal UTF-8 to local encoding
44 44
45 45 All internal strings should be UTF-8 but some repos before the
46 46 implementation of locale support may contain latin1 or possibly
47 47 other character sets. We attempt to decode everything strictly
48 48 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
49 49 replace unknown characters.
50 50 """
51 51 for e in ('UTF-8', _fallbackencoding):
52 52 try:
53 53 u = s.decode(e) # attempt strict decoding
54 54 return u.encode(_encoding, "replace")
55 55 except LookupError, k:
56 56 raise Abort(_("%s, please check your locale settings") % k)
57 57 except UnicodeDecodeError:
58 58 pass
59 59 u = s.decode("utf-8", "replace") # last ditch
60 60 return u.encode(_encoding, "replace")
61 61
62 62 def fromlocal(s):
63 63 """
64 64 Convert a string from the local character encoding to UTF-8
65 65
66 66 We attempt to decode strings using the encoding mode set by
67 67 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
68 68 characters will cause an error message. Other modes include
69 69 'replace', which replaces unknown characters with a special
70 70 Unicode character, and 'ignore', which drops the character.
71 71 """
72 72 try:
73 73 return s.decode(_encoding, _encodingmode).encode("utf-8")
74 74 except UnicodeDecodeError, inst:
75 75 sub = s[max(0, inst.start-10):inst.start+10]
76 76 raise Abort("decoding near '%s': %s!" % (sub, inst))
77 77 except LookupError, k:
78 78 raise Abort(_("%s, please check your locale settings") % k)
79 79
80 80 def locallen(s):
81 81 """Find the length in characters of a local string"""
82 82 return len(s.decode(_encoding, "replace"))
83 83
84 84 # used by parsedate
85 85 defaultdateformats = (
86 86 '%Y-%m-%d %H:%M:%S',
87 87 '%Y-%m-%d %I:%M:%S%p',
88 88 '%Y-%m-%d %H:%M',
89 89 '%Y-%m-%d %I:%M%p',
90 90 '%Y-%m-%d',
91 91 '%m-%d',
92 92 '%m/%d',
93 93 '%m/%d/%y',
94 94 '%m/%d/%Y',
95 95 '%a %b %d %H:%M:%S %Y',
96 96 '%a %b %d %I:%M:%S%p %Y',
97 97 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
98 98 '%b %d %H:%M:%S %Y',
99 99 '%b %d %I:%M:%S%p %Y',
100 100 '%b %d %H:%M:%S',
101 101 '%b %d %I:%M:%S%p',
102 102 '%b %d %H:%M',
103 103 '%b %d %I:%M%p',
104 104 '%b %d %Y',
105 105 '%b %d',
106 106 '%H:%M:%S',
107 107 '%I:%M:%SP',
108 108 '%H:%M',
109 109 '%I:%M%p',
110 110 )
111 111
112 112 extendeddateformats = defaultdateformats + (
113 113 "%Y",
114 114 "%Y-%m",
115 115 "%b",
116 116 "%b %Y",
117 117 )
118 118
119 119 class SignalInterrupt(Exception):
120 120 """Exception raised on SIGTERM and SIGHUP."""
121 121
122 122 # differences from SafeConfigParser:
123 123 # - case-sensitive keys
124 124 # - allows values that are not strings (this means that you may not
125 125 # be able to save the configuration to a file)
126 126 class configparser(ConfigParser.SafeConfigParser):
127 127 def optionxform(self, optionstr):
128 128 return optionstr
129 129
130 130 def set(self, section, option, value):
131 131 return ConfigParser.ConfigParser.set(self, section, option, value)
132 132
133 133 def _interpolate(self, section, option, rawval, vars):
134 134 if not isinstance(rawval, basestring):
135 135 return rawval
136 136 return ConfigParser.SafeConfigParser._interpolate(self, section,
137 137 option, rawval, vars)
138 138
139 139 def cachefunc(func):
140 140 '''cache the result of function calls'''
141 141 # XXX doesn't handle keywords args
142 142 cache = {}
143 143 if func.func_code.co_argcount == 1:
144 144 # we gain a small amount of time because
145 145 # we don't need to pack/unpack the list
146 146 def f(arg):
147 147 if arg not in cache:
148 148 cache[arg] = func(arg)
149 149 return cache[arg]
150 150 else:
151 151 def f(*args):
152 152 if args not in cache:
153 153 cache[args] = func(*args)
154 154 return cache[args]
155 155
156 156 return f
157 157
158 158 def pipefilter(s, cmd):
159 159 '''filter string S through command CMD, returning its output'''
160 160 (pin, pout) = os.popen2(cmd, 'b')
161 161 def writer():
162 162 try:
163 163 pin.write(s)
164 164 pin.close()
165 165 except IOError, inst:
166 166 if inst.errno != errno.EPIPE:
167 167 raise
168 168
169 169 # we should use select instead on UNIX, but this will work on most
170 170 # systems, including Windows
171 171 w = threading.Thread(target=writer)
172 172 w.start()
173 173 f = pout.read()
174 174 pout.close()
175 175 w.join()
176 176 return f
177 177
178 178 def tempfilter(s, cmd):
179 179 '''filter string S through a pair of temporary files with CMD.
180 180 CMD is used as a template to create the real command to be run,
181 181 with the strings INFILE and OUTFILE replaced by the real names of
182 182 the temporary files generated.'''
183 183 inname, outname = None, None
184 184 try:
185 185 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
186 186 fp = os.fdopen(infd, 'wb')
187 187 fp.write(s)
188 188 fp.close()
189 189 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
190 190 os.close(outfd)
191 191 cmd = cmd.replace('INFILE', inname)
192 192 cmd = cmd.replace('OUTFILE', outname)
193 193 code = os.system(cmd)
194 194 if sys.platform == 'OpenVMS' and code & 1:
195 195 code = 0
196 196 if code: raise Abort(_("command '%s' failed: %s") %
197 197 (cmd, explain_exit(code)))
198 198 return open(outname, 'rb').read()
199 199 finally:
200 200 try:
201 201 if inname: os.unlink(inname)
202 202 except: pass
203 203 try:
204 204 if outname: os.unlink(outname)
205 205 except: pass
206 206
207 207 filtertable = {
208 208 'tempfile:': tempfilter,
209 209 'pipe:': pipefilter,
210 210 }
211 211
212 212 def filter(s, cmd):
213 213 "filter a string through a command that transforms its input to its output"
214 214 for name, fn in filtertable.iteritems():
215 215 if cmd.startswith(name):
216 216 return fn(s, cmd[len(name):].lstrip())
217 217 return pipefilter(s, cmd)
218 218
219 219 def binary(s):
220 220 """return true if a string is binary data using diff's heuristic"""
221 221 if s and '\0' in s[:4096]:
222 222 return True
223 223 return False
224 224
225 225 def unique(g):
226 226 """return the uniq elements of iterable g"""
227 227 return dict.fromkeys(g).keys()
228 228
229 229 class Abort(Exception):
230 230 """Raised if a command needs to print an error and exit."""
231 231
232 232 class UnexpectedOutput(Abort):
233 233 """Raised to print an error with part of output and exit."""
234 234
235 235 def always(fn): return True
236 236 def never(fn): return False
237 237
238 238 def expand_glob(pats):
239 239 '''On Windows, expand the implicit globs in a list of patterns'''
240 240 if os.name != 'nt':
241 241 return list(pats)
242 242 ret = []
243 243 for p in pats:
244 244 kind, name = patkind(p, None)
245 245 if kind is None:
246 246 globbed = glob.glob(name)
247 247 if globbed:
248 248 ret.extend(globbed)
249 249 continue
250 250 # if we couldn't expand the glob, just keep it around
251 251 ret.append(p)
252 252 return ret
253 253
254 254 def patkind(name, dflt_pat='glob'):
255 255 """Split a string into an optional pattern kind prefix and the
256 256 actual pattern."""
257 257 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
258 258 if name.startswith(prefix + ':'): return name.split(':', 1)
259 259 return dflt_pat, name
260 260
261 261 def globre(pat, head='^', tail='$'):
262 262 "convert a glob pattern into a regexp"
263 263 i, n = 0, len(pat)
264 264 res = ''
265 265 group = 0
266 266 def peek(): return i < n and pat[i]
267 267 while i < n:
268 268 c = pat[i]
269 269 i = i+1
270 270 if c == '*':
271 271 if peek() == '*':
272 272 i += 1
273 273 res += '.*'
274 274 else:
275 275 res += '[^/]*'
276 276 elif c == '?':
277 277 res += '.'
278 278 elif c == '[':
279 279 j = i
280 280 if j < n and pat[j] in '!]':
281 281 j += 1
282 282 while j < n and pat[j] != ']':
283 283 j += 1
284 284 if j >= n:
285 285 res += '\\['
286 286 else:
287 287 stuff = pat[i:j].replace('\\','\\\\')
288 288 i = j + 1
289 289 if stuff[0] == '!':
290 290 stuff = '^' + stuff[1:]
291 291 elif stuff[0] == '^':
292 292 stuff = '\\' + stuff
293 293 res = '%s[%s]' % (res, stuff)
294 294 elif c == '{':
295 295 group += 1
296 296 res += '(?:'
297 297 elif c == '}' and group:
298 298 res += ')'
299 299 group -= 1
300 300 elif c == ',' and group:
301 301 res += '|'
302 302 elif c == '\\':
303 303 p = peek()
304 304 if p:
305 305 i += 1
306 306 res += re.escape(p)
307 307 else:
308 308 res += re.escape(c)
309 309 else:
310 310 res += re.escape(c)
311 311 return head + res + tail
312 312
313 313 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
314 314
315 315 def pathto(root, n1, n2):
316 316 '''return the relative path from one place to another.
317 317 root should use os.sep to separate directories
318 318 n1 should use os.sep to separate directories
319 319 n2 should use "/" to separate directories
320 320 returns an os.sep-separated path.
321 321
322 322 If n1 is a relative path, it's assumed it's
323 323 relative to root.
324 324 n2 should always be relative to root.
325 325 '''
326 326 if not n1: return localpath(n2)
327 327 if os.path.isabs(n1):
328 328 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
329 329 return os.path.join(root, localpath(n2))
330 330 n2 = '/'.join((pconvert(root), n2))
331 331 a, b = splitpath(n1), n2.split('/')
332 332 a.reverse()
333 333 b.reverse()
334 334 while a and b and a[-1] == b[-1]:
335 335 a.pop()
336 336 b.pop()
337 337 b.reverse()
338 338 return os.sep.join((['..'] * len(a)) + b) or '.'
339 339
340 340 def canonpath(root, cwd, myname):
341 341 """return the canonical path of myname, given cwd and root"""
342 342 if root == os.sep:
343 343 rootsep = os.sep
344 344 elif endswithsep(root):
345 345 rootsep = root
346 346 else:
347 347 rootsep = root + os.sep
348 348 name = myname
349 349 if not os.path.isabs(name):
350 350 name = os.path.join(root, cwd, name)
351 351 name = os.path.normpath(name)
352 352 audit_path = path_auditor(root)
353 353 if name != rootsep and name.startswith(rootsep):
354 354 name = name[len(rootsep):]
355 355 audit_path(name)
356 356 return pconvert(name)
357 357 elif name == root:
358 358 return ''
359 359 else:
360 360 # Determine whether `name' is in the hierarchy at or beneath `root',
361 361 # by iterating name=dirname(name) until that causes no change (can't
362 362 # check name == '/', because that doesn't work on windows). For each
363 363 # `name', compare dev/inode numbers. If they match, the list `rel'
364 364 # holds the reversed list of components making up the relative file
365 365 # name we want.
366 366 root_st = os.stat(root)
367 367 rel = []
368 368 while True:
369 369 try:
370 370 name_st = os.stat(name)
371 371 except OSError:
372 372 break
373 373 if samestat(name_st, root_st):
374 374 if not rel:
375 375 # name was actually the same as root (maybe a symlink)
376 376 return ''
377 377 rel.reverse()
378 378 name = os.path.join(*rel)
379 379 audit_path(name)
380 380 return pconvert(name)
381 381 dirname, basename = os.path.split(name)
382 382 rel.append(basename)
383 383 if dirname == name:
384 384 break
385 385 name = dirname
386 386
387 387 raise Abort('%s not under root' % myname)
388 388
389 389 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
390 390 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
391 391
392 392 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
393 393 globbed=False, default=None):
394 394 default = default or 'relpath'
395 395 if default == 'relpath' and not globbed:
396 396 names = expand_glob(names)
397 397 return _matcher(canonroot, cwd, names, inc, exc, default, src)
398 398
399 399 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
400 400 """build a function to match a set of file patterns
401 401
402 402 arguments:
403 403 canonroot - the canonical root of the tree you're matching against
404 404 cwd - the current working directory, if relevant
405 405 names - patterns to find
406 406 inc - patterns to include
407 407 exc - patterns to exclude
408 408 dflt_pat - if a pattern in names has no explicit type, assume this one
409 409 src - where these patterns came from (e.g. .hgignore)
410 410
411 411 a pattern is one of:
412 412 'glob:<glob>' - a glob relative to cwd
413 413 're:<regexp>' - a regular expression
414 414 'path:<path>' - a path relative to canonroot
415 415 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
416 416 'relpath:<path>' - a path relative to cwd
417 417 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
418 418 '<something>' - one of the cases above, selected by the dflt_pat argument
419 419
420 420 returns:
421 421 a 3-tuple containing
422 422 - list of roots (places where one should start a recursive walk of the fs);
423 423 this often matches the explicit non-pattern names passed in, but also
424 424 includes the initial part of glob: patterns that has no glob characters
425 425 - a bool match(filename) function
426 426 - a bool indicating if any patterns were passed in
427 427 """
428 428
429 429 # a common case: no patterns at all
430 430 if not names and not inc and not exc:
431 431 return [], always, False
432 432
433 433 def contains_glob(name):
434 434 for c in name:
435 435 if c in _globchars: return True
436 436 return False
437 437
438 438 def regex(kind, name, tail):
439 439 '''convert a pattern into a regular expression'''
440 440 if not name:
441 441 return ''
442 442 if kind == 're':
443 443 return name
444 444 elif kind == 'path':
445 445 return '^' + re.escape(name) + '(?:/|$)'
446 446 elif kind == 'relglob':
447 447 return globre(name, '(?:|.*/)', tail)
448 448 elif kind == 'relpath':
449 449 return re.escape(name) + '(?:/|$)'
450 450 elif kind == 'relre':
451 451 if name.startswith('^'):
452 452 return name
453 453 return '.*' + name
454 454 return globre(name, '', tail)
455 455
456 456 def matchfn(pats, tail):
457 457 """build a matching function from a set of patterns"""
458 458 if not pats:
459 459 return
460 460 try:
461 461 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
462 462 if len(pat) > 20000:
463 463 raise OverflowError()
464 464 return re.compile(pat).match
465 465 except OverflowError:
466 466 # We're using a Python with a tiny regex engine and we
467 467 # made it explode, so we'll divide the pattern list in two
468 468 # until it works
469 469 l = len(pats)
470 470 if l < 2:
471 471 raise
472 472 a, b = matchfn(pats[:l//2], tail), matchfn(pats[l//2:], tail)
473 473 return lambda s: a(s) or b(s)
474 474 except re.error:
475 475 for k, p in pats:
476 476 try:
477 477 re.compile('(?:%s)' % regex(k, p, tail))
478 478 except re.error:
479 479 if src:
480 480 raise Abort("%s: invalid pattern (%s): %s" %
481 481 (src, k, p))
482 482 else:
483 483 raise Abort("invalid pattern (%s): %s" % (k, p))
484 484 raise Abort("invalid pattern")
485 485
486 486 def globprefix(pat):
487 487 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
488 488 root = []
489 489 for p in pat.split('/'):
490 490 if contains_glob(p): break
491 491 root.append(p)
492 492 return '/'.join(root) or '.'
493 493
494 494 def normalizepats(names, default):
495 495 pats = []
496 496 roots = []
497 497 anypats = False
498 498 for kind, name in [patkind(p, default) for p in names]:
499 499 if kind in ('glob', 'relpath'):
500 500 name = canonpath(canonroot, cwd, name)
501 501 elif kind in ('relglob', 'path'):
502 502 name = normpath(name)
503 503
504 504 pats.append((kind, name))
505 505
506 506 if kind in ('glob', 're', 'relglob', 'relre'):
507 507 anypats = True
508 508
509 509 if kind == 'glob':
510 510 root = globprefix(name)
511 511 roots.append(root)
512 512 elif kind in ('relpath', 'path'):
513 513 roots.append(name or '.')
514 514 elif kind == 'relglob':
515 515 roots.append('.')
516 516 return roots, pats, anypats
517 517
518 518 roots, pats, anypats = normalizepats(names, dflt_pat)
519 519
520 520 patmatch = matchfn(pats, '$') or always
521 521 incmatch = always
522 522 if inc:
523 523 dummy, inckinds, dummy = normalizepats(inc, 'glob')
524 524 incmatch = matchfn(inckinds, '(?:/|$)')
525 525 excmatch = lambda fn: False
526 526 if exc:
527 527 dummy, exckinds, dummy = normalizepats(exc, 'glob')
528 528 excmatch = matchfn(exckinds, '(?:/|$)')
529 529
530 530 if not names and inc and not exc:
531 531 # common case: hgignore patterns
532 532 match = incmatch
533 533 else:
534 534 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
535 535
536 536 return (roots, match, (inc or exc or anypats) and True)
537 537
538 538 _hgexecutable = None
539 539
540 540 def hgexecutable():
541 541 """return location of the 'hg' executable.
542 542
543 543 Defaults to $HG or 'hg' in the search path.
544 544 """
545 545 if _hgexecutable is None:
546 546 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
547 547 return _hgexecutable
548 548
549 549 def set_hgexecutable(path):
550 550 """set location of the 'hg' executable"""
551 551 global _hgexecutable
552 552 _hgexecutable = path
553 553
554 554 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
555 555 '''enhanced shell command execution.
556 556 run with environment maybe modified, maybe in different dir.
557 557
558 558 if command fails and onerr is None, return status. if ui object,
559 559 print error message and return status, else raise onerr object as
560 560 exception.'''
561 561 def py2shell(val):
562 562 'convert python object into string that is useful to shell'
563 563 if val in (None, False):
564 564 return '0'
565 565 if val == True:
566 566 return '1'
567 567 return str(val)
568 568 oldenv = {}
569 569 for k in environ:
570 570 oldenv[k] = os.environ.get(k)
571 571 if cwd is not None:
572 572 oldcwd = os.getcwd()
573 573 origcmd = cmd
574 574 if os.name == 'nt':
575 575 cmd = '"%s"' % cmd
576 576 try:
577 577 for k, v in environ.iteritems():
578 578 os.environ[k] = py2shell(v)
579 579 os.environ['HG'] = hgexecutable()
580 580 if cwd is not None and oldcwd != cwd:
581 581 os.chdir(cwd)
582 582 rc = os.system(cmd)
583 583 if sys.platform == 'OpenVMS' and rc & 1:
584 584 rc = 0
585 585 if rc and onerr:
586 586 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
587 587 explain_exit(rc)[0])
588 588 if errprefix:
589 589 errmsg = '%s: %s' % (errprefix, errmsg)
590 590 try:
591 591 onerr.warn(errmsg + '\n')
592 592 except AttributeError:
593 593 raise onerr(errmsg)
594 594 return rc
595 595 finally:
596 596 for k, v in oldenv.iteritems():
597 597 if v is None:
598 598 del os.environ[k]
599 599 else:
600 600 os.environ[k] = v
601 601 if cwd is not None and oldcwd != cwd:
602 602 os.chdir(oldcwd)
603 603
604 604 # os.path.lexists is not available on python2.3
605 605 def lexists(filename):
606 606 "test whether a file with this name exists. does not follow symlinks"
607 607 try:
608 608 os.lstat(filename)
609 609 except:
610 610 return False
611 611 return True
612 612
613 613 def rename(src, dst):
614 614 """forcibly rename a file"""
615 615 try:
616 616 os.rename(src, dst)
617 617 except OSError, err: # FIXME: check err (EEXIST ?)
618 618 # on windows, rename to existing file is not allowed, so we
619 619 # must delete destination first. but if file is open, unlink
620 620 # schedules it for delete but does not delete it. rename
621 621 # happens immediately even for open files, so we create
622 622 # temporary file, delete it, rename destination to that name,
623 623 # then delete that. then rename is safe to do.
624 624 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
625 625 os.close(fd)
626 626 os.unlink(temp)
627 627 os.rename(dst, temp)
628 628 os.unlink(temp)
629 629 os.rename(src, dst)
630 630
631 631 def unlink(f):
632 632 """unlink and remove the directory if it is empty"""
633 633 os.unlink(f)
634 634 # try removing directories that might now be empty
635 635 try:
636 636 os.removedirs(os.path.dirname(f))
637 637 except OSError:
638 638 pass
639 639
640 640 def copyfile(src, dest):
641 641 "copy a file, preserving mode"
642 642 if os.path.islink(src):
643 643 try:
644 644 os.unlink(dest)
645 645 except:
646 646 pass
647 647 os.symlink(os.readlink(src), dest)
648 648 else:
649 649 try:
650 650 shutil.copyfile(src, dest)
651 651 shutil.copymode(src, dest)
652 652 except shutil.Error, inst:
653 653 raise Abort(str(inst))
654 654
655 655 def copyfiles(src, dst, hardlink=None):
656 656 """Copy a directory tree using hardlinks if possible"""
657 657
658 658 if hardlink is None:
659 659 hardlink = (os.stat(src).st_dev ==
660 660 os.stat(os.path.dirname(dst)).st_dev)
661 661
662 662 if os.path.isdir(src):
663 663 os.mkdir(dst)
664 664 for name, kind in osutil.listdir(src):
665 665 srcname = os.path.join(src, name)
666 666 dstname = os.path.join(dst, name)
667 667 copyfiles(srcname, dstname, hardlink)
668 668 else:
669 669 if hardlink:
670 670 try:
671 671 os_link(src, dst)
672 672 except (IOError, OSError):
673 673 hardlink = False
674 674 shutil.copy(src, dst)
675 675 else:
676 676 shutil.copy(src, dst)
677 677
678 678 class path_auditor(object):
679 679 '''ensure that a filesystem path contains no banned components.
680 680 the following properties of a path are checked:
681 681
682 682 - under top-level .hg
683 683 - starts at the root of a windows drive
684 684 - contains ".."
685 685 - traverses a symlink (e.g. a/symlink_here/b)
686 686 - inside a nested repository'''
687 687
688 688 def __init__(self, root):
689 689 self.audited = set()
690 690 self.auditeddir = set()
691 691 self.root = root
692 692
693 693 def __call__(self, path):
694 694 if path in self.audited:
695 695 return
696 696 normpath = os.path.normcase(path)
697 697 parts = splitpath(normpath)
698 698 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
699 699 or os.pardir in parts):
700 700 raise Abort(_("path contains illegal component: %s") % path)
701 701 def check(prefix):
702 702 curpath = os.path.join(self.root, prefix)
703 703 try:
704 704 st = os.lstat(curpath)
705 705 except OSError, err:
706 706 # EINVAL can be raised as invalid path syntax under win32.
707 707 # They must be ignored for patterns can be checked too.
708 708 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
709 709 raise
710 710 else:
711 711 if stat.S_ISLNK(st.st_mode):
712 712 raise Abort(_('path %r traverses symbolic link %r') %
713 713 (path, prefix))
714 714 elif (stat.S_ISDIR(st.st_mode) and
715 715 os.path.isdir(os.path.join(curpath, '.hg'))):
716 716 raise Abort(_('path %r is inside repo %r') %
717 717 (path, prefix))
718 718 parts.pop()
719 719 prefixes = []
720 720 for n in range(len(parts)):
721 721 prefix = os.sep.join(parts)
722 722 if prefix in self.auditeddir:
723 723 break
724 724 check(prefix)
725 725 prefixes.append(prefix)
726 726 parts.pop()
727 727
728 728 self.audited.add(path)
729 729 # only add prefixes to the cache after checking everything: we don't
730 730 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
731 731 self.auditeddir.update(prefixes)
732 732
733 733 def _makelock_file(info, pathname):
734 734 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
735 735 os.write(ld, info)
736 736 os.close(ld)
737 737
738 738 def _readlock_file(pathname):
739 739 return posixfile(pathname).read()
740 740
741 741 def nlinks(pathname):
742 742 """Return number of hardlinks for the given file."""
743 743 return os.lstat(pathname).st_nlink
744 744
745 745 if hasattr(os, 'link'):
746 746 os_link = os.link
747 747 else:
748 748 def os_link(src, dst):
749 749 raise OSError(0, _("Hardlinks not supported"))
750 750
751 751 def fstat(fp):
752 752 '''stat file object that may not have fileno method.'''
753 753 try:
754 754 return os.fstat(fp.fileno())
755 755 except AttributeError:
756 756 return os.stat(fp.name)
757 757
758 758 posixfile = file
759 759
760 760 def openhardlinks():
761 761 '''return true if it is safe to hold open file handles to hardlinks'''
762 762 return True
763 763
764 764 getuser_fallback = None
765 765
766 766 def getuser():
767 767 '''return name of current user'''
768 768 try:
769 769 return getpass.getuser()
770 770 except ImportError:
771 771 # import of pwd will fail on windows - try fallback
772 772 if getuser_fallback:
773 773 return getuser_fallback()
774 774 # raised if win32api not available
775 775 raise Abort(_('user name not available - set USERNAME '
776 776 'environment variable'))
777 777
778 778 def username(uid=None):
779 779 """Return the name of the user with the given uid.
780 780
781 781 If uid is None, return the name of the current user."""
782 782 try:
783 783 import pwd
784 784 if uid is None:
785 785 uid = os.getuid()
786 786 try:
787 787 return pwd.getpwuid(uid)[0]
788 788 except KeyError:
789 789 return str(uid)
790 790 except ImportError:
791 791 return None
792 792
793 793 def groupname(gid=None):
794 794 """Return the name of the group with the given gid.
795 795
796 796 If gid is None, return the name of the current group."""
797 797 try:
798 798 import grp
799 799 if gid is None:
800 800 gid = os.getgid()
801 801 try:
802 802 return grp.getgrgid(gid)[0]
803 803 except KeyError:
804 804 return str(gid)
805 805 except ImportError:
806 806 return None
807 807
808 808 # File system features
809 809
810 810 def checkfolding(path):
811 811 """
812 812 Check whether the given path is on a case-sensitive filesystem
813 813
814 814 Requires a path (like /foo/.hg) ending with a foldable final
815 815 directory component.
816 816 """
817 817 s1 = os.stat(path)
818 818 d, b = os.path.split(path)
819 819 p2 = os.path.join(d, b.upper())
820 820 if path == p2:
821 821 p2 = os.path.join(d, b.lower())
822 822 try:
823 823 s2 = os.stat(p2)
824 824 if s2 == s1:
825 825 return False
826 826 return True
827 827 except:
828 828 return True
829 829
830 830 def checkexec(path):
831 831 """
832 832 Check whether the given path is on a filesystem with UNIX-like exec flags
833 833
834 834 Requires a directory (like /foo/.hg)
835 835 """
836 836
837 837 # VFAT on some Linux versions can flip mode but it doesn't persist
838 838 # a FS remount. Frequently we can detect it if files are created
839 839 # with exec bit on.
840 840
841 841 try:
842 842 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
843 843 fh, fn = tempfile.mkstemp("", "", path)
844 844 try:
845 845 os.close(fh)
846 846 m = os.stat(fn).st_mode & 0777
847 847 new_file_has_exec = m & EXECFLAGS
848 848 os.chmod(fn, m ^ EXECFLAGS)
849 849 exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
850 850 finally:
851 851 os.unlink(fn)
852 852 except (IOError, OSError):
853 853 # we don't care, the user probably won't be able to commit anyway
854 854 return False
855 855 return not (new_file_has_exec or exec_flags_cannot_flip)
856 856
857 857 def execfunc(path, fallback):
858 858 '''return an is_exec() function with default to fallback'''
859 859 if checkexec(path):
860 860 return lambda x: is_exec(os.path.join(path, x))
861 861 return fallback
862 862
863 863 def checklink(path):
864 864 """check whether the given path is on a symlink-capable filesystem"""
865 865 # mktemp is not racy because symlink creation will fail if the
866 866 # file already exists
867 867 name = tempfile.mktemp(dir=path)
868 868 try:
869 869 os.symlink(".", name)
870 870 os.unlink(name)
871 871 return True
872 872 except (OSError, AttributeError):
873 873 return False
874 874
875 875 def linkfunc(path, fallback):
876 876 '''return an is_link() function with default to fallback'''
877 877 if checklink(path):
878 878 return lambda x: os.path.islink(os.path.join(path, x))
879 879 return fallback
880 880
881 881 _umask = os.umask(0)
882 882 os.umask(_umask)
883 883
884 884 def needbinarypatch():
885 885 """return True if patches should be applied in binary mode by default."""
886 886 return os.name == 'nt'
887 887
888 888 def endswithsep(path):
889 889 '''Check path ends with os.sep or os.altsep.'''
890 890 return path.endswith(os.sep) or os.altsep and path.endswith(os.altsep)
891 891
892 892 def splitpath(path):
893 893 '''Split path by os.sep.
894 894 Note that this function does not use os.altsep because this is
895 895 an alternative of simple "xxx.split(os.sep)".
896 896 It is recommended to use os.path.normpath() before using this
897 897 function if need.'''
898 898 return path.split(os.sep)
899 899
900 900 def gui():
901 901 '''Are we running in a GUI?'''
902 902 return os.name == "nt" or os.name == "mac" or os.environ.get("DISPLAY")
903 903
904 904 def lookup_reg(key, name=None, scope=None):
905 905 return None
906 906
907 907 # Platform specific variants
908 908 if os.name == 'nt':
909 909 import msvcrt
910 910 nulldev = 'NUL:'
911 911
912 912 class winstdout:
913 913 '''stdout on windows misbehaves if sent through a pipe'''
914 914
915 915 def __init__(self, fp):
916 916 self.fp = fp
917 917
918 918 def __getattr__(self, key):
919 919 return getattr(self.fp, key)
920 920
921 921 def close(self):
922 922 try:
923 923 self.fp.close()
924 924 except: pass
925 925
926 926 def write(self, s):
927 927 try:
928 928 # This is workaround for "Not enough space" error on
929 929 # writing large size of data to console.
930 930 limit = 16000
931 931 l = len(s)
932 932 start = 0
933 933 while start < l:
934 934 end = start + limit
935 935 self.fp.write(s[start:end])
936 936 start = end
937 937 except IOError, inst:
938 938 if inst.errno != 0: raise
939 939 self.close()
940 940 raise IOError(errno.EPIPE, 'Broken pipe')
941 941
942 942 def flush(self):
943 943 try:
944 944 return self.fp.flush()
945 945 except IOError, inst:
946 946 if inst.errno != errno.EINVAL: raise
947 947 self.close()
948 948 raise IOError(errno.EPIPE, 'Broken pipe')
949 949
950 950 sys.stdout = winstdout(sys.stdout)
951 951
952 952 def _is_win_9x():
953 953 '''return true if run on windows 95, 98 or me.'''
954 954 try:
955 955 return sys.getwindowsversion()[3] == 1
956 956 except AttributeError:
957 957 return 'command' in os.environ.get('comspec', '')
958 958
959 959 def openhardlinks():
960 960 return not _is_win_9x and "win32api" in locals()
961 961
962 962 def system_rcpath():
963 963 try:
964 964 return system_rcpath_win32()
965 965 except:
966 966 return [r'c:\mercurial\mercurial.ini']
967 967
968 968 def user_rcpath():
969 969 '''return os-specific hgrc search path to the user dir'''
970 970 try:
971 971 userrc = user_rcpath_win32()
972 972 except:
973 973 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
974 974 path = [userrc]
975 975 userprofile = os.environ.get('USERPROFILE')
976 976 if userprofile:
977 977 path.append(os.path.join(userprofile, 'mercurial.ini'))
978 978 return path
979 979
980 980 def parse_patch_output(output_line):
981 981 """parses the output produced by patch and returns the file name"""
982 982 pf = output_line[14:]
983 983 if pf[0] == '`':
984 984 pf = pf[1:-1] # Remove the quotes
985 985 return pf
986 986
987 987 def sshargs(sshcmd, host, user, port):
988 988 '''Build argument list for ssh or Plink'''
989 989 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
990 990 args = user and ("%s@%s" % (user, host)) or host
991 991 return port and ("%s %s %s" % (args, pflag, port)) or args
992 992
993 993 def testpid(pid):
994 994 '''return False if pid dead, True if running or not known'''
995 995 return True
996 996
997 997 def set_flags(f, flags):
998 998 pass
999 999
1000 1000 def set_binary(fd):
1001 1001 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1002 1002
1003 1003 def pconvert(path):
1004 1004 return '/'.join(splitpath(path))
1005 1005
1006 1006 def localpath(path):
1007 1007 return path.replace('/', '\\')
1008 1008
1009 1009 def normpath(path):
1010 1010 return pconvert(os.path.normpath(path))
1011 1011
1012 1012 makelock = _makelock_file
1013 1013 readlock = _readlock_file
1014 1014
1015 1015 def samestat(s1, s2):
1016 1016 return False
1017 1017
1018 1018 # A sequence of backslashes is special iff it precedes a double quote:
1019 1019 # - if there's an even number of backslashes, the double quote is not
1020 1020 # quoted (i.e. it ends the quoted region)
1021 1021 # - if there's an odd number of backslashes, the double quote is quoted
1022 1022 # - in both cases, every pair of backslashes is unquoted into a single
1023 1023 # backslash
1024 1024 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1025 1025 # So, to quote a string, we must surround it in double quotes, double
1026 1026 # the number of backslashes that preceed double quotes and add another
1027 1027 # backslash before every double quote (being careful with the double
1028 1028 # quote we've appended to the end)
1029 1029 _quotere = None
1030 1030 def shellquote(s):
1031 1031 global _quotere
1032 1032 if _quotere is None:
1033 1033 _quotere = re.compile(r'(\\*)("|\\$)')
1034 1034 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1035 1035
1036 1036 def quotecommand(cmd):
1037 1037 """Build a command string suitable for os.popen* calls."""
1038 1038 # The extra quotes are needed because popen* runs the command
1039 1039 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1040 1040 return '"' + cmd + '"'
1041 1041
1042 1042 def popen(command):
1043 1043 # Work around "popen spawned process may not write to stdout
1044 1044 # under windows"
1045 1045 # http://bugs.python.org/issue1366
1046 1046 command += " 2> %s" % nulldev
1047 1047 return os.popen(quotecommand(command))
1048 1048
1049 1049 def explain_exit(code):
1050 1050 return _("exited with status %d") % code, code
1051 1051
1052 1052 # if you change this stub into a real check, please try to implement the
1053 1053 # username and groupname functions above, too.
1054 1054 def isowner(fp, st=None):
1055 1055 return True
1056 1056
1057 1057 def find_in_path(name, path, default=None):
1058 1058 '''find name in search path. path can be string (will be split
1059 1059 with os.pathsep), or iterable thing that returns strings. if name
1060 1060 found, return path to name. else return default. name is looked up
1061 1061 using cmd.exe rules, using PATHEXT.'''
1062 1062 if isinstance(path, str):
1063 1063 path = path.split(os.pathsep)
1064 1064
1065 1065 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1066 1066 pathext = pathext.lower().split(os.pathsep)
1067 1067 isexec = os.path.splitext(name)[1].lower() in pathext
1068 1068
1069 1069 for p in path:
1070 1070 p_name = os.path.join(p, name)
1071 1071
1072 1072 if isexec and os.path.exists(p_name):
1073 1073 return p_name
1074 1074
1075 1075 for ext in pathext:
1076 1076 p_name_ext = p_name + ext
1077 1077 if os.path.exists(p_name_ext):
1078 1078 return p_name_ext
1079 1079 return default
1080 1080
1081 1081 def set_signal_handler():
1082 1082 try:
1083 1083 set_signal_handler_win32()
1084 1084 except NameError:
1085 1085 pass
1086 1086
1087 1087 try:
1088 1088 # override functions with win32 versions if possible
1089 1089 from util_win32 import *
1090 1090 if not _is_win_9x():
1091 1091 posixfile = posixfile_nt
1092 1092 except ImportError:
1093 1093 pass
1094 1094
1095 1095 else:
1096 1096 nulldev = '/dev/null'
1097 1097
1098 1098 def rcfiles(path):
1099 1099 rcs = [os.path.join(path, 'hgrc')]
1100 1100 rcdir = os.path.join(path, 'hgrc.d')
1101 1101 try:
1102 1102 rcs.extend([os.path.join(rcdir, f)
1103 1103 for f, kind in osutil.listdir(rcdir)
1104 1104 if f.endswith(".rc")])
1105 1105 except OSError:
1106 1106 pass
1107 1107 return rcs
1108 1108
1109 1109 def system_rcpath():
1110 1110 path = []
1111 1111 # old mod_python does not set sys.argv
1112 1112 if len(getattr(sys, 'argv', [])) > 0:
1113 1113 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1114 1114 '/../etc/mercurial'))
1115 1115 path.extend(rcfiles('/etc/mercurial'))
1116 1116 return path
1117 1117
1118 1118 def user_rcpath():
1119 1119 return [os.path.expanduser('~/.hgrc')]
1120 1120
1121 1121 def parse_patch_output(output_line):
1122 1122 """parses the output produced by patch and returns the file name"""
1123 1123 pf = output_line[14:]
1124 1124 if os.sys.platform == 'OpenVMS':
1125 1125 if pf[0] == '`':
1126 1126 pf = pf[1:-1] # Remove the quotes
1127 1127 else:
1128 1128 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1129 1129 pf = pf[1:-1] # Remove the quotes
1130 1130 return pf
1131 1131
1132 1132 def sshargs(sshcmd, host, user, port):
1133 1133 '''Build argument list for ssh'''
1134 1134 args = user and ("%s@%s" % (user, host)) or host
1135 1135 return port and ("%s -p %s" % (args, port)) or args
1136 1136
1137 1137 def is_exec(f):
1138 1138 """check whether a file is executable"""
1139 1139 return (os.lstat(f).st_mode & 0100 != 0)
1140 1140
1141 1141 def set_flags(f, flags):
1142 1142 s = os.lstat(f).st_mode
1143 1143 x = "x" in flags
1144 1144 l = "l" in flags
1145 1145 if l:
1146 1146 if not stat.S_ISLNK(s):
1147 1147 # switch file to link
1148 1148 data = file(f).read()
1149 1149 os.unlink(f)
1150 1150 os.symlink(data, f)
1151 1151 # no chmod needed at this point
1152 1152 return
1153 1153 if stat.S_ISLNK(s):
1154 1154 # switch link to file
1155 1155 data = os.readlink(f)
1156 1156 os.unlink(f)
1157 1157 file(f, "w").write(data)
1158 1158 s = 0666 & ~_umask # avoid restatting for chmod
1159 1159
1160 1160 sx = s & 0100
1161 1161 if x and not sx:
1162 1162 # Turn on +x for every +r bit when making a file executable
1163 1163 # and obey umask.
1164 1164 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1165 1165 elif not x and sx:
1166 1166 # Turn off all +x bits
1167 1167 os.chmod(f, s & 0666)
1168 1168
1169 1169 def set_binary(fd):
1170 1170 pass
1171 1171
1172 1172 def pconvert(path):
1173 1173 return path
1174 1174
1175 1175 def localpath(path):
1176 1176 return path
1177 1177
1178 1178 normpath = os.path.normpath
1179 1179 samestat = os.path.samestat
1180 1180
1181 1181 def makelock(info, pathname):
1182 1182 try:
1183 1183 os.symlink(info, pathname)
1184 1184 except OSError, why:
1185 1185 if why.errno == errno.EEXIST:
1186 1186 raise
1187 1187 else:
1188 1188 _makelock_file(info, pathname)
1189 1189
1190 1190 def readlock(pathname):
1191 1191 try:
1192 1192 return os.readlink(pathname)
1193 1193 except OSError, why:
1194 1194 if why.errno in (errno.EINVAL, errno.ENOSYS):
1195 1195 return _readlock_file(pathname)
1196 1196 else:
1197 1197 raise
1198 1198
1199 1199 def shellquote(s):
1200 1200 if os.sys.platform == 'OpenVMS':
1201 1201 return '"%s"' % s
1202 1202 else:
1203 1203 return "'%s'" % s.replace("'", "'\\''")
1204 1204
1205 1205 def quotecommand(cmd):
1206 1206 return cmd
1207 1207
1208 1208 def popen(command):
1209 1209 return os.popen(command)
1210 1210
1211 1211 def testpid(pid):
1212 1212 '''return False if pid dead, True if running or not sure'''
1213 1213 if os.sys.platform == 'OpenVMS':
1214 1214 return True
1215 1215 try:
1216 1216 os.kill(pid, 0)
1217 1217 return True
1218 1218 except OSError, inst:
1219 1219 return inst.errno != errno.ESRCH
1220 1220
1221 1221 def explain_exit(code):
1222 1222 """return a 2-tuple (desc, code) describing a process's status"""
1223 1223 if os.WIFEXITED(code):
1224 1224 val = os.WEXITSTATUS(code)
1225 1225 return _("exited with status %d") % val, val
1226 1226 elif os.WIFSIGNALED(code):
1227 1227 val = os.WTERMSIG(code)
1228 1228 return _("killed by signal %d") % val, val
1229 1229 elif os.WIFSTOPPED(code):
1230 1230 val = os.WSTOPSIG(code)
1231 1231 return _("stopped by signal %d") % val, val
1232 1232 raise ValueError(_("invalid exit code"))
1233 1233
1234 1234 def isowner(fp, st=None):
1235 1235 """Return True if the file object f belongs to the current user.
1236 1236
1237 1237 The return value of a util.fstat(f) may be passed as the st argument.
1238 1238 """
1239 1239 if st is None:
1240 1240 st = fstat(fp)
1241 1241 return st.st_uid == os.getuid()
1242 1242
1243 1243 def find_in_path(name, path, default=None):
1244 1244 '''find name in search path. path can be string (will be split
1245 1245 with os.pathsep), or iterable thing that returns strings. if name
1246 1246 found, return path to name. else return default.'''
1247 1247 if isinstance(path, str):
1248 1248 path = path.split(os.pathsep)
1249 1249 for p in path:
1250 1250 p_name = os.path.join(p, name)
1251 1251 if os.path.exists(p_name):
1252 1252 return p_name
1253 1253 return default
1254 1254
1255 1255 def set_signal_handler():
1256 1256 pass
1257 1257
1258 1258 def find_exe(name, default=None):
1259 1259 '''find path of an executable.
1260 1260 if name contains a path component, return it as is. otherwise,
1261 1261 use normal executable search path.'''
1262 1262
1263 1263 if os.sep in name or sys.platform == 'OpenVMS':
1264 1264 # don't check the executable bit. if the file isn't
1265 1265 # executable, whoever tries to actually run it will give a
1266 1266 # much more useful error message.
1267 1267 return name
1268 1268 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1269 1269
1270 1270 def _buildencodefun():
1271 1271 e = '_'
1272 1272 win_reserved = [ord(x) for x in '\\:*?"<>|']
1273 1273 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1274 1274 for x in (range(32) + range(126, 256) + win_reserved):
1275 1275 cmap[chr(x)] = "~%02x" % x
1276 1276 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1277 1277 cmap[chr(x)] = e + chr(x).lower()
1278 1278 dmap = {}
1279 1279 for k, v in cmap.iteritems():
1280 1280 dmap[v] = k
1281 1281 def decode(s):
1282 1282 i = 0
1283 1283 while i < len(s):
1284 1284 for l in xrange(1, 4):
1285 1285 try:
1286 1286 yield dmap[s[i:i+l]]
1287 1287 i += l
1288 1288 break
1289 1289 except KeyError:
1290 1290 pass
1291 1291 else:
1292 1292 raise KeyError
1293 1293 return (lambda s: "".join([cmap[c] for c in s]),
1294 1294 lambda s: "".join(list(decode(s))))
1295 1295
1296 1296 encodefilename, decodefilename = _buildencodefun()
1297 1297
1298 1298 def encodedopener(openerfn, fn):
1299 1299 def o(path, *args, **kw):
1300 1300 return openerfn(fn(path), *args, **kw)
1301 1301 return o
1302 1302
1303 1303 def mktempcopy(name, emptyok=False, createmode=None):
1304 1304 """Create a temporary file with the same contents from name
1305 1305
1306 1306 The permission bits are copied from the original file.
1307 1307
1308 1308 If the temporary file is going to be truncated immediately, you
1309 1309 can use emptyok=True as an optimization.
1310 1310
1311 1311 Returns the name of the temporary file.
1312 1312 """
1313 1313 d, fn = os.path.split(name)
1314 1314 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1315 1315 os.close(fd)
1316 1316 # Temporary files are created with mode 0600, which is usually not
1317 1317 # what we want. If the original file already exists, just copy
1318 1318 # its mode. Otherwise, manually obey umask.
1319 1319 try:
1320 1320 st_mode = os.lstat(name).st_mode & 0777
1321 1321 except OSError, inst:
1322 1322 if inst.errno != errno.ENOENT:
1323 1323 raise
1324 1324 st_mode = createmode
1325 1325 if st_mode is None:
1326 1326 st_mode = ~_umask
1327 1327 st_mode &= 0666
1328 1328 os.chmod(temp, st_mode)
1329 1329 if emptyok:
1330 1330 return temp
1331 1331 try:
1332 1332 try:
1333 1333 ifp = posixfile(name, "rb")
1334 1334 except IOError, inst:
1335 1335 if inst.errno == errno.ENOENT:
1336 1336 return temp
1337 1337 if not getattr(inst, 'filename', None):
1338 1338 inst.filename = name
1339 1339 raise
1340 1340 ofp = posixfile(temp, "wb")
1341 1341 for chunk in filechunkiter(ifp):
1342 1342 ofp.write(chunk)
1343 1343 ifp.close()
1344 1344 ofp.close()
1345 1345 except:
1346 1346 try: os.unlink(temp)
1347 1347 except: pass
1348 1348 raise
1349 1349 return temp
1350 1350
1351 1351 class atomictempfile(posixfile):
1352 1352 """file-like object that atomically updates a file
1353 1353
1354 1354 All writes will be redirected to a temporary copy of the original
1355 1355 file. When rename is called, the copy is renamed to the original
1356 1356 name, making the changes visible.
1357 1357 """
1358 1358 def __init__(self, name, mode, createmode):
1359 1359 self.__name = name
1360 1360 self.temp = mktempcopy(name, emptyok=('w' in mode),
1361 1361 createmode=createmode)
1362 1362 posixfile.__init__(self, self.temp, mode)
1363 1363
1364 1364 def rename(self):
1365 1365 if not self.closed:
1366 1366 posixfile.close(self)
1367 1367 rename(self.temp, localpath(self.__name))
1368 1368
1369 1369 def __del__(self):
1370 1370 if not self.closed:
1371 1371 try:
1372 1372 os.unlink(self.temp)
1373 1373 except: pass
1374 1374 posixfile.close(self)
1375 1375
1376 1376 def makedirs(name, mode=None):
1377 1377 """recursive directory creation with parent mode inheritance"""
1378 1378 try:
1379 1379 os.mkdir(name)
1380 1380 if mode is not None:
1381 1381 os.chmod(name, mode)
1382 1382 return
1383 1383 except OSError, err:
1384 1384 if err.errno == errno.EEXIST:
1385 1385 return
1386 1386 if err.errno != errno.ENOENT:
1387 1387 raise
1388 1388 parent = os.path.abspath(os.path.dirname(name))
1389 1389 makedirs(parent, mode)
1390 1390 makedirs(name, mode)
1391 1391
1392 1392 class opener(object):
1393 1393 """Open files relative to a base directory
1394 1394
1395 1395 This class is used to hide the details of COW semantics and
1396 1396 remote file access from higher level code.
1397 1397 """
1398 1398 def __init__(self, base, audit=True):
1399 1399 self.base = base
1400 1400 if audit:
1401 1401 self.audit_path = path_auditor(base)
1402 1402 else:
1403 1403 self.audit_path = always
1404 1404 self.createmode = None
1405 1405
1406 1406 def __getattr__(self, name):
1407 1407 if name == '_can_symlink':
1408 1408 self._can_symlink = checklink(self.base)
1409 1409 return self._can_symlink
1410 1410 raise AttributeError(name)
1411 1411
1412 1412 def _fixfilemode(self, name):
1413 1413 if self.createmode is None:
1414 1414 return
1415 1415 os.chmod(name, self.createmode & 0666)
1416 1416
1417 1417 def __call__(self, path, mode="r", text=False, atomictemp=False):
1418 1418 self.audit_path(path)
1419 1419 f = os.path.join(self.base, path)
1420 1420
1421 1421 if not text and "b" not in mode:
1422 1422 mode += "b" # for that other OS
1423 1423
1424 1424 nlink = -1
1425 1425 if mode[0] != "r":
1426 1426 try:
1427 1427 nlink = nlinks(f)
1428 1428 except OSError:
1429 1429 nlink = 0
1430 1430 d = os.path.dirname(f)
1431 1431 if not os.path.isdir(d):
1432 1432 makedirs(d, self.createmode)
1433 1433 if atomictemp:
1434 1434 return atomictempfile(f, mode, self.createmode)
1435 1435 if nlink > 1:
1436 1436 rename(mktempcopy(f), f)
1437 1437 fp = posixfile(f, mode)
1438 1438 if nlink == 0:
1439 1439 self._fixfilemode(f)
1440 1440 return fp
1441 1441
1442 1442 def symlink(self, src, dst):
1443 1443 self.audit_path(dst)
1444 1444 linkname = os.path.join(self.base, dst)
1445 1445 try:
1446 1446 os.unlink(linkname)
1447 1447 except OSError:
1448 1448 pass
1449 1449
1450 1450 dirname = os.path.dirname(linkname)
1451 1451 if not os.path.exists(dirname):
1452 1452 makedirs(dirname, self.createmode)
1453 1453
1454 1454 if self._can_symlink:
1455 1455 try:
1456 1456 os.symlink(src, linkname)
1457 1457 except OSError, err:
1458 1458 raise OSError(err.errno, _('could not symlink to %r: %s') %
1459 1459 (src, err.strerror), linkname)
1460 1460 else:
1461 1461 f = self(dst, "w")
1462 1462 f.write(src)
1463 1463 f.close()
1464 1464 self._fixfilemode(dst)
1465 1465
1466 1466 class chunkbuffer(object):
1467 1467 """Allow arbitrary sized chunks of data to be efficiently read from an
1468 1468 iterator over chunks of arbitrary size."""
1469 1469
1470 1470 def __init__(self, in_iter):
1471 1471 """in_iter is the iterator that's iterating over the input chunks.
1472 1472 targetsize is how big a buffer to try to maintain."""
1473 1473 self.iter = iter(in_iter)
1474 1474 self.buf = ''
1475 1475 self.targetsize = 2**16
1476 1476
1477 1477 def read(self, l):
1478 1478 """Read L bytes of data from the iterator of chunks of data.
1479 1479 Returns less than L bytes if the iterator runs dry."""
1480 1480 if l > len(self.buf) and self.iter:
1481 1481 # Clamp to a multiple of self.targetsize
1482 1482 targetsize = max(l, self.targetsize)
1483 1483 collector = cStringIO.StringIO()
1484 1484 collector.write(self.buf)
1485 1485 collected = len(self.buf)
1486 1486 for chunk in self.iter:
1487 1487 collector.write(chunk)
1488 1488 collected += len(chunk)
1489 1489 if collected >= targetsize:
1490 1490 break
1491 1491 if collected < targetsize:
1492 1492 self.iter = False
1493 1493 self.buf = collector.getvalue()
1494 1494 if len(self.buf) == l:
1495 1495 s, self.buf = str(self.buf), ''
1496 1496 else:
1497 1497 s, self.buf = self.buf[:l], buffer(self.buf, l)
1498 1498 return s
1499 1499
1500 1500 def filechunkiter(f, size=65536, limit=None):
1501 1501 """Create a generator that produces the data in the file size
1502 1502 (default 65536) bytes at a time, up to optional limit (default is
1503 1503 to read all data). Chunks may be less than size bytes if the
1504 1504 chunk is the last chunk in the file, or the file is a socket or
1505 1505 some other type of file that sometimes reads less data than is
1506 1506 requested."""
1507 1507 assert size >= 0
1508 1508 assert limit is None or limit >= 0
1509 1509 while True:
1510 1510 if limit is None: nbytes = size
1511 1511 else: nbytes = min(limit, size)
1512 1512 s = nbytes and f.read(nbytes)
1513 1513 if not s: break
1514 1514 if limit: limit -= len(s)
1515 1515 yield s
1516 1516
1517 1517 def makedate():
1518 1518 lt = time.localtime()
1519 1519 if lt[8] == 1 and time.daylight:
1520 1520 tz = time.altzone
1521 1521 else:
1522 1522 tz = time.timezone
1523 1523 return time.mktime(lt), tz
1524 1524
1525 1525 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1526 1526 """represent a (unixtime, offset) tuple as a localized time.
1527 1527 unixtime is seconds since the epoch, and offset is the time zone's
1528 1528 number of seconds away from UTC. if timezone is false, do not
1529 1529 append time zone to string."""
1530 1530 t, tz = date or makedate()
1531 1531 s = time.strftime(format, time.gmtime(float(t) - tz))
1532 1532 if timezone:
1533 1533 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1534 1534 return s
1535 1535
1536 1536 def shortdate(date=None):
1537 1537 """turn (timestamp, tzoff) tuple into iso 8631 date."""
1538 1538 return datestr(date, format='%Y-%m-%d', timezone=False)
1539 1539
1540 1540 def strdate(string, format, defaults=[]):
1541 1541 """parse a localized time string and return a (unixtime, offset) tuple.
1542 1542 if the string cannot be parsed, ValueError is raised."""
1543 1543 def timezone(string):
1544 1544 tz = string.split()[-1]
1545 1545 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1546 1546 tz = int(tz)
1547 1547 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1548 1548 return offset
1549 1549 if tz == "GMT" or tz == "UTC":
1550 1550 return 0
1551 1551 return None
1552 1552
1553 1553 # NOTE: unixtime = localunixtime + offset
1554 1554 offset, date = timezone(string), string
1555 1555 if offset != None:
1556 1556 date = " ".join(string.split()[:-1])
1557 1557
1558 1558 # add missing elements from defaults
1559 1559 for part in defaults:
1560 1560 found = [True for p in part if ("%"+p) in format]
1561 1561 if not found:
1562 1562 date += "@" + defaults[part]
1563 1563 format += "@%" + part[0]
1564 1564
1565 1565 timetuple = time.strptime(date, format)
1566 1566 localunixtime = int(calendar.timegm(timetuple))
1567 1567 if offset is None:
1568 1568 # local timezone
1569 1569 unixtime = int(time.mktime(timetuple))
1570 1570 offset = unixtime - localunixtime
1571 1571 else:
1572 1572 unixtime = localunixtime + offset
1573 1573 return unixtime, offset
1574 1574
1575 1575 def parsedate(date, formats=None, defaults=None):
1576 1576 """parse a localized date/time string and return a (unixtime, offset) tuple.
1577 1577
1578 1578 The date may be a "unixtime offset" string or in one of the specified
1579 1579 formats. If the date already is a (unixtime, offset) tuple, it is returned.
1580 1580 """
1581 1581 if not date:
1582 1582 return 0, 0
1583 1583 if type(date) is type((0, 0)) and len(date) == 2:
1584 1584 return date
1585 1585 if not formats:
1586 1586 formats = defaultdateformats
1587 1587 date = date.strip()
1588 1588 try:
1589 1589 when, offset = map(int, date.split(' '))
1590 1590 except ValueError:
1591 1591 # fill out defaults
1592 1592 if not defaults:
1593 1593 defaults = {}
1594 1594 now = makedate()
1595 1595 for part in "d mb yY HI M S".split():
1596 1596 if part not in defaults:
1597 1597 if part[0] in "HMS":
1598 1598 defaults[part] = "00"
1599 1599 elif part[0] in "dm":
1600 1600 defaults[part] = "1"
1601 1601 else:
1602 1602 defaults[part] = datestr(now, "%" + part[0], False)
1603 1603
1604 1604 for format in formats:
1605 1605 try:
1606 1606 when, offset = strdate(date, format, defaults)
1607 1607 except (ValueError, OverflowError):
1608 1608 pass
1609 1609 else:
1610 1610 break
1611 1611 else:
1612 1612 raise Abort(_('invalid date: %r ') % date)
1613 1613 # validate explicit (probably user-specified) date and
1614 1614 # time zone offset. values must fit in signed 32 bits for
1615 1615 # current 32-bit linux runtimes. timezones go from UTC-12
1616 1616 # to UTC+14
1617 1617 if abs(when) > 0x7fffffff:
1618 1618 raise Abort(_('date exceeds 32 bits: %d') % when)
1619 1619 if offset < -50400 or offset > 43200:
1620 1620 raise Abort(_('impossible time zone offset: %d') % offset)
1621 1621 return when, offset
1622 1622
1623 1623 def matchdate(date):
1624 1624 """Return a function that matches a given date match specifier
1625 1625
1626 1626 Formats include:
1627 1627
1628 1628 '{date}' match a given date to the accuracy provided
1629 1629
1630 1630 '<{date}' on or before a given date
1631 1631
1632 1632 '>{date}' on or after a given date
1633 1633
1634 1634 """
1635 1635
1636 1636 def lower(date):
1637 1637 return parsedate(date, extendeddateformats)[0]
1638 1638
1639 1639 def upper(date):
1640 1640 d = dict(mb="12", HI="23", M="59", S="59")
1641 1641 for days in "31 30 29".split():
1642 1642 try:
1643 1643 d["d"] = days
1644 1644 return parsedate(date, extendeddateformats, d)[0]
1645 1645 except:
1646 1646 pass
1647 1647 d["d"] = "28"
1648 1648 return parsedate(date, extendeddateformats, d)[0]
1649 1649
1650 1650 if date[0] == "<":
1651 1651 when = upper(date[1:])
1652 1652 return lambda x: x <= when
1653 1653 elif date[0] == ">":
1654 1654 when = lower(date[1:])
1655 1655 return lambda x: x >= when
1656 1656 elif date[0] == "-":
1657 1657 try:
1658 1658 days = int(date[1:])
1659 1659 except ValueError:
1660 1660 raise Abort(_("invalid day spec: %s") % date[1:])
1661 1661 when = makedate()[0] - days * 3600 * 24
1662 1662 return lambda x: x >= when
1663 1663 elif " to " in date:
1664 1664 a, b = date.split(" to ")
1665 1665 start, stop = lower(a), upper(b)
1666 1666 return lambda x: x >= start and x <= stop
1667 1667 else:
1668 1668 start, stop = lower(date), upper(date)
1669 1669 return lambda x: x >= start and x <= stop
1670 1670
1671 1671 def shortuser(user):
1672 1672 """Return a short representation of a user name or email address."""
1673 1673 f = user.find('@')
1674 1674 if f >= 0:
1675 1675 user = user[:f]
1676 1676 f = user.find('<')
1677 1677 if f >= 0:
1678 1678 user = user[f+1:]
1679 1679 f = user.find(' ')
1680 1680 if f >= 0:
1681 1681 user = user[:f]
1682 1682 f = user.find('.')
1683 1683 if f >= 0:
1684 1684 user = user[:f]
1685 1685 return user
1686 1686
1687 1687 def email(author):
1688 1688 '''get email of author.'''
1689 1689 r = author.find('>')
1690 1690 if r == -1: r = None
1691 1691 return author[author.find('<')+1:r]
1692 1692
1693 1693 def ellipsis(text, maxlength=400):
1694 1694 """Trim string to at most maxlength (default: 400) characters."""
1695 1695 if len(text) <= maxlength:
1696 1696 return text
1697 1697 else:
1698 1698 return "%s..." % (text[:maxlength-3])
1699 1699
1700 1700 def walkrepos(path):
1701 1701 '''yield every hg repository under path, recursively.'''
1702 1702 def errhandler(err):
1703 1703 if err.filename == path:
1704 1704 raise err
1705 1705
1706 1706 for root, dirs, files in os.walk(path, onerror=errhandler):
1707 for d in dirs:
1708 if d == '.hg':
1709 yield root
1710 dirs[:] = []
1711 break
1707 if '.hg' in dirs:
1708 dirs[:] = [] # don't descend further
1709 yield root # found a repository
1712 1710
1713 1711 _rcpath = None
1714 1712
1715 1713 def os_rcpath():
1716 1714 '''return default os-specific hgrc search path'''
1717 1715 path = system_rcpath()
1718 1716 path.extend(user_rcpath())
1719 1717 path = [os.path.normpath(f) for f in path]
1720 1718 return path
1721 1719
1722 1720 def rcpath():
1723 1721 '''return hgrc search path. if env var HGRCPATH is set, use it.
1724 1722 for each item in path, if directory, use files ending in .rc,
1725 1723 else use item.
1726 1724 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1727 1725 if no HGRCPATH, use default os-specific path.'''
1728 1726 global _rcpath
1729 1727 if _rcpath is None:
1730 1728 if 'HGRCPATH' in os.environ:
1731 1729 _rcpath = []
1732 1730 for p in os.environ['HGRCPATH'].split(os.pathsep):
1733 1731 if not p: continue
1734 1732 if os.path.isdir(p):
1735 1733 for f, kind in osutil.listdir(p):
1736 1734 if f.endswith('.rc'):
1737 1735 _rcpath.append(os.path.join(p, f))
1738 1736 else:
1739 1737 _rcpath.append(p)
1740 1738 else:
1741 1739 _rcpath = os_rcpath()
1742 1740 return _rcpath
1743 1741
1744 1742 def bytecount(nbytes):
1745 1743 '''return byte count formatted as readable string, with units'''
1746 1744
1747 1745 units = (
1748 1746 (100, 1<<30, _('%.0f GB')),
1749 1747 (10, 1<<30, _('%.1f GB')),
1750 1748 (1, 1<<30, _('%.2f GB')),
1751 1749 (100, 1<<20, _('%.0f MB')),
1752 1750 (10, 1<<20, _('%.1f MB')),
1753 1751 (1, 1<<20, _('%.2f MB')),
1754 1752 (100, 1<<10, _('%.0f KB')),
1755 1753 (10, 1<<10, _('%.1f KB')),
1756 1754 (1, 1<<10, _('%.2f KB')),
1757 1755 (1, 1, _('%.0f bytes')),
1758 1756 )
1759 1757
1760 1758 for multiplier, divisor, format in units:
1761 1759 if nbytes >= divisor * multiplier:
1762 1760 return format % (nbytes / float(divisor))
1763 1761 return units[-1][2] % nbytes
1764 1762
1765 1763 def drop_scheme(scheme, path):
1766 1764 sc = scheme + ':'
1767 1765 if path.startswith(sc):
1768 1766 path = path[len(sc):]
1769 1767 if path.startswith('//'):
1770 1768 path = path[2:]
1771 1769 return path
1772 1770
1773 1771 def uirepr(s):
1774 1772 # Avoid double backslash in Windows path repr()
1775 1773 return repr(s).replace('\\\\', '\\')
1776 1774
1777 1775 def hidepassword(url):
1778 1776 '''hide user credential in a url string'''
1779 1777 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
1780 1778 netloc = re.sub('([^:]*):([^@]*)@(.*)', r'\1:***@\3', netloc)
1781 1779 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
1782 1780
1783 1781 def removeauth(url):
1784 1782 '''remove all authentication information from a url string'''
1785 1783 scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
1786 1784 netloc = netloc[netloc.find('@')+1:]
1787 1785 return urlparse.urlunparse((scheme, netloc, path, params, query, fragment))
General Comments 0
You need to be logged in to leave comments. Login now