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