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