comparison hgext/fsmonitor/state.py @ 28433:3b67f27bb908

fsmonitor: new experimental extension Extension to plug into a Watchman daemon, speeding up hg status calls by relying on OS events to tell us what files have changed. Originally developed at https://bitbucket.org/facebook/hgwatchman
author Martijn Pieters <mjpieters@fb.com>
date Thu, 03 Mar 2016 14:29:19 +0000
parents
children a0939666b836
comparison
equal deleted inserted replaced
28432:2377c4ac4eec 28433:3b67f27bb908
1 # state.py - fsmonitor persistent state
2 #
3 # Copyright 2013-2016 Facebook, Inc.
4 #
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
7
8 from __future__ import absolute_import
9
10 import errno
11 import os
12 import socket
13 import struct
14
15 from mercurial import pathutil
16 from mercurial.i18n import _
17
18 _version = 4
19 _versionformat = ">I"
20
21 class state(object):
22 def __init__(self, repo):
23 self._opener = repo.opener
24 self._ui = repo.ui
25 self._rootdir = pathutil.normasprefix(repo.root)
26 self._lastclock = None
27
28 self.mode = self._ui.config('fsmonitor', 'mode', default='on')
29 self.walk_on_invalidate = self._ui.configbool(
30 'fsmonitor', 'walk_on_invalidate', False)
31 self.timeout = float(self._ui.config(
32 'fsmonitor', 'timeout', default='2'))
33
34 def get(self):
35 try:
36 file = self._opener('fsmonitor.state', 'rb')
37 except IOError as inst:
38 if inst.errno != errno.ENOENT:
39 raise
40 return None, None, None
41
42 versionbytes = file.read(4)
43 if len(versionbytes) < 4:
44 self._ui.log(
45 'fsmonitor', 'fsmonitor: state file only has %d bytes, '
46 'nuking state\n' % len(versionbytes))
47 self.invalidate()
48 return None, None, None
49 try:
50 diskversion = struct.unpack(_versionformat, versionbytes)[0]
51 if diskversion != _version:
52 # different version, nuke state and start over
53 self._ui.log(
54 'fsmonitor', 'fsmonitor: version switch from %d to '
55 '%d, nuking state\n' % (diskversion, _version))
56 self.invalidate()
57 return None, None, None
58
59 state = file.read().split('\0')
60 # state = hostname\0clock\0ignorehash\0 + list of files, each
61 # followed by a \0
62 diskhostname = state[0]
63 hostname = socket.gethostname()
64 if diskhostname != hostname:
65 # file got moved to a different host
66 self._ui.log('fsmonitor', 'fsmonitor: stored hostname "%s" '
67 'different from current "%s", nuking state\n' %
68 (diskhostname, hostname))
69 self.invalidate()
70 return None, None, None
71
72 clock = state[1]
73 ignorehash = state[2]
74 # discard the value after the last \0
75 notefiles = state[3:-1]
76
77 finally:
78 file.close()
79
80 return clock, ignorehash, notefiles
81
82 def set(self, clock, ignorehash, notefiles):
83 if clock is None:
84 self.invalidate()
85 return
86
87 try:
88 file = self._opener('fsmonitor.state', 'wb')
89 except (IOError, OSError):
90 self._ui.warn(_("warning: unable to write out fsmonitor state\n"))
91 return
92
93 try:
94 file.write(struct.pack(_versionformat, _version))
95 file.write(socket.gethostname() + '\0')
96 file.write(clock + '\0')
97 file.write(ignorehash + '\0')
98 if notefiles:
99 file.write('\0'.join(notefiles))
100 file.write('\0')
101 finally:
102 file.close()
103
104 def invalidate(self):
105 try:
106 os.unlink(os.path.join(self._rootdir, '.hg', 'fsmonitor.state'))
107 except OSError as inst:
108 if inst.errno != errno.ENOENT:
109 raise
110
111 def setlastclock(self, clock):
112 self._lastclock = clock
113
114 def getlastclock(self):
115 return self._lastclock