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