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