hgext/fsmonitor/__init__.py
changeset 34565 4aa57627692a
parent 34564 b79f59425964
child 34580 7259f0ddfc0f
equal deleted inserted replaced
34564:b79f59425964 34565:4aa57627692a
    94 import codecs
    94 import codecs
    95 import hashlib
    95 import hashlib
    96 import os
    96 import os
    97 import stat
    97 import stat
    98 import sys
    98 import sys
       
    99 import weakref
    99 
   100 
   100 from mercurial.i18n import _
   101 from mercurial.i18n import _
       
   102 from mercurial.node import (
       
   103     hex,
       
   104     nullid,
       
   105 )
       
   106 
   101 from mercurial import (
   107 from mercurial import (
   102     context,
   108     context,
   103     encoding,
   109     encoding,
   104     error,
   110     error,
   105     extensions,
   111     extensions,
   553                      status.deleted + status.unknown)
   559                      status.deleted + status.unknown)
   554         wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
   560         wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
   555 
   561 
   556 def makedirstate(repo, dirstate):
   562 def makedirstate(repo, dirstate):
   557     class fsmonitordirstate(dirstate.__class__):
   563     class fsmonitordirstate(dirstate.__class__):
   558         def _fsmonitorinit(self, fsmonitorstate, watchmanclient):
   564         def _fsmonitorinit(self, repo):
   559             # _fsmonitordisable is used in paranoid mode
   565             # _fsmonitordisable is used in paranoid mode
   560             self._fsmonitordisable = False
   566             self._fsmonitordisable = False
   561             self._fsmonitorstate = fsmonitorstate
   567             self._fsmonitorstate = repo._fsmonitorstate
   562             self._watchmanclient = watchmanclient
   568             self._watchmanclient = repo._watchmanclient
       
   569             self._repo = weakref.proxy(repo)
   563 
   570 
   564         def walk(self, *args, **kwargs):
   571         def walk(self, *args, **kwargs):
   565             orig = super(fsmonitordirstate, self).walk
   572             orig = super(fsmonitordirstate, self).walk
   566             if self._fsmonitordisable:
   573             if self._fsmonitordisable:
   567                 return orig(*args, **kwargs)
   574                 return orig(*args, **kwargs)
   573 
   580 
   574         def invalidate(self, *args, **kwargs):
   581         def invalidate(self, *args, **kwargs):
   575             self._fsmonitorstate.invalidate()
   582             self._fsmonitorstate.invalidate()
   576             return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
   583             return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
   577 
   584 
       
   585         if dirstate._ui.configbool(
       
   586             "experimental", "fsmonitor.wc_change_notify"):
       
   587             def setparents(self, p1, p2=nullid):
       
   588                 with state_update(self._repo, name="hg.wc_change",
       
   589                                   oldnode=self._pl[0], newnode=p1,
       
   590                                   partial=False):
       
   591                     return super(fsmonitordirstate, self).setparents(p1, p2)
       
   592 
   578     dirstate.__class__ = fsmonitordirstate
   593     dirstate.__class__ = fsmonitordirstate
   579     dirstate._fsmonitorinit(repo._fsmonitorstate, repo._watchmanclient)
   594     dirstate._fsmonitorinit(repo)
   580 
   595 
   581 def wrapdirstate(orig, self):
   596 def wrapdirstate(orig, self):
   582     ds = orig(self)
   597     ds = orig(self)
   583     # only override the dirstate when Watchman is available for the repo
   598     # only override the dirstate when Watchman is available for the repo
   584     if util.safehasattr(self, '_fsmonitorstate'):
   599     if util.safehasattr(self, '_fsmonitorstate'):
   605         except OSError:
   620         except OSError:
   606             pass
   621             pass
   607 
   622 
   608 class state_update(object):
   623 class state_update(object):
   609     ''' This context manager is responsible for dispatching the state-enter
   624     ''' This context manager is responsible for dispatching the state-enter
   610         and state-leave signals to the watchman service '''
   625         and state-leave signals to the watchman service. The enter and leave
   611 
   626         methods can be invoked manually (for scenarios where context manager
   612     def __init__(self, repo, node, distance, partial):
   627         semantics are not possible). If parameters oldnode and newnode are None,
   613         self.repo = repo
   628         they will be populated based on current working copy in enter and
   614         self.node = node
   629         leave, respectively. Similarly, if the distance is none, it will be
       
   630         calculated based on the oldnode and newnode in the leave method.'''
       
   631 
       
   632     def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
       
   633                  partial=False):
       
   634         self.repo = repo.unfiltered()
       
   635         self.name = name
       
   636         self.oldnode = oldnode
       
   637         self.newnode = newnode
   615         self.distance = distance
   638         self.distance = distance
   616         self.partial = partial
   639         self.partial = partial
   617         self._lock = None
   640         self._lock = None
   618         self.need_leave = False
   641         self.need_leave = False
   619 
   642 
   620     def __enter__(self):
   643     def __enter__(self):
       
   644         self.enter()
       
   645 
       
   646     def enter(self):
   621         # We explicitly need to take a lock here, before we proceed to update
   647         # We explicitly need to take a lock here, before we proceed to update
   622         # watchman about the update operation, so that we don't race with
   648         # watchman about the update operation, so that we don't race with
   623         # some other actor.  merge.update is going to take the wlock almost
   649         # some other actor.  merge.update is going to take the wlock almost
   624         # immediately anyway, so this is effectively extending the lock
   650         # immediately anyway, so this is effectively extending the lock
   625         # around a couple of short sanity checks.
   651         # around a couple of short sanity checks.
       
   652         if self.oldnode is None:
       
   653             self.oldnode = self.repo['.'].node()
   626         self._lock = self.repo.wlock()
   654         self._lock = self.repo.wlock()
   627         self.need_leave = self._state('state-enter')
   655         self.need_leave = self._state(
       
   656             'state-enter',
       
   657             hex(self.oldnode))
   628         return self
   658         return self
   629 
   659 
   630     def __exit__(self, type_, value, tb):
   660     def __exit__(self, type_, value, tb):
       
   661         abort = True if type_ else False
       
   662         self.exit(abort=abort)
       
   663 
       
   664     def exit(self, abort=False):
   631         try:
   665         try:
   632             if self.need_leave:
   666             if self.need_leave:
   633                 status = 'ok' if type_ is None else 'failed'
   667                 status = 'failed' if abort else 'ok'
   634                 self._state('state-leave', status=status)
   668                 if self.newnode is None:
       
   669                     self.newnode = self.repo['.'].node()
       
   670                 if self.distance is None:
       
   671                     self.distance = calcdistance(
       
   672                         self.repo, self.oldnode, self.newnode)
       
   673                 self._state(
       
   674                     'state-leave',
       
   675                     hex(self.newnode),
       
   676                     status=status)
   635         finally:
   677         finally:
       
   678             self.need_leave = False
   636             if self._lock:
   679             if self._lock:
   637                 self._lock.release()
   680                 self._lock.release()
   638 
   681 
   639     def _state(self, cmd, status='ok'):
   682     def _state(self, cmd, commithash, status='ok'):
   640         if not util.safehasattr(self.repo, '_watchmanclient'):
   683         if not util.safehasattr(self.repo, '_watchmanclient'):
   641             return False
   684             return False
   642         try:
   685         try:
   643             commithash = self.repo[self.node].hex()
       
   644             self.repo._watchmanclient.command(cmd, {
   686             self.repo._watchmanclient.command(cmd, {
   645                 'name': 'hg.update',
   687                 'name': self.name,
   646                 'metadata': {
   688                 'metadata': {
   647                     # the target revision
   689                     # the target revision
   648                     'rev': commithash,
   690                     'rev': commithash,
   649                     # approximate number of commits between current and target
   691                     # approximate number of commits between current and target
   650                     'distance': self.distance,
   692                     'distance': self.distance if self.distance else 0,
   651                     # success/failure (only really meaningful for state-leave)
   693                     # success/failure (only really meaningful for state-leave)
   652                     'status': status,
   694                     'status': status,
   653                     # whether the working copy parent is changing
   695                     # whether the working copy parent is changing
   654                     'partial': self.partial,
   696                     'partial': self.partial,
   655             }})
   697             }})
   675 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
   717 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
   676                mergeancestor=False, labels=None, matcher=None, **kwargs):
   718                mergeancestor=False, labels=None, matcher=None, **kwargs):
   677 
   719 
   678     distance = 0
   720     distance = 0
   679     partial = True
   721     partial = True
       
   722     oldnode = repo['.'].node()
       
   723     newnode = repo[node].node()
   680     if matcher is None or matcher.always():
   724     if matcher is None or matcher.always():
   681         partial = False
   725         partial = False
   682         distance = calcdistance(repo.unfiltered(), repo['.'].node(),
   726         distance = calcdistance(repo.unfiltered(), oldnode, newnode)
   683                                 repo[node].node())
   727 
   684 
   728     with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
   685     with state_update(repo, node, distance, partial):
   729                       distance=distance, partial=partial):
   686         return orig(
   730         return orig(
   687             repo, node, branchmerge, force, ancestor, mergeancestor,
   731             repo, node, branchmerge, force, ancestor, mergeancestor,
   688             labels, matcher, **kwargs)
   732             labels, matcher, **kwargs)
   689 
   733 
   690 def reposetup(ui, repo):
   734 def reposetup(ui, repo):
   726         class fsmonitorrepo(repo.__class__):
   770         class fsmonitorrepo(repo.__class__):
   727             def status(self, *args, **kwargs):
   771             def status(self, *args, **kwargs):
   728                 orig = super(fsmonitorrepo, self).status
   772                 orig = super(fsmonitorrepo, self).status
   729                 return overridestatus(orig, self, *args, **kwargs)
   773                 return overridestatus(orig, self, *args, **kwargs)
   730 
   774 
       
   775             if ui.configbool("experimental", "fsmonitor.transaction_notify"):
       
   776                 def transaction(self, *args, **kwargs):
       
   777                     tr = super(fsmonitorrepo, self).transaction(
       
   778                                *args, **kwargs)
       
   779                     if tr.count != 1:
       
   780                         return tr
       
   781                     stateupdate = state_update(self, name="hg.transaction")
       
   782                     stateupdate.enter()
       
   783 
       
   784                     class fsmonitortrans(tr.__class__):
       
   785                         def _abort(self):
       
   786                             try:
       
   787                                 result = super(fsmonitortrans, self)._abort()
       
   788                             finally:
       
   789                                 stateupdate.exit(abort=True)
       
   790                             return result
       
   791 
       
   792                         def close(self):
       
   793                             try:
       
   794                                 result = super(fsmonitortrans, self).close()
       
   795                             finally:
       
   796                                 if self.count == 0:
       
   797                                     stateupdate.exit()
       
   798                             return result
       
   799 
       
   800                     tr.__class__ = fsmonitortrans
       
   801                     return tr
       
   802 
   731         repo.__class__ = fsmonitorrepo
   803         repo.__class__ = fsmonitorrepo