##// END OF EJS Templates
fsmonitor: add support for extra `hg debuginstall` data...
Augie Fackler -
r42876:3358dc6e default draft
parent child Browse files
Show More
@@ -1,832 +1,850 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 tempfile
115 import weakref
116 import weakref
116
117
117 from mercurial.i18n import _
118 from mercurial.i18n import _
118 from mercurial.node import (
119 from mercurial.node import (
119 hex,
120 hex,
120 )
121 )
121
122
122 from mercurial import (
123 from mercurial import (
123 context,
124 context,
124 encoding,
125 encoding,
125 error,
126 error,
126 extensions,
127 extensions,
127 localrepo,
128 localrepo,
128 merge,
129 merge,
129 pathutil,
130 pathutil,
130 pycompat,
131 pycompat,
131 registrar,
132 registrar,
132 scmutil,
133 scmutil,
133 util,
134 util,
134 )
135 )
135 from mercurial import match as matchmod
136 from mercurial import match as matchmod
136
137
137 from . import (
138 from . import (
138 pywatchman,
139 pywatchman,
139 state,
140 state,
140 watchmanclient,
141 watchmanclient,
141 )
142 )
142
143
143 # 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
144 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
145 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
145 # 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
146 # leave the attribute unspecified.
147 # leave the attribute unspecified.
147 testedwith = 'ships-with-hg-core'
148 testedwith = 'ships-with-hg-core'
148
149
149 configtable = {}
150 configtable = {}
150 configitem = registrar.configitem(configtable)
151 configitem = registrar.configitem(configtable)
151
152
152 configitem('fsmonitor', 'mode',
153 configitem('fsmonitor', 'mode',
153 default='on',
154 default='on',
154 )
155 )
155 configitem('fsmonitor', 'walk_on_invalidate',
156 configitem('fsmonitor', 'walk_on_invalidate',
156 default=False,
157 default=False,
157 )
158 )
158 configitem('fsmonitor', 'timeout',
159 configitem('fsmonitor', 'timeout',
159 default='2',
160 default='2',
160 )
161 )
161 configitem('fsmonitor', 'blacklistusers',
162 configitem('fsmonitor', 'blacklistusers',
162 default=list,
163 default=list,
163 )
164 )
164 configitem('fsmonitor', 'watchman_exe',
165 configitem('fsmonitor', 'watchman_exe',
165 default='watchman',
166 default='watchman',
166 )
167 )
167 configitem('fsmonitor', 'verbose',
168 configitem('fsmonitor', 'verbose',
168 default=True,
169 default=True,
169 )
170 )
170 configitem('experimental', 'fsmonitor.transaction_notify',
171 configitem('experimental', 'fsmonitor.transaction_notify',
171 default=False,
172 default=False,
172 )
173 )
173
174
174 # This extension is incompatible with the following blacklisted extensions
175 # This extension is incompatible with the following blacklisted extensions
175 # and will disable itself when encountering one of these:
176 # and will disable itself when encountering one of these:
176 _blacklist = ['largefiles', 'eol']
177 _blacklist = ['largefiles', 'eol']
177
178
179 def debuginstall(ui, fm):
180 fm.write("fsmonitor-watchman",
181 _("fsmonitor checking for watchman binary... (%s)\n"),
182 ui.configpath("fsmonitor", "watchman_exe"))
183 root = tempfile.mkdtemp()
184 c = watchmanclient.client(ui, root)
185 err = None
186 try:
187 v = c.command("version")
188 fm.write("fsmonitor-watchman-version",
189 _(" watchman binary version %s\n"), v["version"])
190 except watchmanclient.Unavailable as e:
191 err = str(e)
192 fm.condwrite(err, "fsmonitor-watchman-error",
193 _(" watchman binary missing or broken: %s\n"), err)
194 return 1 if err else 0
195
178 def _handleunavailable(ui, state, ex):
196 def _handleunavailable(ui, state, ex):
179 """Exception handler for Watchman interaction exceptions"""
197 """Exception handler for Watchman interaction exceptions"""
180 if isinstance(ex, watchmanclient.Unavailable):
198 if isinstance(ex, watchmanclient.Unavailable):
181 # experimental config: fsmonitor.verbose
199 # experimental config: fsmonitor.verbose
182 if ex.warn and ui.configbool('fsmonitor', 'verbose'):
200 if ex.warn and ui.configbool('fsmonitor', 'verbose'):
183 if 'illegal_fstypes' not in str(ex):
201 if 'illegal_fstypes' not in str(ex):
184 ui.warn(str(ex) + '\n')
202 ui.warn(str(ex) + '\n')
185 if ex.invalidate:
203 if ex.invalidate:
186 state.invalidate()
204 state.invalidate()
187 # experimental config: fsmonitor.verbose
205 # experimental config: fsmonitor.verbose
188 if ui.configbool('fsmonitor', 'verbose'):
206 if ui.configbool('fsmonitor', 'verbose'):
189 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
207 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
190 else:
208 else:
191 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
209 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
192
210
193 def _hashignore(ignore):
211 def _hashignore(ignore):
194 """Calculate hash for ignore patterns and filenames
212 """Calculate hash for ignore patterns and filenames
195
213
196 If this information changes between Mercurial invocations, we can't
214 If this information changes between Mercurial invocations, we can't
197 rely on Watchman information anymore and have to re-scan the working
215 rely on Watchman information anymore and have to re-scan the working
198 copy.
216 copy.
199
217
200 """
218 """
201 sha1 = hashlib.sha1()
219 sha1 = hashlib.sha1()
202 sha1.update(repr(ignore))
220 sha1.update(repr(ignore))
203 return sha1.hexdigest()
221 return sha1.hexdigest()
204
222
205 _watchmanencoding = pywatchman.encoding.get_local_encoding()
223 _watchmanencoding = pywatchman.encoding.get_local_encoding()
206 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
224 _fsencoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
207 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
225 _fixencoding = codecs.lookup(_watchmanencoding) != codecs.lookup(_fsencoding)
208
226
209 def _watchmantofsencoding(path):
227 def _watchmantofsencoding(path):
210 """Fix path to match watchman and local filesystem encoding
228 """Fix path to match watchman and local filesystem encoding
211
229
212 watchman's paths encoding can differ from filesystem encoding. For example,
230 watchman's paths encoding can differ from filesystem encoding. For example,
213 on Windows, it's always utf-8.
231 on Windows, it's always utf-8.
214 """
232 """
215 try:
233 try:
216 decoded = path.decode(_watchmanencoding)
234 decoded = path.decode(_watchmanencoding)
217 except UnicodeDecodeError as e:
235 except UnicodeDecodeError as e:
218 raise error.Abort(str(e), hint='watchman encoding error')
236 raise error.Abort(str(e), hint='watchman encoding error')
219
237
220 try:
238 try:
221 encoded = decoded.encode(_fsencoding, 'strict')
239 encoded = decoded.encode(_fsencoding, 'strict')
222 except UnicodeEncodeError as e:
240 except UnicodeEncodeError as e:
223 raise error.Abort(str(e))
241 raise error.Abort(str(e))
224
242
225 return encoded
243 return encoded
226
244
227 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
245 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
228 '''Replacement for dirstate.walk, hooking into Watchman.
246 '''Replacement for dirstate.walk, hooking into Watchman.
229
247
230 Whenever full is False, ignored is False, and the Watchman client is
248 Whenever full is False, ignored is False, and the Watchman client is
231 available, use Watchman combined with saved state to possibly return only a
249 available, use Watchman combined with saved state to possibly return only a
232 subset of files.'''
250 subset of files.'''
233 def bail(reason):
251 def bail(reason):
234 self._ui.debug('fsmonitor: fallback to core status, %s\n' % reason)
252 self._ui.debug('fsmonitor: fallback to core status, %s\n' % reason)
235 return orig(match, subrepos, unknown, ignored, full=True)
253 return orig(match, subrepos, unknown, ignored, full=True)
236
254
237 if full:
255 if full:
238 return bail('full rewalk requested')
256 return bail('full rewalk requested')
239 if ignored:
257 if ignored:
240 return bail('listing ignored files')
258 return bail('listing ignored files')
241 if not self._watchmanclient.available():
259 if not self._watchmanclient.available():
242 return bail('client unavailable')
260 return bail('client unavailable')
243 state = self._fsmonitorstate
261 state = self._fsmonitorstate
244 clock, ignorehash, notefiles = state.get()
262 clock, ignorehash, notefiles = state.get()
245 if not clock:
263 if not clock:
246 if state.walk_on_invalidate:
264 if state.walk_on_invalidate:
247 return bail('no clock')
265 return bail('no clock')
248 # Initial NULL clock value, see
266 # Initial NULL clock value, see
249 # https://facebook.github.io/watchman/docs/clockspec.html
267 # https://facebook.github.io/watchman/docs/clockspec.html
250 clock = 'c:0:0'
268 clock = 'c:0:0'
251 notefiles = []
269 notefiles = []
252
270
253 ignore = self._ignore
271 ignore = self._ignore
254 dirignore = self._dirignore
272 dirignore = self._dirignore
255 if unknown:
273 if unknown:
256 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
274 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
257 # ignore list changed -- can't rely on Watchman state any more
275 # ignore list changed -- can't rely on Watchman state any more
258 if state.walk_on_invalidate:
276 if state.walk_on_invalidate:
259 return bail('ignore rules changed')
277 return bail('ignore rules changed')
260 notefiles = []
278 notefiles = []
261 clock = 'c:0:0'
279 clock = 'c:0:0'
262 else:
280 else:
263 # always ignore
281 # always ignore
264 ignore = util.always
282 ignore = util.always
265 dirignore = util.always
283 dirignore = util.always
266
284
267 matchfn = match.matchfn
285 matchfn = match.matchfn
268 matchalways = match.always()
286 matchalways = match.always()
269 dmap = self._map
287 dmap = self._map
270 if util.safehasattr(dmap, '_map'):
288 if util.safehasattr(dmap, '_map'):
271 # for better performance, directly access the inner dirstate map if the
289 # for better performance, directly access the inner dirstate map if the
272 # standard dirstate implementation is in use.
290 # standard dirstate implementation is in use.
273 dmap = dmap._map
291 dmap = dmap._map
274 nonnormalset = self._map.nonnormalset
292 nonnormalset = self._map.nonnormalset
275
293
276 copymap = self._map.copymap
294 copymap = self._map.copymap
277 getkind = stat.S_IFMT
295 getkind = stat.S_IFMT
278 dirkind = stat.S_IFDIR
296 dirkind = stat.S_IFDIR
279 regkind = stat.S_IFREG
297 regkind = stat.S_IFREG
280 lnkkind = stat.S_IFLNK
298 lnkkind = stat.S_IFLNK
281 join = self._join
299 join = self._join
282 normcase = util.normcase
300 normcase = util.normcase
283 fresh_instance = False
301 fresh_instance = False
284
302
285 exact = skipstep3 = False
303 exact = skipstep3 = False
286 if match.isexact(): # match.exact
304 if match.isexact(): # match.exact
287 exact = True
305 exact = True
288 dirignore = util.always # skip step 2
306 dirignore = util.always # skip step 2
289 elif match.prefix(): # match.match, no patterns
307 elif match.prefix(): # match.match, no patterns
290 skipstep3 = True
308 skipstep3 = True
291
309
292 if not exact and self._checkcase:
310 if not exact and self._checkcase:
293 # note that even though we could receive directory entries, we're only
311 # note that even though we could receive directory entries, we're only
294 # interested in checking if a file with the same name exists. So only
312 # interested in checking if a file with the same name exists. So only
295 # normalize files if possible.
313 # normalize files if possible.
296 normalize = self._normalizefile
314 normalize = self._normalizefile
297 skipstep3 = False
315 skipstep3 = False
298 else:
316 else:
299 normalize = None
317 normalize = None
300
318
301 # step 1: find all explicit files
319 # step 1: find all explicit files
302 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
320 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
303
321
304 skipstep3 = skipstep3 and not (work or dirsnotfound)
322 skipstep3 = skipstep3 and not (work or dirsnotfound)
305 work = [d for d in work if not dirignore(d[0])]
323 work = [d for d in work if not dirignore(d[0])]
306
324
307 if not work and (exact or skipstep3):
325 if not work and (exact or skipstep3):
308 for s in subrepos:
326 for s in subrepos:
309 del results[s]
327 del results[s]
310 del results['.hg']
328 del results['.hg']
311 return results
329 return results
312
330
313 # step 2: query Watchman
331 # step 2: query Watchman
314 try:
332 try:
315 # Use the user-configured timeout for the query.
333 # Use the user-configured timeout for the query.
316 # Add a little slack over the top of the user query to allow for
334 # Add a little slack over the top of the user query to allow for
317 # overheads while transferring the data
335 # overheads while transferring the data
318 self._watchmanclient.settimeout(state.timeout + 0.1)
336 self._watchmanclient.settimeout(state.timeout + 0.1)
319 result = self._watchmanclient.command('query', {
337 result = self._watchmanclient.command('query', {
320 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
338 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
321 'since': clock,
339 'since': clock,
322 'expression': [
340 'expression': [
323 'not', [
341 'not', [
324 'anyof', ['dirname', '.hg'],
342 'anyof', ['dirname', '.hg'],
325 ['name', '.hg', 'wholename']
343 ['name', '.hg', 'wholename']
326 ]
344 ]
327 ],
345 ],
328 'sync_timeout': int(state.timeout * 1000),
346 'sync_timeout': int(state.timeout * 1000),
329 'empty_on_fresh_instance': state.walk_on_invalidate,
347 'empty_on_fresh_instance': state.walk_on_invalidate,
330 })
348 })
331 except Exception as ex:
349 except Exception as ex:
332 _handleunavailable(self._ui, state, ex)
350 _handleunavailable(self._ui, state, ex)
333 self._watchmanclient.clearconnection()
351 self._watchmanclient.clearconnection()
334 return bail('exception during run')
352 return bail('exception during run')
335 else:
353 else:
336 # We need to propagate the last observed clock up so that we
354 # We need to propagate the last observed clock up so that we
337 # can use it for our next query
355 # can use it for our next query
338 state.setlastclock(result['clock'])
356 state.setlastclock(result['clock'])
339 if result['is_fresh_instance']:
357 if result['is_fresh_instance']:
340 if state.walk_on_invalidate:
358 if state.walk_on_invalidate:
341 state.invalidate()
359 state.invalidate()
342 return bail('fresh instance')
360 return bail('fresh instance')
343 fresh_instance = True
361 fresh_instance = True
344 # Ignore any prior noteable files from the state info
362 # Ignore any prior noteable files from the state info
345 notefiles = []
363 notefiles = []
346
364
347 # for file paths which require normalization and we encounter a case
365 # for file paths which require normalization and we encounter a case
348 # collision, we store our own foldmap
366 # collision, we store our own foldmap
349 if normalize:
367 if normalize:
350 foldmap = dict((normcase(k), k) for k in results)
368 foldmap = dict((normcase(k), k) for k in results)
351
369
352 switch_slashes = pycompat.ossep == '\\'
370 switch_slashes = pycompat.ossep == '\\'
353 # The order of the results is, strictly speaking, undefined.
371 # The order of the results is, strictly speaking, undefined.
354 # For case changes on a case insensitive filesystem we may receive
372 # For case changes on a case insensitive filesystem we may receive
355 # two entries, one with exists=True and another with exists=False.
373 # two entries, one with exists=True and another with exists=False.
356 # The exists=True entries in the same response should be interpreted
374 # The exists=True entries in the same response should be interpreted
357 # as being happens-after the exists=False entries due to the way that
375 # as being happens-after the exists=False entries due to the way that
358 # Watchman tracks files. We use this property to reconcile deletes
376 # Watchman tracks files. We use this property to reconcile deletes
359 # for name case changes.
377 # for name case changes.
360 for entry in result['files']:
378 for entry in result['files']:
361 fname = entry['name']
379 fname = entry['name']
362 if _fixencoding:
380 if _fixencoding:
363 fname = _watchmantofsencoding(fname)
381 fname = _watchmantofsencoding(fname)
364 if switch_slashes:
382 if switch_slashes:
365 fname = fname.replace('\\', '/')
383 fname = fname.replace('\\', '/')
366 if normalize:
384 if normalize:
367 normed = normcase(fname)
385 normed = normcase(fname)
368 fname = normalize(fname, True, True)
386 fname = normalize(fname, True, True)
369 foldmap[normed] = fname
387 foldmap[normed] = fname
370 fmode = entry['mode']
388 fmode = entry['mode']
371 fexists = entry['exists']
389 fexists = entry['exists']
372 kind = getkind(fmode)
390 kind = getkind(fmode)
373
391
374 if '/.hg/' in fname or fname.endswith('/.hg'):
392 if '/.hg/' in fname or fname.endswith('/.hg'):
375 return bail('nested-repo-detected')
393 return bail('nested-repo-detected')
376
394
377 if not fexists:
395 if not fexists:
378 # if marked as deleted and we don't already have a change
396 # if marked as deleted and we don't already have a change
379 # record, mark it as deleted. If we already have an entry
397 # record, mark it as deleted. If we already have an entry
380 # for fname then it was either part of walkexplicit or was
398 # for fname then it was either part of walkexplicit or was
381 # an earlier result that was a case change
399 # an earlier result that was a case change
382 if fname not in results and fname in dmap and (
400 if fname not in results and fname in dmap and (
383 matchalways or matchfn(fname)):
401 matchalways or matchfn(fname)):
384 results[fname] = None
402 results[fname] = None
385 elif kind == dirkind:
403 elif kind == dirkind:
386 if fname in dmap and (matchalways or matchfn(fname)):
404 if fname in dmap and (matchalways or matchfn(fname)):
387 results[fname] = None
405 results[fname] = None
388 elif kind == regkind or kind == lnkkind:
406 elif kind == regkind or kind == lnkkind:
389 if fname in dmap:
407 if fname in dmap:
390 if matchalways or matchfn(fname):
408 if matchalways or matchfn(fname):
391 results[fname] = entry
409 results[fname] = entry
392 elif (matchalways or matchfn(fname)) and not ignore(fname):
410 elif (matchalways or matchfn(fname)) and not ignore(fname):
393 results[fname] = entry
411 results[fname] = entry
394 elif fname in dmap and (matchalways or matchfn(fname)):
412 elif fname in dmap and (matchalways or matchfn(fname)):
395 results[fname] = None
413 results[fname] = None
396
414
397 # step 3: query notable files we don't already know about
415 # step 3: query notable files we don't already know about
398 # XXX try not to iterate over the entire dmap
416 # XXX try not to iterate over the entire dmap
399 if normalize:
417 if normalize:
400 # any notable files that have changed case will already be handled
418 # any notable files that have changed case will already be handled
401 # above, so just check membership in the foldmap
419 # above, so just check membership in the foldmap
402 notefiles = set((normalize(f, True, True) for f in notefiles
420 notefiles = set((normalize(f, True, True) for f in notefiles
403 if normcase(f) not in foldmap))
421 if normcase(f) not in foldmap))
404 visit = set((f for f in notefiles if (f not in results and matchfn(f)
422 visit = set((f for f in notefiles if (f not in results and matchfn(f)
405 and (f in dmap or not ignore(f)))))
423 and (f in dmap or not ignore(f)))))
406
424
407 if not fresh_instance:
425 if not fresh_instance:
408 if matchalways:
426 if matchalways:
409 visit.update(f for f in nonnormalset if f not in results)
427 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)
428 visit.update(f for f in copymap if f not in results)
411 else:
429 else:
412 visit.update(f for f in nonnormalset
430 visit.update(f for f in nonnormalset
413 if f not in results and matchfn(f))
431 if f not in results and matchfn(f))
414 visit.update(f for f in copymap
432 visit.update(f for f in copymap
415 if f not in results and matchfn(f))
433 if f not in results and matchfn(f))
416 else:
434 else:
417 if matchalways:
435 if matchalways:
418 visit.update(f for f, st in dmap.iteritems() if f not in results)
436 visit.update(f for f, st in dmap.iteritems() if f not in results)
419 visit.update(f for f in copymap if f not in results)
437 visit.update(f for f in copymap if f not in results)
420 else:
438 else:
421 visit.update(f for f, st in dmap.iteritems()
439 visit.update(f for f, st in dmap.iteritems()
422 if f not in results and matchfn(f))
440 if f not in results and matchfn(f))
423 visit.update(f for f in copymap
441 visit.update(f for f in copymap
424 if f not in results and matchfn(f))
442 if f not in results and matchfn(f))
425
443
426 audit = pathutil.pathauditor(self._root, cached=True).check
444 audit = pathutil.pathauditor(self._root, cached=True).check
427 auditpass = [f for f in visit if audit(f)]
445 auditpass = [f for f in visit if audit(f)]
428 auditpass.sort()
446 auditpass.sort()
429 auditfail = visit.difference(auditpass)
447 auditfail = visit.difference(auditpass)
430 for f in auditfail:
448 for f in auditfail:
431 results[f] = None
449 results[f] = None
432
450
433 nf = iter(auditpass).next
451 nf = iter(auditpass).next
434 for st in util.statfiles([join(f) for f in auditpass]):
452 for st in util.statfiles([join(f) for f in auditpass]):
435 f = nf()
453 f = nf()
436 if st or f in dmap:
454 if st or f in dmap:
437 results[f] = st
455 results[f] = st
438
456
439 for s in subrepos:
457 for s in subrepos:
440 del results[s]
458 del results[s]
441 del results['.hg']
459 del results['.hg']
442 return results
460 return results
443
461
444 def overridestatus(
462 def overridestatus(
445 orig, self, node1='.', node2=None, match=None, ignored=False,
463 orig, self, node1='.', node2=None, match=None, ignored=False,
446 clean=False, unknown=False, listsubrepos=False):
464 clean=False, unknown=False, listsubrepos=False):
447 listignored = ignored
465 listignored = ignored
448 listclean = clean
466 listclean = clean
449 listunknown = unknown
467 listunknown = unknown
450
468
451 def _cmpsets(l1, l2):
469 def _cmpsets(l1, l2):
452 try:
470 try:
453 if 'FSMONITOR_LOG_FILE' in encoding.environ:
471 if 'FSMONITOR_LOG_FILE' in encoding.environ:
454 fn = encoding.environ['FSMONITOR_LOG_FILE']
472 fn = encoding.environ['FSMONITOR_LOG_FILE']
455 f = open(fn, 'wb')
473 f = open(fn, 'wb')
456 else:
474 else:
457 fn = 'fsmonitorfail.log'
475 fn = 'fsmonitorfail.log'
458 f = self.vfs.open(fn, 'wb')
476 f = self.vfs.open(fn, 'wb')
459 except (IOError, OSError):
477 except (IOError, OSError):
460 self.ui.warn(_('warning: unable to write to %s\n') % fn)
478 self.ui.warn(_('warning: unable to write to %s\n') % fn)
461 return
479 return
462
480
463 try:
481 try:
464 for i, (s1, s2) in enumerate(zip(l1, l2)):
482 for i, (s1, s2) in enumerate(zip(l1, l2)):
465 if set(s1) != set(s2):
483 if set(s1) != set(s2):
466 f.write('sets at position %d are unequal\n' % i)
484 f.write('sets at position %d are unequal\n' % i)
467 f.write('watchman returned: %s\n' % s1)
485 f.write('watchman returned: %s\n' % s1)
468 f.write('stat returned: %s\n' % s2)
486 f.write('stat returned: %s\n' % s2)
469 finally:
487 finally:
470 f.close()
488 f.close()
471
489
472 if isinstance(node1, context.changectx):
490 if isinstance(node1, context.changectx):
473 ctx1 = node1
491 ctx1 = node1
474 else:
492 else:
475 ctx1 = self[node1]
493 ctx1 = self[node1]
476 if isinstance(node2, context.changectx):
494 if isinstance(node2, context.changectx):
477 ctx2 = node2
495 ctx2 = node2
478 else:
496 else:
479 ctx2 = self[node2]
497 ctx2 = self[node2]
480
498
481 working = ctx2.rev() is None
499 working = ctx2.rev() is None
482 parentworking = working and ctx1 == self['.']
500 parentworking = working and ctx1 == self['.']
483 match = match or matchmod.always()
501 match = match or matchmod.always()
484
502
485 # Maybe we can use this opportunity to update Watchman's state.
503 # Maybe we can use this opportunity to update Watchman's state.
486 # Mercurial uses workingcommitctx and/or memctx to represent the part of
504 # Mercurial uses workingcommitctx and/or memctx to represent the part of
487 # the workingctx that is to be committed. So don't update the state in
505 # the workingctx that is to be committed. So don't update the state in
488 # that case.
506 # that case.
489 # HG_PENDING is set in the environment when the dirstate is being updated
507 # HG_PENDING is set in the environment when the dirstate is being updated
490 # in the middle of a transaction; we must not update our state in that
508 # in the middle of a transaction; we must not update our state in that
491 # case, or we risk forgetting about changes in the working copy.
509 # case, or we risk forgetting about changes in the working copy.
492 updatestate = (parentworking and match.always() and
510 updatestate = (parentworking and match.always() and
493 not isinstance(ctx2, (context.workingcommitctx,
511 not isinstance(ctx2, (context.workingcommitctx,
494 context.memctx)) and
512 context.memctx)) and
495 'HG_PENDING' not in encoding.environ)
513 'HG_PENDING' not in encoding.environ)
496
514
497 try:
515 try:
498 if self._fsmonitorstate.walk_on_invalidate:
516 if self._fsmonitorstate.walk_on_invalidate:
499 # Use a short timeout to query the current clock. If that
517 # Use a short timeout to query the current clock. If that
500 # takes too long then we assume that the service will be slow
518 # takes too long then we assume that the service will be slow
501 # to answer our query.
519 # to answer our query.
502 # walk_on_invalidate indicates that we prefer to walk the
520 # walk_on_invalidate indicates that we prefer to walk the
503 # tree ourselves because we can ignore portions that Watchman
521 # tree ourselves because we can ignore portions that Watchman
504 # cannot and we tend to be faster in the warmer buffer cache
522 # cannot and we tend to be faster in the warmer buffer cache
505 # cases.
523 # cases.
506 self._watchmanclient.settimeout(0.1)
524 self._watchmanclient.settimeout(0.1)
507 else:
525 else:
508 # Give Watchman more time to potentially complete its walk
526 # Give Watchman more time to potentially complete its walk
509 # and return the initial clock. In this mode we assume that
527 # and return the initial clock. In this mode we assume that
510 # the filesystem will be slower than parsing a potentially
528 # the filesystem will be slower than parsing a potentially
511 # very large Watchman result set.
529 # very large Watchman result set.
512 self._watchmanclient.settimeout(
530 self._watchmanclient.settimeout(
513 self._fsmonitorstate.timeout + 0.1)
531 self._fsmonitorstate.timeout + 0.1)
514 startclock = self._watchmanclient.getcurrentclock()
532 startclock = self._watchmanclient.getcurrentclock()
515 except Exception as ex:
533 except Exception as ex:
516 self._watchmanclient.clearconnection()
534 self._watchmanclient.clearconnection()
517 _handleunavailable(self.ui, self._fsmonitorstate, ex)
535 _handleunavailable(self.ui, self._fsmonitorstate, ex)
518 # boo, Watchman failed. bail
536 # boo, Watchman failed. bail
519 return orig(node1, node2, match, listignored, listclean,
537 return orig(node1, node2, match, listignored, listclean,
520 listunknown, listsubrepos)
538 listunknown, listsubrepos)
521
539
522 if updatestate:
540 if updatestate:
523 # We need info about unknown files. This may make things slower the
541 # We need info about unknown files. This may make things slower the
524 # first time, but whatever.
542 # first time, but whatever.
525 stateunknown = True
543 stateunknown = True
526 else:
544 else:
527 stateunknown = listunknown
545 stateunknown = listunknown
528
546
529 if updatestate:
547 if updatestate:
530 ps = poststatus(startclock)
548 ps = poststatus(startclock)
531 self.addpostdsstatus(ps)
549 self.addpostdsstatus(ps)
532
550
533 r = orig(node1, node2, match, listignored, listclean, stateunknown,
551 r = orig(node1, node2, match, listignored, listclean, stateunknown,
534 listsubrepos)
552 listsubrepos)
535 modified, added, removed, deleted, unknown, ignored, clean = r
553 modified, added, removed, deleted, unknown, ignored, clean = r
536
554
537 if not listunknown:
555 if not listunknown:
538 unknown = []
556 unknown = []
539
557
540 # don't do paranoid checks if we're not going to query Watchman anyway
558 # don't do paranoid checks if we're not going to query Watchman anyway
541 full = listclean or match.traversedir is not None
559 full = listclean or match.traversedir is not None
542 if self._fsmonitorstate.mode == 'paranoid' and not full:
560 if self._fsmonitorstate.mode == 'paranoid' and not full:
543 # run status again and fall back to the old walk this time
561 # run status again and fall back to the old walk this time
544 self.dirstate._fsmonitordisable = True
562 self.dirstate._fsmonitordisable = True
545
563
546 # shut the UI up
564 # shut the UI up
547 quiet = self.ui.quiet
565 quiet = self.ui.quiet
548 self.ui.quiet = True
566 self.ui.quiet = True
549 fout, ferr = self.ui.fout, self.ui.ferr
567 fout, ferr = self.ui.fout, self.ui.ferr
550 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
568 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
551
569
552 try:
570 try:
553 rv2 = orig(
571 rv2 = orig(
554 node1, node2, match, listignored, listclean, listunknown,
572 node1, node2, match, listignored, listclean, listunknown,
555 listsubrepos)
573 listsubrepos)
556 finally:
574 finally:
557 self.dirstate._fsmonitordisable = False
575 self.dirstate._fsmonitordisable = False
558 self.ui.quiet = quiet
576 self.ui.quiet = quiet
559 self.ui.fout, self.ui.ferr = fout, ferr
577 self.ui.fout, self.ui.ferr = fout, ferr
560
578
561 # clean isn't tested since it's set to True above
579 # clean isn't tested since it's set to True above
562 with self.wlock():
580 with self.wlock():
563 _cmpsets(
581 _cmpsets(
564 [modified, added, removed, deleted, unknown, ignored, clean],
582 [modified, added, removed, deleted, unknown, ignored, clean],
565 rv2)
583 rv2)
566 modified, added, removed, deleted, unknown, ignored, clean = rv2
584 modified, added, removed, deleted, unknown, ignored, clean = rv2
567
585
568 return scmutil.status(
586 return scmutil.status(
569 modified, added, removed, deleted, unknown, ignored, clean)
587 modified, added, removed, deleted, unknown, ignored, clean)
570
588
571 class poststatus(object):
589 class poststatus(object):
572 def __init__(self, startclock):
590 def __init__(self, startclock):
573 self._startclock = startclock
591 self._startclock = startclock
574
592
575 def __call__(self, wctx, status):
593 def __call__(self, wctx, status):
576 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
594 clock = wctx.repo()._fsmonitorstate.getlastclock() or self._startclock
577 hashignore = _hashignore(wctx.repo().dirstate._ignore)
595 hashignore = _hashignore(wctx.repo().dirstate._ignore)
578 notefiles = (status.modified + status.added + status.removed +
596 notefiles = (status.modified + status.added + status.removed +
579 status.deleted + status.unknown)
597 status.deleted + status.unknown)
580 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
598 wctx.repo()._fsmonitorstate.set(clock, hashignore, notefiles)
581
599
582 def makedirstate(repo, dirstate):
600 def makedirstate(repo, dirstate):
583 class fsmonitordirstate(dirstate.__class__):
601 class fsmonitordirstate(dirstate.__class__):
584 def _fsmonitorinit(self, repo):
602 def _fsmonitorinit(self, repo):
585 # _fsmonitordisable is used in paranoid mode
603 # _fsmonitordisable is used in paranoid mode
586 self._fsmonitordisable = False
604 self._fsmonitordisable = False
587 self._fsmonitorstate = repo._fsmonitorstate
605 self._fsmonitorstate = repo._fsmonitorstate
588 self._watchmanclient = repo._watchmanclient
606 self._watchmanclient = repo._watchmanclient
589 self._repo = weakref.proxy(repo)
607 self._repo = weakref.proxy(repo)
590
608
591 def walk(self, *args, **kwargs):
609 def walk(self, *args, **kwargs):
592 orig = super(fsmonitordirstate, self).walk
610 orig = super(fsmonitordirstate, self).walk
593 if self._fsmonitordisable:
611 if self._fsmonitordisable:
594 return orig(*args, **kwargs)
612 return orig(*args, **kwargs)
595 return overridewalk(orig, self, *args, **kwargs)
613 return overridewalk(orig, self, *args, **kwargs)
596
614
597 def rebuild(self, *args, **kwargs):
615 def rebuild(self, *args, **kwargs):
598 self._fsmonitorstate.invalidate()
616 self._fsmonitorstate.invalidate()
599 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
617 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
600
618
601 def invalidate(self, *args, **kwargs):
619 def invalidate(self, *args, **kwargs):
602 self._fsmonitorstate.invalidate()
620 self._fsmonitorstate.invalidate()
603 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
621 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
604
622
605 dirstate.__class__ = fsmonitordirstate
623 dirstate.__class__ = fsmonitordirstate
606 dirstate._fsmonitorinit(repo)
624 dirstate._fsmonitorinit(repo)
607
625
608 def wrapdirstate(orig, self):
626 def wrapdirstate(orig, self):
609 ds = orig(self)
627 ds = orig(self)
610 # only override the dirstate when Watchman is available for the repo
628 # only override the dirstate when Watchman is available for the repo
611 if util.safehasattr(self, '_fsmonitorstate'):
629 if util.safehasattr(self, '_fsmonitorstate'):
612 makedirstate(self, ds)
630 makedirstate(self, ds)
613 return ds
631 return ds
614
632
615 def extsetup(ui):
633 def extsetup(ui):
616 extensions.wrapfilecache(
634 extensions.wrapfilecache(
617 localrepo.localrepository, 'dirstate', wrapdirstate)
635 localrepo.localrepository, 'dirstate', wrapdirstate)
618 if pycompat.isdarwin:
636 if pycompat.isdarwin:
619 # An assist for avoiding the dangling-symlink fsevents bug
637 # An assist for avoiding the dangling-symlink fsevents bug
620 extensions.wrapfunction(os, 'symlink', wrapsymlink)
638 extensions.wrapfunction(os, 'symlink', wrapsymlink)
621
639
622 extensions.wrapfunction(merge, 'update', wrapupdate)
640 extensions.wrapfunction(merge, 'update', wrapupdate)
623
641
624 def wrapsymlink(orig, source, link_name):
642 def wrapsymlink(orig, source, link_name):
625 ''' if we create a dangling symlink, also touch the parent dir
643 ''' if we create a dangling symlink, also touch the parent dir
626 to encourage fsevents notifications to work more correctly '''
644 to encourage fsevents notifications to work more correctly '''
627 try:
645 try:
628 return orig(source, link_name)
646 return orig(source, link_name)
629 finally:
647 finally:
630 try:
648 try:
631 os.utime(os.path.dirname(link_name), None)
649 os.utime(os.path.dirname(link_name), None)
632 except OSError:
650 except OSError:
633 pass
651 pass
634
652
635 class state_update(object):
653 class state_update(object):
636 ''' This context manager is responsible for dispatching the state-enter
654 ''' This context manager is responsible for dispatching the state-enter
637 and state-leave signals to the watchman service. The enter and leave
655 and state-leave signals to the watchman service. The enter and leave
638 methods can be invoked manually (for scenarios where context manager
656 methods can be invoked manually (for scenarios where context manager
639 semantics are not possible). If parameters oldnode and newnode are None,
657 semantics are not possible). If parameters oldnode and newnode are None,
640 they will be populated based on current working copy in enter and
658 they will be populated based on current working copy in enter and
641 leave, respectively. Similarly, if the distance is none, it will be
659 leave, respectively. Similarly, if the distance is none, it will be
642 calculated based on the oldnode and newnode in the leave method.'''
660 calculated based on the oldnode and newnode in the leave method.'''
643
661
644 def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
662 def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
645 partial=False):
663 partial=False):
646 self.repo = repo.unfiltered()
664 self.repo = repo.unfiltered()
647 self.name = name
665 self.name = name
648 self.oldnode = oldnode
666 self.oldnode = oldnode
649 self.newnode = newnode
667 self.newnode = newnode
650 self.distance = distance
668 self.distance = distance
651 self.partial = partial
669 self.partial = partial
652 self._lock = None
670 self._lock = None
653 self.need_leave = False
671 self.need_leave = False
654
672
655 def __enter__(self):
673 def __enter__(self):
656 self.enter()
674 self.enter()
657
675
658 def enter(self):
676 def enter(self):
659 # Make sure we have a wlock prior to sending notifications to watchman.
677 # Make sure we have a wlock prior to sending notifications to watchman.
660 # We don't want to race with other actors. In the update case,
678 # We don't want to race with other actors. In the update case,
661 # merge.update is going to take the wlock almost immediately. We are
679 # merge.update is going to take the wlock almost immediately. We are
662 # effectively extending the lock around several short sanity checks.
680 # effectively extending the lock around several short sanity checks.
663 if self.oldnode is None:
681 if self.oldnode is None:
664 self.oldnode = self.repo['.'].node()
682 self.oldnode = self.repo['.'].node()
665
683
666 if self.repo.currentwlock() is None:
684 if self.repo.currentwlock() is None:
667 if util.safehasattr(self.repo, 'wlocknostateupdate'):
685 if util.safehasattr(self.repo, 'wlocknostateupdate'):
668 self._lock = self.repo.wlocknostateupdate()
686 self._lock = self.repo.wlocknostateupdate()
669 else:
687 else:
670 self._lock = self.repo.wlock()
688 self._lock = self.repo.wlock()
671 self.need_leave = self._state(
689 self.need_leave = self._state(
672 'state-enter',
690 'state-enter',
673 hex(self.oldnode))
691 hex(self.oldnode))
674 return self
692 return self
675
693
676 def __exit__(self, type_, value, tb):
694 def __exit__(self, type_, value, tb):
677 abort = True if type_ else False
695 abort = True if type_ else False
678 self.exit(abort=abort)
696 self.exit(abort=abort)
679
697
680 def exit(self, abort=False):
698 def exit(self, abort=False):
681 try:
699 try:
682 if self.need_leave:
700 if self.need_leave:
683 status = 'failed' if abort else 'ok'
701 status = 'failed' if abort else 'ok'
684 if self.newnode is None:
702 if self.newnode is None:
685 self.newnode = self.repo['.'].node()
703 self.newnode = self.repo['.'].node()
686 if self.distance is None:
704 if self.distance is None:
687 self.distance = calcdistance(
705 self.distance = calcdistance(
688 self.repo, self.oldnode, self.newnode)
706 self.repo, self.oldnode, self.newnode)
689 self._state(
707 self._state(
690 'state-leave',
708 'state-leave',
691 hex(self.newnode),
709 hex(self.newnode),
692 status=status)
710 status=status)
693 finally:
711 finally:
694 self.need_leave = False
712 self.need_leave = False
695 if self._lock:
713 if self._lock:
696 self._lock.release()
714 self._lock.release()
697
715
698 def _state(self, cmd, commithash, status='ok'):
716 def _state(self, cmd, commithash, status='ok'):
699 if not util.safehasattr(self.repo, '_watchmanclient'):
717 if not util.safehasattr(self.repo, '_watchmanclient'):
700 return False
718 return False
701 try:
719 try:
702 self.repo._watchmanclient.command(cmd, {
720 self.repo._watchmanclient.command(cmd, {
703 'name': self.name,
721 'name': self.name,
704 'metadata': {
722 'metadata': {
705 # the target revision
723 # the target revision
706 'rev': commithash,
724 'rev': commithash,
707 # approximate number of commits between current and target
725 # approximate number of commits between current and target
708 'distance': self.distance if self.distance else 0,
726 'distance': self.distance if self.distance else 0,
709 # success/failure (only really meaningful for state-leave)
727 # success/failure (only really meaningful for state-leave)
710 'status': status,
728 'status': status,
711 # whether the working copy parent is changing
729 # whether the working copy parent is changing
712 'partial': self.partial,
730 'partial': self.partial,
713 }})
731 }})
714 return True
732 return True
715 except Exception as e:
733 except Exception as e:
716 # Swallow any errors; fire and forget
734 # Swallow any errors; fire and forget
717 self.repo.ui.log(
735 self.repo.ui.log(
718 'watchman', 'Exception %s while running %s\n', e, cmd)
736 'watchman', 'Exception %s while running %s\n', e, cmd)
719 return False
737 return False
720
738
721 # Estimate the distance between two nodes
739 # Estimate the distance between two nodes
722 def calcdistance(repo, oldnode, newnode):
740 def calcdistance(repo, oldnode, newnode):
723 anc = repo.changelog.ancestor(oldnode, newnode)
741 anc = repo.changelog.ancestor(oldnode, newnode)
724 ancrev = repo[anc].rev()
742 ancrev = repo[anc].rev()
725 distance = (abs(repo[oldnode].rev() - ancrev)
743 distance = (abs(repo[oldnode].rev() - ancrev)
726 + abs(repo[newnode].rev() - ancrev))
744 + abs(repo[newnode].rev() - ancrev))
727 return distance
745 return distance
728
746
729 # Bracket working copy updates with calls to the watchman state-enter
747 # Bracket working copy updates with calls to the watchman state-enter
730 # and state-leave commands. This allows clients to perform more intelligent
748 # and state-leave commands. This allows clients to perform more intelligent
731 # settling during bulk file change scenarios
749 # settling during bulk file change scenarios
732 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
750 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
733 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
751 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
734 mergeancestor=False, labels=None, matcher=None, **kwargs):
752 mergeancestor=False, labels=None, matcher=None, **kwargs):
735
753
736 distance = 0
754 distance = 0
737 partial = True
755 partial = True
738 oldnode = repo['.'].node()
756 oldnode = repo['.'].node()
739 newnode = repo[node].node()
757 newnode = repo[node].node()
740 if matcher is None or matcher.always():
758 if matcher is None or matcher.always():
741 partial = False
759 partial = False
742 distance = calcdistance(repo.unfiltered(), oldnode, newnode)
760 distance = calcdistance(repo.unfiltered(), oldnode, newnode)
743
761
744 with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
762 with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
745 distance=distance, partial=partial):
763 distance=distance, partial=partial):
746 return orig(
764 return orig(
747 repo, node, branchmerge, force, ancestor, mergeancestor,
765 repo, node, branchmerge, force, ancestor, mergeancestor,
748 labels, matcher, **kwargs)
766 labels, matcher, **kwargs)
749
767
750 def repo_has_depth_one_nested_repo(repo):
768 def repo_has_depth_one_nested_repo(repo):
751 for f in repo.wvfs.listdir():
769 for f in repo.wvfs.listdir():
752 if os.path.isdir(os.path.join(repo.root, f, '.hg')):
770 if os.path.isdir(os.path.join(repo.root, f, '.hg')):
753 msg = 'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
771 msg = 'fsmonitor: sub-repository %r detected, fsmonitor disabled\n'
754 repo.ui.debug(msg % f)
772 repo.ui.debug(msg % f)
755 return True
773 return True
756 return False
774 return False
757
775
758 def reposetup(ui, repo):
776 def reposetup(ui, repo):
759 # We don't work with largefiles or inotify
777 # We don't work with largefiles or inotify
760 exts = extensions.enabled()
778 exts = extensions.enabled()
761 for ext in _blacklist:
779 for ext in _blacklist:
762 if ext in exts:
780 if ext in exts:
763 ui.warn(_('The fsmonitor extension is incompatible with the %s '
781 ui.warn(_('The fsmonitor extension is incompatible with the %s '
764 'extension and has been disabled.\n') % ext)
782 'extension and has been disabled.\n') % ext)
765 return
783 return
766
784
767 if repo.local():
785 if repo.local():
768 # We don't work with subrepos either.
786 # We don't work with subrepos either.
769 #
787 #
770 # if repo[None].substate can cause a dirstate parse, which is too
788 # if repo[None].substate can cause a dirstate parse, which is too
771 # slow. Instead, look for a file called hgsubstate,
789 # slow. Instead, look for a file called hgsubstate,
772 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
790 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
773 return
791 return
774
792
775 if repo_has_depth_one_nested_repo(repo):
793 if repo_has_depth_one_nested_repo(repo):
776 return
794 return
777
795
778 fsmonitorstate = state.state(repo)
796 fsmonitorstate = state.state(repo)
779 if fsmonitorstate.mode == 'off':
797 if fsmonitorstate.mode == 'off':
780 return
798 return
781
799
782 try:
800 try:
783 client = watchmanclient.client(repo.ui, repo._root)
801 client = watchmanclient.client(repo.ui, repo._root)
784 except Exception as ex:
802 except Exception as ex:
785 _handleunavailable(ui, fsmonitorstate, ex)
803 _handleunavailable(ui, fsmonitorstate, ex)
786 return
804 return
787
805
788 repo._fsmonitorstate = fsmonitorstate
806 repo._fsmonitorstate = fsmonitorstate
789 repo._watchmanclient = client
807 repo._watchmanclient = client
790
808
791 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
809 dirstate, cached = localrepo.isfilecached(repo, 'dirstate')
792 if cached:
810 if cached:
793 # at this point since fsmonitorstate wasn't present,
811 # at this point since fsmonitorstate wasn't present,
794 # repo.dirstate is not a fsmonitordirstate
812 # repo.dirstate is not a fsmonitordirstate
795 makedirstate(repo, dirstate)
813 makedirstate(repo, dirstate)
796
814
797 class fsmonitorrepo(repo.__class__):
815 class fsmonitorrepo(repo.__class__):
798 def status(self, *args, **kwargs):
816 def status(self, *args, **kwargs):
799 orig = super(fsmonitorrepo, self).status
817 orig = super(fsmonitorrepo, self).status
800 return overridestatus(orig, self, *args, **kwargs)
818 return overridestatus(orig, self, *args, **kwargs)
801
819
802 def wlocknostateupdate(self, *args, **kwargs):
820 def wlocknostateupdate(self, *args, **kwargs):
803 return super(fsmonitorrepo, self).wlock(*args, **kwargs)
821 return super(fsmonitorrepo, self).wlock(*args, **kwargs)
804
822
805 def wlock(self, *args, **kwargs):
823 def wlock(self, *args, **kwargs):
806 l = super(fsmonitorrepo, self).wlock(*args, **kwargs)
824 l = super(fsmonitorrepo, self).wlock(*args, **kwargs)
807 if not ui.configbool(
825 if not ui.configbool(
808 "experimental", "fsmonitor.transaction_notify"):
826 "experimental", "fsmonitor.transaction_notify"):
809 return l
827 return l
810 if l.held != 1:
828 if l.held != 1:
811 return l
829 return l
812 origrelease = l.releasefn
830 origrelease = l.releasefn
813
831
814 def staterelease():
832 def staterelease():
815 if origrelease:
833 if origrelease:
816 origrelease()
834 origrelease()
817 if l.stateupdate:
835 if l.stateupdate:
818 l.stateupdate.exit()
836 l.stateupdate.exit()
819 l.stateupdate = None
837 l.stateupdate = None
820
838
821 try:
839 try:
822 l.stateupdate = None
840 l.stateupdate = None
823 l.stateupdate = state_update(self, name="hg.transaction")
841 l.stateupdate = state_update(self, name="hg.transaction")
824 l.stateupdate.enter()
842 l.stateupdate.enter()
825 l.releasefn = staterelease
843 l.releasefn = staterelease
826 except Exception as e:
844 except Exception as e:
827 # Swallow any errors; fire and forget
845 # Swallow any errors; fire and forget
828 self.ui.log(
846 self.ui.log(
829 'watchman', 'Exception in state update %s\n', e)
847 'watchman', 'Exception in state update %s\n', e)
830 return l
848 return l
831
849
832 repo.__class__ = fsmonitorrepo
850 repo.__class__ = fsmonitorrepo
@@ -1,268 +1,278 b''
1 hg debuginstall
1 hg debuginstall
2 $ hg debuginstall
2 $ hg debuginstall
3 checking encoding (ascii)...
3 checking encoding (ascii)...
4 checking Python executable (*) (glob)
4 checking Python executable (*) (glob)
5 checking Python version (2.*) (glob) (no-py3 !)
5 checking Python version (2.*) (glob) (no-py3 !)
6 checking Python version (3.*) (glob) (py3 !)
6 checking Python version (3.*) (glob) (py3 !)
7 checking Python lib (.*[Ll]ib.*)... (re)
7 checking Python lib (.*[Ll]ib.*)... (re)
8 checking Python security support (*) (glob)
8 checking Python security support (*) (glob)
9 TLS 1.2 not supported by Python install; network connections lack modern security (?)
9 TLS 1.2 not supported by Python install; network connections lack modern security (?)
10 SNI not supported by Python install; may have connectivity issues with some servers (?)
10 SNI not supported by Python install; may have connectivity issues with some servers (?)
11 checking Mercurial version (*) (glob)
11 checking Mercurial version (*) (glob)
12 checking Mercurial custom build (*) (glob)
12 checking Mercurial custom build (*) (glob)
13 checking module policy (*) (glob)
13 checking module policy (*) (glob)
14 checking installed modules (*mercurial)... (glob)
14 checking installed modules (*mercurial)... (glob)
15 checking registered compression engines (*zlib*) (glob)
15 checking registered compression engines (*zlib*) (glob)
16 checking available compression engines (*zlib*) (glob)
16 checking available compression engines (*zlib*) (glob)
17 checking available compression engines for wire protocol (*zlib*) (glob)
17 checking available compression engines for wire protocol (*zlib*) (glob)
18 checking "re2" regexp engine \((available|missing)\) (re)
18 checking "re2" regexp engine \((available|missing)\) (re)
19 checking templates (*mercurial?templates)... (glob)
19 checking templates (*mercurial?templates)... (glob)
20 checking default template (*mercurial?templates?map-cmdline.default) (glob)
20 checking default template (*mercurial?templates?map-cmdline.default) (glob)
21 checking commit editor... (*) (glob)
21 checking commit editor... (*) (glob)
22 checking username (test)
22 checking username (test)
23 no problems detected
23 no problems detected
24
24
25 hg debuginstall JSON
25 hg debuginstall JSON
26 $ hg debuginstall -Tjson | sed 's|\\\\|\\|g'
26 $ hg debuginstall -Tjson | sed 's|\\\\|\\|g'
27 [
27 [
28 {
28 {
29 "compengines": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
29 "compengines": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
30 "compenginesavail": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
30 "compenginesavail": ["bz2", "bz2truncated", "none", "zlib"*], (glob)
31 "compenginesserver": [*"zlib"*], (glob)
31 "compenginesserver": [*"zlib"*], (glob)
32 "defaulttemplate": "*mercurial?templates?map-cmdline.default", (glob)
32 "defaulttemplate": "*mercurial?templates?map-cmdline.default", (glob)
33 "defaulttemplateerror": null,
33 "defaulttemplateerror": null,
34 "defaulttemplatenotfound": "default",
34 "defaulttemplatenotfound": "default",
35 "editor": "*", (glob)
35 "editor": "*", (glob)
36 "editornotfound": false,
36 "editornotfound": false,
37 "encoding": "ascii",
37 "encoding": "ascii",
38 "encodingerror": null,
38 "encodingerror": null,
39 "extensionserror": null, (no-pure !)
39 "extensionserror": null, (no-pure !)
40 "hgmodulepolicy": "*", (glob)
40 "hgmodulepolicy": "*", (glob)
41 "hgmodules": "*mercurial", (glob)
41 "hgmodules": "*mercurial", (glob)
42 "hgver": "*", (glob)
42 "hgver": "*", (glob)
43 "hgverextra": "*", (glob)
43 "hgverextra": "*", (glob)
44 "problems": 0,
44 "problems": 0,
45 "pythonexe": "*", (glob)
45 "pythonexe": "*", (glob)
46 "pythonlib": "*", (glob)
46 "pythonlib": "*", (glob)
47 "pythonsecurity": [*], (glob)
47 "pythonsecurity": [*], (glob)
48 "pythonver": "*.*.*", (glob)
48 "pythonver": "*.*.*", (glob)
49 "re2": (true|false), (re)
49 "re2": (true|false), (re)
50 "templatedirs": "*mercurial?templates", (glob)
50 "templatedirs": "*mercurial?templates", (glob)
51 "username": "test",
51 "username": "test",
52 "usernameerror": null,
52 "usernameerror": null,
53 "vinotfound": false
53 "vinotfound": false
54 }
54 }
55 ]
55 ]
56
56
57 hg debuginstall with no username
57 hg debuginstall with no username
58 $ HGUSER= hg debuginstall
58 $ HGUSER= hg debuginstall
59 checking encoding (ascii)...
59 checking encoding (ascii)...
60 checking Python executable (*) (glob)
60 checking Python executable (*) (glob)
61 checking Python version (2.*) (glob) (no-py3 !)
61 checking Python version (2.*) (glob) (no-py3 !)
62 checking Python version (3.*) (glob) (py3 !)
62 checking Python version (3.*) (glob) (py3 !)
63 checking Python lib (.*[Ll]ib.*)... (re)
63 checking Python lib (.*[Ll]ib.*)... (re)
64 checking Python security support (*) (glob)
64 checking Python security support (*) (glob)
65 TLS 1.2 not supported by Python install; network connections lack modern security (?)
65 TLS 1.2 not supported by Python install; network connections lack modern security (?)
66 SNI not supported by Python install; may have connectivity issues with some servers (?)
66 SNI not supported by Python install; may have connectivity issues with some servers (?)
67 checking Mercurial version (*) (glob)
67 checking Mercurial version (*) (glob)
68 checking Mercurial custom build (*) (glob)
68 checking Mercurial custom build (*) (glob)
69 checking module policy (*) (glob)
69 checking module policy (*) (glob)
70 checking installed modules (*mercurial)... (glob)
70 checking installed modules (*mercurial)... (glob)
71 checking registered compression engines (*zlib*) (glob)
71 checking registered compression engines (*zlib*) (glob)
72 checking available compression engines (*zlib*) (glob)
72 checking available compression engines (*zlib*) (glob)
73 checking available compression engines for wire protocol (*zlib*) (glob)
73 checking available compression engines for wire protocol (*zlib*) (glob)
74 checking "re2" regexp engine \((available|missing)\) (re)
74 checking "re2" regexp engine \((available|missing)\) (re)
75 checking templates (*mercurial?templates)... (glob)
75 checking templates (*mercurial?templates)... (glob)
76 checking default template (*mercurial?templates?map-cmdline.default) (glob)
76 checking default template (*mercurial?templates?map-cmdline.default) (glob)
77 checking commit editor... (*) (glob)
77 checking commit editor... (*) (glob)
78 checking username...
78 checking username...
79 no username supplied
79 no username supplied
80 (specify a username in your configuration file)
80 (specify a username in your configuration file)
81 1 problems detected, please check your install!
81 1 problems detected, please check your install!
82 [1]
82 [1]
83
83
84 hg debuginstall with invalid encoding
84 hg debuginstall with invalid encoding
85 $ HGENCODING=invalidenc hg debuginstall | grep encoding
85 $ HGENCODING=invalidenc hg debuginstall | grep encoding
86 checking encoding (invalidenc)...
86 checking encoding (invalidenc)...
87 unknown encoding: invalidenc
87 unknown encoding: invalidenc
88
88
89 exception message in JSON
89 exception message in JSON
90
90
91 $ HGENCODING=invalidenc HGUSER= hg debuginstall -Tjson | grep error
91 $ HGENCODING=invalidenc HGUSER= hg debuginstall -Tjson | grep error
92 "defaulttemplateerror": null,
92 "defaulttemplateerror": null,
93 "encodingerror": "unknown encoding: invalidenc",
93 "encodingerror": "unknown encoding: invalidenc",
94 "extensionserror": null, (no-pure !)
94 "extensionserror": null, (no-pure !)
95 "usernameerror": "no username supplied",
95 "usernameerror": "no username supplied",
96
96
97 path variables are expanded (~ is the same as $TESTTMP)
97 path variables are expanded (~ is the same as $TESTTMP)
98 $ mkdir tools
98 $ mkdir tools
99 $ touch tools/testeditor.exe
99 $ touch tools/testeditor.exe
100 #if execbit
100 #if execbit
101 $ chmod 755 tools/testeditor.exe
101 $ chmod 755 tools/testeditor.exe
102 #endif
102 #endif
103 $ HGEDITOR="~/tools/testeditor.exe" hg debuginstall
103 $ HGEDITOR="~/tools/testeditor.exe" hg debuginstall
104 checking encoding (ascii)...
104 checking encoding (ascii)...
105 checking Python executable (*) (glob)
105 checking Python executable (*) (glob)
106 checking Python version (2.*) (glob) (no-py3 !)
106 checking Python version (2.*) (glob) (no-py3 !)
107 checking Python version (3.*) (glob) (py3 !)
107 checking Python version (3.*) (glob) (py3 !)
108 checking Python lib (.*[Ll]ib.*)... (re)
108 checking Python lib (.*[Ll]ib.*)... (re)
109 checking Python security support (*) (glob)
109 checking Python security support (*) (glob)
110 TLS 1.2 not supported by Python install; network connections lack modern security (?)
110 TLS 1.2 not supported by Python install; network connections lack modern security (?)
111 SNI not supported by Python install; may have connectivity issues with some servers (?)
111 SNI not supported by Python install; may have connectivity issues with some servers (?)
112 checking Mercurial version (*) (glob)
112 checking Mercurial version (*) (glob)
113 checking Mercurial custom build (*) (glob)
113 checking Mercurial custom build (*) (glob)
114 checking module policy (*) (glob)
114 checking module policy (*) (glob)
115 checking installed modules (*mercurial)... (glob)
115 checking installed modules (*mercurial)... (glob)
116 checking registered compression engines (*zlib*) (glob)
116 checking registered compression engines (*zlib*) (glob)
117 checking available compression engines (*zlib*) (glob)
117 checking available compression engines (*zlib*) (glob)
118 checking available compression engines for wire protocol (*zlib*) (glob)
118 checking available compression engines for wire protocol (*zlib*) (glob)
119 checking "re2" regexp engine \((available|missing)\) (re)
119 checking "re2" regexp engine \((available|missing)\) (re)
120 checking templates (*mercurial?templates)... (glob)
120 checking templates (*mercurial?templates)... (glob)
121 checking default template (*mercurial?templates?map-cmdline.default) (glob)
121 checking default template (*mercurial?templates?map-cmdline.default) (glob)
122 checking commit editor... ($TESTTMP/tools/testeditor.exe)
122 checking commit editor... ($TESTTMP/tools/testeditor.exe)
123 checking username (test)
123 checking username (test)
124 no problems detected
124 no problems detected
125
125
126 print out the binary post-shlexsplit in the error message when commit editor is
126 print out the binary post-shlexsplit in the error message when commit editor is
127 not found (this is intentionally using backslashes to mimic a windows usecase).
127 not found (this is intentionally using backslashes to mimic a windows usecase).
128 $ HGEDITOR="c:\foo\bar\baz.exe -y -z" hg debuginstall
128 $ HGEDITOR="c:\foo\bar\baz.exe -y -z" hg debuginstall
129 checking encoding (ascii)...
129 checking encoding (ascii)...
130 checking Python executable (*) (glob)
130 checking Python executable (*) (glob)
131 checking Python version (2.*) (glob) (no-py3 !)
131 checking Python version (2.*) (glob) (no-py3 !)
132 checking Python version (3.*) (glob) (py3 !)
132 checking Python version (3.*) (glob) (py3 !)
133 checking Python lib (.*[Ll]ib.*)... (re)
133 checking Python lib (.*[Ll]ib.*)... (re)
134 checking Python security support (*) (glob)
134 checking Python security support (*) (glob)
135 TLS 1.2 not supported by Python install; network connections lack modern security (?)
135 TLS 1.2 not supported by Python install; network connections lack modern security (?)
136 SNI not supported by Python install; may have connectivity issues with some servers (?)
136 SNI not supported by Python install; may have connectivity issues with some servers (?)
137 checking Mercurial version (*) (glob)
137 checking Mercurial version (*) (glob)
138 checking Mercurial custom build (*) (glob)
138 checking Mercurial custom build (*) (glob)
139 checking module policy (*) (glob)
139 checking module policy (*) (glob)
140 checking installed modules (*mercurial)... (glob)
140 checking installed modules (*mercurial)... (glob)
141 checking registered compression engines (*zlib*) (glob)
141 checking registered compression engines (*zlib*) (glob)
142 checking available compression engines (*zlib*) (glob)
142 checking available compression engines (*zlib*) (glob)
143 checking available compression engines for wire protocol (*zlib*) (glob)
143 checking available compression engines for wire protocol (*zlib*) (glob)
144 checking "re2" regexp engine \((available|missing)\) (re)
144 checking "re2" regexp engine \((available|missing)\) (re)
145 checking templates (*mercurial?templates)... (glob)
145 checking templates (*mercurial?templates)... (glob)
146 checking default template (*mercurial?templates?map-cmdline.default) (glob)
146 checking default template (*mercurial?templates?map-cmdline.default) (glob)
147 checking commit editor... (c:\foo\bar\baz.exe) (windows !)
147 checking commit editor... (c:\foo\bar\baz.exe) (windows !)
148 Can't find editor 'c:\foo\bar\baz.exe' in PATH (windows !)
148 Can't find editor 'c:\foo\bar\baz.exe' in PATH (windows !)
149 checking commit editor... (c:foobarbaz.exe) (no-windows !)
149 checking commit editor... (c:foobarbaz.exe) (no-windows !)
150 Can't find editor 'c:foobarbaz.exe' in PATH (no-windows !)
150 Can't find editor 'c:foobarbaz.exe' in PATH (no-windows !)
151 (specify a commit editor in your configuration file)
151 (specify a commit editor in your configuration file)
152 checking username (test)
152 checking username (test)
153 1 problems detected, please check your install!
153 1 problems detected, please check your install!
154 [1]
154 [1]
155
155
156 debuginstall extension support
157 $ hg debuginstall --config extensions.fsmonitor= --config fsmonitor.watchman_exe=false | grep atchman
158 fsmonitor checking for watchman binary... (false)
159 watchman binary missing or broken: warning: Watchman unavailable: watchman exited with code 1
160 Verify the json works too:
161 $ hg debuginstall --config extensions.fsmonitor= --config fsmonitor.watchman_exe=false -Tjson | grep atchman
162 "fsmonitor-watchman": "false",
163 "fsmonitor-watchman-error": "warning: Watchman unavailable: watchman exited with code 1",
164
165
156 #if test-repo
166 #if test-repo
157 $ . "$TESTDIR/helpers-testrepo.sh"
167 $ . "$TESTDIR/helpers-testrepo.sh"
158
168
159 $ cat >> wixxml.py << EOF
169 $ cat >> wixxml.py << EOF
160 > import os
170 > import os
161 > import subprocess
171 > import subprocess
162 > import sys
172 > import sys
163 > import xml.etree.ElementTree as ET
173 > import xml.etree.ElementTree as ET
164 > from mercurial import pycompat
174 > from mercurial import pycompat
165 >
175 >
166 > # MSYS mangles the path if it expands $TESTDIR
176 > # MSYS mangles the path if it expands $TESTDIR
167 > testdir = os.environ['TESTDIR']
177 > testdir = os.environ['TESTDIR']
168 > ns = {'wix' : 'http://schemas.microsoft.com/wix/2006/wi'}
178 > ns = {'wix' : 'http://schemas.microsoft.com/wix/2006/wi'}
169 >
179 >
170 > def directory(node, relpath):
180 > def directory(node, relpath):
171 > '''generator of files in the xml node, rooted at relpath'''
181 > '''generator of files in the xml node, rooted at relpath'''
172 > dirs = node.findall('./{%(wix)s}Directory' % ns)
182 > dirs = node.findall('./{%(wix)s}Directory' % ns)
173 >
183 >
174 > for d in dirs:
184 > for d in dirs:
175 > for subfile in directory(d, relpath + d.attrib['Name'] + '/'):
185 > for subfile in directory(d, relpath + d.attrib['Name'] + '/'):
176 > yield subfile
186 > yield subfile
177 >
187 >
178 > files = node.findall('./{%(wix)s}Component/{%(wix)s}File' % ns)
188 > files = node.findall('./{%(wix)s}Component/{%(wix)s}File' % ns)
179 >
189 >
180 > for f in files:
190 > for f in files:
181 > yield pycompat.sysbytes(relpath + f.attrib['Name'])
191 > yield pycompat.sysbytes(relpath + f.attrib['Name'])
182 >
192 >
183 > def hgdirectory(relpath):
193 > def hgdirectory(relpath):
184 > '''generator of tracked files, rooted at relpath'''
194 > '''generator of tracked files, rooted at relpath'''
185 > hgdir = "%s/../mercurial" % (testdir)
195 > hgdir = "%s/../mercurial" % (testdir)
186 > args = ['hg', '--cwd', hgdir, 'files', relpath]
196 > args = ['hg', '--cwd', hgdir, 'files', relpath]
187 > proc = subprocess.Popen(args, stdout=subprocess.PIPE,
197 > proc = subprocess.Popen(args, stdout=subprocess.PIPE,
188 > stderr=subprocess.PIPE)
198 > stderr=subprocess.PIPE)
189 > output = proc.communicate()[0]
199 > output = proc.communicate()[0]
190 >
200 >
191 > for line in output.splitlines():
201 > for line in output.splitlines():
192 > if os.name == 'nt':
202 > if os.name == 'nt':
193 > yield line.replace(pycompat.sysbytes(os.sep), b'/')
203 > yield line.replace(pycompat.sysbytes(os.sep), b'/')
194 > else:
204 > else:
195 > yield line
205 > yield line
196 >
206 >
197 > tracked = [f for f in hgdirectory(sys.argv[1])]
207 > tracked = [f for f in hgdirectory(sys.argv[1])]
198 >
208 >
199 > xml = ET.parse("%s/../contrib/packaging/wix/%s.wxs" % (testdir, sys.argv[1]))
209 > xml = ET.parse("%s/../contrib/packaging/wix/%s.wxs" % (testdir, sys.argv[1]))
200 > root = xml.getroot()
210 > root = xml.getroot()
201 > dir = root.find('.//{%(wix)s}DirectoryRef' % ns)
211 > dir = root.find('.//{%(wix)s}DirectoryRef' % ns)
202 >
212 >
203 > installed = [f for f in directory(dir, '')]
213 > installed = [f for f in directory(dir, '')]
204 >
214 >
205 > print('Not installed:')
215 > print('Not installed:')
206 > for f in sorted(set(tracked) - set(installed)):
216 > for f in sorted(set(tracked) - set(installed)):
207 > print(' %s' % pycompat.sysstr(f))
217 > print(' %s' % pycompat.sysstr(f))
208 >
218 >
209 > print('Not tracked:')
219 > print('Not tracked:')
210 > for f in sorted(set(installed) - set(tracked)):
220 > for f in sorted(set(installed) - set(tracked)):
211 > print(' %s' % pycompat.sysstr(f))
221 > print(' %s' % pycompat.sysstr(f))
212 > EOF
222 > EOF
213
223
214 $ ( testrepohgenv; "$PYTHON" wixxml.py help )
224 $ ( testrepohgenv; "$PYTHON" wixxml.py help )
215 Not installed:
225 Not installed:
216 help/common.txt
226 help/common.txt
217 help/hg-ssh.8.txt
227 help/hg-ssh.8.txt
218 help/hg.1.txt
228 help/hg.1.txt
219 help/hgignore.5.txt
229 help/hgignore.5.txt
220 help/hgrc.5.txt
230 help/hgrc.5.txt
221 Not tracked:
231 Not tracked:
222
232
223 $ ( testrepohgenv; "$PYTHON" wixxml.py templates )
233 $ ( testrepohgenv; "$PYTHON" wixxml.py templates )
224 Not installed:
234 Not installed:
225 Not tracked:
235 Not tracked:
226
236
227 #endif
237 #endif
228
238
229 #if virtualenv
239 #if virtualenv
230
240
231 Verify that Mercurial is installable with pip. Note that this MUST be
241 Verify that Mercurial is installable with pip. Note that this MUST be
232 the last test in this file, because we do some nasty things to the
242 the last test in this file, because we do some nasty things to the
233 shell environment in order to make the virtualenv work reliably.
243 shell environment in order to make the virtualenv work reliably.
234
244
235 $ cd $TESTTMP
245 $ cd $TESTTMP
236 Note: --no-site-packages is deprecated, but some places have an
246 Note: --no-site-packages is deprecated, but some places have an
237 ancient virtualenv from their linux distro or similar and it's not yet
247 ancient virtualenv from their linux distro or similar and it's not yet
238 the default for them.
248 the default for them.
239 $ unset PYTHONPATH
249 $ unset PYTHONPATH
240 $ "$PYTHON" -m virtualenv --no-site-packages --never-download installenv >> pip.log
250 $ "$PYTHON" -m virtualenv --no-site-packages --never-download installenv >> pip.log
241 DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. (?)
251 DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. (?)
242 Note: we use this weird path to run pip and hg to avoid platform differences,
252 Note: we use this weird path to run pip and hg to avoid platform differences,
243 since it's bin on most platforms but Scripts on Windows.
253 since it's bin on most platforms but Scripts on Windows.
244 $ ./installenv/*/pip install --no-index $TESTDIR/.. >> pip.log
254 $ ./installenv/*/pip install --no-index $TESTDIR/.. >> pip.log
245 DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. (?)
255 DEPRECATION: Python 2.7 will reach the end of its life on January 1st, 2020. Please upgrade your Python as Python 2.7 won't be maintained after that date. A future version of pip will drop support for Python 2.7. (?)
246 $ ./installenv/*/hg debuginstall || cat pip.log
256 $ ./installenv/*/hg debuginstall || cat pip.log
247 checking encoding (ascii)...
257 checking encoding (ascii)...
248 checking Python executable (*) (glob)
258 checking Python executable (*) (glob)
249 checking Python version (2.*) (glob) (no-py3 !)
259 checking Python version (2.*) (glob) (no-py3 !)
250 checking Python version (3.*) (glob) (py3 !)
260 checking Python version (3.*) (glob) (py3 !)
251 checking Python lib (*)... (glob)
261 checking Python lib (*)... (glob)
252 checking Python security support (*) (glob)
262 checking Python security support (*) (glob)
253 TLS 1.2 not supported by Python install; network connections lack modern security (?)
263 TLS 1.2 not supported by Python install; network connections lack modern security (?)
254 SNI not supported by Python install; may have connectivity issues with some servers (?)
264 SNI not supported by Python install; may have connectivity issues with some servers (?)
255 checking Mercurial version (*) (glob)
265 checking Mercurial version (*) (glob)
256 checking Mercurial custom build (*) (glob)
266 checking Mercurial custom build (*) (glob)
257 checking module policy (*) (glob)
267 checking module policy (*) (glob)
258 checking installed modules (*/mercurial)... (glob)
268 checking installed modules (*/mercurial)... (glob)
259 checking registered compression engines (*) (glob)
269 checking registered compression engines (*) (glob)
260 checking available compression engines (*) (glob)
270 checking available compression engines (*) (glob)
261 checking available compression engines for wire protocol (*) (glob)
271 checking available compression engines for wire protocol (*) (glob)
262 checking "re2" regexp engine \((available|missing)\) (re)
272 checking "re2" regexp engine \((available|missing)\) (re)
263 checking templates ($TESTTMP/installenv/*/site-packages/mercurial/templates)... (glob)
273 checking templates ($TESTTMP/installenv/*/site-packages/mercurial/templates)... (glob)
264 checking default template ($TESTTMP/installenv/*/site-packages/mercurial/templates/map-cmdline.default) (glob)
274 checking default template ($TESTTMP/installenv/*/site-packages/mercurial/templates/map-cmdline.default) (glob)
265 checking commit editor... (*) (glob)
275 checking commit editor... (*) (glob)
266 checking username (test)
276 checking username (test)
267 no problems detected
277 no problems detected
268 #endif
278 #endif
General Comments 0
You need to be logged in to leave comments. Login now