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