from node import *
from i18n import _
import errno, util, os, tempfile, context, heapq
def filemerge(repo, fw, fd, fo, wctx, mctx):
def temp(prefix, ctx):
pre = "%s~%s." % (os.path.basename(ctx.path()), prefix)
(fd, name) = tempfile.mkstemp(prefix=pre)
data = repo.wwritedata(ctx.path(), ctx.data())
f = os.fdopen(fd, "wb")
f.write(data)
f.close()
return name
fcm = wctx.filectx(fw)
fcmdata = wctx.filectx(fd).data()
fco = mctx.filectx(fo)
if not fco.cmp(fcmdata):
return None
fca = fcm.ancestor(fco)
if not fca:
fca = repo.filectx(fw, fileid=nullrev)
a = repo.wjoin(fd)
b = temp("base", fca)
c = temp("other", fco)
if fw != fo:
repo.ui.status(_("merging %s and %s\n") % (fw, fo))
else:
repo.ui.status(_("merging %s\n") % fw)
repo.ui.debug(_("my %s other %s ancestor %s\n") % (fcm, fco, fca))
cmd = (os.environ.get("HGMERGE") or repo.ui.config("ui", "merge")
or "hgmerge")
r = util.system('%s "%s" "%s" "%s"' % (cmd, a, b, c), cwd=repo.root,
environ={'HG_FILE': fd,
'HG_MY_NODE': str(wctx.parents()[0]),
'HG_OTHER_NODE': str(mctx),
'HG_MY_ISLINK': fcm.islink(),
'HG_OTHER_ISLINK': fco.islink(),
'HG_BASE_ISLINK': fca.islink(),})
if r:
repo.ui.warn(_("merging %s failed!\n") % fd)
os.unlink(b)
os.unlink(c)
return r
def checkunknown(wctx, mctx):
"check for collisions between unknown files and files in mctx"
man = mctx.manifest()
for f in wctx.unknown():
if f in man:
if mctx.filectx(f).cmp(wctx.filectx(f).data()):
raise util.Abort(_("untracked local file '%s' differs"
" from remote version") % f)
def checkcollision(mctx):
"check for case folding collisions in the destination context"
folded = {}
for fn in mctx.manifest():
fold = fn.lower()
if fold in folded:
raise util.Abort(_("case-folding collision between %s and %s")
% (fn, folded[fold]))
folded[fold] = fn
def forgetremoved(wctx, mctx):
action = []
man = mctx.manifest()
for f in wctx.deleted() + wctx.removed():
if f not in man:
action.append((f, "f"))
return action
def findcopies(repo, m1, m2, ma, limit):
def nonoverlap(d1, d2, d3):
"Return list of elements in d1 not in d2 or d3"
l = [d for d in d1 if d not in d3 and d not in d2]
l.sort()
return l
def dirname(f):
s = f.rfind("/")
if s == -1:
return ""
return f[:s]
def dirs(files):
d = {}
for f in files:
f = dirname(f)
while f not in d:
d[f] = True
f = dirname(f)
return d
wctx = repo.workingctx()
def makectx(f, n):
if len(n) == 20:
return repo.filectx(f, fileid=n)
return wctx.filectx(f)
ctx = util.cachefunc(makectx)
def findold(fctx):
"find files that path was copied from, back to linkrev limit"
old = {}
seen = {}
orig = fctx.path()
visit = [fctx]
while visit:
fc = visit.pop()
s = str(fc)
if s in seen:
continue
seen[s] = 1
if fc.path() != orig and fc.path() not in old:
old[fc.path()] = 1
if fc.rev() < limit:
continue
visit += fc.parents()
old = old.keys()
old.sort()
return old
copy = {}
fullcopy = {}
diverge = {}
def checkcopies(c, man, aman):
for of in findold(c):
fullcopy[c.path()] = of
if of not in man:
if of in ma:
diverge.setdefault(of, []).append(c.path())
continue
if man[of] == aman.get(of):
continue
c2 = ctx(of, man[of])
ca = c.ancestor(c2)
if not ca:
continue
if ca.path() == c.path() or ca.path() == c2.path():
if c == ca and c2 == ca:
continue
copy[c.path()] = of
if not repo.ui.configbool("merge", "followcopies", True):
return {}, {}
if not m1 or not m2 or not ma:
return {}, {}
repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
u1 = nonoverlap(m1, m2, ma)
u2 = nonoverlap(m2, m1, ma)
if u1:
repo.ui.debug(_(" unmatched files in local:\n %s\n")
% "\n ".join(u1))
if u2:
repo.ui.debug(_(" unmatched files in other:\n %s\n")
% "\n ".join(u2))
for f in u1:
checkcopies(ctx(f, m1[f]), m2, ma)
for f in u2:
checkcopies(ctx(f, m2[f]), m1, ma)
d2 = {}
for of, fl in diverge.items():
for f in fl:
fo = list(fl)
fo.remove(f)
d2[f] = (of, fo)
if fullcopy:
repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n"))
for f in fullcopy:
note = ""
if f in copy: note += "*"
if f in diverge: note += "!"
repo.ui.debug(_(" %s -> %s %s\n") % (f, fullcopy[f], note))
if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
return copy, diverge
repo.ui.debug(_(" checking for directory renames\n"))
d1, d2 = dirs(m1), dirs(m2)
invalid = {}
dirmove = {}
for dst, src in fullcopy.items():
dsrc, ddst = dirname(src), dirname(dst)
if dsrc in invalid:
continue
elif dsrc in d1 and ddst in d1:
invalid[dsrc] = True
elif dsrc in d2 and ddst in d2:
invalid[dsrc] = True
elif dsrc in dirmove and dirmove[dsrc] != ddst:
invalid[dsrc] = True
else:
dirmove[dsrc + "/"] = ddst + "/"
for i in invalid:
if i in dirmove:
del dirmove[i]
del d1, d2, invalid
if not dirmove:
return copy, diverge
for d in dirmove:
repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d]))
for