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