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