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