##// END OF EJS Templates
ui: label prompts, default to yellow prompts
Martin Geisler -
r13774:1ce0e807 default
parent child Browse files
Show More
@@ -1,360 +1,361 b''
1 # color.py color output for the status and qseries commands
1 # color.py color output for the status and qseries 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 program is free software; you can redistribute it and/or modify it
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License as published by the
6 # under the terms of the GNU General Public License as published by the
7 # Free Software Foundation; either version 2 of the License, or (at your
7 # Free Software Foundation; either version 2 of the License, or (at your
8 # option) any later version.
8 # option) any later version.
9 #
9 #
10 # This program is distributed in the hope that it will be useful, but
10 # This program is distributed in the hope that it will be useful, but
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 # Public License for more details.
13 # Public License for more details.
14 #
14 #
15 # You should have received a copy of the GNU General Public License along
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
18
19 '''colorize output from some commands
19 '''colorize output from some commands
20
20
21 This extension modifies the status and resolve commands to add color
21 This extension modifies the status and resolve commands to add color
22 to their output to reflect file status, the qseries command to add
22 to their output to reflect file status, the qseries command to add
23 color to reflect patch status (applied, unapplied, missing), and to
23 color to reflect patch status (applied, unapplied, missing), and to
24 diff-related commands to highlight additions, removals, diff headers,
24 diff-related commands to highlight additions, removals, diff headers,
25 and trailing whitespace.
25 and trailing whitespace.
26
26
27 Other effects in addition to color, like bold and underlined text, are
27 Other effects in addition to color, like bold and underlined text, are
28 also available. Effects are rendered with the ECMA-48 SGR control
28 also available. Effects are rendered with the ECMA-48 SGR control
29 function (aka ANSI escape codes).
29 function (aka ANSI escape codes).
30
30
31 Default effects may be overridden from your configuration file::
31 Default effects may be overridden from your configuration file::
32
32
33 [color]
33 [color]
34 status.modified = blue bold underline red_background
34 status.modified = blue bold underline red_background
35 status.added = green bold
35 status.added = green bold
36 status.removed = red bold blue_background
36 status.removed = red bold blue_background
37 status.deleted = cyan bold underline
37 status.deleted = cyan bold underline
38 status.unknown = magenta bold underline
38 status.unknown = magenta bold underline
39 status.ignored = black bold
39 status.ignored = black bold
40
40
41 # 'none' turns off all effects
41 # 'none' turns off all effects
42 status.clean = none
42 status.clean = none
43 status.copied = none
43 status.copied = none
44
44
45 qseries.applied = blue bold underline
45 qseries.applied = blue bold underline
46 qseries.unapplied = black bold
46 qseries.unapplied = black bold
47 qseries.missing = red bold
47 qseries.missing = red bold
48
48
49 diff.diffline = bold
49 diff.diffline = bold
50 diff.extended = cyan bold
50 diff.extended = cyan bold
51 diff.file_a = red bold
51 diff.file_a = red bold
52 diff.file_b = green bold
52 diff.file_b = green bold
53 diff.hunk = magenta
53 diff.hunk = magenta
54 diff.deleted = red
54 diff.deleted = red
55 diff.inserted = green
55 diff.inserted = green
56 diff.changed = white
56 diff.changed = white
57 diff.trailingwhitespace = bold red_background
57 diff.trailingwhitespace = bold red_background
58
58
59 resolve.unresolved = red bold
59 resolve.unresolved = red bold
60 resolve.resolved = green bold
60 resolve.resolved = green bold
61
61
62 bookmarks.current = green
62 bookmarks.current = green
63
63
64 branches.active = none
64 branches.active = none
65 branches.closed = black bold
65 branches.closed = black bold
66 branches.current = green
66 branches.current = green
67 branches.inactive = none
67 branches.inactive = none
68
68
69 The color extension will try to detect whether to use ANSI codes or
69 The color extension will try to detect whether to use ANSI codes or
70 Win32 console APIs, unless it is made explicit::
70 Win32 console APIs, unless it is made explicit::
71
71
72 [color]
72 [color]
73 mode = ansi
73 mode = ansi
74
74
75 Any value other than 'ansi', 'win32', or 'auto' will disable color.
75 Any value other than 'ansi', 'win32', or 'auto' will disable color.
76
76
77 '''
77 '''
78
78
79 import os
79 import os
80
80
81 from mercurial import commands, dispatch, extensions, ui as uimod, util
81 from mercurial import commands, dispatch, extensions, ui as uimod, util
82 from mercurial.i18n import _
82 from mercurial.i18n import _
83
83
84 # start and stop parameters for effects
84 # start and stop parameters for effects
85 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
85 _effects = {'none': 0, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33,
86 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
86 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, 'bold': 1,
87 'italic': 3, 'underline': 4, 'inverse': 7,
87 'italic': 3, 'underline': 4, 'inverse': 7,
88 'black_background': 40, 'red_background': 41,
88 'black_background': 40, 'red_background': 41,
89 'green_background': 42, 'yellow_background': 43,
89 'green_background': 42, 'yellow_background': 43,
90 'blue_background': 44, 'purple_background': 45,
90 'blue_background': 44, 'purple_background': 45,
91 'cyan_background': 46, 'white_background': 47}
91 'cyan_background': 46, 'white_background': 47}
92
92
93 _styles = {'grep.match': 'red bold',
93 _styles = {'grep.match': 'red bold',
94 'bookmarks.current': 'green',
94 'bookmarks.current': 'green',
95 'branches.active': 'none',
95 'branches.active': 'none',
96 'branches.closed': 'black bold',
96 'branches.closed': 'black bold',
97 'branches.current': 'green',
97 'branches.current': 'green',
98 'branches.inactive': 'none',
98 'branches.inactive': 'none',
99 'diff.changed': 'white',
99 'diff.changed': 'white',
100 'diff.deleted': 'red',
100 'diff.deleted': 'red',
101 'diff.diffline': 'bold',
101 'diff.diffline': 'bold',
102 'diff.extended': 'cyan bold',
102 'diff.extended': 'cyan bold',
103 'diff.file_a': 'red bold',
103 'diff.file_a': 'red bold',
104 'diff.file_b': 'green bold',
104 'diff.file_b': 'green bold',
105 'diff.hunk': 'magenta',
105 'diff.hunk': 'magenta',
106 'diff.inserted': 'green',
106 'diff.inserted': 'green',
107 'diff.trailingwhitespace': 'bold red_background',
107 'diff.trailingwhitespace': 'bold red_background',
108 'diffstat.deleted': 'red',
108 'diffstat.deleted': 'red',
109 'diffstat.inserted': 'green',
109 'diffstat.inserted': 'green',
110 'ui.prompt': 'yellow',
110 'log.changeset': 'yellow',
111 'log.changeset': 'yellow',
111 'resolve.resolved': 'green bold',
112 'resolve.resolved': 'green bold',
112 'resolve.unresolved': 'red bold',
113 'resolve.unresolved': 'red bold',
113 'status.added': 'green bold',
114 'status.added': 'green bold',
114 'status.clean': 'none',
115 'status.clean': 'none',
115 'status.copied': 'none',
116 'status.copied': 'none',
116 'status.deleted': 'cyan bold underline',
117 'status.deleted': 'cyan bold underline',
117 'status.ignored': 'black bold',
118 'status.ignored': 'black bold',
118 'status.modified': 'blue bold',
119 'status.modified': 'blue bold',
119 'status.removed': 'red bold',
120 'status.removed': 'red bold',
120 'status.unknown': 'magenta bold underline'}
121 'status.unknown': 'magenta bold underline'}
121
122
122
123
123 def render_effects(text, effects):
124 def render_effects(text, effects):
124 'Wrap text in commands to turn on each effect.'
125 'Wrap text in commands to turn on each effect.'
125 if not text:
126 if not text:
126 return text
127 return text
127 start = [str(_effects[e]) for e in ['none'] + effects.split()]
128 start = [str(_effects[e]) for e in ['none'] + effects.split()]
128 start = '\033[' + ';'.join(start) + 'm'
129 start = '\033[' + ';'.join(start) + 'm'
129 stop = '\033[' + str(_effects['none']) + 'm'
130 stop = '\033[' + str(_effects['none']) + 'm'
130 return ''.join([start, text, stop])
131 return ''.join([start, text, stop])
131
132
132 def extstyles():
133 def extstyles():
133 for name, ext in extensions.extensions():
134 for name, ext in extensions.extensions():
134 _styles.update(getattr(ext, 'colortable', {}))
135 _styles.update(getattr(ext, 'colortable', {}))
135
136
136 def configstyles(ui):
137 def configstyles(ui):
137 for status, cfgeffects in ui.configitems('color'):
138 for status, cfgeffects in ui.configitems('color'):
138 if '.' not in status:
139 if '.' not in status:
139 continue
140 continue
140 cfgeffects = ui.configlist('color', status)
141 cfgeffects = ui.configlist('color', status)
141 if cfgeffects:
142 if cfgeffects:
142 good = []
143 good = []
143 for e in cfgeffects:
144 for e in cfgeffects:
144 if e in _effects:
145 if e in _effects:
145 good.append(e)
146 good.append(e)
146 else:
147 else:
147 ui.warn(_("ignoring unknown color/effect %r "
148 ui.warn(_("ignoring unknown color/effect %r "
148 "(configured in color.%s)\n")
149 "(configured in color.%s)\n")
149 % (e, status))
150 % (e, status))
150 _styles[status] = ' '.join(good)
151 _styles[status] = ' '.join(good)
151
152
152 class colorui(uimod.ui):
153 class colorui(uimod.ui):
153 def popbuffer(self, labeled=False):
154 def popbuffer(self, labeled=False):
154 if labeled:
155 if labeled:
155 return ''.join(self.label(a, label) for a, label
156 return ''.join(self.label(a, label) for a, label
156 in self._buffers.pop())
157 in self._buffers.pop())
157 return ''.join(a for a, label in self._buffers.pop())
158 return ''.join(a for a, label in self._buffers.pop())
158
159
159 _colormode = 'ansi'
160 _colormode = 'ansi'
160 def write(self, *args, **opts):
161 def write(self, *args, **opts):
161 label = opts.get('label', '')
162 label = opts.get('label', '')
162 if self._buffers:
163 if self._buffers:
163 self._buffers[-1].extend([(str(a), label) for a in args])
164 self._buffers[-1].extend([(str(a), label) for a in args])
164 elif self._colormode == 'win32':
165 elif self._colormode == 'win32':
165 for a in args:
166 for a in args:
166 win32print(a, super(colorui, self).write, **opts)
167 win32print(a, super(colorui, self).write, **opts)
167 else:
168 else:
168 return super(colorui, self).write(
169 return super(colorui, self).write(
169 *[self.label(str(a), label) for a in args], **opts)
170 *[self.label(str(a), label) for a in args], **opts)
170
171
171 def write_err(self, *args, **opts):
172 def write_err(self, *args, **opts):
172 label = opts.get('label', '')
173 label = opts.get('label', '')
173 if self._colormode == 'win32':
174 if self._colormode == 'win32':
174 for a in args:
175 for a in args:
175 win32print(a, super(colorui, self).write_err, **opts)
176 win32print(a, super(colorui, self).write_err, **opts)
176 else:
177 else:
177 return super(colorui, self).write_err(
178 return super(colorui, self).write_err(
178 *[self.label(str(a), label) for a in args], **opts)
179 *[self.label(str(a), label) for a in args], **opts)
179
180
180 def label(self, msg, label):
181 def label(self, msg, label):
181 effects = []
182 effects = []
182 for l in label.split():
183 for l in label.split():
183 s = _styles.get(l, '')
184 s = _styles.get(l, '')
184 if s:
185 if s:
185 effects.append(s)
186 effects.append(s)
186 effects = ''.join(effects)
187 effects = ''.join(effects)
187 if effects:
188 if effects:
188 return '\n'.join([render_effects(s, effects)
189 return '\n'.join([render_effects(s, effects)
189 for s in msg.split('\n')])
190 for s in msg.split('\n')])
190 return msg
191 return msg
191
192
192
193
193 def uisetup(ui):
194 def uisetup(ui):
194 if ui.plain():
195 if ui.plain():
195 return
196 return
196 mode = ui.config('color', 'mode', 'auto')
197 mode = ui.config('color', 'mode', 'auto')
197 if mode == 'auto':
198 if mode == 'auto':
198 if os.name == 'nt' and 'TERM' not in os.environ:
199 if os.name == 'nt' and 'TERM' not in os.environ:
199 # looks line a cmd.exe console, use win32 API or nothing
200 # looks line a cmd.exe console, use win32 API or nothing
200 mode = w32effects and 'win32' or 'none'
201 mode = w32effects and 'win32' or 'none'
201 else:
202 else:
202 mode = 'ansi'
203 mode = 'ansi'
203 if mode == 'win32':
204 if mode == 'win32':
204 if w32effects is None:
205 if w32effects is None:
205 # only warn if color.mode is explicitly set to win32
206 # only warn if color.mode is explicitly set to win32
206 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
207 ui.warn(_('warning: failed to set color mode to %s\n') % mode)
207 return
208 return
208 _effects.update(w32effects)
209 _effects.update(w32effects)
209 elif mode != 'ansi':
210 elif mode != 'ansi':
210 return
211 return
211 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
212 def colorcmd(orig, ui_, opts, cmd, cmdfunc):
212 coloropt = opts['color']
213 coloropt = opts['color']
213 auto = coloropt == 'auto'
214 auto = coloropt == 'auto'
214 always = util.parsebool(coloropt)
215 always = util.parsebool(coloropt)
215 if (always or
216 if (always or
216 (always is None and
217 (always is None and
217 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
218 (auto and (os.environ.get('TERM') != 'dumb' and ui_.formatted())))):
218 colorui._colormode = mode
219 colorui._colormode = mode
219 colorui.__bases__ = (ui_.__class__,)
220 colorui.__bases__ = (ui_.__class__,)
220 ui_.__class__ = colorui
221 ui_.__class__ = colorui
221 extstyles()
222 extstyles()
222 configstyles(ui_)
223 configstyles(ui_)
223 return orig(ui_, opts, cmd, cmdfunc)
224 return orig(ui_, opts, cmd, cmdfunc)
224 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
225 extensions.wrapfunction(dispatch, '_runcommand', colorcmd)
225
226
226 def extsetup(ui):
227 def extsetup(ui):
227 commands.globalopts.append(
228 commands.globalopts.append(
228 ('', 'color', 'auto',
229 ('', 'color', 'auto',
229 # i18n: 'always', 'auto', and 'never' are keywords and should
230 # i18n: 'always', 'auto', and 'never' are keywords and should
230 # not be translated
231 # not be translated
231 _("when to colorize (boolean, always, auto, or never)"),
232 _("when to colorize (boolean, always, auto, or never)"),
232 _('TYPE')))
233 _('TYPE')))
233
234
234 if os.name != 'nt':
235 if os.name != 'nt':
235 w32effects = None
236 w32effects = None
236 else:
237 else:
237 import re, ctypes
238 import re, ctypes
238
239
239 _kernel32 = ctypes.windll.kernel32
240 _kernel32 = ctypes.windll.kernel32
240
241
241 _WORD = ctypes.c_ushort
242 _WORD = ctypes.c_ushort
242
243
243 _INVALID_HANDLE_VALUE = -1
244 _INVALID_HANDLE_VALUE = -1
244
245
245 class _COORD(ctypes.Structure):
246 class _COORD(ctypes.Structure):
246 _fields_ = [('X', ctypes.c_short),
247 _fields_ = [('X', ctypes.c_short),
247 ('Y', ctypes.c_short)]
248 ('Y', ctypes.c_short)]
248
249
249 class _SMALL_RECT(ctypes.Structure):
250 class _SMALL_RECT(ctypes.Structure):
250 _fields_ = [('Left', ctypes.c_short),
251 _fields_ = [('Left', ctypes.c_short),
251 ('Top', ctypes.c_short),
252 ('Top', ctypes.c_short),
252 ('Right', ctypes.c_short),
253 ('Right', ctypes.c_short),
253 ('Bottom', ctypes.c_short)]
254 ('Bottom', ctypes.c_short)]
254
255
255 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
256 class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure):
256 _fields_ = [('dwSize', _COORD),
257 _fields_ = [('dwSize', _COORD),
257 ('dwCursorPosition', _COORD),
258 ('dwCursorPosition', _COORD),
258 ('wAttributes', _WORD),
259 ('wAttributes', _WORD),
259 ('srWindow', _SMALL_RECT),
260 ('srWindow', _SMALL_RECT),
260 ('dwMaximumWindowSize', _COORD)]
261 ('dwMaximumWindowSize', _COORD)]
261
262
262 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
263 _STD_OUTPUT_HANDLE = 0xfffffff5L # (DWORD)-11
263 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
264 _STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12
264
265
265 _FOREGROUND_BLUE = 0x0001
266 _FOREGROUND_BLUE = 0x0001
266 _FOREGROUND_GREEN = 0x0002
267 _FOREGROUND_GREEN = 0x0002
267 _FOREGROUND_RED = 0x0004
268 _FOREGROUND_RED = 0x0004
268 _FOREGROUND_INTENSITY = 0x0008
269 _FOREGROUND_INTENSITY = 0x0008
269
270
270 _BACKGROUND_BLUE = 0x0010
271 _BACKGROUND_BLUE = 0x0010
271 _BACKGROUND_GREEN = 0x0020
272 _BACKGROUND_GREEN = 0x0020
272 _BACKGROUND_RED = 0x0040
273 _BACKGROUND_RED = 0x0040
273 _BACKGROUND_INTENSITY = 0x0080
274 _BACKGROUND_INTENSITY = 0x0080
274
275
275 _COMMON_LVB_REVERSE_VIDEO = 0x4000
276 _COMMON_LVB_REVERSE_VIDEO = 0x4000
276 _COMMON_LVB_UNDERSCORE = 0x8000
277 _COMMON_LVB_UNDERSCORE = 0x8000
277
278
278 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
279 # http://msdn.microsoft.com/en-us/library/ms682088%28VS.85%29.aspx
279 w32effects = {
280 w32effects = {
280 'none': -1,
281 'none': -1,
281 'black': 0,
282 'black': 0,
282 'red': _FOREGROUND_RED,
283 'red': _FOREGROUND_RED,
283 'green': _FOREGROUND_GREEN,
284 'green': _FOREGROUND_GREEN,
284 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
285 'yellow': _FOREGROUND_RED | _FOREGROUND_GREEN,
285 'blue': _FOREGROUND_BLUE,
286 'blue': _FOREGROUND_BLUE,
286 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
287 'magenta': _FOREGROUND_BLUE | _FOREGROUND_RED,
287 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
288 'cyan': _FOREGROUND_BLUE | _FOREGROUND_GREEN,
288 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
289 'white': _FOREGROUND_RED | _FOREGROUND_GREEN | _FOREGROUND_BLUE,
289 'bold': _FOREGROUND_INTENSITY,
290 'bold': _FOREGROUND_INTENSITY,
290 'black_background': 0x100, # unused value > 0x0f
291 'black_background': 0x100, # unused value > 0x0f
291 'red_background': _BACKGROUND_RED,
292 'red_background': _BACKGROUND_RED,
292 'green_background': _BACKGROUND_GREEN,
293 'green_background': _BACKGROUND_GREEN,
293 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
294 'yellow_background': _BACKGROUND_RED | _BACKGROUND_GREEN,
294 'blue_background': _BACKGROUND_BLUE,
295 'blue_background': _BACKGROUND_BLUE,
295 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
296 'purple_background': _BACKGROUND_BLUE | _BACKGROUND_RED,
296 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
297 'cyan_background': _BACKGROUND_BLUE | _BACKGROUND_GREEN,
297 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
298 'white_background': (_BACKGROUND_RED | _BACKGROUND_GREEN |
298 _BACKGROUND_BLUE),
299 _BACKGROUND_BLUE),
299 'bold_background': _BACKGROUND_INTENSITY,
300 'bold_background': _BACKGROUND_INTENSITY,
300 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
301 'underline': _COMMON_LVB_UNDERSCORE, # double-byte charsets only
301 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
302 'inverse': _COMMON_LVB_REVERSE_VIDEO, # double-byte charsets only
302 }
303 }
303
304
304 passthrough = set([_FOREGROUND_INTENSITY,
305 passthrough = set([_FOREGROUND_INTENSITY,
305 _BACKGROUND_INTENSITY,
306 _BACKGROUND_INTENSITY,
306 _COMMON_LVB_UNDERSCORE,
307 _COMMON_LVB_UNDERSCORE,
307 _COMMON_LVB_REVERSE_VIDEO])
308 _COMMON_LVB_REVERSE_VIDEO])
308
309
309 stdout = _kernel32.GetStdHandle(
310 stdout = _kernel32.GetStdHandle(
310 _STD_OUTPUT_HANDLE) # don't close the handle returned
311 _STD_OUTPUT_HANDLE) # don't close the handle returned
311 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
312 if stdout is None or stdout == _INVALID_HANDLE_VALUE:
312 w32effects = None
313 w32effects = None
313 else:
314 else:
314 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
315 csbi = _CONSOLE_SCREEN_BUFFER_INFO()
315 if not _kernel32.GetConsoleScreenBufferInfo(
316 if not _kernel32.GetConsoleScreenBufferInfo(
316 stdout, ctypes.byref(csbi)):
317 stdout, ctypes.byref(csbi)):
317 # stdout may not support GetConsoleScreenBufferInfo()
318 # stdout may not support GetConsoleScreenBufferInfo()
318 # when called from subprocess or redirected
319 # when called from subprocess or redirected
319 w32effects = None
320 w32effects = None
320 else:
321 else:
321 origattr = csbi.wAttributes
322 origattr = csbi.wAttributes
322 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
323 ansire = re.compile('\033\[([^m]*)m([^\033]*)(.*)',
323 re.MULTILINE | re.DOTALL)
324 re.MULTILINE | re.DOTALL)
324
325
325 def win32print(text, orig, **opts):
326 def win32print(text, orig, **opts):
326 label = opts.get('label', '')
327 label = opts.get('label', '')
327 attr = origattr
328 attr = origattr
328
329
329 def mapcolor(val, attr):
330 def mapcolor(val, attr):
330 if val == -1:
331 if val == -1:
331 return origattr
332 return origattr
332 elif val in passthrough:
333 elif val in passthrough:
333 return attr | val
334 return attr | val
334 elif val > 0x0f:
335 elif val > 0x0f:
335 return (val & 0x70) | (attr & 0x8f)
336 return (val & 0x70) | (attr & 0x8f)
336 else:
337 else:
337 return (val & 0x07) | (attr & 0xf8)
338 return (val & 0x07) | (attr & 0xf8)
338
339
339 # determine console attributes based on labels
340 # determine console attributes based on labels
340 for l in label.split():
341 for l in label.split():
341 style = _styles.get(l, '')
342 style = _styles.get(l, '')
342 for effect in style.split():
343 for effect in style.split():
343 attr = mapcolor(w32effects[effect], attr)
344 attr = mapcolor(w32effects[effect], attr)
344
345
345 # hack to ensure regexp finds data
346 # hack to ensure regexp finds data
346 if not text.startswith('\033['):
347 if not text.startswith('\033['):
347 text = '\033[m' + text
348 text = '\033[m' + text
348
349
349 # Look for ANSI-like codes embedded in text
350 # Look for ANSI-like codes embedded in text
350 m = re.match(ansire, text)
351 m = re.match(ansire, text)
351 while m:
352 while m:
352 for sattr in m.group(1).split(';'):
353 for sattr in m.group(1).split(';'):
353 if sattr:
354 if sattr:
354 attr = mapcolor(int(sattr), attr)
355 attr = mapcolor(int(sattr), attr)
355 _kernel32.SetConsoleTextAttribute(stdout, attr)
356 _kernel32.SetConsoleTextAttribute(stdout, attr)
356 orig(m.group(2), **opts)
357 orig(m.group(2), **opts)
357 m = re.match(ansire, m.group(3))
358 m = re.match(ansire, m.group(3))
358
359
359 # Explicity reset original attributes
360 # Explicity reset original attributes
360 _kernel32.SetConsoleTextAttribute(stdout, origattr)
361 _kernel32.SetConsoleTextAttribute(stdout, origattr)
@@ -1,636 +1,636 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import errno, getpass, os, socket, sys, tempfile, traceback
9 import errno, getpass, os, socket, sys, tempfile, traceback
10 import config, util, error
10 import config, util, error
11
11
12 class ui(object):
12 class ui(object):
13 def __init__(self, src=None):
13 def __init__(self, src=None):
14 self._buffers = []
14 self._buffers = []
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
15 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
16 self._reportuntrusted = True
16 self._reportuntrusted = True
17 self._ocfg = config.config() # overlay
17 self._ocfg = config.config() # overlay
18 self._tcfg = config.config() # trusted
18 self._tcfg = config.config() # trusted
19 self._ucfg = config.config() # untrusted
19 self._ucfg = config.config() # untrusted
20 self._trustusers = set()
20 self._trustusers = set()
21 self._trustgroups = set()
21 self._trustgroups = set()
22
22
23 if src:
23 if src:
24 self._tcfg = src._tcfg.copy()
24 self._tcfg = src._tcfg.copy()
25 self._ucfg = src._ucfg.copy()
25 self._ucfg = src._ucfg.copy()
26 self._ocfg = src._ocfg.copy()
26 self._ocfg = src._ocfg.copy()
27 self._trustusers = src._trustusers.copy()
27 self._trustusers = src._trustusers.copy()
28 self._trustgroups = src._trustgroups.copy()
28 self._trustgroups = src._trustgroups.copy()
29 self.environ = src.environ
29 self.environ = src.environ
30 self.fixconfig()
30 self.fixconfig()
31 else:
31 else:
32 # shared read-only environment
32 # shared read-only environment
33 self.environ = os.environ
33 self.environ = os.environ
34 # we always trust global config files
34 # we always trust global config files
35 for f in util.rcpath():
35 for f in util.rcpath():
36 self.readconfig(f, trust=True)
36 self.readconfig(f, trust=True)
37
37
38 def copy(self):
38 def copy(self):
39 return self.__class__(self)
39 return self.__class__(self)
40
40
41 def _is_trusted(self, fp, f):
41 def _is_trusted(self, fp, f):
42 st = util.fstat(fp)
42 st = util.fstat(fp)
43 if util.isowner(st):
43 if util.isowner(st):
44 return True
44 return True
45
45
46 tusers, tgroups = self._trustusers, self._trustgroups
46 tusers, tgroups = self._trustusers, self._trustgroups
47 if '*' in tusers or '*' in tgroups:
47 if '*' in tusers or '*' in tgroups:
48 return True
48 return True
49
49
50 user = util.username(st.st_uid)
50 user = util.username(st.st_uid)
51 group = util.groupname(st.st_gid)
51 group = util.groupname(st.st_gid)
52 if user in tusers or group in tgroups or user == util.username():
52 if user in tusers or group in tgroups or user == util.username():
53 return True
53 return True
54
54
55 if self._reportuntrusted:
55 if self._reportuntrusted:
56 self.warn(_('Not trusting file %s from untrusted '
56 self.warn(_('Not trusting file %s from untrusted '
57 'user %s, group %s\n') % (f, user, group))
57 'user %s, group %s\n') % (f, user, group))
58 return False
58 return False
59
59
60 def readconfig(self, filename, root=None, trust=False,
60 def readconfig(self, filename, root=None, trust=False,
61 sections=None, remap=None):
61 sections=None, remap=None):
62 try:
62 try:
63 fp = open(filename)
63 fp = open(filename)
64 except IOError:
64 except IOError:
65 if not sections: # ignore unless we were looking for something
65 if not sections: # ignore unless we were looking for something
66 return
66 return
67 raise
67 raise
68
68
69 cfg = config.config()
69 cfg = config.config()
70 trusted = sections or trust or self._is_trusted(fp, filename)
70 trusted = sections or trust or self._is_trusted(fp, filename)
71
71
72 try:
72 try:
73 cfg.read(filename, fp, sections=sections, remap=remap)
73 cfg.read(filename, fp, sections=sections, remap=remap)
74 except error.ConfigError, inst:
74 except error.ConfigError, inst:
75 if trusted:
75 if trusted:
76 raise
76 raise
77 self.warn(_("Ignored: %s\n") % str(inst))
77 self.warn(_("Ignored: %s\n") % str(inst))
78
78
79 if self.plain():
79 if self.plain():
80 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
80 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
81 'logtemplate', 'style',
81 'logtemplate', 'style',
82 'traceback', 'verbose'):
82 'traceback', 'verbose'):
83 if k in cfg['ui']:
83 if k in cfg['ui']:
84 del cfg['ui'][k]
84 del cfg['ui'][k]
85 for k, v in cfg.items('alias'):
85 for k, v in cfg.items('alias'):
86 del cfg['alias'][k]
86 del cfg['alias'][k]
87 for k, v in cfg.items('defaults'):
87 for k, v in cfg.items('defaults'):
88 del cfg['defaults'][k]
88 del cfg['defaults'][k]
89
89
90 if trusted:
90 if trusted:
91 self._tcfg.update(cfg)
91 self._tcfg.update(cfg)
92 self._tcfg.update(self._ocfg)
92 self._tcfg.update(self._ocfg)
93 self._ucfg.update(cfg)
93 self._ucfg.update(cfg)
94 self._ucfg.update(self._ocfg)
94 self._ucfg.update(self._ocfg)
95
95
96 if root is None:
96 if root is None:
97 root = os.path.expanduser('~')
97 root = os.path.expanduser('~')
98 self.fixconfig(root=root)
98 self.fixconfig(root=root)
99
99
100 def fixconfig(self, root=None, section=None):
100 def fixconfig(self, root=None, section=None):
101 if section in (None, 'paths'):
101 if section in (None, 'paths'):
102 # expand vars and ~
102 # expand vars and ~
103 # translate paths relative to root (or home) into absolute paths
103 # translate paths relative to root (or home) into absolute paths
104 root = root or os.getcwd()
104 root = root or os.getcwd()
105 for c in self._tcfg, self._ucfg, self._ocfg:
105 for c in self._tcfg, self._ucfg, self._ocfg:
106 for n, p in c.items('paths'):
106 for n, p in c.items('paths'):
107 if not p:
107 if not p:
108 continue
108 continue
109 if '%%' in p:
109 if '%%' in p:
110 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
110 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
111 % (n, p, self.configsource('paths', n)))
111 % (n, p, self.configsource('paths', n)))
112 p = p.replace('%%', '%')
112 p = p.replace('%%', '%')
113 p = util.expandpath(p)
113 p = util.expandpath(p)
114 if '://' not in p and not os.path.isabs(p):
114 if '://' not in p and not os.path.isabs(p):
115 p = os.path.normpath(os.path.join(root, p))
115 p = os.path.normpath(os.path.join(root, p))
116 c.set("paths", n, p)
116 c.set("paths", n, p)
117
117
118 if section in (None, 'ui'):
118 if section in (None, 'ui'):
119 # update ui options
119 # update ui options
120 self.debugflag = self.configbool('ui', 'debug')
120 self.debugflag = self.configbool('ui', 'debug')
121 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
121 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
122 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
122 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
123 if self.verbose and self.quiet:
123 if self.verbose and self.quiet:
124 self.quiet = self.verbose = False
124 self.quiet = self.verbose = False
125 self._reportuntrusted = self.debugflag or self.configbool("ui",
125 self._reportuntrusted = self.debugflag or self.configbool("ui",
126 "report_untrusted", True)
126 "report_untrusted", True)
127 self.tracebackflag = self.configbool('ui', 'traceback', False)
127 self.tracebackflag = self.configbool('ui', 'traceback', False)
128
128
129 if section in (None, 'trusted'):
129 if section in (None, 'trusted'):
130 # update trust information
130 # update trust information
131 self._trustusers.update(self.configlist('trusted', 'users'))
131 self._trustusers.update(self.configlist('trusted', 'users'))
132 self._trustgroups.update(self.configlist('trusted', 'groups'))
132 self._trustgroups.update(self.configlist('trusted', 'groups'))
133
133
134 def setconfig(self, section, name, value, overlay=True):
134 def setconfig(self, section, name, value, overlay=True):
135 if overlay:
135 if overlay:
136 self._ocfg.set(section, name, value)
136 self._ocfg.set(section, name, value)
137 self._tcfg.set(section, name, value)
137 self._tcfg.set(section, name, value)
138 self._ucfg.set(section, name, value)
138 self._ucfg.set(section, name, value)
139 self.fixconfig(section=section)
139 self.fixconfig(section=section)
140
140
141 def _data(self, untrusted):
141 def _data(self, untrusted):
142 return untrusted and self._ucfg or self._tcfg
142 return untrusted and self._ucfg or self._tcfg
143
143
144 def configsource(self, section, name, untrusted=False):
144 def configsource(self, section, name, untrusted=False):
145 return self._data(untrusted).source(section, name) or 'none'
145 return self._data(untrusted).source(section, name) or 'none'
146
146
147 def config(self, section, name, default=None, untrusted=False):
147 def config(self, section, name, default=None, untrusted=False):
148 value = self._data(untrusted).get(section, name, default)
148 value = self._data(untrusted).get(section, name, default)
149 if self.debugflag and not untrusted and self._reportuntrusted:
149 if self.debugflag and not untrusted and self._reportuntrusted:
150 uvalue = self._ucfg.get(section, name)
150 uvalue = self._ucfg.get(section, name)
151 if uvalue is not None and uvalue != value:
151 if uvalue is not None and uvalue != value:
152 self.debug(_("ignoring untrusted configuration option "
152 self.debug(_("ignoring untrusted configuration option "
153 "%s.%s = %s\n") % (section, name, uvalue))
153 "%s.%s = %s\n") % (section, name, uvalue))
154 return value
154 return value
155
155
156 def configpath(self, section, name, default=None, untrusted=False):
156 def configpath(self, section, name, default=None, untrusted=False):
157 'get a path config item, expanded relative to config file'
157 'get a path config item, expanded relative to config file'
158 v = self.config(section, name, default, untrusted)
158 v = self.config(section, name, default, untrusted)
159 if not os.path.isabs(v) or "://" not in v:
159 if not os.path.isabs(v) or "://" not in v:
160 src = self.configsource(section, name, untrusted)
160 src = self.configsource(section, name, untrusted)
161 if ':' in src:
161 if ':' in src:
162 base = os.path.dirname(src.rsplit(':'))
162 base = os.path.dirname(src.rsplit(':'))
163 v = os.path.join(base, os.path.expanduser(v))
163 v = os.path.join(base, os.path.expanduser(v))
164 return v
164 return v
165
165
166 def configbool(self, section, name, default=False, untrusted=False):
166 def configbool(self, section, name, default=False, untrusted=False):
167 v = self.config(section, name, None, untrusted)
167 v = self.config(section, name, None, untrusted)
168 if v is None:
168 if v is None:
169 return default
169 return default
170 if isinstance(v, bool):
170 if isinstance(v, bool):
171 return v
171 return v
172 b = util.parsebool(v)
172 b = util.parsebool(v)
173 if b is None:
173 if b is None:
174 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
174 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
175 % (section, name, v))
175 % (section, name, v))
176 return b
176 return b
177
177
178 def configlist(self, section, name, default=None, untrusted=False):
178 def configlist(self, section, name, default=None, untrusted=False):
179 """Return a list of comma/space separated strings"""
179 """Return a list of comma/space separated strings"""
180
180
181 def _parse_plain(parts, s, offset):
181 def _parse_plain(parts, s, offset):
182 whitespace = False
182 whitespace = False
183 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
183 while offset < len(s) and (s[offset].isspace() or s[offset] == ','):
184 whitespace = True
184 whitespace = True
185 offset += 1
185 offset += 1
186 if offset >= len(s):
186 if offset >= len(s):
187 return None, parts, offset
187 return None, parts, offset
188 if whitespace:
188 if whitespace:
189 parts.append('')
189 parts.append('')
190 if s[offset] == '"' and not parts[-1]:
190 if s[offset] == '"' and not parts[-1]:
191 return _parse_quote, parts, offset + 1
191 return _parse_quote, parts, offset + 1
192 elif s[offset] == '"' and parts[-1][-1] == '\\':
192 elif s[offset] == '"' and parts[-1][-1] == '\\':
193 parts[-1] = parts[-1][:-1] + s[offset]
193 parts[-1] = parts[-1][:-1] + s[offset]
194 return _parse_plain, parts, offset + 1
194 return _parse_plain, parts, offset + 1
195 parts[-1] += s[offset]
195 parts[-1] += s[offset]
196 return _parse_plain, parts, offset + 1
196 return _parse_plain, parts, offset + 1
197
197
198 def _parse_quote(parts, s, offset):
198 def _parse_quote(parts, s, offset):
199 if offset < len(s) and s[offset] == '"': # ""
199 if offset < len(s) and s[offset] == '"': # ""
200 parts.append('')
200 parts.append('')
201 offset += 1
201 offset += 1
202 while offset < len(s) and (s[offset].isspace() or
202 while offset < len(s) and (s[offset].isspace() or
203 s[offset] == ','):
203 s[offset] == ','):
204 offset += 1
204 offset += 1
205 return _parse_plain, parts, offset
205 return _parse_plain, parts, offset
206
206
207 while offset < len(s) and s[offset] != '"':
207 while offset < len(s) and s[offset] != '"':
208 if (s[offset] == '\\' and offset + 1 < len(s)
208 if (s[offset] == '\\' and offset + 1 < len(s)
209 and s[offset + 1] == '"'):
209 and s[offset + 1] == '"'):
210 offset += 1
210 offset += 1
211 parts[-1] += '"'
211 parts[-1] += '"'
212 else:
212 else:
213 parts[-1] += s[offset]
213 parts[-1] += s[offset]
214 offset += 1
214 offset += 1
215
215
216 if offset >= len(s):
216 if offset >= len(s):
217 real_parts = _configlist(parts[-1])
217 real_parts = _configlist(parts[-1])
218 if not real_parts:
218 if not real_parts:
219 parts[-1] = '"'
219 parts[-1] = '"'
220 else:
220 else:
221 real_parts[0] = '"' + real_parts[0]
221 real_parts[0] = '"' + real_parts[0]
222 parts = parts[:-1]
222 parts = parts[:-1]
223 parts.extend(real_parts)
223 parts.extend(real_parts)
224 return None, parts, offset
224 return None, parts, offset
225
225
226 offset += 1
226 offset += 1
227 while offset < len(s) and s[offset] in [' ', ',']:
227 while offset < len(s) and s[offset] in [' ', ',']:
228 offset += 1
228 offset += 1
229
229
230 if offset < len(s):
230 if offset < len(s):
231 if offset + 1 == len(s) and s[offset] == '"':
231 if offset + 1 == len(s) and s[offset] == '"':
232 parts[-1] += '"'
232 parts[-1] += '"'
233 offset += 1
233 offset += 1
234 else:
234 else:
235 parts.append('')
235 parts.append('')
236 else:
236 else:
237 return None, parts, offset
237 return None, parts, offset
238
238
239 return _parse_plain, parts, offset
239 return _parse_plain, parts, offset
240
240
241 def _configlist(s):
241 def _configlist(s):
242 s = s.rstrip(' ,')
242 s = s.rstrip(' ,')
243 if not s:
243 if not s:
244 return []
244 return []
245 parser, parts, offset = _parse_plain, [''], 0
245 parser, parts, offset = _parse_plain, [''], 0
246 while parser:
246 while parser:
247 parser, parts, offset = parser(parts, s, offset)
247 parser, parts, offset = parser(parts, s, offset)
248 return parts
248 return parts
249
249
250 result = self.config(section, name, untrusted=untrusted)
250 result = self.config(section, name, untrusted=untrusted)
251 if result is None:
251 if result is None:
252 result = default or []
252 result = default or []
253 if isinstance(result, basestring):
253 if isinstance(result, basestring):
254 result = _configlist(result.lstrip(' ,\n'))
254 result = _configlist(result.lstrip(' ,\n'))
255 if result is None:
255 if result is None:
256 result = default or []
256 result = default or []
257 return result
257 return result
258
258
259 def has_section(self, section, untrusted=False):
259 def has_section(self, section, untrusted=False):
260 '''tell whether section exists in config.'''
260 '''tell whether section exists in config.'''
261 return section in self._data(untrusted)
261 return section in self._data(untrusted)
262
262
263 def configitems(self, section, untrusted=False):
263 def configitems(self, section, untrusted=False):
264 items = self._data(untrusted).items(section)
264 items = self._data(untrusted).items(section)
265 if self.debugflag and not untrusted and self._reportuntrusted:
265 if self.debugflag and not untrusted and self._reportuntrusted:
266 for k, v in self._ucfg.items(section):
266 for k, v in self._ucfg.items(section):
267 if self._tcfg.get(section, k) != v:
267 if self._tcfg.get(section, k) != v:
268 self.debug(_("ignoring untrusted configuration option "
268 self.debug(_("ignoring untrusted configuration option "
269 "%s.%s = %s\n") % (section, k, v))
269 "%s.%s = %s\n") % (section, k, v))
270 return items
270 return items
271
271
272 def walkconfig(self, untrusted=False):
272 def walkconfig(self, untrusted=False):
273 cfg = self._data(untrusted)
273 cfg = self._data(untrusted)
274 for section in cfg.sections():
274 for section in cfg.sections():
275 for name, value in self.configitems(section, untrusted):
275 for name, value in self.configitems(section, untrusted):
276 yield section, name, value
276 yield section, name, value
277
277
278 def plain(self):
278 def plain(self):
279 '''is plain mode active?
279 '''is plain mode active?
280
280
281 Plain mode means that all configuration variables which affect the
281 Plain mode means that all configuration variables which affect the
282 behavior and output of Mercurial should be ignored. Additionally, the
282 behavior and output of Mercurial should be ignored. Additionally, the
283 output should be stable, reproducible and suitable for use in scripts or
283 output should be stable, reproducible and suitable for use in scripts or
284 applications.
284 applications.
285
285
286 The only way to trigger plain mode is by setting the `HGPLAIN'
286 The only way to trigger plain mode is by setting the `HGPLAIN'
287 environment variable.
287 environment variable.
288 '''
288 '''
289 return 'HGPLAIN' in os.environ
289 return 'HGPLAIN' in os.environ
290
290
291 def username(self):
291 def username(self):
292 """Return default username to be used in commits.
292 """Return default username to be used in commits.
293
293
294 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
294 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
295 and stop searching if one of these is set.
295 and stop searching if one of these is set.
296 If not found and ui.askusername is True, ask the user, else use
296 If not found and ui.askusername is True, ask the user, else use
297 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
297 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
298 """
298 """
299 user = os.environ.get("HGUSER")
299 user = os.environ.get("HGUSER")
300 if user is None:
300 if user is None:
301 user = self.config("ui", "username")
301 user = self.config("ui", "username")
302 if user is not None:
302 if user is not None:
303 user = os.path.expandvars(user)
303 user = os.path.expandvars(user)
304 if user is None:
304 if user is None:
305 user = os.environ.get("EMAIL")
305 user = os.environ.get("EMAIL")
306 if user is None and self.configbool("ui", "askusername"):
306 if user is None and self.configbool("ui", "askusername"):
307 user = self.prompt(_("enter a commit username:"), default=None)
307 user = self.prompt(_("enter a commit username:"), default=None)
308 if user is None and not self.interactive():
308 if user is None and not self.interactive():
309 try:
309 try:
310 user = '%s@%s' % (util.getuser(), socket.getfqdn())
310 user = '%s@%s' % (util.getuser(), socket.getfqdn())
311 self.warn(_("No username found, using '%s' instead\n") % user)
311 self.warn(_("No username found, using '%s' instead\n") % user)
312 except KeyError:
312 except KeyError:
313 pass
313 pass
314 if not user:
314 if not user:
315 raise util.Abort(_('no username supplied (see "hg help config")'))
315 raise util.Abort(_('no username supplied (see "hg help config")'))
316 if "\n" in user:
316 if "\n" in user:
317 raise util.Abort(_("username %s contains a newline\n") % repr(user))
317 raise util.Abort(_("username %s contains a newline\n") % repr(user))
318 return user
318 return user
319
319
320 def shortuser(self, user):
320 def shortuser(self, user):
321 """Return a short representation of a user name or email address."""
321 """Return a short representation of a user name or email address."""
322 if not self.verbose:
322 if not self.verbose:
323 user = util.shortuser(user)
323 user = util.shortuser(user)
324 return user
324 return user
325
325
326 def expandpath(self, loc, default=None):
326 def expandpath(self, loc, default=None):
327 """Return repository location relative to cwd or from [paths]"""
327 """Return repository location relative to cwd or from [paths]"""
328 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
328 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
329 return loc
329 return loc
330
330
331 path = self.config('paths', loc)
331 path = self.config('paths', loc)
332 if not path and default is not None:
332 if not path and default is not None:
333 path = self.config('paths', default)
333 path = self.config('paths', default)
334 return path or loc
334 return path or loc
335
335
336 def pushbuffer(self):
336 def pushbuffer(self):
337 self._buffers.append([])
337 self._buffers.append([])
338
338
339 def popbuffer(self, labeled=False):
339 def popbuffer(self, labeled=False):
340 '''pop the last buffer and return the buffered output
340 '''pop the last buffer and return the buffered output
341
341
342 If labeled is True, any labels associated with buffered
342 If labeled is True, any labels associated with buffered
343 output will be handled. By default, this has no effect
343 output will be handled. By default, this has no effect
344 on the output returned, but extensions and GUI tools may
344 on the output returned, but extensions and GUI tools may
345 handle this argument and returned styled output. If output
345 handle this argument and returned styled output. If output
346 is being buffered so it can be captured and parsed or
346 is being buffered so it can be captured and parsed or
347 processed, labeled should not be set to True.
347 processed, labeled should not be set to True.
348 '''
348 '''
349 return "".join(self._buffers.pop())
349 return "".join(self._buffers.pop())
350
350
351 def write(self, *args, **opts):
351 def write(self, *args, **opts):
352 '''write args to output
352 '''write args to output
353
353
354 By default, this method simply writes to the buffer or stdout,
354 By default, this method simply writes to the buffer or stdout,
355 but extensions or GUI tools may override this method,
355 but extensions or GUI tools may override this method,
356 write_err(), popbuffer(), and label() to style output from
356 write_err(), popbuffer(), and label() to style output from
357 various parts of hg.
357 various parts of hg.
358
358
359 An optional keyword argument, "label", can be passed in.
359 An optional keyword argument, "label", can be passed in.
360 This should be a string containing label names separated by
360 This should be a string containing label names separated by
361 space. Label names take the form of "topic.type". For example,
361 space. Label names take the form of "topic.type". For example,
362 ui.debug() issues a label of "ui.debug".
362 ui.debug() issues a label of "ui.debug".
363
363
364 When labeling output for a specific command, a label of
364 When labeling output for a specific command, a label of
365 "cmdname.type" is recommended. For example, status issues
365 "cmdname.type" is recommended. For example, status issues
366 a label of "status.modified" for modified files.
366 a label of "status.modified" for modified files.
367 '''
367 '''
368 if self._buffers:
368 if self._buffers:
369 self._buffers[-1].extend([str(a) for a in args])
369 self._buffers[-1].extend([str(a) for a in args])
370 else:
370 else:
371 for a in args:
371 for a in args:
372 sys.stdout.write(str(a))
372 sys.stdout.write(str(a))
373
373
374 def write_err(self, *args, **opts):
374 def write_err(self, *args, **opts):
375 try:
375 try:
376 if not getattr(sys.stdout, 'closed', False):
376 if not getattr(sys.stdout, 'closed', False):
377 sys.stdout.flush()
377 sys.stdout.flush()
378 for a in args:
378 for a in args:
379 sys.stderr.write(str(a))
379 sys.stderr.write(str(a))
380 # stderr may be buffered under win32 when redirected to files,
380 # stderr may be buffered under win32 when redirected to files,
381 # including stdout.
381 # including stdout.
382 if not getattr(sys.stderr, 'closed', False):
382 if not getattr(sys.stderr, 'closed', False):
383 sys.stderr.flush()
383 sys.stderr.flush()
384 except IOError, inst:
384 except IOError, inst:
385 if inst.errno not in (errno.EPIPE, errno.EIO):
385 if inst.errno not in (errno.EPIPE, errno.EIO):
386 raise
386 raise
387
387
388 def flush(self):
388 def flush(self):
389 try: sys.stdout.flush()
389 try: sys.stdout.flush()
390 except: pass
390 except: pass
391 try: sys.stderr.flush()
391 try: sys.stderr.flush()
392 except: pass
392 except: pass
393
393
394 def interactive(self):
394 def interactive(self):
395 '''is interactive input allowed?
395 '''is interactive input allowed?
396
396
397 An interactive session is a session where input can be reasonably read
397 An interactive session is a session where input can be reasonably read
398 from `sys.stdin'. If this function returns false, any attempt to read
398 from `sys.stdin'. If this function returns false, any attempt to read
399 from stdin should fail with an error, unless a sensible default has been
399 from stdin should fail with an error, unless a sensible default has been
400 specified.
400 specified.
401
401
402 Interactiveness is triggered by the value of the `ui.interactive'
402 Interactiveness is triggered by the value of the `ui.interactive'
403 configuration variable or - if it is unset - when `sys.stdin' points
403 configuration variable or - if it is unset - when `sys.stdin' points
404 to a terminal device.
404 to a terminal device.
405
405
406 This function refers to input only; for output, see `ui.formatted()'.
406 This function refers to input only; for output, see `ui.formatted()'.
407 '''
407 '''
408 i = self.configbool("ui", "interactive", None)
408 i = self.configbool("ui", "interactive", None)
409 if i is None:
409 if i is None:
410 try:
410 try:
411 return sys.stdin.isatty()
411 return sys.stdin.isatty()
412 except AttributeError:
412 except AttributeError:
413 # some environments replace stdin without implementing isatty
413 # some environments replace stdin without implementing isatty
414 # usually those are non-interactive
414 # usually those are non-interactive
415 return False
415 return False
416
416
417 return i
417 return i
418
418
419 def termwidth(self):
419 def termwidth(self):
420 '''how wide is the terminal in columns?
420 '''how wide is the terminal in columns?
421 '''
421 '''
422 if 'COLUMNS' in os.environ:
422 if 'COLUMNS' in os.environ:
423 try:
423 try:
424 return int(os.environ['COLUMNS'])
424 return int(os.environ['COLUMNS'])
425 except ValueError:
425 except ValueError:
426 pass
426 pass
427 return util.termwidth()
427 return util.termwidth()
428
428
429 def formatted(self):
429 def formatted(self):
430 '''should formatted output be used?
430 '''should formatted output be used?
431
431
432 It is often desirable to format the output to suite the output medium.
432 It is often desirable to format the output to suite the output medium.
433 Examples of this are truncating long lines or colorizing messages.
433 Examples of this are truncating long lines or colorizing messages.
434 However, this is not often not desirable when piping output into other
434 However, this is not often not desirable when piping output into other
435 utilities, e.g. `grep'.
435 utilities, e.g. `grep'.
436
436
437 Formatted output is triggered by the value of the `ui.formatted'
437 Formatted output is triggered by the value of the `ui.formatted'
438 configuration variable or - if it is unset - when `sys.stdout' points
438 configuration variable or - if it is unset - when `sys.stdout' points
439 to a terminal device. Please note that `ui.formatted' should be
439 to a terminal device. Please note that `ui.formatted' should be
440 considered an implementation detail; it is not intended for use outside
440 considered an implementation detail; it is not intended for use outside
441 Mercurial or its extensions.
441 Mercurial or its extensions.
442
442
443 This function refers to output only; for input, see `ui.interactive()'.
443 This function refers to output only; for input, see `ui.interactive()'.
444 This function always returns false when in plain mode, see `ui.plain()'.
444 This function always returns false when in plain mode, see `ui.plain()'.
445 '''
445 '''
446 if self.plain():
446 if self.plain():
447 return False
447 return False
448
448
449 i = self.configbool("ui", "formatted", None)
449 i = self.configbool("ui", "formatted", None)
450 if i is None:
450 if i is None:
451 try:
451 try:
452 return sys.stdout.isatty()
452 return sys.stdout.isatty()
453 except AttributeError:
453 except AttributeError:
454 # some environments replace stdout without implementing isatty
454 # some environments replace stdout without implementing isatty
455 # usually those are non-interactive
455 # usually those are non-interactive
456 return False
456 return False
457
457
458 return i
458 return i
459
459
460 def _readline(self, prompt=''):
460 def _readline(self, prompt=''):
461 if sys.stdin.isatty():
461 if sys.stdin.isatty():
462 try:
462 try:
463 # magically add command line editing support, where
463 # magically add command line editing support, where
464 # available
464 # available
465 import readline
465 import readline
466 # force demandimport to really load the module
466 # force demandimport to really load the module
467 readline.read_history_file
467 readline.read_history_file
468 # windows sometimes raises something other than ImportError
468 # windows sometimes raises something other than ImportError
469 except Exception:
469 except Exception:
470 pass
470 pass
471 line = raw_input(prompt)
471 line = raw_input(prompt)
472 # When stdin is in binary mode on Windows, it can cause
472 # When stdin is in binary mode on Windows, it can cause
473 # raw_input() to emit an extra trailing carriage return
473 # raw_input() to emit an extra trailing carriage return
474 if os.linesep == '\r\n' and line and line[-1] == '\r':
474 if os.linesep == '\r\n' and line and line[-1] == '\r':
475 line = line[:-1]
475 line = line[:-1]
476 return line
476 return line
477
477
478 def prompt(self, msg, default="y"):
478 def prompt(self, msg, default="y"):
479 """Prompt user with msg, read response.
479 """Prompt user with msg, read response.
480 If ui is not interactive, the default is returned.
480 If ui is not interactive, the default is returned.
481 """
481 """
482 if not self.interactive():
482 if not self.interactive():
483 self.write(msg, ' ', default, "\n")
483 self.write(msg, ' ', default, "\n")
484 return default
484 return default
485 try:
485 try:
486 r = self._readline(msg + ' ')
486 r = self._readline(self.label(msg, 'ui.prompt') + ' ')
487 if not r:
487 if not r:
488 return default
488 return default
489 return r
489 return r
490 except EOFError:
490 except EOFError:
491 raise util.Abort(_('response expected'))
491 raise util.Abort(_('response expected'))
492
492
493 def promptchoice(self, msg, choices, default=0):
493 def promptchoice(self, msg, choices, default=0):
494 """Prompt user with msg, read response, and ensure it matches
494 """Prompt user with msg, read response, and ensure it matches
495 one of the provided choices. The index of the choice is returned.
495 one of the provided choices. The index of the choice is returned.
496 choices is a sequence of acceptable responses with the format:
496 choices is a sequence of acceptable responses with the format:
497 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
497 ('&None', 'E&xec', 'Sym&link') Responses are case insensitive.
498 If ui is not interactive, the default is returned.
498 If ui is not interactive, the default is returned.
499 """
499 """
500 resps = [s[s.index('&')+1].lower() for s in choices]
500 resps = [s[s.index('&')+1].lower() for s in choices]
501 while True:
501 while True:
502 r = self.prompt(msg, resps[default])
502 r = self.prompt(msg, resps[default])
503 if r.lower() in resps:
503 if r.lower() in resps:
504 return resps.index(r.lower())
504 return resps.index(r.lower())
505 self.write(_("unrecognized response\n"))
505 self.write(_("unrecognized response\n"))
506
506
507 def getpass(self, prompt=None, default=None):
507 def getpass(self, prompt=None, default=None):
508 if not self.interactive():
508 if not self.interactive():
509 return default
509 return default
510 try:
510 try:
511 return getpass.getpass(prompt or _('password: '))
511 return getpass.getpass(prompt or _('password: '))
512 except EOFError:
512 except EOFError:
513 raise util.Abort(_('response expected'))
513 raise util.Abort(_('response expected'))
514 def status(self, *msg, **opts):
514 def status(self, *msg, **opts):
515 '''write status message to output (if ui.quiet is False)
515 '''write status message to output (if ui.quiet is False)
516
516
517 This adds an output label of "ui.status".
517 This adds an output label of "ui.status".
518 '''
518 '''
519 if not self.quiet:
519 if not self.quiet:
520 opts['label'] = opts.get('label', '') + ' ui.status'
520 opts['label'] = opts.get('label', '') + ' ui.status'
521 self.write(*msg, **opts)
521 self.write(*msg, **opts)
522 def warn(self, *msg, **opts):
522 def warn(self, *msg, **opts):
523 '''write warning message to output (stderr)
523 '''write warning message to output (stderr)
524
524
525 This adds an output label of "ui.warning".
525 This adds an output label of "ui.warning".
526 '''
526 '''
527 opts['label'] = opts.get('label', '') + ' ui.warning'
527 opts['label'] = opts.get('label', '') + ' ui.warning'
528 self.write_err(*msg, **opts)
528 self.write_err(*msg, **opts)
529 def note(self, *msg, **opts):
529 def note(self, *msg, **opts):
530 '''write note to output (if ui.verbose is True)
530 '''write note to output (if ui.verbose is True)
531
531
532 This adds an output label of "ui.note".
532 This adds an output label of "ui.note".
533 '''
533 '''
534 if self.verbose:
534 if self.verbose:
535 opts['label'] = opts.get('label', '') + ' ui.note'
535 opts['label'] = opts.get('label', '') + ' ui.note'
536 self.write(*msg, **opts)
536 self.write(*msg, **opts)
537 def debug(self, *msg, **opts):
537 def debug(self, *msg, **opts):
538 '''write debug message to output (if ui.debugflag is True)
538 '''write debug message to output (if ui.debugflag is True)
539
539
540 This adds an output label of "ui.debug".
540 This adds an output label of "ui.debug".
541 '''
541 '''
542 if self.debugflag:
542 if self.debugflag:
543 opts['label'] = opts.get('label', '') + ' ui.debug'
543 opts['label'] = opts.get('label', '') + ' ui.debug'
544 self.write(*msg, **opts)
544 self.write(*msg, **opts)
545 def edit(self, text, user):
545 def edit(self, text, user):
546 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
546 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
547 text=True)
547 text=True)
548 try:
548 try:
549 f = os.fdopen(fd, "w")
549 f = os.fdopen(fd, "w")
550 f.write(text)
550 f.write(text)
551 f.close()
551 f.close()
552
552
553 editor = self.geteditor()
553 editor = self.geteditor()
554
554
555 util.system("%s \"%s\"" % (editor, name),
555 util.system("%s \"%s\"" % (editor, name),
556 environ={'HGUSER': user},
556 environ={'HGUSER': user},
557 onerr=util.Abort, errprefix=_("edit failed"))
557 onerr=util.Abort, errprefix=_("edit failed"))
558
558
559 f = open(name)
559 f = open(name)
560 t = f.read()
560 t = f.read()
561 f.close()
561 f.close()
562 finally:
562 finally:
563 os.unlink(name)
563 os.unlink(name)
564
564
565 return t
565 return t
566
566
567 def traceback(self, exc=None):
567 def traceback(self, exc=None):
568 '''print exception traceback if traceback printing enabled.
568 '''print exception traceback if traceback printing enabled.
569 only to call in exception handler. returns true if traceback
569 only to call in exception handler. returns true if traceback
570 printed.'''
570 printed.'''
571 if self.tracebackflag:
571 if self.tracebackflag:
572 if exc:
572 if exc:
573 traceback.print_exception(exc[0], exc[1], exc[2])
573 traceback.print_exception(exc[0], exc[1], exc[2])
574 else:
574 else:
575 traceback.print_exc()
575 traceback.print_exc()
576 return self.tracebackflag
576 return self.tracebackflag
577
577
578 def geteditor(self):
578 def geteditor(self):
579 '''return editor to use'''
579 '''return editor to use'''
580 return (os.environ.get("HGEDITOR") or
580 return (os.environ.get("HGEDITOR") or
581 self.config("ui", "editor") or
581 self.config("ui", "editor") or
582 os.environ.get("VISUAL") or
582 os.environ.get("VISUAL") or
583 os.environ.get("EDITOR", "vi"))
583 os.environ.get("EDITOR", "vi"))
584
584
585 def progress(self, topic, pos, item="", unit="", total=None):
585 def progress(self, topic, pos, item="", unit="", total=None):
586 '''show a progress message
586 '''show a progress message
587
587
588 With stock hg, this is simply a debug message that is hidden
588 With stock hg, this is simply a debug message that is hidden
589 by default, but with extensions or GUI tools it may be
589 by default, but with extensions or GUI tools it may be
590 visible. 'topic' is the current operation, 'item' is a
590 visible. 'topic' is the current operation, 'item' is a
591 non-numeric marker of the current position (ie the currently
591 non-numeric marker of the current position (ie the currently
592 in-process file), 'pos' is the current numeric position (ie
592 in-process file), 'pos' is the current numeric position (ie
593 revision, bytes, etc.), unit is a corresponding unit label,
593 revision, bytes, etc.), unit is a corresponding unit label,
594 and total is the highest expected pos.
594 and total is the highest expected pos.
595
595
596 Multiple nested topics may be active at a time.
596 Multiple nested topics may be active at a time.
597
597
598 All topics should be marked closed by setting pos to None at
598 All topics should be marked closed by setting pos to None at
599 termination.
599 termination.
600 '''
600 '''
601
601
602 if pos is None or not self.debugflag:
602 if pos is None or not self.debugflag:
603 return
603 return
604
604
605 if unit:
605 if unit:
606 unit = ' ' + unit
606 unit = ' ' + unit
607 if item:
607 if item:
608 item = ' ' + item
608 item = ' ' + item
609
609
610 if total:
610 if total:
611 pct = 100.0 * pos / total
611 pct = 100.0 * pos / total
612 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
612 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
613 % (topic, item, pos, total, unit, pct))
613 % (topic, item, pos, total, unit, pct))
614 else:
614 else:
615 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
615 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
616
616
617 def log(self, service, message):
617 def log(self, service, message):
618 '''hook for logging facility extensions
618 '''hook for logging facility extensions
619
619
620 service should be a readily-identifiable subsystem, which will
620 service should be a readily-identifiable subsystem, which will
621 allow filtering.
621 allow filtering.
622 message should be a newline-terminated string to log.
622 message should be a newline-terminated string to log.
623 '''
623 '''
624 pass
624 pass
625
625
626 def label(self, msg, label):
626 def label(self, msg, label):
627 '''style msg based on supplied label
627 '''style msg based on supplied label
628
628
629 Like ui.write(), this just returns msg unchanged, but extensions
629 Like ui.write(), this just returns msg unchanged, but extensions
630 and GUI tools can override it to allow styling output without
630 and GUI tools can override it to allow styling output without
631 writing it.
631 writing it.
632
632
633 ui.write(s, 'label') is equivalent to
633 ui.write(s, 'label') is equivalent to
634 ui.write(ui.label(s, 'label')).
634 ui.write(ui.label(s, 'label')).
635 '''
635 '''
636 return msg
636 return msg
@@ -1,125 +1,125 b''
1 Setup
1 Setup
2
2
3 $ echo "[extensions]" >> $HGRCPATH
3 $ echo "[extensions]" >> $HGRCPATH
4 $ echo "color=" >> $HGRCPATH
4 $ echo "color=" >> $HGRCPATH
5 $ hg init repo
5 $ hg init repo
6 $ cd repo
6 $ cd repo
7 $ cat > a <<EOF
7 $ cat > a <<EOF
8 > c
8 > c
9 > c
9 > c
10 > a
10 > a
11 > a
11 > a
12 > b
12 > b
13 > a
13 > a
14 > a
14 > a
15 > c
15 > c
16 > c
16 > c
17 > EOF
17 > EOF
18 $ hg ci -Am adda
18 $ hg ci -Am adda
19 adding a
19 adding a
20 $ cat > a <<EOF
20 $ cat > a <<EOF
21 > c
21 > c
22 > c
22 > c
23 > a
23 > a
24 > a
24 > a
25 > dd
25 > dd
26 > a
26 > a
27 > a
27 > a
28 > c
28 > c
29 > c
29 > c
30 > EOF
30 > EOF
31
31
32 default context
32 default context
33
33
34 $ hg diff --nodates --color=always
34 $ hg diff --nodates --color=always
35 \x1b[0;1mdiff -r cf9f4ba66af2 a\x1b[0m (esc)
35 \x1b[0;1mdiff -r cf9f4ba66af2 a\x1b[0m (esc)
36 \x1b[0;31;1m--- a/a\x1b[0m (esc)
36 \x1b[0;31;1m--- a/a\x1b[0m (esc)
37 \x1b[0;32;1m+++ b/a\x1b[0m (esc)
37 \x1b[0;32;1m+++ b/a\x1b[0m (esc)
38 \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
38 \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
39 c
39 c
40 a
40 a
41 a
41 a
42 \x1b[0;31m-b\x1b[0m (esc)
42 \x1b[0;31m-b\x1b[0m (esc)
43 \x1b[0;32m+dd\x1b[0m (esc)
43 \x1b[0;32m+dd\x1b[0m (esc)
44 a
44 a
45 a
45 a
46 c
46 c
47
47
48 --unified=2
48 --unified=2
49
49
50 $ hg diff --nodates -U 2 --color=always
50 $ hg diff --nodates -U 2 --color=always
51 \x1b[0;1mdiff -r cf9f4ba66af2 a\x1b[0m (esc)
51 \x1b[0;1mdiff -r cf9f4ba66af2 a\x1b[0m (esc)
52 \x1b[0;31;1m--- a/a\x1b[0m (esc)
52 \x1b[0;31;1m--- a/a\x1b[0m (esc)
53 \x1b[0;32;1m+++ b/a\x1b[0m (esc)
53 \x1b[0;32;1m+++ b/a\x1b[0m (esc)
54 \x1b[0;35m@@ -3,5 +3,5 @@\x1b[0m (esc)
54 \x1b[0;35m@@ -3,5 +3,5 @@\x1b[0m (esc)
55 a
55 a
56 a
56 a
57 \x1b[0;31m-b\x1b[0m (esc)
57 \x1b[0;31m-b\x1b[0m (esc)
58 \x1b[0;32m+dd\x1b[0m (esc)
58 \x1b[0;32m+dd\x1b[0m (esc)
59 a
59 a
60 a
60 a
61
61
62 diffstat
62 diffstat
63
63
64 $ hg diff --stat --color=always
64 $ hg diff --stat --color=always
65 a | 2 \x1b[0;32m+\x1b[0m\x1b[0;31m-\x1b[0m (esc)
65 a | 2 \x1b[0;32m+\x1b[0m\x1b[0;31m-\x1b[0m (esc)
66 1 files changed, 1 insertions(+), 1 deletions(-)
66 1 files changed, 1 insertions(+), 1 deletions(-)
67 $ echo "record=" >> $HGRCPATH
67 $ echo "record=" >> $HGRCPATH
68 $ echo "[ui]" >> $HGRCPATH
68 $ echo "[ui]" >> $HGRCPATH
69 $ echo "interactive=true" >> $HGRCPATH
69 $ echo "interactive=true" >> $HGRCPATH
70 $ echo "[diff]" >> $HGRCPATH
70 $ echo "[diff]" >> $HGRCPATH
71 $ echo "git=True" >> $HGRCPATH
71 $ echo "git=True" >> $HGRCPATH
72
72
73 record
73 record
74
74
75 $ chmod 0755 a
75 $ chmod 0755 a
76 $ hg record --color=always -m moda a <<EOF
76 $ hg record --color=always -m moda a <<EOF
77 > y
77 > y
78 > y
78 > y
79 > EOF
79 > EOF
80 \x1b[0;1mdiff --git a/a b/a\x1b[0m (esc)
80 \x1b[0;1mdiff --git a/a b/a\x1b[0m (esc)
81 \x1b[0;36;1mold mode 100644\x1b[0m (esc)
81 \x1b[0;36;1mold mode 100644\x1b[0m (esc)
82 \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
82 \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
83 1 hunks, 1 lines changed
83 1 hunks, 1 lines changed
84 examine changes to 'a'? [Ynsfdaq?]
84 \x1b[0;33mexamine changes to 'a'? [Ynsfdaq?]\x1b[0m (esc)
85 \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
85 \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
86 c
86 c
87 a
87 a
88 a
88 a
89 \x1b[0;31m-b\x1b[0m (esc)
89 \x1b[0;31m-b\x1b[0m (esc)
90 \x1b[0;32m+dd\x1b[0m (esc)
90 \x1b[0;32m+dd\x1b[0m (esc)
91 a
91 a
92 a
92 a
93 c
93 c
94 record this change to 'a'? [Ynsfdaq?]
94 \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m (esc)
95 $ echo
95 $ echo
96
96
97 $ echo "[extensions]" >> $HGRCPATH
97 $ echo "[extensions]" >> $HGRCPATH
98 $ echo "mq=" >> $HGRCPATH
98 $ echo "mq=" >> $HGRCPATH
99 $ hg rollback
99 $ hg rollback
100 repository tip rolled back to revision 0 (undo commit)
100 repository tip rolled back to revision 0 (undo commit)
101 working directory now based on revision 0
101 working directory now based on revision 0
102
102
103 qrecord
103 qrecord
104
104
105 $ hg qrecord --color=always -m moda patch <<EOF
105 $ hg qrecord --color=always -m moda patch <<EOF
106 > y
106 > y
107 > y
107 > y
108 > EOF
108 > EOF
109 \x1b[0;1mdiff --git a/a b/a\x1b[0m (esc)
109 \x1b[0;1mdiff --git a/a b/a\x1b[0m (esc)
110 \x1b[0;36;1mold mode 100644\x1b[0m (esc)
110 \x1b[0;36;1mold mode 100644\x1b[0m (esc)
111 \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
111 \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
112 1 hunks, 1 lines changed
112 1 hunks, 1 lines changed
113 examine changes to 'a'? [Ynsfdaq?]
113 \x1b[0;33mexamine changes to 'a'? [Ynsfdaq?]\x1b[0m (esc)
114 \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
114 \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
115 c
115 c
116 a
116 a
117 a
117 a
118 \x1b[0;31m-b\x1b[0m (esc)
118 \x1b[0;31m-b\x1b[0m (esc)
119 \x1b[0;32m+dd\x1b[0m (esc)
119 \x1b[0;32m+dd\x1b[0m (esc)
120 a
120 a
121 a
121 a
122 c
122 c
123 record this change to 'a'? [Ynsfdaq?]
123 \x1b[0;33mrecord this change to 'a'? [Ynsfdaq?]\x1b[0m (esc)
124 $ echo
124 $ echo
125
125
General Comments 0
You need to be logged in to leave comments. Login now