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