##// END OF EJS Templates
fsmonitor: issue debug messages when we fall back to core status...
Boris Feld -
r35146:0b5716ec default
parent child Browse files
Show More
@@ -1,825 +1,830 b''
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',
165 configitem('experimental', 'fsmonitor.transaction_notify',
166 default=False,
166 default=False,
167 )
167 )
168 configitem('experimental', 'fsmonitor.wc_change_notify',
168 configitem('experimental', 'fsmonitor.wc_change_notify',
169 default=False,
169 default=False,
170 )
170 )
171
171
172 # This extension is incompatible with the following blacklisted extensions
172 # This extension is incompatible with the following blacklisted extensions
173 # and will disable itself when encountering one of these:
173 # and will disable itself when encountering one of these:
174 _blacklist = ['largefiles', 'eol']
174 _blacklist = ['largefiles', 'eol']
175
175
176 def _handleunavailable(ui, state, ex):
176 def _handleunavailable(ui, state, ex):
177 """Exception handler for Watchman interaction exceptions"""
177 """Exception handler for Watchman interaction exceptions"""
178 if isinstance(ex, watchmanclient.Unavailable):
178 if isinstance(ex, watchmanclient.Unavailable):
179 if ex.warn:
179 if ex.warn:
180 ui.warn(str(ex) + '\n')
180 ui.warn(str(ex) + '\n')
181 if ex.invalidate:
181 if ex.invalidate:
182 state.invalidate()
182 state.invalidate()
183 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
183 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
184 else:
184 else:
185 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
185 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
186
186
187 def _hashignore(ignore):
187 def _hashignore(ignore):
188 """Calculate hash for ignore patterns and filenames
188 """Calculate hash for ignore patterns and filenames
189
189
190 If this information changes between Mercurial invocations, we can't
190 If this information changes between Mercurial invocations, we can't
191 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
192 copy.
192 copy.
193
193
194 """
194 """
195 sha1 = hashlib.sha1()
195 sha1 = hashlib.sha1()
196 sha1.update(repr(ignore))
196 sha1.update(repr(ignore))
197 return sha1.hexdigest()
197 return sha1.hexdigest()
198
198
199 _watchmanencoding = pywatchman.encoding.get_local_encoding()
199 _watchmanencoding = pywatchman.encoding.get_local_encoding()
200 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
200 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
201 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
201 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
202
202
203 def _watchmantofsencoding(path):
203 def _watchmantofsencoding(path):
204 """Fix path to match watchman and local filesystem encoding
204 """Fix path to match watchman and local filesystem encoding
205
205
206 watchman's paths encoding can differ from filesystem encoding. For example,
206 watchman's paths encoding can differ from filesystem encoding. For example,
207 on Windows, it's always utf-8.
207 on Windows, it's always utf-8.
208 """
208 """
209 try:
209 try:
210 decoded = path.decode(_watchmanencoding)
210 decoded = path.decode(_watchmanencoding)
211 except UnicodeDecodeError as e:
211 except UnicodeDecodeError as e:
212 raise error.Abort(str(e), hint='watchman encoding error')
212 raise error.Abort(str(e), hint='watchman encoding error')
213
213
214 try:
214 try:
215 encoded = decoded.encode(_fsencoding, 'strict')
215 encoded = decoded.encode(_fsencoding, 'strict')
216 except UnicodeEncodeError as e:
216 except UnicodeEncodeError as e:
217 raise error.Abort(str(e))
217 raise error.Abort(str(e))
218
218
219 return encoded
219 return encoded
220
220
221 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
221 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
222 '''Replacement for dirstate.walk, hooking into Watchman.
222 '''Replacement for dirstate.walk, hooking into Watchman.
223
223
224 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
225 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
226 subset of files.'''
226 subset of files.'''
227 def bail():
227 def bail(reason):
228 self._ui.debug('fsmonitor: fallback to core status, %s\n' % reason)
228 return orig(match, subrepos, unknown, ignored, full=True)
229 return orig(match, subrepos, unknown, ignored, full=True)
229
230
230 if full or ignored or not self._watchmanclient.available():
231 if full:
231 return bail()
232 return bail('full rewalk requested')
233 if ignored:
234 return bail('listing ignored files')
235 if not self._watchmanclient.available():
236 return bail('client unavailable')
232 state = self._fsmonitorstate
237 state = self._fsmonitorstate
233 clock, ignorehash, notefiles = state.get()
238 clock, ignorehash, notefiles = state.get()
234 if not clock:
239 if not clock:
235 if state.walk_on_invalidate:
240 if state.walk_on_invalidate:
236 return bail()
241 return bail('no clock')
237 # Initial NULL clock value, see
242 # Initial NULL clock value, see
238 # https://facebook.github.io/watchman/docs/clockspec.html
243 # https://facebook.github.io/watchman/docs/clockspec.html
239 clock = 'c:0:0'
244 clock = 'c:0:0'
240 notefiles = []
245 notefiles = []
241
246
242 def fwarn(f, msg):
247 def fwarn(f, msg):
243 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
248 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
244 return False
249 return False
245
250
246 def badtype(mode):
251 def badtype(mode):
247 kind = _('unknown')
252 kind = _('unknown')
248 if stat.S_ISCHR(mode):
253 if stat.S_ISCHR(mode):
249 kind = _('character device')
254 kind = _('character device')
250 elif stat.S_ISBLK(mode):
255 elif stat.S_ISBLK(mode):
251 kind = _('block device')
256 kind = _('block device')
252 elif stat.S_ISFIFO(mode):
257 elif stat.S_ISFIFO(mode):
253 kind = _('fifo')
258 kind = _('fifo')
254 elif stat.S_ISSOCK(mode):
259 elif stat.S_ISSOCK(mode):
255 kind = _('socket')
260 kind = _('socket')
256 elif stat.S_ISDIR(mode):
261 elif stat.S_ISDIR(mode):
257 kind = _('directory')
262 kind = _('directory')
258 return _('unsupported file type (type is %s)') % kind
263 return _('unsupported file type (type is %s)') % kind
259
264
260 ignore = self._ignore
265 ignore = self._ignore
261 dirignore = self._dirignore
266 dirignore = self._dirignore
262 if unknown:
267 if unknown:
263 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
268 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
264 # ignore list changed -- can't rely on Watchman state any more
269 # ignore list changed -- can't rely on Watchman state any more
265 if state.walk_on_invalidate:
270 if state.walk_on_invalidate:
266 return bail()
271 return bail('ignore rules changed')
267 notefiles = []
272 notefiles = []
268 clock = 'c:0:0'
273 clock = 'c:0:0'
269 else:
274 else:
270 # always ignore
275 # always ignore
271 ignore = util.always
276 ignore = util.always
272 dirignore = util.always
277 dirignore = util.always
273
278
274 matchfn = match.matchfn
279 matchfn = match.matchfn
275 matchalways = match.always()
280 matchalways = match.always()
276 dmap = self._map
281 dmap = self._map
277 if util.safehasattr(dmap, '_map'):
282 if util.safehasattr(dmap, '_map'):
278 # for better performance, directly access the inner dirstate map if the
283 # for better performance, directly access the inner dirstate map if the
279 # standard dirstate implementation is in use.
284 # standard dirstate implementation is in use.
280 dmap = dmap._map
285 dmap = dmap._map
281 nonnormalset = self._map.nonnormalset
286 nonnormalset = self._map.nonnormalset
282
287
283 copymap = self._map.copymap
288 copymap = self._map.copymap
284 getkind = stat.S_IFMT
289 getkind = stat.S_IFMT
285 dirkind = stat.S_IFDIR
290 dirkind = stat.S_IFDIR
286 regkind = stat.S_IFREG
291 regkind = stat.S_IFREG
287 lnkkind = stat.S_IFLNK
292 lnkkind = stat.S_IFLNK
288 join = self._join
293 join = self._join
289 normcase = util.normcase
294 normcase = util.normcase
290 fresh_instance = False
295 fresh_instance = False
291
296
292 exact = skipstep3 = False
297 exact = skipstep3 = False
293 if match.isexact(): # match.exact
298 if match.isexact(): # match.exact
294 exact = True
299 exact = True
295 dirignore = util.always # skip step 2
300 dirignore = util.always # skip step 2
296 elif match.prefix(): # match.match, no patterns
301 elif match.prefix(): # match.match, no patterns
297 skipstep3 = True
302 skipstep3 = True
298
303
299 if not exact and self._checkcase:
304 if not exact and self._checkcase:
300 # note that even though we could receive directory entries, we're only
305 # note that even though we could receive directory entries, we're only
301 # interested in checking if a file with the same name exists. So only
306 # interested in checking if a file with the same name exists. So only
302 # normalize files if possible.
307 # normalize files if possible.
303 normalize = self._normalizefile
308 normalize = self._normalizefile
304 skipstep3 = False
309 skipstep3 = False
305 else:
310 else:
306 normalize = None
311 normalize = None
307
312
308 # step 1: find all explicit files
313 # step 1: find all explicit files
309 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
314 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
310
315
311 skipstep3 = skipstep3 and not (work or dirsnotfound)
316 skipstep3 = skipstep3 and not (work or dirsnotfound)
312 work = [d for d in work if not dirignore(d[0])]
317 work = [d for d in work if not dirignore(d[0])]
313
318
314 if not work and (exact or skipstep3):
319 if not work and (exact or skipstep3):
315 for s in subrepos:
320 for s in subrepos:
316 del results[s]
321 del results[s]
317 del results['.hg']
322 del results['.hg']
318 return results
323 return results
319
324
320 # step 2: query Watchman
325 # step 2: query Watchman
321 try:
326 try:
322 # Use the user-configured timeout for the query.
327 # Use the user-configured timeout for the query.
323 # Add a little slack over the top of the user query to allow for
328 # Add a little slack over the top of the user query to allow for
324 # overheads while transferring the data
329 # overheads while transferring the data
325 self._watchmanclient.settimeout(state.timeout + 0.1)
330 self._watchmanclient.settimeout(state.timeout + 0.1)
326 result = self._watchmanclient.command('query', {
331 result = self._watchmanclient.command('query', {
327 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
332 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
328 'since': clock,
333 'since': clock,
329 'expression': [
334 'expression': [
330 'not', [
335 'not', [
331 'anyof', ['dirname', '.hg'],
336 'anyof', ['dirname', '.hg'],
332 ['name', '.hg', 'wholename']
337 ['name', '.hg', 'wholename']
333 ]
338 ]
334 ],
339 ],
335 'sync_timeout': int(state.timeout * 1000),
340 'sync_timeout': int(state.timeout * 1000),
336 'empty_on_fresh_instance': state.walk_on_invalidate,
341 'empty_on_fresh_instance': state.walk_on_invalidate,
337 })
342 })
338 except Exception as ex:
343 except Exception as ex:
339 _handleunavailable(self._ui, state, ex)
344 _handleunavailable(self._ui, state, ex)
340 self._watchmanclient.clearconnection()
345 self._watchmanclient.clearconnection()
341 return bail()
346 return bail('exception during run')
342 else:
347 else:
343 # We need to propagate the last observed clock up so that we
348 # We need to propagate the last observed clock up so that we
344 # can use it for our next query
349 # can use it for our next query
345 state.setlastclock(result['clock'])
350 state.setlastclock(result['clock'])
346 if result['is_fresh_instance']:
351 if result['is_fresh_instance']:
347 if state.walk_on_invalidate:
352 if state.walk_on_invalidate:
348 state.invalidate()
353 state.invalidate()
349 return bail()
354 return bail('fresh instance')
350 fresh_instance = True
355 fresh_instance = True
351 # Ignore any prior noteable files from the state info
356 # Ignore any prior noteable files from the state info
352 notefiles = []
357 notefiles = []
353
358
354 # for file paths which require normalization and we encounter a case
359 # for file paths which require normalization and we encounter a case
355 # collision, we store our own foldmap
360 # collision, we store our own foldmap
356 if normalize:
361 if normalize:
357 foldmap = dict((normcase(k), k) for k in results)
362 foldmap = dict((normcase(k), k) for k in results)
358
363
359 switch_slashes = pycompat.ossep == '\\'
364 switch_slashes = pycompat.ossep == '\\'
360 # The order of the results is, strictly speaking, undefined.
365 # The order of the results is, strictly speaking, undefined.
361 # For case changes on a case insensitive filesystem we may receive
366 # For case changes on a case insensitive filesystem we may receive
362 # two entries, one with exists=True and another with exists=False.
367 # two entries, one with exists=True and another with exists=False.
363 # The exists=True entries in the same response should be interpreted
368 # The exists=True entries in the same response should be interpreted
364 # as being happens-after the exists=False entries due to the way that
369 # as being happens-after the exists=False entries due to the way that
365 # Watchman tracks files. We use this property to reconcile deletes
370 # Watchman tracks files. We use this property to reconcile deletes
366 # for name case changes.
371 # for name case changes.
367 for entry in result['files']:
372 for entry in result['files']:
368 fname = entry['name']
373 fname = entry['name']
369 if _fixencoding:
374 if _fixencoding:
370 fname = _watchmantofsencoding(fname)
375 fname = _watchmantofsencoding(fname)
371 if switch_slashes:
376 if switch_slashes:
372 fname = fname.replace('\\', '/')
377 fname = fname.replace('\\', '/')
373 if normalize:
378 if normalize:
374 normed = normcase(fname)
379 normed = normcase(fname)
375 fname = normalize(fname, True, True)
380 fname = normalize(fname, True, True)
376 foldmap[normed] = fname
381 foldmap[normed] = fname
377 fmode = entry['mode']
382 fmode = entry['mode']
378 fexists = entry['exists']
383 fexists = entry['exists']
379 kind = getkind(fmode)
384 kind = getkind(fmode)
380
385
381 if not fexists:
386 if not fexists:
382 # if marked as deleted and we don't already have a change
387 # if marked as deleted and we don't already have a change
383 # record, mark it as deleted. If we already have an entry
388 # record, mark it as deleted. If we already have an entry
384 # for fname then it was either part of walkexplicit or was
389 # for fname then it was either part of walkexplicit or was
385 # an earlier result that was a case change
390 # an earlier result that was a case change
386 if fname not in results and fname in dmap and (
391 if fname not in results and fname in dmap and (
387 matchalways or matchfn(fname)):
392 matchalways or matchfn(fname)):
388 results[fname] = None
393 results[fname] = None
389 elif kind == dirkind:
394 elif kind == dirkind:
390 if fname in dmap and (matchalways or matchfn(fname)):
395 if fname in dmap and (matchalways or matchfn(fname)):
391 results[fname] = None
396 results[fname] = None
392 elif kind == regkind or kind == lnkkind:
397 elif kind == regkind or kind == lnkkind:
393 if fname in dmap:
398 if fname in dmap:
394 if matchalways or matchfn(fname):
399 if matchalways or matchfn(fname):
395 results[fname] = entry
400 results[fname] = entry
396 elif (matchalways or matchfn(fname)) and not ignore(fname):
401 elif (matchalways or matchfn(fname)) and not ignore(fname):
397 results[fname] = entry
402 results[fname] = entry
398 elif fname in dmap and (matchalways or matchfn(fname)):
403 elif fname in dmap and (matchalways or matchfn(fname)):
399 results[fname] = None
404 results[fname] = None
400
405
401 # step 3: query notable files we don't already know about
406 # step 3: query notable files we don't already know about
402 # XXX try not to iterate over the entire dmap
407 # XXX try not to iterate over the entire dmap
403 if normalize:
408 if normalize:
404 # any notable files that have changed case will already be handled
409 # any notable files that have changed case will already be handled
405 # above, so just check membership in the foldmap
410 # above, so just check membership in the foldmap
406 notefiles = set((normalize(f, True, True) for f in notefiles
411 notefiles = set((normalize(f, True, True) for f in notefiles
407 if normcase(f) not in foldmap))
412 if normcase(f) not in foldmap))
408 visit = set((f for f in notefiles if (f not in results and matchfn(f)
413 visit = set((f for f in notefiles if (f not in results and matchfn(f)
409 and (f in dmap or not ignore(f)))))
414 and (f in dmap or not ignore(f)))))
410
415
411 if not fresh_instance:
416 if not fresh_instance:
412 if matchalways:
417 if matchalways:
413 visit.update(f for f in nonnormalset if f not in results)
418 visit.update(f for f in nonnormalset if f not in results)
414 visit.update(f for f in copymap if f not in results)
419 visit.update(f for f in copymap if f not in results)
415 else:
420 else:
416 visit.update(f for f in nonnormalset
421 visit.update(f for f in nonnormalset
417 if f not in results and matchfn(f))
422 if f not in results and matchfn(f))
418 visit.update(f for f in copymap
423 visit.update(f for f in copymap
419 if f not in results and matchfn(f))
424 if f not in results and matchfn(f))
420 else:
425 else:
421 if matchalways:
426 if matchalways:
422 visit.update(f for f, st in dmap.iteritems() if f not in results)
427 visit.update(f for f, st in dmap.iteritems() if f not in results)
423 visit.update(f for f in copymap if f not in results)
428 visit.update(f for f in copymap if f not in results)
424 else:
429 else:
425 visit.update(f for f, st in dmap.iteritems()
430 visit.update(f for f, st in dmap.iteritems()
426 if f not in results and matchfn(f))
431 if f not in results and matchfn(f))
427 visit.update(f for f in copymap
432 visit.update(f for f in copymap
428 if f not in results and matchfn(f))
433 if f not in results and matchfn(f))
429
434
430 audit = pathutil.pathauditor(self._root, cached=True).check
435 audit = pathutil.pathauditor(self._root, cached=True).check
431 auditpass = [f for f in visit if audit(f)]
436 auditpass = [f for f in visit if audit(f)]
432 auditpass.sort()
437 auditpass.sort()
433 auditfail = visit.difference(auditpass)
438 auditfail = visit.difference(auditpass)
434 for f in auditfail:
439 for f in auditfail:
435 results[f] = None
440 results[f] = None
436
441
437 nf = iter(auditpass).next
442 nf = iter(auditpass).next
438 for st in util.statfiles([join(f) for f in auditpass]):
443 for st in util.statfiles([join(f) for f in auditpass]):
439 f = nf()
444 f = nf()
440 if st or f in dmap:
445 if st or f in dmap:
441 results[f] = st
446 results[f] = st
442
447
443 for s in subrepos:
448 for s in subrepos:
444 del results[s]
449 del results[s]
445 del results['.hg']
450 del results['.hg']
446 return results
451 return results
447
452
448 def overridestatus(
453 def overridestatus(
449 orig, self, node1='.', node2=None, match=None, ignored=False,
454 orig, self, node1='.', node2=None, match=None, ignored=False,
450 clean=False, unknown=False, listsubrepos=False):
455 clean=False, unknown=False, listsubrepos=False):
451 listignored = ignored
456 listignored = ignored
452 listclean = clean
457 listclean = clean
453 listunknown = unknown
458 listunknown = unknown
454
459
455 def _cmpsets(l1, l2):
460 def _cmpsets(l1, l2):
456 try:
461 try:
457 if 'FSMONITOR_LOG_FILE' in encoding.environ:
462 if 'FSMONITOR_LOG_FILE' in encoding.environ:
458 fn = encoding.environ['FSMONITOR_LOG_FILE']
463 fn = encoding.environ['FSMONITOR_LOG_FILE']
459 f = open(fn, 'wb')
464 f = open(fn, 'wb')
460 else:
465 else:
461 fn = 'fsmonitorfail.log'
466 fn = 'fsmonitorfail.log'
462 f = self.opener(fn, 'wb')
467 f = self.opener(fn, 'wb')
463 except (IOError, OSError):
468 except (IOError, OSError):
464 self.ui.warn(_('warning: unable to write to %s\n') % fn)
469 self.ui.warn(_('warning: unable to write to %s\n') % fn)
465 return
470 return
466
471
467 try:
472 try:
468 for i, (s1, s2) in enumerate(zip(l1, l2)):
473 for i, (s1, s2) in enumerate(zip(l1, l2)):
469 if set(s1) != set(s2):
474 if set(s1) != set(s2):
470 f.write('sets at position %d are unequal\n' % i)
475 f.write('sets at position %d are unequal\n' % i)
471 f.write('watchman returned: %s\n' % s1)
476 f.write('watchman returned: %s\n' % s1)
472 f.write('stat returned: %s\n' % s2)
477 f.write('stat returned: %s\n' % s2)
473 finally:
478 finally:
474 f.close()
479 f.close()
475
480
476 if isinstance(node1, context.changectx):
481 if isinstance(node1, context.changectx):
477 ctx1 = node1
482 ctx1 = node1
478 else:
483 else:
479 ctx1 = self[node1]
484 ctx1 = self[node1]
480 if isinstance(node2, context.changectx):
485 if isinstance(node2, context.changectx):
481 ctx2 = node2
486 ctx2 = node2
482 else:
487 else:
483 ctx2 = self[node2]
488 ctx2 = self[node2]
484
489
485 working = ctx2.rev() is None
490 working = ctx2.rev() is None
486 parentworking = working and ctx1 == self['.']
491 parentworking = working and ctx1 == self['.']
487 match = match or matchmod.always(self.root, self.getcwd())
492 match = match or matchmod.always(self.root, self.getcwd())
488
493
489 # Maybe we can use this opportunity to update Watchman's state.
494 # Maybe we can use this opportunity to update Watchman's state.
490 # Mercurial uses workingcommitctx and/or memctx to represent the part of
495 # Mercurial uses workingcommitctx and/or memctx to represent the part of
491 # the workingctx that is to be committed. So don't update the state in
496 # the workingctx that is to be committed. So don't update the state in
492 # that case.
497 # that case.
493 # HG_PENDING is set in the environment when the dirstate is being updated
498 # HG_PENDING is set in the environment when the dirstate is being updated
494 # in the middle of a transaction; we must not update our state in that
499 # in the middle of a transaction; we must not update our state in that
495 # case, or we risk forgetting about changes in the working copy.
500 # case, or we risk forgetting about changes in the working copy.
496 updatestate = (parentworking and match.always() and
501 updatestate = (parentworking and match.always() and
497 not isinstance(ctx2, (context.workingcommitctx,
502 not isinstance(ctx2, (context.workingcommitctx,
498 context.memctx)) and
503 context.memctx)) and
499 'HG_PENDING' not in encoding.environ)
504 'HG_PENDING' not in encoding.environ)
500
505
501 try:
506 try:
502 if self._fsmonitorstate.walk_on_invalidate:
507 if self._fsmonitorstate.walk_on_invalidate:
503 # Use a short timeout to query the current clock. If that
508 # Use a short timeout to query the current clock. If that
504 # takes too long then we assume that the service will be slow
509 # takes too long then we assume that the service will be slow
505 # to answer our query.
510 # to answer our query.
506 # walk_on_invalidate indicates that we prefer to walk the
511 # walk_on_invalidate indicates that we prefer to walk the
507 # tree ourselves because we can ignore portions that Watchman
512 # tree ourselves because we can ignore portions that Watchman
508 # cannot and we tend to be faster in the warmer buffer cache
513 # cannot and we tend to be faster in the warmer buffer cache
509 # cases.
514 # cases.
510 self._watchmanclient.settimeout(0.1)
515 self._watchmanclient.settimeout(0.1)
511 else:
516 else:
512 # Give Watchman more time to potentially complete its walk
517 # Give Watchman more time to potentially complete its walk
513 # and return the initial clock. In this mode we assume that
518 # and return the initial clock. In this mode we assume that
514 # the filesystem will be slower than parsing a potentially
519 # the filesystem will be slower than parsing a potentially
515 # very large Watchman result set.
520 # very large Watchman result set.
516 self._watchmanclient.settimeout(
521 self._watchmanclient.settimeout(
517 self._fsmonitorstate.timeout + 0.1)
522 self._fsmonitorstate.timeout + 0.1)
518 startclock = self._watchmanclient.getcurrentclock()
523 startclock = self._watchmanclient.getcurrentclock()
519 except Exception as ex:
524 except Exception as ex:
520 self._watchmanclient.clearconnection()
525 self._watchmanclient.clearconnection()
521 _handleunavailable(self.ui, self._fsmonitorstate, ex)
526 _handleunavailable(self.ui, self._fsmonitorstate, ex)
522 # boo, Watchman failed. bail
527 # boo, Watchman failed. bail
523 return orig(node1, node2, match, listignored, listclean,
528 return orig(node1, node2, match, listignored, listclean,
524 listunknown, listsubrepos)
529 listunknown, listsubrepos)
525
530
526 if updatestate:
531 if updatestate:
527 # We need info about unknown files. This may make things slower the
532 # We need info about unknown files. This may make things slower the
528 # first time, but whatever.
533 # first time, but whatever.
529 stateunknown = True
534 stateunknown = True
530 else:
535 else:
531 stateunknown = listunknown
536 stateunknown = listunknown
532
537
533 if updatestate:
538 if updatestate:
534 ps = poststatus(startclock)
539 ps = poststatus(startclock)
535 self.addpostdsstatus(ps)
540 self.addpostdsstatus(ps)
536
541
537 r = orig(node1, node2, match, listignored, listclean, stateunknown,
542 r = orig(node1, node2, match, listignored, listclean, stateunknown,
538 listsubrepos)
543 listsubrepos)
539 modified, added, removed, deleted, unknown, ignored, clean = r
544 modified, added, removed, deleted, unknown, ignored, clean = r
540
545
541 if not listunknown:
546 if not listunknown:
542 unknown = []
547 unknown = []
543
548
544 # don't do paranoid checks if we're not going to query Watchman anyway
549 # don't do paranoid checks if we're not going to query Watchman anyway
545 full = listclean or match.traversedir is not None
550 full = listclean or match.traversedir is not None
546 if self._fsmonitorstate.mode == 'paranoid' and not full:
551 if self._fsmonitorstate.mode == 'paranoid' and not full:
547 # run status again and fall back to the old walk this time
552 # run status again and fall back to the old walk this time
548 self.dirstate._fsmonitordisable = True
553 self.dirstate._fsmonitordisable = True
549
554
550 # shut the UI up
555 # shut the UI up
551 quiet = self.ui.quiet
556 quiet = self.ui.quiet
552 self.ui.quiet = True
557 self.ui.quiet = True
553 fout, ferr = self.ui.fout, self.ui.ferr
558 fout, ferr = self.ui.fout, self.ui.ferr
554 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
559 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
555
560
556 try:
561 try:
557 rv2 = orig(
562 rv2 = orig(
558 node1, node2, match, listignored, listclean, listunknown,
563 node1, node2, match, listignored, listclean, listunknown,
559 listsubrepos)
564 listsubrepos)
560 finally:
565 finally:
561 self.dirstate._fsmonitordisable = False
566 self.dirstate._fsmonitordisable = False
562 self.ui.quiet = quiet
567 self.ui.quiet = quiet
563 self.ui.fout, self.ui.ferr = fout, ferr
568 self.ui.fout, self.ui.ferr = fout, ferr
564
569
565 # clean isn't tested since it's set to True above
570 # clean isn't tested since it's set to True above
566 _cmpsets([modified, added, removed, deleted, unknown, ignored, clean],
571 _cmpsets([modified, added, removed, deleted, unknown, ignored, clean],
567 rv2)
572 rv2)
568 modified, added, removed, deleted, unknown, ignored, clean = rv2
573 modified, added, removed, deleted, unknown, ignored, clean = rv2
569
574
570 return scmutil.status(
575 return scmutil.status(
571 modified, added, removed, deleted, unknown, ignored, clean)
576 modified, added, removed, deleted, unknown, ignored, clean)
572
577
573 class poststatus(object):
578 class poststatus(object):
574 def __init__(self, startclock):
579 def __init__(self, startclock):
575 self._startclock = startclock
580 self._startclock = startclock
576
581
577 def __call__(self, wctx, status):
582 def __call__(self, wctx, status):
578 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
583 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
579 hashignore = _hashignore(wctx.repo().dirstate._ignore)
584 hashignore = _hashignore(wctx.repo().dirstate._ignore)
580 notefiles = (status.modified + status.added + status.removed +
585 notefiles = (status.modified + status.added + status.removed +
581 status.deleted + status.unknown)
586 status.deleted + status.unknown)
582 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
587 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
583
588
584 def makedirstate(repo, dirstate):
589 def makedirstate(repo, dirstate):
585 class fsmonitordirstate(dirstate.__class__):
590 class fsmonitordirstate(dirstate.__class__):
586 def _fsmonitorinit(self, repo):
591 def _fsmonitorinit(self, repo):
587 # _fsmonitordisable is used in paranoid mode
592 # _fsmonitordisable is used in paranoid mode
588 self._fsmonitordisable = False
593 self._fsmonitordisable = False
589 self._fsmonitorstate = repo._fsmonitorstate
594 self._fsmonitorstate = repo._fsmonitorstate
590 self._watchmanclient = repo._watchmanclient
595 self._watchmanclient = repo._watchmanclient
591 self._repo = weakref.proxy(repo)
596 self._repo = weakref.proxy(repo)
592
597
593 def walk(self, *args, **kwargs):
598 def walk(self, *args, **kwargs):
594 orig = super(fsmonitordirstate, self).walk
599 orig = super(fsmonitordirstate, self).walk
595 if self._fsmonitordisable:
600 if self._fsmonitordisable:
596 return orig(*args, **kwargs)
601 return orig(*args, **kwargs)
597 return overridewalk(orig, self, *args, **kwargs)
602 return overridewalk(orig, self, *args, **kwargs)
598
603
599 def rebuild(self, *args, **kwargs):
604 def rebuild(self, *args, **kwargs):
600 self._fsmonitorstate.invalidate()
605 self._fsmonitorstate.invalidate()
601 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
606 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
602
607
603 def invalidate(self, *args, **kwargs):
608 def invalidate(self, *args, **kwargs):
604 self._fsmonitorstate.invalidate()
609 self._fsmonitorstate.invalidate()
605 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
610 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
606
611
607 if dirstate._ui.configbool(
612 if dirstate._ui.configbool(
608 "experimental", "fsmonitor.wc_change_notify"):
613 "experimental", "fsmonitor.wc_change_notify"):
609 def setparents(self, p1, p2=nullid):
614 def setparents(self, p1, p2=nullid):
610 with state_update(self._repo, name="hg.wc_change",
615 with state_update(self._repo, name="hg.wc_change",
611 oldnode=self._pl[0], newnode=p1,
616 oldnode=self._pl[0], newnode=p1,
612 partial=False):
617 partial=False):
613 return super(fsmonitordirstate, self).setparents(p1, p2)
618 return super(fsmonitordirstate, self).setparents(p1, p2)
614
619
615 dirstate.__class__ = fsmonitordirstate
620 dirstate.__class__ = fsmonitordirstate
616 dirstate._fsmonitorinit(repo)
621 dirstate._fsmonitorinit(repo)
617
622
618 def wrapdirstate(orig, self):
623 def wrapdirstate(orig, self):
619 ds = orig(self)
624 ds = orig(self)
620 # only override the dirstate when Watchman is available for the repo
625 # only override the dirstate when Watchman is available for the repo
621 if util.safehasattr(self, '_fsmonitorstate'):
626 if util.safehasattr(self, '_fsmonitorstate'):
622 makedirstate(self, ds)
627 makedirstate(self, ds)
623 return ds
628 return ds
624
629
625 def extsetup(ui):
630 def extsetup(ui):
626 extensions.wrapfilecache(
631 extensions.wrapfilecache(
627 localrepo.localrepository, 'dirstate', wrapdirstate)
632 localrepo.localrepository, 'dirstate', wrapdirstate)
628 if pycompat.isdarwin:
633 if pycompat.isdarwin:
629 # An assist for avoiding the dangling-symlink fsevents bug
634 # An assist for avoiding the dangling-symlink fsevents bug
630 extensions.wrapfunction(os, 'symlink', wrapsymlink)
635 extensions.wrapfunction(os, 'symlink', wrapsymlink)
631
636
632 extensions.wrapfunction(merge, 'update', wrapupdate)
637 extensions.wrapfunction(merge, 'update', wrapupdate)
633
638
634 def wrapsymlink(orig, source, link_name):
639 def wrapsymlink(orig, source, link_name):
635 ''' if we create a dangling symlink, also touch the parent dir
640 ''' if we create a dangling symlink, also touch the parent dir
636 to encourage fsevents notifications to work more correctly '''
641 to encourage fsevents notifications to work more correctly '''
637 try:
642 try:
638 return orig(source, link_name)
643 return orig(source, link_name)
639 finally:
644 finally:
640 try:
645 try:
641 os.utime(os.path.dirname(link_name), None)
646 os.utime(os.path.dirname(link_name), None)
642 except OSError:
647 except OSError:
643 pass
648 pass
644
649
645 class state_update(object):
650 class state_update(object):
646 ''' This context manager is responsible for dispatching the state-enter
651 ''' This context manager is responsible for dispatching the state-enter
647 and state-leave signals to the watchman service. The enter and leave
652 and state-leave signals to the watchman service. The enter and leave
648 methods can be invoked manually (for scenarios where context manager
653 methods can be invoked manually (for scenarios where context manager
649 semantics are not possible). If parameters oldnode and newnode are None,
654 semantics are not possible). If parameters oldnode and newnode are None,
650 they will be populated based on current working copy in enter and
655 they will be populated based on current working copy in enter and
651 leave, respectively. Similarly, if the distance is none, it will be
656 leave, respectively. Similarly, if the distance is none, it will be
652 calculated based on the oldnode and newnode in the leave method.'''
657 calculated based on the oldnode and newnode in the leave method.'''
653
658
654 def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
659 def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
655 partial=False):
660 partial=False):
656 self.repo = repo.unfiltered()
661 self.repo = repo.unfiltered()
657 self.name = name
662 self.name = name
658 self.oldnode = oldnode
663 self.oldnode = oldnode
659 self.newnode = newnode
664 self.newnode = newnode
660 self.distance = distance
665 self.distance = distance
661 self.partial = partial
666 self.partial = partial
662 self._lock = None
667 self._lock = None
663 self.need_leave = False
668 self.need_leave = False
664
669
665 def __enter__(self):
670 def __enter__(self):
666 self.enter()
671 self.enter()
667
672
668 def enter(self):
673 def enter(self):
669 # We explicitly need to take a lock here, before we proceed to update
674 # We explicitly need to take a lock here, before we proceed to update
670 # watchman about the update operation, so that we don't race with
675 # watchman about the update operation, so that we don't race with
671 # some other actor. merge.update is going to take the wlock almost
676 # some other actor. merge.update is going to take the wlock almost
672 # immediately anyway, so this is effectively extending the lock
677 # immediately anyway, so this is effectively extending the lock
673 # around a couple of short sanity checks.
678 # around a couple of short sanity checks.
674 if self.oldnode is None:
679 if self.oldnode is None:
675 self.oldnode = self.repo['.'].node()
680 self.oldnode = self.repo['.'].node()
676 self._lock = self.repo.wlock()
681 self._lock = self.repo.wlock()
677 self.need_leave = self._state(
682 self.need_leave = self._state(
678 'state-enter',
683 'state-enter',
679 hex(self.oldnode))
684 hex(self.oldnode))
680 return self
685 return self
681
686
682 def __exit__(self, type_, value, tb):
687 def __exit__(self, type_, value, tb):
683 abort = True if type_ else False
688 abort = True if type_ else False
684 self.exit(abort=abort)
689 self.exit(abort=abort)
685
690
686 def exit(self, abort=False):
691 def exit(self, abort=False):
687 try:
692 try:
688 if self.need_leave:
693 if self.need_leave:
689 status = 'failed' if abort else 'ok'
694 status = 'failed' if abort else 'ok'
690 if self.newnode is None:
695 if self.newnode is None:
691 self.newnode = self.repo['.'].node()
696 self.newnode = self.repo['.'].node()
692 if self.distance is None:
697 if self.distance is None:
693 self.distance = calcdistance(
698 self.distance = calcdistance(
694 self.repo, self.oldnode, self.newnode)
699 self.repo, self.oldnode, self.newnode)
695 self._state(
700 self._state(
696 'state-leave',
701 'state-leave',
697 hex(self.newnode),
702 hex(self.newnode),
698 status=status)
703 status=status)
699 finally:
704 finally:
700 self.need_leave = False
705 self.need_leave = False
701 if self._lock:
706 if self._lock:
702 self._lock.release()
707 self._lock.release()
703
708
704 def _state(self, cmd, commithash, status='ok'):
709 def _state(self, cmd, commithash, status='ok'):
705 if not util.safehasattr(self.repo, '_watchmanclient'):
710 if not util.safehasattr(self.repo, '_watchmanclient'):
706 return False
711 return False
707 try:
712 try:
708 self.repo._watchmanclient.command(cmd, {
713 self.repo._watchmanclient.command(cmd, {
709 'name': self.name,
714 'name': self.name,
710 'metadata': {
715 'metadata': {
711 # the target revision
716 # the target revision
712 'rev': commithash,
717 'rev': commithash,
713 # approximate number of commits between current and target
718 # approximate number of commits between current and target
714 'distance': self.distance if self.distance else 0,
719 'distance': self.distance if self.distance else 0,
715 # success/failure (only really meaningful for state-leave)
720 # success/failure (only really meaningful for state-leave)
716 'status': status,
721 'status': status,
717 # whether the working copy parent is changing
722 # whether the working copy parent is changing
718 'partial': self.partial,
723 'partial': self.partial,
719 }})
724 }})
720 return True
725 return True
721 except Exception as e:
726 except Exception as e:
722 # Swallow any errors; fire and forget
727 # Swallow any errors; fire and forget
723 self.repo.ui.log(
728 self.repo.ui.log(
724 'watchman', 'Exception %s while running %s\n', e, cmd)
729 'watchman', 'Exception %s while running %s\n', e, cmd)
725 return False
730 return False
726
731
727 # Estimate the distance between two nodes
732 # Estimate the distance between two nodes
728 def calcdistance(repo, oldnode, newnode):
733 def calcdistance(repo, oldnode, newnode):
729 anc = repo.changelog.ancestor(oldnode, newnode)
734 anc = repo.changelog.ancestor(oldnode, newnode)
730 ancrev = repo[anc].rev()
735 ancrev = repo[anc].rev()
731 distance = (abs(repo[oldnode].rev() - ancrev)
736 distance = (abs(repo[oldnode].rev() - ancrev)
732 + abs(repo[newnode].rev() - ancrev))
737 + abs(repo[newnode].rev() - ancrev))
733 return distance
738 return distance
734
739
735 # Bracket working copy updates with calls to the watchman state-enter
740 # Bracket working copy updates with calls to the watchman state-enter
736 # and state-leave commands. This allows clients to perform more intelligent
741 # and state-leave commands. This allows clients to perform more intelligent
737 # settling during bulk file change scenarios
742 # settling during bulk file change scenarios
738 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
743 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
739 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
744 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
740 mergeancestor=False, labels=None, matcher=None, **kwargs):
745 mergeancestor=False, labels=None, matcher=None, **kwargs):
741
746
742 distance = 0
747 distance = 0
743 partial = True
748 partial = True
744 oldnode = repo['.'].node()
749 oldnode = repo['.'].node()
745 newnode = repo[node].node()
750 newnode = repo[node].node()
746 if matcher is None or matcher.always():
751 if matcher is None or matcher.always():
747 partial = False
752 partial = False
748 distance = calcdistance(repo.unfiltered(), oldnode, newnode)
753 distance = calcdistance(repo.unfiltered(), oldnode, newnode)
749
754
750 with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
755 with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
751 distance=distance, partial=partial):
756 distance=distance, partial=partial):
752 return orig(
757 return orig(
753 repo, node, branchmerge, force, ancestor, mergeancestor,
758 repo, node, branchmerge, force, ancestor, mergeancestor,
754 labels, matcher, **kwargs)
759 labels, matcher, **kwargs)
755
760
756 def reposetup(ui, repo):
761 def reposetup(ui, repo):
757 # We don't work with largefiles or inotify
762 # We don't work with largefiles or inotify
758 exts = extensions.enabled()
763 exts = extensions.enabled()
759 for ext in _blacklist:
764 for ext in _blacklist:
760 if ext in exts:
765 if ext in exts:
761 ui.warn(_('The fsmonitor extension is incompatible with the %s '
766 ui.warn(_('The fsmonitor extension is incompatible with the %s '
762 'extension and has been disabled.\n') % ext)
767 'extension and has been disabled.\n') % ext)
763 return
768 return
764
769
765 if repo.local():
770 if repo.local():
766 # We don't work with subrepos either.
771 # We don't work with subrepos either.
767 #
772 #
768 # if repo[None].substate can cause a dirstate parse, which is too
773 # if repo[None].substate can cause a dirstate parse, which is too
769 # slow. Instead, look for a file called hgsubstate,
774 # slow. Instead, look for a file called hgsubstate,
770 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
775 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
771 return
776 return
772
777
773 fsmonitorstate = state.state(repo)
778 fsmonitorstate = state.state(repo)
774 if fsmonitorstate.mode == 'off':
779 if fsmonitorstate.mode == 'off':
775 return
780 return
776
781
777 try:
782 try:
778 client = watchmanclient.client(repo)
783 client = watchmanclient.client(repo)
779 except Exception as ex:
784 except Exception as ex:
780 _handleunavailable(ui, fsmonitorstate, ex)
785 _handleunavailable(ui, fsmonitorstate, ex)
781 return
786 return
782
787
783 repo._fsmonitorstate = fsmonitorstate
788 repo._fsmonitorstate = fsmonitorstate
784 repo._watchmanclient = client
789 repo._watchmanclient = client
785
790
786 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
791 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
787 if cached:
792 if cached:
788 # at this point since fsmonitorstate wasn't present,
793 # at this point since fsmonitorstate wasn't present,
789 # repo.dirstate is not a fsmonitordirstate
794 # repo.dirstate is not a fsmonitordirstate
790 makedirstate(repo, dirstate)
795 makedirstate(repo, dirstate)
791
796
792 class fsmonitorrepo(repo.__class__):
797 class fsmonitorrepo(repo.__class__):
793 def status(self, *args, **kwargs):
798 def status(self, *args, **kwargs):
794 orig = super(fsmonitorrepo, self).status
799 orig = super(fsmonitorrepo, self).status
795 return overridestatus(orig, self, *args, **kwargs)
800 return overridestatus(orig, self, *args, **kwargs)
796
801
797 if ui.configbool("experimental", "fsmonitor.transaction_notify"):
802 if ui.configbool("experimental", "fsmonitor.transaction_notify"):
798 def transaction(self, *args, **kwargs):
803 def transaction(self, *args, **kwargs):
799 tr = super(fsmonitorrepo, self).transaction(
804 tr = super(fsmonitorrepo, self).transaction(
800 *args, **kwargs)
805 *args, **kwargs)
801 if tr.count != 1:
806 if tr.count != 1:
802 return tr
807 return tr
803 stateupdate = state_update(self, name="hg.transaction")
808 stateupdate = state_update(self, name="hg.transaction")
804 stateupdate.enter()
809 stateupdate.enter()
805
810
806 class fsmonitortrans(tr.__class__):
811 class fsmonitortrans(tr.__class__):
807 def _abort(self):
812 def _abort(self):
808 try:
813 try:
809 result = super(fsmonitortrans, self)._abort()
814 result = super(fsmonitortrans, self)._abort()
810 finally:
815 finally:
811 stateupdate.exit(abort=True)
816 stateupdate.exit(abort=True)
812 return result
817 return result
813
818
814 def close(self):
819 def close(self):
815 try:
820 try:
816 result = super(fsmonitortrans, self).close()
821 result = super(fsmonitortrans, self).close()
817 finally:
822 finally:
818 if self.count == 0:
823 if self.count == 0:
819 stateupdate.exit()
824 stateupdate.exit()
820 return result
825 return result
821
826
822 tr.__class__ = fsmonitortrans
827 tr.__class__ = fsmonitortrans
823 return tr
828 return tr
824
829
825 repo.__class__ = fsmonitorrepo
830 repo.__class__ = fsmonitorrepo
General Comments 0
You need to be logged in to leave comments. Login now