##// END OF EJS Templates
fsmonitor: declare missing config options...
Gregory Szorc -
r34887:dbb54232 default
parent child Browse files
Show More
@@ -1,819 +1,825
1 # __init__.py - fsmonitor initialization and overrides
1 # __init__.py - fsmonitor initialization and overrides
2 #
2 #
3 # Copyright 2013-2016 Facebook, Inc.
3 # Copyright 2013-2016 Facebook, Inc.
4 #
4 #
5 # This software may be used and distributed according to the terms of the
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.
6 # GNU General Public License version 2 or any later version.
7
7
8 '''Faster status operations with the Watchman file monitor (EXPERIMENTAL)
8 '''Faster status operations with the Watchman file monitor (EXPERIMENTAL)
9
9
10 Integrates the file-watching program Watchman with Mercurial to produce faster
10 Integrates the file-watching program Watchman with Mercurial to produce faster
11 status results.
11 status results.
12
12
13 On a particular Linux system, for a real-world repository with over 400,000
13 On a particular Linux system, for a real-world repository with over 400,000
14 files hosted on ext4, vanilla `hg status` takes 1.3 seconds. On the same
14 files hosted on ext4, vanilla `hg status` takes 1.3 seconds. On the same
15 system, with fsmonitor it takes about 0.3 seconds.
15 system, with fsmonitor it takes about 0.3 seconds.
16
16
17 fsmonitor requires no configuration -- it will tell Watchman about your
17 fsmonitor requires no configuration -- it will tell Watchman about your
18 repository as necessary. You'll need to install Watchman from
18 repository as necessary. You'll need to install Watchman from
19 https://facebook.github.io/watchman/ and make sure it is in your PATH.
19 https://facebook.github.io/watchman/ and make sure it is in your PATH.
20
20
21 fsmonitor is incompatible with the largefiles and eol extensions, and
21 fsmonitor is incompatible with the largefiles and eol extensions, and
22 will disable itself if any of those are active.
22 will disable itself if any of those are active.
23
23
24 The following configuration options exist:
24 The following configuration options exist:
25
25
26 ::
26 ::
27
27
28 [fsmonitor]
28 [fsmonitor]
29 mode = {off, on, paranoid}
29 mode = {off, on, paranoid}
30
30
31 When `mode = off`, fsmonitor will disable itself (similar to not loading the
31 When `mode = off`, fsmonitor will disable itself (similar to not loading the
32 extension at all). When `mode = on`, fsmonitor will be enabled (the default).
32 extension at all). When `mode = on`, fsmonitor will be enabled (the default).
33 When `mode = paranoid`, fsmonitor will query both Watchman and the filesystem,
33 When `mode = paranoid`, fsmonitor will query both Watchman and the filesystem,
34 and ensure that the results are consistent.
34 and ensure that the results are consistent.
35
35
36 ::
36 ::
37
37
38 [fsmonitor]
38 [fsmonitor]
39 timeout = (float)
39 timeout = (float)
40
40
41 A value, in seconds, that determines how long fsmonitor will wait for Watchman
41 A value, in seconds, that determines how long fsmonitor will wait for Watchman
42 to return results. Defaults to `2.0`.
42 to return results. Defaults to `2.0`.
43
43
44 ::
44 ::
45
45
46 [fsmonitor]
46 [fsmonitor]
47 blacklistusers = (list of userids)
47 blacklistusers = (list of userids)
48
48
49 A list of usernames for which fsmonitor will disable itself altogether.
49 A list of usernames for which fsmonitor will disable itself altogether.
50
50
51 ::
51 ::
52
52
53 [fsmonitor]
53 [fsmonitor]
54 walk_on_invalidate = (boolean)
54 walk_on_invalidate = (boolean)
55
55
56 Whether or not to walk the whole repo ourselves when our cached state has been
56 Whether or not to walk the whole repo ourselves when our cached state has been
57 invalidated, for example when Watchman has been restarted or .hgignore rules
57 invalidated, for example when Watchman has been restarted or .hgignore rules
58 have been changed. Walking the repo in that case can result in competing for
58 have been changed. Walking the repo in that case can result in competing for
59 I/O with Watchman. For large repos it is recommended to set this value to
59 I/O with Watchman. For large repos it is recommended to set this value to
60 false. You may wish to set this to true if you have a very fast filesystem
60 false. You may wish to set this to true if you have a very fast filesystem
61 that can outpace the IPC overhead of getting the result data for the full repo
61 that can outpace the IPC overhead of getting the result data for the full repo
62 from Watchman. Defaults to false.
62 from Watchman. Defaults to false.
63
63
64 ::
64 ::
65
65
66 [fsmonitor]
66 [fsmonitor]
67 warn_when_unused = (boolean)
67 warn_when_unused = (boolean)
68
68
69 Whether to print a warning during certain operations when fsmonitor would be
69 Whether to print a warning during certain operations when fsmonitor would be
70 beneficial to performance but isn't enabled.
70 beneficial to performance but isn't enabled.
71
71
72 ::
72 ::
73
73
74 [fsmonitor]
74 [fsmonitor]
75 warn_update_file_count = (integer)
75 warn_update_file_count = (integer)
76
76
77 If ``warn_when_unused`` is set and fsmonitor isn't enabled, a warning will
77 If ``warn_when_unused`` is set and fsmonitor isn't enabled, a warning will
78 be printed during working directory updates if this many files will be
78 be printed during working directory updates if this many files will be
79 created.
79 created.
80 '''
80 '''
81
81
82 # Platforms Supported
82 # Platforms Supported
83 # ===================
83 # ===================
84 #
84 #
85 # **Linux:** *Stable*. Watchman and fsmonitor are both known to work reliably,
85 # **Linux:** *Stable*. Watchman and fsmonitor are both known to work reliably,
86 # even under severe loads.
86 # even under severe loads.
87 #
87 #
88 # **Mac OS X:** *Stable*. The Mercurial test suite passes with fsmonitor
88 # **Mac OS X:** *Stable*. The Mercurial test suite passes with fsmonitor
89 # turned on, on case-insensitive HFS+. There has been a reasonable amount of
89 # turned on, on case-insensitive HFS+. There has been a reasonable amount of
90 # user testing under normal loads.
90 # user testing under normal loads.
91 #
91 #
92 # **Solaris, BSD:** *Alpha*. watchman and fsmonitor are believed to work, but
92 # **Solaris, BSD:** *Alpha*. watchman and fsmonitor are believed to work, but
93 # very little testing has been done.
93 # very little testing has been done.
94 #
94 #
95 # **Windows:** *Alpha*. Not in a release version of watchman or fsmonitor yet.
95 # **Windows:** *Alpha*. Not in a release version of watchman or fsmonitor yet.
96 #
96 #
97 # Known Issues
97 # Known Issues
98 # ============
98 # ============
99 #
99 #
100 # * fsmonitor will disable itself if any of the following extensions are
100 # * fsmonitor will disable itself if any of the following extensions are
101 # enabled: largefiles, inotify, eol; or if the repository has subrepos.
101 # enabled: largefiles, inotify, eol; or if the repository has subrepos.
102 # * fsmonitor will produce incorrect results if nested repos that are not
102 # * fsmonitor will produce incorrect results if nested repos that are not
103 # subrepos exist. *Workaround*: add nested repo paths to your `.hgignore`.
103 # subrepos exist. *Workaround*: add nested repo paths to your `.hgignore`.
104 #
104 #
105 # The issues related to nested repos and subrepos are probably not fundamental
105 # The issues related to nested repos and subrepos are probably not fundamental
106 # ones. Patches to fix them are welcome.
106 # ones. Patches to fix them are welcome.
107
107
108 from __future__ import absolute_import
108 from __future__ import absolute_import
109
109
110 import codecs
110 import codecs
111 import hashlib
111 import hashlib
112 import os
112 import os
113 import stat
113 import stat
114 import sys
114 import sys
115 import weakref
115 import weakref
116
116
117 from mercurial.i18n import _
117 from mercurial.i18n import _
118 from mercurial.node import (
118 from mercurial.node import (
119 hex,
119 hex,
120 nullid,
120 nullid,
121 )
121 )
122
122
123 from mercurial import (
123 from mercurial import (
124 context,
124 context,
125 encoding,
125 encoding,
126 error,
126 error,
127 extensions,
127 extensions,
128 localrepo,
128 localrepo,
129 merge,
129 merge,
130 pathutil,
130 pathutil,
131 pycompat,
131 pycompat,
132 registrar,
132 registrar,
133 scmutil,
133 scmutil,
134 util,
134 util,
135 )
135 )
136 from mercurial import match as matchmod
136 from mercurial import match as matchmod
137
137
138 from . import (
138 from . import (
139 pywatchman,
139 pywatchman,
140 state,
140 state,
141 watchmanclient,
141 watchmanclient,
142 )
142 )
143
143
144 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
144 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
145 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
145 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
146 # be specifying the version(s) of Mercurial they are tested with, or
146 # be specifying the version(s) of Mercurial they are tested with, or
147 # leave the attribute unspecified.
147 # leave the attribute unspecified.
148 testedwith = 'ships-with-hg-core'
148 testedwith = 'ships-with-hg-core'
149
149
150 configtable = {}
150 configtable = {}
151 configitem = registrar.configitem(configtable)
151 configitem = registrar.configitem(configtable)
152
152
153 configitem('fsmonitor', 'mode',
153 configitem('fsmonitor', 'mode',
154 default='on',
154 default='on',
155 )
155 )
156 configitem('fsmonitor', 'walk_on_invalidate',
156 configitem('fsmonitor', 'walk_on_invalidate',
157 default=False,
157 default=False,
158 )
158 )
159 configitem('fsmonitor', 'timeout',
159 configitem('fsmonitor', 'timeout',
160 default='2',
160 default='2',
161 )
161 )
162 configitem('fsmonitor', 'blacklistusers',
162 configitem('fsmonitor', 'blacklistusers',
163 default=list,
163 default=list,
164 )
164 )
165 configitem('experimental', 'fsmonitor.transaction_notify',
166 default=False,
167 )
168 configitem('experimental', 'fsmonitor.wc_change_notify',
169 default=False,
170 )
165
171
166 # This extension is incompatible with the following blacklisted extensions
172 # This extension is incompatible with the following blacklisted extensions
167 # and will disable itself when encountering one of these:
173 # and will disable itself when encountering one of these:
168 _blacklist = ['largefiles', 'eol']
174 _blacklist = ['largefiles', 'eol']
169
175
170 def _handleunavailable(ui, state, ex):
176 def _handleunavailable(ui, state, ex):
171 """Exception handler for Watchman interaction exceptions"""
177 """Exception handler for Watchman interaction exceptions"""
172 if isinstance(ex, watchmanclient.Unavailable):
178 if isinstance(ex, watchmanclient.Unavailable):
173 if ex.warn:
179 if ex.warn:
174 ui.warn(str(ex) + '\n')
180 ui.warn(str(ex) + '\n')
175 if ex.invalidate:
181 if ex.invalidate:
176 state.invalidate()
182 state.invalidate()
177 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
183 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
178 else:
184 else:
179 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
185 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
180
186
181 def _hashignore(ignore):
187 def _hashignore(ignore):
182 """Calculate hash for ignore patterns and filenames
188 """Calculate hash for ignore patterns and filenames
183
189
184 If this information changes between Mercurial invocations, we can't
190 If this information changes between Mercurial invocations, we can't
185 rely on Watchman information anymore and have to re-scan the working
191 rely on Watchman information anymore and have to re-scan the working
186 copy.
192 copy.
187
193
188 """
194 """
189 sha1 = hashlib.sha1()
195 sha1 = hashlib.sha1()
190 sha1.update(repr(ignore))
196 sha1.update(repr(ignore))
191 return sha1.hexdigest()
197 return sha1.hexdigest()
192
198
193 _watchmanencoding = pywatchman.encoding.get_local_encoding()
199 _watchmanencoding = pywatchman.encoding.get_local_encoding()
194 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
200 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
195 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
201 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
196
202
197 def _watchmantofsencoding(path):
203 def _watchmantofsencoding(path):
198 """Fix path to match watchman and local filesystem encoding
204 """Fix path to match watchman and local filesystem encoding
199
205
200 watchman's paths encoding can differ from filesystem encoding. For example,
206 watchman's paths encoding can differ from filesystem encoding. For example,
201 on Windows, it's always utf-8.
207 on Windows, it's always utf-8.
202 """
208 """
203 try:
209 try:
204 decoded = path.decode(_watchmanencoding)
210 decoded = path.decode(_watchmanencoding)
205 except UnicodeDecodeError as e:
211 except UnicodeDecodeError as e:
206 raise error.Abort(str(e), hint='watchman encoding error')
212 raise error.Abort(str(e), hint='watchman encoding error')
207
213
208 try:
214 try:
209 encoded = decoded.encode(_fsencoding, 'strict')
215 encoded = decoded.encode(_fsencoding, 'strict')
210 except UnicodeEncodeError as e:
216 except UnicodeEncodeError as e:
211 raise error.Abort(str(e))
217 raise error.Abort(str(e))
212
218
213 return encoded
219 return encoded
214
220
215 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
221 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
216 '''Replacement for dirstate.walk, hooking into Watchman.
222 '''Replacement for dirstate.walk, hooking into Watchman.
217
223
218 Whenever full is False, ignored is False, and the Watchman client is
224 Whenever full is False, ignored is False, and the Watchman client is
219 available, use Watchman combined with saved state to possibly return only a
225 available, use Watchman combined with saved state to possibly return only a
220 subset of files.'''
226 subset of files.'''
221 def bail():
227 def bail():
222 return orig(match, subrepos, unknown, ignored, full=True)
228 return orig(match, subrepos, unknown, ignored, full=True)
223
229
224 if full or ignored or not self._watchmanclient.available():
230 if full or ignored or not self._watchmanclient.available():
225 return bail()
231 return bail()
226 state = self._fsmonitorstate
232 state = self._fsmonitorstate
227 clock, ignorehash, notefiles = state.get()
233 clock, ignorehash, notefiles = state.get()
228 if not clock:
234 if not clock:
229 if state.walk_on_invalidate:
235 if state.walk_on_invalidate:
230 return bail()
236 return bail()
231 # Initial NULL clock value, see
237 # Initial NULL clock value, see
232 # https://facebook.github.io/watchman/docs/clockspec.html
238 # https://facebook.github.io/watchman/docs/clockspec.html
233 clock = 'c:0:0'
239 clock = 'c:0:0'
234 notefiles = []
240 notefiles = []
235
241
236 def fwarn(f, msg):
242 def fwarn(f, msg):
237 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
243 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
238 return False
244 return False
239
245
240 def badtype(mode):
246 def badtype(mode):
241 kind = _('unknown')
247 kind = _('unknown')
242 if stat.S_ISCHR(mode):
248 if stat.S_ISCHR(mode):
243 kind = _('character device')
249 kind = _('character device')
244 elif stat.S_ISBLK(mode):
250 elif stat.S_ISBLK(mode):
245 kind = _('block device')
251 kind = _('block device')
246 elif stat.S_ISFIFO(mode):
252 elif stat.S_ISFIFO(mode):
247 kind = _('fifo')
253 kind = _('fifo')
248 elif stat.S_ISSOCK(mode):
254 elif stat.S_ISSOCK(mode):
249 kind = _('socket')
255 kind = _('socket')
250 elif stat.S_ISDIR(mode):
256 elif stat.S_ISDIR(mode):
251 kind = _('directory')
257 kind = _('directory')
252 return _('unsupported file type (type is %s)') % kind
258 return _('unsupported file type (type is %s)') % kind
253
259
254 ignore = self._ignore
260 ignore = self._ignore
255 dirignore = self._dirignore
261 dirignore = self._dirignore
256 if unknown:
262 if unknown:
257 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
263 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
258 # ignore list changed -- can't rely on Watchman state any more
264 # ignore list changed -- can't rely on Watchman state any more
259 if state.walk_on_invalidate:
265 if state.walk_on_invalidate:
260 return bail()
266 return bail()
261 notefiles = []
267 notefiles = []
262 clock = 'c:0:0'
268 clock = 'c:0:0'
263 else:
269 else:
264 # always ignore
270 # always ignore
265 ignore = util.always
271 ignore = util.always
266 dirignore = util.always
272 dirignore = util.always
267
273
268 matchfn = match.matchfn
274 matchfn = match.matchfn
269 matchalways = match.always()
275 matchalways = match.always()
270 dmap = self._map._map
276 dmap = self._map._map
271 nonnormalset = getattr(self, '_nonnormalset', None)
277 nonnormalset = getattr(self, '_nonnormalset', None)
272
278
273 copymap = self._map.copymap
279 copymap = self._map.copymap
274 getkind = stat.S_IFMT
280 getkind = stat.S_IFMT
275 dirkind = stat.S_IFDIR
281 dirkind = stat.S_IFDIR
276 regkind = stat.S_IFREG
282 regkind = stat.S_IFREG
277 lnkkind = stat.S_IFLNK
283 lnkkind = stat.S_IFLNK
278 join = self._join
284 join = self._join
279 normcase = util.normcase
285 normcase = util.normcase
280 fresh_instance = False
286 fresh_instance = False
281
287
282 exact = skipstep3 = False
288 exact = skipstep3 = False
283 if match.isexact(): # match.exact
289 if match.isexact(): # match.exact
284 exact = True
290 exact = True
285 dirignore = util.always # skip step 2
291 dirignore = util.always # skip step 2
286 elif match.prefix(): # match.match, no patterns
292 elif match.prefix(): # match.match, no patterns
287 skipstep3 = True
293 skipstep3 = True
288
294
289 if not exact and self._checkcase:
295 if not exact and self._checkcase:
290 # note that even though we could receive directory entries, we're only
296 # note that even though we could receive directory entries, we're only
291 # interested in checking if a file with the same name exists. So only
297 # interested in checking if a file with the same name exists. So only
292 # normalize files if possible.
298 # normalize files if possible.
293 normalize = self._normalizefile
299 normalize = self._normalizefile
294 skipstep3 = False
300 skipstep3 = False
295 else:
301 else:
296 normalize = None
302 normalize = None
297
303
298 # step 1: find all explicit files
304 # step 1: find all explicit files
299 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
305 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
300
306
301 skipstep3 = skipstep3 and not (work or dirsnotfound)
307 skipstep3 = skipstep3 and not (work or dirsnotfound)
302 work = [d for d in work if not dirignore(d[0])]
308 work = [d for d in work if not dirignore(d[0])]
303
309
304 if not work and (exact or skipstep3):
310 if not work and (exact or skipstep3):
305 for s in subrepos:
311 for s in subrepos:
306 del results[s]
312 del results[s]
307 del results['.hg']
313 del results['.hg']
308 return results
314 return results
309
315
310 # step 2: query Watchman
316 # step 2: query Watchman
311 try:
317 try:
312 # Use the user-configured timeout for the query.
318 # Use the user-configured timeout for the query.
313 # Add a little slack over the top of the user query to allow for
319 # Add a little slack over the top of the user query to allow for
314 # overheads while transferring the data
320 # overheads while transferring the data
315 self._watchmanclient.settimeout(state.timeout + 0.1)
321 self._watchmanclient.settimeout(state.timeout + 0.1)
316 result = self._watchmanclient.command('query', {
322 result = self._watchmanclient.command('query', {
317 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
323 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
318 'since': clock,
324 'since': clock,
319 'expression': [
325 'expression': [
320 'not', [
326 'not', [
321 'anyof', ['dirname', '.hg'],
327 'anyof', ['dirname', '.hg'],
322 ['name', '.hg', 'wholename']
328 ['name', '.hg', 'wholename']
323 ]
329 ]
324 ],
330 ],
325 'sync_timeout': int(state.timeout * 1000),
331 'sync_timeout': int(state.timeout * 1000),
326 'empty_on_fresh_instance': state.walk_on_invalidate,
332 'empty_on_fresh_instance': state.walk_on_invalidate,
327 })
333 })
328 except Exception as ex:
334 except Exception as ex:
329 _handleunavailable(self._ui, state, ex)
335 _handleunavailable(self._ui, state, ex)
330 self._watchmanclient.clearconnection()
336 self._watchmanclient.clearconnection()
331 return bail()
337 return bail()
332 else:
338 else:
333 # We need to propagate the last observed clock up so that we
339 # We need to propagate the last observed clock up so that we
334 # can use it for our next query
340 # can use it for our next query
335 state.setlastclock(result['clock'])
341 state.setlastclock(result['clock'])
336 if result['is_fresh_instance']:
342 if result['is_fresh_instance']:
337 if state.walk_on_invalidate:
343 if state.walk_on_invalidate:
338 state.invalidate()
344 state.invalidate()
339 return bail()
345 return bail()
340 fresh_instance = True
346 fresh_instance = True
341 # Ignore any prior noteable files from the state info
347 # Ignore any prior noteable files from the state info
342 notefiles = []
348 notefiles = []
343
349
344 # for file paths which require normalization and we encounter a case
350 # for file paths which require normalization and we encounter a case
345 # collision, we store our own foldmap
351 # collision, we store our own foldmap
346 if normalize:
352 if normalize:
347 foldmap = dict((normcase(k), k) for k in results)
353 foldmap = dict((normcase(k), k) for k in results)
348
354
349 switch_slashes = pycompat.ossep == '\\'
355 switch_slashes = pycompat.ossep == '\\'
350 # The order of the results is, strictly speaking, undefined.
356 # The order of the results is, strictly speaking, undefined.
351 # For case changes on a case insensitive filesystem we may receive
357 # For case changes on a case insensitive filesystem we may receive
352 # two entries, one with exists=True and another with exists=False.
358 # two entries, one with exists=True and another with exists=False.
353 # The exists=True entries in the same response should be interpreted
359 # The exists=True entries in the same response should be interpreted
354 # as being happens-after the exists=False entries due to the way that
360 # as being happens-after the exists=False entries due to the way that
355 # Watchman tracks files. We use this property to reconcile deletes
361 # Watchman tracks files. We use this property to reconcile deletes
356 # for name case changes.
362 # for name case changes.
357 for entry in result['files']:
363 for entry in result['files']:
358 fname = entry['name']
364 fname = entry['name']
359 if _fixencoding:
365 if _fixencoding:
360 fname = _watchmantofsencoding(fname)
366 fname = _watchmantofsencoding(fname)
361 if switch_slashes:
367 if switch_slashes:
362 fname = fname.replace('\\', '/')
368 fname = fname.replace('\\', '/')
363 if normalize:
369 if normalize:
364 normed = normcase(fname)
370 normed = normcase(fname)
365 fname = normalize(fname, True, True)
371 fname = normalize(fname, True, True)
366 foldmap[normed] = fname
372 foldmap[normed] = fname
367 fmode = entry['mode']
373 fmode = entry['mode']
368 fexists = entry['exists']
374 fexists = entry['exists']
369 kind = getkind(fmode)
375 kind = getkind(fmode)
370
376
371 if not fexists:
377 if not fexists:
372 # if marked as deleted and we don't already have a change
378 # if marked as deleted and we don't already have a change
373 # record, mark it as deleted. If we already have an entry
379 # record, mark it as deleted. If we already have an entry
374 # for fname then it was either part of walkexplicit or was
380 # for fname then it was either part of walkexplicit or was
375 # an earlier result that was a case change
381 # an earlier result that was a case change
376 if fname not in results and fname in dmap and (
382 if fname not in results and fname in dmap and (
377 matchalways or matchfn(fname)):
383 matchalways or matchfn(fname)):
378 results[fname] = None
384 results[fname] = None
379 elif kind == dirkind:
385 elif kind == dirkind:
380 if fname in dmap and (matchalways or matchfn(fname)):
386 if fname in dmap and (matchalways or matchfn(fname)):
381 results[fname] = None
387 results[fname] = None
382 elif kind == regkind or kind == lnkkind:
388 elif kind == regkind or kind == lnkkind:
383 if fname in dmap:
389 if fname in dmap:
384 if matchalways or matchfn(fname):
390 if matchalways or matchfn(fname):
385 results[fname] = entry
391 results[fname] = entry
386 elif (matchalways or matchfn(fname)) and not ignore(fname):
392 elif (matchalways or matchfn(fname)) and not ignore(fname):
387 results[fname] = entry
393 results[fname] = entry
388 elif fname in dmap and (matchalways or matchfn(fname)):
394 elif fname in dmap and (matchalways or matchfn(fname)):
389 results[fname] = None
395 results[fname] = None
390
396
391 # step 3: query notable files we don't already know about
397 # step 3: query notable files we don't already know about
392 # XXX try not to iterate over the entire dmap
398 # XXX try not to iterate over the entire dmap
393 if normalize:
399 if normalize:
394 # any notable files that have changed case will already be handled
400 # any notable files that have changed case will already be handled
395 # above, so just check membership in the foldmap
401 # above, so just check membership in the foldmap
396 notefiles = set((normalize(f, True, True) for f in notefiles
402 notefiles = set((normalize(f, True, True) for f in notefiles
397 if normcase(f) not in foldmap))
403 if normcase(f) not in foldmap))
398 visit = set((f for f in notefiles if (f not in results and matchfn(f)
404 visit = set((f for f in notefiles if (f not in results and matchfn(f)
399 and (f in dmap or not ignore(f)))))
405 and (f in dmap or not ignore(f)))))
400
406
401 if nonnormalset is not None and not fresh_instance:
407 if nonnormalset is not None and not fresh_instance:
402 if matchalways:
408 if matchalways:
403 visit.update(f for f in nonnormalset if f not in results)
409 visit.update(f for f in nonnormalset if f not in results)
404 visit.update(f for f in copymap if f not in results)
410 visit.update(f for f in copymap if f not in results)
405 else:
411 else:
406 visit.update(f for f in nonnormalset
412 visit.update(f for f in nonnormalset
407 if f not in results and matchfn(f))
413 if f not in results and matchfn(f))
408 visit.update(f for f in copymap
414 visit.update(f for f in copymap
409 if f not in results and matchfn(f))
415 if f not in results and matchfn(f))
410 else:
416 else:
411 if matchalways:
417 if matchalways:
412 visit.update(f for f, st in dmap.iteritems()
418 visit.update(f for f, st in dmap.iteritems()
413 if (f not in results and
419 if (f not in results and
414 (st[2] < 0 or st[0] != 'n' or fresh_instance)))
420 (st[2] < 0 or st[0] != 'n' or fresh_instance)))
415 visit.update(f for f in copymap if f not in results)
421 visit.update(f for f in copymap if f not in results)
416 else:
422 else:
417 visit.update(f for f, st in dmap.iteritems()
423 visit.update(f for f, st in dmap.iteritems()
418 if (f not in results and
424 if (f not in results and
419 (st[2] < 0 or st[0] != 'n' or fresh_instance)
425 (st[2] < 0 or st[0] != 'n' or fresh_instance)
420 and matchfn(f)))
426 and matchfn(f)))
421 visit.update(f for f in copymap
427 visit.update(f for f in copymap
422 if f not in results and matchfn(f))
428 if f not in results and matchfn(f))
423
429
424 audit = pathutil.pathauditor(self._root, cached=True).check
430 audit = pathutil.pathauditor(self._root, cached=True).check
425 auditpass = [f for f in visit if audit(f)]
431 auditpass = [f for f in visit if audit(f)]
426 auditpass.sort()
432 auditpass.sort()
427 auditfail = visit.difference(auditpass)
433 auditfail = visit.difference(auditpass)
428 for f in auditfail:
434 for f in auditfail:
429 results[f] = None
435 results[f] = None
430
436
431 nf = iter(auditpass).next
437 nf = iter(auditpass).next
432 for st in util.statfiles([join(f) for f in auditpass]):
438 for st in util.statfiles([join(f) for f in auditpass]):
433 f = nf()
439 f = nf()
434 if st or f in dmap:
440 if st or f in dmap:
435 results[f] = st
441 results[f] = st
436
442
437 for s in subrepos:
443 for s in subrepos:
438 del results[s]
444 del results[s]
439 del results['.hg']
445 del results['.hg']
440 return results
446 return results
441
447
442 def overridestatus(
448 def overridestatus(
443 orig, self, node1='.', node2=None, match=None, ignored=False,
449 orig, self, node1='.', node2=None, match=None, ignored=False,
444 clean=False, unknown=False, listsubrepos=False):
450 clean=False, unknown=False, listsubrepos=False):
445 listignored = ignored
451 listignored = ignored
446 listclean = clean
452 listclean = clean
447 listunknown = unknown
453 listunknown = unknown
448
454
449 def _cmpsets(l1, l2):
455 def _cmpsets(l1, l2):
450 try:
456 try:
451 if 'FSMONITOR_LOG_FILE' in encoding.environ:
457 if 'FSMONITOR_LOG_FILE' in encoding.environ:
452 fn = encoding.environ['FSMONITOR_LOG_FILE']
458 fn = encoding.environ['FSMONITOR_LOG_FILE']
453 f = open(fn, 'wb')
459 f = open(fn, 'wb')
454 else:
460 else:
455 fn = 'fsmonitorfail.log'
461 fn = 'fsmonitorfail.log'
456 f = self.opener(fn, 'wb')
462 f = self.opener(fn, 'wb')
457 except (IOError, OSError):
463 except (IOError, OSError):
458 self.ui.warn(_('warning: unable to write to %s\n') % fn)
464 self.ui.warn(_('warning: unable to write to %s\n') % fn)
459 return
465 return
460
466
461 try:
467 try:
462 for i, (s1, s2) in enumerate(zip(l1, l2)):
468 for i, (s1, s2) in enumerate(zip(l1, l2)):
463 if set(s1) != set(s2):
469 if set(s1) != set(s2):
464 f.write('sets at position %d are unequal\n' % i)
470 f.write('sets at position %d are unequal\n' % i)
465 f.write('watchman returned: %s\n' % s1)
471 f.write('watchman returned: %s\n' % s1)
466 f.write('stat returned: %s\n' % s2)
472 f.write('stat returned: %s\n' % s2)
467 finally:
473 finally:
468 f.close()
474 f.close()
469
475
470 if isinstance(node1, context.changectx):
476 if isinstance(node1, context.changectx):
471 ctx1 = node1
477 ctx1 = node1
472 else:
478 else:
473 ctx1 = self[node1]
479 ctx1 = self[node1]
474 if isinstance(node2, context.changectx):
480 if isinstance(node2, context.changectx):
475 ctx2 = node2
481 ctx2 = node2
476 else:
482 else:
477 ctx2 = self[node2]
483 ctx2 = self[node2]
478
484
479 working = ctx2.rev() is None
485 working = ctx2.rev() is None
480 parentworking = working and ctx1 == self['.']
486 parentworking = working and ctx1 == self['.']
481 match = match or matchmod.always(self.root, self.getcwd())
487 match = match or matchmod.always(self.root, self.getcwd())
482
488
483 # Maybe we can use this opportunity to update Watchman's state.
489 # Maybe we can use this opportunity to update Watchman's state.
484 # Mercurial uses workingcommitctx and/or memctx to represent the part of
490 # Mercurial uses workingcommitctx and/or memctx to represent the part of
485 # the workingctx that is to be committed. So don't update the state in
491 # the workingctx that is to be committed. So don't update the state in
486 # that case.
492 # that case.
487 # HG_PENDING is set in the environment when the dirstate is being updated
493 # HG_PENDING is set in the environment when the dirstate is being updated
488 # in the middle of a transaction; we must not update our state in that
494 # in the middle of a transaction; we must not update our state in that
489 # case, or we risk forgetting about changes in the working copy.
495 # case, or we risk forgetting about changes in the working copy.
490 updatestate = (parentworking and match.always() and
496 updatestate = (parentworking and match.always() and
491 not isinstance(ctx2, (context.workingcommitctx,
497 not isinstance(ctx2, (context.workingcommitctx,
492 context.memctx)) and
498 context.memctx)) and
493 'HG_PENDING' not in encoding.environ)
499 'HG_PENDING' not in encoding.environ)
494
500
495 try:
501 try:
496 if self._fsmonitorstate.walk_on_invalidate:
502 if self._fsmonitorstate.walk_on_invalidate:
497 # Use a short timeout to query the current clock. If that
503 # Use a short timeout to query the current clock. If that
498 # takes too long then we assume that the service will be slow
504 # takes too long then we assume that the service will be slow
499 # to answer our query.
505 # to answer our query.
500 # walk_on_invalidate indicates that we prefer to walk the
506 # walk_on_invalidate indicates that we prefer to walk the
501 # tree ourselves because we can ignore portions that Watchman
507 # tree ourselves because we can ignore portions that Watchman
502 # cannot and we tend to be faster in the warmer buffer cache
508 # cannot and we tend to be faster in the warmer buffer cache
503 # cases.
509 # cases.
504 self._watchmanclient.settimeout(0.1)
510 self._watchmanclient.settimeout(0.1)
505 else:
511 else:
506 # Give Watchman more time to potentially complete its walk
512 # Give Watchman more time to potentially complete its walk
507 # and return the initial clock. In this mode we assume that
513 # and return the initial clock. In this mode we assume that
508 # the filesystem will be slower than parsing a potentially
514 # the filesystem will be slower than parsing a potentially
509 # very large Watchman result set.
515 # very large Watchman result set.
510 self._watchmanclient.settimeout(
516 self._watchmanclient.settimeout(
511 self._fsmonitorstate.timeout + 0.1)
517 self._fsmonitorstate.timeout + 0.1)
512 startclock = self._watchmanclient.getcurrentclock()
518 startclock = self._watchmanclient.getcurrentclock()
513 except Exception as ex:
519 except Exception as ex:
514 self._watchmanclient.clearconnection()
520 self._watchmanclient.clearconnection()
515 _handleunavailable(self.ui, self._fsmonitorstate, ex)
521 _handleunavailable(self.ui, self._fsmonitorstate, ex)
516 # boo, Watchman failed. bail
522 # boo, Watchman failed. bail
517 return orig(node1, node2, match, listignored, listclean,
523 return orig(node1, node2, match, listignored, listclean,
518 listunknown, listsubrepos)
524 listunknown, listsubrepos)
519
525
520 if updatestate:
526 if updatestate:
521 # We need info about unknown files. This may make things slower the
527 # We need info about unknown files. This may make things slower the
522 # first time, but whatever.
528 # first time, but whatever.
523 stateunknown = True
529 stateunknown = True
524 else:
530 else:
525 stateunknown = listunknown
531 stateunknown = listunknown
526
532
527 if updatestate:
533 if updatestate:
528 ps = poststatus(startclock)
534 ps = poststatus(startclock)
529 self.addpostdsstatus(ps)
535 self.addpostdsstatus(ps)
530
536
531 r = orig(node1, node2, match, listignored, listclean, stateunknown,
537 r = orig(node1, node2, match, listignored, listclean, stateunknown,
532 listsubrepos)
538 listsubrepos)
533 modified, added, removed, deleted, unknown, ignored, clean = r
539 modified, added, removed, deleted, unknown, ignored, clean = r
534
540
535 if not listunknown:
541 if not listunknown:
536 unknown = []
542 unknown = []
537
543
538 # don't do paranoid checks if we're not going to query Watchman anyway
544 # don't do paranoid checks if we're not going to query Watchman anyway
539 full = listclean or match.traversedir is not None
545 full = listclean or match.traversedir is not None
540 if self._fsmonitorstate.mode == 'paranoid' and not full:
546 if self._fsmonitorstate.mode == 'paranoid' and not full:
541 # run status again and fall back to the old walk this time
547 # run status again and fall back to the old walk this time
542 self.dirstate._fsmonitordisable = True
548 self.dirstate._fsmonitordisable = True
543
549
544 # shut the UI up
550 # shut the UI up
545 quiet = self.ui.quiet
551 quiet = self.ui.quiet
546 self.ui.quiet = True
552 self.ui.quiet = True
547 fout, ferr = self.ui.fout, self.ui.ferr
553 fout, ferr = self.ui.fout, self.ui.ferr
548 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
554 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
549
555
550 try:
556 try:
551 rv2 = orig(
557 rv2 = orig(
552 node1, node2, match, listignored, listclean, listunknown,
558 node1, node2, match, listignored, listclean, listunknown,
553 listsubrepos)
559 listsubrepos)
554 finally:
560 finally:
555 self.dirstate._fsmonitordisable = False
561 self.dirstate._fsmonitordisable = False
556 self.ui.quiet = quiet
562 self.ui.quiet = quiet
557 self.ui.fout, self.ui.ferr = fout, ferr
563 self.ui.fout, self.ui.ferr = fout, ferr
558
564
559 # clean isn't tested since it's set to True above
565 # clean isn't tested since it's set to True above
560 _cmpsets([modified, added, removed, deleted, unknown, ignored, clean],
566 _cmpsets([modified, added, removed, deleted, unknown, ignored, clean],
561 rv2)
567 rv2)
562 modified, added, removed, deleted, unknown, ignored, clean = rv2
568 modified, added, removed, deleted, unknown, ignored, clean = rv2
563
569
564 return scmutil.status(
570 return scmutil.status(
565 modified, added, removed, deleted, unknown, ignored, clean)
571 modified, added, removed, deleted, unknown, ignored, clean)
566
572
567 class poststatus(object):
573 class poststatus(object):
568 def __init__(self, startclock):
574 def __init__(self, startclock):
569 self._startclock = startclock
575 self._startclock = startclock
570
576
571 def __call__(self, wctx, status):
577 def __call__(self, wctx, status):
572 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
578 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
573 hashignore = _hashignore(wctx.repo().dirstate._ignore)
579 hashignore = _hashignore(wctx.repo().dirstate._ignore)
574 notefiles = (status.modified + status.added + status.removed +
580 notefiles = (status.modified + status.added + status.removed +
575 status.deleted + status.unknown)
581 status.deleted + status.unknown)
576 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
582 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
577
583
578 def makedirstate(repo, dirstate):
584 def makedirstate(repo, dirstate):
579 class fsmonitordirstate(dirstate.__class__):
585 class fsmonitordirstate(dirstate.__class__):
580 def _fsmonitorinit(self, repo):
586 def _fsmonitorinit(self, repo):
581 # _fsmonitordisable is used in paranoid mode
587 # _fsmonitordisable is used in paranoid mode
582 self._fsmonitordisable = False
588 self._fsmonitordisable = False
583 self._fsmonitorstate = repo._fsmonitorstate
589 self._fsmonitorstate = repo._fsmonitorstate
584 self._watchmanclient = repo._watchmanclient
590 self._watchmanclient = repo._watchmanclient
585 self._repo = weakref.proxy(repo)
591 self._repo = weakref.proxy(repo)
586
592
587 def walk(self, *args, **kwargs):
593 def walk(self, *args, **kwargs):
588 orig = super(fsmonitordirstate, self).walk
594 orig = super(fsmonitordirstate, self).walk
589 if self._fsmonitordisable:
595 if self._fsmonitordisable:
590 return orig(*args, **kwargs)
596 return orig(*args, **kwargs)
591 return overridewalk(orig, self, *args, **kwargs)
597 return overridewalk(orig, self, *args, **kwargs)
592
598
593 def rebuild(self, *args, **kwargs):
599 def rebuild(self, *args, **kwargs):
594 self._fsmonitorstate.invalidate()
600 self._fsmonitorstate.invalidate()
595 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
601 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
596
602
597 def invalidate(self, *args, **kwargs):
603 def invalidate(self, *args, **kwargs):
598 self._fsmonitorstate.invalidate()
604 self._fsmonitorstate.invalidate()
599 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
605 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
600
606
601 if dirstate._ui.configbool(
607 if dirstate._ui.configbool(
602 "experimental", "fsmonitor.wc_change_notify"):
608 "experimental", "fsmonitor.wc_change_notify"):
603 def setparents(self, p1, p2=nullid):
609 def setparents(self, p1, p2=nullid):
604 with state_update(self._repo, name="hg.wc_change",
610 with state_update(self._repo, name="hg.wc_change",
605 oldnode=self._pl[0], newnode=p1,
611 oldnode=self._pl[0], newnode=p1,
606 partial=False):
612 partial=False):
607 return super(fsmonitordirstate, self).setparents(p1, p2)
613 return super(fsmonitordirstate, self).setparents(p1, p2)
608
614
609 dirstate.__class__ = fsmonitordirstate
615 dirstate.__class__ = fsmonitordirstate
610 dirstate._fsmonitorinit(repo)
616 dirstate._fsmonitorinit(repo)
611
617
612 def wrapdirstate(orig, self):
618 def wrapdirstate(orig, self):
613 ds = orig(self)
619 ds = orig(self)
614 # only override the dirstate when Watchman is available for the repo
620 # only override the dirstate when Watchman is available for the repo
615 if util.safehasattr(self, '_fsmonitorstate'):
621 if util.safehasattr(self, '_fsmonitorstate'):
616 makedirstate(self, ds)
622 makedirstate(self, ds)
617 return ds
623 return ds
618
624
619 def extsetup(ui):
625 def extsetup(ui):
620 extensions.wrapfilecache(
626 extensions.wrapfilecache(
621 localrepo.localrepository, 'dirstate', wrapdirstate)
627 localrepo.localrepository, 'dirstate', wrapdirstate)
622 if pycompat.isdarwin:
628 if pycompat.isdarwin:
623 # An assist for avoiding the dangling-symlink fsevents bug
629 # An assist for avoiding the dangling-symlink fsevents bug
624 extensions.wrapfunction(os, 'symlink', wrapsymlink)
630 extensions.wrapfunction(os, 'symlink', wrapsymlink)
625
631
626 extensions.wrapfunction(merge, 'update', wrapupdate)
632 extensions.wrapfunction(merge, 'update', wrapupdate)
627
633
628 def wrapsymlink(orig, source, link_name):
634 def wrapsymlink(orig, source, link_name):
629 ''' if we create a dangling symlink, also touch the parent dir
635 ''' if we create a dangling symlink, also touch the parent dir
630 to encourage fsevents notifications to work more correctly '''
636 to encourage fsevents notifications to work more correctly '''
631 try:
637 try:
632 return orig(source, link_name)
638 return orig(source, link_name)
633 finally:
639 finally:
634 try:
640 try:
635 os.utime(os.path.dirname(link_name), None)
641 os.utime(os.path.dirname(link_name), None)
636 except OSError:
642 except OSError:
637 pass
643 pass
638
644
639 class state_update(object):
645 class state_update(object):
640 ''' This context manager is responsible for dispatching the state-enter
646 ''' This context manager is responsible for dispatching the state-enter
641 and state-leave signals to the watchman service. The enter and leave
647 and state-leave signals to the watchman service. The enter and leave
642 methods can be invoked manually (for scenarios where context manager
648 methods can be invoked manually (for scenarios where context manager
643 semantics are not possible). If parameters oldnode and newnode are None,
649 semantics are not possible). If parameters oldnode and newnode are None,
644 they will be populated based on current working copy in enter and
650 they will be populated based on current working copy in enter and
645 leave, respectively. Similarly, if the distance is none, it will be
651 leave, respectively. Similarly, if the distance is none, it will be
646 calculated based on the oldnode and newnode in the leave method.'''
652 calculated based on the oldnode and newnode in the leave method.'''
647
653
648 def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
654 def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
649 partial=False):
655 partial=False):
650 self.repo = repo.unfiltered()
656 self.repo = repo.unfiltered()
651 self.name = name
657 self.name = name
652 self.oldnode = oldnode
658 self.oldnode = oldnode
653 self.newnode = newnode
659 self.newnode = newnode
654 self.distance = distance
660 self.distance = distance
655 self.partial = partial
661 self.partial = partial
656 self._lock = None
662 self._lock = None
657 self.need_leave = False
663 self.need_leave = False
658
664
659 def __enter__(self):
665 def __enter__(self):
660 self.enter()
666 self.enter()
661
667
662 def enter(self):
668 def enter(self):
663 # We explicitly need to take a lock here, before we proceed to update
669 # We explicitly need to take a lock here, before we proceed to update
664 # watchman about the update operation, so that we don't race with
670 # watchman about the update operation, so that we don't race with
665 # some other actor. merge.update is going to take the wlock almost
671 # some other actor. merge.update is going to take the wlock almost
666 # immediately anyway, so this is effectively extending the lock
672 # immediately anyway, so this is effectively extending the lock
667 # around a couple of short sanity checks.
673 # around a couple of short sanity checks.
668 if self.oldnode is None:
674 if self.oldnode is None:
669 self.oldnode = self.repo['.'].node()
675 self.oldnode = self.repo['.'].node()
670 self._lock = self.repo.wlock()
676 self._lock = self.repo.wlock()
671 self.need_leave = self._state(
677 self.need_leave = self._state(
672 'state-enter',
678 'state-enter',
673 hex(self.oldnode))
679 hex(self.oldnode))
674 return self
680 return self
675
681
676 def __exit__(self, type_, value, tb):
682 def __exit__(self, type_, value, tb):
677 abort = True if type_ else False
683 abort = True if type_ else False
678 self.exit(abort=abort)
684 self.exit(abort=abort)
679
685
680 def exit(self, abort=False):
686 def exit(self, abort=False):
681 try:
687 try:
682 if self.need_leave:
688 if self.need_leave:
683 status = 'failed' if abort else 'ok'
689 status = 'failed' if abort else 'ok'
684 if self.newnode is None:
690 if self.newnode is None:
685 self.newnode = self.repo['.'].node()
691 self.newnode = self.repo['.'].node()
686 if self.distance is None:
692 if self.distance is None:
687 self.distance = calcdistance(
693 self.distance = calcdistance(
688 self.repo, self.oldnode, self.newnode)
694 self.repo, self.oldnode, self.newnode)
689 self._state(
695 self._state(
690 'state-leave',
696 'state-leave',
691 hex(self.newnode),
697 hex(self.newnode),
692 status=status)
698 status=status)
693 finally:
699 finally:
694 self.need_leave = False
700 self.need_leave = False
695 if self._lock:
701 if self._lock:
696 self._lock.release()
702 self._lock.release()
697
703
698 def _state(self, cmd, commithash, status='ok'):
704 def _state(self, cmd, commithash, status='ok'):
699 if not util.safehasattr(self.repo, '_watchmanclient'):
705 if not util.safehasattr(self.repo, '_watchmanclient'):
700 return False
706 return False
701 try:
707 try:
702 self.repo._watchmanclient.command(cmd, {
708 self.repo._watchmanclient.command(cmd, {
703 'name': self.name,
709 'name': self.name,
704 'metadata': {
710 'metadata': {
705 # the target revision
711 # the target revision
706 'rev': commithash,
712 'rev': commithash,
707 # approximate number of commits between current and target
713 # approximate number of commits between current and target
708 'distance': self.distance if self.distance else 0,
714 'distance': self.distance if self.distance else 0,
709 # success/failure (only really meaningful for state-leave)
715 # success/failure (only really meaningful for state-leave)
710 'status': status,
716 'status': status,
711 # whether the working copy parent is changing
717 # whether the working copy parent is changing
712 'partial': self.partial,
718 'partial': self.partial,
713 }})
719 }})
714 return True
720 return True
715 except Exception as e:
721 except Exception as e:
716 # Swallow any errors; fire and forget
722 # Swallow any errors; fire and forget
717 self.repo.ui.log(
723 self.repo.ui.log(
718 'watchman', 'Exception %s while running %s\n', e, cmd)
724 'watchman', 'Exception %s while running %s\n', e, cmd)
719 return False
725 return False
720
726
721 # Estimate the distance between two nodes
727 # Estimate the distance between two nodes
722 def calcdistance(repo, oldnode, newnode):
728 def calcdistance(repo, oldnode, newnode):
723 anc = repo.changelog.ancestor(oldnode, newnode)
729 anc = repo.changelog.ancestor(oldnode, newnode)
724 ancrev = repo[anc].rev()
730 ancrev = repo[anc].rev()
725 distance = (abs(repo[oldnode].rev() - ancrev)
731 distance = (abs(repo[oldnode].rev() - ancrev)
726 + abs(repo[newnode].rev() - ancrev))
732 + abs(repo[newnode].rev() - ancrev))
727 return distance
733 return distance
728
734
729 # Bracket working copy updates with calls to the watchman state-enter
735 # Bracket working copy updates with calls to the watchman state-enter
730 # and state-leave commands. This allows clients to perform more intelligent
736 # and state-leave commands. This allows clients to perform more intelligent
731 # settling during bulk file change scenarios
737 # settling during bulk file change scenarios
732 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
738 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
733 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
739 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
734 mergeancestor=False, labels=None, matcher=None, **kwargs):
740 mergeancestor=False, labels=None, matcher=None, **kwargs):
735
741
736 distance = 0
742 distance = 0
737 partial = True
743 partial = True
738 oldnode = repo['.'].node()
744 oldnode = repo['.'].node()
739 newnode = repo[node].node()
745 newnode = repo[node].node()
740 if matcher is None or matcher.always():
746 if matcher is None or matcher.always():
741 partial = False
747 partial = False
742 distance = calcdistance(repo.unfiltered(), oldnode, newnode)
748 distance = calcdistance(repo.unfiltered(), oldnode, newnode)
743
749
744 with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
750 with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
745 distance=distance, partial=partial):
751 distance=distance, partial=partial):
746 return orig(
752 return orig(
747 repo, node, branchmerge, force, ancestor, mergeancestor,
753 repo, node, branchmerge, force, ancestor, mergeancestor,
748 labels, matcher, **kwargs)
754 labels, matcher, **kwargs)
749
755
750 def reposetup(ui, repo):
756 def reposetup(ui, repo):
751 # We don't work with largefiles or inotify
757 # We don't work with largefiles or inotify
752 exts = extensions.enabled()
758 exts = extensions.enabled()
753 for ext in _blacklist:
759 for ext in _blacklist:
754 if ext in exts:
760 if ext in exts:
755 ui.warn(_('The fsmonitor extension is incompatible with the %s '
761 ui.warn(_('The fsmonitor extension is incompatible with the %s '
756 'extension and has been disabled.\n') % ext)
762 'extension and has been disabled.\n') % ext)
757 return
763 return
758
764
759 if repo.local():
765 if repo.local():
760 # We don't work with subrepos either.
766 # We don't work with subrepos either.
761 #
767 #
762 # if repo[None].substate can cause a dirstate parse, which is too
768 # if repo[None].substate can cause a dirstate parse, which is too
763 # slow. Instead, look for a file called hgsubstate,
769 # slow. Instead, look for a file called hgsubstate,
764 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
770 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
765 return
771 return
766
772
767 fsmonitorstate = state.state(repo)
773 fsmonitorstate = state.state(repo)
768 if fsmonitorstate.mode == 'off':
774 if fsmonitorstate.mode == 'off':
769 return
775 return
770
776
771 try:
777 try:
772 client = watchmanclient.client(repo)
778 client = watchmanclient.client(repo)
773 except Exception as ex:
779 except Exception as ex:
774 _handleunavailable(ui, fsmonitorstate, ex)
780 _handleunavailable(ui, fsmonitorstate, ex)
775 return
781 return
776
782
777 repo._fsmonitorstate = fsmonitorstate
783 repo._fsmonitorstate = fsmonitorstate
778 repo._watchmanclient = client
784 repo._watchmanclient = client
779
785
780 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
786 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
781 if cached:
787 if cached:
782 # at this point since fsmonitorstate wasn't present,
788 # at this point since fsmonitorstate wasn't present,
783 # repo.dirstate is not a fsmonitordirstate
789 # repo.dirstate is not a fsmonitordirstate
784 makedirstate(repo, dirstate)
790 makedirstate(repo, dirstate)
785
791
786 class fsmonitorrepo(repo.__class__):
792 class fsmonitorrepo(repo.__class__):
787 def status(self, *args, **kwargs):
793 def status(self, *args, **kwargs):
788 orig = super(fsmonitorrepo, self).status
794 orig = super(fsmonitorrepo, self).status
789 return overridestatus(orig, self, *args, **kwargs)
795 return overridestatus(orig, self, *args, **kwargs)
790
796
791 if ui.configbool("experimental", "fsmonitor.transaction_notify"):
797 if ui.configbool("experimental", "fsmonitor.transaction_notify"):
792 def transaction(self, *args, **kwargs):
798 def transaction(self, *args, **kwargs):
793 tr = super(fsmonitorrepo, self).transaction(
799 tr = super(fsmonitorrepo, self).transaction(
794 *args, **kwargs)
800 *args, **kwargs)
795 if tr.count != 1:
801 if tr.count != 1:
796 return tr
802 return tr
797 stateupdate = state_update(self, name="hg.transaction")
803 stateupdate = state_update(self, name="hg.transaction")
798 stateupdate.enter()
804 stateupdate.enter()
799
805
800 class fsmonitortrans(tr.__class__):
806 class fsmonitortrans(tr.__class__):
801 def _abort(self):
807 def _abort(self):
802 try:
808 try:
803 result = super(fsmonitortrans, self)._abort()
809 result = super(fsmonitortrans, self)._abort()
804 finally:
810 finally:
805 stateupdate.exit(abort=True)
811 stateupdate.exit(abort=True)
806 return result
812 return result
807
813
808 def close(self):
814 def close(self):
809 try:
815 try:
810 result = super(fsmonitortrans, self).close()
816 result = super(fsmonitortrans, self).close()
811 finally:
817 finally:
812 if self.count == 0:
818 if self.count == 0:
813 stateupdate.exit()
819 stateupdate.exit()
814 return result
820 return result
815
821
816 tr.__class__ = fsmonitortrans
822 tr.__class__ = fsmonitortrans
817 return tr
823 return tr
818
824
819 repo.__class__ = fsmonitorrepo
825 repo.__class__ = fsmonitorrepo
General Comments 0
You need to be logged in to leave comments. Login now