2016-03-03 17:29:19 +03:00
|
|
|
# state.py - fsmonitor persistent state
|
|
|
|
#
|
|
|
|
# Copyright 2013-2016 Facebook, Inc.
|
|
|
|
#
|
|
|
|
# This software may be used and distributed according to the terms of the
|
|
|
|
# GNU General Public License version 2 or any later version.
|
|
|
|
|
|
|
|
from __future__ import absolute_import
|
|
|
|
|
|
|
|
import errno
|
|
|
|
import os
|
|
|
|
import socket
|
|
|
|
import struct
|
|
|
|
|
2019-01-30 03:25:33 +03:00
|
|
|
from edenscm.mercurial import pathutil, util
|
|
|
|
from edenscm.mercurial.i18n import _
|
2018-06-05 22:23:44 +03:00
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
|
|
|
|
_version = 4
|
|
|
|
_versionformat = ">I"
|
|
|
|
|
2018-05-30 12:16:33 +03:00
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
class state(object):
|
|
|
|
def __init__(self, repo):
|
2018-09-28 17:08:56 +03:00
|
|
|
self._vfs = repo.localvfs
|
2016-03-03 17:29:19 +03:00
|
|
|
self._ui = repo.ui
|
|
|
|
self._rootdir = pathutil.normasprefix(repo.root)
|
|
|
|
self._lastclock = None
|
2018-07-02 23:26:14 +03:00
|
|
|
self._lastisfresh = False
|
2019-02-12 21:52:09 +03:00
|
|
|
self._lastchangedfilecount = 0
|
2016-03-03 17:29:19 +03:00
|
|
|
|
2018-05-30 12:16:33 +03:00
|
|
|
self.mode = self._ui.config("fsmonitor", "mode")
|
|
|
|
self.walk_on_invalidate = self._ui.configbool("fsmonitor", "walk_on_invalidate")
|
|
|
|
self.timeout = float(self._ui.config("fsmonitor", "timeout"))
|
2018-06-15 07:23:07 +03:00
|
|
|
self._repo = repo
|
|
|
|
self._droplist = []
|
2018-10-20 05:35:00 +03:00
|
|
|
self._ignorelist = []
|
2016-03-03 17:29:19 +03:00
|
|
|
|
2018-06-22 23:09:25 +03:00
|
|
|
@property
|
|
|
|
def _usetreestate(self):
|
|
|
|
return "treestate" in self._repo.requirements
|
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
def get(self):
|
2018-06-15 07:23:07 +03:00
|
|
|
"""return clock, ignorehash, notefiles"""
|
|
|
|
if self._usetreestate:
|
|
|
|
clock = self._repo.dirstate.getclock()
|
|
|
|
# XXX: ignorehash is already broken, so return None
|
|
|
|
ignorehash = None
|
|
|
|
# note files are already included in nonnormalset, so they will be
|
|
|
|
# processed anyway, do not return a separate notefiles.
|
|
|
|
notefiles = []
|
|
|
|
return clock, ignorehash, notefiles
|
2016-03-03 17:29:19 +03:00
|
|
|
try:
|
2018-05-30 12:16:33 +03:00
|
|
|
file = self._vfs("fsmonitor.state", "rb")
|
2016-03-03 17:29:19 +03:00
|
|
|
except IOError as inst:
|
|
|
|
if inst.errno != errno.ENOENT:
|
|
|
|
raise
|
|
|
|
return None, None, None
|
|
|
|
|
|
|
|
versionbytes = file.read(4)
|
|
|
|
if len(versionbytes) < 4:
|
|
|
|
self._ui.log(
|
2018-05-30 12:16:33 +03:00
|
|
|
"fsmonitor",
|
|
|
|
"fsmonitor: state file only has %d bytes, "
|
|
|
|
"nuking state\n" % len(versionbytes),
|
|
|
|
)
|
2018-06-05 22:23:44 +03:00
|
|
|
self.invalidate(reason="state_file_truncated")
|
2016-03-03 17:29:19 +03:00
|
|
|
return None, None, None
|
|
|
|
try:
|
|
|
|
diskversion = struct.unpack(_versionformat, versionbytes)[0]
|
|
|
|
if diskversion != _version:
|
|
|
|
# different version, nuke state and start over
|
|
|
|
self._ui.log(
|
2018-05-30 12:16:33 +03:00
|
|
|
"fsmonitor",
|
|
|
|
"fsmonitor: version switch from %d to "
|
|
|
|
"%d, nuking state\n" % (diskversion, _version),
|
|
|
|
)
|
2018-06-05 22:23:44 +03:00
|
|
|
self.invalidate(reason="state_file_wrong_version")
|
2016-03-03 17:29:19 +03:00
|
|
|
return None, None, None
|
|
|
|
|
2018-05-30 12:16:33 +03:00
|
|
|
state = file.read().split("\0")
|
2016-03-03 17:29:19 +03:00
|
|
|
# state = hostname\0clock\0ignorehash\0 + list of files, each
|
|
|
|
# followed by a \0
|
2016-11-25 18:30:46 +03:00
|
|
|
if len(state) < 3:
|
|
|
|
self._ui.log(
|
2018-05-30 12:16:33 +03:00
|
|
|
"fsmonitor",
|
|
|
|
"fsmonitor: state file truncated (expected "
|
|
|
|
"3 chunks, found %d), nuking state\n",
|
|
|
|
len(state),
|
|
|
|
)
|
2018-06-05 22:23:44 +03:00
|
|
|
self.invalidate(reason="state_file_truncated")
|
2016-11-25 18:30:46 +03:00
|
|
|
return None, None, None
|
2016-03-03 17:29:19 +03:00
|
|
|
diskhostname = state[0]
|
|
|
|
hostname = socket.gethostname()
|
|
|
|
if diskhostname != hostname:
|
|
|
|
# file got moved to a different host
|
2018-05-30 12:16:33 +03:00
|
|
|
self._ui.log(
|
|
|
|
"fsmonitor",
|
|
|
|
'fsmonitor: stored hostname "%s" '
|
|
|
|
'different from current "%s", nuking state\n'
|
|
|
|
% (diskhostname, hostname),
|
|
|
|
)
|
2018-06-05 22:23:44 +03:00
|
|
|
self.invalidate(reason="hostname_mismatch")
|
2016-03-03 17:29:19 +03:00
|
|
|
return None, None, None
|
|
|
|
|
|
|
|
clock = state[1]
|
|
|
|
ignorehash = state[2]
|
|
|
|
# discard the value after the last \0
|
|
|
|
notefiles = state[3:-1]
|
|
|
|
|
|
|
|
finally:
|
|
|
|
file.close()
|
|
|
|
|
2018-05-30 12:16:33 +03:00
|
|
|
if "fsmonitor_details" in getattr(self._ui, "track", ()):
|
2018-10-30 06:24:55 +03:00
|
|
|
from . import _reprshort
|
|
|
|
|
2018-05-30 12:16:33 +03:00
|
|
|
self._ui.log(
|
2018-10-30 06:24:55 +03:00
|
|
|
"fsmonitor_details",
|
|
|
|
"clock, notefiles = %r, %s" % (clock, _reprshort(notefiles)),
|
2018-05-30 12:16:33 +03:00
|
|
|
)
|
2018-05-18 06:07:41 +03:00
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
return clock, ignorehash, notefiles
|
|
|
|
|
2018-06-15 07:23:07 +03:00
|
|
|
def setdroplist(self, droplist):
|
|
|
|
"""set a list of files to be dropped from dirstate upon 'set'.
|
|
|
|
|
|
|
|
This is used to clean up deleted untracked files from treestate, which
|
|
|
|
tracks untracked files.
|
|
|
|
"""
|
|
|
|
self._droplist = droplist
|
|
|
|
|
2018-10-20 05:35:00 +03:00
|
|
|
def setignorelist(self, ignorelist):
|
|
|
|
"""set a list of files that are found ignored when processing notefiles"""
|
|
|
|
if self._ui.configbool("fsmonitor", "track-ignore-files"):
|
|
|
|
self._ignorelist = ignorelist
|
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
def set(self, clock, ignorehash, notefiles):
|
2018-06-18 22:36:38 +03:00
|
|
|
if "fsmonitor_details" in getattr(self._ui, "track", ()):
|
2018-10-30 06:24:55 +03:00
|
|
|
from . import _reprshort
|
|
|
|
|
2018-06-18 22:36:38 +03:00
|
|
|
self._ui.log(
|
|
|
|
"fsmonitor_details",
|
2018-10-30 06:24:55 +03:00
|
|
|
"set clock, notefiles = %r, %s" % (clock, _reprshort(notefiles)),
|
2018-06-18 22:36:38 +03:00
|
|
|
)
|
|
|
|
|
2018-06-15 07:23:07 +03:00
|
|
|
if self._usetreestate:
|
|
|
|
ds = self._repo.dirstate
|
|
|
|
dmap = ds._map
|
2018-07-02 23:26:14 +03:00
|
|
|
changed = bool(self._droplist) or bool(self._lastisfresh)
|
2019-02-12 21:52:09 +03:00
|
|
|
if self._lastchangedfilecount >= self._ui.configint(
|
|
|
|
"fsmonitor", "watchman-changed-file-threshold"
|
|
|
|
):
|
|
|
|
changed = True
|
2018-06-15 07:23:07 +03:00
|
|
|
for path in self._droplist:
|
2018-10-12 19:35:08 +03:00
|
|
|
dmap.deletefile(path, None)
|
2018-06-15 07:23:07 +03:00
|
|
|
self._droplist = []
|
|
|
|
for path in notefiles:
|
|
|
|
changed |= ds.needcheck(path)
|
2018-10-20 05:35:00 +03:00
|
|
|
for path in self._ignorelist:
|
|
|
|
changed |= ds.needcheck(path)
|
|
|
|
self._ignorelist = []
|
2018-06-15 07:23:07 +03:00
|
|
|
# Avoid updating dirstate frequently if nothing changed.
|
|
|
|
# But do update dirstate if the clock is reset to None, or is
|
|
|
|
# moving away from None.
|
|
|
|
if not clock or changed or not ds.getclock():
|
|
|
|
ds.setclock(clock)
|
|
|
|
return
|
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
if clock is None:
|
2018-06-05 22:23:44 +03:00
|
|
|
self.invalidate(reason="no_clock")
|
2016-03-03 17:29:19 +03:00
|
|
|
return
|
|
|
|
|
2018-06-15 07:23:07 +03:00
|
|
|
# The code runs with a wlock taken, and dirstate has passed its
|
|
|
|
# identity check. So we can update both dirstate and fsmonitor state.
|
|
|
|
# See _poststatusfixup in context.py
|
2017-06-13 01:34:31 +03:00
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
try:
|
2018-05-30 12:16:33 +03:00
|
|
|
file = self._vfs("fsmonitor.state", "wb", atomictemp=True, checkambig=True)
|
2016-03-03 17:29:19 +03:00
|
|
|
except (IOError, OSError):
|
|
|
|
self._ui.warn(_("warning: unable to write out fsmonitor state\n"))
|
|
|
|
return
|
|
|
|
|
2016-11-25 18:30:46 +03:00
|
|
|
with file:
|
2016-03-03 17:29:19 +03:00
|
|
|
file.write(struct.pack(_versionformat, _version))
|
2018-05-30 12:16:33 +03:00
|
|
|
file.write(socket.gethostname() + "\0")
|
|
|
|
file.write(clock + "\0")
|
|
|
|
file.write(ignorehash + "\0")
|
2016-03-03 17:29:19 +03:00
|
|
|
if notefiles:
|
2018-05-30 12:16:33 +03:00
|
|
|
file.write("\0".join(notefiles))
|
|
|
|
file.write("\0")
|
2016-03-03 17:29:19 +03:00
|
|
|
|
2018-06-05 22:23:44 +03:00
|
|
|
def invalidate(self, reason=None):
|
|
|
|
if reason:
|
|
|
|
self._ui.log("command_info", watchman_invalidate_reason=reason)
|
2016-03-03 17:29:19 +03:00
|
|
|
try:
|
2018-05-30 12:16:33 +03:00
|
|
|
os.unlink(os.path.join(self._rootdir, ".hg", "fsmonitor.state"))
|
2016-03-03 17:29:19 +03:00
|
|
|
except OSError as inst:
|
|
|
|
if inst.errno != errno.ENOENT:
|
|
|
|
raise
|
2018-05-30 12:16:33 +03:00
|
|
|
if "fsmonitor_details" in getattr(self._ui, "track", ()):
|
|
|
|
self._ui.log("fsmonitor_details", "fsmonitor state invalidated")
|
2016-03-03 17:29:19 +03:00
|
|
|
|
|
|
|
def setlastclock(self, clock):
|
2018-05-30 12:16:33 +03:00
|
|
|
if "fsmonitor_details" in getattr(self._ui, "track", ()):
|
|
|
|
self._ui.log("fsmonitor_details", "setlastclock: %r" % clock)
|
2016-03-03 17:29:19 +03:00
|
|
|
self._lastclock = clock
|
|
|
|
|
2018-07-02 23:26:14 +03:00
|
|
|
def setlastisfresh(self, isfresh):
|
|
|
|
if "fsmonitor_details" in getattr(self._ui, "track", ()):
|
|
|
|
self._ui.log("fsmonitor_details", "setlastisfresh: %r" % isfresh)
|
|
|
|
self._lastisfresh = isfresh
|
|
|
|
|
2019-02-12 21:52:09 +03:00
|
|
|
def setwatchmanchangedfilecount(self, filecount):
|
|
|
|
self._lastchangedfilecount = filecount
|
|
|
|
|
2016-03-03 17:29:19 +03:00
|
|
|
def getlastclock(self):
|
2018-05-30 12:16:33 +03:00
|
|
|
if "fsmonitor_details" in getattr(self._ui, "track", ()):
|
|
|
|
self._ui.log("fsmonitor_details", "getlastclock: %r" % self._lastclock)
|
2018-06-15 22:50:04 +03:00
|
|
|
return self._lastclock
|