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