##// END OF EJS Templates
merge with crew.
Vadim Gelfer -
r2194:ee90e5a9 merge default
parent child Browse files
Show More
@@ -1,868 +1,872 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
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8
9 9 This contains helper routines that are independent of the SCM core and hide
10 10 platform-specific details from the core.
11 11 """
12 12
13 13 import os, errno
14 14 from i18n import gettext as _
15 15 from demandload import *
16 16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
17 17 demandload(globals(), "threading time")
18 18
19 19 class SignalInterrupt(Exception):
20 20 """Exception raised on SIGTERM and SIGHUP."""
21 21
22 22 def pipefilter(s, cmd):
23 23 '''filter string S through command CMD, returning its output'''
24 24 (pout, pin) = popen2.popen2(cmd, -1, 'b')
25 25 def writer():
26 26 try:
27 27 pin.write(s)
28 28 pin.close()
29 29 except IOError, inst:
30 30 if inst.errno != errno.EPIPE:
31 31 raise
32 32
33 33 # we should use select instead on UNIX, but this will work on most
34 34 # systems, including Windows
35 35 w = threading.Thread(target=writer)
36 36 w.start()
37 37 f = pout.read()
38 38 pout.close()
39 39 w.join()
40 40 return f
41 41
42 42 def tempfilter(s, cmd):
43 43 '''filter string S through a pair of temporary files with CMD.
44 44 CMD is used as a template to create the real command to be run,
45 45 with the strings INFILE and OUTFILE replaced by the real names of
46 46 the temporary files generated.'''
47 47 inname, outname = None, None
48 48 try:
49 49 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
50 50 fp = os.fdopen(infd, 'wb')
51 51 fp.write(s)
52 52 fp.close()
53 53 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
54 54 os.close(outfd)
55 55 cmd = cmd.replace('INFILE', inname)
56 56 cmd = cmd.replace('OUTFILE', outname)
57 57 code = os.system(cmd)
58 58 if code: raise Abort(_("command '%s' failed: %s") %
59 59 (cmd, explain_exit(code)))
60 60 return open(outname, 'rb').read()
61 61 finally:
62 62 try:
63 63 if inname: os.unlink(inname)
64 64 except: pass
65 65 try:
66 66 if outname: os.unlink(outname)
67 67 except: pass
68 68
69 69 filtertable = {
70 70 'tempfile:': tempfilter,
71 71 'pipe:': pipefilter,
72 72 }
73 73
74 74 def filter(s, cmd):
75 75 "filter a string through a command that transforms its input to its output"
76 76 for name, fn in filtertable.iteritems():
77 77 if cmd.startswith(name):
78 78 return fn(s, cmd[len(name):].lstrip())
79 79 return pipefilter(s, cmd)
80 80
81 81 def find_in_path(name, path, default=None):
82 82 '''find name in search path. path can be string (will be split
83 83 with os.pathsep), or iterable thing that returns strings. if name
84 84 found, return path to name. else return default.'''
85 85 if isinstance(path, str):
86 86 path = path.split(os.pathsep)
87 87 for p in path:
88 88 p_name = os.path.join(p, name)
89 89 if os.path.exists(p_name):
90 90 return p_name
91 91 return default
92 92
93 93 def patch(strip, patchname, ui):
94 94 """apply the patch <patchname> to the working directory.
95 95 a list of patched files is returned"""
96 96 patcher = find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
97 97 fp = os.popen('"%s" -p%d < "%s"' % (patcher, strip, patchname))
98 98 files = {}
99 99 for line in fp:
100 100 line = line.rstrip()
101 101 ui.status("%s\n" % line)
102 102 if line.startswith('patching file '):
103 103 pf = parse_patch_output(line)
104 104 files.setdefault(pf, 1)
105 105 code = fp.close()
106 106 if code:
107 107 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
108 108 return files.keys()
109 109
110 110 def binary(s):
111 111 """return true if a string is binary data using diff's heuristic"""
112 112 if s and '\0' in s[:4096]:
113 113 return True
114 114 return False
115 115
116 116 def unique(g):
117 117 """return the uniq elements of iterable g"""
118 118 seen = {}
119 119 for f in g:
120 120 if f not in seen:
121 121 seen[f] = 1
122 122 yield f
123 123
124 124 class Abort(Exception):
125 125 """Raised if a command needs to print an error and exit."""
126 126
127 127 def always(fn): return True
128 128 def never(fn): return False
129 129
130 130 def patkind(name, dflt_pat='glob'):
131 131 """Split a string into an optional pattern kind prefix and the
132 132 actual pattern."""
133 133 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
134 134 if name.startswith(prefix + ':'): return name.split(':', 1)
135 135 return dflt_pat, name
136 136
137 137 def globre(pat, head='^', tail='$'):
138 138 "convert a glob pattern into a regexp"
139 139 i, n = 0, len(pat)
140 140 res = ''
141 141 group = False
142 142 def peek(): return i < n and pat[i]
143 143 while i < n:
144 144 c = pat[i]
145 145 i = i+1
146 146 if c == '*':
147 147 if peek() == '*':
148 148 i += 1
149 149 res += '.*'
150 150 else:
151 151 res += '[^/]*'
152 152 elif c == '?':
153 153 res += '.'
154 154 elif c == '[':
155 155 j = i
156 156 if j < n and pat[j] in '!]':
157 157 j += 1
158 158 while j < n and pat[j] != ']':
159 159 j += 1
160 160 if j >= n:
161 161 res += '\\['
162 162 else:
163 163 stuff = pat[i:j].replace('\\','\\\\')
164 164 i = j + 1
165 165 if stuff[0] == '!':
166 166 stuff = '^' + stuff[1:]
167 167 elif stuff[0] == '^':
168 168 stuff = '\\' + stuff
169 169 res = '%s[%s]' % (res, stuff)
170 170 elif c == '{':
171 171 group = True
172 172 res += '(?:'
173 173 elif c == '}' and group:
174 174 res += ')'
175 175 group = False
176 176 elif c == ',' and group:
177 177 res += '|'
178 178 elif c == '\\':
179 179 p = peek()
180 180 if p:
181 181 i += 1
182 182 res += re.escape(p)
183 183 else:
184 184 res += re.escape(c)
185 185 else:
186 186 res += re.escape(c)
187 187 return head + res + tail
188 188
189 189 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
190 190
191 191 def pathto(n1, n2):
192 192 '''return the relative path from one place to another.
193 193 this returns a path in the form used by the local filesystem, not hg.'''
194 194 if not n1: return localpath(n2)
195 195 a, b = n1.split('/'), n2.split('/')
196 196 a.reverse()
197 197 b.reverse()
198 198 while a and b and a[-1] == b[-1]:
199 199 a.pop()
200 200 b.pop()
201 201 b.reverse()
202 202 return os.sep.join((['..'] * len(a)) + b)
203 203
204 204 def canonpath(root, cwd, myname):
205 205 """return the canonical path of myname, given cwd and root"""
206 206 if root == os.sep:
207 207 rootsep = os.sep
208 208 else:
209 209 rootsep = root + os.sep
210 210 name = myname
211 211 if not os.path.isabs(name):
212 212 name = os.path.join(root, cwd, name)
213 213 name = os.path.normpath(name)
214 214 if name.startswith(rootsep):
215 215 name = name[len(rootsep):]
216 216 audit_path(name)
217 217 return pconvert(name)
218 218 elif name == root:
219 219 return ''
220 220 else:
221 221 # Determine whether `name' is in the hierarchy at or beneath `root',
222 222 # by iterating name=dirname(name) until that causes no change (can't
223 223 # check name == '/', because that doesn't work on windows). For each
224 224 # `name', compare dev/inode numbers. If they match, the list `rel'
225 225 # holds the reversed list of components making up the relative file
226 226 # name we want.
227 227 root_st = os.stat(root)
228 228 rel = []
229 229 while True:
230 230 try:
231 231 name_st = os.stat(name)
232 232 except OSError:
233 233 break
234 if os.path.samestat(name_st, root_st):
234 if samestat(name_st, root_st):
235 235 rel.reverse()
236 236 name = os.path.join(*rel)
237 237 audit_path(name)
238 238 return pconvert(name)
239 239 dirname, basename = os.path.split(name)
240 240 rel.append(basename)
241 241 if dirname == name:
242 242 break
243 243 name = dirname
244 244
245 245 raise Abort('%s not under root' % myname)
246 246
247 247 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
248 248 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
249 249
250 250 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
251 251 if os.name == 'nt':
252 252 dflt_pat = 'glob'
253 253 else:
254 254 dflt_pat = 'relpath'
255 255 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
256 256
257 257 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
258 258 """build a function to match a set of file patterns
259 259
260 260 arguments:
261 261 canonroot - the canonical root of the tree you're matching against
262 262 cwd - the current working directory, if relevant
263 263 names - patterns to find
264 264 inc - patterns to include
265 265 exc - patterns to exclude
266 266 head - a regex to prepend to patterns to control whether a match is rooted
267 267
268 268 a pattern is one of:
269 269 'glob:<rooted glob>'
270 270 're:<rooted regexp>'
271 271 'path:<rooted path>'
272 272 'relglob:<relative glob>'
273 273 'relpath:<relative path>'
274 274 'relre:<relative regexp>'
275 275 '<rooted path or regexp>'
276 276
277 277 returns:
278 278 a 3-tuple containing
279 279 - list of explicit non-pattern names passed in
280 280 - a bool match(filename) function
281 281 - a bool indicating if any patterns were passed in
282 282
283 283 todo:
284 284 make head regex a rooted bool
285 285 """
286 286
287 287 def contains_glob(name):
288 288 for c in name:
289 289 if c in _globchars: return True
290 290 return False
291 291
292 292 def regex(kind, name, tail):
293 293 '''convert a pattern into a regular expression'''
294 294 if kind == 're':
295 295 return name
296 296 elif kind == 'path':
297 297 return '^' + re.escape(name) + '(?:/|$)'
298 298 elif kind == 'relglob':
299 299 return head + globre(name, '(?:|.*/)', tail)
300 300 elif kind == 'relpath':
301 301 return head + re.escape(name) + tail
302 302 elif kind == 'relre':
303 303 if name.startswith('^'):
304 304 return name
305 305 return '.*' + name
306 306 return head + globre(name, '', tail)
307 307
308 308 def matchfn(pats, tail):
309 309 """build a matching function from a set of patterns"""
310 310 if not pats:
311 311 return
312 312 matches = []
313 313 for k, p in pats:
314 314 try:
315 315 pat = '(?:%s)' % regex(k, p, tail)
316 316 matches.append(re.compile(pat).match)
317 317 except re.error:
318 318 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
319 319 else: raise Abort("invalid pattern (%s): %s" % (k, p))
320 320
321 321 def buildfn(text):
322 322 for m in matches:
323 323 r = m(text)
324 324 if r:
325 325 return r
326 326
327 327 return buildfn
328 328
329 329 def globprefix(pat):
330 330 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
331 331 root = []
332 332 for p in pat.split(os.sep):
333 333 if contains_glob(p): break
334 334 root.append(p)
335 335 return '/'.join(root)
336 336
337 337 pats = []
338 338 files = []
339 339 roots = []
340 340 for kind, name in [patkind(p, dflt_pat) for p in names]:
341 341 if kind in ('glob', 'relpath'):
342 342 name = canonpath(canonroot, cwd, name)
343 343 if name == '':
344 344 kind, name = 'glob', '**'
345 345 if kind in ('glob', 'path', 're'):
346 346 pats.append((kind, name))
347 347 if kind == 'glob':
348 348 root = globprefix(name)
349 349 if root: roots.append(root)
350 350 elif kind == 'relpath':
351 351 files.append((kind, name))
352 352 roots.append(name)
353 353
354 354 patmatch = matchfn(pats, '$') or always
355 355 filematch = matchfn(files, '(?:/|$)') or always
356 356 incmatch = always
357 357 if inc:
358 358 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
359 359 excmatch = lambda fn: False
360 360 if exc:
361 361 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
362 362
363 363 return (roots,
364 364 lambda fn: (incmatch(fn) and not excmatch(fn) and
365 365 (fn.endswith('/') or
366 366 (not pats and not files) or
367 367 (pats and patmatch(fn)) or
368 368 (files and filematch(fn)))),
369 369 (inc or exc or (pats and pats != [('glob', '**')])) and True)
370 370
371 371 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
372 372 '''enhanced shell command execution.
373 373 run with environment maybe modified, maybe in different dir.
374 374
375 375 if command fails and onerr is None, return status. if ui object,
376 376 print error message and return status, else raise onerr object as
377 377 exception.'''
378 378 oldenv = {}
379 379 for k in environ:
380 380 oldenv[k] = os.environ.get(k)
381 381 if cwd is not None:
382 382 oldcwd = os.getcwd()
383 383 try:
384 384 for k, v in environ.iteritems():
385 385 os.environ[k] = str(v)
386 386 if cwd is not None and oldcwd != cwd:
387 387 os.chdir(cwd)
388 388 rc = os.system(cmd)
389 389 if rc and onerr:
390 390 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
391 391 explain_exit(rc)[0])
392 392 if errprefix:
393 393 errmsg = '%s: %s' % (errprefix, errmsg)
394 394 try:
395 395 onerr.warn(errmsg + '\n')
396 396 except AttributeError:
397 397 raise onerr(errmsg)
398 398 return rc
399 399 finally:
400 400 for k, v in oldenv.iteritems():
401 401 if v is None:
402 402 del os.environ[k]
403 403 else:
404 404 os.environ[k] = v
405 405 if cwd is not None and oldcwd != cwd:
406 406 os.chdir(oldcwd)
407 407
408 408 def rename(src, dst):
409 409 """forcibly rename a file"""
410 410 try:
411 411 os.rename(src, dst)
412 412 except OSError, err:
413 413 # on windows, rename to existing file is not allowed, so we
414 414 # must delete destination first. but if file is open, unlink
415 415 # schedules it for delete but does not delete it. rename
416 416 # happens immediately even for open files, so we create
417 417 # temporary file, delete it, rename destination to that name,
418 418 # then delete that. then rename is safe to do.
419 419 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
420 420 os.close(fd)
421 421 os.unlink(temp)
422 422 os.rename(dst, temp)
423 423 os.unlink(temp)
424 424 os.rename(src, dst)
425 425
426 426 def unlink(f):
427 427 """unlink and remove the directory if it is empty"""
428 428 os.unlink(f)
429 429 # try removing directories that might now be empty
430 430 try:
431 431 os.removedirs(os.path.dirname(f))
432 432 except OSError:
433 433 pass
434 434
435 435 def copyfiles(src, dst, hardlink=None):
436 436 """Copy a directory tree using hardlinks if possible"""
437 437
438 438 if hardlink is None:
439 439 hardlink = (os.stat(src).st_dev ==
440 440 os.stat(os.path.dirname(dst)).st_dev)
441 441
442 442 if os.path.isdir(src):
443 443 os.mkdir(dst)
444 444 for name in os.listdir(src):
445 445 srcname = os.path.join(src, name)
446 446 dstname = os.path.join(dst, name)
447 447 copyfiles(srcname, dstname, hardlink)
448 448 else:
449 449 if hardlink:
450 450 try:
451 451 os_link(src, dst)
452 452 except (IOError, OSError):
453 453 hardlink = False
454 454 shutil.copy(src, dst)
455 455 else:
456 456 shutil.copy(src, dst)
457 457
458 458 def audit_path(path):
459 459 """Abort if path contains dangerous components"""
460 460 parts = os.path.normcase(path).split(os.sep)
461 461 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
462 462 or os.pardir in parts):
463 463 raise Abort(_("path contains illegal component: %s\n") % path)
464 464
465 465 def _makelock_file(info, pathname):
466 466 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
467 467 os.write(ld, info)
468 468 os.close(ld)
469 469
470 470 def _readlock_file(pathname):
471 471 return posixfile(pathname).read()
472 472
473 473 def nlinks(pathname):
474 474 """Return number of hardlinks for the given file."""
475 475 return os.stat(pathname).st_nlink
476 476
477 477 if hasattr(os, 'link'):
478 478 os_link = os.link
479 479 else:
480 480 def os_link(src, dst):
481 481 raise OSError(0, _("Hardlinks not supported"))
482 482
483 483 def fstat(fp):
484 484 '''stat file object that may not have fileno method.'''
485 485 try:
486 486 return os.fstat(fp.fileno())
487 487 except AttributeError:
488 488 return os.stat(fp.name)
489 489
490 490 posixfile = file
491 491
492 492 # Platform specific variants
493 493 if os.name == 'nt':
494 494 demandload(globals(), "msvcrt")
495 495 nulldev = 'NUL:'
496 496
497 497 class winstdout:
498 498 '''stdout on windows misbehaves if sent through a pipe'''
499 499
500 500 def __init__(self, fp):
501 501 self.fp = fp
502 502
503 503 def __getattr__(self, key):
504 504 return getattr(self.fp, key)
505 505
506 506 def close(self):
507 507 try:
508 508 self.fp.close()
509 509 except: pass
510 510
511 511 def write(self, s):
512 512 try:
513 513 return self.fp.write(s)
514 514 except IOError, inst:
515 515 if inst.errno != 0: raise
516 516 self.close()
517 517 raise IOError(errno.EPIPE, 'Broken pipe')
518 518
519 519 sys.stdout = winstdout(sys.stdout)
520 520
521 521 def system_rcpath():
522 522 try:
523 523 return system_rcpath_win32()
524 524 except:
525 525 return [r'c:\mercurial\mercurial.ini']
526 526
527 527 def os_rcpath():
528 528 '''return default os-specific hgrc search path'''
529 529 return system_rcpath() + [os.path.join(os.path.expanduser('~'),
530 530 'mercurial.ini')]
531 531
532 532 def parse_patch_output(output_line):
533 533 """parses the output produced by patch and returns the file name"""
534 534 pf = output_line[14:]
535 535 if pf[0] == '`':
536 536 pf = pf[1:-1] # Remove the quotes
537 537 return pf
538 538
539 539 def testpid(pid):
540 540 '''return False if pid dead, True if running or not known'''
541 541 return True
542 542
543 543 def is_exec(f, last):
544 544 return last
545 545
546 546 def set_exec(f, mode):
547 547 pass
548 548
549 549 def set_binary(fd):
550 550 msvcrt.setmode(fd.fileno(), os.O_BINARY)
551 551
552 552 def pconvert(path):
553 553 return path.replace("\\", "/")
554 554
555 555 def localpath(path):
556 556 return path.replace('/', '\\')
557 557
558 558 def normpath(path):
559 559 return pconvert(os.path.normpath(path))
560 560
561 561 makelock = _makelock_file
562 562 readlock = _readlock_file
563 563
564 def samestat(s1, s2):
565 return False
566
564 567 def explain_exit(code):
565 568 return _("exited with status %d") % code, code
566 569
567 570 try:
568 571 # override functions with win32 versions if possible
569 572 from util_win32 import *
570 573 except ImportError:
571 574 pass
572 575
573 576 else:
574 577 nulldev = '/dev/null'
575 578
576 579 def rcfiles(path):
577 580 rcs = [os.path.join(path, 'hgrc')]
578 581 rcdir = os.path.join(path, 'hgrc.d')
579 582 try:
580 583 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
581 584 if f.endswith(".rc")])
582 585 except OSError, inst: pass
583 586 return rcs
584 587
585 588 def os_rcpath():
586 589 '''return default os-specific hgrc search path'''
587 590 path = []
588 591 if len(sys.argv) > 0:
589 592 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
590 593 '/../etc/mercurial'))
591 594 path.extend(rcfiles('/etc/mercurial'))
592 595 path.append(os.path.expanduser('~/.hgrc'))
593 596 path = [os.path.normpath(f) for f in path]
594 597 return path
595 598
596 599 def parse_patch_output(output_line):
597 600 """parses the output produced by patch and returns the file name"""
598 601 pf = output_line[14:]
599 602 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
600 603 pf = pf[1:-1] # Remove the quotes
601 604 return pf
602 605
603 606 def is_exec(f, last):
604 607 """check whether a file is executable"""
605 608 return (os.stat(f).st_mode & 0100 != 0)
606 609
607 610 def set_exec(f, mode):
608 611 s = os.stat(f).st_mode
609 612 if (s & 0100 != 0) == mode:
610 613 return
611 614 if mode:
612 615 # Turn on +x for every +r bit when making a file executable
613 616 # and obey umask.
614 617 umask = os.umask(0)
615 618 os.umask(umask)
616 619 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
617 620 else:
618 621 os.chmod(f, s & 0666)
619 622
620 623 def set_binary(fd):
621 624 pass
622 625
623 626 def pconvert(path):
624 627 return path
625 628
626 629 def localpath(path):
627 630 return path
628 631
629 632 normpath = os.path.normpath
633 samestat = os.path.samestat
630 634
631 635 def makelock(info, pathname):
632 636 try:
633 637 os.symlink(info, pathname)
634 638 except OSError, why:
635 639 if why.errno == errno.EEXIST:
636 640 raise
637 641 else:
638 642 _makelock_file(info, pathname)
639 643
640 644 def readlock(pathname):
641 645 try:
642 646 return os.readlink(pathname)
643 647 except OSError, why:
644 648 if why.errno == errno.EINVAL:
645 649 return _readlock_file(pathname)
646 650 else:
647 651 raise
648 652
649 653 def testpid(pid):
650 654 '''return False if pid dead, True if running or not sure'''
651 655 try:
652 656 os.kill(pid, 0)
653 657 return True
654 658 except OSError, inst:
655 659 return inst.errno != errno.ESRCH
656 660
657 661 def explain_exit(code):
658 662 """return a 2-tuple (desc, code) describing a process's status"""
659 663 if os.WIFEXITED(code):
660 664 val = os.WEXITSTATUS(code)
661 665 return _("exited with status %d") % val, val
662 666 elif os.WIFSIGNALED(code):
663 667 val = os.WTERMSIG(code)
664 668 return _("killed by signal %d") % val, val
665 669 elif os.WIFSTOPPED(code):
666 670 val = os.WSTOPSIG(code)
667 671 return _("stopped by signal %d") % val, val
668 672 raise ValueError(_("invalid exit code"))
669 673
670 674 def opener(base, audit=True):
671 675 """
672 676 return a function that opens files relative to base
673 677
674 678 this function is used to hide the details of COW semantics and
675 679 remote file access from higher level code.
676 680 """
677 681 p = base
678 682 audit_p = audit
679 683
680 684 def mktempcopy(name):
681 685 d, fn = os.path.split(name)
682 686 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
683 687 os.close(fd)
684 688 fp = posixfile(temp, "wb")
685 689 try:
686 690 fp.write(posixfile(name, "rb").read())
687 691 except:
688 692 try: os.unlink(temp)
689 693 except: pass
690 694 raise
691 695 fp.close()
692 696 st = os.lstat(name)
693 697 os.chmod(temp, st.st_mode)
694 698 return temp
695 699
696 700 class atomictempfile(posixfile):
697 701 """the file will only be copied when rename is called"""
698 702 def __init__(self, name, mode):
699 703 self.__name = name
700 704 self.temp = mktempcopy(name)
701 705 posixfile.__init__(self, self.temp, mode)
702 706 def rename(self):
703 707 if not self.closed:
704 708 posixfile.close(self)
705 709 rename(self.temp, self.__name)
706 710 def __del__(self):
707 711 if not self.closed:
708 712 try:
709 713 os.unlink(self.temp)
710 714 except: pass
711 715 posixfile.close(self)
712 716
713 717 class atomicfile(atomictempfile):
714 718 """the file will only be copied on close"""
715 719 def __init__(self, name, mode):
716 720 atomictempfile.__init__(self, name, mode)
717 721 def close(self):
718 722 self.rename()
719 723 def __del__(self):
720 724 self.rename()
721 725
722 726 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
723 727 if audit_p:
724 728 audit_path(path)
725 729 f = os.path.join(p, path)
726 730
727 731 if not text:
728 732 mode += "b" # for that other OS
729 733
730 734 if mode[0] != "r":
731 735 try:
732 736 nlink = nlinks(f)
733 737 except OSError:
734 738 d = os.path.dirname(f)
735 739 if not os.path.isdir(d):
736 740 os.makedirs(d)
737 741 else:
738 742 if atomic:
739 743 return atomicfile(f, mode)
740 744 elif atomictemp:
741 745 return atomictempfile(f, mode)
742 746 if nlink > 1:
743 747 rename(mktempcopy(f), f)
744 748 return posixfile(f, mode)
745 749
746 750 return o
747 751
748 752 class chunkbuffer(object):
749 753 """Allow arbitrary sized chunks of data to be efficiently read from an
750 754 iterator over chunks of arbitrary size."""
751 755
752 756 def __init__(self, in_iter, targetsize = 2**16):
753 757 """in_iter is the iterator that's iterating over the input chunks.
754 758 targetsize is how big a buffer to try to maintain."""
755 759 self.in_iter = iter(in_iter)
756 760 self.buf = ''
757 761 self.targetsize = int(targetsize)
758 762 if self.targetsize <= 0:
759 763 raise ValueError(_("targetsize must be greater than 0, was %d") %
760 764 targetsize)
761 765 self.iterempty = False
762 766
763 767 def fillbuf(self):
764 768 """Ignore target size; read every chunk from iterator until empty."""
765 769 if not self.iterempty:
766 770 collector = cStringIO.StringIO()
767 771 collector.write(self.buf)
768 772 for ch in self.in_iter:
769 773 collector.write(ch)
770 774 self.buf = collector.getvalue()
771 775 self.iterempty = True
772 776
773 777 def read(self, l):
774 778 """Read L bytes of data from the iterator of chunks of data.
775 779 Returns less than L bytes if the iterator runs dry."""
776 780 if l > len(self.buf) and not self.iterempty:
777 781 # Clamp to a multiple of self.targetsize
778 782 targetsize = self.targetsize * ((l // self.targetsize) + 1)
779 783 collector = cStringIO.StringIO()
780 784 collector.write(self.buf)
781 785 collected = len(self.buf)
782 786 for chunk in self.in_iter:
783 787 collector.write(chunk)
784 788 collected += len(chunk)
785 789 if collected >= targetsize:
786 790 break
787 791 if collected < targetsize:
788 792 self.iterempty = True
789 793 self.buf = collector.getvalue()
790 794 s, self.buf = self.buf[:l], buffer(self.buf, l)
791 795 return s
792 796
793 797 def filechunkiter(f, size = 65536):
794 798 """Create a generator that produces all the data in the file size
795 799 (default 65536) bytes at a time. Chunks may be less than size
796 800 bytes if the chunk is the last chunk in the file, or the file is a
797 801 socket or some other type of file that sometimes reads less data
798 802 than is requested."""
799 803 s = f.read(size)
800 804 while len(s) > 0:
801 805 yield s
802 806 s = f.read(size)
803 807
804 808 def makedate():
805 809 lt = time.localtime()
806 810 if lt[8] == 1 and time.daylight:
807 811 tz = time.altzone
808 812 else:
809 813 tz = time.timezone
810 814 return time.mktime(lt), tz
811 815
812 816 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
813 817 """represent a (unixtime, offset) tuple as a localized time.
814 818 unixtime is seconds since the epoch, and offset is the time zone's
815 819 number of seconds away from UTC. if timezone is false, do not
816 820 append time zone to string."""
817 821 t, tz = date or makedate()
818 822 s = time.strftime(format, time.gmtime(float(t) - tz))
819 823 if timezone:
820 824 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
821 825 return s
822 826
823 827 def shortuser(user):
824 828 """Return a short representation of a user name or email address."""
825 829 f = user.find('@')
826 830 if f >= 0:
827 831 user = user[:f]
828 832 f = user.find('<')
829 833 if f >= 0:
830 834 user = user[f+1:]
831 835 return user
832 836
833 837 def walkrepos(path):
834 838 '''yield every hg repository under path, recursively.'''
835 839 def errhandler(err):
836 840 if err.filename == path:
837 841 raise err
838 842
839 843 for root, dirs, files in os.walk(path, onerror=errhandler):
840 844 for d in dirs:
841 845 if d == '.hg':
842 846 yield root
843 847 dirs[:] = []
844 848 break
845 849
846 850 _rcpath = None
847 851
848 852 def rcpath():
849 853 '''return hgrc search path. if env var HGRCPATH is set, use it.
850 854 for each item in path, if directory, use files ending in .rc,
851 855 else use item.
852 856 make HGRCPATH empty to only look in .hg/hgrc of current repo.
853 857 if no HGRCPATH, use default os-specific path.'''
854 858 global _rcpath
855 859 if _rcpath is None:
856 860 if 'HGRCPATH' in os.environ:
857 861 _rcpath = []
858 862 for p in os.environ['HGRCPATH'].split(os.pathsep):
859 863 if not p: continue
860 864 if os.path.isdir(p):
861 865 for f in os.listdir(p):
862 866 if f.endswith('.rc'):
863 867 _rcpath.append(os.path.join(p, f))
864 868 else:
865 869 _rcpath.append(p)
866 870 else:
867 871 _rcpath = os_rcpath()
868 872 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now