mercurial/patch.py
changeset 35277 6ba79cf34f5e
parent 35191 a1d2fc32bb99
child 35310 10cce12fdcd3
equal deleted inserted replaced
35276:205c3c6c1a51 35277:6ba79cf34f5e
     8 
     8 
     9 from __future__ import absolute_import, print_function
     9 from __future__ import absolute_import, print_function
    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
    16 import os
    17 import os
    17 import posixpath
    18 import posixpath
  2250     buildopts = {
  2251     buildopts = {
  2251         'nodates': get('nodates'),
  2252         'nodates': get('nodates'),
  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')
  2258 
  2260 
  2259         # since this is in the experimental section, we need to call
  2261         # since this is in the experimental section, we need to call
  2461     else:
  2463     else:
  2462         return difffn(opts, None)
  2464         return difffn(opts, None)
  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'),
  2469                     ('old', 'diff.extended'),
  2474                     ('old', 'diff.extended'),
  2470                     ('new', 'diff.extended'),
  2475                     ('new', 'diff.extended'),
  2477                     ('-', 'diff.deleted'),
  2482                     ('-', 'diff.deleted'),
  2478                     ('+', 'diff.inserted')]
  2483                     ('+', 'diff.inserted')]
  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', '')
  2485             if head:
  2493             if head:
  2486                 if line.startswith('@'):
  2494                 if line.startswith('@'):
  2504                     if diffline:
  2512                     if diffline:
  2505                         for token in tabsplitter.findall(stripline):
  2513                         for token in tabsplitter.findall(stripline):
  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
  2513             else:
  2528             else:
  2514                 yield (line, '')
  2529                 yield (line, '')
  2515             if line != stripline:
  2530             if line != stripline:
  2516                 yield (line[len(stripline):], 'diff.trailingwhitespace')
  2531                 yield (line[len(stripline):], 'diff.trailingwhitespace')
       
  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
  2517 
  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)
  2521 
  2600