##// END OF EJS Templates
simplemerge: flush stdout before writing to stderr....
Patrick Mezard -
r4408:1ef4445c default
parent child Browse files
Show More
@@ -1,557 +1,562 b''
1 1 #!/usr/bin/env python
2 2 # Copyright (C) 2004, 2005 Canonical Ltd
3 3 #
4 4 # This program is free software; you can redistribute it and/or modify
5 5 # it under the terms of the GNU General Public License as published by
6 6 # the Free Software Foundation; either version 2 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software
16 16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 17
18 18
19 19 # mbp: "you know that thing where cvs gives you conflict markers?"
20 20 # s: "i hate that."
21 21
22 22 from mercurial import demandimport
23 23 demandimport.enable()
24 24
25 25 from mercurial import util, mdiff, fancyopts
26 26 from mercurial.i18n import _
27 27
28 28
29 29 class CantReprocessAndShowBase(Exception):
30 30 pass
31
32
33 def warn(message):
34 sys.stdout.flush()
35 sys.stderr.write(message)
36 sys.stderr.flush()
31 37
32 38
33 39 def intersect(ra, rb):
34 40 """Given two ranges return the range where they intersect or None.
35 41
36 42 >>> intersect((0, 10), (0, 6))
37 43 (0, 6)
38 44 >>> intersect((0, 10), (5, 15))
39 45 (5, 10)
40 46 >>> intersect((0, 10), (10, 15))
41 47 >>> intersect((0, 9), (10, 15))
42 48 >>> intersect((0, 9), (7, 15))
43 49 (7, 9)
44 50 """
45 51 assert ra[0] <= ra[1]
46 52 assert rb[0] <= rb[1]
47 53
48 54 sa = max(ra[0], rb[0])
49 55 sb = min(ra[1], rb[1])
50 56 if sa < sb:
51 57 return sa, sb
52 58 else:
53 59 return None
54 60
55 61
56 62 def compare_range(a, astart, aend, b, bstart, bend):
57 63 """Compare a[astart:aend] == b[bstart:bend], without slicing.
58 64 """
59 65 if (aend-astart) != (bend-bstart):
60 66 return False
61 67 for ia, ib in zip(xrange(astart, aend), xrange(bstart, bend)):
62 68 if a[ia] != b[ib]:
63 69 return False
64 70 else:
65 71 return True
66 72
67 73
68 74
69 75
70 76 class Merge3Text(object):
71 77 """3-way merge of texts.
72 78
73 79 Given strings BASE, OTHER, THIS, tries to produce a combined text
74 80 incorporating the changes from both BASE->OTHER and BASE->THIS."""
75 81 def __init__(self, basetext, atext, btext, base=None, a=None, b=None):
76 82 self.basetext = basetext
77 83 self.atext = atext
78 84 self.btext = btext
79 85 if base is None:
80 86 base = mdiff.splitnewlines(basetext)
81 87 if a is None:
82 88 a = mdiff.splitnewlines(atext)
83 89 if b is None:
84 90 b = mdiff.splitnewlines(btext)
85 91 self.base = base
86 92 self.a = a
87 93 self.b = b
88 94
89 95
90 96
91 97 def merge_lines(self,
92 98 name_a=None,
93 99 name_b=None,
94 100 name_base=None,
95 101 start_marker='<<<<<<<',
96 102 mid_marker='=======',
97 103 end_marker='>>>>>>>',
98 104 base_marker=None,
99 105 reprocess=False):
100 106 """Return merge in cvs-like form.
101 107 """
102 108 self.conflicts = False
103 109 newline = '\n'
104 110 if len(self.a) > 0:
105 111 if self.a[0].endswith('\r\n'):
106 112 newline = '\r\n'
107 113 elif self.a[0].endswith('\r'):
108 114 newline = '\r'
109 115 if base_marker and reprocess:
110 116 raise CantReprocessAndShowBase()
111 117 if name_a:
112 118 start_marker = start_marker + ' ' + name_a
113 119 if name_b:
114 120 end_marker = end_marker + ' ' + name_b
115 121 if name_base and base_marker:
116 122 base_marker = base_marker + ' ' + name_base
117 123 merge_regions = self.merge_regions()
118 124 if reprocess is True:
119 125 merge_regions = self.reprocess_merge_regions(merge_regions)
120 126 for t in merge_regions:
121 127 what = t[0]
122 128 if what == 'unchanged':
123 129 for i in range(t[1], t[2]):
124 130 yield self.base[i]
125 131 elif what == 'a' or what == 'same':
126 132 for i in range(t[1], t[2]):
127 133 yield self.a[i]
128 134 elif what == 'b':
129 135 for i in range(t[1], t[2]):
130 136 yield self.b[i]
131 137 elif what == 'conflict':
132 138 self.conflicts = True
133 139 yield start_marker + newline
134 140 for i in range(t[3], t[4]):
135 141 yield self.a[i]
136 142 if base_marker is not None:
137 143 yield base_marker + newline
138 144 for i in range(t[1], t[2]):
139 145 yield self.base[i]
140 146 yield mid_marker + newline
141 147 for i in range(t[5], t[6]):
142 148 yield self.b[i]
143 149 yield end_marker + newline
144 150 else:
145 151 raise ValueError(what)
146 152
147 153
148 154
149 155
150 156
151 157 def merge_annotated(self):
152 158 """Return merge with conflicts, showing origin of lines.
153 159
154 160 Most useful for debugging merge.
155 161 """
156 162 for t in self.merge_regions():
157 163 what = t[0]
158 164 if what == 'unchanged':
159 165 for i in range(t[1], t[2]):
160 166 yield 'u | ' + self.base[i]
161 167 elif what == 'a' or what == 'same':
162 168 for i in range(t[1], t[2]):
163 169 yield what[0] + ' | ' + self.a[i]
164 170 elif what == 'b':
165 171 for i in range(t[1], t[2]):
166 172 yield 'b | ' + self.b[i]
167 173 elif what == 'conflict':
168 174 yield '<<<<\n'
169 175 for i in range(t[3], t[4]):
170 176 yield 'A | ' + self.a[i]
171 177 yield '----\n'
172 178 for i in range(t[5], t[6]):
173 179 yield 'B | ' + self.b[i]
174 180 yield '>>>>\n'
175 181 else:
176 182 raise ValueError(what)
177 183
178 184
179 185
180 186
181 187
182 188 def merge_groups(self):
183 189 """Yield sequence of line groups. Each one is a tuple:
184 190
185 191 'unchanged', lines
186 192 Lines unchanged from base
187 193
188 194 'a', lines
189 195 Lines taken from a
190 196
191 197 'same', lines
192 198 Lines taken from a (and equal to b)
193 199
194 200 'b', lines
195 201 Lines taken from b
196 202
197 203 'conflict', base_lines, a_lines, b_lines
198 204 Lines from base were changed to either a or b and conflict.
199 205 """
200 206 for t in self.merge_regions():
201 207 what = t[0]
202 208 if what == 'unchanged':
203 209 yield what, self.base[t[1]:t[2]]
204 210 elif what == 'a' or what == 'same':
205 211 yield what, self.a[t[1]:t[2]]
206 212 elif what == 'b':
207 213 yield what, self.b[t[1]:t[2]]
208 214 elif what == 'conflict':
209 215 yield (what,
210 216 self.base[t[1]:t[2]],
211 217 self.a[t[3]:t[4]],
212 218 self.b[t[5]:t[6]])
213 219 else:
214 220 raise ValueError(what)
215 221
216 222
217 223 def merge_regions(self):
218 224 """Return sequences of matching and conflicting regions.
219 225
220 226 This returns tuples, where the first value says what kind we
221 227 have:
222 228
223 229 'unchanged', start, end
224 230 Take a region of base[start:end]
225 231
226 232 'same', astart, aend
227 233 b and a are different from base but give the same result
228 234
229 235 'a', start, end
230 236 Non-clashing insertion from a[start:end]
231 237
232 238 Method is as follows:
233 239
234 240 The two sequences align only on regions which match the base
235 241 and both descendents. These are found by doing a two-way diff
236 242 of each one against the base, and then finding the
237 243 intersections between those regions. These "sync regions"
238 244 are by definition unchanged in both and easily dealt with.
239 245
240 246 The regions in between can be in any of three cases:
241 247 conflicted, or changed on only one side.
242 248 """
243 249
244 250 # section a[0:ia] has been disposed of, etc
245 251 iz = ia = ib = 0
246 252
247 253 for zmatch, zend, amatch, aend, bmatch, bend in self.find_sync_regions():
248 254 #print 'match base [%d:%d]' % (zmatch, zend)
249 255
250 256 matchlen = zend - zmatch
251 257 assert matchlen >= 0
252 258 assert matchlen == (aend - amatch)
253 259 assert matchlen == (bend - bmatch)
254 260
255 261 len_a = amatch - ia
256 262 len_b = bmatch - ib
257 263 len_base = zmatch - iz
258 264 assert len_a >= 0
259 265 assert len_b >= 0
260 266 assert len_base >= 0
261 267
262 268 #print 'unmatched a=%d, b=%d' % (len_a, len_b)
263 269
264 270 if len_a or len_b:
265 271 # try to avoid actually slicing the lists
266 272 equal_a = compare_range(self.a, ia, amatch,
267 273 self.base, iz, zmatch)
268 274 equal_b = compare_range(self.b, ib, bmatch,
269 275 self.base, iz, zmatch)
270 276 same = compare_range(self.a, ia, amatch,
271 277 self.b, ib, bmatch)
272 278
273 279 if same:
274 280 yield 'same', ia, amatch
275 281 elif equal_a and not equal_b:
276 282 yield 'b', ib, bmatch
277 283 elif equal_b and not equal_a:
278 284 yield 'a', ia, amatch
279 285 elif not equal_a and not equal_b:
280 286 yield 'conflict', iz, zmatch, ia, amatch, ib, bmatch
281 287 else:
282 288 raise AssertionError("can't handle a=b=base but unmatched")
283 289
284 290 ia = amatch
285 291 ib = bmatch
286 292 iz = zmatch
287 293
288 294 # if the same part of the base was deleted on both sides
289 295 # that's OK, we can just skip it.
290 296
291 297
292 298 if matchlen > 0:
293 299 assert ia == amatch
294 300 assert ib == bmatch
295 301 assert iz == zmatch
296 302
297 303 yield 'unchanged', zmatch, zend
298 304 iz = zend
299 305 ia = aend
300 306 ib = bend
301 307
302 308
303 309 def reprocess_merge_regions(self, merge_regions):
304 310 """Where there are conflict regions, remove the agreed lines.
305 311
306 312 Lines where both A and B have made the same changes are
307 313 eliminated.
308 314 """
309 315 for region in merge_regions:
310 316 if region[0] != "conflict":
311 317 yield region
312 318 continue
313 319 type, iz, zmatch, ia, amatch, ib, bmatch = region
314 320 a_region = self.a[ia:amatch]
315 321 b_region = self.b[ib:bmatch]
316 322 matches = mdiff.get_matching_blocks(''.join(a_region),
317 323 ''.join(b_region))
318 324 next_a = ia
319 325 next_b = ib
320 326 for region_ia, region_ib, region_len in matches[:-1]:
321 327 region_ia += ia
322 328 region_ib += ib
323 329 reg = self.mismatch_region(next_a, region_ia, next_b,
324 330 region_ib)
325 331 if reg is not None:
326 332 yield reg
327 333 yield 'same', region_ia, region_len+region_ia
328 334 next_a = region_ia + region_len
329 335 next_b = region_ib + region_len
330 336 reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
331 337 if reg is not None:
332 338 yield reg
333 339
334 340
335 341 def mismatch_region(next_a, region_ia, next_b, region_ib):
336 342 if next_a < region_ia or next_b < region_ib:
337 343 return 'conflict', None, None, next_a, region_ia, next_b, region_ib
338 344 mismatch_region = staticmethod(mismatch_region)
339 345
340 346
341 347 def find_sync_regions(self):
342 348 """Return a list of sync regions, where both descendents match the base.
343 349
344 350 Generates a list of (base1, base2, a1, a2, b1, b2). There is
345 351 always a zero-length sync region at the end of all the files.
346 352 """
347 353
348 354 ia = ib = 0
349 355 amatches = mdiff.get_matching_blocks(self.basetext, self.atext)
350 356 bmatches = mdiff.get_matching_blocks(self.basetext, self.btext)
351 357 len_a = len(amatches)
352 358 len_b = len(bmatches)
353 359
354 360 sl = []
355 361
356 362 while ia < len_a and ib < len_b:
357 363 abase, amatch, alen = amatches[ia]
358 364 bbase, bmatch, blen = bmatches[ib]
359 365
360 366 # there is an unconflicted block at i; how long does it
361 367 # extend? until whichever one ends earlier.
362 368 i = intersect((abase, abase+alen), (bbase, bbase+blen))
363 369 if i:
364 370 intbase = i[0]
365 371 intend = i[1]
366 372 intlen = intend - intbase
367 373
368 374 # found a match of base[i[0], i[1]]; this may be less than
369 375 # the region that matches in either one
370 376 assert intlen <= alen
371 377 assert intlen <= blen
372 378 assert abase <= intbase
373 379 assert bbase <= intbase
374 380
375 381 asub = amatch + (intbase - abase)
376 382 bsub = bmatch + (intbase - bbase)
377 383 aend = asub + intlen
378 384 bend = bsub + intlen
379 385
380 386 assert self.base[intbase:intend] == self.a[asub:aend], \
381 387 (self.base[intbase:intend], self.a[asub:aend])
382 388
383 389 assert self.base[intbase:intend] == self.b[bsub:bend]
384 390
385 391 sl.append((intbase, intend,
386 392 asub, aend,
387 393 bsub, bend))
388 394
389 395 # advance whichever one ends first in the base text
390 396 if (abase + alen) < (bbase + blen):
391 397 ia += 1
392 398 else:
393 399 ib += 1
394 400
395 401 intbase = len(self.base)
396 402 abase = len(self.a)
397 403 bbase = len(self.b)
398 404 sl.append((intbase, intbase, abase, abase, bbase, bbase))
399 405
400 406 return sl
401 407
402 408
403 409
404 410 def find_unconflicted(self):
405 411 """Return a list of ranges in base that are not conflicted."""
406 412 am = mdiff.get_matching_blocks(self.basetext, self.atext)
407 413 bm = mdiff.get_matching_blocks(self.basetext, self.btext)
408 414
409 415 unc = []
410 416
411 417 while am and bm:
412 418 # there is an unconflicted block at i; how long does it
413 419 # extend? until whichever one ends earlier.
414 420 a1 = am[0][0]
415 421 a2 = a1 + am[0][2]
416 422 b1 = bm[0][0]
417 423 b2 = b1 + bm[0][2]
418 424 i = intersect((a1, a2), (b1, b2))
419 425 if i:
420 426 unc.append(i)
421 427
422 428 if a2 < b2:
423 429 del am[0]
424 430 else:
425 431 del bm[0]
426 432
427 433 return unc
428 434
429 435
430 436 # bzr compatible interface, for the tests
431 437 class Merge3(Merge3Text):
432 438 """3-way merge of texts.
433 439
434 440 Given BASE, OTHER, THIS, tries to produce a combined text
435 441 incorporating the changes from both BASE->OTHER and BASE->THIS.
436 442 All three will typically be sequences of lines."""
437 443 def __init__(self, base, a, b):
438 444 basetext = '\n'.join([i.strip('\n') for i in base] + [''])
439 445 atext = '\n'.join([i.strip('\n') for i in a] + [''])
440 446 btext = '\n'.join([i.strip('\n') for i in b] + [''])
441 447 if util.binary(basetext) or util.binary(atext) or util.binary(btext):
442 448 raise util.Abort(_("don't know how to merge binary files"))
443 449 Merge3Text.__init__(self, basetext, atext, btext, base, a, b)
444 450
445 451
446 452 def simplemerge(local, base, other, **opts):
447 453 def readfile(filename):
448 454 f = open(filename, "rb")
449 455 text = f.read()
450 456 f.close()
451 457 if util.binary(text):
452 458 msg = _("%s looks like a binary file.") % filename
453 459 if not opts.get('text'):
454 460 raise util.Abort(msg)
455 461 elif not opts.get('quiet'):
456 sys.stderr.write(_('warning: %s\n') % msg)
462 warn(_('warning: %s\n') % msg)
457 463 return text
458 464
459 465 name_a = local
460 466 name_b = other
461 467 labels = opts.get('label', [])
462 468 if labels:
463 469 name_a = labels.pop(0)
464 470 if labels:
465 471 name_b = labels.pop(0)
466 472 if labels:
467 473 raise util.Abort(_("can only specify two labels."))
468 474
469 475 localtext = readfile(local)
470 476 basetext = readfile(base)
471 477 othertext = readfile(other)
472 478
473 479 orig = local
474 480 local = os.path.realpath(local)
475 481 if not opts.get('print'):
476 482 opener = util.opener(os.path.dirname(local))
477 483 out = opener(os.path.basename(local), "w", atomictemp=True)
478 484 else:
479 485 out = sys.stdout
480 486
481 487 reprocess = not opts.get('no_minimal')
482 488
483 489 m3 = Merge3Text(basetext, localtext, othertext)
484 490 for line in m3.merge_lines(name_a=name_a, name_b=name_b,
485 491 reprocess=reprocess):
486 492 out.write(line)
487 493
488 494 if not opts.get('print'):
489 495 out.rename()
490 496
491 497 if m3.conflicts:
492 498 if not opts.get('quiet'):
493 sys.stdout.flush()
494 sys.stderr.write(_("warning: conflicts during merge.\n"))
499 warn(_("warning: conflicts during merge.\n"))
495 500 return 1
496 501
497 502 options = [('L', 'label', [], _('labels to use on conflict markers')),
498 503 ('a', 'text', None, _('treat all files as text')),
499 504 ('p', 'print', None,
500 505 _('print results instead of overwriting LOCAL')),
501 506 ('', 'no-minimal', None,
502 507 _('do not try to minimize conflict regions')),
503 508 ('h', 'help', None, _('display help and exit')),
504 509 ('q', 'quiet', None, _('suppress output'))]
505 510
506 511 usage = _('''simplemerge [OPTS] LOCAL BASE OTHER
507 512
508 513 Simple three-way file merge utility with a minimal feature set.
509 514
510 515 Apply to LOCAL the changes necessary to go from BASE to OTHER.
511 516
512 517 By default, LOCAL is overwritten with the results of this operation.
513 518 ''')
514 519
515 520 def showhelp():
516 521 sys.stdout.write(usage)
517 522 sys.stdout.write('\noptions:\n')
518 523
519 524 out_opts = []
520 525 for shortopt, longopt, default, desc in options:
521 526 out_opts.append(('%2s%s' % (shortopt and '-%s' % shortopt,
522 527 longopt and ' --%s' % longopt),
523 528 '%s' % desc))
524 529 opts_len = max([len(opt[0]) for opt in out_opts])
525 530 for first, second in out_opts:
526 531 sys.stdout.write(' %-*s %s\n' % (opts_len, first, second))
527 532
528 533 class ParseError(Exception):
529 534 """Exception raised on errors in parsing the command line."""
530 535
531 536 def main(argv):
532 537 try:
533 538 opts = {}
534 539 try:
535 540 args = fancyopts.fancyopts(argv[1:], options, opts)
536 541 except fancyopts.getopt.GetoptError, e:
537 542 raise ParseError(e)
538 543 if opts['help']:
539 544 showhelp()
540 545 return 0
541 546 if len(args) != 3:
542 547 raise ParseError(_('wrong number of arguments'))
543 548 return simplemerge(*args, **opts)
544 549 except ParseError, e:
545 550 sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
546 551 showhelp()
547 552 return 1
548 553 except util.Abort, e:
549 554 sys.stderr.write("abort: %s\n" % e)
550 555 return 255
551 556 except KeyboardInterrupt:
552 557 return 255
553 558
554 559 if __name__ == '__main__':
555 560 import sys
556 561 import os
557 562 sys.exit(main(sys.argv))
General Comments 0
You need to be logged in to leave comments. Login now