##// END OF EJS Templates
ui: avoid needless casting to a str...
Gregory Szorc -
r27110:f04bd381 default
parent child Browse files
Show More
@@ -1,689 +1,688 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 Labels
32 Labels
33 ------
33 ------
34
34
35 Text receives color effects depending on the labels that it has. Many
35 Text receives color effects depending on the labels that it has. Many
36 default Mercurial commands emit labelled text. You can also define
36 default Mercurial commands emit labelled text. You can also define
37 your own labels in templates using the label function, see :hg:`help
37 your own labels in templates using the label function, see :hg:`help
38 templates`. A single portion of text may have more than one label. In
38 templates`. A single portion of text may have more than one label. In
39 that case, effects given to the last label will override any other
39 that case, effects given to the last label will override any other
40 effects. This includes the special "none" effect, which nullifies
40 effects. This includes the special "none" effect, which nullifies
41 other effects.
41 other effects.
42
42
43 Labels are normally invisible. In order to see these labels and their
43 Labels are normally invisible. In order to see these labels and their
44 position in the text, use the global --color=debug option. The same
44 position in the text, use the global --color=debug option. The same
45 anchor text may be associated to multiple labels, e.g.
45 anchor text may be associated to multiple labels, e.g.
46
46
47 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
47 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
48
48
49 The following are the default effects for some default labels. Default
49 The following are the default effects for some default labels. Default
50 effects may be overridden from your configuration file::
50 effects may be overridden from your configuration file::
51
51
52 [color]
52 [color]
53 status.modified = blue bold underline red_background
53 status.modified = blue bold underline red_background
54 status.added = green bold
54 status.added = green bold
55 status.removed = red bold blue_background
55 status.removed = red bold blue_background
56 status.deleted = cyan bold underline
56 status.deleted = cyan bold underline
57 status.unknown = magenta bold underline
57 status.unknown = magenta bold underline
58 status.ignored = black bold
58 status.ignored = black bold
59
59
60 # 'none' turns off all effects
60 # 'none' turns off all effects
61 status.clean = none
61 status.clean = none
62 status.copied = none
62 status.copied = none
63
63
64 qseries.applied = blue bold underline
64 qseries.applied = blue bold underline
65 qseries.unapplied = black bold
65 qseries.unapplied = black bold
66 qseries.missing = red bold
66 qseries.missing = red bold
67
67
68 diff.diffline = bold
68 diff.diffline = bold
69 diff.extended = cyan bold
69 diff.extended = cyan bold
70 diff.file_a = red bold
70 diff.file_a = red bold
71 diff.file_b = green bold
71 diff.file_b = green bold
72 diff.hunk = magenta
72 diff.hunk = magenta
73 diff.deleted = red
73 diff.deleted = red
74 diff.inserted = green
74 diff.inserted = green
75 diff.changed = white
75 diff.changed = white
76 diff.tab =
76 diff.tab =
77 diff.trailingwhitespace = bold red_background
77 diff.trailingwhitespace = bold red_background
78
78
79 # Blank so it inherits the style of the surrounding label
79 # Blank so it inherits the style of the surrounding label
80 changeset.public =
80 changeset.public =
81 changeset.draft =
81 changeset.draft =
82 changeset.secret =
82 changeset.secret =
83
83
84 resolve.unresolved = red bold
84 resolve.unresolved = red bold
85 resolve.resolved = green bold
85 resolve.resolved = green bold
86
86
87 bookmarks.active = green
87 bookmarks.active = green
88
88
89 branches.active = none
89 branches.active = none
90 branches.closed = black bold
90 branches.closed = black bold
91 branches.current = green
91 branches.current = green
92 branches.inactive = none
92 branches.inactive = none
93
93
94 tags.normal = green
94 tags.normal = green
95 tags.local = black bold
95 tags.local = black bold
96
96
97 rebase.rebased = blue
97 rebase.rebased = blue
98 rebase.remaining = red bold
98 rebase.remaining = red bold
99
99
100 shelve.age = cyan
100 shelve.age = cyan
101 shelve.newest = green bold
101 shelve.newest = green bold
102 shelve.name = blue bold
102 shelve.name = blue bold
103
103
104 histedit.remaining = red bold
104 histedit.remaining = red bold
105
105
106 Custom colors
106 Custom colors
107 -------------
107 -------------
108
108
109 Because there are only eight standard colors, this module allows you
109 Because there are only eight standard colors, this module allows you
110 to define color names for other color slots which might be available
110 to define color names for other color slots which might be available
111 for your terminal type, assuming terminfo mode. For instance::
111 for your terminal type, assuming terminfo mode. For instance::
112
112
113 color.brightblue = 12
113 color.brightblue = 12
114 color.pink = 207
114 color.pink = 207
115 color.orange = 202
115 color.orange = 202
116
116
117 to set 'brightblue' to color slot 12 (useful for 16 color terminals
117 to set 'brightblue' to color slot 12 (useful for 16 color terminals
118 that have brighter colors defined in the upper eight) and, 'pink' and
118 that have brighter colors defined in the upper eight) and, 'pink' and
119 'orange' to colors in 256-color xterm's default color cube. These
119 'orange' to colors in 256-color xterm's default color cube. These
120 defined colors may then be used as any of the pre-defined eight,
120 defined colors may then be used as any of the pre-defined eight,
121 including appending '_background' to set the background to that color.
121 including appending '_background' to set the background to that color.
122
122
123 Modes
123 Modes
124 -----
124 -----
125
125
126 By default, the color extension will use ANSI mode (or win32 mode on
126 By default, the color extension will use ANSI mode (or win32 mode on
127 Windows) if it detects a terminal. To override auto mode (to enable
127 Windows) if it detects a terminal. To override auto mode (to enable
128 terminfo mode, for example), set the following configuration option::
128 terminfo mode, for example), set the following configuration option::
129
129
130 [color]
130 [color]
131 mode = terminfo
131 mode = terminfo
132
132
133 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
133 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
134 disable color.
134 disable color.
135
135
136 Note that on some systems, terminfo mode may cause problems when using
136 Note that on some systems, terminfo mode may cause problems when using
137 color with the pager extension and less -R. less with the -R option
137 color with the pager extension and less -R. less with the -R option
138 will only display ECMA-48 color codes, and terminfo mode may sometimes
138 will only display ECMA-48 color codes, and terminfo mode may sometimes
139 emit codes that less doesn't understand. You can work around this by
139 emit codes that less doesn't understand. You can work around this by
140 either using ansi mode (or auto mode), or by using less -r (which will
140 either using ansi mode (or auto mode), or by using less -r (which will
141 pass through all terminal control codes, not just color control
141 pass through all terminal control codes, not just color control
142 codes).
142 codes).
143
143
144 On some systems (such as MSYS in Windows), the terminal may support
144 On some systems (such as MSYS in Windows), the terminal may support
145 a different color mode than the pager (activated via the "pager"
145 a different color mode than the pager (activated via the "pager"
146 extension). It is possible to define separate modes depending on whether
146 extension). It is possible to define separate modes depending on whether
147 the pager is active::
147 the pager is active::
148
148
149 [color]
149 [color]
150 mode = auto
150 mode = auto
151 pagermode = ansi
151 pagermode = ansi
152
152
153 If ``pagermode`` is not defined, the ``mode`` will be used.
153 If ``pagermode`` is not defined, the ``mode`` will be used.
154 '''
154 '''
155
155
156 import os
156 import os
157
157
158 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
158 from mercurial import cmdutil, commands, dispatch, extensions, subrepo, util
159 from mercurial import ui as uimod
159 from mercurial import ui as uimod
160 from mercurial import templater, error
160 from mercurial import templater, error
161 from mercurial.i18n import _
161 from mercurial.i18n import _
162
162
163 cmdtable = {}
163 cmdtable = {}
164 command = cmdutil.command(cmdtable)
164 command = cmdutil.command(cmdtable)
165 # Note for extension authors: ONLY specify testedwith = 'internal' for
165 # Note for extension authors: ONLY specify testedwith = 'internal' for
166 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
166 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
167 # be specifying the version(s) of Mercurial they are tested with, or
167 # be specifying the version(s) of Mercurial they are tested with, or
168 # leave the attribute unspecified.
168 # leave the attribute unspecified.
169 testedwith = 'internal'
169 testedwith = 'internal'
170
170
171 # start and stop parameters for effects
171 # start and stop parameters for effects
172 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
172 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
173 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
173 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
174 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
174 'italic': 3, 'underline': 4, 'inverse': 7, 'dim': 2,
175 'black_background': 40, 'red_background': 41,
175 'black_background': 40, 'red_background': 41,
176 'green_background': 42, 'yellow_background': 43,
176 'green_background': 42, 'yellow_background': 43,
177 'blue_background': 44, 'purple_background': 45,
177 'blue_background': 44, 'purple_background': 45,
178 'cyan_background': 46, 'white_background': 47}
178 'cyan_background': 46, 'white_background': 47}
179
179
180 def _terminfosetup(ui, mode):
180 def _terminfosetup(ui, mode):
181 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
181 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
182
182
183 global _terminfo_params
183 global _terminfo_params
184 # If we failed to load curses, we go ahead and return.
184 # If we failed to load curses, we go ahead and return.
185 if not _terminfo_params:
185 if not _terminfo_params:
186 return
186 return
187 # Otherwise, see what the config file says.
187 # Otherwise, see what the config file says.
188 if mode not in ('auto', 'terminfo'):
188 if mode not in ('auto', 'terminfo'):
189 return
189 return
190
190
191 _terminfo_params.update((key[6:], (False, int(val)))
191 _terminfo_params.update((key[6:], (False, int(val)))
192 for key, val in ui.configitems('color')
192 for key, val in ui.configitems('color')
193 if key.startswith('color.'))
193 if key.startswith('color.'))
194
194
195 try:
195 try:
196 curses.setupterm()
196 curses.setupterm()
197 except curses.error as e:
197 except curses.error as e:
198 _terminfo_params = {}
198 _terminfo_params = {}
199 return
199 return
200
200
201 for key, (b, e) in _terminfo_params.items():
201 for key, (b, e) in _terminfo_params.items():
202 if not b:
202 if not b:
203 continue
203 continue
204 if not curses.tigetstr(e):
204 if not curses.tigetstr(e):
205 # Most terminals don't support dim, invis, etc, so don't be
205 # Most terminals don't support dim, invis, etc, so don't be
206 # noisy and use ui.debug().
206 # noisy and use ui.debug().
207 ui.debug("no terminfo entry for %s\n" % e)
207 ui.debug("no terminfo entry for %s\n" % e)
208 del _terminfo_params[key]
208 del _terminfo_params[key]
209 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
209 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
210 # Only warn about missing terminfo entries if we explicitly asked for
210 # Only warn about missing terminfo entries if we explicitly asked for
211 # terminfo mode.
211 # terminfo mode.
212 if mode == "terminfo":
212 if mode == "terminfo":
213 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
213 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
214 "ECMA-48 color\n"))
214 "ECMA-48 color\n"))
215 _terminfo_params = {}
215 _terminfo_params = {}
216
216
217 def _modesetup(ui, coloropt):
217 def _modesetup(ui, coloropt):
218 global _terminfo_params
218 global _terminfo_params
219
219
220 if coloropt == 'debug':
220 if coloropt == 'debug':
221 return 'debug'
221 return 'debug'
222
222
223 auto = (coloropt == 'auto')
223 auto = (coloropt == 'auto')
224 always = not auto and util.parsebool(coloropt)
224 always = not auto and util.parsebool(coloropt)
225 if not always and not auto:
225 if not always and not auto:
226 return None
226 return None
227
227
228 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
228 formatted = always or (os.environ.get('TERM') != 'dumb' and ui.formatted())
229
229
230 mode = ui.config('color', 'mode', 'auto')
230 mode = ui.config('color', 'mode', 'auto')
231
231
232 # If pager is active, color.pagermode overrides color.mode.
232 # If pager is active, color.pagermode overrides color.mode.
233 if getattr(ui, 'pageractive', False):
233 if getattr(ui, 'pageractive', False):
234 mode = ui.config('color', 'pagermode', mode)
234 mode = ui.config('color', 'pagermode', mode)
235
235
236 realmode = mode
236 realmode = mode
237 if mode == 'auto':
237 if mode == 'auto':
238 if os.name == 'nt':
238 if os.name == 'nt':
239 term = os.environ.get('TERM')
239 term = os.environ.get('TERM')
240 # TERM won't be defined in a vanilla cmd.exe environment.
240 # TERM won't be defined in a vanilla cmd.exe environment.
241
241
242 # UNIX-like environments on Windows such as Cygwin and MSYS will
242 # UNIX-like environments on Windows such as Cygwin and MSYS will
243 # set TERM. They appear to make a best effort attempt at setting it
243 # set TERM. They appear to make a best effort attempt at setting it
244 # to something appropriate. However, not all environments with TERM
244 # to something appropriate. However, not all environments with TERM
245 # defined support ANSI. Since "ansi" could result in terminal
245 # defined support ANSI. Since "ansi" could result in terminal
246 # gibberish, we error on the side of selecting "win32". However, if
246 # gibberish, we error on the side of selecting "win32". However, if
247 # w32effects is not defined, we almost certainly don't support
247 # w32effects is not defined, we almost certainly don't support
248 # "win32", so don't even try.
248 # "win32", so don't even try.
249 if (term and 'xterm' in term) or not w32effects:
249 if (term and 'xterm' in term) or not w32effects:
250 realmode = 'ansi'
250 realmode = 'ansi'
251 else:
251 else:
252 realmode = 'win32'
252 realmode = 'win32'
253 else:
253 else:
254 realmode = 'ansi'
254 realmode = 'ansi'
255
255
256 def modewarn():
256 def modewarn():
257 # only warn if color.mode was explicitly set and we're in
257 # only warn if color.mode was explicitly set and we're in
258 # an interactive terminal
258 # an interactive terminal
259 if mode == realmode and ui.interactive():
259 if mode == realmode and ui.interactive():
260 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
260 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
261
261
262 if realmode == 'win32':
262 if realmode == 'win32':
263 _terminfo_params = {}
263 _terminfo_params = {}
264 if not w32effects:
264 if not w32effects:
265 modewarn()
265 modewarn()
266 return None
266 return None
267 _effects.update(w32effects)
267 _effects.update(w32effects)
268 elif realmode == 'ansi':
268 elif realmode == 'ansi':
269 _terminfo_params = {}
269 _terminfo_params = {}
270 elif realmode == 'terminfo':
270 elif realmode == 'terminfo':
271 _terminfosetup(ui, mode)
271 _terminfosetup(ui, mode)
272 if not _terminfo_params:
272 if not _terminfo_params:
273 ## FIXME Shouldn't we return None in this case too?
273 ## FIXME Shouldn't we return None in this case too?
274 modewarn()
274 modewarn()
275 realmode = 'ansi'
275 realmode = 'ansi'
276 else:
276 else:
277 return None
277 return None
278
278
279 if always or (auto and formatted):
279 if always or (auto and formatted):
280 return realmode
280 return realmode
281 return None
281 return None
282
282
283 try:
283 try:
284 import curses
284 import curses
285 # Mapping from effect name to terminfo attribute name or color number.
285 # Mapping from effect name to terminfo attribute name or color number.
286 # This will also force-load the curses module.
286 # This will also force-load the curses module.
287 _terminfo_params = {'none': (True, 'sgr0'),
287 _terminfo_params = {'none': (True, 'sgr0'),
288 'standout': (True, 'smso'),
288 'standout': (True, 'smso'),
289 'underline': (True, 'smul'),
289 'underline': (True, 'smul'),
290 'reverse': (True, 'rev'),
290 'reverse': (True, 'rev'),
291 'inverse': (True, 'rev'),
291 'inverse': (True, 'rev'),
292 'blink': (True, 'blink'),
292 'blink': (True, 'blink'),
293 'dim': (True, 'dim'),
293 'dim': (True, 'dim'),
294 'bold': (True, 'bold'),
294 'bold': (True, 'bold'),
295 'invisible': (True, 'invis'),
295 'invisible': (True, 'invis'),
296 'italic': (True, 'sitm'),
296 'italic': (True, 'sitm'),
297 'black': (False, curses.COLOR_BLACK),
297 'black': (False, curses.COLOR_BLACK),
298 'red': (False, curses.COLOR_RED),
298 'red': (False, curses.COLOR_RED),
299 'green': (False, curses.COLOR_GREEN),
299 'green': (False, curses.COLOR_GREEN),
300 'yellow': (False, curses.COLOR_YELLOW),
300 'yellow': (False, curses.COLOR_YELLOW),
301 'blue': (False, curses.COLOR_BLUE),
301 'blue': (False, curses.COLOR_BLUE),
302 'magenta': (False, curses.COLOR_MAGENTA),
302 'magenta': (False, curses.COLOR_MAGENTA),
303 'cyan': (False, curses.COLOR_CYAN),
303 'cyan': (False, curses.COLOR_CYAN),
304 'white': (False, curses.COLOR_WHITE)}
304 'white': (False, curses.COLOR_WHITE)}
305 except ImportError:
305 except ImportError:
306 _terminfo_params = {}
306 _terminfo_params = {}
307
307
308 _styles = {'grep.match': 'red bold',
308 _styles = {'grep.match': 'red bold',
309 'grep.linenumber': 'green',
309 'grep.linenumber': 'green',
310 'grep.rev': 'green',
310 'grep.rev': 'green',
311 'grep.change': 'green',
311 'grep.change': 'green',
312 'grep.sep': 'cyan',
312 'grep.sep': 'cyan',
313 'grep.filename': 'magenta',
313 'grep.filename': 'magenta',
314 'grep.user': 'magenta',
314 'grep.user': 'magenta',
315 'grep.date': 'magenta',
315 'grep.date': 'magenta',
316 'bookmarks.active': 'green',
316 'bookmarks.active': 'green',
317 'branches.active': 'none',
317 'branches.active': 'none',
318 'branches.closed': 'black bold',
318 'branches.closed': 'black bold',
319 'branches.current': 'green',
319 'branches.current': 'green',
320 'branches.inactive': 'none',
320 'branches.inactive': 'none',
321 'diff.changed': 'white',
321 'diff.changed': 'white',
322 'diff.deleted': 'red',
322 'diff.deleted': 'red',
323 'diff.diffline': 'bold',
323 'diff.diffline': 'bold',
324 'diff.extended': 'cyan bold',
324 'diff.extended': 'cyan bold',
325 'diff.file_a': 'red bold',
325 'diff.file_a': 'red bold',
326 'diff.file_b': 'green bold',
326 'diff.file_b': 'green bold',
327 'diff.hunk': 'magenta',
327 'diff.hunk': 'magenta',
328 'diff.inserted': 'green',
328 'diff.inserted': 'green',
329 'diff.tab': '',
329 'diff.tab': '',
330 'diff.trailingwhitespace': 'bold red_background',
330 'diff.trailingwhitespace': 'bold red_background',
331 'changeset.public' : '',
331 'changeset.public' : '',
332 'changeset.draft' : '',
332 'changeset.draft' : '',
333 'changeset.secret' : '',
333 'changeset.secret' : '',
334 'diffstat.deleted': 'red',
334 'diffstat.deleted': 'red',
335 'diffstat.inserted': 'green',
335 'diffstat.inserted': 'green',
336 'histedit.remaining': 'red bold',
336 'histedit.remaining': 'red bold',
337 'ui.prompt': 'yellow',
337 'ui.prompt': 'yellow',
338 'log.changeset': 'yellow',
338 'log.changeset': 'yellow',
339 'patchbomb.finalsummary': '',
339 'patchbomb.finalsummary': '',
340 'patchbomb.from': 'magenta',
340 'patchbomb.from': 'magenta',
341 'patchbomb.to': 'cyan',
341 'patchbomb.to': 'cyan',
342 'patchbomb.subject': 'green',
342 'patchbomb.subject': 'green',
343 'patchbomb.diffstats': '',
343 'patchbomb.diffstats': '',
344 'rebase.rebased': 'blue',
344 'rebase.rebased': 'blue',
345 'rebase.remaining': 'red bold',
345 'rebase.remaining': 'red bold',
346 'resolve.resolved': 'green bold',
346 'resolve.resolved': 'green bold',
347 'resolve.unresolved': 'red bold',
347 'resolve.unresolved': 'red bold',
348 'shelve.age': 'cyan',
348 'shelve.age': 'cyan',
349 'shelve.newest': 'green bold',
349 'shelve.newest': 'green bold',
350 'shelve.name': 'blue bold',
350 'shelve.name': 'blue bold',
351 'status.added': 'green bold',
351 'status.added': 'green bold',
352 'status.clean': 'none',
352 'status.clean': 'none',
353 'status.copied': 'none',
353 'status.copied': 'none',
354 'status.deleted': 'cyan bold underline',
354 'status.deleted': 'cyan bold underline',
355 'status.ignored': 'black bold',
355 'status.ignored': 'black bold',
356 'status.modified': 'blue bold',
356 'status.modified': 'blue bold',
357 'status.removed': 'red bold',
357 'status.removed': 'red bold',
358 'status.unknown': 'magenta bold underline',
358 'status.unknown': 'magenta bold underline',
359 'tags.normal': 'green',
359 'tags.normal': 'green',
360 'tags.local': 'black bold'}
360 'tags.local': 'black bold'}
361
361
362
362
363 def _effect_str(effect):
363 def _effect_str(effect):
364 '''Helper function for render_effects().'''
364 '''Helper function for render_effects().'''
365
365
366 bg = False
366 bg = False
367 if effect.endswith('_background'):
367 if effect.endswith('_background'):
368 bg = True
368 bg = True
369 effect = effect[:-11]
369 effect = effect[:-11]
370 attr, val = _terminfo_params[effect]
370 attr, val = _terminfo_params[effect]
371 if attr:
371 if attr:
372 return curses.tigetstr(val)
372 return curses.tigetstr(val)
373 elif bg:
373 elif bg:
374 return curses.tparm(curses.tigetstr('setab'), val)
374 return curses.tparm(curses.tigetstr('setab'), val)
375 else:
375 else:
376 return curses.tparm(curses.tigetstr('setaf'), val)
376 return curses.tparm(curses.tigetstr('setaf'), val)
377
377
378 def render_effects(text, effects):
378 def render_effects(text, effects):
379 'Wrap text in commands to turn on each effect.'
379 'Wrap text in commands to turn on each effect.'
380 if not text:
380 if not text:
381 return text
381 return text
382 if not _terminfo_params:
382 if not _terminfo_params:
383 start = [str(_effects[e]) for e in ['none'] + effects.split()]
383 start = [str(_effects[e]) for e in ['none'] + effects.split()]
384 start = '\033[' + ';'.join(start) + 'm'
384 start = '\033[' + ';'.join(start) + 'm'
385 stop = '\033[' + str(_effects['none']) + 'm'
385 stop = '\033[' + str(_effects['none']) + 'm'
386 else:
386 else:
387 start = ''.join(_effect_str(effect)
387 start = ''.join(_effect_str(effect)
388 for effect in ['none'] + effects.split())
388 for effect in ['none'] + effects.split())
389 stop = _effect_str('none')
389 stop = _effect_str('none')
390 return ''.join([start, text, stop])
390 return ''.join([start, text, stop])
391
391
392 def extstyles():
392 def extstyles():
393 for name, ext in extensions.extensions():
393 for name, ext in extensions.extensions():
394 _styles.update(getattr(ext, 'colortable', {}))
394 _styles.update(getattr(ext, 'colortable', {}))
395
395
396 def valideffect(effect):
396 def valideffect(effect):
397 'Determine if the effect is valid or not.'
397 'Determine if the effect is valid or not.'
398 good = False
398 good = False
399 if not _terminfo_params and effect in _effects:
399 if not _terminfo_params and effect in _effects:
400 good = True
400 good = True
401 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
401 elif effect in _terminfo_params or effect[:-11] in _terminfo_params:
402 good = True
402 good = True
403 return good
403 return good
404
404
405 def configstyles(ui):
405 def configstyles(ui):
406 for status, cfgeffects in ui.configitems('color'):
406 for status, cfgeffects in ui.configitems('color'):
407 if '.' not in status or status.startswith('color.'):
407 if '.' not in status or status.startswith('color.'):
408 continue
408 continue
409 cfgeffects = ui.configlist('color', status)
409 cfgeffects = ui.configlist('color', status)
410 if cfgeffects:
410 if cfgeffects:
411 good = []
411 good = []
412 for e in cfgeffects:
412 for e in cfgeffects:
413 if valideffect(e):
413 if valideffect(e):
414 good.append(e)
414 good.append(e)
415 else:
415 else:
416 ui.warn(_("ignoring unknown color/effect %r "
416 ui.warn(_("ignoring unknown color/effect %r "
417 "(configured in color.%s)\n")
417 "(configured in color.%s)\n")
418 % (e, status))
418 % (e, status))
419 _styles[status] = ' '.join(good)
419 _styles[status] = ' '.join(good)
420
420
421 class colorui(uimod.ui):
421 class colorui(uimod.ui):
422 def popbuffer(self):
422 def popbuffer(self):
423 if self._colormode is None:
423 if self._colormode is None:
424 return super(colorui, self).popbuffer()
424 return super(colorui, self).popbuffer()
425
425
426 self._bufferstates.pop()
426 self._bufferstates.pop()
427 return ''.join(self._buffers.pop())
427 return ''.join(self._buffers.pop())
428
428
429 _colormode = 'ansi'
429 _colormode = 'ansi'
430 def write(self, *args, **opts):
430 def write(self, *args, **opts):
431 if self._colormode is None:
431 if self._colormode is None:
432 return super(colorui, self).write(*args, **opts)
432 return super(colorui, self).write(*args, **opts)
433
433
434 label = opts.get('label', '')
434 label = opts.get('label', '')
435 if self._buffers:
435 if self._buffers:
436 if self._bufferapplylabels:
436 if self._bufferapplylabels:
437 self._buffers[-1].extend(self.label(str(a), label)
437 self._buffers[-1].extend(self.label(a, label) for a in args)
438 for a in args)
439 else:
438 else:
440 self._buffers[-1].extend(str(a) for a in args)
439 self._buffers[-1].extend(args)
441 elif self._colormode == 'win32':
440 elif self._colormode == 'win32':
442 for a in args:
441 for a in args:
443 win32print(a, super(colorui, self).write, **opts)
442 win32print(a, super(colorui, self).write, **opts)
444 else:
443 else:
445 return super(colorui, self).write(
444 return super(colorui, self).write(
446 *[self.label(str(a), label) for a in args], **opts)
445 *[self.label(a, label) for a in args], **opts)
447
446
448 def write_err(self, *args, **opts):
447 def write_err(self, *args, **opts):
449 if self._colormode is None:
448 if self._colormode is None:
450 return super(colorui, self).write_err(*args, **opts)
449 return super(colorui, self).write_err(*args, **opts)
451
450
452 label = opts.get('label', '')
451 label = opts.get('label', '')
453 if self._bufferstates and self._bufferstates[-1][0]:
452 if self._bufferstates and self._bufferstates[-1][0]:
454 return self.write(*args, **opts)
453 return self.write(*args, **opts)
455 if self._colormode == 'win32':
454 if self._colormode == 'win32':
456 for a in args:
455 for a in args:
457 win32print(a, super(colorui, self).write_err, **opts)
456 win32print(a, super(colorui, self).write_err, **opts)
458 else:
457 else:
459 return super(colorui, self).write_err(
458 return super(colorui, self).write_err(
460 *[self.label(str(a), label) for a in args], **opts)
459 *[self.label(a, label) for a in args], **opts)
461
460
462 def showlabel(self, msg, label):
461 def showlabel(self, msg, label):
463 if label and msg:
462 if label and msg:
464 if msg[-1] == '\n':
463 if msg[-1] == '\n':
465 return "[%s|%s]\n" % (label, msg[:-1])
464 return "[%s|%s]\n" % (label, msg[:-1])
466 else:
465 else:
467 return "[%s|%s]" % (label, msg)
466 return "[%s|%s]" % (label, msg)
468 else:
467 else:
469 return msg
468 return msg
470
469
471 def label(self, msg, label):
470 def label(self, msg, label):
472 if self._colormode is None:
471 if self._colormode is None:
473 return super(colorui, self).label(msg, label)
472 return super(colorui, self).label(msg, label)
474
473
475 if self._colormode == 'debug':
474 if self._colormode == 'debug':
476 return self.showlabel(msg, label)
475 return self.showlabel(msg, label)
477
476
478 effects = []
477 effects = []
479 for l in label.split():
478 for l in label.split():
480 s = _styles.get(l, '')
479 s = _styles.get(l, '')
481 if s:
480 if s:
482 effects.append(s)
481 effects.append(s)
483 elif valideffect(l):
482 elif valideffect(l):
484 effects.append(l)
483 effects.append(l)
485 effects = ' '.join(effects)
484 effects = ' '.join(effects)
486 if effects:
485 if effects:
487 return '\n'.join([render_effects(s, effects)
486 return '\n'.join([render_effects(s, effects)
488 for s in msg.split('\n')])
487 for s in msg.split('\n')])
489 return msg
488 return msg
490
489
491 def templatelabel(context, mapping, args):
490 def templatelabel(context, mapping, args):
492 if len(args) != 2:
491 if len(args) != 2:
493 # i18n: "label" is a keyword
492 # i18n: "label" is a keyword
494 raise error.ParseError(_("label expects two arguments"))
493 raise error.ParseError(_("label expects two arguments"))
495
494
496 # add known effects to the mapping so symbols like 'red', 'bold',
495 # add known effects to the mapping so symbols like 'red', 'bold',
497 # etc. don't need to be quoted
496 # etc. don't need to be quoted
498 mapping.update(dict([(k, k) for k in _effects]))
497 mapping.update(dict([(k, k) for k in _effects]))
499
498
500 thing = args[1][0](context, mapping, args[1][1])
499 thing = args[1][0](context, mapping, args[1][1])
501
500
502 # apparently, repo could be a string that is the favicon?
501 # apparently, repo could be a string that is the favicon?
503 repo = mapping.get('repo', '')
502 repo = mapping.get('repo', '')
504 if isinstance(repo, str):
503 if isinstance(repo, str):
505 return thing
504 return thing
506
505
507 label = args[0][0](context, mapping, args[0][1])
506 label = args[0][0](context, mapping, args[0][1])
508
507
509 thing = templater.stringify(thing)
508 thing = templater.stringify(thing)
510 label = templater.stringify(label)
509 label = templater.stringify(label)
511
510
512 return repo.ui.label(thing, label)
511 return repo.ui.label(thing, label)
513
512
514 def uisetup(ui):
513 def uisetup(ui):
515 if ui.plain():
514 if ui.plain():
516 return
515 return
517 if not isinstance(ui, colorui):
516 if not isinstance(ui, colorui):
518 colorui.__bases__ = (ui.__class__,)
517 colorui.__bases__ = (ui.__class__,)
519 ui.__class__ = colorui
518 ui.__class__ = colorui
520 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
519 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
521 mode = _modesetup(ui_, opts['color'])
520 mode = _modesetup(ui_, opts['color'])
522 colorui._colormode = mode
521 colorui._colormode = mode
523 if mode and mode != 'debug':
522 if mode and mode != 'debug':
524 extstyles()
523 extstyles()
525 configstyles(ui_)
524 configstyles(ui_)
526 return orig(ui_, opts, cmd, cmdfunc)
525 return orig(ui_, opts, cmd, cmdfunc)
527 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
526 def colorgit(orig, gitsub, commands, env=None, stream=False, cwd=None):
528 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
527 if gitsub.ui._colormode and len(commands) and commands[0] == "diff":
529 # insert the argument in the front,
528 # insert the argument in the front,
530 # the end of git diff arguments is used for paths
529 # the end of git diff arguments is used for paths
531 commands.insert(1, '--color')
530 commands.insert(1, '--color')
532 return orig(gitsub, commands, env, stream, cwd)
531 return orig(gitsub, commands, env, stream, cwd)
533 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
532 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
534 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
533 extensions.wrapfunction(subrepo.gitsubrepo, '_gitnodir', colorgit)
535 templatelabel.__doc__ = templater.funcs['label'].__doc__
534 templatelabel.__doc__ = templater.funcs['label'].__doc__
536 templater.funcs['label'] = templatelabel
535 templater.funcs['label'] = templatelabel
537
536
538 def extsetup(ui):
537 def extsetup(ui):
539 commands.globalopts.append(
538 commands.globalopts.append(
540 ('', 'color', 'auto',
539 ('', 'color', 'auto',
541 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
540 # i18n: 'always', 'auto', 'never', and 'debug' are keywords
542 # and should not be translated
541 # and should not be translated
543 _("when to colorize (boolean, always, auto, never, or debug)"),
542 _("when to colorize (boolean, always, auto, never, or debug)"),
544 _('TYPE')))
543 _('TYPE')))
545
544
546 @command('debugcolor', [], 'hg debugcolor')
545 @command('debugcolor', [], 'hg debugcolor')
547 def debugcolor(ui, repo, **opts):
546 def debugcolor(ui, repo, **opts):
548 global _styles
547 global _styles
549 _styles = {}
548 _styles = {}
550 for effect in _effects.keys():
549 for effect in _effects.keys():
551 _styles[effect] = effect
550 _styles[effect] = effect
552 ui.write(('color mode: %s\n') % ui._colormode)
551 ui.write(('color mode: %s\n') % ui._colormode)
553 ui.write(_('available colors:\n'))
552 ui.write(_('available colors:\n'))
554 for label, colors in _styles.items():
553 for label, colors in _styles.items():
555 ui.write(('%s\n') % colors, label=label)
554 ui.write(('%s\n') % colors, label=label)
556
555
557 if os.name != 'nt':
556 if os.name != 'nt':
558 w32effects = None
557 w32effects = None
559 else:
558 else:
560 import re, ctypes
559 import re, ctypes
561
560
562 _kernel32 = ctypes.windll.kernel32
561 _kernel32 = ctypes.windll.kernel32
563
562
564 _WORD = ctypes.c_ushort
563 _WORD = ctypes.c_ushort
565
564
566 _INVALID_HANDLE_VALUE = -1
565 _INVALID_HANDLE_VALUE = -1
567
566
568 class _COORD(ctypes.Structure):
567 class _COORD(ctypes.Structure):
569 _fields_ = [('X', ctypes.c_short),
568 _fields_ = [('X', ctypes.c_short),
570 ('Y', ctypes.c_short)]
569 ('Y', ctypes.c_short)]
571
570
572 class _SMALL_RECT(ctypes.Structure):
571 class _SMALL_RECT(ctypes.Structure):
573 _fields_ = [('Left', ctypes.c_short),
572 _fields_ = [('Left', ctypes.c_short),
574 ('Top', ctypes.c_short),
573 ('Top', ctypes.c_short),
575 ('Right', ctypes.c_short),
574 ('Right', ctypes.c_short),
576 ('Bottom', ctypes.c_short)]
575 ('Bottom', ctypes.c_short)]
577
576
578 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
577 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
579 _fields_ = [('dwSize', _COORD),
578 _fields_ = [('dwSize', _COORD),
580 ('dwCursorPosition', _COORD),
579 ('dwCursorPosition', _COORD),
581 ('wAttributes', _WORD),
580 ('wAttributes', _WORD),
582 ('srWindow', _SMALL_RECT),
581 ('srWindow', _SMALL_RECT),
583 ('dwMaximumWindowSize', _COORD)]
582 ('dwMaximumWindowSize', _COORD)]
584
583
585 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
584 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
586 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
585 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
587
586
588 _FOREGROUND_BLUE = 0x0001
587 _FOREGROUND_BLUE = 0x0001
589 _FOREGROUND_GREEN = 0x0002
588 _FOREGROUND_GREEN = 0x0002
590 _FOREGROUND_RED = 0x0004
589 _FOREGROUND_RED = 0x0004
591 _FOREGROUND_INTENSITY = 0x0008
590 _FOREGROUND_INTENSITY = 0x0008
592
591
593 _BACKGROUND_BLUE = 0x0010
592 _BACKGROUND_BLUE = 0x0010
594 _BACKGROUND_GREEN = 0x0020
593 _BACKGROUND_GREEN = 0x0020
595 _BACKGROUND_RED = 0x0040
594 _BACKGROUND_RED = 0x0040
596 _BACKGROUND_INTENSITY = 0x0080
595 _BACKGROUND_INTENSITY = 0x0080
597
596
598 _COMMON_LVB_REVERSE_VIDEO = 0x4000
597 _COMMON_LVB_REVERSE_VIDEO = 0x4000
599 _COMMON_LVB_UNDERSCORE = 0x8000
598 _COMMON_LVB_UNDERSCORE = 0x8000
600
599
601 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
600 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
602 w32effects = {
601 w32effects = {
603 'none': -1,
602 'none': -1,
604 'black': 0,
603 'black': 0,
605 'red': _FOREGROUND_RED,
604 'red': _FOREGROUND_RED,
606 'green': _FOREGROUND_GREEN,
605 'green': _FOREGROUND_GREEN,
607 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
606 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
608 'blue': _FOREGROUND_BLUE,
607 'blue': _FOREGROUND_BLUE,
609 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
608 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
610 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
609 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
611 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
610 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
612 'bold': _FOREGROUND_INTENSITY,
611 'bold': _FOREGROUND_INTENSITY,
613 'black_background': 0x100, # unused value > 0x0f
612 'black_background': 0x100, # unused value > 0x0f
614 'red_background': _BACKGROUND_RED,
613 'red_background': _BACKGROUND_RED,
615 'green_background': _BACKGROUND_GREEN,
614 'green_background': _BACKGROUND_GREEN,
616 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
615 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
617 'blue_background': _BACKGROUND_BLUE,
616 'blue_background': _BACKGROUND_BLUE,
618 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
617 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
619 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
618 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
620 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
619 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
621 _BACKGROUND_BLUE),
620 _BACKGROUND_BLUE),
622 'bold_background': _BACKGROUND_INTENSITY,
621 'bold_background': _BACKGROUND_INTENSITY,
623 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
622 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
624 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
623 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
625 }
624 }
626
625
627 passthrough = set([_FOREGROUND_INTENSITY,
626 passthrough = set([_FOREGROUND_INTENSITY,
628 _BACKGROUND_INTENSITY,
627 _BACKGROUND_INTENSITY,
629 _COMMON_LVB_UNDERSCORE,
628 _COMMON_LVB_UNDERSCORE,
630 _COMMON_LVB_REVERSE_VIDEO])
629 _COMMON_LVB_REVERSE_VIDEO])
631
630
632 stdout = _kernel32.GetStdHandle(
631 stdout = _kernel32.GetStdHandle(
633 _STD_OUTPUT_HANDLE) # don't close the handle returned
632 _STD_OUTPUT_HANDLE) # don't close the handle returned
634 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
633 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
635 w32effects = None
634 w32effects = None
636 else:
635 else:
637 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
636 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
638 if not _kernel32.GetConsoleScreenBufferInfo(
637 if not _kernel32.GetConsoleScreenBufferInfo(
639 stdout, ctypes.byref(csbi)):
638 stdout, ctypes.byref(csbi)):
640 # stdout may not support GetConsoleScreenBufferInfo()
639 # stdout may not support GetConsoleScreenBufferInfo()
641 # when called from subprocess or redirected
640 # when called from subprocess or redirected
642 w32effects = None
641 w32effects = None
643 else:
642 else:
644 origattr = csbi.wAttributes
643 origattr = csbi.wAttributes
645 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
644 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
646 re.MULTILINE | re.DOTALL)
645 re.MULTILINE | re.DOTALL)
647
646
648 def win32print(text, orig, **opts):
647 def win32print(text, orig, **opts):
649 label = opts.get('label', '')
648 label = opts.get('label', '')
650 attr = origattr
649 attr = origattr
651
650
652 def mapcolor(val, attr):
651 def mapcolor(val, attr):
653 if val == -1:
652 if val == -1:
654 return origattr
653 return origattr
655 elif val in passthrough:
654 elif val in passthrough:
656 return attr | val
655 return attr | val
657 elif val > 0x0f:
656 elif val > 0x0f:
658 return (val & 0x70) | (attr & 0x8f)
657 return (val & 0x70) | (attr & 0x8f)
659 else:
658 else:
660 return (val & 0x07) | (attr & 0xf8)
659 return (val & 0x07) | (attr & 0xf8)
661
660
662 # determine console attributes based on labels
661 # determine console attributes based on labels
663 for l in label.split():
662 for l in label.split():
664 style = _styles.get(l, '')
663 style = _styles.get(l, '')
665 for effect in style.split():
664 for effect in style.split():
666 try:
665 try:
667 attr = mapcolor(w32effects[effect], attr)
666 attr = mapcolor(w32effects[effect], attr)
668 except KeyError:
667 except KeyError:
669 # w32effects could not have certain attributes so we skip
668 # w32effects could not have certain attributes so we skip
670 # them if not found
669 # them if not found
671 pass
670 pass
672 # hack to ensure regexp finds data
671 # hack to ensure regexp finds data
673 if not text.startswith('\033['):
672 if not text.startswith('\033['):
674 text = '\033[m' + text
673 text = '\033[m' + text
675
674
676 # Look for ANSI-like codes embedded in text
675 # Look for ANSI-like codes embedded in text
677 m = re.match(ansire, text)
676 m = re.match(ansire, text)
678
677
679 try:
678 try:
680 while m:
679 while m:
681 for sattr in m.group(1).split(';'):
680 for sattr in m.group(1).split(';'):
682 if sattr:
681 if sattr:
683 attr = mapcolor(int(sattr), attr)
682 attr = mapcolor(int(sattr), attr)
684 _kernel32.SetConsoleTextAttribute(stdout, attr)
683 _kernel32.SetConsoleTextAttribute(stdout, attr)
685 orig(m.group(2), **opts)
684 orig(m.group(2), **opts)
686 m = re.match(ansire, m.group(3))
685 m = re.match(ansire, m.group(3))
687 finally:
686 finally:
688 # Explicitly reset original attributes
687 # Explicitly reset original attributes
689 _kernel32.SetConsoleTextAttribute(stdout, origattr)
688 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,1140 +1,1140 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 errno
10 import errno
11 import getpass
11 import getpass
12 import inspect
12 import inspect
13 import os
13 import os
14 import socket
14 import socket
15 import sys
15 import sys
16 import tempfile
16 import tempfile
17 import traceback
17 import traceback
18
18
19 from .i18n import _
19 from .i18n import _
20 from .node import hex
20 from .node import hex
21
21
22 from . import (
22 from . import (
23 config,
23 config,
24 error,
24 error,
25 formatter,
25 formatter,
26 progress,
26 progress,
27 scmutil,
27 scmutil,
28 util,
28 util,
29 )
29 )
30
30
31 samplehgrcs = {
31 samplehgrcs = {
32 'user':
32 'user':
33 """# example user config (see "hg help config" for more info)
33 """# example user config (see "hg help config" for more info)
34 [ui]
34 [ui]
35 # name and email, e.g.
35 # name and email, e.g.
36 # username = Jane Doe <jdoe@example.com>
36 # username = Jane Doe <jdoe@example.com>
37 username =
37 username =
38
38
39 [extensions]
39 [extensions]
40 # uncomment these lines to enable some popular extensions
40 # uncomment these lines to enable some popular extensions
41 # (see "hg help extensions" for more info)
41 # (see "hg help extensions" for more info)
42 #
42 #
43 # pager =
43 # pager =
44 # progress =
44 # progress =
45 # color =""",
45 # color =""",
46
46
47 'cloned':
47 'cloned':
48 """# example repository config (see "hg help config" for more info)
48 """# example repository config (see "hg help config" for more info)
49 [paths]
49 [paths]
50 default = %s
50 default = %s
51
51
52 # path aliases to other clones of this repo in URLs or filesystem paths
52 # path aliases to other clones of this repo in URLs or filesystem paths
53 # (see "hg help config.paths" for more info)
53 # (see "hg help config.paths" for more info)
54 #
54 #
55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
55 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
56 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
57 # my-clone = /home/jdoe/jdoes-clone
57 # my-clone = /home/jdoe/jdoes-clone
58
58
59 [ui]
59 [ui]
60 # name and email (local to this repository, optional), e.g.
60 # name and email (local to this repository, optional), e.g.
61 # username = Jane Doe <jdoe@example.com>
61 # username = Jane Doe <jdoe@example.com>
62 """,
62 """,
63
63
64 'local':
64 'local':
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 # path aliases to other clones of this repo in URLs or filesystem paths
67 # path aliases to other clones of this repo in URLs or filesystem paths
68 # (see "hg help config.paths" for more info)
68 # (see "hg help config.paths" for more info)
69 #
69 #
70 # default = http://example.com/hg/example-repo
70 # default = http://example.com/hg/example-repo
71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
71 # default-push = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
72 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
73 # my-clone = /home/jdoe/jdoes-clone
73 # my-clone = /home/jdoe/jdoes-clone
74
74
75 [ui]
75 [ui]
76 # name and email (local to this repository, optional), e.g.
76 # name and email (local to this repository, optional), e.g.
77 # username = Jane Doe <jdoe@example.com>
77 # username = Jane Doe <jdoe@example.com>
78 """,
78 """,
79
79
80 'global':
80 'global':
81 """# example system-wide hg config (see "hg help config" for more info)
81 """# example system-wide hg config (see "hg help config" for more info)
82
82
83 [extensions]
83 [extensions]
84 # uncomment these lines to enable some popular extensions
84 # uncomment these lines to enable some popular extensions
85 # (see "hg help extensions" for more info)
85 # (see "hg help extensions" for more info)
86 #
86 #
87 # blackbox =
87 # blackbox =
88 # progress =
88 # progress =
89 # color =
89 # color =
90 # pager =""",
90 # pager =""",
91 }
91 }
92
92
93 class ui(object):
93 class ui(object):
94 def __init__(self, src=None):
94 def __init__(self, src=None):
95 # _buffers: used for temporary capture of output
95 # _buffers: used for temporary capture of output
96 self._buffers = []
96 self._buffers = []
97 # 3-tuple describing how each buffer in the stack behaves.
97 # 3-tuple describing how each buffer in the stack behaves.
98 # Values are (capture stderr, capture subprocesses, apply labels).
98 # Values are (capture stderr, capture subprocesses, apply labels).
99 self._bufferstates = []
99 self._bufferstates = []
100 # When a buffer is active, defines whether we are expanding labels.
100 # When a buffer is active, defines whether we are expanding labels.
101 # This exists to prevent an extra list lookup.
101 # This exists to prevent an extra list lookup.
102 self._bufferapplylabels = None
102 self._bufferapplylabels = None
103 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
103 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
104 self._reportuntrusted = True
104 self._reportuntrusted = True
105 self._ocfg = config.config() # overlay
105 self._ocfg = config.config() # overlay
106 self._tcfg = config.config() # trusted
106 self._tcfg = config.config() # trusted
107 self._ucfg = config.config() # untrusted
107 self._ucfg = config.config() # untrusted
108 self._trustusers = set()
108 self._trustusers = set()
109 self._trustgroups = set()
109 self._trustgroups = set()
110 self.callhooks = True
110 self.callhooks = True
111
111
112 if src:
112 if src:
113 self.fout = src.fout
113 self.fout = src.fout
114 self.ferr = src.ferr
114 self.ferr = src.ferr
115 self.fin = src.fin
115 self.fin = src.fin
116
116
117 self._tcfg = src._tcfg.copy()
117 self._tcfg = src._tcfg.copy()
118 self._ucfg = src._ucfg.copy()
118 self._ucfg = src._ucfg.copy()
119 self._ocfg = src._ocfg.copy()
119 self._ocfg = src._ocfg.copy()
120 self._trustusers = src._trustusers.copy()
120 self._trustusers = src._trustusers.copy()
121 self._trustgroups = src._trustgroups.copy()
121 self._trustgroups = src._trustgroups.copy()
122 self.environ = src.environ
122 self.environ = src.environ
123 self.callhooks = src.callhooks
123 self.callhooks = src.callhooks
124 self.fixconfig()
124 self.fixconfig()
125 else:
125 else:
126 self.fout = sys.stdout
126 self.fout = sys.stdout
127 self.ferr = sys.stderr
127 self.ferr = sys.stderr
128 self.fin = sys.stdin
128 self.fin = sys.stdin
129
129
130 # shared read-only environment
130 # shared read-only environment
131 self.environ = os.environ
131 self.environ = os.environ
132 # we always trust global config files
132 # we always trust global config files
133 for f in scmutil.rcpath():
133 for f in scmutil.rcpath():
134 self.readconfig(f, trust=True)
134 self.readconfig(f, trust=True)
135
135
136 def copy(self):
136 def copy(self):
137 return self.__class__(self)
137 return self.__class__(self)
138
138
139 def formatter(self, topic, opts):
139 def formatter(self, topic, opts):
140 return formatter.formatter(self, topic, opts)
140 return formatter.formatter(self, topic, opts)
141
141
142 def _trusted(self, fp, f):
142 def _trusted(self, fp, f):
143 st = util.fstat(fp)
143 st = util.fstat(fp)
144 if util.isowner(st):
144 if util.isowner(st):
145 return True
145 return True
146
146
147 tusers, tgroups = self._trustusers, self._trustgroups
147 tusers, tgroups = self._trustusers, self._trustgroups
148 if '*' in tusers or '*' in tgroups:
148 if '*' in tusers or '*' in tgroups:
149 return True
149 return True
150
150
151 user = util.username(st.st_uid)
151 user = util.username(st.st_uid)
152 group = util.groupname(st.st_gid)
152 group = util.groupname(st.st_gid)
153 if user in tusers or group in tgroups or user == util.username():
153 if user in tusers or group in tgroups or user == util.username():
154 return True
154 return True
155
155
156 if self._reportuntrusted:
156 if self._reportuntrusted:
157 self.warn(_('not trusting file %s from untrusted '
157 self.warn(_('not trusting file %s from untrusted '
158 'user %s, group %s\n') % (f, user, group))
158 'user %s, group %s\n') % (f, user, group))
159 return False
159 return False
160
160
161 def readconfig(self, filename, root=None, trust=False,
161 def readconfig(self, filename, root=None, trust=False,
162 sections=None, remap=None):
162 sections=None, remap=None):
163 try:
163 try:
164 fp = open(filename)
164 fp = open(filename)
165 except IOError:
165 except IOError:
166 if not sections: # ignore unless we were looking for something
166 if not sections: # ignore unless we were looking for something
167 return
167 return
168 raise
168 raise
169
169
170 cfg = config.config()
170 cfg = config.config()
171 trusted = sections or trust or self._trusted(fp, filename)
171 trusted = sections or trust or self._trusted(fp, filename)
172
172
173 try:
173 try:
174 cfg.read(filename, fp, sections=sections, remap=remap)
174 cfg.read(filename, fp, sections=sections, remap=remap)
175 fp.close()
175 fp.close()
176 except error.ConfigError as inst:
176 except error.ConfigError as inst:
177 if trusted:
177 if trusted:
178 raise
178 raise
179 self.warn(_("ignored: %s\n") % str(inst))
179 self.warn(_("ignored: %s\n") % str(inst))
180
180
181 if self.plain():
181 if self.plain():
182 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
182 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
183 'logtemplate', 'statuscopies', 'style',
183 'logtemplate', 'statuscopies', 'style',
184 'traceback', 'verbose'):
184 'traceback', 'verbose'):
185 if k in cfg['ui']:
185 if k in cfg['ui']:
186 del cfg['ui'][k]
186 del cfg['ui'][k]
187 for k, v in cfg.items('defaults'):
187 for k, v in cfg.items('defaults'):
188 del cfg['defaults'][k]
188 del cfg['defaults'][k]
189 # Don't remove aliases from the configuration if in the exceptionlist
189 # Don't remove aliases from the configuration if in the exceptionlist
190 if self.plain('alias'):
190 if self.plain('alias'):
191 for k, v in cfg.items('alias'):
191 for k, v in cfg.items('alias'):
192 del cfg['alias'][k]
192 del cfg['alias'][k]
193 if self.plain('revsetalias'):
193 if self.plain('revsetalias'):
194 for k, v in cfg.items('revsetalias'):
194 for k, v in cfg.items('revsetalias'):
195 del cfg['revsetalias'][k]
195 del cfg['revsetalias'][k]
196
196
197 if trusted:
197 if trusted:
198 self._tcfg.update(cfg)
198 self._tcfg.update(cfg)
199 self._tcfg.update(self._ocfg)
199 self._tcfg.update(self._ocfg)
200 self._ucfg.update(cfg)
200 self._ucfg.update(cfg)
201 self._ucfg.update(self._ocfg)
201 self._ucfg.update(self._ocfg)
202
202
203 if root is None:
203 if root is None:
204 root = os.path.expanduser('~')
204 root = os.path.expanduser('~')
205 self.fixconfig(root=root)
205 self.fixconfig(root=root)
206
206
207 def fixconfig(self, root=None, section=None):
207 def fixconfig(self, root=None, section=None):
208 if section in (None, 'paths'):
208 if section in (None, 'paths'):
209 # expand vars and ~
209 # expand vars and ~
210 # translate paths relative to root (or home) into absolute paths
210 # translate paths relative to root (or home) into absolute paths
211 root = root or os.getcwd()
211 root = root or os.getcwd()
212 for c in self._tcfg, self._ucfg, self._ocfg:
212 for c in self._tcfg, self._ucfg, self._ocfg:
213 for n, p in c.items('paths'):
213 for n, p in c.items('paths'):
214 if not p:
214 if not p:
215 continue
215 continue
216 if '%%' in p:
216 if '%%' in p:
217 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
217 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
218 % (n, p, self.configsource('paths', n)))
218 % (n, p, self.configsource('paths', n)))
219 p = p.replace('%%', '%')
219 p = p.replace('%%', '%')
220 p = util.expandpath(p)
220 p = util.expandpath(p)
221 if not util.hasscheme(p) and not os.path.isabs(p):
221 if not util.hasscheme(p) and not os.path.isabs(p):
222 p = os.path.normpath(os.path.join(root, p))
222 p = os.path.normpath(os.path.join(root, p))
223 c.set("paths", n, p)
223 c.set("paths", n, p)
224
224
225 if section in (None, 'ui'):
225 if section in (None, 'ui'):
226 # update ui options
226 # update ui options
227 self.debugflag = self.configbool('ui', 'debug')
227 self.debugflag = self.configbool('ui', 'debug')
228 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
228 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
229 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
229 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
230 if self.verbose and self.quiet:
230 if self.verbose and self.quiet:
231 self.quiet = self.verbose = False
231 self.quiet = self.verbose = False
232 self._reportuntrusted = self.debugflag or self.configbool("ui",
232 self._reportuntrusted = self.debugflag or self.configbool("ui",
233 "report_untrusted", True)
233 "report_untrusted", True)
234 self.tracebackflag = self.configbool('ui', 'traceback', False)
234 self.tracebackflag = self.configbool('ui', 'traceback', False)
235
235
236 if section in (None, 'trusted'):
236 if section in (None, 'trusted'):
237 # update trust information
237 # update trust information
238 self._trustusers.update(self.configlist('trusted', 'users'))
238 self._trustusers.update(self.configlist('trusted', 'users'))
239 self._trustgroups.update(self.configlist('trusted', 'groups'))
239 self._trustgroups.update(self.configlist('trusted', 'groups'))
240
240
241 def backupconfig(self, section, item):
241 def backupconfig(self, section, item):
242 return (self._ocfg.backup(section, item),
242 return (self._ocfg.backup(section, item),
243 self._tcfg.backup(section, item),
243 self._tcfg.backup(section, item),
244 self._ucfg.backup(section, item),)
244 self._ucfg.backup(section, item),)
245 def restoreconfig(self, data):
245 def restoreconfig(self, data):
246 self._ocfg.restore(data[0])
246 self._ocfg.restore(data[0])
247 self._tcfg.restore(data[1])
247 self._tcfg.restore(data[1])
248 self._ucfg.restore(data[2])
248 self._ucfg.restore(data[2])
249
249
250 def setconfig(self, section, name, value, source=''):
250 def setconfig(self, section, name, value, source=''):
251 for cfg in (self._ocfg, self._tcfg, self._ucfg):
251 for cfg in (self._ocfg, self._tcfg, self._ucfg):
252 cfg.set(section, name, value, source)
252 cfg.set(section, name, value, source)
253 self.fixconfig(section=section)
253 self.fixconfig(section=section)
254
254
255 def _data(self, untrusted):
255 def _data(self, untrusted):
256 return untrusted and self._ucfg or self._tcfg
256 return untrusted and self._ucfg or self._tcfg
257
257
258 def configsource(self, section, name, untrusted=False):
258 def configsource(self, section, name, untrusted=False):
259 return self._data(untrusted).source(section, name) or 'none'
259 return self._data(untrusted).source(section, name) or 'none'
260
260
261 def config(self, section, name, default=None, untrusted=False):
261 def config(self, section, name, default=None, untrusted=False):
262 if isinstance(name, list):
262 if isinstance(name, list):
263 alternates = name
263 alternates = name
264 else:
264 else:
265 alternates = [name]
265 alternates = [name]
266
266
267 for n in alternates:
267 for n in alternates:
268 value = self._data(untrusted).get(section, n, None)
268 value = self._data(untrusted).get(section, n, None)
269 if value is not None:
269 if value is not None:
270 name = n
270 name = n
271 break
271 break
272 else:
272 else:
273 value = default
273 value = default
274
274
275 if self.debugflag and not untrusted and self._reportuntrusted:
275 if self.debugflag and not untrusted and self._reportuntrusted:
276 for n in alternates:
276 for n in alternates:
277 uvalue = self._ucfg.get(section, n)
277 uvalue = self._ucfg.get(section, n)
278 if uvalue is not None and uvalue != value:
278 if uvalue is not None and uvalue != value:
279 self.debug("ignoring untrusted configuration option "
279 self.debug("ignoring untrusted configuration option "
280 "%s.%s = %s\n" % (section, n, uvalue))
280 "%s.%s = %s\n" % (section, n, uvalue))
281 return value
281 return value
282
282
283 def configpath(self, section, name, default=None, untrusted=False):
283 def configpath(self, section, name, default=None, untrusted=False):
284 'get a path config item, expanded relative to repo root or config file'
284 'get a path config item, expanded relative to repo root or config file'
285 v = self.config(section, name, default, untrusted)
285 v = self.config(section, name, default, untrusted)
286 if v is None:
286 if v is None:
287 return None
287 return None
288 if not os.path.isabs(v) or "://" not in v:
288 if not os.path.isabs(v) or "://" not in v:
289 src = self.configsource(section, name, untrusted)
289 src = self.configsource(section, name, untrusted)
290 if ':' in src:
290 if ':' in src:
291 base = os.path.dirname(src.rsplit(':')[0])
291 base = os.path.dirname(src.rsplit(':')[0])
292 v = os.path.join(base, os.path.expanduser(v))
292 v = os.path.join(base, os.path.expanduser(v))
293 return v
293 return v
294
294
295 def configbool(self, section, name, default=False, untrusted=False):
295 def configbool(self, section, name, default=False, untrusted=False):
296 """parse a configuration element as a boolean
296 """parse a configuration element as a boolean
297
297
298 >>> u = ui(); s = 'foo'
298 >>> u = ui(); s = 'foo'
299 >>> u.setconfig(s, 'true', 'yes')
299 >>> u.setconfig(s, 'true', 'yes')
300 >>> u.configbool(s, 'true')
300 >>> u.configbool(s, 'true')
301 True
301 True
302 >>> u.setconfig(s, 'false', 'no')
302 >>> u.setconfig(s, 'false', 'no')
303 >>> u.configbool(s, 'false')
303 >>> u.configbool(s, 'false')
304 False
304 False
305 >>> u.configbool(s, 'unknown')
305 >>> u.configbool(s, 'unknown')
306 False
306 False
307 >>> u.configbool(s, 'unknown', True)
307 >>> u.configbool(s, 'unknown', True)
308 True
308 True
309 >>> u.setconfig(s, 'invalid', 'somevalue')
309 >>> u.setconfig(s, 'invalid', 'somevalue')
310 >>> u.configbool(s, 'invalid')
310 >>> u.configbool(s, 'invalid')
311 Traceback (most recent call last):
311 Traceback (most recent call last):
312 ...
312 ...
313 ConfigError: foo.invalid is not a boolean ('somevalue')
313 ConfigError: foo.invalid is not a boolean ('somevalue')
314 """
314 """
315
315
316 v = self.config(section, name, None, untrusted)
316 v = self.config(section, name, None, untrusted)
317 if v is None:
317 if v is None:
318 return default
318 return default
319 if isinstance(v, bool):
319 if isinstance(v, bool):
320 return v
320 return v
321 b = util.parsebool(v)
321 b = util.parsebool(v)
322 if b is None:
322 if b is None:
323 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
323 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
324 % (section, name, v))
324 % (section, name, v))
325 return b
325 return b
326
326
327 def configint(self, section, name, default=None, untrusted=False):
327 def configint(self, section, name, default=None, untrusted=False):
328 """parse a configuration element as an integer
328 """parse a configuration element as an integer
329
329
330 >>> u = ui(); s = 'foo'
330 >>> u = ui(); s = 'foo'
331 >>> u.setconfig(s, 'int1', '42')
331 >>> u.setconfig(s, 'int1', '42')
332 >>> u.configint(s, 'int1')
332 >>> u.configint(s, 'int1')
333 42
333 42
334 >>> u.setconfig(s, 'int2', '-42')
334 >>> u.setconfig(s, 'int2', '-42')
335 >>> u.configint(s, 'int2')
335 >>> u.configint(s, 'int2')
336 -42
336 -42
337 >>> u.configint(s, 'unknown', 7)
337 >>> u.configint(s, 'unknown', 7)
338 7
338 7
339 >>> u.setconfig(s, 'invalid', 'somevalue')
339 >>> u.setconfig(s, 'invalid', 'somevalue')
340 >>> u.configint(s, 'invalid')
340 >>> u.configint(s, 'invalid')
341 Traceback (most recent call last):
341 Traceback (most recent call last):
342 ...
342 ...
343 ConfigError: foo.invalid is not an integer ('somevalue')
343 ConfigError: foo.invalid is not an integer ('somevalue')
344 """
344 """
345
345
346 v = self.config(section, name, None, untrusted)
346 v = self.config(section, name, None, untrusted)
347 if v is None:
347 if v is None:
348 return default
348 return default
349 try:
349 try:
350 return int(v)
350 return int(v)
351 except ValueError:
351 except ValueError:
352 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
352 raise error.ConfigError(_("%s.%s is not an integer ('%s')")
353 % (section, name, v))
353 % (section, name, v))
354
354
355 def configbytes(self, section, name, default=0, untrusted=False):
355 def configbytes(self, section, name, default=0, untrusted=False):
356 """parse a configuration element as a quantity in bytes
356 """parse a configuration element as a quantity in bytes
357
357
358 Units can be specified as b (bytes), k or kb (kilobytes), m or
358 Units can be specified as b (bytes), k or kb (kilobytes), m or
359 mb (megabytes), g or gb (gigabytes).
359 mb (megabytes), g or gb (gigabytes).
360
360
361 >>> u = ui(); s = 'foo'
361 >>> u = ui(); s = 'foo'
362 >>> u.setconfig(s, 'val1', '42')
362 >>> u.setconfig(s, 'val1', '42')
363 >>> u.configbytes(s, 'val1')
363 >>> u.configbytes(s, 'val1')
364 42
364 42
365 >>> u.setconfig(s, 'val2', '42.5 kb')
365 >>> u.setconfig(s, 'val2', '42.5 kb')
366 >>> u.configbytes(s, 'val2')
366 >>> u.configbytes(s, 'val2')
367 43520
367 43520
368 >>> u.configbytes(s, 'unknown', '7 MB')
368 >>> u.configbytes(s, 'unknown', '7 MB')
369 7340032
369 7340032
370 >>> u.setconfig(s, 'invalid', 'somevalue')
370 >>> u.setconfig(s, 'invalid', 'somevalue')
371 >>> u.configbytes(s, 'invalid')
371 >>> u.configbytes(s, 'invalid')
372 Traceback (most recent call last):
372 Traceback (most recent call last):
373 ...
373 ...
374 ConfigError: foo.invalid is not a byte quantity ('somevalue')
374 ConfigError: foo.invalid is not a byte quantity ('somevalue')
375 """
375 """
376
376
377 value = self.config(section, name)
377 value = self.config(section, name)
378 if value is None:
378 if value is None:
379 if not isinstance(default, str):
379 if not isinstance(default, str):
380 return default
380 return default
381 value = default
381 value = default
382 try:
382 try:
383 return util.sizetoint(value)
383 return util.sizetoint(value)
384 except error.ParseError:
384 except error.ParseError:
385 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
385 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
386 % (section, name, value))
386 % (section, name, value))
387
387
388 def configlist(self, section, name, default=None, untrusted=False):
388 def configlist(self, section, name, default=None, untrusted=False):
389 """parse a configuration element as a list of comma/space separated
389 """parse a configuration element as a list of comma/space separated
390 strings
390 strings
391
391
392 >>> u = ui(); s = 'foo'
392 >>> u = ui(); s = 'foo'
393 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
393 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
394 >>> u.configlist(s, 'list1')
394 >>> u.configlist(s, 'list1')
395 ['this', 'is', 'a small', 'test']
395 ['this', 'is', 'a small', 'test']
396 """
396 """
397
397
398 def _parse_plain(parts, s, offset):
398 def _parse_plain(parts, s, offset):
399 whitespace = False
399 whitespace = False
400 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
400 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
401 whitespace = True
401 whitespace = True
402 offset += 1
402 offset += 1
403 if offset >= len(s):
403 if offset >= len(s):
404 return None, parts, offset
404 return None, parts, offset
405 if whitespace:
405 if whitespace:
406 parts.append('')
406 parts.append('')
407 if s[offset] == '"' and not parts[-1]:
407 if s[offset] == '"' and not parts[-1]:
408 return _parse_quote, parts, offset + 1
408 return _parse_quote, parts, offset + 1
409 elif s[offset] == '"' and parts[-1][-1] == '\\':
409 elif s[offset] == '"' and parts[-1][-1] == '\\':
410 parts[-1] = parts[-1][:-1] + s[offset]
410 parts[-1] = parts[-1][:-1] + s[offset]
411 return _parse_plain, parts, offset + 1
411 return _parse_plain, parts, offset + 1
412 parts[-1] += s[offset]
412 parts[-1] += s[offset]
413 return _parse_plain, parts, offset + 1
413 return _parse_plain, parts, offset + 1
414
414
415 def _parse_quote(parts, s, offset):
415 def _parse_quote(parts, s, offset):
416 if offset < len(s) and s[offset] == '"': # ""
416 if offset < len(s) and s[offset] == '"': # ""
417 parts.append('')
417 parts.append('')
418 offset += 1
418 offset += 1
419 while offset < len(s) and (s[offset].isspace() or
419 while offset < len(s) and (s[offset].isspace() or
420 s[offset] == ','):
420 s[offset] == ','):
421 offset += 1
421 offset += 1
422 return _parse_plain, parts, offset
422 return _parse_plain, parts, offset
423
423
424 while offset < len(s) and s[offset] != '"':
424 while offset < len(s) and s[offset] != '"':
425 if (s[offset] == '\\' and offset + 1 < len(s)
425 if (s[offset] == '\\' and offset + 1 < len(s)
426 and s[offset + 1] == '"'):
426 and s[offset + 1] == '"'):
427 offset += 1
427 offset += 1
428 parts[-1] += '"'
428 parts[-1] += '"'
429 else:
429 else:
430 parts[-1] += s[offset]
430 parts[-1] += s[offset]
431 offset += 1
431 offset += 1
432
432
433 if offset >= len(s):
433 if offset >= len(s):
434 real_parts = _configlist(parts[-1])
434 real_parts = _configlist(parts[-1])
435 if not real_parts:
435 if not real_parts:
436 parts[-1] = '"'
436 parts[-1] = '"'
437 else:
437 else:
438 real_parts[0] = '"' + real_parts[0]
438 real_parts[0] = '"' + real_parts[0]
439 parts = parts[:-1]
439 parts = parts[:-1]
440 parts.extend(real_parts)
440 parts.extend(real_parts)
441 return None, parts, offset
441 return None, parts, offset
442
442
443 offset += 1
443 offset += 1
444 while offset < len(s) and s[offset] in [' ', ',']:
444 while offset < len(s) and s[offset] in [' ', ',']:
445 offset += 1
445 offset += 1
446
446
447 if offset < len(s):
447 if offset < len(s):
448 if offset + 1 == len(s) and s[offset] == '"':
448 if offset + 1 == len(s) and s[offset] == '"':
449 parts[-1] += '"'
449 parts[-1] += '"'
450 offset += 1
450 offset += 1
451 else:
451 else:
452 parts.append('')
452 parts.append('')
453 else:
453 else:
454 return None, parts, offset
454 return None, parts, offset
455
455
456 return _parse_plain, parts, offset
456 return _parse_plain, parts, offset
457
457
458 def _configlist(s):
458 def _configlist(s):
459 s = s.rstrip(' ,')
459 s = s.rstrip(' ,')
460 if not s:
460 if not s:
461 return []
461 return []
462 parser, parts, offset = _parse_plain, [''], 0
462 parser, parts, offset = _parse_plain, [''], 0
463 while parser:
463 while parser:
464 parser, parts, offset = parser(parts, s, offset)
464 parser, parts, offset = parser(parts, s, offset)
465 return parts
465 return parts
466
466
467 result = self.config(section, name, untrusted=untrusted)
467 result = self.config(section, name, untrusted=untrusted)
468 if result is None:
468 if result is None:
469 result = default or []
469 result = default or []
470 if isinstance(result, basestring):
470 if isinstance(result, basestring):
471 result = _configlist(result.lstrip(' ,\n'))
471 result = _configlist(result.lstrip(' ,\n'))
472 if result is None:
472 if result is None:
473 result = default or []
473 result = default or []
474 return result
474 return result
475
475
476 def has_section(self, section, untrusted=False):
476 def has_section(self, section, untrusted=False):
477 '''tell whether section exists in config.'''
477 '''tell whether section exists in config.'''
478 return section in self._data(untrusted)
478 return section in self._data(untrusted)
479
479
480 def configitems(self, section, untrusted=False):
480 def configitems(self, section, untrusted=False):
481 items = self._data(untrusted).items(section)
481 items = self._data(untrusted).items(section)
482 if self.debugflag and not untrusted and self._reportuntrusted:
482 if self.debugflag and not untrusted and self._reportuntrusted:
483 for k, v in self._ucfg.items(section):
483 for k, v in self._ucfg.items(section):
484 if self._tcfg.get(section, k) != v:
484 if self._tcfg.get(section, k) != v:
485 self.debug("ignoring untrusted configuration option "
485 self.debug("ignoring untrusted configuration option "
486 "%s.%s = %s\n" % (section, k, v))
486 "%s.%s = %s\n" % (section, k, v))
487 return items
487 return items
488
488
489 def walkconfig(self, untrusted=False):
489 def walkconfig(self, untrusted=False):
490 cfg = self._data(untrusted)
490 cfg = self._data(untrusted)
491 for section in cfg.sections():
491 for section in cfg.sections():
492 for name, value in self.configitems(section, untrusted):
492 for name, value in self.configitems(section, untrusted):
493 yield section, name, value
493 yield section, name, value
494
494
495 def plain(self, feature=None):
495 def plain(self, feature=None):
496 '''is plain mode active?
496 '''is plain mode active?
497
497
498 Plain mode means that all configuration variables which affect
498 Plain mode means that all configuration variables which affect
499 the behavior and output of Mercurial should be
499 the behavior and output of Mercurial should be
500 ignored. Additionally, the output should be stable,
500 ignored. Additionally, the output should be stable,
501 reproducible and suitable for use in scripts or applications.
501 reproducible and suitable for use in scripts or applications.
502
502
503 The only way to trigger plain mode is by setting either the
503 The only way to trigger plain mode is by setting either the
504 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
504 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
505
505
506 The return value can either be
506 The return value can either be
507 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
507 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
508 - True otherwise
508 - True otherwise
509 '''
509 '''
510 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
510 if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
511 return False
511 return False
512 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
512 exceptions = os.environ.get('HGPLAINEXCEPT', '').strip().split(',')
513 if feature and exceptions:
513 if feature and exceptions:
514 return feature not in exceptions
514 return feature not in exceptions
515 return True
515 return True
516
516
517 def username(self):
517 def username(self):
518 """Return default username to be used in commits.
518 """Return default username to be used in commits.
519
519
520 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
520 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
521 and stop searching if one of these is set.
521 and stop searching if one of these is set.
522 If not found and ui.askusername is True, ask the user, else use
522 If not found and ui.askusername is True, ask the user, else use
523 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
523 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
524 """
524 """
525 user = os.environ.get("HGUSER")
525 user = os.environ.get("HGUSER")
526 if user is None:
526 if user is None:
527 user = self.config("ui", ["username", "user"])
527 user = self.config("ui", ["username", "user"])
528 if user is not None:
528 if user is not None:
529 user = os.path.expandvars(user)
529 user = os.path.expandvars(user)
530 if user is None:
530 if user is None:
531 user = os.environ.get("EMAIL")
531 user = os.environ.get("EMAIL")
532 if user is None and self.configbool("ui", "askusername"):
532 if user is None and self.configbool("ui", "askusername"):
533 user = self.prompt(_("enter a commit username:"), default=None)
533 user = self.prompt(_("enter a commit username:"), default=None)
534 if user is None and not self.interactive():
534 if user is None and not self.interactive():
535 try:
535 try:
536 user = '%s@%s' % (util.getuser(), socket.getfqdn())
536 user = '%s@%s' % (util.getuser(), socket.getfqdn())
537 self.warn(_("no username found, using '%s' instead\n") % user)
537 self.warn(_("no username found, using '%s' instead\n") % user)
538 except KeyError:
538 except KeyError:
539 pass
539 pass
540 if not user:
540 if not user:
541 raise error.Abort(_('no username supplied'),
541 raise error.Abort(_('no username supplied'),
542 hint=_('use "hg config --edit" '
542 hint=_('use "hg config --edit" '
543 'to set your username'))
543 'to set your username'))
544 if "\n" in user:
544 if "\n" in user:
545 raise error.Abort(_("username %s contains a newline\n")
545 raise error.Abort(_("username %s contains a newline\n")
546 % repr(user))
546 % repr(user))
547 return user
547 return user
548
548
549 def shortuser(self, user):
549 def shortuser(self, user):
550 """Return a short representation of a user name or email address."""
550 """Return a short representation of a user name or email address."""
551 if not self.verbose:
551 if not self.verbose:
552 user = util.shortuser(user)
552 user = util.shortuser(user)
553 return user
553 return user
554
554
555 def expandpath(self, loc, default=None):
555 def expandpath(self, loc, default=None):
556 """Return repository location relative to cwd or from [paths]"""
556 """Return repository location relative to cwd or from [paths]"""
557 try:
557 try:
558 p = self.paths.getpath(loc)
558 p = self.paths.getpath(loc)
559 if p:
559 if p:
560 return p.rawloc
560 return p.rawloc
561 except error.RepoError:
561 except error.RepoError:
562 pass
562 pass
563
563
564 if default:
564 if default:
565 try:
565 try:
566 p = self.paths.getpath(default)
566 p = self.paths.getpath(default)
567 if p:
567 if p:
568 return p.rawloc
568 return p.rawloc
569 except error.RepoError:
569 except error.RepoError:
570 pass
570 pass
571
571
572 return loc
572 return loc
573
573
574 @util.propertycache
574 @util.propertycache
575 def paths(self):
575 def paths(self):
576 return paths(self)
576 return paths(self)
577
577
578 def pushbuffer(self, error=False, subproc=False, labeled=False):
578 def pushbuffer(self, error=False, subproc=False, labeled=False):
579 """install a buffer to capture standard output of the ui object
579 """install a buffer to capture standard output of the ui object
580
580
581 If error is True, the error output will be captured too.
581 If error is True, the error output will be captured too.
582
582
583 If subproc is True, output from subprocesses (typically hooks) will be
583 If subproc is True, output from subprocesses (typically hooks) will be
584 captured too.
584 captured too.
585
585
586 If labeled is True, any labels associated with buffered
586 If labeled is True, any labels associated with buffered
587 output will be handled. By default, this has no effect
587 output will be handled. By default, this has no effect
588 on the output returned, but extensions and GUI tools may
588 on the output returned, but extensions and GUI tools may
589 handle this argument and returned styled output. If output
589 handle this argument and returned styled output. If output
590 is being buffered so it can be captured and parsed or
590 is being buffered so it can be captured and parsed or
591 processed, labeled should not be set to True.
591 processed, labeled should not be set to True.
592 """
592 """
593 self._buffers.append([])
593 self._buffers.append([])
594 self._bufferstates.append((error, subproc, labeled))
594 self._bufferstates.append((error, subproc, labeled))
595 self._bufferapplylabels = labeled
595 self._bufferapplylabels = labeled
596
596
597 def popbuffer(self):
597 def popbuffer(self):
598 '''pop the last buffer and return the buffered output'''
598 '''pop the last buffer and return the buffered output'''
599 self._bufferstates.pop()
599 self._bufferstates.pop()
600 if self._bufferstates:
600 if self._bufferstates:
601 self._bufferapplylabels = self._bufferstates[-1][2]
601 self._bufferapplylabels = self._bufferstates[-1][2]
602 else:
602 else:
603 self._bufferapplylabels = None
603 self._bufferapplylabels = None
604
604
605 return "".join(self._buffers.pop())
605 return "".join(self._buffers.pop())
606
606
607 def write(self, *args, **opts):
607 def write(self, *args, **opts):
608 '''write args to output
608 '''write args to output
609
609
610 By default, this method simply writes to the buffer or stdout,
610 By default, this method simply writes to the buffer or stdout,
611 but extensions or GUI tools may override this method,
611 but extensions or GUI tools may override this method,
612 write_err(), popbuffer(), and label() to style output from
612 write_err(), popbuffer(), and label() to style output from
613 various parts of hg.
613 various parts of hg.
614
614
615 An optional keyword argument, "label", can be passed in.
615 An optional keyword argument, "label", can be passed in.
616 This should be a string containing label names separated by
616 This should be a string containing label names separated by
617 space. Label names take the form of "topic.type". For example,
617 space. Label names take the form of "topic.type". For example,
618 ui.debug() issues a label of "ui.debug".
618 ui.debug() issues a label of "ui.debug".
619
619
620 When labeling output for a specific command, a label of
620 When labeling output for a specific command, a label of
621 "cmdname.type" is recommended. For example, status issues
621 "cmdname.type" is recommended. For example, status issues
622 a label of "status.modified" for modified files.
622 a label of "status.modified" for modified files.
623 '''
623 '''
624 if self._buffers:
624 if self._buffers:
625 self._buffers[-1].extend([str(a) for a in args])
625 self._buffers[-1].extend(a for a in args)
626 else:
626 else:
627 self._progclear()
627 self._progclear()
628 for a in args:
628 for a in args:
629 self.fout.write(str(a))
629 self.fout.write(a)
630
630
631 def write_err(self, *args, **opts):
631 def write_err(self, *args, **opts):
632 self._progclear()
632 self._progclear()
633 try:
633 try:
634 if self._bufferstates and self._bufferstates[-1][0]:
634 if self._bufferstates and self._bufferstates[-1][0]:
635 return self.write(*args, **opts)
635 return self.write(*args, **opts)
636 if not getattr(self.fout, 'closed', False):
636 if not getattr(self.fout, 'closed', False):
637 self.fout.flush()
637 self.fout.flush()
638 for a in args:
638 for a in args:
639 self.ferr.write(str(a))
639 self.ferr.write(a)
640 # stderr may be buffered under win32 when redirected to files,
640 # stderr may be buffered under win32 when redirected to files,
641 # including stdout.
641 # including stdout.
642 if not getattr(self.ferr, 'closed', False):
642 if not getattr(self.ferr, 'closed', False):
643 self.ferr.flush()
643 self.ferr.flush()
644 except IOError as inst:
644 except IOError as inst:
645 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
645 if inst.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
646 raise
646 raise
647
647
648 def flush(self):
648 def flush(self):
649 try: self.fout.flush()
649 try: self.fout.flush()
650 except (IOError, ValueError): pass
650 except (IOError, ValueError): pass
651 try: self.ferr.flush()
651 try: self.ferr.flush()
652 except (IOError, ValueError): pass
652 except (IOError, ValueError): pass
653
653
654 def _isatty(self, fh):
654 def _isatty(self, fh):
655 if self.configbool('ui', 'nontty', False):
655 if self.configbool('ui', 'nontty', False):
656 return False
656 return False
657 return util.isatty(fh)
657 return util.isatty(fh)
658
658
659 def interactive(self):
659 def interactive(self):
660 '''is interactive input allowed?
660 '''is interactive input allowed?
661
661
662 An interactive session is a session where input can be reasonably read
662 An interactive session is a session where input can be reasonably read
663 from `sys.stdin'. If this function returns false, any attempt to read
663 from `sys.stdin'. If this function returns false, any attempt to read
664 from stdin should fail with an error, unless a sensible default has been
664 from stdin should fail with an error, unless a sensible default has been
665 specified.
665 specified.
666
666
667 Interactiveness is triggered by the value of the `ui.interactive'
667 Interactiveness is triggered by the value of the `ui.interactive'
668 configuration variable or - if it is unset - when `sys.stdin' points
668 configuration variable or - if it is unset - when `sys.stdin' points
669 to a terminal device.
669 to a terminal device.
670
670
671 This function refers to input only; for output, see `ui.formatted()'.
671 This function refers to input only; for output, see `ui.formatted()'.
672 '''
672 '''
673 i = self.configbool("ui", "interactive", None)
673 i = self.configbool("ui", "interactive", None)
674 if i is None:
674 if i is None:
675 # some environments replace stdin without implementing isatty
675 # some environments replace stdin without implementing isatty
676 # usually those are non-interactive
676 # usually those are non-interactive
677 return self._isatty(self.fin)
677 return self._isatty(self.fin)
678
678
679 return i
679 return i
680
680
681 def termwidth(self):
681 def termwidth(self):
682 '''how wide is the terminal in columns?
682 '''how wide is the terminal in columns?
683 '''
683 '''
684 if 'COLUMNS' in os.environ:
684 if 'COLUMNS' in os.environ:
685 try:
685 try:
686 return int(os.environ['COLUMNS'])
686 return int(os.environ['COLUMNS'])
687 except ValueError:
687 except ValueError:
688 pass
688 pass
689 return util.termwidth()
689 return util.termwidth()
690
690
691 def formatted(self):
691 def formatted(self):
692 '''should formatted output be used?
692 '''should formatted output be used?
693
693
694 It is often desirable to format the output to suite the output medium.
694 It is often desirable to format the output to suite the output medium.
695 Examples of this are truncating long lines or colorizing messages.
695 Examples of this are truncating long lines or colorizing messages.
696 However, this is not often not desirable when piping output into other
696 However, this is not often not desirable when piping output into other
697 utilities, e.g. `grep'.
697 utilities, e.g. `grep'.
698
698
699 Formatted output is triggered by the value of the `ui.formatted'
699 Formatted output is triggered by the value of the `ui.formatted'
700 configuration variable or - if it is unset - when `sys.stdout' points
700 configuration variable or - if it is unset - when `sys.stdout' points
701 to a terminal device. Please note that `ui.formatted' should be
701 to a terminal device. Please note that `ui.formatted' should be
702 considered an implementation detail; it is not intended for use outside
702 considered an implementation detail; it is not intended for use outside
703 Mercurial or its extensions.
703 Mercurial or its extensions.
704
704
705 This function refers to output only; for input, see `ui.interactive()'.
705 This function refers to output only; for input, see `ui.interactive()'.
706 This function always returns false when in plain mode, see `ui.plain()'.
706 This function always returns false when in plain mode, see `ui.plain()'.
707 '''
707 '''
708 if self.plain():
708 if self.plain():
709 return False
709 return False
710
710
711 i = self.configbool("ui", "formatted", None)
711 i = self.configbool("ui", "formatted", None)
712 if i is None:
712 if i is None:
713 # some environments replace stdout without implementing isatty
713 # some environments replace stdout without implementing isatty
714 # usually those are non-interactive
714 # usually those are non-interactive
715 return self._isatty(self.fout)
715 return self._isatty(self.fout)
716
716
717 return i
717 return i
718
718
719 def _readline(self, prompt=''):
719 def _readline(self, prompt=''):
720 if self._isatty(self.fin):
720 if self._isatty(self.fin):
721 try:
721 try:
722 # magically add command line editing support, where
722 # magically add command line editing support, where
723 # available
723 # available
724 import readline
724 import readline
725 # force demandimport to really load the module
725 # force demandimport to really load the module
726 readline.read_history_file
726 readline.read_history_file
727 # windows sometimes raises something other than ImportError
727 # windows sometimes raises something other than ImportError
728 except Exception:
728 except Exception:
729 pass
729 pass
730
730
731 # call write() so output goes through subclassed implementation
731 # call write() so output goes through subclassed implementation
732 # e.g. color extension on Windows
732 # e.g. color extension on Windows
733 self.write(prompt)
733 self.write(prompt)
734
734
735 # instead of trying to emulate raw_input, swap (self.fin,
735 # instead of trying to emulate raw_input, swap (self.fin,
736 # self.fout) with (sys.stdin, sys.stdout)
736 # self.fout) with (sys.stdin, sys.stdout)
737 oldin = sys.stdin
737 oldin = sys.stdin
738 oldout = sys.stdout
738 oldout = sys.stdout
739 sys.stdin = self.fin
739 sys.stdin = self.fin
740 sys.stdout = self.fout
740 sys.stdout = self.fout
741 # prompt ' ' must exist; otherwise readline may delete entire line
741 # prompt ' ' must exist; otherwise readline may delete entire line
742 # - http://bugs.python.org/issue12833
742 # - http://bugs.python.org/issue12833
743 line = raw_input(' ')
743 line = raw_input(' ')
744 sys.stdin = oldin
744 sys.stdin = oldin
745 sys.stdout = oldout
745 sys.stdout = oldout
746
746
747 # When stdin is in binary mode on Windows, it can cause
747 # When stdin is in binary mode on Windows, it can cause
748 # raw_input() to emit an extra trailing carriage return
748 # raw_input() to emit an extra trailing carriage return
749 if os.linesep == '\r\n' and line and line[-1] == '\r':
749 if os.linesep == '\r\n' and line and line[-1] == '\r':
750 line = line[:-1]
750 line = line[:-1]
751 return line
751 return line
752
752
753 def prompt(self, msg, default="y"):
753 def prompt(self, msg, default="y"):
754 """Prompt user with msg, read response.
754 """Prompt user with msg, read response.
755 If ui is not interactive, the default is returned.
755 If ui is not interactive, the default is returned.
756 """
756 """
757 if not self.interactive():
757 if not self.interactive():
758 self.write(msg, ' ', default, "\n")
758 self.write(msg, ' ', default, "\n")
759 return default
759 return default
760 try:
760 try:
761 r = self._readline(self.label(msg, 'ui.prompt'))
761 r = self._readline(self.label(msg, 'ui.prompt'))
762 if not r:
762 if not r:
763 r = default
763 r = default
764 if self.configbool('ui', 'promptecho'):
764 if self.configbool('ui', 'promptecho'):
765 self.write(r, "\n")
765 self.write(r, "\n")
766 return r
766 return r
767 except EOFError:
767 except EOFError:
768 raise error.ResponseExpected()
768 raise error.ResponseExpected()
769
769
770 @staticmethod
770 @staticmethod
771 def extractchoices(prompt):
771 def extractchoices(prompt):
772 """Extract prompt message and list of choices from specified prompt.
772 """Extract prompt message and list of choices from specified prompt.
773
773
774 This returns tuple "(message, choices)", and "choices" is the
774 This returns tuple "(message, choices)", and "choices" is the
775 list of tuple "(response character, text without &)".
775 list of tuple "(response character, text without &)".
776 """
776 """
777 parts = prompt.split('$$')
777 parts = prompt.split('$$')
778 msg = parts[0].rstrip(' ')
778 msg = parts[0].rstrip(' ')
779 choices = [p.strip(' ') for p in parts[1:]]
779 choices = [p.strip(' ') for p in parts[1:]]
780 return (msg,
780 return (msg,
781 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
781 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
782 for s in choices])
782 for s in choices])
783
783
784 def promptchoice(self, prompt, default=0):
784 def promptchoice(self, prompt, default=0):
785 """Prompt user with a message, read response, and ensure it matches
785 """Prompt user with a message, read response, and ensure it matches
786 one of the provided choices. The prompt is formatted as follows:
786 one of the provided choices. The prompt is formatted as follows:
787
787
788 "would you like fries with that (Yn)? $$ &Yes $$ &No"
788 "would you like fries with that (Yn)? $$ &Yes $$ &No"
789
789
790 The index of the choice is returned. Responses are case
790 The index of the choice is returned. Responses are case
791 insensitive. If ui is not interactive, the default is
791 insensitive. If ui is not interactive, the default is
792 returned.
792 returned.
793 """
793 """
794
794
795 msg, choices = self.extractchoices(prompt)
795 msg, choices = self.extractchoices(prompt)
796 resps = [r for r, t in choices]
796 resps = [r for r, t in choices]
797 while True:
797 while True:
798 r = self.prompt(msg, resps[default])
798 r = self.prompt(msg, resps[default])
799 if r.lower() in resps:
799 if r.lower() in resps:
800 return resps.index(r.lower())
800 return resps.index(r.lower())
801 self.write(_("unrecognized response\n"))
801 self.write(_("unrecognized response\n"))
802
802
803 def getpass(self, prompt=None, default=None):
803 def getpass(self, prompt=None, default=None):
804 if not self.interactive():
804 if not self.interactive():
805 return default
805 return default
806 try:
806 try:
807 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
807 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
808 # disable getpass() only if explicitly specified. it's still valid
808 # disable getpass() only if explicitly specified. it's still valid
809 # to interact with tty even if fin is not a tty.
809 # to interact with tty even if fin is not a tty.
810 if self.configbool('ui', 'nontty'):
810 if self.configbool('ui', 'nontty'):
811 return self.fin.readline().rstrip('\n')
811 return self.fin.readline().rstrip('\n')
812 else:
812 else:
813 return getpass.getpass('')
813 return getpass.getpass('')
814 except EOFError:
814 except EOFError:
815 raise error.ResponseExpected()
815 raise error.ResponseExpected()
816 def status(self, *msg, **opts):
816 def status(self, *msg, **opts):
817 '''write status message to output (if ui.quiet is False)
817 '''write status message to output (if ui.quiet is False)
818
818
819 This adds an output label of "ui.status".
819 This adds an output label of "ui.status".
820 '''
820 '''
821 if not self.quiet:
821 if not self.quiet:
822 opts['label'] = opts.get('label', '') + ' ui.status'
822 opts['label'] = opts.get('label', '') + ' ui.status'
823 self.write(*msg, **opts)
823 self.write(*msg, **opts)
824 def warn(self, *msg, **opts):
824 def warn(self, *msg, **opts):
825 '''write warning message to output (stderr)
825 '''write warning message to output (stderr)
826
826
827 This adds an output label of "ui.warning".
827 This adds an output label of "ui.warning".
828 '''
828 '''
829 opts['label'] = opts.get('label', '') + ' ui.warning'
829 opts['label'] = opts.get('label', '') + ' ui.warning'
830 self.write_err(*msg, **opts)
830 self.write_err(*msg, **opts)
831 def note(self, *msg, **opts):
831 def note(self, *msg, **opts):
832 '''write note to output (if ui.verbose is True)
832 '''write note to output (if ui.verbose is True)
833
833
834 This adds an output label of "ui.note".
834 This adds an output label of "ui.note".
835 '''
835 '''
836 if self.verbose:
836 if self.verbose:
837 opts['label'] = opts.get('label', '') + ' ui.note'
837 opts['label'] = opts.get('label', '') + ' ui.note'
838 self.write(*msg, **opts)
838 self.write(*msg, **opts)
839 def debug(self, *msg, **opts):
839 def debug(self, *msg, **opts):
840 '''write debug message to output (if ui.debugflag is True)
840 '''write debug message to output (if ui.debugflag is True)
841
841
842 This adds an output label of "ui.debug".
842 This adds an output label of "ui.debug".
843 '''
843 '''
844 if self.debugflag:
844 if self.debugflag:
845 opts['label'] = opts.get('label', '') + ' ui.debug'
845 opts['label'] = opts.get('label', '') + ' ui.debug'
846 self.write(*msg, **opts)
846 self.write(*msg, **opts)
847
847
848 def edit(self, text, user, extra=None, editform=None, pending=None):
848 def edit(self, text, user, extra=None, editform=None, pending=None):
849 if extra is None:
849 if extra is None:
850 extra = {}
850 extra = {}
851 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
851 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
852 text=True)
852 text=True)
853 try:
853 try:
854 f = os.fdopen(fd, "w")
854 f = os.fdopen(fd, "w")
855 f.write(text)
855 f.write(text)
856 f.close()
856 f.close()
857
857
858 environ = {'HGUSER': user}
858 environ = {'HGUSER': user}
859 if 'transplant_source' in extra:
859 if 'transplant_source' in extra:
860 environ.update({'HGREVISION': hex(extra['transplant_source'])})
860 environ.update({'HGREVISION': hex(extra['transplant_source'])})
861 for label in ('intermediate-source', 'source', 'rebase_source'):
861 for label in ('intermediate-source', 'source', 'rebase_source'):
862 if label in extra:
862 if label in extra:
863 environ.update({'HGREVISION': extra[label]})
863 environ.update({'HGREVISION': extra[label]})
864 break
864 break
865 if editform:
865 if editform:
866 environ.update({'HGEDITFORM': editform})
866 environ.update({'HGEDITFORM': editform})
867 if pending:
867 if pending:
868 environ.update({'HG_PENDING': pending})
868 environ.update({'HG_PENDING': pending})
869
869
870 editor = self.geteditor()
870 editor = self.geteditor()
871
871
872 self.system("%s \"%s\"" % (editor, name),
872 self.system("%s \"%s\"" % (editor, name),
873 environ=environ,
873 environ=environ,
874 onerr=error.Abort, errprefix=_("edit failed"))
874 onerr=error.Abort, errprefix=_("edit failed"))
875
875
876 f = open(name)
876 f = open(name)
877 t = f.read()
877 t = f.read()
878 f.close()
878 f.close()
879 finally:
879 finally:
880 os.unlink(name)
880 os.unlink(name)
881
881
882 return t
882 return t
883
883
884 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
884 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None):
885 '''execute shell command with appropriate output stream. command
885 '''execute shell command with appropriate output stream. command
886 output will be redirected if fout is not stdout.
886 output will be redirected if fout is not stdout.
887 '''
887 '''
888 out = self.fout
888 out = self.fout
889 if any(s[1] for s in self._bufferstates):
889 if any(s[1] for s in self._bufferstates):
890 out = self
890 out = self
891 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
891 return util.system(cmd, environ=environ, cwd=cwd, onerr=onerr,
892 errprefix=errprefix, out=out)
892 errprefix=errprefix, out=out)
893
893
894 def traceback(self, exc=None, force=False):
894 def traceback(self, exc=None, force=False):
895 '''print exception traceback if traceback printing enabled or forced.
895 '''print exception traceback if traceback printing enabled or forced.
896 only to call in exception handler. returns true if traceback
896 only to call in exception handler. returns true if traceback
897 printed.'''
897 printed.'''
898 if self.tracebackflag or force:
898 if self.tracebackflag or force:
899 if exc is None:
899 if exc is None:
900 exc = sys.exc_info()
900 exc = sys.exc_info()
901 cause = getattr(exc[1], 'cause', None)
901 cause = getattr(exc[1], 'cause', None)
902
902
903 if cause is not None:
903 if cause is not None:
904 causetb = traceback.format_tb(cause[2])
904 causetb = traceback.format_tb(cause[2])
905 exctb = traceback.format_tb(exc[2])
905 exctb = traceback.format_tb(exc[2])
906 exconly = traceback.format_exception_only(cause[0], cause[1])
906 exconly = traceback.format_exception_only(cause[0], cause[1])
907
907
908 # exclude frame where 'exc' was chained and rethrown from exctb
908 # exclude frame where 'exc' was chained and rethrown from exctb
909 self.write_err('Traceback (most recent call last):\n',
909 self.write_err('Traceback (most recent call last):\n',
910 ''.join(exctb[:-1]),
910 ''.join(exctb[:-1]),
911 ''.join(causetb),
911 ''.join(causetb),
912 ''.join(exconly))
912 ''.join(exconly))
913 else:
913 else:
914 output = traceback.format_exception(exc[0], exc[1], exc[2])
914 output = traceback.format_exception(exc[0], exc[1], exc[2])
915 self.write_err(''.join(output))
915 self.write_err(''.join(output))
916 return self.tracebackflag or force
916 return self.tracebackflag or force
917
917
918 def geteditor(self):
918 def geteditor(self):
919 '''return editor to use'''
919 '''return editor to use'''
920 if sys.platform == 'plan9':
920 if sys.platform == 'plan9':
921 # vi is the MIPS instruction simulator on Plan 9. We
921 # vi is the MIPS instruction simulator on Plan 9. We
922 # instead default to E to plumb commit messages to
922 # instead default to E to plumb commit messages to
923 # avoid confusion.
923 # avoid confusion.
924 editor = 'E'
924 editor = 'E'
925 else:
925 else:
926 editor = 'vi'
926 editor = 'vi'
927 return (os.environ.get("HGEDITOR") or
927 return (os.environ.get("HGEDITOR") or
928 self.config("ui", "editor") or
928 self.config("ui", "editor") or
929 os.environ.get("VISUAL") or
929 os.environ.get("VISUAL") or
930 os.environ.get("EDITOR", editor))
930 os.environ.get("EDITOR", editor))
931
931
932 @util.propertycache
932 @util.propertycache
933 def _progbar(self):
933 def _progbar(self):
934 """setup the progbar singleton to the ui object"""
934 """setup the progbar singleton to the ui object"""
935 if (self.quiet or self.debugflag
935 if (self.quiet or self.debugflag
936 or self.configbool('progress', 'disable', False)
936 or self.configbool('progress', 'disable', False)
937 or not progress.shouldprint(self)):
937 or not progress.shouldprint(self)):
938 return None
938 return None
939 return getprogbar(self)
939 return getprogbar(self)
940
940
941 def _progclear(self):
941 def _progclear(self):
942 """clear progress bar output if any. use it before any output"""
942 """clear progress bar output if any. use it before any output"""
943 if '_progbar' not in vars(self): # nothing loaded yet
943 if '_progbar' not in vars(self): # nothing loaded yet
944 return
944 return
945 if self._progbar is not None and self._progbar.printed:
945 if self._progbar is not None and self._progbar.printed:
946 self._progbar.clear()
946 self._progbar.clear()
947
947
948 def progress(self, topic, pos, item="", unit="", total=None):
948 def progress(self, topic, pos, item="", unit="", total=None):
949 '''show a progress message
949 '''show a progress message
950
950
951 With stock hg, this is simply a debug message that is hidden
951 With stock hg, this is simply a debug message that is hidden
952 by default, but with extensions or GUI tools it may be
952 by default, but with extensions or GUI tools it may be
953 visible. 'topic' is the current operation, 'item' is a
953 visible. 'topic' is the current operation, 'item' is a
954 non-numeric marker of the current position (i.e. the currently
954 non-numeric marker of the current position (i.e. the currently
955 in-process file), 'pos' is the current numeric position (i.e.
955 in-process file), 'pos' is the current numeric position (i.e.
956 revision, bytes, etc.), unit is a corresponding unit label,
956 revision, bytes, etc.), unit is a corresponding unit label,
957 and total is the highest expected pos.
957 and total is the highest expected pos.
958
958
959 Multiple nested topics may be active at a time.
959 Multiple nested topics may be active at a time.
960
960
961 All topics should be marked closed by setting pos to None at
961 All topics should be marked closed by setting pos to None at
962 termination.
962 termination.
963 '''
963 '''
964 if self._progbar is not None:
964 if self._progbar is not None:
965 self._progbar.progress(topic, pos, item=item, unit=unit,
965 self._progbar.progress(topic, pos, item=item, unit=unit,
966 total=total)
966 total=total)
967 if pos is None or not self.configbool('progress', 'debug'):
967 if pos is None or not self.configbool('progress', 'debug'):
968 return
968 return
969
969
970 if unit:
970 if unit:
971 unit = ' ' + unit
971 unit = ' ' + unit
972 if item:
972 if item:
973 item = ' ' + item
973 item = ' ' + item
974
974
975 if total:
975 if total:
976 pct = 100.0 * pos / total
976 pct = 100.0 * pos / total
977 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
977 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
978 % (topic, item, pos, total, unit, pct))
978 % (topic, item, pos, total, unit, pct))
979 else:
979 else:
980 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
980 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
981
981
982 def log(self, service, *msg, **opts):
982 def log(self, service, *msg, **opts):
983 '''hook for logging facility extensions
983 '''hook for logging facility extensions
984
984
985 service should be a readily-identifiable subsystem, which will
985 service should be a readily-identifiable subsystem, which will
986 allow filtering.
986 allow filtering.
987
987
988 *msg should be a newline-terminated format string to log, and
988 *msg should be a newline-terminated format string to log, and
989 then any values to %-format into that format string.
989 then any values to %-format into that format string.
990
990
991 **opts currently has no defined meanings.
991 **opts currently has no defined meanings.
992 '''
992 '''
993
993
994 def label(self, msg, label):
994 def label(self, msg, label):
995 '''style msg based on supplied label
995 '''style msg based on supplied label
996
996
997 Like ui.write(), this just returns msg unchanged, but extensions
997 Like ui.write(), this just returns msg unchanged, but extensions
998 and GUI tools can override it to allow styling output without
998 and GUI tools can override it to allow styling output without
999 writing it.
999 writing it.
1000
1000
1001 ui.write(s, 'label') is equivalent to
1001 ui.write(s, 'label') is equivalent to
1002 ui.write(ui.label(s, 'label')).
1002 ui.write(ui.label(s, 'label')).
1003 '''
1003 '''
1004 return msg
1004 return msg
1005
1005
1006 def develwarn(self, msg):
1006 def develwarn(self, msg):
1007 """issue a developer warning message"""
1007 """issue a developer warning message"""
1008 msg = 'devel-warn: ' + msg
1008 msg = 'devel-warn: ' + msg
1009 if self.tracebackflag:
1009 if self.tracebackflag:
1010 util.debugstacktrace(msg, 2, self.ferr, self.fout)
1010 util.debugstacktrace(msg, 2, self.ferr, self.fout)
1011 else:
1011 else:
1012 curframe = inspect.currentframe()
1012 curframe = inspect.currentframe()
1013 calframe = inspect.getouterframes(curframe, 2)
1013 calframe = inspect.getouterframes(curframe, 2)
1014 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1014 self.write_err('%s at: %s:%s (%s)\n' % ((msg,) + calframe[2][1:4]))
1015
1015
1016 class paths(dict):
1016 class paths(dict):
1017 """Represents a collection of paths and their configs.
1017 """Represents a collection of paths and their configs.
1018
1018
1019 Data is initially derived from ui instances and the config files they have
1019 Data is initially derived from ui instances and the config files they have
1020 loaded.
1020 loaded.
1021 """
1021 """
1022 def __init__(self, ui):
1022 def __init__(self, ui):
1023 dict.__init__(self)
1023 dict.__init__(self)
1024
1024
1025 for name, loc in ui.configitems('paths'):
1025 for name, loc in ui.configitems('paths'):
1026 # No location is the same as not existing.
1026 # No location is the same as not existing.
1027 if not loc:
1027 if not loc:
1028 continue
1028 continue
1029
1029
1030 # TODO ignore default-push once all consumers stop referencing it
1030 # TODO ignore default-push once all consumers stop referencing it
1031 # since it is handled specifically below.
1031 # since it is handled specifically below.
1032
1032
1033 self[name] = path(name, rawloc=loc)
1033 self[name] = path(name, rawloc=loc)
1034
1034
1035 # Handle default-push, which is a one-off that defines the push URL for
1035 # Handle default-push, which is a one-off that defines the push URL for
1036 # the "default" path.
1036 # the "default" path.
1037 defaultpush = ui.config('paths', 'default-push')
1037 defaultpush = ui.config('paths', 'default-push')
1038 if defaultpush:
1038 if defaultpush:
1039 # "default-push" can be defined without "default" entry. This is a
1039 # "default-push" can be defined without "default" entry. This is a
1040 # bit weird, but is allowed for backwards compatibility.
1040 # bit weird, but is allowed for backwards compatibility.
1041 if 'default' not in self:
1041 if 'default' not in self:
1042 self['default'] = path('default', rawloc=defaultpush)
1042 self['default'] = path('default', rawloc=defaultpush)
1043 self['default']._pushloc = defaultpush
1043 self['default']._pushloc = defaultpush
1044
1044
1045 def getpath(self, name, default=None):
1045 def getpath(self, name, default=None):
1046 """Return a ``path`` from a string, falling back to a default.
1046 """Return a ``path`` from a string, falling back to a default.
1047
1047
1048 ``name`` can be a named path or locations. Locations are filesystem
1048 ``name`` can be a named path or locations. Locations are filesystem
1049 paths or URIs.
1049 paths or URIs.
1050
1050
1051 Returns None if ``name`` is not a registered path, a URI, or a local
1051 Returns None if ``name`` is not a registered path, a URI, or a local
1052 path to a repo.
1052 path to a repo.
1053 """
1053 """
1054 # Only fall back to default if no path was requested.
1054 # Only fall back to default if no path was requested.
1055 if name is None:
1055 if name is None:
1056 if default:
1056 if default:
1057 try:
1057 try:
1058 return self[default]
1058 return self[default]
1059 except KeyError:
1059 except KeyError:
1060 return None
1060 return None
1061 else:
1061 else:
1062 return None
1062 return None
1063
1063
1064 # Most likely empty string.
1064 # Most likely empty string.
1065 # This may need to raise in the future.
1065 # This may need to raise in the future.
1066 if not name:
1066 if not name:
1067 return None
1067 return None
1068
1068
1069 try:
1069 try:
1070 return self[name]
1070 return self[name]
1071 except KeyError:
1071 except KeyError:
1072 # Try to resolve as a local path or URI.
1072 # Try to resolve as a local path or URI.
1073 try:
1073 try:
1074 return path(None, rawloc=name)
1074 return path(None, rawloc=name)
1075 except ValueError:
1075 except ValueError:
1076 raise error.RepoError(_('repository %s does not exist') %
1076 raise error.RepoError(_('repository %s does not exist') %
1077 name)
1077 name)
1078
1078
1079 assert False
1079 assert False
1080
1080
1081 class path(object):
1081 class path(object):
1082 """Represents an individual path and its configuration."""
1082 """Represents an individual path and its configuration."""
1083
1083
1084 def __init__(self, name, rawloc=None, pushloc=None):
1084 def __init__(self, name, rawloc=None, pushloc=None):
1085 """Construct a path from its config options.
1085 """Construct a path from its config options.
1086
1086
1087 ``name`` is the symbolic name of the path.
1087 ``name`` is the symbolic name of the path.
1088 ``rawloc`` is the raw location, as defined in the config.
1088 ``rawloc`` is the raw location, as defined in the config.
1089 ``pushloc`` is the raw locations pushes should be made to.
1089 ``pushloc`` is the raw locations pushes should be made to.
1090
1090
1091 If ``name`` is not defined, we require that the location be a) a local
1091 If ``name`` is not defined, we require that the location be a) a local
1092 filesystem path with a .hg directory or b) a URL. If not,
1092 filesystem path with a .hg directory or b) a URL. If not,
1093 ``ValueError`` is raised.
1093 ``ValueError`` is raised.
1094 """
1094 """
1095 if not rawloc:
1095 if not rawloc:
1096 raise ValueError('rawloc must be defined')
1096 raise ValueError('rawloc must be defined')
1097
1097
1098 # Locations may define branches via syntax <base>#<branch>.
1098 # Locations may define branches via syntax <base>#<branch>.
1099 u = util.url(rawloc)
1099 u = util.url(rawloc)
1100 branch = None
1100 branch = None
1101 if u.fragment:
1101 if u.fragment:
1102 branch = u.fragment
1102 branch = u.fragment
1103 u.fragment = None
1103 u.fragment = None
1104
1104
1105 self.url = u
1105 self.url = u
1106 self.branch = branch
1106 self.branch = branch
1107
1107
1108 self.name = name
1108 self.name = name
1109 self.rawloc = rawloc
1109 self.rawloc = rawloc
1110 self.loc = str(u)
1110 self.loc = str(u)
1111 self._pushloc = pushloc
1111 self._pushloc = pushloc
1112
1112
1113 # When given a raw location but not a symbolic name, validate the
1113 # When given a raw location but not a symbolic name, validate the
1114 # location is valid.
1114 # location is valid.
1115 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1115 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1116 raise ValueError('location is not a URL or path to a local '
1116 raise ValueError('location is not a URL or path to a local '
1117 'repo: %s' % rawloc)
1117 'repo: %s' % rawloc)
1118
1118
1119 def _isvalidlocalpath(self, path):
1119 def _isvalidlocalpath(self, path):
1120 """Returns True if the given path is a potentially valid repository.
1120 """Returns True if the given path is a potentially valid repository.
1121 This is its own function so that extensions can change the definition of
1121 This is its own function so that extensions can change the definition of
1122 'valid' in this case (like when pulling from a git repo into a hg
1122 'valid' in this case (like when pulling from a git repo into a hg
1123 one)."""
1123 one)."""
1124 return os.path.isdir(os.path.join(path, '.hg'))
1124 return os.path.isdir(os.path.join(path, '.hg'))
1125
1125
1126 @property
1126 @property
1127 def pushloc(self):
1127 def pushloc(self):
1128 return self._pushloc or self.loc
1128 return self._pushloc or self.loc
1129
1129
1130 # we instantiate one globally shared progress bar to avoid
1130 # we instantiate one globally shared progress bar to avoid
1131 # competing progress bars when multiple UI objects get created
1131 # competing progress bars when multiple UI objects get created
1132 _progresssingleton = None
1132 _progresssingleton = None
1133
1133
1134 def getprogbar(ui):
1134 def getprogbar(ui):
1135 global _progresssingleton
1135 global _progresssingleton
1136 if _progresssingleton is None:
1136 if _progresssingleton is None:
1137 # passing 'ui' object to the singleton is fishy,
1137 # passing 'ui' object to the singleton is fishy,
1138 # this is how the extension used to work but feel free to rework it.
1138 # this is how the extension used to work but feel free to rework it.
1139 _progresssingleton = progress.progbar(ui)
1139 _progresssingleton = progress.progbar(ui)
1140 return _progresssingleton
1140 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now