##// END OF EJS Templates
Don't lie that "binary file has changed"...
tailgunner@smtp.ru -
r4103:544838cc default
parent child Browse files
Show More
@@ -1,252 +1,257 b''
1 # mdiff.py - diff and patch routines for mercurial
1 # mdiff.py - diff and patch routines for mercurial
2 #
2 #
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from demandload import demandload
8 from demandload import demandload
9 import bdiff, mpatch
9 import bdiff, mpatch
10 demandload(globals(), "re struct util")
10 demandload(globals(), "re struct util md5")
11
11
12 def splitnewlines(text):
12 def splitnewlines(text):
13 '''like str.splitlines, but only split on newlines.'''
13 '''like str.splitlines, but only split on newlines.'''
14 lines = [l + '\n' for l in text.split('\n')]
14 lines = [l + '\n' for l in text.split('\n')]
15 if lines:
15 if lines:
16 if lines[-1] == '\n':
16 if lines[-1] == '\n':
17 lines.pop()
17 lines.pop()
18 else:
18 else:
19 lines[-1] = lines[-1][:-1]
19 lines[-1] = lines[-1][:-1]
20 return lines
20 return lines
21
21
22 class diffopts(object):
22 class diffopts(object):
23 '''context is the number of context lines
23 '''context is the number of context lines
24 text treats all files as text
24 text treats all files as text
25 showfunc enables diff -p output
25 showfunc enables diff -p output
26 git enables the git extended patch format
26 git enables the git extended patch format
27 nodates removes dates from diff headers
27 nodates removes dates from diff headers
28 ignorews ignores all whitespace changes in the diff
28 ignorews ignores all whitespace changes in the diff
29 ignorewsamount ignores changes in the amount of whitespace
29 ignorewsamount ignores changes in the amount of whitespace
30 ignoreblanklines ignores changes whose lines are all blank'''
30 ignoreblanklines ignores changes whose lines are all blank'''
31
31
32 defaults = {
32 defaults = {
33 'context': 3,
33 'context': 3,
34 'text': False,
34 'text': False,
35 'showfunc': True,
35 'showfunc': True,
36 'git': False,
36 'git': False,
37 'nodates': False,
37 'nodates': False,
38 'ignorews': False,
38 'ignorews': False,
39 'ignorewsamount': False,
39 'ignorewsamount': False,
40 'ignoreblanklines': False,
40 'ignoreblanklines': False,
41 }
41 }
42
42
43 __slots__ = defaults.keys()
43 __slots__ = defaults.keys()
44
44
45 def __init__(self, **opts):
45 def __init__(self, **opts):
46 for k in self.__slots__:
46 for k in self.__slots__:
47 v = opts.get(k)
47 v = opts.get(k)
48 if v is None:
48 if v is None:
49 v = self.defaults[k]
49 v = self.defaults[k]
50 setattr(self, k, v)
50 setattr(self, k, v)
51
51
52 defaultopts = diffopts()
52 defaultopts = diffopts()
53
53
54 def unidiff(a, ad, b, bd, fn, r=None, opts=defaultopts):
54 def unidiff(a, ad, b, bd, fn, r=None, opts=defaultopts):
55 def datetag(date):
55 def datetag(date):
56 return (opts.git or opts.nodates) and '\n' or '\t%s\n' % date
56 return (opts.git or opts.nodates) and '\n' or '\t%s\n' % date
57
57
58 if not a and not b: return ""
58 if not a and not b: return ""
59 epoch = util.datestr((0, 0))
59 epoch = util.datestr((0, 0))
60
60
61 if not opts.text and (util.binary(a) or util.binary(b)):
61 if not opts.text and (util.binary(a) or util.binary(b)):
62 def h(v):
63 # md5 is used instead of sha1 because md5 is supposedly faster
64 return md5.new(v).digest()
65 if a and b and len(a) == len(b) and h(a) == h(b):
66 return ""
62 l = ['Binary file %s has changed\n' % fn]
67 l = ['Binary file %s has changed\n' % fn]
63 elif not a:
68 elif not a:
64 b = splitnewlines(b)
69 b = splitnewlines(b)
65 if a is None:
70 if a is None:
66 l1 = '--- /dev/null%s' % datetag(epoch)
71 l1 = '--- /dev/null%s' % datetag(epoch)
67 else:
72 else:
68 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
73 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
69 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
74 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
70 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
75 l3 = "@@ -0,0 +1,%d @@\n" % len(b)
71 l = [l1, l2, l3] + ["+" + e for e in b]
76 l = [l1, l2, l3] + ["+" + e for e in b]
72 elif not b:
77 elif not b:
73 a = splitnewlines(a)
78 a = splitnewlines(a)
74 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
79 l1 = "--- %s%s" % ("a/" + fn, datetag(ad))
75 if b is None:
80 if b is None:
76 l2 = '+++ /dev/null%s' % datetag(epoch)
81 l2 = '+++ /dev/null%s' % datetag(epoch)
77 else:
82 else:
78 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
83 l2 = "+++ %s%s" % ("b/" + fn, datetag(bd))
79 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
84 l3 = "@@ -1,%d +0,0 @@\n" % len(a)
80 l = [l1, l2, l3] + ["-" + e for e in a]
85 l = [l1, l2, l3] + ["-" + e for e in a]
81 else:
86 else:
82 al = splitnewlines(a)
87 al = splitnewlines(a)
83 bl = splitnewlines(b)
88 bl = splitnewlines(b)
84 l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, opts=opts))
89 l = list(bunidiff(a, b, al, bl, "a/" + fn, "b/" + fn, opts=opts))
85 if not l: return ""
90 if not l: return ""
86 # difflib uses a space, rather than a tab
91 # difflib uses a space, rather than a tab
87 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
92 l[0] = "%s%s" % (l[0][:-2], datetag(ad))
88 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
93 l[1] = "%s%s" % (l[1][:-2], datetag(bd))
89
94
90 for ln in xrange(len(l)):
95 for ln in xrange(len(l)):
91 if l[ln][-1] != '\n':
96 if l[ln][-1] != '\n':
92 l[ln] += "\n\ No newline at end of file\n"
97 l[ln] += "\n\ No newline at end of file\n"
93
98
94 if r:
99 if r:
95 l.insert(0, "diff %s %s\n" %
100 l.insert(0, "diff %s %s\n" %
96 (' '.join(["-r %s" % rev for rev in r]), fn))
101 (' '.join(["-r %s" % rev for rev in r]), fn))
97
102
98 return "".join(l)
103 return "".join(l)
99
104
100 # somewhat self contained replacement for difflib.unified_diff
105 # somewhat self contained replacement for difflib.unified_diff
101 # t1 and t2 are the text to be diffed
106 # t1 and t2 are the text to be diffed
102 # l1 and l2 are the text broken up into lines
107 # l1 and l2 are the text broken up into lines
103 # header1 and header2 are the filenames for the diff output
108 # header1 and header2 are the filenames for the diff output
104 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
109 def bunidiff(t1, t2, l1, l2, header1, header2, opts=defaultopts):
105 def contextend(l, len):
110 def contextend(l, len):
106 ret = l + opts.context
111 ret = l + opts.context
107 if ret > len:
112 if ret > len:
108 ret = len
113 ret = len
109 return ret
114 return ret
110
115
111 def contextstart(l):
116 def contextstart(l):
112 ret = l - opts.context
117 ret = l - opts.context
113 if ret < 0:
118 if ret < 0:
114 return 0
119 return 0
115 return ret
120 return ret
116
121
117 def yieldhunk(hunk, header):
122 def yieldhunk(hunk, header):
118 if header:
123 if header:
119 for x in header:
124 for x in header:
120 yield x
125 yield x
121 (astart, a2, bstart, b2, delta) = hunk
126 (astart, a2, bstart, b2, delta) = hunk
122 aend = contextend(a2, len(l1))
127 aend = contextend(a2, len(l1))
123 alen = aend - astart
128 alen = aend - astart
124 blen = b2 - bstart + aend - a2
129 blen = b2 - bstart + aend - a2
125
130
126 func = ""
131 func = ""
127 if opts.showfunc:
132 if opts.showfunc:
128 # walk backwards from the start of the context
133 # walk backwards from the start of the context
129 # to find a line starting with an alphanumeric char.
134 # to find a line starting with an alphanumeric char.
130 for x in xrange(astart, -1, -1):
135 for x in xrange(astart, -1, -1):
131 t = l1[x].rstrip()
136 t = l1[x].rstrip()
132 if funcre.match(t):
137 if funcre.match(t):
133 func = ' ' + t[:40]
138 func = ' ' + t[:40]
134 break
139 break
135
140
136 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
141 yield "@@ -%d,%d +%d,%d @@%s\n" % (astart + 1, alen,
137 bstart + 1, blen, func)
142 bstart + 1, blen, func)
138 for x in delta:
143 for x in delta:
139 yield x
144 yield x
140 for x in xrange(a2, aend):
145 for x in xrange(a2, aend):
141 yield ' ' + l1[x]
146 yield ' ' + l1[x]
142
147
143 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
148 header = [ "--- %s\t\n" % header1, "+++ %s\t\n" % header2 ]
144
149
145 if opts.showfunc:
150 if opts.showfunc:
146 funcre = re.compile('\w')
151 funcre = re.compile('\w')
147 if opts.ignorewsamount:
152 if opts.ignorewsamount:
148 wsamountre = re.compile('[ \t]+')
153 wsamountre = re.compile('[ \t]+')
149 wsappendedre = re.compile(' \n')
154 wsappendedre = re.compile(' \n')
150 if opts.ignoreblanklines:
155 if opts.ignoreblanklines:
151 wsblanklinesre = re.compile('\n')
156 wsblanklinesre = re.compile('\n')
152 if opts.ignorews:
157 if opts.ignorews:
153 wsre = re.compile('[ \t]')
158 wsre = re.compile('[ \t]')
154
159
155 # bdiff.blocks gives us the matching sequences in the files. The loop
160 # bdiff.blocks gives us the matching sequences in the files. The loop
156 # below finds the spaces between those matching sequences and translates
161 # below finds the spaces between those matching sequences and translates
157 # them into diff output.
162 # them into diff output.
158 #
163 #
159 diff = bdiff.blocks(t1, t2)
164 diff = bdiff.blocks(t1, t2)
160 hunk = None
165 hunk = None
161 for i in xrange(len(diff)):
166 for i in xrange(len(diff)):
162 # The first match is special.
167 # The first match is special.
163 # we've either found a match starting at line 0 or a match later
168 # we've either found a match starting at line 0 or a match later
164 # in the file. If it starts later, old and new below will both be
169 # in the file. If it starts later, old and new below will both be
165 # empty and we'll continue to the next match.
170 # empty and we'll continue to the next match.
166 if i > 0:
171 if i > 0:
167 s = diff[i-1]
172 s = diff[i-1]
168 else:
173 else:
169 s = [0, 0, 0, 0]
174 s = [0, 0, 0, 0]
170 delta = []
175 delta = []
171 s1 = diff[i]
176 s1 = diff[i]
172 a1 = s[1]
177 a1 = s[1]
173 a2 = s1[0]
178 a2 = s1[0]
174 b1 = s[3]
179 b1 = s[3]
175 b2 = s1[2]
180 b2 = s1[2]
176
181
177 old = l1[a1:a2]
182 old = l1[a1:a2]
178 new = l2[b1:b2]
183 new = l2[b1:b2]
179
184
180 # bdiff sometimes gives huge matches past eof, this check eats them,
185 # bdiff sometimes gives huge matches past eof, this check eats them,
181 # and deals with the special first match case described above
186 # and deals with the special first match case described above
182 if not old and not new:
187 if not old and not new:
183 continue
188 continue
184
189
185 if opts.ignoreblanklines:
190 if opts.ignoreblanklines:
186 wsold = wsblanklinesre.sub('', "".join(old))
191 wsold = wsblanklinesre.sub('', "".join(old))
187 wsnew = wsblanklinesre.sub('', "".join(new))
192 wsnew = wsblanklinesre.sub('', "".join(new))
188 if wsold == wsnew:
193 if wsold == wsnew:
189 continue
194 continue
190
195
191 if opts.ignorewsamount:
196 if opts.ignorewsamount:
192 wsold = wsamountre.sub(' ', "".join(old))
197 wsold = wsamountre.sub(' ', "".join(old))
193 wsold = wsappendedre.sub('\n', wsold)
198 wsold = wsappendedre.sub('\n', wsold)
194 wsnew = wsamountre.sub(' ', "".join(new))
199 wsnew = wsamountre.sub(' ', "".join(new))
195 wsnew = wsappendedre.sub('\n', wsnew)
200 wsnew = wsappendedre.sub('\n', wsnew)
196 if wsold == wsnew:
201 if wsold == wsnew:
197 continue
202 continue
198
203
199 if opts.ignorews:
204 if opts.ignorews:
200 wsold = wsre.sub('', "".join(old))
205 wsold = wsre.sub('', "".join(old))
201 wsnew = wsre.sub('', "".join(new))
206 wsnew = wsre.sub('', "".join(new))
202 if wsold == wsnew:
207 if wsold == wsnew:
203 continue
208 continue
204
209
205 astart = contextstart(a1)
210 astart = contextstart(a1)
206 bstart = contextstart(b1)
211 bstart = contextstart(b1)
207 prev = None
212 prev = None
208 if hunk:
213 if hunk:
209 # join with the previous hunk if it falls inside the context
214 # join with the previous hunk if it falls inside the context
210 if astart < hunk[1] + opts.context + 1:
215 if astart < hunk[1] + opts.context + 1:
211 prev = hunk
216 prev = hunk
212 astart = hunk[1]
217 astart = hunk[1]
213 bstart = hunk[3]
218 bstart = hunk[3]
214 else:
219 else:
215 for x in yieldhunk(hunk, header):
220 for x in yieldhunk(hunk, header):
216 yield x
221 yield x
217 # we only want to yield the header if the files differ, and
222 # we only want to yield the header if the files differ, and
218 # we only want to yield it once.
223 # we only want to yield it once.
219 header = None
224 header = None
220 if prev:
225 if prev:
221 # we've joined the previous hunk, record the new ending points.
226 # we've joined the previous hunk, record the new ending points.
222 hunk[1] = a2
227 hunk[1] = a2
223 hunk[3] = b2
228 hunk[3] = b2
224 delta = hunk[4]
229 delta = hunk[4]
225 else:
230 else:
226 # create a new hunk
231 # create a new hunk
227 hunk = [ astart, a2, bstart, b2, delta ]
232 hunk = [ astart, a2, bstart, b2, delta ]
228
233
229 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
234 delta[len(delta):] = [ ' ' + x for x in l1[astart:a1] ]
230 delta[len(delta):] = [ '-' + x for x in old ]
235 delta[len(delta):] = [ '-' + x for x in old ]
231 delta[len(delta):] = [ '+' + x for x in new ]
236 delta[len(delta):] = [ '+' + x for x in new ]
232
237
233 if hunk:
238 if hunk:
234 for x in yieldhunk(hunk, header):
239 for x in yieldhunk(hunk, header):
235 yield x
240 yield x
236
241
237 def patchtext(bin):
242 def patchtext(bin):
238 pos = 0
243 pos = 0
239 t = []
244 t = []
240 while pos < len(bin):
245 while pos < len(bin):
241 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
246 p1, p2, l = struct.unpack(">lll", bin[pos:pos + 12])
242 pos += 12
247 pos += 12
243 t.append(bin[pos:pos + l])
248 t.append(bin[pos:pos + l])
244 pos += l
249 pos += l
245 return "".join(t)
250 return "".join(t)
246
251
247 def patch(a, bin):
252 def patch(a, bin):
248 return mpatch.patches(a, [bin])
253 return mpatch.patches(a, [bin])
249
254
250 patches = mpatch.patches
255 patches = mpatch.patches
251 patchedsize = mpatch.patchedsize
256 patchedsize = mpatch.patchedsize
252 textdiff = bdiff.bdiff
257 textdiff = bdiff.bdiff
General Comments 0
You need to be logged in to leave comments. Login now