##// END OF EJS Templates
simplemerge: make minimize() a free function...
Martin von Zweigbergk -
r49395:a0cb1623 default draft
parent child Browse files
Show More
@@ -1,531 +1,530 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_groups(self):
90 def merge_groups(self):
91 """Yield sequence of line groups. Each one is a tuple:
91 """Yield sequence of line groups. Each one is a tuple:
92
92
93 'unchanged', lines
93 'unchanged', lines
94 Lines unchanged from base
94 Lines unchanged from base
95
95
96 'a', lines
96 'a', lines
97 Lines taken from a
97 Lines taken from a
98
98
99 'same', lines
99 'same', lines
100 Lines taken from a (and equal to b)
100 Lines taken from a (and equal to b)
101
101
102 'b', lines
102 'b', lines
103 Lines taken from b
103 Lines taken from b
104
104
105 'conflict', (base_lines, a_lines, b_lines)
105 'conflict', (base_lines, a_lines, b_lines)
106 Lines from base were changed to either a or b and conflict.
106 Lines from base were changed to either a or b and conflict.
107 """
107 """
108 for t in self.merge_regions():
108 for t in self.merge_regions():
109 what = t[0]
109 what = t[0]
110 if what == b'unchanged':
110 if what == b'unchanged':
111 yield what, self.base[t[1] : t[2]]
111 yield what, self.base[t[1] : t[2]]
112 elif what == b'a' or what == b'same':
112 elif what == b'a' or what == b'same':
113 yield what, self.a[t[1] : t[2]]
113 yield what, self.a[t[1] : t[2]]
114 elif what == b'b':
114 elif what == b'b':
115 yield what, self.b[t[1] : t[2]]
115 yield what, self.b[t[1] : t[2]]
116 elif what == b'conflict':
116 elif what == b'conflict':
117 yield (
117 yield (
118 what,
118 what,
119 (
119 (
120 self.base[t[1] : t[2]],
120 self.base[t[1] : t[2]],
121 self.a[t[3] : t[4]],
121 self.a[t[3] : t[4]],
122 self.b[t[5] : t[6]],
122 self.b[t[5] : t[6]],
123 ),
123 ),
124 )
124 )
125 else:
125 else:
126 raise ValueError(what)
126 raise ValueError(what)
127
127
128 def merge_regions(self):
128 def merge_regions(self):
129 """Return sequences of matching and conflicting regions.
129 """Return sequences of matching and conflicting regions.
130
130
131 This returns tuples, where the first value says what kind we
131 This returns tuples, where the first value says what kind we
132 have:
132 have:
133
133
134 'unchanged', start, end
134 'unchanged', start, end
135 Take a region of base[start:end]
135 Take a region of base[start:end]
136
136
137 'same', astart, aend
137 'same', astart, aend
138 b and a are different from base but give the same result
138 b and a are different from base but give the same result
139
139
140 'a', start, end
140 'a', start, end
141 Non-clashing insertion from a[start:end]
141 Non-clashing insertion from a[start:end]
142
142
143 'conflict', zstart, zend, astart, aend, bstart, bend
143 'conflict', zstart, zend, astart, aend, bstart, bend
144 Conflict between a and b, with z as common ancestor
144 Conflict between a and b, with z as common ancestor
145
145
146 Method is as follows:
146 Method is as follows:
147
147
148 The two sequences align only on regions which match the base
148 The two sequences align only on regions which match the base
149 and both descendants. These are found by doing a two-way diff
149 and both descendants. These are found by doing a two-way diff
150 of each one against the base, and then finding the
150 of each one against the base, and then finding the
151 intersections between those regions. These "sync regions"
151 intersections between those regions. These "sync regions"
152 are by definition unchanged in both and easily dealt with.
152 are by definition unchanged in both and easily dealt with.
153
153
154 The regions in between can be in any of three cases:
154 The regions in between can be in any of three cases:
155 conflicted, or changed on only one side.
155 conflicted, or changed on only one side.
156 """
156 """
157
157
158 # section a[0:ia] has been disposed of, etc
158 # section a[0:ia] has been disposed of, etc
159 iz = ia = ib = 0
159 iz = ia = ib = 0
160
160
161 for region in self.find_sync_regions():
161 for region in self.find_sync_regions():
162 zmatch, zend, amatch, aend, bmatch, bend = region
162 zmatch, zend, amatch, aend, bmatch, bend = region
163 # print 'match base [%d:%d]' % (zmatch, zend)
163 # print 'match base [%d:%d]' % (zmatch, zend)
164
164
165 matchlen = zend - zmatch
165 matchlen = zend - zmatch
166 assert matchlen >= 0
166 assert matchlen >= 0
167 assert matchlen == (aend - amatch)
167 assert matchlen == (aend - amatch)
168 assert matchlen == (bend - bmatch)
168 assert matchlen == (bend - bmatch)
169
169
170 len_a = amatch - ia
170 len_a = amatch - ia
171 len_b = bmatch - ib
171 len_b = bmatch - ib
172 len_base = zmatch - iz
172 len_base = zmatch - iz
173 assert len_a >= 0
173 assert len_a >= 0
174 assert len_b >= 0
174 assert len_b >= 0
175 assert len_base >= 0
175 assert len_base >= 0
176
176
177 # print 'unmatched a=%d, b=%d' % (len_a, len_b)
177 # print 'unmatched a=%d, b=%d' % (len_a, len_b)
178
178
179 if len_a or len_b:
179 if len_a or len_b:
180 # try to avoid actually slicing the lists
180 # try to avoid actually slicing the lists
181 equal_a = compare_range(
181 equal_a = compare_range(
182 self.a, ia, amatch, self.base, iz, zmatch
182 self.a, ia, amatch, self.base, iz, zmatch
183 )
183 )
184 equal_b = compare_range(
184 equal_b = compare_range(
185 self.b, ib, bmatch, self.base, iz, zmatch
185 self.b, ib, bmatch, self.base, iz, zmatch
186 )
186 )
187 same = compare_range(self.a, ia, amatch, self.b, ib, bmatch)
187 same = compare_range(self.a, ia, amatch, self.b, ib, bmatch)
188
188
189 if same:
189 if same:
190 yield b'same', ia, amatch
190 yield b'same', ia, amatch
191 elif equal_a and not equal_b:
191 elif equal_a and not equal_b:
192 yield b'b', ib, bmatch
192 yield b'b', ib, bmatch
193 elif equal_b and not equal_a:
193 elif equal_b and not equal_a:
194 yield b'a', ia, amatch
194 yield b'a', ia, amatch
195 elif not equal_a and not equal_b:
195 elif not equal_a and not equal_b:
196 yield b'conflict', iz, zmatch, ia, amatch, ib, bmatch
196 yield b'conflict', iz, zmatch, ia, amatch, ib, bmatch
197 else:
197 else:
198 raise AssertionError(b"can't handle a=b=base but unmatched")
198 raise AssertionError(b"can't handle a=b=base but unmatched")
199
199
200 ia = amatch
200 ia = amatch
201 ib = bmatch
201 ib = bmatch
202 iz = zmatch
202 iz = zmatch
203
203
204 # if the same part of the base was deleted on both sides
204 # if the same part of the base was deleted on both sides
205 # that's OK, we can just skip it.
205 # that's OK, we can just skip it.
206
206
207 if matchlen > 0:
207 if matchlen > 0:
208 assert ia == amatch
208 assert ia == amatch
209 assert ib == bmatch
209 assert ib == bmatch
210 assert iz == zmatch
210 assert iz == zmatch
211
211
212 yield b'unchanged', zmatch, zend
212 yield b'unchanged', zmatch, zend
213 iz = zend
213 iz = zend
214 ia = aend
214 ia = aend
215 ib = bend
215 ib = bend
216
216
217 def minimize(self, merge_groups):
218 """Trim conflict regions of lines where A and B sides match.
219
220 Lines where both A and B have made the same changes at the beginning
221 or the end of each merge region are eliminated from the conflict
222 region and are instead considered the same.
223 """
224 for what, lines in merge_groups:
225 if what != b"conflict":
226 yield what, lines
227 continue
228 base_lines, a_lines, b_lines = lines
229 alen = len(a_lines)
230 blen = len(b_lines)
231
232 # find matches at the front
233 ii = 0
234 while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]:
235 ii += 1
236 startmatches = ii
237
238 # find matches at the end
239 ii = 0
240 while (
241 ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]
242 ):
243 ii += 1
244 endmatches = ii
245
246 if startmatches > 0:
247 yield b'same', a_lines[:startmatches]
248
249 yield (
250 b'conflict',
251 (
252 base_lines,
253 a_lines[startmatches : alen - endmatches],
254 b_lines[startmatches : blen - endmatches],
255 ),
256 )
257
258 if endmatches > 0:
259 yield b'same', a_lines[alen - endmatches :]
260
261 def find_sync_regions(self):
217 def find_sync_regions(self):
262 """Return a list of sync regions, where both descendants match the base.
218 """Return a list of sync regions, where both descendants match the base.
263
219
264 Generates a list of (base1, base2, a1, a2, b1, b2). There is
220 Generates a list of (base1, base2, a1, a2, b1, b2). There is
265 always a zero-length sync region at the end of all the files.
221 always a zero-length sync region at the end of all the files.
266 """
222 """
267
223
268 ia = ib = 0
224 ia = ib = 0
269 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
225 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
270 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
226 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
271 len_a = len(amatches)
227 len_a = len(amatches)
272 len_b = len(bmatches)
228 len_b = len(bmatches)
273
229
274 sl = []
230 sl = []
275
231
276 while ia < len_a and ib < len_b:
232 while ia < len_a and ib < len_b:
277 abase, amatch, alen = amatches[ia]
233 abase, amatch, alen = amatches[ia]
278 bbase, bmatch, blen = bmatches[ib]
234 bbase, bmatch, blen = bmatches[ib]
279
235
280 # there is an unconflicted block at i; how long does it
236 # there is an unconflicted block at i; how long does it
281 # extend? until whichever one ends earlier.
237 # extend? until whichever one ends earlier.
282 i = intersect((abase, abase + alen), (bbase, bbase + blen))
238 i = intersect((abase, abase + alen), (bbase, bbase + blen))
283 if i:
239 if i:
284 intbase = i[0]
240 intbase = i[0]
285 intend = i[1]
241 intend = i[1]
286 intlen = intend - intbase
242 intlen = intend - intbase
287
243
288 # found a match of base[i[0], i[1]]; this may be less than
244 # found a match of base[i[0], i[1]]; this may be less than
289 # the region that matches in either one
245 # the region that matches in either one
290 assert intlen <= alen
246 assert intlen <= alen
291 assert intlen <= blen
247 assert intlen <= blen
292 assert abase <= intbase
248 assert abase <= intbase
293 assert bbase <= intbase
249 assert bbase <= intbase
294
250
295 asub = amatch + (intbase - abase)
251 asub = amatch + (intbase - abase)
296 bsub = bmatch + (intbase - bbase)
252 bsub = bmatch + (intbase - bbase)
297 aend = asub + intlen
253 aend = asub + intlen
298 bend = bsub + intlen
254 bend = bsub + intlen
299
255
300 assert self.base[intbase:intend] == self.a[asub:aend], (
256 assert self.base[intbase:intend] == self.a[asub:aend], (
301 self.base[intbase:intend],
257 self.base[intbase:intend],
302 self.a[asub:aend],
258 self.a[asub:aend],
303 )
259 )
304
260
305 assert self.base[intbase:intend] == self.b[bsub:bend]
261 assert self.base[intbase:intend] == self.b[bsub:bend]
306
262
307 sl.append((intbase, intend, asub, aend, bsub, bend))
263 sl.append((intbase, intend, asub, aend, bsub, bend))
308
264
309 # advance whichever one ends first in the base text
265 # advance whichever one ends first in the base text
310 if (abase + alen) < (bbase + blen):
266 if (abase + alen) < (bbase + blen):
311 ia += 1
267 ia += 1
312 else:
268 else:
313 ib += 1
269 ib += 1
314
270
315 intbase = len(self.base)
271 intbase = len(self.base)
316 abase = len(self.a)
272 abase = len(self.a)
317 bbase = len(self.b)
273 bbase = len(self.b)
318 sl.append((intbase, intbase, abase, abase, bbase, bbase))
274 sl.append((intbase, intbase, abase, abase, bbase, bbase))
319
275
320 return sl
276 return sl
321
277
322
278
323 def _verifytext(text, path, ui, opts):
279 def _verifytext(text, path, ui, opts):
324 """verifies that text is non-binary (unless opts[text] is passed,
280 """verifies that text is non-binary (unless opts[text] is passed,
325 then we just warn)"""
281 then we just warn)"""
326 if stringutil.binary(text):
282 if stringutil.binary(text):
327 msg = _(b"%s looks like a binary file.") % path
283 msg = _(b"%s looks like a binary file.") % path
328 if not opts.get('quiet'):
284 if not opts.get('quiet'):
329 ui.warn(_(b'warning: %s\n') % msg)
285 ui.warn(_(b'warning: %s\n') % msg)
330 if not opts.get('text'):
286 if not opts.get('text'):
331 raise error.Abort(msg)
287 raise error.Abort(msg)
332 return text
288 return text
333
289
334
290
335 def _picklabels(overrides):
291 def _picklabels(overrides):
336 if len(overrides) > 3:
292 if len(overrides) > 3:
337 raise error.Abort(_(b"can only specify three labels."))
293 raise error.Abort(_(b"can only specify three labels."))
338 result = [None, None, None]
294 result = [None, None, None]
339 for i, override in enumerate(overrides):
295 for i, override in enumerate(overrides):
340 result[i] = override
296 result[i] = override
341 return result
297 return result
342
298
343
299
344 def _detect_newline(m3):
300 def _detect_newline(m3):
345 if len(m3.a) > 0:
301 if len(m3.a) > 0:
346 if m3.a[0].endswith(b'\r\n'):
302 if m3.a[0].endswith(b'\r\n'):
347 return b'\r\n'
303 return b'\r\n'
348 elif m3.a[0].endswith(b'\r'):
304 elif m3.a[0].endswith(b'\r'):
349 return b'\r'
305 return b'\r'
350 return b'\n'
306 return b'\n'
351
307
352
308
309 def _minimize(merge_groups):
310 """Trim conflict regions of lines where A and B sides match.
311
312 Lines where both A and B have made the same changes at the beginning
313 or the end of each merge region are eliminated from the conflict
314 region and are instead considered the same.
315 """
316 for what, lines in merge_groups:
317 if what != b"conflict":
318 yield what, lines
319 continue
320 base_lines, a_lines, b_lines = lines
321 alen = len(a_lines)
322 blen = len(b_lines)
323
324 # find matches at the front
325 ii = 0
326 while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]:
327 ii += 1
328 startmatches = ii
329
330 # find matches at the end
331 ii = 0
332 while ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]:
333 ii += 1
334 endmatches = ii
335
336 if startmatches > 0:
337 yield b'same', a_lines[:startmatches]
338
339 yield (
340 b'conflict',
341 (
342 base_lines,
343 a_lines[startmatches : alen - endmatches],
344 b_lines[startmatches : blen - endmatches],
345 ),
346 )
347
348 if endmatches > 0:
349 yield b'same', a_lines[alen - endmatches :]
350
351
353 def render_minimized(
352 def render_minimized(
354 m3,
353 m3,
355 name_a=None,
354 name_a=None,
356 name_b=None,
355 name_b=None,
357 start_marker=b'<<<<<<<',
356 start_marker=b'<<<<<<<',
358 mid_marker=b'=======',
357 mid_marker=b'=======',
359 end_marker=b'>>>>>>>',
358 end_marker=b'>>>>>>>',
360 ):
359 ):
361 """Return merge in cvs-like form."""
360 """Return merge in cvs-like form."""
362 newline = _detect_newline(m3)
361 newline = _detect_newline(m3)
363 conflicts = False
362 conflicts = False
364 if name_a:
363 if name_a:
365 start_marker = start_marker + b' ' + name_a
364 start_marker = start_marker + b' ' + name_a
366 if name_b:
365 if name_b:
367 end_marker = end_marker + b' ' + name_b
366 end_marker = end_marker + b' ' + name_b
368 merge_groups = m3.merge_groups()
367 merge_groups = m3.merge_groups()
369 merge_groups = m3.minimize(merge_groups)
368 merge_groups = _minimize(merge_groups)
370 lines = []
369 lines = []
371 for what, group_lines in merge_groups:
370 for what, group_lines in merge_groups:
372 if what == b'conflict':
371 if what == b'conflict':
373 base_lines, a_lines, b_lines = group_lines
372 base_lines, a_lines, b_lines = group_lines
374 conflicts = True
373 conflicts = True
375 lines.append(start_marker + newline)
374 lines.append(start_marker + newline)
376 lines.extend(a_lines)
375 lines.extend(a_lines)
377 lines.append(mid_marker + newline)
376 lines.append(mid_marker + newline)
378 lines.extend(b_lines)
377 lines.extend(b_lines)
379 lines.append(end_marker + newline)
378 lines.append(end_marker + newline)
380 else:
379 else:
381 lines.extend(group_lines)
380 lines.extend(group_lines)
382 return lines, conflicts
381 return lines, conflicts
383
382
384
383
385 def render_merge3(m3, name_a, name_b, name_base):
384 def render_merge3(m3, name_a, name_b, name_base):
386 """Render conflicts as 3-way conflict markers."""
385 """Render conflicts as 3-way conflict markers."""
387 newline = _detect_newline(m3)
386 newline = _detect_newline(m3)
388 conflicts = False
387 conflicts = False
389 lines = []
388 lines = []
390 for what, group_lines in m3.merge_groups():
389 for what, group_lines in m3.merge_groups():
391 if what == b'conflict':
390 if what == b'conflict':
392 base_lines, a_lines, b_lines = group_lines
391 base_lines, a_lines, b_lines = group_lines
393 conflicts = True
392 conflicts = True
394 lines.append(b'<<<<<<< ' + name_a + newline)
393 lines.append(b'<<<<<<< ' + name_a + newline)
395 lines.extend(a_lines)
394 lines.extend(a_lines)
396 lines.append(b'||||||| ' + name_base + newline)
395 lines.append(b'||||||| ' + name_base + newline)
397 lines.extend(base_lines)
396 lines.extend(base_lines)
398 lines.append(b'=======' + newline)
397 lines.append(b'=======' + newline)
399 lines.extend(b_lines)
398 lines.extend(b_lines)
400 lines.append(b'>>>>>>> ' + name_b + newline)
399 lines.append(b'>>>>>>> ' + name_b + newline)
401 else:
400 else:
402 lines.extend(group_lines)
401 lines.extend(group_lines)
403 return lines, conflicts
402 return lines, conflicts
404
403
405
404
406 def render_mergediff(m3, name_a, name_b, name_base):
405 def render_mergediff(m3, name_a, name_b, name_base):
407 """Render conflicts as conflict markers with one snapshot and one diff."""
406 """Render conflicts as conflict markers with one snapshot and one diff."""
408 newline = _detect_newline(m3)
407 newline = _detect_newline(m3)
409 lines = []
408 lines = []
410 conflicts = False
409 conflicts = False
411 for what, group_lines in m3.merge_groups():
410 for what, group_lines in m3.merge_groups():
412 if what == b'conflict':
411 if what == b'conflict':
413 base_lines, a_lines, b_lines = group_lines
412 base_lines, a_lines, b_lines = group_lines
414 base_text = b''.join(base_lines)
413 base_text = b''.join(base_lines)
415 b_blocks = list(
414 b_blocks = list(
416 mdiff.allblocks(
415 mdiff.allblocks(
417 base_text,
416 base_text,
418 b''.join(b_lines),
417 b''.join(b_lines),
419 lines1=base_lines,
418 lines1=base_lines,
420 lines2=b_lines,
419 lines2=b_lines,
421 )
420 )
422 )
421 )
423 a_blocks = list(
422 a_blocks = list(
424 mdiff.allblocks(
423 mdiff.allblocks(
425 base_text,
424 base_text,
426 b''.join(a_lines),
425 b''.join(a_lines),
427 lines1=base_lines,
426 lines1=base_lines,
428 lines2=b_lines,
427 lines2=b_lines,
429 )
428 )
430 )
429 )
431
430
432 def matching_lines(blocks):
431 def matching_lines(blocks):
433 return sum(
432 return sum(
434 block[1] - block[0]
433 block[1] - block[0]
435 for block, kind in blocks
434 for block, kind in blocks
436 if kind == b'='
435 if kind == b'='
437 )
436 )
438
437
439 def diff_lines(blocks, lines1, lines2):
438 def diff_lines(blocks, lines1, lines2):
440 for block, kind in blocks:
439 for block, kind in blocks:
441 if kind == b'=':
440 if kind == b'=':
442 for line in lines1[block[0] : block[1]]:
441 for line in lines1[block[0] : block[1]]:
443 yield b' ' + line
442 yield b' ' + line
444 else:
443 else:
445 for line in lines1[block[0] : block[1]]:
444 for line in lines1[block[0] : block[1]]:
446 yield b'-' + line
445 yield b'-' + line
447 for line in lines2[block[2] : block[3]]:
446 for line in lines2[block[2] : block[3]]:
448 yield b'+' + line
447 yield b'+' + line
449
448
450 lines.append(b"<<<<<<<" + newline)
449 lines.append(b"<<<<<<<" + newline)
451 if matching_lines(a_blocks) < matching_lines(b_blocks):
450 if matching_lines(a_blocks) < matching_lines(b_blocks):
452 lines.append(b"======= " + name_a + newline)
451 lines.append(b"======= " + name_a + newline)
453 lines.extend(a_lines)
452 lines.extend(a_lines)
454 lines.append(b"------- " + name_base + newline)
453 lines.append(b"------- " + name_base + newline)
455 lines.append(b"+++++++ " + name_b + newline)
454 lines.append(b"+++++++ " + name_b + newline)
456 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
455 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
457 else:
456 else:
458 lines.append(b"------- " + name_base + newline)
457 lines.append(b"------- " + name_base + newline)
459 lines.append(b"+++++++ " + name_a + newline)
458 lines.append(b"+++++++ " + name_a + newline)
460 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
459 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
461 lines.append(b"======= " + name_b + newline)
460 lines.append(b"======= " + name_b + newline)
462 lines.extend(b_lines)
461 lines.extend(b_lines)
463 lines.append(b">>>>>>>" + newline)
462 lines.append(b">>>>>>>" + newline)
464 conflicts = True
463 conflicts = True
465 else:
464 else:
466 lines.extend(group_lines)
465 lines.extend(group_lines)
467 return lines, conflicts
466 return lines, conflicts
468
467
469
468
470 def _resolve(m3, sides):
469 def _resolve(m3, sides):
471 lines = []
470 lines = []
472 for what, group_lines in m3.merge_groups():
471 for what, group_lines in m3.merge_groups():
473 if what == b'conflict':
472 if what == b'conflict':
474 for side in sides:
473 for side in sides:
475 lines.extend(group_lines[side])
474 lines.extend(group_lines[side])
476 else:
475 else:
477 lines.extend(group_lines)
476 lines.extend(group_lines)
478 return lines
477 return lines
479
478
480
479
481 def simplemerge(ui, localctx, basectx, otherctx, **opts):
480 def simplemerge(ui, localctx, basectx, otherctx, **opts):
482 """Performs the simplemerge algorithm.
481 """Performs the simplemerge algorithm.
483
482
484 The merged result is written into `localctx`.
483 The merged result is written into `localctx`.
485 """
484 """
486
485
487 def readctx(ctx):
486 def readctx(ctx):
488 # Merges were always run in the working copy before, which means
487 # Merges were always run in the working copy before, which means
489 # they used decoded data, if the user defined any repository
488 # they used decoded data, if the user defined any repository
490 # filters.
489 # filters.
491 #
490 #
492 # Maintain that behavior today for BC, though perhaps in the future
491 # Maintain that behavior today for BC, though perhaps in the future
493 # it'd be worth considering whether merging encoded data (what the
492 # it'd be worth considering whether merging encoded data (what the
494 # repository usually sees) might be more useful.
493 # repository usually sees) might be more useful.
495 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
494 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
496
495
497 try:
496 try:
498 localtext = readctx(localctx)
497 localtext = readctx(localctx)
499 basetext = readctx(basectx)
498 basetext = readctx(basectx)
500 othertext = readctx(otherctx)
499 othertext = readctx(otherctx)
501 except error.Abort:
500 except error.Abort:
502 return 1
501 return 1
503
502
504 m3 = Merge3Text(basetext, localtext, othertext)
503 m3 = Merge3Text(basetext, localtext, othertext)
505 conflicts = False
504 conflicts = False
506 mode = opts.get('mode', b'merge')
505 mode = opts.get('mode', b'merge')
507 if mode == b'union':
506 if mode == b'union':
508 lines = _resolve(m3, (1, 2))
507 lines = _resolve(m3, (1, 2))
509 elif mode == b'local':
508 elif mode == b'local':
510 lines = _resolve(m3, (1,))
509 lines = _resolve(m3, (1,))
511 elif mode == b'other':
510 elif mode == b'other':
512 lines = _resolve(m3, (2,))
511 lines = _resolve(m3, (2,))
513 else:
512 else:
514 name_a, name_b, name_base = _picklabels(opts.get('label', []))
513 name_a, name_b, name_base = _picklabels(opts.get('label', []))
515 if mode == b'mergediff':
514 if mode == b'mergediff':
516 lines, conflicts = render_mergediff(m3, name_a, name_b, name_base)
515 lines, conflicts = render_mergediff(m3, name_a, name_b, name_base)
517 elif mode == b'merge3':
516 elif mode == b'merge3':
518 lines, conflicts = render_merge3(m3, name_a, name_b, name_base)
517 lines, conflicts = render_merge3(m3, name_a, name_b, name_base)
519 else:
518 else:
520 lines, conflicts = render_minimized(m3, name_a, name_b)
519 lines, conflicts = render_minimized(m3, name_a, name_b)
521
520
522 mergedtext = b''.join(lines)
521 mergedtext = b''.join(lines)
523 if opts.get('print'):
522 if opts.get('print'):
524 ui.fout.write(mergedtext)
523 ui.fout.write(mergedtext)
525 else:
524 else:
526 # localctx.flags() already has the merged flags (done in
525 # localctx.flags() already has the merged flags (done in
527 # mergestate.resolve())
526 # mergestate.resolve())
528 localctx.write(mergedtext, localctx.flags())
527 localctx.write(mergedtext, localctx.flags())
529
528
530 if conflicts:
529 if conflicts:
531 return 1
530 return 1
General Comments 0
You need to be logged in to leave comments. Login now