##// END OF EJS Templates
simplemerge: enable option to resolve conflicts one way...
Jordi Gutiérrez Hermoso -
r26223:ed12abab default
parent child Browse files
Show More
@@ -1,410 +1,418
1 1 # Copyright (C) 2004, 2005 Canonical Ltd
2 2 #
3 3 # This program is free software; you can redistribute it and/or modify
4 4 # it under the terms of the GNU General Public License as published by
5 5 # the Free Software Foundation; either version 2 of the License, or
6 6 # (at your option) any later version.
7 7 #
8 8 # This program is distributed in the hope that it will be useful,
9 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 11 # GNU General Public License for more details.
12 12 #
13 13 # You should have received a copy of the GNU General Public License
14 14 # along with this program; if not, see <http://www.gnu.org/licenses/>.
15 15
16 16 # mbp: "you know that thing where cvs gives you conflict markers?"
17 17 # s: "i hate that."
18 18
19 19 from __future__ import absolute_import
20 20
21 21 import os
22 22 import sys
23 23
24 24 from .i18n import _
25 25 from . import (
26 26 mdiff,
27 27 scmutil,
28 28 util,
29 29 )
30 30
31 31 class CantReprocessAndShowBase(Exception):
32 32 pass
33 33
34 34 def intersect(ra, rb):
35 35 """Given two ranges return the range where they intersect or None.
36 36
37 37 >>> intersect((0, 10), (0, 6))
38 38 (0, 6)
39 39 >>> intersect((0, 10), (5, 15))
40 40 (5, 10)
41 41 >>> intersect((0, 10), (10, 15))
42 42 >>> intersect((0, 9), (10, 15))
43 43 >>> intersect((0, 9), (7, 15))
44 44 (7, 9)
45 45 """
46 46 assert ra[0] <= ra[1]
47 47 assert rb[0] <= rb[1]
48 48
49 49 sa = max(ra[0], rb[0])
50 50 sb = min(ra[1], rb[1])
51 51 if sa < sb:
52 52 return sa, sb
53 53 else:
54 54 return None
55 55
56 56 def compare_range(a, astart, aend, b, bstart, bend):
57 57 """Compare a[astart:aend] == b[bstart:bend], without slicing.
58 58 """
59 59 if (aend - astart) != (bend - bstart):
60 60 return False
61 61 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
62 62 if a[ia] != b[ib]:
63 63 return False
64 64 else:
65 65 return True
66 66
67 67 class Merge3Text(object):
68 68 """3-way merge of texts.
69 69
70 70 Given strings BASE, OTHER, THIS, tries to produce a combined text
71 71 incorporating the changes from both BASE->OTHER and BASE->THIS."""
72 72 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
73 73 self.basetext = basetext
74 74 self.atext = atext
75 75 self.btext = btext
76 76 if base is None:
77 77 base = mdiff.splitnewlines(basetext)
78 78 if a is None:
79 79 a = mdiff.splitnewlines(atext)
80 80 if b is None:
81 81 b = mdiff.splitnewlines(btext)
82 82 self.base = base
83 83 self.a = a
84 84 self.b = b
85 85
86 86 def merge_lines(self,
87 87 name_a=None,
88 88 name_b=None,
89 89 name_base=None,
90 90 start_marker='<<<<<<<',
91 91 mid_marker='=======',
92 92 end_marker='>>>>>>>',
93 base_marker=None):
93 base_marker=None,
94 localorother=None):
94 95 """Return merge in cvs-like form.
95 96 """
96 97 self.conflicts = False
97 98 newline = '\n'
98 99 if len(self.a) > 0:
99 100 if self.a[0].endswith('\r\n'):
100 101 newline = '\r\n'
101 102 elif self.a[0].endswith('\r'):
102 103 newline = '\r'
103 104 if name_a and start_marker:
104 105 start_marker = start_marker + ' ' + name_a
105 106 if name_b and end_marker:
106 107 end_marker = end_marker + ' ' + name_b
107 108 if name_base and base_marker:
108 109 base_marker = base_marker + ' ' + name_base
109 110 merge_regions = self.merge_regions()
110 111 for t in merge_regions:
111 112 what = t[0]
112 113 if what == 'unchanged':
113 114 for i in range(t[1], t[2]):
114 115 yield self.base[i]
115 116 elif what == 'a' or what == 'same':
116 117 for i in range(t[1], t[2]):
117 118 yield self.a[i]
118 119 elif what == 'b':
119 120 for i in range(t[1], t[2]):
120 121 yield self.b[i]
121 122 elif what == 'conflict':
122 self.conflicts = True
123 if start_marker is not None:
124 yield start_marker + newline
125 for i in range(t[3], t[4]):
126 yield self.a[i]
127 if base_marker is not None:
128 yield base_marker + newline
129 for i in range(t[1], t[2]):
130 yield self.base[i]
131 if mid_marker is not None:
132 yield mid_marker + newline
133 for i in range(t[5], t[6]):
134 yield self.b[i]
135 if end_marker is not None:
136 yield end_marker + newline
123 if localorother == 'local':
124 for i in range(t[3], t[4]):
125 yield self.a[i]
126 elif localorother == 'other':
127 for i in range(t[5], t[6]):
128 yield self.b[i]
129 else:
130 self.conflicts = True
131 if start_marker is not None:
132 yield start_marker + newline
133 for i in range(t[3], t[4]):
134 yield self.a[i]
135 if base_marker is not None:
136 yield base_marker + newline
137 for i in range(t[1], t[2]):
138 yield self.base[i]
139 if mid_marker is not None:
140 yield mid_marker + newline
141 for i in range(t[5], t[6]):
142 yield self.b[i]
143 if end_marker is not None:
144 yield end_marker + newline
137 145 else:
138 146 raise ValueError(what)
139 147
140 148 def merge_groups(self):
141 149 """Yield sequence of line groups. Each one is a tuple:
142 150
143 151 'unchanged', lines
144 152 Lines unchanged from base
145 153
146 154 'a', lines
147 155 Lines taken from a
148 156
149 157 'same', lines
150 158 Lines taken from a (and equal to b)
151 159
152 160 'b', lines
153 161 Lines taken from b
154 162
155 163 'conflict', base_lines, a_lines, b_lines
156 164 Lines from base were changed to either a or b and conflict.
157 165 """
158 166 for t in self.merge_regions():
159 167 what = t[0]
160 168 if what == 'unchanged':
161 169 yield what, self.base[t[1]:t[2]]
162 170 elif what == 'a' or what == 'same':
163 171 yield what, self.a[t[1]:t[2]]
164 172 elif what == 'b':
165 173 yield what, self.b[t[1]:t[2]]
166 174 elif what == 'conflict':
167 175 yield (what,
168 176 self.base[t[1]:t[2]],
169 177 self.a[t[3]:t[4]],
170 178 self.b[t[5]:t[6]])
171 179 else:
172 180 raise ValueError(what)
173 181
174 182 def merge_regions(self):
175 183 """Return sequences of matching and conflicting regions.
176 184
177 185 This returns tuples, where the first value says what kind we
178 186 have:
179 187
180 188 'unchanged', start, end
181 189 Take a region of base[start:end]
182 190
183 191 'same', astart, aend
184 192 b and a are different from base but give the same result
185 193
186 194 'a', start, end
187 195 Non-clashing insertion from a[start:end]
188 196
189 197 Method is as follows:
190 198
191 199 The two sequences align only on regions which match the base
192 200 and both descendants. These are found by doing a two-way diff
193 201 of each one against the base, and then finding the
194 202 intersections between those regions. These "sync regions"
195 203 are by definition unchanged in both and easily dealt with.
196 204
197 205 The regions in between can be in any of three cases:
198 206 conflicted, or changed on only one side.
199 207 """
200 208
201 209 # section a[0:ia] has been disposed of, etc
202 210 iz = ia = ib = 0
203 211
204 212 for region in self.find_sync_regions():
205 213 zmatch, zend, amatch, aend, bmatch, bend = region
206 214 #print 'match base [%d:%d]' % (zmatch, zend)
207 215
208 216 matchlen = zend - zmatch
209 217 assert matchlen >= 0
210 218 assert matchlen == (aend - amatch)
211 219 assert matchlen == (bend - bmatch)
212 220
213 221 len_a = amatch - ia
214 222 len_b = bmatch - ib
215 223 len_base = zmatch - iz
216 224 assert len_a >= 0
217 225 assert len_b >= 0
218 226 assert len_base >= 0
219 227
220 228 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
221 229
222 230 if len_a or len_b:
223 231 # try to avoid actually slicing the lists
224 232 equal_a = compare_range(self.a, ia, amatch,
225 233 self.base, iz, zmatch)
226 234 equal_b = compare_range(self.b, ib, bmatch,
227 235 self.base, iz, zmatch)
228 236 same = compare_range(self.a, ia, amatch,
229 237 self.b, ib, bmatch)
230 238
231 239 if same:
232 240 yield 'same', ia, amatch
233 241 elif equal_a and not equal_b:
234 242 yield 'b', ib, bmatch
235 243 elif equal_b and not equal_a:
236 244 yield 'a', ia, amatch
237 245 elif not equal_a and not equal_b:
238 246 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
239 247 else:
240 248 raise AssertionError("can't handle a=b=base but unmatched")
241 249
242 250 ia = amatch
243 251 ib = bmatch
244 252 iz = zmatch
245 253
246 254 # if the same part of the base was deleted on both sides
247 255 # that's OK, we can just skip it.
248 256
249 257
250 258 if matchlen > 0:
251 259 assert ia == amatch
252 260 assert ib == bmatch
253 261 assert iz == zmatch
254 262
255 263 yield 'unchanged', zmatch, zend
256 264 iz = zend
257 265 ia = aend
258 266 ib = bend
259 267
260 268 def find_sync_regions(self):
261 269 """Return a list of sync regions, where both descendants match the base.
262 270
263 271 Generates a list of (base1, base2, a1, a2, b1, b2). There is
264 272 always a zero-length sync region at the end of all the files.
265 273 """
266 274
267 275 ia = ib = 0
268 276 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
269 277 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
270 278 len_a = len(amatches)
271 279 len_b = len(bmatches)
272 280
273 281 sl = []
274 282
275 283 while ia < len_a and ib < len_b:
276 284 abase, amatch, alen = amatches[ia]
277 285 bbase, bmatch, blen = bmatches[ib]
278 286
279 287 # there is an unconflicted block at i; how long does it
280 288 # extend? until whichever one ends earlier.
281 289 i = intersect((abase, abase + alen), (bbase, bbase + blen))
282 290 if i:
283 291 intbase = i[0]
284 292 intend = i[1]
285 293 intlen = intend - intbase
286 294
287 295 # found a match of base[i[0], i[1]]; this may be less than
288 296 # the region that matches in either one
289 297 assert intlen <= alen
290 298 assert intlen <= blen
291 299 assert abase <= intbase
292 300 assert bbase <= intbase
293 301
294 302 asub = amatch + (intbase - abase)
295 303 bsub = bmatch + (intbase - bbase)
296 304 aend = asub + intlen
297 305 bend = bsub + intlen
298 306
299 307 assert self.base[intbase:intend] == self.a[asub:aend], \
300 308 (self.base[intbase:intend], self.a[asub:aend])
301 309
302 310 assert self.base[intbase:intend] == self.b[bsub:bend]
303 311
304 312 sl.append((intbase, intend,
305 313 asub, aend,
306 314 bsub, bend))
307 315
308 316 # advance whichever one ends first in the base text
309 317 if (abase + alen) < (bbase + blen):
310 318 ia += 1
311 319 else:
312 320 ib += 1
313 321
314 322 intbase = len(self.base)
315 323 abase = len(self.a)
316 324 bbase = len(self.b)
317 325 sl.append((intbase, intbase, abase, abase, bbase, bbase))
318 326
319 327 return sl
320 328
321 329 def find_unconflicted(self):
322 330 """Return a list of ranges in base that are not conflicted."""
323 331 am = mdiff.get_matching_blocks(self.basetext, self.atext)
324 332 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
325 333
326 334 unc = []
327 335
328 336 while am and bm:
329 337 # there is an unconflicted block at i; how long does it
330 338 # extend? until whichever one ends earlier.
331 339 a1 = am[0][0]
332 340 a2 = a1 + am[0][2]
333 341 b1 = bm[0][0]
334 342 b2 = b1 + bm[0][2]
335 343 i = intersect((a1, a2), (b1, b2))
336 344 if i:
337 345 unc.append(i)
338 346
339 347 if a2 < b2:
340 348 del am[0]
341 349 else:
342 350 del bm[0]
343 351
344 352 return unc
345 353
346 354 def simplemerge(ui, local, base, other, **opts):
347 355 def readfile(filename):
348 356 f = open(filename, "rb")
349 357 text = f.read()
350 358 f.close()
351 359 if util.binary(text):
352 360 msg = _("%s looks like a binary file.") % filename
353 361 if not opts.get('quiet'):
354 362 ui.warn(_('warning: %s\n') % msg)
355 363 if not opts.get('text'):
356 364 raise util.Abort(msg)
357 365 return text
358 366
359 367 mode = opts.get('mode','merge')
360 368 if mode == 'union':
361 369 name_a = None
362 370 name_b = None
363 371 name_base = None
364 372 else:
365 373 name_a = local
366 374 name_b = other
367 375 name_base = None
368 376 labels = opts.get('label', [])
369 377 if len(labels) > 0:
370 378 name_a = labels[0]
371 379 if len(labels) > 1:
372 380 name_b = labels[1]
373 381 if len(labels) > 2:
374 382 name_base = labels[2]
375 383 if len(labels) > 3:
376 384 raise util.Abort(_("can only specify three labels."))
377 385
378 386 try:
379 387 localtext = readfile(local)
380 388 basetext = readfile(base)
381 389 othertext = readfile(other)
382 390 except util.Abort:
383 391 return 1
384 392
385 393 local = os.path.realpath(local)
386 394 if not opts.get('print'):
387 395 opener = scmutil.opener(os.path.dirname(local))
388 396 out = opener(os.path.basename(local), "w", atomictemp=True)
389 397 else:
390 398 out = sys.stdout
391 399
392 400 m3 = Merge3Text(basetext, localtext, othertext)
393 extrakwargs = {}
401 extrakwargs = {"localorother": opts.get("localorother", None)}
394 402 if mode == 'union':
395 403 extrakwargs['start_marker'] = None
396 404 extrakwargs['mid_marker'] = None
397 405 extrakwargs['end_marker'] = None
398 406 elif name_base is not None:
399 407 extrakwargs['base_marker'] = '|||||||'
400 408 extrakwargs['name_base'] = name_base
401 409 for line in m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs):
402 410 out.write(line)
403 411
404 412 if not opts.get('print'):
405 413 out.close()
406 414
407 415 if m3.conflicts and not mode == 'union':
408 416 if not opts.get('quiet'):
409 417 ui.warn(_("warning: conflicts during merge.\n"))
410 418 return 1
General Comments 0
You need to be logged in to leave comments. Login now