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