##// END OF EJS Templates
py3: make the regular expression bytes to prevent TypeError
Pulkit Goyal -
r31491:492c64af default
parent child Browse files
Show More
@@ -1,922 +1,922
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import atexit
11 11 import difflib
12 12 import errno
13 13 import getopt
14 14 import os
15 15 import pdb
16 16 import re
17 17 import signal
18 18 import sys
19 19 import time
20 20 import traceback
21 21
22 22
23 23 from .i18n import _
24 24
25 25 from . import (
26 26 cmdutil,
27 27 color,
28 28 commands,
29 29 debugcommands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 fileset,
36 36 help,
37 37 hg,
38 38 hook,
39 39 profiling,
40 40 pycompat,
41 41 revset,
42 42 scmutil,
43 43 templatefilters,
44 44 templatekw,
45 45 templater,
46 46 ui as uimod,
47 47 util,
48 48 )
49 49
50 50 class request(object):
51 51 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
52 52 ferr=None):
53 53 self.args = args
54 54 self.ui = ui
55 55 self.repo = repo
56 56
57 57 # input/output/error streams
58 58 self.fin = fin
59 59 self.fout = fout
60 60 self.ferr = ferr
61 61
62 62 def run():
63 63 "run the command in sys.argv"
64 64 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
65 65
66 66 def _getsimilar(symbols, value):
67 67 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
68 68 # The cutoff for similarity here is pretty arbitrary. It should
69 69 # probably be investigated and tweaked.
70 70 return [s for s in symbols if sim(s) > 0.6]
71 71
72 72 def _reportsimilar(write, similar):
73 73 if len(similar) == 1:
74 74 write(_("(did you mean %s?)\n") % similar[0])
75 75 elif similar:
76 76 ss = ", ".join(sorted(similar))
77 77 write(_("(did you mean one of %s?)\n") % ss)
78 78
79 79 def _formatparse(write, inst):
80 80 similar = []
81 81 if isinstance(inst, error.UnknownIdentifier):
82 82 # make sure to check fileset first, as revset can invoke fileset
83 83 similar = _getsimilar(inst.symbols, inst.function)
84 84 if len(inst.args) > 1:
85 85 write(_("hg: parse error at %s: %s\n") %
86 86 (inst.args[1], inst.args[0]))
87 87 if (inst.args[0][0] == ' '):
88 88 write(_("unexpected leading whitespace\n"))
89 89 else:
90 90 write(_("hg: parse error: %s\n") % inst.args[0])
91 91 _reportsimilar(write, similar)
92 92 if inst.hint:
93 93 write(_("(%s)\n") % inst.hint)
94 94
95 95 def dispatch(req):
96 96 "run the command specified in req.args"
97 97 if req.ferr:
98 98 ferr = req.ferr
99 99 elif req.ui:
100 100 ferr = req.ui.ferr
101 101 else:
102 102 ferr = util.stderr
103 103
104 104 try:
105 105 if not req.ui:
106 106 req.ui = uimod.ui.load()
107 107 if '--traceback' in req.args:
108 108 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
109 109
110 110 # set ui streams from the request
111 111 if req.fin:
112 112 req.ui.fin = req.fin
113 113 if req.fout:
114 114 req.ui.fout = req.fout
115 115 if req.ferr:
116 116 req.ui.ferr = req.ferr
117 117 except error.Abort as inst:
118 118 ferr.write(_("abort: %s\n") % inst)
119 119 if inst.hint:
120 120 ferr.write(_("(%s)\n") % inst.hint)
121 121 return -1
122 122 except error.ParseError as inst:
123 123 _formatparse(ferr.write, inst)
124 124 return -1
125 125
126 126 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
127 127 starttime = util.timer()
128 128 ret = None
129 129 try:
130 130 ret = _runcatch(req)
131 131 except KeyboardInterrupt:
132 132 try:
133 133 req.ui.warn(_("interrupted!\n"))
134 134 except IOError as inst:
135 135 if inst.errno != errno.EPIPE:
136 136 raise
137 137 ret = -1
138 138 finally:
139 139 duration = util.timer() - starttime
140 140 req.ui.flush()
141 141 if req.ui.logblockedtimes:
142 142 req.ui._blockedtimes['command_duration'] = duration * 1000
143 143 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
144 144 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
145 145 msg, ret or 0, duration)
146 146 return ret
147 147
148 148 def _runcatch(req):
149 149 def catchterm(*args):
150 150 raise error.SignalInterrupt
151 151
152 152 ui = req.ui
153 153 try:
154 154 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
155 155 num = getattr(signal, name, None)
156 156 if num:
157 157 signal.signal(num, catchterm)
158 158 except ValueError:
159 159 pass # happens if called in a thread
160 160
161 161 def _runcatchfunc():
162 162 try:
163 163 debugger = 'pdb'
164 164 debugtrace = {
165 165 'pdb' : pdb.set_trace
166 166 }
167 167 debugmortem = {
168 168 'pdb' : pdb.post_mortem
169 169 }
170 170
171 171 # read --config before doing anything else
172 172 # (e.g. to change trust settings for reading .hg/hgrc)
173 173 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
174 174
175 175 if req.repo:
176 176 # copy configs that were passed on the cmdline (--config) to
177 177 # the repo ui
178 178 for sec, name, val in cfgs:
179 179 req.repo.ui.setconfig(sec, name, val, source='--config')
180 180
181 181 # developer config: ui.debugger
182 182 debugger = ui.config("ui", "debugger")
183 183 debugmod = pdb
184 184 if not debugger or ui.plain():
185 185 # if we are in HGPLAIN mode, then disable custom debugging
186 186 debugger = 'pdb'
187 187 elif '--debugger' in req.args:
188 188 # This import can be slow for fancy debuggers, so only
189 189 # do it when absolutely necessary, i.e. when actual
190 190 # debugging has been requested
191 191 with demandimport.deactivated():
192 192 try:
193 193 debugmod = __import__(debugger)
194 194 except ImportError:
195 195 pass # Leave debugmod = pdb
196 196
197 197 debugtrace[debugger] = debugmod.set_trace
198 198 debugmortem[debugger] = debugmod.post_mortem
199 199
200 200 # enter the debugger before command execution
201 201 if '--debugger' in req.args:
202 202 ui.warn(_("entering debugger - "
203 203 "type c to continue starting hg or h for help\n"))
204 204
205 205 if (debugger != 'pdb' and
206 206 debugtrace[debugger] == debugtrace['pdb']):
207 207 ui.warn(_("%s debugger specified "
208 208 "but its module was not found\n") % debugger)
209 209 with demandimport.deactivated():
210 210 debugtrace[debugger]()
211 211 try:
212 212 return _dispatch(req)
213 213 finally:
214 214 ui.flush()
215 215 except: # re-raises
216 216 # enter the debugger when we hit an exception
217 217 if '--debugger' in req.args:
218 218 traceback.print_exc()
219 219 debugmortem[debugger](sys.exc_info()[2])
220 220 ui.traceback()
221 221 raise
222 222
223 223 return callcatch(ui, _runcatchfunc)
224 224
225 225 def callcatch(ui, func):
226 226 """like scmutil.callcatch but handles more high-level exceptions about
227 227 config parsing and commands. besides, use handlecommandexception to handle
228 228 uncaught exceptions.
229 229 """
230 230 try:
231 231 return scmutil.callcatch(ui, func)
232 232 except error.AmbiguousCommand as inst:
233 233 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
234 234 (inst.args[0], " ".join(inst.args[1])))
235 235 except error.CommandError as inst:
236 236 if inst.args[0]:
237 237 ui.pager('help')
238 238 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
239 239 commands.help_(ui, inst.args[0], full=False, command=True)
240 240 else:
241 241 ui.pager('help')
242 242 ui.warn(_("hg: %s\n") % inst.args[1])
243 243 commands.help_(ui, 'shortlist')
244 244 except error.ParseError as inst:
245 245 _formatparse(ui.warn, inst)
246 246 return -1
247 247 except error.UnknownCommand as inst:
248 248 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
249 249 try:
250 250 # check if the command is in a disabled extension
251 251 # (but don't check for extensions themselves)
252 252 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
253 253 ui.warn(nocmdmsg)
254 254 ui.write(formatted)
255 255 except (error.UnknownCommand, error.Abort):
256 256 suggested = False
257 257 if len(inst.args) == 2:
258 258 sim = _getsimilar(inst.args[1], inst.args[0])
259 259 if sim:
260 260 ui.warn(nocmdmsg)
261 261 _reportsimilar(ui.warn, sim)
262 262 suggested = True
263 263 if not suggested:
264 264 ui.pager('help')
265 265 ui.warn(nocmdmsg)
266 266 commands.help_(ui, 'shortlist')
267 267 except IOError:
268 268 raise
269 269 except KeyboardInterrupt:
270 270 raise
271 271 except: # probably re-raises
272 272 if not handlecommandexception(ui):
273 273 raise
274 274
275 275 return -1
276 276
277 277 def aliasargs(fn, givenargs):
278 278 args = getattr(fn, 'args', [])
279 279 if args:
280 280 cmd = ' '.join(map(util.shellquote, args))
281 281
282 282 nums = []
283 283 def replacer(m):
284 284 num = int(m.group(1)) - 1
285 285 nums.append(num)
286 286 if num < len(givenargs):
287 287 return givenargs[num]
288 288 raise error.Abort(_('too few arguments for command alias'))
289 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
289 cmd = re.sub(br'\$(\d+|\$)', replacer, cmd)
290 290 givenargs = [x for i, x in enumerate(givenargs)
291 291 if i not in nums]
292 292 args = pycompat.shlexsplit(cmd)
293 293 return args + givenargs
294 294
295 295 def aliasinterpolate(name, args, cmd):
296 296 '''interpolate args into cmd for shell aliases
297 297
298 298 This also handles $0, $@ and "$@".
299 299 '''
300 300 # util.interpolate can't deal with "$@" (with quotes) because it's only
301 301 # built to match prefix + patterns.
302 302 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
303 303 replacemap['$0'] = name
304 304 replacemap['$$'] = '$'
305 305 replacemap['$@'] = ' '.join(args)
306 306 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
307 307 # parameters, separated out into words. Emulate the same behavior here by
308 308 # quoting the arguments individually. POSIX shells will then typically
309 309 # tokenize each argument into exactly one word.
310 310 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
311 311 # escape '\$' for regex
312 312 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
313 313 r = re.compile(regex)
314 314 return r.sub(lambda x: replacemap[x.group()], cmd)
315 315
316 316 class cmdalias(object):
317 317 def __init__(self, name, definition, cmdtable, source):
318 318 self.name = self.cmd = name
319 319 self.cmdname = ''
320 320 self.definition = definition
321 321 self.fn = None
322 322 self.givenargs = []
323 323 self.opts = []
324 324 self.help = ''
325 325 self.badalias = None
326 326 self.unknowncmd = False
327 327 self.source = source
328 328
329 329 try:
330 330 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
331 331 for alias, e in cmdtable.iteritems():
332 332 if e is entry:
333 333 self.cmd = alias
334 334 break
335 335 self.shadows = True
336 336 except error.UnknownCommand:
337 337 self.shadows = False
338 338
339 339 if not self.definition:
340 340 self.badalias = _("no definition for alias '%s'") % self.name
341 341 return
342 342
343 343 if self.definition.startswith('!'):
344 344 self.shell = True
345 345 def fn(ui, *args):
346 346 env = {'HG_ARGS': ' '.join((self.name,) + args)}
347 347 def _checkvar(m):
348 348 if m.groups()[0] == '$':
349 349 return m.group()
350 350 elif int(m.groups()[0]) <= len(args):
351 351 return m.group()
352 352 else:
353 353 ui.debug("No argument found for substitution "
354 354 "of %i variable in alias '%s' definition."
355 355 % (int(m.groups()[0]), self.name))
356 356 return ''
357 357 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
358 358 cmd = aliasinterpolate(self.name, args, cmd)
359 359 return ui.system(cmd, environ=env,
360 360 blockedtag='alias_%s' % self.name)
361 361 self.fn = fn
362 362 return
363 363
364 364 try:
365 365 args = pycompat.shlexsplit(self.definition)
366 366 except ValueError as inst:
367 367 self.badalias = (_("error in definition for alias '%s': %s")
368 368 % (self.name, inst))
369 369 return
370 370 self.cmdname = cmd = args.pop(0)
371 371 self.givenargs = args
372 372
373 373 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
374 374 if _earlygetopt([invalidarg], args):
375 375 self.badalias = (_("error in definition for alias '%s': %s may "
376 376 "only be given on the command line")
377 377 % (self.name, invalidarg))
378 378 return
379 379
380 380 try:
381 381 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
382 382 if len(tableentry) > 2:
383 383 self.fn, self.opts, self.help = tableentry
384 384 else:
385 385 self.fn, self.opts = tableentry
386 386
387 387 if self.help.startswith("hg " + cmd):
388 388 # drop prefix in old-style help lines so hg shows the alias
389 389 self.help = self.help[4 + len(cmd):]
390 390 self.__doc__ = self.fn.__doc__
391 391
392 392 except error.UnknownCommand:
393 393 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
394 394 % (self.name, cmd))
395 395 self.unknowncmd = True
396 396 except error.AmbiguousCommand:
397 397 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
398 398 % (self.name, cmd))
399 399
400 400 @property
401 401 def args(self):
402 402 args = map(util.expandpath, self.givenargs)
403 403 return aliasargs(self.fn, args)
404 404
405 405 def __getattr__(self, name):
406 406 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
407 407 if name not in adefaults:
408 408 raise AttributeError(name)
409 409 if self.badalias or util.safehasattr(self, 'shell'):
410 410 return adefaults[name]
411 411 return getattr(self.fn, name)
412 412
413 413 def __call__(self, ui, *args, **opts):
414 414 if self.badalias:
415 415 hint = None
416 416 if self.unknowncmd:
417 417 try:
418 418 # check if the command is in a disabled extension
419 419 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
420 420 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
421 421 except error.UnknownCommand:
422 422 pass
423 423 raise error.Abort(self.badalias, hint=hint)
424 424 if self.shadows:
425 425 ui.debug("alias '%s' shadows command '%s'\n" %
426 426 (self.name, self.cmdname))
427 427
428 428 ui.log('commandalias', "alias '%s' expands to '%s'\n",
429 429 self.name, self.definition)
430 430 if util.safehasattr(self, 'shell'):
431 431 return self.fn(ui, *args, **opts)
432 432 else:
433 433 try:
434 434 return util.checksignature(self.fn)(ui, *args, **opts)
435 435 except error.SignatureError:
436 436 args = ' '.join([self.cmdname] + self.args)
437 437 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
438 438 raise
439 439
440 440 def addaliases(ui, cmdtable):
441 441 # aliases are processed after extensions have been loaded, so they
442 442 # may use extension commands. Aliases can also use other alias definitions,
443 443 # but only if they have been defined prior to the current definition.
444 444 for alias, definition in ui.configitems('alias'):
445 445 source = ui.configsource('alias', alias)
446 446 aliasdef = cmdalias(alias, definition, cmdtable, source)
447 447
448 448 try:
449 449 olddef = cmdtable[aliasdef.cmd][0]
450 450 if olddef.definition == aliasdef.definition:
451 451 continue
452 452 except (KeyError, AttributeError):
453 453 # definition might not exist or it might not be a cmdalias
454 454 pass
455 455
456 456 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
457 457
458 458 def _parse(ui, args):
459 459 options = {}
460 460 cmdoptions = {}
461 461
462 462 try:
463 463 args = fancyopts.fancyopts(args, commands.globalopts, options)
464 464 except getopt.GetoptError as inst:
465 465 raise error.CommandError(None, inst)
466 466
467 467 if args:
468 468 cmd, args = args[0], args[1:]
469 469 aliases, entry = cmdutil.findcmd(cmd, commands.table,
470 470 ui.configbool("ui", "strict"))
471 471 cmd = aliases[0]
472 472 args = aliasargs(entry[0], args)
473 473 defaults = ui.config("defaults", cmd)
474 474 if defaults:
475 475 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
476 476 c = list(entry[1])
477 477 else:
478 478 cmd = None
479 479 c = []
480 480
481 481 # combine global options into local
482 482 for o in commands.globalopts:
483 483 c.append((o[0], o[1], options[o[1]], o[3]))
484 484
485 485 try:
486 486 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
487 487 except getopt.GetoptError as inst:
488 488 raise error.CommandError(cmd, inst)
489 489
490 490 # separate global options back out
491 491 for o in commands.globalopts:
492 492 n = o[1]
493 493 options[n] = cmdoptions[n]
494 494 del cmdoptions[n]
495 495
496 496 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
497 497
498 498 def _parseconfig(ui, config):
499 499 """parse the --config options from the command line"""
500 500 configs = []
501 501
502 502 for cfg in config:
503 503 try:
504 504 name, value = [cfgelem.strip()
505 505 for cfgelem in cfg.split('=', 1)]
506 506 section, name = name.split('.', 1)
507 507 if not section or not name:
508 508 raise IndexError
509 509 ui.setconfig(section, name, value, '--config')
510 510 configs.append((section, name, value))
511 511 except (IndexError, ValueError):
512 512 raise error.Abort(_('malformed --config option: %r '
513 513 '(use --config section.name=value)') % cfg)
514 514
515 515 return configs
516 516
517 517 def _earlygetopt(aliases, args):
518 518 """Return list of values for an option (or aliases).
519 519
520 520 The values are listed in the order they appear in args.
521 521 The options and values are removed from args.
522 522
523 523 >>> args = ['x', '--cwd', 'foo', 'y']
524 524 >>> _earlygetopt(['--cwd'], args), args
525 525 (['foo'], ['x', 'y'])
526 526
527 527 >>> args = ['x', '--cwd=bar', 'y']
528 528 >>> _earlygetopt(['--cwd'], args), args
529 529 (['bar'], ['x', 'y'])
530 530
531 531 >>> args = ['x', '-R', 'foo', 'y']
532 532 >>> _earlygetopt(['-R'], args), args
533 533 (['foo'], ['x', 'y'])
534 534
535 535 >>> args = ['x', '-Rbar', 'y']
536 536 >>> _earlygetopt(['-R'], args), args
537 537 (['bar'], ['x', 'y'])
538 538 """
539 539 try:
540 540 argcount = args.index("--")
541 541 except ValueError:
542 542 argcount = len(args)
543 543 shortopts = [opt for opt in aliases if len(opt) == 2]
544 544 values = []
545 545 pos = 0
546 546 while pos < argcount:
547 547 fullarg = arg = args[pos]
548 548 equals = arg.find('=')
549 549 if equals > -1:
550 550 arg = arg[:equals]
551 551 if arg in aliases:
552 552 del args[pos]
553 553 if equals > -1:
554 554 values.append(fullarg[equals + 1:])
555 555 argcount -= 1
556 556 else:
557 557 if pos + 1 >= argcount:
558 558 # ignore and let getopt report an error if there is no value
559 559 break
560 560 values.append(args.pop(pos))
561 561 argcount -= 2
562 562 elif arg[:2] in shortopts:
563 563 # short option can have no following space, e.g. hg log -Rfoo
564 564 values.append(args.pop(pos)[2:])
565 565 argcount -= 1
566 566 else:
567 567 pos += 1
568 568 return values
569 569
570 570 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
571 571 # run pre-hook, and abort if it fails
572 572 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
573 573 pats=cmdpats, opts=cmdoptions)
574 574 try:
575 575 ret = _runcommand(ui, options, cmd, d)
576 576 # run post-hook, passing command result
577 577 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
578 578 result=ret, pats=cmdpats, opts=cmdoptions)
579 579 except Exception:
580 580 # run failure hook and re-raise
581 581 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
582 582 pats=cmdpats, opts=cmdoptions)
583 583 raise
584 584 return ret
585 585
586 586 def _getlocal(ui, rpath, wd=None):
587 587 """Return (path, local ui object) for the given target path.
588 588
589 589 Takes paths in [cwd]/.hg/hgrc into account."
590 590 """
591 591 if wd is None:
592 592 try:
593 593 wd = pycompat.getcwd()
594 594 except OSError as e:
595 595 raise error.Abort(_("error getting current working directory: %s") %
596 596 e.strerror)
597 597 path = cmdutil.findrepo(wd) or ""
598 598 if not path:
599 599 lui = ui
600 600 else:
601 601 lui = ui.copy()
602 602 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
603 603
604 604 if rpath and rpath[-1]:
605 605 path = lui.expandpath(rpath[-1])
606 606 lui = ui.copy()
607 607 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
608 608
609 609 return path, lui
610 610
611 611 def _checkshellalias(lui, ui, args):
612 612 """Return the function to run the shell alias, if it is required"""
613 613 options = {}
614 614
615 615 try:
616 616 args = fancyopts.fancyopts(args, commands.globalopts, options)
617 617 except getopt.GetoptError:
618 618 return
619 619
620 620 if not args:
621 621 return
622 622
623 623 cmdtable = commands.table
624 624
625 625 cmd = args[0]
626 626 try:
627 627 strict = ui.configbool("ui", "strict")
628 628 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
629 629 except (error.AmbiguousCommand, error.UnknownCommand):
630 630 return
631 631
632 632 cmd = aliases[0]
633 633 fn = entry[0]
634 634
635 635 if cmd and util.safehasattr(fn, 'shell'):
636 636 d = lambda: fn(ui, *args[1:])
637 637 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
638 638 [], {})
639 639
640 640 _loaded = set()
641 641
642 642 # list of (objname, loadermod, loadername) tuple:
643 643 # - objname is the name of an object in extension module, from which
644 644 # extra information is loaded
645 645 # - loadermod is the module where loader is placed
646 646 # - loadername is the name of the function, which takes (ui, extensionname,
647 647 # extraobj) arguments
648 648 extraloaders = [
649 649 ('cmdtable', commands, 'loadcmdtable'),
650 650 ('colortable', color, 'loadcolortable'),
651 651 ('filesetpredicate', fileset, 'loadpredicate'),
652 652 ('revsetpredicate', revset, 'loadpredicate'),
653 653 ('templatefilter', templatefilters, 'loadfilter'),
654 654 ('templatefunc', templater, 'loadfunction'),
655 655 ('templatekeyword', templatekw, 'loadkeyword'),
656 656 ]
657 657
658 658 def _dispatch(req):
659 659 args = req.args
660 660 ui = req.ui
661 661
662 662 # check for cwd
663 663 cwd = _earlygetopt(['--cwd'], args)
664 664 if cwd:
665 665 os.chdir(cwd[-1])
666 666
667 667 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
668 668 path, lui = _getlocal(ui, rpath)
669 669
670 670 # Side-effect of accessing is debugcommands module is guaranteed to be
671 671 # imported and commands.table is populated.
672 672 debugcommands.command
673 673
674 674 uis = set([ui, lui])
675 675
676 676 if req.repo:
677 677 uis.add(req.repo.ui)
678 678
679 679 if '--profile' in args:
680 680 for ui_ in uis:
681 681 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
682 682
683 683 with profiling.maybeprofile(lui):
684 684 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
685 685 # reposetup. Programs like TortoiseHg will call _dispatch several
686 686 # times so we keep track of configured extensions in _loaded.
687 687 extensions.loadall(lui)
688 688 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
689 689 # Propagate any changes to lui.__class__ by extensions
690 690 ui.__class__ = lui.__class__
691 691
692 692 # (uisetup and extsetup are handled in extensions.loadall)
693 693
694 694 for name, module in exts:
695 695 for objname, loadermod, loadername in extraloaders:
696 696 extraobj = getattr(module, objname, None)
697 697 if extraobj is not None:
698 698 getattr(loadermod, loadername)(ui, name, extraobj)
699 699 _loaded.add(name)
700 700
701 701 # (reposetup is handled in hg.repository)
702 702
703 703 addaliases(lui, commands.table)
704 704
705 705 # All aliases and commands are completely defined, now.
706 706 # Check abbreviation/ambiguity of shell alias.
707 707 shellaliasfn = _checkshellalias(lui, ui, args)
708 708 if shellaliasfn:
709 709 return shellaliasfn()
710 710
711 711 # check for fallback encoding
712 712 fallback = lui.config('ui', 'fallbackencoding')
713 713 if fallback:
714 714 encoding.fallbackencoding = fallback
715 715
716 716 fullargs = args
717 717 cmd, func, args, options, cmdoptions = _parse(lui, args)
718 718
719 719 if options["config"]:
720 720 raise error.Abort(_("option --config may not be abbreviated!"))
721 721 if options["cwd"]:
722 722 raise error.Abort(_("option --cwd may not be abbreviated!"))
723 723 if options["repository"]:
724 724 raise error.Abort(_(
725 725 "option -R has to be separated from other options (e.g. not "
726 726 "-qR) and --repository may only be abbreviated as --repo!"))
727 727
728 728 if options["encoding"]:
729 729 encoding.encoding = options["encoding"]
730 730 if options["encodingmode"]:
731 731 encoding.encodingmode = options["encodingmode"]
732 732 if options["time"]:
733 733 def get_times():
734 734 t = os.times()
735 735 if t[4] == 0.0:
736 736 # Windows leaves this as zero, so use time.clock()
737 737 t = (t[0], t[1], t[2], t[3], time.clock())
738 738 return t
739 739 s = get_times()
740 740 def print_time():
741 741 t = get_times()
742 742 ui.warn(
743 743 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
744 744 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
745 745 atexit.register(print_time)
746 746
747 747 if options['verbose'] or options['debug'] or options['quiet']:
748 748 for opt in ('verbose', 'debug', 'quiet'):
749 749 val = str(bool(options[opt]))
750 750 if pycompat.ispy3:
751 751 val = val.encode('ascii')
752 752 for ui_ in uis:
753 753 ui_.setconfig('ui', opt, val, '--' + opt)
754 754
755 755 if options['traceback']:
756 756 for ui_ in uis:
757 757 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
758 758
759 759 if options['noninteractive']:
760 760 for ui_ in uis:
761 761 ui_.setconfig('ui', 'interactive', 'off', '-y')
762 762
763 763 if util.parsebool(options['pager']):
764 764 ui.pager('internal-always-' + cmd)
765 765 elif options['pager'] != 'auto':
766 766 ui.disablepager()
767 767
768 768 if cmdoptions.get('insecure', False):
769 769 for ui_ in uis:
770 770 ui_.insecureconnections = True
771 771
772 772 # setup color handling
773 773 coloropt = options['color']
774 774 for ui_ in uis:
775 775 if coloropt:
776 776 ui_.setconfig('ui', 'color', coloropt, '--color')
777 777 color.setup(ui_)
778 778
779 779 if options['version']:
780 780 return commands.version_(ui)
781 781 if options['help']:
782 782 return commands.help_(ui, cmd, command=cmd is not None)
783 783 elif not cmd:
784 784 return commands.help_(ui, 'shortlist')
785 785
786 786 repo = None
787 787 cmdpats = args[:]
788 788 if not func.norepo:
789 789 # use the repo from the request only if we don't have -R
790 790 if not rpath and not cwd:
791 791 repo = req.repo
792 792
793 793 if repo:
794 794 # set the descriptors of the repo ui to those of ui
795 795 repo.ui.fin = ui.fin
796 796 repo.ui.fout = ui.fout
797 797 repo.ui.ferr = ui.ferr
798 798 else:
799 799 try:
800 800 repo = hg.repository(ui, path=path)
801 801 if not repo.local():
802 802 raise error.Abort(_("repository '%s' is not local")
803 803 % path)
804 804 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
805 805 'repo')
806 806 except error.RequirementError:
807 807 raise
808 808 except error.RepoError:
809 809 if rpath and rpath[-1]: # invalid -R path
810 810 raise
811 811 if not func.optionalrepo:
812 812 if func.inferrepo and args and not path:
813 813 # try to infer -R from command args
814 814 repos = map(cmdutil.findrepo, args)
815 815 guess = repos[0]
816 816 if guess and repos.count(guess) == len(repos):
817 817 req.args = ['--repository', guess] + fullargs
818 818 return _dispatch(req)
819 819 if not path:
820 820 raise error.RepoError(_("no repository found in"
821 821 " '%s' (.hg not found)")
822 822 % pycompat.getcwd())
823 823 raise
824 824 if repo:
825 825 ui = repo.ui
826 826 if options['hidden']:
827 827 repo = repo.unfiltered()
828 828 args.insert(0, repo)
829 829 elif rpath:
830 830 ui.warn(_("warning: --repository ignored\n"))
831 831
832 832 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
833 833 ui.log("command", '%s\n', msg)
834 834 strcmdopt = pycompat.strkwargs(cmdoptions)
835 835 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
836 836 try:
837 837 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
838 838 cmdpats, cmdoptions)
839 839 finally:
840 840 if repo and repo != req.repo:
841 841 repo.close()
842 842
843 843 def _runcommand(ui, options, cmd, cmdfunc):
844 844 """Run a command function, possibly with profiling enabled."""
845 845 try:
846 846 return cmdfunc()
847 847 except error.SignatureError:
848 848 raise error.CommandError(cmd, _('invalid arguments'))
849 849
850 850 def _exceptionwarning(ui):
851 851 """Produce a warning message for the current active exception"""
852 852
853 853 # For compatibility checking, we discard the portion of the hg
854 854 # version after the + on the assumption that if a "normal
855 855 # user" is running a build with a + in it the packager
856 856 # probably built from fairly close to a tag and anyone with a
857 857 # 'make local' copy of hg (where the version number can be out
858 858 # of date) will be clueful enough to notice the implausible
859 859 # version number and try updating.
860 860 ct = util.versiontuple(n=2)
861 861 worst = None, ct, ''
862 862 if ui.config('ui', 'supportcontact', None) is None:
863 863 for name, mod in extensions.extensions():
864 864 testedwith = getattr(mod, 'testedwith', '')
865 865 if pycompat.ispy3 and isinstance(testedwith, str):
866 866 testedwith = testedwith.encode(u'utf-8')
867 867 report = getattr(mod, 'buglink', _('the extension author.'))
868 868 if not testedwith.strip():
869 869 # We found an untested extension. It's likely the culprit.
870 870 worst = name, 'unknown', report
871 871 break
872 872
873 873 # Never blame on extensions bundled with Mercurial.
874 874 if extensions.ismoduleinternal(mod):
875 875 continue
876 876
877 877 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
878 878 if ct in tested:
879 879 continue
880 880
881 881 lower = [t for t in tested if t < ct]
882 882 nearest = max(lower or tested)
883 883 if worst[0] is None or nearest < worst[1]:
884 884 worst = name, nearest, report
885 885 if worst[0] is not None:
886 886 name, testedwith, report = worst
887 887 if not isinstance(testedwith, (bytes, str)):
888 888 testedwith = '.'.join([str(c) for c in testedwith])
889 889 warning = (_('** Unknown exception encountered with '
890 890 'possibly-broken third-party extension %s\n'
891 891 '** which supports versions %s of Mercurial.\n'
892 892 '** Please disable %s and try your action again.\n'
893 893 '** If that fixes the bug please report it to %s\n')
894 894 % (name, testedwith, name, report))
895 895 else:
896 896 bugtracker = ui.config('ui', 'supportcontact', None)
897 897 if bugtracker is None:
898 898 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
899 899 warning = (_("** unknown exception encountered, "
900 900 "please report by visiting\n** ") + bugtracker + '\n')
901 901 if pycompat.ispy3:
902 902 sysversion = sys.version.encode(u'utf-8')
903 903 else:
904 904 sysversion = sys.version
905 905 sysversion = sysversion.replace('\n', '')
906 906 warning += ((_("** Python %s\n") % sysversion) +
907 907 (_("** Mercurial Distributed SCM (version %s)\n") %
908 908 util.version()) +
909 909 (_("** Extensions loaded: %s\n") %
910 910 ", ".join([x[0] for x in extensions.extensions()])))
911 911 return warning
912 912
913 913 def handlecommandexception(ui):
914 914 """Produce a warning message for broken commands
915 915
916 916 Called when handling an exception; the exception is reraised if
917 917 this function returns False, ignored otherwise.
918 918 """
919 919 warning = _exceptionwarning(ui)
920 920 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
921 921 ui.warn(warning)
922 922 return False # re-raise the exception
@@ -1,659 +1,659
1 1 # posix.py - Posix utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import errno
11 11 import fcntl
12 12 import getpass
13 13 import grp
14 14 import os
15 15 import pwd
16 16 import re
17 17 import select
18 18 import stat
19 19 import sys
20 20 import tempfile
21 21 import unicodedata
22 22
23 23 from .i18n import _
24 24 from . import (
25 25 encoding,
26 26 pycompat,
27 27 )
28 28
29 29 posixfile = open
30 30 normpath = os.path.normpath
31 31 samestat = os.path.samestat
32 32 try:
33 33 oslink = os.link
34 34 except AttributeError:
35 35 # Some platforms build Python without os.link on systems that are
36 36 # vaguely unix-like but don't have hardlink support. For those
37 37 # poor souls, just say we tried and that it failed so we fall back
38 38 # to copies.
39 39 def oslink(src, dst):
40 40 raise OSError(errno.EINVAL,
41 41 'hardlinks not supported: %s to %s' % (src, dst))
42 42 unlink = os.unlink
43 43 rename = os.rename
44 44 removedirs = os.removedirs
45 45 expandglobs = False
46 46
47 47 umask = os.umask(0)
48 48 os.umask(umask)
49 49
50 50 def split(p):
51 51 '''Same as posixpath.split, but faster
52 52
53 53 >>> import posixpath
54 54 >>> for f in ['/absolute/path/to/file',
55 55 ... 'relative/path/to/file',
56 56 ... 'file_alone',
57 57 ... 'path/to/directory/',
58 58 ... '/multiple/path//separators',
59 59 ... '/file_at_root',
60 60 ... '///multiple_leading_separators_at_root',
61 61 ... '']:
62 62 ... assert split(f) == posixpath.split(f), f
63 63 '''
64 64 ht = p.rsplit('/', 1)
65 65 if len(ht) == 1:
66 66 return '', p
67 67 nh = ht[0].rstrip('/')
68 68 if nh:
69 69 return nh, ht[1]
70 70 return ht[0] + '/', ht[1]
71 71
72 72 def openhardlinks():
73 73 '''return true if it is safe to hold open file handles to hardlinks'''
74 74 return True
75 75
76 76 def nlinks(name):
77 77 '''return number of hardlinks for the given file'''
78 78 return os.lstat(name).st_nlink
79 79
80 80 def parsepatchoutput(output_line):
81 81 """parses the output produced by patch and returns the filename"""
82 82 pf = output_line[14:]
83 83 if pycompat.sysplatform == 'OpenVMS':
84 84 if pf[0] == '`':
85 85 pf = pf[1:-1] # Remove the quotes
86 86 else:
87 87 if pf.startswith("'") and pf.endswith("'") and " " in pf:
88 88 pf = pf[1:-1] # Remove the quotes
89 89 return pf
90 90
91 91 def sshargs(sshcmd, host, user, port):
92 92 '''Build argument list for ssh'''
93 93 args = user and ("%s@%s" % (user, host)) or host
94 94 return port and ("%s -p %s" % (args, port)) or args
95 95
96 96 def isexec(f):
97 97 """check whether a file is executable"""
98 98 return (os.lstat(f).st_mode & 0o100 != 0)
99 99
100 100 def setflags(f, l, x):
101 101 s = os.lstat(f).st_mode
102 102 if l:
103 103 if not stat.S_ISLNK(s):
104 104 # switch file to link
105 105 fp = open(f)
106 106 data = fp.read()
107 107 fp.close()
108 108 os.unlink(f)
109 109 try:
110 110 os.symlink(data, f)
111 111 except OSError:
112 112 # failed to make a link, rewrite file
113 113 fp = open(f, "w")
114 114 fp.write(data)
115 115 fp.close()
116 116 # no chmod needed at this point
117 117 return
118 118 if stat.S_ISLNK(s):
119 119 # switch link to file
120 120 data = os.readlink(f)
121 121 os.unlink(f)
122 122 fp = open(f, "w")
123 123 fp.write(data)
124 124 fp.close()
125 125 s = 0o666 & ~umask # avoid restatting for chmod
126 126
127 127 sx = s & 0o100
128 128 if x and not sx:
129 129 # Turn on +x for every +r bit when making a file executable
130 130 # and obey umask.
131 131 os.chmod(f, s | (s & 0o444) >> 2 & ~umask)
132 132 elif not x and sx:
133 133 # Turn off all +x bits
134 134 os.chmod(f, s & 0o666)
135 135
136 136 def copymode(src, dst, mode=None):
137 137 '''Copy the file mode from the file at path src to dst.
138 138 If src doesn't exist, we're using mode instead. If mode is None, we're
139 139 using umask.'''
140 140 try:
141 141 st_mode = os.lstat(src).st_mode & 0o777
142 142 except OSError as inst:
143 143 if inst.errno != errno.ENOENT:
144 144 raise
145 145 st_mode = mode
146 146 if st_mode is None:
147 147 st_mode = ~umask
148 148 st_mode &= 0o666
149 149 os.chmod(dst, st_mode)
150 150
151 151 def checkexec(path):
152 152 """
153 153 Check whether the given path is on a filesystem with UNIX-like exec flags
154 154
155 155 Requires a directory (like /foo/.hg)
156 156 """
157 157
158 158 # VFAT on some Linux versions can flip mode but it doesn't persist
159 159 # a FS remount. Frequently we can detect it if files are created
160 160 # with exec bit on.
161 161
162 162 try:
163 163 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
164 164 cachedir = os.path.join(path, '.hg', 'cache')
165 165 if os.path.isdir(cachedir):
166 166 checkisexec = os.path.join(cachedir, 'checkisexec')
167 167 checknoexec = os.path.join(cachedir, 'checknoexec')
168 168
169 169 try:
170 170 m = os.stat(checkisexec).st_mode
171 171 except OSError as e:
172 172 if e.errno != errno.ENOENT:
173 173 raise
174 174 # checkisexec does not exist - fall through ...
175 175 else:
176 176 # checkisexec exists, check if it actually is exec
177 177 if m & EXECFLAGS != 0:
178 178 # ensure checkisexec exists, check it isn't exec
179 179 try:
180 180 m = os.stat(checknoexec).st_mode
181 181 except OSError as e:
182 182 if e.errno != errno.ENOENT:
183 183 raise
184 184 file(checknoexec, 'w').close() # might fail
185 185 m = os.stat(checknoexec).st_mode
186 186 if m & EXECFLAGS == 0:
187 187 # check-exec is exec and check-no-exec is not exec
188 188 return True
189 189 # checknoexec exists but is exec - delete it
190 190 os.unlink(checknoexec)
191 191 # checkisexec exists but is not exec - delete it
192 192 os.unlink(checkisexec)
193 193
194 194 # check using one file, leave it as checkisexec
195 195 checkdir = cachedir
196 196 else:
197 197 # check directly in path and don't leave checkisexec behind
198 198 checkdir = path
199 199 checkisexec = None
200 200 fh, fn = tempfile.mkstemp(dir=checkdir, prefix='hg-checkexec-')
201 201 try:
202 202 os.close(fh)
203 203 m = os.stat(fn).st_mode
204 204 if m & EXECFLAGS == 0:
205 205 os.chmod(fn, m & 0o777 | EXECFLAGS)
206 206 if os.stat(fn).st_mode & EXECFLAGS != 0:
207 207 if checkisexec is not None:
208 208 os.rename(fn, checkisexec)
209 209 fn = None
210 210 return True
211 211 finally:
212 212 if fn is not None:
213 213 os.unlink(fn)
214 214 except (IOError, OSError):
215 215 # we don't care, the user probably won't be able to commit anyway
216 216 return False
217 217
218 218 def checklink(path):
219 219 """check whether the given path is on a symlink-capable filesystem"""
220 220 # mktemp is not racy because symlink creation will fail if the
221 221 # file already exists
222 222 while True:
223 223 cachedir = os.path.join(path, '.hg', 'cache')
224 224 checklink = os.path.join(cachedir, 'checklink')
225 225 # try fast path, read only
226 226 if os.path.islink(checklink):
227 227 return True
228 228 if os.path.isdir(cachedir):
229 229 checkdir = cachedir
230 230 else:
231 231 checkdir = path
232 232 cachedir = None
233 233 name = tempfile.mktemp(dir=checkdir, prefix='checklink-')
234 234 try:
235 235 fd = None
236 236 if cachedir is None:
237 237 fd = tempfile.NamedTemporaryFile(dir=checkdir,
238 238 prefix='hg-checklink-')
239 239 target = os.path.basename(fd.name)
240 240 else:
241 241 # create a fixed file to link to; doesn't matter if it
242 242 # already exists.
243 243 target = 'checklink-target'
244 244 open(os.path.join(cachedir, target), 'w').close()
245 245 try:
246 246 os.symlink(target, name)
247 247 if cachedir is None:
248 248 os.unlink(name)
249 249 else:
250 250 try:
251 251 os.rename(name, checklink)
252 252 except OSError:
253 253 os.unlink(name)
254 254 return True
255 255 except OSError as inst:
256 256 # link creation might race, try again
257 257 if inst[0] == errno.EEXIST:
258 258 continue
259 259 raise
260 260 finally:
261 261 if fd is not None:
262 262 fd.close()
263 263 except AttributeError:
264 264 return False
265 265 except OSError as inst:
266 266 # sshfs might report failure while successfully creating the link
267 267 if inst[0] == errno.EIO and os.path.exists(name):
268 268 os.unlink(name)
269 269 return False
270 270
271 271 def checkosfilename(path):
272 272 '''Check that the base-relative path is a valid filename on this platform.
273 273 Returns None if the path is ok, or a UI string describing the problem.'''
274 274 pass # on posix platforms, every path is ok
275 275
276 276 def setbinary(fd):
277 277 pass
278 278
279 279 def pconvert(path):
280 280 return path
281 281
282 282 def localpath(path):
283 283 return path
284 284
285 285 def samefile(fpath1, fpath2):
286 286 """Returns whether path1 and path2 refer to the same file. This is only
287 287 guaranteed to work for files, not directories."""
288 288 return os.path.samefile(fpath1, fpath2)
289 289
290 290 def samedevice(fpath1, fpath2):
291 291 """Returns whether fpath1 and fpath2 are on the same device. This is only
292 292 guaranteed to work for files, not directories."""
293 293 st1 = os.lstat(fpath1)
294 294 st2 = os.lstat(fpath2)
295 295 return st1.st_dev == st2.st_dev
296 296
297 297 # os.path.normcase is a no-op, which doesn't help us on non-native filesystems
298 298 def normcase(path):
299 299 return path.lower()
300 300
301 301 # what normcase does to ASCII strings
302 302 normcasespec = encoding.normcasespecs.lower
303 303 # fallback normcase function for non-ASCII strings
304 304 normcasefallback = normcase
305 305
306 306 if pycompat.sysplatform == 'darwin':
307 307
308 308 def normcase(path):
309 309 '''
310 310 Normalize a filename for OS X-compatible comparison:
311 311 - escape-encode invalid characters
312 312 - decompose to NFD
313 313 - lowercase
314 314 - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
315 315
316 316 >>> normcase('UPPER')
317 317 'upper'
318 318 >>> normcase('Caf\xc3\xa9')
319 319 'cafe\\xcc\\x81'
320 320 >>> normcase('\xc3\x89')
321 321 'e\\xcc\\x81'
322 322 >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
323 323 '%b8%ca%c3\\xca\\xbe%c8.jpg'
324 324 '''
325 325
326 326 try:
327 327 return encoding.asciilower(path) # exception for non-ASCII
328 328 except UnicodeDecodeError:
329 329 return normcasefallback(path)
330 330
331 331 normcasespec = encoding.normcasespecs.lower
332 332
333 333 def normcasefallback(path):
334 334 try:
335 335 u = path.decode('utf-8')
336 336 except UnicodeDecodeError:
337 337 # OS X percent-encodes any bytes that aren't valid utf-8
338 338 s = ''
339 339 pos = 0
340 340 l = len(path)
341 341 while pos < l:
342 342 try:
343 343 c = encoding.getutf8char(path, pos)
344 344 pos += len(c)
345 345 except ValueError:
346 346 c = '%%%02X' % ord(path[pos])
347 347 pos += 1
348 348 s += c
349 349
350 350 u = s.decode('utf-8')
351 351
352 352 # Decompose then lowercase (HFS+ technote specifies lower)
353 353 enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
354 354 # drop HFS+ ignored characters
355 355 return encoding.hfsignoreclean(enc)
356 356
357 357 if pycompat.sysplatform == 'cygwin':
358 358 # workaround for cygwin, in which mount point part of path is
359 359 # treated as case sensitive, even though underlying NTFS is case
360 360 # insensitive.
361 361
362 362 # default mount points
363 363 cygwinmountpoints = sorted([
364 364 "/usr/bin",
365 365 "/usr/lib",
366 366 "/cygdrive",
367 367 ], reverse=True)
368 368
369 369 # use upper-ing as normcase as same as NTFS workaround
370 370 def normcase(path):
371 371 pathlen = len(path)
372 372 if (pathlen == 0) or (path[0] != pycompat.ossep):
373 373 # treat as relative
374 374 return encoding.upper(path)
375 375
376 376 # to preserve case of mountpoint part
377 377 for mp in cygwinmountpoints:
378 378 if not path.startswith(mp):
379 379 continue
380 380
381 381 mplen = len(mp)
382 382 if mplen == pathlen: # mount point itself
383 383 return mp
384 384 if path[mplen] == pycompat.ossep:
385 385 return mp + encoding.upper(path[mplen:])
386 386
387 387 return encoding.upper(path)
388 388
389 389 normcasespec = encoding.normcasespecs.other
390 390 normcasefallback = normcase
391 391
392 392 # Cygwin translates native ACLs to POSIX permissions,
393 393 # but these translations are not supported by native
394 394 # tools, so the exec bit tends to be set erroneously.
395 395 # Therefore, disable executable bit access on Cygwin.
396 396 def checkexec(path):
397 397 return False
398 398
399 399 # Similarly, Cygwin's symlink emulation is likely to create
400 400 # problems when Mercurial is used from both Cygwin and native
401 401 # Windows, with other native tools, or on shared volumes
402 402 def checklink(path):
403 403 return False
404 404
405 405 _needsshellquote = None
406 406 def shellquote(s):
407 407 if pycompat.sysplatform == 'OpenVMS':
408 408 return '"%s"' % s
409 409 global _needsshellquote
410 410 if _needsshellquote is None:
411 _needsshellquote = re.compile(r'[^a-zA-Z0-9._/+-]').search
411 _needsshellquote = re.compile(br'[^a-zA-Z0-9._/+-]').search
412 412 if s and not _needsshellquote(s):
413 413 # "s" shouldn't have to be quoted
414 414 return s
415 415 else:
416 416 return "'%s'" % s.replace("'", "'\\''")
417 417
418 418 def quotecommand(cmd):
419 419 return cmd
420 420
421 421 def popen(command, mode='r'):
422 422 return os.popen(command, mode)
423 423
424 424 def testpid(pid):
425 425 '''return False if pid dead, True if running or not sure'''
426 426 if pycompat.sysplatform == 'OpenVMS':
427 427 return True
428 428 try:
429 429 os.kill(pid, 0)
430 430 return True
431 431 except OSError as inst:
432 432 return inst.errno != errno.ESRCH
433 433
434 434 def explainexit(code):
435 435 """return a 2-tuple (desc, code) describing a subprocess status
436 436 (codes from kill are negative - not os.system/wait encoding)"""
437 437 if code >= 0:
438 438 return _("exited with status %d") % code, code
439 439 return _("killed by signal %d") % -code, -code
440 440
441 441 def isowner(st):
442 442 """Return True if the stat object st is from the current user."""
443 443 return st.st_uid == os.getuid()
444 444
445 445 def findexe(command):
446 446 '''Find executable for command searching like which does.
447 447 If command is a basename then PATH is searched for command.
448 448 PATH isn't searched if command is an absolute or relative path.
449 449 If command isn't found None is returned.'''
450 450 if pycompat.sysplatform == 'OpenVMS':
451 451 return command
452 452
453 453 def findexisting(executable):
454 454 'Will return executable if existing file'
455 455 if os.path.isfile(executable) and os.access(executable, os.X_OK):
456 456 return executable
457 457 return None
458 458
459 459 if pycompat.ossep in command:
460 460 return findexisting(command)
461 461
462 462 if pycompat.sysplatform == 'plan9':
463 463 return findexisting(os.path.join('/bin', command))
464 464
465 465 for path in encoding.environ.get('PATH', '').split(pycompat.ospathsep):
466 466 executable = findexisting(os.path.join(path, command))
467 467 if executable is not None:
468 468 return executable
469 469 return None
470 470
471 471 def setsignalhandler():
472 472 pass
473 473
474 474 _wantedkinds = set([stat.S_IFREG, stat.S_IFLNK])
475 475
476 476 def statfiles(files):
477 477 '''Stat each file in files. Yield each stat, or None if a file does not
478 478 exist or has a type we don't care about.'''
479 479 lstat = os.lstat
480 480 getkind = stat.S_IFMT
481 481 for nf in files:
482 482 try:
483 483 st = lstat(nf)
484 484 if getkind(st.st_mode) not in _wantedkinds:
485 485 st = None
486 486 except OSError as err:
487 487 if err.errno not in (errno.ENOENT, errno.ENOTDIR):
488 488 raise
489 489 st = None
490 490 yield st
491 491
492 492 def getuser():
493 493 '''return name of current user'''
494 494 return getpass.getuser()
495 495
496 496 def username(uid=None):
497 497 """Return the name of the user with the given uid.
498 498
499 499 If uid is None, return the name of the current user."""
500 500
501 501 if uid is None:
502 502 uid = os.getuid()
503 503 try:
504 504 return pwd.getpwuid(uid)[0]
505 505 except KeyError:
506 506 return str(uid)
507 507
508 508 def groupname(gid=None):
509 509 """Return the name of the group with the given gid.
510 510
511 511 If gid is None, return the name of the current group."""
512 512
513 513 if gid is None:
514 514 gid = os.getgid()
515 515 try:
516 516 return grp.getgrgid(gid)[0]
517 517 except KeyError:
518 518 return str(gid)
519 519
520 520 def groupmembers(name):
521 521 """Return the list of members of the group with the given
522 522 name, KeyError if the group does not exist.
523 523 """
524 524 return list(grp.getgrnam(name).gr_mem)
525 525
526 526 def spawndetached(args):
527 527 return os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
528 528 args[0], args)
529 529
530 530 def gethgcmd():
531 531 return sys.argv[:1]
532 532
533 533 def makedir(path, notindexed):
534 534 os.mkdir(path)
535 535
536 536 def unlinkpath(f, ignoremissing=False):
537 537 """unlink and remove the directory if it is empty"""
538 538 try:
539 539 os.unlink(f)
540 540 except OSError as e:
541 541 if not (ignoremissing and e.errno == errno.ENOENT):
542 542 raise
543 543 # try removing directories that might now be empty
544 544 try:
545 545 os.removedirs(os.path.dirname(f))
546 546 except OSError:
547 547 pass
548 548
549 549 def lookupreg(key, name=None, scope=None):
550 550 return None
551 551
552 552 def hidewindow():
553 553 """Hide current shell window.
554 554
555 555 Used to hide the window opened when starting asynchronous
556 556 child process under Windows, unneeded on other systems.
557 557 """
558 558 pass
559 559
560 560 class cachestat(object):
561 561 def __init__(self, path):
562 562 self.stat = os.stat(path)
563 563
564 564 def cacheable(self):
565 565 return bool(self.stat.st_ino)
566 566
567 567 __hash__ = object.__hash__
568 568
569 569 def __eq__(self, other):
570 570 try:
571 571 # Only dev, ino, size, mtime and atime are likely to change. Out
572 572 # of these, we shouldn't compare atime but should compare the
573 573 # rest. However, one of the other fields changing indicates
574 574 # something fishy going on, so return False if anything but atime
575 575 # changes.
576 576 return (self.stat.st_mode == other.stat.st_mode and
577 577 self.stat.st_ino == other.stat.st_ino and
578 578 self.stat.st_dev == other.stat.st_dev and
579 579 self.stat.st_nlink == other.stat.st_nlink and
580 580 self.stat.st_uid == other.stat.st_uid and
581 581 self.stat.st_gid == other.stat.st_gid and
582 582 self.stat.st_size == other.stat.st_size and
583 583 self.stat.st_mtime == other.stat.st_mtime and
584 584 self.stat.st_ctime == other.stat.st_ctime)
585 585 except AttributeError:
586 586 return False
587 587
588 588 def __ne__(self, other):
589 589 return not self == other
590 590
591 591 def executablepath():
592 592 return None # available on Windows only
593 593
594 594 def statislink(st):
595 595 '''check whether a stat result is a symlink'''
596 596 return st and stat.S_ISLNK(st.st_mode)
597 597
598 598 def statisexec(st):
599 599 '''check whether a stat result is an executable file'''
600 600 return st and (st.st_mode & 0o100 != 0)
601 601
602 602 def poll(fds):
603 603 """block until something happens on any file descriptor
604 604
605 605 This is a generic helper that will check for any activity
606 606 (read, write. exception) and return the list of touched files.
607 607
608 608 In unsupported cases, it will raise a NotImplementedError"""
609 609 try:
610 610 while True:
611 611 try:
612 612 res = select.select(fds, fds, fds)
613 613 break
614 614 except select.error as inst:
615 615 if inst.args[0] == errno.EINTR:
616 616 continue
617 617 raise
618 618 except ValueError: # out of range file descriptor
619 619 raise NotImplementedError()
620 620 return sorted(list(set(sum(res, []))))
621 621
622 622 def readpipe(pipe):
623 623 """Read all available data from a pipe."""
624 624 # We can't fstat() a pipe because Linux will always report 0.
625 625 # So, we set the pipe to non-blocking mode and read everything
626 626 # that's available.
627 627 flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
628 628 flags |= os.O_NONBLOCK
629 629 oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
630 630
631 631 try:
632 632 chunks = []
633 633 while True:
634 634 try:
635 635 s = pipe.read()
636 636 if not s:
637 637 break
638 638 chunks.append(s)
639 639 except IOError:
640 640 break
641 641
642 642 return ''.join(chunks)
643 643 finally:
644 644 fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
645 645
646 646 def bindunixsocket(sock, path):
647 647 """Bind the UNIX domain socket to the specified path"""
648 648 # use relative path instead of full path at bind() if possible, since
649 649 # AF_UNIX path has very small length limit (107 chars) on common
650 650 # platforms (see sys/un.h)
651 651 dirname, basename = os.path.split(path)
652 652 bakwdfd = None
653 653 if dirname:
654 654 bakwdfd = os.open('.', os.O_DIRECTORY)
655 655 os.chdir(dirname)
656 656 sock.bind(basename)
657 657 if bakwdfd:
658 658 os.fchdir(bakwdfd)
659 659 os.close(bakwdfd)
General Comments 0
You need to be logged in to leave comments. Login now