state.py
149 lines
| 4.7 KiB
| text/x-python
|
PythonLexer
|
r28433 | # 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. | ||||
import errno | ||||
import os | ||||
import socket | ||||
import struct | ||||
|
r29205 | from mercurial.i18n import _ | ||
|
r32816 | from mercurial import ( | ||
|
r43710 | encoding, | ||
|
r32816 | pathutil, | ||
util, | ||||
) | ||||
|
r28433 | |||
_version = 4 | ||||
|
r43347 | _versionformat = b">I" | ||
|
r28433 | |||
|
r43346 | |||
|
r49801 | class state: | ||
|
r28433 | def __init__(self, repo): | ||
|
r31215 | self._vfs = repo.vfs | ||
|
r28433 | self._ui = repo.ui | ||
self._rootdir = pathutil.normasprefix(repo.root) | ||||
self._lastclock = None | ||||
|
r32816 | self._identity = util.filestat(None) | ||
|
r28433 | |||
|
r43347 | self.mode = self._ui.config(b'fsmonitor', b'mode') | ||
|
r28433 | self.walk_on_invalidate = self._ui.configbool( | ||
|
r43347 | b'fsmonitor', b'walk_on_invalidate' | ||
|
r43346 | ) | ||
|
r43347 | self.timeout = float(self._ui.config(b'fsmonitor', b'timeout')) | ||
|
r28433 | |||
def get(self): | ||||
try: | ||||
|
r43347 | file = self._vfs(b'fsmonitor.state', b'rb') | ||
|
r28433 | except IOError as inst: | ||
|
r32816 | self._identity = util.filestat(None) | ||
|
r28433 | if inst.errno != errno.ENOENT: | ||
raise | ||||
return None, None, None | ||||
|
r32816 | self._identity = util.filestat.fromfp(file) | ||
|
r28433 | versionbytes = file.read(4) | ||
if len(versionbytes) < 4: | ||||
self._ui.log( | ||||
|
r43347 | b'fsmonitor', | ||
b'fsmonitor: state file only has %d bytes, ' | ||||
b'nuking state\n' % len(versionbytes), | ||||
|
r43346 | ) | ||
|
r28433 | self.invalidate() | ||
return None, None, None | ||||
try: | ||||
diskversion = struct.unpack(_versionformat, versionbytes)[0] | ||||
if diskversion != _version: | ||||
# different version, nuke state and start over | ||||
self._ui.log( | ||||
|
r43347 | b'fsmonitor', | ||
b'fsmonitor: version switch from %d to ' | ||||
b'%d, nuking state\n' % (diskversion, _version), | ||||
|
r43346 | ) | ||
|
r28433 | self.invalidate() | ||
return None, None, None | ||||
|
r43347 | state = file.read().split(b'\0') | ||
|
r28433 | # state = hostname\0clock\0ignorehash\0 + list of files, each | ||
# followed by a \0 | ||||
|
r30539 | if len(state) < 3: | ||
self._ui.log( | ||||
|
r43347 | b'fsmonitor', | ||
b'fsmonitor: state file truncated (expected ' | ||||
b'3 chunks, found %d), nuking state\n', | ||||
|
r43346 | len(state), | ||
) | ||||
|
r30539 | self.invalidate() | ||
return None, None, None | ||||
|
r28433 | diskhostname = state[0] | ||
|
r43710 | hostname = encoding.strtolocal(socket.gethostname()) | ||
|
r28433 | if diskhostname != hostname: | ||
# file got moved to a different host | ||||
|
r43346 | self._ui.log( | ||
|
r43347 | b'fsmonitor', | ||
b'fsmonitor: stored hostname "%s" ' | ||||
b'different from current "%s", nuking state\n' | ||||
|
r43346 | % (diskhostname, hostname), | ||
) | ||||
|
r28433 | self.invalidate() | ||
return None, None, None | ||||
clock = state[1] | ||||
ignorehash = state[2] | ||||
# discard the value after the last \0 | ||||
notefiles = state[3:-1] | ||||
finally: | ||||
file.close() | ||||
return clock, ignorehash, notefiles | ||||
def set(self, clock, ignorehash, notefiles): | ||||
if clock is None: | ||||
self.invalidate() | ||||
return | ||||
|
r32816 | # Read the identity from the file on disk rather than from the open file | ||
# pointer below, because the latter is actually a brand new file. | ||||
|
r43347 | identity = util.filestat.frompath(self._vfs.join(b'fsmonitor.state')) | ||
|
r32816 | if identity != self._identity: | ||
|
r43347 | self._ui.debug( | ||
b'skip updating fsmonitor.state: identity mismatch\n' | ||||
) | ||||
|
r32816 | return | ||
|
r28433 | try: | ||
|
r43346 | file = self._vfs( | ||
|
r43347 | b'fsmonitor.state', b'wb', atomictemp=True, checkambig=True | ||
|
r43346 | ) | ||
|
r28433 | except (IOError, OSError): | ||
|
r43347 | self._ui.warn(_(b"warning: unable to write out fsmonitor state\n")) | ||
|
r28433 | return | ||
|
r30539 | with file: | ||
|
r28433 | file.write(struct.pack(_versionformat, _version)) | ||
|
r43710 | file.write(encoding.strtolocal(socket.gethostname()) + b'\0') | ||
|
r43347 | file.write(clock + b'\0') | ||
file.write(ignorehash + b'\0') | ||||
|
r28433 | if notefiles: | ||
|
r43347 | file.write(b'\0'.join(notefiles)) | ||
file.write(b'\0') | ||||
|
r28433 | |||
def invalidate(self): | ||||
try: | ||||
|
r43347 | os.unlink(os.path.join(self._rootdir, b'.hg', b'fsmonitor.state')) | ||
|
r28433 | except OSError as inst: | ||
if inst.errno != errno.ENOENT: | ||||
raise | ||||
|
r32816 | self._identity = util.filestat(None) | ||
|
r28433 | |||
def setlastclock(self, clock): | ||||
self._lastclock = clock | ||||
def getlastclock(self): | ||||
return self._lastclock | ||||