##// END OF EJS Templates
patch: add within-line color diff capacity...
Matthieu Laneuville -
r35278:6ba79cf3 default
parent child Browse files
Show More
@@ -1518,7 +1518,7 b' def diffordiffstat(ui, repo, diffopts, n'
1518 width = 80
1518 width = 80
1519 if not ui.plain():
1519 if not ui.plain():
1520 width = ui.termwidth()
1520 width = ui.termwidth()
1521 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
1521 chunks = patch.diff(repo, node1, node2, match, changes, opts=diffopts,
1522 prefix=prefix, relroot=relroot,
1522 prefix=prefix, relroot=relroot,
1523 hunksfilterfn=hunksfilterfn)
1523 hunksfilterfn=hunksfilterfn)
1524 for chunk, label in patch.diffstatui(util.iterlines(chunks),
1524 for chunk, label in patch.diffstatui(util.iterlines(chunks),
@@ -1526,7 +1526,7 b' def diffordiffstat(ui, repo, diffopts, n'
1526 write(chunk, label=label)
1526 write(chunk, label=label)
1527 else:
1527 else:
1528 for chunk, label in patch.diffui(repo, node1, node2, match,
1528 for chunk, label in patch.diffui(repo, node1, node2, match,
1529 changes, diffopts, prefix=prefix,
1529 changes, opts=diffopts, prefix=prefix,
1530 relroot=relroot,
1530 relroot=relroot,
1531 hunksfilterfn=hunksfilterfn):
1531 hunksfilterfn=hunksfilterfn):
1532 write(chunk, label=label)
1532 write(chunk, label=label)
@@ -87,12 +87,14 b' except ImportError:'
87 'branches.inactive': 'none',
87 'branches.inactive': 'none',
88 'diff.changed': 'white',
88 'diff.changed': 'white',
89 'diff.deleted': 'red',
89 'diff.deleted': 'red',
90 'diff.deleted.highlight': 'red bold underline',
90 'diff.diffline': 'bold',
91 'diff.diffline': 'bold',
91 'diff.extended': 'cyan bold',
92 'diff.extended': 'cyan bold',
92 'diff.file_a': 'red bold',
93 'diff.file_a': 'red bold',
93 'diff.file_b': 'green bold',
94 'diff.file_b': 'green bold',
94 'diff.hunk': 'magenta',
95 'diff.hunk': 'magenta',
95 'diff.inserted': 'green',
96 'diff.inserted': 'green',
97 'diff.inserted.highlight': 'green bold underline',
96 'diff.tab': '',
98 'diff.tab': '',
97 'diff.trailingwhitespace': 'bold red_background',
99 'diff.trailingwhitespace': 'bold red_background',
98 'changeset.public': '',
100 'changeset.public': '',
@@ -481,6 +481,9 b" coreconfigitem('experimental', 'evolutio"
481 coreconfigitem('experimental', 'evolution.track-operation',
481 coreconfigitem('experimental', 'evolution.track-operation',
482 default=True,
482 default=True,
483 )
483 )
484 coreconfigitem('experimental', 'worddiff',
485 default=False,
486 )
484 coreconfigitem('experimental', 'maxdeltachainspan',
487 coreconfigitem('experimental', 'maxdeltachainspan',
485 default=-1,
488 default=-1,
486 )
489 )
@@ -67,6 +67,7 b' class diffopts(object):'
67 'ignoreblanklines': False,
67 'ignoreblanklines': False,
68 'upgrade': False,
68 'upgrade': False,
69 'showsimilarity': False,
69 'showsimilarity': False,
70 'worddiff': False,
70 }
71 }
71
72
72 def __init__(self, **opts):
73 def __init__(self, **opts):
@@ -10,6 +10,7 b' from __future__ import absolute_import, '
10
10
11 import collections
11 import collections
12 import copy
12 import copy
13 import difflib
13 import email
14 import email
14 import errno
15 import errno
15 import hashlib
16 import hashlib
@@ -2252,6 +2253,7 b' def difffeatureopts(ui, opts=None, untru'
2252 'showfunc': get('show_function', 'showfunc'),
2253 'showfunc': get('show_function', 'showfunc'),
2253 'context': get('unified', getter=ui.config),
2254 'context': get('unified', getter=ui.config),
2254 }
2255 }
2256 buildopts['worddiff'] = ui.configbool('experimental', 'worddiff')
2255
2257
2256 if git:
2258 if git:
2257 buildopts['git'] = get('git')
2259 buildopts['git'] = get('git')
@@ -2463,6 +2465,9 b' def diffhunks(repo, node1=None, node2=No'
2463
2465
2464 def difflabel(func, *args, **kw):
2466 def difflabel(func, *args, **kw):
2465 '''yields 2-tuples of (output, label) based on the output of func()'''
2467 '''yields 2-tuples of (output, label) based on the output of func()'''
2468 inlinecolor = False
2469 if kw.get('opts'):
2470 inlinecolor = kw['opts'].worddiff
2466 headprefixes = [('diff', 'diff.diffline'),
2471 headprefixes = [('diff', 'diff.diffline'),
2467 ('copy', 'diff.extended'),
2472 ('copy', 'diff.extended'),
2468 ('rename', 'diff.extended'),
2473 ('rename', 'diff.extended'),
@@ -2479,6 +2484,9 b' def difflabel(func, *args, **kw):'
2479 head = False
2484 head = False
2480 for chunk in func(*args, **kw):
2485 for chunk in func(*args, **kw):
2481 lines = chunk.split('\n')
2486 lines = chunk.split('\n')
2487 matches = {}
2488 if inlinecolor:
2489 matches = _findmatches(lines)
2482 for i, line in enumerate(lines):
2490 for i, line in enumerate(lines):
2483 if i != 0:
2491 if i != 0:
2484 yield ('\n', '')
2492 yield ('\n', '')
@@ -2506,7 +2514,14 b' def difflabel(func, *args, **kw):'
2506 if '\t' == token[0]:
2514 if '\t' == token[0]:
2507 yield (token, 'diff.tab')
2515 yield (token, 'diff.tab')
2508 else:
2516 else:
2509 yield (token, label)
2517 if i in matches:
2518 for l, t in _inlinediff(
2519 lines[i].rstrip(),
2520 lines[matches[i]].rstrip(),
2521 label):
2522 yield (t, l)
2523 else:
2524 yield (token, label)
2510 else:
2525 else:
2511 yield (stripline, label)
2526 yield (stripline, label)
2512 break
2527 break
@@ -2515,6 +2530,70 b' def difflabel(func, *args, **kw):'
2515 if line != stripline:
2530 if line != stripline:
2516 yield (line[len(stripline):], 'diff.trailingwhitespace')
2531 yield (line[len(stripline):], 'diff.trailingwhitespace')
2517
2532
2533 def _findmatches(slist):
2534 '''Look for insertion matches to deletion and returns a dict of
2535 correspondences.
2536 '''
2537 lastmatch = 0
2538 matches = {}
2539 for i, line in enumerate(slist):
2540 if line == '':
2541 continue
2542 if line[0] == '-':
2543 lastmatch = max(lastmatch, i)
2544 newgroup = False
2545 for j, newline in enumerate(slist[lastmatch + 1:]):
2546 if newline == '':
2547 continue
2548 if newline[0] == '-' and newgroup: # too far, no match
2549 break
2550 if newline[0] == '+': # potential match
2551 newgroup = True
2552 sim = difflib.SequenceMatcher(None, line, newline).ratio()
2553 if sim > 0.7:
2554 lastmatch = lastmatch + 1 + j
2555 matches[i] = lastmatch
2556 matches[lastmatch] = i
2557 break
2558 return matches
2559
2560 def _inlinediff(s1, s2, operation):
2561 '''Perform string diff to highlight specific changes.'''
2562 operation_skip = '+?' if operation == 'diff.deleted' else '-?'
2563 if operation == 'diff.deleted':
2564 s2, s1 = s1, s2
2565
2566 buff = []
2567 # we never want to higlight the leading +-
2568 if operation == 'diff.deleted' and s2.startswith('-'):
2569 label = operation
2570 token = '-'
2571 s2 = s2[1:]
2572 s1 = s1[1:]
2573 elif operation == 'diff.inserted' and s1.startswith('+'):
2574 label = operation
2575 token = '+'
2576 s2 = s2[1:]
2577 s1 = s1[1:]
2578
2579 s = difflib.ndiff(re.split(br'(\W)', s2), re.split(br'(\W)', s1))
2580 for part in s:
2581 if part[0] in operation_skip:
2582 continue
2583 l = operation + '.highlight'
2584 if part[0] in ' ':
2585 l = operation
2586 if l == label: # contiguous token with same label
2587 token += part[2:]
2588 continue
2589 else:
2590 buff.append((label, token))
2591 label = l
2592 token = part[2:]
2593 buff.append((label, token))
2594
2595 return buff
2596
2518 def diffui(*args, **kw):
2597 def diffui(*args, **kw):
2519 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2598 '''like diff(), but yields 2-tuples of (output, label) for ui.write()'''
2520 return difflabel(diff, *args, **kw)
2599 return difflabel(diff, *args, **kw)
@@ -259,3 +259,95 b' test tabs'
259 \x1b[0;32m+\x1b[0m\x1b[0;1;35m \x1b[0m\x1b[0;32mall\x1b[0m\x1b[0;1;35m \x1b[0m\x1b[0;32mtabs\x1b[0m\x1b[0;1;41m \x1b[0m (esc)
259 \x1b[0;32m+\x1b[0m\x1b[0;1;35m \x1b[0m\x1b[0;32mall\x1b[0m\x1b[0;1;35m \x1b[0m\x1b[0;32mtabs\x1b[0m\x1b[0;1;41m \x1b[0m (esc)
260
260
261 $ cd ..
261 $ cd ..
262
263 test inline color diff
264
265 $ hg init inline
266 $ cd inline
267 $ cat > file1 << EOF
268 > this is the first line
269 > this is the second line
270 > third line starts with space
271 > + starts with a plus sign
272 >
273 > this line won't change
274 >
275 > two lines are going to
276 > be changed into three!
277 >
278 > three of those lines will
279 > collapse onto one
280 > (to see if it works)
281 > EOF
282 $ hg add file1
283 $ hg ci -m 'commit'
284 $ cat > file1 << EOF
285 > that is the first paragraph
286 > this is the second line
287 > third line starts with space
288 > - starts with a minus sign
289 >
290 > this line won't change
291 >
292 > two lines are going to
293 > (entirely magically,
294 > assuming this works)
295 > be changed into four!
296 >
297 > three of those lines have
298 > collapsed onto one
299 > EOF
300 $ hg diff --config experimental.worddiff=False --color=debug
301 [diff.diffline|diff --git a/file1 b/file1]
302 [diff.file_a|--- a/file1]
303 [diff.file_b|+++ b/file1]
304 [diff.hunk|@@ -1,13 +1,14 @@]
305 [diff.deleted|-this is the first line]
306 [diff.deleted|-this is the second line]
307 [diff.deleted|- third line starts with space]
308 [diff.deleted|-+ starts with a plus sign]
309 [diff.inserted|+that is the first paragraph]
310 [diff.inserted|+ this is the second line]
311 [diff.inserted|+third line starts with space]
312 [diff.inserted|+- starts with a minus sign]
313
314 this line won't change
315
316 two lines are going to
317 [diff.deleted|-be changed into three!]
318 [diff.inserted|+(entirely magically,]
319 [diff.inserted|+ assuming this works)]
320 [diff.inserted|+be changed into four!]
321
322 [diff.deleted|-three of those lines will]
323 [diff.deleted|-collapse onto one]
324 [diff.deleted|-(to see if it works)]
325 [diff.inserted|+three of those lines have]
326 [diff.inserted|+collapsed onto one]
327 $ hg diff --config experimental.worddiff=True --color=debug
328 [diff.diffline|diff --git a/file1 b/file1]
329 [diff.file_a|--- a/file1]
330 [diff.file_b|+++ b/file1]
331 [diff.hunk|@@ -1,13 +1,14 @@]
332 [diff.deleted|-this is the ][diff.deleted.highlight|first][diff.deleted| line]
333 [diff.deleted|-this is the second line]
334 [diff.deleted|-][diff.deleted.highlight| ][diff.deleted|third line starts with space]
335 [diff.deleted|-][diff.deleted.highlight|+][diff.deleted| starts with a ][diff.deleted.highlight|plus][diff.deleted| sign]
336 [diff.inserted|+that is the first paragraph]
337 [diff.inserted|+][diff.inserted.highlight| ][diff.inserted|this is the ][diff.inserted.highlight|second][diff.inserted| line]
338 [diff.inserted|+third line starts with space]
339 [diff.inserted|+][diff.inserted.highlight|-][diff.inserted| starts with a ][diff.inserted.highlight|minus][diff.inserted| sign]
340
341 this line won't change
342
343 two lines are going to
344 [diff.deleted|-be changed into ][diff.deleted.highlight|three][diff.deleted|!]
345 [diff.inserted|+(entirely magically,]
346 [diff.inserted|+ assuming this works)]
347 [diff.inserted|+be changed into ][diff.inserted.highlight|four][diff.inserted|!]
348
349 [diff.deleted|-three of those lines ][diff.deleted.highlight|will]
350 [diff.deleted|-][diff.deleted.highlight|collapse][diff.deleted| onto one]
351 [diff.deleted|-(to see if it works)]
352 [diff.inserted|+three of those lines ][diff.inserted.highlight|have]
353 [diff.inserted|+][diff.inserted.highlight|collapsed][diff.inserted| onto one]
General Comments 0
You need to be logged in to leave comments. Login now