##// END OF EJS Templates
pager: use less as a fallback on Unix...
Yuya Nishihara -
r32078:bf5e13e3 stable
parent child Browse files
Show More
@@ -1,35 +1,35 b''
1 Some Mercurial commands produce a lot of output, and Mercurial will
1 Some Mercurial commands produce a lot of output, and Mercurial will
2 attempt to use a pager to make those commands more pleasant.
2 attempt to use a pager to make those commands more pleasant.
3
3
4 To set the pager that should be used, set the application variable::
4 To set the pager that should be used, set the application variable::
5
5
6 [pager]
6 [pager]
7 pager = less -FRX
7 pager = less -FRX
8
8
9 If no pager is set, the pager extensions uses the environment variable
9 If no pager is set, the pager extensions uses the environment variable
10 $PAGER. If neither pager.pager, nor $PAGER is set, a default pager
10 $PAGER. If neither pager.pager, nor $PAGER is set, a default pager
11 will be used, typically `more`.
11 will be used, typically `less` on Unix and `more` on Windows.
12
12
13 You can disable the pager for certain commands by adding them to the
13 You can disable the pager for certain commands by adding them to the
14 pager.ignore list::
14 pager.ignore list::
15
15
16 [pager]
16 [pager]
17 ignore = version, help, update
17 ignore = version, help, update
18
18
19 To ignore global commands like :hg:`version` or :hg:`help`, you have
19 To ignore global commands like :hg:`version` or :hg:`help`, you have
20 to specify them in your user configuration file.
20 to specify them in your user configuration file.
21
21
22 To control whether the pager is used at all for an individual command,
22 To control whether the pager is used at all for an individual command,
23 you can use --pager=<value>::
23 you can use --pager=<value>::
24
24
25 - use as needed: `auto`.
25 - use as needed: `auto`.
26 - require the pager: `yes` or `on`.
26 - require the pager: `yes` or `on`.
27 - suppress the pager: `no` or `off` (any unrecognized value
27 - suppress the pager: `no` or `off` (any unrecognized value
28 will also work).
28 will also work).
29
29
30 To globally turn off all attempts to use a pager, set::
30 To globally turn off all attempts to use a pager, set::
31
31
32 [pager]
32 [pager]
33 enable = false
33 enable = false
34
34
35 which will prevent the pager from running.
35 which will prevent the pager from running.
@@ -1,98 +1,99 b''
1 # rcutil.py - utilities about config paths, special config sections etc.
1 # rcutil.py - utilities about config paths, special config sections etc.
2 #
2 #
3 # Copyright Mercurial Contributors
3 # Copyright Mercurial Contributors
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import os
10 import os
11
11
12 from . import (
12 from . import (
13 encoding,
13 encoding,
14 osutil,
14 osutil,
15 pycompat,
15 pycompat,
16 util,
16 util,
17 )
17 )
18
18
19 if pycompat.osname == 'nt':
19 if pycompat.osname == 'nt':
20 from . import scmwindows as scmplatform
20 from . import scmwindows as scmplatform
21 else:
21 else:
22 from . import scmposix as scmplatform
22 from . import scmposix as scmplatform
23
23
24 fallbackpager = scmplatform.fallbackpager
24 systemrcpath = scmplatform.systemrcpath
25 systemrcpath = scmplatform.systemrcpath
25 userrcpath = scmplatform.userrcpath
26 userrcpath = scmplatform.userrcpath
26
27
27 def _expandrcpath(path):
28 def _expandrcpath(path):
28 '''path could be a file or a directory. return a list of file paths'''
29 '''path could be a file or a directory. return a list of file paths'''
29 p = util.expandpath(path)
30 p = util.expandpath(path)
30 if os.path.isdir(p):
31 if os.path.isdir(p):
31 join = os.path.join
32 join = os.path.join
32 return [join(p, f) for f, k in osutil.listdir(p) if f.endswith('.rc')]
33 return [join(p, f) for f, k in osutil.listdir(p) if f.endswith('.rc')]
33 return [p]
34 return [p]
34
35
35 def envrcitems(env=None):
36 def envrcitems(env=None):
36 '''Return [(section, name, value, source)] config items.
37 '''Return [(section, name, value, source)] config items.
37
38
38 The config items are extracted from environment variables specified by env,
39 The config items are extracted from environment variables specified by env,
39 used to override systemrc, but not userrc.
40 used to override systemrc, but not userrc.
40
41
41 If env is not provided, encoding.environ will be used.
42 If env is not provided, encoding.environ will be used.
42 '''
43 '''
43 if env is None:
44 if env is None:
44 env = encoding.environ
45 env = encoding.environ
45 checklist = [
46 checklist = [
46 ('EDITOR', 'ui', 'editor'),
47 ('EDITOR', 'ui', 'editor'),
47 ('VISUAL', 'ui', 'editor'),
48 ('VISUAL', 'ui', 'editor'),
48 ('PAGER', 'pager', 'pager'),
49 ('PAGER', 'pager', 'pager'),
49 ]
50 ]
50 result = []
51 result = []
51 for envname, section, configname in checklist:
52 for envname, section, configname in checklist:
52 if envname not in env:
53 if envname not in env:
53 continue
54 continue
54 result.append((section, configname, env[envname], '$%s' % envname))
55 result.append((section, configname, env[envname], '$%s' % envname))
55 return result
56 return result
56
57
57 def defaultrcpath():
58 def defaultrcpath():
58 '''return rc paths in default.d'''
59 '''return rc paths in default.d'''
59 path = []
60 path = []
60 defaultpath = os.path.join(util.datapath, 'default.d')
61 defaultpath = os.path.join(util.datapath, 'default.d')
61 if os.path.isdir(defaultpath):
62 if os.path.isdir(defaultpath):
62 path = _expandrcpath(defaultpath)
63 path = _expandrcpath(defaultpath)
63 return path
64 return path
64
65
65 def rccomponents():
66 def rccomponents():
66 '''return an ordered [(type, obj)] about where to load configs.
67 '''return an ordered [(type, obj)] about where to load configs.
67
68
68 respect $HGRCPATH. if $HGRCPATH is empty, only .hg/hgrc of current repo is
69 respect $HGRCPATH. if $HGRCPATH is empty, only .hg/hgrc of current repo is
69 used. if $HGRCPATH is not set, the platform default will be used.
70 used. if $HGRCPATH is not set, the platform default will be used.
70
71
71 if a directory is provided, *.rc files under it will be used.
72 if a directory is provided, *.rc files under it will be used.
72
73
73 type could be either 'path' or 'items', if type is 'path', obj is a string,
74 type could be either 'path' or 'items', if type is 'path', obj is a string,
74 and is the config file path. if type is 'items', obj is a list of (section,
75 and is the config file path. if type is 'items', obj is a list of (section,
75 name, value, source) that should fill the config directly.
76 name, value, source) that should fill the config directly.
76 '''
77 '''
77 envrc = ('items', envrcitems())
78 envrc = ('items', envrcitems())
78
79
79 if 'HGRCPATH' in encoding.environ:
80 if 'HGRCPATH' in encoding.environ:
80 # assume HGRCPATH is all about user configs so environments can be
81 # assume HGRCPATH is all about user configs so environments can be
81 # overridden.
82 # overridden.
82 _rccomponents = [envrc]
83 _rccomponents = [envrc]
83 for p in encoding.environ['HGRCPATH'].split(pycompat.ospathsep):
84 for p in encoding.environ['HGRCPATH'].split(pycompat.ospathsep):
84 if not p:
85 if not p:
85 continue
86 continue
86 _rccomponents.extend(('path', p) for p in _expandrcpath(p))
87 _rccomponents.extend(('path', p) for p in _expandrcpath(p))
87 else:
88 else:
88 normpaths = lambda paths: [('path', os.path.normpath(p)) for p in paths]
89 normpaths = lambda paths: [('path', os.path.normpath(p)) for p in paths]
89 _rccomponents = normpaths(defaultrcpath() + systemrcpath())
90 _rccomponents = normpaths(defaultrcpath() + systemrcpath())
90 _rccomponents.append(envrc)
91 _rccomponents.append(envrc)
91 _rccomponents.extend(normpaths(userrcpath()))
92 _rccomponents.extend(normpaths(userrcpath()))
92 return _rccomponents
93 return _rccomponents
93
94
94 def defaultpagerenv():
95 def defaultpagerenv():
95 '''return a dict of default environment variables and their values,
96 '''return a dict of default environment variables and their values,
96 intended to be set before starting a pager.
97 intended to be set before starting a pager.
97 '''
98 '''
98 return {'LESS': 'FRX', 'LV': '-c'}
99 return {'LESS': 'FRX', 'LV': '-c'}
@@ -1,79 +1,85 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import array
3 import array
4 import errno
4 import errno
5 import fcntl
5 import fcntl
6 import os
6 import os
7 import sys
7 import sys
8
8
9 from . import (
9 from . import (
10 encoding,
10 encoding,
11 osutil,
11 osutil,
12 pycompat,
12 pycompat,
13 )
13 )
14
14
15 # BSD 'more' escapes ANSI color sequences by default. This can be disabled by
16 # $MORE variable, but there's no compatible option with Linux 'more'. Given
17 # OS X is widely used and most modern Unix systems would have 'less', setting
18 # 'less' as the default seems reasonable.
19 fallbackpager = 'less'
20
15 def _rcfiles(path):
21 def _rcfiles(path):
16 rcs = [os.path.join(path, 'hgrc')]
22 rcs = [os.path.join(path, 'hgrc')]
17 rcdir = os.path.join(path, 'hgrc.d')
23 rcdir = os.path.join(path, 'hgrc.d')
18 try:
24 try:
19 rcs.extend([os.path.join(rcdir, f)
25 rcs.extend([os.path.join(rcdir, f)
20 for f, kind in osutil.listdir(rcdir)
26 for f, kind in osutil.listdir(rcdir)
21 if f.endswith(".rc")])
27 if f.endswith(".rc")])
22 except OSError:
28 except OSError:
23 pass
29 pass
24 return rcs
30 return rcs
25
31
26 def systemrcpath():
32 def systemrcpath():
27 path = []
33 path = []
28 if pycompat.sysplatform == 'plan9':
34 if pycompat.sysplatform == 'plan9':
29 root = 'lib/mercurial'
35 root = 'lib/mercurial'
30 else:
36 else:
31 root = 'etc/mercurial'
37 root = 'etc/mercurial'
32 # old mod_python does not set sys.argv
38 # old mod_python does not set sys.argv
33 if len(getattr(sys, 'argv', [])) > 0:
39 if len(getattr(sys, 'argv', [])) > 0:
34 p = os.path.dirname(os.path.dirname(pycompat.sysargv[0]))
40 p = os.path.dirname(os.path.dirname(pycompat.sysargv[0]))
35 if p != '/':
41 if p != '/':
36 path.extend(_rcfiles(os.path.join(p, root)))
42 path.extend(_rcfiles(os.path.join(p, root)))
37 path.extend(_rcfiles('/' + root))
43 path.extend(_rcfiles('/' + root))
38 return path
44 return path
39
45
40 def userrcpath():
46 def userrcpath():
41 if pycompat.sysplatform == 'plan9':
47 if pycompat.sysplatform == 'plan9':
42 return [encoding.environ['home'] + '/lib/hgrc']
48 return [encoding.environ['home'] + '/lib/hgrc']
43 elif pycompat.sysplatform == 'darwin':
49 elif pycompat.sysplatform == 'darwin':
44 return [os.path.expanduser('~/.hgrc')]
50 return [os.path.expanduser('~/.hgrc')]
45 else:
51 else:
46 confighome = encoding.environ.get('XDG_CONFIG_HOME')
52 confighome = encoding.environ.get('XDG_CONFIG_HOME')
47 if confighome is None or not os.path.isabs(confighome):
53 if confighome is None or not os.path.isabs(confighome):
48 confighome = os.path.expanduser('~/.config')
54 confighome = os.path.expanduser('~/.config')
49
55
50 return [os.path.expanduser('~/.hgrc'),
56 return [os.path.expanduser('~/.hgrc'),
51 os.path.join(confighome, 'hg', 'hgrc')]
57 os.path.join(confighome, 'hg', 'hgrc')]
52
58
53 def termsize(ui):
59 def termsize(ui):
54 try:
60 try:
55 import termios
61 import termios
56 TIOCGWINSZ = termios.TIOCGWINSZ # unavailable on IRIX (issue3449)
62 TIOCGWINSZ = termios.TIOCGWINSZ # unavailable on IRIX (issue3449)
57 except (AttributeError, ImportError):
63 except (AttributeError, ImportError):
58 return 80, 24
64 return 80, 24
59
65
60 for dev in (ui.ferr, ui.fout, ui.fin):
66 for dev in (ui.ferr, ui.fout, ui.fin):
61 try:
67 try:
62 try:
68 try:
63 fd = dev.fileno()
69 fd = dev.fileno()
64 except AttributeError:
70 except AttributeError:
65 continue
71 continue
66 if not os.isatty(fd):
72 if not os.isatty(fd):
67 continue
73 continue
68 arri = fcntl.ioctl(fd, TIOCGWINSZ, '\0' * 8)
74 arri = fcntl.ioctl(fd, TIOCGWINSZ, '\0' * 8)
69 height, width = array.array(r'h', arri)[:2]
75 height, width = array.array(r'h', arri)[:2]
70 if width > 0 and height > 0:
76 if width > 0 and height > 0:
71 return width, height
77 return width, height
72 except ValueError:
78 except ValueError:
73 pass
79 pass
74 except IOError as e:
80 except IOError as e:
75 if e[0] == errno.EINVAL:
81 if e[0] == errno.EINVAL:
76 pass
82 pass
77 else:
83 else:
78 raise
84 raise
79 return 80, 24
85 return 80, 24
@@ -1,59 +1,62 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import os
3 import os
4
4
5 from . import (
5 from . import (
6 encoding,
6 encoding,
7 osutil,
7 osutil,
8 pycompat,
8 pycompat,
9 util,
9 util,
10 win32,
10 win32,
11 )
11 )
12
12
13 try:
13 try:
14 import _winreg as winreg
14 import _winreg as winreg
15 winreg.CloseKey
15 winreg.CloseKey
16 except ImportError:
16 except ImportError:
17 import winreg
17 import winreg
18
18
19 # MS-DOS 'more' is the only pager available by default on Windows.
20 fallbackpager = 'more'
21
19 def systemrcpath():
22 def systemrcpath():
20 '''return default os-specific hgrc search path'''
23 '''return default os-specific hgrc search path'''
21 rcpath = []
24 rcpath = []
22 filename = util.executablepath()
25 filename = util.executablepath()
23 # Use mercurial.ini found in directory with hg.exe
26 # Use mercurial.ini found in directory with hg.exe
24 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
27 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
25 rcpath.append(progrc)
28 rcpath.append(progrc)
26 # Use hgrc.d found in directory with hg.exe
29 # Use hgrc.d found in directory with hg.exe
27 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
30 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
28 if os.path.isdir(progrcd):
31 if os.path.isdir(progrcd):
29 for f, kind in osutil.listdir(progrcd):
32 for f, kind in osutil.listdir(progrcd):
30 if f.endswith('.rc'):
33 if f.endswith('.rc'):
31 rcpath.append(os.path.join(progrcd, f))
34 rcpath.append(os.path.join(progrcd, f))
32 # else look for a system rcpath in the registry
35 # else look for a system rcpath in the registry
33 value = util.lookupreg('SOFTWARE\\Mercurial', None,
36 value = util.lookupreg('SOFTWARE\\Mercurial', None,
34 winreg.HKEY_LOCAL_MACHINE)
37 winreg.HKEY_LOCAL_MACHINE)
35 if not isinstance(value, str) or not value:
38 if not isinstance(value, str) or not value:
36 return rcpath
39 return rcpath
37 value = util.localpath(value)
40 value = util.localpath(value)
38 for p in value.split(pycompat.ospathsep):
41 for p in value.split(pycompat.ospathsep):
39 if p.lower().endswith('mercurial.ini'):
42 if p.lower().endswith('mercurial.ini'):
40 rcpath.append(p)
43 rcpath.append(p)
41 elif os.path.isdir(p):
44 elif os.path.isdir(p):
42 for f, kind in osutil.listdir(p):
45 for f, kind in osutil.listdir(p):
43 if f.endswith('.rc'):
46 if f.endswith('.rc'):
44 rcpath.append(os.path.join(p, f))
47 rcpath.append(os.path.join(p, f))
45 return rcpath
48 return rcpath
46
49
47 def userrcpath():
50 def userrcpath():
48 '''return os-specific hgrc search path to the user dir'''
51 '''return os-specific hgrc search path to the user dir'''
49 home = os.path.expanduser('~')
52 home = os.path.expanduser('~')
50 path = [os.path.join(home, 'mercurial.ini'),
53 path = [os.path.join(home, 'mercurial.ini'),
51 os.path.join(home, '.hgrc')]
54 os.path.join(home, '.hgrc')]
52 userprofile = encoding.environ.get('USERPROFILE')
55 userprofile = encoding.environ.get('USERPROFILE')
53 if userprofile and userprofile != home:
56 if userprofile and userprofile != home:
54 path.append(os.path.join(userprofile, 'mercurial.ini'))
57 path.append(os.path.join(userprofile, 'mercurial.ini'))
55 path.append(os.path.join(userprofile, '.hgrc'))
58 path.append(os.path.join(userprofile, '.hgrc'))
56 return path
59 return path
57
60
58 def termsize(ui):
61 def termsize(ui):
59 return win32.termsize()
62 return win32.termsize()
@@ -1,1675 +1,1674 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 progress,
33 progress,
34 pycompat,
34 pycompat,
35 rcutil,
35 rcutil,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39
39
40 urlreq = util.urlreq
40 urlreq = util.urlreq
41
41
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 if not c.isalnum())
44 if not c.isalnum())
45
45
46 samplehgrcs = {
46 samplehgrcs = {
47 'user':
47 'user':
48 """# example user config (see 'hg help config' for more info)
48 """# example user config (see 'hg help config' for more info)
49 [ui]
49 [ui]
50 # name and email, e.g.
50 # name and email, e.g.
51 # username = Jane Doe <jdoe@example.com>
51 # username = Jane Doe <jdoe@example.com>
52 username =
52 username =
53
53
54 # uncomment to colorize command output
54 # uncomment to colorize command output
55 # color = auto
55 # color = auto
56
56
57 [extensions]
57 [extensions]
58 # uncomment these lines to enable some popular extensions
58 # uncomment these lines to enable some popular extensions
59 # (see 'hg help extensions' for more info)
59 # (see 'hg help extensions' for more info)
60 #
60 #
61 # pager =""",
61 # pager =""",
62
62
63 'cloned':
63 'cloned':
64 """# example repository config (see 'hg help config' for more info)
64 """# example repository config (see 'hg help config' for more info)
65 [paths]
65 [paths]
66 default = %s
66 default = %s
67
67
68 # path aliases to other clones of this repo in URLs or filesystem paths
68 # path aliases to other clones of this repo in URLs or filesystem paths
69 # (see 'hg help config.paths' for more info)
69 # (see 'hg help config.paths' for more info)
70 #
70 #
71 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
71 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 # my-clone = /home/jdoe/jdoes-clone
73 # my-clone = /home/jdoe/jdoes-clone
74
74
75 [ui]
75 [ui]
76 # name and email (local to this repository, optional), e.g.
76 # name and email (local to this repository, optional), e.g.
77 # username = Jane Doe <jdoe@example.com>
77 # username = Jane Doe <jdoe@example.com>
78 """,
78 """,
79
79
80 'local':
80 'local':
81 """# example repository config (see 'hg help config' for more info)
81 """# example repository config (see 'hg help config' for more info)
82 [paths]
82 [paths]
83 # path aliases to other clones of this repo in URLs or filesystem paths
83 # path aliases to other clones of this repo in URLs or filesystem paths
84 # (see 'hg help config.paths' for more info)
84 # (see 'hg help config.paths' for more info)
85 #
85 #
86 # default = http://example.com/hg/example-repo
86 # default = http://example.com/hg/example-repo
87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
87 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
88 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
89 # my-clone = /home/jdoe/jdoes-clone
89 # my-clone = /home/jdoe/jdoes-clone
90
90
91 [ui]
91 [ui]
92 # name and email (local to this repository, optional), e.g.
92 # name and email (local to this repository, optional), e.g.
93 # username = Jane Doe <jdoe@example.com>
93 # username = Jane Doe <jdoe@example.com>
94 """,
94 """,
95
95
96 'global':
96 'global':
97 """# example system-wide hg config (see 'hg help config' for more info)
97 """# example system-wide hg config (see 'hg help config' for more info)
98
98
99 [ui]
99 [ui]
100 # uncomment to colorize command output
100 # uncomment to colorize command output
101 # color = auto
101 # color = auto
102
102
103 [extensions]
103 [extensions]
104 # uncomment these lines to enable some popular extensions
104 # uncomment these lines to enable some popular extensions
105 # (see 'hg help extensions' for more info)
105 # (see 'hg help extensions' for more info)
106 #
106 #
107 # blackbox =
107 # blackbox =
108 # pager =""",
108 # pager =""",
109 }
109 }
110
110
111
111
112 class httppasswordmgrdbproxy(object):
112 class httppasswordmgrdbproxy(object):
113 """Delays loading urllib2 until it's needed."""
113 """Delays loading urllib2 until it's needed."""
114 def __init__(self):
114 def __init__(self):
115 self._mgr = None
115 self._mgr = None
116
116
117 def _get_mgr(self):
117 def _get_mgr(self):
118 if self._mgr is None:
118 if self._mgr is None:
119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
119 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
120 return self._mgr
120 return self._mgr
121
121
122 def add_password(self, *args, **kwargs):
122 def add_password(self, *args, **kwargs):
123 return self._get_mgr().add_password(*args, **kwargs)
123 return self._get_mgr().add_password(*args, **kwargs)
124
124
125 def find_user_password(self, *args, **kwargs):
125 def find_user_password(self, *args, **kwargs):
126 return self._get_mgr().find_user_password(*args, **kwargs)
126 return self._get_mgr().find_user_password(*args, **kwargs)
127
127
128 def _catchterm(*args):
128 def _catchterm(*args):
129 raise error.SignalInterrupt
129 raise error.SignalInterrupt
130
130
131 class ui(object):
131 class ui(object):
132 def __init__(self, src=None):
132 def __init__(self, src=None):
133 """Create a fresh new ui object if no src given
133 """Create a fresh new ui object if no src given
134
134
135 Use uimod.ui.load() to create a ui which knows global and user configs.
135 Use uimod.ui.load() to create a ui which knows global and user configs.
136 In most cases, you should use ui.copy() to create a copy of an existing
136 In most cases, you should use ui.copy() to create a copy of an existing
137 ui object.
137 ui object.
138 """
138 """
139 # _buffers: used for temporary capture of output
139 # _buffers: used for temporary capture of output
140 self._buffers = []
140 self._buffers = []
141 # _exithandlers: callbacks run at the end of a request
141 # _exithandlers: callbacks run at the end of a request
142 self._exithandlers = []
142 self._exithandlers = []
143 # 3-tuple describing how each buffer in the stack behaves.
143 # 3-tuple describing how each buffer in the stack behaves.
144 # Values are (capture stderr, capture subprocesses, apply labels).
144 # Values are (capture stderr, capture subprocesses, apply labels).
145 self._bufferstates = []
145 self._bufferstates = []
146 # When a buffer is active, defines whether we are expanding labels.
146 # When a buffer is active, defines whether we are expanding labels.
147 # This exists to prevent an extra list lookup.
147 # This exists to prevent an extra list lookup.
148 self._bufferapplylabels = None
148 self._bufferapplylabels = None
149 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
149 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
150 self._reportuntrusted = True
150 self._reportuntrusted = True
151 self._ocfg = config.config() # overlay
151 self._ocfg = config.config() # overlay
152 self._tcfg = config.config() # trusted
152 self._tcfg = config.config() # trusted
153 self._ucfg = config.config() # untrusted
153 self._ucfg = config.config() # untrusted
154 self._trustusers = set()
154 self._trustusers = set()
155 self._trustgroups = set()
155 self._trustgroups = set()
156 self.callhooks = True
156 self.callhooks = True
157 # Insecure server connections requested.
157 # Insecure server connections requested.
158 self.insecureconnections = False
158 self.insecureconnections = False
159 # Blocked time
159 # Blocked time
160 self.logblockedtimes = False
160 self.logblockedtimes = False
161 # color mode: see mercurial/color.py for possible value
161 # color mode: see mercurial/color.py for possible value
162 self._colormode = None
162 self._colormode = None
163 self._terminfoparams = {}
163 self._terminfoparams = {}
164 self._styles = {}
164 self._styles = {}
165
165
166 if src:
166 if src:
167 self._exithandlers = src._exithandlers
167 self._exithandlers = src._exithandlers
168 self.fout = src.fout
168 self.fout = src.fout
169 self.ferr = src.ferr
169 self.ferr = src.ferr
170 self.fin = src.fin
170 self.fin = src.fin
171 self.pageractive = src.pageractive
171 self.pageractive = src.pageractive
172 self._disablepager = src._disablepager
172 self._disablepager = src._disablepager
173
173
174 self._tcfg = src._tcfg.copy()
174 self._tcfg = src._tcfg.copy()
175 self._ucfg = src._ucfg.copy()
175 self._ucfg = src._ucfg.copy()
176 self._ocfg = src._ocfg.copy()
176 self._ocfg = src._ocfg.copy()
177 self._trustusers = src._trustusers.copy()
177 self._trustusers = src._trustusers.copy()
178 self._trustgroups = src._trustgroups.copy()
178 self._trustgroups = src._trustgroups.copy()
179 self.environ = src.environ
179 self.environ = src.environ
180 self.callhooks = src.callhooks
180 self.callhooks = src.callhooks
181 self.insecureconnections = src.insecureconnections
181 self.insecureconnections = src.insecureconnections
182 self._colormode = src._colormode
182 self._colormode = src._colormode
183 self._terminfoparams = src._terminfoparams.copy()
183 self._terminfoparams = src._terminfoparams.copy()
184 self._styles = src._styles.copy()
184 self._styles = src._styles.copy()
185
185
186 self.fixconfig()
186 self.fixconfig()
187
187
188 self.httppasswordmgrdb = src.httppasswordmgrdb
188 self.httppasswordmgrdb = src.httppasswordmgrdb
189 self._blockedtimes = src._blockedtimes
189 self._blockedtimes = src._blockedtimes
190 else:
190 else:
191 self.fout = util.stdout
191 self.fout = util.stdout
192 self.ferr = util.stderr
192 self.ferr = util.stderr
193 self.fin = util.stdin
193 self.fin = util.stdin
194 self.pageractive = False
194 self.pageractive = False
195 self._disablepager = False
195 self._disablepager = False
196
196
197 # shared read-only environment
197 # shared read-only environment
198 self.environ = encoding.environ
198 self.environ = encoding.environ
199
199
200 self.httppasswordmgrdb = httppasswordmgrdbproxy()
200 self.httppasswordmgrdb = httppasswordmgrdbproxy()
201 self._blockedtimes = collections.defaultdict(int)
201 self._blockedtimes = collections.defaultdict(int)
202
202
203 allowed = self.configlist('experimental', 'exportableenviron')
203 allowed = self.configlist('experimental', 'exportableenviron')
204 if '*' in allowed:
204 if '*' in allowed:
205 self._exportableenviron = self.environ
205 self._exportableenviron = self.environ
206 else:
206 else:
207 self._exportableenviron = {}
207 self._exportableenviron = {}
208 for k in allowed:
208 for k in allowed:
209 if k in self.environ:
209 if k in self.environ:
210 self._exportableenviron[k] = self.environ[k]
210 self._exportableenviron[k] = self.environ[k]
211
211
212 @classmethod
212 @classmethod
213 def load(cls):
213 def load(cls):
214 """Create a ui and load global and user configs"""
214 """Create a ui and load global and user configs"""
215 u = cls()
215 u = cls()
216 # we always trust global config files and environment variables
216 # we always trust global config files and environment variables
217 for t, f in rcutil.rccomponents():
217 for t, f in rcutil.rccomponents():
218 if t == 'path':
218 if t == 'path':
219 u.readconfig(f, trust=True)
219 u.readconfig(f, trust=True)
220 elif t == 'items':
220 elif t == 'items':
221 sections = set()
221 sections = set()
222 for section, name, value, source in f:
222 for section, name, value, source in f:
223 # do not set u._ocfg
223 # do not set u._ocfg
224 # XXX clean this up once immutable config object is a thing
224 # XXX clean this up once immutable config object is a thing
225 u._tcfg.set(section, name, value, source)
225 u._tcfg.set(section, name, value, source)
226 u._ucfg.set(section, name, value, source)
226 u._ucfg.set(section, name, value, source)
227 sections.add(section)
227 sections.add(section)
228 for section in sections:
228 for section in sections:
229 u.fixconfig(section=section)
229 u.fixconfig(section=section)
230 else:
230 else:
231 raise error.ProgrammingError('unknown rctype: %s' % t)
231 raise error.ProgrammingError('unknown rctype: %s' % t)
232 return u
232 return u
233
233
234 def copy(self):
234 def copy(self):
235 return self.__class__(self)
235 return self.__class__(self)
236
236
237 def resetstate(self):
237 def resetstate(self):
238 """Clear internal state that shouldn't persist across commands"""
238 """Clear internal state that shouldn't persist across commands"""
239 if self._progbar:
239 if self._progbar:
240 self._progbar.resetstate() # reset last-print time of progress bar
240 self._progbar.resetstate() # reset last-print time of progress bar
241 self.httppasswordmgrdb = httppasswordmgrdbproxy()
241 self.httppasswordmgrdb = httppasswordmgrdbproxy()
242
242
243 @contextlib.contextmanager
243 @contextlib.contextmanager
244 def timeblockedsection(self, key):
244 def timeblockedsection(self, key):
245 # this is open-coded below - search for timeblockedsection to find them
245 # this is open-coded below - search for timeblockedsection to find them
246 starttime = util.timer()
246 starttime = util.timer()
247 try:
247 try:
248 yield
248 yield
249 finally:
249 finally:
250 self._blockedtimes[key + '_blocked'] += \
250 self._blockedtimes[key + '_blocked'] += \
251 (util.timer() - starttime) * 1000
251 (util.timer() - starttime) * 1000
252
252
253 def formatter(self, topic, opts):
253 def formatter(self, topic, opts):
254 return formatter.formatter(self, topic, opts)
254 return formatter.formatter(self, topic, opts)
255
255
256 def _trusted(self, fp, f):
256 def _trusted(self, fp, f):
257 st = util.fstat(fp)
257 st = util.fstat(fp)
258 if util.isowner(st):
258 if util.isowner(st):
259 return True
259 return True
260
260
261 tusers, tgroups = self._trustusers, self._trustgroups
261 tusers, tgroups = self._trustusers, self._trustgroups
262 if '*' in tusers or '*' in tgroups:
262 if '*' in tusers or '*' in tgroups:
263 return True
263 return True
264
264
265 user = util.username(st.st_uid)
265 user = util.username(st.st_uid)
266 group = util.groupname(st.st_gid)
266 group = util.groupname(st.st_gid)
267 if user in tusers or group in tgroups or user == util.username():
267 if user in tusers or group in tgroups or user == util.username():
268 return True
268 return True
269
269
270 if self._reportuntrusted:
270 if self._reportuntrusted:
271 self.warn(_('not trusting file %s from untrusted '
271 self.warn(_('not trusting file %s from untrusted '
272 'user %s, group %s\n') % (f, user, group))
272 'user %s, group %s\n') % (f, user, group))
273 return False
273 return False
274
274
275 def readconfig(self, filename, root=None, trust=False,
275 def readconfig(self, filename, root=None, trust=False,
276 sections=None, remap=None):
276 sections=None, remap=None):
277 try:
277 try:
278 fp = open(filename, u'rb')
278 fp = open(filename, u'rb')
279 except IOError:
279 except IOError:
280 if not sections: # ignore unless we were looking for something
280 if not sections: # ignore unless we were looking for something
281 return
281 return
282 raise
282 raise
283
283
284 cfg = config.config()
284 cfg = config.config()
285 trusted = sections or trust or self._trusted(fp, filename)
285 trusted = sections or trust or self._trusted(fp, filename)
286
286
287 try:
287 try:
288 cfg.read(filename, fp, sections=sections, remap=remap)
288 cfg.read(filename, fp, sections=sections, remap=remap)
289 fp.close()
289 fp.close()
290 except error.ConfigError as inst:
290 except error.ConfigError as inst:
291 if trusted:
291 if trusted:
292 raise
292 raise
293 self.warn(_("ignored: %s\n") % str(inst))
293 self.warn(_("ignored: %s\n") % str(inst))
294
294
295 if self.plain():
295 if self.plain():
296 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
296 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
297 'logtemplate', 'statuscopies', 'style',
297 'logtemplate', 'statuscopies', 'style',
298 'traceback', 'verbose'):
298 'traceback', 'verbose'):
299 if k in cfg['ui']:
299 if k in cfg['ui']:
300 del cfg['ui'][k]
300 del cfg['ui'][k]
301 for k, v in cfg.items('defaults'):
301 for k, v in cfg.items('defaults'):
302 del cfg['defaults'][k]
302 del cfg['defaults'][k]
303 for k, v in cfg.items('commands'):
303 for k, v in cfg.items('commands'):
304 del cfg['commands'][k]
304 del cfg['commands'][k]
305 # Don't remove aliases from the configuration if in the exceptionlist
305 # Don't remove aliases from the configuration if in the exceptionlist
306 if self.plain('alias'):
306 if self.plain('alias'):
307 for k, v in cfg.items('alias'):
307 for k, v in cfg.items('alias'):
308 del cfg['alias'][k]
308 del cfg['alias'][k]
309 if self.plain('revsetalias'):
309 if self.plain('revsetalias'):
310 for k, v in cfg.items('revsetalias'):
310 for k, v in cfg.items('revsetalias'):
311 del cfg['revsetalias'][k]
311 del cfg['revsetalias'][k]
312 if self.plain('templatealias'):
312 if self.plain('templatealias'):
313 for k, v in cfg.items('templatealias'):
313 for k, v in cfg.items('templatealias'):
314 del cfg['templatealias'][k]
314 del cfg['templatealias'][k]
315
315
316 if trusted:
316 if trusted:
317 self._tcfg.update(cfg)
317 self._tcfg.update(cfg)
318 self._tcfg.update(self._ocfg)
318 self._tcfg.update(self._ocfg)
319 self._ucfg.update(cfg)
319 self._ucfg.update(cfg)
320 self._ucfg.update(self._ocfg)
320 self._ucfg.update(self._ocfg)
321
321
322 if root is None:
322 if root is None:
323 root = os.path.expanduser('~')
323 root = os.path.expanduser('~')
324 self.fixconfig(root=root)
324 self.fixconfig(root=root)
325
325
326 def fixconfig(self, root=None, section=None):
326 def fixconfig(self, root=None, section=None):
327 if section in (None, 'paths'):
327 if section in (None, 'paths'):
328 # expand vars and ~
328 # expand vars and ~
329 # translate paths relative to root (or home) into absolute paths
329 # translate paths relative to root (or home) into absolute paths
330 root = root or pycompat.getcwd()
330 root = root or pycompat.getcwd()
331 for c in self._tcfg, self._ucfg, self._ocfg:
331 for c in self._tcfg, self._ucfg, self._ocfg:
332 for n, p in c.items('paths'):
332 for n, p in c.items('paths'):
333 # Ignore sub-options.
333 # Ignore sub-options.
334 if ':' in n:
334 if ':' in n:
335 continue
335 continue
336 if not p:
336 if not p:
337 continue
337 continue
338 if '%%' in p:
338 if '%%' in p:
339 s = self.configsource('paths', n) or 'none'
339 s = self.configsource('paths', n) or 'none'
340 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
340 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
341 % (n, p, s))
341 % (n, p, s))
342 p = p.replace('%%', '%')
342 p = p.replace('%%', '%')
343 p = util.expandpath(p)
343 p = util.expandpath(p)
344 if not util.hasscheme(p) and not os.path.isabs(p):
344 if not util.hasscheme(p) and not os.path.isabs(p):
345 p = os.path.normpath(os.path.join(root, p))
345 p = os.path.normpath(os.path.join(root, p))
346 c.set("paths", n, p)
346 c.set("paths", n, p)
347
347
348 if section in (None, 'ui'):
348 if section in (None, 'ui'):
349 # update ui options
349 # update ui options
350 self.debugflag = self.configbool('ui', 'debug')
350 self.debugflag = self.configbool('ui', 'debug')
351 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
351 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
352 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
352 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
353 if self.verbose and self.quiet:
353 if self.verbose and self.quiet:
354 self.quiet = self.verbose = False
354 self.quiet = self.verbose = False
355 self._reportuntrusted = self.debugflag or self.configbool("ui",
355 self._reportuntrusted = self.debugflag or self.configbool("ui",
356 "report_untrusted", True)
356 "report_untrusted", True)
357 self.tracebackflag = self.configbool('ui', 'traceback', False)
357 self.tracebackflag = self.configbool('ui', 'traceback', False)
358 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
358 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
359
359
360 if section in (None, 'trusted'):
360 if section in (None, 'trusted'):
361 # update trust information
361 # update trust information
362 self._trustusers.update(self.configlist('trusted', 'users'))
362 self._trustusers.update(self.configlist('trusted', 'users'))
363 self._trustgroups.update(self.configlist('trusted', 'groups'))
363 self._trustgroups.update(self.configlist('trusted', 'groups'))
364
364
365 def backupconfig(self, section, item):
365 def backupconfig(self, section, item):
366 return (self._ocfg.backup(section, item),
366 return (self._ocfg.backup(section, item),
367 self._tcfg.backup(section, item),
367 self._tcfg.backup(section, item),
368 self._ucfg.backup(section, item),)
368 self._ucfg.backup(section, item),)
369 def restoreconfig(self, data):
369 def restoreconfig(self, data):
370 self._ocfg.restore(data[0])
370 self._ocfg.restore(data[0])
371 self._tcfg.restore(data[1])
371 self._tcfg.restore(data[1])
372 self._ucfg.restore(data[2])
372 self._ucfg.restore(data[2])
373
373
374 def setconfig(self, section, name, value, source=''):
374 def setconfig(self, section, name, value, source=''):
375 for cfg in (self._ocfg, self._tcfg, self._ucfg):
375 for cfg in (self._ocfg, self._tcfg, self._ucfg):
376 cfg.set(section, name, value, source)
376 cfg.set(section, name, value, source)
377 self.fixconfig(section=section)
377 self.fixconfig(section=section)
378
378
379 def _data(self, untrusted):
379 def _data(self, untrusted):
380 return untrusted and self._ucfg or self._tcfg
380 return untrusted and self._ucfg or self._tcfg
381
381
382 def configsource(self, section, name, untrusted=False):
382 def configsource(self, section, name, untrusted=False):
383 return self._data(untrusted).source(section, name)
383 return self._data(untrusted).source(section, name)
384
384
385 def config(self, section, name, default=None, untrusted=False):
385 def config(self, section, name, default=None, untrusted=False):
386 if isinstance(name, list):
386 if isinstance(name, list):
387 alternates = name
387 alternates = name
388 else:
388 else:
389 alternates = [name]
389 alternates = [name]
390
390
391 for n in alternates:
391 for n in alternates:
392 value = self._data(untrusted).get(section, n, None)
392 value = self._data(untrusted).get(section, n, None)
393 if value is not None:
393 if value is not None:
394 name = n
394 name = n
395 break
395 break
396 else:
396 else:
397 value = default
397 value = default
398
398
399 if self.debugflag and not untrusted and self._reportuntrusted:
399 if self.debugflag and not untrusted and self._reportuntrusted:
400 for n in alternates:
400 for n in alternates:
401 uvalue = self._ucfg.get(section, n)
401 uvalue = self._ucfg.get(section, n)
402 if uvalue is not None and uvalue != value:
402 if uvalue is not None and uvalue != value:
403 self.debug("ignoring untrusted configuration option "
403 self.debug("ignoring untrusted configuration option "
404 "%s.%s = %s\n" % (section, n, uvalue))
404 "%s.%s = %s\n" % (section, n, uvalue))
405 return value
405 return value
406
406
407 def configsuboptions(self, section, name, default=None, untrusted=False):
407 def configsuboptions(self, section, name, default=None, untrusted=False):
408 """Get a config option and all sub-options.
408 """Get a config option and all sub-options.
409
409
410 Some config options have sub-options that are declared with the
410 Some config options have sub-options that are declared with the
411 format "key:opt = value". This method is used to return the main
411 format "key:opt = value". This method is used to return the main
412 option and all its declared sub-options.
412 option and all its declared sub-options.
413
413
414 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
414 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
415 is a dict of defined sub-options where keys and values are strings.
415 is a dict of defined sub-options where keys and values are strings.
416 """
416 """
417 data = self._data(untrusted)
417 data = self._data(untrusted)
418 main = data.get(section, name, default)
418 main = data.get(section, name, default)
419 if self.debugflag and not untrusted and self._reportuntrusted:
419 if self.debugflag and not untrusted and self._reportuntrusted:
420 uvalue = self._ucfg.get(section, name)
420 uvalue = self._ucfg.get(section, name)
421 if uvalue is not None and uvalue != main:
421 if uvalue is not None and uvalue != main:
422 self.debug('ignoring untrusted configuration option '
422 self.debug('ignoring untrusted configuration option '
423 '%s.%s = %s\n' % (section, name, uvalue))
423 '%s.%s = %s\n' % (section, name, uvalue))
424
424
425 sub = {}
425 sub = {}
426 prefix = '%s:' % name
426 prefix = '%s:' % name
427 for k, v in data.items(section):
427 for k, v in data.items(section):
428 if k.startswith(prefix):
428 if k.startswith(prefix):
429 sub[k[len(prefix):]] = v
429 sub[k[len(prefix):]] = v
430
430
431 if self.debugflag and not untrusted and self._reportuntrusted:
431 if self.debugflag and not untrusted and self._reportuntrusted:
432 for k, v in sub.items():
432 for k, v in sub.items():
433 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
433 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
434 if uvalue is not None and uvalue != v:
434 if uvalue is not None and uvalue != v:
435 self.debug('ignoring untrusted configuration option '
435 self.debug('ignoring untrusted configuration option '
436 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
436 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
437
437
438 return main, sub
438 return main, sub
439
439
440 def configpath(self, section, name, default=None, untrusted=False):
440 def configpath(self, section, name, default=None, untrusted=False):
441 'get a path config item, expanded relative to repo root or config file'
441 'get a path config item, expanded relative to repo root or config file'
442 v = self.config(section, name, default, untrusted)
442 v = self.config(section, name, default, untrusted)
443 if v is None:
443 if v is None:
444 return None
444 return None
445 if not os.path.isabs(v) or "://" not in v:
445 if not os.path.isabs(v) or "://" not in v:
446 src = self.configsource(section, name, untrusted)
446 src = self.configsource(section, name, untrusted)
447 if ':' in src:
447 if ':' in src:
448 base = os.path.dirname(src.rsplit(':')[0])
448 base = os.path.dirname(src.rsplit(':')[0])
449 v = os.path.join(base, os.path.expanduser(v))
449 v = os.path.join(base, os.path.expanduser(v))
450 return v
450 return v
451
451
452 def configbool(self, section, name, default=False, untrusted=False):
452 def configbool(self, section, name, default=False, untrusted=False):
453 """parse a configuration element as a boolean
453 """parse a configuration element as a boolean
454
454
455 >>> u = ui(); s = 'foo'
455 >>> u = ui(); s = 'foo'
456 >>> u.setconfig(s, 'true', 'yes')
456 >>> u.setconfig(s, 'true', 'yes')
457 >>> u.configbool(s, 'true')
457 >>> u.configbool(s, 'true')
458 True
458 True
459 >>> u.setconfig(s, 'false', 'no')
459 >>> u.setconfig(s, 'false', 'no')
460 >>> u.configbool(s, 'false')
460 >>> u.configbool(s, 'false')
461 False
461 False
462 >>> u.configbool(s, 'unknown')
462 >>> u.configbool(s, 'unknown')
463 False
463 False
464 >>> u.configbool(s, 'unknown', True)
464 >>> u.configbool(s, 'unknown', True)
465 True
465 True
466 >>> u.setconfig(s, 'invalid', 'somevalue')
466 >>> u.setconfig(s, 'invalid', 'somevalue')
467 >>> u.configbool(s, 'invalid')
467 >>> u.configbool(s, 'invalid')
468 Traceback (most recent call last):
468 Traceback (most recent call last):
469 ...
469 ...
470 ConfigError: foo.invalid is not a boolean ('somevalue')
470 ConfigError: foo.invalid is not a boolean ('somevalue')
471 """
471 """
472
472
473 v = self.config(section, name, None, untrusted)
473 v = self.config(section, name, None, untrusted)
474 if v is None:
474 if v is None:
475 return default
475 return default
476 if isinstance(v, bool):
476 if isinstance(v, bool):
477 return v
477 return v
478 b = util.parsebool(v)
478 b = util.parsebool(v)
479 if b is None:
479 if b is None:
480 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
480 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
481 % (section, name, v))
481 % (section, name, v))
482 return b
482 return b
483
483
484 def configwith(self, convert, section, name, default=None,
484 def configwith(self, convert, section, name, default=None,
485 desc=None, untrusted=False):
485 desc=None, untrusted=False):
486 """parse a configuration element with a conversion function
486 """parse a configuration element with a conversion function
487
487
488 >>> u = ui(); s = 'foo'
488 >>> u = ui(); s = 'foo'
489 >>> u.setconfig(s, 'float1', '42')
489 >>> u.setconfig(s, 'float1', '42')
490 >>> u.configwith(float, s, 'float1')
490 >>> u.configwith(float, s, 'float1')
491 42.0
491 42.0
492 >>> u.setconfig(s, 'float2', '-4.25')
492 >>> u.setconfig(s, 'float2', '-4.25')
493 >>> u.configwith(float, s, 'float2')
493 >>> u.configwith(float, s, 'float2')
494 -4.25
494 -4.25
495 >>> u.configwith(float, s, 'unknown', 7)
495 >>> u.configwith(float, s, 'unknown', 7)
496 7
496 7
497 >>> u.setconfig(s, 'invalid', 'somevalue')
497 >>> u.setconfig(s, 'invalid', 'somevalue')
498 >>> u.configwith(float, s, 'invalid')
498 >>> u.configwith(float, s, 'invalid')
499 Traceback (most recent call last):
499 Traceback (most recent call last):
500 ...
500 ...
501 ConfigError: foo.invalid is not a valid float ('somevalue')
501 ConfigError: foo.invalid is not a valid float ('somevalue')
502 >>> u.configwith(float, s, 'invalid', desc='womble')
502 >>> u.configwith(float, s, 'invalid', desc='womble')
503 Traceback (most recent call last):
503 Traceback (most recent call last):
504 ...
504 ...
505 ConfigError: foo.invalid is not a valid womble ('somevalue')
505 ConfigError: foo.invalid is not a valid womble ('somevalue')
506 """
506 """
507
507
508 v = self.config(section, name, None, untrusted)
508 v = self.config(section, name, None, untrusted)
509 if v is None:
509 if v is None:
510 return default
510 return default
511 try:
511 try:
512 return convert(v)
512 return convert(v)
513 except ValueError:
513 except ValueError:
514 if desc is None:
514 if desc is None:
515 desc = convert.__name__
515 desc = convert.__name__
516 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
516 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
517 % (section, name, desc, v))
517 % (section, name, desc, v))
518
518
519 def configint(self, section, name, default=None, untrusted=False):
519 def configint(self, section, name, default=None, untrusted=False):
520 """parse a configuration element as an integer
520 """parse a configuration element as an integer
521
521
522 >>> u = ui(); s = 'foo'
522 >>> u = ui(); s = 'foo'
523 >>> u.setconfig(s, 'int1', '42')
523 >>> u.setconfig(s, 'int1', '42')
524 >>> u.configint(s, 'int1')
524 >>> u.configint(s, 'int1')
525 42
525 42
526 >>> u.setconfig(s, 'int2', '-42')
526 >>> u.setconfig(s, 'int2', '-42')
527 >>> u.configint(s, 'int2')
527 >>> u.configint(s, 'int2')
528 -42
528 -42
529 >>> u.configint(s, 'unknown', 7)
529 >>> u.configint(s, 'unknown', 7)
530 7
530 7
531 >>> u.setconfig(s, 'invalid', 'somevalue')
531 >>> u.setconfig(s, 'invalid', 'somevalue')
532 >>> u.configint(s, 'invalid')
532 >>> u.configint(s, 'invalid')
533 Traceback (most recent call last):
533 Traceback (most recent call last):
534 ...
534 ...
535 ConfigError: foo.invalid is not a valid integer ('somevalue')
535 ConfigError: foo.invalid is not a valid integer ('somevalue')
536 """
536 """
537
537
538 return self.configwith(int, section, name, default, 'integer',
538 return self.configwith(int, section, name, default, 'integer',
539 untrusted)
539 untrusted)
540
540
541 def configbytes(self, section, name, default=0, untrusted=False):
541 def configbytes(self, section, name, default=0, untrusted=False):
542 """parse a configuration element as a quantity in bytes
542 """parse a configuration element as a quantity in bytes
543
543
544 Units can be specified as b (bytes), k or kb (kilobytes), m or
544 Units can be specified as b (bytes), k or kb (kilobytes), m or
545 mb (megabytes), g or gb (gigabytes).
545 mb (megabytes), g or gb (gigabytes).
546
546
547 >>> u = ui(); s = 'foo'
547 >>> u = ui(); s = 'foo'
548 >>> u.setconfig(s, 'val1', '42')
548 >>> u.setconfig(s, 'val1', '42')
549 >>> u.configbytes(s, 'val1')
549 >>> u.configbytes(s, 'val1')
550 42
550 42
551 >>> u.setconfig(s, 'val2', '42.5 kb')
551 >>> u.setconfig(s, 'val2', '42.5 kb')
552 >>> u.configbytes(s, 'val2')
552 >>> u.configbytes(s, 'val2')
553 43520
553 43520
554 >>> u.configbytes(s, 'unknown', '7 MB')
554 >>> u.configbytes(s, 'unknown', '7 MB')
555 7340032
555 7340032
556 >>> u.setconfig(s, 'invalid', 'somevalue')
556 >>> u.setconfig(s, 'invalid', 'somevalue')
557 >>> u.configbytes(s, 'invalid')
557 >>> u.configbytes(s, 'invalid')
558 Traceback (most recent call last):
558 Traceback (most recent call last):
559 ...
559 ...
560 ConfigError: foo.invalid is not a byte quantity ('somevalue')
560 ConfigError: foo.invalid is not a byte quantity ('somevalue')
561 """
561 """
562
562
563 value = self.config(section, name, None, untrusted)
563 value = self.config(section, name, None, untrusted)
564 if value is None:
564 if value is None:
565 if not isinstance(default, str):
565 if not isinstance(default, str):
566 return default
566 return default
567 value = default
567 value = default
568 try:
568 try:
569 return util.sizetoint(value)
569 return util.sizetoint(value)
570 except error.ParseError:
570 except error.ParseError:
571 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
571 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
572 % (section, name, value))
572 % (section, name, value))
573
573
574 def configlist(self, section, name, default=None, untrusted=False):
574 def configlist(self, section, name, default=None, untrusted=False):
575 """parse a configuration element as a list of comma/space separated
575 """parse a configuration element as a list of comma/space separated
576 strings
576 strings
577
577
578 >>> u = ui(); s = 'foo'
578 >>> u = ui(); s = 'foo'
579 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
579 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
580 >>> u.configlist(s, 'list1')
580 >>> u.configlist(s, 'list1')
581 ['this', 'is', 'a small', 'test']
581 ['this', 'is', 'a small', 'test']
582 """
582 """
583 # default is not always a list
583 # default is not always a list
584 if isinstance(default, bytes):
584 if isinstance(default, bytes):
585 default = config.parselist(default)
585 default = config.parselist(default)
586 return self.configwith(config.parselist, section, name, default or [],
586 return self.configwith(config.parselist, section, name, default or [],
587 'list', untrusted)
587 'list', untrusted)
588
588
589 def hasconfig(self, section, name, untrusted=False):
589 def hasconfig(self, section, name, untrusted=False):
590 return self._data(untrusted).hasitem(section, name)
590 return self._data(untrusted).hasitem(section, name)
591
591
592 def has_section(self, section, untrusted=False):
592 def has_section(self, section, untrusted=False):
593 '''tell whether section exists in config.'''
593 '''tell whether section exists in config.'''
594 return section in self._data(untrusted)
594 return section in self._data(untrusted)
595
595
596 def configitems(self, section, untrusted=False, ignoresub=False):
596 def configitems(self, section, untrusted=False, ignoresub=False):
597 items = self._data(untrusted).items(section)
597 items = self._data(untrusted).items(section)
598 if ignoresub:
598 if ignoresub:
599 newitems = {}
599 newitems = {}
600 for k, v in items:
600 for k, v in items:
601 if ':' not in k:
601 if ':' not in k:
602 newitems[k] = v
602 newitems[k] = v
603 items = newitems.items()
603 items = newitems.items()
604 if self.debugflag and not untrusted and self._reportuntrusted:
604 if self.debugflag and not untrusted and self._reportuntrusted:
605 for k, v in self._ucfg.items(section):
605 for k, v in self._ucfg.items(section):
606 if self._tcfg.get(section, k) != v:
606 if self._tcfg.get(section, k) != v:
607 self.debug("ignoring untrusted configuration option "
607 self.debug("ignoring untrusted configuration option "
608 "%s.%s = %s\n" % (section, k, v))
608 "%s.%s = %s\n" % (section, k, v))
609 return items
609 return items
610
610
611 def walkconfig(self, untrusted=False):
611 def walkconfig(self, untrusted=False):
612 cfg = self._data(untrusted)
612 cfg = self._data(untrusted)
613 for section in cfg.sections():
613 for section in cfg.sections():
614 for name, value in self.configitems(section, untrusted):
614 for name, value in self.configitems(section, untrusted):
615 yield section, name, value
615 yield section, name, value
616
616
617 def plain(self, feature=None):
617 def plain(self, feature=None):
618 '''is plain mode active?
618 '''is plain mode active?
619
619
620 Plain mode means that all configuration variables which affect
620 Plain mode means that all configuration variables which affect
621 the behavior and output of Mercurial should be
621 the behavior and output of Mercurial should be
622 ignored. Additionally, the output should be stable,
622 ignored. Additionally, the output should be stable,
623 reproducible and suitable for use in scripts or applications.
623 reproducible and suitable for use in scripts or applications.
624
624
625 The only way to trigger plain mode is by setting either the
625 The only way to trigger plain mode is by setting either the
626 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
626 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
627
627
628 The return value can either be
628 The return value can either be
629 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
629 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
630 - True otherwise
630 - True otherwise
631 '''
631 '''
632 if ('HGPLAIN' not in encoding.environ and
632 if ('HGPLAIN' not in encoding.environ and
633 'HGPLAINEXCEPT' not in encoding.environ):
633 'HGPLAINEXCEPT' not in encoding.environ):
634 return False
634 return False
635 exceptions = encoding.environ.get('HGPLAINEXCEPT',
635 exceptions = encoding.environ.get('HGPLAINEXCEPT',
636 '').strip().split(',')
636 '').strip().split(',')
637 if feature and exceptions:
637 if feature and exceptions:
638 return feature not in exceptions
638 return feature not in exceptions
639 return True
639 return True
640
640
641 def username(self):
641 def username(self):
642 """Return default username to be used in commits.
642 """Return default username to be used in commits.
643
643
644 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
644 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
645 and stop searching if one of these is set.
645 and stop searching if one of these is set.
646 If not found and ui.askusername is True, ask the user, else use
646 If not found and ui.askusername is True, ask the user, else use
647 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
647 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
648 """
648 """
649 user = encoding.environ.get("HGUSER")
649 user = encoding.environ.get("HGUSER")
650 if user is None:
650 if user is None:
651 user = self.config("ui", ["username", "user"])
651 user = self.config("ui", ["username", "user"])
652 if user is not None:
652 if user is not None:
653 user = os.path.expandvars(user)
653 user = os.path.expandvars(user)
654 if user is None:
654 if user is None:
655 user = encoding.environ.get("EMAIL")
655 user = encoding.environ.get("EMAIL")
656 if user is None and self.configbool("ui", "askusername"):
656 if user is None and self.configbool("ui", "askusername"):
657 user = self.prompt(_("enter a commit username:"), default=None)
657 user = self.prompt(_("enter a commit username:"), default=None)
658 if user is None and not self.interactive():
658 if user is None and not self.interactive():
659 try:
659 try:
660 user = '%s@%s' % (util.getuser(), socket.getfqdn())
660 user = '%s@%s' % (util.getuser(), socket.getfqdn())
661 self.warn(_("no username found, using '%s' instead\n") % user)
661 self.warn(_("no username found, using '%s' instead\n") % user)
662 except KeyError:
662 except KeyError:
663 pass
663 pass
664 if not user:
664 if not user:
665 raise error.Abort(_('no username supplied'),
665 raise error.Abort(_('no username supplied'),
666 hint=_("use 'hg config --edit' "
666 hint=_("use 'hg config --edit' "
667 'to set your username'))
667 'to set your username'))
668 if "\n" in user:
668 if "\n" in user:
669 raise error.Abort(_("username %s contains a newline\n")
669 raise error.Abort(_("username %s contains a newline\n")
670 % repr(user))
670 % repr(user))
671 return user
671 return user
672
672
673 def shortuser(self, user):
673 def shortuser(self, user):
674 """Return a short representation of a user name or email address."""
674 """Return a short representation of a user name or email address."""
675 if not self.verbose:
675 if not self.verbose:
676 user = util.shortuser(user)
676 user = util.shortuser(user)
677 return user
677 return user
678
678
679 def expandpath(self, loc, default=None):
679 def expandpath(self, loc, default=None):
680 """Return repository location relative to cwd or from [paths]"""
680 """Return repository location relative to cwd or from [paths]"""
681 try:
681 try:
682 p = self.paths.getpath(loc)
682 p = self.paths.getpath(loc)
683 if p:
683 if p:
684 return p.rawloc
684 return p.rawloc
685 except error.RepoError:
685 except error.RepoError:
686 pass
686 pass
687
687
688 if default:
688 if default:
689 try:
689 try:
690 p = self.paths.getpath(default)
690 p = self.paths.getpath(default)
691 if p:
691 if p:
692 return p.rawloc
692 return p.rawloc
693 except error.RepoError:
693 except error.RepoError:
694 pass
694 pass
695
695
696 return loc
696 return loc
697
697
698 @util.propertycache
698 @util.propertycache
699 def paths(self):
699 def paths(self):
700 return paths(self)
700 return paths(self)
701
701
702 def pushbuffer(self, error=False, subproc=False, labeled=False):
702 def pushbuffer(self, error=False, subproc=False, labeled=False):
703 """install a buffer to capture standard output of the ui object
703 """install a buffer to capture standard output of the ui object
704
704
705 If error is True, the error output will be captured too.
705 If error is True, the error output will be captured too.
706
706
707 If subproc is True, output from subprocesses (typically hooks) will be
707 If subproc is True, output from subprocesses (typically hooks) will be
708 captured too.
708 captured too.
709
709
710 If labeled is True, any labels associated with buffered
710 If labeled is True, any labels associated with buffered
711 output will be handled. By default, this has no effect
711 output will be handled. By default, this has no effect
712 on the output returned, but extensions and GUI tools may
712 on the output returned, but extensions and GUI tools may
713 handle this argument and returned styled output. If output
713 handle this argument and returned styled output. If output
714 is being buffered so it can be captured and parsed or
714 is being buffered so it can be captured and parsed or
715 processed, labeled should not be set to True.
715 processed, labeled should not be set to True.
716 """
716 """
717 self._buffers.append([])
717 self._buffers.append([])
718 self._bufferstates.append((error, subproc, labeled))
718 self._bufferstates.append((error, subproc, labeled))
719 self._bufferapplylabels = labeled
719 self._bufferapplylabels = labeled
720
720
721 def popbuffer(self):
721 def popbuffer(self):
722 '''pop the last buffer and return the buffered output'''
722 '''pop the last buffer and return the buffered output'''
723 self._bufferstates.pop()
723 self._bufferstates.pop()
724 if self._bufferstates:
724 if self._bufferstates:
725 self._bufferapplylabels = self._bufferstates[-1][2]
725 self._bufferapplylabels = self._bufferstates[-1][2]
726 else:
726 else:
727 self._bufferapplylabels = None
727 self._bufferapplylabels = None
728
728
729 return "".join(self._buffers.pop())
729 return "".join(self._buffers.pop())
730
730
731 def write(self, *args, **opts):
731 def write(self, *args, **opts):
732 '''write args to output
732 '''write args to output
733
733
734 By default, this method simply writes to the buffer or stdout.
734 By default, this method simply writes to the buffer or stdout.
735 Color mode can be set on the UI class to have the output decorated
735 Color mode can be set on the UI class to have the output decorated
736 with color modifier before being written to stdout.
736 with color modifier before being written to stdout.
737
737
738 The color used is controlled by an optional keyword argument, "label".
738 The color used is controlled by an optional keyword argument, "label".
739 This should be a string containing label names separated by space.
739 This should be a string containing label names separated by space.
740 Label names take the form of "topic.type". For example, ui.debug()
740 Label names take the form of "topic.type". For example, ui.debug()
741 issues a label of "ui.debug".
741 issues a label of "ui.debug".
742
742
743 When labeling output for a specific command, a label of
743 When labeling output for a specific command, a label of
744 "cmdname.type" is recommended. For example, status issues
744 "cmdname.type" is recommended. For example, status issues
745 a label of "status.modified" for modified files.
745 a label of "status.modified" for modified files.
746 '''
746 '''
747 if self._buffers and not opts.get('prompt', False):
747 if self._buffers and not opts.get('prompt', False):
748 if self._bufferapplylabels:
748 if self._bufferapplylabels:
749 label = opts.get('label', '')
749 label = opts.get('label', '')
750 self._buffers[-1].extend(self.label(a, label) for a in args)
750 self._buffers[-1].extend(self.label(a, label) for a in args)
751 else:
751 else:
752 self._buffers[-1].extend(args)
752 self._buffers[-1].extend(args)
753 elif self._colormode == 'win32':
753 elif self._colormode == 'win32':
754 # windows color printing is its own can of crab, defer to
754 # windows color printing is its own can of crab, defer to
755 # the color module and that is it.
755 # the color module and that is it.
756 color.win32print(self, self._write, *args, **opts)
756 color.win32print(self, self._write, *args, **opts)
757 else:
757 else:
758 msgs = args
758 msgs = args
759 if self._colormode is not None:
759 if self._colormode is not None:
760 label = opts.get('label', '')
760 label = opts.get('label', '')
761 msgs = [self.label(a, label) for a in args]
761 msgs = [self.label(a, label) for a in args]
762 self._write(*msgs, **opts)
762 self._write(*msgs, **opts)
763
763
764 def _write(self, *msgs, **opts):
764 def _write(self, *msgs, **opts):
765 self._progclear()
765 self._progclear()
766 # opencode timeblockedsection because this is a critical path
766 # opencode timeblockedsection because this is a critical path
767 starttime = util.timer()
767 starttime = util.timer()
768 try:
768 try:
769 for a in msgs:
769 for a in msgs:
770 self.fout.write(a)
770 self.fout.write(a)
771 except IOError as err:
771 except IOError as err:
772 raise error.StdioError(err)
772 raise error.StdioError(err)
773 finally:
773 finally:
774 self._blockedtimes['stdio_blocked'] += \
774 self._blockedtimes['stdio_blocked'] += \
775 (util.timer() - starttime) * 1000
775 (util.timer() - starttime) * 1000
776
776
777 def write_err(self, *args, **opts):
777 def write_err(self, *args, **opts):
778 self._progclear()
778 self._progclear()
779 if self._bufferstates and self._bufferstates[-1][0]:
779 if self._bufferstates and self._bufferstates[-1][0]:
780 self.write(*args, **opts)
780 self.write(*args, **opts)
781 elif self._colormode == 'win32':
781 elif self._colormode == 'win32':
782 # windows color printing is its own can of crab, defer to
782 # windows color printing is its own can of crab, defer to
783 # the color module and that is it.
783 # the color module and that is it.
784 color.win32print(self, self._write_err, *args, **opts)
784 color.win32print(self, self._write_err, *args, **opts)
785 else:
785 else:
786 msgs = args
786 msgs = args
787 if self._colormode is not None:
787 if self._colormode is not None:
788 label = opts.get('label', '')
788 label = opts.get('label', '')
789 msgs = [self.label(a, label) for a in args]
789 msgs = [self.label(a, label) for a in args]
790 self._write_err(*msgs, **opts)
790 self._write_err(*msgs, **opts)
791
791
792 def _write_err(self, *msgs, **opts):
792 def _write_err(self, *msgs, **opts):
793 try:
793 try:
794 with self.timeblockedsection('stdio'):
794 with self.timeblockedsection('stdio'):
795 if not getattr(self.fout, 'closed', False):
795 if not getattr(self.fout, 'closed', False):
796 self.fout.flush()
796 self.fout.flush()
797 for a in msgs:
797 for a in msgs:
798 self.ferr.write(a)
798 self.ferr.write(a)
799 # stderr may be buffered under win32 when redirected to files,
799 # stderr may be buffered under win32 when redirected to files,
800 # including stdout.
800 # including stdout.
801 if not getattr(self.ferr, 'closed', False):
801 if not getattr(self.ferr, 'closed', False):
802 self.ferr.flush()
802 self.ferr.flush()
803 except IOError as inst:
803 except IOError as inst:
804 raise error.StdioError(inst)
804 raise error.StdioError(inst)
805
805
806 def flush(self):
806 def flush(self):
807 # opencode timeblockedsection because this is a critical path
807 # opencode timeblockedsection because this is a critical path
808 starttime = util.timer()
808 starttime = util.timer()
809 try:
809 try:
810 try:
810 try:
811 self.fout.flush()
811 self.fout.flush()
812 except IOError as err:
812 except IOError as err:
813 raise error.StdioError(err)
813 raise error.StdioError(err)
814 finally:
814 finally:
815 try:
815 try:
816 self.ferr.flush()
816 self.ferr.flush()
817 except IOError as err:
817 except IOError as err:
818 raise error.StdioError(err)
818 raise error.StdioError(err)
819 finally:
819 finally:
820 self._blockedtimes['stdio_blocked'] += \
820 self._blockedtimes['stdio_blocked'] += \
821 (util.timer() - starttime) * 1000
821 (util.timer() - starttime) * 1000
822
822
823 def _isatty(self, fh):
823 def _isatty(self, fh):
824 if self.configbool('ui', 'nontty', False):
824 if self.configbool('ui', 'nontty', False):
825 return False
825 return False
826 return util.isatty(fh)
826 return util.isatty(fh)
827
827
828 def disablepager(self):
828 def disablepager(self):
829 self._disablepager = True
829 self._disablepager = True
830
830
831 def pager(self, command):
831 def pager(self, command):
832 """Start a pager for subsequent command output.
832 """Start a pager for subsequent command output.
833
833
834 Commands which produce a long stream of output should call
834 Commands which produce a long stream of output should call
835 this function to activate the user's preferred pagination
835 this function to activate the user's preferred pagination
836 mechanism (which may be no pager). Calling this function
836 mechanism (which may be no pager). Calling this function
837 precludes any future use of interactive functionality, such as
837 precludes any future use of interactive functionality, such as
838 prompting the user or activating curses.
838 prompting the user or activating curses.
839
839
840 Args:
840 Args:
841 command: The full, non-aliased name of the command. That is, "log"
841 command: The full, non-aliased name of the command. That is, "log"
842 not "history, "summary" not "summ", etc.
842 not "history, "summary" not "summ", etc.
843 """
843 """
844 if (self._disablepager
844 if (self._disablepager
845 or self.pageractive
845 or self.pageractive
846 or command in self.configlist('pager', 'ignore')
846 or command in self.configlist('pager', 'ignore')
847 or not self.configbool('pager', 'enable', True)
847 or not self.configbool('pager', 'enable', True)
848 or not self.configbool('pager', 'attend-' + command, True)
848 or not self.configbool('pager', 'attend-' + command, True)
849 # TODO: if we want to allow HGPLAINEXCEPT=pager,
849 # TODO: if we want to allow HGPLAINEXCEPT=pager,
850 # formatted() will need some adjustment.
850 # formatted() will need some adjustment.
851 or not self.formatted()
851 or not self.formatted()
852 or self.plain()
852 or self.plain()
853 # TODO: expose debugger-enabled on the UI object
853 # TODO: expose debugger-enabled on the UI object
854 or '--debugger' in pycompat.sysargv):
854 or '--debugger' in pycompat.sysargv):
855 # We only want to paginate if the ui appears to be
855 # We only want to paginate if the ui appears to be
856 # interactive, the user didn't say HGPLAIN or
856 # interactive, the user didn't say HGPLAIN or
857 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
857 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
858 return
858 return
859
859
860 fallbackpager = 'more'
860 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
861 pagercmd = self.config('pager', 'pager', fallbackpager)
862 if not pagercmd:
861 if not pagercmd:
863 return
862 return
864
863
865 pagerenv = {}
864 pagerenv = {}
866 for name, value in rcutil.defaultpagerenv().items():
865 for name, value in rcutil.defaultpagerenv().items():
867 if name not in encoding.environ:
866 if name not in encoding.environ:
868 pagerenv[name] = value
867 pagerenv[name] = value
869
868
870 self.debug('starting pager for command %r\n' % command)
869 self.debug('starting pager for command %r\n' % command)
871 self.flush()
870 self.flush()
872
871
873 wasformatted = self.formatted()
872 wasformatted = self.formatted()
874 if util.safehasattr(signal, "SIGPIPE"):
873 if util.safehasattr(signal, "SIGPIPE"):
875 signal.signal(signal.SIGPIPE, _catchterm)
874 signal.signal(signal.SIGPIPE, _catchterm)
876 if self._runpager(pagercmd, pagerenv):
875 if self._runpager(pagercmd, pagerenv):
877 self.pageractive = True
876 self.pageractive = True
878 # Preserve the formatted-ness of the UI. This is important
877 # Preserve the formatted-ness of the UI. This is important
879 # because we mess with stdout, which might confuse
878 # because we mess with stdout, which might confuse
880 # auto-detection of things being formatted.
879 # auto-detection of things being formatted.
881 self.setconfig('ui', 'formatted', wasformatted, 'pager')
880 self.setconfig('ui', 'formatted', wasformatted, 'pager')
882 self.setconfig('ui', 'interactive', False, 'pager')
881 self.setconfig('ui', 'interactive', False, 'pager')
883
882
884 # If pagermode differs from color.mode, reconfigure color now that
883 # If pagermode differs from color.mode, reconfigure color now that
885 # pageractive is set.
884 # pageractive is set.
886 cm = self._colormode
885 cm = self._colormode
887 if cm != self.config('color', 'pagermode', cm):
886 if cm != self.config('color', 'pagermode', cm):
888 color.setup(self)
887 color.setup(self)
889 else:
888 else:
890 # If the pager can't be spawned in dispatch when --pager=on is
889 # If the pager can't be spawned in dispatch when --pager=on is
891 # given, don't try again when the command runs, to avoid a duplicate
890 # given, don't try again when the command runs, to avoid a duplicate
892 # warning about a missing pager command.
891 # warning about a missing pager command.
893 self.disablepager()
892 self.disablepager()
894
893
895 def _runpager(self, command, env=None):
894 def _runpager(self, command, env=None):
896 """Actually start the pager and set up file descriptors.
895 """Actually start the pager and set up file descriptors.
897
896
898 This is separate in part so that extensions (like chg) can
897 This is separate in part so that extensions (like chg) can
899 override how a pager is invoked.
898 override how a pager is invoked.
900 """
899 """
901 if command == 'cat':
900 if command == 'cat':
902 # Save ourselves some work.
901 # Save ourselves some work.
903 return False
902 return False
904 # If the command doesn't contain any of these characters, we
903 # If the command doesn't contain any of these characters, we
905 # assume it's a binary and exec it directly. This means for
904 # assume it's a binary and exec it directly. This means for
906 # simple pager command configurations, we can degrade
905 # simple pager command configurations, we can degrade
907 # gracefully and tell the user about their broken pager.
906 # gracefully and tell the user about their broken pager.
908 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
907 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
909
908
910 if pycompat.osname == 'nt' and not shell:
909 if pycompat.osname == 'nt' and not shell:
911 # Window's built-in `more` cannot be invoked with shell=False, but
910 # Window's built-in `more` cannot be invoked with shell=False, but
912 # its `more.com` can. Hide this implementation detail from the
911 # its `more.com` can. Hide this implementation detail from the
913 # user so we can also get sane bad PAGER behavior. MSYS has
912 # user so we can also get sane bad PAGER behavior. MSYS has
914 # `more.exe`, so do a cmd.exe style resolution of the executable to
913 # `more.exe`, so do a cmd.exe style resolution of the executable to
915 # determine which one to use.
914 # determine which one to use.
916 fullcmd = util.findexe(command)
915 fullcmd = util.findexe(command)
917 if not fullcmd:
916 if not fullcmd:
918 self.warn(_("missing pager command '%s', skipping pager\n")
917 self.warn(_("missing pager command '%s', skipping pager\n")
919 % command)
918 % command)
920 return False
919 return False
921
920
922 command = fullcmd
921 command = fullcmd
923
922
924 try:
923 try:
925 pager = subprocess.Popen(
924 pager = subprocess.Popen(
926 command, shell=shell, bufsize=-1,
925 command, shell=shell, bufsize=-1,
927 close_fds=util.closefds, stdin=subprocess.PIPE,
926 close_fds=util.closefds, stdin=subprocess.PIPE,
928 stdout=util.stdout, stderr=util.stderr,
927 stdout=util.stdout, stderr=util.stderr,
929 env=util.shellenviron(env))
928 env=util.shellenviron(env))
930 except OSError as e:
929 except OSError as e:
931 if e.errno == errno.ENOENT and not shell:
930 if e.errno == errno.ENOENT and not shell:
932 self.warn(_("missing pager command '%s', skipping pager\n")
931 self.warn(_("missing pager command '%s', skipping pager\n")
933 % command)
932 % command)
934 return False
933 return False
935 raise
934 raise
936
935
937 # back up original file descriptors
936 # back up original file descriptors
938 stdoutfd = os.dup(util.stdout.fileno())
937 stdoutfd = os.dup(util.stdout.fileno())
939 stderrfd = os.dup(util.stderr.fileno())
938 stderrfd = os.dup(util.stderr.fileno())
940
939
941 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
940 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
942 if self._isatty(util.stderr):
941 if self._isatty(util.stderr):
943 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
942 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
944
943
945 @self.atexit
944 @self.atexit
946 def killpager():
945 def killpager():
947 if util.safehasattr(signal, "SIGINT"):
946 if util.safehasattr(signal, "SIGINT"):
948 signal.signal(signal.SIGINT, signal.SIG_IGN)
947 signal.signal(signal.SIGINT, signal.SIG_IGN)
949 # restore original fds, closing pager.stdin copies in the process
948 # restore original fds, closing pager.stdin copies in the process
950 os.dup2(stdoutfd, util.stdout.fileno())
949 os.dup2(stdoutfd, util.stdout.fileno())
951 os.dup2(stderrfd, util.stderr.fileno())
950 os.dup2(stderrfd, util.stderr.fileno())
952 pager.stdin.close()
951 pager.stdin.close()
953 pager.wait()
952 pager.wait()
954
953
955 return True
954 return True
956
955
957 def atexit(self, func, *args, **kwargs):
956 def atexit(self, func, *args, **kwargs):
958 '''register a function to run after dispatching a request
957 '''register a function to run after dispatching a request
959
958
960 Handlers do not stay registered across request boundaries.'''
959 Handlers do not stay registered across request boundaries.'''
961 self._exithandlers.append((func, args, kwargs))
960 self._exithandlers.append((func, args, kwargs))
962 return func
961 return func
963
962
964 def interface(self, feature):
963 def interface(self, feature):
965 """what interface to use for interactive console features?
964 """what interface to use for interactive console features?
966
965
967 The interface is controlled by the value of `ui.interface` but also by
966 The interface is controlled by the value of `ui.interface` but also by
968 the value of feature-specific configuration. For example:
967 the value of feature-specific configuration. For example:
969
968
970 ui.interface.histedit = text
969 ui.interface.histedit = text
971 ui.interface.chunkselector = curses
970 ui.interface.chunkselector = curses
972
971
973 Here the features are "histedit" and "chunkselector".
972 Here the features are "histedit" and "chunkselector".
974
973
975 The configuration above means that the default interfaces for commands
974 The configuration above means that the default interfaces for commands
976 is curses, the interface for histedit is text and the interface for
975 is curses, the interface for histedit is text and the interface for
977 selecting chunk is crecord (the best curses interface available).
976 selecting chunk is crecord (the best curses interface available).
978
977
979 Consider the following example:
978 Consider the following example:
980 ui.interface = curses
979 ui.interface = curses
981 ui.interface.histedit = text
980 ui.interface.histedit = text
982
981
983 Then histedit will use the text interface and chunkselector will use
982 Then histedit will use the text interface and chunkselector will use
984 the default curses interface (crecord at the moment).
983 the default curses interface (crecord at the moment).
985 """
984 """
986 alldefaults = frozenset(["text", "curses"])
985 alldefaults = frozenset(["text", "curses"])
987
986
988 featureinterfaces = {
987 featureinterfaces = {
989 "chunkselector": [
988 "chunkselector": [
990 "text",
989 "text",
991 "curses",
990 "curses",
992 ]
991 ]
993 }
992 }
994
993
995 # Feature-specific interface
994 # Feature-specific interface
996 if feature not in featureinterfaces.keys():
995 if feature not in featureinterfaces.keys():
997 # Programming error, not user error
996 # Programming error, not user error
998 raise ValueError("Unknown feature requested %s" % feature)
997 raise ValueError("Unknown feature requested %s" % feature)
999
998
1000 availableinterfaces = frozenset(featureinterfaces[feature])
999 availableinterfaces = frozenset(featureinterfaces[feature])
1001 if alldefaults > availableinterfaces:
1000 if alldefaults > availableinterfaces:
1002 # Programming error, not user error. We need a use case to
1001 # Programming error, not user error. We need a use case to
1003 # define the right thing to do here.
1002 # define the right thing to do here.
1004 raise ValueError(
1003 raise ValueError(
1005 "Feature %s does not handle all default interfaces" %
1004 "Feature %s does not handle all default interfaces" %
1006 feature)
1005 feature)
1007
1006
1008 if self.plain():
1007 if self.plain():
1009 return "text"
1008 return "text"
1010
1009
1011 # Default interface for all the features
1010 # Default interface for all the features
1012 defaultinterface = "text"
1011 defaultinterface = "text"
1013 i = self.config("ui", "interface", None)
1012 i = self.config("ui", "interface", None)
1014 if i in alldefaults:
1013 if i in alldefaults:
1015 defaultinterface = i
1014 defaultinterface = i
1016
1015
1017 choseninterface = defaultinterface
1016 choseninterface = defaultinterface
1018 f = self.config("ui", "interface.%s" % feature, None)
1017 f = self.config("ui", "interface.%s" % feature, None)
1019 if f in availableinterfaces:
1018 if f in availableinterfaces:
1020 choseninterface = f
1019 choseninterface = f
1021
1020
1022 if i is not None and defaultinterface != i:
1021 if i is not None and defaultinterface != i:
1023 if f is not None:
1022 if f is not None:
1024 self.warn(_("invalid value for ui.interface: %s\n") %
1023 self.warn(_("invalid value for ui.interface: %s\n") %
1025 (i,))
1024 (i,))
1026 else:
1025 else:
1027 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1026 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1028 (i, choseninterface))
1027 (i, choseninterface))
1029 if f is not None and choseninterface != f:
1028 if f is not None and choseninterface != f:
1030 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1029 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1031 (feature, f, choseninterface))
1030 (feature, f, choseninterface))
1032
1031
1033 return choseninterface
1032 return choseninterface
1034
1033
1035 def interactive(self):
1034 def interactive(self):
1036 '''is interactive input allowed?
1035 '''is interactive input allowed?
1037
1036
1038 An interactive session is a session where input can be reasonably read
1037 An interactive session is a session where input can be reasonably read
1039 from `sys.stdin'. If this function returns false, any attempt to read
1038 from `sys.stdin'. If this function returns false, any attempt to read
1040 from stdin should fail with an error, unless a sensible default has been
1039 from stdin should fail with an error, unless a sensible default has been
1041 specified.
1040 specified.
1042
1041
1043 Interactiveness is triggered by the value of the `ui.interactive'
1042 Interactiveness is triggered by the value of the `ui.interactive'
1044 configuration variable or - if it is unset - when `sys.stdin' points
1043 configuration variable or - if it is unset - when `sys.stdin' points
1045 to a terminal device.
1044 to a terminal device.
1046
1045
1047 This function refers to input only; for output, see `ui.formatted()'.
1046 This function refers to input only; for output, see `ui.formatted()'.
1048 '''
1047 '''
1049 i = self.configbool("ui", "interactive", None)
1048 i = self.configbool("ui", "interactive", None)
1050 if i is None:
1049 if i is None:
1051 # some environments replace stdin without implementing isatty
1050 # some environments replace stdin without implementing isatty
1052 # usually those are non-interactive
1051 # usually those are non-interactive
1053 return self._isatty(self.fin)
1052 return self._isatty(self.fin)
1054
1053
1055 return i
1054 return i
1056
1055
1057 def termwidth(self):
1056 def termwidth(self):
1058 '''how wide is the terminal in columns?
1057 '''how wide is the terminal in columns?
1059 '''
1058 '''
1060 if 'COLUMNS' in encoding.environ:
1059 if 'COLUMNS' in encoding.environ:
1061 try:
1060 try:
1062 return int(encoding.environ['COLUMNS'])
1061 return int(encoding.environ['COLUMNS'])
1063 except ValueError:
1062 except ValueError:
1064 pass
1063 pass
1065 return scmutil.termsize(self)[0]
1064 return scmutil.termsize(self)[0]
1066
1065
1067 def formatted(self):
1066 def formatted(self):
1068 '''should formatted output be used?
1067 '''should formatted output be used?
1069
1068
1070 It is often desirable to format the output to suite the output medium.
1069 It is often desirable to format the output to suite the output medium.
1071 Examples of this are truncating long lines or colorizing messages.
1070 Examples of this are truncating long lines or colorizing messages.
1072 However, this is not often not desirable when piping output into other
1071 However, this is not often not desirable when piping output into other
1073 utilities, e.g. `grep'.
1072 utilities, e.g. `grep'.
1074
1073
1075 Formatted output is triggered by the value of the `ui.formatted'
1074 Formatted output is triggered by the value of the `ui.formatted'
1076 configuration variable or - if it is unset - when `sys.stdout' points
1075 configuration variable or - if it is unset - when `sys.stdout' points
1077 to a terminal device. Please note that `ui.formatted' should be
1076 to a terminal device. Please note that `ui.formatted' should be
1078 considered an implementation detail; it is not intended for use outside
1077 considered an implementation detail; it is not intended for use outside
1079 Mercurial or its extensions.
1078 Mercurial or its extensions.
1080
1079
1081 This function refers to output only; for input, see `ui.interactive()'.
1080 This function refers to output only; for input, see `ui.interactive()'.
1082 This function always returns false when in plain mode, see `ui.plain()'.
1081 This function always returns false when in plain mode, see `ui.plain()'.
1083 '''
1082 '''
1084 if self.plain():
1083 if self.plain():
1085 return False
1084 return False
1086
1085
1087 i = self.configbool("ui", "formatted", None)
1086 i = self.configbool("ui", "formatted", None)
1088 if i is None:
1087 if i is None:
1089 # some environments replace stdout without implementing isatty
1088 # some environments replace stdout without implementing isatty
1090 # usually those are non-interactive
1089 # usually those are non-interactive
1091 return self._isatty(self.fout)
1090 return self._isatty(self.fout)
1092
1091
1093 return i
1092 return i
1094
1093
1095 def _readline(self, prompt=''):
1094 def _readline(self, prompt=''):
1096 if self._isatty(self.fin):
1095 if self._isatty(self.fin):
1097 try:
1096 try:
1098 # magically add command line editing support, where
1097 # magically add command line editing support, where
1099 # available
1098 # available
1100 import readline
1099 import readline
1101 # force demandimport to really load the module
1100 # force demandimport to really load the module
1102 readline.read_history_file
1101 readline.read_history_file
1103 # windows sometimes raises something other than ImportError
1102 # windows sometimes raises something other than ImportError
1104 except Exception:
1103 except Exception:
1105 pass
1104 pass
1106
1105
1107 # call write() so output goes through subclassed implementation
1106 # call write() so output goes through subclassed implementation
1108 # e.g. color extension on Windows
1107 # e.g. color extension on Windows
1109 self.write(prompt, prompt=True)
1108 self.write(prompt, prompt=True)
1110
1109
1111 # instead of trying to emulate raw_input, swap (self.fin,
1110 # instead of trying to emulate raw_input, swap (self.fin,
1112 # self.fout) with (sys.stdin, sys.stdout)
1111 # self.fout) with (sys.stdin, sys.stdout)
1113 oldin = sys.stdin
1112 oldin = sys.stdin
1114 oldout = sys.stdout
1113 oldout = sys.stdout
1115 sys.stdin = self.fin
1114 sys.stdin = self.fin
1116 sys.stdout = self.fout
1115 sys.stdout = self.fout
1117 # prompt ' ' must exist; otherwise readline may delete entire line
1116 # prompt ' ' must exist; otherwise readline may delete entire line
1118 # - http://bugs.python.org/issue12833
1117 # - http://bugs.python.org/issue12833
1119 with self.timeblockedsection('stdio'):
1118 with self.timeblockedsection('stdio'):
1120 line = raw_input(' ')
1119 line = raw_input(' ')
1121 sys.stdin = oldin
1120 sys.stdin = oldin
1122 sys.stdout = oldout
1121 sys.stdout = oldout
1123
1122
1124 # When stdin is in binary mode on Windows, it can cause
1123 # When stdin is in binary mode on Windows, it can cause
1125 # raw_input() to emit an extra trailing carriage return
1124 # raw_input() to emit an extra trailing carriage return
1126 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1125 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1127 line = line[:-1]
1126 line = line[:-1]
1128 return line
1127 return line
1129
1128
1130 def prompt(self, msg, default="y"):
1129 def prompt(self, msg, default="y"):
1131 """Prompt user with msg, read response.
1130 """Prompt user with msg, read response.
1132 If ui is not interactive, the default is returned.
1131 If ui is not interactive, the default is returned.
1133 """
1132 """
1134 if not self.interactive():
1133 if not self.interactive():
1135 self.write(msg, ' ', default or '', "\n")
1134 self.write(msg, ' ', default or '', "\n")
1136 return default
1135 return default
1137 try:
1136 try:
1138 r = self._readline(self.label(msg, 'ui.prompt'))
1137 r = self._readline(self.label(msg, 'ui.prompt'))
1139 if not r:
1138 if not r:
1140 r = default
1139 r = default
1141 if self.configbool('ui', 'promptecho'):
1140 if self.configbool('ui', 'promptecho'):
1142 self.write(r, "\n")
1141 self.write(r, "\n")
1143 return r
1142 return r
1144 except EOFError:
1143 except EOFError:
1145 raise error.ResponseExpected()
1144 raise error.ResponseExpected()
1146
1145
1147 @staticmethod
1146 @staticmethod
1148 def extractchoices(prompt):
1147 def extractchoices(prompt):
1149 """Extract prompt message and list of choices from specified prompt.
1148 """Extract prompt message and list of choices from specified prompt.
1150
1149
1151 This returns tuple "(message, choices)", and "choices" is the
1150 This returns tuple "(message, choices)", and "choices" is the
1152 list of tuple "(response character, text without &)".
1151 list of tuple "(response character, text without &)".
1153
1152
1154 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1153 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1155 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1154 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1156 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1155 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1157 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1156 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1158 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1157 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1159 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1158 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1160 """
1159 """
1161
1160
1162 # Sadly, the prompt string may have been built with a filename
1161 # Sadly, the prompt string may have been built with a filename
1163 # containing "$$" so let's try to find the first valid-looking
1162 # containing "$$" so let's try to find the first valid-looking
1164 # prompt to start parsing. Sadly, we also can't rely on
1163 # prompt to start parsing. Sadly, we also can't rely on
1165 # choices containing spaces, ASCII, or basically anything
1164 # choices containing spaces, ASCII, or basically anything
1166 # except an ampersand followed by a character.
1165 # except an ampersand followed by a character.
1167 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1166 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1168 msg = m.group(1)
1167 msg = m.group(1)
1169 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1168 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1170 return (msg,
1169 return (msg,
1171 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1170 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1172 for s in choices])
1171 for s in choices])
1173
1172
1174 def promptchoice(self, prompt, default=0):
1173 def promptchoice(self, prompt, default=0):
1175 """Prompt user with a message, read response, and ensure it matches
1174 """Prompt user with a message, read response, and ensure it matches
1176 one of the provided choices. The prompt is formatted as follows:
1175 one of the provided choices. The prompt is formatted as follows:
1177
1176
1178 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1177 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1179
1178
1180 The index of the choice is returned. Responses are case
1179 The index of the choice is returned. Responses are case
1181 insensitive. If ui is not interactive, the default is
1180 insensitive. If ui is not interactive, the default is
1182 returned.
1181 returned.
1183 """
1182 """
1184
1183
1185 msg, choices = self.extractchoices(prompt)
1184 msg, choices = self.extractchoices(prompt)
1186 resps = [r for r, t in choices]
1185 resps = [r for r, t in choices]
1187 while True:
1186 while True:
1188 r = self.prompt(msg, resps[default])
1187 r = self.prompt(msg, resps[default])
1189 if r.lower() in resps:
1188 if r.lower() in resps:
1190 return resps.index(r.lower())
1189 return resps.index(r.lower())
1191 self.write(_("unrecognized response\n"))
1190 self.write(_("unrecognized response\n"))
1192
1191
1193 def getpass(self, prompt=None, default=None):
1192 def getpass(self, prompt=None, default=None):
1194 if not self.interactive():
1193 if not self.interactive():
1195 return default
1194 return default
1196 try:
1195 try:
1197 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1196 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1198 # disable getpass() only if explicitly specified. it's still valid
1197 # disable getpass() only if explicitly specified. it's still valid
1199 # to interact with tty even if fin is not a tty.
1198 # to interact with tty even if fin is not a tty.
1200 with self.timeblockedsection('stdio'):
1199 with self.timeblockedsection('stdio'):
1201 if self.configbool('ui', 'nontty'):
1200 if self.configbool('ui', 'nontty'):
1202 l = self.fin.readline()
1201 l = self.fin.readline()
1203 if not l:
1202 if not l:
1204 raise EOFError
1203 raise EOFError
1205 return l.rstrip('\n')
1204 return l.rstrip('\n')
1206 else:
1205 else:
1207 return getpass.getpass('')
1206 return getpass.getpass('')
1208 except EOFError:
1207 except EOFError:
1209 raise error.ResponseExpected()
1208 raise error.ResponseExpected()
1210 def status(self, *msg, **opts):
1209 def status(self, *msg, **opts):
1211 '''write status message to output (if ui.quiet is False)
1210 '''write status message to output (if ui.quiet is False)
1212
1211
1213 This adds an output label of "ui.status".
1212 This adds an output label of "ui.status".
1214 '''
1213 '''
1215 if not self.quiet:
1214 if not self.quiet:
1216 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1215 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1217 self.write(*msg, **opts)
1216 self.write(*msg, **opts)
1218 def warn(self, *msg, **opts):
1217 def warn(self, *msg, **opts):
1219 '''write warning message to output (stderr)
1218 '''write warning message to output (stderr)
1220
1219
1221 This adds an output label of "ui.warning".
1220 This adds an output label of "ui.warning".
1222 '''
1221 '''
1223 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1222 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1224 self.write_err(*msg, **opts)
1223 self.write_err(*msg, **opts)
1225 def note(self, *msg, **opts):
1224 def note(self, *msg, **opts):
1226 '''write note to output (if ui.verbose is True)
1225 '''write note to output (if ui.verbose is True)
1227
1226
1228 This adds an output label of "ui.note".
1227 This adds an output label of "ui.note".
1229 '''
1228 '''
1230 if self.verbose:
1229 if self.verbose:
1231 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1230 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1232 self.write(*msg, **opts)
1231 self.write(*msg, **opts)
1233 def debug(self, *msg, **opts):
1232 def debug(self, *msg, **opts):
1234 '''write debug message to output (if ui.debugflag is True)
1233 '''write debug message to output (if ui.debugflag is True)
1235
1234
1236 This adds an output label of "ui.debug".
1235 This adds an output label of "ui.debug".
1237 '''
1236 '''
1238 if self.debugflag:
1237 if self.debugflag:
1239 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1238 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1240 self.write(*msg, **opts)
1239 self.write(*msg, **opts)
1241
1240
1242 def edit(self, text, user, extra=None, editform=None, pending=None,
1241 def edit(self, text, user, extra=None, editform=None, pending=None,
1243 repopath=None):
1242 repopath=None):
1244 extra_defaults = {
1243 extra_defaults = {
1245 'prefix': 'editor',
1244 'prefix': 'editor',
1246 'suffix': '.txt',
1245 'suffix': '.txt',
1247 }
1246 }
1248 if extra is not None:
1247 if extra is not None:
1249 extra_defaults.update(extra)
1248 extra_defaults.update(extra)
1250 extra = extra_defaults
1249 extra = extra_defaults
1251
1250
1252 rdir = None
1251 rdir = None
1253 if self.configbool('experimental', 'editortmpinhg'):
1252 if self.configbool('experimental', 'editortmpinhg'):
1254 rdir = repopath
1253 rdir = repopath
1255 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1254 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1256 suffix=extra['suffix'],
1255 suffix=extra['suffix'],
1257 dir=rdir)
1256 dir=rdir)
1258 try:
1257 try:
1259 f = os.fdopen(fd, r'wb')
1258 f = os.fdopen(fd, r'wb')
1260 f.write(util.tonativeeol(text))
1259 f.write(util.tonativeeol(text))
1261 f.close()
1260 f.close()
1262
1261
1263 environ = {'HGUSER': user}
1262 environ = {'HGUSER': user}
1264 if 'transplant_source' in extra:
1263 if 'transplant_source' in extra:
1265 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1264 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1266 for label in ('intermediate-source', 'source', 'rebase_source'):
1265 for label in ('intermediate-source', 'source', 'rebase_source'):
1267 if label in extra:
1266 if label in extra:
1268 environ.update({'HGREVISION': extra[label]})
1267 environ.update({'HGREVISION': extra[label]})
1269 break
1268 break
1270 if editform:
1269 if editform:
1271 environ.update({'HGEDITFORM': editform})
1270 environ.update({'HGEDITFORM': editform})
1272 if pending:
1271 if pending:
1273 environ.update({'HG_PENDING': pending})
1272 environ.update({'HG_PENDING': pending})
1274
1273
1275 editor = self.geteditor()
1274 editor = self.geteditor()
1276
1275
1277 self.system("%s \"%s\"" % (editor, name),
1276 self.system("%s \"%s\"" % (editor, name),
1278 environ=environ,
1277 environ=environ,
1279 onerr=error.Abort, errprefix=_("edit failed"),
1278 onerr=error.Abort, errprefix=_("edit failed"),
1280 blockedtag='editor')
1279 blockedtag='editor')
1281
1280
1282 f = open(name, r'rb')
1281 f = open(name, r'rb')
1283 t = util.fromnativeeol(f.read())
1282 t = util.fromnativeeol(f.read())
1284 f.close()
1283 f.close()
1285 finally:
1284 finally:
1286 os.unlink(name)
1285 os.unlink(name)
1287
1286
1288 return t
1287 return t
1289
1288
1290 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1289 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1291 blockedtag=None):
1290 blockedtag=None):
1292 '''execute shell command with appropriate output stream. command
1291 '''execute shell command with appropriate output stream. command
1293 output will be redirected if fout is not stdout.
1292 output will be redirected if fout is not stdout.
1294
1293
1295 if command fails and onerr is None, return status, else raise onerr
1294 if command fails and onerr is None, return status, else raise onerr
1296 object as exception.
1295 object as exception.
1297 '''
1296 '''
1298 if blockedtag is None:
1297 if blockedtag is None:
1299 # Long cmds tend to be because of an absolute path on cmd. Keep
1298 # Long cmds tend to be because of an absolute path on cmd. Keep
1300 # the tail end instead
1299 # the tail end instead
1301 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1300 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1302 blockedtag = 'unknown_system_' + cmdsuffix
1301 blockedtag = 'unknown_system_' + cmdsuffix
1303 out = self.fout
1302 out = self.fout
1304 if any(s[1] for s in self._bufferstates):
1303 if any(s[1] for s in self._bufferstates):
1305 out = self
1304 out = self
1306 with self.timeblockedsection(blockedtag):
1305 with self.timeblockedsection(blockedtag):
1307 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1306 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1308 if rc and onerr:
1307 if rc and onerr:
1309 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1308 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1310 util.explainexit(rc)[0])
1309 util.explainexit(rc)[0])
1311 if errprefix:
1310 if errprefix:
1312 errmsg = '%s: %s' % (errprefix, errmsg)
1311 errmsg = '%s: %s' % (errprefix, errmsg)
1313 raise onerr(errmsg)
1312 raise onerr(errmsg)
1314 return rc
1313 return rc
1315
1314
1316 def _runsystem(self, cmd, environ, cwd, out):
1315 def _runsystem(self, cmd, environ, cwd, out):
1317 """actually execute the given shell command (can be overridden by
1316 """actually execute the given shell command (can be overridden by
1318 extensions like chg)"""
1317 extensions like chg)"""
1319 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1318 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1320
1319
1321 def traceback(self, exc=None, force=False):
1320 def traceback(self, exc=None, force=False):
1322 '''print exception traceback if traceback printing enabled or forced.
1321 '''print exception traceback if traceback printing enabled or forced.
1323 only to call in exception handler. returns true if traceback
1322 only to call in exception handler. returns true if traceback
1324 printed.'''
1323 printed.'''
1325 if self.tracebackflag or force:
1324 if self.tracebackflag or force:
1326 if exc is None:
1325 if exc is None:
1327 exc = sys.exc_info()
1326 exc = sys.exc_info()
1328 cause = getattr(exc[1], 'cause', None)
1327 cause = getattr(exc[1], 'cause', None)
1329
1328
1330 if cause is not None:
1329 if cause is not None:
1331 causetb = traceback.format_tb(cause[2])
1330 causetb = traceback.format_tb(cause[2])
1332 exctb = traceback.format_tb(exc[2])
1331 exctb = traceback.format_tb(exc[2])
1333 exconly = traceback.format_exception_only(cause[0], cause[1])
1332 exconly = traceback.format_exception_only(cause[0], cause[1])
1334
1333
1335 # exclude frame where 'exc' was chained and rethrown from exctb
1334 # exclude frame where 'exc' was chained and rethrown from exctb
1336 self.write_err('Traceback (most recent call last):\n',
1335 self.write_err('Traceback (most recent call last):\n',
1337 ''.join(exctb[:-1]),
1336 ''.join(exctb[:-1]),
1338 ''.join(causetb),
1337 ''.join(causetb),
1339 ''.join(exconly))
1338 ''.join(exconly))
1340 else:
1339 else:
1341 output = traceback.format_exception(exc[0], exc[1], exc[2])
1340 output = traceback.format_exception(exc[0], exc[1], exc[2])
1342 data = r''.join(output)
1341 data = r''.join(output)
1343 if pycompat.ispy3:
1342 if pycompat.ispy3:
1344 enc = pycompat.sysstr(encoding.encoding)
1343 enc = pycompat.sysstr(encoding.encoding)
1345 data = data.encode(enc, errors=r'replace')
1344 data = data.encode(enc, errors=r'replace')
1346 self.write_err(data)
1345 self.write_err(data)
1347 return self.tracebackflag or force
1346 return self.tracebackflag or force
1348
1347
1349 def geteditor(self):
1348 def geteditor(self):
1350 '''return editor to use'''
1349 '''return editor to use'''
1351 if pycompat.sysplatform == 'plan9':
1350 if pycompat.sysplatform == 'plan9':
1352 # vi is the MIPS instruction simulator on Plan 9. We
1351 # vi is the MIPS instruction simulator on Plan 9. We
1353 # instead default to E to plumb commit messages to
1352 # instead default to E to plumb commit messages to
1354 # avoid confusion.
1353 # avoid confusion.
1355 editor = 'E'
1354 editor = 'E'
1356 else:
1355 else:
1357 editor = 'vi'
1356 editor = 'vi'
1358 return (encoding.environ.get("HGEDITOR") or
1357 return (encoding.environ.get("HGEDITOR") or
1359 self.config("ui", "editor", editor))
1358 self.config("ui", "editor", editor))
1360
1359
1361 @util.propertycache
1360 @util.propertycache
1362 def _progbar(self):
1361 def _progbar(self):
1363 """setup the progbar singleton to the ui object"""
1362 """setup the progbar singleton to the ui object"""
1364 if (self.quiet or self.debugflag
1363 if (self.quiet or self.debugflag
1365 or self.configbool('progress', 'disable', False)
1364 or self.configbool('progress', 'disable', False)
1366 or not progress.shouldprint(self)):
1365 or not progress.shouldprint(self)):
1367 return None
1366 return None
1368 return getprogbar(self)
1367 return getprogbar(self)
1369
1368
1370 def _progclear(self):
1369 def _progclear(self):
1371 """clear progress bar output if any. use it before any output"""
1370 """clear progress bar output if any. use it before any output"""
1372 if '_progbar' not in vars(self): # nothing loaded yet
1371 if '_progbar' not in vars(self): # nothing loaded yet
1373 return
1372 return
1374 if self._progbar is not None and self._progbar.printed:
1373 if self._progbar is not None and self._progbar.printed:
1375 self._progbar.clear()
1374 self._progbar.clear()
1376
1375
1377 def progress(self, topic, pos, item="", unit="", total=None):
1376 def progress(self, topic, pos, item="", unit="", total=None):
1378 '''show a progress message
1377 '''show a progress message
1379
1378
1380 By default a textual progress bar will be displayed if an operation
1379 By default a textual progress bar will be displayed if an operation
1381 takes too long. 'topic' is the current operation, 'item' is a
1380 takes too long. 'topic' is the current operation, 'item' is a
1382 non-numeric marker of the current position (i.e. the currently
1381 non-numeric marker of the current position (i.e. the currently
1383 in-process file), 'pos' is the current numeric position (i.e.
1382 in-process file), 'pos' is the current numeric position (i.e.
1384 revision, bytes, etc.), unit is a corresponding unit label,
1383 revision, bytes, etc.), unit is a corresponding unit label,
1385 and total is the highest expected pos.
1384 and total is the highest expected pos.
1386
1385
1387 Multiple nested topics may be active at a time.
1386 Multiple nested topics may be active at a time.
1388
1387
1389 All topics should be marked closed by setting pos to None at
1388 All topics should be marked closed by setting pos to None at
1390 termination.
1389 termination.
1391 '''
1390 '''
1392 if self._progbar is not None:
1391 if self._progbar is not None:
1393 self._progbar.progress(topic, pos, item=item, unit=unit,
1392 self._progbar.progress(topic, pos, item=item, unit=unit,
1394 total=total)
1393 total=total)
1395 if pos is None or not self.configbool('progress', 'debug'):
1394 if pos is None or not self.configbool('progress', 'debug'):
1396 return
1395 return
1397
1396
1398 if unit:
1397 if unit:
1399 unit = ' ' + unit
1398 unit = ' ' + unit
1400 if item:
1399 if item:
1401 item = ' ' + item
1400 item = ' ' + item
1402
1401
1403 if total:
1402 if total:
1404 pct = 100.0 * pos / total
1403 pct = 100.0 * pos / total
1405 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1404 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1406 % (topic, item, pos, total, unit, pct))
1405 % (topic, item, pos, total, unit, pct))
1407 else:
1406 else:
1408 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1407 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1409
1408
1410 def log(self, service, *msg, **opts):
1409 def log(self, service, *msg, **opts):
1411 '''hook for logging facility extensions
1410 '''hook for logging facility extensions
1412
1411
1413 service should be a readily-identifiable subsystem, which will
1412 service should be a readily-identifiable subsystem, which will
1414 allow filtering.
1413 allow filtering.
1415
1414
1416 *msg should be a newline-terminated format string to log, and
1415 *msg should be a newline-terminated format string to log, and
1417 then any values to %-format into that format string.
1416 then any values to %-format into that format string.
1418
1417
1419 **opts currently has no defined meanings.
1418 **opts currently has no defined meanings.
1420 '''
1419 '''
1421
1420
1422 def label(self, msg, label):
1421 def label(self, msg, label):
1423 '''style msg based on supplied label
1422 '''style msg based on supplied label
1424
1423
1425 If some color mode is enabled, this will add the necessary control
1424 If some color mode is enabled, this will add the necessary control
1426 characters to apply such color. In addition, 'debug' color mode adds
1425 characters to apply such color. In addition, 'debug' color mode adds
1427 markup showing which label affects a piece of text.
1426 markup showing which label affects a piece of text.
1428
1427
1429 ui.write(s, 'label') is equivalent to
1428 ui.write(s, 'label') is equivalent to
1430 ui.write(ui.label(s, 'label')).
1429 ui.write(ui.label(s, 'label')).
1431 '''
1430 '''
1432 if self._colormode is not None:
1431 if self._colormode is not None:
1433 return color.colorlabel(self, msg, label)
1432 return color.colorlabel(self, msg, label)
1434 return msg
1433 return msg
1435
1434
1436 def develwarn(self, msg, stacklevel=1, config=None):
1435 def develwarn(self, msg, stacklevel=1, config=None):
1437 """issue a developer warning message
1436 """issue a developer warning message
1438
1437
1439 Use 'stacklevel' to report the offender some layers further up in the
1438 Use 'stacklevel' to report the offender some layers further up in the
1440 stack.
1439 stack.
1441 """
1440 """
1442 if not self.configbool('devel', 'all-warnings'):
1441 if not self.configbool('devel', 'all-warnings'):
1443 if config is not None and not self.configbool('devel', config):
1442 if config is not None and not self.configbool('devel', config):
1444 return
1443 return
1445 msg = 'devel-warn: ' + msg
1444 msg = 'devel-warn: ' + msg
1446 stacklevel += 1 # get in develwarn
1445 stacklevel += 1 # get in develwarn
1447 if self.tracebackflag:
1446 if self.tracebackflag:
1448 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1447 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1449 self.log('develwarn', '%s at:\n%s' %
1448 self.log('develwarn', '%s at:\n%s' %
1450 (msg, ''.join(util.getstackframes(stacklevel))))
1449 (msg, ''.join(util.getstackframes(stacklevel))))
1451 else:
1450 else:
1452 curframe = inspect.currentframe()
1451 curframe = inspect.currentframe()
1453 calframe = inspect.getouterframes(curframe, 2)
1452 calframe = inspect.getouterframes(curframe, 2)
1454 self.write_err('%s at: %s:%s (%s)\n'
1453 self.write_err('%s at: %s:%s (%s)\n'
1455 % ((msg,) + calframe[stacklevel][1:4]))
1454 % ((msg,) + calframe[stacklevel][1:4]))
1456 self.log('develwarn', '%s at: %s:%s (%s)\n',
1455 self.log('develwarn', '%s at: %s:%s (%s)\n',
1457 msg, *calframe[stacklevel][1:4])
1456 msg, *calframe[stacklevel][1:4])
1458 curframe = calframe = None # avoid cycles
1457 curframe = calframe = None # avoid cycles
1459
1458
1460 def deprecwarn(self, msg, version):
1459 def deprecwarn(self, msg, version):
1461 """issue a deprecation warning
1460 """issue a deprecation warning
1462
1461
1463 - msg: message explaining what is deprecated and how to upgrade,
1462 - msg: message explaining what is deprecated and how to upgrade,
1464 - version: last version where the API will be supported,
1463 - version: last version where the API will be supported,
1465 """
1464 """
1466 if not (self.configbool('devel', 'all-warnings')
1465 if not (self.configbool('devel', 'all-warnings')
1467 or self.configbool('devel', 'deprec-warn')):
1466 or self.configbool('devel', 'deprec-warn')):
1468 return
1467 return
1469 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1468 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1470 " update your code.)") % version
1469 " update your code.)") % version
1471 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1470 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1472
1471
1473 def exportableenviron(self):
1472 def exportableenviron(self):
1474 """The environment variables that are safe to export, e.g. through
1473 """The environment variables that are safe to export, e.g. through
1475 hgweb.
1474 hgweb.
1476 """
1475 """
1477 return self._exportableenviron
1476 return self._exportableenviron
1478
1477
1479 @contextlib.contextmanager
1478 @contextlib.contextmanager
1480 def configoverride(self, overrides, source=""):
1479 def configoverride(self, overrides, source=""):
1481 """Context manager for temporary config overrides
1480 """Context manager for temporary config overrides
1482 `overrides` must be a dict of the following structure:
1481 `overrides` must be a dict of the following structure:
1483 {(section, name) : value}"""
1482 {(section, name) : value}"""
1484 backups = {}
1483 backups = {}
1485 try:
1484 try:
1486 for (section, name), value in overrides.items():
1485 for (section, name), value in overrides.items():
1487 backups[(section, name)] = self.backupconfig(section, name)
1486 backups[(section, name)] = self.backupconfig(section, name)
1488 self.setconfig(section, name, value, source)
1487 self.setconfig(section, name, value, source)
1489 yield
1488 yield
1490 finally:
1489 finally:
1491 for __, backup in backups.items():
1490 for __, backup in backups.items():
1492 self.restoreconfig(backup)
1491 self.restoreconfig(backup)
1493 # just restoring ui.quiet config to the previous value is not enough
1492 # just restoring ui.quiet config to the previous value is not enough
1494 # as it does not update ui.quiet class member
1493 # as it does not update ui.quiet class member
1495 if ('ui', 'quiet') in overrides:
1494 if ('ui', 'quiet') in overrides:
1496 self.fixconfig(section='ui')
1495 self.fixconfig(section='ui')
1497
1496
1498 class paths(dict):
1497 class paths(dict):
1499 """Represents a collection of paths and their configs.
1498 """Represents a collection of paths and their configs.
1500
1499
1501 Data is initially derived from ui instances and the config files they have
1500 Data is initially derived from ui instances and the config files they have
1502 loaded.
1501 loaded.
1503 """
1502 """
1504 def __init__(self, ui):
1503 def __init__(self, ui):
1505 dict.__init__(self)
1504 dict.__init__(self)
1506
1505
1507 for name, loc in ui.configitems('paths', ignoresub=True):
1506 for name, loc in ui.configitems('paths', ignoresub=True):
1508 # No location is the same as not existing.
1507 # No location is the same as not existing.
1509 if not loc:
1508 if not loc:
1510 continue
1509 continue
1511 loc, sub = ui.configsuboptions('paths', name)
1510 loc, sub = ui.configsuboptions('paths', name)
1512 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1511 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1513
1512
1514 def getpath(self, name, default=None):
1513 def getpath(self, name, default=None):
1515 """Return a ``path`` from a string, falling back to default.
1514 """Return a ``path`` from a string, falling back to default.
1516
1515
1517 ``name`` can be a named path or locations. Locations are filesystem
1516 ``name`` can be a named path or locations. Locations are filesystem
1518 paths or URIs.
1517 paths or URIs.
1519
1518
1520 Returns None if ``name`` is not a registered path, a URI, or a local
1519 Returns None if ``name`` is not a registered path, a URI, or a local
1521 path to a repo.
1520 path to a repo.
1522 """
1521 """
1523 # Only fall back to default if no path was requested.
1522 # Only fall back to default if no path was requested.
1524 if name is None:
1523 if name is None:
1525 if not default:
1524 if not default:
1526 default = ()
1525 default = ()
1527 elif not isinstance(default, (tuple, list)):
1526 elif not isinstance(default, (tuple, list)):
1528 default = (default,)
1527 default = (default,)
1529 for k in default:
1528 for k in default:
1530 try:
1529 try:
1531 return self[k]
1530 return self[k]
1532 except KeyError:
1531 except KeyError:
1533 continue
1532 continue
1534 return None
1533 return None
1535
1534
1536 # Most likely empty string.
1535 # Most likely empty string.
1537 # This may need to raise in the future.
1536 # This may need to raise in the future.
1538 if not name:
1537 if not name:
1539 return None
1538 return None
1540
1539
1541 try:
1540 try:
1542 return self[name]
1541 return self[name]
1543 except KeyError:
1542 except KeyError:
1544 # Try to resolve as a local path or URI.
1543 # Try to resolve as a local path or URI.
1545 try:
1544 try:
1546 # We don't pass sub-options in, so no need to pass ui instance.
1545 # We don't pass sub-options in, so no need to pass ui instance.
1547 return path(None, None, rawloc=name)
1546 return path(None, None, rawloc=name)
1548 except ValueError:
1547 except ValueError:
1549 raise error.RepoError(_('repository %s does not exist') %
1548 raise error.RepoError(_('repository %s does not exist') %
1550 name)
1549 name)
1551
1550
1552 _pathsuboptions = {}
1551 _pathsuboptions = {}
1553
1552
1554 def pathsuboption(option, attr):
1553 def pathsuboption(option, attr):
1555 """Decorator used to declare a path sub-option.
1554 """Decorator used to declare a path sub-option.
1556
1555
1557 Arguments are the sub-option name and the attribute it should set on
1556 Arguments are the sub-option name and the attribute it should set on
1558 ``path`` instances.
1557 ``path`` instances.
1559
1558
1560 The decorated function will receive as arguments a ``ui`` instance,
1559 The decorated function will receive as arguments a ``ui`` instance,
1561 ``path`` instance, and the string value of this option from the config.
1560 ``path`` instance, and the string value of this option from the config.
1562 The function should return the value that will be set on the ``path``
1561 The function should return the value that will be set on the ``path``
1563 instance.
1562 instance.
1564
1563
1565 This decorator can be used to perform additional verification of
1564 This decorator can be used to perform additional verification of
1566 sub-options and to change the type of sub-options.
1565 sub-options and to change the type of sub-options.
1567 """
1566 """
1568 def register(func):
1567 def register(func):
1569 _pathsuboptions[option] = (attr, func)
1568 _pathsuboptions[option] = (attr, func)
1570 return func
1569 return func
1571 return register
1570 return register
1572
1571
1573 @pathsuboption('pushurl', 'pushloc')
1572 @pathsuboption('pushurl', 'pushloc')
1574 def pushurlpathoption(ui, path, value):
1573 def pushurlpathoption(ui, path, value):
1575 u = util.url(value)
1574 u = util.url(value)
1576 # Actually require a URL.
1575 # Actually require a URL.
1577 if not u.scheme:
1576 if not u.scheme:
1578 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1577 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1579 return None
1578 return None
1580
1579
1581 # Don't support the #foo syntax in the push URL to declare branch to
1580 # Don't support the #foo syntax in the push URL to declare branch to
1582 # push.
1581 # push.
1583 if u.fragment:
1582 if u.fragment:
1584 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1583 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1585 'ignoring)\n') % path.name)
1584 'ignoring)\n') % path.name)
1586 u.fragment = None
1585 u.fragment = None
1587
1586
1588 return str(u)
1587 return str(u)
1589
1588
1590 @pathsuboption('pushrev', 'pushrev')
1589 @pathsuboption('pushrev', 'pushrev')
1591 def pushrevpathoption(ui, path, value):
1590 def pushrevpathoption(ui, path, value):
1592 return value
1591 return value
1593
1592
1594 class path(object):
1593 class path(object):
1595 """Represents an individual path and its configuration."""
1594 """Represents an individual path and its configuration."""
1596
1595
1597 def __init__(self, ui, name, rawloc=None, suboptions=None):
1596 def __init__(self, ui, name, rawloc=None, suboptions=None):
1598 """Construct a path from its config options.
1597 """Construct a path from its config options.
1599
1598
1600 ``ui`` is the ``ui`` instance the path is coming from.
1599 ``ui`` is the ``ui`` instance the path is coming from.
1601 ``name`` is the symbolic name of the path.
1600 ``name`` is the symbolic name of the path.
1602 ``rawloc`` is the raw location, as defined in the config.
1601 ``rawloc`` is the raw location, as defined in the config.
1603 ``pushloc`` is the raw locations pushes should be made to.
1602 ``pushloc`` is the raw locations pushes should be made to.
1604
1603
1605 If ``name`` is not defined, we require that the location be a) a local
1604 If ``name`` is not defined, we require that the location be a) a local
1606 filesystem path with a .hg directory or b) a URL. If not,
1605 filesystem path with a .hg directory or b) a URL. If not,
1607 ``ValueError`` is raised.
1606 ``ValueError`` is raised.
1608 """
1607 """
1609 if not rawloc:
1608 if not rawloc:
1610 raise ValueError('rawloc must be defined')
1609 raise ValueError('rawloc must be defined')
1611
1610
1612 # Locations may define branches via syntax <base>#<branch>.
1611 # Locations may define branches via syntax <base>#<branch>.
1613 u = util.url(rawloc)
1612 u = util.url(rawloc)
1614 branch = None
1613 branch = None
1615 if u.fragment:
1614 if u.fragment:
1616 branch = u.fragment
1615 branch = u.fragment
1617 u.fragment = None
1616 u.fragment = None
1618
1617
1619 self.url = u
1618 self.url = u
1620 self.branch = branch
1619 self.branch = branch
1621
1620
1622 self.name = name
1621 self.name = name
1623 self.rawloc = rawloc
1622 self.rawloc = rawloc
1624 self.loc = '%s' % u
1623 self.loc = '%s' % u
1625
1624
1626 # When given a raw location but not a symbolic name, validate the
1625 # When given a raw location but not a symbolic name, validate the
1627 # location is valid.
1626 # location is valid.
1628 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1627 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1629 raise ValueError('location is not a URL or path to a local '
1628 raise ValueError('location is not a URL or path to a local '
1630 'repo: %s' % rawloc)
1629 'repo: %s' % rawloc)
1631
1630
1632 suboptions = suboptions or {}
1631 suboptions = suboptions or {}
1633
1632
1634 # Now process the sub-options. If a sub-option is registered, its
1633 # Now process the sub-options. If a sub-option is registered, its
1635 # attribute will always be present. The value will be None if there
1634 # attribute will always be present. The value will be None if there
1636 # was no valid sub-option.
1635 # was no valid sub-option.
1637 for suboption, (attr, func) in _pathsuboptions.iteritems():
1636 for suboption, (attr, func) in _pathsuboptions.iteritems():
1638 if suboption not in suboptions:
1637 if suboption not in suboptions:
1639 setattr(self, attr, None)
1638 setattr(self, attr, None)
1640 continue
1639 continue
1641
1640
1642 value = func(ui, self, suboptions[suboption])
1641 value = func(ui, self, suboptions[suboption])
1643 setattr(self, attr, value)
1642 setattr(self, attr, value)
1644
1643
1645 def _isvalidlocalpath(self, path):
1644 def _isvalidlocalpath(self, path):
1646 """Returns True if the given path is a potentially valid repository.
1645 """Returns True if the given path is a potentially valid repository.
1647 This is its own function so that extensions can change the definition of
1646 This is its own function so that extensions can change the definition of
1648 'valid' in this case (like when pulling from a git repo into a hg
1647 'valid' in this case (like when pulling from a git repo into a hg
1649 one)."""
1648 one)."""
1650 return os.path.isdir(os.path.join(path, '.hg'))
1649 return os.path.isdir(os.path.join(path, '.hg'))
1651
1650
1652 @property
1651 @property
1653 def suboptions(self):
1652 def suboptions(self):
1654 """Return sub-options and their values for this path.
1653 """Return sub-options and their values for this path.
1655
1654
1656 This is intended to be used for presentation purposes.
1655 This is intended to be used for presentation purposes.
1657 """
1656 """
1658 d = {}
1657 d = {}
1659 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1658 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1660 value = getattr(self, attr)
1659 value = getattr(self, attr)
1661 if value is not None:
1660 if value is not None:
1662 d[subopt] = value
1661 d[subopt] = value
1663 return d
1662 return d
1664
1663
1665 # we instantiate one globally shared progress bar to avoid
1664 # we instantiate one globally shared progress bar to avoid
1666 # competing progress bars when multiple UI objects get created
1665 # competing progress bars when multiple UI objects get created
1667 _progresssingleton = None
1666 _progresssingleton = None
1668
1667
1669 def getprogbar(ui):
1668 def getprogbar(ui):
1670 global _progresssingleton
1669 global _progresssingleton
1671 if _progresssingleton is None:
1670 if _progresssingleton is None:
1672 # passing 'ui' object to the singleton is fishy,
1671 # passing 'ui' object to the singleton is fishy,
1673 # this is how the extension used to work but feel free to rework it.
1672 # this is how the extension used to work but feel free to rework it.
1674 _progresssingleton = progress.progbar(ui)
1673 _progresssingleton = progress.progbar(ui)
1675 return _progresssingleton
1674 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now