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