##// END OF EJS Templates
color: colorize output of hg resolve -l
Georg Brandl -
r10223:51421ab5 default
parent child Browse files
Show More
@@ -1,343 +1,368 b''
1 1 # color.py color output for the status and qseries commands
2 2 #
3 3 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
4 4 #
5 5 # This program is free software; you can redistribute it and/or modify it
6 6 # under the terms of the GNU General Public License as published by the
7 7 # Free Software Foundation; either version 2 of the License, or (at your
8 8 # option) any later version.
9 9 #
10 10 # This program is distributed in the hope that it will be useful, but
11 11 # WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
13 13 # Public License for more details.
14 14 #
15 15 # You should have received a copy of the GNU General Public License along
16 16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 18
19 19 '''colorize output from some commands
20 20
21 This extension modifies the status command to add color to its output
22 to reflect file status, the qseries command to add color to reflect
21 This extension modifies the status and resolve commands to add color to their
22 output to reflect file status, the qseries command to add color to reflect
23 23 patch status (applied, unapplied, missing), and to diff-related
24 24 commands to highlight additions, removals, diff headers, and trailing
25 25 whitespace.
26 26
27 27 Other effects in addition to color, like bold and underlined text, are
28 28 also available. Effects are rendered with the ECMA-48 SGR control
29 29 function (aka ANSI escape codes). This module also provides the
30 30 render_text function, which can be used to add effects to any text.
31 31
32 32 Default effects may be overridden from the .hgrc file::
33 33
34 34 [color]
35 35 status.modified = blue bold underline red_background
36 36 status.added = green bold
37 37 status.removed = red bold blue_background
38 38 status.deleted = cyan bold underline
39 39 status.unknown = magenta bold underline
40 40 status.ignored = black bold
41 41
42 42 # 'none' turns off all effects
43 43 status.clean = none
44 44 status.copied = none
45 45
46 46 qseries.applied = blue bold underline
47 47 qseries.unapplied = black bold
48 48 qseries.missing = red bold
49 49
50 50 diff.diffline = bold
51 51 diff.extended = cyan bold
52 52 diff.file_a = red bold
53 53 diff.file_b = green bold
54 54 diff.hunk = magenta
55 55 diff.deleted = red
56 56 diff.inserted = green
57 57 diff.changed = white
58 58 diff.trailingwhitespace = bold red_background
59 59
60 resolve.unresolved = red bold
61 resolve.resolved = green bold
62
60 63 bookmarks.current = green
61 64 '''
62 65
63 66 import os, sys
64 67
65 68 from mercurial import cmdutil, commands, extensions, error
66 69 from mercurial.i18n import _
67 70
68 71 # start and stop parameters for effects
69 72 _effect_params = {'none': 0,
70 73 'black': 30,
71 74 'red': 31,
72 75 'green': 32,
73 76 'yellow': 33,
74 77 'blue': 34,
75 78 'magenta': 35,
76 79 'cyan': 36,
77 80 'white': 37,
78 81 'bold': 1,
79 82 'italic': 3,
80 83 'underline': 4,
81 84 'inverse': 7,
82 85 'black_background': 40,
83 86 'red_background': 41,
84 87 'green_background': 42,
85 88 'yellow_background': 43,
86 89 'blue_background': 44,
87 90 'purple_background': 45,
88 91 'cyan_background': 46,
89 92 'white_background': 47}
90 93
91 94 def render_effects(text, effects):
92 95 'Wrap text in commands to turn on each effect.'
93 96 start = [str(_effect_params[e]) for e in ['none'] + effects]
94 97 start = '\033[' + ';'.join(start) + 'm'
95 98 stop = '\033[' + str(_effect_params['none']) + 'm'
96 99 return ''.join([start, text, stop])
97 100
98 def colorstatus(orig, ui, repo, *pats, **opts):
99 '''run the status command with colored output'''
100
101 delimiter = opts['print0'] and '\0' or '\n'
101 def _colorstatuslike(abbreviations, effectdefs, orig, ui, repo, *pats, **opts):
102 '''run a status-like command with colorized output'''
103 delimiter = opts.get('print0') and '\0' or '\n'
102 104
103 105 nostatus = opts.get('no_status')
104 106 opts['no_status'] = False
105 # run status and capture its output
107 # run original command and capture its output
106 108 ui.pushbuffer()
107 109 retval = orig(ui, repo, *pats, **opts)
108 110 # filter out empty strings
109 111 lines_with_status = [line for line in ui.popbuffer().split(delimiter) if line]
110 112
111 113 if nostatus:
112 114 lines = [l[2:] for l in lines_with_status]
113 115 else:
114 116 lines = lines_with_status
115 117
116 118 # apply color to output and display it
117 119 for i in xrange(len(lines)):
118 status = _status_abbreviations[lines_with_status[i][0]]
119 effects = _status_effects[status]
120 status = abbreviations[lines_with_status[i][0]]
121 effects = effectdefs[status]
120 122 if effects:
121 123 lines[i] = render_effects(lines[i], effects)
122 124 ui.write(lines[i] + delimiter)
123 125 return retval
124 126
127
125 128 _status_abbreviations = { 'M': 'modified',
126 129 'A': 'added',
127 130 'R': 'removed',
128 131 '!': 'deleted',
129 132 '?': 'unknown',
130 133 'I': 'ignored',
131 134 'C': 'clean',
132 135 ' ': 'copied', }
133 136
134 137 _status_effects = { 'modified': ['blue', 'bold'],
135 138 'added': ['green', 'bold'],
136 139 'removed': ['red', 'bold'],
137 140 'deleted': ['cyan', 'bold', 'underline'],
138 141 'unknown': ['magenta', 'bold', 'underline'],
139 142 'ignored': ['black', 'bold'],
140 143 'clean': ['none'],
141 144 'copied': ['none'], }
142 145
146 def colorstatus(orig, ui, repo, *pats, **opts):
147 '''run the status command with colored output'''
148 return _colorstatuslike(_status_abbreviations, _status_effects,
149 orig, ui, repo, *pats, **opts)
150
151
152 _resolve_abbreviations = { 'U': 'unresolved',
153 'R': 'resolved', }
154
155 _resolve_effects = { 'unresolved': ['red', 'bold'],
156 'resolved': ['green', 'bold'], }
157
158 def colorresolve(orig, ui, repo, *pats, **opts):
159 '''run the resolve command with colored output'''
160 if not opts.get('list'):
161 # only colorize for resolve -l
162 return orig(ui, repo, *pats, **opts)
163 return _colorstatuslike(_resolve_abbreviations, _resolve_effects,
164 orig, ui, repo, *pats, **opts)
165
166
143 167 _bookmark_effects = { 'current': ['green'] }
144 168
145 169 def colorbookmarks(orig, ui, repo, *pats, **opts):
146 170 def colorize(orig, s):
147 171 lines = s.split('\n')
148 172 for i, line in enumerate(lines):
149 173 if line.startswith(" *"):
150 174 lines[i] = render_effects(line, _bookmark_effects['current'])
151 175 orig('\n'.join(lines))
152 176 oldwrite = extensions.wrapfunction(ui, 'write', colorize)
153 177 try:
154 178 orig(ui, repo, *pats, **opts)
155 179 finally:
156 180 ui.write = oldwrite
157 181
158 182 def colorqseries(orig, ui, repo, *dummy, **opts):
159 183 '''run the qseries command with colored output'''
160 184 ui.pushbuffer()
161 185 retval = orig(ui, repo, **opts)
162 186 patchlines = ui.popbuffer().splitlines()
163 187 patchnames = repo.mq.series
164 188
165 189 for patch, patchname in zip(patchlines, patchnames):
166 190 if opts['missing']:
167 191 effects = _patch_effects['missing']
168 192 # Determine if patch is applied.
169 193 elif [ applied for applied in repo.mq.applied
170 194 if patchname == applied.name ]:
171 195 effects = _patch_effects['applied']
172 196 else:
173 197 effects = _patch_effects['unapplied']
174 198
175 199 patch = patch.replace(patchname, render_effects(patchname, effects), 1)
176 200 ui.write(patch + '\n')
177 201 return retval
178 202
179 203 _patch_effects = { 'applied': ['blue', 'bold', 'underline'],
180 204 'missing': ['red', 'bold'],
181 205 'unapplied': ['black', 'bold'], }
182 206 def colorwrap(orig, *args):
183 207 '''wrap ui.write for colored diff output'''
184 208 def _colorize(s):
185 209 lines = s.split('\n')
186 210 for i, line in enumerate(lines):
187 211 stripline = line
188 212 if line and line[0] in '+-':
189 213 # highlight trailing whitespace, but only in changed lines
190 214 stripline = line.rstrip()
191 215 for prefix, style in _diff_prefixes:
192 216 if stripline.startswith(prefix):
193 217 lines[i] = render_effects(stripline, _diff_effects[style])
194 218 break
195 219 if line != stripline:
196 220 lines[i] += render_effects(
197 221 line[len(stripline):], _diff_effects['trailingwhitespace'])
198 222 return '\n'.join(lines)
199 223 orig(*[_colorize(s) for s in args])
200 224
201 225 def colorshowpatch(orig, self, node):
202 226 '''wrap cmdutil.changeset_printer.showpatch with colored output'''
203 227 oldwrite = extensions.wrapfunction(self.ui, 'write', colorwrap)
204 228 try:
205 229 orig(self, node)
206 230 finally:
207 231 self.ui.write = oldwrite
208 232
209 233 def colordiffstat(orig, s):
210 234 lines = s.split('\n')
211 235 for i, line in enumerate(lines):
212 236 if line and line[-1] in '+-':
213 237 name, graph = line.rsplit(' ', 1)
214 238 graph = graph.replace('-',
215 239 render_effects('-', _diff_effects['deleted']))
216 240 graph = graph.replace('+',
217 241 render_effects('+', _diff_effects['inserted']))
218 242 lines[i] = ' '.join([name, graph])
219 243 orig('\n'.join(lines))
220 244
221 245 def colordiff(orig, ui, repo, *pats, **opts):
222 246 '''run the diff command with colored output'''
223 247 if opts.get('stat'):
224 248 wrapper = colordiffstat
225 249 else:
226 250 wrapper = colorwrap
227 251 oldwrite = extensions.wrapfunction(ui, 'write', wrapper)
228 252 try:
229 253 orig(ui, repo, *pats, **opts)
230 254 finally:
231 255 ui.write = oldwrite
232 256
233 257 def colorchurn(orig, ui, repo, *pats, **opts):
234 258 '''run the churn command with colored output'''
235 259 if not opts.get('diffstat'):
236 260 return orig(ui, repo, *pats, **opts)
237 261 oldwrite = extensions.wrapfunction(ui, 'write', colordiffstat)
238 262 try:
239 263 orig(ui, repo, *pats, **opts)
240 264 finally:
241 265 ui.write = oldwrite
242 266
243 267 _diff_prefixes = [('diff', 'diffline'),
244 268 ('copy', 'extended'),
245 269 ('rename', 'extended'),
246 270 ('old', 'extended'),
247 271 ('new', 'extended'),
248 272 ('deleted', 'extended'),
249 273 ('---', 'file_a'),
250 274 ('+++', 'file_b'),
251 275 ('@', 'hunk'),
252 276 ('-', 'deleted'),
253 277 ('+', 'inserted')]
254 278
255 279 _diff_effects = {'diffline': ['bold'],
256 280 'extended': ['cyan', 'bold'],
257 281 'file_a': ['red', 'bold'],
258 282 'file_b': ['green', 'bold'],
259 283 'hunk': ['magenta'],
260 284 'deleted': ['red'],
261 285 'inserted': ['green'],
262 286 'changed': ['white'],
263 287 'trailingwhitespace': ['bold', 'red_background']}
264 288
265 289 def extsetup(ui):
266 290 '''Initialize the extension.'''
267 291 _setupcmd(ui, 'diff', commands.table, colordiff, _diff_effects)
268 292 _setupcmd(ui, 'incoming', commands.table, None, _diff_effects)
269 293 _setupcmd(ui, 'log', commands.table, None, _diff_effects)
270 294 _setupcmd(ui, 'outgoing', commands.table, None, _diff_effects)
271 295 _setupcmd(ui, 'tip', commands.table, None, _diff_effects)
272 296 _setupcmd(ui, 'status', commands.table, colorstatus, _status_effects)
297 _setupcmd(ui, 'resolve', commands.table, colorresolve, _resolve_effects)
273 298
274 299 try:
275 300 mq = extensions.find('mq')
276 301 _setupcmd(ui, 'qdiff', mq.cmdtable, colordiff, _diff_effects)
277 302 _setupcmd(ui, 'qseries', mq.cmdtable, colorqseries, _patch_effects)
278 303 except KeyError:
279 304 mq = None
280 305
281 306 try:
282 307 rec = extensions.find('record')
283 308 _setupcmd(ui, 'record', rec.cmdtable, colordiff, _diff_effects)
284 309 except KeyError:
285 310 rec = None
286 311
287 312 if mq and rec:
288 313 _setupcmd(ui, 'qrecord', rec.cmdtable, colordiff, _diff_effects)
289 314 try:
290 315 churn = extensions.find('churn')
291 316 _setupcmd(ui, 'churn', churn.cmdtable, colorchurn, _diff_effects)
292 317 except KeyError:
293 318 churn = None
294 319
295 320 try:
296 321 bookmarks = extensions.find('bookmarks')
297 322 _setupcmd(ui, 'bookmarks', bookmarks.cmdtable, colorbookmarks,
298 323 _bookmark_effects)
299 324 except KeyError:
300 325 # The bookmarks extension is not enabled
301 326 pass
302 327
303 328 def _setupcmd(ui, cmd, table, func, effectsmap):
304 329 '''patch in command to command table and load effect map'''
305 330 def nocolor(orig, *args, **opts):
306 331
307 332 if (opts['no_color'] or opts['color'] == 'never' or
308 333 (opts['color'] == 'auto' and (os.environ.get('TERM') == 'dumb'
309 334 or not sys.__stdout__.isatty()))):
310 335 del opts['no_color']
311 336 del opts['color']
312 337 return orig(*args, **opts)
313 338
314 339 oldshowpatch = extensions.wrapfunction(cmdutil.changeset_printer,
315 340 'showpatch', colorshowpatch)
316 341 del opts['no_color']
317 342 del opts['color']
318 343 try:
319 344 if func is not None:
320 345 return func(orig, *args, **opts)
321 346 return orig(*args, **opts)
322 347 finally:
323 348 cmdutil.changeset_printer.showpatch = oldshowpatch
324 349
325 350 entry = extensions.wrapcommand(table, cmd, nocolor)
326 351 entry[1].extend([
327 352 ('', 'color', 'auto', _("when to colorize (always, auto, or never)")),
328 353 ('', 'no-color', None, _("don't colorize output (DEPRECATED)")),
329 354 ])
330 355
331 356 for status in effectsmap:
332 357 configkey = cmd + '.' + status
333 358 effects = ui.configlist('color', configkey)
334 359 if effects:
335 360 good = []
336 361 for e in effects:
337 362 if e in _effect_params:
338 363 good.append(e)
339 364 else:
340 365 ui.warn(_("ignoring unknown color/effect %r "
341 366 "(configured in color.%s)\n")
342 367 % (e, configkey))
343 368 effectsmap[status] = good
@@ -1,100 +1,120 b''
1 1 #!/bin/sh
2 2
3 3 echo "[extensions]" >> $HGRCPATH
4 4 echo "color=" >> $HGRCPATH
5 5
6 6 hg init repo1
7 7 cd repo1
8 8 mkdir a b a/1 b/1 b/2
9 9 touch in_root a/in_a b/in_b a/1/in_a_1 b/1/in_b_1 b/2/in_b_2
10 10 echo "hg status in repo root:"
11 11 hg status --color=always
12 12 echo "hg status . in repo root:"
13 13 hg status --color=always .
14 14 for dir in a b a/1 b/1 b/2; do
15 15 echo "hg status in $dir:"
16 16 hg status --color=always --cwd "$dir"
17 17 echo "hg status . in $dir:"
18 18 hg status --color=always --cwd "$dir" .
19 19 echo "hg status .. in $dir:"
20 20 hg status --color=always --cwd "$dir" ..
21 21 done
22 22 cd ..
23 23
24 24 hg init repo2
25 25 cd repo2
26 26 touch modified removed deleted ignored
27 27 echo "^ignored$" > .hgignore
28 28 hg ci -A -m 'initial checkin' -d "1000000 0"
29 29 touch modified added unknown ignored
30 30 hg add added
31 31 hg remove removed
32 32 rm deleted
33 33 echo "hg status:"
34 34 hg status --color=always
35 35 echo "hg status modified added removed deleted unknown never-existed ignored:"
36 36 hg status --color=always modified added removed deleted unknown never-existed ignored
37 37 hg copy modified copied
38 38 echo "hg status -C:"
39 39 hg status --color=always -C
40 40 echo "hg status -A:"
41 41 hg status --color=always -A
42 42 echo "^ignoreddir$" > .hgignore
43 43 mkdir ignoreddir
44 44 touch ignoreddir/file
45 45 echo "hg status ignoreddir/file:"
46 46 hg status --color=always ignoreddir/file
47 47 echo "hg status -i ignoreddir/file:"
48 48 hg status --color=always -i ignoreddir/file
49 49 cd ..
50 50
51 51 # check 'status -q' and some combinations
52 52 hg init repo3
53 53 cd repo3
54 54 touch modified removed deleted ignored
55 55 echo "^ignored$" > .hgignore
56 56 hg commit -A -m 'initial checkin'
57 57 touch added unknown ignored
58 58 hg add added
59 59 echo "test" >> modified
60 60 hg remove removed
61 61 rm deleted
62 62 hg copy modified copied
63 63
64 64 echo "% test unknown color"
65 65 hg --config color.status.modified=periwinkle status --color=always
66 66
67 67 # Run status with 2 different flags.
68 68 # Check if result is the same or different.
69 69 # If result is not as expected, raise error
70 70 assert() {
71 71 hg status --color=always $1 > ../a
72 72 hg status --color=always $2 > ../b
73 73 out=`diff ../a ../b`
74 74 if [ $? -ne 0 ]; then
75 75 out=1
76 76 else
77 77 out=0
78 78 fi
79 79 if [ $3 -eq 0 ]; then
80 80 df="same"
81 81 else
82 82 df="different"
83 83 fi
84 84 if [ $out -ne $3 ]; then
85 85 echo "Error on $1 and $2, should be $df."
86 86 fi
87 87 }
88 88
89 89 # assert flag1 flag2 [0-same | 1-different]
90 90 assert "-q" "-mard" 0
91 91 assert "-A" "-marduicC" 0
92 92 assert "-qA" "-mardcC" 0
93 93 assert "-qAui" "-A" 0
94 94 assert "-qAu" "-marducC" 0
95 95 assert "-qAi" "-mardicC" 0
96 96 assert "-qu" "-u" 0
97 97 assert "-q" "-u" 1
98 98 assert "-m" "-a" 1
99 99 assert "-r" "-d" 1
100 100
101 cd ..
102
103 # test 'resolve -l'
104 hg init repo4
105 cd repo4
106 echo "file a" > a
107 echo "file b" > b
108 hg add a b
109 hg commit -m "initial"
110 echo "file a change 1" > a
111 echo "file b change 1" > b
112 hg commit -m "head 1"
113 hg update 0
114 echo "file a change 2" > a
115 echo "file b change 2" > b
116 hg commit -m "head 2"
117 hg merge
118 hg resolve -m b
119 echo "hg resolve with one unresolved, one resolved:"
120 hg resolve --color=always -l
@@ -1,134 +1,147 b''
1 1 hg status in repo root:
2 2 ? a/1/in_a_1
3 3 ? a/in_a
4 4 ? b/1/in_b_1
5 5 ? b/2/in_b_2
6 6 ? b/in_b
7 7 ? in_root
8 8 hg status . in repo root:
9 9 ? a/1/in_a_1
10 10 ? a/in_a
11 11 ? b/1/in_b_1
12 12 ? b/2/in_b_2
13 13 ? b/in_b
14 14 ? in_root
15 15 hg status in a:
16 16 ? a/1/in_a_1
17 17 ? a/in_a
18 18 ? b/1/in_b_1
19 19 ? b/2/in_b_2
20 20 ? b/in_b
21 21 ? in_root
22 22 hg status . in a:
23 23 ? 1/in_a_1
24 24 ? in_a
25 25 hg status .. in a:
26 26 ? 1/in_a_1
27 27 ? in_a
28 28 ? ../b/1/in_b_1
29 29 ? ../b/2/in_b_2
30 30 ? ../b/in_b
31 31 ? ../in_root
32 32 hg status in b:
33 33 ? a/1/in_a_1
34 34 ? a/in_a
35 35 ? b/1/in_b_1
36 36 ? b/2/in_b_2
37 37 ? b/in_b
38 38 ? in_root
39 39 hg status . in b:
40 40 ? 1/in_b_1
41 41 ? 2/in_b_2
42 42 ? in_b
43 43 hg status .. in b:
44 44 ? ../a/1/in_a_1
45 45 ? ../a/in_a
46 46 ? 1/in_b_1
47 47 ? 2/in_b_2
48 48 ? in_b
49 49 ? ../in_root
50 50 hg status in a/1:
51 51 ? a/1/in_a_1
52 52 ? a/in_a
53 53 ? b/1/in_b_1
54 54 ? b/2/in_b_2
55 55 ? b/in_b
56 56 ? in_root
57 57 hg status . in a/1:
58 58 ? in_a_1
59 59 hg status .. in a/1:
60 60 ? in_a_1
61 61 ? ../in_a
62 62 hg status in b/1:
63 63 ? a/1/in_a_1
64 64 ? a/in_a
65 65 ? b/1/in_b_1
66 66 ? b/2/in_b_2
67 67 ? b/in_b
68 68 ? in_root
69 69 hg status . in b/1:
70 70 ? in_b_1
71 71 hg status .. in b/1:
72 72 ? in_b_1
73 73 ? ../2/in_b_2
74 74 ? ../in_b
75 75 hg status in b/2:
76 76 ? a/1/in_a_1
77 77 ? a/in_a
78 78 ? b/1/in_b_1
79 79 ? b/2/in_b_2
80 80 ? b/in_b
81 81 ? in_root
82 82 hg status . in b/2:
83 83 ? in_b_2
84 84 hg status .. in b/2:
85 85 ? ../1/in_b_1
86 86 ? in_b_2
87 87 ? ../in_b
88 88 adding .hgignore
89 89 adding deleted
90 90 adding modified
91 91 adding removed
92 92 hg status:
93 93 A added
94 94 R removed
95 95 ! deleted
96 96 ? unknown
97 97 hg status modified added removed deleted unknown never-existed ignored:
98 98 never-existed: No such file or directory
99 99 A added
100 100 R removed
101 101 ! deleted
102 102 ? unknown
103 103 hg status -C:
104 104 A added
105 105 A copied
106 106  modified
107 107 R removed
108 108 ! deleted
109 109 ? unknown
110 110 hg status -A:
111 111 A added
112 112 A copied
113 113  modified
114 114 R removed
115 115 ! deleted
116 116 ? unknown
117 117 I ignored
118 118 C .hgignore
119 119 C modified
120 120 hg status ignoreddir/file:
121 121 hg status -i ignoreddir/file:
122 122 I ignoreddir/file
123 123 adding .hgignore
124 124 adding deleted
125 125 adding modified
126 126 adding removed
127 127 % test unknown color
128 128 ignoring unknown color/effect 'periwinkle' (configured in color.status.modified)
129 129 M modified
130 130 A added
131 131 A copied
132 132 R removed
133 133 ! deleted
134 134 ? unknown
135 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
136 created new head
137 merging a
138 warning: conflicts during merge.
139 merging a failed!
140 merging b
141 warning: conflicts during merge.
142 merging b failed!
143 0 files updated, 0 files merged, 0 files removed, 2 files unresolved
144 use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon
145 hg resolve with one unresolved, one resolved:
146 U a
147 R b
General Comments 0
You need to be logged in to leave comments. Login now