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()