##// END OF EJS Templates
reduce memory used by util.opener when making a temp copy of a file.
Vadim Gelfer -
r2237:4a069064 default
parent child Browse files
Show More
@@ -1,878 +1,880 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 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 564 def samestat(s1, s2):
565 565 return False
566 566
567 567 def explain_exit(code):
568 568 return _("exited with status %d") % code, code
569 569
570 570 try:
571 571 # override functions with win32 versions if possible
572 572 from util_win32 import *
573 573 except ImportError:
574 574 pass
575 575
576 576 else:
577 577 nulldev = '/dev/null'
578 578
579 579 def rcfiles(path):
580 580 rcs = [os.path.join(path, 'hgrc')]
581 581 rcdir = os.path.join(path, 'hgrc.d')
582 582 try:
583 583 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
584 584 if f.endswith(".rc")])
585 585 except OSError, inst: pass
586 586 return rcs
587 587
588 588 def os_rcpath():
589 589 '''return default os-specific hgrc search path'''
590 590 path = []
591 591 if len(sys.argv) > 0:
592 592 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
593 593 '/../etc/mercurial'))
594 594 path.extend(rcfiles('/etc/mercurial'))
595 595 path.append(os.path.expanduser('~/.hgrc'))
596 596 path = [os.path.normpath(f) for f in path]
597 597 return path
598 598
599 599 def parse_patch_output(output_line):
600 600 """parses the output produced by patch and returns the file name"""
601 601 pf = output_line[14:]
602 602 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
603 603 pf = pf[1:-1] # Remove the quotes
604 604 return pf
605 605
606 606 def is_exec(f, last):
607 607 """check whether a file is executable"""
608 608 return (os.stat(f).st_mode & 0100 != 0)
609 609
610 610 def set_exec(f, mode):
611 611 s = os.stat(f).st_mode
612 612 if (s & 0100 != 0) == mode:
613 613 return
614 614 if mode:
615 615 # Turn on +x for every +r bit when making a file executable
616 616 # and obey umask.
617 617 umask = os.umask(0)
618 618 os.umask(umask)
619 619 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
620 620 else:
621 621 os.chmod(f, s & 0666)
622 622
623 623 def set_binary(fd):
624 624 pass
625 625
626 626 def pconvert(path):
627 627 return path
628 628
629 629 def localpath(path):
630 630 return path
631 631
632 632 normpath = os.path.normpath
633 633 samestat = os.path.samestat
634 634
635 635 def makelock(info, pathname):
636 636 try:
637 637 os.symlink(info, pathname)
638 638 except OSError, why:
639 639 if why.errno == errno.EEXIST:
640 640 raise
641 641 else:
642 642 _makelock_file(info, pathname)
643 643
644 644 def readlock(pathname):
645 645 try:
646 646 return os.readlink(pathname)
647 647 except OSError, why:
648 648 if why.errno == errno.EINVAL:
649 649 return _readlock_file(pathname)
650 650 else:
651 651 raise
652 652
653 653 def testpid(pid):
654 654 '''return False if pid dead, True if running or not sure'''
655 655 try:
656 656 os.kill(pid, 0)
657 657 return True
658 658 except OSError, inst:
659 659 return inst.errno != errno.ESRCH
660 660
661 661 def explain_exit(code):
662 662 """return a 2-tuple (desc, code) describing a process's status"""
663 663 if os.WIFEXITED(code):
664 664 val = os.WEXITSTATUS(code)
665 665 return _("exited with status %d") % val, val
666 666 elif os.WIFSIGNALED(code):
667 667 val = os.WTERMSIG(code)
668 668 return _("killed by signal %d") % val, val
669 669 elif os.WIFSTOPPED(code):
670 670 val = os.WSTOPSIG(code)
671 671 return _("stopped by signal %d") % val, val
672 672 raise ValueError(_("invalid exit code"))
673 673
674 674 def opener(base, audit=True):
675 675 """
676 676 return a function that opens files relative to base
677 677
678 678 this function is used to hide the details of COW semantics and
679 679 remote file access from higher level code.
680 680 """
681 681 p = base
682 682 audit_p = audit
683 683
684 684 def mktempcopy(name):
685 685 d, fn = os.path.split(name)
686 686 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
687 687 os.close(fd)
688 fp = posixfile(temp, "wb")
688 ofp = posixfile(temp, "wb")
689 689 try:
690 690 try:
691 s = posixfile(name, "rb").read()
691 ifp = posixfile(name, "rb")
692 692 except IOError, inst:
693 693 if not getattr(inst, 'filename', None):
694 694 inst.filename = name
695 695 raise
696 fp.write(s)
696 for chunk in filechunkiter(ifp):
697 ofp.write(chunk)
698 ifp.close()
699 ofp.close()
697 700 except:
698 701 try: os.unlink(temp)
699 702 except: pass
700 703 raise
701 fp.close()
702 704 st = os.lstat(name)
703 705 os.chmod(temp, st.st_mode)
704 706 return temp
705 707
706 708 class atomictempfile(posixfile):
707 709 """the file will only be copied when rename is called"""
708 710 def __init__(self, name, mode):
709 711 self.__name = name
710 712 self.temp = mktempcopy(name)
711 713 posixfile.__init__(self, self.temp, mode)
712 714 def rename(self):
713 715 if not self.closed:
714 716 posixfile.close(self)
715 717 rename(self.temp, self.__name)
716 718 def __del__(self):
717 719 if not self.closed:
718 720 try:
719 721 os.unlink(self.temp)
720 722 except: pass
721 723 posixfile.close(self)
722 724
723 725 class atomicfile(atomictempfile):
724 726 """the file will only be copied on close"""
725 727 def __init__(self, name, mode):
726 728 atomictempfile.__init__(self, name, mode)
727 729 def close(self):
728 730 self.rename()
729 731 def __del__(self):
730 732 self.rename()
731 733
732 734 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
733 735 if audit_p:
734 736 audit_path(path)
735 737 f = os.path.join(p, path)
736 738
737 739 if not text:
738 740 mode += "b" # for that other OS
739 741
740 742 if mode[0] != "r":
741 743 try:
742 744 nlink = nlinks(f)
743 745 except OSError:
744 746 d = os.path.dirname(f)
745 747 if not os.path.isdir(d):
746 748 os.makedirs(d)
747 749 else:
748 750 if atomic:
749 751 return atomicfile(f, mode)
750 752 elif atomictemp:
751 753 return atomictempfile(f, mode)
752 754 if nlink > 1:
753 755 rename(mktempcopy(f), f)
754 756 return posixfile(f, mode)
755 757
756 758 return o
757 759
758 760 class chunkbuffer(object):
759 761 """Allow arbitrary sized chunks of data to be efficiently read from an
760 762 iterator over chunks of arbitrary size."""
761 763
762 764 def __init__(self, in_iter, targetsize = 2**16):
763 765 """in_iter is the iterator that's iterating over the input chunks.
764 766 targetsize is how big a buffer to try to maintain."""
765 767 self.in_iter = iter(in_iter)
766 768 self.buf = ''
767 769 self.targetsize = int(targetsize)
768 770 if self.targetsize <= 0:
769 771 raise ValueError(_("targetsize must be greater than 0, was %d") %
770 772 targetsize)
771 773 self.iterempty = False
772 774
773 775 def fillbuf(self):
774 776 """Ignore target size; read every chunk from iterator until empty."""
775 777 if not self.iterempty:
776 778 collector = cStringIO.StringIO()
777 779 collector.write(self.buf)
778 780 for ch in self.in_iter:
779 781 collector.write(ch)
780 782 self.buf = collector.getvalue()
781 783 self.iterempty = True
782 784
783 785 def read(self, l):
784 786 """Read L bytes of data from the iterator of chunks of data.
785 787 Returns less than L bytes if the iterator runs dry."""
786 788 if l > len(self.buf) and not self.iterempty:
787 789 # Clamp to a multiple of self.targetsize
788 790 targetsize = self.targetsize * ((l // self.targetsize) + 1)
789 791 collector = cStringIO.StringIO()
790 792 collector.write(self.buf)
791 793 collected = len(self.buf)
792 794 for chunk in self.in_iter:
793 795 collector.write(chunk)
794 796 collected += len(chunk)
795 797 if collected >= targetsize:
796 798 break
797 799 if collected < targetsize:
798 800 self.iterempty = True
799 801 self.buf = collector.getvalue()
800 802 s, self.buf = self.buf[:l], buffer(self.buf, l)
801 803 return s
802 804
803 805 def filechunkiter(f, size = 65536):
804 806 """Create a generator that produces all the data in the file size
805 807 (default 65536) bytes at a time. Chunks may be less than size
806 808 bytes if the chunk is the last chunk in the file, or the file is a
807 809 socket or some other type of file that sometimes reads less data
808 810 than is requested."""
809 811 s = f.read(size)
810 812 while len(s) > 0:
811 813 yield s
812 814 s = f.read(size)
813 815
814 816 def makedate():
815 817 lt = time.localtime()
816 818 if lt[8] == 1 and time.daylight:
817 819 tz = time.altzone
818 820 else:
819 821 tz = time.timezone
820 822 return time.mktime(lt), tz
821 823
822 824 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
823 825 """represent a (unixtime, offset) tuple as a localized time.
824 826 unixtime is seconds since the epoch, and offset is the time zone's
825 827 number of seconds away from UTC. if timezone is false, do not
826 828 append time zone to string."""
827 829 t, tz = date or makedate()
828 830 s = time.strftime(format, time.gmtime(float(t) - tz))
829 831 if timezone:
830 832 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
831 833 return s
832 834
833 835 def shortuser(user):
834 836 """Return a short representation of a user name or email address."""
835 837 f = user.find('@')
836 838 if f >= 0:
837 839 user = user[:f]
838 840 f = user.find('<')
839 841 if f >= 0:
840 842 user = user[f+1:]
841 843 return user
842 844
843 845 def walkrepos(path):
844 846 '''yield every hg repository under path, recursively.'''
845 847 def errhandler(err):
846 848 if err.filename == path:
847 849 raise err
848 850
849 851 for root, dirs, files in os.walk(path, onerror=errhandler):
850 852 for d in dirs:
851 853 if d == '.hg':
852 854 yield root
853 855 dirs[:] = []
854 856 break
855 857
856 858 _rcpath = None
857 859
858 860 def rcpath():
859 861 '''return hgrc search path. if env var HGRCPATH is set, use it.
860 862 for each item in path, if directory, use files ending in .rc,
861 863 else use item.
862 864 make HGRCPATH empty to only look in .hg/hgrc of current repo.
863 865 if no HGRCPATH, use default os-specific path.'''
864 866 global _rcpath
865 867 if _rcpath is None:
866 868 if 'HGRCPATH' in os.environ:
867 869 _rcpath = []
868 870 for p in os.environ['HGRCPATH'].split(os.pathsep):
869 871 if not p: continue
870 872 if os.path.isdir(p):
871 873 for f in os.listdir(p):
872 874 if f.endswith('.rc'):
873 875 _rcpath.append(os.path.join(p, f))
874 876 else:
875 877 _rcpath.append(p)
876 878 else:
877 879 _rcpath = os_rcpath()
878 880 return _rcpath
General Comments 0
You need to be logged in to leave comments. Login now