Mercurial > traipse_dev
comparison upmana/mercurial/merge.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 # merge.py - directory-level update/merge handling for Mercurial | |
2 # | |
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com> | |
4 # | |
5 # This software may be used and distributed according to the terms of the | |
6 # GNU General Public License version 2, incorporated herein by reference. | |
7 | |
8 from node import nullid, nullrev, hex, bin | |
9 from i18n import _ | |
10 import util, filemerge, copies, subrepo | |
11 import errno, os, shutil | |
12 | |
13 class mergestate(object): | |
14 '''track 3-way merge state of individual files''' | |
15 def __init__(self, repo): | |
16 self._repo = repo | |
17 self._read() | |
18 def reset(self, node=None): | |
19 self._state = {} | |
20 if node: | |
21 self._local = node | |
22 shutil.rmtree(self._repo.join("merge"), True) | |
23 def _read(self): | |
24 self._state = {} | |
25 try: | |
26 localnode = None | |
27 f = self._repo.opener("merge/state") | |
28 for i, l in enumerate(f): | |
29 if i == 0: | |
30 localnode = l[:-1] | |
31 else: | |
32 bits = l[:-1].split("\0") | |
33 self._state[bits[0]] = bits[1:] | |
34 self._local = bin(localnode) | |
35 except IOError, err: | |
36 if err.errno != errno.ENOENT: | |
37 raise | |
38 def _write(self): | |
39 f = self._repo.opener("merge/state", "w") | |
40 f.write(hex(self._local) + "\n") | |
41 for d, v in self._state.iteritems(): | |
42 f.write("\0".join([d] + v) + "\n") | |
43 def add(self, fcl, fco, fca, fd, flags): | |
44 hash = util.sha1(fcl.path()).hexdigest() | |
45 self._repo.opener("merge/" + hash, "w").write(fcl.data()) | |
46 self._state[fd] = ['u', hash, fcl.path(), fca.path(), | |
47 hex(fca.filenode()), fco.path(), flags] | |
48 self._write() | |
49 def __contains__(self, dfile): | |
50 return dfile in self._state | |
51 def __getitem__(self, dfile): | |
52 return self._state[dfile][0] | |
53 def __iter__(self): | |
54 l = self._state.keys() | |
55 l.sort() | |
56 for f in l: | |
57 yield f | |
58 def mark(self, dfile, state): | |
59 self._state[dfile][0] = state | |
60 self._write() | |
61 def resolve(self, dfile, wctx, octx): | |
62 if self[dfile] == 'r': | |
63 return 0 | |
64 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile] | |
65 f = self._repo.opener("merge/" + hash) | |
66 self._repo.wwrite(dfile, f.read(), flags) | |
67 fcd = wctx[dfile] | |
68 fco = octx[ofile] | |
69 fca = self._repo.filectx(afile, fileid=anode) | |
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca) | |
71 if not r: | |
72 self.mark(dfile, 'r') | |
73 return r | |
74 | |
75 def _checkunknown(wctx, mctx): | |
76 "check for collisions between unknown files and files in mctx" | |
77 for f in wctx.unknown(): | |
78 if f in mctx and mctx[f].cmp(wctx[f].data()): | |
79 raise util.Abort(_("untracked file in working directory differs" | |
80 " from file in requested revision: '%s'") % f) | |
81 | |
82 def _checkcollision(mctx): | |
83 "check for case folding collisions in the destination context" | |
84 folded = {} | |
85 for fn in mctx: | |
86 fold = fn.lower() | |
87 if fold in folded: | |
88 raise util.Abort(_("case-folding collision between %s and %s") | |
89 % (fn, folded[fold])) | |
90 folded[fold] = fn | |
91 | |
92 def _forgetremoved(wctx, mctx, branchmerge): | |
93 """ | |
94 Forget removed files | |
95 | |
96 If we're jumping between revisions (as opposed to merging), and if | |
97 neither the working directory nor the target rev has the file, | |
98 then we need to remove it from the dirstate, to prevent the | |
99 dirstate from listing the file when it is no longer in the | |
100 manifest. | |
101 | |
102 If we're merging, and the other revision has removed a file | |
103 that is not present in the working directory, we need to mark it | |
104 as removed. | |
105 """ | |
106 | |
107 action = [] | |
108 state = branchmerge and 'r' or 'f' | |
109 for f in wctx.deleted(): | |
110 if f not in mctx: | |
111 action.append((f, state)) | |
112 | |
113 if not branchmerge: | |
114 for f in wctx.removed(): | |
115 if f not in mctx: | |
116 action.append((f, "f")) | |
117 | |
118 return action | |
119 | |
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial): | |
121 """ | |
122 Merge p1 and p2 with ancestor ma and generate merge action list | |
123 | |
124 overwrite = whether we clobber working files | |
125 partial = function to filter file lists | |
126 """ | |
127 | |
128 def fmerge(f, f2, fa): | |
129 """merge flags""" | |
130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2) | |
131 if m == n: # flags agree | |
132 return m # unchanged | |
133 if m and n and not a: # flags set, don't agree, differ from parent | |
134 r = repo.ui.prompt( | |
135 _(" conflicting flags for %s\n" | |
136 "(n)one, e(x)ec or sym(l)ink?") % f, | |
137 (_("&None"), _("E&xec"), _("Sym&link")), _("n")) | |
138 return r != _("n") and r or '' | |
139 if m and m != a: # changed from a to m | |
140 return m | |
141 if n and n != a: # changed from a to n | |
142 return n | |
143 return '' # flag was cleared | |
144 | |
145 def act(msg, m, f, *args): | |
146 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) | |
147 action.append((f, m) + args) | |
148 | |
149 action, copy = [], {} | |
150 | |
151 if overwrite: | |
152 pa = p1 | |
153 elif pa == p2: # backwards | |
154 pa = p1.p1() | |
155 elif pa and repo.ui.configbool("merge", "followcopies", True): | |
156 dirs = repo.ui.configbool("merge", "followdirs", True) | |
157 copy, diverge = copies.copies(repo, p1, p2, pa, dirs) | |
158 for of, fl in diverge.iteritems(): | |
159 act("divergent renames", "dr", of, fl) | |
160 | |
161 repo.ui.note(_("resolving manifests\n")) | |
162 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial))) | |
163 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2)) | |
164 | |
165 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest() | |
166 copied = set(copy.values()) | |
167 | |
168 # Compare manifests | |
169 for f, n in m1.iteritems(): | |
170 if partial and not partial(f): | |
171 continue | |
172 if f in m2: | |
173 rflags = fmerge(f, f, f) | |
174 a = ma.get(f, nullid) | |
175 if n == m2[f] or m2[f] == a: # same or local newer | |
176 if m1.flags(f) != rflags: | |
177 act("update permissions", "e", f, rflags) | |
178 elif n == a: # remote newer | |
179 act("remote is newer", "g", f, rflags) | |
180 else: # both changed | |
181 act("versions differ", "m", f, f, f, rflags, False) | |
182 elif f in copied: # files we'll deal with on m2 side | |
183 pass | |
184 elif f in copy: | |
185 f2 = copy[f] | |
186 if f2 not in m2: # directory rename | |
187 act("remote renamed directory to " + f2, "d", | |
188 f, None, f2, m1.flags(f)) | |
189 else: # case 2 A,B/B/B or case 4,21 A/B/B | |
190 act("local copied/moved to " + f2, "m", | |
191 f, f2, f, fmerge(f, f2, f2), False) | |
192 elif f in ma: # clean, a different, no remote | |
193 if n != ma[f]: | |
194 if repo.ui.prompt( | |
195 _(" local changed %s which remote deleted\n" | |
196 "use (c)hanged version or (d)elete?") % f, | |
197 (_("&Changed"), _("&Delete")), _("c")) == _("d"): | |
198 act("prompt delete", "r", f) | |
199 else: | |
200 act("prompt keep", "a", f) | |
201 elif n[20:] == "a": # added, no remote | |
202 act("remote deleted", "f", f) | |
203 elif n[20:] != "u": | |
204 act("other deleted", "r", f) | |
205 | |
206 for f, n in m2.iteritems(): | |
207 if partial and not partial(f): | |
208 continue | |
209 if f in m1 or f in copied: # files already visited | |
210 continue | |
211 if f in copy: | |
212 f2 = copy[f] | |
213 if f2 not in m1: # directory rename | |
214 act("local renamed directory to " + f2, "d", | |
215 None, f, f2, m2.flags(f)) | |
216 elif f2 in m2: # rename case 1, A/A,B/A | |
217 act("remote copied to " + f, "m", | |
218 f2, f, f, fmerge(f2, f, f2), False) | |
219 else: # case 3,20 A/B/A | |
220 act("remote moved to " + f, "m", | |
221 f2, f, f, fmerge(f2, f, f2), True) | |
222 elif f not in ma: | |
223 act("remote created", "g", f, m2.flags(f)) | |
224 elif n != ma[f]: | |
225 if repo.ui.prompt( | |
226 _("remote changed %s which local deleted\n" | |
227 "use (c)hanged version or leave (d)eleted?") % f, | |
228 (_("&Changed"), _("&Deleted")), _("c")) == _("c"): | |
229 act("prompt recreating", "g", f, m2.flags(f)) | |
230 | |
231 return action | |
232 | |
233 def actionkey(a): | |
234 return a[1] == 'r' and -1 or 0, a | |
235 | |
236 def applyupdates(repo, action, wctx, mctx): | |
237 "apply the merge action list to the working directory" | |
238 | |
239 updated, merged, removed, unresolved = 0, 0, 0, 0 | |
240 ms = mergestate(repo) | |
241 ms.reset(wctx.parents()[0].node()) | |
242 moves = [] | |
243 action.sort(key=actionkey) | |
244 substate = wctx.substate # prime | |
245 | |
246 # prescan for merges | |
247 for a in action: | |
248 f, m = a[:2] | |
249 if m == 'm': # merge | |
250 f2, fd, flags, move = a[2:] | |
251 if f == '.hgsubstate': # merged internally | |
252 continue | |
253 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd)) | |
254 fcl = wctx[f] | |
255 fco = mctx[f2] | |
256 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev) | |
257 ms.add(fcl, fco, fca, fd, flags) | |
258 if f != fd and move: | |
259 moves.append(f) | |
260 | |
261 # remove renamed files after safely stored | |
262 for f in moves: | |
263 if util.lexists(repo.wjoin(f)): | |
264 repo.ui.debug(_("removing %s\n") % f) | |
265 os.unlink(repo.wjoin(f)) | |
266 | |
267 audit_path = util.path_auditor(repo.root) | |
268 | |
269 for a in action: | |
270 f, m = a[:2] | |
271 if f and f[0] == "/": | |
272 continue | |
273 if m == "r": # remove | |
274 repo.ui.note(_("removing %s\n") % f) | |
275 audit_path(f) | |
276 if f == '.hgsubstate': # subrepo states need updating | |
277 subrepo.submerge(repo, wctx, mctx, wctx) | |
278 try: | |
279 util.unlink(repo.wjoin(f)) | |
280 except OSError, inst: | |
281 if inst.errno != errno.ENOENT: | |
282 repo.ui.warn(_("update failed to remove %s: %s!\n") % | |
283 (f, inst.strerror)) | |
284 removed += 1 | |
285 elif m == "m": # merge | |
286 if f == '.hgsubstate': # subrepo states need updating | |
287 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx)) | |
288 continue | |
289 f2, fd, flags, move = a[2:] | |
290 r = ms.resolve(fd, wctx, mctx) | |
291 if r > 0: | |
292 unresolved += 1 | |
293 else: | |
294 if r is None: | |
295 updated += 1 | |
296 else: | |
297 merged += 1 | |
298 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags) | |
299 if f != fd and move and util.lexists(repo.wjoin(f)): | |
300 repo.ui.debug(_("removing %s\n") % f) | |
301 os.unlink(repo.wjoin(f)) | |
302 elif m == "g": # get | |
303 flags = a[2] | |
304 repo.ui.note(_("getting %s\n") % f) | |
305 t = mctx.filectx(f).data() | |
306 repo.wwrite(f, t, flags) | |
307 updated += 1 | |
308 if f == '.hgsubstate': # subrepo states need updating | |
309 subrepo.submerge(repo, wctx, mctx, wctx) | |
310 elif m == "d": # directory rename | |
311 f2, fd, flags = a[2:] | |
312 if f: | |
313 repo.ui.note(_("moving %s to %s\n") % (f, fd)) | |
314 t = wctx.filectx(f).data() | |
315 repo.wwrite(fd, t, flags) | |
316 util.unlink(repo.wjoin(f)) | |
317 if f2: | |
318 repo.ui.note(_("getting %s to %s\n") % (f2, fd)) | |
319 t = mctx.filectx(f2).data() | |
320 repo.wwrite(fd, t, flags) | |
321 updated += 1 | |
322 elif m == "dr": # divergent renames | |
323 fl = a[2] | |
324 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f) | |
325 for nf in fl: | |
326 repo.ui.warn(" %s\n" % nf) | |
327 elif m == "e": # exec | |
328 flags = a[2] | |
329 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags) | |
330 | |
331 return updated, merged, removed, unresolved | |
332 | |
333 def recordupdates(repo, action, branchmerge): | |
334 "record merge actions to the dirstate" | |
335 | |
336 for a in action: | |
337 f, m = a[:2] | |
338 if m == "r": # remove | |
339 if branchmerge: | |
340 repo.dirstate.remove(f) | |
341 else: | |
342 repo.dirstate.forget(f) | |
343 elif m == "a": # re-add | |
344 if not branchmerge: | |
345 repo.dirstate.add(f) | |
346 elif m == "f": # forget | |
347 repo.dirstate.forget(f) | |
348 elif m == "e": # exec change | |
349 repo.dirstate.normallookup(f) | |
350 elif m == "g": # get | |
351 if branchmerge: | |
352 repo.dirstate.normaldirty(f) | |
353 else: | |
354 repo.dirstate.normal(f) | |
355 elif m == "m": # merge | |
356 f2, fd, flag, move = a[2:] | |
357 if branchmerge: | |
358 # We've done a branch merge, mark this file as merged | |
359 # so that we properly record the merger later | |
360 repo.dirstate.merge(fd) | |
361 if f != f2: # copy/rename | |
362 if move: | |
363 repo.dirstate.remove(f) | |
364 if f != fd: | |
365 repo.dirstate.copy(f, fd) | |
366 else: | |
367 repo.dirstate.copy(f2, fd) | |
368 else: | |
369 # We've update-merged a locally modified file, so | |
370 # we set the dirstate to emulate a normal checkout | |
371 # of that file some time in the past. Thus our | |
372 # merge will appear as a normal local file | |
373 # modification. | |
374 repo.dirstate.normallookup(fd) | |
375 if move: | |
376 repo.dirstate.forget(f) | |
377 elif m == "d": # directory rename | |
378 f2, fd, flag = a[2:] | |
379 if not f2 and f not in repo.dirstate: | |
380 # untracked file moved | |
381 continue | |
382 if branchmerge: | |
383 repo.dirstate.add(fd) | |
384 if f: | |
385 repo.dirstate.remove(f) | |
386 repo.dirstate.copy(f, fd) | |
387 if f2: | |
388 repo.dirstate.copy(f2, fd) | |
389 else: | |
390 repo.dirstate.normal(fd) | |
391 if f: | |
392 repo.dirstate.forget(f) | |
393 | |
394 def update(repo, node, branchmerge, force, partial): | |
395 """ | |
396 Perform a merge between the working directory and the given node | |
397 | |
398 branchmerge = whether to merge between branches | |
399 force = whether to force branch merging or file overwriting | |
400 partial = a function to filter file lists (dirstate not updated) | |
401 """ | |
402 | |
403 wlock = repo.wlock() | |
404 try: | |
405 wc = repo[None] | |
406 if node is None: | |
407 # tip of current branch | |
408 try: | |
409 node = repo.branchtags()[wc.branch()] | |
410 except KeyError: | |
411 if wc.branch() == "default": # no default branch! | |
412 node = repo.lookup("tip") # update to tip | |
413 else: | |
414 raise util.Abort(_("branch %s not found") % wc.branch()) | |
415 overwrite = force and not branchmerge | |
416 pl = wc.parents() | |
417 p1, p2 = pl[0], repo[node] | |
418 pa = p1.ancestor(p2) | |
419 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2) | |
420 fastforward = False | |
421 | |
422 ### check phase | |
423 if not overwrite and len(pl) > 1: | |
424 raise util.Abort(_("outstanding uncommitted merges")) | |
425 if branchmerge: | |
426 if pa == p2: | |
427 raise util.Abort(_("can't merge with ancestor")) | |
428 elif pa == p1: | |
429 if p1.branch() != p2.branch(): | |
430 fastforward = True | |
431 else: | |
432 raise util.Abort(_("nothing to merge (use 'hg update'" | |
433 " or check 'hg heads')")) | |
434 if not force and (wc.files() or wc.deleted()): | |
435 raise util.Abort(_("outstanding uncommitted changes " | |
436 "(use 'hg status' to list changes)")) | |
437 elif not overwrite: | |
438 if pa == p1 or pa == p2: # linear | |
439 pass # all good | |
440 elif p1.branch() == p2.branch(): | |
441 if wc.files() or wc.deleted(): | |
442 raise util.Abort(_("crosses branches (use 'hg merge' or " | |
443 "'hg update -C' to discard changes)")) | |
444 raise util.Abort(_("crosses branches (use 'hg merge' " | |
445 "or 'hg update -C')")) | |
446 elif wc.files() or wc.deleted(): | |
447 raise util.Abort(_("crosses named branches (use " | |
448 "'hg update -C' to discard changes)")) | |
449 else: | |
450 # Allow jumping branches if there are no changes | |
451 overwrite = True | |
452 | |
453 ### calculate phase | |
454 action = [] | |
455 if not force: | |
456 _checkunknown(wc, p2) | |
457 if not util.checkcase(repo.path): | |
458 _checkcollision(p2) | |
459 action += _forgetremoved(wc, p2, branchmerge) | |
460 action += manifestmerge(repo, wc, p2, pa, overwrite, partial) | |
461 | |
462 ### apply phase | |
463 if not branchmerge: # just jump to the new rev | |
464 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, '' | |
465 if not partial: | |
466 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2) | |
467 | |
468 stats = applyupdates(repo, action, wc, p2) | |
469 | |
470 if not partial: | |
471 recordupdates(repo, action, branchmerge) | |
472 repo.dirstate.setparents(fp1, fp2) | |
473 if not branchmerge and not fastforward: | |
474 repo.dirstate.setbranch(p2.branch()) | |
475 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3]) | |
476 | |
477 return stats | |
478 finally: | |
479 wlock.release() |