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