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