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