sapling/mergedriver.py

158 lines
4.9 KiB
Python

# mergedriver.py
#
# Copyright 2015 Facebook, Inc.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
"""custom merge drivers for autoresolved files"""
from __future__ import absolute_import
import errno
from mercurial.i18n import _
from mercurial import (
commands,
error,
extensions,
hook,
merge,
util,
)
def wrappreprocess(orig, repo, ms, wctx, labels=None):
ui = repo.ui
r, raised = _rundriver(repo, ms, 'preprocess', wctx, labels)
ms.commit()
if raised:
ms._mdstate = 'u'
ms._dirty = True
ms.commit()
ui.warn(_('warning: merge driver failed to preprocess files\n'))
ui.warn(_(
'(hg resolve --all to retry, or '
'hg resolve --all --skip to skip merge driver)\n'))
return False
elif r or list(ms.driverresolved()):
ms._mdstate = 'm'
else:
ms._mdstate = 's'
ms._dirty = True
ms.commit()
return True
def wrapconclude(orig, repo, ms, wctx, labels=None):
ui = repo.ui
r, raised = _rundriver(repo, ms, 'conclude', wctx, labels)
ms.commit()
if raised:
ms._mdstate = 'u'
ms._dirty = True
ms.commit()
ui.warn(_('warning: merge driver failed to resolve files\n'))
ui.warn(_(
'(hg resolve --all to retry, or '
'hg resolve --all --skip to skip merge driver)\n'))
return False
# assume that driver-resolved files have all been resolved
driverresolved = list(ms.driverresolved())
for f in driverresolved:
ms.mark(f, 'r')
ms._mdstate = 's'
ms._dirty = True
ms.commit()
return True
def wrapmdprop(orig, self):
try:
return orig(self)
except error.ConfigError:
# skip this error and go with the new one
self._dirty = True
return self._repo.ui.config('experimental', 'mergedriver')
def wrapresolve(orig, ui, repo, *pats, **opts):
backup = None
if opts.get('skip'):
backup = ui.backupconfig('experimental', 'mergedriver')
ui.setconfig('experimental', 'mergedriver', '',
'mergedriver extension')
ui.warn(_('warning: skipping merge driver '
'(you MUST regenerate artifacts afterwards)\n'))
try:
ret = orig(ui, repo, *pats, **opts)
# load up and commit the merge state again to make sure the driver gets
# written out
if backup is not None:
ms = merge.mergestate.read(repo)
if opts.get('skip'):
# force people to resolve by hand
for f in ms.driverresolved():
ms.mark(f, 'u')
ms.commit()
return ret
finally:
if backup is not None:
ui.restoreconfig(backup)
def _rundriver(repo, ms, op, wctx, labels):
ui = repo.ui
mergedriver = ms.mergedriver
if not mergedriver.startswith('python:'):
raise error.ConfigError(_("merge driver must be a python hook"))
ms.commit()
raised = False
try:
res = hook.runhooks(ui, repo, 'mergedriver-%s' % op,
[(op, '%s:%s' % (mergedriver, op))],
throw=False, mergestate=ms, wctx=wctx,
labels=labels)
r, raised = res[op]
except ImportError:
# underlying function prints out warning
r = True
raised = True
except (IOError, error.HookLoadError) as inst:
if isinstance(inst, IOError) and inst.errno == errno.ENOENT:
# this will usually happen when transitioning from not having a
# merge driver to having one -- don't fail for this important use
# case
r, raised = False, False
else:
ui.warn(_("%s\n") % inst)
r = True
raised = True
return r, raised
def extsetup(ui):
extensions.wrapfunction(merge, 'driverpreprocess', wrappreprocess)
extensions.wrapfunction(merge, 'driverconclude', wrapconclude)
wrappropertycache(merge.mergestate, 'mergedriver', wrapmdprop)
entry = extensions.wrapcommand(commands.table, 'resolve', wrapresolve)
entry[1].append(('', 'skip', None, _('skip merge driver')))
def wrappropertycache(cls, propname, wrapper):
"""Wraps a filecache property. These can't be wrapped using the normal
wrapfunction. This should eventually go into upstream Mercurial.
"""
assert callable(wrapper)
for currcls in cls.__mro__:
if propname in currcls.__dict__:
origfn = currcls.__dict__[propname].func
assert callable(origfn)
def wrap(*args, **kwargs):
return wrapper(origfn, *args, **kwargs)
currcls.__dict__[propname].func = wrap
break
if currcls is object:
raise AttributeError(_("type '%s' has no property '%s'") % (origcls,
propname))