fsmonitor: hook up state-enter, state-leave signals
authorMartijn Pieters <mjpieters@fb.com>
Thu, 10 Mar 2016 16:04:09 +0000
changeset 28443 49d65663d7e4
parent 28442 3be2e89c5d9f
child 28444 12fdf49fa4dd
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.
hgext/fsmonitor/__init__.py
--- 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()