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