1 # merge.py - directory-level update/merge handling for Mercurial
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
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.
8 from node import nullid, nullrev, hex, bin
10 import util, filemerge, copies, subrepo
11 import errno, os, shutil
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
18 def reset(self, node=None):
22 shutil.rmtree(self._repo.join("merge"), True)
27 f = self._repo.opener("merge/state")
28 for i, l in enumerate(f):
32 bits = l[:-1].split("\0")
33 self._state[bits[0]] = bits[1:]
34 self._local = bin(localnode)
36 if err.errno != errno.ENOENT:
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]
49 def __contains__(self, dfile):
50 return dfile in self._state
51 def __getitem__(self, dfile):
52 return self._state[dfile][0]
54 l = self._state.keys()
58 def mark(self, dfile, state):
59 self._state[dfile][0] = state
61 def resolve(self, dfile, wctx, octx):
62 if self[dfile] == 'r':
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)
69 fca = self._repo.filectx(afile, fileid=anode)
70 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
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)
82 def _checkcollision(mctx):
83 "check for case folding collisions in the destination context"
88 raise util.Abort(_("case-folding collision between %s and %s")
92 def _forgetremoved(wctx, mctx, branchmerge):
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
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
108 state = branchmerge and 'r' or 'f'
109 for f in wctx.deleted():
111 action.append((f, state))
114 for f in wctx.removed():
116 action.append((f, "f"))
120 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
122 Merge p1 and p2 with ancestor ma and generate merge action list
124 overwrite = whether we clobber working files
125 partial = function to filter file lists
128 def fmerge(f, f2, fa):
130 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
131 if m == n: # flags agree
133 if m and n and not a: # flags set, don't agree, differ from parent
134 r = repo.ui.promptchoice(
135 _(" conflicting flags for %s\n"
136 "(n)one, e(x)ec or sym(l)ink?") % f,
137 (_("&None"), _("E&xec"), _("Sym&link")), 0)
138 if r == 1: return "x" # Exec
139 if r == 2: return "l" # Symlink
141 if m and m != a: # changed from a to m
143 if n and n != a: # changed from a to n
145 return '' # flag was cleared
147 def act(msg, m, f, *args):
148 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
149 action.append((f, m) + args)
151 action, copy = [], {}
155 elif pa == p2: # backwards
157 elif pa and repo.ui.configbool("merge", "followcopies", True):
158 dirs = repo.ui.configbool("merge", "followdirs", True)
159 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
160 for of, fl in diverge.iteritems():
161 act("divergent renames", "dr", of, fl)
163 repo.ui.note(_("resolving manifests\n"))
164 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
165 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
167 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
168 copied = set(copy.values())
171 for f, n in m1.iteritems():
172 if partial and not partial(f):
175 rflags = fmerge(f, f, f)
176 a = ma.get(f, nullid)
177 if n == m2[f] or m2[f] == a: # same or local newer
178 if m1.flags(f) != rflags:
179 act("update permissions", "e", f, rflags)
180 elif n == a: # remote newer
181 act("remote is newer", "g", f, rflags)
183 act("versions differ", "m", f, f, f, rflags, False)
184 elif f in copied: # files we'll deal with on m2 side
188 if f2 not in m2: # directory rename
189 act("remote renamed directory to " + f2, "d",
190 f, None, f2, m1.flags(f))
191 else: # case 2 A,B/B/B or case 4,21 A/B/B
192 act("local copied/moved to " + f2, "m",
193 f, f2, f, fmerge(f, f2, f2), False)
194 elif f in ma: # clean, a different, no remote
196 if repo.ui.promptchoice(
197 _(" local changed %s which remote deleted\n"
198 "use (c)hanged version or (d)elete?") % f,
199 (_("&Changed"), _("&Delete")), 0):
200 act("prompt delete", "r", f)
202 act("prompt keep", "a", f)
203 elif n[20:] == "a": # added, no remote
204 act("remote deleted", "f", f)
206 act("other deleted", "r", f)
208 for f, n in m2.iteritems():
209 if partial and not partial(f):
211 if f in m1 or f in copied: # files already visited
215 if f2 not in m1: # directory rename
216 act("local renamed directory to " + f2, "d",
217 None, f, f2, m2.flags(f))
218 elif f2 in m2: # rename case 1, A/A,B/A
219 act("remote copied to " + f, "m",
220 f2, f, f, fmerge(f2, f, f2), False)
221 else: # case 3,20 A/B/A
222 act("remote moved to " + f, "m",
223 f2, f, f, fmerge(f2, f, f2), True)
225 act("remote created", "g", f, m2.flags(f))
227 if repo.ui.promptchoice(
228 _("remote changed %s which local deleted\n"
229 "use (c)hanged version or leave (d)eleted?") % f,
230 (_("&Changed"), _("&Deleted")), 0) == 0:
231 act("prompt recreating", "g", f, m2.flags(f))
236 return a[1] == 'r' and -1 or 0, a
238 def applyupdates(repo, action, wctx, mctx):
239 "apply the merge action list to the working directory"
241 updated, merged, removed, unresolved = 0, 0, 0, 0
242 ms = mergestate(repo)
243 ms.reset(wctx.parents()[0].node())
245 action.sort(key=actionkey)
246 substate = wctx.substate # prime
252 f2, fd, flags, move = a[2:]
253 if f == '.hgsubstate': # merged internally
255 repo.ui.debug(_("preserving %s for resolve of %s\n") % (f, fd))
258 fca = fcl.ancestor(fco) or repo.filectx(f, fileid=nullrev)
259 ms.add(fcl, fco, fca, fd, flags)
263 # remove renamed files after safely stored
265 if util.lexists(repo.wjoin(f)):
266 repo.ui.debug(_("removing %s\n") % f)
267 os.unlink(repo.wjoin(f))
269 audit_path = util.path_auditor(repo.root)
273 if f and f[0] == "/":
275 if m == "r": # remove
276 repo.ui.note(_("removing %s\n") % f)
278 if f == '.hgsubstate': # subrepo states need updating
279 subrepo.submerge(repo, wctx, mctx, wctx)
281 util.unlink(repo.wjoin(f))
282 except OSError, inst:
283 if inst.errno != errno.ENOENT:
284 repo.ui.warn(_("update failed to remove %s: %s!\n") %
287 elif m == "m": # merge
288 if f == '.hgsubstate': # subrepo states need updating
289 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx))
291 f2, fd, flags, move = a[2:]
292 r = ms.resolve(fd, wctx, mctx)
293 if r is not None and r > 0:
300 util.set_flags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
301 if f != fd and move and util.lexists(repo.wjoin(f)):
302 repo.ui.debug(_("removing %s\n") % f)
303 os.unlink(repo.wjoin(f))
306 repo.ui.note(_("getting %s\n") % f)
307 t = mctx.filectx(f).data()
308 repo.wwrite(f, t, flags)
310 if f == '.hgsubstate': # subrepo states need updating
311 subrepo.submerge(repo, wctx, mctx, wctx)
312 elif m == "d": # directory rename
313 f2, fd, flags = a[2:]
315 repo.ui.note(_("moving %s to %s\n") % (f, fd))
316 t = wctx.filectx(f).data()
317 repo.wwrite(fd, t, flags)
318 util.unlink(repo.wjoin(f))
320 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
321 t = mctx.filectx(f2).data()
322 repo.wwrite(fd, t, flags)
324 elif m == "dr": # divergent renames
326 repo.ui.warn(_("warning: detected divergent renames of %s to:\n") % f)
328 repo.ui.warn(" %s\n" % nf)
329 elif m == "e": # exec
331 util.set_flags(repo.wjoin(f), 'l' in flags, 'x' in flags)
333 return updated, merged, removed, unresolved
335 def recordupdates(repo, action, branchmerge):
336 "record merge actions to the dirstate"
340 if m == "r": # remove
342 repo.dirstate.remove(f)
344 repo.dirstate.forget(f)
345 elif m == "a": # re-add
348 elif m == "f": # forget
349 repo.dirstate.forget(f)
350 elif m == "e": # exec change
351 repo.dirstate.normallookup(f)
354 repo.dirstate.normaldirty(f)
356 repo.dirstate.normal(f)
357 elif m == "m": # merge
358 f2, fd, flag, move = a[2:]
360 # We've done a branch merge, mark this file as merged
361 # so that we properly record the merger later
362 repo.dirstate.merge(fd)
363 if f != f2: # copy/rename
365 repo.dirstate.remove(f)
367 repo.dirstate.copy(f, fd)
369 repo.dirstate.copy(f2, fd)
371 # We've update-merged a locally modified file, so
372 # we set the dirstate to emulate a normal checkout
373 # of that file some time in the past. Thus our
374 # merge will appear as a normal local file
376 repo.dirstate.normallookup(fd)
378 repo.dirstate.forget(f)
379 elif m == "d": # directory rename
381 if not f2 and f not in repo.dirstate:
382 # untracked file moved
385 repo.dirstate.add(fd)
387 repo.dirstate.remove(f)
388 repo.dirstate.copy(f, fd)
390 repo.dirstate.copy(f2, fd)
392 repo.dirstate.normal(fd)
394 repo.dirstate.forget(f)
396 def update(repo, node, branchmerge, force, partial):
398 Perform a merge between the working directory and the given node
400 branchmerge = whether to merge between branches
401 force = whether to force branch merging or file overwriting
402 partial = a function to filter file lists (dirstate not updated)
409 # tip of current branch
411 node = repo.branchtags()[wc.branch()]
413 if wc.branch() == "default": # no default branch!
414 node = repo.lookup("tip") # update to tip
416 raise util.Abort(_("branch %s not found") % wc.branch())
417 overwrite = force and not branchmerge
419 p1, p2 = pl[0], repo[node]
421 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
425 if not overwrite and len(pl) > 1:
426 raise util.Abort(_("outstanding uncommitted merges"))
429 raise util.Abort(_("can't merge with ancestor"))
431 if p1.branch() != p2.branch():
434 raise util.Abort(_("nothing to merge (use 'hg update'"
435 " or check 'hg heads')"))
436 if not force and (wc.files() or wc.deleted()):
437 raise util.Abort(_("outstanding uncommitted changes "
438 "(use 'hg status' to list changes)"))
440 if pa == p1 or pa == p2: # linear
442 elif p1.branch() == p2.branch():
443 if wc.files() or wc.deleted():
444 raise util.Abort(_("crosses branches (use 'hg merge' or "
445 "'hg update -C' to discard changes)"))
446 raise util.Abort(_("crosses branches (use 'hg merge' "
447 "or 'hg update -C')"))
448 elif wc.files() or wc.deleted():
449 raise util.Abort(_("crosses named branches (use "
450 "'hg update -C' to discard changes)"))
452 # Allow jumping branches if there are no changes
458 _checkunknown(wc, p2)
459 if not util.checkcase(repo.path):
461 action += _forgetremoved(wc, p2, branchmerge)
462 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
465 if not branchmerge: # just jump to the new rev
466 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
468 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
470 stats = applyupdates(repo, action, wc, p2)
473 recordupdates(repo, action, branchmerge)
474 repo.dirstate.setparents(fp1, fp2)
475 if not branchmerge and not fastforward:
476 repo.dirstate.setbranch(p2.branch())
477 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])