##// END OF EJS Templates
Merge with crew
Lee Cantey -
r4627:d4e4d0f4 merge default
parent child Browse files
Show More
@@ -1,1531 +1,1531 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 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 _
16 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile
17 17 import os, threading, time, calendar, ConfigParser, locale, glob
18 18
19 19 try:
20 20 _encoding = os.environ.get("HGENCODING")
21 21 if sys.platform == 'darwin' and not _encoding:
22 22 # On darwin, getpreferredencoding ignores the locale environment and
23 23 # always returns mac-roman. We override this if the environment is
24 24 # not C (has been customized by the user).
25 25 locale.setlocale(locale.LC_CTYPE, '')
26 26 _encoding = locale.getlocale()[1]
27 27 if not _encoding:
28 28 _encoding = locale.getpreferredencoding() or 'ascii'
29 29 except locale.Error:
30 30 _encoding = 'ascii'
31 31 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
32 32 _fallbackencoding = 'ISO-8859-1'
33 33
34 34 def tolocal(s):
35 35 """
36 36 Convert a string from internal UTF-8 to local encoding
37 37
38 38 All internal strings should be UTF-8 but some repos before the
39 39 implementation of locale support may contain latin1 or possibly
40 40 other character sets. We attempt to decode everything strictly
41 41 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
42 42 replace unknown characters.
43 43 """
44 44 for e in ('UTF-8', _fallbackencoding):
45 45 try:
46 46 u = s.decode(e) # attempt strict decoding
47 47 return u.encode(_encoding, "replace")
48 48 except LookupError, k:
49 49 raise Abort(_("%s, please check your locale settings") % k)
50 50 except UnicodeDecodeError:
51 51 pass
52 52 u = s.decode("utf-8", "replace") # last ditch
53 53 return u.encode(_encoding, "replace")
54 54
55 55 def fromlocal(s):
56 56 """
57 57 Convert a string from the local character encoding to UTF-8
58 58
59 59 We attempt to decode strings using the encoding mode set by
60 60 HG_ENCODINGMODE, which defaults to 'strict'. In this mode, unknown
61 61 characters will cause an error message. Other modes include
62 62 'replace', which replaces unknown characters with a special
63 63 Unicode character, and 'ignore', which drops the character.
64 64 """
65 65 try:
66 66 return s.decode(_encoding, _encodingmode).encode("utf-8")
67 67 except UnicodeDecodeError, inst:
68 68 sub = s[max(0, inst.start-10):inst.start+10]
69 69 raise Abort("decoding near '%s': %s!" % (sub, inst))
70 70 except LookupError, k:
71 71 raise Abort(_("%s, please check your locale settings") % k)
72 72
73 73 def locallen(s):
74 74 """Find the length in characters of a local string"""
75 75 return len(s.decode(_encoding, "replace"))
76 76
77 77 def localsub(s, a, b=None):
78 78 try:
79 79 u = s.decode(_encoding, _encodingmode)
80 80 if b is not None:
81 81 u = u[a:b]
82 82 else:
83 83 u = u[:a]
84 84 return u.encode(_encoding, _encodingmode)
85 85 except UnicodeDecodeError, inst:
86 86 sub = s[max(0, inst.start-10), inst.start+10]
87 87 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
88 88
89 89 # used by parsedate
90 90 defaultdateformats = (
91 91 '%Y-%m-%d %H:%M:%S',
92 92 '%Y-%m-%d %I:%M:%S%p',
93 93 '%Y-%m-%d %H:%M',
94 94 '%Y-%m-%d %I:%M%p',
95 95 '%Y-%m-%d',
96 96 '%m-%d',
97 97 '%m/%d',
98 98 '%m/%d/%y',
99 99 '%m/%d/%Y',
100 100 '%a %b %d %H:%M:%S %Y',
101 101 '%a %b %d %I:%M:%S%p %Y',
102 102 '%b %d %H:%M:%S %Y',
103 103 '%b %d %I:%M:%S%p %Y',
104 104 '%b %d %H:%M:%S',
105 105 '%b %d %I:%M:%S%p',
106 106 '%b %d %H:%M',
107 107 '%b %d %I:%M%p',
108 108 '%b %d %Y',
109 109 '%b %d',
110 110 '%H:%M:%S',
111 111 '%I:%M:%SP',
112 112 '%H:%M',
113 113 '%I:%M%p',
114 114 )
115 115
116 116 extendeddateformats = defaultdateformats + (
117 117 "%Y",
118 118 "%Y-%m",
119 119 "%b",
120 120 "%b %Y",
121 121 )
122 122
123 123 class SignalInterrupt(Exception):
124 124 """Exception raised on SIGTERM and SIGHUP."""
125 125
126 126 # differences from SafeConfigParser:
127 127 # - case-sensitive keys
128 128 # - allows values that are not strings (this means that you may not
129 129 # be able to save the configuration to a file)
130 130 class configparser(ConfigParser.SafeConfigParser):
131 131 def optionxform(self, optionstr):
132 132 return optionstr
133 133
134 134 def set(self, section, option, value):
135 135 return ConfigParser.ConfigParser.set(self, section, option, value)
136 136
137 137 def _interpolate(self, section, option, rawval, vars):
138 138 if not isinstance(rawval, basestring):
139 139 return rawval
140 140 return ConfigParser.SafeConfigParser._interpolate(self, section,
141 141 option, rawval, vars)
142 142
143 143 def cachefunc(func):
144 144 '''cache the result of function calls'''
145 145 # XXX doesn't handle keywords args
146 146 cache = {}
147 147 if func.func_code.co_argcount == 1:
148 148 # we gain a small amount of time because
149 149 # we don't need to pack/unpack the list
150 150 def f(arg):
151 151 if arg not in cache:
152 152 cache[arg] = func(arg)
153 153 return cache[arg]
154 154 else:
155 155 def f(*args):
156 156 if args not in cache:
157 157 cache[args] = func(*args)
158 158 return cache[args]
159 159
160 160 return f
161 161
162 162 def pipefilter(s, cmd):
163 163 '''filter string S through command CMD, returning its output'''
164 (pout, pin) = popen2.popen2(cmd, -1, 'b')
164 (pin, pout) = os.popen2(cmd, 'b')
165 165 def writer():
166 166 try:
167 167 pin.write(s)
168 168 pin.close()
169 169 except IOError, inst:
170 170 if inst.errno != errno.EPIPE:
171 171 raise
172 172
173 173 # we should use select instead on UNIX, but this will work on most
174 174 # systems, including Windows
175 175 w = threading.Thread(target=writer)
176 176 w.start()
177 177 f = pout.read()
178 178 pout.close()
179 179 w.join()
180 180 return f
181 181
182 182 def tempfilter(s, cmd):
183 183 '''filter string S through a pair of temporary files with CMD.
184 184 CMD is used as a template to create the real command to be run,
185 185 with the strings INFILE and OUTFILE replaced by the real names of
186 186 the temporary files generated.'''
187 187 inname, outname = None, None
188 188 try:
189 189 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
190 190 fp = os.fdopen(infd, 'wb')
191 191 fp.write(s)
192 192 fp.close()
193 193 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
194 194 os.close(outfd)
195 195 cmd = cmd.replace('INFILE', inname)
196 196 cmd = cmd.replace('OUTFILE', outname)
197 197 code = os.system(cmd)
198 198 if code: raise Abort(_("command '%s' failed: %s") %
199 199 (cmd, explain_exit(code)))
200 200 return open(outname, 'rb').read()
201 201 finally:
202 202 try:
203 203 if inname: os.unlink(inname)
204 204 except: pass
205 205 try:
206 206 if outname: os.unlink(outname)
207 207 except: pass
208 208
209 209 filtertable = {
210 210 'tempfile:': tempfilter,
211 211 'pipe:': pipefilter,
212 212 }
213 213
214 214 def filter(s, cmd):
215 215 "filter a string through a command that transforms its input to its output"
216 216 for name, fn in filtertable.iteritems():
217 217 if cmd.startswith(name):
218 218 return fn(s, cmd[len(name):].lstrip())
219 219 return pipefilter(s, cmd)
220 220
221 221 def binary(s):
222 222 """return true if a string is binary data using diff's heuristic"""
223 223 if s and '\0' in s[:4096]:
224 224 return True
225 225 return False
226 226
227 227 def unique(g):
228 228 """return the uniq elements of iterable g"""
229 229 seen = {}
230 230 l = []
231 231 for f in g:
232 232 if f not in seen:
233 233 seen[f] = 1
234 234 l.append(f)
235 235 return l
236 236
237 237 class Abort(Exception):
238 238 """Raised if a command needs to print an error and exit."""
239 239
240 240 class UnexpectedOutput(Abort):
241 241 """Raised to print an error with part of output and exit."""
242 242
243 243 def always(fn): return True
244 244 def never(fn): return False
245 245
246 246 def expand_glob(pats):
247 247 '''On Windows, expand the implicit globs in a list of patterns'''
248 248 if os.name != 'nt':
249 249 return list(pats)
250 250 ret = []
251 251 for p in pats:
252 252 kind, name = patkind(p, None)
253 253 if kind is None:
254 254 globbed = glob.glob(name)
255 255 if globbed:
256 256 ret.extend(globbed)
257 257 continue
258 258 # if we couldn't expand the glob, just keep it around
259 259 ret.append(p)
260 260 return ret
261 261
262 262 def patkind(name, dflt_pat='glob'):
263 263 """Split a string into an optional pattern kind prefix and the
264 264 actual pattern."""
265 265 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
266 266 if name.startswith(prefix + ':'): return name.split(':', 1)
267 267 return dflt_pat, name
268 268
269 269 def globre(pat, head='^', tail='$'):
270 270 "convert a glob pattern into a regexp"
271 271 i, n = 0, len(pat)
272 272 res = ''
273 273 group = False
274 274 def peek(): return i < n and pat[i]
275 275 while i < n:
276 276 c = pat[i]
277 277 i = i+1
278 278 if c == '*':
279 279 if peek() == '*':
280 280 i += 1
281 281 res += '.*'
282 282 else:
283 283 res += '[^/]*'
284 284 elif c == '?':
285 285 res += '.'
286 286 elif c == '[':
287 287 j = i
288 288 if j < n and pat[j] in '!]':
289 289 j += 1
290 290 while j < n and pat[j] != ']':
291 291 j += 1
292 292 if j >= n:
293 293 res += '\\['
294 294 else:
295 295 stuff = pat[i:j].replace('\\','\\\\')
296 296 i = j + 1
297 297 if stuff[0] == '!':
298 298 stuff = '^' + stuff[1:]
299 299 elif stuff[0] == '^':
300 300 stuff = '\\' + stuff
301 301 res = '%s[%s]' % (res, stuff)
302 302 elif c == '{':
303 303 group = True
304 304 res += '(?:'
305 305 elif c == '}' and group:
306 306 res += ')'
307 307 group = False
308 308 elif c == ',' and group:
309 309 res += '|'
310 310 elif c == '\\':
311 311 p = peek()
312 312 if p:
313 313 i += 1
314 314 res += re.escape(p)
315 315 else:
316 316 res += re.escape(c)
317 317 else:
318 318 res += re.escape(c)
319 319 return head + res + tail
320 320
321 321 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
322 322
323 323 def pathto(root, n1, n2):
324 324 '''return the relative path from one place to another.
325 325 root should use os.sep to separate directories
326 326 n1 should use os.sep to separate directories
327 327 n2 should use "/" to separate directories
328 328 returns an os.sep-separated path.
329 329
330 330 If n1 is a relative path, it's assumed it's
331 331 relative to root.
332 332 n2 should always be relative to root.
333 333 '''
334 334 if not n1: return localpath(n2)
335 335 if os.path.isabs(n1):
336 336 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
337 337 return os.path.join(root, localpath(n2))
338 338 n2 = '/'.join((pconvert(root), n2))
339 339 a, b = n1.split(os.sep), n2.split('/')
340 340 a.reverse()
341 341 b.reverse()
342 342 while a and b and a[-1] == b[-1]:
343 343 a.pop()
344 344 b.pop()
345 345 b.reverse()
346 346 return os.sep.join((['..'] * len(a)) + b)
347 347
348 348 def canonpath(root, cwd, myname):
349 349 """return the canonical path of myname, given cwd and root"""
350 350 if root == os.sep:
351 351 rootsep = os.sep
352 352 elif root.endswith(os.sep):
353 353 rootsep = root
354 354 else:
355 355 rootsep = root + os.sep
356 356 name = myname
357 357 if not os.path.isabs(name):
358 358 name = os.path.join(root, cwd, name)
359 359 name = os.path.normpath(name)
360 360 if name != rootsep and name.startswith(rootsep):
361 361 name = name[len(rootsep):]
362 362 audit_path(name)
363 363 return pconvert(name)
364 364 elif name == root:
365 365 return ''
366 366 else:
367 367 # Determine whether `name' is in the hierarchy at or beneath `root',
368 368 # by iterating name=dirname(name) until that causes no change (can't
369 369 # check name == '/', because that doesn't work on windows). For each
370 370 # `name', compare dev/inode numbers. If they match, the list `rel'
371 371 # holds the reversed list of components making up the relative file
372 372 # name we want.
373 373 root_st = os.stat(root)
374 374 rel = []
375 375 while True:
376 376 try:
377 377 name_st = os.stat(name)
378 378 except OSError:
379 379 break
380 380 if samestat(name_st, root_st):
381 381 if not rel:
382 382 # name was actually the same as root (maybe a symlink)
383 383 return ''
384 384 rel.reverse()
385 385 name = os.path.join(*rel)
386 386 audit_path(name)
387 387 return pconvert(name)
388 388 dirname, basename = os.path.split(name)
389 389 rel.append(basename)
390 390 if dirname == name:
391 391 break
392 392 name = dirname
393 393
394 394 raise Abort('%s not under root' % myname)
395 395
396 396 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
397 397 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
398 398
399 399 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
400 400 globbed=False, default=None):
401 401 default = default or 'relpath'
402 402 if default == 'relpath' and not globbed:
403 403 names = expand_glob(names)
404 404 return _matcher(canonroot, cwd, names, inc, exc, default, src)
405 405
406 406 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
407 407 """build a function to match a set of file patterns
408 408
409 409 arguments:
410 410 canonroot - the canonical root of the tree you're matching against
411 411 cwd - the current working directory, if relevant
412 412 names - patterns to find
413 413 inc - patterns to include
414 414 exc - patterns to exclude
415 415 dflt_pat - if a pattern in names has no explicit type, assume this one
416 416 src - where these patterns came from (e.g. .hgignore)
417 417
418 418 a pattern is one of:
419 419 'glob:<glob>' - a glob relative to cwd
420 420 're:<regexp>' - a regular expression
421 421 'path:<path>' - a path relative to canonroot
422 422 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
423 423 'relpath:<path>' - a path relative to cwd
424 424 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
425 425 '<something>' - one of the cases above, selected by the dflt_pat argument
426 426
427 427 returns:
428 428 a 3-tuple containing
429 429 - list of roots (places where one should start a recursive walk of the fs);
430 430 this often matches the explicit non-pattern names passed in, but also
431 431 includes the initial part of glob: patterns that has no glob characters
432 432 - a bool match(filename) function
433 433 - a bool indicating if any patterns were passed in
434 434 """
435 435
436 436 # a common case: no patterns at all
437 437 if not names and not inc and not exc:
438 438 return [], always, False
439 439
440 440 def contains_glob(name):
441 441 for c in name:
442 442 if c in _globchars: return True
443 443 return False
444 444
445 445 def regex(kind, name, tail):
446 446 '''convert a pattern into a regular expression'''
447 447 if not name:
448 448 return ''
449 449 if kind == 're':
450 450 return name
451 451 elif kind == 'path':
452 452 return '^' + re.escape(name) + '(?:/|$)'
453 453 elif kind == 'relglob':
454 454 return globre(name, '(?:|.*/)', tail)
455 455 elif kind == 'relpath':
456 456 return re.escape(name) + '(?:/|$)'
457 457 elif kind == 'relre':
458 458 if name.startswith('^'):
459 459 return name
460 460 return '.*' + name
461 461 return globre(name, '', tail)
462 462
463 463 def matchfn(pats, tail):
464 464 """build a matching function from a set of patterns"""
465 465 if not pats:
466 466 return
467 467 try:
468 468 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
469 469 return re.compile(pat).match
470 470 except re.error:
471 471 for k, p in pats:
472 472 try:
473 473 re.compile('(?:%s)' % regex(k, p, tail))
474 474 except re.error:
475 475 if src:
476 476 raise Abort("%s: invalid pattern (%s): %s" %
477 477 (src, k, p))
478 478 else:
479 479 raise Abort("invalid pattern (%s): %s" % (k, p))
480 480 raise Abort("invalid pattern")
481 481
482 482 def globprefix(pat):
483 483 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
484 484 root = []
485 485 for p in pat.split('/'):
486 486 if contains_glob(p): break
487 487 root.append(p)
488 488 return '/'.join(root) or '.'
489 489
490 490 def normalizepats(names, default):
491 491 pats = []
492 492 roots = []
493 493 anypats = False
494 494 for kind, name in [patkind(p, default) for p in names]:
495 495 if kind in ('glob', 'relpath'):
496 496 name = canonpath(canonroot, cwd, name)
497 497 elif kind in ('relglob', 'path'):
498 498 name = normpath(name)
499 499
500 500 pats.append((kind, name))
501 501
502 502 if kind in ('glob', 're', 'relglob', 'relre'):
503 503 anypats = True
504 504
505 505 if kind == 'glob':
506 506 root = globprefix(name)
507 507 roots.append(root)
508 508 elif kind in ('relpath', 'path'):
509 509 roots.append(name or '.')
510 510 elif kind == 'relglob':
511 511 roots.append('.')
512 512 return roots, pats, anypats
513 513
514 514 roots, pats, anypats = normalizepats(names, dflt_pat)
515 515
516 516 patmatch = matchfn(pats, '$') or always
517 517 incmatch = always
518 518 if inc:
519 519 dummy, inckinds, dummy = normalizepats(inc, 'glob')
520 520 incmatch = matchfn(inckinds, '(?:/|$)')
521 521 excmatch = lambda fn: False
522 522 if exc:
523 523 dummy, exckinds, dummy = normalizepats(exc, 'glob')
524 524 excmatch = matchfn(exckinds, '(?:/|$)')
525 525
526 526 if not names and inc and not exc:
527 527 # common case: hgignore patterns
528 528 match = incmatch
529 529 else:
530 530 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
531 531
532 532 return (roots, match, (inc or exc or anypats) and True)
533 533
534 534 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
535 535 '''enhanced shell command execution.
536 536 run with environment maybe modified, maybe in different dir.
537 537
538 538 if command fails and onerr is None, return status. if ui object,
539 539 print error message and return status, else raise onerr object as
540 540 exception.'''
541 541 def py2shell(val):
542 542 'convert python object into string that is useful to shell'
543 543 if val in (None, False):
544 544 return '0'
545 545 if val == True:
546 546 return '1'
547 547 return str(val)
548 548 oldenv = {}
549 549 for k in environ:
550 550 oldenv[k] = os.environ.get(k)
551 551 if cwd is not None:
552 552 oldcwd = os.getcwd()
553 553 origcmd = cmd
554 554 if os.name == 'nt':
555 555 cmd = '"%s"' % cmd
556 556 try:
557 557 for k, v in environ.iteritems():
558 558 os.environ[k] = py2shell(v)
559 559 if cwd is not None and oldcwd != cwd:
560 560 os.chdir(cwd)
561 561 rc = os.system(cmd)
562 562 if rc and onerr:
563 563 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
564 564 explain_exit(rc)[0])
565 565 if errprefix:
566 566 errmsg = '%s: %s' % (errprefix, errmsg)
567 567 try:
568 568 onerr.warn(errmsg + '\n')
569 569 except AttributeError:
570 570 raise onerr(errmsg)
571 571 return rc
572 572 finally:
573 573 for k, v in oldenv.iteritems():
574 574 if v is None:
575 575 del os.environ[k]
576 576 else:
577 577 os.environ[k] = v
578 578 if cwd is not None and oldcwd != cwd:
579 579 os.chdir(oldcwd)
580 580
581 581 # os.path.lexists is not available on python2.3
582 582 def lexists(filename):
583 583 "test whether a file with this name exists. does not follow symlinks"
584 584 try:
585 585 os.lstat(filename)
586 586 except:
587 587 return False
588 588 return True
589 589
590 590 def rename(src, dst):
591 591 """forcibly rename a file"""
592 592 try:
593 593 os.rename(src, dst)
594 594 except OSError, err:
595 595 # on windows, rename to existing file is not allowed, so we
596 596 # must delete destination first. but if file is open, unlink
597 597 # schedules it for delete but does not delete it. rename
598 598 # happens immediately even for open files, so we create
599 599 # temporary file, delete it, rename destination to that name,
600 600 # then delete that. then rename is safe to do.
601 601 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
602 602 os.close(fd)
603 603 os.unlink(temp)
604 604 os.rename(dst, temp)
605 605 os.unlink(temp)
606 606 os.rename(src, dst)
607 607
608 608 def unlink(f):
609 609 """unlink and remove the directory if it is empty"""
610 610 os.unlink(f)
611 611 # try removing directories that might now be empty
612 612 try:
613 613 os.removedirs(os.path.dirname(f))
614 614 except OSError:
615 615 pass
616 616
617 617 def copyfile(src, dest):
618 618 "copy a file, preserving mode"
619 619 if os.path.islink(src):
620 620 try:
621 621 os.unlink(dest)
622 622 except:
623 623 pass
624 624 os.symlink(os.readlink(src), dest)
625 625 else:
626 626 try:
627 627 shutil.copyfile(src, dest)
628 628 shutil.copymode(src, dest)
629 629 except shutil.Error, inst:
630 630 raise Abort(str(inst))
631 631
632 632 def copyfiles(src, dst, hardlink=None):
633 633 """Copy a directory tree using hardlinks if possible"""
634 634
635 635 if hardlink is None:
636 636 hardlink = (os.stat(src).st_dev ==
637 637 os.stat(os.path.dirname(dst)).st_dev)
638 638
639 639 if os.path.isdir(src):
640 640 os.mkdir(dst)
641 641 for name in os.listdir(src):
642 642 srcname = os.path.join(src, name)
643 643 dstname = os.path.join(dst, name)
644 644 copyfiles(srcname, dstname, hardlink)
645 645 else:
646 646 if hardlink:
647 647 try:
648 648 os_link(src, dst)
649 649 except (IOError, OSError):
650 650 hardlink = False
651 651 shutil.copy(src, dst)
652 652 else:
653 653 shutil.copy(src, dst)
654 654
655 655 def audit_path(path):
656 656 """Abort if path contains dangerous components"""
657 657 parts = os.path.normcase(path).split(os.sep)
658 658 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
659 659 or os.pardir in parts):
660 660 raise Abort(_("path contains illegal component: %s") % path)
661 661
662 662 def _makelock_file(info, pathname):
663 663 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
664 664 os.write(ld, info)
665 665 os.close(ld)
666 666
667 667 def _readlock_file(pathname):
668 668 return posixfile(pathname).read()
669 669
670 670 def nlinks(pathname):
671 671 """Return number of hardlinks for the given file."""
672 672 return os.lstat(pathname).st_nlink
673 673
674 674 if hasattr(os, 'link'):
675 675 os_link = os.link
676 676 else:
677 677 def os_link(src, dst):
678 678 raise OSError(0, _("Hardlinks not supported"))
679 679
680 680 def fstat(fp):
681 681 '''stat file object that may not have fileno method.'''
682 682 try:
683 683 return os.fstat(fp.fileno())
684 684 except AttributeError:
685 685 return os.stat(fp.name)
686 686
687 687 posixfile = file
688 688
689 689 def is_win_9x():
690 690 '''return true if run on windows 95, 98 or me.'''
691 691 try:
692 692 return sys.getwindowsversion()[3] == 1
693 693 except AttributeError:
694 694 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
695 695
696 696 getuser_fallback = None
697 697
698 698 def getuser():
699 699 '''return name of current user'''
700 700 try:
701 701 return getpass.getuser()
702 702 except ImportError:
703 703 # import of pwd will fail on windows - try fallback
704 704 if getuser_fallback:
705 705 return getuser_fallback()
706 706 # raised if win32api not available
707 707 raise Abort(_('user name not available - set USERNAME '
708 708 'environment variable'))
709 709
710 710 def username(uid=None):
711 711 """Return the name of the user with the given uid.
712 712
713 713 If uid is None, return the name of the current user."""
714 714 try:
715 715 import pwd
716 716 if uid is None:
717 717 uid = os.getuid()
718 718 try:
719 719 return pwd.getpwuid(uid)[0]
720 720 except KeyError:
721 721 return str(uid)
722 722 except ImportError:
723 723 return None
724 724
725 725 def groupname(gid=None):
726 726 """Return the name of the group with the given gid.
727 727
728 728 If gid is None, return the name of the current group."""
729 729 try:
730 730 import grp
731 731 if gid is None:
732 732 gid = os.getgid()
733 733 try:
734 734 return grp.getgrgid(gid)[0]
735 735 except KeyError:
736 736 return str(gid)
737 737 except ImportError:
738 738 return None
739 739
740 740 # File system features
741 741
742 742 def checkfolding(path):
743 743 """
744 744 Check whether the given path is on a case-sensitive filesystem
745 745
746 746 Requires a path (like /foo/.hg) ending with a foldable final
747 747 directory component.
748 748 """
749 749 s1 = os.stat(path)
750 750 d, b = os.path.split(path)
751 751 p2 = os.path.join(d, b.upper())
752 752 if path == p2:
753 753 p2 = os.path.join(d, b.lower())
754 754 try:
755 755 s2 = os.stat(p2)
756 756 if s2 == s1:
757 757 return False
758 758 return True
759 759 except:
760 760 return True
761 761
762 762 def checkexec(path):
763 763 """
764 764 Check whether the given path is on a filesystem with UNIX-like exec flags
765 765
766 766 Requires a directory (like /foo/.hg)
767 767 """
768 768 fh, fn = tempfile.mkstemp("", "", path)
769 769 os.close(fh)
770 770 m = os.stat(fn).st_mode
771 771 os.chmod(fn, m ^ 0111)
772 772 r = (os.stat(fn).st_mode != m)
773 773 os.unlink(fn)
774 774 return r
775 775
776 776 def execfunc(path, fallback):
777 777 '''return an is_exec() function with default to fallback'''
778 778 if checkexec(path):
779 779 return lambda x: is_exec(os.path.join(path, x))
780 780 return fallback
781 781
782 782 def checklink(path):
783 783 """check whether the given path is on a symlink-capable filesystem"""
784 784 # mktemp is not racy because symlink creation will fail if the
785 785 # file already exists
786 786 name = tempfile.mktemp(dir=path)
787 787 try:
788 788 os.symlink(".", name)
789 789 os.unlink(name)
790 790 return True
791 791 except (OSError, AttributeError):
792 792 return False
793 793
794 794 def linkfunc(path, fallback):
795 795 '''return an is_link() function with default to fallback'''
796 796 if checklink(path):
797 797 return lambda x: os.path.islink(os.path.join(path, x))
798 798 return fallback
799 799
800 800 _umask = os.umask(0)
801 801 os.umask(_umask)
802 802
803 803 def needbinarypatch():
804 804 """return True if patches should be applied in binary mode by default."""
805 805 return os.name == 'nt'
806 806
807 807 # Platform specific variants
808 808 if os.name == 'nt':
809 809 import msvcrt
810 810 nulldev = 'NUL:'
811 811
812 812 class winstdout:
813 813 '''stdout on windows misbehaves if sent through a pipe'''
814 814
815 815 def __init__(self, fp):
816 816 self.fp = fp
817 817
818 818 def __getattr__(self, key):
819 819 return getattr(self.fp, key)
820 820
821 821 def close(self):
822 822 try:
823 823 self.fp.close()
824 824 except: pass
825 825
826 826 def write(self, s):
827 827 try:
828 828 return self.fp.write(s)
829 829 except IOError, inst:
830 830 if inst.errno != 0: raise
831 831 self.close()
832 832 raise IOError(errno.EPIPE, 'Broken pipe')
833 833
834 834 def flush(self):
835 835 try:
836 836 return self.fp.flush()
837 837 except IOError, inst:
838 838 if inst.errno != errno.EINVAL: raise
839 839 self.close()
840 840 raise IOError(errno.EPIPE, 'Broken pipe')
841 841
842 842 sys.stdout = winstdout(sys.stdout)
843 843
844 844 def system_rcpath():
845 845 try:
846 846 return system_rcpath_win32()
847 847 except:
848 848 return [r'c:\mercurial\mercurial.ini']
849 849
850 850 def user_rcpath():
851 851 '''return os-specific hgrc search path to the user dir'''
852 852 try:
853 853 userrc = user_rcpath_win32()
854 854 except:
855 855 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
856 856 path = [userrc]
857 857 userprofile = os.environ.get('USERPROFILE')
858 858 if userprofile:
859 859 path.append(os.path.join(userprofile, 'mercurial.ini'))
860 860 return path
861 861
862 862 def parse_patch_output(output_line):
863 863 """parses the output produced by patch and returns the file name"""
864 864 pf = output_line[14:]
865 865 if pf[0] == '`':
866 866 pf = pf[1:-1] # Remove the quotes
867 867 return pf
868 868
869 869 def testpid(pid):
870 870 '''return False if pid dead, True if running or not known'''
871 871 return True
872 872
873 873 def set_exec(f, mode):
874 874 pass
875 875
876 876 def set_link(f, mode):
877 877 pass
878 878
879 879 def set_binary(fd):
880 880 msvcrt.setmode(fd.fileno(), os.O_BINARY)
881 881
882 882 def pconvert(path):
883 883 return path.replace("\\", "/")
884 884
885 885 def localpath(path):
886 886 return path.replace('/', '\\')
887 887
888 888 def normpath(path):
889 889 return pconvert(os.path.normpath(path))
890 890
891 891 makelock = _makelock_file
892 892 readlock = _readlock_file
893 893
894 894 def samestat(s1, s2):
895 895 return False
896 896
897 897 # A sequence of backslashes is special iff it precedes a double quote:
898 898 # - if there's an even number of backslashes, the double quote is not
899 899 # quoted (i.e. it ends the quoted region)
900 900 # - if there's an odd number of backslashes, the double quote is quoted
901 901 # - in both cases, every pair of backslashes is unquoted into a single
902 902 # backslash
903 903 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
904 904 # So, to quote a string, we must surround it in double quotes, double
905 905 # the number of backslashes that preceed double quotes and add another
906 906 # backslash before every double quote (being careful with the double
907 907 # quote we've appended to the end)
908 908 _quotere = None
909 909 def shellquote(s):
910 910 global _quotere
911 911 if _quotere is None:
912 912 _quotere = re.compile(r'(\\*)("|\\$)')
913 913 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
914 914
915 915 def explain_exit(code):
916 916 return _("exited with status %d") % code, code
917 917
918 918 # if you change this stub into a real check, please try to implement the
919 919 # username and groupname functions above, too.
920 920 def isowner(fp, st=None):
921 921 return True
922 922
923 923 def find_in_path(name, path, default=None):
924 924 '''find name in search path. path can be string (will be split
925 925 with os.pathsep), or iterable thing that returns strings. if name
926 926 found, return path to name. else return default. name is looked up
927 927 using cmd.exe rules, using PATHEXT.'''
928 928 if isinstance(path, str):
929 929 path = path.split(os.pathsep)
930 930
931 931 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
932 932 pathext = pathext.lower().split(os.pathsep)
933 933 isexec = os.path.splitext(name)[1].lower() in pathext
934 934
935 935 for p in path:
936 936 p_name = os.path.join(p, name)
937 937
938 938 if isexec and os.path.exists(p_name):
939 939 return p_name
940 940
941 941 for ext in pathext:
942 942 p_name_ext = p_name + ext
943 943 if os.path.exists(p_name_ext):
944 944 return p_name_ext
945 945 return default
946 946
947 947 try:
948 948 # override functions with win32 versions if possible
949 949 from util_win32 import *
950 950 if not is_win_9x():
951 951 posixfile = posixfile_nt
952 952 except ImportError:
953 953 pass
954 954
955 955 else:
956 956 nulldev = '/dev/null'
957 957
958 958 def rcfiles(path):
959 959 rcs = [os.path.join(path, 'hgrc')]
960 960 rcdir = os.path.join(path, 'hgrc.d')
961 961 try:
962 962 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
963 963 if f.endswith(".rc")])
964 964 except OSError:
965 965 pass
966 966 return rcs
967 967
968 968 def system_rcpath():
969 969 path = []
970 970 # old mod_python does not set sys.argv
971 971 if len(getattr(sys, 'argv', [])) > 0:
972 972 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
973 973 '/../etc/mercurial'))
974 974 path.extend(rcfiles('/etc/mercurial'))
975 975 return path
976 976
977 977 def user_rcpath():
978 978 return [os.path.expanduser('~/.hgrc')]
979 979
980 980 def parse_patch_output(output_line):
981 981 """parses the output produced by patch and returns the file name"""
982 982 pf = output_line[14:]
983 983 if pf.startswith("'") and pf.endswith("'") and " " in pf:
984 984 pf = pf[1:-1] # Remove the quotes
985 985 return pf
986 986
987 987 def is_exec(f):
988 988 """check whether a file is executable"""
989 989 return (os.lstat(f).st_mode & 0100 != 0)
990 990
991 991 def set_exec(f, mode):
992 992 s = os.lstat(f).st_mode
993 993 if (s & 0100 != 0) == mode:
994 994 return
995 995 if mode:
996 996 # Turn on +x for every +r bit when making a file executable
997 997 # and obey umask.
998 998 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
999 999 else:
1000 1000 os.chmod(f, s & 0666)
1001 1001
1002 1002 def set_link(f, mode):
1003 1003 """make a file a symbolic link/regular file
1004 1004
1005 1005 if a file is changed to a link, its contents become the link data
1006 1006 if a link is changed to a file, its link data become its contents
1007 1007 """
1008 1008
1009 1009 m = os.path.islink(f)
1010 1010 if m == bool(mode):
1011 1011 return
1012 1012
1013 1013 if mode: # switch file to link
1014 1014 data = file(f).read()
1015 1015 os.unlink(f)
1016 1016 os.symlink(data, f)
1017 1017 else:
1018 1018 data = os.readlink(f)
1019 1019 os.unlink(f)
1020 1020 file(f, "w").write(data)
1021 1021
1022 1022 def set_binary(fd):
1023 1023 pass
1024 1024
1025 1025 def pconvert(path):
1026 1026 return path
1027 1027
1028 1028 def localpath(path):
1029 1029 return path
1030 1030
1031 1031 normpath = os.path.normpath
1032 1032 samestat = os.path.samestat
1033 1033
1034 1034 def makelock(info, pathname):
1035 1035 try:
1036 1036 os.symlink(info, pathname)
1037 1037 except OSError, why:
1038 1038 if why.errno == errno.EEXIST:
1039 1039 raise
1040 1040 else:
1041 1041 _makelock_file(info, pathname)
1042 1042
1043 1043 def readlock(pathname):
1044 1044 try:
1045 1045 return os.readlink(pathname)
1046 1046 except OSError, why:
1047 1047 if why.errno == errno.EINVAL:
1048 1048 return _readlock_file(pathname)
1049 1049 else:
1050 1050 raise
1051 1051
1052 1052 def shellquote(s):
1053 1053 return "'%s'" % s.replace("'", "'\\''")
1054 1054
1055 1055 def testpid(pid):
1056 1056 '''return False if pid dead, True if running or not sure'''
1057 1057 try:
1058 1058 os.kill(pid, 0)
1059 1059 return True
1060 1060 except OSError, inst:
1061 1061 return inst.errno != errno.ESRCH
1062 1062
1063 1063 def explain_exit(code):
1064 1064 """return a 2-tuple (desc, code) describing a process's status"""
1065 1065 if os.WIFEXITED(code):
1066 1066 val = os.WEXITSTATUS(code)
1067 1067 return _("exited with status %d") % val, val
1068 1068 elif os.WIFSIGNALED(code):
1069 1069 val = os.WTERMSIG(code)
1070 1070 return _("killed by signal %d") % val, val
1071 1071 elif os.WIFSTOPPED(code):
1072 1072 val = os.WSTOPSIG(code)
1073 1073 return _("stopped by signal %d") % val, val
1074 1074 raise ValueError(_("invalid exit code"))
1075 1075
1076 1076 def isowner(fp, st=None):
1077 1077 """Return True if the file object f belongs to the current user.
1078 1078
1079 1079 The return value of a util.fstat(f) may be passed as the st argument.
1080 1080 """
1081 1081 if st is None:
1082 1082 st = fstat(fp)
1083 1083 return st.st_uid == os.getuid()
1084 1084
1085 1085 def find_in_path(name, path, default=None):
1086 1086 '''find name in search path. path can be string (will be split
1087 1087 with os.pathsep), or iterable thing that returns strings. if name
1088 1088 found, return path to name. else return default.'''
1089 1089 if isinstance(path, str):
1090 1090 path = path.split(os.pathsep)
1091 1091 for p in path:
1092 1092 p_name = os.path.join(p, name)
1093 1093 if os.path.exists(p_name):
1094 1094 return p_name
1095 1095 return default
1096 1096
1097 1097 def find_exe(name, default=None):
1098 1098 '''find path of an executable.
1099 1099 if name contains a path component, return it as is. otherwise,
1100 1100 use normal executable search path.'''
1101 1101
1102 1102 if os.sep in name:
1103 1103 # don't check the executable bit. if the file isn't
1104 1104 # executable, whoever tries to actually run it will give a
1105 1105 # much more useful error message.
1106 1106 return name
1107 1107 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1108 1108
1109 1109 def _buildencodefun():
1110 1110 e = '_'
1111 1111 win_reserved = [ord(x) for x in '\\:*?"<>|']
1112 1112 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1113 1113 for x in (range(32) + range(126, 256) + win_reserved):
1114 1114 cmap[chr(x)] = "~%02x" % x
1115 1115 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1116 1116 cmap[chr(x)] = e + chr(x).lower()
1117 1117 dmap = {}
1118 1118 for k, v in cmap.iteritems():
1119 1119 dmap[v] = k
1120 1120 def decode(s):
1121 1121 i = 0
1122 1122 while i < len(s):
1123 1123 for l in xrange(1, 4):
1124 1124 try:
1125 1125 yield dmap[s[i:i+l]]
1126 1126 i += l
1127 1127 break
1128 1128 except KeyError:
1129 1129 pass
1130 1130 else:
1131 1131 raise KeyError
1132 1132 return (lambda s: "".join([cmap[c] for c in s]),
1133 1133 lambda s: "".join(list(decode(s))))
1134 1134
1135 1135 encodefilename, decodefilename = _buildencodefun()
1136 1136
1137 1137 def encodedopener(openerfn, fn):
1138 1138 def o(path, *args, **kw):
1139 1139 return openerfn(fn(path), *args, **kw)
1140 1140 return o
1141 1141
1142 1142 def opener(base, audit=True):
1143 1143 """
1144 1144 return a function that opens files relative to base
1145 1145
1146 1146 this function is used to hide the details of COW semantics and
1147 1147 remote file access from higher level code.
1148 1148 """
1149 1149 p = base
1150 1150 audit_p = audit
1151 1151
1152 1152 def mktempcopy(name, emptyok=False):
1153 1153 d, fn = os.path.split(name)
1154 1154 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1155 1155 os.close(fd)
1156 1156 # Temporary files are created with mode 0600, which is usually not
1157 1157 # what we want. If the original file already exists, just copy
1158 1158 # its mode. Otherwise, manually obey umask.
1159 1159 try:
1160 1160 st_mode = os.lstat(name).st_mode
1161 1161 except OSError, inst:
1162 1162 if inst.errno != errno.ENOENT:
1163 1163 raise
1164 1164 st_mode = 0666 & ~_umask
1165 1165 os.chmod(temp, st_mode)
1166 1166 if emptyok:
1167 1167 return temp
1168 1168 try:
1169 1169 try:
1170 1170 ifp = posixfile(name, "rb")
1171 1171 except IOError, inst:
1172 1172 if inst.errno == errno.ENOENT:
1173 1173 return temp
1174 1174 if not getattr(inst, 'filename', None):
1175 1175 inst.filename = name
1176 1176 raise
1177 1177 ofp = posixfile(temp, "wb")
1178 1178 for chunk in filechunkiter(ifp):
1179 1179 ofp.write(chunk)
1180 1180 ifp.close()
1181 1181 ofp.close()
1182 1182 except:
1183 1183 try: os.unlink(temp)
1184 1184 except: pass
1185 1185 raise
1186 1186 return temp
1187 1187
1188 1188 class atomictempfile(posixfile):
1189 1189 """the file will only be copied when rename is called"""
1190 1190 def __init__(self, name, mode):
1191 1191 self.__name = name
1192 1192 self.temp = mktempcopy(name, emptyok=('w' in mode))
1193 1193 posixfile.__init__(self, self.temp, mode)
1194 1194 def rename(self):
1195 1195 if not self.closed:
1196 1196 posixfile.close(self)
1197 1197 rename(self.temp, localpath(self.__name))
1198 1198 def __del__(self):
1199 1199 if not self.closed:
1200 1200 try:
1201 1201 os.unlink(self.temp)
1202 1202 except: pass
1203 1203 posixfile.close(self)
1204 1204
1205 1205 def o(path, mode="r", text=False, atomictemp=False):
1206 1206 if audit_p:
1207 1207 audit_path(path)
1208 1208 f = os.path.join(p, path)
1209 1209
1210 1210 if not text:
1211 1211 mode += "b" # for that other OS
1212 1212
1213 1213 if mode[0] != "r":
1214 1214 try:
1215 1215 nlink = nlinks(f)
1216 1216 except OSError:
1217 1217 nlink = 0
1218 1218 d = os.path.dirname(f)
1219 1219 if not os.path.isdir(d):
1220 1220 os.makedirs(d)
1221 1221 if atomictemp:
1222 1222 return atomictempfile(f, mode)
1223 1223 if nlink > 1:
1224 1224 rename(mktempcopy(f), f)
1225 1225 return posixfile(f, mode)
1226 1226
1227 1227 return o
1228 1228
1229 1229 class chunkbuffer(object):
1230 1230 """Allow arbitrary sized chunks of data to be efficiently read from an
1231 1231 iterator over chunks of arbitrary size."""
1232 1232
1233 1233 def __init__(self, in_iter, targetsize = 2**16):
1234 1234 """in_iter is the iterator that's iterating over the input chunks.
1235 1235 targetsize is how big a buffer to try to maintain."""
1236 1236 self.in_iter = iter(in_iter)
1237 1237 self.buf = ''
1238 1238 self.targetsize = int(targetsize)
1239 1239 if self.targetsize <= 0:
1240 1240 raise ValueError(_("targetsize must be greater than 0, was %d") %
1241 1241 targetsize)
1242 1242 self.iterempty = False
1243 1243
1244 1244 def fillbuf(self):
1245 1245 """Ignore target size; read every chunk from iterator until empty."""
1246 1246 if not self.iterempty:
1247 1247 collector = cStringIO.StringIO()
1248 1248 collector.write(self.buf)
1249 1249 for ch in self.in_iter:
1250 1250 collector.write(ch)
1251 1251 self.buf = collector.getvalue()
1252 1252 self.iterempty = True
1253 1253
1254 1254 def read(self, l):
1255 1255 """Read L bytes of data from the iterator of chunks of data.
1256 1256 Returns less than L bytes if the iterator runs dry."""
1257 1257 if l > len(self.buf) and not self.iterempty:
1258 1258 # Clamp to a multiple of self.targetsize
1259 1259 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1260 1260 collector = cStringIO.StringIO()
1261 1261 collector.write(self.buf)
1262 1262 collected = len(self.buf)
1263 1263 for chunk in self.in_iter:
1264 1264 collector.write(chunk)
1265 1265 collected += len(chunk)
1266 1266 if collected >= targetsize:
1267 1267 break
1268 1268 if collected < targetsize:
1269 1269 self.iterempty = True
1270 1270 self.buf = collector.getvalue()
1271 1271 s, self.buf = self.buf[:l], buffer(self.buf, l)
1272 1272 return s
1273 1273
1274 1274 def filechunkiter(f, size=65536, limit=None):
1275 1275 """Create a generator that produces the data in the file size
1276 1276 (default 65536) bytes at a time, up to optional limit (default is
1277 1277 to read all data). Chunks may be less than size bytes if the
1278 1278 chunk is the last chunk in the file, or the file is a socket or
1279 1279 some other type of file that sometimes reads less data than is
1280 1280 requested."""
1281 1281 assert size >= 0
1282 1282 assert limit is None or limit >= 0
1283 1283 while True:
1284 1284 if limit is None: nbytes = size
1285 1285 else: nbytes = min(limit, size)
1286 1286 s = nbytes and f.read(nbytes)
1287 1287 if not s: break
1288 1288 if limit: limit -= len(s)
1289 1289 yield s
1290 1290
1291 1291 def makedate():
1292 1292 lt = time.localtime()
1293 1293 if lt[8] == 1 and time.daylight:
1294 1294 tz = time.altzone
1295 1295 else:
1296 1296 tz = time.timezone
1297 1297 return time.mktime(lt), tz
1298 1298
1299 1299 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True):
1300 1300 """represent a (unixtime, offset) tuple as a localized time.
1301 1301 unixtime is seconds since the epoch, and offset is the time zone's
1302 1302 number of seconds away from UTC. if timezone is false, do not
1303 1303 append time zone to string."""
1304 1304 t, tz = date or makedate()
1305 1305 s = time.strftime(format, time.gmtime(float(t) - tz))
1306 1306 if timezone:
1307 1307 s += " %+03d%02d" % (-tz / 3600, ((-tz % 3600) / 60))
1308 1308 return s
1309 1309
1310 1310 def strdate(string, format, defaults):
1311 1311 """parse a localized time string and return a (unixtime, offset) tuple.
1312 1312 if the string cannot be parsed, ValueError is raised."""
1313 1313 def timezone(string):
1314 1314 tz = string.split()[-1]
1315 1315 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1316 1316 tz = int(tz)
1317 1317 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1318 1318 return offset
1319 1319 if tz == "GMT" or tz == "UTC":
1320 1320 return 0
1321 1321 return None
1322 1322
1323 1323 # NOTE: unixtime = localunixtime + offset
1324 1324 offset, date = timezone(string), string
1325 1325 if offset != None:
1326 1326 date = " ".join(string.split()[:-1])
1327 1327
1328 1328 # add missing elements from defaults
1329 1329 for part in defaults:
1330 1330 found = [True for p in part if ("%"+p) in format]
1331 1331 if not found:
1332 1332 date += "@" + defaults[part]
1333 1333 format += "@%" + part[0]
1334 1334
1335 1335 timetuple = time.strptime(date, format)
1336 1336 localunixtime = int(calendar.timegm(timetuple))
1337 1337 if offset is None:
1338 1338 # local timezone
1339 1339 unixtime = int(time.mktime(timetuple))
1340 1340 offset = unixtime - localunixtime
1341 1341 else:
1342 1342 unixtime = localunixtime + offset
1343 1343 return unixtime, offset
1344 1344
1345 1345 def parsedate(string, formats=None, defaults=None):
1346 1346 """parse a localized time string and return a (unixtime, offset) tuple.
1347 1347 The date may be a "unixtime offset" string or in one of the specified
1348 1348 formats."""
1349 1349 if not string:
1350 1350 return 0, 0
1351 1351 if not formats:
1352 1352 formats = defaultdateformats
1353 1353 string = string.strip()
1354 1354 try:
1355 1355 when, offset = map(int, string.split(' '))
1356 1356 except ValueError:
1357 1357 # fill out defaults
1358 1358 if not defaults:
1359 1359 defaults = {}
1360 1360 now = makedate()
1361 1361 for part in "d mb yY HI M S".split():
1362 1362 if part not in defaults:
1363 1363 if part[0] in "HMS":
1364 1364 defaults[part] = "00"
1365 1365 elif part[0] in "dm":
1366 1366 defaults[part] = "1"
1367 1367 else:
1368 1368 defaults[part] = datestr(now, "%" + part[0], False)
1369 1369
1370 1370 for format in formats:
1371 1371 try:
1372 1372 when, offset = strdate(string, format, defaults)
1373 1373 except ValueError:
1374 1374 pass
1375 1375 else:
1376 1376 break
1377 1377 else:
1378 1378 raise Abort(_('invalid date: %r ') % string)
1379 1379 # validate explicit (probably user-specified) date and
1380 1380 # time zone offset. values must fit in signed 32 bits for
1381 1381 # current 32-bit linux runtimes. timezones go from UTC-12
1382 1382 # to UTC+14
1383 1383 if abs(when) > 0x7fffffff:
1384 1384 raise Abort(_('date exceeds 32 bits: %d') % when)
1385 1385 if offset < -50400 or offset > 43200:
1386 1386 raise Abort(_('impossible time zone offset: %d') % offset)
1387 1387 return when, offset
1388 1388
1389 1389 def matchdate(date):
1390 1390 """Return a function that matches a given date match specifier
1391 1391
1392 1392 Formats include:
1393 1393
1394 1394 '{date}' match a given date to the accuracy provided
1395 1395
1396 1396 '<{date}' on or before a given date
1397 1397
1398 1398 '>{date}' on or after a given date
1399 1399
1400 1400 """
1401 1401
1402 1402 def lower(date):
1403 1403 return parsedate(date, extendeddateformats)[0]
1404 1404
1405 1405 def upper(date):
1406 1406 d = dict(mb="12", HI="23", M="59", S="59")
1407 1407 for days in "31 30 29".split():
1408 1408 try:
1409 1409 d["d"] = days
1410 1410 return parsedate(date, extendeddateformats, d)[0]
1411 1411 except:
1412 1412 pass
1413 1413 d["d"] = "28"
1414 1414 return parsedate(date, extendeddateformats, d)[0]
1415 1415
1416 1416 if date[0] == "<":
1417 1417 when = upper(date[1:])
1418 1418 return lambda x: x <= when
1419 1419 elif date[0] == ">":
1420 1420 when = lower(date[1:])
1421 1421 return lambda x: x >= when
1422 1422 elif date[0] == "-":
1423 1423 try:
1424 1424 days = int(date[1:])
1425 1425 except ValueError:
1426 1426 raise Abort(_("invalid day spec: %s") % date[1:])
1427 1427 when = makedate()[0] - days * 3600 * 24
1428 1428 return lambda x: x >= when
1429 1429 elif " to " in date:
1430 1430 a, b = date.split(" to ")
1431 1431 start, stop = lower(a), upper(b)
1432 1432 return lambda x: x >= start and x <= stop
1433 1433 else:
1434 1434 start, stop = lower(date), upper(date)
1435 1435 return lambda x: x >= start and x <= stop
1436 1436
1437 1437 def shortuser(user):
1438 1438 """Return a short representation of a user name or email address."""
1439 1439 f = user.find('@')
1440 1440 if f >= 0:
1441 1441 user = user[:f]
1442 1442 f = user.find('<')
1443 1443 if f >= 0:
1444 1444 user = user[f+1:]
1445 1445 f = user.find(' ')
1446 1446 if f >= 0:
1447 1447 user = user[:f]
1448 1448 f = user.find('.')
1449 1449 if f >= 0:
1450 1450 user = user[:f]
1451 1451 return user
1452 1452
1453 1453 def ellipsis(text, maxlength=400):
1454 1454 """Trim string to at most maxlength (default: 400) characters."""
1455 1455 if len(text) <= maxlength:
1456 1456 return text
1457 1457 else:
1458 1458 return "%s..." % (text[:maxlength-3])
1459 1459
1460 1460 def walkrepos(path):
1461 1461 '''yield every hg repository under path, recursively.'''
1462 1462 def errhandler(err):
1463 1463 if err.filename == path:
1464 1464 raise err
1465 1465
1466 1466 for root, dirs, files in os.walk(path, onerror=errhandler):
1467 1467 for d in dirs:
1468 1468 if d == '.hg':
1469 1469 yield root
1470 1470 dirs[:] = []
1471 1471 break
1472 1472
1473 1473 _rcpath = None
1474 1474
1475 1475 def os_rcpath():
1476 1476 '''return default os-specific hgrc search path'''
1477 1477 path = system_rcpath()
1478 1478 path.extend(user_rcpath())
1479 1479 path = [os.path.normpath(f) for f in path]
1480 1480 return path
1481 1481
1482 1482 def rcpath():
1483 1483 '''return hgrc search path. if env var HGRCPATH is set, use it.
1484 1484 for each item in path, if directory, use files ending in .rc,
1485 1485 else use item.
1486 1486 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1487 1487 if no HGRCPATH, use default os-specific path.'''
1488 1488 global _rcpath
1489 1489 if _rcpath is None:
1490 1490 if 'HGRCPATH' in os.environ:
1491 1491 _rcpath = []
1492 1492 for p in os.environ['HGRCPATH'].split(os.pathsep):
1493 1493 if not p: continue
1494 1494 if os.path.isdir(p):
1495 1495 for f in os.listdir(p):
1496 1496 if f.endswith('.rc'):
1497 1497 _rcpath.append(os.path.join(p, f))
1498 1498 else:
1499 1499 _rcpath.append(p)
1500 1500 else:
1501 1501 _rcpath = os_rcpath()
1502 1502 return _rcpath
1503 1503
1504 1504 def bytecount(nbytes):
1505 1505 '''return byte count formatted as readable string, with units'''
1506 1506
1507 1507 units = (
1508 1508 (100, 1<<30, _('%.0f GB')),
1509 1509 (10, 1<<30, _('%.1f GB')),
1510 1510 (1, 1<<30, _('%.2f GB')),
1511 1511 (100, 1<<20, _('%.0f MB')),
1512 1512 (10, 1<<20, _('%.1f MB')),
1513 1513 (1, 1<<20, _('%.2f MB')),
1514 1514 (100, 1<<10, _('%.0f KB')),
1515 1515 (10, 1<<10, _('%.1f KB')),
1516 1516 (1, 1<<10, _('%.2f KB')),
1517 1517 (1, 1, _('%.0f bytes')),
1518 1518 )
1519 1519
1520 1520 for multiplier, divisor, format in units:
1521 1521 if nbytes >= divisor * multiplier:
1522 1522 return format % (nbytes / float(divisor))
1523 1523 return units[-1][2] % nbytes
1524 1524
1525 1525 def drop_scheme(scheme, path):
1526 1526 sc = scheme + ':'
1527 1527 if path.startswith(sc):
1528 1528 path = path[len(sc):]
1529 1529 if path.startswith('//'):
1530 1530 path = path[2:]
1531 1531 return path
General Comments 0
You need to be logged in to leave comments. Login now