##// END OF EJS Templates
color: move triggering of the initialisation logic in core...
Pierre-Yves David -
r31105:45be7590 default
parent child Browse files
Show More
@@ -1,252 +1,240
1 1 # color.py color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 '''colorize output from some commands
9 9
10 10 The color extension colorizes output from several Mercurial commands.
11 11 For example, the diff command shows additions in green and deletions
12 12 in red, while the status command shows modified files in magenta. Many
13 13 other commands have analogous colors. It is possible to customize
14 14 these colors.
15 15
16 16 Effects
17 17 -------
18 18
19 19 Other effects in addition to color, like bold and underlined text, are
20 20 also available. By default, the terminfo database is used to find the
21 21 terminal codes used to change color and effect. If terminfo is not
22 22 available, then effects are rendered with the ECMA-48 SGR control
23 23 function (aka ANSI escape codes).
24 24
25 25 The available effects in terminfo mode are 'blink', 'bold', 'dim',
26 26 'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
27 27 ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
28 28 'underline'. How each is rendered depends on the terminal emulator.
29 29 Some may not be available for a given terminal type, and will be
30 30 silently ignored.
31 31
32 32 If the terminfo entry for your terminal is missing codes for an effect
33 33 or has the wrong codes, you can add or override those codes in your
34 34 configuration::
35 35
36 36 [color]
37 37 terminfo.dim = \E[2m
38 38
39 39 where '\E' is substituted with an escape character.
40 40
41 41 Labels
42 42 ------
43 43
44 44 Text receives color effects depending on the labels that it has. Many
45 45 default Mercurial commands emit labelled text. You can also define
46 46 your own labels in templates using the label function, see :hg:`help
47 47 templates`. A single portion of text may have more than one label. In
48 48 that case, effects given to the last label will override any other
49 49 effects. This includes the special "none" effect, which nullifies
50 50 other effects.
51 51
52 52 Labels are normally invisible. In order to see these labels and their
53 53 position in the text, use the global --color=debug option. The same
54 54 anchor text may be associated to multiple labels, e.g.
55 55
56 56 [log.changeset changeset.secret|changeset: 22611:6f0a53c8f587]
57 57
58 58 The following are the default effects for some default labels. Default
59 59 effects may be overridden from your configuration file::
60 60
61 61 [color]
62 62 status.modified = blue bold underline red_background
63 63 status.added = green bold
64 64 status.removed = red bold blue_background
65 65 status.deleted = cyan bold underline
66 66 status.unknown = magenta bold underline
67 67 status.ignored = black bold
68 68
69 69 # 'none' turns off all effects
70 70 status.clean = none
71 71 status.copied = none
72 72
73 73 qseries.applied = blue bold underline
74 74 qseries.unapplied = black bold
75 75 qseries.missing = red bold
76 76
77 77 diff.diffline = bold
78 78 diff.extended = cyan bold
79 79 diff.file_a = red bold
80 80 diff.file_b = green bold
81 81 diff.hunk = magenta
82 82 diff.deleted = red
83 83 diff.inserted = green
84 84 diff.changed = white
85 85 diff.tab =
86 86 diff.trailingwhitespace = bold red_background
87 87
88 88 # Blank so it inherits the style of the surrounding label
89 89 changeset.public =
90 90 changeset.draft =
91 91 changeset.secret =
92 92
93 93 resolve.unresolved = red bold
94 94 resolve.resolved = green bold
95 95
96 96 bookmarks.active = green
97 97
98 98 branches.active = none
99 99 branches.closed = black bold
100 100 branches.current = green
101 101 branches.inactive = none
102 102
103 103 tags.normal = green
104 104 tags.local = black bold
105 105
106 106 rebase.rebased = blue
107 107 rebase.remaining = red bold
108 108
109 109 shelve.age = cyan
110 110 shelve.newest = green bold
111 111 shelve.name = blue bold
112 112
113 113 histedit.remaining = red bold
114 114
115 115 Custom colors
116 116 -------------
117 117
118 118 Because there are only eight standard colors, this module allows you
119 119 to define color names for other color slots which might be available
120 120 for your terminal type, assuming terminfo mode. For instance::
121 121
122 122 color.brightblue = 12
123 123 color.pink = 207
124 124 color.orange = 202
125 125
126 126 to set 'brightblue' to color slot 12 (useful for 16 color terminals
127 127 that have brighter colors defined in the upper eight) and, 'pink' and
128 128 'orange' to colors in 256-color xterm's default color cube. These
129 129 defined colors may then be used as any of the pre-defined eight,
130 130 including appending '_background' to set the background to that color.
131 131
132 132 Modes
133 133 -----
134 134
135 135 By default, the color extension will use ANSI mode (or win32 mode on
136 136 Windows) if it detects a terminal. To override auto mode (to enable
137 137 terminfo mode, for example), set the following configuration option::
138 138
139 139 [color]
140 140 mode = terminfo
141 141
142 142 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
143 143 disable color.
144 144
145 145 Note that on some systems, terminfo mode may cause problems when using
146 146 color with the pager extension and less -R. less with the -R option
147 147 will only display ECMA-48 color codes, and terminfo mode may sometimes
148 148 emit codes that less doesn't understand. You can work around this by
149 149 either using ansi mode (or auto mode), or by using less -r (which will
150 150 pass through all terminal control codes, not just color control
151 151 codes).
152 152
153 153 On some systems (such as MSYS in Windows), the terminal may support
154 154 a different color mode than the pager (activated via the "pager"
155 155 extension). It is possible to define separate modes depending on whether
156 156 the pager is active::
157 157
158 158 [color]
159 159 mode = auto
160 160 pagermode = ansi
161 161
162 162 If ``pagermode`` is not defined, the ``mode`` will be used.
163 163 '''
164 164
165 165 from __future__ import absolute_import
166 166
167 167 try:
168 168 import curses
169 169 curses.COLOR_BLACK # force import
170 170 except ImportError:
171 171 curses = None
172 172
173 173 from mercurial.i18n import _
174 174 from mercurial import (
175 175 cmdutil,
176 176 color,
177 177 commands,
178 dispatch,
179 extensions,
180 ui as uimod,
181 178 )
182 179
183 180 cmdtable = {}
184 181 command = cmdutil.command(cmdtable)
185 182 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
186 183 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
187 184 # be specifying the version(s) of Mercurial they are tested with, or
188 185 # leave the attribute unspecified.
189 186 testedwith = 'ships-with-hg-core'
190 187
191 def uisetup(ui):
192 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
193 mode = color._modesetup(ui_, opts['color'])
194 uimod.ui._colormode = mode
195 if mode and mode != 'debug':
196 color.configstyles(ui_)
197 return orig(ui_, opts, cmd, cmdfunc)
198 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
199
200 188 def extsetup(ui):
201 189 # change default color config
202 190 for idx, entry in enumerate(commands.globalopts):
203 191 if entry[1] == 'color':
204 192 patch = ('auto', entry[3].replace(' (EXPERIMENTAL)', ''))
205 193 new = entry[:2] + patch + entry[4:]
206 194 commands.globalopts[idx] = new
207 195 break
208 196
209 197 @command('debugcolor',
210 198 [('', 'style', None, _('show all configured styles'))],
211 199 'hg debugcolor')
212 200 def debugcolor(ui, repo, **opts):
213 201 """show available color, effects or style"""
214 202 ui.write(('color mode: %s\n') % ui._colormode)
215 203 if opts.get('style'):
216 204 return _debugdisplaystyle(ui)
217 205 else:
218 206 return _debugdisplaycolor(ui)
219 207
220 208 def _debugdisplaycolor(ui):
221 209 oldstyle = color._styles.copy()
222 210 try:
223 211 color._styles.clear()
224 212 for effect in color._effects.keys():
225 213 color._styles[effect] = effect
226 214 if color._terminfo_params:
227 215 for k, v in ui.configitems('color'):
228 216 if k.startswith('color.'):
229 217 color._styles[k] = k[6:]
230 218 elif k.startswith('terminfo.'):
231 219 color._styles[k] = k[9:]
232 220 ui.write(_('available colors:\n'))
233 221 # sort label with a '_' after the other to group '_background' entry.
234 222 items = sorted(color._styles.items(),
235 223 key=lambda i: ('_' in i[0], i[0], i[1]))
236 224 for colorname, label in items:
237 225 ui.write(('%s\n') % colorname, label=label)
238 226 finally:
239 227 color._styles.clear()
240 228 color._styles.update(oldstyle)
241 229
242 230 def _debugdisplaystyle(ui):
243 231 ui.write(_('available style:\n'))
244 232 width = max(len(s) for s in color._styles)
245 233 for label, effects in sorted(color._styles.items()):
246 234 ui.write('%s' % label, label=label)
247 235 if effects:
248 236 # 50
249 237 ui.write(': ')
250 238 ui.write(' ' * (max(0, width - len(label))))
251 239 ui.write(', '.join(ui.label(e, e) for e in effects.split()))
252 240 ui.write('\n')
@@ -1,446 +1,457
1 1 # utility for color output for Mercurial commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com> and other
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 from .i18n import _
11 11
12 12 from . import (
13 13 encoding,
14 14 pycompat,
15 15 util
16 16 )
17 17
18 18 try:
19 19 import curses
20 20 # Mapping from effect name to terminfo attribute name (or raw code) or
21 21 # color number. This will also force-load the curses module.
22 22 _terminfo_params = {'none': (True, 'sgr0', ''),
23 23 'standout': (True, 'smso', ''),
24 24 'underline': (True, 'smul', ''),
25 25 'reverse': (True, 'rev', ''),
26 26 'inverse': (True, 'rev', ''),
27 27 'blink': (True, 'blink', ''),
28 28 'dim': (True, 'dim', ''),
29 29 'bold': (True, 'bold', ''),
30 30 'invisible': (True, 'invis', ''),
31 31 'italic': (True, 'sitm', ''),
32 32 'black': (False, curses.COLOR_BLACK, ''),
33 33 'red': (False, curses.COLOR_RED, ''),
34 34 'green': (False, curses.COLOR_GREEN, ''),
35 35 'yellow': (False, curses.COLOR_YELLOW, ''),
36 36 'blue': (False, curses.COLOR_BLUE, ''),
37 37 'magenta': (False, curses.COLOR_MAGENTA, ''),
38 38 'cyan': (False, curses.COLOR_CYAN, ''),
39 39 'white': (False, curses.COLOR_WHITE, '')}
40 40 except ImportError:
41 41 curses = None
42 42 _terminfo_params = {}
43 43
44 44 # start and stop parameters for effects
45 45 _effects = {'none': 0,
46 46 'black': 30,
47 47 'red': 31,
48 48 'green': 32,
49 49 'yellow': 33,
50 50 'blue': 34,
51 51 'magenta': 35,
52 52 'cyan': 36,
53 53 'white': 37,
54 54 'bold': 1,
55 55 'italic': 3,
56 56 'underline': 4,
57 57 'inverse': 7,
58 58 'dim': 2,
59 59 'black_background': 40,
60 60 'red_background': 41,
61 61 'green_background': 42,
62 62 'yellow_background': 43,
63 63 'blue_background': 44,
64 64 'purple_background': 45,
65 65 'cyan_background': 46,
66 66 'white_background': 47}
67 67
68 68 _styles = {'grep.match': 'red bold',
69 69 'grep.linenumber': 'green',
70 70 'grep.rev': 'green',
71 71 'grep.change': 'green',
72 72 'grep.sep': 'cyan',
73 73 'grep.filename': 'magenta',
74 74 'grep.user': 'magenta',
75 75 'grep.date': 'magenta',
76 76 'bookmarks.active': 'green',
77 77 'branches.active': 'none',
78 78 'branches.closed': 'black bold',
79 79 'branches.current': 'green',
80 80 'branches.inactive': 'none',
81 81 'diff.changed': 'white',
82 82 'diff.deleted': 'red',
83 83 'diff.diffline': 'bold',
84 84 'diff.extended': 'cyan bold',
85 85 'diff.file_a': 'red bold',
86 86 'diff.file_b': 'green bold',
87 87 'diff.hunk': 'magenta',
88 88 'diff.inserted': 'green',
89 89 'diff.tab': '',
90 90 'diff.trailingwhitespace': 'bold red_background',
91 91 'changeset.public' : '',
92 92 'changeset.draft' : '',
93 93 'changeset.secret' : '',
94 94 'diffstat.deleted': 'red',
95 95 'diffstat.inserted': 'green',
96 96 'histedit.remaining': 'red bold',
97 97 'ui.prompt': 'yellow',
98 98 'log.changeset': 'yellow',
99 99 'patchbomb.finalsummary': '',
100 100 'patchbomb.from': 'magenta',
101 101 'patchbomb.to': 'cyan',
102 102 'patchbomb.subject': 'green',
103 103 'patchbomb.diffstats': '',
104 104 'rebase.rebased': 'blue',
105 105 'rebase.remaining': 'red bold',
106 106 'resolve.resolved': 'green bold',
107 107 'resolve.unresolved': 'red bold',
108 108 'shelve.age': 'cyan',
109 109 'shelve.newest': 'green bold',
110 110 'shelve.name': 'blue bold',
111 111 'status.added': 'green bold',
112 112 'status.clean': 'none',
113 113 'status.copied': 'none',
114 114 'status.deleted': 'cyan bold underline',
115 115 'status.ignored': 'black bold',
116 116 'status.modified': 'blue bold',
117 117 'status.removed': 'red bold',
118 118 'status.unknown': 'magenta bold underline',
119 119 'tags.normal': 'green',
120 120 'tags.local': 'black bold'}
121 121
122 122 def loadcolortable(ui, extname, colortable):
123 123 _styles.update(colortable)
124 124
125 125 def _terminfosetup(ui, mode):
126 126 '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
127 127
128 128 # If we failed to load curses, we go ahead and return.
129 129 if curses is None:
130 130 return
131 131 # Otherwise, see what the config file says.
132 132 if mode not in ('auto', 'terminfo'):
133 133 return
134 134
135 135 for key, val in ui.configitems('color'):
136 136 if key.startswith('color.'):
137 137 newval = (False, int(val), '')
138 138 _terminfo_params[key[6:]] = newval
139 139 elif key.startswith('terminfo.'):
140 140 newval = (True, '', val.replace('\\E', '\x1b'))
141 141 _terminfo_params[key[9:]] = newval
142 142 try:
143 143 curses.setupterm()
144 144 except curses.error as e:
145 145 _terminfo_params.clear()
146 146 return
147 147
148 148 for key, (b, e, c) in _terminfo_params.items():
149 149 if not b:
150 150 continue
151 151 if not c and not curses.tigetstr(e):
152 152 # Most terminals don't support dim, invis, etc, so don't be
153 153 # noisy and use ui.debug().
154 154 ui.debug("no terminfo entry for %s\n" % e)
155 155 del _terminfo_params[key]
156 156 if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
157 157 # Only warn about missing terminfo entries if we explicitly asked for
158 158 # terminfo mode.
159 159 if mode == "terminfo":
160 160 ui.warn(_("no terminfo entry for setab/setaf: reverting to "
161 161 "ECMA-48 color\n"))
162 162 _terminfo_params.clear()
163 163
164 def setup(ui, coloropts):
165 """configure color on a ui
166
167 The 'coloropts' argument is the value of the '--color' command line
168 argument. That function both set the colormode for the ui object and read
169 the configuration looking for custom colors and effect definitions."""
170 mode = _modesetup(ui, coloropts)
171 ui.__class__._colormode = mode
172 if mode and mode != 'debug':
173 configstyles(ui)
174
164 175 def _modesetup(ui, coloropt):
165 176 if ui.plain():
166 177 return None
167 178 if coloropt == 'debug':
168 179 return 'debug'
169 180
170 181 auto = (coloropt == 'auto')
171 182 always = not auto and util.parsebool(coloropt)
172 183 if not always and not auto:
173 184 return None
174 185
175 186 formatted = (always or (encoding.environ.get('TERM') != 'dumb'
176 187 and ui.formatted()))
177 188
178 189 mode = ui.config('color', 'mode', 'auto')
179 190
180 191 # If pager is active, color.pagermode overrides color.mode.
181 192 if getattr(ui, 'pageractive', False):
182 193 mode = ui.config('color', 'pagermode', mode)
183 194
184 195 realmode = mode
185 196 if mode == 'auto':
186 197 if pycompat.osname == 'nt':
187 198 term = encoding.environ.get('TERM')
188 199 # TERM won't be defined in a vanilla cmd.exe environment.
189 200
190 201 # UNIX-like environments on Windows such as Cygwin and MSYS will
191 202 # set TERM. They appear to make a best effort attempt at setting it
192 203 # to something appropriate. However, not all environments with TERM
193 204 # defined support ANSI. Since "ansi" could result in terminal
194 205 # gibberish, we error on the side of selecting "win32". However, if
195 206 # w32effects is not defined, we almost certainly don't support
196 207 # "win32", so don't even try.
197 208 if (term and 'xterm' in term) or not w32effects:
198 209 realmode = 'ansi'
199 210 else:
200 211 realmode = 'win32'
201 212 else:
202 213 realmode = 'ansi'
203 214
204 215 def modewarn():
205 216 # only warn if color.mode was explicitly set and we're in
206 217 # a formatted terminal
207 218 if mode == realmode and ui.formatted():
208 219 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
209 220
210 221 if realmode == 'win32':
211 222 _terminfo_params.clear()
212 223 if not w32effects:
213 224 modewarn()
214 225 return None
215 226 _effects.update(w32effects)
216 227 elif realmode == 'ansi':
217 228 _terminfo_params.clear()
218 229 elif realmode == 'terminfo':
219 230 _terminfosetup(ui, mode)
220 231 if not _terminfo_params:
221 232 ## FIXME Shouldn't we return None in this case too?
222 233 modewarn()
223 234 realmode = 'ansi'
224 235 else:
225 236 return None
226 237
227 238 if always or (auto and formatted):
228 239 return realmode
229 240 return None
230 241
231 242 def configstyles(ui):
232 243 for status, cfgeffects in ui.configitems('color'):
233 244 if '.' not in status or status.startswith(('color.', 'terminfo.')):
234 245 continue
235 246 cfgeffects = ui.configlist('color', status)
236 247 if cfgeffects:
237 248 good = []
238 249 for e in cfgeffects:
239 250 if valideffect(e):
240 251 good.append(e)
241 252 else:
242 253 ui.warn(_("ignoring unknown color/effect %r "
243 254 "(configured in color.%s)\n")
244 255 % (e, status))
245 256 _styles[status] = ' '.join(good)
246 257
247 258 def valideffect(effect):
248 259 'Determine if the effect is valid or not.'
249 260 return ((not _terminfo_params and effect in _effects)
250 261 or (effect in _terminfo_params
251 262 or effect[:-11] in _terminfo_params))
252 263
253 264 def _effect_str(effect):
254 265 '''Helper function for render_effects().'''
255 266
256 267 bg = False
257 268 if effect.endswith('_background'):
258 269 bg = True
259 270 effect = effect[:-11]
260 271 try:
261 272 attr, val, termcode = _terminfo_params[effect]
262 273 except KeyError:
263 274 return ''
264 275 if attr:
265 276 if termcode:
266 277 return termcode
267 278 else:
268 279 return curses.tigetstr(val)
269 280 elif bg:
270 281 return curses.tparm(curses.tigetstr('setab'), val)
271 282 else:
272 283 return curses.tparm(curses.tigetstr('setaf'), val)
273 284
274 285 def _render_effects(text, effects):
275 286 'Wrap text in commands to turn on each effect.'
276 287 if not text:
277 288 return text
278 289 if _terminfo_params:
279 290 start = ''.join(_effect_str(effect)
280 291 for effect in ['none'] + effects.split())
281 292 stop = _effect_str('none')
282 293 else:
283 294 start = [str(_effects[e]) for e in ['none'] + effects.split()]
284 295 start = '\033[' + ';'.join(start) + 'm'
285 296 stop = '\033[' + str(_effects['none']) + 'm'
286 297 return ''.join([start, text, stop])
287 298
288 299 def colorlabel(ui, msg, label):
289 300 """add color control code according to the mode"""
290 301 if ui._colormode == 'debug':
291 302 if label and msg:
292 303 if msg[-1] == '\n':
293 304 msg = "[%s|%s]\n" % (label, msg[:-1])
294 305 else:
295 306 msg = "[%s|%s]" % (label, msg)
296 307 elif ui._colormode is not None:
297 308 effects = []
298 309 for l in label.split():
299 310 s = _styles.get(l, '')
300 311 if s:
301 312 effects.append(s)
302 313 elif valideffect(l):
303 314 effects.append(l)
304 315 effects = ' '.join(effects)
305 316 if effects:
306 317 msg = '\n'.join([_render_effects(line, effects)
307 318 for line in msg.split('\n')])
308 319 return msg
309 320
310 321 w32effects = None
311 322 if pycompat.osname == 'nt':
312 323 import ctypes
313 324 import re
314 325
315 326 _kernel32 = ctypes.windll.kernel32
316 327
317 328 _WORD = ctypes.c_ushort
318 329
319 330 _INVALID_HANDLE_VALUE = -1
320 331
321 332 class _COORD(ctypes.Structure):
322 333 _fields_ = [('X', ctypes.c_short),
323 334 ('Y', ctypes.c_short)]
324 335
325 336 class _SMALL_RECT(ctypes.Structure):
326 337 _fields_ = [('Left', ctypes.c_short),
327 338 ('Top', ctypes.c_short),
328 339 ('Right', ctypes.c_short),
329 340 ('Bottom', ctypes.c_short)]
330 341
331 342 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
332 343 _fields_ = [('dwSize', _COORD),
333 344 ('dwCursorPosition', _COORD),
334 345 ('wAttributes', _WORD),
335 346 ('srWindow', _SMALL_RECT),
336 347 ('dwMaximumWindowSize', _COORD)]
337 348
338 349 _STD_OUTPUT_HANDLE = 0xfffffff5 # (DWORD)-11
339 350 _STD_ERROR_HANDLE = 0xfffffff4 # (DWORD)-12
340 351
341 352 _FOREGROUND_BLUE = 0x0001
342 353 _FOREGROUND_GREEN = 0x0002
343 354 _FOREGROUND_RED = 0x0004
344 355 _FOREGROUND_INTENSITY = 0x0008
345 356
346 357 _BACKGROUND_BLUE = 0x0010
347 358 _BACKGROUND_GREEN = 0x0020
348 359 _BACKGROUND_RED = 0x0040
349 360 _BACKGROUND_INTENSITY = 0x0080
350 361
351 362 _COMMON_LVB_REVERSE_VIDEO = 0x4000
352 363 _COMMON_LVB_UNDERSCORE = 0x8000
353 364
354 365 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
355 366 w32effects = {
356 367 'none': -1,
357 368 'black': 0,
358 369 'red': _FOREGROUND_RED,
359 370 'green': _FOREGROUND_GREEN,
360 371 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
361 372 'blue': _FOREGROUND_BLUE,
362 373 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
363 374 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
364 375 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
365 376 'bold': _FOREGROUND_INTENSITY,
366 377 'black_background': 0x100, # unused value > 0x0f
367 378 'red_background': _BACKGROUND_RED,
368 379 'green_background': _BACKGROUND_GREEN,
369 380 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
370 381 'blue_background': _BACKGROUND_BLUE,
371 382 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
372 383 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
373 384 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
374 385 _BACKGROUND_BLUE),
375 386 'bold_background': _BACKGROUND_INTENSITY,
376 387 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
377 388 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
378 389 }
379 390
380 391 passthrough = set([_FOREGROUND_INTENSITY,
381 392 _BACKGROUND_INTENSITY,
382 393 _COMMON_LVB_UNDERSCORE,
383 394 _COMMON_LVB_REVERSE_VIDEO])
384 395
385 396 stdout = _kernel32.GetStdHandle(
386 397 _STD_OUTPUT_HANDLE) # don't close the handle returned
387 398 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
388 399 w32effects = None
389 400 else:
390 401 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
391 402 if not _kernel32.GetConsoleScreenBufferInfo(
392 403 stdout, ctypes.byref(csbi)):
393 404 # stdout may not support GetConsoleScreenBufferInfo()
394 405 # when called from subprocess or redirected
395 406 w32effects = None
396 407 else:
397 408 origattr = csbi.wAttributes
398 409 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
399 410 re.MULTILINE | re.DOTALL)
400 411
401 412 def win32print(writefunc, *msgs, **opts):
402 413 for text in msgs:
403 414 _win32print(text, writefunc, **opts)
404 415
405 416 def _win32print(text, writefunc, **opts):
406 417 label = opts.get('label', '')
407 418 attr = origattr
408 419
409 420 def mapcolor(val, attr):
410 421 if val == -1:
411 422 return origattr
412 423 elif val in passthrough:
413 424 return attr | val
414 425 elif val > 0x0f:
415 426 return (val & 0x70) | (attr & 0x8f)
416 427 else:
417 428 return (val & 0x07) | (attr & 0xf8)
418 429
419 430 # determine console attributes based on labels
420 431 for l in label.split():
421 432 style = _styles.get(l, '')
422 433 for effect in style.split():
423 434 try:
424 435 attr = mapcolor(w32effects[effect], attr)
425 436 except KeyError:
426 437 # w32effects could not have certain attributes so we skip
427 438 # them if not found
428 439 pass
429 440 # hack to ensure regexp finds data
430 441 if not text.startswith('\033['):
431 442 text = '\033[m' + text
432 443
433 444 # Look for ANSI-like codes embedded in text
434 445 m = re.match(ansire, text)
435 446
436 447 try:
437 448 while m:
438 449 for sattr in m.group(1).split(';'):
439 450 if sattr:
440 451 attr = mapcolor(int(sattr), attr)
441 452 _kernel32.SetConsoleTextAttribute(stdout, attr)
442 453 writefunc(m.group(2), **opts)
443 454 m = re.match(ansire, m.group(3))
444 455 finally:
445 456 # Explicitly reset original attributes
446 457 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,903 +1,907
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import, print_function
9 9
10 10 import atexit
11 11 import difflib
12 12 import errno
13 13 import getopt
14 14 import os
15 15 import pdb
16 16 import re
17 17 import signal
18 18 import sys
19 19 import time
20 20 import traceback
21 21
22 22
23 23 from .i18n import _
24 24
25 25 from . import (
26 26 cmdutil,
27 27 color,
28 28 commands,
29 29 debugcommands,
30 30 demandimport,
31 31 encoding,
32 32 error,
33 33 extensions,
34 34 fancyopts,
35 35 fileset,
36 36 help,
37 37 hg,
38 38 hook,
39 39 profiling,
40 40 pycompat,
41 41 revset,
42 42 scmutil,
43 43 templatefilters,
44 44 templatekw,
45 45 templater,
46 46 ui as uimod,
47 47 util,
48 48 )
49 49
50 50 class request(object):
51 51 def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
52 52 ferr=None):
53 53 self.args = args
54 54 self.ui = ui
55 55 self.repo = repo
56 56
57 57 # input/output/error streams
58 58 self.fin = fin
59 59 self.fout = fout
60 60 self.ferr = ferr
61 61
62 62 def run():
63 63 "run the command in sys.argv"
64 64 sys.exit((dispatch(request(pycompat.sysargv[1:])) or 0) & 255)
65 65
66 66 def _getsimilar(symbols, value):
67 67 sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
68 68 # The cutoff for similarity here is pretty arbitrary. It should
69 69 # probably be investigated and tweaked.
70 70 return [s for s in symbols if sim(s) > 0.6]
71 71
72 72 def _reportsimilar(write, similar):
73 73 if len(similar) == 1:
74 74 write(_("(did you mean %s?)\n") % similar[0])
75 75 elif similar:
76 76 ss = ", ".join(sorted(similar))
77 77 write(_("(did you mean one of %s?)\n") % ss)
78 78
79 79 def _formatparse(write, inst):
80 80 similar = []
81 81 if isinstance(inst, error.UnknownIdentifier):
82 82 # make sure to check fileset first, as revset can invoke fileset
83 83 similar = _getsimilar(inst.symbols, inst.function)
84 84 if len(inst.args) > 1:
85 85 write(_("hg: parse error at %s: %s\n") %
86 86 (inst.args[1], inst.args[0]))
87 87 if (inst.args[0][0] == ' '):
88 88 write(_("unexpected leading whitespace\n"))
89 89 else:
90 90 write(_("hg: parse error: %s\n") % inst.args[0])
91 91 _reportsimilar(write, similar)
92 92 if inst.hint:
93 93 write(_("(%s)\n") % inst.hint)
94 94
95 95 def dispatch(req):
96 96 "run the command specified in req.args"
97 97 if req.ferr:
98 98 ferr = req.ferr
99 99 elif req.ui:
100 100 ferr = req.ui.ferr
101 101 else:
102 102 ferr = util.stderr
103 103
104 104 try:
105 105 if not req.ui:
106 106 req.ui = uimod.ui.load()
107 107 if '--traceback' in req.args:
108 108 req.ui.setconfig('ui', 'traceback', 'on', '--traceback')
109 109
110 110 # set ui streams from the request
111 111 if req.fin:
112 112 req.ui.fin = req.fin
113 113 if req.fout:
114 114 req.ui.fout = req.fout
115 115 if req.ferr:
116 116 req.ui.ferr = req.ferr
117 117 except error.Abort as inst:
118 118 ferr.write(_("abort: %s\n") % inst)
119 119 if inst.hint:
120 120 ferr.write(_("(%s)\n") % inst.hint)
121 121 return -1
122 122 except error.ParseError as inst:
123 123 _formatparse(ferr.write, inst)
124 124 return -1
125 125
126 126 msg = ' '.join(' ' in a and repr(a) or a for a in req.args)
127 127 starttime = util.timer()
128 128 ret = None
129 129 try:
130 130 ret = _runcatch(req)
131 131 except KeyboardInterrupt:
132 132 try:
133 133 req.ui.warn(_("interrupted!\n"))
134 134 except IOError as inst:
135 135 if inst.errno != errno.EPIPE:
136 136 raise
137 137 ret = -1
138 138 finally:
139 139 duration = util.timer() - starttime
140 140 req.ui.flush()
141 141 if req.ui.logblockedtimes:
142 142 req.ui._blockedtimes['command_duration'] = duration * 1000
143 143 req.ui.log('uiblocked', 'ui blocked ms', **req.ui._blockedtimes)
144 144 req.ui.log("commandfinish", "%s exited %s after %0.2f seconds\n",
145 145 msg, ret or 0, duration)
146 146 return ret
147 147
148 148 def _runcatch(req):
149 149 def catchterm(*args):
150 150 raise error.SignalInterrupt
151 151
152 152 ui = req.ui
153 153 try:
154 154 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
155 155 num = getattr(signal, name, None)
156 156 if num:
157 157 signal.signal(num, catchterm)
158 158 except ValueError:
159 159 pass # happens if called in a thread
160 160
161 161 def _runcatchfunc():
162 162 try:
163 163 debugger = 'pdb'
164 164 debugtrace = {
165 165 'pdb' : pdb.set_trace
166 166 }
167 167 debugmortem = {
168 168 'pdb' : pdb.post_mortem
169 169 }
170 170
171 171 # read --config before doing anything else
172 172 # (e.g. to change trust settings for reading .hg/hgrc)
173 173 cfgs = _parseconfig(req.ui, _earlygetopt(['--config'], req.args))
174 174
175 175 if req.repo:
176 176 # copy configs that were passed on the cmdline (--config) to
177 177 # the repo ui
178 178 for sec, name, val in cfgs:
179 179 req.repo.ui.setconfig(sec, name, val, source='--config')
180 180
181 181 # developer config: ui.debugger
182 182 debugger = ui.config("ui", "debugger")
183 183 debugmod = pdb
184 184 if not debugger or ui.plain():
185 185 # if we are in HGPLAIN mode, then disable custom debugging
186 186 debugger = 'pdb'
187 187 elif '--debugger' in req.args:
188 188 # This import can be slow for fancy debuggers, so only
189 189 # do it when absolutely necessary, i.e. when actual
190 190 # debugging has been requested
191 191 with demandimport.deactivated():
192 192 try:
193 193 debugmod = __import__(debugger)
194 194 except ImportError:
195 195 pass # Leave debugmod = pdb
196 196
197 197 debugtrace[debugger] = debugmod.set_trace
198 198 debugmortem[debugger] = debugmod.post_mortem
199 199
200 200 # enter the debugger before command execution
201 201 if '--debugger' in req.args:
202 202 ui.warn(_("entering debugger - "
203 203 "type c to continue starting hg or h for help\n"))
204 204
205 205 if (debugger != 'pdb' and
206 206 debugtrace[debugger] == debugtrace['pdb']):
207 207 ui.warn(_("%s debugger specified "
208 208 "but its module was not found\n") % debugger)
209 209 with demandimport.deactivated():
210 210 debugtrace[debugger]()
211 211 try:
212 212 return _dispatch(req)
213 213 finally:
214 214 ui.flush()
215 215 except: # re-raises
216 216 # enter the debugger when we hit an exception
217 217 if '--debugger' in req.args:
218 218 traceback.print_exc()
219 219 debugmortem[debugger](sys.exc_info()[2])
220 220 ui.traceback()
221 221 raise
222 222
223 223 return callcatch(ui, _runcatchfunc)
224 224
225 225 def callcatch(ui, func):
226 226 """like scmutil.callcatch but handles more high-level exceptions about
227 227 config parsing and commands. besides, use handlecommandexception to handle
228 228 uncaught exceptions.
229 229 """
230 230 try:
231 231 return scmutil.callcatch(ui, func)
232 232 except error.AmbiguousCommand as inst:
233 233 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
234 234 (inst.args[0], " ".join(inst.args[1])))
235 235 except error.CommandError as inst:
236 236 if inst.args[0]:
237 237 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
238 238 commands.help_(ui, inst.args[0], full=False, command=True)
239 239 else:
240 240 ui.warn(_("hg: %s\n") % inst.args[1])
241 241 commands.help_(ui, 'shortlist')
242 242 except error.ParseError as inst:
243 243 _formatparse(ui.warn, inst)
244 244 return -1
245 245 except error.UnknownCommand as inst:
246 246 nocmdmsg = _("hg: unknown command '%s'\n") % inst.args[0]
247 247 try:
248 248 # check if the command is in a disabled extension
249 249 # (but don't check for extensions themselves)
250 250 formatted = help.formattedhelp(ui, inst.args[0], unknowncmd=True)
251 251 ui.warn(nocmdmsg)
252 252 ui.write(formatted)
253 253 except (error.UnknownCommand, error.Abort):
254 254 suggested = False
255 255 if len(inst.args) == 2:
256 256 sim = _getsimilar(inst.args[1], inst.args[0])
257 257 if sim:
258 258 ui.warn(nocmdmsg)
259 259 _reportsimilar(ui.warn, sim)
260 260 suggested = True
261 261 if not suggested:
262 262 ui.pager('help')
263 263 ui.warn(nocmdmsg)
264 264 commands.help_(ui, 'shortlist')
265 265 except IOError:
266 266 raise
267 267 except KeyboardInterrupt:
268 268 raise
269 269 except: # probably re-raises
270 270 if not handlecommandexception(ui):
271 271 raise
272 272
273 273 return -1
274 274
275 275 def aliasargs(fn, givenargs):
276 276 args = getattr(fn, 'args', [])
277 277 if args:
278 278 cmd = ' '.join(map(util.shellquote, args))
279 279
280 280 nums = []
281 281 def replacer(m):
282 282 num = int(m.group(1)) - 1
283 283 nums.append(num)
284 284 if num < len(givenargs):
285 285 return givenargs[num]
286 286 raise error.Abort(_('too few arguments for command alias'))
287 287 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
288 288 givenargs = [x for i, x in enumerate(givenargs)
289 289 if i not in nums]
290 290 args = pycompat.shlexsplit(cmd)
291 291 return args + givenargs
292 292
293 293 def aliasinterpolate(name, args, cmd):
294 294 '''interpolate args into cmd for shell aliases
295 295
296 296 This also handles $0, $@ and "$@".
297 297 '''
298 298 # util.interpolate can't deal with "$@" (with quotes) because it's only
299 299 # built to match prefix + patterns.
300 300 replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
301 301 replacemap['$0'] = name
302 302 replacemap['$$'] = '$'
303 303 replacemap['$@'] = ' '.join(args)
304 304 # Typical Unix shells interpolate "$@" (with quotes) as all the positional
305 305 # parameters, separated out into words. Emulate the same behavior here by
306 306 # quoting the arguments individually. POSIX shells will then typically
307 307 # tokenize each argument into exactly one word.
308 308 replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
309 309 # escape '\$' for regex
310 310 regex = '|'.join(replacemap.keys()).replace('$', r'\$')
311 311 r = re.compile(regex)
312 312 return r.sub(lambda x: replacemap[x.group()], cmd)
313 313
314 314 class cmdalias(object):
315 315 def __init__(self, name, definition, cmdtable, source):
316 316 self.name = self.cmd = name
317 317 self.cmdname = ''
318 318 self.definition = definition
319 319 self.fn = None
320 320 self.givenargs = []
321 321 self.opts = []
322 322 self.help = ''
323 323 self.badalias = None
324 324 self.unknowncmd = False
325 325 self.source = source
326 326
327 327 try:
328 328 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
329 329 for alias, e in cmdtable.iteritems():
330 330 if e is entry:
331 331 self.cmd = alias
332 332 break
333 333 self.shadows = True
334 334 except error.UnknownCommand:
335 335 self.shadows = False
336 336
337 337 if not self.definition:
338 338 self.badalias = _("no definition for alias '%s'") % self.name
339 339 return
340 340
341 341 if self.definition.startswith('!'):
342 342 self.shell = True
343 343 def fn(ui, *args):
344 344 env = {'HG_ARGS': ' '.join((self.name,) + args)}
345 345 def _checkvar(m):
346 346 if m.groups()[0] == '$':
347 347 return m.group()
348 348 elif int(m.groups()[0]) <= len(args):
349 349 return m.group()
350 350 else:
351 351 ui.debug("No argument found for substitution "
352 352 "of %i variable in alias '%s' definition."
353 353 % (int(m.groups()[0]), self.name))
354 354 return ''
355 355 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
356 356 cmd = aliasinterpolate(self.name, args, cmd)
357 357 return ui.system(cmd, environ=env)
358 358 self.fn = fn
359 359 return
360 360
361 361 try:
362 362 args = pycompat.shlexsplit(self.definition)
363 363 except ValueError as inst:
364 364 self.badalias = (_("error in definition for alias '%s': %s")
365 365 % (self.name, inst))
366 366 return
367 367 self.cmdname = cmd = args.pop(0)
368 368 self.givenargs = args
369 369
370 370 for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
371 371 if _earlygetopt([invalidarg], args):
372 372 self.badalias = (_("error in definition for alias '%s': %s may "
373 373 "only be given on the command line")
374 374 % (self.name, invalidarg))
375 375 return
376 376
377 377 try:
378 378 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
379 379 if len(tableentry) > 2:
380 380 self.fn, self.opts, self.help = tableentry
381 381 else:
382 382 self.fn, self.opts = tableentry
383 383
384 384 if self.help.startswith("hg " + cmd):
385 385 # drop prefix in old-style help lines so hg shows the alias
386 386 self.help = self.help[4 + len(cmd):]
387 387 self.__doc__ = self.fn.__doc__
388 388
389 389 except error.UnknownCommand:
390 390 self.badalias = (_("alias '%s' resolves to unknown command '%s'")
391 391 % (self.name, cmd))
392 392 self.unknowncmd = True
393 393 except error.AmbiguousCommand:
394 394 self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
395 395 % (self.name, cmd))
396 396
397 397 @property
398 398 def args(self):
399 399 args = map(util.expandpath, self.givenargs)
400 400 return aliasargs(self.fn, args)
401 401
402 402 def __getattr__(self, name):
403 403 adefaults = {'norepo': True, 'optionalrepo': False, 'inferrepo': False}
404 404 if name not in adefaults:
405 405 raise AttributeError(name)
406 406 if self.badalias or util.safehasattr(self, 'shell'):
407 407 return adefaults[name]
408 408 return getattr(self.fn, name)
409 409
410 410 def __call__(self, ui, *args, **opts):
411 411 if self.badalias:
412 412 hint = None
413 413 if self.unknowncmd:
414 414 try:
415 415 # check if the command is in a disabled extension
416 416 cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
417 417 hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
418 418 except error.UnknownCommand:
419 419 pass
420 420 raise error.Abort(self.badalias, hint=hint)
421 421 if self.shadows:
422 422 ui.debug("alias '%s' shadows command '%s'\n" %
423 423 (self.name, self.cmdname))
424 424
425 425 ui.log('commandalias', "alias '%s' expands to '%s'\n",
426 426 self.name, self.definition)
427 427 if util.safehasattr(self, 'shell'):
428 428 return self.fn(ui, *args, **opts)
429 429 else:
430 430 try:
431 431 return util.checksignature(self.fn)(ui, *args, **opts)
432 432 except error.SignatureError:
433 433 args = ' '.join([self.cmdname] + self.args)
434 434 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
435 435 raise
436 436
437 437 def addaliases(ui, cmdtable):
438 438 # aliases are processed after extensions have been loaded, so they
439 439 # may use extension commands. Aliases can also use other alias definitions,
440 440 # but only if they have been defined prior to the current definition.
441 441 for alias, definition in ui.configitems('alias'):
442 442 source = ui.configsource('alias', alias)
443 443 aliasdef = cmdalias(alias, definition, cmdtable, source)
444 444
445 445 try:
446 446 olddef = cmdtable[aliasdef.cmd][0]
447 447 if olddef.definition == aliasdef.definition:
448 448 continue
449 449 except (KeyError, AttributeError):
450 450 # definition might not exist or it might not be a cmdalias
451 451 pass
452 452
453 453 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
454 454
455 455 def _parse(ui, args):
456 456 options = {}
457 457 cmdoptions = {}
458 458
459 459 try:
460 460 args = fancyopts.fancyopts(args, commands.globalopts, options)
461 461 except getopt.GetoptError as inst:
462 462 raise error.CommandError(None, inst)
463 463
464 464 if args:
465 465 cmd, args = args[0], args[1:]
466 466 aliases, entry = cmdutil.findcmd(cmd, commands.table,
467 467 ui.configbool("ui", "strict"))
468 468 cmd = aliases[0]
469 469 args = aliasargs(entry[0], args)
470 470 defaults = ui.config("defaults", cmd)
471 471 if defaults:
472 472 args = map(util.expandpath, pycompat.shlexsplit(defaults)) + args
473 473 c = list(entry[1])
474 474 else:
475 475 cmd = None
476 476 c = []
477 477
478 478 # combine global options into local
479 479 for o in commands.globalopts:
480 480 c.append((o[0], o[1], options[o[1]], o[3]))
481 481
482 482 try:
483 483 args = fancyopts.fancyopts(args, c, cmdoptions, gnu=True)
484 484 except getopt.GetoptError as inst:
485 485 raise error.CommandError(cmd, inst)
486 486
487 487 # separate global options back out
488 488 for o in commands.globalopts:
489 489 n = o[1]
490 490 options[n] = cmdoptions[n]
491 491 del cmdoptions[n]
492 492
493 493 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
494 494
495 495 def _parseconfig(ui, config):
496 496 """parse the --config options from the command line"""
497 497 configs = []
498 498
499 499 for cfg in config:
500 500 try:
501 501 name, value = [cfgelem.strip()
502 502 for cfgelem in cfg.split('=', 1)]
503 503 section, name = name.split('.', 1)
504 504 if not section or not name:
505 505 raise IndexError
506 506 ui.setconfig(section, name, value, '--config')
507 507 configs.append((section, name, value))
508 508 except (IndexError, ValueError):
509 509 raise error.Abort(_('malformed --config option: %r '
510 510 '(use --config section.name=value)') % cfg)
511 511
512 512 return configs
513 513
514 514 def _earlygetopt(aliases, args):
515 515 """Return list of values for an option (or aliases).
516 516
517 517 The values are listed in the order they appear in args.
518 518 The options and values are removed from args.
519 519
520 520 >>> args = ['x', '--cwd', 'foo', 'y']
521 521 >>> _earlygetopt(['--cwd'], args), args
522 522 (['foo'], ['x', 'y'])
523 523
524 524 >>> args = ['x', '--cwd=bar', 'y']
525 525 >>> _earlygetopt(['--cwd'], args), args
526 526 (['bar'], ['x', 'y'])
527 527
528 528 >>> args = ['x', '-R', 'foo', 'y']
529 529 >>> _earlygetopt(['-R'], args), args
530 530 (['foo'], ['x', 'y'])
531 531
532 532 >>> args = ['x', '-Rbar', 'y']
533 533 >>> _earlygetopt(['-R'], args), args
534 534 (['bar'], ['x', 'y'])
535 535 """
536 536 try:
537 537 argcount = args.index("--")
538 538 except ValueError:
539 539 argcount = len(args)
540 540 shortopts = [opt for opt in aliases if len(opt) == 2]
541 541 values = []
542 542 pos = 0
543 543 while pos < argcount:
544 544 fullarg = arg = args[pos]
545 545 equals = arg.find('=')
546 546 if equals > -1:
547 547 arg = arg[:equals]
548 548 if arg in aliases:
549 549 del args[pos]
550 550 if equals > -1:
551 551 values.append(fullarg[equals + 1:])
552 552 argcount -= 1
553 553 else:
554 554 if pos + 1 >= argcount:
555 555 # ignore and let getopt report an error if there is no value
556 556 break
557 557 values.append(args.pop(pos))
558 558 argcount -= 2
559 559 elif arg[:2] in shortopts:
560 560 # short option can have no following space, e.g. hg log -Rfoo
561 561 values.append(args.pop(pos)[2:])
562 562 argcount -= 1
563 563 else:
564 564 pos += 1
565 565 return values
566 566
567 567 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
568 568 # run pre-hook, and abort if it fails
569 569 hook.hook(lui, repo, "pre-%s" % cmd, True, args=" ".join(fullargs),
570 570 pats=cmdpats, opts=cmdoptions)
571 571 try:
572 572 ret = _runcommand(ui, options, cmd, d)
573 573 # run post-hook, passing command result
574 574 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
575 575 result=ret, pats=cmdpats, opts=cmdoptions)
576 576 except Exception:
577 577 # run failure hook and re-raise
578 578 hook.hook(lui, repo, "fail-%s" % cmd, False, args=" ".join(fullargs),
579 579 pats=cmdpats, opts=cmdoptions)
580 580 raise
581 581 return ret
582 582
583 583 def _getlocal(ui, rpath, wd=None):
584 584 """Return (path, local ui object) for the given target path.
585 585
586 586 Takes paths in [cwd]/.hg/hgrc into account."
587 587 """
588 588 if wd is None:
589 589 try:
590 590 wd = pycompat.getcwd()
591 591 except OSError as e:
592 592 raise error.Abort(_("error getting current working directory: %s") %
593 593 e.strerror)
594 594 path = cmdutil.findrepo(wd) or ""
595 595 if not path:
596 596 lui = ui
597 597 else:
598 598 lui = ui.copy()
599 599 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
600 600
601 601 if rpath and rpath[-1]:
602 602 path = lui.expandpath(rpath[-1])
603 603 lui = ui.copy()
604 604 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
605 605
606 606 return path, lui
607 607
608 608 def _checkshellalias(lui, ui, args):
609 609 """Return the function to run the shell alias, if it is required"""
610 610 options = {}
611 611
612 612 try:
613 613 args = fancyopts.fancyopts(args, commands.globalopts, options)
614 614 except getopt.GetoptError:
615 615 return
616 616
617 617 if not args:
618 618 return
619 619
620 620 cmdtable = commands.table
621 621
622 622 cmd = args[0]
623 623 try:
624 624 strict = ui.configbool("ui", "strict")
625 625 aliases, entry = cmdutil.findcmd(cmd, cmdtable, strict)
626 626 except (error.AmbiguousCommand, error.UnknownCommand):
627 627 return
628 628
629 629 cmd = aliases[0]
630 630 fn = entry[0]
631 631
632 632 if cmd and util.safehasattr(fn, 'shell'):
633 633 d = lambda: fn(ui, *args[1:])
634 634 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d,
635 635 [], {})
636 636
637 637 _loaded = set()
638 638
639 639 # list of (objname, loadermod, loadername) tuple:
640 640 # - objname is the name of an object in extension module, from which
641 641 # extra information is loaded
642 642 # - loadermod is the module where loader is placed
643 643 # - loadername is the name of the function, which takes (ui, extensionname,
644 644 # extraobj) arguments
645 645 extraloaders = [
646 646 ('cmdtable', commands, 'loadcmdtable'),
647 647 ('colortable', color, 'loadcolortable'),
648 648 ('filesetpredicate', fileset, 'loadpredicate'),
649 649 ('revsetpredicate', revset, 'loadpredicate'),
650 650 ('templatefilter', templatefilters, 'loadfilter'),
651 651 ('templatefunc', templater, 'loadfunction'),
652 652 ('templatekeyword', templatekw, 'loadkeyword'),
653 653 ]
654 654
655 655 def _dispatch(req):
656 656 args = req.args
657 657 ui = req.ui
658 658
659 659 # check for cwd
660 660 cwd = _earlygetopt(['--cwd'], args)
661 661 if cwd:
662 662 os.chdir(cwd[-1])
663 663
664 664 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
665 665 path, lui = _getlocal(ui, rpath)
666 666
667 667 # Side-effect of accessing is debugcommands module is guaranteed to be
668 668 # imported and commands.table is populated.
669 669 debugcommands.command
670 670
671 671 uis = set([ui, lui])
672 672
673 673 if req.repo:
674 674 uis.add(req.repo.ui)
675 675
676 676 if '--profile' in args:
677 677 for ui_ in uis:
678 678 ui_.setconfig('profiling', 'enabled', 'true', '--profile')
679 679
680 680 with profiling.maybeprofile(lui):
681 681 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
682 682 # reposetup. Programs like TortoiseHg will call _dispatch several
683 683 # times so we keep track of configured extensions in _loaded.
684 684 extensions.loadall(lui)
685 685 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
686 686 # Propagate any changes to lui.__class__ by extensions
687 687 ui.__class__ = lui.__class__
688 688
689 689 # (uisetup and extsetup are handled in extensions.loadall)
690 690
691 691 for name, module in exts:
692 692 for objname, loadermod, loadername in extraloaders:
693 693 extraobj = getattr(module, objname, None)
694 694 if extraobj is not None:
695 695 getattr(loadermod, loadername)(ui, name, extraobj)
696 696 _loaded.add(name)
697 697
698 698 # (reposetup is handled in hg.repository)
699 699
700 700 addaliases(lui, commands.table)
701 701
702 702 # All aliases and commands are completely defined, now.
703 703 # Check abbreviation/ambiguity of shell alias.
704 704 shellaliasfn = _checkshellalias(lui, ui, args)
705 705 if shellaliasfn:
706 706 return shellaliasfn()
707 707
708 708 # check for fallback encoding
709 709 fallback = lui.config('ui', 'fallbackencoding')
710 710 if fallback:
711 711 encoding.fallbackencoding = fallback
712 712
713 713 fullargs = args
714 714 cmd, func, args, options, cmdoptions = _parse(lui, args)
715 715
716 716 if options["config"]:
717 717 raise error.Abort(_("option --config may not be abbreviated!"))
718 718 if options["cwd"]:
719 719 raise error.Abort(_("option --cwd may not be abbreviated!"))
720 720 if options["repository"]:
721 721 raise error.Abort(_(
722 722 "option -R has to be separated from other options (e.g. not "
723 723 "-qR) and --repository may only be abbreviated as --repo!"))
724 724
725 725 if options["encoding"]:
726 726 encoding.encoding = options["encoding"]
727 727 if options["encodingmode"]:
728 728 encoding.encodingmode = options["encodingmode"]
729 729 if options["time"]:
730 730 def get_times():
731 731 t = os.times()
732 732 if t[4] == 0.0:
733 733 # Windows leaves this as zero, so use time.clock()
734 734 t = (t[0], t[1], t[2], t[3], time.clock())
735 735 return t
736 736 s = get_times()
737 737 def print_time():
738 738 t = get_times()
739 739 ui.warn(
740 740 _("time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
741 741 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
742 742 atexit.register(print_time)
743 743
744 744 if options['verbose'] or options['debug'] or options['quiet']:
745 745 for opt in ('verbose', 'debug', 'quiet'):
746 746 val = str(bool(options[opt]))
747 747 for ui_ in uis:
748 748 ui_.setconfig('ui', opt, val, '--' + opt)
749 749
750 750 if options['traceback']:
751 751 for ui_ in uis:
752 752 ui_.setconfig('ui', 'traceback', 'on', '--traceback')
753 753
754 754 if options['noninteractive']:
755 755 for ui_ in uis:
756 756 ui_.setconfig('ui', 'interactive', 'off', '-y')
757 757
758 758 if util.parsebool(options['pager']):
759 759 ui.pager('internal-always-' + cmd)
760 760 elif options['pager'] != 'auto':
761 761 ui.disablepager()
762 762
763 763 if cmdoptions.get('insecure', False):
764 764 for ui_ in uis:
765 765 ui_.insecureconnections = True
766 766
767 # setup color handling
768 for ui_ in uis:
769 color.setup(ui_, options['color'])
770
767 771 if options['version']:
768 772 return commands.version_(ui)
769 773 if options['help']:
770 774 return commands.help_(ui, cmd, command=cmd is not None)
771 775 elif not cmd:
772 776 return commands.help_(ui, 'shortlist')
773 777
774 778 repo = None
775 779 cmdpats = args[:]
776 780 if not func.norepo:
777 781 # use the repo from the request only if we don't have -R
778 782 if not rpath and not cwd:
779 783 repo = req.repo
780 784
781 785 if repo:
782 786 # set the descriptors of the repo ui to those of ui
783 787 repo.ui.fin = ui.fin
784 788 repo.ui.fout = ui.fout
785 789 repo.ui.ferr = ui.ferr
786 790 else:
787 791 try:
788 792 repo = hg.repository(ui, path=path)
789 793 if not repo.local():
790 794 raise error.Abort(_("repository '%s' is not local")
791 795 % path)
792 796 repo.ui.setconfig("bundle", "mainreporoot", repo.root,
793 797 'repo')
794 798 except error.RequirementError:
795 799 raise
796 800 except error.RepoError:
797 801 if rpath and rpath[-1]: # invalid -R path
798 802 raise
799 803 if not func.optionalrepo:
800 804 if func.inferrepo and args and not path:
801 805 # try to infer -R from command args
802 806 repos = map(cmdutil.findrepo, args)
803 807 guess = repos[0]
804 808 if guess and repos.count(guess) == len(repos):
805 809 req.args = ['--repository', guess] + fullargs
806 810 return _dispatch(req)
807 811 if not path:
808 812 raise error.RepoError(_("no repository found in"
809 813 " '%s' (.hg not found)")
810 814 % pycompat.getcwd())
811 815 raise
812 816 if repo:
813 817 ui = repo.ui
814 818 if options['hidden']:
815 819 repo = repo.unfiltered()
816 820 args.insert(0, repo)
817 821 elif rpath:
818 822 ui.warn(_("warning: --repository ignored\n"))
819 823
820 824 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
821 825 ui.log("command", '%s\n', msg)
822 826 strcmdopt = pycompat.strkwargs(cmdoptions)
823 827 d = lambda: util.checksignature(func)(ui, *args, **strcmdopt)
824 828 try:
825 829 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
826 830 cmdpats, cmdoptions)
827 831 finally:
828 832 if repo and repo != req.repo:
829 833 repo.close()
830 834
831 835 def _runcommand(ui, options, cmd, cmdfunc):
832 836 """Run a command function, possibly with profiling enabled."""
833 837 try:
834 838 return cmdfunc()
835 839 except error.SignatureError:
836 840 raise error.CommandError(cmd, _('invalid arguments'))
837 841
838 842 def _exceptionwarning(ui):
839 843 """Produce a warning message for the current active exception"""
840 844
841 845 # For compatibility checking, we discard the portion of the hg
842 846 # version after the + on the assumption that if a "normal
843 847 # user" is running a build with a + in it the packager
844 848 # probably built from fairly close to a tag and anyone with a
845 849 # 'make local' copy of hg (where the version number can be out
846 850 # of date) will be clueful enough to notice the implausible
847 851 # version number and try updating.
848 852 ct = util.versiontuple(n=2)
849 853 worst = None, ct, ''
850 854 if ui.config('ui', 'supportcontact', None) is None:
851 855 for name, mod in extensions.extensions():
852 856 testedwith = getattr(mod, 'testedwith', '')
853 857 report = getattr(mod, 'buglink', _('the extension author.'))
854 858 if not testedwith.strip():
855 859 # We found an untested extension. It's likely the culprit.
856 860 worst = name, 'unknown', report
857 861 break
858 862
859 863 # Never blame on extensions bundled with Mercurial.
860 864 if extensions.ismoduleinternal(mod):
861 865 continue
862 866
863 867 tested = [util.versiontuple(t, 2) for t in testedwith.split()]
864 868 if ct in tested:
865 869 continue
866 870
867 871 lower = [t for t in tested if t < ct]
868 872 nearest = max(lower or tested)
869 873 if worst[0] is None or nearest < worst[1]:
870 874 worst = name, nearest, report
871 875 if worst[0] is not None:
872 876 name, testedwith, report = worst
873 877 if not isinstance(testedwith, str):
874 878 testedwith = '.'.join([str(c) for c in testedwith])
875 879 warning = (_('** Unknown exception encountered with '
876 880 'possibly-broken third-party extension %s\n'
877 881 '** which supports versions %s of Mercurial.\n'
878 882 '** Please disable %s and try your action again.\n'
879 883 '** If that fixes the bug please report it to %s\n')
880 884 % (name, testedwith, name, report))
881 885 else:
882 886 bugtracker = ui.config('ui', 'supportcontact', None)
883 887 if bugtracker is None:
884 888 bugtracker = _("https://mercurial-scm.org/wiki/BugTracker")
885 889 warning = (_("** unknown exception encountered, "
886 890 "please report by visiting\n** ") + bugtracker + '\n')
887 891 warning += ((_("** Python %s\n") % sys.version.replace('\n', '')) +
888 892 (_("** Mercurial Distributed SCM (version %s)\n") %
889 893 util.version()) +
890 894 (_("** Extensions loaded: %s\n") %
891 895 ", ".join([x[0] for x in extensions.extensions()])))
892 896 return warning
893 897
894 898 def handlecommandexception(ui):
895 899 """Produce a warning message for broken commands
896 900
897 901 Called when handling an exception; the exception is reraised if
898 902 this function returns False, ignored otherwise.
899 903 """
900 904 warning = _exceptionwarning(ui)
901 905 ui.log("commandexception", "%s\n%s\n", warning, traceback.format_exc())
902 906 ui.warn(warning)
903 907 return False # re-raise the exception
@@ -1,387 +1,388
1 1 $ cat <<EOF >> $HGRCPATH
2 2 > [extensions]
3 3 > color =
4 4 > [color]
5 5 > mode = ansi
6 6 > EOF
7 7 Terminfo codes compatibility fix
8 8 $ echo "color.none=0" >> $HGRCPATH
9 9
10 10 $ hg init repo1
11 11 $ cd repo1
12 12 $ mkdir a b a/1 b/1 b/2
13 13 $ touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
14 14
15 15 hg status in repo root:
16 16
17 17 $ hg status --color=always
18 18 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
19 19 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
20 20 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
21 21 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
22 22 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
23 23 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
24 24
25 25 $ hg status --color=debug
26 26 [status.unknown|? ][status.unknown|a/1/in_a_1]
27 27 [status.unknown|? ][status.unknown|a/in_a]
28 28 [status.unknown|? ][status.unknown|b/1/in_b_1]
29 29 [status.unknown|? ][status.unknown|b/2/in_b_2]
30 30 [status.unknown|? ][status.unknown|b/in_b]
31 31 [status.unknown|? ][status.unknown|in_root]
32 32
33 33 hg status with template
34 34 $ hg status -T "{label('red', path)}\n" --color=debug
35 35 [red|a/1/in_a_1]
36 36 [red|a/in_a]
37 37 [red|b/1/in_b_1]
38 38 [red|b/2/in_b_2]
39 39 [red|b/in_b]
40 40 [red|in_root]
41 41
42 42 hg status . in repo root:
43 43
44 44 $ hg status --color=always .
45 45 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
46 46 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
47 47 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
48 48 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
49 49 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
50 50 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
51 51
52 52 $ hg status --color=always --cwd a
53 53 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
54 54 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
55 55 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
56 56 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
57 57 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
58 58 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
59 59 $ hg status --color=always --cwd a .
60 60 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_a_1\x1b[0m (esc)
61 61 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a\x1b[0m (esc)
62 62 $ hg status --color=always --cwd a ..
63 63 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_a_1\x1b[0m (esc)
64 64 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a\x1b[0m (esc)
65 65 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/1/in_b_1\x1b[0m (esc)
66 66 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/2/in_b_2\x1b[0m (esc)
67 67 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../b/in_b\x1b[0m (esc)
68 68 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_root\x1b[0m (esc)
69 69
70 70 $ hg status --color=always --cwd b
71 71 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
72 72 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
73 73 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
74 74 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
75 75 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
76 76 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
77 77 $ hg status --color=always --cwd b .
78 78 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_b_1\x1b[0m (esc)
79 79 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m2/in_b_2\x1b[0m (esc)
80 80 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b\x1b[0m (esc)
81 81 $ hg status --color=always --cwd b ..
82 82 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../a/1/in_a_1\x1b[0m (esc)
83 83 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../a/in_a\x1b[0m (esc)
84 84 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m1/in_b_1\x1b[0m (esc)
85 85 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m2/in_b_2\x1b[0m (esc)
86 86 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b\x1b[0m (esc)
87 87 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_root\x1b[0m (esc)
88 88
89 89 $ hg status --color=always --cwd a/1
90 90 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
91 91 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
92 92 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
93 93 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
94 94 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
95 95 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
96 96 $ hg status --color=always --cwd a/1 .
97 97 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a_1\x1b[0m (esc)
98 98 $ hg status --color=always --cwd a/1 ..
99 99 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_a_1\x1b[0m (esc)
100 100 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_a\x1b[0m (esc)
101 101
102 102 $ hg status --color=always --cwd b/1
103 103 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
104 104 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
105 105 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
106 106 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
107 107 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
108 108 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
109 109 $ hg status --color=always --cwd b/1 .
110 110 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_1\x1b[0m (esc)
111 111 $ hg status --color=always --cwd b/1 ..
112 112 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_1\x1b[0m (esc)
113 113 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../2/in_b_2\x1b[0m (esc)
114 114 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_b\x1b[0m (esc)
115 115
116 116 $ hg status --color=always --cwd b/2
117 117 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/1/in_a_1\x1b[0m (esc)
118 118 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4ma/in_a\x1b[0m (esc)
119 119 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/1/in_b_1\x1b[0m (esc)
120 120 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/2/in_b_2\x1b[0m (esc)
121 121 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
122 122 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
123 123 $ hg status --color=always --cwd b/2 .
124 124 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_2\x1b[0m (esc)
125 125 $ hg status --color=always --cwd b/2 ..
126 126 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../1/in_b_1\x1b[0m (esc)
127 127 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_b_2\x1b[0m (esc)
128 128 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4m../in_b\x1b[0m (esc)
129 129
130 130 Make sure --color=never works
131 131 $ hg status --color=never
132 132 ? a/1/in_a_1
133 133 ? a/in_a
134 134 ? b/1/in_b_1
135 135 ? b/2/in_b_2
136 136 ? b/in_b
137 137 ? in_root
138 138
139 139 Make sure ui.formatted=False works
140 140 $ hg status --config ui.formatted=False
141 141 ? a/1/in_a_1
142 142 ? a/in_a
143 143 ? b/1/in_b_1
144 144 ? b/2/in_b_2
145 145 ? b/in_b
146 146 ? in_root
147 147
148 148 $ cd ..
149 149
150 150 $ hg init repo2
151 151 $ cd repo2
152 152 $ touch modified removed deleted ignored
153 153 $ echo "^ignored$" > .hgignore
154 154 $ hg ci -A -m 'initial checkin'
155 155 adding .hgignore
156 156 adding deleted
157 157 adding modified
158 158 adding removed
159 159 $ hg log --color=debug
160 160 [log.changeset changeset.draft|changeset: 0:389aef86a55e]
161 161 [log.tag|tag: tip]
162 162 [log.user|user: test]
163 163 [log.date|date: Thu Jan 01 00:00:00 1970 +0000]
164 164 [log.summary|summary: initial checkin]
165 165
166 166 $ hg log -Tcompact --color=debug
167 167 [log.changeset changeset.draft|0][tip] [log.node|389aef86a55e] [log.date|1970-01-01 00:00 +0000] [log.user|test]
168 168 [ui.note log.description|initial checkin]
169 169
170 170 Labels on empty strings should not be displayed, labels on custom
171 171 templates should be.
172 172
173 173 $ hg log --color=debug -T '{label("my.label",author)}\n{label("skipped.label","")}'
174 174 [my.label|test]
175 175 $ touch modified added unknown ignored
176 176 $ hg add added
177 177 $ hg remove removed
178 178 $ rm deleted
179 179
180 180 hg status:
181 181
182 182 $ hg status --color=always
183 183 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
184 184 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
185 185 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
186 186 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
187 187
188 188 hg status modified added removed deleted unknown never-existed ignored:
189 189
190 190 $ hg status --color=always modified added removed deleted unknown never-existed ignored
191 191 never-existed: * (glob)
192 192 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
193 193 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
194 194 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
195 195 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
196 196
197 197 $ hg copy modified copied
198 198
199 199 hg status -C:
200 200
201 201 $ hg status --color=always -C
202 202 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
203 203 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
204 204 \x1b[0;0m modified\x1b[0m (esc)
205 205 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
206 206 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
207 207 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
208 208
209 209 hg status -A:
210 210
211 211 $ hg status --color=always -A
212 212 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
213 213 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
214 214 \x1b[0;0m modified\x1b[0m (esc)
215 215 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
216 216 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
217 217 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
218 218 \x1b[0;30;1mI \x1b[0m\x1b[0;30;1mignored\x1b[0m (esc)
219 219 \x1b[0;0mC \x1b[0m\x1b[0;0m.hgignore\x1b[0m (esc)
220 220 \x1b[0;0mC \x1b[0m\x1b[0;0mmodified\x1b[0m (esc)
221 221
222 222
223 223 hg status -A (with terminfo color):
224 224
225 225 #if tic
226 226
227 227 $ mkdir "$TESTTMP/terminfo"
228 228 $ TERMINFO="$TESTTMP/terminfo" tic "$TESTDIR/hgterm.ti"
229 229 $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status --config color.mode=terminfo --color=always -A
230 230 \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1madded\x1b[30m (esc)
231 231 \x1b[30m\x1b[32m\x1b[1mA \x1b[30m\x1b[30m\x1b[32m\x1b[1mcopied\x1b[30m (esc)
232 232 \x1b[30m\x1b[30m modified\x1b[30m (esc)
233 233 \x1b[30m\x1b[31m\x1b[1mR \x1b[30m\x1b[30m\x1b[31m\x1b[1mremoved\x1b[30m (esc)
234 234 \x1b[30m\x1b[36m\x1b[1m\x1b[4m! \x1b[30m\x1b[30m\x1b[36m\x1b[1m\x1b[4mdeleted\x1b[30m (esc)
235 235 \x1b[30m\x1b[35m\x1b[1m\x1b[4m? \x1b[30m\x1b[30m\x1b[35m\x1b[1m\x1b[4munknown\x1b[30m (esc)
236 236 \x1b[30m\x1b[30m\x1b[1mI \x1b[30m\x1b[30m\x1b[30m\x1b[1mignored\x1b[30m (esc)
237 237 \x1b[30m\x1b[30mC \x1b[30m\x1b[30m\x1b[30m.hgignore\x1b[30m (esc)
238 238 \x1b[30m\x1b[30mC \x1b[30m\x1b[30m\x1b[30mmodified\x1b[30m (esc)
239 239
240 240 The user can define effects with raw terminfo codes:
241 241
242 242 $ cat <<EOF >> $HGRCPATH
243 243 > # Completely bogus code for dim
244 244 > terminfo.dim = \E[88m
245 245 > # We can override what's in the terminfo database, too
246 246 > terminfo.bold = \E[2m
247 247 > EOF
248 248 $ TERM=hgterm TERMINFO="$TESTTMP/terminfo" hg status --config color.mode=terminfo --config color.status.clean=dim --color=always -A
249 249 \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2madded\x1b[30m (esc)
250 250 \x1b[30m\x1b[32m\x1b[2mA \x1b[30m\x1b[30m\x1b[32m\x1b[2mcopied\x1b[30m (esc)
251 251 \x1b[30m\x1b[30m modified\x1b[30m (esc)
252 252 \x1b[30m\x1b[31m\x1b[2mR \x1b[30m\x1b[30m\x1b[31m\x1b[2mremoved\x1b[30m (esc)
253 253 \x1b[30m\x1b[36m\x1b[2m\x1b[4m! \x1b[30m\x1b[30m\x1b[36m\x1b[2m\x1b[4mdeleted\x1b[30m (esc)
254 254 \x1b[30m\x1b[35m\x1b[2m\x1b[4m? \x1b[30m\x1b[30m\x1b[35m\x1b[2m\x1b[4munknown\x1b[30m (esc)
255 255 \x1b[30m\x1b[30m\x1b[2mI \x1b[30m\x1b[30m\x1b[30m\x1b[2mignored\x1b[30m (esc)
256 256 \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88m.hgignore\x1b[30m (esc)
257 257 \x1b[30m\x1b[88mC \x1b[30m\x1b[30m\x1b[88mmodified\x1b[30m (esc)
258 258
259 259 #endif
260 260
261 261
262 262 $ echo "^ignoreddir$" > .hgignore
263 263 $ mkdir ignoreddir
264 264 $ touch ignoreddir/file
265 265
266 266 hg status ignoreddir/file:
267 267
268 268 $ hg status --color=always ignoreddir/file
269 269
270 270 hg status -i ignoreddir/file:
271 271
272 272 $ hg status --color=always -i ignoreddir/file
273 273 \x1b[0;30;1mI \x1b[0m\x1b[0;30;1mignoreddir/file\x1b[0m (esc)
274 274 $ cd ..
275 275
276 276 check 'status -q' and some combinations
277 277
278 278 $ hg init repo3
279 279 $ cd repo3
280 280 $ touch modified removed deleted ignored
281 281 $ echo "^ignored$" > .hgignore
282 282 $ hg commit -A -m 'initial checkin'
283 283 adding .hgignore
284 284 adding deleted
285 285 adding modified
286 286 adding removed
287 287 $ touch added unknown ignored
288 288 $ hg add added
289 289 $ echo "test" >> modified
290 290 $ hg remove removed
291 291 $ rm deleted
292 292 $ hg copy modified copied
293 293
294 294 test unknown color
295 295
296 296 $ hg --config color.status.modified=periwinkle status --color=always
297 297 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified)
298 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified)
298 299 M modified
299 300 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1madded\x1b[0m (esc)
300 301 \x1b[0;32;1mA \x1b[0m\x1b[0;32;1mcopied\x1b[0m (esc)
301 302 \x1b[0;31;1mR \x1b[0m\x1b[0;31;1mremoved\x1b[0m (esc)
302 303 \x1b[0;36;1;4m! \x1b[0m\x1b[0;36;1;4mdeleted\x1b[0m (esc)
303 304 \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4munknown\x1b[0m (esc)
304 305
305 306 Run status with 2 different flags.
306 307 Check if result is the same or different.
307 308 If result is not as expected, raise error
308 309
309 310 $ assert() {
310 311 > hg status --color=always $1 > ../a
311 312 > hg status --color=always $2 > ../b
312 313 > if diff ../a ../b > /dev/null; then
313 314 > out=0
314 315 > else
315 316 > out=1
316 317 > fi
317 318 > if [ $3 -eq 0 ]; then
318 319 > df="same"
319 320 > else
320 321 > df="different"
321 322 > fi
322 323 > if [ $out -ne $3 ]; then
323 324 > echo "Error on $1 and $2, should be $df."
324 325 > fi
325 326 > }
326 327
327 328 assert flag1 flag2 [0-same | 1-different]
328 329
329 330 $ assert "-q" "-mard" 0
330 331 $ assert "-A" "-marduicC" 0
331 332 $ assert "-qA" "-mardcC" 0
332 333 $ assert "-qAui" "-A" 0
333 334 $ assert "-qAu" "-marducC" 0
334 335 $ assert "-qAi" "-mardicC" 0
335 336 $ assert "-qu" "-u" 0
336 337 $ assert "-q" "-u" 1
337 338 $ assert "-m" "-a" 1
338 339 $ assert "-r" "-d" 1
339 340 $ cd ..
340 341
341 342 test 'resolve -l'
342 343
343 344 $ hg init repo4
344 345 $ cd repo4
345 346 $ echo "file a" > a
346 347 $ echo "file b" > b
347 348 $ hg add a b
348 349 $ hg commit -m "initial"
349 350 $ echo "file a change 1" > a
350 351 $ echo "file b change 1" > b
351 352 $ hg commit -m "head 1"
352 353 $ hg update 0
353 354 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
354 355 $ echo "file a change 2" > a
355 356 $ echo "file b change 2" > b
356 357 $ hg commit -m "head 2"
357 358 created new head
358 359 $ hg merge
359 360 merging a
360 361 merging b
361 362 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
362 363 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
363 364 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
364 365 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
365 366 [1]
366 367 $ hg resolve -m b
367 368
368 369 hg resolve with one unresolved, one resolved:
369 370
370 371 $ hg resolve --color=always -l
371 372 \x1b[0;31;1mU \x1b[0m\x1b[0;31;1ma\x1b[0m (esc)
372 373 \x1b[0;32;1mR \x1b[0m\x1b[0;32;1mb\x1b[0m (esc)
373 374
374 375 color coding of error message with current availability of curses
375 376
376 377 $ hg unknowncommand > /dev/null
377 378 hg: unknown command 'unknowncommand'
378 379 [255]
379 380
380 381 color coding of error message without curses
381 382
382 383 $ echo 'raise ImportError' > curses.py
383 384 $ PYTHONPATH=`pwd`:$PYTHONPATH hg unknowncommand > /dev/null
384 385 hg: unknown command 'unknowncommand'
385 386 [255]
386 387
387 388 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now