comparison upmana/mercurial/merge.py @ 28:ff154cf3350c ornery-orc

Traipse 'OpenRPG' {100203-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 (Stable) New Features: New Bookmarks Feature New 'boot' command to remote admin New confirmation window for sent nodes Miniatures Layer pop up box allows users to turn off Mini labels, from FlexiRPG New Zoom Mouse plugin added New Images added to Plugin UI Switching to Element Tree New Map efficiency, from FlexiRPG New Status Bar to Update Manager New TrueDebug Class in orpg_log (See documentation for usage) New Portable Mercurial New Tip of the Day, from Core and community New Reference Syntax added for custom PC sheets New Child Reference for gametree New Parent Reference for gametree New Gametree Recursion method, mapping, context sensitivity, and effeciency.. New Features node with bonus nodes and Node Referencing help added New Dieroller structure from Core New DieRoller portability for odd Dice New 7th Sea die roller; ie [7k3] = [7d10.takeHighest(3).open(10)] New 'Mythos' System die roller added New vs. die roller method for WoD; ie [3v3] = [3d10.vs(3)]. Included for Mythos roller also New Warhammer FRPG Die Roller (Special thanks to Puu-san for the support) New EZ_Tree Reference system. Push a button, Traipse the tree, get a reference (Beta!) New Grids act more like Spreadsheets in Use mode, with Auto Calc Fixes: Fix to allow for portability to an OpenSUSE linux OS Fix to mplay_client for Fedora and OpenSUSE Fix to Text based Server Fix to Remote Admin Commands Fix to Pretty Print, from Core Fix to Splitter Nodes not being created Fix to massive amounts of images loading, from Core Fix to Map from gametree not showing to all clients Fix to gametree about menus Fix to Password Manager check on startup Fix to PC Sheets from tool nodes. They now use the tabber_panel Fix to Whiteboard ID to prevent random line or text deleting. Fixes to Server, Remote Server, and Server GUI Fix to Update Manager; cleaner clode for saved repositories Fixes made to Settings Panel and now reactive settings when Ok is pressed Fixes to Alternity roller's attack roll. Uses a simple Tuple instead of a Splice Fix to Use panel of Forms and Tabbers. Now longer enters design mode Fix made Image Fetching. New fetching image and new failed image Fix to whiteboard ID's to prevent non updated clients from ruining the fix. default_manifest.xml renamed to default_upmana.xml
author sirebral
date Wed, 03 Feb 2010 22:16:49 -0600
parents
children
comparison
equal deleted inserted replaced
27:51428d30c59e 28:ff154cf3350c
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()