mirror of
https://github.com/facebook/sapling.git
synced 2024-10-11 17:27:53 +03:00
9dc21f8d0b
Summary: D13853115 adds `edenscm/` to `sys.path` and code still uses `import mercurial`. That has nasty problems if both `import mercurial` and `import edenscm.mercurial` are used, because Python would think `mercurial.foo` and `edenscm.mercurial.foo` are different modules so code like `try: ... except mercurial.error.Foo: ...`, or `isinstance(x, mercurial.foo.Bar)` would fail to handle the `edenscm.mercurial` version. There are also some module-level states (ex. `extensions._extensions`) that would cause trouble if they have multiple versions in a single process. Change imports to use the `edenscm` so ideally the `mercurial` is no longer imported at all. Add checks in extensions.py to catch unexpected extensions importing modules from the old (wrong) locations when running tests. Reviewed By: phillco Differential Revision: D13868981 fbshipit-source-id: f4e2513766957fd81d85407994f7521a08e4de48
187 lines
5.8 KiB
Python
187 lines
5.8 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
|
|
import sys
|
|
|
|
from edenscm.mercurial import commands, error, extensions, hook, merge
|
|
from edenscm.mercurial.i18n import _
|
|
|
|
|
|
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
|
|
overrides = {}
|
|
if opts.get("skip"):
|
|
backup = ui.config("experimental", "mergedriver")
|
|
overrides[("experimental", "mergedriver")] = ""
|
|
ui.warn(
|
|
_(
|
|
"warning: skipping merge driver "
|
|
"(you MUST regenerate artifacts afterwards)\n"
|
|
)
|
|
)
|
|
|
|
with ui.configoverride(overrides, "mergedriver"):
|
|
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:
|
|
with repo.wlock():
|
|
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
|
|
|
|
|
|
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
|
|
# Don't write .pyc files for the loaded hooks (restore this setting
|
|
# after running). Like the `loadedmodules` fix below, this is to prevent
|
|
# drivers changed during a rebase from being loaded inconsistently.
|
|
origbytecodesetting = sys.dont_write_bytecode
|
|
sys.dont_write_bytecode = True
|
|
origmodules = set(sys.modules.keys())
|
|
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
|
|
finally:
|
|
# Evict the loaded module and all of its imports from memory. This is
|
|
# necessary to ensure we always use the latest driver code from ., and
|
|
# prevent cases with a half-loaded driver (where some of the cached
|
|
# modules were loaded from an older commit.)
|
|
loadedmodules = set(sys.modules.keys()) - origmodules
|
|
for mod in loadedmodules:
|
|
del sys.modules[mod]
|
|
sys.dont_write_bytecode = origbytecodesetting
|
|
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(_("%s has no property '%s'") % (type(currcls), propname))
|