comparison hgext/fsmonitor/__init__.py @ 28443:49d65663d7e4

fsmonitor: hook up state-enter, state-leave signals Keeping the codebase in sync with upstream: Watchman 4.4 introduced an advanced settling feature that allows publishing tools to notify subscribing tools of the boundaries for important filesystem operations. https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling has more information about how this feature works. This diff connects a signal that we're calling `hg.update` to the mercurial update function so that mercurial can indirectly notify tools (such as IDEs or build machinery) when it is changing the working copy. This will allow those tools to pause their normal actions as the files are changing and defer them until the end of the operation. In addition to sending the enter/leave signals for the state, we are able to publish useful metadata along the same channel. In this case we are passing the following pieces of information: 1. destination revision hash 2. An estimate of the distance between the current state and the target state 3. A success indicator. 4. Whether it is a partial update The distance is estimate may be useful to tools that wish to change their strategy after the update has complete. For example, a large update may be efficient to deal with by walking some internal state in the subscriber rather than feeding every individual file notification through its normal (small) delta mechanism. We estimate the distance by comparing the repository revision number. In some cases we cannot come up with a number so we report 0. This is ok; we're offering this for informational purposes only and don't guarantee its accuracy. The success indicator is only really meaningful when we generate the state-leave notification; it indicates the overall success of the update.
author Martijn Pieters <mjpieters@fb.com>
date Thu, 10 Mar 2016 16:04:09 +0000
parents 3b67f27bb908
children a0939666b836
comparison
equal deleted inserted replaced
28442:3be2e89c5d9f 28443:49d65663d7e4
97 97
98 from mercurial import ( 98 from mercurial import (
99 context, 99 context,
100 extensions, 100 extensions,
101 localrepo, 101 localrepo,
102 merge,
102 pathutil, 103 pathutil,
103 scmutil, 104 scmutil,
104 util, 105 util,
105 ) 106 )
106 from mercurial import match as matchmod 107 from mercurial import match as matchmod
545 wrapfilecache(localrepo.localrepository, 'dirstate', wrapdirstate) 546 wrapfilecache(localrepo.localrepository, 'dirstate', wrapdirstate)
546 if sys.platform == 'darwin': 547 if sys.platform == 'darwin':
547 # An assist for avoiding the dangling-symlink fsevents bug 548 # An assist for avoiding the dangling-symlink fsevents bug
548 extensions.wrapfunction(os, 'symlink', wrapsymlink) 549 extensions.wrapfunction(os, 'symlink', wrapsymlink)
549 550
551 extensions.wrapfunction(merge, 'update', wrapupdate)
552
550 def wrapsymlink(orig, source, link_name): 553 def wrapsymlink(orig, source, link_name):
551 ''' if we create a dangling symlink, also touch the parent dir 554 ''' if we create a dangling symlink, also touch the parent dir
552 to encourage fsevents notifications to work more correctly ''' 555 to encourage fsevents notifications to work more correctly '''
553 try: 556 try:
554 return orig(source, link_name) 557 return orig(source, link_name)
555 finally: 558 finally:
556 try: 559 try:
557 os.utime(os.path.dirname(link_name), None) 560 os.utime(os.path.dirname(link_name), None)
558 except OSError: 561 except OSError:
559 pass 562 pass
563
564 class state_update(object):
565 ''' This context mananger is responsible for dispatching the state-enter
566 and state-leave signals to the watchman service '''
567
568 def __init__(self, repo, node, distance, partial):
569 self.repo = repo
570 self.node = node
571 self.distance = distance
572 self.partial = partial
573
574 def __enter__(self):
575 self._state('state-enter')
576 return self
577
578 def __exit__(self, type_, value, tb):
579 status = 'ok' if type_ is None else 'failed'
580 self._state('state-leave', status=status)
581
582 def _state(self, cmd, status='ok'):
583 if not util.safehasattr(self.repo, '_watchmanclient'):
584 return
585 try:
586 commithash = self.repo[self.node].hex()
587 self.repo._watchmanclient.command(cmd, {
588 'name': 'hg.update',
589 'metadata': {
590 # the target revision
591 'rev': commithash,
592 # approximate number of commits between current and target
593 'distance': self.distance,
594 # success/failure (only really meaningful for state-leave)
595 'status': status,
596 # whether the working copy parent is changing
597 'partial': self.partial,
598 }})
599 except Exception as e:
600 # Swallow any errors; fire and forget
601 self.repo.ui.log(
602 'watchman', 'Exception %s while running %s\n', e, cmd)
603
604 # Bracket working copy updates with calls to the watchman state-enter
605 # and state-leave commands. This allows clients to perform more intelligent
606 # settling during bulk file change scenarios
607 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
608 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
609 mergeancestor=False, labels=None, matcher=None, **kwargs):
610
611 distance = 0
612 partial = True
613 if matcher is None or matcher.always():
614 partial = False
615 wc = repo[None]
616 parents = wc.parents()
617 if len(parents) == 2:
618 anc = repo.changelog.ancestor(parents[0].node(), parents[1].node())
619 ancrev = repo[anc].rev()
620 distance = abs(repo[node].rev() - ancrev)
621 elif len(parents) == 1:
622 distance = abs(repo[node].rev() - parents[0].rev())
623
624 with state_update(repo, node, distance, partial):
625 return orig(
626 repo, node, branchmerge, force, ancestor, mergeancestor,
627 labels, matcher, *kwargs)
560 628
561 def reposetup(ui, repo): 629 def reposetup(ui, repo):
562 # We don't work with largefiles or inotify 630 # We don't work with largefiles or inotify
563 exts = extensions.enabled() 631 exts = extensions.enabled()
564 for ext in _blacklist: 632 for ext in _blacklist: