##// END OF EJS Templates
shortname: truncate at '.' too
Matt Mackall -
r3533:bb44489b default
parent child Browse files
Show More
@@ -1,1021 +1,1024
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 Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import gettext as _
16 16 from demandload import *
17 17 demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile")
18 18 demandload(globals(), "os threading time calendar ConfigParser")
19 19
20 20 # used by parsedate
21 21 defaultdateformats = ('%Y-%m-%d %H:%M:%S', '%Y-%m-%d %H:%M',
22 22 '%a %b %d %H:%M:%S %Y')
23 23
24 24 class SignalInterrupt(Exception):
25 25 """Exception raised on SIGTERM and SIGHUP."""
26 26
27 27 # like SafeConfigParser but with case-sensitive keys
28 28 class configparser(ConfigParser.SafeConfigParser):
29 29 def optionxform(self, optionstr):
30 30 return optionstr
31 31
32 32 def cachefunc(func):
33 33 '''cache the result of function calls'''
34 34 # XXX doesn't handle keywords args
35 35 cache = {}
36 36 if func.func_code.co_argcount == 1:
37 37 # we gain a small amount of time because
38 38 # we don't need to pack/unpack the list
39 39 def f(arg):
40 40 if arg not in cache:
41 41 cache[arg] = func(arg)
42 42 return cache[arg]
43 43 else:
44 44 def f(*args):
45 45 if args not in cache:
46 46 cache[args] = func(*args)
47 47 return cache[args]
48 48
49 49 return f
50 50
51 51 def pipefilter(s, cmd):
52 52 '''filter string S through command CMD, returning its output'''
53 53 (pout, pin) = popen2.popen2(cmd, -1, 'b')
54 54 def writer():
55 55 try:
56 56 pin.write(s)
57 57 pin.close()
58 58 except IOError, inst:
59 59 if inst.errno != errno.EPIPE:
60 60 raise
61 61
62 62 # we should use select instead on UNIX, but this will work on most
63 63 # systems, including Windows
64 64 w = threading.Thread(target=writer)
65 65 w.start()
66 66 f = pout.read()
67 67 pout.close()
68 68 w.join()
69 69 return f
70 70
71 71 def tempfilter(s, cmd):
72 72 '''filter string S through a pair of temporary files with CMD.
73 73 CMD is used as a template to create the real command to be run,
74 74 with the strings INFILE and OUTFILE replaced by the real names of
75 75 the temporary files generated.'''
76 76 inname, outname = None, None
77 77 try:
78 78 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
79 79 fp = os.fdopen(infd, 'wb')
80 80 fp.write(s)
81 81 fp.close()
82 82 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
83 83 os.close(outfd)
84 84 cmd = cmd.replace('INFILE', inname)
85 85 cmd = cmd.replace('OUTFILE', outname)
86 86 code = os.system(cmd)
87 87 if code: raise Abort(_("command '%s' failed: %s") %
88 88 (cmd, explain_exit(code)))
89 89 return open(outname, 'rb').read()
90 90 finally:
91 91 try:
92 92 if inname: os.unlink(inname)
93 93 except: pass
94 94 try:
95 95 if outname: os.unlink(outname)
96 96 except: pass
97 97
98 98 filtertable = {
99 99 'tempfile:': tempfilter,
100 100 'pipe:': pipefilter,
101 101 }
102 102
103 103 def filter(s, cmd):
104 104 "filter a string through a command that transforms its input to its output"
105 105 for name, fn in filtertable.iteritems():
106 106 if cmd.startswith(name):
107 107 return fn(s, cmd[len(name):].lstrip())
108 108 return pipefilter(s, cmd)
109 109
110 110 def find_in_path(name, path, default=None):
111 111 '''find name in search path. path can be string (will be split
112 112 with os.pathsep), or iterable thing that returns strings. if name
113 113 found, return path to name. else return default.'''
114 114 if isinstance(path, str):
115 115 path = path.split(os.pathsep)
116 116 for p in path:
117 117 p_name = os.path.join(p, name)
118 118 if os.path.exists(p_name):
119 119 return p_name
120 120 return default
121 121
122 122 def binary(s):
123 123 """return true if a string is binary data using diff's heuristic"""
124 124 if s and '\0' in s[:4096]:
125 125 return True
126 126 return False
127 127
128 128 def unique(g):
129 129 """return the uniq elements of iterable g"""
130 130 seen = {}
131 131 for f in g:
132 132 if f not in seen:
133 133 seen[f] = 1
134 134 yield f
135 135
136 136 class Abort(Exception):
137 137 """Raised if a command needs to print an error and exit."""
138 138
139 139 def always(fn): return True
140 140 def never(fn): return False
141 141
142 142 def patkind(name, dflt_pat='glob'):
143 143 """Split a string into an optional pattern kind prefix and the
144 144 actual pattern."""
145 145 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
146 146 if name.startswith(prefix + ':'): return name.split(':', 1)
147 147 return dflt_pat, name
148 148
149 149 def globre(pat, head='^', tail='$'):
150 150 "convert a glob pattern into a regexp"
151 151 i, n = 0, len(pat)
152 152 res = ''
153 153 group = False
154 154 def peek(): return i < n and pat[i]
155 155 while i < n:
156 156 c = pat[i]
157 157 i = i+1
158 158 if c == '*':
159 159 if peek() == '*':
160 160 i += 1
161 161 res += '.*'
162 162 else:
163 163 res += '[^/]*'
164 164 elif c == '?':
165 165 res += '.'
166 166 elif c == '[':
167 167 j = i
168 168 if j < n and pat[j] in '!]':
169 169 j += 1
170 170 while j < n and pat[j] != ']':
171 171 j += 1
172 172 if j >= n:
173 173 res += '\\['
174 174 else:
175 175 stuff = pat[i:j].replace('\\','\\\\')
176 176 i = j + 1
177 177 if stuff[0] == '!':
178 178 stuff = '^' + stuff[1:]
179 179 elif stuff[0] == '^':
180 180 stuff = '\\' + stuff
181 181 res = '%s[%s]' % (res, stuff)
182 182 elif c == '{':
183 183 group = True
184 184 res += '(?:'
185 185 elif c == '}' and group:
186 186 res += ')'
187 187 group = False
188 188 elif c == ',' and group:
189 189 res += '|'
190 190 elif c == '\\':
191 191 p = peek()
192 192 if p:
193 193 i += 1
194 194 res += re.escape(p)
195 195 else:
196 196 res += re.escape(c)
197 197 else:
198 198 res += re.escape(c)
199 199 return head + res + tail
200 200
201 201 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
202 202
203 203 def pathto(n1, n2):
204 204 '''return the relative path from one place to another.
205 205 this returns a path in the form used by the local filesystem, not hg.'''
206 206 if not n1: return localpath(n2)
207 207 a, b = n1.split('/'), n2.split('/')
208 208 a.reverse()
209 209 b.reverse()
210 210 while a and b and a[-1] == b[-1]:
211 211 a.pop()
212 212 b.pop()
213 213 b.reverse()
214 214 return os.sep.join((['..'] * len(a)) + b)
215 215
216 216 def canonpath(root, cwd, myname):
217 217 """return the canonical path of myname, given cwd and root"""
218 218 if root == os.sep:
219 219 rootsep = os.sep
220 220 elif root.endswith(os.sep):
221 221 rootsep = root
222 222 else:
223 223 rootsep = root + os.sep
224 224 name = myname
225 225 if not os.path.isabs(name):
226 226 name = os.path.join(root, cwd, name)
227 227 name = os.path.normpath(name)
228 228 if name != rootsep and name.startswith(rootsep):
229 229 name = name[len(rootsep):]
230 230 audit_path(name)
231 231 return pconvert(name)
232 232 elif name == root:
233 233 return ''
234 234 else:
235 235 # Determine whether `name' is in the hierarchy at or beneath `root',
236 236 # by iterating name=dirname(name) until that causes no change (can't
237 237 # check name == '/', because that doesn't work on windows). For each
238 238 # `name', compare dev/inode numbers. If they match, the list `rel'
239 239 # holds the reversed list of components making up the relative file
240 240 # name we want.
241 241 root_st = os.stat(root)
242 242 rel = []
243 243 while True:
244 244 try:
245 245 name_st = os.stat(name)
246 246 except OSError:
247 247 break
248 248 if samestat(name_st, root_st):
249 249 rel.reverse()
250 250 name = os.path.join(*rel)
251 251 audit_path(name)
252 252 return pconvert(name)
253 253 dirname, basename = os.path.split(name)
254 254 rel.append(basename)
255 255 if dirname == name:
256 256 break
257 257 name = dirname
258 258
259 259 raise Abort('%s not under root' % myname)
260 260
261 261 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
262 262 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
263 263
264 264 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
265 265 if os.name == 'nt':
266 266 dflt_pat = 'glob'
267 267 else:
268 268 dflt_pat = 'relpath'
269 269 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
270 270
271 271 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
272 272 """build a function to match a set of file patterns
273 273
274 274 arguments:
275 275 canonroot - the canonical root of the tree you're matching against
276 276 cwd - the current working directory, if relevant
277 277 names - patterns to find
278 278 inc - patterns to include
279 279 exc - patterns to exclude
280 280 head - a regex to prepend to patterns to control whether a match is rooted
281 281
282 282 a pattern is one of:
283 283 'glob:<rooted glob>'
284 284 're:<rooted regexp>'
285 285 'path:<rooted path>'
286 286 'relglob:<relative glob>'
287 287 'relpath:<relative path>'
288 288 'relre:<relative regexp>'
289 289 '<rooted path or regexp>'
290 290
291 291 returns:
292 292 a 3-tuple containing
293 293 - list of explicit non-pattern names passed in
294 294 - a bool match(filename) function
295 295 - a bool indicating if any patterns were passed in
296 296
297 297 todo:
298 298 make head regex a rooted bool
299 299 """
300 300
301 301 def contains_glob(name):
302 302 for c in name:
303 303 if c in _globchars: return True
304 304 return False
305 305
306 306 def regex(kind, name, tail):
307 307 '''convert a pattern into a regular expression'''
308 308 if kind == 're':
309 309 return name
310 310 elif kind == 'path':
311 311 return '^' + re.escape(name) + '(?:/|$)'
312 312 elif kind == 'relglob':
313 313 return head + globre(name, '(?:|.*/)', tail)
314 314 elif kind == 'relpath':
315 315 return head + re.escape(name) + tail
316 316 elif kind == 'relre':
317 317 if name.startswith('^'):
318 318 return name
319 319 return '.*' + name
320 320 return head + globre(name, '', tail)
321 321
322 322 def matchfn(pats, tail):
323 323 """build a matching function from a set of patterns"""
324 324 if not pats:
325 325 return
326 326 matches = []
327 327 for k, p in pats:
328 328 try:
329 329 pat = '(?:%s)' % regex(k, p, tail)
330 330 matches.append(re.compile(pat).match)
331 331 except re.error:
332 332 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
333 333 else: raise Abort("invalid pattern (%s): %s" % (k, p))
334 334
335 335 def buildfn(text):
336 336 for m in matches:
337 337 r = m(text)
338 338 if r:
339 339 return r
340 340
341 341 return buildfn
342 342
343 343 def globprefix(pat):
344 344 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
345 345 root = []
346 346 for p in pat.split(os.sep):
347 347 if contains_glob(p): break
348 348 root.append(p)
349 349 return '/'.join(root)
350 350
351 351 pats = []
352 352 files = []
353 353 roots = []
354 354 for kind, name in [patkind(p, dflt_pat) for p in names]:
355 355 if kind in ('glob', 'relpath'):
356 356 name = canonpath(canonroot, cwd, name)
357 357 if name == '':
358 358 kind, name = 'glob', '**'
359 359 if kind in ('glob', 'path', 're'):
360 360 pats.append((kind, name))
361 361 if kind == 'glob':
362 362 root = globprefix(name)
363 363 if root: roots.append(root)
364 364 elif kind == 'relpath':
365 365 files.append((kind, name))
366 366 roots.append(name)
367 367
368 368 patmatch = matchfn(pats, '$') or always
369 369 filematch = matchfn(files, '(?:/|$)') or always
370 370 incmatch = always
371 371 if inc:
372 372 inckinds = [patkind(canonpath(canonroot, cwd, i)) for i in inc]
373 373 incmatch = matchfn(inckinds, '(?:/|$)')
374 374 excmatch = lambda fn: False
375 375 if exc:
376 376 exckinds = [patkind(canonpath(canonroot, cwd, x)) for x in exc]
377 377 excmatch = matchfn(exckinds, '(?:/|$)')
378 378
379 379 return (roots,
380 380 lambda fn: (incmatch(fn) and not excmatch(fn) and
381 381 (fn.endswith('/') or
382 382 (not pats and not files) or
383 383 (pats and patmatch(fn)) or
384 384 (files and filematch(fn)))),
385 385 (inc or exc or (pats and pats != [('glob', '**')])) and True)
386 386
387 387 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
388 388 '''enhanced shell command execution.
389 389 run with environment maybe modified, maybe in different dir.
390 390
391 391 if command fails and onerr is None, return status. if ui object,
392 392 print error message and return status, else raise onerr object as
393 393 exception.'''
394 394 def py2shell(val):
395 395 'convert python object into string that is useful to shell'
396 396 if val in (None, False):
397 397 return '0'
398 398 if val == True:
399 399 return '1'
400 400 return str(val)
401 401 oldenv = {}
402 402 for k in environ:
403 403 oldenv[k] = os.environ.get(k)
404 404 if cwd is not None:
405 405 oldcwd = os.getcwd()
406 406 try:
407 407 for k, v in environ.iteritems():
408 408 os.environ[k] = py2shell(v)
409 409 if cwd is not None and oldcwd != cwd:
410 410 os.chdir(cwd)
411 411 rc = os.system(cmd)
412 412 if rc and onerr:
413 413 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
414 414 explain_exit(rc)[0])
415 415 if errprefix:
416 416 errmsg = '%s: %s' % (errprefix, errmsg)
417 417 try:
418 418 onerr.warn(errmsg + '\n')
419 419 except AttributeError:
420 420 raise onerr(errmsg)
421 421 return rc
422 422 finally:
423 423 for k, v in oldenv.iteritems():
424 424 if v is None:
425 425 del os.environ[k]
426 426 else:
427 427 os.environ[k] = v
428 428 if cwd is not None and oldcwd != cwd:
429 429 os.chdir(oldcwd)
430 430
431 431 def rename(src, dst):
432 432 """forcibly rename a file"""
433 433 try:
434 434 os.rename(src, dst)
435 435 except OSError, err:
436 436 # on windows, rename to existing file is not allowed, so we
437 437 # must delete destination first. but if file is open, unlink
438 438 # schedules it for delete but does not delete it. rename
439 439 # happens immediately even for open files, so we create
440 440 # temporary file, delete it, rename destination to that name,
441 441 # then delete that. then rename is safe to do.
442 442 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
443 443 os.close(fd)
444 444 os.unlink(temp)
445 445 os.rename(dst, temp)
446 446 os.unlink(temp)
447 447 os.rename(src, dst)
448 448
449 449 def unlink(f):
450 450 """unlink and remove the directory if it is empty"""
451 451 os.unlink(f)
452 452 # try removing directories that might now be empty
453 453 try:
454 454 os.removedirs(os.path.dirname(f))
455 455 except OSError:
456 456 pass
457 457
458 458 def copyfiles(src, dst, hardlink=None):
459 459 """Copy a directory tree using hardlinks if possible"""
460 460
461 461 if hardlink is None:
462 462 hardlink = (os.stat(src).st_dev ==
463 463 os.stat(os.path.dirname(dst)).st_dev)
464 464
465 465 if os.path.isdir(src):
466 466 os.mkdir(dst)
467 467 for name in os.listdir(src):
468 468 srcname = os.path.join(src, name)
469 469 dstname = os.path.join(dst, name)
470 470 copyfiles(srcname, dstname, hardlink)
471 471 else:
472 472 if hardlink:
473 473 try:
474 474 os_link(src, dst)
475 475 except (IOError, OSError):
476 476 hardlink = False
477 477 shutil.copy(src, dst)
478 478 else:
479 479 shutil.copy(src, dst)
480 480
481 481 def audit_path(path):
482 482 """Abort if path contains dangerous components"""
483 483 parts = os.path.normcase(path).split(os.sep)
484 484 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
485 485 or os.pardir in parts):
486 486 raise Abort(_("path contains illegal component: %s\n") % path)
487 487
488 488 def _makelock_file(info, pathname):
489 489 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
490 490 os.write(ld, info)
491 491 os.close(ld)
492 492
493 493 def _readlock_file(pathname):
494 494 return posixfile(pathname).read()
495 495
496 496 def nlinks(pathname):
497 497 """Return number of hardlinks for the given file."""
498 498 return os.lstat(pathname).st_nlink
499 499
500 500 if hasattr(os, 'link'):
501 501 os_link = os.link
502 502 else:
503 503 def os_link(src, dst):
504 504 raise OSError(0, _("Hardlinks not supported"))
505 505
506 506 def fstat(fp):
507 507 '''stat file object that may not have fileno method.'''
508 508 try:
509 509 return os.fstat(fp.fileno())
510 510 except AttributeError:
511 511 return os.stat(fp.name)
512 512
513 513 posixfile = file
514 514
515 515 def is_win_9x():
516 516 '''return true if run on windows 95, 98 or me.'''
517 517 try:
518 518 return sys.getwindowsversion()[3] == 1
519 519 except AttributeError:
520 520 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
521 521
522 522 # Platform specific variants
523 523 if os.name == 'nt':
524 524 demandload(globals(), "msvcrt")
525 525 nulldev = 'NUL:'
526 526
527 527 class winstdout:
528 528 '''stdout on windows misbehaves if sent through a pipe'''
529 529
530 530 def __init__(self, fp):
531 531 self.fp = fp
532 532
533 533 def __getattr__(self, key):
534 534 return getattr(self.fp, key)
535 535
536 536 def close(self):
537 537 try:
538 538 self.fp.close()
539 539 except: pass
540 540
541 541 def write(self, s):
542 542 try:
543 543 return self.fp.write(s)
544 544 except IOError, inst:
545 545 if inst.errno != 0: raise
546 546 self.close()
547 547 raise IOError(errno.EPIPE, 'Broken pipe')
548 548
549 549 sys.stdout = winstdout(sys.stdout)
550 550
551 551 def system_rcpath():
552 552 try:
553 553 return system_rcpath_win32()
554 554 except:
555 555 return [r'c:\mercurial\mercurial.ini']
556 556
557 557 def os_rcpath():
558 558 '''return default os-specific hgrc search path'''
559 559 path = system_rcpath()
560 560 path.append(user_rcpath())
561 561 userprofile = os.environ.get('USERPROFILE')
562 562 if userprofile:
563 563 path.append(os.path.join(userprofile, 'mercurial.ini'))
564 564 return path
565 565
566 566 def user_rcpath():
567 567 '''return os-specific hgrc search path to the user dir'''
568 568 return os.path.join(os.path.expanduser('~'), 'mercurial.ini')
569 569
570 570 def parse_patch_output(output_line):
571 571 """parses the output produced by patch and returns the file name"""
572 572 pf = output_line[14:]
573 573 if pf[0] == '`':
574 574 pf = pf[1:-1] # Remove the quotes
575 575 return pf
576 576
577 577 def testpid(pid):
578 578 '''return False if pid dead, True if running or not known'''
579 579 return True
580 580
581 581 def is_exec(f, last):
582 582 return last
583 583
584 584 def set_exec(f, mode):
585 585 pass
586 586
587 587 def set_binary(fd):
588 588 msvcrt.setmode(fd.fileno(), os.O_BINARY)
589 589
590 590 def pconvert(path):
591 591 return path.replace("\\", "/")
592 592
593 593 def localpath(path):
594 594 return path.replace('/', '\\')
595 595
596 596 def normpath(path):
597 597 return pconvert(os.path.normpath(path))
598 598
599 599 makelock = _makelock_file
600 600 readlock = _readlock_file
601 601
602 602 def samestat(s1, s2):
603 603 return False
604 604
605 605 def shellquote(s):
606 606 return '"%s"' % s.replace('"', '\\"')
607 607
608 608 def explain_exit(code):
609 609 return _("exited with status %d") % code, code
610 610
611 611 try:
612 612 # override functions with win32 versions if possible
613 613 from util_win32 import *
614 614 if not is_win_9x():
615 615 posixfile = posixfile_nt
616 616 except ImportError:
617 617 pass
618 618
619 619 else:
620 620 nulldev = '/dev/null'
621 621
622 622 def rcfiles(path):
623 623 rcs = [os.path.join(path, 'hgrc')]
624 624 rcdir = os.path.join(path, 'hgrc.d')
625 625 try:
626 626 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
627 627 if f.endswith(".rc")])
628 628 except OSError:
629 629 pass
630 630 return rcs
631 631
632 632 def os_rcpath():
633 633 '''return default os-specific hgrc search path'''
634 634 path = []
635 635 # old mod_python does not set sys.argv
636 636 if len(getattr(sys, 'argv', [])) > 0:
637 637 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
638 638 '/../etc/mercurial'))
639 639 path.extend(rcfiles('/etc/mercurial'))
640 640 path.append(os.path.expanduser('~/.hgrc'))
641 641 path = [os.path.normpath(f) for f in path]
642 642 return path
643 643
644 644 def parse_patch_output(output_line):
645 645 """parses the output produced by patch and returns the file name"""
646 646 pf = output_line[14:]
647 647 if pf.startswith("'") and pf.endswith("'") and " " in pf:
648 648 pf = pf[1:-1] # Remove the quotes
649 649 return pf
650 650
651 651 def is_exec(f, last):
652 652 """check whether a file is executable"""
653 653 return (os.lstat(f).st_mode & 0100 != 0)
654 654
655 655 def set_exec(f, mode):
656 656 s = os.lstat(f).st_mode
657 657 if (s & 0100 != 0) == mode:
658 658 return
659 659 if mode:
660 660 # Turn on +x for every +r bit when making a file executable
661 661 # and obey umask.
662 662 umask = os.umask(0)
663 663 os.umask(umask)
664 664 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
665 665 else:
666 666 os.chmod(f, s & 0666)
667 667
668 668 def set_binary(fd):
669 669 pass
670 670
671 671 def pconvert(path):
672 672 return path
673 673
674 674 def localpath(path):
675 675 return path
676 676
677 677 normpath = os.path.normpath
678 678 samestat = os.path.samestat
679 679
680 680 def makelock(info, pathname):
681 681 try:
682 682 os.symlink(info, pathname)
683 683 except OSError, why:
684 684 if why.errno == errno.EEXIST:
685 685 raise
686 686 else:
687 687 _makelock_file(info, pathname)
688 688
689 689 def readlock(pathname):
690 690 try:
691 691 return os.readlink(pathname)
692 692 except OSError, why:
693 693 if why.errno == errno.EINVAL:
694 694 return _readlock_file(pathname)
695 695 else:
696 696 raise
697 697
698 698 def shellquote(s):
699 699 return "'%s'" % s.replace("'", "'\\''")
700 700
701 701 def testpid(pid):
702 702 '''return False if pid dead, True if running or not sure'''
703 703 try:
704 704 os.kill(pid, 0)
705 705 return True
706 706 except OSError, inst:
707 707 return inst.errno != errno.ESRCH
708 708
709 709 def explain_exit(code):
710 710 """return a 2-tuple (desc, code) describing a process's status"""
711 711 if os.WIFEXITED(code):
712 712 val = os.WEXITSTATUS(code)
713 713 return _("exited with status %d") % val, val
714 714 elif os.WIFSIGNALED(code):
715 715 val = os.WTERMSIG(code)
716 716 return _("killed by signal %d") % val, val
717 717 elif os.WIFSTOPPED(code):
718 718 val = os.WSTOPSIG(code)
719 719 return _("stopped by signal %d") % val, val
720 720 raise ValueError(_("invalid exit code"))
721 721
722 722 def opener(base, audit=True):
723 723 """
724 724 return a function that opens files relative to base
725 725
726 726 this function is used to hide the details of COW semantics and
727 727 remote file access from higher level code.
728 728 """
729 729 p = base
730 730 audit_p = audit
731 731
732 732 def mktempcopy(name):
733 733 d, fn = os.path.split(name)
734 734 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
735 735 os.close(fd)
736 736 ofp = posixfile(temp, "wb")
737 737 try:
738 738 try:
739 739 ifp = posixfile(name, "rb")
740 740 except IOError, inst:
741 741 if not getattr(inst, 'filename', None):
742 742 inst.filename = name
743 743 raise
744 744 for chunk in filechunkiter(ifp):
745 745 ofp.write(chunk)
746 746 ifp.close()
747 747 ofp.close()
748 748 except:
749 749 try: os.unlink(temp)
750 750 except: pass
751 751 raise
752 752 st = os.lstat(name)
753 753 os.chmod(temp, st.st_mode)
754 754 return temp
755 755
756 756 class atomictempfile(posixfile):
757 757 """the file will only be copied when rename is called"""
758 758 def __init__(self, name, mode):
759 759 self.__name = name
760 760 self.temp = mktempcopy(name)
761 761 posixfile.__init__(self, self.temp, mode)
762 762 def rename(self):
763 763 if not self.closed:
764 764 posixfile.close(self)
765 765 rename(self.temp, localpath(self.__name))
766 766 def __del__(self):
767 767 if not self.closed:
768 768 try:
769 769 os.unlink(self.temp)
770 770 except: pass
771 771 posixfile.close(self)
772 772
773 773 class atomicfile(atomictempfile):
774 774 """the file will only be copied on close"""
775 775 def __init__(self, name, mode):
776 776 atomictempfile.__init__(self, name, mode)
777 777 def close(self):
778 778 self.rename()
779 779 def __del__(self):
780 780 self.rename()
781 781
782 782 def o(path, mode="r", text=False, atomic=False, atomictemp=False):
783 783 if audit_p:
784 784 audit_path(path)
785 785 f = os.path.join(p, path)
786 786
787 787 if not text:
788 788 mode += "b" # for that other OS
789 789
790 790 if mode[0] != "r":
791 791 try:
792 792 nlink = nlinks(f)
793 793 except OSError:
794 794 d = os.path.dirname(f)
795 795 if not os.path.isdir(d):
796 796 os.makedirs(d)
797 797 else:
798 798 if atomic:
799 799 return atomicfile(f, mode)
800 800 elif atomictemp:
801 801 return atomictempfile(f, mode)
802 802 if nlink > 1:
803 803 rename(mktempcopy(f), f)
804 804 return posixfile(f, mode)
805 805
806 806 return o
807 807
808 808 class chunkbuffer(object):
809 809 """Allow arbitrary sized chunks of data to be efficiently read from an
810 810 iterator over chunks of arbitrary size."""
811 811
812 812 def __init__(self, in_iter, targetsize = 2**16):
813 813 """in_iter is the iterator that's iterating over the input chunks.
814 814 targetsize is how big a buffer to try to maintain."""
815 815 self.in_iter = iter(in_iter)
816 816 self.buf = ''
817 817 self.targetsize = int(targetsize)
818 818 if self.targetsize <= 0:
819 819 raise ValueError(_("targetsize must be greater than 0, was %d") %
820 820 targetsize)
821 821 self.iterempty = False
822 822
823 823 def fillbuf(self):
824 824 """Ignore target size; read every chunk from iterator until empty."""
825 825 if not self.iterempty:
826 826 collector = cStringIO.StringIO()
827 827 collector.write(self.buf)
828 828 for ch in self.in_iter:
829 829 collector.write(ch)
830 830 self.buf = collector.getvalue()
831 831 self.iterempty = True
832 832
833 833 def read(self, l):
834 834 """Read L bytes of data from the iterator of chunks of data.
835 835 Returns less than L bytes if the iterator runs dry."""
836 836 if l > len(self.buf) and not self.iterempty:
837 837 # Clamp to a multiple of self.targetsize
838 838 targetsize = self.targetsize * ((l // self.targetsize) + 1)
839 839 collector = cStringIO.StringIO()
840 840 collector.write(self.buf)
841 841 collected = len(self.buf)
842 842 for chunk in self.in_iter:
843 843 collector.write(chunk)
844 844 collected += len(chunk)
845 845 if collected >= targetsize:
846 846 break
847 847 if collected < targetsize:
848 848 self.iterempty = True
849 849 self.buf = collector.getvalue()
850 850 s, self.buf = self.buf[:l], buffer(self.buf, l)
851 851 return s
852 852
853 853 def filechunkiter(f, size=65536, limit=None):
854 854 """Create a generator that produces the data in the file size
855 855 (default 65536) bytes at a time, up to optional limit (default is
856 856 to read all data). Chunks may be less than size bytes if the
857 857 chunk is the last chunk in the file, or the file is a socket or
858 858 some other type of file that sometimes reads less data than is
859 859 requested."""
860 860 assert size >= 0
861 861 assert limit is None or limit >= 0
862 862 while True:
863 863 if limit is None: nbytes = size
864 864 else: nbytes = min(limit, size)
865 865 s = nbytes and f.read(nbytes)
866 866 if not s: break
867 867 if limit: limit -= len(s)
868 868 yield s
869 869
870 870 def makedate():
871 871 lt = time.localtime()
872 872 if lt[8] == 1 and time.daylight:
873 873 tz = time.altzone
874 874 else:
875 875 tz = time.timezone
876 876 return time.mktime(lt), tz
877 877
878 878 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
879 879 """represent a (unixtime, offset) tuple as a localized time.
880 880 unixtime is seconds since the epoch, and offset is the time zone's
881 881 number of seconds away from UTC. if timezone is false, do not
882 882 append time zone to string."""
883 883 t, tz = date or makedate()
884 884 s = time.strftime(format, time.gmtime(float(t) - tz))
885 885 if timezone:
886 886 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
887 887 return s
888 888
889 889 def strdate(string, format='%a %b %d %H:%M:%S %Y'):
890 890 """parse a localized time string and return a (unixtime, offset) tuple.
891 891 if the string cannot be parsed, ValueError is raised."""
892 892 def hastimezone(string):
893 893 return (string[-4:].isdigit() and
894 894 (string[-5] == '+' or string[-5] == '-') and
895 895 string[-6].isspace())
896 896
897 897 # NOTE: unixtime = localunixtime + offset
898 898 if hastimezone(string):
899 899 date, tz = string[:-6], string[-5:]
900 900 tz = int(tz)
901 901 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
902 902 else:
903 903 date, offset = string, None
904 904 timetuple = time.strptime(date, format)
905 905 localunixtime = int(calendar.timegm(timetuple))
906 906 if offset is None:
907 907 # local timezone
908 908 unixtime = int(time.mktime(timetuple))
909 909 offset = unixtime - localunixtime
910 910 else:
911 911 unixtime = localunixtime + offset
912 912 return unixtime, offset
913 913
914 914 def parsedate(string, formats=None):
915 915 """parse a localized time string and return a (unixtime, offset) tuple.
916 916 The date may be a "unixtime offset" string or in one of the specified
917 917 formats."""
918 918 if not formats:
919 919 formats = defaultdateformats
920 920 try:
921 921 when, offset = map(int, string.split(' '))
922 922 except ValueError:
923 923 for format in formats:
924 924 try:
925 925 when, offset = strdate(string, format)
926 926 except ValueError:
927 927 pass
928 928 else:
929 929 break
930 930 else:
931 931 raise ValueError(_('invalid date: %r '
932 932 'see hg(1) manual page for details')
933 933 % string)
934 934 # validate explicit (probably user-specified) date and
935 935 # time zone offset. values must fit in signed 32 bits for
936 936 # current 32-bit linux runtimes. timezones go from UTC-12
937 937 # to UTC+14
938 938 if abs(when) > 0x7fffffff:
939 939 raise ValueError(_('date exceeds 32 bits: %d') % when)
940 940 if offset < -50400 or offset > 43200:
941 941 raise ValueError(_('impossible time zone offset: %d') % offset)
942 942 return when, offset
943 943
944 944 def shortuser(user):
945 945 """Return a short representation of a user name or email address."""
946 946 f = user.find('@')
947 947 if f >= 0:
948 948 user = user[:f]
949 949 f = user.find('<')
950 950 if f >= 0:
951 951 user = user[f+1:]
952 952 f = user.find(' ')
953 953 if f >= 0:
954 954 user = user[:f]
955 f = user.find('.')
956 if f >= 0:
957 user = user[:f]
955 958 return user
956 959
957 960 def walkrepos(path):
958 961 '''yield every hg repository under path, recursively.'''
959 962 def errhandler(err):
960 963 if err.filename == path:
961 964 raise err
962 965
963 966 for root, dirs, files in os.walk(path, onerror=errhandler):
964 967 for d in dirs:
965 968 if d == '.hg':
966 969 yield root
967 970 dirs[:] = []
968 971 break
969 972
970 973 _rcpath = None
971 974
972 975 def rcpath():
973 976 '''return hgrc search path. if env var HGRCPATH is set, use it.
974 977 for each item in path, if directory, use files ending in .rc,
975 978 else use item.
976 979 make HGRCPATH empty to only look in .hg/hgrc of current repo.
977 980 if no HGRCPATH, use default os-specific path.'''
978 981 global _rcpath
979 982 if _rcpath is None:
980 983 if 'HGRCPATH' in os.environ:
981 984 _rcpath = []
982 985 for p in os.environ['HGRCPATH'].split(os.pathsep):
983 986 if not p: continue
984 987 if os.path.isdir(p):
985 988 for f in os.listdir(p):
986 989 if f.endswith('.rc'):
987 990 _rcpath.append(os.path.join(p, f))
988 991 else:
989 992 _rcpath.append(p)
990 993 else:
991 994 _rcpath = os_rcpath()
992 995 return _rcpath
993 996
994 997 def bytecount(nbytes):
995 998 '''return byte count formatted as readable string, with units'''
996 999
997 1000 units = (
998 1001 (100, 1<<30, _('%.0f GB')),
999 1002 (10, 1<<30, _('%.1f GB')),
1000 1003 (1, 1<<30, _('%.2f GB')),
1001 1004 (100, 1<<20, _('%.0f MB')),
1002 1005 (10, 1<<20, _('%.1f MB')),
1003 1006 (1, 1<<20, _('%.2f MB')),
1004 1007 (100, 1<<10, _('%.0f KB')),
1005 1008 (10, 1<<10, _('%.1f KB')),
1006 1009 (1, 1<<10, _('%.2f KB')),
1007 1010 (1, 1, _('%.0f bytes')),
1008 1011 )
1009 1012
1010 1013 for multiplier, divisor, format in units:
1011 1014 if nbytes >= divisor * multiplier:
1012 1015 return format % (nbytes / float(divisor))
1013 1016 return units[-1][2] % nbytes
1014 1017
1015 1018 def drop_scheme(scheme, path):
1016 1019 sc = scheme + ':'
1017 1020 if path.startswith(sc):
1018 1021 path = path[len(sc):]
1019 1022 if path.startswith('//'):
1020 1023 path = path[2:]
1021 1024 return path
General Comments 0
You need to be logged in to leave comments. Login now