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