##// END OF EJS Templates
color: move 'write' logic to the core ui class...
Pierre-Yves David -
r31091:ad074f90 default
parent child Browse files
Show More
@@ -1,401 +1,386 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 def write(self, *args, **opts):
301 if self._colormode is None:
302 return super(colorui, self).write(*args, **opts)
303
304 label = opts.get('label', '')
305 if self._buffers and not opts.get('prompt', False):
306 if self._bufferapplylabels:
307 self._buffers[-1].extend(self.label(a, label) for a in args)
308 else:
309 self._buffers[-1].extend(args)
310 elif self._colormode == 'win32':
311 color.win32print(super(colorui, self).write, *args, **opts)
312 else:
313 return super(colorui, self).write(
314 *[self.label(a, label) for a in args], **opts)
315
300
316 def write_err(self, *args, **opts):
301 def write_err(self, *args, **opts):
317 if self._colormode is None:
302 if self._colormode is None:
318 return super(colorui, self).write_err(*args, **opts)
303 return super(colorui, self).write_err(*args, **opts)
319
304
320 label = opts.get('label', '')
305 label = opts.get('label', '')
321 if self._bufferstates and self._bufferstates[-1][0]:
306 if self._bufferstates and self._bufferstates[-1][0]:
322 return self.write(*args, **opts)
307 return self.write(*args, **opts)
323 if self._colormode == 'win32':
308 if self._colormode == 'win32':
324 color.win32print(super(colorui, self).write_err, *args, **opts)
309 color.win32print(super(colorui, self).write_err, *args, **opts)
325 else:
310 else:
326 return super(colorui, self).write_err(
311 return super(colorui, self).write_err(
327 *[self.label(a, label) for a in args], **opts)
312 *[self.label(a, label) for a in args], **opts)
328
313
329 def uisetup(ui):
314 def uisetup(ui):
330 if ui.plain():
315 if ui.plain():
331 return
316 return
332 if not isinstance(ui, colorui):
317 if not isinstance(ui, colorui):
333 colorui.__bases__ = (ui.__class__,)
318 colorui.__bases__ = (ui.__class__,)
334 ui.__class__ = colorui
319 ui.__class__ = colorui
335 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
320 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
336 mode = _modesetup(ui_, opts['color'])
321 mode = _modesetup(ui_, opts['color'])
337 colorui._colormode = mode
322 colorui._colormode = mode
338 if mode and mode != 'debug':
323 if mode and mode != 'debug':
339 color.configstyles(ui_)
324 color.configstyles(ui_)
340 return orig(ui_, opts, cmd, cmdfunc)
325 return orig(ui_, opts, cmd, cmdfunc)
341 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
326 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
342 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
327 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
343 # insert the argument in the front,
328 # insert the argument in the front,
344 # the end of git diff arguments is used for paths
329 # the end of git diff arguments is used for paths
345 commands.insert(1, '--color')
330 commands.insert(1, '--color')
346 return orig(gitsub, commands, env, stream, cwd)
331 return orig(gitsub, commands, env, stream, cwd)
347 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
332 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
348 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
333 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
349
334
350 def extsetup(ui):
335 def extsetup(ui):
351 commands.globalopts.append(
336 commands.globalopts.append(
352 ('', 'color', 'auto',
337 ('', 'color', 'auto',
353 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
338 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
354 # and should not be translated
339 # and should not be translated
355 _("when to colorize (boolean, always, auto, never, or debug)"),
340 _("when to colorize (boolean, always, auto, never, or debug)"),
356 _('TYPE')))
341 _('TYPE')))
357
342
358 @command('debugcolor',
343 @command('debugcolor',
359 [('', 'style', None, _('show all configured styles'))],
344 [('', 'style', None, _('show all configured styles'))],
360 'hg debugcolor')
345 'hg debugcolor')
361 def debugcolor(ui, repo, **opts):
346 def debugcolor(ui, repo, **opts):
362 """show available color, effects or style"""
347 """show available color, effects or style"""
363 ui.write(('color mode: %s\n') % ui._colormode)
348 ui.write(('color mode: %s\n') % ui._colormode)
364 if opts.get('style'):
349 if opts.get('style'):
365 return _debugdisplaystyle(ui)
350 return _debugdisplaystyle(ui)
366 else:
351 else:
367 return _debugdisplaycolor(ui)
352 return _debugdisplaycolor(ui)
368
353
369 def _debugdisplaycolor(ui):
354 def _debugdisplaycolor(ui):
370 oldstyle = color._styles.copy()
355 oldstyle = color._styles.copy()
371 try:
356 try:
372 color._styles.clear()
357 color._styles.clear()
373 for effect in color._effects.keys():
358 for effect in color._effects.keys():
374 color._styles[effect] = effect
359 color._styles[effect] = effect
375 if color._terminfo_params:
360 if color._terminfo_params:
376 for k, v in ui.configitems('color'):
361 for k, v in ui.configitems('color'):
377 if k.startswith('color.'):
362 if k.startswith('color.'):
378 color._styles[k] = k[6:]
363 color._styles[k] = k[6:]
379 elif k.startswith('terminfo.'):
364 elif k.startswith('terminfo.'):
380 color._styles[k] = k[9:]
365 color._styles[k] = k[9:]
381 ui.write(_('available colors:\n'))
366 ui.write(_('available colors:\n'))
382 # sort label with a '_' after the other to group '_background' entry.
367 # sort label with a '_' after the other to group '_background' entry.
383 items = sorted(color._styles.items(),
368 items = sorted(color._styles.items(),
384 key=lambda i: ('_' in i[0], i[0], i[1]))
369 key=lambda i: ('_' in i[0], i[0], i[1]))
385 for colorname, label in items:
370 for colorname, label in items:
386 ui.write(('%s\n') % colorname, label=label)
371 ui.write(('%s\n') % colorname, label=label)
387 finally:
372 finally:
388 color._styles.clear()
373 color._styles.clear()
389 color._styles.update(oldstyle)
374 color._styles.update(oldstyle)
390
375
391 def _debugdisplaystyle(ui):
376 def _debugdisplaystyle(ui):
392 ui.write(_('available style:\n'))
377 ui.write(_('available style:\n'))
393 width = max(len(s) for s in color._styles)
378 width = max(len(s) for s in color._styles)
394 for label, effects in sorted(color._styles.items()):
379 for label, effects in sorted(color._styles.items()):
395 ui.write('%s' % label, label=label)
380 ui.write('%s' % label, label=label)
396 if effects:
381 if effects:
397 # 50
382 # 50
398 ui.write(': ')
383 ui.write(': ')
399 ui.write(' ' * (max(0, width - len(label))))
384 ui.write(' ' * (max(0, width - len(label))))
400 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
385 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
401 ui.write('\n')
386 ui.write('\n')
@@ -1,1621 +1,1632 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 but extensions or GUI tools may override this method,
783 Color mode can be set on the UI class to have the output decorated
784 write_err(), popbuffer(), and label() to style output from
784 with color modifier before being written to stdout.
785 various parts of hg.
786
785
787 An optional keyword argument, "label", can be passed in.
786 The color used is controlled by an optional keyword argument, "label".
788 This should be a string containing label names separated by
787 This should be a string containing label names separated by space.
789 space. Label names take the form of "topic.type". For example,
788 Label names take the form of "topic.type". For example, ui.debug()
790 ui.debug() issues a label of "ui.debug".
789 issues a label of "ui.debug".
791
790
792 When labeling output for a specific command, a label of
791 When labeling output for a specific command, a label of
793 "cmdname.type" is recommended. For example, status issues
792 "cmdname.type" is recommended. For example, status issues
794 a label of "status.modified" for modified files.
793 a label of "status.modified" for modified files.
795 '''
794 '''
796 if self._buffers and not opts.get('prompt', False):
795 if self._buffers and not opts.get('prompt', False):
797 self._buffers[-1].extend(a for a in args)
796 if self._bufferapplylabels:
797 label = opts.get('label', '')
798 self._buffers[-1].extend(self.label(a, label) for a in args)
799 else:
800 self._buffers[-1].extend(args)
801 elif self._colormode == 'win32':
802 # windows color printing is its own can of crab, defer to
803 # the color module and that is it.
804 color.win32print(self._write, *args, **opts)
798 else:
805 else:
799 self._write(*args, **opts)
806 msgs = args
807 if self._colormode is not None:
808 label = opts.get('label', '')
809 msgs = [self.label(a, label) for a in args]
810 self._write(*msgs, **opts)
800
811
801 def _write(self, *msgs, **opts):
812 def _write(self, *msgs, **opts):
802 self._progclear()
813 self._progclear()
803 # opencode timeblockedsection because this is a critical path
814 # opencode timeblockedsection because this is a critical path
804 starttime = util.timer()
815 starttime = util.timer()
805 try:
816 try:
806 for a in msgs:
817 for a in msgs:
807 self.fout.write(a)
818 self.fout.write(a)
808 finally:
819 finally:
809 self._blockedtimes['stdio_blocked'] += \
820 self._blockedtimes['stdio_blocked'] += \
810 (util.timer() - starttime) * 1000
821 (util.timer() - starttime) * 1000
811
822
812 def write_err(self, *args, **opts):
823 def write_err(self, *args, **opts):
813 self._progclear()
824 self._progclear()
814 try:
825 try:
815 if self._bufferstates and self._bufferstates[-1][0]:
826 if self._bufferstates and self._bufferstates[-1][0]:
816 return self.write(*args, **opts)
827 return self.write(*args, **opts)
817 with self.timeblockedsection('stdio'):
828 with self.timeblockedsection('stdio'):
818 if not getattr(self.fout, 'closed', False):
829 if not getattr(self.fout, 'closed', False):
819 self.fout.flush()
830 self.fout.flush()
820 for a in args:
831 for a in args:
821 self.ferr.write(a)
832 self.ferr.write(a)
822 # stderr may be buffered under win32 when redirected to files,
833 # stderr may be buffered under win32 when redirected to files,
823 # including stdout.
834 # including stdout.
824 if not getattr(self.ferr, 'closed', False):
835 if not getattr(self.ferr, 'closed', False):
825 self.ferr.flush()
836 self.ferr.flush()
826 except IOError as inst:
837 except IOError as inst:
827 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
838 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
828 raise
839 raise
829
840
830 def flush(self):
841 def flush(self):
831 # opencode timeblockedsection because this is a critical path
842 # opencode timeblockedsection because this is a critical path
832 starttime = util.timer()
843 starttime = util.timer()
833 try:
844 try:
834 try: self.fout.flush()
845 try: self.fout.flush()
835 except (IOError, ValueError): pass
846 except (IOError, ValueError): pass
836 try: self.ferr.flush()
847 try: self.ferr.flush()
837 except (IOError, ValueError): pass
848 except (IOError, ValueError): pass
838 finally:
849 finally:
839 self._blockedtimes['stdio_blocked'] += \
850 self._blockedtimes['stdio_blocked'] += \
840 (util.timer() - starttime) * 1000
851 (util.timer() - starttime) * 1000
841
852
842 def _isatty(self, fh):
853 def _isatty(self, fh):
843 if self.configbool('ui', 'nontty', False):
854 if self.configbool('ui', 'nontty', False):
844 return False
855 return False
845 return util.isatty(fh)
856 return util.isatty(fh)
846
857
847 def disablepager(self):
858 def disablepager(self):
848 self._disablepager = True
859 self._disablepager = True
849
860
850 def pager(self, command):
861 def pager(self, command):
851 """Start a pager for subsequent command output.
862 """Start a pager for subsequent command output.
852
863
853 Commands which produce a long stream of output should call
864 Commands which produce a long stream of output should call
854 this function to activate the user's preferred pagination
865 this function to activate the user's preferred pagination
855 mechanism (which may be no pager). Calling this function
866 mechanism (which may be no pager). Calling this function
856 precludes any future use of interactive functionality, such as
867 precludes any future use of interactive functionality, such as
857 prompting the user or activating curses.
868 prompting the user or activating curses.
858
869
859 Args:
870 Args:
860 command: The full, non-aliased name of the command. That is, "log"
871 command: The full, non-aliased name of the command. That is, "log"
861 not "history, "summary" not "summ", etc.
872 not "history, "summary" not "summ", etc.
862 """
873 """
863 if (self._disablepager
874 if (self._disablepager
864 or self.pageractive
875 or self.pageractive
865 or command in self.configlist('pager', 'ignore')
876 or command in self.configlist('pager', 'ignore')
866 or not self.configbool('pager', 'enable', True)
877 or not self.configbool('pager', 'enable', True)
867 or not self.configbool('pager', 'attend-' + command, True)
878 or not self.configbool('pager', 'attend-' + command, True)
868 # TODO: if we want to allow HGPLAINEXCEPT=pager,
879 # TODO: if we want to allow HGPLAINEXCEPT=pager,
869 # formatted() will need some adjustment.
880 # formatted() will need some adjustment.
870 or not self.formatted()
881 or not self.formatted()
871 or self.plain()
882 or self.plain()
872 # TODO: expose debugger-enabled on the UI object
883 # TODO: expose debugger-enabled on the UI object
873 or '--debugger' in sys.argv):
884 or '--debugger' in sys.argv):
874 # We only want to paginate if the ui appears to be
885 # We only want to paginate if the ui appears to be
875 # interactive, the user didn't say HGPLAIN or
886 # interactive, the user didn't say HGPLAIN or
876 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
887 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
877 return
888 return
878
889
879 # TODO: add a "system defaults" config section so this default
890 # TODO: add a "system defaults" config section so this default
880 # of more(1) can be easily replaced with a global
891 # of more(1) can be easily replaced with a global
881 # configuration file. For example, on OS X the sane default is
892 # configuration file. For example, on OS X the sane default is
882 # less(1), not more(1), and on debian it's
893 # less(1), not more(1), and on debian it's
883 # sensible-pager(1). We should probably also give the system
894 # sensible-pager(1). We should probably also give the system
884 # default editor command similar treatment.
895 # default editor command similar treatment.
885 envpager = encoding.environ.get('PAGER', 'more')
896 envpager = encoding.environ.get('PAGER', 'more')
886 pagercmd = self.config('pager', 'pager', envpager)
897 pagercmd = self.config('pager', 'pager', envpager)
887 if not pagercmd:
898 if not pagercmd:
888 return
899 return
889
900
890 self.debug('starting pager for command %r\n' % command)
901 self.debug('starting pager for command %r\n' % command)
891 self.pageractive = True
902 self.pageractive = True
892 # Preserve the formatted-ness of the UI. This is important
903 # Preserve the formatted-ness of the UI. This is important
893 # because we mess with stdout, which might confuse
904 # because we mess with stdout, which might confuse
894 # auto-detection of things being formatted.
905 # auto-detection of things being formatted.
895 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
906 self.setconfig('ui', 'formatted', self.formatted(), 'pager')
896 self.setconfig('ui', 'interactive', False, 'pager')
907 self.setconfig('ui', 'interactive', False, 'pager')
897 if util.safehasattr(signal, "SIGPIPE"):
908 if util.safehasattr(signal, "SIGPIPE"):
898 signal.signal(signal.SIGPIPE, _catchterm)
909 signal.signal(signal.SIGPIPE, _catchterm)
899 self._runpager(pagercmd)
910 self._runpager(pagercmd)
900
911
901 def _runpager(self, command):
912 def _runpager(self, command):
902 """Actually start the pager and set up file descriptors.
913 """Actually start the pager and set up file descriptors.
903
914
904 This is separate in part so that extensions (like chg) can
915 This is separate in part so that extensions (like chg) can
905 override how a pager is invoked.
916 override how a pager is invoked.
906 """
917 """
907 pager = subprocess.Popen(command, shell=True, bufsize=-1,
918 pager = subprocess.Popen(command, shell=True, bufsize=-1,
908 close_fds=util.closefds, stdin=subprocess.PIPE,
919 close_fds=util.closefds, stdin=subprocess.PIPE,
909 stdout=util.stdout, stderr=util.stderr)
920 stdout=util.stdout, stderr=util.stderr)
910
921
911 # back up original file descriptors
922 # back up original file descriptors
912 stdoutfd = os.dup(util.stdout.fileno())
923 stdoutfd = os.dup(util.stdout.fileno())
913 stderrfd = os.dup(util.stderr.fileno())
924 stderrfd = os.dup(util.stderr.fileno())
914
925
915 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
926 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
916 if self._isatty(util.stderr):
927 if self._isatty(util.stderr):
917 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
928 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
918
929
919 @atexit.register
930 @atexit.register
920 def killpager():
931 def killpager():
921 if util.safehasattr(signal, "SIGINT"):
932 if util.safehasattr(signal, "SIGINT"):
922 signal.signal(signal.SIGINT, signal.SIG_IGN)
933 signal.signal(signal.SIGINT, signal.SIG_IGN)
923 # restore original fds, closing pager.stdin copies in the process
934 # restore original fds, closing pager.stdin copies in the process
924 os.dup2(stdoutfd, util.stdout.fileno())
935 os.dup2(stdoutfd, util.stdout.fileno())
925 os.dup2(stderrfd, util.stderr.fileno())
936 os.dup2(stderrfd, util.stderr.fileno())
926 pager.stdin.close()
937 pager.stdin.close()
927 pager.wait()
938 pager.wait()
928
939
929 def interface(self, feature):
940 def interface(self, feature):
930 """what interface to use for interactive console features?
941 """what interface to use for interactive console features?
931
942
932 The interface is controlled by the value of `ui.interface` but also by
943 The interface is controlled by the value of `ui.interface` but also by
933 the value of feature-specific configuration. For example:
944 the value of feature-specific configuration. For example:
934
945
935 ui.interface.histedit = text
946 ui.interface.histedit = text
936 ui.interface.chunkselector = curses
947 ui.interface.chunkselector = curses
937
948
938 Here the features are "histedit" and "chunkselector".
949 Here the features are "histedit" and "chunkselector".
939
950
940 The configuration above means that the default interfaces for commands
951 The configuration above means that the default interfaces for commands
941 is curses, the interface for histedit is text and the interface for
952 is curses, the interface for histedit is text and the interface for
942 selecting chunk is crecord (the best curses interface available).
953 selecting chunk is crecord (the best curses interface available).
943
954
944 Consider the following example:
955 Consider the following example:
945 ui.interface = curses
956 ui.interface = curses
946 ui.interface.histedit = text
957 ui.interface.histedit = text
947
958
948 Then histedit will use the text interface and chunkselector will use
959 Then histedit will use the text interface and chunkselector will use
949 the default curses interface (crecord at the moment).
960 the default curses interface (crecord at the moment).
950 """
961 """
951 alldefaults = frozenset(["text", "curses"])
962 alldefaults = frozenset(["text", "curses"])
952
963
953 featureinterfaces = {
964 featureinterfaces = {
954 "chunkselector": [
965 "chunkselector": [
955 "text",
966 "text",
956 "curses",
967 "curses",
957 ]
968 ]
958 }
969 }
959
970
960 # Feature-specific interface
971 # Feature-specific interface
961 if feature not in featureinterfaces.keys():
972 if feature not in featureinterfaces.keys():
962 # Programming error, not user error
973 # Programming error, not user error
963 raise ValueError("Unknown feature requested %s" % feature)
974 raise ValueError("Unknown feature requested %s" % feature)
964
975
965 availableinterfaces = frozenset(featureinterfaces[feature])
976 availableinterfaces = frozenset(featureinterfaces[feature])
966 if alldefaults > availableinterfaces:
977 if alldefaults > availableinterfaces:
967 # Programming error, not user error. We need a use case to
978 # Programming error, not user error. We need a use case to
968 # define the right thing to do here.
979 # define the right thing to do here.
969 raise ValueError(
980 raise ValueError(
970 "Feature %s does not handle all default interfaces" %
981 "Feature %s does not handle all default interfaces" %
971 feature)
982 feature)
972
983
973 if self.plain():
984 if self.plain():
974 return "text"
985 return "text"
975
986
976 # Default interface for all the features
987 # Default interface for all the features
977 defaultinterface = "text"
988 defaultinterface = "text"
978 i = self.config("ui", "interface", None)
989 i = self.config("ui", "interface", None)
979 if i in alldefaults:
990 if i in alldefaults:
980 defaultinterface = i
991 defaultinterface = i
981
992
982 choseninterface = defaultinterface
993 choseninterface = defaultinterface
983 f = self.config("ui", "interface.%s" % feature, None)
994 f = self.config("ui", "interface.%s" % feature, None)
984 if f in availableinterfaces:
995 if f in availableinterfaces:
985 choseninterface = f
996 choseninterface = f
986
997
987 if i is not None and defaultinterface != i:
998 if i is not None and defaultinterface != i:
988 if f is not None:
999 if f is not None:
989 self.warn(_("invalid value for ui.interface: %s\n") %
1000 self.warn(_("invalid value for ui.interface: %s\n") %
990 (i,))
1001 (i,))
991 else:
1002 else:
992 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1003 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
993 (i, choseninterface))
1004 (i, choseninterface))
994 if f is not None and choseninterface != f:
1005 if f is not None and choseninterface != f:
995 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1006 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
996 (feature, f, choseninterface))
1007 (feature, f, choseninterface))
997
1008
998 return choseninterface
1009 return choseninterface
999
1010
1000 def interactive(self):
1011 def interactive(self):
1001 '''is interactive input allowed?
1012 '''is interactive input allowed?
1002
1013
1003 An interactive session is a session where input can be reasonably read
1014 An interactive session is a session where input can be reasonably read
1004 from `sys.stdin'. If this function returns false, any attempt to read
1015 from `sys.stdin'. If this function returns false, any attempt to read
1005 from stdin should fail with an error, unless a sensible default has been
1016 from stdin should fail with an error, unless a sensible default has been
1006 specified.
1017 specified.
1007
1018
1008 Interactiveness is triggered by the value of the `ui.interactive'
1019 Interactiveness is triggered by the value of the `ui.interactive'
1009 configuration variable or - if it is unset - when `sys.stdin' points
1020 configuration variable or - if it is unset - when `sys.stdin' points
1010 to a terminal device.
1021 to a terminal device.
1011
1022
1012 This function refers to input only; for output, see `ui.formatted()'.
1023 This function refers to input only; for output, see `ui.formatted()'.
1013 '''
1024 '''
1014 i = self.configbool("ui", "interactive", None)
1025 i = self.configbool("ui", "interactive", None)
1015 if i is None:
1026 if i is None:
1016 # some environments replace stdin without implementing isatty
1027 # some environments replace stdin without implementing isatty
1017 # usually those are non-interactive
1028 # usually those are non-interactive
1018 return self._isatty(self.fin)
1029 return self._isatty(self.fin)
1019
1030
1020 return i
1031 return i
1021
1032
1022 def termwidth(self):
1033 def termwidth(self):
1023 '''how wide is the terminal in columns?
1034 '''how wide is the terminal in columns?
1024 '''
1035 '''
1025 if 'COLUMNS' in encoding.environ:
1036 if 'COLUMNS' in encoding.environ:
1026 try:
1037 try:
1027 return int(encoding.environ['COLUMNS'])
1038 return int(encoding.environ['COLUMNS'])
1028 except ValueError:
1039 except ValueError:
1029 pass
1040 pass
1030 return scmutil.termsize(self)[0]
1041 return scmutil.termsize(self)[0]
1031
1042
1032 def formatted(self):
1043 def formatted(self):
1033 '''should formatted output be used?
1044 '''should formatted output be used?
1034
1045
1035 It is often desirable to format the output to suite the output medium.
1046 It is often desirable to format the output to suite the output medium.
1036 Examples of this are truncating long lines or colorizing messages.
1047 Examples of this are truncating long lines or colorizing messages.
1037 However, this is not often not desirable when piping output into other
1048 However, this is not often not desirable when piping output into other
1038 utilities, e.g. `grep'.
1049 utilities, e.g. `grep'.
1039
1050
1040 Formatted output is triggered by the value of the `ui.formatted'
1051 Formatted output is triggered by the value of the `ui.formatted'
1041 configuration variable or - if it is unset - when `sys.stdout' points
1052 configuration variable or - if it is unset - when `sys.stdout' points
1042 to a terminal device. Please note that `ui.formatted' should be
1053 to a terminal device. Please note that `ui.formatted' should be
1043 considered an implementation detail; it is not intended for use outside
1054 considered an implementation detail; it is not intended for use outside
1044 Mercurial or its extensions.
1055 Mercurial or its extensions.
1045
1056
1046 This function refers to output only; for input, see `ui.interactive()'.
1057 This function refers to output only; for input, see `ui.interactive()'.
1047 This function always returns false when in plain mode, see `ui.plain()'.
1058 This function always returns false when in plain mode, see `ui.plain()'.
1048 '''
1059 '''
1049 if self.plain():
1060 if self.plain():
1050 return False
1061 return False
1051
1062
1052 i = self.configbool("ui", "formatted", None)
1063 i = self.configbool("ui", "formatted", None)
1053 if i is None:
1064 if i is None:
1054 # some environments replace stdout without implementing isatty
1065 # some environments replace stdout without implementing isatty
1055 # usually those are non-interactive
1066 # usually those are non-interactive
1056 return self._isatty(self.fout)
1067 return self._isatty(self.fout)
1057
1068
1058 return i
1069 return i
1059
1070
1060 def _readline(self, prompt=''):
1071 def _readline(self, prompt=''):
1061 if self._isatty(self.fin):
1072 if self._isatty(self.fin):
1062 try:
1073 try:
1063 # magically add command line editing support, where
1074 # magically add command line editing support, where
1064 # available
1075 # available
1065 import readline
1076 import readline
1066 # force demandimport to really load the module
1077 # force demandimport to really load the module
1067 readline.read_history_file
1078 readline.read_history_file
1068 # windows sometimes raises something other than ImportError
1079 # windows sometimes raises something other than ImportError
1069 except Exception:
1080 except Exception:
1070 pass
1081 pass
1071
1082
1072 # call write() so output goes through subclassed implementation
1083 # call write() so output goes through subclassed implementation
1073 # e.g. color extension on Windows
1084 # e.g. color extension on Windows
1074 self.write(prompt, prompt=True)
1085 self.write(prompt, prompt=True)
1075
1086
1076 # instead of trying to emulate raw_input, swap (self.fin,
1087 # instead of trying to emulate raw_input, swap (self.fin,
1077 # self.fout) with (sys.stdin, sys.stdout)
1088 # self.fout) with (sys.stdin, sys.stdout)
1078 oldin = sys.stdin
1089 oldin = sys.stdin
1079 oldout = sys.stdout
1090 oldout = sys.stdout
1080 sys.stdin = self.fin
1091 sys.stdin = self.fin
1081 sys.stdout = self.fout
1092 sys.stdout = self.fout
1082 # prompt ' ' must exist; otherwise readline may delete entire line
1093 # prompt ' ' must exist; otherwise readline may delete entire line
1083 # - http://bugs.python.org/issue12833
1094 # - http://bugs.python.org/issue12833
1084 with self.timeblockedsection('stdio'):
1095 with self.timeblockedsection('stdio'):
1085 line = raw_input(' ')
1096 line = raw_input(' ')
1086 sys.stdin = oldin
1097 sys.stdin = oldin
1087 sys.stdout = oldout
1098 sys.stdout = oldout
1088
1099
1089 # When stdin is in binary mode on Windows, it can cause
1100 # When stdin is in binary mode on Windows, it can cause
1090 # raw_input() to emit an extra trailing carriage return
1101 # raw_input() to emit an extra trailing carriage return
1091 if os.linesep == '\r\n' and line and line[-1] == '\r':
1102 if os.linesep == '\r\n' and line and line[-1] == '\r':
1092 line = line[:-1]
1103 line = line[:-1]
1093 return line
1104 return line
1094
1105
1095 def prompt(self, msg, default="y"):
1106 def prompt(self, msg, default="y"):
1096 """Prompt user with msg, read response.
1107 """Prompt user with msg, read response.
1097 If ui is not interactive, the default is returned.
1108 If ui is not interactive, the default is returned.
1098 """
1109 """
1099 if not self.interactive():
1110 if not self.interactive():
1100 self.write(msg, ' ', default or '', "\n")
1111 self.write(msg, ' ', default or '', "\n")
1101 return default
1112 return default
1102 try:
1113 try:
1103 r = self._readline(self.label(msg, 'ui.prompt'))
1114 r = self._readline(self.label(msg, 'ui.prompt'))
1104 if not r:
1115 if not r:
1105 r = default
1116 r = default
1106 if self.configbool('ui', 'promptecho'):
1117 if self.configbool('ui', 'promptecho'):
1107 self.write(r, "\n")
1118 self.write(r, "\n")
1108 return r
1119 return r
1109 except EOFError:
1120 except EOFError:
1110 raise error.ResponseExpected()
1121 raise error.ResponseExpected()
1111
1122
1112 @staticmethod
1123 @staticmethod
1113 def extractchoices(prompt):
1124 def extractchoices(prompt):
1114 """Extract prompt message and list of choices from specified prompt.
1125 """Extract prompt message and list of choices from specified prompt.
1115
1126
1116 This returns tuple "(message, choices)", and "choices" is the
1127 This returns tuple "(message, choices)", and "choices" is the
1117 list of tuple "(response character, text without &)".
1128 list of tuple "(response character, text without &)".
1118
1129
1119 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1130 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1120 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1131 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1121 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1132 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1122 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1133 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1123 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1134 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1124 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1135 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1125 """
1136 """
1126
1137
1127 # Sadly, the prompt string may have been built with a filename
1138 # Sadly, the prompt string may have been built with a filename
1128 # containing "$$" so let's try to find the first valid-looking
1139 # containing "$$" so let's try to find the first valid-looking
1129 # prompt to start parsing. Sadly, we also can't rely on
1140 # prompt to start parsing. Sadly, we also can't rely on
1130 # choices containing spaces, ASCII, or basically anything
1141 # choices containing spaces, ASCII, or basically anything
1131 # except an ampersand followed by a character.
1142 # except an ampersand followed by a character.
1132 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1143 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1133 msg = m.group(1)
1144 msg = m.group(1)
1134 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1145 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1135 return (msg,
1146 return (msg,
1136 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1147 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1137 for s in choices])
1148 for s in choices])
1138
1149
1139 def promptchoice(self, prompt, default=0):
1150 def promptchoice(self, prompt, default=0):
1140 """Prompt user with a message, read response, and ensure it matches
1151 """Prompt user with a message, read response, and ensure it matches
1141 one of the provided choices. The prompt is formatted as follows:
1152 one of the provided choices. The prompt is formatted as follows:
1142
1153
1143 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1154 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1144
1155
1145 The index of the choice is returned. Responses are case
1156 The index of the choice is returned. Responses are case
1146 insensitive. If ui is not interactive, the default is
1157 insensitive. If ui is not interactive, the default is
1147 returned.
1158 returned.
1148 """
1159 """
1149
1160
1150 msg, choices = self.extractchoices(prompt)
1161 msg, choices = self.extractchoices(prompt)
1151 resps = [r for r, t in choices]
1162 resps = [r for r, t in choices]
1152 while True:
1163 while True:
1153 r = self.prompt(msg, resps[default])
1164 r = self.prompt(msg, resps[default])
1154 if r.lower() in resps:
1165 if r.lower() in resps:
1155 return resps.index(r.lower())
1166 return resps.index(r.lower())
1156 self.write(_("unrecognized response\n"))
1167 self.write(_("unrecognized response\n"))
1157
1168
1158 def getpass(self, prompt=None, default=None):
1169 def getpass(self, prompt=None, default=None):
1159 if not self.interactive():
1170 if not self.interactive():
1160 return default
1171 return default
1161 try:
1172 try:
1162 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1173 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1163 # disable getpass() only if explicitly specified. it's still valid
1174 # disable getpass() only if explicitly specified. it's still valid
1164 # to interact with tty even if fin is not a tty.
1175 # to interact with tty even if fin is not a tty.
1165 with self.timeblockedsection('stdio'):
1176 with self.timeblockedsection('stdio'):
1166 if self.configbool('ui', 'nontty'):
1177 if self.configbool('ui', 'nontty'):
1167 l = self.fin.readline()
1178 l = self.fin.readline()
1168 if not l:
1179 if not l:
1169 raise EOFError
1180 raise EOFError
1170 return l.rstrip('\n')
1181 return l.rstrip('\n')
1171 else:
1182 else:
1172 return getpass.getpass('')
1183 return getpass.getpass('')
1173 except EOFError:
1184 except EOFError:
1174 raise error.ResponseExpected()
1185 raise error.ResponseExpected()
1175 def status(self, *msg, **opts):
1186 def status(self, *msg, **opts):
1176 '''write status message to output (if ui.quiet is False)
1187 '''write status message to output (if ui.quiet is False)
1177
1188
1178 This adds an output label of "ui.status".
1189 This adds an output label of "ui.status".
1179 '''
1190 '''
1180 if not self.quiet:
1191 if not self.quiet:
1181 opts['label'] = opts.get('label', '') + ' ui.status'
1192 opts['label'] = opts.get('label', '') + ' ui.status'
1182 self.write(*msg, **opts)
1193 self.write(*msg, **opts)
1183 def warn(self, *msg, **opts):
1194 def warn(self, *msg, **opts):
1184 '''write warning message to output (stderr)
1195 '''write warning message to output (stderr)
1185
1196
1186 This adds an output label of "ui.warning".
1197 This adds an output label of "ui.warning".
1187 '''
1198 '''
1188 opts['label'] = opts.get('label', '') + ' ui.warning'
1199 opts['label'] = opts.get('label', '') + ' ui.warning'
1189 self.write_err(*msg, **opts)
1200 self.write_err(*msg, **opts)
1190 def note(self, *msg, **opts):
1201 def note(self, *msg, **opts):
1191 '''write note to output (if ui.verbose is True)
1202 '''write note to output (if ui.verbose is True)
1192
1203
1193 This adds an output label of "ui.note".
1204 This adds an output label of "ui.note".
1194 '''
1205 '''
1195 if self.verbose:
1206 if self.verbose:
1196 opts['label'] = opts.get('label', '') + ' ui.note'
1207 opts['label'] = opts.get('label', '') + ' ui.note'
1197 self.write(*msg, **opts)
1208 self.write(*msg, **opts)
1198 def debug(self, *msg, **opts):
1209 def debug(self, *msg, **opts):
1199 '''write debug message to output (if ui.debugflag is True)
1210 '''write debug message to output (if ui.debugflag is True)
1200
1211
1201 This adds an output label of "ui.debug".
1212 This adds an output label of "ui.debug".
1202 '''
1213 '''
1203 if self.debugflag:
1214 if self.debugflag:
1204 opts['label'] = opts.get('label', '') + ' ui.debug'
1215 opts['label'] = opts.get('label', '') + ' ui.debug'
1205 self.write(*msg, **opts)
1216 self.write(*msg, **opts)
1206
1217
1207 def edit(self, text, user, extra=None, editform=None, pending=None,
1218 def edit(self, text, user, extra=None, editform=None, pending=None,
1208 repopath=None):
1219 repopath=None):
1209 extra_defaults = {
1220 extra_defaults = {
1210 'prefix': 'editor',
1221 'prefix': 'editor',
1211 'suffix': '.txt',
1222 'suffix': '.txt',
1212 }
1223 }
1213 if extra is not None:
1224 if extra is not None:
1214 extra_defaults.update(extra)
1225 extra_defaults.update(extra)
1215 extra = extra_defaults
1226 extra = extra_defaults
1216
1227
1217 rdir = None
1228 rdir = None
1218 if self.configbool('experimental', 'editortmpinhg'):
1229 if self.configbool('experimental', 'editortmpinhg'):
1219 rdir = repopath
1230 rdir = repopath
1220 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1231 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1221 suffix=extra['suffix'], text=True,
1232 suffix=extra['suffix'], text=True,
1222 dir=rdir)
1233 dir=rdir)
1223 try:
1234 try:
1224 f = os.fdopen(fd, pycompat.sysstr("w"))
1235 f = os.fdopen(fd, pycompat.sysstr("w"))
1225 f.write(text)
1236 f.write(text)
1226 f.close()
1237 f.close()
1227
1238
1228 environ = {'HGUSER': user}
1239 environ = {'HGUSER': user}
1229 if 'transplant_source' in extra:
1240 if 'transplant_source' in extra:
1230 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1241 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1231 for label in ('intermediate-source', 'source', 'rebase_source'):
1242 for label in ('intermediate-source', 'source', 'rebase_source'):
1232 if label in extra:
1243 if label in extra:
1233 environ.update({'HGREVISION': extra[label]})
1244 environ.update({'HGREVISION': extra[label]})
1234 break
1245 break
1235 if editform:
1246 if editform:
1236 environ.update({'HGEDITFORM': editform})
1247 environ.update({'HGEDITFORM': editform})
1237 if pending:
1248 if pending:
1238 environ.update({'HG_PENDING': pending})
1249 environ.update({'HG_PENDING': pending})
1239
1250
1240 editor = self.geteditor()
1251 editor = self.geteditor()
1241
1252
1242 self.system("%s \"%s\"" % (editor, name),
1253 self.system("%s \"%s\"" % (editor, name),
1243 environ=environ,
1254 environ=environ,
1244 onerr=error.Abort, errprefix=_("edit failed"),
1255 onerr=error.Abort, errprefix=_("edit failed"),
1245 blockedtag='editor')
1256 blockedtag='editor')
1246
1257
1247 f = open(name)
1258 f = open(name)
1248 t = f.read()
1259 t = f.read()
1249 f.close()
1260 f.close()
1250 finally:
1261 finally:
1251 os.unlink(name)
1262 os.unlink(name)
1252
1263
1253 return t
1264 return t
1254
1265
1255 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1266 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1256 blockedtag=None):
1267 blockedtag=None):
1257 '''execute shell command with appropriate output stream. command
1268 '''execute shell command with appropriate output stream. command
1258 output will be redirected if fout is not stdout.
1269 output will be redirected if fout is not stdout.
1259 '''
1270 '''
1260 if blockedtag is None:
1271 if blockedtag is None:
1261 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1272 blockedtag = 'unknown_system_' + cmd.translate(None, _keepalnum)
1262 out = self.fout
1273 out = self.fout
1263 if any(s[1] for s in self._bufferstates):
1274 if any(s[1] for s in self._bufferstates):
1264 out = self
1275 out = self
1265 with self.timeblockedsection(blockedtag):
1276 with self.timeblockedsection(blockedtag):
1266 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1277 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
1267 errprefix=errprefix, out=out)
1278 errprefix=errprefix, out=out)
1268
1279
1269 def traceback(self, exc=None, force=False):
1280 def traceback(self, exc=None, force=False):
1270 '''print exception traceback if traceback printing enabled or forced.
1281 '''print exception traceback if traceback printing enabled or forced.
1271 only to call in exception handler. returns true if traceback
1282 only to call in exception handler. returns true if traceback
1272 printed.'''
1283 printed.'''
1273 if self.tracebackflag or force:
1284 if self.tracebackflag or force:
1274 if exc is None:
1285 if exc is None:
1275 exc = sys.exc_info()
1286 exc = sys.exc_info()
1276 cause = getattr(exc[1], 'cause', None)
1287 cause = getattr(exc[1], 'cause', None)
1277
1288
1278 if cause is not None:
1289 if cause is not None:
1279 causetb = traceback.format_tb(cause[2])
1290 causetb = traceback.format_tb(cause[2])
1280 exctb = traceback.format_tb(exc[2])
1291 exctb = traceback.format_tb(exc[2])
1281 exconly = traceback.format_exception_only(cause[0], cause[1])
1292 exconly = traceback.format_exception_only(cause[0], cause[1])
1282
1293
1283 # exclude frame where 'exc' was chained and rethrown from exctb
1294 # exclude frame where 'exc' was chained and rethrown from exctb
1284 self.write_err('Traceback (most recent call last):\n',
1295 self.write_err('Traceback (most recent call last):\n',
1285 ''.join(exctb[:-1]),
1296 ''.join(exctb[:-1]),
1286 ''.join(causetb),
1297 ''.join(causetb),
1287 ''.join(exconly))
1298 ''.join(exconly))
1288 else:
1299 else:
1289 output = traceback.format_exception(exc[0], exc[1], exc[2])
1300 output = traceback.format_exception(exc[0], exc[1], exc[2])
1290 self.write_err(''.join(output))
1301 self.write_err(''.join(output))
1291 return self.tracebackflag or force
1302 return self.tracebackflag or force
1292
1303
1293 def geteditor(self):
1304 def geteditor(self):
1294 '''return editor to use'''
1305 '''return editor to use'''
1295 if pycompat.sysplatform == 'plan9':
1306 if pycompat.sysplatform == 'plan9':
1296 # vi is the MIPS instruction simulator on Plan 9. We
1307 # vi is the MIPS instruction simulator on Plan 9. We
1297 # instead default to E to plumb commit messages to
1308 # instead default to E to plumb commit messages to
1298 # avoid confusion.
1309 # avoid confusion.
1299 editor = 'E'
1310 editor = 'E'
1300 else:
1311 else:
1301 editor = 'vi'
1312 editor = 'vi'
1302 return (encoding.environ.get("HGEDITOR") or
1313 return (encoding.environ.get("HGEDITOR") or
1303 self.config("ui", "editor") or
1314 self.config("ui", "editor") or
1304 encoding.environ.get("VISUAL") or
1315 encoding.environ.get("VISUAL") or
1305 encoding.environ.get("EDITOR", editor))
1316 encoding.environ.get("EDITOR", editor))
1306
1317
1307 @util.propertycache
1318 @util.propertycache
1308 def _progbar(self):
1319 def _progbar(self):
1309 """setup the progbar singleton to the ui object"""
1320 """setup the progbar singleton to the ui object"""
1310 if (self.quiet or self.debugflag
1321 if (self.quiet or self.debugflag
1311 or self.configbool('progress', 'disable', False)
1322 or self.configbool('progress', 'disable', False)
1312 or not progress.shouldprint(self)):
1323 or not progress.shouldprint(self)):
1313 return None
1324 return None
1314 return getprogbar(self)
1325 return getprogbar(self)
1315
1326
1316 def _progclear(self):
1327 def _progclear(self):
1317 """clear progress bar output if any. use it before any output"""
1328 """clear progress bar output if any. use it before any output"""
1318 if '_progbar' not in vars(self): # nothing loaded yet
1329 if '_progbar' not in vars(self): # nothing loaded yet
1319 return
1330 return
1320 if self._progbar is not None and self._progbar.printed:
1331 if self._progbar is not None and self._progbar.printed:
1321 self._progbar.clear()
1332 self._progbar.clear()
1322
1333
1323 def progress(self, topic, pos, item="", unit="", total=None):
1334 def progress(self, topic, pos, item="", unit="", total=None):
1324 '''show a progress message
1335 '''show a progress message
1325
1336
1326 By default a textual progress bar will be displayed if an operation
1337 By default a textual progress bar will be displayed if an operation
1327 takes too long. 'topic' is the current operation, 'item' is a
1338 takes too long. 'topic' is the current operation, 'item' is a
1328 non-numeric marker of the current position (i.e. the currently
1339 non-numeric marker of the current position (i.e. the currently
1329 in-process file), 'pos' is the current numeric position (i.e.
1340 in-process file), 'pos' is the current numeric position (i.e.
1330 revision, bytes, etc.), unit is a corresponding unit label,
1341 revision, bytes, etc.), unit is a corresponding unit label,
1331 and total is the highest expected pos.
1342 and total is the highest expected pos.
1332
1343
1333 Multiple nested topics may be active at a time.
1344 Multiple nested topics may be active at a time.
1334
1345
1335 All topics should be marked closed by setting pos to None at
1346 All topics should be marked closed by setting pos to None at
1336 termination.
1347 termination.
1337 '''
1348 '''
1338 if self._progbar is not None:
1349 if self._progbar is not None:
1339 self._progbar.progress(topic, pos, item=item, unit=unit,
1350 self._progbar.progress(topic, pos, item=item, unit=unit,
1340 total=total)
1351 total=total)
1341 if pos is None or not self.configbool('progress', 'debug'):
1352 if pos is None or not self.configbool('progress', 'debug'):
1342 return
1353 return
1343
1354
1344 if unit:
1355 if unit:
1345 unit = ' ' + unit
1356 unit = ' ' + unit
1346 if item:
1357 if item:
1347 item = ' ' + item
1358 item = ' ' + item
1348
1359
1349 if total:
1360 if total:
1350 pct = 100.0 * pos / total
1361 pct = 100.0 * pos / total
1351 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1362 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1352 % (topic, item, pos, total, unit, pct))
1363 % (topic, item, pos, total, unit, pct))
1353 else:
1364 else:
1354 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1365 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1355
1366
1356 def log(self, service, *msg, **opts):
1367 def log(self, service, *msg, **opts):
1357 '''hook for logging facility extensions
1368 '''hook for logging facility extensions
1358
1369
1359 service should be a readily-identifiable subsystem, which will
1370 service should be a readily-identifiable subsystem, which will
1360 allow filtering.
1371 allow filtering.
1361
1372
1362 *msg should be a newline-terminated format string to log, and
1373 *msg should be a newline-terminated format string to log, and
1363 then any values to %-format into that format string.
1374 then any values to %-format into that format string.
1364
1375
1365 **opts currently has no defined meanings.
1376 **opts currently has no defined meanings.
1366 '''
1377 '''
1367
1378
1368 def label(self, msg, label):
1379 def label(self, msg, label):
1369 '''style msg based on supplied label
1380 '''style msg based on supplied label
1370
1381
1371 If some color mode is enabled, this will add the necessary control
1382 If some color mode is enabled, this will add the necessary control
1372 characters to apply such color. In addition, 'debug' color mode adds
1383 characters to apply such color. In addition, 'debug' color mode adds
1373 markup showing which label affects a piece of text.
1384 markup showing which label affects a piece of text.
1374
1385
1375 ui.write(s, 'label') is equivalent to
1386 ui.write(s, 'label') is equivalent to
1376 ui.write(ui.label(s, 'label')).
1387 ui.write(ui.label(s, 'label')).
1377 '''
1388 '''
1378 if self._colormode is not None:
1389 if self._colormode is not None:
1379 return color.colorlabel(self, msg, label)
1390 return color.colorlabel(self, msg, label)
1380 return msg
1391 return msg
1381
1392
1382 def develwarn(self, msg, stacklevel=1, config=None):
1393 def develwarn(self, msg, stacklevel=1, config=None):
1383 """issue a developer warning message
1394 """issue a developer warning message
1384
1395
1385 Use 'stacklevel' to report the offender some layers further up in the
1396 Use 'stacklevel' to report the offender some layers further up in the
1386 stack.
1397 stack.
1387 """
1398 """
1388 if not self.configbool('devel', 'all-warnings'):
1399 if not self.configbool('devel', 'all-warnings'):
1389 if config is not None and not self.configbool('devel', config):
1400 if config is not None and not self.configbool('devel', config):
1390 return
1401 return
1391 msg = 'devel-warn: ' + msg
1402 msg = 'devel-warn: ' + msg
1392 stacklevel += 1 # get in develwarn
1403 stacklevel += 1 # get in develwarn
1393 if self.tracebackflag:
1404 if self.tracebackflag:
1394 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1405 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1395 self.log('develwarn', '%s at:\n%s' %
1406 self.log('develwarn', '%s at:\n%s' %
1396 (msg, ''.join(util.getstackframes(stacklevel))))
1407 (msg, ''.join(util.getstackframes(stacklevel))))
1397 else:
1408 else:
1398 curframe = inspect.currentframe()
1409 curframe = inspect.currentframe()
1399 calframe = inspect.getouterframes(curframe, 2)
1410 calframe = inspect.getouterframes(curframe, 2)
1400 self.write_err('%s at: %s:%s (%s)\n'
1411 self.write_err('%s at: %s:%s (%s)\n'
1401 % ((msg,) + calframe[stacklevel][1:4]))
1412 % ((msg,) + calframe[stacklevel][1:4]))
1402 self.log('develwarn', '%s at: %s:%s (%s)\n',
1413 self.log('develwarn', '%s at: %s:%s (%s)\n',
1403 msg, *calframe[stacklevel][1:4])
1414 msg, *calframe[stacklevel][1:4])
1404 curframe = calframe = None # avoid cycles
1415 curframe = calframe = None # avoid cycles
1405
1416
1406 def deprecwarn(self, msg, version):
1417 def deprecwarn(self, msg, version):
1407 """issue a deprecation warning
1418 """issue a deprecation warning
1408
1419
1409 - msg: message explaining what is deprecated and how to upgrade,
1420 - msg: message explaining what is deprecated and how to upgrade,
1410 - version: last version where the API will be supported,
1421 - version: last version where the API will be supported,
1411 """
1422 """
1412 if not (self.configbool('devel', 'all-warnings')
1423 if not (self.configbool('devel', 'all-warnings')
1413 or self.configbool('devel', 'deprec-warn')):
1424 or self.configbool('devel', 'deprec-warn')):
1414 return
1425 return
1415 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1426 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1416 " update your code.)") % version
1427 " update your code.)") % version
1417 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1428 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1418
1429
1419 def exportableenviron(self):
1430 def exportableenviron(self):
1420 """The environment variables that are safe to export, e.g. through
1431 """The environment variables that are safe to export, e.g. through
1421 hgweb.
1432 hgweb.
1422 """
1433 """
1423 return self._exportableenviron
1434 return self._exportableenviron
1424
1435
1425 @contextlib.contextmanager
1436 @contextlib.contextmanager
1426 def configoverride(self, overrides, source=""):
1437 def configoverride(self, overrides, source=""):
1427 """Context manager for temporary config overrides
1438 """Context manager for temporary config overrides
1428 `overrides` must be a dict of the following structure:
1439 `overrides` must be a dict of the following structure:
1429 {(section, name) : value}"""
1440 {(section, name) : value}"""
1430 backups = {}
1441 backups = {}
1431 try:
1442 try:
1432 for (section, name), value in overrides.items():
1443 for (section, name), value in overrides.items():
1433 backups[(section, name)] = self.backupconfig(section, name)
1444 backups[(section, name)] = self.backupconfig(section, name)
1434 self.setconfig(section, name, value, source)
1445 self.setconfig(section, name, value, source)
1435 yield
1446 yield
1436 finally:
1447 finally:
1437 for __, backup in backups.items():
1448 for __, backup in backups.items():
1438 self.restoreconfig(backup)
1449 self.restoreconfig(backup)
1439 # just restoring ui.quiet config to the previous value is not enough
1450 # just restoring ui.quiet config to the previous value is not enough
1440 # as it does not update ui.quiet class member
1451 # as it does not update ui.quiet class member
1441 if ('ui', 'quiet') in overrides:
1452 if ('ui', 'quiet') in overrides:
1442 self.fixconfig(section='ui')
1453 self.fixconfig(section='ui')
1443
1454
1444 class paths(dict):
1455 class paths(dict):
1445 """Represents a collection of paths and their configs.
1456 """Represents a collection of paths and their configs.
1446
1457
1447 Data is initially derived from ui instances and the config files they have
1458 Data is initially derived from ui instances and the config files they have
1448 loaded.
1459 loaded.
1449 """
1460 """
1450 def __init__(self, ui):
1461 def __init__(self, ui):
1451 dict.__init__(self)
1462 dict.__init__(self)
1452
1463
1453 for name, loc in ui.configitems('paths', ignoresub=True):
1464 for name, loc in ui.configitems('paths', ignoresub=True):
1454 # No location is the same as not existing.
1465 # No location is the same as not existing.
1455 if not loc:
1466 if not loc:
1456 continue
1467 continue
1457 loc, sub = ui.configsuboptions('paths', name)
1468 loc, sub = ui.configsuboptions('paths', name)
1458 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1469 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1459
1470
1460 def getpath(self, name, default=None):
1471 def getpath(self, name, default=None):
1461 """Return a ``path`` from a string, falling back to default.
1472 """Return a ``path`` from a string, falling back to default.
1462
1473
1463 ``name`` can be a named path or locations. Locations are filesystem
1474 ``name`` can be a named path or locations. Locations are filesystem
1464 paths or URIs.
1475 paths or URIs.
1465
1476
1466 Returns None if ``name`` is not a registered path, a URI, or a local
1477 Returns None if ``name`` is not a registered path, a URI, or a local
1467 path to a repo.
1478 path to a repo.
1468 """
1479 """
1469 # Only fall back to default if no path was requested.
1480 # Only fall back to default if no path was requested.
1470 if name is None:
1481 if name is None:
1471 if not default:
1482 if not default:
1472 default = ()
1483 default = ()
1473 elif not isinstance(default, (tuple, list)):
1484 elif not isinstance(default, (tuple, list)):
1474 default = (default,)
1485 default = (default,)
1475 for k in default:
1486 for k in default:
1476 try:
1487 try:
1477 return self[k]
1488 return self[k]
1478 except KeyError:
1489 except KeyError:
1479 continue
1490 continue
1480 return None
1491 return None
1481
1492
1482 # Most likely empty string.
1493 # Most likely empty string.
1483 # This may need to raise in the future.
1494 # This may need to raise in the future.
1484 if not name:
1495 if not name:
1485 return None
1496 return None
1486
1497
1487 try:
1498 try:
1488 return self[name]
1499 return self[name]
1489 except KeyError:
1500 except KeyError:
1490 # Try to resolve as a local path or URI.
1501 # Try to resolve as a local path or URI.
1491 try:
1502 try:
1492 # We don't pass sub-options in, so no need to pass ui instance.
1503 # We don't pass sub-options in, so no need to pass ui instance.
1493 return path(None, None, rawloc=name)
1504 return path(None, None, rawloc=name)
1494 except ValueError:
1505 except ValueError:
1495 raise error.RepoError(_('repository %s does not exist') %
1506 raise error.RepoError(_('repository %s does not exist') %
1496 name)
1507 name)
1497
1508
1498 _pathsuboptions = {}
1509 _pathsuboptions = {}
1499
1510
1500 def pathsuboption(option, attr):
1511 def pathsuboption(option, attr):
1501 """Decorator used to declare a path sub-option.
1512 """Decorator used to declare a path sub-option.
1502
1513
1503 Arguments are the sub-option name and the attribute it should set on
1514 Arguments are the sub-option name and the attribute it should set on
1504 ``path`` instances.
1515 ``path`` instances.
1505
1516
1506 The decorated function will receive as arguments a ``ui`` instance,
1517 The decorated function will receive as arguments a ``ui`` instance,
1507 ``path`` instance, and the string value of this option from the config.
1518 ``path`` instance, and the string value of this option from the config.
1508 The function should return the value that will be set on the ``path``
1519 The function should return the value that will be set on the ``path``
1509 instance.
1520 instance.
1510
1521
1511 This decorator can be used to perform additional verification of
1522 This decorator can be used to perform additional verification of
1512 sub-options and to change the type of sub-options.
1523 sub-options and to change the type of sub-options.
1513 """
1524 """
1514 def register(func):
1525 def register(func):
1515 _pathsuboptions[option] = (attr, func)
1526 _pathsuboptions[option] = (attr, func)
1516 return func
1527 return func
1517 return register
1528 return register
1518
1529
1519 @pathsuboption('pushurl', 'pushloc')
1530 @pathsuboption('pushurl', 'pushloc')
1520 def pushurlpathoption(ui, path, value):
1531 def pushurlpathoption(ui, path, value):
1521 u = util.url(value)
1532 u = util.url(value)
1522 # Actually require a URL.
1533 # Actually require a URL.
1523 if not u.scheme:
1534 if not u.scheme:
1524 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1535 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1525 return None
1536 return None
1526
1537
1527 # Don't support the #foo syntax in the push URL to declare branch to
1538 # Don't support the #foo syntax in the push URL to declare branch to
1528 # push.
1539 # push.
1529 if u.fragment:
1540 if u.fragment:
1530 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1541 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1531 'ignoring)\n') % path.name)
1542 'ignoring)\n') % path.name)
1532 u.fragment = None
1543 u.fragment = None
1533
1544
1534 return str(u)
1545 return str(u)
1535
1546
1536 @pathsuboption('pushrev', 'pushrev')
1547 @pathsuboption('pushrev', 'pushrev')
1537 def pushrevpathoption(ui, path, value):
1548 def pushrevpathoption(ui, path, value):
1538 return value
1549 return value
1539
1550
1540 class path(object):
1551 class path(object):
1541 """Represents an individual path and its configuration."""
1552 """Represents an individual path and its configuration."""
1542
1553
1543 def __init__(self, ui, name, rawloc=None, suboptions=None):
1554 def __init__(self, ui, name, rawloc=None, suboptions=None):
1544 """Construct a path from its config options.
1555 """Construct a path from its config options.
1545
1556
1546 ``ui`` is the ``ui`` instance the path is coming from.
1557 ``ui`` is the ``ui`` instance the path is coming from.
1547 ``name`` is the symbolic name of the path.
1558 ``name`` is the symbolic name of the path.
1548 ``rawloc`` is the raw location, as defined in the config.
1559 ``rawloc`` is the raw location, as defined in the config.
1549 ``pushloc`` is the raw locations pushes should be made to.
1560 ``pushloc`` is the raw locations pushes should be made to.
1550
1561
1551 If ``name`` is not defined, we require that the location be a) a local
1562 If ``name`` is not defined, we require that the location be a) a local
1552 filesystem path with a .hg directory or b) a URL. If not,
1563 filesystem path with a .hg directory or b) a URL. If not,
1553 ``ValueError`` is raised.
1564 ``ValueError`` is raised.
1554 """
1565 """
1555 if not rawloc:
1566 if not rawloc:
1556 raise ValueError('rawloc must be defined')
1567 raise ValueError('rawloc must be defined')
1557
1568
1558 # Locations may define branches via syntax <base>#<branch>.
1569 # Locations may define branches via syntax <base>#<branch>.
1559 u = util.url(rawloc)
1570 u = util.url(rawloc)
1560 branch = None
1571 branch = None
1561 if u.fragment:
1572 if u.fragment:
1562 branch = u.fragment
1573 branch = u.fragment
1563 u.fragment = None
1574 u.fragment = None
1564
1575
1565 self.url = u
1576 self.url = u
1566 self.branch = branch
1577 self.branch = branch
1567
1578
1568 self.name = name
1579 self.name = name
1569 self.rawloc = rawloc
1580 self.rawloc = rawloc
1570 self.loc = str(u)
1581 self.loc = str(u)
1571
1582
1572 # When given a raw location but not a symbolic name, validate the
1583 # When given a raw location but not a symbolic name, validate the
1573 # location is valid.
1584 # location is valid.
1574 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1585 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1575 raise ValueError('location is not a URL or path to a local '
1586 raise ValueError('location is not a URL or path to a local '
1576 'repo: %s' % rawloc)
1587 'repo: %s' % rawloc)
1577
1588
1578 suboptions = suboptions or {}
1589 suboptions = suboptions or {}
1579
1590
1580 # Now process the sub-options. If a sub-option is registered, its
1591 # Now process the sub-options. If a sub-option is registered, its
1581 # attribute will always be present. The value will be None if there
1592 # attribute will always be present. The value will be None if there
1582 # was no valid sub-option.
1593 # was no valid sub-option.
1583 for suboption, (attr, func) in _pathsuboptions.iteritems():
1594 for suboption, (attr, func) in _pathsuboptions.iteritems():
1584 if suboption not in suboptions:
1595 if suboption not in suboptions:
1585 setattr(self, attr, None)
1596 setattr(self, attr, None)
1586 continue
1597 continue
1587
1598
1588 value = func(ui, self, suboptions[suboption])
1599 value = func(ui, self, suboptions[suboption])
1589 setattr(self, attr, value)
1600 setattr(self, attr, value)
1590
1601
1591 def _isvalidlocalpath(self, path):
1602 def _isvalidlocalpath(self, path):
1592 """Returns True if the given path is a potentially valid repository.
1603 """Returns True if the given path is a potentially valid repository.
1593 This is its own function so that extensions can change the definition of
1604 This is its own function so that extensions can change the definition of
1594 'valid' in this case (like when pulling from a git repo into a hg
1605 'valid' in this case (like when pulling from a git repo into a hg
1595 one)."""
1606 one)."""
1596 return os.path.isdir(os.path.join(path, '.hg'))
1607 return os.path.isdir(os.path.join(path, '.hg'))
1597
1608
1598 @property
1609 @property
1599 def suboptions(self):
1610 def suboptions(self):
1600 """Return sub-options and their values for this path.
1611 """Return sub-options and their values for this path.
1601
1612
1602 This is intended to be used for presentation purposes.
1613 This is intended to be used for presentation purposes.
1603 """
1614 """
1604 d = {}
1615 d = {}
1605 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1616 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1606 value = getattr(self, attr)
1617 value = getattr(self, attr)
1607 if value is not None:
1618 if value is not None:
1608 d[subopt] = value
1619 d[subopt] = value
1609 return d
1620 return d
1610
1621
1611 # we instantiate one globally shared progress bar to avoid
1622 # we instantiate one globally shared progress bar to avoid
1612 # competing progress bars when multiple UI objects get created
1623 # competing progress bars when multiple UI objects get created
1613 _progresssingleton = None
1624 _progresssingleton = None
1614
1625
1615 def getprogbar(ui):
1626 def getprogbar(ui):
1616 global _progresssingleton
1627 global _progresssingleton
1617 if _progresssingleton is None:
1628 if _progresssingleton is None:
1618 # passing 'ui' object to the singleton is fishy,
1629 # passing 'ui' object to the singleton is fishy,
1619 # this is how the extension used to work but feel free to rework it.
1630 # this is how the extension used to work but feel free to rework it.
1620 _progresssingleton = progress.progbar(ui)
1631 _progresssingleton = progress.progbar(ui)
1621 return _progresssingleton
1632 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now