diff --git a/hgext/fsmonitor/__init__.py b/hgext/fsmonitor/__init__.py --- a/hgext/fsmonitor/__init__.py +++ b/hgext/fsmonitor/__init__.py @@ -96,8 +96,14 @@ import os import stat import sys +import weakref from mercurial.i18n import _ +from mercurial.node import ( + hex, + nullid, +) + from mercurial import ( context, encoding, @@ -555,11 +561,12 @@ def makedirstate(repo, dirstate): class fsmonitordirstate(dirstate.__class__): - def _fsmonitorinit(self, fsmonitorstate, watchmanclient): + def _fsmonitorinit(self, repo): # _fsmonitordisable is used in paranoid mode self._fsmonitordisable = False - self._fsmonitorstate = fsmonitorstate - self._watchmanclient = watchmanclient + self._fsmonitorstate = repo._fsmonitorstate + self._watchmanclient = repo._watchmanclient + self._repo = weakref.proxy(repo) def walk(self, *args, **kwargs): orig = super(fsmonitordirstate, self).walk @@ -575,8 +582,16 @@ self._fsmonitorstate.invalidate() return super(fsmonitordirstate, self).invalidate(*args, **kwargs) + if dirstate._ui.configbool( + "experimental", "fsmonitor.wc_change_notify"): + def setparents(self, p1, p2=nullid): + with state_update(self._repo, name="hg.wc_change", + oldnode=self._pl[0], newnode=p1, + partial=False): + return super(fsmonitordirstate, self).setparents(p1, p2) + dirstate.__class__ = fsmonitordirstate - dirstate._fsmonitorinit(repo._fsmonitorstate, repo._watchmanclient) + dirstate._fsmonitorinit(repo) def wrapdirstate(orig, self): ds = orig(self) @@ -607,47 +622,74 @@ class state_update(object): ''' This context manager is responsible for dispatching the state-enter - and state-leave signals to the watchman service ''' + and state-leave signals to the watchman service. The enter and leave + methods can be invoked manually (for scenarios where context manager + semantics are not possible). If parameters oldnode and newnode are None, + they will be populated based on current working copy in enter and + leave, respectively. Similarly, if the distance is none, it will be + calculated based on the oldnode and newnode in the leave method.''' - def __init__(self, repo, node, distance, partial): - self.repo = repo - self.node = node + def __init__(self, repo, name, oldnode=None, newnode=None, distance=None, + partial=False): + self.repo = repo.unfiltered() + self.name = name + self.oldnode = oldnode + self.newnode = newnode self.distance = distance self.partial = partial self._lock = None self.need_leave = False def __enter__(self): + self.enter() + + def enter(self): # We explicitly need to take a lock here, before we proceed to update # watchman about the update operation, so that we don't race with # some other actor. merge.update is going to take the wlock almost # immediately anyway, so this is effectively extending the lock # around a couple of short sanity checks. + if self.oldnode is None: + self.oldnode = self.repo['.'].node() self._lock = self.repo.wlock() - self.need_leave = self._state('state-enter') + self.need_leave = self._state( + 'state-enter', + hex(self.oldnode)) return self def __exit__(self, type_, value, tb): + abort = True if type_ else False + self.exit(abort=abort) + + def exit(self, abort=False): try: if self.need_leave: - status = 'ok' if type_ is None else 'failed' - self._state('state-leave', status=status) + status = 'failed' if abort else 'ok' + if self.newnode is None: + self.newnode = self.repo['.'].node() + if self.distance is None: + self.distance = calcdistance( + self.repo, self.oldnode, self.newnode) + self._state( + 'state-leave', + hex(self.newnode), + status=status) finally: + self.need_leave = False if self._lock: self._lock.release() - def _state(self, cmd, status='ok'): + def _state(self, cmd, commithash, status='ok'): if not util.safehasattr(self.repo, '_watchmanclient'): return False try: - commithash = self.repo[self.node].hex() self.repo._watchmanclient.command(cmd, { - 'name': 'hg.update', + 'name': self.name, 'metadata': { # the target revision 'rev': commithash, # approximate number of commits between current and target - 'distance': self.distance, + 'distance': self.distance if self.distance else 0, # success/failure (only really meaningful for state-leave) 'status': status, # whether the working copy parent is changing @@ -677,12 +719,14 @@ distance = 0 partial = True + oldnode = repo['.'].node() + newnode = repo[node].node() if matcher is None or matcher.always(): partial = False - distance = calcdistance(repo.unfiltered(), repo['.'].node(), - repo[node].node()) + distance = calcdistance(repo.unfiltered(), oldnode, newnode) - with state_update(repo, node, distance, partial): + with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode, + distance=distance, partial=partial): return orig( repo, node, branchmerge, force, ancestor, mergeancestor, labels, matcher, **kwargs) @@ -728,4 +772,32 @@ orig = super(fsmonitorrepo, self).status return overridestatus(orig, self, *args, **kwargs) + if ui.configbool("experimental", "fsmonitor.transaction_notify"): + def transaction(self, *args, **kwargs): + tr = super(fsmonitorrepo, self).transaction( + *args, **kwargs) + if tr.count != 1: + return tr + stateupdate = state_update(self, name="hg.transaction") + stateupdate.enter() + + class fsmonitortrans(tr.__class__): + def _abort(self): + try: + result = super(fsmonitortrans, self)._abort() + finally: + stateupdate.exit(abort=True) + return result + + def close(self): + try: + result = super(fsmonitortrans, self).close() + finally: + if self.count == 0: + stateupdate.exit() + return result + + tr.__class__ = fsmonitortrans + return tr + repo.__class__ = fsmonitorrepo