Mercurial > hg
changeset 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 | 3be2e89c5d9f |
children | 12fdf49fa4dd |
files | hgext/fsmonitor/__init__.py |
diffstat | 1 files changed, 68 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- a/hgext/fsmonitor/__init__.py Thu Mar 10 10:56:02 2016 +0100 +++ b/hgext/fsmonitor/__init__.py Thu Mar 10 16:04:09 2016 +0000 @@ -99,6 +99,7 @@ context, extensions, localrepo, + merge, pathutil, scmutil, util, @@ -547,6 +548,8 @@ # An assist for avoiding the dangling-symlink fsevents bug extensions.wrapfunction(os, 'symlink', wrapsymlink) + extensions.wrapfunction(merge, 'update', wrapupdate) + def wrapsymlink(orig, source, link_name): ''' if we create a dangling symlink, also touch the parent dir to encourage fsevents notifications to work more correctly ''' @@ -558,6 +561,71 @@ except OSError: pass +class state_update(object): + ''' This context mananger is responsible for dispatching the state-enter + and state-leave signals to the watchman service ''' + + def __init__(self, repo, node, distance, partial): + self.repo = repo + self.node = node + self.distance = distance + self.partial = partial + + def __enter__(self): + self._state('state-enter') + return self + + def __exit__(self, type_, value, tb): + status = 'ok' if type_ is None else 'failed' + self._state('state-leave', status=status) + + def _state(self, cmd, status='ok'): + if not util.safehasattr(self.repo, '_watchmanclient'): + return + try: + commithash = self.repo[self.node].hex() + self.repo._watchmanclient.command(cmd, { + 'name': 'hg.update', + 'metadata': { + # the target revision + 'rev': commithash, + # approximate number of commits between current and target + 'distance': self.distance, + # success/failure (only really meaningful for state-leave) + 'status': status, + # whether the working copy parent is changing + 'partial': self.partial, + }}) + except Exception as e: + # Swallow any errors; fire and forget + self.repo.ui.log( + 'watchman', 'Exception %s while running %s\n', e, cmd) + +# Bracket working copy updates with calls to the watchman state-enter +# and state-leave commands. This allows clients to perform more intelligent +# settling during bulk file change scenarios +# https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling +def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None, + mergeancestor=False, labels=None, matcher=None, **kwargs): + + distance = 0 + partial = True + if matcher is None or matcher.always(): + partial = False + wc = repo[None] + parents = wc.parents() + if len(parents) == 2: + anc = repo.changelog.ancestor(parents[0].node(), parents[1].node()) + ancrev = repo[anc].rev() + distance = abs(repo[node].rev() - ancrev) + elif len(parents) == 1: + distance = abs(repo[node].rev() - parents[0].rev()) + + with state_update(repo, node, distance, partial): + return orig( + repo, node, branchmerge, force, ancestor, mergeancestor, + labels, matcher, *kwargs) + def reposetup(ui, repo): # We don't work with largefiles or inotify exts = extensions.enabled()