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