comparison upmana/mercurial/simplemerge.py @ 121:496dbf12a6cb alpha

Traipse Alpha 'OpenRPG' {091030-00} Traipse is a distribution of OpenRPG that is designed to be easy to setup and go. Traipse also makes it easy for developers to work on code without fear of sacrifice. 'Ornery-Orc' continues the trend of 'Grumpy' and adds fixes to the code. 'Ornery-Orc's main goal is to offer more advanced features and enhance the productivity of the user. Update Summary (Cleaning up for Beta): Adds Bookmarks (Alpha) with cool Smiley Star and Plus Symbol images! Changes made to the map for increased portability. SnowDog has changes planned in Core, though. Added an initial push to the BCG. Not much to see, just shows off how it is re-writing Main code. Fix to remote admin commands Minor fix to texted based server, works in /System/ folder Some Core changes to gametree to correctly disply Pretty Print, thanks David! Fix to Splitter Nodes not being created. Added images to Plugin Control panel for Autostart feature Fix to massive amounts of images loading; from Core fix to gsclient so with_statement imports Added 'boot' command to remote admin Prep work in Pass tool for remote admin rankings and different passwords, ei, Server, Admin, Moderator, etc. Remote Admin Commands more organized, more prep work. Added Confirmation window for sent nodes. Minor changes to allow for portability to an OpenSUSE linux OS (hopefully without breaking) {091028} Made changes to gametree to start working with Element Tree, mostly from Core Minor changes to Map to start working with Element Tree, from Core Preliminary changes to map efficiency, from FlexiRPG Miniatures Layer pop up box allows users to turn off Mini labels, from FlexiRPG Changes to main.py to start working with Element Tree {091029} Changes made to server to start working with Element Tree. Changes made to Meta Server Lib. Prepping test work for a multi meta network page. Minor bug fixed with mini to gametree Zoom Mouse plugin added. {091030} Getting ready for Beta. Server needs debugging so Alpha remains bugged. Plugin UI code cleaned. Auto start works with a graphic, pop-up asks to enable or disable plugin. Update Manager now has a partially working Status Bar. Status Bar captures terminal text, so Merc out put is visible. Manifest.xml file, will be renamed, is now much cleaner. Debug Console has a clear button and a Report Bug button. Prep work for a Term2Win class in Debug Console. Known: Current Alpha fails in Windows.
author sirebral
date Fri, 30 Oct 2009 22:21:40 -0500
parents
children
comparison
equal deleted inserted replaced
120:d86e762a994f 121:496dbf12a6cb
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