##// END OF EJS Templates
filemerge: extract `_picklabels` as a helper function...
Phil Cohen -
r33829:aa6c290a default
parent child Browse files
Show More
@@ -1,504 +1,506 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 import os
21 import os
22
22
23 from .i18n import _
23 from .i18n import _
24 from . import (
24 from . import (
25 error,
25 error,
26 mdiff,
26 mdiff,
27 pycompat,
27 pycompat,
28 util,
28 util,
29 vfs as vfsmod,
29 vfs as vfsmod,
30 )
30 )
31
31
32 class CantReprocessAndShowBase(Exception):
32 class CantReprocessAndShowBase(Exception):
33 pass
33 pass
34
34
35 def intersect(ra, rb):
35 def intersect(ra, rb):
36 """Given two ranges return the range where they intersect or None.
36 """Given two ranges return the range where they intersect or None.
37
37
38 >>> intersect((0, 10), (0, 6))
38 >>> intersect((0, 10), (0, 6))
39 (0, 6)
39 (0, 6)
40 >>> intersect((0, 10), (5, 15))
40 >>> intersect((0, 10), (5, 15))
41 (5, 10)
41 (5, 10)
42 >>> intersect((0, 10), (10, 15))
42 >>> intersect((0, 10), (10, 15))
43 >>> intersect((0, 9), (10, 15))
43 >>> intersect((0, 9), (10, 15))
44 >>> intersect((0, 9), (7, 15))
44 >>> intersect((0, 9), (7, 15))
45 (7, 9)
45 (7, 9)
46 """
46 """
47 assert ra[0] <= ra[1]
47 assert ra[0] <= ra[1]
48 assert rb[0] <= rb[1]
48 assert rb[0] <= rb[1]
49
49
50 sa = max(ra[0], rb[0])
50 sa = max(ra[0], rb[0])
51 sb = min(ra[1], rb[1])
51 sb = min(ra[1], rb[1])
52 if sa < sb:
52 if sa < sb:
53 return sa, sb
53 return sa, sb
54 else:
54 else:
55 return None
55 return None
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 """
59 """
60 if (aend - astart) != (bend - bstart):
60 if (aend - astart) != (bend - bstart):
61 return False
61 return False
62 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
62 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
63 if a[ia] != b[ib]:
63 if a[ia] != b[ib]:
64 return False
64 return False
65 else:
65 else:
66 return True
66 return True
67
67
68 class Merge3Text(object):
68 class Merge3Text(object):
69 """3-way merge of texts.
69 """3-way merge of texts.
70
70
71 Given strings BASE, OTHER, THIS, tries to produce a combined text
71 Given strings BASE, OTHER, THIS, tries to produce a combined text
72 incorporating the changes from both BASE->OTHER and BASE->THIS."""
72 incorporating the changes from both BASE->OTHER and BASE->THIS."""
73 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
73 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
74 self.basetext = basetext
74 self.basetext = basetext
75 self.atext = atext
75 self.atext = atext
76 self.btext = btext
76 self.btext = btext
77 if base is None:
77 if base is None:
78 base = mdiff.splitnewlines(basetext)
78 base = mdiff.splitnewlines(basetext)
79 if a is None:
79 if a is None:
80 a = mdiff.splitnewlines(atext)
80 a = mdiff.splitnewlines(atext)
81 if b is None:
81 if b is None:
82 b = mdiff.splitnewlines(btext)
82 b = mdiff.splitnewlines(btext)
83 self.base = base
83 self.base = base
84 self.a = a
84 self.a = a
85 self.b = b
85 self.b = b
86
86
87 def merge_lines(self,
87 def merge_lines(self,
88 name_a=None,
88 name_a=None,
89 name_b=None,
89 name_b=None,
90 name_base=None,
90 name_base=None,
91 start_marker='<<<<<<<',
91 start_marker='<<<<<<<',
92 mid_marker='=======',
92 mid_marker='=======',
93 end_marker='>>>>>>>',
93 end_marker='>>>>>>>',
94 base_marker=None,
94 base_marker=None,
95 localorother=None,
95 localorother=None,
96 minimize=False):
96 minimize=False):
97 """Return merge in cvs-like form.
97 """Return merge in cvs-like form.
98 """
98 """
99 self.conflicts = False
99 self.conflicts = False
100 newline = '\n'
100 newline = '\n'
101 if len(self.a) > 0:
101 if len(self.a) > 0:
102 if self.a[0].endswith('\r\n'):
102 if self.a[0].endswith('\r\n'):
103 newline = '\r\n'
103 newline = '\r\n'
104 elif self.a[0].endswith('\r'):
104 elif self.a[0].endswith('\r'):
105 newline = '\r'
105 newline = '\r'
106 if name_a and start_marker:
106 if name_a and start_marker:
107 start_marker = start_marker + ' ' + name_a
107 start_marker = start_marker + ' ' + name_a
108 if name_b and end_marker:
108 if name_b and end_marker:
109 end_marker = end_marker + ' ' + name_b
109 end_marker = end_marker + ' ' + name_b
110 if name_base and base_marker:
110 if name_base and base_marker:
111 base_marker = base_marker + ' ' + name_base
111 base_marker = base_marker + ' ' + name_base
112 merge_regions = self.merge_regions()
112 merge_regions = self.merge_regions()
113 if minimize:
113 if minimize:
114 merge_regions = self.minimize(merge_regions)
114 merge_regions = self.minimize(merge_regions)
115 for t in merge_regions:
115 for t in merge_regions:
116 what = t[0]
116 what = t[0]
117 if what == 'unchanged':
117 if what == 'unchanged':
118 for i in range(t[1], t[2]):
118 for i in range(t[1], t[2]):
119 yield self.base[i]
119 yield self.base[i]
120 elif what == 'a' or what == 'same':
120 elif what == 'a' or what == 'same':
121 for i in range(t[1], t[2]):
121 for i in range(t[1], t[2]):
122 yield self.a[i]
122 yield self.a[i]
123 elif what == 'b':
123 elif what == 'b':
124 for i in range(t[1], t[2]):
124 for i in range(t[1], t[2]):
125 yield self.b[i]
125 yield self.b[i]
126 elif what == 'conflict':
126 elif what == 'conflict':
127 if localorother == 'local':
127 if localorother == 'local':
128 for i in range(t[3], t[4]):
128 for i in range(t[3], t[4]):
129 yield self.a[i]
129 yield self.a[i]
130 elif localorother == 'other':
130 elif localorother == 'other':
131 for i in range(t[5], t[6]):
131 for i in range(t[5], t[6]):
132 yield self.b[i]
132 yield self.b[i]
133 else:
133 else:
134 self.conflicts = True
134 self.conflicts = True
135 if start_marker is not None:
135 if start_marker is not None:
136 yield start_marker + newline
136 yield start_marker + newline
137 for i in range(t[3], t[4]):
137 for i in range(t[3], t[4]):
138 yield self.a[i]
138 yield self.a[i]
139 if base_marker is not None:
139 if base_marker is not None:
140 yield base_marker + newline
140 yield base_marker + newline
141 for i in range(t[1], t[2]):
141 for i in range(t[1], t[2]):
142 yield self.base[i]
142 yield self.base[i]
143 if mid_marker is not None:
143 if mid_marker is not None:
144 yield mid_marker + newline
144 yield mid_marker + newline
145 for i in range(t[5], t[6]):
145 for i in range(t[5], t[6]):
146 yield self.b[i]
146 yield self.b[i]
147 if end_marker is not None:
147 if end_marker is not None:
148 yield end_marker + newline
148 yield end_marker + newline
149 else:
149 else:
150 raise ValueError(what)
150 raise ValueError(what)
151
151
152 def merge_groups(self):
152 def merge_groups(self):
153 """Yield sequence of line groups. Each one is a tuple:
153 """Yield sequence of line groups. Each one is a tuple:
154
154
155 'unchanged', lines
155 'unchanged', lines
156 Lines unchanged from base
156 Lines unchanged from base
157
157
158 'a', lines
158 'a', lines
159 Lines taken from a
159 Lines taken from a
160
160
161 'same', lines
161 'same', lines
162 Lines taken from a (and equal to b)
162 Lines taken from a (and equal to b)
163
163
164 'b', lines
164 'b', lines
165 Lines taken from b
165 Lines taken from b
166
166
167 'conflict', base_lines, a_lines, b_lines
167 'conflict', base_lines, a_lines, b_lines
168 Lines from base were changed to either a or b and conflict.
168 Lines from base were changed to either a or b and conflict.
169 """
169 """
170 for t in self.merge_regions():
170 for t in self.merge_regions():
171 what = t[0]
171 what = t[0]
172 if what == 'unchanged':
172 if what == 'unchanged':
173 yield what, self.base[t[1]:t[2]]
173 yield what, self.base[t[1]:t[2]]
174 elif what == 'a' or what == 'same':
174 elif what == 'a' or what == 'same':
175 yield what, self.a[t[1]:t[2]]
175 yield what, self.a[t[1]:t[2]]
176 elif what == 'b':
176 elif what == 'b':
177 yield what, self.b[t[1]:t[2]]
177 yield what, self.b[t[1]:t[2]]
178 elif what == 'conflict':
178 elif what == 'conflict':
179 yield (what,
179 yield (what,
180 self.base[t[1]:t[2]],
180 self.base[t[1]:t[2]],
181 self.a[t[3]:t[4]],
181 self.a[t[3]:t[4]],
182 self.b[t[5]:t[6]])
182 self.b[t[5]:t[6]])
183 else:
183 else:
184 raise ValueError(what)
184 raise ValueError(what)
185
185
186 def merge_regions(self):
186 def merge_regions(self):
187 """Return sequences of matching and conflicting regions.
187 """Return sequences of matching and conflicting regions.
188
188
189 This returns tuples, where the first value says what kind we
189 This returns tuples, where the first value says what kind we
190 have:
190 have:
191
191
192 'unchanged', start, end
192 'unchanged', start, end
193 Take a region of base[start:end]
193 Take a region of base[start:end]
194
194
195 'same', astart, aend
195 'same', astart, aend
196 b and a are different from base but give the same result
196 b and a are different from base but give the same result
197
197
198 'a', start, end
198 'a', start, end
199 Non-clashing insertion from a[start:end]
199 Non-clashing insertion from a[start:end]
200
200
201 'conflict', zstart, zend, astart, aend, bstart, bend
201 'conflict', zstart, zend, astart, aend, bstart, bend
202 Conflict between a and b, with z as common ancestor
202 Conflict between a and b, with z as common ancestor
203
203
204 Method is as follows:
204 Method is as follows:
205
205
206 The two sequences align only on regions which match the base
206 The two sequences align only on regions which match the base
207 and both descendants. These are found by doing a two-way diff
207 and both descendants. These are found by doing a two-way diff
208 of each one against the base, and then finding the
208 of each one against the base, and then finding the
209 intersections between those regions. These "sync regions"
209 intersections between those regions. These "sync regions"
210 are by definition unchanged in both and easily dealt with.
210 are by definition unchanged in both and easily dealt with.
211
211
212 The regions in between can be in any of three cases:
212 The regions in between can be in any of three cases:
213 conflicted, or changed on only one side.
213 conflicted, or changed on only one side.
214 """
214 """
215
215
216 # section a[0:ia] has been disposed of, etc
216 # section a[0:ia] has been disposed of, etc
217 iz = ia = ib = 0
217 iz = ia = ib = 0
218
218
219 for region in self.find_sync_regions():
219 for region in self.find_sync_regions():
220 zmatch, zend, amatch, aend, bmatch, bend = region
220 zmatch, zend, amatch, aend, bmatch, bend = region
221 #print 'match base [%d:%d]' % (zmatch, zend)
221 #print 'match base [%d:%d]' % (zmatch, zend)
222
222
223 matchlen = zend - zmatch
223 matchlen = zend - zmatch
224 assert matchlen >= 0
224 assert matchlen >= 0
225 assert matchlen == (aend - amatch)
225 assert matchlen == (aend - amatch)
226 assert matchlen == (bend - bmatch)
226 assert matchlen == (bend - bmatch)
227
227
228 len_a = amatch - ia
228 len_a = amatch - ia
229 len_b = bmatch - ib
229 len_b = bmatch - ib
230 len_base = zmatch - iz
230 len_base = zmatch - iz
231 assert len_a >= 0
231 assert len_a >= 0
232 assert len_b >= 0
232 assert len_b >= 0
233 assert len_base >= 0
233 assert len_base >= 0
234
234
235 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
235 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
236
236
237 if len_a or len_b:
237 if len_a or len_b:
238 # try to avoid actually slicing the lists
238 # try to avoid actually slicing the lists
239 equal_a = compare_range(self.a, ia, amatch,
239 equal_a = compare_range(self.a, ia, amatch,
240 self.base, iz, zmatch)
240 self.base, iz, zmatch)
241 equal_b = compare_range(self.b, ib, bmatch,
241 equal_b = compare_range(self.b, ib, bmatch,
242 self.base, iz, zmatch)
242 self.base, iz, zmatch)
243 same = compare_range(self.a, ia, amatch,
243 same = compare_range(self.a, ia, amatch,
244 self.b, ib, bmatch)
244 self.b, ib, bmatch)
245
245
246 if same:
246 if same:
247 yield 'same', ia, amatch
247 yield 'same', ia, amatch
248 elif equal_a and not equal_b:
248 elif equal_a and not equal_b:
249 yield 'b', ib, bmatch
249 yield 'b', ib, bmatch
250 elif equal_b and not equal_a:
250 elif equal_b and not equal_a:
251 yield 'a', ia, amatch
251 yield 'a', ia, amatch
252 elif not equal_a and not equal_b:
252 elif not equal_a and not equal_b:
253 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
253 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
254 else:
254 else:
255 raise AssertionError("can't handle a=b=base but unmatched")
255 raise AssertionError("can't handle a=b=base but unmatched")
256
256
257 ia = amatch
257 ia = amatch
258 ib = bmatch
258 ib = bmatch
259 iz = zmatch
259 iz = zmatch
260
260
261 # if the same part of the base was deleted on both sides
261 # if the same part of the base was deleted on both sides
262 # that's OK, we can just skip it.
262 # that's OK, we can just skip it.
263
263
264
264
265 if matchlen > 0:
265 if matchlen > 0:
266 assert ia == amatch
266 assert ia == amatch
267 assert ib == bmatch
267 assert ib == bmatch
268 assert iz == zmatch
268 assert iz == zmatch
269
269
270 yield 'unchanged', zmatch, zend
270 yield 'unchanged', zmatch, zend
271 iz = zend
271 iz = zend
272 ia = aend
272 ia = aend
273 ib = bend
273 ib = bend
274
274
275 def minimize(self, merge_regions):
275 def minimize(self, merge_regions):
276 """Trim conflict regions of lines where A and B sides match.
276 """Trim conflict regions of lines where A and B sides match.
277
277
278 Lines where both A and B have made the same changes at the beginning
278 Lines where both A and B have made the same changes at the beginning
279 or the end of each merge region are eliminated from the conflict
279 or the end of each merge region are eliminated from the conflict
280 region and are instead considered the same.
280 region and are instead considered the same.
281 """
281 """
282 for region in merge_regions:
282 for region in merge_regions:
283 if region[0] != "conflict":
283 if region[0] != "conflict":
284 yield region
284 yield region
285 continue
285 continue
286 issue, z1, z2, a1, a2, b1, b2 = region
286 issue, z1, z2, a1, a2, b1, b2 = region
287 alen = a2 - a1
287 alen = a2 - a1
288 blen = b2 - b1
288 blen = b2 - b1
289
289
290 # find matches at the front
290 # find matches at the front
291 ii = 0
291 ii = 0
292 while ii < alen and ii < blen and \
292 while ii < alen and ii < blen and \
293 self.a[a1 + ii] == self.b[b1 + ii]:
293 self.a[a1 + ii] == self.b[b1 + ii]:
294 ii += 1
294 ii += 1
295 startmatches = ii
295 startmatches = ii
296
296
297 # find matches at the end
297 # find matches at the end
298 ii = 0
298 ii = 0
299 while ii < alen and ii < blen and \
299 while ii < alen and ii < blen and \
300 self.a[a2 - ii - 1] == self.b[b2 - ii - 1]:
300 self.a[a2 - ii - 1] == self.b[b2 - ii - 1]:
301 ii += 1
301 ii += 1
302 endmatches = ii
302 endmatches = ii
303
303
304 if startmatches > 0:
304 if startmatches > 0:
305 yield 'same', a1, a1 + startmatches
305 yield 'same', a1, a1 + startmatches
306
306
307 yield ('conflict', z1, z2,
307 yield ('conflict', z1, z2,
308 a1 + startmatches, a2 - endmatches,
308 a1 + startmatches, a2 - endmatches,
309 b1 + startmatches, b2 - endmatches)
309 b1 + startmatches, b2 - endmatches)
310
310
311 if endmatches > 0:
311 if endmatches > 0:
312 yield 'same', a2 - endmatches, a2
312 yield 'same', a2 - endmatches, a2
313
313
314 def find_sync_regions(self):
314 def find_sync_regions(self):
315 """Return a list of sync regions, where both descendants match the base.
315 """Return a list of sync regions, where both descendants match the base.
316
316
317 Generates a list of (base1, base2, a1, a2, b1, b2). There is
317 Generates a list of (base1, base2, a1, a2, b1, b2). There is
318 always a zero-length sync region at the end of all the files.
318 always a zero-length sync region at the end of all the files.
319 """
319 """
320
320
321 ia = ib = 0
321 ia = ib = 0
322 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
322 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
323 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
323 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
324 len_a = len(amatches)
324 len_a = len(amatches)
325 len_b = len(bmatches)
325 len_b = len(bmatches)
326
326
327 sl = []
327 sl = []
328
328
329 while ia < len_a and ib < len_b:
329 while ia < len_a and ib < len_b:
330 abase, amatch, alen = amatches[ia]
330 abase, amatch, alen = amatches[ia]
331 bbase, bmatch, blen = bmatches[ib]
331 bbase, bmatch, blen = bmatches[ib]
332
332
333 # there is an unconflicted block at i; how long does it
333 # there is an unconflicted block at i; how long does it
334 # extend? until whichever one ends earlier.
334 # extend? until whichever one ends earlier.
335 i = intersect((abase, abase + alen), (bbase, bbase + blen))
335 i = intersect((abase, abase + alen), (bbase, bbase + blen))
336 if i:
336 if i:
337 intbase = i[0]
337 intbase = i[0]
338 intend = i[1]
338 intend = i[1]
339 intlen = intend - intbase
339 intlen = intend - intbase
340
340
341 # found a match of base[i[0], i[1]]; this may be less than
341 # found a match of base[i[0], i[1]]; this may be less than
342 # the region that matches in either one
342 # the region that matches in either one
343 assert intlen <= alen
343 assert intlen <= alen
344 assert intlen <= blen
344 assert intlen <= blen
345 assert abase <= intbase
345 assert abase <= intbase
346 assert bbase <= intbase
346 assert bbase <= intbase
347
347
348 asub = amatch + (intbase - abase)
348 asub = amatch + (intbase - abase)
349 bsub = bmatch + (intbase - bbase)
349 bsub = bmatch + (intbase - bbase)
350 aend = asub + intlen
350 aend = asub + intlen
351 bend = bsub + intlen
351 bend = bsub + intlen
352
352
353 assert self.base[intbase:intend] == self.a[asub:aend], \
353 assert self.base[intbase:intend] == self.a[asub:aend], \
354 (self.base[intbase:intend], self.a[asub:aend])
354 (self.base[intbase:intend], self.a[asub:aend])
355
355
356 assert self.base[intbase:intend] == self.b[bsub:bend]
356 assert self.base[intbase:intend] == self.b[bsub:bend]
357
357
358 sl.append((intbase, intend,
358 sl.append((intbase, intend,
359 asub, aend,
359 asub, aend,
360 bsub, bend))
360 bsub, bend))
361
361
362 # advance whichever one ends first in the base text
362 # advance whichever one ends first in the base text
363 if (abase + alen) < (bbase + blen):
363 if (abase + alen) < (bbase + blen):
364 ia += 1
364 ia += 1
365 else:
365 else:
366 ib += 1
366 ib += 1
367
367
368 intbase = len(self.base)
368 intbase = len(self.base)
369 abase = len(self.a)
369 abase = len(self.a)
370 bbase = len(self.b)
370 bbase = len(self.b)
371 sl.append((intbase, intbase, abase, abase, bbase, bbase))
371 sl.append((intbase, intbase, abase, abase, bbase, bbase))
372
372
373 return sl
373 return sl
374
374
375 def find_unconflicted(self):
375 def find_unconflicted(self):
376 """Return a list of ranges in base that are not conflicted."""
376 """Return a list of ranges in base that are not conflicted."""
377 am = mdiff.get_matching_blocks(self.basetext, self.atext)
377 am = mdiff.get_matching_blocks(self.basetext, self.atext)
378 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
378 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
379
379
380 unc = []
380 unc = []
381
381
382 while am and bm:
382 while am and bm:
383 # there is an unconflicted block at i; how long does it
383 # there is an unconflicted block at i; how long does it
384 # extend? until whichever one ends earlier.
384 # extend? until whichever one ends earlier.
385 a1 = am[0][0]
385 a1 = am[0][0]
386 a2 = a1 + am[0][2]
386 a2 = a1 + am[0][2]
387 b1 = bm[0][0]
387 b1 = bm[0][0]
388 b2 = b1 + bm[0][2]
388 b2 = b1 + bm[0][2]
389 i = intersect((a1, a2), (b1, b2))
389 i = intersect((a1, a2), (b1, b2))
390 if i:
390 if i:
391 unc.append(i)
391 unc.append(i)
392
392
393 if a2 < b2:
393 if a2 < b2:
394 del am[0]
394 del am[0]
395 else:
395 else:
396 del bm[0]
396 del bm[0]
397
397
398 return unc
398 return unc
399
399
400 def _verifytext(text, path, ui, opts):
400 def _verifytext(text, path, ui, opts):
401 """verifies that text is non-binary (unless opts[text] is passed,
401 """verifies that text is non-binary (unless opts[text] is passed,
402 then we just warn)"""
402 then we just warn)"""
403 if util.binary(text):
403 if util.binary(text):
404 msg = _("%s looks like a binary file.") % path
404 msg = _("%s looks like a binary file.") % path
405 if not opts.get('quiet'):
405 if not opts.get('quiet'):
406 ui.warn(_('warning: %s\n') % msg)
406 ui.warn(_('warning: %s\n') % msg)
407 if not opts.get('text'):
407 if not opts.get('text'):
408 raise error.Abort(msg)
408 raise error.Abort(msg)
409 return text
409 return text
410
410
411 def _picklabels(defaults, overrides):
412 name_a, name_b, name_base = defaults
413
414 if len(overrides) > 0:
415 name_a = overrides[0]
416 if len(overrides) > 1:
417 name_b = overrides[1]
418 if len(overrides) > 2:
419 name_base = overrides[2]
420 if len(overrides) > 3:
421 raise error.Abort(_("can only specify three labels."))
422
423 return [name_a, name_b, name_base]
424
411 def simplemerge(ui, localfile, basefile, otherfile,
425 def simplemerge(ui, localfile, basefile, otherfile,
412 localctx=None, basectx=None, otherctx=None, repo=None, **opts):
426 localctx=None, basectx=None, otherctx=None, repo=None, **opts):
413 """Performs the simplemerge algorithm.
427 """Performs the simplemerge algorithm.
414
428
415 {local|base|other}ctx are optional. If passed, they (local/base/other) will
429 {local|base|other}ctx are optional. If passed, they (local/base/other) will
416 be read from and the merge result written to (local). You should pass
430 be read from and the merge result written to (local). You should pass
417 explicit labels in this mode since the default is to use the file paths."""
431 explicit labels in this mode since the default is to use the file paths."""
418 def readfile(filename):
432 def readfile(filename):
419 f = open(filename, "rb")
433 f = open(filename, "rb")
420 text = f.read()
434 text = f.read()
421 f.close()
435 f.close()
422 return _verifytext(text, filename, ui, opts)
436 return _verifytext(text, filename, ui, opts)
423
437
424 def readctx(ctx):
438 def readctx(ctx):
425 if not ctx:
439 if not ctx:
426 return None
440 return None
427 if not repo:
441 if not repo:
428 raise error.ProgrammingError('simplemerge: repo must be passed if '
442 raise error.ProgrammingError('simplemerge: repo must be passed if '
429 'using contexts')
443 'using contexts')
430 # `wwritedata` is used to get the post-filter data from `ctx` (i.e.,
444 # `wwritedata` is used to get the post-filter data from `ctx` (i.e.,
431 # what would have been in the working copy). Since merges were run in
445 # what would have been in the working copy). Since merges were run in
432 # the working copy, and thus used post-filter data, we do the same to
446 # the working copy, and thus used post-filter data, we do the same to
433 # maintain behavior.
447 # maintain behavior.
434 return repo.wwritedata(ctx.path(),
448 return repo.wwritedata(ctx.path(),
435 _verifytext(ctx.data(), ctx.path(), ui, opts))
449 _verifytext(ctx.data(), ctx.path(), ui, opts))
436
450
437 class ctxwriter(object):
451 class ctxwriter(object):
438 def __init__(self, ctx):
452 def __init__(self, ctx):
439 self.ctx = ctx
453 self.ctx = ctx
440 self.text = ""
454 self.text = ""
441
455
442 def write(self, text):
456 def write(self, text):
443 self.text += text
457 self.text += text
444
458
445 def close(self):
459 def close(self):
446 self.ctx.write(self.text, self.ctx.flags())
460 self.ctx.write(self.text, self.ctx.flags())
447
461
448 mode = opts.get('mode','merge')
462 mode = opts.get('mode','merge')
449 if mode == 'union':
463 name_a, name_b, name_base = None, None, None
450 name_a = None
464 if mode != 'union':
451 name_b = None
465 name_a, name_b, name_base = _picklabels([localfile,
452 name_base = None
466 otherfile, None],
453 else:
467 opts.get('label', []))
454 name_a = localfile
455 name_b = otherfile
456 name_base = None
457 labels = opts.get('label', [])
458 if len(labels) > 0:
459 name_a = labels[0]
460 if len(labels) > 1:
461 name_b = labels[1]
462 if len(labels) > 2:
463 name_base = labels[2]
464 if len(labels) > 3:
465 raise error.Abort(_("can only specify three labels."))
466
468
467 try:
469 try:
468 localtext = readctx(localctx) if localctx else readfile(localfile)
470 localtext = readctx(localctx) if localctx else readfile(localfile)
469 basetext = readctx(basectx) if basectx else readfile(basefile)
471 basetext = readctx(basectx) if basectx else readfile(basefile)
470 othertext = readctx(otherctx) if otherctx else readfile(otherfile)
472 othertext = readctx(otherctx) if otherctx else readfile(otherfile)
471 except error.Abort:
473 except error.Abort:
472 return 1
474 return 1
473
475
474 if opts.get('print'):
476 if opts.get('print'):
475 out = ui.fout
477 out = ui.fout
476 elif localctx:
478 elif localctx:
477 out = ctxwriter(localctx)
479 out = ctxwriter(localctx)
478 else:
480 else:
479 localfile = os.path.realpath(localfile)
481 localfile = os.path.realpath(localfile)
480 opener = vfsmod.vfs(os.path.dirname(localfile))
482 opener = vfsmod.vfs(os.path.dirname(localfile))
481 out = opener(os.path.basename(localfile), "w", atomictemp=True)
483 out = opener(os.path.basename(localfile), "w", atomictemp=True)
482
484
483 m3 = Merge3Text(basetext, localtext, othertext)
485 m3 = Merge3Text(basetext, localtext, othertext)
484 extrakwargs = {
486 extrakwargs = {
485 "localorother": opts.get("localorother", None),
487 "localorother": opts.get("localorother", None),
486 'minimize': True,
488 'minimize': True,
487 }
489 }
488 if mode == 'union':
490 if mode == 'union':
489 extrakwargs['start_marker'] = None
491 extrakwargs['start_marker'] = None
490 extrakwargs['mid_marker'] = None
492 extrakwargs['mid_marker'] = None
491 extrakwargs['end_marker'] = None
493 extrakwargs['end_marker'] = None
492 elif name_base is not None:
494 elif name_base is not None:
493 extrakwargs['base_marker'] = '|||||||'
495 extrakwargs['base_marker'] = '|||||||'
494 extrakwargs['name_base'] = name_base
496 extrakwargs['name_base'] = name_base
495 extrakwargs['minimize'] = False
497 extrakwargs['minimize'] = False
496 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
498 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
497 **pycompat.strkwargs(extrakwargs)):
499 **pycompat.strkwargs(extrakwargs)):
498 out.write(line)
500 out.write(line)
499
501
500 if not opts.get('print'):
502 if not opts.get('print'):
501 out.close()
503 out.close()
502
504
503 if m3.conflicts and not mode == 'union':
505 if m3.conflicts and not mode == 'union':
504 return 1
506 return 1
General Comments 0
You need to be logged in to leave comments. Login now