Mercurial > traipse_dev
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 |