##// END OF EJS Templates
dirstate: remove excess attribute lookups for dirstate.status (issue5714)...
Durham Goode -
r34936:ffeea240 stable
parent child Browse files
Show More
@@ -1,1406 +1,1420 b''
1 1 # dirstate.py - working directory tracking for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
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 from __future__ import absolute_import
9 9
10 10 import collections
11 11 import contextlib
12 12 import errno
13 13 import os
14 14 import stat
15 15
16 16 from .i18n import _
17 17 from .node import nullid
18 18 from . import (
19 19 encoding,
20 20 error,
21 21 match as matchmod,
22 22 pathutil,
23 23 policy,
24 24 pycompat,
25 25 scmutil,
26 26 txnutil,
27 27 util,
28 28 )
29 29
30 30 parsers = policy.importmod(r'parsers')
31 31
32 32 propertycache = util.propertycache
33 33 filecache = scmutil.filecache
34 34 _rangemask = 0x7fffffff
35 35
36 36 dirstatetuple = parsers.dirstatetuple
37 37
38 38 class repocache(filecache):
39 39 """filecache for files in .hg/"""
40 40 def join(self, obj, fname):
41 41 return obj._opener.join(fname)
42 42
43 43 class rootcache(filecache):
44 44 """filecache for files in the repository root"""
45 45 def join(self, obj, fname):
46 46 return obj._join(fname)
47 47
48 48 def _getfsnow(vfs):
49 49 '''Get "now" timestamp on filesystem'''
50 50 tmpfd, tmpname = vfs.mkstemp()
51 51 try:
52 52 return os.fstat(tmpfd).st_mtime
53 53 finally:
54 54 os.close(tmpfd)
55 55 vfs.unlink(tmpname)
56 56
57 57 class dirstate(object):
58 58
59 59 def __init__(self, opener, ui, root, validate, sparsematchfn):
60 60 '''Create a new dirstate object.
61 61
62 62 opener is an open()-like callable that can be used to open the
63 63 dirstate file; root is the root of the directory tracked by
64 64 the dirstate.
65 65 '''
66 66 self._opener = opener
67 67 self._validate = validate
68 68 self._root = root
69 69 self._sparsematchfn = sparsematchfn
70 70 # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is
71 71 # UNC path pointing to root share (issue4557)
72 72 self._rootdir = pathutil.normasprefix(root)
73 73 self._dirty = False
74 74 self._lastnormaltime = 0
75 75 self._ui = ui
76 76 self._filecache = {}
77 77 self._parentwriters = 0
78 78 self._filename = 'dirstate'
79 79 self._pendingfilename = '%s.pending' % self._filename
80 80 self._plchangecallbacks = {}
81 81 self._origpl = None
82 82 self._updatedfiles = set()
83 83
84 84 @contextlib.contextmanager
85 85 def parentchange(self):
86 86 '''Context manager for handling dirstate parents.
87 87
88 88 If an exception occurs in the scope of the context manager,
89 89 the incoherent dirstate won't be written when wlock is
90 90 released.
91 91 '''
92 92 self._parentwriters += 1
93 93 yield
94 94 # Typically we want the "undo" step of a context manager in a
95 95 # finally block so it happens even when an exception
96 96 # occurs. In this case, however, we only want to decrement
97 97 # parentwriters if the code in the with statement exits
98 98 # normally, so we don't have a try/finally here on purpose.
99 99 self._parentwriters -= 1
100 100
101 101 def beginparentchange(self):
102 102 '''Marks the beginning of a set of changes that involve changing
103 103 the dirstate parents. If there is an exception during this time,
104 104 the dirstate will not be written when the wlock is released. This
105 105 prevents writing an incoherent dirstate where the parent doesn't
106 106 match the contents.
107 107 '''
108 108 self._ui.deprecwarn('beginparentchange is obsoleted by the '
109 109 'parentchange context manager.', '4.3')
110 110 self._parentwriters += 1
111 111
112 112 def endparentchange(self):
113 113 '''Marks the end of a set of changes that involve changing the
114 114 dirstate parents. Once all parent changes have been marked done,
115 115 the wlock will be free to write the dirstate on release.
116 116 '''
117 117 self._ui.deprecwarn('endparentchange is obsoleted by the '
118 118 'parentchange context manager.', '4.3')
119 119 if self._parentwriters > 0:
120 120 self._parentwriters -= 1
121 121
122 122 def pendingparentchange(self):
123 123 '''Returns true if the dirstate is in the middle of a set of changes
124 124 that modify the dirstate parent.
125 125 '''
126 126 return self._parentwriters > 0
127 127
128 128 @propertycache
129 129 def _map(self):
130 130 '''Return the dirstate contents as a map from filename to
131 131 (state, mode, size, time).'''
132 132 self._map = dirstatemap(self._ui, self._opener, self._root)
133 133 return self._map
134 134
135 135 @property
136 136 def _sparsematcher(self):
137 137 """The matcher for the sparse checkout.
138 138
139 139 The working directory may not include every file from a manifest. The
140 140 matcher obtained by this property will match a path if it is to be
141 141 included in the working directory.
142 142 """
143 143 # TODO there is potential to cache this property. For now, the matcher
144 144 # is resolved on every access. (But the called function does use a
145 145 # cache to keep the lookup fast.)
146 146 return self._sparsematchfn()
147 147
148 148 @repocache('branch')
149 149 def _branch(self):
150 150 try:
151 151 return self._opener.read("branch").strip() or "default"
152 152 except IOError as inst:
153 153 if inst.errno != errno.ENOENT:
154 154 raise
155 155 return "default"
156 156
157 157 @property
158 158 def _pl(self):
159 159 return self._map.parents()
160 160
161 161 def dirs(self):
162 162 return self._map.dirs
163 163
164 164 @rootcache('.hgignore')
165 165 def _ignore(self):
166 166 files = self._ignorefiles()
167 167 if not files:
168 168 return matchmod.never(self._root, '')
169 169
170 170 pats = ['include:%s' % f for f in files]
171 171 return matchmod.match(self._root, '', [], pats, warn=self._ui.warn)
172 172
173 173 @propertycache
174 174 def _slash(self):
175 175 return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/'
176 176
177 177 @propertycache
178 178 def _checklink(self):
179 179 return util.checklink(self._root)
180 180
181 181 @propertycache
182 182 def _checkexec(self):
183 183 return util.checkexec(self._root)
184 184
185 185 @propertycache
186 186 def _checkcase(self):
187 187 return not util.fscasesensitive(self._join('.hg'))
188 188
189 189 def _join(self, f):
190 190 # much faster than os.path.join()
191 191 # it's safe because f is always a relative path
192 192 return self._rootdir + f
193 193
194 194 def flagfunc(self, buildfallback):
195 195 if self._checklink and self._checkexec:
196 196 def f(x):
197 197 try:
198 198 st = os.lstat(self._join(x))
199 199 if util.statislink(st):
200 200 return 'l'
201 201 if util.statisexec(st):
202 202 return 'x'
203 203 except OSError:
204 204 pass
205 205 return ''
206 206 return f
207 207
208 208 fallback = buildfallback()
209 209 if self._checklink:
210 210 def f(x):
211 211 if os.path.islink(self._join(x)):
212 212 return 'l'
213 213 if 'x' in fallback(x):
214 214 return 'x'
215 215 return ''
216 216 return f
217 217 if self._checkexec:
218 218 def f(x):
219 219 if 'l' in fallback(x):
220 220 return 'l'
221 221 if util.isexec(self._join(x)):
222 222 return 'x'
223 223 return ''
224 224 return f
225 225 else:
226 226 return fallback
227 227
228 228 @propertycache
229 229 def _cwd(self):
230 230 # internal config: ui.forcecwd
231 231 forcecwd = self._ui.config('ui', 'forcecwd')
232 232 if forcecwd:
233 233 return forcecwd
234 234 return pycompat.getcwd()
235 235
236 236 def getcwd(self):
237 237 '''Return the path from which a canonical path is calculated.
238 238
239 239 This path should be used to resolve file patterns or to convert
240 240 canonical paths back to file paths for display. It shouldn't be
241 241 used to get real file paths. Use vfs functions instead.
242 242 '''
243 243 cwd = self._cwd
244 244 if cwd == self._root:
245 245 return ''
246 246 # self._root ends with a path separator if self._root is '/' or 'C:\'
247 247 rootsep = self._root
248 248 if not util.endswithsep(rootsep):
249 249 rootsep += pycompat.ossep
250 250 if cwd.startswith(rootsep):
251 251 return cwd[len(rootsep):]
252 252 else:
253 253 # we're outside the repo. return an absolute path.
254 254 return cwd
255 255
256 256 def pathto(self, f, cwd=None):
257 257 if cwd is None:
258 258 cwd = self.getcwd()
259 259 path = util.pathto(self._root, cwd, f)
260 260 if self._slash:
261 261 return util.pconvert(path)
262 262 return path
263 263
264 264 def __getitem__(self, key):
265 265 '''Return the current state of key (a filename) in the dirstate.
266 266
267 267 States are:
268 268 n normal
269 269 m needs merging
270 270 r marked for removal
271 271 a marked for addition
272 272 ? not tracked
273 273 '''
274 274 return self._map.get(key, ("?",))[0]
275 275
276 276 def __contains__(self, key):
277 277 return key in self._map
278 278
279 279 def __iter__(self):
280 280 return iter(sorted(self._map))
281 281
282 282 def items(self):
283 283 return self._map.iteritems()
284 284
285 285 iteritems = items
286 286
287 287 def parents(self):
288 288 return [self._validate(p) for p in self._pl]
289 289
290 290 def p1(self):
291 291 return self._validate(self._pl[0])
292 292
293 293 def p2(self):
294 294 return self._validate(self._pl[1])
295 295
296 296 def branch(self):
297 297 return encoding.tolocal(self._branch)
298 298
299 299 def setparents(self, p1, p2=nullid):
300 300 """Set dirstate parents to p1 and p2.
301 301
302 302 When moving from two parents to one, 'm' merged entries a
303 303 adjusted to normal and previous copy records discarded and
304 304 returned by the call.
305 305
306 306 See localrepo.setparents()
307 307 """
308 308 if self._parentwriters == 0:
309 309 raise ValueError("cannot set dirstate parent without "
310 310 "calling dirstate.beginparentchange")
311 311
312 312 self._dirty = True
313 313 oldp2 = self._pl[1]
314 314 if self._origpl is None:
315 315 self._origpl = self._pl
316 316 self._map.setparents(p1, p2)
317 317 copies = {}
318 318 if oldp2 != nullid and p2 == nullid:
319 319 candidatefiles = self._map.nonnormalset.union(
320 320 self._map.otherparentset)
321 321 for f in candidatefiles:
322 322 s = self._map.get(f)
323 323 if s is None:
324 324 continue
325 325
326 326 # Discard 'm' markers when moving away from a merge state
327 327 if s[0] == 'm':
328 328 source = self._map.copymap.get(f)
329 329 if source:
330 330 copies[f] = source
331 331 self.normallookup(f)
332 332 # Also fix up otherparent markers
333 333 elif s[0] == 'n' and s[2] == -2:
334 334 source = self._map.copymap.get(f)
335 335 if source:
336 336 copies[f] = source
337 337 self.add(f)
338 338 return copies
339 339
340 340 def setbranch(self, branch):
341 341 self._branch = encoding.fromlocal(branch)
342 342 f = self._opener('branch', 'w', atomictemp=True, checkambig=True)
343 343 try:
344 344 f.write(self._branch + '\n')
345 345 f.close()
346 346
347 347 # make sure filecache has the correct stat info for _branch after
348 348 # replacing the underlying file
349 349 ce = self._filecache['_branch']
350 350 if ce:
351 351 ce.refresh()
352 352 except: # re-raises
353 353 f.discard()
354 354 raise
355 355
356 356 def invalidate(self):
357 357 '''Causes the next access to reread the dirstate.
358 358
359 359 This is different from localrepo.invalidatedirstate() because it always
360 360 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
361 361 check whether the dirstate has changed before rereading it.'''
362 362
363 363 for a in ("_map", "_branch", "_ignore"):
364 364 if a in self.__dict__:
365 365 delattr(self, a)
366 366 self._lastnormaltime = 0
367 367 self._dirty = False
368 368 self._updatedfiles.clear()
369 369 self._parentwriters = 0
370 370 self._origpl = None
371 371
372 372 def copy(self, source, dest):
373 373 """Mark dest as a copy of source. Unmark dest if source is None."""
374 374 if source == dest:
375 375 return
376 376 self._dirty = True
377 377 if source is not None:
378 378 self._map.copymap[dest] = source
379 379 self._updatedfiles.add(source)
380 380 self._updatedfiles.add(dest)
381 381 elif self._map.copymap.pop(dest, None):
382 382 self._updatedfiles.add(dest)
383 383
384 384 def copied(self, file):
385 385 return self._map.copymap.get(file, None)
386 386
387 387 def copies(self):
388 388 return self._map.copymap
389 389
390 390 def _droppath(self, f):
391 391 if self[f] not in "?r" and "dirs" in self._map.__dict__:
392 392 self._map.dirs.delpath(f)
393 393
394 394 if "filefoldmap" in self._map.__dict__:
395 395 normed = util.normcase(f)
396 396 if normed in self._map.filefoldmap:
397 397 del self._map.filefoldmap[normed]
398 398
399 399 self._updatedfiles.add(f)
400 400
401 401 def _addpath(self, f, state, mode, size, mtime):
402 402 oldstate = self[f]
403 403 if state == 'a' or oldstate == 'r':
404 404 scmutil.checkfilename(f)
405 405 if f in self._map.dirs:
406 406 raise error.Abort(_('directory %r already in dirstate') % f)
407 407 # shadows
408 408 for d in util.finddirs(f):
409 409 if d in self._map.dirs:
410 410 break
411 411 entry = self._map.get(d)
412 412 if entry is not None and entry[0] != 'r':
413 413 raise error.Abort(
414 414 _('file %r in dirstate clashes with %r') % (d, f))
415 415 if oldstate in "?r" and "dirs" in self._map.__dict__:
416 416 self._map.dirs.addpath(f)
417 417 self._dirty = True
418 418 self._updatedfiles.add(f)
419 419 self._map[f] = dirstatetuple(state, mode, size, mtime)
420 420 if state != 'n' or mtime == -1:
421 421 self._map.nonnormalset.add(f)
422 422 if size == -2:
423 423 self._map.otherparentset.add(f)
424 424
425 425 def normal(self, f):
426 426 '''Mark a file normal and clean.'''
427 427 s = os.lstat(self._join(f))
428 428 mtime = s.st_mtime
429 429 self._addpath(f, 'n', s.st_mode,
430 430 s.st_size & _rangemask, mtime & _rangemask)
431 431 self._map.copymap.pop(f, None)
432 432 if f in self._map.nonnormalset:
433 433 self._map.nonnormalset.remove(f)
434 434 if mtime > self._lastnormaltime:
435 435 # Remember the most recent modification timeslot for status(),
436 436 # to make sure we won't miss future size-preserving file content
437 437 # modifications that happen within the same timeslot.
438 438 self._lastnormaltime = mtime
439 439
440 440 def normallookup(self, f):
441 441 '''Mark a file normal, but possibly dirty.'''
442 442 if self._pl[1] != nullid:
443 443 # if there is a merge going on and the file was either
444 444 # in state 'm' (-1) or coming from other parent (-2) before
445 445 # being removed, restore that state.
446 446 entry = self._map.get(f)
447 447 if entry is not None:
448 448 if entry[0] == 'r' and entry[2] in (-1, -2):
449 449 source = self._map.copymap.get(f)
450 450 if entry[2] == -1:
451 451 self.merge(f)
452 452 elif entry[2] == -2:
453 453 self.otherparent(f)
454 454 if source:
455 455 self.copy(source, f)
456 456 return
457 457 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
458 458 return
459 459 self._addpath(f, 'n', 0, -1, -1)
460 460 self._map.copymap.pop(f, None)
461 461 if f in self._map.nonnormalset:
462 462 self._map.nonnormalset.remove(f)
463 463
464 464 def otherparent(self, f):
465 465 '''Mark as coming from the other parent, always dirty.'''
466 466 if self._pl[1] == nullid:
467 467 raise error.Abort(_("setting %r to other parent "
468 468 "only allowed in merges") % f)
469 469 if f in self and self[f] == 'n':
470 470 # merge-like
471 471 self._addpath(f, 'm', 0, -2, -1)
472 472 else:
473 473 # add-like
474 474 self._addpath(f, 'n', 0, -2, -1)
475 475 self._map.copymap.pop(f, None)
476 476
477 477 def add(self, f):
478 478 '''Mark a file added.'''
479 479 self._addpath(f, 'a', 0, -1, -1)
480 480 self._map.copymap.pop(f, None)
481 481
482 482 def remove(self, f):
483 483 '''Mark a file removed.'''
484 484 self._dirty = True
485 485 self._droppath(f)
486 486 size = 0
487 487 if self._pl[1] != nullid:
488 488 entry = self._map.get(f)
489 489 if entry is not None:
490 490 # backup the previous state
491 491 if entry[0] == 'm': # merge
492 492 size = -1
493 493 elif entry[0] == 'n' and entry[2] == -2: # other parent
494 494 size = -2
495 495 self._map.otherparentset.add(f)
496 496 self._map[f] = dirstatetuple('r', 0, size, 0)
497 497 self._map.nonnormalset.add(f)
498 498 if size == 0:
499 499 self._map.copymap.pop(f, None)
500 500
501 501 def merge(self, f):
502 502 '''Mark a file merged.'''
503 503 if self._pl[1] == nullid:
504 504 return self.normallookup(f)
505 505 return self.otherparent(f)
506 506
507 507 def drop(self, f):
508 508 '''Drop a file from the dirstate'''
509 509 if f in self._map:
510 510 self._dirty = True
511 511 self._droppath(f)
512 512 del self._map[f]
513 513 if f in self._map.nonnormalset:
514 514 self._map.nonnormalset.remove(f)
515 515 self._map.copymap.pop(f, None)
516 516
517 517 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
518 518 if exists is None:
519 519 exists = os.path.lexists(os.path.join(self._root, path))
520 520 if not exists:
521 521 # Maybe a path component exists
522 522 if not ignoremissing and '/' in path:
523 523 d, f = path.rsplit('/', 1)
524 524 d = self._normalize(d, False, ignoremissing, None)
525 525 folded = d + "/" + f
526 526 else:
527 527 # No path components, preserve original case
528 528 folded = path
529 529 else:
530 530 # recursively normalize leading directory components
531 531 # against dirstate
532 532 if '/' in normed:
533 533 d, f = normed.rsplit('/', 1)
534 534 d = self._normalize(d, False, ignoremissing, True)
535 535 r = self._root + "/" + d
536 536 folded = d + "/" + util.fspath(f, r)
537 537 else:
538 538 folded = util.fspath(normed, self._root)
539 539 storemap[normed] = folded
540 540
541 541 return folded
542 542
543 543 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
544 544 normed = util.normcase(path)
545 545 folded = self._map.filefoldmap.get(normed, None)
546 546 if folded is None:
547 547 if isknown:
548 548 folded = path
549 549 else:
550 550 folded = self._discoverpath(path, normed, ignoremissing, exists,
551 551 self._map.filefoldmap)
552 552 return folded
553 553
554 554 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
555 555 normed = util.normcase(path)
556 556 folded = self._map.filefoldmap.get(normed, None)
557 557 if folded is None:
558 558 folded = self._map.dirfoldmap.get(normed, None)
559 559 if folded is None:
560 560 if isknown:
561 561 folded = path
562 562 else:
563 563 # store discovered result in dirfoldmap so that future
564 564 # normalizefile calls don't start matching directories
565 565 folded = self._discoverpath(path, normed, ignoremissing, exists,
566 566 self._map.dirfoldmap)
567 567 return folded
568 568
569 569 def normalize(self, path, isknown=False, ignoremissing=False):
570 570 '''
571 571 normalize the case of a pathname when on a casefolding filesystem
572 572
573 573 isknown specifies whether the filename came from walking the
574 574 disk, to avoid extra filesystem access.
575 575
576 576 If ignoremissing is True, missing path are returned
577 577 unchanged. Otherwise, we try harder to normalize possibly
578 578 existing path components.
579 579
580 580 The normalized case is determined based on the following precedence:
581 581
582 582 - version of name already stored in the dirstate
583 583 - version of name stored on disk
584 584 - version provided via command arguments
585 585 '''
586 586
587 587 if self._checkcase:
588 588 return self._normalize(path, isknown, ignoremissing)
589 589 return path
590 590
591 591 def clear(self):
592 592 self._map.clear()
593 593 self._lastnormaltime = 0
594 594 self._updatedfiles.clear()
595 595 self._dirty = True
596 596
597 597 def rebuild(self, parent, allfiles, changedfiles=None):
598 598 if changedfiles is None:
599 599 # Rebuild entire dirstate
600 600 changedfiles = allfiles
601 601 lastnormaltime = self._lastnormaltime
602 602 self.clear()
603 603 self._lastnormaltime = lastnormaltime
604 604
605 605 if self._origpl is None:
606 606 self._origpl = self._pl
607 607 self._map.setparents(parent, nullid)
608 608 for f in changedfiles:
609 609 if f in allfiles:
610 610 self.normallookup(f)
611 611 else:
612 612 self.drop(f)
613 613
614 614 self._dirty = True
615 615
616 616 def identity(self):
617 617 '''Return identity of dirstate itself to detect changing in storage
618 618
619 619 If identity of previous dirstate is equal to this, writing
620 620 changes based on the former dirstate out can keep consistency.
621 621 '''
622 622 return self._map.identity
623 623
624 624 def write(self, tr):
625 625 if not self._dirty:
626 626 return
627 627
628 628 filename = self._filename
629 629 if tr:
630 630 # 'dirstate.write()' is not only for writing in-memory
631 631 # changes out, but also for dropping ambiguous timestamp.
632 632 # delayed writing re-raise "ambiguous timestamp issue".
633 633 # See also the wiki page below for detail:
634 634 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
635 635
636 636 # emulate dropping timestamp in 'parsers.pack_dirstate'
637 637 now = _getfsnow(self._opener)
638 638 dmap = self._map
639 639 for f in self._updatedfiles:
640 640 e = dmap.get(f)
641 641 if e is not None and e[0] == 'n' and e[3] == now:
642 642 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
643 643 self._map.nonnormalset.add(f)
644 644
645 645 # emulate that all 'dirstate.normal' results are written out
646 646 self._lastnormaltime = 0
647 647 self._updatedfiles.clear()
648 648
649 649 # delay writing in-memory changes out
650 650 tr.addfilegenerator('dirstate', (self._filename,),
651 651 self._writedirstate, location='plain')
652 652 return
653 653
654 654 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
655 655 self._writedirstate(st)
656 656
657 657 def addparentchangecallback(self, category, callback):
658 658 """add a callback to be called when the wd parents are changed
659 659
660 660 Callback will be called with the following arguments:
661 661 dirstate, (oldp1, oldp2), (newp1, newp2)
662 662
663 663 Category is a unique identifier to allow overwriting an old callback
664 664 with a newer callback.
665 665 """
666 666 self._plchangecallbacks[category] = callback
667 667
668 668 def _writedirstate(self, st):
669 669 # notify callbacks about parents change
670 670 if self._origpl is not None and self._origpl != self._pl:
671 671 for c, callback in sorted(self._plchangecallbacks.iteritems()):
672 672 callback(self, self._origpl, self._pl)
673 673 self._origpl = None
674 674 # use the modification time of the newly created temporary file as the
675 675 # filesystem's notion of 'now'
676 676 now = util.fstat(st).st_mtime & _rangemask
677 677
678 678 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
679 679 # timestamp of each entries in dirstate, because of 'now > mtime'
680 680 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
681 681 if delaywrite > 0:
682 682 # do we have any files to delay for?
683 683 for f, e in self._map.iteritems():
684 684 if e[0] == 'n' and e[3] == now:
685 685 import time # to avoid useless import
686 686 # rather than sleep n seconds, sleep until the next
687 687 # multiple of n seconds
688 688 clock = time.time()
689 689 start = int(clock) - (int(clock) % delaywrite)
690 690 end = start + delaywrite
691 691 time.sleep(end - clock)
692 692 now = end # trust our estimate that the end is near now
693 693 break
694 694
695 695 self._map.write(st, now)
696 696 self._lastnormaltime = 0
697 697 self._dirty = False
698 698
699 699 def _dirignore(self, f):
700 700 if f == '.':
701 701 return False
702 702 if self._ignore(f):
703 703 return True
704 704 for p in util.finddirs(f):
705 705 if self._ignore(p):
706 706 return True
707 707 return False
708 708
709 709 def _ignorefiles(self):
710 710 files = []
711 711 if os.path.exists(self._join('.hgignore')):
712 712 files.append(self._join('.hgignore'))
713 713 for name, path in self._ui.configitems("ui"):
714 714 if name == 'ignore' or name.startswith('ignore.'):
715 715 # we need to use os.path.join here rather than self._join
716 716 # because path is arbitrary and user-specified
717 717 files.append(os.path.join(self._rootdir, util.expandpath(path)))
718 718 return files
719 719
720 720 def _ignorefileandline(self, f):
721 721 files = collections.deque(self._ignorefiles())
722 722 visited = set()
723 723 while files:
724 724 i = files.popleft()
725 725 patterns = matchmod.readpatternfile(i, self._ui.warn,
726 726 sourceinfo=True)
727 727 for pattern, lineno, line in patterns:
728 728 kind, p = matchmod._patsplit(pattern, 'glob')
729 729 if kind == "subinclude":
730 730 if p not in visited:
731 731 files.append(p)
732 732 continue
733 733 m = matchmod.match(self._root, '', [], [pattern],
734 734 warn=self._ui.warn)
735 735 if m(f):
736 736 return (i, lineno, line)
737 737 visited.add(i)
738 738 return (None, -1, "")
739 739
740 740 def _walkexplicit(self, match, subrepos):
741 741 '''Get stat data about the files explicitly specified by match.
742 742
743 743 Return a triple (results, dirsfound, dirsnotfound).
744 744 - results is a mapping from filename to stat result. It also contains
745 745 listings mapping subrepos and .hg to None.
746 746 - dirsfound is a list of files found to be directories.
747 747 - dirsnotfound is a list of files that the dirstate thinks are
748 748 directories and that were not found.'''
749 749
750 750 def badtype(mode):
751 751 kind = _('unknown')
752 752 if stat.S_ISCHR(mode):
753 753 kind = _('character device')
754 754 elif stat.S_ISBLK(mode):
755 755 kind = _('block device')
756 756 elif stat.S_ISFIFO(mode):
757 757 kind = _('fifo')
758 758 elif stat.S_ISSOCK(mode):
759 759 kind = _('socket')
760 760 elif stat.S_ISDIR(mode):
761 761 kind = _('directory')
762 762 return _('unsupported file type (type is %s)') % kind
763 763
764 764 matchedir = match.explicitdir
765 765 badfn = match.bad
766 766 dmap = self._map
767 767 lstat = os.lstat
768 768 getkind = stat.S_IFMT
769 769 dirkind = stat.S_IFDIR
770 770 regkind = stat.S_IFREG
771 771 lnkkind = stat.S_IFLNK
772 772 join = self._join
773 773 dirsfound = []
774 774 foundadd = dirsfound.append
775 775 dirsnotfound = []
776 776 notfoundadd = dirsnotfound.append
777 777
778 778 if not match.isexact() and self._checkcase:
779 779 normalize = self._normalize
780 780 else:
781 781 normalize = None
782 782
783 783 files = sorted(match.files())
784 784 subrepos.sort()
785 785 i, j = 0, 0
786 786 while i < len(files) and j < len(subrepos):
787 787 subpath = subrepos[j] + "/"
788 788 if files[i] < subpath:
789 789 i += 1
790 790 continue
791 791 while i < len(files) and files[i].startswith(subpath):
792 792 del files[i]
793 793 j += 1
794 794
795 795 if not files or '.' in files:
796 796 files = ['.']
797 797 results = dict.fromkeys(subrepos)
798 798 results['.hg'] = None
799 799
800 800 alldirs = None
801 801 for ff in files:
802 802 # constructing the foldmap is expensive, so don't do it for the
803 803 # common case where files is ['.']
804 804 if normalize and ff != '.':
805 805 nf = normalize(ff, False, True)
806 806 else:
807 807 nf = ff
808 808 if nf in results:
809 809 continue
810 810
811 811 try:
812 812 st = lstat(join(nf))
813 813 kind = getkind(st.st_mode)
814 814 if kind == dirkind:
815 815 if nf in dmap:
816 816 # file replaced by dir on disk but still in dirstate
817 817 results[nf] = None
818 818 if matchedir:
819 819 matchedir(nf)
820 820 foundadd((nf, ff))
821 821 elif kind == regkind or kind == lnkkind:
822 822 results[nf] = st
823 823 else:
824 824 badfn(ff, badtype(kind))
825 825 if nf in dmap:
826 826 results[nf] = None
827 827 except OSError as inst: # nf not found on disk - it is dirstate only
828 828 if nf in dmap: # does it exactly match a missing file?
829 829 results[nf] = None
830 830 else: # does it match a missing directory?
831 831 if alldirs is None:
832 832 alldirs = util.dirs(dmap._map)
833 833 if nf in alldirs:
834 834 if matchedir:
835 835 matchedir(nf)
836 836 notfoundadd(nf)
837 837 else:
838 838 badfn(ff, encoding.strtolocal(inst.strerror))
839 839
840 840 # Case insensitive filesystems cannot rely on lstat() failing to detect
841 841 # a case-only rename. Prune the stat object for any file that does not
842 842 # match the case in the filesystem, if there are multiple files that
843 843 # normalize to the same path.
844 844 if match.isexact() and self._checkcase:
845 845 normed = {}
846 846
847 847 for f, st in results.iteritems():
848 848 if st is None:
849 849 continue
850 850
851 851 nc = util.normcase(f)
852 852 paths = normed.get(nc)
853 853
854 854 if paths is None:
855 855 paths = set()
856 856 normed[nc] = paths
857 857
858 858 paths.add(f)
859 859
860 860 for norm, paths in normed.iteritems():
861 861 if len(paths) > 1:
862 862 for path in paths:
863 863 folded = self._discoverpath(path, norm, True, None,
864 864 self._map.dirfoldmap)
865 865 if path != folded:
866 866 results[path] = None
867 867
868 868 return results, dirsfound, dirsnotfound
869 869
870 870 def walk(self, match, subrepos, unknown, ignored, full=True):
871 871 '''
872 872 Walk recursively through the directory tree, finding all files
873 873 matched by match.
874 874
875 875 If full is False, maybe skip some known-clean files.
876 876
877 877 Return a dict mapping filename to stat-like object (either
878 878 mercurial.osutil.stat instance or return value of os.stat()).
879 879
880 880 '''
881 881 # full is a flag that extensions that hook into walk can use -- this
882 882 # implementation doesn't use it at all. This satisfies the contract
883 883 # because we only guarantee a "maybe".
884 884
885 885 if ignored:
886 886 ignore = util.never
887 887 dirignore = util.never
888 888 elif unknown:
889 889 ignore = self._ignore
890 890 dirignore = self._dirignore
891 891 else:
892 892 # if not unknown and not ignored, drop dir recursion and step 2
893 893 ignore = util.always
894 894 dirignore = util.always
895 895
896 896 matchfn = match.matchfn
897 897 matchalways = match.always()
898 898 matchtdir = match.traversedir
899 899 dmap = self._map
900 900 listdir = util.listdir
901 901 lstat = os.lstat
902 902 dirkind = stat.S_IFDIR
903 903 regkind = stat.S_IFREG
904 904 lnkkind = stat.S_IFLNK
905 905 join = self._join
906 906
907 907 exact = skipstep3 = False
908 908 if match.isexact(): # match.exact
909 909 exact = True
910 910 dirignore = util.always # skip step 2
911 911 elif match.prefix(): # match.match, no patterns
912 912 skipstep3 = True
913 913
914 914 if not exact and self._checkcase:
915 915 normalize = self._normalize
916 916 normalizefile = self._normalizefile
917 917 skipstep3 = False
918 918 else:
919 919 normalize = self._normalize
920 920 normalizefile = None
921 921
922 922 # step 1: find all explicit files
923 923 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
924 924
925 925 skipstep3 = skipstep3 and not (work or dirsnotfound)
926 926 work = [d for d in work if not dirignore(d[0])]
927 927
928 928 # step 2: visit subdirectories
929 929 def traverse(work, alreadynormed):
930 930 wadd = work.append
931 931 while work:
932 932 nd = work.pop()
933 933 if not match.visitdir(nd):
934 934 continue
935 935 skip = None
936 936 if nd == '.':
937 937 nd = ''
938 938 else:
939 939 skip = '.hg'
940 940 try:
941 941 entries = listdir(join(nd), stat=True, skip=skip)
942 942 except OSError as inst:
943 943 if inst.errno in (errno.EACCES, errno.ENOENT):
944 944 match.bad(self.pathto(nd),
945 945 encoding.strtolocal(inst.strerror))
946 946 continue
947 947 raise
948 948 for f, kind, st in entries:
949 949 if normalizefile:
950 950 # even though f might be a directory, we're only
951 951 # interested in comparing it to files currently in the
952 952 # dmap -- therefore normalizefile is enough
953 953 nf = normalizefile(nd and (nd + "/" + f) or f, True,
954 954 True)
955 955 else:
956 956 nf = nd and (nd + "/" + f) or f
957 957 if nf not in results:
958 958 if kind == dirkind:
959 959 if not ignore(nf):
960 960 if matchtdir:
961 961 matchtdir(nf)
962 962 wadd(nf)
963 963 if nf in dmap and (matchalways or matchfn(nf)):
964 964 results[nf] = None
965 965 elif kind == regkind or kind == lnkkind:
966 966 if nf in dmap:
967 967 if matchalways or matchfn(nf):
968 968 results[nf] = st
969 969 elif ((matchalways or matchfn(nf))
970 970 and not ignore(nf)):
971 971 # unknown file -- normalize if necessary
972 972 if not alreadynormed:
973 973 nf = normalize(nf, False, True)
974 974 results[nf] = st
975 975 elif nf in dmap and (matchalways or matchfn(nf)):
976 976 results[nf] = None
977 977
978 978 for nd, d in work:
979 979 # alreadynormed means that processwork doesn't have to do any
980 980 # expensive directory normalization
981 981 alreadynormed = not normalize or nd == d
982 982 traverse([d], alreadynormed)
983 983
984 984 for s in subrepos:
985 985 del results[s]
986 986 del results['.hg']
987 987
988 988 # step 3: visit remaining files from dmap
989 989 if not skipstep3 and not exact:
990 990 # If a dmap file is not in results yet, it was either
991 991 # a) not matching matchfn b) ignored, c) missing, or d) under a
992 992 # symlink directory.
993 993 if not results and matchalways:
994 994 visit = [f for f in dmap]
995 995 else:
996 996 visit = [f for f in dmap if f not in results and matchfn(f)]
997 997 visit.sort()
998 998
999 999 if unknown:
1000 1000 # unknown == True means we walked all dirs under the roots
1001 1001 # that wasn't ignored, and everything that matched was stat'ed
1002 1002 # and is already in results.
1003 1003 # The rest must thus be ignored or under a symlink.
1004 1004 audit_path = pathutil.pathauditor(self._root, cached=True)
1005 1005
1006 1006 for nf in iter(visit):
1007 1007 # If a stat for the same file was already added with a
1008 1008 # different case, don't add one for this, since that would
1009 1009 # make it appear as if the file exists under both names
1010 1010 # on disk.
1011 1011 if (normalizefile and
1012 1012 normalizefile(nf, True, True) in results):
1013 1013 results[nf] = None
1014 1014 # Report ignored items in the dmap as long as they are not
1015 1015 # under a symlink directory.
1016 1016 elif audit_path.check(nf):
1017 1017 try:
1018 1018 results[nf] = lstat(join(nf))
1019 1019 # file was just ignored, no links, and exists
1020 1020 except OSError:
1021 1021 # file doesn't exist
1022 1022 results[nf] = None
1023 1023 else:
1024 1024 # It's either missing or under a symlink directory
1025 1025 # which we in this case report as missing
1026 1026 results[nf] = None
1027 1027 else:
1028 1028 # We may not have walked the full directory tree above,
1029 1029 # so stat and check everything we missed.
1030 1030 iv = iter(visit)
1031 1031 for st in util.statfiles([join(i) for i in visit]):
1032 1032 results[next(iv)] = st
1033 1033 return results
1034 1034
1035 1035 def status(self, match, subrepos, ignored, clean, unknown):
1036 1036 '''Determine the status of the working copy relative to the
1037 1037 dirstate and return a pair of (unsure, status), where status is of type
1038 1038 scmutil.status and:
1039 1039
1040 1040 unsure:
1041 1041 files that might have been modified since the dirstate was
1042 1042 written, but need to be read to be sure (size is the same
1043 1043 but mtime differs)
1044 1044 status.modified:
1045 1045 files that have definitely been modified since the dirstate
1046 1046 was written (different size or mode)
1047 1047 status.clean:
1048 1048 files that have definitely not been modified since the
1049 1049 dirstate was written
1050 1050 '''
1051 1051 listignored, listclean, listunknown = ignored, clean, unknown
1052 1052 lookup, modified, added, unknown, ignored = [], [], [], [], []
1053 1053 removed, deleted, clean = [], [], []
1054 1054
1055 1055 dmap = self._map
1056 dmap.preload()
1057 dcontains = dmap.__contains__
1058 dget = dmap.__getitem__
1056 1059 ladd = lookup.append # aka "unsure"
1057 1060 madd = modified.append
1058 1061 aadd = added.append
1059 1062 uadd = unknown.append
1060 1063 iadd = ignored.append
1061 1064 radd = removed.append
1062 1065 dadd = deleted.append
1063 1066 cadd = clean.append
1064 1067 mexact = match.exact
1065 1068 dirignore = self._dirignore
1066 1069 checkexec = self._checkexec
1067 1070 copymap = self._map.copymap
1068 1071 lastnormaltime = self._lastnormaltime
1069 1072
1070 1073 # We need to do full walks when either
1071 1074 # - we're listing all clean files, or
1072 1075 # - match.traversedir does something, because match.traversedir should
1073 1076 # be called for every dir in the working dir
1074 1077 full = listclean or match.traversedir is not None
1075 1078 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1076 1079 full=full).iteritems():
1077 if fn not in dmap:
1080 if not dcontains(fn):
1078 1081 if (listignored or mexact(fn)) and dirignore(fn):
1079 1082 if listignored:
1080 1083 iadd(fn)
1081 1084 else:
1082 1085 uadd(fn)
1083 1086 continue
1084 1087
1085 1088 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1086 1089 # written like that for performance reasons. dmap[fn] is not a
1087 1090 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1088 1091 # opcode has fast paths when the value to be unpacked is a tuple or
1089 1092 # a list, but falls back to creating a full-fledged iterator in
1090 1093 # general. That is much slower than simply accessing and storing the
1091 1094 # tuple members one by one.
1092 t = dmap[fn]
1095 t = dget(fn)
1093 1096 state = t[0]
1094 1097 mode = t[1]
1095 1098 size = t[2]
1096 1099 time = t[3]
1097 1100
1098 1101 if not st and state in "nma":
1099 1102 dadd(fn)
1100 1103 elif state == 'n':
1101 1104 if (size >= 0 and
1102 1105 ((size != st.st_size and size != st.st_size & _rangemask)
1103 1106 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1104 1107 or size == -2 # other parent
1105 1108 or fn in copymap):
1106 1109 madd(fn)
1107 1110 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1108 1111 ladd(fn)
1109 1112 elif st.st_mtime == lastnormaltime:
1110 1113 # fn may have just been marked as normal and it may have
1111 1114 # changed in the same second without changing its size.
1112 1115 # This can happen if we quickly do multiple commits.
1113 1116 # Force lookup, so we don't miss such a racy file change.
1114 1117 ladd(fn)
1115 1118 elif listclean:
1116 1119 cadd(fn)
1117 1120 elif state == 'm':
1118 1121 madd(fn)
1119 1122 elif state == 'a':
1120 1123 aadd(fn)
1121 1124 elif state == 'r':
1122 1125 radd(fn)
1123 1126
1124 1127 return (lookup, scmutil.status(modified, added, removed, deleted,
1125 1128 unknown, ignored, clean))
1126 1129
1127 1130 def matches(self, match):
1128 1131 '''
1129 1132 return files in the dirstate (in whatever state) filtered by match
1130 1133 '''
1131 1134 dmap = self._map
1132 1135 if match.always():
1133 1136 return dmap.keys()
1134 1137 files = match.files()
1135 1138 if match.isexact():
1136 1139 # fast path -- filter the other way around, since typically files is
1137 1140 # much smaller than dmap
1138 1141 return [f for f in files if f in dmap]
1139 1142 if match.prefix() and all(fn in dmap for fn in files):
1140 1143 # fast path -- all the values are known to be files, so just return
1141 1144 # that
1142 1145 return list(files)
1143 1146 return [f for f in dmap if match(f)]
1144 1147
1145 1148 def _actualfilename(self, tr):
1146 1149 if tr:
1147 1150 return self._pendingfilename
1148 1151 else:
1149 1152 return self._filename
1150 1153
1151 1154 def savebackup(self, tr, backupname):
1152 1155 '''Save current dirstate into backup file'''
1153 1156 filename = self._actualfilename(tr)
1154 1157 assert backupname != filename
1155 1158
1156 1159 # use '_writedirstate' instead of 'write' to write changes certainly,
1157 1160 # because the latter omits writing out if transaction is running.
1158 1161 # output file will be used to create backup of dirstate at this point.
1159 1162 if self._dirty or not self._opener.exists(filename):
1160 1163 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1161 1164 checkambig=True))
1162 1165
1163 1166 if tr:
1164 1167 # ensure that subsequent tr.writepending returns True for
1165 1168 # changes written out above, even if dirstate is never
1166 1169 # changed after this
1167 1170 tr.addfilegenerator('dirstate', (self._filename,),
1168 1171 self._writedirstate, location='plain')
1169 1172
1170 1173 # ensure that pending file written above is unlinked at
1171 1174 # failure, even if tr.writepending isn't invoked until the
1172 1175 # end of this transaction
1173 1176 tr.registertmp(filename, location='plain')
1174 1177
1175 1178 self._opener.tryunlink(backupname)
1176 1179 # hardlink backup is okay because _writedirstate is always called
1177 1180 # with an "atomictemp=True" file.
1178 1181 util.copyfile(self._opener.join(filename),
1179 1182 self._opener.join(backupname), hardlink=True)
1180 1183
1181 1184 def restorebackup(self, tr, backupname):
1182 1185 '''Restore dirstate by backup file'''
1183 1186 # this "invalidate()" prevents "wlock.release()" from writing
1184 1187 # changes of dirstate out after restoring from backup file
1185 1188 self.invalidate()
1186 1189 filename = self._actualfilename(tr)
1187 1190 self._opener.rename(backupname, filename, checkambig=True)
1188 1191
1189 1192 def clearbackup(self, tr, backupname):
1190 1193 '''Clear backup file'''
1191 1194 self._opener.unlink(backupname)
1192 1195
1193 1196 class dirstatemap(object):
1194 1197 def __init__(self, ui, opener, root):
1195 1198 self._ui = ui
1196 1199 self._opener = opener
1197 1200 self._root = root
1198 1201 self._filename = 'dirstate'
1199 1202
1200 1203 self._parents = None
1201 1204 self._dirtyparents = False
1202 1205
1203 1206 # for consistent view between _pl() and _read() invocations
1204 1207 self._pendingmode = None
1205 1208
1206 1209 @propertycache
1207 1210 def _map(self):
1208 1211 self._map = {}
1209 1212 self.read()
1210 1213 return self._map
1211 1214
1212 1215 @propertycache
1213 1216 def copymap(self):
1214 1217 self.copymap = {}
1215 1218 self._map
1216 1219 return self.copymap
1217 1220
1218 1221 def clear(self):
1219 self._map = {}
1220 self.copymap = {}
1222 self._map.clear()
1223 self.copymap.clear()
1221 1224 self.setparents(nullid, nullid)
1222 1225
1223 1226 def iteritems(self):
1224 1227 return self._map.iteritems()
1225 1228
1226 1229 def __len__(self):
1227 1230 return len(self._map)
1228 1231
1229 1232 def __iter__(self):
1230 1233 return iter(self._map)
1231 1234
1232 1235 def get(self, key, default=None):
1233 1236 return self._map.get(key, default)
1234 1237
1235 1238 def __contains__(self, key):
1236 1239 return key in self._map
1237 1240
1238 1241 def __setitem__(self, key, value):
1239 1242 self._map[key] = value
1240 1243
1241 1244 def __getitem__(self, key):
1242 1245 return self._map[key]
1243 1246
1244 1247 def __delitem__(self, key):
1245 1248 del self._map[key]
1246 1249
1247 1250 def keys(self):
1248 1251 return self._map.keys()
1249 1252
1253 def preload(self):
1254 """Loads the underlying data, if it's not already loaded"""
1255 self._map
1256
1250 1257 def nonnormalentries(self):
1251 1258 '''Compute the nonnormal dirstate entries from the dmap'''
1252 1259 try:
1253 1260 return parsers.nonnormalotherparententries(self._map)
1254 1261 except AttributeError:
1255 1262 nonnorm = set()
1256 1263 otherparent = set()
1257 1264 for fname, e in self._map.iteritems():
1258 1265 if e[0] != 'n' or e[3] == -1:
1259 1266 nonnorm.add(fname)
1260 1267 if e[0] == 'n' and e[2] == -2:
1261 1268 otherparent.add(fname)
1262 1269 return nonnorm, otherparent
1263 1270
1264 1271 @propertycache
1265 1272 def filefoldmap(self):
1266 1273 """Returns a dictionary mapping normalized case paths to their
1267 1274 non-normalized versions.
1268 1275 """
1269 1276 try:
1270 1277 makefilefoldmap = parsers.make_file_foldmap
1271 1278 except AttributeError:
1272 1279 pass
1273 1280 else:
1274 1281 return makefilefoldmap(self._map, util.normcasespec,
1275 1282 util.normcasefallback)
1276 1283
1277 1284 f = {}
1278 1285 normcase = util.normcase
1279 1286 for name, s in self._map.iteritems():
1280 1287 if s[0] != 'r':
1281 1288 f[normcase(name)] = name
1282 1289 f['.'] = '.' # prevents useless util.fspath() invocation
1283 1290 return f
1284 1291
1285 1292 @propertycache
1286 1293 def dirs(self):
1287 1294 """Returns a set-like object containing all the directories in the
1288 1295 current dirstate.
1289 1296 """
1290 1297 return util.dirs(self._map, 'r')
1291 1298
1292 1299 def _opendirstatefile(self):
1293 1300 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1294 1301 if self._pendingmode is not None and self._pendingmode != mode:
1295 1302 fp.close()
1296 1303 raise error.Abort(_('working directory state may be '
1297 1304 'changed parallelly'))
1298 1305 self._pendingmode = mode
1299 1306 return fp
1300 1307
1301 1308 def parents(self):
1302 1309 if not self._parents:
1303 1310 try:
1304 1311 fp = self._opendirstatefile()
1305 1312 st = fp.read(40)
1306 1313 fp.close()
1307 1314 except IOError as err:
1308 1315 if err.errno != errno.ENOENT:
1309 1316 raise
1310 1317 # File doesn't exist, so the current state is empty
1311 1318 st = ''
1312 1319
1313 1320 l = len(st)
1314 1321 if l == 40:
1315 1322 self._parents = st[:20], st[20:40]
1316 1323 elif l == 0:
1317 1324 self._parents = [nullid, nullid]
1318 1325 else:
1319 1326 raise error.Abort(_('working directory state appears '
1320 1327 'damaged!'))
1321 1328
1322 1329 return self._parents
1323 1330
1324 1331 def setparents(self, p1, p2):
1325 1332 self._parents = (p1, p2)
1326 1333 self._dirtyparents = True
1327 1334
1328 1335 def read(self):
1329 1336 # ignore HG_PENDING because identity is used only for writing
1330 1337 self.identity = util.filestat.frompath(
1331 1338 self._opener.join(self._filename))
1332 1339
1333 1340 try:
1334 1341 fp = self._opendirstatefile()
1335 1342 try:
1336 1343 st = fp.read()
1337 1344 finally:
1338 1345 fp.close()
1339 1346 except IOError as err:
1340 1347 if err.errno != errno.ENOENT:
1341 1348 raise
1342 1349 return
1343 1350 if not st:
1344 1351 return
1345 1352
1346 1353 if util.safehasattr(parsers, 'dict_new_presized'):
1347 1354 # Make an estimate of the number of files in the dirstate based on
1348 1355 # its size. From a linear regression on a set of real-world repos,
1349 1356 # all over 10,000 files, the size of a dirstate entry is 85
1350 1357 # bytes. The cost of resizing is significantly higher than the cost
1351 1358 # of filling in a larger presized dict, so subtract 20% from the
1352 1359 # size.
1353 1360 #
1354 1361 # This heuristic is imperfect in many ways, so in a future dirstate
1355 1362 # format update it makes sense to just record the number of entries
1356 1363 # on write.
1357 1364 self._map = parsers.dict_new_presized(len(st) / 71)
1358 1365
1359 1366 # Python's garbage collector triggers a GC each time a certain number
1360 1367 # of container objects (the number being defined by
1361 1368 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1362 1369 # for each file in the dirstate. The C version then immediately marks
1363 1370 # them as not to be tracked by the collector. However, this has no
1364 1371 # effect on when GCs are triggered, only on what objects the GC looks
1365 1372 # into. This means that O(number of files) GCs are unavoidable.
1366 1373 # Depending on when in the process's lifetime the dirstate is parsed,
1367 1374 # this can get very expensive. As a workaround, disable GC while
1368 1375 # parsing the dirstate.
1369 1376 #
1370 1377 # (we cannot decorate the function directly since it is in a C module)
1371 1378 parse_dirstate = util.nogc(parsers.parse_dirstate)
1372 1379 p = parse_dirstate(self._map, self.copymap, st)
1373 1380 if not self._dirtyparents:
1374 1381 self.setparents(*p)
1375 1382
1383 # Avoid excess attribute lookups by fast pathing certain checks
1384 self.__contains__ = self._map.__contains__
1385 self.__getitem__ = self._map.__getitem__
1386 self.__setitem__ = self._map.__setitem__
1387 self.__delitem__ = self._map.__delitem__
1388 self.get = self._map.get
1389
1376 1390 def write(self, st, now):
1377 1391 st.write(parsers.pack_dirstate(self._map, self.copymap,
1378 1392 self.parents(), now))
1379 1393 st.close()
1380 1394 self._dirtyparents = False
1381 1395 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1382 1396
1383 1397 @propertycache
1384 1398 def nonnormalset(self):
1385 1399 nonnorm, otherparents = self.nonnormalentries()
1386 1400 self.otherparentset = otherparents
1387 1401 return nonnorm
1388 1402
1389 1403 @propertycache
1390 1404 def otherparentset(self):
1391 1405 nonnorm, otherparents = self.nonnormalentries()
1392 1406 self.nonnormalset = nonnorm
1393 1407 return otherparents
1394 1408
1395 1409 @propertycache
1396 1410 def identity(self):
1397 1411 self._map
1398 1412 return self.identity
1399 1413
1400 1414 @propertycache
1401 1415 def dirfoldmap(self):
1402 1416 f = {}
1403 1417 normcase = util.normcase
1404 1418 for name in self.dirs:
1405 1419 f[normcase(name)] = name
1406 1420 return f
General Comments 0
You need to be logged in to leave comments. Login now