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