##// END OF EJS Templates
Tidy code, fix typo
Bryan O'Sullivan -
r6500:a3175cd7 default
parent child Browse files
Show More
@@ -1,1772 +1,1773 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 re, urlparse, imp
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 main_is_frozen():
557 557 """return True if we are a frozen executable.
558 558
559 559 The code supports py2exe (most common, Windows only) and tools/freeze
560 560 (portable, not much used).
561 561 """
562 562 return (hasattr(sys, "frozen") or # new py2exe
563 563 hasattr(sys, "importers") or # old py2exe
564 564 imp.is_frozen("__main__")) # tools/freeze
565 565
566 566 def hgexecutable():
567 567 """return location of the 'hg' executable.
568 568
569 569 Defaults to $HG or 'hg' in the search path.
570 570 """
571 571 if _hgexecutable is None:
572 if os.environ.has_key('HG'):
573 set_hgexecutable(os.environ.get('HG'))
572 hg = os.environ.get('HG')
573 if hg:
574 set_hgexecutable(hg)
574 575 elif main_is_frozen():
575 576 set_hgexecutable(sys.executable)
576 577 else:
577 sel_hgexecutable(find_exe('hg', 'hg'))
578 set_hgexecutable(find_exe('hg', 'hg'))
578 579 return _hgexecutable
579 580
580 581 def set_hgexecutable(path):
581 582 """set location of the 'hg' executable"""
582 583 global _hgexecutable
583 584 _hgexecutable = path
584 585
585 586 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
586 587 '''enhanced shell command execution.
587 588 run with environment maybe modified, maybe in different dir.
588 589
589 590 if command fails and onerr is None, return status. if ui object,
590 591 print error message and return status, else raise onerr object as
591 592 exception.'''
592 593 def py2shell(val):
593 594 'convert python object into string that is useful to shell'
594 595 if val in (None, False):
595 596 return '0'
596 597 if val == True:
597 598 return '1'
598 599 return str(val)
599 600 oldenv = {}
600 601 for k in environ:
601 602 oldenv[k] = os.environ.get(k)
602 603 if cwd is not None:
603 604 oldcwd = os.getcwd()
604 605 origcmd = cmd
605 606 if os.name == 'nt':
606 607 cmd = '"%s"' % cmd
607 608 try:
608 609 for k, v in environ.iteritems():
609 610 os.environ[k] = py2shell(v)
610 611 os.environ['HG'] = hgexecutable()
611 612 if cwd is not None and oldcwd != cwd:
612 613 os.chdir(cwd)
613 614 rc = os.system(cmd)
614 615 if sys.platform == 'OpenVMS' and rc & 1:
615 616 rc = 0
616 617 if rc and onerr:
617 618 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
618 619 explain_exit(rc)[0])
619 620 if errprefix:
620 621 errmsg = '%s: %s' % (errprefix, errmsg)
621 622 try:
622 623 onerr.warn(errmsg + '\n')
623 624 except AttributeError:
624 625 raise onerr(errmsg)
625 626 return rc
626 627 finally:
627 628 for k, v in oldenv.iteritems():
628 629 if v is None:
629 630 del os.environ[k]
630 631 else:
631 632 os.environ[k] = v
632 633 if cwd is not None and oldcwd != cwd:
633 634 os.chdir(oldcwd)
634 635
635 636 # os.path.lexists is not available on python2.3
636 637 def lexists(filename):
637 638 "test whether a file with this name exists. does not follow symlinks"
638 639 try:
639 640 os.lstat(filename)
640 641 except:
641 642 return False
642 643 return True
643 644
644 645 def rename(src, dst):
645 646 """forcibly rename a file"""
646 647 try:
647 648 os.rename(src, dst)
648 649 except OSError, err: # FIXME: check err (EEXIST ?)
649 650 # on windows, rename to existing file is not allowed, so we
650 651 # must delete destination first. but if file is open, unlink
651 652 # schedules it for delete but does not delete it. rename
652 653 # happens immediately even for open files, so we create
653 654 # temporary file, delete it, rename destination to that name,
654 655 # then delete that. then rename is safe to do.
655 656 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
656 657 os.close(fd)
657 658 os.unlink(temp)
658 659 os.rename(dst, temp)
659 660 os.unlink(temp)
660 661 os.rename(src, dst)
661 662
662 663 def unlink(f):
663 664 """unlink and remove the directory if it is empty"""
664 665 os.unlink(f)
665 666 # try removing directories that might now be empty
666 667 try:
667 668 os.removedirs(os.path.dirname(f))
668 669 except OSError:
669 670 pass
670 671
671 672 def copyfile(src, dest):
672 673 "copy a file, preserving mode"
673 674 if os.path.islink(src):
674 675 try:
675 676 os.unlink(dest)
676 677 except:
677 678 pass
678 679 os.symlink(os.readlink(src), dest)
679 680 else:
680 681 try:
681 682 shutil.copyfile(src, dest)
682 683 shutil.copymode(src, dest)
683 684 except shutil.Error, inst:
684 685 raise Abort(str(inst))
685 686
686 687 def copyfiles(src, dst, hardlink=None):
687 688 """Copy a directory tree using hardlinks if possible"""
688 689
689 690 if hardlink is None:
690 691 hardlink = (os.stat(src).st_dev ==
691 692 os.stat(os.path.dirname(dst)).st_dev)
692 693
693 694 if os.path.isdir(src):
694 695 os.mkdir(dst)
695 696 for name, kind in osutil.listdir(src):
696 697 srcname = os.path.join(src, name)
697 698 dstname = os.path.join(dst, name)
698 699 copyfiles(srcname, dstname, hardlink)
699 700 else:
700 701 if hardlink:
701 702 try:
702 703 os_link(src, dst)
703 704 except (IOError, OSError):
704 705 hardlink = False
705 706 shutil.copy(src, dst)
706 707 else:
707 708 shutil.copy(src, dst)
708 709
709 710 class path_auditor(object):
710 711 '''ensure that a filesystem path contains no banned components.
711 712 the following properties of a path are checked:
712 713
713 714 - under top-level .hg
714 715 - starts at the root of a windows drive
715 716 - contains ".."
716 717 - traverses a symlink (e.g. a/symlink_here/b)
717 718 - inside a nested repository'''
718 719
719 720 def __init__(self, root):
720 721 self.audited = set()
721 722 self.auditeddir = set()
722 723 self.root = root
723 724
724 725 def __call__(self, path):
725 726 if path in self.audited:
726 727 return
727 728 normpath = os.path.normcase(path)
728 729 parts = normpath.split(os.sep)
729 730 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
730 731 or os.pardir in parts):
731 732 raise Abort(_("path contains illegal component: %s") % path)
732 733 def check(prefix):
733 734 curpath = os.path.join(self.root, prefix)
734 735 try:
735 736 st = os.lstat(curpath)
736 737 except OSError, err:
737 738 # EINVAL can be raised as invalid path syntax under win32.
738 739 # They must be ignored for patterns can be checked too.
739 740 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
740 741 raise
741 742 else:
742 743 if stat.S_ISLNK(st.st_mode):
743 744 raise Abort(_('path %r traverses symbolic link %r') %
744 745 (path, prefix))
745 746 elif (stat.S_ISDIR(st.st_mode) and
746 747 os.path.isdir(os.path.join(curpath, '.hg'))):
747 748 raise Abort(_('path %r is inside repo %r') %
748 749 (path, prefix))
749 750
750 751 prefixes = []
751 752 for c in strutil.rfindall(normpath, os.sep):
752 753 prefix = normpath[:c]
753 754 if prefix in self.auditeddir:
754 755 break
755 756 check(prefix)
756 757 prefixes.append(prefix)
757 758
758 759 self.audited.add(path)
759 760 # only add prefixes to the cache after checking everything: we don't
760 761 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
761 762 self.auditeddir.update(prefixes)
762 763
763 764 def _makelock_file(info, pathname):
764 765 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
765 766 os.write(ld, info)
766 767 os.close(ld)
767 768
768 769 def _readlock_file(pathname):
769 770 return posixfile(pathname).read()
770 771
771 772 def nlinks(pathname):
772 773 """Return number of hardlinks for the given file."""
773 774 return os.lstat(pathname).st_nlink
774 775
775 776 if hasattr(os, 'link'):
776 777 os_link = os.link
777 778 else:
778 779 def os_link(src, dst):
779 780 raise OSError(0, _("Hardlinks not supported"))
780 781
781 782 def fstat(fp):
782 783 '''stat file object that may not have fileno method.'''
783 784 try:
784 785 return os.fstat(fp.fileno())
785 786 except AttributeError:
786 787 return os.stat(fp.name)
787 788
788 789 posixfile = file
789 790
790 791 def openhardlinks():
791 792 '''return true if it is safe to hold open file handles to hardlinks'''
792 793 return True
793 794
794 795 getuser_fallback = None
795 796
796 797 def getuser():
797 798 '''return name of current user'''
798 799 try:
799 800 return getpass.getuser()
800 801 except ImportError:
801 802 # import of pwd will fail on windows - try fallback
802 803 if getuser_fallback:
803 804 return getuser_fallback()
804 805 # raised if win32api not available
805 806 raise Abort(_('user name not available - set USERNAME '
806 807 'environment variable'))
807 808
808 809 def username(uid=None):
809 810 """Return the name of the user with the given uid.
810 811
811 812 If uid is None, return the name of the current user."""
812 813 try:
813 814 import pwd
814 815 if uid is None:
815 816 uid = os.getuid()
816 817 try:
817 818 return pwd.getpwuid(uid)[0]
818 819 except KeyError:
819 820 return str(uid)
820 821 except ImportError:
821 822 return None
822 823
823 824 def groupname(gid=None):
824 825 """Return the name of the group with the given gid.
825 826
826 827 If gid is None, return the name of the current group."""
827 828 try:
828 829 import grp
829 830 if gid is None:
830 831 gid = os.getgid()
831 832 try:
832 833 return grp.getgrgid(gid)[0]
833 834 except KeyError:
834 835 return str(gid)
835 836 except ImportError:
836 837 return None
837 838
838 839 # File system features
839 840
840 841 def checkfolding(path):
841 842 """
842 843 Check whether the given path is on a case-sensitive filesystem
843 844
844 845 Requires a path (like /foo/.hg) ending with a foldable final
845 846 directory component.
846 847 """
847 848 s1 = os.stat(path)
848 849 d, b = os.path.split(path)
849 850 p2 = os.path.join(d, b.upper())
850 851 if path == p2:
851 852 p2 = os.path.join(d, b.lower())
852 853 try:
853 854 s2 = os.stat(p2)
854 855 if s2 == s1:
855 856 return False
856 857 return True
857 858 except:
858 859 return True
859 860
860 861 def checkexec(path):
861 862 """
862 863 Check whether the given path is on a filesystem with UNIX-like exec flags
863 864
864 865 Requires a directory (like /foo/.hg)
865 866 """
866 867 try:
867 868 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
868 869 fh, fn = tempfile.mkstemp("", "", path)
869 870 os.close(fh)
870 871 m = os.stat(fn).st_mode
871 872 # VFAT on Linux can flip mode but it doesn't persist a FS remount.
872 873 # frequently we can detect it if files are created with exec bit on.
873 874 new_file_has_exec = m & EXECFLAGS
874 875 os.chmod(fn, m ^ EXECFLAGS)
875 876 exec_flags_cannot_flip = (os.stat(fn).st_mode == m)
876 877 os.unlink(fn)
877 878 except (IOError,OSError):
878 879 # we don't care, the user probably won't be able to commit anyway
879 880 return False
880 881 return not (new_file_has_exec or exec_flags_cannot_flip)
881 882
882 883 def execfunc(path, fallback):
883 884 '''return an is_exec() function with default to fallback'''
884 885 if checkexec(path):
885 886 return lambda x: is_exec(os.path.join(path, x))
886 887 return fallback
887 888
888 889 def checklink(path):
889 890 """check whether the given path is on a symlink-capable filesystem"""
890 891 # mktemp is not racy because symlink creation will fail if the
891 892 # file already exists
892 893 name = tempfile.mktemp(dir=path)
893 894 try:
894 895 os.symlink(".", name)
895 896 os.unlink(name)
896 897 return True
897 898 except (OSError, AttributeError):
898 899 return False
899 900
900 901 def linkfunc(path, fallback):
901 902 '''return an is_link() function with default to fallback'''
902 903 if checklink(path):
903 904 return lambda x: os.path.islink(os.path.join(path, x))
904 905 return fallback
905 906
906 907 _umask = os.umask(0)
907 908 os.umask(_umask)
908 909
909 910 def needbinarypatch():
910 911 """return True if patches should be applied in binary mode by default."""
911 912 return os.name == 'nt'
912 913
913 914 # Platform specific variants
914 915 if os.name == 'nt':
915 916 import msvcrt
916 917 nulldev = 'NUL:'
917 918
918 919 class winstdout:
919 920 '''stdout on windows misbehaves if sent through a pipe'''
920 921
921 922 def __init__(self, fp):
922 923 self.fp = fp
923 924
924 925 def __getattr__(self, key):
925 926 return getattr(self.fp, key)
926 927
927 928 def close(self):
928 929 try:
929 930 self.fp.close()
930 931 except: pass
931 932
932 933 def write(self, s):
933 934 try:
934 935 # This is workaround for "Not enough space" error on
935 936 # writing large size of data to console.
936 937 limit = 16000
937 938 l = len(s)
938 939 start = 0
939 940 while start < l:
940 941 end = start + limit
941 942 self.fp.write(s[start:end])
942 943 start = end
943 944 except IOError, inst:
944 945 if inst.errno != 0: raise
945 946 self.close()
946 947 raise IOError(errno.EPIPE, 'Broken pipe')
947 948
948 949 def flush(self):
949 950 try:
950 951 return self.fp.flush()
951 952 except IOError, inst:
952 953 if inst.errno != errno.EINVAL: raise
953 954 self.close()
954 955 raise IOError(errno.EPIPE, 'Broken pipe')
955 956
956 957 sys.stdout = winstdout(sys.stdout)
957 958
958 959 def _is_win_9x():
959 960 '''return true if run on windows 95, 98 or me.'''
960 961 try:
961 962 return sys.getwindowsversion()[3] == 1
962 963 except AttributeError:
963 964 return 'command' in os.environ.get('comspec', '')
964 965
965 966 def openhardlinks():
966 967 return not _is_win_9x and "win32api" in locals()
967 968
968 969 def system_rcpath():
969 970 try:
970 971 return system_rcpath_win32()
971 972 except:
972 973 return [r'c:\mercurial\mercurial.ini']
973 974
974 975 def user_rcpath():
975 976 '''return os-specific hgrc search path to the user dir'''
976 977 try:
977 978 userrc = user_rcpath_win32()
978 979 except:
979 980 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
980 981 path = [userrc]
981 982 userprofile = os.environ.get('USERPROFILE')
982 983 if userprofile:
983 984 path.append(os.path.join(userprofile, 'mercurial.ini'))
984 985 return path
985 986
986 987 def parse_patch_output(output_line):
987 988 """parses the output produced by patch and returns the file name"""
988 989 pf = output_line[14:]
989 990 if pf[0] == '`':
990 991 pf = pf[1:-1] # Remove the quotes
991 992 return pf
992 993
993 994 def sshargs(sshcmd, host, user, port):
994 995 '''Build argument list for ssh or Plink'''
995 996 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
996 997 args = user and ("%s@%s" % (user, host)) or host
997 998 return port and ("%s %s %s" % (args, pflag, port)) or args
998 999
999 1000 def testpid(pid):
1000 1001 '''return False if pid dead, True if running or not known'''
1001 1002 return True
1002 1003
1003 1004 def set_exec(f, mode):
1004 1005 pass
1005 1006
1006 1007 def set_link(f, mode):
1007 1008 pass
1008 1009
1009 1010 def set_binary(fd):
1010 1011 msvcrt.setmode(fd.fileno(), os.O_BINARY)
1011 1012
1012 1013 def pconvert(path):
1013 1014 return path.replace("\\", "/")
1014 1015
1015 1016 def localpath(path):
1016 1017 return path.replace('/', '\\')
1017 1018
1018 1019 def normpath(path):
1019 1020 return pconvert(os.path.normpath(path))
1020 1021
1021 1022 makelock = _makelock_file
1022 1023 readlock = _readlock_file
1023 1024
1024 1025 def samestat(s1, s2):
1025 1026 return False
1026 1027
1027 1028 # A sequence of backslashes is special iff it precedes a double quote:
1028 1029 # - if there's an even number of backslashes, the double quote is not
1029 1030 # quoted (i.e. it ends the quoted region)
1030 1031 # - if there's an odd number of backslashes, the double quote is quoted
1031 1032 # - in both cases, every pair of backslashes is unquoted into a single
1032 1033 # backslash
1033 1034 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
1034 1035 # So, to quote a string, we must surround it in double quotes, double
1035 1036 # the number of backslashes that preceed double quotes and add another
1036 1037 # backslash before every double quote (being careful with the double
1037 1038 # quote we've appended to the end)
1038 1039 _quotere = None
1039 1040 def shellquote(s):
1040 1041 global _quotere
1041 1042 if _quotere is None:
1042 1043 _quotere = re.compile(r'(\\*)("|\\$)')
1043 1044 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1044 1045
1045 1046 def quotecommand(cmd):
1046 1047 """Build a command string suitable for os.popen* calls."""
1047 1048 # The extra quotes are needed because popen* runs the command
1048 1049 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1049 1050 return '"' + cmd + '"'
1050 1051
1051 1052 def popen(command):
1052 1053 # Work around "popen spawned process may not write to stdout
1053 1054 # under windows"
1054 1055 # http://bugs.python.org/issue1366
1055 1056 command += " 2> %s" % nulldev
1056 1057 return os.popen(quotecommand(command))
1057 1058
1058 1059 def explain_exit(code):
1059 1060 return _("exited with status %d") % code, code
1060 1061
1061 1062 # if you change this stub into a real check, please try to implement the
1062 1063 # username and groupname functions above, too.
1063 1064 def isowner(fp, st=None):
1064 1065 return True
1065 1066
1066 1067 def find_in_path(name, path, default=None):
1067 1068 '''find name in search path. path can be string (will be split
1068 1069 with os.pathsep), or iterable thing that returns strings. if name
1069 1070 found, return path to name. else return default. name is looked up
1070 1071 using cmd.exe rules, using PATHEXT.'''
1071 1072 if isinstance(path, str):
1072 1073 path = path.split(os.pathsep)
1073 1074
1074 1075 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1075 1076 pathext = pathext.lower().split(os.pathsep)
1076 1077 isexec = os.path.splitext(name)[1].lower() in pathext
1077 1078
1078 1079 for p in path:
1079 1080 p_name = os.path.join(p, name)
1080 1081
1081 1082 if isexec and os.path.exists(p_name):
1082 1083 return p_name
1083 1084
1084 1085 for ext in pathext:
1085 1086 p_name_ext = p_name + ext
1086 1087 if os.path.exists(p_name_ext):
1087 1088 return p_name_ext
1088 1089 return default
1089 1090
1090 1091 def set_signal_handler():
1091 1092 try:
1092 1093 set_signal_handler_win32()
1093 1094 except NameError:
1094 1095 pass
1095 1096
1096 1097 try:
1097 1098 # override functions with win32 versions if possible
1098 1099 from util_win32 import *
1099 1100 if not _is_win_9x():
1100 1101 posixfile = posixfile_nt
1101 1102 except ImportError:
1102 1103 pass
1103 1104
1104 1105 else:
1105 1106 nulldev = '/dev/null'
1106 1107
1107 1108 def rcfiles(path):
1108 1109 rcs = [os.path.join(path, 'hgrc')]
1109 1110 rcdir = os.path.join(path, 'hgrc.d')
1110 1111 try:
1111 1112 rcs.extend([os.path.join(rcdir, f)
1112 1113 for f, kind in osutil.listdir(rcdir)
1113 1114 if f.endswith(".rc")])
1114 1115 except OSError:
1115 1116 pass
1116 1117 return rcs
1117 1118
1118 1119 def system_rcpath():
1119 1120 path = []
1120 1121 # old mod_python does not set sys.argv
1121 1122 if len(getattr(sys, 'argv', [])) > 0:
1122 1123 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1123 1124 '/../etc/mercurial'))
1124 1125 path.extend(rcfiles('/etc/mercurial'))
1125 1126 return path
1126 1127
1127 1128 def user_rcpath():
1128 1129 return [os.path.expanduser('~/.hgrc')]
1129 1130
1130 1131 def parse_patch_output(output_line):
1131 1132 """parses the output produced by patch and returns the file name"""
1132 1133 pf = output_line[14:]
1133 1134 if os.sys.platform == 'OpenVMS':
1134 1135 if pf[0] == '`':
1135 1136 pf = pf[1:-1] # Remove the quotes
1136 1137 else:
1137 1138 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1138 1139 pf = pf[1:-1] # Remove the quotes
1139 1140 return pf
1140 1141
1141 1142 def sshargs(sshcmd, host, user, port):
1142 1143 '''Build argument list for ssh'''
1143 1144 args = user and ("%s@%s" % (user, host)) or host
1144 1145 return port and ("%s -p %s" % (args, port)) or args
1145 1146
1146 1147 def is_exec(f):
1147 1148 """check whether a file is executable"""
1148 1149 return (os.lstat(f).st_mode & 0100 != 0)
1149 1150
1150 1151 def set_exec(f, mode):
1151 1152 s = os.lstat(f).st_mode
1152 1153 if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
1153 1154 return
1154 1155 if mode:
1155 1156 # Turn on +x for every +r bit when making a file executable
1156 1157 # and obey umask.
1157 1158 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1158 1159 else:
1159 1160 os.chmod(f, s & 0666)
1160 1161
1161 1162 def set_link(f, mode):
1162 1163 """make a file a symbolic link/regular file
1163 1164
1164 1165 if a file is changed to a link, its contents become the link data
1165 1166 if a link is changed to a file, its link data become its contents
1166 1167 """
1167 1168
1168 1169 m = os.path.islink(f)
1169 1170 if m == bool(mode):
1170 1171 return
1171 1172
1172 1173 if mode: # switch file to link
1173 1174 data = file(f).read()
1174 1175 os.unlink(f)
1175 1176 os.symlink(data, f)
1176 1177 else:
1177 1178 data = os.readlink(f)
1178 1179 os.unlink(f)
1179 1180 file(f, "w").write(data)
1180 1181
1181 1182 def set_binary(fd):
1182 1183 pass
1183 1184
1184 1185 def pconvert(path):
1185 1186 return path
1186 1187
1187 1188 def localpath(path):
1188 1189 return path
1189 1190
1190 1191 normpath = os.path.normpath
1191 1192 samestat = os.path.samestat
1192 1193
1193 1194 def makelock(info, pathname):
1194 1195 try:
1195 1196 os.symlink(info, pathname)
1196 1197 except OSError, why:
1197 1198 if why.errno == errno.EEXIST:
1198 1199 raise
1199 1200 else:
1200 1201 _makelock_file(info, pathname)
1201 1202
1202 1203 def readlock(pathname):
1203 1204 try:
1204 1205 return os.readlink(pathname)
1205 1206 except OSError, why:
1206 1207 if why.errno in (errno.EINVAL, errno.ENOSYS):
1207 1208 return _readlock_file(pathname)
1208 1209 else:
1209 1210 raise
1210 1211
1211 1212 def shellquote(s):
1212 1213 if os.sys.platform == 'OpenVMS':
1213 1214 return '"%s"' % s
1214 1215 else:
1215 1216 return "'%s'" % s.replace("'", "'\\''")
1216 1217
1217 1218 def quotecommand(cmd):
1218 1219 return cmd
1219 1220
1220 1221 def popen(command):
1221 1222 return os.popen(command)
1222 1223
1223 1224 def testpid(pid):
1224 1225 '''return False if pid dead, True if running or not sure'''
1225 1226 if os.sys.platform == 'OpenVMS':
1226 1227 return True
1227 1228 try:
1228 1229 os.kill(pid, 0)
1229 1230 return True
1230 1231 except OSError, inst:
1231 1232 return inst.errno != errno.ESRCH
1232 1233
1233 1234 def explain_exit(code):
1234 1235 """return a 2-tuple (desc, code) describing a process's status"""
1235 1236 if os.WIFEXITED(code):
1236 1237 val = os.WEXITSTATUS(code)
1237 1238 return _("exited with status %d") % val, val
1238 1239 elif os.WIFSIGNALED(code):
1239 1240 val = os.WTERMSIG(code)
1240 1241 return _("killed by signal %d") % val, val
1241 1242 elif os.WIFSTOPPED(code):
1242 1243 val = os.WSTOPSIG(code)
1243 1244 return _("stopped by signal %d") % val, val
1244 1245 raise ValueError(_("invalid exit code"))
1245 1246
1246 1247 def isowner(fp, st=None):
1247 1248 """Return True if the file object f belongs to the current user.
1248 1249
1249 1250 The return value of a util.fstat(f) may be passed as the st argument.
1250 1251 """
1251 1252 if st is None:
1252 1253 st = fstat(fp)
1253 1254 return st.st_uid == os.getuid()
1254 1255
1255 1256 def find_in_path(name, path, default=None):
1256 1257 '''find name in search path. path can be string (will be split
1257 1258 with os.pathsep), or iterable thing that returns strings. if name
1258 1259 found, return path to name. else return default.'''
1259 1260 if isinstance(path, str):
1260 1261 path = path.split(os.pathsep)
1261 1262 for p in path:
1262 1263 p_name = os.path.join(p, name)
1263 1264 if os.path.exists(p_name):
1264 1265 return p_name
1265 1266 return default
1266 1267
1267 1268 def set_signal_handler():
1268 1269 pass
1269 1270
1270 1271 def find_exe(name, default=None):
1271 1272 '''find path of an executable.
1272 1273 if name contains a path component, return it as is. otherwise,
1273 1274 use normal executable search path.'''
1274 1275
1275 1276 if os.sep in name or sys.platform == 'OpenVMS':
1276 1277 # don't check the executable bit. if the file isn't
1277 1278 # executable, whoever tries to actually run it will give a
1278 1279 # much more useful error message.
1279 1280 return name
1280 1281 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1281 1282
1282 1283 def _buildencodefun():
1283 1284 e = '_'
1284 1285 win_reserved = [ord(x) for x in '\\:*?"<>|']
1285 1286 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1286 1287 for x in (range(32) + range(126, 256) + win_reserved):
1287 1288 cmap[chr(x)] = "~%02x" % x
1288 1289 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1289 1290 cmap[chr(x)] = e + chr(x).lower()
1290 1291 dmap = {}
1291 1292 for k, v in cmap.iteritems():
1292 1293 dmap[v] = k
1293 1294 def decode(s):
1294 1295 i = 0
1295 1296 while i < len(s):
1296 1297 for l in xrange(1, 4):
1297 1298 try:
1298 1299 yield dmap[s[i:i+l]]
1299 1300 i += l
1300 1301 break
1301 1302 except KeyError:
1302 1303 pass
1303 1304 else:
1304 1305 raise KeyError
1305 1306 return (lambda s: "".join([cmap[c] for c in s]),
1306 1307 lambda s: "".join(list(decode(s))))
1307 1308
1308 1309 encodefilename, decodefilename = _buildencodefun()
1309 1310
1310 1311 def encodedopener(openerfn, fn):
1311 1312 def o(path, *args, **kw):
1312 1313 return openerfn(fn(path), *args, **kw)
1313 1314 return o
1314 1315
1315 1316 def mktempcopy(name, emptyok=False):
1316 1317 """Create a temporary file with the same contents from name
1317 1318
1318 1319 The permission bits are copied from the original file.
1319 1320
1320 1321 If the temporary file is going to be truncated immediately, you
1321 1322 can use emptyok=True as an optimization.
1322 1323
1323 1324 Returns the name of the temporary file.
1324 1325 """
1325 1326 d, fn = os.path.split(name)
1326 1327 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1327 1328 os.close(fd)
1328 1329 # Temporary files are created with mode 0600, which is usually not
1329 1330 # what we want. If the original file already exists, just copy
1330 1331 # its mode. Otherwise, manually obey umask.
1331 1332 try:
1332 1333 st_mode = os.lstat(name).st_mode
1333 1334 except OSError, inst:
1334 1335 if inst.errno != errno.ENOENT:
1335 1336 raise
1336 1337 st_mode = 0666 & ~_umask
1337 1338 os.chmod(temp, st_mode)
1338 1339 if emptyok:
1339 1340 return temp
1340 1341 try:
1341 1342 try:
1342 1343 ifp = posixfile(name, "rb")
1343 1344 except IOError, inst:
1344 1345 if inst.errno == errno.ENOENT:
1345 1346 return temp
1346 1347 if not getattr(inst, 'filename', None):
1347 1348 inst.filename = name
1348 1349 raise
1349 1350 ofp = posixfile(temp, "wb")
1350 1351 for chunk in filechunkiter(ifp):
1351 1352 ofp.write(chunk)
1352 1353 ifp.close()
1353 1354 ofp.close()
1354 1355 except:
1355 1356 try: os.unlink(temp)
1356 1357 except: pass
1357 1358 raise
1358 1359 return temp
1359 1360
1360 1361 class atomictempfile(posixfile):
1361 1362 """file-like object that atomically updates a file
1362 1363
1363 1364 All writes will be redirected to a temporary copy of the original
1364 1365 file. When rename is called, the copy is renamed to the original
1365 1366 name, making the changes visible.
1366 1367 """
1367 1368 def __init__(self, name, mode):
1368 1369 self.__name = name
1369 1370 self.temp = mktempcopy(name, emptyok=('w' in mode))
1370 1371 posixfile.__init__(self, self.temp, mode)
1371 1372
1372 1373 def rename(self):
1373 1374 if not self.closed:
1374 1375 posixfile.close(self)
1375 1376 rename(self.temp, localpath(self.__name))
1376 1377
1377 1378 def __del__(self):
1378 1379 if not self.closed:
1379 1380 try:
1380 1381 os.unlink(self.temp)
1381 1382 except: pass
1382 1383 posixfile.close(self)
1383 1384
1384 1385 class opener(object):
1385 1386 """Open files relative to a base directory
1386 1387
1387 1388 This class is used to hide the details of COW semantics and
1388 1389 remote file access from higher level code.
1389 1390 """
1390 1391 def __init__(self, base, audit=True):
1391 1392 self.base = base
1392 1393 if audit:
1393 1394 self.audit_path = path_auditor(base)
1394 1395 else:
1395 1396 self.audit_path = always
1396 1397
1397 1398 def __getattr__(self, name):
1398 1399 if name == '_can_symlink':
1399 1400 self._can_symlink = checklink(self.base)
1400 1401 return self._can_symlink
1401 1402 raise AttributeError(name)
1402 1403
1403 1404 def __call__(self, path, mode="r", text=False, atomictemp=False):
1404 1405 self.audit_path(path)
1405 1406 f = os.path.join(self.base, path)
1406 1407
1407 1408 if not text and "b" not in mode:
1408 1409 mode += "b" # for that other OS
1409 1410
1410 1411 if mode[0] != "r":
1411 1412 try:
1412 1413 nlink = nlinks(f)
1413 1414 except OSError:
1414 1415 nlink = 0
1415 1416 d = os.path.dirname(f)
1416 1417 if not os.path.isdir(d):
1417 1418 os.makedirs(d)
1418 1419 if atomictemp:
1419 1420 return atomictempfile(f, mode)
1420 1421 if nlink > 1:
1421 1422 rename(mktempcopy(f), f)
1422 1423 return posixfile(f, mode)
1423 1424
1424 1425 def symlink(self, src, dst):
1425 1426 self.audit_path(dst)
1426 1427 linkname = os.path.join(self.base, dst)
1427 1428 try:
1428 1429 os.unlink(linkname)
1429 1430 except OSError:
1430 1431 pass
1431 1432
1432 1433 dirname = os.path.dirname(linkname)
1433 1434 if not os.path.exists(dirname):
1434 1435 os.makedirs(dirname)
1435 1436
1436 1437 if self._can_symlink:
1437 1438 try:
1438 1439 os.symlink(src, linkname)
1439 1440 except OSError, err:
1440 1441 raise OSError(err.errno, _('could not symlink to %r: %s') %
1441 1442 (src, err.strerror), linkname)
1442 1443 else:
1443 1444 f = self(dst, "w")
1444 1445 f.write(src)
1445 1446 f.close()
1446 1447
1447 1448 class chunkbuffer(object):
1448 1449 """Allow arbitrary sized chunks of data to be efficiently read from an
1449 1450 iterator over chunks of arbitrary size."""
1450 1451
1451 1452 def __init__(self, in_iter):
1452 1453 """in_iter is the iterator that's iterating over the input chunks.
1453 1454 targetsize is how big a buffer to try to maintain."""
1454 1455 self.iter = iter(in_iter)
1455 1456 self.buf = ''
1456 1457 self.targetsize = 2**16
1457 1458
1458 1459 def read(self, l):
1459 1460 """Read L bytes of data from the iterator of chunks of data.
1460 1461 Returns less than L bytes if the iterator runs dry."""
1461 1462 if l > len(self.buf) and self.iter:
1462 1463 # Clamp to a multiple of self.targetsize
1463 1464 targetsize = max(l, self.targetsize)
1464 1465 collector = cStringIO.StringIO()
1465 1466 collector.write(self.buf)
1466 1467 collected = len(self.buf)
1467 1468 for chunk in self.iter:
1468 1469 collector.write(chunk)
1469 1470 collected += len(chunk)
1470 1471 if collected >= targetsize:
1471 1472 break
1472 1473 if collected < targetsize:
1473 1474 self.iter = False
1474 1475 self.buf = collector.getvalue()
1475 1476 if len(self.buf) == l:
1476 1477 s, self.buf = str(self.buf), ''
1477 1478 else:
1478 1479 s, self.buf = self.buf[:l], buffer(self.buf, l)
1479 1480 return s
1480 1481
1481 1482 def filechunkiter(f, size=65536, limit=None):
1482 1483 """Create a generator that produces the data in the file size
1483 1484 (default 65536) bytes at a time, up to optional limit (default is
1484 1485 to read all data). Chunks may be less than size bytes if the
1485 1486 chunk is the last chunk in the file, or the file is a socket or
1486 1487 some other type of file that sometimes reads less data than is
1487 1488 requested."""
1488 1489 assert size >= 0
1489 1490 assert limit is None or limit >= 0
1490 1491 while True:
1491 1492 if limit is None: nbytes = size
1492 1493 else: nbytes = min(limit, size)
1493 1494 s = nbytes and f.read(nbytes)
1494 1495 if not s: break
1495 1496 if limit: limit -= len(s)
1496 1497 yield s
1497 1498
1498 1499 def makedate():
1499 1500 lt = time.localtime()
1500 1501 if lt[8] == 1 and time.daylight:
1501 1502 tz = time.altzone
1502 1503 else:
1503 1504 tz = time.timezone
1504 1505 return time.mktime(lt), tz
1505 1506
1506 1507 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1507 1508 """represent a (unixtime, offset) tuple as a localized time.
1508 1509 unixtime is seconds since the epoch, and offset is the time zone's
1509 1510 number of seconds away from UTC. if timezone is false, do not
1510 1511 append time zone to string."""
1511 1512 t, tz = date or makedate()
1512 1513 s = time.strftime(format, time.gmtime(float(t) - tz))
1513 1514 if timezone:
1514 1515 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1515 1516 return s
1516 1517
1517 1518 def strdate(string, format, defaults=[]):
1518 1519 """parse a localized time string and return a (unixtime, offset) tuple.
1519 1520 if the string cannot be parsed, ValueError is raised."""
1520 1521 def timezone(string):
1521 1522 tz = string.split()[-1]
1522 1523 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1523 1524 tz = int(tz)
1524 1525 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1525 1526 return offset
1526 1527 if tz == "GMT" or tz == "UTC":
1527 1528 return 0
1528 1529 return None
1529 1530
1530 1531 # NOTE: unixtime = localunixtime + offset
1531 1532 offset, date = timezone(string), string
1532 1533 if offset != None:
1533 1534 date = " ".join(string.split()[:-1])
1534 1535
1535 1536 # add missing elements from defaults
1536 1537 for part in defaults:
1537 1538 found = [True for p in part if ("%"+p) in format]
1538 1539 if not found:
1539 1540 date += "@" + defaults[part]
1540 1541 format += "@%" + part[0]
1541 1542
1542 1543 timetuple = time.strptime(date, format)
1543 1544 localunixtime = int(calendar.timegm(timetuple))
1544 1545 if offset is None:
1545 1546 # local timezone
1546 1547 unixtime = int(time.mktime(timetuple))
1547 1548 offset = unixtime - localunixtime
1548 1549 else:
1549 1550 unixtime = localunixtime + offset
1550 1551 return unixtime, offset
1551 1552
1552 1553 def parsedate(string, formats=None, defaults=None):
1553 1554 """parse a localized time string and return a (unixtime, offset) tuple.
1554 1555 The date may be a "unixtime offset" string or in one of the specified
1555 1556 formats."""
1556 1557 if not string:
1557 1558 return 0, 0
1558 1559 if not formats:
1559 1560 formats = defaultdateformats
1560 1561 string = string.strip()
1561 1562 try:
1562 1563 when, offset = map(int, string.split(' '))
1563 1564 except ValueError:
1564 1565 # fill out defaults
1565 1566 if not defaults:
1566 1567 defaults = {}
1567 1568 now = makedate()
1568 1569 for part in "d mb yY HI M S".split():
1569 1570 if part not in defaults:
1570 1571 if part[0] in "HMS":
1571 1572 defaults[part] = "00"
1572 1573 elif part[0] in "dm":
1573 1574 defaults[part] = "1"
1574 1575 else:
1575 1576 defaults[part] = datestr(now, "%" + part[0], False)
1576 1577
1577 1578 for format in formats:
1578 1579 try:
1579 1580 when, offset = strdate(string, format, defaults)
1580 1581 except ValueError:
1581 1582 pass
1582 1583 else:
1583 1584 break
1584 1585 else:
1585 1586 raise Abort(_('invalid date: %r ') % string)
1586 1587 # validate explicit (probably user-specified) date and
1587 1588 # time zone offset. values must fit in signed 32 bits for
1588 1589 # current 32-bit linux runtimes. timezones go from UTC-12
1589 1590 # to UTC+14
1590 1591 if abs(when) > 0x7fffffff:
1591 1592 raise Abort(_('date exceeds 32 bits: %d') % when)
1592 1593 if offset < -50400 or offset > 43200:
1593 1594 raise Abort(_('impossible time zone offset: %d') % offset)
1594 1595 return when, offset
1595 1596
1596 1597 def matchdate(date):
1597 1598 """Return a function that matches a given date match specifier
1598 1599
1599 1600 Formats include:
1600 1601
1601 1602 '{date}' match a given date to the accuracy provided
1602 1603
1603 1604 '<{date}' on or before a given date
1604 1605
1605 1606 '>{date}' on or after a given date
1606 1607
1607 1608 """
1608 1609
1609 1610 def lower(date):
1610 1611 return parsedate(date, extendeddateformats)[0]
1611 1612
1612 1613 def upper(date):
1613 1614 d = dict(mb="12", HI="23", M="59", S="59")
1614 1615 for days in "31 30 29".split():
1615 1616 try:
1616 1617 d["d"] = days
1617 1618 return parsedate(date, extendeddateformats, d)[0]
1618 1619 except:
1619 1620 pass
1620 1621 d["d"] = "28"
1621 1622 return parsedate(date, extendeddateformats, d)[0]
1622 1623
1623 1624 if date[0] == "<":
1624 1625 when = upper(date[1:])
1625 1626 return lambda x: x <= when
1626 1627 elif date[0] == ">":
1627 1628 when = lower(date[1:])
1628 1629 return lambda x: x >= when
1629 1630 elif date[0] == "-":
1630 1631 try:
1631 1632 days = int(date[1:])
1632 1633 except ValueError:
1633 1634 raise Abort(_("invalid day spec: %s") % date[1:])
1634 1635 when = makedate()[0] - days * 3600 * 24
1635 1636 return lambda x: x >= when
1636 1637 elif " to " in date:
1637 1638 a, b = date.split(" to ")
1638 1639 start, stop = lower(a), upper(b)
1639 1640 return lambda x: x >= start and x <= stop
1640 1641 else:
1641 1642 start, stop = lower(date), upper(date)
1642 1643 return lambda x: x >= start and x <= stop
1643 1644
1644 1645 def shortuser(user):
1645 1646 """Return a short representation of a user name or email address."""
1646 1647 f = user.find('@')
1647 1648 if f >= 0:
1648 1649 user = user[:f]
1649 1650 f = user.find('<')
1650 1651 if f >= 0:
1651 1652 user = user[f+1:]
1652 1653 f = user.find(' ')
1653 1654 if f >= 0:
1654 1655 user = user[:f]
1655 1656 f = user.find('.')
1656 1657 if f >= 0:
1657 1658 user = user[:f]
1658 1659 return user
1659 1660
1660 1661 def ellipsis(text, maxlength=400):
1661 1662 """Trim string to at most maxlength (default: 400) characters."""
1662 1663 if len(text) <= maxlength:
1663 1664 return text
1664 1665 else:
1665 1666 return "%s..." % (text[:maxlength-3])
1666 1667
1667 1668 def walkrepos(path):
1668 1669 '''yield every hg repository under path, recursively.'''
1669 1670 def errhandler(err):
1670 1671 if err.filename == path:
1671 1672 raise err
1672 1673
1673 1674 for root, dirs, files in os.walk(path, onerror=errhandler):
1674 1675 for d in dirs:
1675 1676 if d == '.hg':
1676 1677 yield root
1677 1678 dirs[:] = []
1678 1679 break
1679 1680
1680 1681 _rcpath = None
1681 1682
1682 1683 def os_rcpath():
1683 1684 '''return default os-specific hgrc search path'''
1684 1685 path = system_rcpath()
1685 1686 path.extend(user_rcpath())
1686 1687 path = [os.path.normpath(f) for f in path]
1687 1688 return path
1688 1689
1689 1690 def rcpath():
1690 1691 '''return hgrc search path. if env var HGRCPATH is set, use it.
1691 1692 for each item in path, if directory, use files ending in .rc,
1692 1693 else use item.
1693 1694 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1694 1695 if no HGRCPATH, use default os-specific path.'''
1695 1696 global _rcpath
1696 1697 if _rcpath is None:
1697 1698 if 'HGRCPATH' in os.environ:
1698 1699 _rcpath = []
1699 1700 for p in os.environ['HGRCPATH'].split(os.pathsep):
1700 1701 if not p: continue
1701 1702 if os.path.isdir(p):
1702 1703 for f, kind in osutil.listdir(p):
1703 1704 if f.endswith('.rc'):
1704 1705 _rcpath.append(os.path.join(p, f))
1705 1706 else:
1706 1707 _rcpath.append(p)
1707 1708 else:
1708 1709 _rcpath = os_rcpath()
1709 1710 return _rcpath
1710 1711
1711 1712 def bytecount(nbytes):
1712 1713 '''return byte count formatted as readable string, with units'''
1713 1714
1714 1715 units = (
1715 1716 (100, 1<<30, _('%.0f GB')),
1716 1717 (10, 1<<30, _('%.1f GB')),
1717 1718 (1, 1<<30, _('%.2f GB')),
1718 1719 (100, 1<<20, _('%.0f MB')),
1719 1720 (10, 1<<20, _('%.1f MB')),
1720 1721 (1, 1<<20, _('%.2f MB')),
1721 1722 (100, 1<<10, _('%.0f KB')),
1722 1723 (10, 1<<10, _('%.1f KB')),
1723 1724 (1, 1<<10, _('%.2f KB')),
1724 1725 (1, 1, _('%.0f bytes')),
1725 1726 )
1726 1727
1727 1728 for multiplier, divisor, format in units:
1728 1729 if nbytes >= divisor * multiplier:
1729 1730 return format % (nbytes / float(divisor))
1730 1731 return units[-1][2] % nbytes
1731 1732
1732 1733 def drop_scheme(scheme, path):
1733 1734 sc = scheme + ':'
1734 1735 if path.startswith(sc):
1735 1736 path = path[len(sc):]
1736 1737 if path.startswith('//'):
1737 1738 path = path[2:]
1738 1739 return path
1739 1740
1740 1741 def uirepr(s):
1741 1742 # Avoid double backslash in Windows path repr()
1742 1743 return repr(s).replace('\\\\', '\\')
1743 1744
1744 1745 def hidepassword(url):
1745 1746 '''replaces the password in the url string by three asterisks (***)
1746 1747
1747 1748 >>> hidepassword('http://www.example.com/some/path#fragment')
1748 1749 'http://www.example.com/some/path#fragment'
1749 1750 >>> hidepassword('http://me@www.example.com/some/path#fragment')
1750 1751 'http://me@www.example.com/some/path#fragment'
1751 1752 >>> hidepassword('http://me:simplepw@www.example.com/path#frag')
1752 1753 'http://me:***@www.example.com/path#frag'
1753 1754 >>> hidepassword('http://me:complex:pw@www.example.com/path#frag')
1754 1755 'http://me:***@www.example.com/path#frag'
1755 1756 >>> hidepassword('/path/to/repo')
1756 1757 '/path/to/repo'
1757 1758 >>> hidepassword('relative/path/to/repo')
1758 1759 'relative/path/to/repo'
1759 1760 >>> hidepassword('c:\\\\path\\\\to\\\\repo')
1760 1761 'c:\\\\path\\\\to\\\\repo'
1761 1762 >>> hidepassword('c:/path/to/repo')
1762 1763 'c:/path/to/repo'
1763 1764 >>> hidepassword('bundle://path/to/bundle')
1764 1765 'bundle://path/to/bundle'
1765 1766 '''
1766 1767 url_parts = list(urlparse.urlparse(url))
1767 1768 host_with_pw_pattern = re.compile('^([^:]*):([^@]*)@(.*)$')
1768 1769 if host_with_pw_pattern.match(url_parts[1]):
1769 1770 url_parts[1] = re.sub(host_with_pw_pattern, r'\1:***@\3',
1770 1771 url_parts[1])
1771 1772 return urlparse.urlunparse(url_parts)
1772 1773
General Comments 0
You need to be logged in to leave comments. Login now