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