2005-06-04 01:44:34 +04:00
|
|
|
# mdiff.py - diff and patch routines for mercurial
|
|
|
|
#
|
2006-08-12 23:30:02 +04:00
|
|
|
# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
|
2005-06-04 01:44:34 +04:00
|
|
|
#
|
2009-04-26 03:08:54 +04:00
|
|
|
# This software may be used and distributed according to the terms of the
|
2010-01-20 07:20:08 +03:00
|
|
|
# GNU General Public License version 2 or any later version.
|
2005-06-04 01:44:34 +04:00
|
|
|
|
2015-12-22 08:26:14 +03:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
import re
|
|
|
|
import struct
|
|
|
|
import zlib
|
|
|
|
|
|
|
|
from .i18n import _
|
|
|
|
from . import (
|
|
|
|
error,
|
2016-08-13 06:12:50 +03:00
|
|
|
policy,
|
2017-03-26 18:28:21 +03:00
|
|
|
pycompat,
|
2015-12-22 08:26:14 +03:00
|
|
|
util,
|
|
|
|
)
|
2005-05-04 01:16:10 +04:00
|
|
|
|
2016-08-13 06:12:50 +03:00
|
|
|
bdiff = policy.importmod(r'bdiff')
|
2016-08-13 06:18:58 +03:00
|
|
|
mpatch = policy.importmod(r'mpatch')
|
2016-08-13 06:12:50 +03:00
|
|
|
|
2017-04-26 16:03:37 +03:00
|
|
|
blocks = bdiff.blocks
|
|
|
|
fixws = bdiff.fixws
|
2017-05-02 11:05:22 +03:00
|
|
|
patches = mpatch.patches
|
|
|
|
patchedsize = mpatch.patchedsize
|
|
|
|
textdiff = bdiff.bdiff
|
|
|
|
|
flake8: enable F821 check
Summary:
This check is useful and detects real errors (ex. fbconduit). Unfortunately
`arc lint` will run it with both py2 and py3 so a lot of py2 builtins will
still be warned.
I didn't find a clean way to disable py3 check. So this diff tries to fix them.
For `xrange`, the change was done by a script:
```
import sys
import redbaron
headertypes = {'comment', 'endl', 'from_import', 'import', 'string',
'assignment', 'atomtrailers'}
xrangefix = '''try:
xrange(0)
except NameError:
xrange = range
'''
def isxrange(x):
try:
return x[0].value == 'xrange'
except Exception:
return False
def main(argv):
for i, path in enumerate(argv):
print('(%d/%d) scanning %s' % (i + 1, len(argv), path))
content = open(path).read()
try:
red = redbaron.RedBaron(content)
except Exception:
print(' warning: failed to parse')
continue
hasxrange = red.find('atomtrailersnode', value=isxrange)
hasxrangefix = 'xrange = range' in content
if hasxrangefix or not hasxrange:
print(' no need to change')
continue
# find a place to insert the compatibility statement
changed = False
for node in red:
if node.type in headertypes:
continue
# node.insert_before is an easier API, but it has bugs changing
# other "finally" and "except" positions. So do the insert
# manually.
# # node.insert_before(xrangefix)
line = node.absolute_bounding_box.top_left.line - 1
lines = content.splitlines(1)
content = ''.join(lines[:line]) + xrangefix + ''.join(lines[line:])
changed = True
break
if changed:
# "content" is faster than "red.dumps()"
open(path, 'w').write(content)
print(' updated')
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
```
For other py2 builtins that do not have a py3 equivalent, some `# noqa`
were added as a workaround for now.
Reviewed By: DurhamG
Differential Revision: D6934535
fbshipit-source-id: 546b62830af144bc8b46788d2e0fd00496838939
2018-02-10 04:31:44 +03:00
|
|
|
try:
|
|
|
|
xrange(0)
|
|
|
|
except NameError:
|
|
|
|
xrange = range
|
|
|
|
|
2006-05-11 00:39:12 +04:00
|
|
|
def splitnewlines(text):
|
2006-05-10 21:31:54 +04:00
|
|
|
'''like str.splitlines, but only split on newlines.'''
|
2006-05-11 00:39:12 +04:00
|
|
|
lines = [l + '\n' for l in text.split('\n')]
|
|
|
|
if lines:
|
|
|
|
if lines[-1] == '\n':
|
|
|
|
lines.pop()
|
|
|
|
else:
|
|
|
|
lines[-1] = lines[-1][:-1]
|
|
|
|
return lines
|
2006-05-10 21:31:54 +04:00
|
|
|
|
2006-08-13 03:13:27 +04:00
|
|
|
class diffopts(object):
|
|
|
|
'''context is the number of context lines
|
|
|
|
text treats all files as text
|
|
|
|
showfunc enables diff -p output
|
2006-08-15 09:48:03 +04:00
|
|
|
git enables the git extended patch format
|
2006-09-26 03:05:24 +04:00
|
|
|
nodates removes dates from diff headers
|
2014-11-13 10:19:44 +03:00
|
|
|
nobinary ignores binary files
|
2014-11-13 10:25:32 +03:00
|
|
|
noprefix disables the 'a/' and 'b/' prefixes (ignored in plain mode)
|
2006-08-13 03:13:27 +04:00
|
|
|
ignorews ignores all whitespace changes in the diff
|
|
|
|
ignorewsamount ignores changes in the amount of whitespace
|
2010-01-01 22:54:05 +03:00
|
|
|
ignoreblanklines ignores changes whose lines are all blank
|
|
|
|
upgrade generates git diffs to avoid data loss
|
|
|
|
'''
|
2006-08-13 03:13:27 +04:00
|
|
|
|
|
|
|
defaults = {
|
|
|
|
'context': 3,
|
|
|
|
'text': False,
|
2008-01-16 20:14:24 +03:00
|
|
|
'showfunc': False,
|
2006-08-15 09:48:03 +04:00
|
|
|
'git': False,
|
2006-09-26 03:05:24 +04:00
|
|
|
'nodates': False,
|
2014-06-21 09:56:49 +04:00
|
|
|
'nobinary': False,
|
2014-11-13 10:25:32 +03:00
|
|
|
'noprefix': False,
|
2017-01-09 22:13:47 +03:00
|
|
|
'index': 0,
|
2006-08-13 03:13:27 +04:00
|
|
|
'ignorews': False,
|
|
|
|
'ignorewsamount': False,
|
2017-08-30 04:20:50 +03:00
|
|
|
'ignorewseol': False,
|
2006-08-13 03:13:27 +04:00
|
|
|
'ignoreblanklines': False,
|
2010-01-01 22:54:05 +03:00
|
|
|
'upgrade': False,
|
2017-01-09 21:51:44 +03:00
|
|
|
'showsimilarity': False,
|
2017-10-25 18:13:38 +03:00
|
|
|
'worddiff': False,
|
2006-08-13 03:13:27 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
def __init__(self, **opts):
|
2017-03-26 18:28:21 +03:00
|
|
|
opts = pycompat.byteskwargs(opts)
|
2016-06-25 23:52:46 +03:00
|
|
|
for k in self.defaults.keys():
|
2006-08-13 03:13:27 +04:00
|
|
|
v = opts.get(k)
|
|
|
|
if v is None:
|
|
|
|
v = self.defaults[k]
|
|
|
|
setattr(self, k, v)
|
|
|
|
|
2008-04-05 00:15:14 +04:00
|
|
|
try:
|
|
|
|
self.context = int(self.context)
|
|
|
|
except ValueError:
|
2015-10-08 22:55:45 +03:00
|
|
|
raise error.Abort(_('diff context lines count must be '
|
2008-04-05 00:15:14 +04:00
|
|
|
'an integer, not %r') % self.context)
|
|
|
|
|
2010-01-01 21:53:05 +03:00
|
|
|
def copy(self, **kwargs):
|
|
|
|
opts = dict((k, getattr(self, k)) for k in self.defaults)
|
2017-06-26 21:53:32 +03:00
|
|
|
opts = pycompat.strkwargs(opts)
|
2010-01-01 21:53:05 +03:00
|
|
|
opts.update(kwargs)
|
|
|
|
return diffopts(**opts)
|
|
|
|
|
2006-08-13 03:13:27 +04:00
|
|
|
defaultopts = diffopts()
|
|
|
|
|
2009-11-11 20:31:42 +03:00
|
|
|
def wsclean(opts, text, blank=True):
|
2007-07-14 21:44:47 +04:00
|
|
|
if opts.ignorews:
|
2011-11-18 17:23:03 +04:00
|
|
|
text = bdiff.fixws(text, 1)
|
2007-07-14 21:44:47 +04:00
|
|
|
elif opts.ignorewsamount:
|
2011-11-18 17:23:03 +04:00
|
|
|
text = bdiff.fixws(text, 0)
|
2009-11-11 20:31:42 +03:00
|
|
|
if blank and opts.ignoreblanklines:
|
2011-11-14 00:37:14 +04:00
|
|
|
text = re.sub('\n+', '\n', text).strip('\n')
|
2017-08-30 04:20:50 +03:00
|
|
|
if opts.ignorewseol:
|
|
|
|
text = re.sub(r'[ \t\r\f]+\n', r'\n', text)
|
2007-07-14 21:44:47 +04:00
|
|
|
return text
|
|
|
|
|
2011-11-18 15:04:31 +04:00
|
|
|
def splitblock(base1, lines1, base2, lines2, opts):
|
|
|
|
# The input lines matches except for interwoven blank lines. We
|
|
|
|
# transform it into a sequence of matching blocks and blank blocks.
|
|
|
|
lines1 = [(wsclean(opts, l) and 1 or 0) for l in lines1]
|
|
|
|
lines2 = [(wsclean(opts, l) and 1 or 0) for l in lines2]
|
|
|
|
s1, e1 = 0, len(lines1)
|
|
|
|
s2, e2 = 0, len(lines2)
|
|
|
|
while s1 < e1 or s2 < e2:
|
|
|
|
i1, i2, btype = s1, s2, '='
|
|
|
|
if (i1 >= e1 or lines1[i1] == 0
|
|
|
|
or i2 >= e2 or lines2[i2] == 0):
|
|
|
|
# Consume the block of blank lines
|
|
|
|
btype = '~'
|
|
|
|
while i1 < e1 and lines1[i1] == 0:
|
|
|
|
i1 += 1
|
|
|
|
while i2 < e2 and lines2[i2] == 0:
|
|
|
|
i2 += 1
|
|
|
|
else:
|
|
|
|
# Consume the matching lines
|
|
|
|
while i1 < e1 and lines1[i1] == 1 and lines2[i2] == 1:
|
|
|
|
i1 += 1
|
|
|
|
i2 += 1
|
|
|
|
yield [base1 + s1, base1 + i1, base2 + s2, base2 + i2], btype
|
|
|
|
s1 = i1
|
|
|
|
s2 = i2
|
|
|
|
|
2017-04-01 13:24:59 +03:00
|
|
|
def hunkinrange(hunk, linerange):
|
|
|
|
"""Return True if `hunk` defined as (start, length) is in `linerange`
|
|
|
|
defined as (lowerbound, upperbound).
|
|
|
|
|
|
|
|
>>> hunkinrange((5, 10), (2, 7))
|
|
|
|
True
|
|
|
|
>>> hunkinrange((5, 10), (6, 12))
|
|
|
|
True
|
|
|
|
>>> hunkinrange((5, 10), (13, 17))
|
|
|
|
True
|
|
|
|
>>> hunkinrange((5, 10), (3, 17))
|
|
|
|
True
|
|
|
|
>>> hunkinrange((5, 10), (1, 3))
|
|
|
|
False
|
|
|
|
>>> hunkinrange((5, 10), (18, 20))
|
|
|
|
False
|
|
|
|
>>> hunkinrange((5, 10), (1, 5))
|
|
|
|
False
|
|
|
|
>>> hunkinrange((5, 10), (15, 27))
|
|
|
|
False
|
|
|
|
"""
|
|
|
|
start, length = hunk
|
|
|
|
lowerbound, upperbound = linerange
|
|
|
|
return lowerbound < start + length and start < upperbound
|
|
|
|
|
2017-01-03 20:15:58 +03:00
|
|
|
def blocksinrange(blocks, rangeb):
|
|
|
|
"""filter `blocks` like (a1, a2, b1, b2) from items outside line range
|
|
|
|
`rangeb` from ``(b1, b2)`` point of view.
|
|
|
|
|
|
|
|
Return `filteredblocks, rangea` where:
|
|
|
|
|
|
|
|
* `filteredblocks` is list of ``block = (a1, a2, b1, b2), stype`` items of
|
|
|
|
`blocks` that are inside `rangeb` from ``(b1, b2)`` point of view; a
|
|
|
|
block ``(b1, b2)`` being inside `rangeb` if
|
|
|
|
``rangeb[0] < b2 and b1 < rangeb[1]``;
|
|
|
|
* `rangea` is the line range w.r.t. to ``(a1, a2)`` parts of `blocks`.
|
|
|
|
"""
|
|
|
|
lbb, ubb = rangeb
|
|
|
|
lba, uba = None, None
|
|
|
|
filteredblocks = []
|
|
|
|
for block in blocks:
|
|
|
|
(a1, a2, b1, b2), stype = block
|
|
|
|
if lbb >= b1 and ubb <= b2 and stype == '=':
|
|
|
|
# rangeb is within a single "=" hunk, restrict back linerange1
|
|
|
|
# by offsetting rangeb
|
|
|
|
lba = lbb - b1 + a1
|
|
|
|
uba = ubb - b1 + a1
|
|
|
|
else:
|
|
|
|
if b1 <= lbb < b2:
|
|
|
|
if stype == '=':
|
|
|
|
lba = a2 - (b2 - lbb)
|
|
|
|
else:
|
|
|
|
lba = a1
|
|
|
|
if b1 < ubb <= b2:
|
|
|
|
if stype == '=':
|
|
|
|
uba = a1 + (ubb - b1)
|
|
|
|
else:
|
|
|
|
uba = a2
|
2017-04-01 13:24:59 +03:00
|
|
|
if hunkinrange((b1, (b2 - b1)), rangeb):
|
2017-01-03 20:15:58 +03:00
|
|
|
filteredblocks.append(block)
|
|
|
|
if lba is None or uba is None or uba < lba:
|
|
|
|
raise error.Abort(_('line range exceeds file size'))
|
|
|
|
return filteredblocks, (lba, uba)
|
|
|
|
|
2016-09-27 15:46:34 +03:00
|
|
|
def allblocks(text1, text2, opts=None, lines1=None, lines2=None):
|
2011-11-18 15:01:04 +04:00
|
|
|
"""Return (block, type) tuples, where block is an mdiff.blocks
|
|
|
|
line entry. type is '=' for blocks matching exactly one another
|
|
|
|
(bdiff blocks), '!' for non-matching blocks and '~' for blocks
|
2016-09-27 15:46:34 +03:00
|
|
|
matching only after having filtered blank lines.
|
2011-11-18 15:01:04 +04:00
|
|
|
line1 and line2 are text1 and text2 split with splitnewlines() if
|
|
|
|
they are already available.
|
2011-11-18 14:53:38 +04:00
|
|
|
"""
|
|
|
|
if opts is None:
|
|
|
|
opts = defaultopts
|
2017-08-30 04:20:50 +03:00
|
|
|
if opts.ignorews or opts.ignorewsamount or opts.ignorewseol:
|
2011-11-18 14:53:38 +04:00
|
|
|
text1 = wsclean(opts, text1, False)
|
|
|
|
text2 = wsclean(opts, text2, False)
|
|
|
|
diff = bdiff.blocks(text1, text2)
|
|
|
|
for i, s1 in enumerate(diff):
|
|
|
|
# The first match is special.
|
|
|
|
# we've either found a match starting at line 0 or a match later
|
|
|
|
# in the file. If it starts later, old and new below will both be
|
|
|
|
# empty and we'll continue to the next match.
|
|
|
|
if i > 0:
|
|
|
|
s = diff[i - 1]
|
|
|
|
else:
|
|
|
|
s = [0, 0, 0, 0]
|
|
|
|
s = [s[1], s1[0], s[3], s1[2]]
|
|
|
|
|
|
|
|
# bdiff sometimes gives huge matches past eof, this check eats them,
|
|
|
|
# and deals with the special first match case described above
|
2011-11-18 17:16:47 +04:00
|
|
|
if s[0] != s[1] or s[2] != s[3]:
|
2011-11-18 15:01:04 +04:00
|
|
|
type = '!'
|
|
|
|
if opts.ignoreblanklines:
|
2011-11-18 17:16:47 +04:00
|
|
|
if lines1 is None:
|
|
|
|
lines1 = splitnewlines(text1)
|
|
|
|
if lines2 is None:
|
|
|
|
lines2 = splitnewlines(text2)
|
|
|
|
old = wsclean(opts, "".join(lines1[s[0]:s[1]]))
|
|
|
|
new = wsclean(opts, "".join(lines2[s[2]:s[3]]))
|
|
|
|
if old == new:
|
2011-11-18 15:01:04 +04:00
|
|
|
type = '~'
|
|
|
|
yield s, type
|
|
|
|
yield s1, '='
|
2011-11-18 14:53:38 +04:00
|
|
|
|
2018-02-12 17:09:25 +03:00
|
|
|
def unidiff(a, ad, b, bd, fn1, fn2, opts=defaultopts, check_binary=True):
|
2017-03-03 19:46:40 +03:00
|
|
|
"""Return a unified diff as a (headers, hunks) tuple.
|
2017-03-03 15:51:22 +03:00
|
|
|
|
|
|
|
If the diff is not null, `headers` is a list with unified diff header
|
2017-03-03 19:46:40 +03:00
|
|
|
lines "--- <original>" and "+++ <new>" and `hunks` is a generator yielding
|
|
|
|
(hunkrange, hunklines) coming from _unidiff().
|
|
|
|
Otherwise, `headers` and `hunks` are empty.
|
2018-02-12 17:09:25 +03:00
|
|
|
|
|
|
|
Setting `check_binary` to false will skip the binary check, i.e. when
|
|
|
|
it has been done in advance. Files are expected to be text in this case.
|
2017-03-03 15:51:22 +03:00
|
|
|
"""
|
2012-04-05 17:39:07 +04:00
|
|
|
def datetag(date, fn=None):
|
2007-06-23 02:06:04 +04:00
|
|
|
if not opts.git and not opts.nodates:
|
2017-03-03 15:51:22 +03:00
|
|
|
return '\t%s' % date
|
2012-04-05 17:39:07 +04:00
|
|
|
if fn and ' ' in fn:
|
2017-03-03 15:51:22 +03:00
|
|
|
return '\t'
|
|
|
|
return ''
|
2006-08-30 04:08:42 +04:00
|
|
|
|
2017-03-03 19:46:40 +03:00
|
|
|
sentinel = [], ()
|
2010-01-25 09:05:27 +03:00
|
|
|
if not a and not b:
|
2017-03-03 15:51:22 +03:00
|
|
|
return sentinel
|
2014-11-13 10:29:14 +03:00
|
|
|
|
|
|
|
if opts.noprefix:
|
|
|
|
aprefix = bprefix = ''
|
|
|
|
else:
|
|
|
|
aprefix = 'a/'
|
|
|
|
bprefix = 'b/'
|
|
|
|
|
2005-10-04 22:25:48 +04:00
|
|
|
epoch = util.datestr((0, 0))
|
2005-06-06 22:51:09 +04:00
|
|
|
|
2011-11-07 05:49:00 +04:00
|
|
|
fn1 = util.pconvert(fn1)
|
|
|
|
fn2 = util.pconvert(fn2)
|
|
|
|
|
2017-03-03 19:46:28 +03:00
|
|
|
def checknonewline(lines):
|
|
|
|
for text in lines:
|
2017-03-26 18:22:51 +03:00
|
|
|
if text[-1:] != '\n':
|
2017-03-03 19:46:28 +03:00
|
|
|
text += "\n\ No newline at end of file\n"
|
|
|
|
yield text
|
|
|
|
|
2018-02-12 17:09:25 +03:00
|
|
|
if not opts.text and check_binary and (util.binary(a) or util.binary(b)):
|
2008-08-09 04:10:22 +04:00
|
|
|
if a and b and len(a) == len(b) and a == b:
|
2017-03-03 15:51:22 +03:00
|
|
|
return sentinel
|
|
|
|
headerlines = []
|
2017-03-03 19:46:40 +03:00
|
|
|
hunks = (None, ['Binary file %s has changed\n' % fn1]),
|
2006-02-15 16:22:16 +03:00
|
|
|
elif not a:
|
2006-05-11 00:39:12 +04:00
|
|
|
b = splitnewlines(b)
|
2006-02-15 16:22:16 +03:00
|
|
|
if a is None:
|
2012-04-05 17:39:07 +04:00
|
|
|
l1 = '--- /dev/null%s' % datetag(epoch)
|
2006-02-15 16:22:16 +03:00
|
|
|
else:
|
2014-11-13 10:29:14 +03:00
|
|
|
l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
|
|
|
|
l2 = "+++ %s%s" % (bprefix + fn2, datetag(bd, fn2))
|
2017-03-03 15:51:22 +03:00
|
|
|
headerlines = [l1, l2]
|
2017-03-03 19:46:40 +03:00
|
|
|
size = len(b)
|
|
|
|
hunkrange = (0, 0, 1, size)
|
|
|
|
hunklines = ["@@ -0,0 +1,%d @@\n" % size] + ["+" + e for e in b]
|
|
|
|
hunks = (hunkrange, checknonewline(hunklines)),
|
2006-02-15 16:22:16 +03:00
|
|
|
elif not b:
|
2006-05-11 00:39:12 +04:00
|
|
|
a = splitnewlines(a)
|
2014-11-13 10:29:14 +03:00
|
|
|
l1 = "--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1))
|
2006-02-15 16:22:16 +03:00
|
|
|
if b is None:
|
2012-04-05 17:39:07 +04:00
|
|
|
l2 = '+++ /dev/null%s' % datetag(epoch)
|
2006-02-15 16:22:16 +03:00
|
|
|
else:
|
2014-11-13 10:29:14 +03:00
|
|
|
l2 = "+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2))
|
2017-03-03 15:51:22 +03:00
|
|
|
headerlines = [l1, l2]
|
2017-03-03 19:46:40 +03:00
|
|
|
size = len(a)
|
|
|
|
hunkrange = (1, size, 0, 0)
|
|
|
|
hunklines = ["@@ -1,%d +0,0 @@\n" % size] + ["-" + e for e in a]
|
|
|
|
hunks = (hunkrange, checknonewline(hunklines)),
|
2005-06-06 22:51:09 +04:00
|
|
|
else:
|
2017-03-03 19:46:40 +03:00
|
|
|
diffhunks = _unidiff(a, b, opts=opts)
|
|
|
|
try:
|
|
|
|
hunkrange, hunklines = next(diffhunks)
|
|
|
|
except StopIteration:
|
2017-03-03 15:51:22 +03:00
|
|
|
return sentinel
|
2010-03-09 20:31:57 +03:00
|
|
|
|
2017-03-03 15:51:22 +03:00
|
|
|
headerlines = [
|
|
|
|
"--- %s%s%s" % (aprefix, fn1, datetag(ad, fn1)),
|
|
|
|
"+++ %s%s%s" % (bprefix, fn2, datetag(bd, fn2)),
|
|
|
|
]
|
2017-03-03 19:46:40 +03:00
|
|
|
def rewindhunks():
|
|
|
|
yield hunkrange, checknonewline(hunklines)
|
|
|
|
for hr, hl in diffhunks:
|
|
|
|
yield hr, checknonewline(hl)
|
|
|
|
|
|
|
|
hunks = rewindhunks()
|
2005-05-27 01:02:28 +04:00
|
|
|
|
2017-03-03 19:46:40 +03:00
|
|
|
return headerlines, hunks
|
2005-05-04 01:16:10 +04:00
|
|
|
|
2016-09-27 21:27:35 +03:00
|
|
|
def _unidiff(t1, t2, opts=defaultopts):
|
2017-03-02 19:22:46 +03:00
|
|
|
"""Yield hunks of a headerless unified diff from t1 and t2 texts.
|
|
|
|
|
|
|
|
Each hunk consists of a (hunkrange, hunklines) tuple where `hunkrange` is a
|
|
|
|
tuple (s1, l1, s2, l2) representing the range information of the hunk to
|
|
|
|
form the '@@ -s1,l1 +s2,l2 @@' header and `hunklines` is a list of lines
|
|
|
|
of the hunk combining said header followed by line additions and
|
|
|
|
deletions.
|
|
|
|
"""
|
2016-09-27 21:27:35 +03:00
|
|
|
l1 = splitnewlines(t1)
|
|
|
|
l2 = splitnewlines(t2)
|
2006-01-24 05:02:10 +03:00
|
|
|
def contextend(l, len):
|
2006-08-13 03:13:27 +04:00
|
|
|
ret = l + opts.context
|
2006-01-24 05:02:10 +03:00
|
|
|
if ret > len:
|
|
|
|
ret = len
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def contextstart(l):
|
2006-08-13 03:13:27 +04:00
|
|
|
ret = l - opts.context
|
2006-01-24 05:02:10 +03:00
|
|
|
if ret < 0:
|
|
|
|
return 0
|
|
|
|
return ret
|
|
|
|
|
2011-09-20 02:58:03 +04:00
|
|
|
lastfunc = [0, '']
|
2010-03-09 20:31:57 +03:00
|
|
|
def yieldhunk(hunk):
|
2006-01-24 05:02:10 +03:00
|
|
|
(astart, a2, bstart, b2, delta) = hunk
|
|
|
|
aend = contextend(a2, len(l1))
|
|
|
|
alen = aend - astart
|
|
|
|
blen = b2 - bstart + aend - a2
|
|
|
|
|
|
|
|
func = ""
|
2006-08-13 03:13:27 +04:00
|
|
|
if opts.showfunc:
|
2011-09-20 02:58:03 +04:00
|
|
|
lastpos, func = lastfunc
|
|
|
|
# walk backwards from the start of the context up to the start of
|
|
|
|
# the previous hunk context until we find a line starting with an
|
|
|
|
# alphanumeric char.
|
|
|
|
for i in xrange(astart - 1, lastpos - 1, -1):
|
|
|
|
if l1[i][0].isalnum():
|
|
|
|
func = ' ' + l1[i].rstrip()[:40]
|
|
|
|
lastfunc[1] = func
|
2006-01-24 05:02:10 +03:00
|
|
|
break
|
2011-09-20 02:58:03 +04:00
|
|
|
# by recording this hunk's starting point as the next place to
|
|
|
|
# start looking for function lines, we avoid reading any line in
|
|
|
|
# the file more than once.
|
|
|
|
lastfunc[0] = astart
|
2006-01-24 05:02:10 +03:00
|
|
|
|
mdiff/patch: fix bad hunk handling for unified diffs with zero context
Prior to this patch "hg diff -U0", i.e., zero lines of context, would
output hunk headers with a start line one greater than what GNU patch
and git output. Guido van Rossum documents the unified diff format[1]
as having a start line value "one lower than one would expect" for
zero length hunks.
Comparing the behaviour of the three systems prior to this patch in
transforming
c1
c3
to
c1
c2
c3
- GNU "diff -U0" reports the hunk as "@@ -1,0 +2 @@"
- "git diff -U0" reports the hunk as "@@ -1,0 +2 @@"
- "hg diff -U0" reports the hunk as "@@ -2,0 +2,1 @@"
After this patch, "hg diff -U0" reports "@@ -1,0 +2,1 @@".
Since "hg export --config diff.unified=0" outputs zero-context unified
diffs, "hg import" has also been updated to account for start lines
one less than expected for zero length hunk ranges.
[1]: http://www.artima.com/weblogs/viewpost.jsp?thread=164293
2011-11-10 04:55:59 +04:00
|
|
|
# zero-length hunk ranges report their start line as one less
|
|
|
|
if alen:
|
|
|
|
astart += 1
|
|
|
|
if blen:
|
|
|
|
bstart += 1
|
|
|
|
|
2017-03-02 19:22:46 +03:00
|
|
|
hunkrange = astart, alen, bstart, blen
|
|
|
|
hunklines = (
|
|
|
|
["@@ -%d,%d +%d,%d @@%s\n" % (hunkrange + (func,))]
|
|
|
|
+ delta
|
|
|
|
+ [' ' + l1[x] for x in xrange(a2, aend)]
|
|
|
|
)
|
|
|
|
yield hunkrange, hunklines
|
2006-01-24 05:02:10 +03:00
|
|
|
|
|
|
|
# bdiff.blocks gives us the matching sequences in the files. The loop
|
|
|
|
# below finds the spaces between those matching sequences and translates
|
|
|
|
# them into diff output.
|
|
|
|
#
|
|
|
|
hunk = None
|
2012-02-07 00:17:50 +04:00
|
|
|
ignoredlines = 0
|
2011-11-18 15:01:04 +04:00
|
|
|
for s, stype in allblocks(t1, t2, opts, l1, l2):
|
2012-02-07 00:17:50 +04:00
|
|
|
a1, a2, b1, b2 = s
|
2011-11-18 15:01:04 +04:00
|
|
|
if stype != '!':
|
2012-02-07 00:17:50 +04:00
|
|
|
if stype == '~':
|
|
|
|
# The diff context lines are based on t1 content. When
|
|
|
|
# blank lines are ignored, the new lines offsets must
|
|
|
|
# be adjusted as if equivalent blocks ('~') had the
|
|
|
|
# same sizes on both sides.
|
|
|
|
ignoredlines += (b2 - b1) - (a2 - a1)
|
2011-11-18 15:01:04 +04:00
|
|
|
continue
|
2006-01-24 05:02:10 +03:00
|
|
|
delta = []
|
|
|
|
old = l1[a1:a2]
|
|
|
|
new = l2[b1:b2]
|
|
|
|
|
2012-02-07 00:17:50 +04:00
|
|
|
b1 -= ignoredlines
|
|
|
|
b2 -= ignoredlines
|
2006-01-24 05:02:10 +03:00
|
|
|
astart = contextstart(a1)
|
|
|
|
bstart = contextstart(b1)
|
|
|
|
prev = None
|
|
|
|
if hunk:
|
|
|
|
# join with the previous hunk if it falls inside the context
|
2006-08-13 03:13:27 +04:00
|
|
|
if astart < hunk[1] + opts.context + 1:
|
2006-01-24 05:02:10 +03:00
|
|
|
prev = hunk
|
|
|
|
astart = hunk[1]
|
|
|
|
bstart = hunk[3]
|
|
|
|
else:
|
2010-03-09 20:31:57 +03:00
|
|
|
for x in yieldhunk(hunk):
|
2006-01-24 05:02:10 +03:00
|
|
|
yield x
|
|
|
|
if prev:
|
|
|
|
# we've joined the previous hunk, record the new ending points.
|
|
|
|
hunk[1] = a2
|
|
|
|
hunk[3] = b2
|
|
|
|
delta = hunk[4]
|
|
|
|
else:
|
|
|
|
# create a new hunk
|
2010-01-25 09:05:27 +03:00
|
|
|
hunk = [astart, a2, bstart, b2, delta]
|
2006-01-24 05:02:10 +03:00
|
|
|
|
2010-01-25 09:05:27 +03:00
|
|
|
delta[len(delta):] = [' ' + x for x in l1[astart:a1]]
|
|
|
|
delta[len(delta):] = ['-' + x for x in old]
|
|
|
|
delta[len(delta):] = ['+' + x for x in new]
|
2006-01-24 05:02:10 +03:00
|
|
|
|
|
|
|
if hunk:
|
2010-03-09 20:31:57 +03:00
|
|
|
for x in yieldhunk(hunk):
|
2006-01-24 05:02:10 +03:00
|
|
|
yield x
|
|
|
|
|
2012-11-07 02:04:05 +04:00
|
|
|
def b85diff(to, tn):
|
|
|
|
'''print base85-encoded binary diff'''
|
|
|
|
def fmtline(line):
|
|
|
|
l = len(line)
|
|
|
|
if l <= 26:
|
|
|
|
l = chr(ord('A') + l - 1)
|
|
|
|
else:
|
|
|
|
l = chr(l - 26 + ord('a') - 1)
|
2017-04-26 15:56:47 +03:00
|
|
|
return '%c%s\n' % (l, util.b85encode(line, True))
|
2012-11-07 02:04:05 +04:00
|
|
|
|
|
|
|
def chunk(text, csize=52):
|
|
|
|
l = len(text)
|
|
|
|
i = 0
|
|
|
|
while i < l:
|
|
|
|
yield text[i:i + csize]
|
|
|
|
i += csize
|
|
|
|
|
2012-11-16 03:16:41 +04:00
|
|
|
if to is None:
|
|
|
|
to = ''
|
|
|
|
if tn is None:
|
|
|
|
tn = ''
|
|
|
|
|
|
|
|
if to == tn:
|
|
|
|
return ''
|
2012-11-07 02:04:05 +04:00
|
|
|
|
|
|
|
# TODO: deltas
|
2012-11-16 03:16:41 +04:00
|
|
|
ret = []
|
|
|
|
ret.append('GIT binary patch\n')
|
2017-10-02 02:18:06 +03:00
|
|
|
ret.append('literal %d\n' % len(tn))
|
2012-11-07 02:04:05 +04:00
|
|
|
for l in chunk(zlib.compress(tn)):
|
|
|
|
ret.append(fmtline(l))
|
|
|
|
ret.append('\n')
|
2012-11-16 03:16:41 +04:00
|
|
|
|
2012-11-07 02:04:05 +04:00
|
|
|
return ''.join(ret)
|
|
|
|
|
2005-05-21 05:42:29 +04:00
|
|
|
def patchtext(bin):
|
|
|
|
pos = 0
|
|
|
|
t = []
|
|
|
|
while pos < len(bin):
|
|
|
|
p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
|
|
|
|
pos += 12
|
|
|
|
t.append(bin[pos:pos + l])
|
|
|
|
pos += l
|
|
|
|
return "".join(t)
|
|
|
|
|
2005-05-04 01:16:10 +04:00
|
|
|
def patch(a, bin):
|
2010-08-23 15:28:04 +04:00
|
|
|
if len(a) == 0:
|
|
|
|
# skip over trivial delta header
|
2011-12-16 01:27:11 +04:00
|
|
|
return util.buffer(bin, 12)
|
2005-10-04 22:25:48 +04:00
|
|
|
return mpatch.patches(a, [bin])
|
2005-06-22 23:23:01 +04:00
|
|
|
|
2007-04-17 03:17:39 +04:00
|
|
|
# similar to difflib.SequenceMatcher.get_matching_blocks
|
|
|
|
def get_matching_blocks(a, b):
|
|
|
|
return [(d[0], d[2], d[1] - d[0]) for d in bdiff.blocks(a, b)]
|
|
|
|
|
2007-10-04 02:17:27 +04:00
|
|
|
def trivialdiffheader(length):
|
2016-01-12 06:00:07 +03:00
|
|
|
return struct.pack(">lll", 0, 0, length) if length else ''
|
2007-10-04 02:17:27 +04:00
|
|
|
|
2015-01-22 00:35:09 +03:00
|
|
|
def replacediffheader(oldlen, newlen):
|
|
|
|
return struct.pack(">lll", 0, oldlen, newlen)
|