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