##// END OF EJS Templates
simplemerge: move default labels to simplemerge extension...
Martin von Zweigbergk -
r49392:36f15913 default draft
parent child Browse files
Show More
@@ -1,104 +1,108 b''
1 #!/usr/bin/env python3
1 #!/usr/bin/env python3
2 from __future__ import absolute_import
2 from __future__ import absolute_import
3
3
4 import getopt
4 import getopt
5 import sys
5 import sys
6
6
7 import hgdemandimport
7 import hgdemandimport
8
8
9 hgdemandimport.enable()
9 hgdemandimport.enable()
10
10
11 from mercurial.i18n import _
11 from mercurial.i18n import _
12 from mercurial import (
12 from mercurial import (
13 context,
13 context,
14 error,
14 error,
15 fancyopts,
15 fancyopts,
16 pycompat,
16 pycompat,
17 simplemerge,
17 simplemerge,
18 ui as uimod,
18 ui as uimod,
19 )
19 )
20 from mercurial.utils import procutil, stringutil
20 from mercurial.utils import procutil, stringutil
21
21
22 options = [
22 options = [
23 (b'L', b'label', [], _(b'labels to use on conflict markers')),
23 (b'L', b'label', [], _(b'labels to use on conflict markers')),
24 (b'a', b'text', None, _(b'treat all files as text')),
24 (b'a', b'text', None, _(b'treat all files as text')),
25 (b'p', b'print', None, _(b'print results instead of overwriting LOCAL')),
25 (b'p', b'print', None, _(b'print results instead of overwriting LOCAL')),
26 (b'', b'no-minimal', None, _(b'no effect (DEPRECATED)')),
26 (b'', b'no-minimal', None, _(b'no effect (DEPRECATED)')),
27 (b'h', b'help', None, _(b'display help and exit')),
27 (b'h', b'help', None, _(b'display help and exit')),
28 (b'q', b'quiet', None, _(b'suppress output')),
28 (b'q', b'quiet', None, _(b'suppress output')),
29 ]
29 ]
30
30
31 usage = _(
31 usage = _(
32 b'''simplemerge [OPTS] LOCAL BASE OTHER
32 b'''simplemerge [OPTS] LOCAL BASE OTHER
33
33
34 Simple three-way file merge utility with a minimal feature set.
34 Simple three-way file merge utility with a minimal feature set.
35
35
36 Apply to LOCAL the changes necessary to go from BASE to OTHER.
36 Apply to LOCAL the changes necessary to go from BASE to OTHER.
37
37
38 By default, LOCAL is overwritten with the results of this operation.
38 By default, LOCAL is overwritten with the results of this operation.
39 '''
39 '''
40 )
40 )
41
41
42
42
43 class ParseError(Exception):
43 class ParseError(Exception):
44 """Exception raised on errors in parsing the command line."""
44 """Exception raised on errors in parsing the command line."""
45
45
46
46
47 def showhelp():
47 def showhelp():
48 procutil.stdout.write(usage)
48 procutil.stdout.write(usage)
49 procutil.stdout.write(b'\noptions:\n')
49 procutil.stdout.write(b'\noptions:\n')
50
50
51 out_opts = []
51 out_opts = []
52 for shortopt, longopt, default, desc in options:
52 for shortopt, longopt, default, desc in options:
53 out_opts.append(
53 out_opts.append(
54 (
54 (
55 b'%2s%s'
55 b'%2s%s'
56 % (
56 % (
57 shortopt and b'-%s' % shortopt,
57 shortopt and b'-%s' % shortopt,
58 longopt and b' --%s' % longopt,
58 longopt and b' --%s' % longopt,
59 ),
59 ),
60 b'%s' % desc,
60 b'%s' % desc,
61 )
61 )
62 )
62 )
63 opts_len = max([len(opt[0]) for opt in out_opts])
63 opts_len = max([len(opt[0]) for opt in out_opts])
64 for first, second in out_opts:
64 for first, second in out_opts:
65 procutil.stdout.write(b' %-*s %s\n' % (opts_len, first, second))
65 procutil.stdout.write(b' %-*s %s\n' % (opts_len, first, second))
66
66
67
67
68 try:
68 try:
69 for fp in (sys.stdin, procutil.stdout, sys.stderr):
69 for fp in (sys.stdin, procutil.stdout, sys.stderr):
70 procutil.setbinary(fp)
70 procutil.setbinary(fp)
71
71
72 opts = {}
72 opts = {}
73 try:
73 try:
74 bargv = [a.encode('utf8') for a in sys.argv[1:]]
74 bargv = [a.encode('utf8') for a in sys.argv[1:]]
75 args = fancyopts.fancyopts(bargv, options, opts)
75 args = fancyopts.fancyopts(bargv, options, opts)
76 except getopt.GetoptError as e:
76 except getopt.GetoptError as e:
77 raise ParseError(e)
77 raise ParseError(e)
78 if opts[b'help']:
78 if opts[b'help']:
79 showhelp()
79 showhelp()
80 sys.exit(0)
80 sys.exit(0)
81 if len(args) != 3:
81 if len(args) != 3:
82 raise ParseError(_(b'wrong number of arguments').decode('utf8'))
82 raise ParseError(_(b'wrong number of arguments').decode('utf8'))
83 if len(opts[b'label']) > 2:
83 if len(opts[b'label']) > 2:
84 opts[b'mode'] = b'merge3'
84 opts[b'mode'] = b'merge3'
85 local, base, other = args
85 local, base, other = args
86 overrides = opts[b'label']
87 labels = [local, other, base]
88 labels[: len(overrides)] = overrides
89 opts[b'label'] = labels
86 sys.exit(
90 sys.exit(
87 simplemerge.simplemerge(
91 simplemerge.simplemerge(
88 uimod.ui.load(),
92 uimod.ui.load(),
89 context.arbitraryfilectx(local),
93 context.arbitraryfilectx(local),
90 context.arbitraryfilectx(base),
94 context.arbitraryfilectx(base),
91 context.arbitraryfilectx(other),
95 context.arbitraryfilectx(other),
92 **pycompat.strkwargs(opts)
96 **pycompat.strkwargs(opts)
93 )
97 )
94 )
98 )
95 except ParseError as e:
99 except ParseError as e:
96 e = stringutil.forcebytestr(e)
100 e = stringutil.forcebytestr(e)
97 procutil.stdout.write(b"%s: %s\n" % (sys.argv[0].encode('utf8'), e))
101 procutil.stdout.write(b"%s: %s\n" % (sys.argv[0].encode('utf8'), e))
98 showhelp()
102 showhelp()
99 sys.exit(1)
103 sys.exit(1)
100 except error.Abort as e:
104 except error.Abort as e:
101 procutil.stderr.write(b"abort: %s\n" % e)
105 procutil.stderr.write(b"abort: %s\n" % e)
102 sys.exit(255)
106 sys.exit(255)
103 except KeyboardInterrupt:
107 except KeyboardInterrupt:
104 sys.exit(255)
108 sys.exit(255)
@@ -1,530 +1,528 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):
217 def minimize(self, merge_groups):
218 """Trim conflict regions of lines where A and B sides match.
218 """Trim conflict regions of lines where A and B sides match.
219
219
220 Lines where both A and B have made the same changes at the beginning
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
221 or the end of each merge region are eliminated from the conflict
222 region and are instead considered the same.
222 region and are instead considered the same.
223 """
223 """
224 for what, lines in merge_groups:
224 for what, lines in merge_groups:
225 if what != b"conflict":
225 if what != b"conflict":
226 yield what, lines
226 yield what, lines
227 continue
227 continue
228 base_lines, a_lines, b_lines = lines
228 base_lines, a_lines, b_lines = lines
229 alen = len(a_lines)
229 alen = len(a_lines)
230 blen = len(b_lines)
230 blen = len(b_lines)
231
231
232 # find matches at the front
232 # find matches at the front
233 ii = 0
233 ii = 0
234 while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]:
234 while ii < alen and ii < blen and a_lines[ii] == b_lines[ii]:
235 ii += 1
235 ii += 1
236 startmatches = ii
236 startmatches = ii
237
237
238 # find matches at the end
238 # find matches at the end
239 ii = 0
239 ii = 0
240 while (
240 while (
241 ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]
241 ii < alen and ii < blen and a_lines[-ii - 1] == b_lines[-ii - 1]
242 ):
242 ):
243 ii += 1
243 ii += 1
244 endmatches = ii
244 endmatches = ii
245
245
246 if startmatches > 0:
246 if startmatches > 0:
247 yield b'same', a_lines[:startmatches]
247 yield b'same', a_lines[:startmatches]
248
248
249 yield (
249 yield (
250 b'conflict',
250 b'conflict',
251 (
251 (
252 base_lines,
252 base_lines,
253 a_lines[startmatches : alen - endmatches],
253 a_lines[startmatches : alen - endmatches],
254 b_lines[startmatches : blen - endmatches],
254 b_lines[startmatches : blen - endmatches],
255 ),
255 ),
256 )
256 )
257
257
258 if endmatches > 0:
258 if endmatches > 0:
259 yield b'same', a_lines[alen - endmatches :]
259 yield b'same', a_lines[alen - endmatches :]
260
260
261 def find_sync_regions(self):
261 def find_sync_regions(self):
262 """Return a list of sync regions, where both descendants match the base.
262 """Return a list of sync regions, where both descendants match the base.
263
263
264 Generates a list of (base1, base2, a1, a2, b1, b2). There is
264 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.
265 always a zero-length sync region at the end of all the files.
266 """
266 """
267
267
268 ia = ib = 0
268 ia = ib = 0
269 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
269 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
270 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
270 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
271 len_a = len(amatches)
271 len_a = len(amatches)
272 len_b = len(bmatches)
272 len_b = len(bmatches)
273
273
274 sl = []
274 sl = []
275
275
276 while ia < len_a and ib < len_b:
276 while ia < len_a and ib < len_b:
277 abase, amatch, alen = amatches[ia]
277 abase, amatch, alen = amatches[ia]
278 bbase, bmatch, blen = bmatches[ib]
278 bbase, bmatch, blen = bmatches[ib]
279
279
280 # there is an unconflicted block at i; how long does it
280 # there is an unconflicted block at i; how long does it
281 # extend? until whichever one ends earlier.
281 # extend? until whichever one ends earlier.
282 i = intersect((abase, abase + alen), (bbase, bbase + blen))
282 i = intersect((abase, abase + alen), (bbase, bbase + blen))
283 if i:
283 if i:
284 intbase = i[0]
284 intbase = i[0]
285 intend = i[1]
285 intend = i[1]
286 intlen = intend - intbase
286 intlen = intend - intbase
287
287
288 # found a match of base[i[0], i[1]]; this may be less than
288 # found a match of base[i[0], i[1]]; this may be less than
289 # the region that matches in either one
289 # the region that matches in either one
290 assert intlen <= alen
290 assert intlen <= alen
291 assert intlen <= blen
291 assert intlen <= blen
292 assert abase <= intbase
292 assert abase <= intbase
293 assert bbase <= intbase
293 assert bbase <= intbase
294
294
295 asub = amatch + (intbase - abase)
295 asub = amatch + (intbase - abase)
296 bsub = bmatch + (intbase - bbase)
296 bsub = bmatch + (intbase - bbase)
297 aend = asub + intlen
297 aend = asub + intlen
298 bend = bsub + intlen
298 bend = bsub + intlen
299
299
300 assert self.base[intbase:intend] == self.a[asub:aend], (
300 assert self.base[intbase:intend] == self.a[asub:aend], (
301 self.base[intbase:intend],
301 self.base[intbase:intend],
302 self.a[asub:aend],
302 self.a[asub:aend],
303 )
303 )
304
304
305 assert self.base[intbase:intend] == self.b[bsub:bend]
305 assert self.base[intbase:intend] == self.b[bsub:bend]
306
306
307 sl.append((intbase, intend, asub, aend, bsub, bend))
307 sl.append((intbase, intend, asub, aend, bsub, bend))
308
308
309 # advance whichever one ends first in the base text
309 # advance whichever one ends first in the base text
310 if (abase + alen) < (bbase + blen):
310 if (abase + alen) < (bbase + blen):
311 ia += 1
311 ia += 1
312 else:
312 else:
313 ib += 1
313 ib += 1
314
314
315 intbase = len(self.base)
315 intbase = len(self.base)
316 abase = len(self.a)
316 abase = len(self.a)
317 bbase = len(self.b)
317 bbase = len(self.b)
318 sl.append((intbase, intbase, abase, abase, bbase, bbase))
318 sl.append((intbase, intbase, abase, abase, bbase, bbase))
319
319
320 return sl
320 return sl
321
321
322
322
323 def _verifytext(text, path, ui, opts):
323 def _verifytext(text, path, ui, opts):
324 """verifies that text is non-binary (unless opts[text] is passed,
324 """verifies that text is non-binary (unless opts[text] is passed,
325 then we just warn)"""
325 then we just warn)"""
326 if stringutil.binary(text):
326 if stringutil.binary(text):
327 msg = _(b"%s looks like a binary file.") % path
327 msg = _(b"%s looks like a binary file.") % path
328 if not opts.get('quiet'):
328 if not opts.get('quiet'):
329 ui.warn(_(b'warning: %s\n') % msg)
329 ui.warn(_(b'warning: %s\n') % msg)
330 if not opts.get('text'):
330 if not opts.get('text'):
331 raise error.Abort(msg)
331 raise error.Abort(msg)
332 return text
332 return text
333
333
334
334
335 def _picklabels(defaults, overrides):
335 def _picklabels(overrides):
336 if len(overrides) > 3:
336 if len(overrides) > 3:
337 raise error.Abort(_(b"can only specify three labels."))
337 raise error.Abort(_(b"can only specify three labels."))
338 result = defaults[:]
338 result = [None, None, None]
339 for i, override in enumerate(overrides):
339 for i, override in enumerate(overrides):
340 result[i] = override
340 result[i] = override
341 return result
341 return result
342
342
343
343
344 def _detect_newline(m3):
344 def _detect_newline(m3):
345 if len(m3.a) > 0:
345 if len(m3.a) > 0:
346 if m3.a[0].endswith(b'\r\n'):
346 if m3.a[0].endswith(b'\r\n'):
347 return b'\r\n'
347 return b'\r\n'
348 elif m3.a[0].endswith(b'\r'):
348 elif m3.a[0].endswith(b'\r'):
349 return b'\r'
349 return b'\r'
350 return b'\n'
350 return b'\n'
351
351
352
352
353 def render_markers(
353 def render_markers(
354 m3,
354 m3,
355 name_a=None,
355 name_a=None,
356 name_b=None,
356 name_b=None,
357 name_base=None,
357 name_base=None,
358 start_marker=b'<<<<<<<',
358 start_marker=b'<<<<<<<',
359 mid_marker=b'=======',
359 mid_marker=b'=======',
360 end_marker=b'>>>>>>>',
360 end_marker=b'>>>>>>>',
361 base_marker=None,
361 base_marker=None,
362 minimize=False,
362 minimize=False,
363 ):
363 ):
364 """Return merge in cvs-like form."""
364 """Return merge in cvs-like form."""
365 newline = _detect_newline(m3)
365 newline = _detect_newline(m3)
366 conflicts = False
366 conflicts = False
367 if name_a and start_marker:
367 if name_a and start_marker:
368 start_marker = start_marker + b' ' + name_a
368 start_marker = start_marker + b' ' + name_a
369 if name_b and end_marker:
369 if name_b and end_marker:
370 end_marker = end_marker + b' ' + name_b
370 end_marker = end_marker + b' ' + name_b
371 if name_base and base_marker:
371 if name_base and base_marker:
372 base_marker = base_marker + b' ' + name_base
372 base_marker = base_marker + b' ' + name_base
373 merge_groups = m3.merge_groups()
373 merge_groups = m3.merge_groups()
374 if minimize:
374 if minimize:
375 merge_groups = m3.minimize(merge_groups)
375 merge_groups = m3.minimize(merge_groups)
376 lines = []
376 lines = []
377 for what, group_lines in merge_groups:
377 for what, group_lines in merge_groups:
378 if what == b'conflict':
378 if what == b'conflict':
379 base_lines, a_lines, b_lines = group_lines
379 base_lines, a_lines, b_lines = group_lines
380 conflicts = True
380 conflicts = True
381 if start_marker is not None:
381 if start_marker is not None:
382 lines.append(start_marker + newline)
382 lines.append(start_marker + newline)
383 lines.extend(a_lines)
383 lines.extend(a_lines)
384 if base_marker is not None:
384 if base_marker is not None:
385 lines.append(base_marker + newline)
385 lines.append(base_marker + newline)
386 lines.extend(base_lines)
386 lines.extend(base_lines)
387 if mid_marker is not None:
387 if mid_marker is not None:
388 lines.append(mid_marker + newline)
388 lines.append(mid_marker + newline)
389 lines.extend(b_lines)
389 lines.extend(b_lines)
390 if end_marker is not None:
390 if end_marker is not None:
391 lines.append(end_marker + newline)
391 lines.append(end_marker + newline)
392 else:
392 else:
393 lines.extend(group_lines)
393 lines.extend(group_lines)
394 return lines, conflicts
394 return lines, conflicts
395
395
396
396
397 def render_mergediff(m3, name_a, name_b, name_base):
397 def render_mergediff(m3, name_a, name_b, name_base):
398 newline = _detect_newline(m3)
398 newline = _detect_newline(m3)
399 lines = []
399 lines = []
400 conflicts = False
400 conflicts = False
401 for what, group_lines in m3.merge_groups():
401 for what, group_lines in m3.merge_groups():
402 if what == b'conflict':
402 if what == b'conflict':
403 base_lines, a_lines, b_lines = group_lines
403 base_lines, a_lines, b_lines = group_lines
404 base_text = b''.join(base_lines)
404 base_text = b''.join(base_lines)
405 b_blocks = list(
405 b_blocks = list(
406 mdiff.allblocks(
406 mdiff.allblocks(
407 base_text,
407 base_text,
408 b''.join(b_lines),
408 b''.join(b_lines),
409 lines1=base_lines,
409 lines1=base_lines,
410 lines2=b_lines,
410 lines2=b_lines,
411 )
411 )
412 )
412 )
413 a_blocks = list(
413 a_blocks = list(
414 mdiff.allblocks(
414 mdiff.allblocks(
415 base_text,
415 base_text,
416 b''.join(a_lines),
416 b''.join(a_lines),
417 lines1=base_lines,
417 lines1=base_lines,
418 lines2=b_lines,
418 lines2=b_lines,
419 )
419 )
420 )
420 )
421
421
422 def matching_lines(blocks):
422 def matching_lines(blocks):
423 return sum(
423 return sum(
424 block[1] - block[0]
424 block[1] - block[0]
425 for block, kind in blocks
425 for block, kind in blocks
426 if kind == b'='
426 if kind == b'='
427 )
427 )
428
428
429 def diff_lines(blocks, lines1, lines2):
429 def diff_lines(blocks, lines1, lines2):
430 for block, kind in blocks:
430 for block, kind in blocks:
431 if kind == b'=':
431 if kind == b'=':
432 for line in lines1[block[0] : block[1]]:
432 for line in lines1[block[0] : block[1]]:
433 yield b' ' + line
433 yield b' ' + line
434 else:
434 else:
435 for line in lines1[block[0] : block[1]]:
435 for line in lines1[block[0] : block[1]]:
436 yield b'-' + line
436 yield b'-' + line
437 for line in lines2[block[2] : block[3]]:
437 for line in lines2[block[2] : block[3]]:
438 yield b'+' + line
438 yield b'+' + line
439
439
440 lines.append(b"<<<<<<<" + newline)
440 lines.append(b"<<<<<<<" + newline)
441 if matching_lines(a_blocks) < matching_lines(b_blocks):
441 if matching_lines(a_blocks) < matching_lines(b_blocks):
442 lines.append(b"======= " + name_a + newline)
442 lines.append(b"======= " + name_a + newline)
443 lines.extend(a_lines)
443 lines.extend(a_lines)
444 lines.append(b"------- " + name_base + newline)
444 lines.append(b"------- " + name_base + newline)
445 lines.append(b"+++++++ " + name_b + newline)
445 lines.append(b"+++++++ " + name_b + newline)
446 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
446 lines.extend(diff_lines(b_blocks, base_lines, b_lines))
447 else:
447 else:
448 lines.append(b"------- " + name_base + newline)
448 lines.append(b"------- " + name_base + newline)
449 lines.append(b"+++++++ " + name_a + newline)
449 lines.append(b"+++++++ " + name_a + newline)
450 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
450 lines.extend(diff_lines(a_blocks, base_lines, a_lines))
451 lines.append(b"======= " + name_b + newline)
451 lines.append(b"======= " + name_b + newline)
452 lines.extend(b_lines)
452 lines.extend(b_lines)
453 lines.append(b">>>>>>>" + newline)
453 lines.append(b">>>>>>>" + newline)
454 conflicts = True
454 conflicts = True
455 else:
455 else:
456 lines.extend(group_lines)
456 lines.extend(group_lines)
457 return lines, conflicts
457 return lines, conflicts
458
458
459
459
460 def _resolve(m3, sides):
460 def _resolve(m3, sides):
461 lines = []
461 lines = []
462 for what, group_lines in m3.merge_groups():
462 for what, group_lines in m3.merge_groups():
463 if what == b'conflict':
463 if what == b'conflict':
464 for side in sides:
464 for side in sides:
465 lines.extend(group_lines[side])
465 lines.extend(group_lines[side])
466 else:
466 else:
467 lines.extend(group_lines)
467 lines.extend(group_lines)
468 return lines
468 return lines
469
469
470
470
471 def simplemerge(ui, localctx, basectx, otherctx, **opts):
471 def simplemerge(ui, localctx, basectx, otherctx, **opts):
472 """Performs the simplemerge algorithm.
472 """Performs the simplemerge algorithm.
473
473
474 The merged result is written into `localctx`.
474 The merged result is written into `localctx`.
475 """
475 """
476
476
477 def readctx(ctx):
477 def readctx(ctx):
478 # Merges were always run in the working copy before, which means
478 # Merges were always run in the working copy before, which means
479 # they used decoded data, if the user defined any repository
479 # they used decoded data, if the user defined any repository
480 # filters.
480 # filters.
481 #
481 #
482 # Maintain that behavior today for BC, though perhaps in the future
482 # Maintain that behavior today for BC, though perhaps in the future
483 # it'd be worth considering whether merging encoded data (what the
483 # it'd be worth considering whether merging encoded data (what the
484 # repository usually sees) might be more useful.
484 # repository usually sees) might be more useful.
485 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
485 return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
486
486
487 try:
487 try:
488 localtext = readctx(localctx)
488 localtext = readctx(localctx)
489 basetext = readctx(basectx)
489 basetext = readctx(basectx)
490 othertext = readctx(otherctx)
490 othertext = readctx(otherctx)
491 except error.Abort:
491 except error.Abort:
492 return 1
492 return 1
493
493
494 m3 = Merge3Text(basetext, localtext, othertext)
494 m3 = Merge3Text(basetext, localtext, othertext)
495 conflicts = False
495 conflicts = False
496 mode = opts.get('mode', b'merge')
496 mode = opts.get('mode', b'merge')
497 if mode == b'union':
497 if mode == b'union':
498 lines = _resolve(m3, (1, 2))
498 lines = _resolve(m3, (1, 2))
499 elif mode == b'local':
499 elif mode == b'local':
500 lines = _resolve(m3, (1,))
500 lines = _resolve(m3, (1,))
501 elif mode == b'other':
501 elif mode == b'other':
502 lines = _resolve(m3, (2,))
502 lines = _resolve(m3, (2,))
503 else:
503 else:
504 name_a, name_b, name_base = _picklabels(
504 name_a, name_b, name_base = _picklabels(opts.get('label', []))
505 [localctx.path(), otherctx.path(), None], opts.get('label', [])
506 )
507 if mode == b'mergediff':
505 if mode == b'mergediff':
508 lines, conflicts = render_mergediff(m3, name_a, name_b, name_base)
506 lines, conflicts = render_mergediff(m3, name_a, name_b, name_base)
509 else:
507 else:
510 extrakwargs = {
508 extrakwargs = {
511 'minimize': True,
509 'minimize': True,
512 }
510 }
513 if mode == b'merge3':
511 if mode == b'merge3':
514 extrakwargs['base_marker'] = b'|||||||'
512 extrakwargs['base_marker'] = b'|||||||'
515 extrakwargs['name_base'] = name_base
513 extrakwargs['name_base'] = name_base
516 extrakwargs['minimize'] = False
514 extrakwargs['minimize'] = False
517 lines, conflicts = render_markers(
515 lines, conflicts = render_markers(
518 m3, name_a=name_a, name_b=name_b, **extrakwargs
516 m3, name_a=name_a, name_b=name_b, **extrakwargs
519 )
517 )
520
518
521 mergedtext = b''.join(lines)
519 mergedtext = b''.join(lines)
522 if opts.get('print'):
520 if opts.get('print'):
523 ui.fout.write(mergedtext)
521 ui.fout.write(mergedtext)
524 else:
522 else:
525 # localctx.flags() already has the merged flags (done in
523 # localctx.flags() already has the merged flags (done in
526 # mergestate.resolve())
524 # mergestate.resolve())
527 localctx.write(mergedtext, localctx.flags())
525 localctx.write(mergedtext, localctx.flags())
528
526
529 if conflicts:
527 if conflicts:
530 return 1
528 return 1
General Comments 0
You need to be logged in to leave comments. Login now