##// END OF EJS Templates
dirstate: avoid reading the map when possible (issue5713) (issue5717)...
Durham Goode -
r34935:6e66033f stable
parent child Browse files
Show More
@@ -1,1400 +1,1406 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 self._read()
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 def _read(self):
357 self._map = dirstatemap(self._ui, self._opener, self._root)
358 self._map.read()
359
360 356 def invalidate(self):
361 357 '''Causes the next access to reread the dirstate.
362 358
363 359 This is different from localrepo.invalidatedirstate() because it always
364 360 rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
365 361 check whether the dirstate has changed before rereading it.'''
366 362
367 363 for a in ("_map", "_branch", "_ignore"):
368 364 if a in self.__dict__:
369 365 delattr(self, a)
370 366 self._lastnormaltime = 0
371 367 self._dirty = False
372 368 self._updatedfiles.clear()
373 369 self._parentwriters = 0
374 370 self._origpl = None
375 371
376 372 def copy(self, source, dest):
377 373 """Mark dest as a copy of source. Unmark dest if source is None."""
378 374 if source == dest:
379 375 return
380 376 self._dirty = True
381 377 if source is not None:
382 378 self._map.copymap[dest] = source
383 379 self._updatedfiles.add(source)
384 380 self._updatedfiles.add(dest)
385 381 elif self._map.copymap.pop(dest, None):
386 382 self._updatedfiles.add(dest)
387 383
388 384 def copied(self, file):
389 385 return self._map.copymap.get(file, None)
390 386
391 387 def copies(self):
392 388 return self._map.copymap
393 389
394 390 def _droppath(self, f):
395 391 if self[f] not in "?r" and "dirs" in self._map.__dict__:
396 392 self._map.dirs.delpath(f)
397 393
398 394 if "filefoldmap" in self._map.__dict__:
399 395 normed = util.normcase(f)
400 396 if normed in self._map.filefoldmap:
401 397 del self._map.filefoldmap[normed]
402 398
403 399 self._updatedfiles.add(f)
404 400
405 401 def _addpath(self, f, state, mode, size, mtime):
406 402 oldstate = self[f]
407 403 if state == 'a' or oldstate == 'r':
408 404 scmutil.checkfilename(f)
409 405 if f in self._map.dirs:
410 406 raise error.Abort(_('directory %r already in dirstate') % f)
411 407 # shadows
412 408 for d in util.finddirs(f):
413 409 if d in self._map.dirs:
414 410 break
415 411 entry = self._map.get(d)
416 412 if entry is not None and entry[0] != 'r':
417 413 raise error.Abort(
418 414 _('file %r in dirstate clashes with %r') % (d, f))
419 415 if oldstate in "?r" and "dirs" in self._map.__dict__:
420 416 self._map.dirs.addpath(f)
421 417 self._dirty = True
422 418 self._updatedfiles.add(f)
423 419 self._map[f] = dirstatetuple(state, mode, size, mtime)
424 420 if state != 'n' or mtime == -1:
425 421 self._map.nonnormalset.add(f)
426 422 if size == -2:
427 423 self._map.otherparentset.add(f)
428 424
429 425 def normal(self, f):
430 426 '''Mark a file normal and clean.'''
431 427 s = os.lstat(self._join(f))
432 428 mtime = s.st_mtime
433 429 self._addpath(f, 'n', s.st_mode,
434 430 s.st_size & _rangemask, mtime & _rangemask)
435 431 self._map.copymap.pop(f, None)
436 432 if f in self._map.nonnormalset:
437 433 self._map.nonnormalset.remove(f)
438 434 if mtime > self._lastnormaltime:
439 435 # Remember the most recent modification timeslot for status(),
440 436 # to make sure we won't miss future size-preserving file content
441 437 # modifications that happen within the same timeslot.
442 438 self._lastnormaltime = mtime
443 439
444 440 def normallookup(self, f):
445 441 '''Mark a file normal, but possibly dirty.'''
446 442 if self._pl[1] != nullid:
447 443 # if there is a merge going on and the file was either
448 444 # in state 'm' (-1) or coming from other parent (-2) before
449 445 # being removed, restore that state.
450 446 entry = self._map.get(f)
451 447 if entry is not None:
452 448 if entry[0] == 'r' and entry[2] in (-1, -2):
453 449 source = self._map.copymap.get(f)
454 450 if entry[2] == -1:
455 451 self.merge(f)
456 452 elif entry[2] == -2:
457 453 self.otherparent(f)
458 454 if source:
459 455 self.copy(source, f)
460 456 return
461 457 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
462 458 return
463 459 self._addpath(f, 'n', 0, -1, -1)
464 460 self._map.copymap.pop(f, None)
465 461 if f in self._map.nonnormalset:
466 462 self._map.nonnormalset.remove(f)
467 463
468 464 def otherparent(self, f):
469 465 '''Mark as coming from the other parent, always dirty.'''
470 466 if self._pl[1] == nullid:
471 467 raise error.Abort(_("setting %r to other parent "
472 468 "only allowed in merges") % f)
473 469 if f in self and self[f] == 'n':
474 470 # merge-like
475 471 self._addpath(f, 'm', 0, -2, -1)
476 472 else:
477 473 # add-like
478 474 self._addpath(f, 'n', 0, -2, -1)
479 475 self._map.copymap.pop(f, None)
480 476
481 477 def add(self, f):
482 478 '''Mark a file added.'''
483 479 self._addpath(f, 'a', 0, -1, -1)
484 480 self._map.copymap.pop(f, None)
485 481
486 482 def remove(self, f):
487 483 '''Mark a file removed.'''
488 484 self._dirty = True
489 485 self._droppath(f)
490 486 size = 0
491 487 if self._pl[1] != nullid:
492 488 entry = self._map.get(f)
493 489 if entry is not None:
494 490 # backup the previous state
495 491 if entry[0] == 'm': # merge
496 492 size = -1
497 493 elif entry[0] == 'n' and entry[2] == -2: # other parent
498 494 size = -2
499 495 self._map.otherparentset.add(f)
500 496 self._map[f] = dirstatetuple('r', 0, size, 0)
501 497 self._map.nonnormalset.add(f)
502 498 if size == 0:
503 499 self._map.copymap.pop(f, None)
504 500
505 501 def merge(self, f):
506 502 '''Mark a file merged.'''
507 503 if self._pl[1] == nullid:
508 504 return self.normallookup(f)
509 505 return self.otherparent(f)
510 506
511 507 def drop(self, f):
512 508 '''Drop a file from the dirstate'''
513 509 if f in self._map:
514 510 self._dirty = True
515 511 self._droppath(f)
516 512 del self._map[f]
517 513 if f in self._map.nonnormalset:
518 514 self._map.nonnormalset.remove(f)
519 515 self._map.copymap.pop(f, None)
520 516
521 517 def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
522 518 if exists is None:
523 519 exists = os.path.lexists(os.path.join(self._root, path))
524 520 if not exists:
525 521 # Maybe a path component exists
526 522 if not ignoremissing and '/' in path:
527 523 d, f = path.rsplit('/', 1)
528 524 d = self._normalize(d, False, ignoremissing, None)
529 525 folded = d + "/" + f
530 526 else:
531 527 # No path components, preserve original case
532 528 folded = path
533 529 else:
534 530 # recursively normalize leading directory components
535 531 # against dirstate
536 532 if '/' in normed:
537 533 d, f = normed.rsplit('/', 1)
538 534 d = self._normalize(d, False, ignoremissing, True)
539 535 r = self._root + "/" + d
540 536 folded = d + "/" + util.fspath(f, r)
541 537 else:
542 538 folded = util.fspath(normed, self._root)
543 539 storemap[normed] = folded
544 540
545 541 return folded
546 542
547 543 def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
548 544 normed = util.normcase(path)
549 545 folded = self._map.filefoldmap.get(normed, None)
550 546 if folded is None:
551 547 if isknown:
552 548 folded = path
553 549 else:
554 550 folded = self._discoverpath(path, normed, ignoremissing, exists,
555 551 self._map.filefoldmap)
556 552 return folded
557 553
558 554 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
559 555 normed = util.normcase(path)
560 556 folded = self._map.filefoldmap.get(normed, None)
561 557 if folded is None:
562 558 folded = self._map.dirfoldmap.get(normed, None)
563 559 if folded is None:
564 560 if isknown:
565 561 folded = path
566 562 else:
567 563 # store discovered result in dirfoldmap so that future
568 564 # normalizefile calls don't start matching directories
569 565 folded = self._discoverpath(path, normed, ignoremissing, exists,
570 566 self._map.dirfoldmap)
571 567 return folded
572 568
573 569 def normalize(self, path, isknown=False, ignoremissing=False):
574 570 '''
575 571 normalize the case of a pathname when on a casefolding filesystem
576 572
577 573 isknown specifies whether the filename came from walking the
578 574 disk, to avoid extra filesystem access.
579 575
580 576 If ignoremissing is True, missing path are returned
581 577 unchanged. Otherwise, we try harder to normalize possibly
582 578 existing path components.
583 579
584 580 The normalized case is determined based on the following precedence:
585 581
586 582 - version of name already stored in the dirstate
587 583 - version of name stored on disk
588 584 - version provided via command arguments
589 585 '''
590 586
591 587 if self._checkcase:
592 588 return self._normalize(path, isknown, ignoremissing)
593 589 return path
594 590
595 591 def clear(self):
596 592 self._map.clear()
597 593 self._lastnormaltime = 0
598 594 self._updatedfiles.clear()
599 595 self._dirty = True
600 596
601 597 def rebuild(self, parent, allfiles, changedfiles=None):
602 598 if changedfiles is None:
603 599 # Rebuild entire dirstate
604 600 changedfiles = allfiles
605 601 lastnormaltime = self._lastnormaltime
606 602 self.clear()
607 603 self._lastnormaltime = lastnormaltime
608 604
609 605 if self._origpl is None:
610 606 self._origpl = self._pl
611 607 self._map.setparents(parent, nullid)
612 608 for f in changedfiles:
613 609 if f in allfiles:
614 610 self.normallookup(f)
615 611 else:
616 612 self.drop(f)
617 613
618 614 self._dirty = True
619 615
620 616 def identity(self):
621 617 '''Return identity of dirstate itself to detect changing in storage
622 618
623 619 If identity of previous dirstate is equal to this, writing
624 620 changes based on the former dirstate out can keep consistency.
625 621 '''
626 622 return self._map.identity
627 623
628 624 def write(self, tr):
629 625 if not self._dirty:
630 626 return
631 627
632 628 filename = self._filename
633 629 if tr:
634 630 # 'dirstate.write()' is not only for writing in-memory
635 631 # changes out, but also for dropping ambiguous timestamp.
636 632 # delayed writing re-raise "ambiguous timestamp issue".
637 633 # See also the wiki page below for detail:
638 634 # https://www.mercurial-scm.org/wiki/DirstateTransactionPlan
639 635
640 636 # emulate dropping timestamp in 'parsers.pack_dirstate'
641 637 now = _getfsnow(self._opener)
642 638 dmap = self._map
643 639 for f in self._updatedfiles:
644 640 e = dmap.get(f)
645 641 if e is not None and e[0] == 'n' and e[3] == now:
646 642 dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
647 643 self._map.nonnormalset.add(f)
648 644
649 645 # emulate that all 'dirstate.normal' results are written out
650 646 self._lastnormaltime = 0
651 647 self._updatedfiles.clear()
652 648
653 649 # delay writing in-memory changes out
654 650 tr.addfilegenerator('dirstate', (self._filename,),
655 651 self._writedirstate, location='plain')
656 652 return
657 653
658 654 st = self._opener(filename, "w", atomictemp=True, checkambig=True)
659 655 self._writedirstate(st)
660 656
661 657 def addparentchangecallback(self, category, callback):
662 658 """add a callback to be called when the wd parents are changed
663 659
664 660 Callback will be called with the following arguments:
665 661 dirstate, (oldp1, oldp2), (newp1, newp2)
666 662
667 663 Category is a unique identifier to allow overwriting an old callback
668 664 with a newer callback.
669 665 """
670 666 self._plchangecallbacks[category] = callback
671 667
672 668 def _writedirstate(self, st):
673 669 # notify callbacks about parents change
674 670 if self._origpl is not None and self._origpl != self._pl:
675 671 for c, callback in sorted(self._plchangecallbacks.iteritems()):
676 672 callback(self, self._origpl, self._pl)
677 673 self._origpl = None
678 674 # use the modification time of the newly created temporary file as the
679 675 # filesystem's notion of 'now'
680 676 now = util.fstat(st).st_mtime & _rangemask
681 677
682 678 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
683 679 # timestamp of each entries in dirstate, because of 'now > mtime'
684 680 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
685 681 if delaywrite > 0:
686 682 # do we have any files to delay for?
687 683 for f, e in self._map.iteritems():
688 684 if e[0] == 'n' and e[3] == now:
689 685 import time # to avoid useless import
690 686 # rather than sleep n seconds, sleep until the next
691 687 # multiple of n seconds
692 688 clock = time.time()
693 689 start = int(clock) - (int(clock) % delaywrite)
694 690 end = start + delaywrite
695 691 time.sleep(end - clock)
696 692 now = end # trust our estimate that the end is near now
697 693 break
698 694
699 695 self._map.write(st, now)
700 696 self._lastnormaltime = 0
701 697 self._dirty = False
702 698
703 699 def _dirignore(self, f):
704 700 if f == '.':
705 701 return False
706 702 if self._ignore(f):
707 703 return True
708 704 for p in util.finddirs(f):
709 705 if self._ignore(p):
710 706 return True
711 707 return False
712 708
713 709 def _ignorefiles(self):
714 710 files = []
715 711 if os.path.exists(self._join('.hgignore')):
716 712 files.append(self._join('.hgignore'))
717 713 for name, path in self._ui.configitems("ui"):
718 714 if name == 'ignore' or name.startswith('ignore.'):
719 715 # we need to use os.path.join here rather than self._join
720 716 # because path is arbitrary and user-specified
721 717 files.append(os.path.join(self._rootdir, util.expandpath(path)))
722 718 return files
723 719
724 720 def _ignorefileandline(self, f):
725 721 files = collections.deque(self._ignorefiles())
726 722 visited = set()
727 723 while files:
728 724 i = files.popleft()
729 725 patterns = matchmod.readpatternfile(i, self._ui.warn,
730 726 sourceinfo=True)
731 727 for pattern, lineno, line in patterns:
732 728 kind, p = matchmod._patsplit(pattern, 'glob')
733 729 if kind == "subinclude":
734 730 if p not in visited:
735 731 files.append(p)
736 732 continue
737 733 m = matchmod.match(self._root, '', [], [pattern],
738 734 warn=self._ui.warn)
739 735 if m(f):
740 736 return (i, lineno, line)
741 737 visited.add(i)
742 738 return (None, -1, "")
743 739
744 740 def _walkexplicit(self, match, subrepos):
745 741 '''Get stat data about the files explicitly specified by match.
746 742
747 743 Return a triple (results, dirsfound, dirsnotfound).
748 744 - results is a mapping from filename to stat result. It also contains
749 745 listings mapping subrepos and .hg to None.
750 746 - dirsfound is a list of files found to be directories.
751 747 - dirsnotfound is a list of files that the dirstate thinks are
752 748 directories and that were not found.'''
753 749
754 750 def badtype(mode):
755 751 kind = _('unknown')
756 752 if stat.S_ISCHR(mode):
757 753 kind = _('character device')
758 754 elif stat.S_ISBLK(mode):
759 755 kind = _('block device')
760 756 elif stat.S_ISFIFO(mode):
761 757 kind = _('fifo')
762 758 elif stat.S_ISSOCK(mode):
763 759 kind = _('socket')
764 760 elif stat.S_ISDIR(mode):
765 761 kind = _('directory')
766 762 return _('unsupported file type (type is %s)') % kind
767 763
768 764 matchedir = match.explicitdir
769 765 badfn = match.bad
770 766 dmap = self._map
771 767 lstat = os.lstat
772 768 getkind = stat.S_IFMT
773 769 dirkind = stat.S_IFDIR
774 770 regkind = stat.S_IFREG
775 771 lnkkind = stat.S_IFLNK
776 772 join = self._join
777 773 dirsfound = []
778 774 foundadd = dirsfound.append
779 775 dirsnotfound = []
780 776 notfoundadd = dirsnotfound.append
781 777
782 778 if not match.isexact() and self._checkcase:
783 779 normalize = self._normalize
784 780 else:
785 781 normalize = None
786 782
787 783 files = sorted(match.files())
788 784 subrepos.sort()
789 785 i, j = 0, 0
790 786 while i < len(files) and j < len(subrepos):
791 787 subpath = subrepos[j] + "/"
792 788 if files[i] < subpath:
793 789 i += 1
794 790 continue
795 791 while i < len(files) and files[i].startswith(subpath):
796 792 del files[i]
797 793 j += 1
798 794
799 795 if not files or '.' in files:
800 796 files = ['.']
801 797 results = dict.fromkeys(subrepos)
802 798 results['.hg'] = None
803 799
804 800 alldirs = None
805 801 for ff in files:
806 802 # constructing the foldmap is expensive, so don't do it for the
807 803 # common case where files is ['.']
808 804 if normalize and ff != '.':
809 805 nf = normalize(ff, False, True)
810 806 else:
811 807 nf = ff
812 808 if nf in results:
813 809 continue
814 810
815 811 try:
816 812 st = lstat(join(nf))
817 813 kind = getkind(st.st_mode)
818 814 if kind == dirkind:
819 815 if nf in dmap:
820 816 # file replaced by dir on disk but still in dirstate
821 817 results[nf] = None
822 818 if matchedir:
823 819 matchedir(nf)
824 820 foundadd((nf, ff))
825 821 elif kind == regkind or kind == lnkkind:
826 822 results[nf] = st
827 823 else:
828 824 badfn(ff, badtype(kind))
829 825 if nf in dmap:
830 826 results[nf] = None
831 827 except OSError as inst: # nf not found on disk - it is dirstate only
832 828 if nf in dmap: # does it exactly match a missing file?
833 829 results[nf] = None
834 830 else: # does it match a missing directory?
835 831 if alldirs is None:
836 832 alldirs = util.dirs(dmap._map)
837 833 if nf in alldirs:
838 834 if matchedir:
839 835 matchedir(nf)
840 836 notfoundadd(nf)
841 837 else:
842 838 badfn(ff, encoding.strtolocal(inst.strerror))
843 839
844 840 # Case insensitive filesystems cannot rely on lstat() failing to detect
845 841 # a case-only rename. Prune the stat object for any file that does not
846 842 # match the case in the filesystem, if there are multiple files that
847 843 # normalize to the same path.
848 844 if match.isexact() and self._checkcase:
849 845 normed = {}
850 846
851 847 for f, st in results.iteritems():
852 848 if st is None:
853 849 continue
854 850
855 851 nc = util.normcase(f)
856 852 paths = normed.get(nc)
857 853
858 854 if paths is None:
859 855 paths = set()
860 856 normed[nc] = paths
861 857
862 858 paths.add(f)
863 859
864 860 for norm, paths in normed.iteritems():
865 861 if len(paths) > 1:
866 862 for path in paths:
867 863 folded = self._discoverpath(path, norm, True, None,
868 864 self._map.dirfoldmap)
869 865 if path != folded:
870 866 results[path] = None
871 867
872 868 return results, dirsfound, dirsnotfound
873 869
874 870 def walk(self, match, subrepos, unknown, ignored, full=True):
875 871 '''
876 872 Walk recursively through the directory tree, finding all files
877 873 matched by match.
878 874
879 875 If full is False, maybe skip some known-clean files.
880 876
881 877 Return a dict mapping filename to stat-like object (either
882 878 mercurial.osutil.stat instance or return value of os.stat()).
883 879
884 880 '''
885 881 # full is a flag that extensions that hook into walk can use -- this
886 882 # implementation doesn't use it at all. This satisfies the contract
887 883 # because we only guarantee a "maybe".
888 884
889 885 if ignored:
890 886 ignore = util.never
891 887 dirignore = util.never
892 888 elif unknown:
893 889 ignore = self._ignore
894 890 dirignore = self._dirignore
895 891 else:
896 892 # if not unknown and not ignored, drop dir recursion and step 2
897 893 ignore = util.always
898 894 dirignore = util.always
899 895
900 896 matchfn = match.matchfn
901 897 matchalways = match.always()
902 898 matchtdir = match.traversedir
903 899 dmap = self._map
904 900 listdir = util.listdir
905 901 lstat = os.lstat
906 902 dirkind = stat.S_IFDIR
907 903 regkind = stat.S_IFREG
908 904 lnkkind = stat.S_IFLNK
909 905 join = self._join
910 906
911 907 exact = skipstep3 = False
912 908 if match.isexact(): # match.exact
913 909 exact = True
914 910 dirignore = util.always # skip step 2
915 911 elif match.prefix(): # match.match, no patterns
916 912 skipstep3 = True
917 913
918 914 if not exact and self._checkcase:
919 915 normalize = self._normalize
920 916 normalizefile = self._normalizefile
921 917 skipstep3 = False
922 918 else:
923 919 normalize = self._normalize
924 920 normalizefile = None
925 921
926 922 # step 1: find all explicit files
927 923 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
928 924
929 925 skipstep3 = skipstep3 and not (work or dirsnotfound)
930 926 work = [d for d in work if not dirignore(d[0])]
931 927
932 928 # step 2: visit subdirectories
933 929 def traverse(work, alreadynormed):
934 930 wadd = work.append
935 931 while work:
936 932 nd = work.pop()
937 933 if not match.visitdir(nd):
938 934 continue
939 935 skip = None
940 936 if nd == '.':
941 937 nd = ''
942 938 else:
943 939 skip = '.hg'
944 940 try:
945 941 entries = listdir(join(nd), stat=True, skip=skip)
946 942 except OSError as inst:
947 943 if inst.errno in (errno.EACCES, errno.ENOENT):
948 944 match.bad(self.pathto(nd),
949 945 encoding.strtolocal(inst.strerror))
950 946 continue
951 947 raise
952 948 for f, kind, st in entries:
953 949 if normalizefile:
954 950 # even though f might be a directory, we're only
955 951 # interested in comparing it to files currently in the
956 952 # dmap -- therefore normalizefile is enough
957 953 nf = normalizefile(nd and (nd + "/" + f) or f, True,
958 954 True)
959 955 else:
960 956 nf = nd and (nd + "/" + f) or f
961 957 if nf not in results:
962 958 if kind == dirkind:
963 959 if not ignore(nf):
964 960 if matchtdir:
965 961 matchtdir(nf)
966 962 wadd(nf)
967 963 if nf in dmap and (matchalways or matchfn(nf)):
968 964 results[nf] = None
969 965 elif kind == regkind or kind == lnkkind:
970 966 if nf in dmap:
971 967 if matchalways or matchfn(nf):
972 968 results[nf] = st
973 969 elif ((matchalways or matchfn(nf))
974 970 and not ignore(nf)):
975 971 # unknown file -- normalize if necessary
976 972 if not alreadynormed:
977 973 nf = normalize(nf, False, True)
978 974 results[nf] = st
979 975 elif nf in dmap and (matchalways or matchfn(nf)):
980 976 results[nf] = None
981 977
982 978 for nd, d in work:
983 979 # alreadynormed means that processwork doesn't have to do any
984 980 # expensive directory normalization
985 981 alreadynormed = not normalize or nd == d
986 982 traverse([d], alreadynormed)
987 983
988 984 for s in subrepos:
989 985 del results[s]
990 986 del results['.hg']
991 987
992 988 # step 3: visit remaining files from dmap
993 989 if not skipstep3 and not exact:
994 990 # If a dmap file is not in results yet, it was either
995 991 # a) not matching matchfn b) ignored, c) missing, or d) under a
996 992 # symlink directory.
997 993 if not results and matchalways:
998 994 visit = [f for f in dmap]
999 995 else:
1000 996 visit = [f for f in dmap if f not in results and matchfn(f)]
1001 997 visit.sort()
1002 998
1003 999 if unknown:
1004 1000 # unknown == True means we walked all dirs under the roots
1005 1001 # that wasn't ignored, and everything that matched was stat'ed
1006 1002 # and is already in results.
1007 1003 # The rest must thus be ignored or under a symlink.
1008 1004 audit_path = pathutil.pathauditor(self._root, cached=True)
1009 1005
1010 1006 for nf in iter(visit):
1011 1007 # If a stat for the same file was already added with a
1012 1008 # different case, don't add one for this, since that would
1013 1009 # make it appear as if the file exists under both names
1014 1010 # on disk.
1015 1011 if (normalizefile and
1016 1012 normalizefile(nf, True, True) in results):
1017 1013 results[nf] = None
1018 1014 # Report ignored items in the dmap as long as they are not
1019 1015 # under a symlink directory.
1020 1016 elif audit_path.check(nf):
1021 1017 try:
1022 1018 results[nf] = lstat(join(nf))
1023 1019 # file was just ignored, no links, and exists
1024 1020 except OSError:
1025 1021 # file doesn't exist
1026 1022 results[nf] = None
1027 1023 else:
1028 1024 # It's either missing or under a symlink directory
1029 1025 # which we in this case report as missing
1030 1026 results[nf] = None
1031 1027 else:
1032 1028 # We may not have walked the full directory tree above,
1033 1029 # so stat and check everything we missed.
1034 1030 iv = iter(visit)
1035 1031 for st in util.statfiles([join(i) for i in visit]):
1036 1032 results[next(iv)] = st
1037 1033 return results
1038 1034
1039 1035 def status(self, match, subrepos, ignored, clean, unknown):
1040 1036 '''Determine the status of the working copy relative to the
1041 1037 dirstate and return a pair of (unsure, status), where status is of type
1042 1038 scmutil.status and:
1043 1039
1044 1040 unsure:
1045 1041 files that might have been modified since the dirstate was
1046 1042 written, but need to be read to be sure (size is the same
1047 1043 but mtime differs)
1048 1044 status.modified:
1049 1045 files that have definitely been modified since the dirstate
1050 1046 was written (different size or mode)
1051 1047 status.clean:
1052 1048 files that have definitely not been modified since the
1053 1049 dirstate was written
1054 1050 '''
1055 1051 listignored, listclean, listunknown = ignored, clean, unknown
1056 1052 lookup, modified, added, unknown, ignored = [], [], [], [], []
1057 1053 removed, deleted, clean = [], [], []
1058 1054
1059 1055 dmap = self._map
1060 1056 ladd = lookup.append # aka "unsure"
1061 1057 madd = modified.append
1062 1058 aadd = added.append
1063 1059 uadd = unknown.append
1064 1060 iadd = ignored.append
1065 1061 radd = removed.append
1066 1062 dadd = deleted.append
1067 1063 cadd = clean.append
1068 1064 mexact = match.exact
1069 1065 dirignore = self._dirignore
1070 1066 checkexec = self._checkexec
1071 1067 copymap = self._map.copymap
1072 1068 lastnormaltime = self._lastnormaltime
1073 1069
1074 1070 # We need to do full walks when either
1075 1071 # - we're listing all clean files, or
1076 1072 # - match.traversedir does something, because match.traversedir should
1077 1073 # be called for every dir in the working dir
1078 1074 full = listclean or match.traversedir is not None
1079 1075 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1080 1076 full=full).iteritems():
1081 1077 if fn not in dmap:
1082 1078 if (listignored or mexact(fn)) and dirignore(fn):
1083 1079 if listignored:
1084 1080 iadd(fn)
1085 1081 else:
1086 1082 uadd(fn)
1087 1083 continue
1088 1084
1089 1085 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1090 1086 # written like that for performance reasons. dmap[fn] is not a
1091 1087 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1092 1088 # opcode has fast paths when the value to be unpacked is a tuple or
1093 1089 # a list, but falls back to creating a full-fledged iterator in
1094 1090 # general. That is much slower than simply accessing and storing the
1095 1091 # tuple members one by one.
1096 1092 t = dmap[fn]
1097 1093 state = t[0]
1098 1094 mode = t[1]
1099 1095 size = t[2]
1100 1096 time = t[3]
1101 1097
1102 1098 if not st and state in "nma":
1103 1099 dadd(fn)
1104 1100 elif state == 'n':
1105 1101 if (size >= 0 and
1106 1102 ((size != st.st_size and size != st.st_size & _rangemask)
1107 1103 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1108 1104 or size == -2 # other parent
1109 1105 or fn in copymap):
1110 1106 madd(fn)
1111 1107 elif time != st.st_mtime and time != st.st_mtime & _rangemask:
1112 1108 ladd(fn)
1113 1109 elif st.st_mtime == lastnormaltime:
1114 1110 # fn may have just been marked as normal and it may have
1115 1111 # changed in the same second without changing its size.
1116 1112 # This can happen if we quickly do multiple commits.
1117 1113 # Force lookup, so we don't miss such a racy file change.
1118 1114 ladd(fn)
1119 1115 elif listclean:
1120 1116 cadd(fn)
1121 1117 elif state == 'm':
1122 1118 madd(fn)
1123 1119 elif state == 'a':
1124 1120 aadd(fn)
1125 1121 elif state == 'r':
1126 1122 radd(fn)
1127 1123
1128 1124 return (lookup, scmutil.status(modified, added, removed, deleted,
1129 1125 unknown, ignored, clean))
1130 1126
1131 1127 def matches(self, match):
1132 1128 '''
1133 1129 return files in the dirstate (in whatever state) filtered by match
1134 1130 '''
1135 1131 dmap = self._map
1136 1132 if match.always():
1137 1133 return dmap.keys()
1138 1134 files = match.files()
1139 1135 if match.isexact():
1140 1136 # fast path -- filter the other way around, since typically files is
1141 1137 # much smaller than dmap
1142 1138 return [f for f in files if f in dmap]
1143 1139 if match.prefix() and all(fn in dmap for fn in files):
1144 1140 # fast path -- all the values are known to be files, so just return
1145 1141 # that
1146 1142 return list(files)
1147 1143 return [f for f in dmap if match(f)]
1148 1144
1149 1145 def _actualfilename(self, tr):
1150 1146 if tr:
1151 1147 return self._pendingfilename
1152 1148 else:
1153 1149 return self._filename
1154 1150
1155 1151 def savebackup(self, tr, backupname):
1156 1152 '''Save current dirstate into backup file'''
1157 1153 filename = self._actualfilename(tr)
1158 1154 assert backupname != filename
1159 1155
1160 1156 # use '_writedirstate' instead of 'write' to write changes certainly,
1161 1157 # because the latter omits writing out if transaction is running.
1162 1158 # output file will be used to create backup of dirstate at this point.
1163 1159 if self._dirty or not self._opener.exists(filename):
1164 1160 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1165 1161 checkambig=True))
1166 1162
1167 1163 if tr:
1168 1164 # ensure that subsequent tr.writepending returns True for
1169 1165 # changes written out above, even if dirstate is never
1170 1166 # changed after this
1171 1167 tr.addfilegenerator('dirstate', (self._filename,),
1172 1168 self._writedirstate, location='plain')
1173 1169
1174 1170 # ensure that pending file written above is unlinked at
1175 1171 # failure, even if tr.writepending isn't invoked until the
1176 1172 # end of this transaction
1177 1173 tr.registertmp(filename, location='plain')
1178 1174
1179 1175 self._opener.tryunlink(backupname)
1180 1176 # hardlink backup is okay because _writedirstate is always called
1181 1177 # with an "atomictemp=True" file.
1182 1178 util.copyfile(self._opener.join(filename),
1183 1179 self._opener.join(backupname), hardlink=True)
1184 1180
1185 1181 def restorebackup(self, tr, backupname):
1186 1182 '''Restore dirstate by backup file'''
1187 1183 # this "invalidate()" prevents "wlock.release()" from writing
1188 1184 # changes of dirstate out after restoring from backup file
1189 1185 self.invalidate()
1190 1186 filename = self._actualfilename(tr)
1191 1187 self._opener.rename(backupname, filename, checkambig=True)
1192 1188
1193 1189 def clearbackup(self, tr, backupname):
1194 1190 '''Clear backup file'''
1195 1191 self._opener.unlink(backupname)
1196 1192
1197 1193 class dirstatemap(object):
1198 1194 def __init__(self, ui, opener, root):
1199 1195 self._ui = ui
1200 1196 self._opener = opener
1201 1197 self._root = root
1202 1198 self._filename = 'dirstate'
1203 1199
1204 self._map = {}
1205 self.copymap = {}
1206 1200 self._parents = None
1207 1201 self._dirtyparents = False
1208 1202
1209 1203 # for consistent view between _pl() and _read() invocations
1210 1204 self._pendingmode = None
1211 1205
1206 @propertycache
1207 def _map(self):
1208 self._map = {}
1209 self.read()
1210 return self._map
1211
1212 @propertycache
1213 def copymap(self):
1214 self.copymap = {}
1215 self._map
1216 return self.copymap
1217
1212 1218 def clear(self):
1213 1219 self._map = {}
1214 1220 self.copymap = {}
1215 1221 self.setparents(nullid, nullid)
1216 1222
1217 1223 def iteritems(self):
1218 1224 return self._map.iteritems()
1219 1225
1220 1226 def __len__(self):
1221 1227 return len(self._map)
1222 1228
1223 1229 def __iter__(self):
1224 1230 return iter(self._map)
1225 1231
1226 1232 def get(self, key, default=None):
1227 1233 return self._map.get(key, default)
1228 1234
1229 1235 def __contains__(self, key):
1230 1236 return key in self._map
1231 1237
1232 1238 def __setitem__(self, key, value):
1233 1239 self._map[key] = value
1234 1240
1235 1241 def __getitem__(self, key):
1236 1242 return self._map[key]
1237 1243
1238 1244 def __delitem__(self, key):
1239 1245 del self._map[key]
1240 1246
1241 1247 def keys(self):
1242 1248 return self._map.keys()
1243 1249
1244 1250 def nonnormalentries(self):
1245 1251 '''Compute the nonnormal dirstate entries from the dmap'''
1246 1252 try:
1247 1253 return parsers.nonnormalotherparententries(self._map)
1248 1254 except AttributeError:
1249 1255 nonnorm = set()
1250 1256 otherparent = set()
1251 1257 for fname, e in self._map.iteritems():
1252 1258 if e[0] != 'n' or e[3] == -1:
1253 1259 nonnorm.add(fname)
1254 1260 if e[0] == 'n' and e[2] == -2:
1255 1261 otherparent.add(fname)
1256 1262 return nonnorm, otherparent
1257 1263
1258 1264 @propertycache
1259 1265 def filefoldmap(self):
1260 1266 """Returns a dictionary mapping normalized case paths to their
1261 1267 non-normalized versions.
1262 1268 """
1263 1269 try:
1264 1270 makefilefoldmap = parsers.make_file_foldmap
1265 1271 except AttributeError:
1266 1272 pass
1267 1273 else:
1268 1274 return makefilefoldmap(self._map, util.normcasespec,
1269 1275 util.normcasefallback)
1270 1276
1271 1277 f = {}
1272 1278 normcase = util.normcase
1273 1279 for name, s in self._map.iteritems():
1274 1280 if s[0] != 'r':
1275 1281 f[normcase(name)] = name
1276 1282 f['.'] = '.' # prevents useless util.fspath() invocation
1277 1283 return f
1278 1284
1279 1285 @propertycache
1280 1286 def dirs(self):
1281 1287 """Returns a set-like object containing all the directories in the
1282 1288 current dirstate.
1283 1289 """
1284 1290 return util.dirs(self._map, 'r')
1285 1291
1286 1292 def _opendirstatefile(self):
1287 1293 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1288 1294 if self._pendingmode is not None and self._pendingmode != mode:
1289 1295 fp.close()
1290 1296 raise error.Abort(_('working directory state may be '
1291 1297 'changed parallelly'))
1292 1298 self._pendingmode = mode
1293 1299 return fp
1294 1300
1295 1301 def parents(self):
1296 1302 if not self._parents:
1297 1303 try:
1298 1304 fp = self._opendirstatefile()
1299 1305 st = fp.read(40)
1300 1306 fp.close()
1301 1307 except IOError as err:
1302 1308 if err.errno != errno.ENOENT:
1303 1309 raise
1304 1310 # File doesn't exist, so the current state is empty
1305 1311 st = ''
1306 1312
1307 1313 l = len(st)
1308 1314 if l == 40:
1309 1315 self._parents = st[:20], st[20:40]
1310 1316 elif l == 0:
1311 1317 self._parents = [nullid, nullid]
1312 1318 else:
1313 1319 raise error.Abort(_('working directory state appears '
1314 1320 'damaged!'))
1315 1321
1316 1322 return self._parents
1317 1323
1318 1324 def setparents(self, p1, p2):
1319 1325 self._parents = (p1, p2)
1320 1326 self._dirtyparents = True
1321 1327
1322 1328 def read(self):
1323 1329 # ignore HG_PENDING because identity is used only for writing
1324 1330 self.identity = util.filestat.frompath(
1325 1331 self._opener.join(self._filename))
1326 1332
1327 1333 try:
1328 1334 fp = self._opendirstatefile()
1329 1335 try:
1330 1336 st = fp.read()
1331 1337 finally:
1332 1338 fp.close()
1333 1339 except IOError as err:
1334 1340 if err.errno != errno.ENOENT:
1335 1341 raise
1336 1342 return
1337 1343 if not st:
1338 1344 return
1339 1345
1340 1346 if util.safehasattr(parsers, 'dict_new_presized'):
1341 1347 # Make an estimate of the number of files in the dirstate based on
1342 1348 # its size. From a linear regression on a set of real-world repos,
1343 1349 # all over 10,000 files, the size of a dirstate entry is 85
1344 1350 # bytes. The cost of resizing is significantly higher than the cost
1345 1351 # of filling in a larger presized dict, so subtract 20% from the
1346 1352 # size.
1347 1353 #
1348 1354 # This heuristic is imperfect in many ways, so in a future dirstate
1349 1355 # format update it makes sense to just record the number of entries
1350 1356 # on write.
1351 1357 self._map = parsers.dict_new_presized(len(st) / 71)
1352 1358
1353 1359 # Python's garbage collector triggers a GC each time a certain number
1354 1360 # of container objects (the number being defined by
1355 1361 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1356 1362 # for each file in the dirstate. The C version then immediately marks
1357 1363 # them as not to be tracked by the collector. However, this has no
1358 1364 # effect on when GCs are triggered, only on what objects the GC looks
1359 1365 # into. This means that O(number of files) GCs are unavoidable.
1360 1366 # Depending on when in the process's lifetime the dirstate is parsed,
1361 1367 # this can get very expensive. As a workaround, disable GC while
1362 1368 # parsing the dirstate.
1363 1369 #
1364 1370 # (we cannot decorate the function directly since it is in a C module)
1365 1371 parse_dirstate = util.nogc(parsers.parse_dirstate)
1366 1372 p = parse_dirstate(self._map, self.copymap, st)
1367 1373 if not self._dirtyparents:
1368 1374 self.setparents(*p)
1369 1375
1370 1376 def write(self, st, now):
1371 1377 st.write(parsers.pack_dirstate(self._map, self.copymap,
1372 1378 self.parents(), now))
1373 1379 st.close()
1374 1380 self._dirtyparents = False
1375 1381 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1376 1382
1377 1383 @propertycache
1378 1384 def nonnormalset(self):
1379 1385 nonnorm, otherparents = self.nonnormalentries()
1380 1386 self.otherparentset = otherparents
1381 1387 return nonnorm
1382 1388
1383 1389 @propertycache
1384 1390 def otherparentset(self):
1385 1391 nonnorm, otherparents = self.nonnormalentries()
1386 1392 self.nonnormalset = nonnorm
1387 1393 return otherparents
1388 1394
1389 1395 @propertycache
1390 1396 def identity(self):
1391 self.read()
1397 self._map
1392 1398 return self.identity
1393 1399
1394 1400 @propertycache
1395 1401 def dirfoldmap(self):
1396 1402 f = {}
1397 1403 normcase = util.normcase
1398 1404 for name in self.dirs:
1399 1405 f[normcase(name)] = name
1400 1406 return f
General Comments 0
You need to be logged in to leave comments. Login now