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