##// END OF EJS Templates
py3: use python 3 compatible variables in hgext/fsmontor/__init__.py...
Pulkit Goyal -
r30666:6ada1658 default
parent child Browse files
Show More
@@ -1,695 +1,696 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 hashlib
95 95 import os
96 96 import stat
97 import sys
98 97
99 98 from mercurial.i18n import _
100 99 from mercurial import (
101 100 context,
101 encoding,
102 102 extensions,
103 103 localrepo,
104 104 merge,
105 105 pathutil,
106 pycompat,
106 107 scmutil,
107 108 util,
108 109 )
109 110 from mercurial import match as matchmod
110 111
111 112 from . import (
112 113 state,
113 114 watchmanclient,
114 115 )
115 116
116 117 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
117 118 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
118 119 # be specifying the version(s) of Mercurial they are tested with, or
119 120 # leave the attribute unspecified.
120 121 testedwith = 'ships-with-hg-core'
121 122
122 123 # This extension is incompatible with the following blacklisted extensions
123 124 # and will disable itself when encountering one of these:
124 125 _blacklist = ['largefiles', 'eol']
125 126
126 127 def _handleunavailable(ui, state, ex):
127 128 """Exception handler for Watchman interaction exceptions"""
128 129 if isinstance(ex, watchmanclient.Unavailable):
129 130 if ex.warn:
130 131 ui.warn(str(ex) + '\n')
131 132 if ex.invalidate:
132 133 state.invalidate()
133 134 ui.log('fsmonitor', 'Watchman unavailable: %s\n', ex.msg)
134 135 else:
135 136 ui.log('fsmonitor', 'Watchman exception: %s\n', ex)
136 137
137 138 def _hashignore(ignore):
138 139 """Calculate hash for ignore patterns and filenames
139 140
140 141 If this information changes between Mercurial invocations, we can't
141 142 rely on Watchman information anymore and have to re-scan the working
142 143 copy.
143 144
144 145 """
145 146 sha1 = hashlib.sha1()
146 147 if util.safehasattr(ignore, 'includepat'):
147 148 sha1.update(ignore.includepat)
148 149 sha1.update('\0\0')
149 150 if util.safehasattr(ignore, 'excludepat'):
150 151 sha1.update(ignore.excludepat)
151 152 sha1.update('\0\0')
152 153 if util.safehasattr(ignore, 'patternspat'):
153 154 sha1.update(ignore.patternspat)
154 155 sha1.update('\0\0')
155 156 if util.safehasattr(ignore, '_files'):
156 157 for f in ignore._files:
157 158 sha1.update(f)
158 159 sha1.update('\0')
159 160 return sha1.hexdigest()
160 161
161 162 def overridewalk(orig, self, match, subrepos, unknown, ignored, full=True):
162 163 '''Replacement for dirstate.walk, hooking into Watchman.
163 164
164 165 Whenever full is False, ignored is False, and the Watchman client is
165 166 available, use Watchman combined with saved state to possibly return only a
166 167 subset of files.'''
167 168 def bail():
168 169 return orig(match, subrepos, unknown, ignored, full=True)
169 170
170 171 if full or ignored or not self._watchmanclient.available():
171 172 return bail()
172 173 state = self._fsmonitorstate
173 174 clock, ignorehash, notefiles = state.get()
174 175 if not clock:
175 176 if state.walk_on_invalidate:
176 177 return bail()
177 178 # Initial NULL clock value, see
178 179 # https://facebook.github.io/watchman/docs/clockspec.html
179 180 clock = 'c:0:0'
180 181 notefiles = []
181 182
182 183 def fwarn(f, msg):
183 184 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
184 185 return False
185 186
186 187 def badtype(mode):
187 188 kind = _('unknown')
188 189 if stat.S_ISCHR(mode):
189 190 kind = _('character device')
190 191 elif stat.S_ISBLK(mode):
191 192 kind = _('block device')
192 193 elif stat.S_ISFIFO(mode):
193 194 kind = _('fifo')
194 195 elif stat.S_ISSOCK(mode):
195 196 kind = _('socket')
196 197 elif stat.S_ISDIR(mode):
197 198 kind = _('directory')
198 199 return _('unsupported file type (type is %s)') % kind
199 200
200 201 ignore = self._ignore
201 202 dirignore = self._dirignore
202 203 if unknown:
203 204 if _hashignore(ignore) != ignorehash and clock != 'c:0:0':
204 205 # ignore list changed -- can't rely on Watchman state any more
205 206 if state.walk_on_invalidate:
206 207 return bail()
207 208 notefiles = []
208 209 clock = 'c:0:0'
209 210 else:
210 211 # always ignore
211 212 ignore = util.always
212 213 dirignore = util.always
213 214
214 215 matchfn = match.matchfn
215 216 matchalways = match.always()
216 217 dmap = self._map
217 218 nonnormalset = getattr(self, '_nonnormalset', None)
218 219
219 220 copymap = self._copymap
220 221 getkind = stat.S_IFMT
221 222 dirkind = stat.S_IFDIR
222 223 regkind = stat.S_IFREG
223 224 lnkkind = stat.S_IFLNK
224 225 join = self._join
225 226 normcase = util.normcase
226 227 fresh_instance = False
227 228
228 229 exact = skipstep3 = False
229 230 if matchfn == match.exact: # match.exact
230 231 exact = True
231 232 dirignore = util.always # skip step 2
232 233 elif match.files() and not match.anypats(): # match.match, no patterns
233 234 skipstep3 = True
234 235
235 236 if not exact and self._checkcase:
236 237 # note that even though we could receive directory entries, we're only
237 238 # interested in checking if a file with the same name exists. So only
238 239 # normalize files if possible.
239 240 normalize = self._normalizefile
240 241 skipstep3 = False
241 242 else:
242 243 normalize = None
243 244
244 245 # step 1: find all explicit files
245 246 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
246 247
247 248 skipstep3 = skipstep3 and not (work or dirsnotfound)
248 249 work = [d for d in work if not dirignore(d[0])]
249 250
250 251 if not work and (exact or skipstep3):
251 252 for s in subrepos:
252 253 del results[s]
253 254 del results['.hg']
254 255 return results
255 256
256 257 # step 2: query Watchman
257 258 try:
258 259 # Use the user-configured timeout for the query.
259 260 # Add a little slack over the top of the user query to allow for
260 261 # overheads while transferring the data
261 262 self._watchmanclient.settimeout(state.timeout + 0.1)
262 263 result = self._watchmanclient.command('query', {
263 264 'fields': ['mode', 'mtime', 'size', 'exists', 'name'],
264 265 'since': clock,
265 266 'expression': [
266 267 'not', [
267 268 'anyof', ['dirname', '.hg'],
268 269 ['name', '.hg', 'wholename']
269 270 ]
270 271 ],
271 272 'sync_timeout': int(state.timeout * 1000),
272 273 'empty_on_fresh_instance': state.walk_on_invalidate,
273 274 })
274 275 except Exception as ex:
275 276 _handleunavailable(self._ui, state, ex)
276 277 self._watchmanclient.clearconnection()
277 278 return bail()
278 279 else:
279 280 # We need to propagate the last observed clock up so that we
280 281 # can use it for our next query
281 282 state.setlastclock(result['clock'])
282 283 if result['is_fresh_instance']:
283 284 if state.walk_on_invalidate:
284 285 state.invalidate()
285 286 return bail()
286 287 fresh_instance = True
287 288 # Ignore any prior noteable files from the state info
288 289 notefiles = []
289 290
290 291 # for file paths which require normalization and we encounter a case
291 292 # collision, we store our own foldmap
292 293 if normalize:
293 294 foldmap = dict((normcase(k), k) for k in results)
294 295
295 switch_slashes = os.sep == '\\'
296 switch_slashes = pycompat.ossep == '\\'
296 297 # The order of the results is, strictly speaking, undefined.
297 298 # For case changes on a case insensitive filesystem we may receive
298 299 # two entries, one with exists=True and another with exists=False.
299 300 # The exists=True entries in the same response should be interpreted
300 301 # as being happens-after the exists=False entries due to the way that
301 302 # Watchman tracks files. We use this property to reconcile deletes
302 303 # for name case changes.
303 304 for entry in result['files']:
304 305 fname = entry['name']
305 306 if switch_slashes:
306 307 fname = fname.replace('\\', '/')
307 308 if normalize:
308 309 normed = normcase(fname)
309 310 fname = normalize(fname, True, True)
310 311 foldmap[normed] = fname
311 312 fmode = entry['mode']
312 313 fexists = entry['exists']
313 314 kind = getkind(fmode)
314 315
315 316 if not fexists:
316 317 # if marked as deleted and we don't already have a change
317 318 # record, mark it as deleted. If we already have an entry
318 319 # for fname then it was either part of walkexplicit or was
319 320 # an earlier result that was a case change
320 321 if fname not in results and fname in dmap and (
321 322 matchalways or matchfn(fname)):
322 323 results[fname] = None
323 324 elif kind == dirkind:
324 325 if fname in dmap and (matchalways or matchfn(fname)):
325 326 results[fname] = None
326 327 elif kind == regkind or kind == lnkkind:
327 328 if fname in dmap:
328 329 if matchalways or matchfn(fname):
329 330 results[fname] = entry
330 331 elif (matchalways or matchfn(fname)) and not ignore(fname):
331 332 results[fname] = entry
332 333 elif fname in dmap and (matchalways or matchfn(fname)):
333 334 results[fname] = None
334 335
335 336 # step 3: query notable files we don't already know about
336 337 # XXX try not to iterate over the entire dmap
337 338 if normalize:
338 339 # any notable files that have changed case will already be handled
339 340 # above, so just check membership in the foldmap
340 341 notefiles = set((normalize(f, True, True) for f in notefiles
341 342 if normcase(f) not in foldmap))
342 343 visit = set((f for f in notefiles if (f not in results and matchfn(f)
343 344 and (f in dmap or not ignore(f)))))
344 345
345 346 if nonnormalset is not None and not fresh_instance:
346 347 if matchalways:
347 348 visit.update(f for f in nonnormalset if f not in results)
348 349 visit.update(f for f in copymap if f not in results)
349 350 else:
350 351 visit.update(f for f in nonnormalset
351 352 if f not in results and matchfn(f))
352 353 visit.update(f for f in copymap
353 354 if f not in results and matchfn(f))
354 355 else:
355 356 if matchalways:
356 357 visit.update(f for f, st in dmap.iteritems()
357 358 if (f not in results and
358 359 (st[2] < 0 or st[0] != 'n' or fresh_instance)))
359 360 visit.update(f for f in copymap if f not in results)
360 361 else:
361 362 visit.update(f for f, st in dmap.iteritems()
362 363 if (f not in results and
363 364 (st[2] < 0 or st[0] != 'n' or fresh_instance)
364 365 and matchfn(f)))
365 366 visit.update(f for f in copymap
366 367 if f not in results and matchfn(f))
367 368
368 369 audit = pathutil.pathauditor(self._root).check
369 370 auditpass = [f for f in visit if audit(f)]
370 371 auditpass.sort()
371 372 auditfail = visit.difference(auditpass)
372 373 for f in auditfail:
373 374 results[f] = None
374 375
375 376 nf = iter(auditpass).next
376 377 for st in util.statfiles([join(f) for f in auditpass]):
377 378 f = nf()
378 379 if st or f in dmap:
379 380 results[f] = st
380 381
381 382 for s in subrepos:
382 383 del results[s]
383 384 del results['.hg']
384 385 return results
385 386
386 387 def overridestatus(
387 388 orig, self, node1='.', node2=None, match=None, ignored=False,
388 389 clean=False, unknown=False, listsubrepos=False):
389 390 listignored = ignored
390 391 listclean = clean
391 392 listunknown = unknown
392 393
393 394 def _cmpsets(l1, l2):
394 395 try:
395 if 'FSMONITOR_LOG_FILE' in os.environ:
396 fn = os.environ['FSMONITOR_LOG_FILE']
396 if 'FSMONITOR_LOG_FILE' in encoding.environ:
397 fn = encoding.environ['FSMONITOR_LOG_FILE']
397 398 f = open(fn, 'wb')
398 399 else:
399 400 fn = 'fsmonitorfail.log'
400 401 f = self.opener(fn, 'wb')
401 402 except (IOError, OSError):
402 403 self.ui.warn(_('warning: unable to write to %s\n') % fn)
403 404 return
404 405
405 406 try:
406 407 for i, (s1, s2) in enumerate(zip(l1, l2)):
407 408 if set(s1) != set(s2):
408 409 f.write('sets at position %d are unequal\n' % i)
409 410 f.write('watchman returned: %s\n' % s1)
410 411 f.write('stat returned: %s\n' % s2)
411 412 finally:
412 413 f.close()
413 414
414 415 if isinstance(node1, context.changectx):
415 416 ctx1 = node1
416 417 else:
417 418 ctx1 = self[node1]
418 419 if isinstance(node2, context.changectx):
419 420 ctx2 = node2
420 421 else:
421 422 ctx2 = self[node2]
422 423
423 424 working = ctx2.rev() is None
424 425 parentworking = working and ctx1 == self['.']
425 426 match = match or matchmod.always(self.root, self.getcwd())
426 427
427 428 # Maybe we can use this opportunity to update Watchman's state.
428 429 # Mercurial uses workingcommitctx and/or memctx to represent the part of
429 430 # the workingctx that is to be committed. So don't update the state in
430 431 # that case.
431 432 # HG_PENDING is set in the environment when the dirstate is being updated
432 433 # in the middle of a transaction; we must not update our state in that
433 434 # case, or we risk forgetting about changes in the working copy.
434 435 updatestate = (parentworking and match.always() and
435 436 not isinstance(ctx2, (context.workingcommitctx,
436 437 context.memctx)) and
437 'HG_PENDING' not in os.environ)
438 'HG_PENDING' not in encoding.environ)
438 439
439 440 try:
440 441 if self._fsmonitorstate.walk_on_invalidate:
441 442 # Use a short timeout to query the current clock. If that
442 443 # takes too long then we assume that the service will be slow
443 444 # to answer our query.
444 445 # walk_on_invalidate indicates that we prefer to walk the
445 446 # tree ourselves because we can ignore portions that Watchman
446 447 # cannot and we tend to be faster in the warmer buffer cache
447 448 # cases.
448 449 self._watchmanclient.settimeout(0.1)
449 450 else:
450 451 # Give Watchman more time to potentially complete its walk
451 452 # and return the initial clock. In this mode we assume that
452 453 # the filesystem will be slower than parsing a potentially
453 454 # very large Watchman result set.
454 455 self._watchmanclient.settimeout(
455 456 self._fsmonitorstate.timeout + 0.1)
456 457 startclock = self._watchmanclient.getcurrentclock()
457 458 except Exception as ex:
458 459 self._watchmanclient.clearconnection()
459 460 _handleunavailable(self.ui, self._fsmonitorstate, ex)
460 461 # boo, Watchman failed. bail
461 462 return orig(node1, node2, match, listignored, listclean,
462 463 listunknown, listsubrepos)
463 464
464 465 if updatestate:
465 466 # We need info about unknown files. This may make things slower the
466 467 # first time, but whatever.
467 468 stateunknown = True
468 469 else:
469 470 stateunknown = listunknown
470 471
471 472 r = orig(node1, node2, match, listignored, listclean, stateunknown,
472 473 listsubrepos)
473 474 modified, added, removed, deleted, unknown, ignored, clean = r
474 475
475 476 if updatestate:
476 477 notefiles = modified + added + removed + deleted + unknown
477 478 self._fsmonitorstate.set(
478 479 self._fsmonitorstate.getlastclock() or startclock,
479 480 _hashignore(self.dirstate._ignore),
480 481 notefiles)
481 482
482 483 if not listunknown:
483 484 unknown = []
484 485
485 486 # don't do paranoid checks if we're not going to query Watchman anyway
486 487 full = listclean or match.traversedir is not None
487 488 if self._fsmonitorstate.mode == 'paranoid' and not full:
488 489 # run status again and fall back to the old walk this time
489 490 self.dirstate._fsmonitordisable = True
490 491
491 492 # shut the UI up
492 493 quiet = self.ui.quiet
493 494 self.ui.quiet = True
494 495 fout, ferr = self.ui.fout, self.ui.ferr
495 496 self.ui.fout = self.ui.ferr = open(os.devnull, 'wb')
496 497
497 498 try:
498 499 rv2 = orig(
499 500 node1, node2, match, listignored, listclean, listunknown,
500 501 listsubrepos)
501 502 finally:
502 503 self.dirstate._fsmonitordisable = False
503 504 self.ui.quiet = quiet
504 505 self.ui.fout, self.ui.ferr = fout, ferr
505 506
506 507 # clean isn't tested since it's set to True above
507 508 _cmpsets([modified, added, removed, deleted, unknown, ignored, clean],
508 509 rv2)
509 510 modified, added, removed, deleted, unknown, ignored, clean = rv2
510 511
511 512 return scmutil.status(
512 513 modified, added, removed, deleted, unknown, ignored, clean)
513 514
514 515 def makedirstate(cls):
515 516 class fsmonitordirstate(cls):
516 517 def _fsmonitorinit(self, fsmonitorstate, watchmanclient):
517 518 # _fsmonitordisable is used in paranoid mode
518 519 self._fsmonitordisable = False
519 520 self._fsmonitorstate = fsmonitorstate
520 521 self._watchmanclient = watchmanclient
521 522
522 523 def walk(self, *args, **kwargs):
523 524 orig = super(fsmonitordirstate, self).walk
524 525 if self._fsmonitordisable:
525 526 return orig(*args, **kwargs)
526 527 return overridewalk(orig, self, *args, **kwargs)
527 528
528 529 def rebuild(self, *args, **kwargs):
529 530 self._fsmonitorstate.invalidate()
530 531 return super(fsmonitordirstate, self).rebuild(*args, **kwargs)
531 532
532 533 def invalidate(self, *args, **kwargs):
533 534 self._fsmonitorstate.invalidate()
534 535 return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
535 536
536 537 return fsmonitordirstate
537 538
538 539 def wrapdirstate(orig, self):
539 540 ds = orig(self)
540 541 # only override the dirstate when Watchman is available for the repo
541 542 if util.safehasattr(self, '_fsmonitorstate'):
542 543 ds.__class__ = makedirstate(ds.__class__)
543 544 ds._fsmonitorinit(self._fsmonitorstate, self._watchmanclient)
544 545 return ds
545 546
546 547 def extsetup(ui):
547 548 wrapfilecache(localrepo.localrepository, 'dirstate', wrapdirstate)
548 if sys.platform == 'darwin':
549 if pycompat.sysplatform == 'darwin':
549 550 # An assist for avoiding the dangling-symlink fsevents bug
550 551 extensions.wrapfunction(os, 'symlink', wrapsymlink)
551 552
552 553 extensions.wrapfunction(merge, 'update', wrapupdate)
553 554
554 555 def wrapsymlink(orig, source, link_name):
555 556 ''' if we create a dangling symlink, also touch the parent dir
556 557 to encourage fsevents notifications to work more correctly '''
557 558 try:
558 559 return orig(source, link_name)
559 560 finally:
560 561 try:
561 562 os.utime(os.path.dirname(link_name), None)
562 563 except OSError:
563 564 pass
564 565
565 566 class state_update(object):
566 567 ''' This context manager is responsible for dispatching the state-enter
567 568 and state-leave signals to the watchman service '''
568 569
569 570 def __init__(self, repo, node, distance, partial):
570 571 self.repo = repo
571 572 self.node = node
572 573 self.distance = distance
573 574 self.partial = partial
574 575
575 576 def __enter__(self):
576 577 self._state('state-enter')
577 578 return self
578 579
579 580 def __exit__(self, type_, value, tb):
580 581 status = 'ok' if type_ is None else 'failed'
581 582 self._state('state-leave', status=status)
582 583
583 584 def _state(self, cmd, status='ok'):
584 585 if not util.safehasattr(self.repo, '_watchmanclient'):
585 586 return
586 587 try:
587 588 commithash = self.repo[self.node].hex()
588 589 self.repo._watchmanclient.command(cmd, {
589 590 'name': 'hg.update',
590 591 'metadata': {
591 592 # the target revision
592 593 'rev': commithash,
593 594 # approximate number of commits between current and target
594 595 'distance': self.distance,
595 596 # success/failure (only really meaningful for state-leave)
596 597 'status': status,
597 598 # whether the working copy parent is changing
598 599 'partial': self.partial,
599 600 }})
600 601 except Exception as e:
601 602 # Swallow any errors; fire and forget
602 603 self.repo.ui.log(
603 604 'watchman', 'Exception %s while running %s\n', e, cmd)
604 605
605 606 # Bracket working copy updates with calls to the watchman state-enter
606 607 # and state-leave commands. This allows clients to perform more intelligent
607 608 # settling during bulk file change scenarios
608 609 # https://facebook.github.io/watchman/docs/cmd/subscribe.html#advanced-settling
609 610 def wrapupdate(orig, repo, node, branchmerge, force, ancestor=None,
610 611 mergeancestor=False, labels=None, matcher=None, **kwargs):
611 612
612 613 distance = 0
613 614 partial = True
614 615 if matcher is None or matcher.always():
615 616 partial = False
616 617 wc = repo[None]
617 618 parents = wc.parents()
618 619 if len(parents) == 2:
619 620 anc = repo.changelog.ancestor(parents[0].node(), parents[1].node())
620 621 ancrev = repo[anc].rev()
621 622 distance = abs(repo[node].rev() - ancrev)
622 623 elif len(parents) == 1:
623 624 distance = abs(repo[node].rev() - parents[0].rev())
624 625
625 626 with state_update(repo, node, distance, partial):
626 627 return orig(
627 628 repo, node, branchmerge, force, ancestor, mergeancestor,
628 629 labels, matcher, *kwargs)
629 630
630 631 def reposetup(ui, repo):
631 632 # We don't work with largefiles or inotify
632 633 exts = extensions.enabled()
633 634 for ext in _blacklist:
634 635 if ext in exts:
635 636 ui.warn(_('The fsmonitor extension is incompatible with the %s '
636 637 'extension and has been disabled.\n') % ext)
637 638 return
638 639
639 640 if util.safehasattr(repo, 'dirstate'):
640 641 # We don't work with subrepos either. Note that we can get passed in
641 642 # e.g. a statichttprepo, which throws on trying to access the substate.
642 643 # XXX This sucks.
643 644 try:
644 645 # if repo[None].substate can cause a dirstate parse, which is too
645 646 # slow. Instead, look for a file called hgsubstate,
646 647 if repo.wvfs.exists('.hgsubstate') or repo.wvfs.exists('.hgsub'):
647 648 return
648 649 except AttributeError:
649 650 return
650 651
651 652 fsmonitorstate = state.state(repo)
652 653 if fsmonitorstate.mode == 'off':
653 654 return
654 655
655 656 try:
656 657 client = watchmanclient.client(repo)
657 658 except Exception as ex:
658 659 _handleunavailable(ui, fsmonitorstate, ex)
659 660 return
660 661
661 662 repo._fsmonitorstate = fsmonitorstate
662 663 repo._watchmanclient = client
663 664
664 665 # at this point since fsmonitorstate wasn't present, repo.dirstate is
665 666 # not a fsmonitordirstate
666 667 repo.dirstate.__class__ = makedirstate(repo.dirstate.__class__)
667 668 # nuke the dirstate so that _fsmonitorinit and subsequent configuration
668 669 # changes take effect on it
669 670 del repo._filecache['dirstate']
670 671 delattr(repo.unfiltered(), 'dirstate')
671 672
672 673 class fsmonitorrepo(repo.__class__):
673 674 def status(self, *args, **kwargs):
674 675 orig = super(fsmonitorrepo, self).status
675 676 return overridestatus(orig, self, *args, **kwargs)
676 677
677 678 repo.__class__ = fsmonitorrepo
678 679
679 680 def wrapfilecache(cls, propname, wrapper):
680 681 """Wraps a filecache property. These can't be wrapped using the normal
681 682 wrapfunction. This should eventually go into upstream Mercurial.
682 683 """
683 684 assert callable(wrapper)
684 685 for currcls in cls.__mro__:
685 686 if propname in currcls.__dict__:
686 687 origfn = currcls.__dict__[propname].func
687 688 assert callable(origfn)
688 689 def wrap(*args, **kwargs):
689 690 return wrapper(origfn, *args, **kwargs)
690 691 currcls.__dict__[propname].func = wrap
691 692 break
692 693
693 694 if currcls is object:
694 695 raise AttributeError(
695 696 _("type '%s' has no property '%s'") % (cls, propname))
@@ -1,68 +1,53 b''
1 1 #require test-repo
2 2
3 3 $ . "$TESTDIR/helpers-testrepo.sh"
4 4 $ check_code="$TESTDIR"/../contrib/check-code.py
5 5 $ cd "$TESTDIR"/..
6 6
7 7 New errors are not allowed. Warnings are strongly discouraged.
8 8 (The writing "no-che?k-code" is for not skipping this file when checking.)
9 9
10 10 $ hg locate -X contrib/python-zstandard -X hgext/fsmonitor/pywatchman |
11 11 > sed 's-\\-/-g' | xargs "$check_code" --warnings --per-file=0 || false
12 hgext/fsmonitor/__init__.py:295:
13 > switch_slashes = os.sep == '\\'
14 use pycompat.ossep instead (py3)
15 hgext/fsmonitor/__init__.py:395:
16 > if 'FSMONITOR_LOG_FILE' in os.environ:
17 use encoding.environ instead (py3)
18 hgext/fsmonitor/__init__.py:396:
19 > fn = os.environ['FSMONITOR_LOG_FILE']
20 use encoding.environ instead (py3)
21 hgext/fsmonitor/__init__.py:437:
22 > 'HG_PENDING' not in os.environ)
23 use encoding.environ instead (py3)
24 hgext/fsmonitor/__init__.py:548:
25 > if sys.platform == 'darwin':
26 use pycompat.sysplatform instead (py3)
27 12 Skipping i18n/polib.py it has no-che?k-code (glob)
28 13 mercurial/demandimport.py:309:
29 14 > if os.environ.get('HGDEMANDIMPORT') != 'disable':
30 15 use encoding.environ instead (py3)
31 16 mercurial/encoding.py:54:
32 17 > environ = os.environ
33 18 use encoding.environ instead (py3)
34 19 mercurial/encoding.py:56:
35 20 > environ = os.environb
36 21 use encoding.environ instead (py3)
37 22 mercurial/encoding.py:61:
38 23 > for k, v in os.environ.items())
39 24 use encoding.environ instead (py3)
40 25 mercurial/encoding.py:203:
41 26 > for k, v in os.environ.items())
42 27 use encoding.environ instead (py3)
43 28 Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
44 29 Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
45 30 mercurial/policy.py:45:
46 31 > policy = os.environ.get('HGMODULEPOLICY', policy)
47 32 use encoding.environ instead (py3)
48 33 Skipping mercurial/statprof.py it has no-che?k-code (glob)
49 34 mercurial/win32.py:443:
50 35 > env, os.getcwd(), ctypes.byref(si), ctypes.byref(pi))
51 36 use pycompat.getcwd instead (py3)
52 37 [1]
53 38
54 39 @commands in debugcommands.py should be in alphabetical order.
55 40
56 41 >>> import re
57 42 >>> commands = []
58 43 >>> with open('mercurial/debugcommands.py', 'rb') as fh:
59 44 ... for line in fh:
60 45 ... m = re.match("^@command\('([a-z]+)", line)
61 46 ... if m:
62 47 ... commands.append(m.group(1))
63 48 >>> scommands = list(sorted(commands))
64 49 >>> for i, command in enumerate(scommands):
65 50 ... if command != commands[i]:
66 51 ... print('commands in debugcommands.py not sorted; first differing '
67 52 ... 'command is %s; expected %s' % (commands[i], command))
68 53 ... break
General Comments 0
You need to be logged in to leave comments. Login now