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