##// END OF EJS Templates
simplemerge: make merge_groups() yield only 2-tuples...
Martin von Zweigbergk -
r49379:374bf34c default
parent child Browse files
Show More
@@ -1,550 +1,552 b''
1 # Copyright (C) 2004, 2005 Canonical Ltd
1 # Copyright (C) 2004, 2005 Canonical Ltd
2 #
2 #
3 # This program is free software; you can redistribute it and/or modify
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
6 # (at your option) any later version.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU General Public License
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
15
15
16 # mbp: "you know that thing where cvs gives you conflict markers?"
16 # mbp: "you know that thing where cvs gives you conflict markers?"
17 # s: "i hate that."
17 # s: "i hate that."
18
18
19 from __future__ import absolute_import
19 from __future__ import absolute_import
20
20
21 from .i18n import _
21 from .i18n import _
22 from . import (
22 from . import (
23 error,
23 error,
24 mdiff,
24 mdiff,
25 pycompat,
25 pycompat,
26 )
26 )
27 from .utils import stringutil
27 from .utils import stringutil
28
28
29
29
30 class CantReprocessAndShowBase(Exception):
30 class CantReprocessAndShowBase(Exception):
31 pass
31 pass
32
32
33
33
34 def intersect(ra, rb):
34 def intersect(ra, rb):
35 """Given two ranges return the range where they intersect or None.
35 """Given two ranges return the range where they intersect or None.
36
36
37 >>> intersect((0, 10), (0, 6))
37 >>> intersect((0, 10), (0, 6))
38 (0, 6)
38 (0, 6)
39 >>> intersect((0, 10), (5, 15))
39 >>> intersect((0, 10), (5, 15))
40 (5, 10)
40 (5, 10)
41 >>> intersect((0, 10), (10, 15))
41 >>> intersect((0, 10), (10, 15))
42 >>> intersect((0, 9), (10, 15))
42 >>> intersect((0, 9), (10, 15))
43 >>> intersect((0, 9), (7, 15))
43 >>> intersect((0, 9), (7, 15))
44 (7, 9)
44 (7, 9)
45 """
45 """
46 assert ra[0] <= ra[1]
46 assert ra[0] <= ra[1]
47 assert rb[0] <= rb[1]
47 assert rb[0] <= rb[1]
48
48
49 sa = max(ra[0], rb[0])
49 sa = max(ra[0], rb[0])
50 sb = min(ra[1], rb[1])
50 sb = min(ra[1], rb[1])
51 if sa < sb:
51 if sa < sb:
52 return sa, sb
52 return sa, sb
53 else:
53 else:
54 return None
54 return None
55
55
56
56
57 def compare_range(a, astart, aend, b, bstart, bend):
57 def compare_range(a, astart, aend, b, bstart, bend):
58 """Compare a[astart:aend] == b[bstart:bend], without slicing."""
58 """Compare a[astart:aend] == b[bstart:bend], without slicing."""
59 if (aend - astart) != (bend - bstart):
59 if (aend - astart) != (bend - bstart):
60 return False
60 return False
61 for ia, ib in zip(
61 for ia, ib in zip(
62 pycompat.xrange(astart, aend), pycompat.xrange(bstart, bend)
62 pycompat.xrange(astart, aend), pycompat.xrange(bstart, bend)
63 ):
63 ):
64 if a[ia] != b[ib]:
64 if a[ia] != b[ib]:
65 return False
65 return False
66 else:
66 else:
67 return True
67 return True
68
68
69
69
70 class Merge3Text(object):
70 class Merge3Text(object):
71 """3-way merge of texts.
71 """3-way merge of texts.
72
72
73 Given strings BASE, OTHER, THIS, tries to produce a combined text
73 Given strings BASE, OTHER, THIS, tries to produce a combined text
74 incorporating the changes from both BASE->OTHER and BASE->THIS."""
74 incorporating the changes from both BASE->OTHER and BASE->THIS."""
75
75
76 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
76 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
77 self.basetext = basetext
77 self.basetext = basetext
78 self.atext = atext
78 self.atext = atext
79 self.btext = btext
79 self.btext = btext
80 if base is None:
80 if base is None:
81 base = mdiff.splitnewlines(basetext)
81 base = mdiff.splitnewlines(basetext)
82 if a is None:
82 if a is None:
83 a = mdiff.splitnewlines(atext)
83 a = mdiff.splitnewlines(atext)
84 if b is None:
84 if b is None:
85 b = mdiff.splitnewlines(btext)
85 b = mdiff.splitnewlines(btext)
86 self.base = base
86 self.base = base
87 self.a = a
87 self.a = a
88 self.b = b
88 self.b = b
89
89
90 def merge_lines(
90 def merge_lines(
91 self,
91 self,
92 name_a=None,
92 name_a=None,
93 name_b=None,
93 name_b=None,
94 name_base=None,
94 name_base=None,
95 start_marker=b'<<<<<<<',
95 start_marker=b'<<<<<<<',
96 mid_marker=b'=======',
96 mid_marker=b'=======',
97 end_marker=b'>>>>>>>',
97 end_marker=b'>>>>>>>',
98 base_marker=None,
98 base_marker=None,
99 minimize=False,
99 minimize=False,
100 ):
100 ):
101 """Return merge in cvs-like form."""
101 """Return merge in cvs-like form."""
102 self.conflicts = False
102 self.conflicts = False
103 newline = b'\n'
103 newline = b'\n'
104 if len(self.a) > 0:
104 if len(self.a) > 0:
105 if self.a[0].endswith(b'\r\n'):
105 if self.a[0].endswith(b'\r\n'):
106 newline = b'\r\n'
106 newline = b'\r\n'
107 elif self.a[0].endswith(b'\r'):
107 elif self.a[0].endswith(b'\r'):
108 newline = b'\r'
108 newline = b'\r'
109 if name_a and start_marker:
109 if name_a and start_marker:
110 start_marker = start_marker + b' ' + name_a
110 start_marker = start_marker + b' ' + name_a
111 if name_b and end_marker:
111 if name_b and end_marker:
112 end_marker = end_marker + b' ' + name_b
112 end_marker = end_marker + b' ' + name_b
113 if name_base and base_marker:
113 if name_base and base_marker:
114 base_marker = base_marker + b' ' + name_base
114 base_marker = base_marker + b' ' + name_base
115 merge_regions = self.merge_regions()
115 merge_regions = self.merge_regions()
116 if minimize:
116 if minimize:
117 merge_regions = self.minimize(merge_regions)
117 merge_regions = self.minimize(merge_regions)
118 for t in merge_regions:
118 for t in merge_regions:
119 what = t[0]
119 what = t[0]
120 if what == b'unchanged':
120 if what == b'unchanged':
121 for i in range(t[1], t[2]):
121 for i in range(t[1], t[2]):
122 yield self.base[i]
122 yield self.base[i]
123 elif what == b'a' or what == b'same':
123 elif what == b'a' or what == b'same':
124 for i in range(t[1], t[2]):
124 for i in range(t[1], t[2]):
125 yield self.a[i]
125 yield self.a[i]
126 elif what == b'b':
126 elif what == b'b':
127 for i in range(t[1], t[2]):
127 for i in range(t[1], t[2]):
128 yield self.b[i]
128 yield self.b[i]
129 elif what == b'conflict':
129 elif what == b'conflict':
130 self.conflicts = True
130 self.conflicts = True
131 if start_marker is not None:
131 if start_marker is not None:
132 yield start_marker + newline
132 yield start_marker + newline
133 for i in range(t[3], t[4]):
133 for i in range(t[3], t[4]):
134 yield self.a[i]
134 yield self.a[i]
135 if base_marker is not None:
135 if base_marker is not None:
136 yield base_marker + newline
136 yield base_marker + newline
137 for i in range(t[1], t[2]):
137 for i in range(t[1], t[2]):
138 yield self.base[i]
138 yield self.base[i]
139 if mid_marker is not None:
139 if mid_marker is not None:
140 yield mid_marker + newline
140 yield mid_marker + newline
141 for i in range(t[5], t[6]):
141 for i in range(t[5], t[6]):
142 yield self.b[i]
142 yield self.b[i]
143 if end_marker is not None:
143 if end_marker is not None:
144 yield end_marker + newline
144 yield end_marker + newline
145 else:
145 else:
146 raise ValueError(what)
146 raise ValueError(what)
147
147
148 def merge_groups(self):
148 def merge_groups(self):
149 """Yield sequence of line groups. Each one is a tuple:
149 """Yield sequence of line groups. Each one is a tuple:
150
150
151 'unchanged', lines
151 'unchanged', lines
152 Lines unchanged from base
152 Lines unchanged from base
153
153
154 'a', lines
154 'a', lines
155 Lines taken from a
155 Lines taken from a
156
156
157 'same', lines
157 'same', lines
158 Lines taken from a (and equal to b)
158 Lines taken from a (and equal to b)
159
159
160 'b', lines
160 'b', lines
161 Lines taken from b
161 Lines taken from b
162
162
163 'conflict', base_lines, a_lines, b_lines
163 'conflict', (base_lines, a_lines, b_lines)
164 Lines from base were changed to either a or b and conflict.
164 Lines from base were changed to either a or b and conflict.
165 """
165 """
166 for t in self.merge_regions():
166 for t in self.merge_regions():
167 what = t[0]
167 what = t[0]
168 if what == b'unchanged':
168 if what == b'unchanged':
169 yield what, self.base[t[1] : t[2]]
169 yield what, self.base[t[1] : t[2]]
170 elif what == b'a' or what == b'same':
170 elif what == b'a' or what == b'same':
171 yield what, self.a[t[1] : t[2]]
171 yield what, self.a[t[1] : t[2]]
172 elif what == b'b':
172 elif what == b'b':
173 yield what, self.b[t[1] : t[2]]
173 yield what, self.b[t[1] : t[2]]
174 elif what == b'conflict':
174 elif what == b'conflict':
175 yield (
175 yield (
176 what,
176 what,
177 self.base[t[1] : t[2]],
177 (
178 self.a[t[3] : t[4]],
178 self.base[t[1] : t[2]],
179 self.b[t[5] : t[6]],
179 self.a[t[3] : t[4]],
180 self.b[t[5] : t[6]],
181 ),
180 )
182 )
181 else:
183 else:
182 raise ValueError(what)
184 raise ValueError(what)
183
185
184 def merge_regions(self):
186 def merge_regions(self):
185 """Return sequences of matching and conflicting regions.
187 """Return sequences of matching and conflicting regions.
186
188
187 This returns tuples, where the first value says what kind we
189 This returns tuples, where the first value says what kind we
188 have:
190 have:
189
191
190 'unchanged', start, end
192 'unchanged', start, end
191 Take a region of base[start:end]
193 Take a region of base[start:end]
192
194
193 'same', astart, aend
195 'same', astart, aend
194 b and a are different from base but give the same result
196 b and a are different from base but give the same result
195
197
196 'a', start, end
198 'a', start, end
197 Non-clashing insertion from a[start:end]
199 Non-clashing insertion from a[start:end]
198
200
199 'conflict', zstart, zend, astart, aend, bstart, bend
201 'conflict', zstart, zend, astart, aend, bstart, bend
200 Conflict between a and b, with z as common ancestor
202 Conflict between a and b, with z as common ancestor
201
203
202 Method is as follows:
204 Method is as follows:
203
205
204 The two sequences align only on regions which match the base
206 The two sequences align only on regions which match the base
205 and both descendants. These are found by doing a two-way diff
207 and both descendants. These are found by doing a two-way diff
206 of each one against the base, and then finding the
208 of each one against the base, and then finding the
207 intersections between those regions. These "sync regions"
209 intersections between those regions. These "sync regions"
208 are by definition unchanged in both and easily dealt with.
210 are by definition unchanged in both and easily dealt with.
209
211
210 The regions in between can be in any of three cases:
212 The regions in between can be in any of three cases:
211 conflicted, or changed on only one side.
213 conflicted, or changed on only one side.
212 """
214 """
213
215
214 # section a[0:ia] has been disposed of, etc
216 # section a[0:ia] has been disposed of, etc
215 iz = ia = ib = 0
217 iz = ia = ib = 0
216
218
217 for region in self.find_sync_regions():
219 for region in self.find_sync_regions():
218 zmatch, zend, amatch, aend, bmatch, bend = region
220 zmatch, zend, amatch, aend, bmatch, bend = region
219 # print 'match base [%d:%d]' % (zmatch, zend)
221 # print 'match base [%d:%d]' % (zmatch, zend)
220
222
221 matchlen = zend - zmatch
223 matchlen = zend - zmatch
222 assert matchlen >= 0
224 assert matchlen >= 0
223 assert matchlen == (aend - amatch)
225 assert matchlen == (aend - amatch)
224 assert matchlen == (bend - bmatch)
226 assert matchlen == (bend - bmatch)
225
227
226 len_a = amatch - ia
228 len_a = amatch - ia
227 len_b = bmatch - ib
229 len_b = bmatch - ib
228 len_base = zmatch - iz
230 len_base = zmatch - iz
229 assert len_a >= 0
231 assert len_a >= 0
230 assert len_b >= 0
232 assert len_b >= 0
231 assert len_base >= 0
233 assert len_base >= 0
232
234
233 # print 'unmatched a=%d, b=%d' % (len_a, len_b)
235 # print 'unmatched a=%d, b=%d' % (len_a, len_b)
234
236
235 if len_a or len_b:
237 if len_a or len_b:
236 # try to avoid actually slicing the lists
238 # try to avoid actually slicing the lists
237 equal_a = compare_range(
239 equal_a = compare_range(
238 self.a, ia, amatch, self.base, iz, zmatch
240 self.a, ia, amatch, self.base, iz, zmatch
239 )
241 )
240 equal_b = compare_range(
242 equal_b = compare_range(
241 self.b, ib, bmatch, self.base, iz, zmatch
243 self.b, ib, bmatch, self.base, iz, zmatch
242 )
244 )
243 same = compare_range(self.a, ia, amatch, self.b, ib, bmatch)
245 same = compare_range(self.a, ia, amatch, self.b, ib, bmatch)
244
246
245 if same:
247 if same:
246 yield b'same', ia, amatch
248 yield b'same', ia, amatch
247 elif equal_a and not equal_b:
249 elif equal_a and not equal_b:
248 yield b'b', ib, bmatch
250 yield b'b', ib, bmatch
249 elif equal_b and not equal_a:
251 elif equal_b and not equal_a:
250 yield b'a', ia, amatch
252 yield b'a', ia, amatch
251 elif not equal_a and not equal_b:
253 elif not equal_a and not equal_b:
252 yield b'conflict', iz, zmatch, ia, amatch, ib, bmatch
254 yield b'conflict', iz, zmatch, ia, amatch, ib, bmatch
253 else:
255 else:
254 raise AssertionError(b"can't handle a=b=base but unmatched")
256 raise AssertionError(b"can't handle a=b=base but unmatched")
255
257
256 ia = amatch
258 ia = amatch
257 ib = bmatch
259 ib = bmatch
258 iz = zmatch
260 iz = zmatch
259
261
260 # if the same part of the base was deleted on both sides
262 # if the same part of the base was deleted on both sides
261 # that's OK, we can just skip it.
263 # that's OK, we can just skip it.
262
264
263 if matchlen > 0:
265 if matchlen > 0:
264 assert ia == amatch
266 assert ia == amatch
265 assert ib == bmatch
267 assert ib == bmatch
266 assert iz == zmatch
268 assert iz == zmatch
267
269
268 yield b'unchanged', zmatch, zend
270 yield b'unchanged', zmatch, zend
269 iz = zend
271 iz = zend
270 ia = aend
272 ia = aend
271 ib = bend
273 ib = bend
272
274
273 def minimize(self, merge_regions):
275 def minimize(self, merge_regions):
274 """Trim conflict regions of lines where A and B sides match.
276 """Trim conflict regions of lines where A and B sides match.
275
277
276 Lines where both A and B have made the same changes at the beginning
278 Lines where both A and B have made the same changes at the beginning
277 or the end of each merge region are eliminated from the conflict
279 or the end of each merge region are eliminated from the conflict
278 region and are instead considered the same.
280 region and are instead considered the same.
279 """
281 """
280 for region in merge_regions:
282 for region in merge_regions:
281 if region[0] != b"conflict":
283 if region[0] != b"conflict":
282 yield region
284 yield region
283 continue
285 continue
284 # pytype thinks this tuple contains only 3 things, but
286 # pytype thinks this tuple contains only 3 things, but
285 # that's clearly not true because this code successfully
287 # that's clearly not true because this code successfully
286 # executes. It might be wise to rework merge_regions to be
288 # executes. It might be wise to rework merge_regions to be
287 # some kind of attrs type.
289 # some kind of attrs type.
288 (
290 (
289 issue,
291 issue,
290 z1,
292 z1,
291 z2,
293 z2,
292 a1,
294 a1,
293 a2,
295 a2,
294 b1,
296 b1,
295 b2,
297 b2,
296 ) = region # pytype: disable=bad-unpacking
298 ) = region # pytype: disable=bad-unpacking
297 alen = a2 - a1
299 alen = a2 - a1
298 blen = b2 - b1
300 blen = b2 - b1
299
301
300 # find matches at the front
302 # find matches at the front
301 ii = 0
303 ii = 0
302 while (
304 while (
303 ii < alen and ii < blen and self.a[a1 + ii] == self.b[b1 + ii]
305 ii < alen and ii < blen and self.a[a1 + ii] == self.b[b1 + ii]
304 ):
306 ):
305 ii += 1
307 ii += 1
306 startmatches = ii
308 startmatches = ii
307
309
308 # find matches at the end
310 # find matches at the end
309 ii = 0
311 ii = 0
310 while (
312 while (
311 ii < alen
313 ii < alen
312 and ii < blen
314 and ii < blen
313 and self.a[a2 - ii - 1] == self.b[b2 - ii - 1]
315 and self.a[a2 - ii - 1] == self.b[b2 - ii - 1]
314 ):
316 ):
315 ii += 1
317 ii += 1
316 endmatches = ii
318 endmatches = ii
317
319
318 if startmatches > 0:
320 if startmatches > 0:
319 yield b'same', a1, a1 + startmatches
321 yield b'same', a1, a1 + startmatches
320
322
321 yield (
323 yield (
322 b'conflict',
324 b'conflict',
323 z1,
325 z1,
324 z2,
326 z2,
325 a1 + startmatches,
327 a1 + startmatches,
326 a2 - endmatches,
328 a2 - endmatches,
327 b1 + startmatches,
329 b1 + startmatches,
328 b2 - endmatches,
330 b2 - endmatches,
329 )
331 )
330
332
331 if endmatches > 0:
333 if endmatches > 0:
332 yield b'same', a2 - endmatches, a2
334 yield b'same', a2 - endmatches, a2
333
335
334 def find_sync_regions(self):
336 def find_sync_regions(self):
335 """Return a list of sync regions, where both descendants match the base.
337 """Return a list of sync regions, where both descendants match the base.
336
338
337 Generates a list of (base1, base2, a1, a2, b1, b2). There is
339 Generates a list of (base1, base2, a1, a2, b1, b2). There is
338 always a zero-length sync region at the end of all the files.
340 always a zero-length sync region at the end of all the files.
339 """
341 """
340
342
341 ia = ib = 0
343 ia = ib = 0
342 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
344 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
343 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
345 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
344 len_a = len(amatches)
346 len_a = len(amatches)
345 len_b = len(bmatches)
347 len_b = len(bmatches)
346
348
347 sl = []
349 sl = []
348
350
349 while ia < len_a and ib < len_b:
351 while ia < len_a and ib < len_b:
350 abase, amatch, alen = amatches[ia]
352 abase, amatch, alen = amatches[ia]
351 bbase, bmatch, blen = bmatches[ib]
353 bbase, bmatch, blen = bmatches[ib]
352
354
353 # there is an unconflicted block at i; how long does it
355 # there is an unconflicted block at i; how long does it
354 # extend? until whichever one ends earlier.
356 # extend? until whichever one ends earlier.
355 i = intersect((abase, abase + alen), (bbase, bbase + blen))
357 i = intersect((abase, abase + alen), (bbase, bbase + blen))
356 if i:
358 if i:
357 intbase = i[0]
359 intbase = i[0]
358 intend = i[1]
360 intend = i[1]
359 intlen = intend - intbase
361 intlen = intend - intbase
360
362
361 # found a match of base[i[0], i[1]]; this may be less than
363 # found a match of base[i[0], i[1]]; this may be less than
362 # the region that matches in either one
364 # the region that matches in either one
363 assert intlen <= alen
365 assert intlen <= alen
364 assert intlen <= blen
366 assert intlen <= blen
365 assert abase <= intbase
367 assert abase <= intbase
366 assert bbase <= intbase
368 assert bbase <= intbase
367
369
368 asub = amatch + (intbase - abase)
370 asub = amatch + (intbase - abase)
369 bsub = bmatch + (intbase - bbase)
371 bsub = bmatch + (intbase - bbase)
370 aend = asub + intlen
372 aend = asub + intlen
371 bend = bsub + intlen
373 bend = bsub + intlen
372
374
373 assert self.base[intbase:intend] == self.a[asub:aend], (
375 assert self.base[intbase:intend] == self.a[asub:aend], (
374 self.base[intbase:intend],
376 self.base[intbase:intend],
375 self.a[asub:aend],
377 self.a[asub:aend],
376 )
378 )
377
379
378 assert self.base[intbase:intend] == self.b[bsub:bend]
380 assert self.base[intbase:intend] == self.b[bsub:bend]
379
381
380 sl.append((intbase, intend, asub, aend, bsub, bend))
382 sl.append((intbase, intend, asub, aend, bsub, bend))
381
383
382 # advance whichever one ends first in the base text
384 # advance whichever one ends first in the base text
383 if (abase + alen) < (bbase + blen):
385 if (abase + alen) < (bbase + blen):
384 ia += 1
386 ia += 1
385 else:
387 else:
386 ib += 1
388 ib += 1
387
389
388 intbase = len(self.base)
390 intbase = len(self.base)
389 abase = len(self.a)
391 abase = len(self.a)
390 bbase = len(self.b)
392 bbase = len(self.b)
391 sl.append((intbase, intbase, abase, abase, bbase, bbase))
393 sl.append((intbase, intbase, abase, abase, bbase, bbase))
392
394
393 return sl
395 return sl
394
396
395
397
396 def _verifytext(text, path, ui, opts):
398 def _verifytext(text, path, ui, opts):
397 """verifies that text is non-binary (unless opts[text] is passed,
399 """verifies that text is non-binary (unless opts[text] is passed,
398 then we just warn)"""
400 then we just warn)"""
399 if stringutil.binary(text):
401 if stringutil.binary(text):
400 msg = _(b"%s looks like a binary file.") % path
402 msg = _(b"%s looks like a binary file.") % path
401 if not opts.get('quiet'):
403 if not opts.get('quiet'):
402 ui.warn(_(b'warning: %s\n') % msg)
404 ui.warn(_(b'warning: %s\n') % msg)
403 if not opts.get('text'):
405 if not opts.get('text'):
404 raise error.Abort(msg)
406 raise error.Abort(msg)
405 return text
407 return text
406
408
407
409
408 def _picklabels(defaults, overrides):
410 def _picklabels(defaults, overrides):
409 if len(overrides) > 3:
411 if len(overrides) > 3:
410 raise error.Abort(_(b"can only specify three labels."))
412 raise error.Abort(_(b"can only specify three labels."))
411 result = defaults[:]
413 result = defaults[:]
412 for i, override in enumerate(overrides):
414 for i, override in enumerate(overrides):
413 result[i] = override
415 result[i] = override
414 return result
416 return result
415
417
416
418
417 def _mergediff(m3, name_a, name_b, name_base):
419 def _mergediff(m3, name_a, name_b, name_base):
418 lines = []
420 lines = []
419 conflicts = False
421 conflicts = False
420 for group in m3.merge_groups():
422 for what, group_lines in m3.merge_groups():
421 if group[0] == b'conflict':
423 if what == b'conflict':
422 base_lines, a_lines, b_lines = group[1:]
424 base_lines, a_lines, b_lines = group_lines
423 base_text = b''.join(base_lines)
425 base_text = b''.join(base_lines)
424 b_blocks = list(
426 b_blocks = list(
425 mdiff.allblocks(
427 mdiff.allblocks(
426 base_text,
428 base_text,
427 b''.join(b_lines),
429 b''.join(b_lines),
428 lines1=base_lines,
430 lines1=base_lines,
429 lines2=b_lines,
431 lines2=b_lines,
430 )
432 )
431 )
433 )
432 a_blocks = list(
434 a_blocks = list(
433 mdiff.allblocks(
435 mdiff.allblocks(
434 base_text,
436 base_text,
435 b''.join(a_lines),
437 b''.join(a_lines),
436 lines1=base_lines,
438 lines1=base_lines,
437 lines2=b_lines,
439 lines2=b_lines,
438 )
440 )
439 )
441 )
440
442
441 def matching_lines(blocks):
443 def matching_lines(blocks):
442 return sum(
444 return sum(
443 block[1] - block[0]
445 block[1] - block[0]
444 for block, kind in blocks
446 for block, kind in blocks
445 if kind == b'='
447 if kind == b'='
446 )
448 )
447
449
448 def diff_lines(blocks, lines1, lines2):
450 def diff_lines(blocks, lines1, lines2):
449 for block, kind in blocks:
451 for block, kind in blocks:
450 if kind == b'=':
452 if kind == b'=':
451 for line in lines1[block[0] : block[1]]:
453 for line in lines1[block[0] : block[1]]:
452 yield b' ' + line
454 yield b' ' + line
453 else:
455 else:
454 for line in lines1[block[0] : block[1]]:
456 for line in lines1[block[0] : block[1]]:
455 yield b'-' + line
457 yield b'-' + line
456 for line in lines2[block[2] : block[3]]:
458 for line in lines2[block[2] : block[3]]:
457 yield b'+' + line
459 yield b'+' + line
458
460
459 lines.append(b"<<<<<<<\n")
461 lines.append(b"<<<<<<<\n")
460 if matching_lines(a_blocks) < matching_lines(b_blocks):
462 if matching_lines(a_blocks) < matching_lines(b_blocks):
461 lines.append(b"======= %s\n" % name_a)
463 lines.append(b"======= %s\n" % name_a)
462 lines.extend(a_lines)
464 lines.extend(a_lines)
463 lines.append(b"------- %s\n" % name_base)
465 lines.append(b"------- %s\n" % name_base)
464 lines.append(b"+++++++ %s\n" % name_b)
466 lines.append(b"+++++++ %s\n" % name_b)
465 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
467 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
466 else:
468 else:
467 lines.append(b"------- %s\n" % name_base)
469 lines.append(b"------- %s\n" % name_base)
468 lines.append(b"+++++++ %s\n" % name_a)
470 lines.append(b"+++++++ %s\n" % name_a)
469 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
471 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
470 lines.append(b"======= %s\n" % name_b)
472 lines.append(b"======= %s\n" % name_b)
471 lines.extend(b_lines)
473 lines.extend(b_lines)
472 lines.append(b">>>>>>>\n")
474 lines.append(b">>>>>>>\n")
473 conflicts = True
475 conflicts = True
474 else:
476 else:
475 lines.extend(group[1])
477 lines.extend(group_lines)
476 return lines, conflicts
478 return lines, conflicts
477
479
478
480
479 def _resolve(m3, sides):
481 def _resolve(m3, sides):
480 lines = []
482 lines = []
481 for group in m3.merge_groups():
483 for what, group_lines in m3.merge_groups():
482 if group[0] == b'conflict':
484 if what == b'conflict':
483 for side in sides:
485 for side in sides:
484 lines.extend(group[side + 1])
486 lines.extend(group_lines[side])
485 else:
487 else:
486 lines.extend(group[1])
488 lines.extend(group_lines)
487 return lines
489 return lines
488
490
489
491
490 def simplemerge(ui, localctx, basectx, otherctx, **opts):
492 def simplemerge(ui, localctx, basectx, otherctx, **opts):
491 """Performs the simplemerge algorithm.
493 """Performs the simplemerge algorithm.
492
494
493 The merged result is written into `localctx`.
495 The merged result is written into `localctx`.
494 """
496 """
495
497
496 def readctx(ctx):
498 def readctx(ctx):
497 # Merges were always run in the working copy before, which means
499 # Merges were always run in the working copy before, which means
498 # they used decoded data, if the user defined any repository
500 # they used decoded data, if the user defined any repository
499 # filters.
501 # filters.
500 #
502 #
501 # Maintain that behavior today for BC, though perhaps in the future
503 # Maintain that behavior today for BC, though perhaps in the future
502 # it'd be worth considering whether merging encoded data (what the
504 # it'd be worth considering whether merging encoded data (what the
503 # repository usually sees) might be more useful.
505 # repository usually sees) might be more useful.
504 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
506 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
505
507
506 try:
508 try:
507 localtext = readctx(localctx)
509 localtext = readctx(localctx)
508 basetext = readctx(basectx)
510 basetext = readctx(basectx)
509 othertext = readctx(otherctx)
511 othertext = readctx(otherctx)
510 except error.Abort:
512 except error.Abort:
511 return 1
513 return 1
512
514
513 m3 = Merge3Text(basetext, localtext, othertext)
515 m3 = Merge3Text(basetext, localtext, othertext)
514 conflicts = False
516 conflicts = False
515 mode = opts.get('mode', b'merge')
517 mode = opts.get('mode', b'merge')
516 if mode == b'union':
518 if mode == b'union':
517 lines = _resolve(m3, (1, 2))
519 lines = _resolve(m3, (1, 2))
518 elif mode == b'local':
520 elif mode == b'local':
519 lines = _resolve(m3, (1,))
521 lines = _resolve(m3, (1,))
520 elif mode == b'other':
522 elif mode == b'other':
521 lines = _resolve(m3, (2,))
523 lines = _resolve(m3, (2,))
522 else:
524 else:
523 name_a, name_b, name_base = _picklabels(
525 name_a, name_b, name_base = _picklabels(
524 [localctx.path(), otherctx.path(), None], opts.get('label', [])
526 [localctx.path(), otherctx.path(), None], opts.get('label', [])
525 )
527 )
526 if mode == b'mergediff':
528 if mode == b'mergediff':
527 lines, conflicts = _mergediff(m3, name_a, name_b, name_base)
529 lines, conflicts = _mergediff(m3, name_a, name_b, name_base)
528 else:
530 else:
529 extrakwargs = {
531 extrakwargs = {
530 'minimize': True,
532 'minimize': True,
531 }
533 }
532 if name_base is not None:
534 if name_base is not None:
533 extrakwargs['base_marker'] = b'|||||||'
535 extrakwargs['base_marker'] = b'|||||||'
534 extrakwargs['name_base'] = name_base
536 extrakwargs['name_base'] = name_base
535 extrakwargs['minimize'] = False
537 extrakwargs['minimize'] = False
536 lines = list(
538 lines = list(
537 m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs)
539 m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs)
538 )
540 )
539 conflicts = m3.conflicts
541 conflicts = m3.conflicts
540
542
541 mergedtext = b''.join(lines)
543 mergedtext = b''.join(lines)
542 if opts.get('print'):
544 if opts.get('print'):
543 ui.fout.write(mergedtext)
545 ui.fout.write(mergedtext)
544 else:
546 else:
545 # localctx.flags() already has the merged flags (done in
547 # localctx.flags() already has the merged flags (done in
546 # mergestate.resolve())
548 # mergestate.resolve())
547 localctx.write(mergedtext, localctx.flags())
549 localctx.write(mergedtext, localctx.flags())
548
550
549 if conflicts:
551 if conflicts:
550 return 1
552 return 1
@@ -1,386 +1,386 b''
1 # Copyright (C) 2004, 2005 Canonical Ltd
1 # Copyright (C) 2004, 2005 Canonical Ltd
2 #
2 #
3 # This program is free software; you can redistribute it and/or modify
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
6 # (at your option) any later version.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU General Public License
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
15
15
16 from __future__ import absolute_import
16 from __future__ import absolute_import
17
17
18 import unittest
18 import unittest
19 from mercurial import (
19 from mercurial import (
20 error,
20 error,
21 simplemerge,
21 simplemerge,
22 util,
22 util,
23 )
23 )
24
24
25 from mercurial.utils import stringutil
25 from mercurial.utils import stringutil
26
26
27 TestCase = unittest.TestCase
27 TestCase = unittest.TestCase
28 # bzr compatible interface, for the tests
28 # bzr compatible interface, for the tests
29 class Merge3(simplemerge.Merge3Text):
29 class Merge3(simplemerge.Merge3Text):
30 """3-way merge of texts.
30 """3-way merge of texts.
31
31
32 Given BASE, OTHER, THIS, tries to produce a combined text
32 Given BASE, OTHER, THIS, tries to produce a combined text
33 incorporating the changes from both BASE->OTHER and BASE->THIS.
33 incorporating the changes from both BASE->OTHER and BASE->THIS.
34 All three will typically be sequences of lines."""
34 All three will typically be sequences of lines."""
35
35
36 def __init__(self, base, a, b):
36 def __init__(self, base, a, b):
37 basetext = b'\n'.join([i.strip(b'\n') for i in base] + [b''])
37 basetext = b'\n'.join([i.strip(b'\n') for i in base] + [b''])
38 atext = b'\n'.join([i.strip(b'\n') for i in a] + [b''])
38 atext = b'\n'.join([i.strip(b'\n') for i in a] + [b''])
39 btext = b'\n'.join([i.strip(b'\n') for i in b] + [b''])
39 btext = b'\n'.join([i.strip(b'\n') for i in b] + [b''])
40 if (
40 if (
41 stringutil.binary(basetext)
41 stringutil.binary(basetext)
42 or stringutil.binary(atext)
42 or stringutil.binary(atext)
43 or stringutil.binary(btext)
43 or stringutil.binary(btext)
44 ):
44 ):
45 raise error.Abort(b"don't know how to merge binary files")
45 raise error.Abort(b"don't know how to merge binary files")
46 simplemerge.Merge3Text.__init__(
46 simplemerge.Merge3Text.__init__(
47 self, basetext, atext, btext, base, a, b
47 self, basetext, atext, btext, base, a, b
48 )
48 )
49
49
50
50
51 CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
51 CantReprocessAndShowBase = simplemerge.CantReprocessAndShowBase
52
52
53
53
54 def split_lines(t):
54 def split_lines(t):
55 return util.stringio(t).readlines()
55 return util.stringio(t).readlines()
56
56
57
57
58 ############################################################
58 ############################################################
59 # test case data from the gnu diffutils manual
59 # test case data from the gnu diffutils manual
60 # common base
60 # common base
61 TZU = split_lines(
61 TZU = split_lines(
62 b""" The Nameless is the origin of Heaven and Earth;
62 b""" The Nameless is the origin of Heaven and Earth;
63 The named is the mother of all things.
63 The named is the mother of all things.
64
64
65 Therefore let there always be non-being,
65 Therefore let there always be non-being,
66 so we may see their subtlety,
66 so we may see their subtlety,
67 And let there always be being,
67 And let there always be being,
68 so we may see their outcome.
68 so we may see their outcome.
69 The two are the same,
69 The two are the same,
70 But after they are produced,
70 But after they are produced,
71 they have different names.
71 they have different names.
72 They both may be called deep and profound.
72 They both may be called deep and profound.
73 Deeper and more profound,
73 Deeper and more profound,
74 The door of all subtleties!
74 The door of all subtleties!
75 """
75 """
76 )
76 )
77
77
78 LAO = split_lines(
78 LAO = split_lines(
79 b""" The Way that can be told of is not the eternal Way;
79 b""" The Way that can be told of is not the eternal Way;
80 The name that can be named is not the eternal name.
80 The name that can be named is not the eternal name.
81 The Nameless is the origin of Heaven and Earth;
81 The Nameless is the origin of Heaven and Earth;
82 The Named is the mother of all things.
82 The Named is the mother of all things.
83 Therefore let there always be non-being,
83 Therefore let there always be non-being,
84 so we may see their subtlety,
84 so we may see their subtlety,
85 And let there always be being,
85 And let there always be being,
86 so we may see their outcome.
86 so we may see their outcome.
87 The two are the same,
87 The two are the same,
88 But after they are produced,
88 But after they are produced,
89 they have different names.
89 they have different names.
90 """
90 """
91 )
91 )
92
92
93
93
94 TAO = split_lines(
94 TAO = split_lines(
95 b""" The Way that can be told of is not the eternal Way;
95 b""" The Way that can be told of is not the eternal Way;
96 The name that can be named is not the eternal name.
96 The name that can be named is not the eternal name.
97 The Nameless is the origin of Heaven and Earth;
97 The Nameless is the origin of Heaven and Earth;
98 The named is the mother of all things.
98 The named is the mother of all things.
99
99
100 Therefore let there always be non-being,
100 Therefore let there always be non-being,
101 so we may see their subtlety,
101 so we may see their subtlety,
102 And let there always be being,
102 And let there always be being,
103 so we may see their result.
103 so we may see their result.
104 The two are the same,
104 The two are the same,
105 But after they are produced,
105 But after they are produced,
106 they have different names.
106 they have different names.
107
107
108 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
108 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
109
109
110 """
110 """
111 )
111 )
112
112
113 MERGED_RESULT = split_lines(
113 MERGED_RESULT = split_lines(
114 b"""\
114 b"""\
115 The Way that can be told of is not the eternal Way;
115 The Way that can be told of is not the eternal Way;
116 The name that can be named is not the eternal name.
116 The name that can be named is not the eternal name.
117 The Nameless is the origin of Heaven and Earth;
117 The Nameless is the origin of Heaven and Earth;
118 The Named is the mother of all things.
118 The Named is the mother of all things.
119 Therefore let there always be non-being,
119 Therefore let there always be non-being,
120 so we may see their subtlety,
120 so we may see their subtlety,
121 And let there always be being,
121 And let there always be being,
122 so we may see their result.
122 so we may see their result.
123 The two are the same,
123 The two are the same,
124 But after they are produced,
124 But after they are produced,
125 they have different names.\
125 they have different names.\
126 \n<<<<<<< LAO\
126 \n<<<<<<< LAO\
127 \n=======
127 \n=======
128
128
129 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
129 -- The Way of Lao-Tzu, tr. Wing-tsit Chan
130 \
130 \
131 \n>>>>>>> TAO
131 \n>>>>>>> TAO
132 """
132 """
133 )
133 )
134
134
135
135
136 class TestMerge3(TestCase):
136 class TestMerge3(TestCase):
137 def log(self, msg):
137 def log(self, msg):
138 pass
138 pass
139
139
140 def test_no_changes(self):
140 def test_no_changes(self):
141 """No conflicts because nothing changed"""
141 """No conflicts because nothing changed"""
142 m3 = Merge3([b'aaa', b'bbb'], [b'aaa', b'bbb'], [b'aaa', b'bbb'])
142 m3 = Merge3([b'aaa', b'bbb'], [b'aaa', b'bbb'], [b'aaa', b'bbb'])
143
143
144 self.assertEqual(
144 self.assertEqual(
145 list(m3.find_sync_regions()),
145 list(m3.find_sync_regions()),
146 [(0, 2, 0, 2, 0, 2), (2, 2, 2, 2, 2, 2)],
146 [(0, 2, 0, 2, 0, 2), (2, 2, 2, 2, 2, 2)],
147 )
147 )
148
148
149 self.assertEqual(list(m3.merge_regions()), [(b'unchanged', 0, 2)])
149 self.assertEqual(list(m3.merge_regions()), [(b'unchanged', 0, 2)])
150
150
151 self.assertEqual(
151 self.assertEqual(
152 list(m3.merge_groups()), [(b'unchanged', [b'aaa', b'bbb'])]
152 list(m3.merge_groups()), [(b'unchanged', [b'aaa', b'bbb'])]
153 )
153 )
154
154
155 def test_front_insert(self):
155 def test_front_insert(self):
156 m3 = Merge3([b'zz'], [b'aaa', b'bbb', b'zz'], [b'zz'])
156 m3 = Merge3([b'zz'], [b'aaa', b'bbb', b'zz'], [b'zz'])
157
157
158 # todo: should use a sentinel at end as from get_matching_blocks
158 # todo: should use a sentinel at end as from get_matching_blocks
159 # to match without zz
159 # to match without zz
160 self.assertEqual(
160 self.assertEqual(
161 list(m3.find_sync_regions()),
161 list(m3.find_sync_regions()),
162 [(0, 1, 2, 3, 0, 1), (1, 1, 3, 3, 1, 1)],
162 [(0, 1, 2, 3, 0, 1), (1, 1, 3, 3, 1, 1)],
163 )
163 )
164
164
165 self.assertEqual(
165 self.assertEqual(
166 list(m3.merge_regions()), [(b'a', 0, 2), (b'unchanged', 0, 1)]
166 list(m3.merge_regions()), [(b'a', 0, 2), (b'unchanged', 0, 1)]
167 )
167 )
168
168
169 self.assertEqual(
169 self.assertEqual(
170 list(m3.merge_groups()),
170 list(m3.merge_groups()),
171 [(b'a', [b'aaa', b'bbb']), (b'unchanged', [b'zz'])],
171 [(b'a', [b'aaa', b'bbb']), (b'unchanged', [b'zz'])],
172 )
172 )
173
173
174 def test_null_insert(self):
174 def test_null_insert(self):
175 m3 = Merge3([], [b'aaa', b'bbb'], [])
175 m3 = Merge3([], [b'aaa', b'bbb'], [])
176 # todo: should use a sentinel at end as from get_matching_blocks
176 # todo: should use a sentinel at end as from get_matching_blocks
177 # to match without zz
177 # to match without zz
178 self.assertEqual(list(m3.find_sync_regions()), [(0, 0, 2, 2, 0, 0)])
178 self.assertEqual(list(m3.find_sync_regions()), [(0, 0, 2, 2, 0, 0)])
179
179
180 self.assertEqual(list(m3.merge_regions()), [(b'a', 0, 2)])
180 self.assertEqual(list(m3.merge_regions()), [(b'a', 0, 2)])
181
181
182 self.assertEqual(list(m3.merge_lines()), [b'aaa', b'bbb'])
182 self.assertEqual(list(m3.merge_lines()), [b'aaa', b'bbb'])
183
183
184 def test_no_conflicts(self):
184 def test_no_conflicts(self):
185 """No conflicts because only one side changed"""
185 """No conflicts because only one side changed"""
186 m3 = Merge3(
186 m3 = Merge3(
187 [b'aaa', b'bbb'], [b'aaa', b'111', b'bbb'], [b'aaa', b'bbb']
187 [b'aaa', b'bbb'], [b'aaa', b'111', b'bbb'], [b'aaa', b'bbb']
188 )
188 )
189
189
190 self.assertEqual(
190 self.assertEqual(
191 list(m3.find_sync_regions()),
191 list(m3.find_sync_regions()),
192 [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 1, 2), (2, 2, 3, 3, 2, 2)],
192 [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 1, 2), (2, 2, 3, 3, 2, 2)],
193 )
193 )
194
194
195 self.assertEqual(
195 self.assertEqual(
196 list(m3.merge_regions()),
196 list(m3.merge_regions()),
197 [(b'unchanged', 0, 1), (b'a', 1, 2), (b'unchanged', 1, 2)],
197 [(b'unchanged', 0, 1), (b'a', 1, 2), (b'unchanged', 1, 2)],
198 )
198 )
199
199
200 def test_append_a(self):
200 def test_append_a(self):
201 m3 = Merge3(
201 m3 = Merge3(
202 [b'aaa\n', b'bbb\n'],
202 [b'aaa\n', b'bbb\n'],
203 [b'aaa\n', b'bbb\n', b'222\n'],
203 [b'aaa\n', b'bbb\n', b'222\n'],
204 [b'aaa\n', b'bbb\n'],
204 [b'aaa\n', b'bbb\n'],
205 )
205 )
206
206
207 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
207 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
208
208
209 def test_append_b(self):
209 def test_append_b(self):
210 m3 = Merge3(
210 m3 = Merge3(
211 [b'aaa\n', b'bbb\n'],
211 [b'aaa\n', b'bbb\n'],
212 [b'aaa\n', b'bbb\n'],
212 [b'aaa\n', b'bbb\n'],
213 [b'aaa\n', b'bbb\n', b'222\n'],
213 [b'aaa\n', b'bbb\n', b'222\n'],
214 )
214 )
215
215
216 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
216 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
217
217
218 def test_append_agreement(self):
218 def test_append_agreement(self):
219 m3 = Merge3(
219 m3 = Merge3(
220 [b'aaa\n', b'bbb\n'],
220 [b'aaa\n', b'bbb\n'],
221 [b'aaa\n', b'bbb\n', b'222\n'],
221 [b'aaa\n', b'bbb\n', b'222\n'],
222 [b'aaa\n', b'bbb\n', b'222\n'],
222 [b'aaa\n', b'bbb\n', b'222\n'],
223 )
223 )
224
224
225 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
225 self.assertEqual(b''.join(m3.merge_lines()), b'aaa\nbbb\n222\n')
226
226
227 def test_append_clash(self):
227 def test_append_clash(self):
228 m3 = Merge3(
228 m3 = Merge3(
229 [b'aaa\n', b'bbb\n'],
229 [b'aaa\n', b'bbb\n'],
230 [b'aaa\n', b'bbb\n', b'222\n'],
230 [b'aaa\n', b'bbb\n', b'222\n'],
231 [b'aaa\n', b'bbb\n', b'333\n'],
231 [b'aaa\n', b'bbb\n', b'333\n'],
232 )
232 )
233
233
234 ml = m3.merge_lines(
234 ml = m3.merge_lines(
235 name_a=b'a',
235 name_a=b'a',
236 name_b=b'b',
236 name_b=b'b',
237 start_marker=b'<<',
237 start_marker=b'<<',
238 mid_marker=b'--',
238 mid_marker=b'--',
239 end_marker=b'>>',
239 end_marker=b'>>',
240 )
240 )
241 self.assertEqual(
241 self.assertEqual(
242 b''.join(ml),
242 b''.join(ml),
243 b'aaa\n' b'bbb\n' b'<< a\n' b'222\n' b'--\n' b'333\n' b'>> b\n',
243 b'aaa\n' b'bbb\n' b'<< a\n' b'222\n' b'--\n' b'333\n' b'>> b\n',
244 )
244 )
245
245
246 def test_insert_agreement(self):
246 def test_insert_agreement(self):
247 m3 = Merge3(
247 m3 = Merge3(
248 [b'aaa\n', b'bbb\n'],
248 [b'aaa\n', b'bbb\n'],
249 [b'aaa\n', b'222\n', b'bbb\n'],
249 [b'aaa\n', b'222\n', b'bbb\n'],
250 [b'aaa\n', b'222\n', b'bbb\n'],
250 [b'aaa\n', b'222\n', b'bbb\n'],
251 )
251 )
252
252
253 ml = m3.merge_lines(
253 ml = m3.merge_lines(
254 name_a=b'a',
254 name_a=b'a',
255 name_b=b'b',
255 name_b=b'b',
256 start_marker=b'<<',
256 start_marker=b'<<',
257 mid_marker=b'--',
257 mid_marker=b'--',
258 end_marker=b'>>',
258 end_marker=b'>>',
259 )
259 )
260 self.assertEqual(b''.join(ml), b'aaa\n222\nbbb\n')
260 self.assertEqual(b''.join(ml), b'aaa\n222\nbbb\n')
261
261
262 def test_insert_clash(self):
262 def test_insert_clash(self):
263 """Both try to insert lines in the same place."""
263 """Both try to insert lines in the same place."""
264 m3 = Merge3(
264 m3 = Merge3(
265 [b'aaa\n', b'bbb\n'],
265 [b'aaa\n', b'bbb\n'],
266 [b'aaa\n', b'111\n', b'bbb\n'],
266 [b'aaa\n', b'111\n', b'bbb\n'],
267 [b'aaa\n', b'222\n', b'bbb\n'],
267 [b'aaa\n', b'222\n', b'bbb\n'],
268 )
268 )
269
269
270 self.assertEqual(
270 self.assertEqual(
271 list(m3.find_sync_regions()),
271 list(m3.find_sync_regions()),
272 [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 2, 3), (2, 2, 3, 3, 3, 3)],
272 [(0, 1, 0, 1, 0, 1), (1, 2, 2, 3, 2, 3), (2, 2, 3, 3, 3, 3)],
273 )
273 )
274
274
275 self.assertEqual(
275 self.assertEqual(
276 list(m3.merge_regions()),
276 list(m3.merge_regions()),
277 [
277 [
278 (b'unchanged', 0, 1),
278 (b'unchanged', 0, 1),
279 (b'conflict', 1, 1, 1, 2, 1, 2),
279 (b'conflict', 1, 1, 1, 2, 1, 2),
280 (b'unchanged', 1, 2),
280 (b'unchanged', 1, 2),
281 ],
281 ],
282 )
282 )
283
283
284 self.assertEqual(
284 self.assertEqual(
285 list(m3.merge_groups()),
285 list(m3.merge_groups()),
286 [
286 [
287 (b'unchanged', [b'aaa\n']),
287 (b'unchanged', [b'aaa\n']),
288 (b'conflict', [], [b'111\n'], [b'222\n']),
288 (b'conflict', ([], [b'111\n'], [b'222\n'])),
289 (b'unchanged', [b'bbb\n']),
289 (b'unchanged', [b'bbb\n']),
290 ],
290 ],
291 )
291 )
292
292
293 ml = m3.merge_lines(
293 ml = m3.merge_lines(
294 name_a=b'a',
294 name_a=b'a',
295 name_b=b'b',
295 name_b=b'b',
296 start_marker=b'<<',
296 start_marker=b'<<',
297 mid_marker=b'--',
297 mid_marker=b'--',
298 end_marker=b'>>',
298 end_marker=b'>>',
299 )
299 )
300 self.assertEqual(
300 self.assertEqual(
301 b''.join(ml),
301 b''.join(ml),
302 b'''aaa
302 b'''aaa
303 << a
303 << a
304 111
304 111
305 --
305 --
306 222
306 222
307 >> b
307 >> b
308 bbb
308 bbb
309 ''',
309 ''',
310 )
310 )
311
311
312 def test_replace_clash(self):
312 def test_replace_clash(self):
313 """Both try to insert lines in the same place."""
313 """Both try to insert lines in the same place."""
314 m3 = Merge3(
314 m3 = Merge3(
315 [b'aaa', b'000', b'bbb'],
315 [b'aaa', b'000', b'bbb'],
316 [b'aaa', b'111', b'bbb'],
316 [b'aaa', b'111', b'bbb'],
317 [b'aaa', b'222', b'bbb'],
317 [b'aaa', b'222', b'bbb'],
318 )
318 )
319
319
320 self.assertEqual(
320 self.assertEqual(
321 list(m3.find_sync_regions()),
321 list(m3.find_sync_regions()),
322 [(0, 1, 0, 1, 0, 1), (2, 3, 2, 3, 2, 3), (3, 3, 3, 3, 3, 3)],
322 [(0, 1, 0, 1, 0, 1), (2, 3, 2, 3, 2, 3), (3, 3, 3, 3, 3, 3)],
323 )
323 )
324
324
325 def test_replace_multi(self):
325 def test_replace_multi(self):
326 """Replacement with regions of different size."""
326 """Replacement with regions of different size."""
327 m3 = Merge3(
327 m3 = Merge3(
328 [b'aaa', b'000', b'000', b'bbb'],
328 [b'aaa', b'000', b'000', b'bbb'],
329 [b'aaa', b'111', b'111', b'111', b'bbb'],
329 [b'aaa', b'111', b'111', b'111', b'bbb'],
330 [b'aaa', b'222', b'222', b'222', b'222', b'bbb'],
330 [b'aaa', b'222', b'222', b'222', b'222', b'bbb'],
331 )
331 )
332
332
333 self.assertEqual(
333 self.assertEqual(
334 list(m3.find_sync_regions()),
334 list(m3.find_sync_regions()),
335 [(0, 1, 0, 1, 0, 1), (3, 4, 4, 5, 5, 6), (4, 4, 5, 5, 6, 6)],
335 [(0, 1, 0, 1, 0, 1), (3, 4, 4, 5, 5, 6), (4, 4, 5, 5, 6, 6)],
336 )
336 )
337
337
338 def test_merge_poem(self):
338 def test_merge_poem(self):
339 """Test case from diff3 manual"""
339 """Test case from diff3 manual"""
340 m3 = Merge3(TZU, LAO, TAO)
340 m3 = Merge3(TZU, LAO, TAO)
341 ml = list(m3.merge_lines(b'LAO', b'TAO'))
341 ml = list(m3.merge_lines(b'LAO', b'TAO'))
342 self.log(b'merge result:')
342 self.log(b'merge result:')
343 self.log(b''.join(ml))
343 self.log(b''.join(ml))
344 self.assertEqual(ml, MERGED_RESULT)
344 self.assertEqual(ml, MERGED_RESULT)
345
345
346 def test_binary(self):
346 def test_binary(self):
347 with self.assertRaises(error.Abort):
347 with self.assertRaises(error.Abort):
348 Merge3([b'\x00'], [b'a'], [b'b'])
348 Merge3([b'\x00'], [b'a'], [b'b'])
349
349
350 def test_dos_text(self):
350 def test_dos_text(self):
351 base_text = b'a\r\n'
351 base_text = b'a\r\n'
352 this_text = b'b\r\n'
352 this_text = b'b\r\n'
353 other_text = b'c\r\n'
353 other_text = b'c\r\n'
354 m3 = Merge3(
354 m3 = Merge3(
355 base_text.splitlines(True),
355 base_text.splitlines(True),
356 other_text.splitlines(True),
356 other_text.splitlines(True),
357 this_text.splitlines(True),
357 this_text.splitlines(True),
358 )
358 )
359 m_lines = m3.merge_lines(b'OTHER', b'THIS')
359 m_lines = m3.merge_lines(b'OTHER', b'THIS')
360 self.assertEqual(
360 self.assertEqual(
361 b'<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
361 b'<<<<<<< OTHER\r\nc\r\n=======\r\nb\r\n'
362 b'>>>>>>> THIS\r\n'.splitlines(True),
362 b'>>>>>>> THIS\r\n'.splitlines(True),
363 list(m_lines),
363 list(m_lines),
364 )
364 )
365
365
366 def test_mac_text(self):
366 def test_mac_text(self):
367 base_text = b'a\r'
367 base_text = b'a\r'
368 this_text = b'b\r'
368 this_text = b'b\r'
369 other_text = b'c\r'
369 other_text = b'c\r'
370 m3 = Merge3(
370 m3 = Merge3(
371 base_text.splitlines(True),
371 base_text.splitlines(True),
372 other_text.splitlines(True),
372 other_text.splitlines(True),
373 this_text.splitlines(True),
373 this_text.splitlines(True),
374 )
374 )
375 m_lines = m3.merge_lines(b'OTHER', b'THIS')
375 m_lines = m3.merge_lines(b'OTHER', b'THIS')
376 self.assertEqual(
376 self.assertEqual(
377 b'<<<<<<< OTHER\rc\r=======\rb\r'
377 b'<<<<<<< OTHER\rc\r=======\rb\r'
378 b'>>>>>>> THIS\r'.splitlines(True),
378 b'>>>>>>> THIS\r'.splitlines(True),
379 list(m_lines),
379 list(m_lines),
380 )
380 )
381
381
382
382
383 if __name__ == '__main__':
383 if __name__ == '__main__':
384 import silenttestrunner
384 import silenttestrunner
385
385
386 silenttestrunner.main(__name__)
386 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now