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