comparison hgext/fsmonitor/__init__.py @ 34565:4aa57627692a

fsmonitor: add new watchman notifications to fsmonitor extension The fsmonitor extension currently sends state-enter and state-leave notifications to watchman on the update operation. This commit creates additional notifications for the following events : - transaction creation and commit/abort. A state-enter notification will be sent when a transaction is created. It will provide the working copy parent's hash. A state-leave notification will be sent when the transaction is committed or aborted. It will provide the working copy parent's hash. - calls to set-parent will cause state-enter and state-leave notifications to be sent. The state-enter notification will be sent prior to the set-parent operation and the working copy parent's hash will be provided at this time. The state-leave notification will be sent after the set-parent operation completes providing the working copy parents hash. Test Plan: tested on dev server to check that necessary notifications were sent/received Differential Revision: https://phab.mercurial-scm.org/D989
author Eamonn Kent <ekent@fb.com>
date Mon, 09 Oct 2017 10:09:36 -0700
parents b79f59425964
children 7259f0ddfc0f
comparison
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