Mercurial > hg
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 |