hgext/fsmonitor/__init__.py
changeset 43077 687b865b95ad
parent 43076 2372284d9457
child 43085 eef9a2d67051
equal deleted inserted replaced
43076:2372284d9457 43077:687b865b95ad
   141 
   141 
   142 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   142 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
   143 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   143 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
   144 # be specifying the version(s) of Mercurial they are tested with, or
   144 # be specifying the version(s) of Mercurial they are tested with, or
   145 # leave the attribute unspecified.
   145 # leave the attribute unspecified.
   146 testedwith = 'ships-with-hg-core'
   146 testedwith = b'ships-with-hg-core'
   147 
   147 
   148 configtable = {}
   148 configtable = {}
   149 configitem = registrar.configitem(configtable)
   149 configitem = registrar.configitem(configtable)
   150 
   150 
   151 configitem(
   151 configitem(
   152     'fsmonitor', 'mode', default='on',
   152     b'fsmonitor', b'mode', default=b'on',
   153 )
   153 )
   154 configitem(
   154 configitem(
   155     'fsmonitor', 'walk_on_invalidate', default=False,
   155     b'fsmonitor', b'walk_on_invalidate', default=False,
   156 )
   156 )
   157 configitem(
   157 configitem(
   158     'fsmonitor', 'timeout', default='2',
   158     b'fsmonitor', b'timeout', default=b'2',
   159 )
   159 )
   160 configitem(
   160 configitem(
   161     'fsmonitor', 'blacklistusers', default=list,
   161     b'fsmonitor', b'blacklistusers', default=list,
   162 )
   162 )
   163 configitem(
   163 configitem(
   164     'fsmonitor', 'watchman_exe', default='watchman',
   164     b'fsmonitor', b'watchman_exe', default=b'watchman',
   165 )
   165 )
   166 configitem(
   166 configitem(
   167     'fsmonitor', 'verbose', default=True, experimental=True,
   167     b'fsmonitor', b'verbose', default=True, experimental=True,
   168 )
   168 )
   169 configitem(
   169 configitem(
   170     'experimental', 'fsmonitor.transaction_notify', default=False,
   170     b'experimental', b'fsmonitor.transaction_notify', default=False,
   171 )
   171 )
   172 
   172 
   173 # This extension is incompatible with the following blacklisted extensions
   173 # This extension is incompatible with the following blacklisted extensions
   174 # and will disable itself when encountering one of these:
   174 # and will disable itself when encountering one of these:
   175 _blacklist = ['largefiles', 'eol']
   175 _blacklist = [b'largefiles', b'eol']
   176 
   176 
   177 
   177 
   178 def debuginstall(ui, fm):
   178 def debuginstall(ui, fm):
   179     fm.write(
   179     fm.write(
   180         "fsmonitor-watchman",
   180         b"fsmonitor-watchman",
   181         _("fsmonitor checking for watchman binary... (%s)\n"),
   181         _(b"fsmonitor checking for watchman binary... (%s)\n"),
   182         ui.configpath("fsmonitor", "watchman_exe"),
   182         ui.configpath(b"fsmonitor", b"watchman_exe"),
   183     )
   183     )
   184     root = tempfile.mkdtemp()
   184     root = tempfile.mkdtemp()
   185     c = watchmanclient.client(ui, root)
   185     c = watchmanclient.client(ui, root)
   186     err = None
   186     err = None
   187     try:
   187     try:
   188         v = c.command("version")
   188         v = c.command(b"version")
   189         fm.write(
   189         fm.write(
   190             "fsmonitor-watchman-version",
   190             b"fsmonitor-watchman-version",
   191             _(" watchman binary version %s\n"),
   191             _(b" watchman binary version %s\n"),
   192             v["version"],
   192             v[b"version"],
   193         )
   193         )
   194     except watchmanclient.Unavailable as e:
   194     except watchmanclient.Unavailable as e:
   195         err = str(e)
   195         err = str(e)
   196     fm.condwrite(
   196     fm.condwrite(
   197         err,
   197         err,
   198         "fsmonitor-watchman-error",
   198         b"fsmonitor-watchman-error",
   199         _(" watchman binary missing or broken: %s\n"),
   199         _(b" watchman binary missing or broken: %s\n"),
   200         err,
   200         err,
   201     )
   201     )
   202     return 1 if err else 0
   202     return 1 if err else 0
   203 
   203 
   204 
   204 
   205 def _handleunavailable(ui, state, ex):
   205 def _handleunavailable(ui, state, ex):
   206     """Exception handler for Watchman interaction exceptions"""
   206     """Exception handler for Watchman interaction exceptions"""
   207     if isinstance(ex, watchmanclient.Unavailable):
   207     if isinstance(ex, watchmanclient.Unavailable):
   208         # experimental config: fsmonitor.verbose
   208         # experimental config: fsmonitor.verbose
   209         if ex.warn and ui.configbool('fsmonitor', 'verbose'):
   209         if ex.warn and ui.configbool(b'fsmonitor', b'verbose'):
   210             if 'illegal_fstypes' not in str(ex):
   210             if b'illegal_fstypes' not in str(ex):
   211                 ui.warn(str(ex) + '\n')
   211                 ui.warn(str(ex) + b'\n')
   212         if ex.invalidate:
   212         if ex.invalidate:
   213             state.invalidate()
   213             state.invalidate()
   214         # experimental config: fsmonitor.verbose
   214         # experimental config: fsmonitor.verbose
   215         if ui.configbool('fsmonitor', 'verbose'):
   215         if ui.configbool(b'fsmonitor', b'verbose'):
   216             ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
   216             ui.log(b'fsmonitor', b'Watchman unavailable: %s\n', ex.msg)
   217     else:
   217     else:
   218         ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
   218         ui.log(b'fsmonitor', b'Watchman exception: %s\n', ex)
   219 
   219 
   220 
   220 
   221 def _hashignore(ignore):
   221 def _hashignore(ignore):
   222     """Calculate hash for ignore patterns and filenames
   222     """Calculate hash for ignore patterns and filenames
   223 
   223 
   243     on Windows, it's always utf-8.
   243     on Windows, it's always utf-8.
   244     """
   244     """
   245     try:
   245     try:
   246         decoded = path.decode(_watchmanencoding)
   246         decoded = path.decode(_watchmanencoding)
   247     except UnicodeDecodeError as e:
   247     except UnicodeDecodeError as e:
   248         raise error.Abort(str(e), hint='watchman encoding error')
   248         raise error.Abort(str(e), hint=b'watchman encoding error')
   249 
   249 
   250     try:
   250     try:
   251         encoded = decoded.encode(_fsencoding, 'strict')
   251         encoded = decoded.encode(_fsencoding, 'strict')
   252     except UnicodeEncodeError as e:
   252     except UnicodeEncodeError as e:
   253         raise error.Abort(str(e))
   253         raise error.Abort(str(e))
   261     Whenever full is False, ignored is False, and the Watchman client is
   261     Whenever full is False, ignored is False, and the Watchman client is
   262     available, use Watchman combined with saved state to possibly return only a
   262     available, use Watchman combined with saved state to possibly return only a
   263     subset of files.'''
   263     subset of files.'''
   264 
   264 
   265     def bail(reason):
   265     def bail(reason):
   266         self._ui.debug('fsmonitor: fallback to core status, %s\n' % reason)
   266         self._ui.debug(b'fsmonitor: fallback to core status, %s\n' % reason)
   267         return orig(match, subrepos, unknown, ignored, full=True)
   267         return orig(match, subrepos, unknown, ignored, full=True)
   268 
   268 
   269     if full:
   269     if full:
   270         return bail('full rewalk requested')
   270         return bail(b'full rewalk requested')
   271     if ignored:
   271     if ignored:
   272         return bail('listing ignored files')
   272         return bail(b'listing ignored files')
   273     if not self._watchmanclient.available():
   273     if not self._watchmanclient.available():
   274         return bail('client unavailable')
   274         return bail(b'client unavailable')
   275     state = self._fsmonitorstate
   275     state = self._fsmonitorstate
   276     clock, ignorehash, notefiles = state.get()
   276     clock, ignorehash, notefiles = state.get()
   277     if not clock:
   277     if not clock:
   278         if state.walk_on_invalidate:
   278         if state.walk_on_invalidate:
   279             return bail('no clock')
   279             return bail(b'no clock')
   280         # Initial NULL clock value, see
   280         # Initial NULL clock value, see
   281         # https://facebook.github.io/watchman/docs/clockspec.html
   281         # https://facebook.github.io/watchman/docs/clockspec.html
   282         clock = 'c:0:0'
   282         clock = b'c:0:0'
   283         notefiles = []
   283         notefiles = []
   284 
   284 
   285     ignore = self._ignore
   285     ignore = self._ignore
   286     dirignore = self._dirignore
   286     dirignore = self._dirignore
   287     if unknown:
   287     if unknown:
   288         if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
   288         if _hashignore(ignore) != ignorehash and clock != b'c:0:0':
   289             # ignore list changed -- can't rely on Watchman state any more
   289             # ignore list changed -- can't rely on Watchman state any more
   290             if state.walk_on_invalidate:
   290             if state.walk_on_invalidate:
   291                 return bail('ignore rules changed')
   291                 return bail(b'ignore rules changed')
   292             notefiles = []
   292             notefiles = []
   293             clock = 'c:0:0'
   293             clock = b'c:0:0'
   294     else:
   294     else:
   295         # always ignore
   295         # always ignore
   296         ignore = util.always
   296         ignore = util.always
   297         dirignore = util.always
   297         dirignore = util.always
   298 
   298 
   299     matchfn = match.matchfn
   299     matchfn = match.matchfn
   300     matchalways = match.always()
   300     matchalways = match.always()
   301     dmap = self._map
   301     dmap = self._map
   302     if util.safehasattr(dmap, '_map'):
   302     if util.safehasattr(dmap, b'_map'):
   303         # for better performance, directly access the inner dirstate map if the
   303         # for better performance, directly access the inner dirstate map if the
   304         # standard dirstate implementation is in use.
   304         # standard dirstate implementation is in use.
   305         dmap = dmap._map
   305         dmap = dmap._map
   306     nonnormalset = self._map.nonnormalset
   306     nonnormalset = self._map.nonnormalset
   307 
   307 
   337     work = [d for d in work if not dirignore(d[0])]
   337     work = [d for d in work if not dirignore(d[0])]
   338 
   338 
   339     if not work and (exact or skipstep3):
   339     if not work and (exact or skipstep3):
   340         for s in subrepos:
   340         for s in subrepos:
   341             del results[s]
   341             del results[s]
   342         del results['.hg']
   342         del results[b'.hg']
   343         return results
   343         return results
   344 
   344 
   345     # step 2: query Watchman
   345     # step 2: query Watchman
   346     try:
   346     try:
   347         # Use the user-configured timeout for the query.
   347         # Use the user-configured timeout for the query.
   348         # Add a little slack over the top of the user query to allow for
   348         # Add a little slack over the top of the user query to allow for
   349         # overheads while transferring the data
   349         # overheads while transferring the data
   350         self._watchmanclient.settimeout(state.timeout + 0.1)
   350         self._watchmanclient.settimeout(state.timeout + 0.1)
   351         result = self._watchmanclient.command(
   351         result = self._watchmanclient.command(
   352             'query',
   352             b'query',
   353             {
   353             {
   354                 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
   354                 b'fields': [b'mode', b'mtime', b'size', b'exists', b'name'],
   355                 'since': clock,
   355                 b'since': clock,
   356                 'expression': [
   356                 b'expression': [
   357                     'not',
   357                     b'not',
   358                     ['anyof', ['dirname', '.hg'], ['name', '.hg', 'wholename']],
   358                     [
       
   359                         b'anyof',
       
   360                         [b'dirname', b'.hg'],
       
   361                         [b'name', b'.hg', b'wholename'],
       
   362                     ],
   359                 ],
   363                 ],
   360                 'sync_timeout': int(state.timeout * 1000),
   364                 b'sync_timeout': int(state.timeout * 1000),
   361                 'empty_on_fresh_instance': state.walk_on_invalidate,
   365                 b'empty_on_fresh_instance': state.walk_on_invalidate,
   362             },
   366             },
   363         )
   367         )
   364     except Exception as ex:
   368     except Exception as ex:
   365         _handleunavailable(self._ui, state, ex)
   369         _handleunavailable(self._ui, state, ex)
   366         self._watchmanclient.clearconnection()
   370         self._watchmanclient.clearconnection()
   367         return bail('exception during run')
   371         return bail(b'exception during run')
   368     else:
   372     else:
   369         # We need to propagate the last observed clock up so that we
   373         # We need to propagate the last observed clock up so that we
   370         # can use it for our next query
   374         # can use it for our next query
   371         state.setlastclock(result['clock'])
   375         state.setlastclock(result[b'clock'])
   372         if result['is_fresh_instance']:
   376         if result[b'is_fresh_instance']:
   373             if state.walk_on_invalidate:
   377             if state.walk_on_invalidate:
   374                 state.invalidate()
   378                 state.invalidate()
   375                 return bail('fresh instance')
   379                 return bail(b'fresh instance')
   376             fresh_instance = True
   380             fresh_instance = True
   377             # Ignore any prior noteable files from the state info
   381             # Ignore any prior noteable files from the state info
   378             notefiles = []
   382             notefiles = []
   379 
   383 
   380     # for file paths which require normalization and we encounter a case
   384     # for file paths which require normalization and we encounter a case
   381     # collision, we store our own foldmap
   385     # collision, we store our own foldmap
   382     if normalize:
   386     if normalize:
   383         foldmap = dict((normcase(k), k) for k in results)
   387         foldmap = dict((normcase(k), k) for k in results)
   384 
   388 
   385     switch_slashes = pycompat.ossep == '\\'
   389     switch_slashes = pycompat.ossep == b'\\'
   386     # The order of the results is, strictly speaking, undefined.
   390     # The order of the results is, strictly speaking, undefined.
   387     # For case changes on a case insensitive filesystem we may receive
   391     # For case changes on a case insensitive filesystem we may receive
   388     # two entries, one with exists=True and another with exists=False.
   392     # two entries, one with exists=True and another with exists=False.
   389     # The exists=True entries in the same response should be interpreted
   393     # The exists=True entries in the same response should be interpreted
   390     # as being happens-after the exists=False entries due to the way that
   394     # as being happens-after the exists=False entries due to the way that
   391     # Watchman tracks files.  We use this property to reconcile deletes
   395     # Watchman tracks files.  We use this property to reconcile deletes
   392     # for name case changes.
   396     # for name case changes.
   393     for entry in result['files']:
   397     for entry in result[b'files']:
   394         fname = entry['name']
   398         fname = entry[b'name']
   395         if _fixencoding:
   399         if _fixencoding:
   396             fname = _watchmantofsencoding(fname)
   400             fname = _watchmantofsencoding(fname)
   397         if switch_slashes:
   401         if switch_slashes:
   398             fname = fname.replace('\\', '/')
   402             fname = fname.replace(b'\\', b'/')
   399         if normalize:
   403         if normalize:
   400             normed = normcase(fname)
   404             normed = normcase(fname)
   401             fname = normalize(fname, True, True)
   405             fname = normalize(fname, True, True)
   402             foldmap[normed] = fname
   406             foldmap[normed] = fname
   403         fmode = entry['mode']
   407         fmode = entry[b'mode']
   404         fexists = entry['exists']
   408         fexists = entry[b'exists']
   405         kind = getkind(fmode)
   409         kind = getkind(fmode)
   406 
   410 
   407         if '/.hg/' in fname or fname.endswith('/.hg'):
   411         if b'/.hg/' in fname or fname.endswith(b'/.hg'):
   408             return bail('nested-repo-detected')
   412             return bail(b'nested-repo-detected')
   409 
   413 
   410         if not fexists:
   414         if not fexists:
   411             # if marked as deleted and we don't already have a change
   415             # if marked as deleted and we don't already have a change
   412             # record, mark it as deleted.  If we already have an entry
   416             # record, mark it as deleted.  If we already have an entry
   413             # for fname then it was either part of walkexplicit or was
   417             # for fname then it was either part of walkexplicit or was
   486         if st or f in dmap:
   490         if st or f in dmap:
   487             results[f] = st
   491             results[f] = st
   488 
   492 
   489     for s in subrepos:
   493     for s in subrepos:
   490         del results[s]
   494         del results[s]
   491     del results['.hg']
   495     del results[b'.hg']
   492     return results
   496     return results
   493 
   497 
   494 
   498 
   495 def overridestatus(
   499 def overridestatus(
   496     orig,
   500     orig,
   497     self,
   501     self,
   498     node1='.',
   502     node1=b'.',
   499     node2=None,
   503     node2=None,
   500     match=None,
   504     match=None,
   501     ignored=False,
   505     ignored=False,
   502     clean=False,
   506     clean=False,
   503     unknown=False,
   507     unknown=False,
   507     listclean = clean
   511     listclean = clean
   508     listunknown = unknown
   512     listunknown = unknown
   509 
   513 
   510     def _cmpsets(l1, l2):
   514     def _cmpsets(l1, l2):
   511         try:
   515         try:
   512             if 'FSMONITOR_LOG_FILE' in encoding.environ:
   516             if b'FSMONITOR_LOG_FILE' in encoding.environ:
   513                 fn = encoding.environ['FSMONITOR_LOG_FILE']
   517                 fn = encoding.environ[b'FSMONITOR_LOG_FILE']
   514                 f = open(fn, 'wb')
   518                 f = open(fn, b'wb')
   515             else:
   519             else:
   516                 fn = 'fsmonitorfail.log'
   520                 fn = b'fsmonitorfail.log'
   517                 f = self.vfs.open(fn, 'wb')
   521                 f = self.vfs.open(fn, b'wb')
   518         except (IOError, OSError):
   522         except (IOError, OSError):
   519             self.ui.warn(_('warning: unable to write to %s\n') % fn)
   523             self.ui.warn(_(b'warning: unable to write to %s\n') % fn)
   520             return
   524             return
   521 
   525 
   522         try:
   526         try:
   523             for i, (s1, s2) in enumerate(zip(l1, l2)):
   527             for i, (s1, s2) in enumerate(zip(l1, l2)):
   524                 if set(s1) != set(s2):
   528                 if set(s1) != set(s2):
   525                     f.write('sets at position %d are unequal\n' % i)
   529                     f.write(b'sets at position %d are unequal\n' % i)
   526                     f.write('watchman returned: %s\n' % s1)
   530                     f.write(b'watchman returned: %s\n' % s1)
   527                     f.write('stat returned: %s\n' % s2)
   531                     f.write(b'stat returned: %s\n' % s2)
   528         finally:
   532         finally:
   529             f.close()
   533             f.close()
   530 
   534 
   531     if isinstance(node1, context.changectx):
   535     if isinstance(node1, context.changectx):
   532         ctx1 = node1
   536         ctx1 = node1
   536         ctx2 = node2
   540         ctx2 = node2
   537     else:
   541     else:
   538         ctx2 = self[node2]
   542         ctx2 = self[node2]
   539 
   543 
   540     working = ctx2.rev() is None
   544     working = ctx2.rev() is None
   541     parentworking = working and ctx1 == self['.']
   545     parentworking = working and ctx1 == self[b'.']
   542     match = match or matchmod.always()
   546     match = match or matchmod.always()
   543 
   547 
   544     # Maybe we can use this opportunity to update Watchman's state.
   548     # Maybe we can use this opportunity to update Watchman's state.
   545     # Mercurial uses workingcommitctx and/or memctx to represent the part of
   549     # Mercurial uses workingcommitctx and/or memctx to represent the part of
   546     # the workingctx that is to be committed. So don't update the state in
   550     # the workingctx that is to be committed. So don't update the state in
   550     # case, or we risk forgetting about changes in the working copy.
   554     # case, or we risk forgetting about changes in the working copy.
   551     updatestate = (
   555     updatestate = (
   552         parentworking
   556         parentworking
   553         and match.always()
   557         and match.always()
   554         and not isinstance(ctx2, (context.workingcommitctx, context.memctx))
   558         and not isinstance(ctx2, (context.workingcommitctx, context.memctx))
   555         and 'HG_PENDING' not in encoding.environ
   559         and b'HG_PENDING' not in encoding.environ
   556     )
   560     )
   557 
   561 
   558     try:
   562     try:
   559         if self._fsmonitorstate.walk_on_invalidate:
   563         if self._fsmonitorstate.walk_on_invalidate:
   560             # Use a short timeout to query the current clock.  If that
   564             # Use a short timeout to query the current clock.  If that
   605     if not listunknown:
   609     if not listunknown:
   606         unknown = []
   610         unknown = []
   607 
   611 
   608     # don't do paranoid checks if we're not going to query Watchman anyway
   612     # don't do paranoid checks if we're not going to query Watchman anyway
   609     full = listclean or match.traversedir is not None
   613     full = listclean or match.traversedir is not None
   610     if self._fsmonitorstate.mode == 'paranoid' and not full:
   614     if self._fsmonitorstate.mode == b'paranoid' and not full:
   611         # run status again and fall back to the old walk this time
   615         # run status again and fall back to the old walk this time
   612         self.dirstate._fsmonitordisable = True
   616         self.dirstate._fsmonitordisable = True
   613 
   617 
   614         # shut the UI up
   618         # shut the UI up
   615         quiet = self.ui.quiet
   619         quiet = self.ui.quiet
   616         self.ui.quiet = True
   620         self.ui.quiet = True
   617         fout, ferr = self.ui.fout, self.ui.ferr
   621         fout, ferr = self.ui.fout, self.ui.ferr
   618         self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
   622         self.ui.fout = self.ui.ferr = open(os.devnull, b'wb')
   619 
   623 
   620         try:
   624         try:
   621             rv2 = orig(
   625             rv2 = orig(
   622                 node1,
   626                 node1,
   623                 node2,
   627                 node2,
   690 
   694 
   691 
   695 
   692 def wrapdirstate(orig, self):
   696 def wrapdirstate(orig, self):
   693     ds = orig(self)
   697     ds = orig(self)
   694     # only override the dirstate when Watchman is available for the repo
   698     # only override the dirstate when Watchman is available for the repo
   695     if util.safehasattr(self, '_fsmonitorstate'):
   699     if util.safehasattr(self, b'_fsmonitorstate'):
   696         makedirstate(self, ds)
   700         makedirstate(self, ds)
   697     return ds
   701     return ds
   698 
   702 
   699 
   703 
   700 def extsetup(ui):
   704 def extsetup(ui):
   701     extensions.wrapfilecache(
   705     extensions.wrapfilecache(
   702         localrepo.localrepository, 'dirstate', wrapdirstate
   706         localrepo.localrepository, b'dirstate', wrapdirstate
   703     )
   707     )
   704     if pycompat.isdarwin:
   708     if pycompat.isdarwin:
   705         # An assist for avoiding the dangling-symlink fsevents bug
   709         # An assist for avoiding the dangling-symlink fsevents bug
   706         extensions.wrapfunction(os, 'symlink', wrapsymlink)
   710         extensions.wrapfunction(os, b'symlink', wrapsymlink)
   707 
   711 
   708     extensions.wrapfunction(merge, 'update', wrapupdate)
   712     extensions.wrapfunction(merge, b'update', wrapupdate)
   709 
   713 
   710 
   714 
   711 def wrapsymlink(orig, source, link_name):
   715 def wrapsymlink(orig, source, link_name):
   712     ''' if we create a dangling symlink, also touch the parent dir
   716     ''' if we create a dangling symlink, also touch the parent dir
   713     to encourage fsevents notifications to work more correctly '''
   717     to encourage fsevents notifications to work more correctly '''
   754         # Make sure we have a wlock prior to sending notifications to watchman.
   758         # Make sure we have a wlock prior to sending notifications to watchman.
   755         # We don't want to race with other actors. In the update case,
   759         # We don't want to race with other actors. In the update case,
   756         # merge.update is going to take the wlock almost immediately. We are
   760         # merge.update is going to take the wlock almost immediately. We are
   757         # effectively extending the lock around several short sanity checks.
   761         # effectively extending the lock around several short sanity checks.
   758         if self.oldnode is None:
   762         if self.oldnode is None:
   759             self.oldnode = self.repo['.'].node()
   763             self.oldnode = self.repo[b'.'].node()
   760 
   764 
   761         if self.repo.currentwlock() is None:
   765         if self.repo.currentwlock() is None:
   762             if util.safehasattr(self.repo, 'wlocknostateupdate'):
   766             if util.safehasattr(self.repo, b'wlocknostateupdate'):
   763                 self._lock = self.repo.wlocknostateupdate()
   767                 self._lock = self.repo.wlocknostateupdate()
   764             else:
   768             else:
   765                 self._lock = self.repo.wlock()
   769                 self._lock = self.repo.wlock()
   766         self.need_leave = self._state('state-enter', hex(self.oldnode))
   770         self.need_leave = self._state(b'state-enter', hex(self.oldnode))
   767         return self
   771         return self
   768 
   772 
   769     def __exit__(self, type_, value, tb):
   773     def __exit__(self, type_, value, tb):
   770         abort = True if type_ else False
   774         abort = True if type_ else False
   771         self.exit(abort=abort)
   775         self.exit(abort=abort)
   772 
   776 
   773     def exit(self, abort=False):
   777     def exit(self, abort=False):
   774         try:
   778         try:
   775             if self.need_leave:
   779             if self.need_leave:
   776                 status = 'failed' if abort else 'ok'
   780                 status = b'failed' if abort else b'ok'
   777                 if self.newnode is None:
   781                 if self.newnode is None:
   778                     self.newnode = self.repo['.'].node()
   782                     self.newnode = self.repo[b'.'].node()
   779                 if self.distance is None:
   783                 if self.distance is None:
   780                     self.distance = calcdistance(
   784                     self.distance = calcdistance(
   781                         self.repo, self.oldnode, self.newnode
   785                         self.repo, self.oldnode, self.newnode
   782                     )
   786                     )
   783                 self._state('state-leave', hex(self.newnode), status=status)
   787                 self._state(b'state-leave', hex(self.newnode), status=status)
   784         finally:
   788         finally:
   785             self.need_leave = False
   789             self.need_leave = False
   786             if self._lock:
   790             if self._lock:
   787                 self._lock.release()
   791                 self._lock.release()
   788 
   792 
   789     def _state(self, cmd, commithash, status='ok'):
   793     def _state(self, cmd, commithash, status=b'ok'):
   790         if not util.safehasattr(self.repo, '_watchmanclient'):
   794         if not util.safehasattr(self.repo, b'_watchmanclient'):
   791             return False
   795             return False
   792         try:
   796         try:
   793             self.repo._watchmanclient.command(
   797             self.repo._watchmanclient.command(
   794                 cmd,
   798                 cmd,
   795                 {
   799                 {
   796                     'name': self.name,
   800                     b'name': self.name,
   797                     'metadata': {
   801                     b'metadata': {
   798                         # the target revision
   802                         # the target revision
   799                         'rev': commithash,
   803                         b'rev': commithash,
   800                         # approximate number of commits between current and target
   804                         # approximate number of commits between current and target
   801                         'distance': self.distance if self.distance else 0,
   805                         b'distance': self.distance if self.distance else 0,
   802                         # success/failure (only really meaningful for state-leave)
   806                         # success/failure (only really meaningful for state-leave)
   803                         'status': status,
   807                         b'status': status,
   804                         # whether the working copy parent is changing
   808                         # whether the working copy parent is changing
   805                         'partial': self.partial,
   809                         b'partial': self.partial,
   806                     },
   810                     },
   807                 },
   811                 },
   808             )
   812             )
   809             return True
   813             return True
   810         except Exception as e:
   814         except Exception as e:
   811             # Swallow any errors; fire and forget
   815             # Swallow any errors; fire and forget
   812             self.repo.ui.log(
   816             self.repo.ui.log(
   813                 'watchman', 'Exception %s while running %s\n', e, cmd
   817                 b'watchman', b'Exception %s while running %s\n', e, cmd
   814             )
   818             )
   815             return False
   819             return False
   816 
   820 
   817 
   821 
   818 # Estimate the distance between two nodes
   822 # Estimate the distance between two nodes
   842     **kwargs
   846     **kwargs
   843 ):
   847 ):
   844 
   848 
   845     distance = 0
   849     distance = 0
   846     partial = True
   850     partial = True
   847     oldnode = repo['.'].node()
   851     oldnode = repo[b'.'].node()
   848     newnode = repo[node].node()
   852     newnode = repo[node].node()
   849     if matcher is None or matcher.always():
   853     if matcher is None or matcher.always():
   850         partial = False
   854         partial = False
   851         distance = calcdistance(repo.unfiltered(), oldnode, newnode)
   855         distance = calcdistance(repo.unfiltered(), oldnode, newnode)
   852 
   856 
   853     with state_update(
   857     with state_update(
   854         repo,
   858         repo,
   855         name="hg.update",
   859         name=b"hg.update",
   856         oldnode=oldnode,
   860         oldnode=oldnode,
   857         newnode=newnode,
   861         newnode=newnode,
   858         distance=distance,
   862         distance=distance,
   859         partial=partial,
   863         partial=partial,
   860     ):
   864     ):
   871         )
   875         )
   872 
   876 
   873 
   877 
   874 def repo_has_depth_one_nested_repo(repo):
   878 def repo_has_depth_one_nested_repo(repo):
   875     for f in repo.wvfs.listdir():
   879     for f in repo.wvfs.listdir():
   876         if os.path.isdir(os.path.join(repo.root, f, '.hg')):
   880         if os.path.isdir(os.path.join(repo.root, f, b'.hg')):
   877             msg = 'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
   881             msg = b'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
   878             repo.ui.debug(msg % f)
   882             repo.ui.debug(msg % f)
   879             return True
   883             return True
   880     return False
   884     return False
   881 
   885 
   882 
   886 
   885     exts = extensions.enabled()
   889     exts = extensions.enabled()
   886     for ext in _blacklist:
   890     for ext in _blacklist:
   887         if ext in exts:
   891         if ext in exts:
   888             ui.warn(
   892             ui.warn(
   889                 _(
   893                 _(
   890                     'The fsmonitor extension is incompatible with the %s '
   894                     b'The fsmonitor extension is incompatible with the %s '
   891                     'extension and has been disabled.\n'
   895                     b'extension and has been disabled.\n'
   892                 )
   896                 )
   893                 % ext
   897                 % ext
   894             )
   898             )
   895             return
   899             return
   896 
   900 
   897     if repo.local():
   901     if repo.local():
   898         # We don't work with subrepos either.
   902         # We don't work with subrepos either.
   899         #
   903         #
   900         # if repo[None].substate can cause a dirstate parse, which is too
   904         # if repo[None].substate can cause a dirstate parse, which is too
   901         # slow. Instead, look for a file called hgsubstate,
   905         # slow. Instead, look for a file called hgsubstate,
   902         if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
   906         if repo.wvfs.exists(b'.hgsubstate') or repo.wvfs.exists(b'.hgsub'):
   903             return
   907             return
   904 
   908 
   905         if repo_has_depth_one_nested_repo(repo):
   909         if repo_has_depth_one_nested_repo(repo):
   906             return
   910             return
   907 
   911 
   908         fsmonitorstate = state.state(repo)
   912         fsmonitorstate = state.state(repo)
   909         if fsmonitorstate.mode == 'off':
   913         if fsmonitorstate.mode == b'off':
   910             return
   914             return
   911 
   915 
   912         try:
   916         try:
   913             client = watchmanclient.client(repo.ui, repo._root)
   917             client = watchmanclient.client(repo.ui, repo._root)
   914         except Exception as ex:
   918         except Exception as ex:
   916             return
   920             return
   917 
   921 
   918         repo._fsmonitorstate = fsmonitorstate
   922         repo._fsmonitorstate = fsmonitorstate
   919         repo._watchmanclient = client
   923         repo._watchmanclient = client
   920 
   924 
   921         dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
   925         dirstate, cached = localrepo.isfilecached(repo, b'dirstate')
   922         if cached:
   926         if cached:
   923             # at this point since fsmonitorstate wasn't present,
   927             # at this point since fsmonitorstate wasn't present,
   924             # repo.dirstate is not a fsmonitordirstate
   928             # repo.dirstate is not a fsmonitordirstate
   925             makedirstate(repo, dirstate)
   929             makedirstate(repo, dirstate)
   926 
   930 
   933                 return super(fsmonitorrepo, self).wlock(*args, **kwargs)
   937                 return super(fsmonitorrepo, self).wlock(*args, **kwargs)
   934 
   938 
   935             def wlock(self, *args, **kwargs):
   939             def wlock(self, *args, **kwargs):
   936                 l = super(fsmonitorrepo, self).wlock(*args, **kwargs)
   940                 l = super(fsmonitorrepo, self).wlock(*args, **kwargs)
   937                 if not ui.configbool(
   941                 if not ui.configbool(
   938                     "experimental", "fsmonitor.transaction_notify"
   942                     b"experimental", b"fsmonitor.transaction_notify"
   939                 ):
   943                 ):
   940                     return l
   944                     return l
   941                 if l.held != 1:
   945                 if l.held != 1:
   942                     return l
   946                     return l
   943                 origrelease = l.releasefn
   947                 origrelease = l.releasefn
   949                         l.stateupdate.exit()
   953                         l.stateupdate.exit()
   950                         l.stateupdate = None
   954                         l.stateupdate = None
   951 
   955 
   952                 try:
   956                 try:
   953                     l.stateupdate = None
   957                     l.stateupdate = None
   954                     l.stateupdate = state_update(self, name="hg.transaction")
   958                     l.stateupdate = state_update(self, name=b"hg.transaction")
   955                     l.stateupdate.enter()
   959                     l.stateupdate.enter()
   956                     l.releasefn = staterelease
   960                     l.releasefn = staterelease
   957                 except Exception as e:
   961                 except Exception as e:
   958                     # Swallow any errors; fire and forget
   962                     # Swallow any errors; fire and forget
   959                     self.ui.log('watchman', 'Exception in state update %s\n', e)
   963                     self.ui.log(
       
   964                         b'watchman', b'Exception in state update %s\n', e
       
   965                     )
   960                 return l
   966                 return l
   961 
   967 
   962         repo.__class__ = fsmonitorrepo
   968         repo.__class__ = fsmonitorrepo