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