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