##// END OF EJS Templates
dirstate: add comment on why we don't need to check if something is a dir/file...
Kyle Lippincott -
r38995:0d032756 default
parent child Browse files
Show More
@@ -1,1496 +1,1501 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 896 visitentries = match.visitchildrenset(nd)
897 897 if not visitentries:
898 898 continue
899 899 if visitentries == 'this' or visitentries == 'all':
900 900 visitentries = None
901 901 skip = None
902 902 if nd == '.':
903 903 nd = ''
904 904 else:
905 905 skip = '.hg'
906 906 try:
907 907 entries = listdir(join(nd), stat=True, skip=skip)
908 908 except OSError as inst:
909 909 if inst.errno in (errno.EACCES, errno.ENOENT):
910 910 match.bad(self.pathto(nd),
911 911 encoding.strtolocal(inst.strerror))
912 912 continue
913 913 raise
914 914 for f, kind, st in entries:
915 # If we needed to inspect any files, visitentries would have
916 # been 'this' or 'all', and we would have set it to None
917 # above. If we have visitentries populated here, we don't
918 # care about any files in this directory, so no need to
919 # check the type of `f`.
915 920 if visitentries and f not in visitentries:
916 921 continue
917 922 if normalizefile:
918 923 # even though f might be a directory, we're only
919 924 # interested in comparing it to files currently in the
920 925 # dmap -- therefore normalizefile is enough
921 926 nf = normalizefile(nd and (nd + "/" + f) or f, True,
922 927 True)
923 928 else:
924 929 nf = nd and (nd + "/" + f) or f
925 930 if nf not in results:
926 931 if kind == dirkind:
927 932 if not ignore(nf):
928 933 if matchtdir:
929 934 matchtdir(nf)
930 935 wadd(nf)
931 936 if nf in dmap and (matchalways or matchfn(nf)):
932 937 results[nf] = None
933 938 elif kind == regkind or kind == lnkkind:
934 939 if nf in dmap:
935 940 if matchalways or matchfn(nf):
936 941 results[nf] = st
937 942 elif ((matchalways or matchfn(nf))
938 943 and not ignore(nf)):
939 944 # unknown file -- normalize if necessary
940 945 if not alreadynormed:
941 946 nf = normalize(nf, False, True)
942 947 results[nf] = st
943 948 elif nf in dmap and (matchalways or matchfn(nf)):
944 949 results[nf] = None
945 950
946 951 for nd, d in work:
947 952 # alreadynormed means that processwork doesn't have to do any
948 953 # expensive directory normalization
949 954 alreadynormed = not normalize or nd == d
950 955 traverse([d], alreadynormed)
951 956
952 957 for s in subrepos:
953 958 del results[s]
954 959 del results['.hg']
955 960
956 961 # step 3: visit remaining files from dmap
957 962 if not skipstep3 and not exact:
958 963 # If a dmap file is not in results yet, it was either
959 964 # a) not matching matchfn b) ignored, c) missing, or d) under a
960 965 # symlink directory.
961 966 if not results and matchalways:
962 967 visit = [f for f in dmap]
963 968 else:
964 969 visit = [f for f in dmap if f not in results and matchfn(f)]
965 970 visit.sort()
966 971
967 972 if unknown:
968 973 # unknown == True means we walked all dirs under the roots
969 974 # that wasn't ignored, and everything that matched was stat'ed
970 975 # and is already in results.
971 976 # The rest must thus be ignored or under a symlink.
972 977 audit_path = pathutil.pathauditor(self._root, cached=True)
973 978
974 979 for nf in iter(visit):
975 980 # If a stat for the same file was already added with a
976 981 # different case, don't add one for this, since that would
977 982 # make it appear as if the file exists under both names
978 983 # on disk.
979 984 if (normalizefile and
980 985 normalizefile(nf, True, True) in results):
981 986 results[nf] = None
982 987 # Report ignored items in the dmap as long as they are not
983 988 # under a symlink directory.
984 989 elif audit_path.check(nf):
985 990 try:
986 991 results[nf] = lstat(join(nf))
987 992 # file was just ignored, no links, and exists
988 993 except OSError:
989 994 # file doesn't exist
990 995 results[nf] = None
991 996 else:
992 997 # It's either missing or under a symlink directory
993 998 # which we in this case report as missing
994 999 results[nf] = None
995 1000 else:
996 1001 # We may not have walked the full directory tree above,
997 1002 # so stat and check everything we missed.
998 1003 iv = iter(visit)
999 1004 for st in util.statfiles([join(i) for i in visit]):
1000 1005 results[next(iv)] = st
1001 1006 return results
1002 1007
1003 1008 def status(self, match, subrepos, ignored, clean, unknown):
1004 1009 '''Determine the status of the working copy relative to the
1005 1010 dirstate and return a pair of (unsure, status), where status is of type
1006 1011 scmutil.status and:
1007 1012
1008 1013 unsure:
1009 1014 files that might have been modified since the dirstate was
1010 1015 written, but need to be read to be sure (size is the same
1011 1016 but mtime differs)
1012 1017 status.modified:
1013 1018 files that have definitely been modified since the dirstate
1014 1019 was written (different size or mode)
1015 1020 status.clean:
1016 1021 files that have definitely not been modified since the
1017 1022 dirstate was written
1018 1023 '''
1019 1024 listignored, listclean, listunknown = ignored, clean, unknown
1020 1025 lookup, modified, added, unknown, ignored = [], [], [], [], []
1021 1026 removed, deleted, clean = [], [], []
1022 1027
1023 1028 dmap = self._map
1024 1029 dmap.preload()
1025 1030 dcontains = dmap.__contains__
1026 1031 dget = dmap.__getitem__
1027 1032 ladd = lookup.append # aka "unsure"
1028 1033 madd = modified.append
1029 1034 aadd = added.append
1030 1035 uadd = unknown.append
1031 1036 iadd = ignored.append
1032 1037 radd = removed.append
1033 1038 dadd = deleted.append
1034 1039 cadd = clean.append
1035 1040 mexact = match.exact
1036 1041 dirignore = self._dirignore
1037 1042 checkexec = self._checkexec
1038 1043 copymap = self._map.copymap
1039 1044 lastnormaltime = self._lastnormaltime
1040 1045
1041 1046 # We need to do full walks when either
1042 1047 # - we're listing all clean files, or
1043 1048 # - match.traversedir does something, because match.traversedir should
1044 1049 # be called for every dir in the working dir
1045 1050 full = listclean or match.traversedir is not None
1046 1051 for fn, st in self.walk(match, subrepos, listunknown, listignored,
1047 1052 full=full).iteritems():
1048 1053 if not dcontains(fn):
1049 1054 if (listignored or mexact(fn)) and dirignore(fn):
1050 1055 if listignored:
1051 1056 iadd(fn)
1052 1057 else:
1053 1058 uadd(fn)
1054 1059 continue
1055 1060
1056 1061 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
1057 1062 # written like that for performance reasons. dmap[fn] is not a
1058 1063 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
1059 1064 # opcode has fast paths when the value to be unpacked is a tuple or
1060 1065 # a list, but falls back to creating a full-fledged iterator in
1061 1066 # general. That is much slower than simply accessing and storing the
1062 1067 # tuple members one by one.
1063 1068 t = dget(fn)
1064 1069 state = t[0]
1065 1070 mode = t[1]
1066 1071 size = t[2]
1067 1072 time = t[3]
1068 1073
1069 1074 if not st and state in "nma":
1070 1075 dadd(fn)
1071 1076 elif state == 'n':
1072 1077 if (size >= 0 and
1073 1078 ((size != st.st_size and size != st.st_size & _rangemask)
1074 1079 or ((mode ^ st.st_mode) & 0o100 and checkexec))
1075 1080 or size == -2 # other parent
1076 1081 or fn in copymap):
1077 1082 madd(fn)
1078 1083 elif (time != st[stat.ST_MTIME]
1079 1084 and time != st[stat.ST_MTIME] & _rangemask):
1080 1085 ladd(fn)
1081 1086 elif st[stat.ST_MTIME] == lastnormaltime:
1082 1087 # fn may have just been marked as normal and it may have
1083 1088 # changed in the same second without changing its size.
1084 1089 # This can happen if we quickly do multiple commits.
1085 1090 # Force lookup, so we don't miss such a racy file change.
1086 1091 ladd(fn)
1087 1092 elif listclean:
1088 1093 cadd(fn)
1089 1094 elif state == 'm':
1090 1095 madd(fn)
1091 1096 elif state == 'a':
1092 1097 aadd(fn)
1093 1098 elif state == 'r':
1094 1099 radd(fn)
1095 1100
1096 1101 return (lookup, scmutil.status(modified, added, removed, deleted,
1097 1102 unknown, ignored, clean))
1098 1103
1099 1104 def matches(self, match):
1100 1105 '''
1101 1106 return files in the dirstate (in whatever state) filtered by match
1102 1107 '''
1103 1108 dmap = self._map
1104 1109 if match.always():
1105 1110 return dmap.keys()
1106 1111 files = match.files()
1107 1112 if match.isexact():
1108 1113 # fast path -- filter the other way around, since typically files is
1109 1114 # much smaller than dmap
1110 1115 return [f for f in files if f in dmap]
1111 1116 if match.prefix() and all(fn in dmap for fn in files):
1112 1117 # fast path -- all the values are known to be files, so just return
1113 1118 # that
1114 1119 return list(files)
1115 1120 return [f for f in dmap if match(f)]
1116 1121
1117 1122 def _actualfilename(self, tr):
1118 1123 if tr:
1119 1124 return self._pendingfilename
1120 1125 else:
1121 1126 return self._filename
1122 1127
1123 1128 def savebackup(self, tr, backupname):
1124 1129 '''Save current dirstate into backup file'''
1125 1130 filename = self._actualfilename(tr)
1126 1131 assert backupname != filename
1127 1132
1128 1133 # use '_writedirstate' instead of 'write' to write changes certainly,
1129 1134 # because the latter omits writing out if transaction is running.
1130 1135 # output file will be used to create backup of dirstate at this point.
1131 1136 if self._dirty or not self._opener.exists(filename):
1132 1137 self._writedirstate(self._opener(filename, "w", atomictemp=True,
1133 1138 checkambig=True))
1134 1139
1135 1140 if tr:
1136 1141 # ensure that subsequent tr.writepending returns True for
1137 1142 # changes written out above, even if dirstate is never
1138 1143 # changed after this
1139 1144 tr.addfilegenerator('dirstate', (self._filename,),
1140 1145 self._writedirstate, location='plain')
1141 1146
1142 1147 # ensure that pending file written above is unlinked at
1143 1148 # failure, even if tr.writepending isn't invoked until the
1144 1149 # end of this transaction
1145 1150 tr.registertmp(filename, location='plain')
1146 1151
1147 1152 self._opener.tryunlink(backupname)
1148 1153 # hardlink backup is okay because _writedirstate is always called
1149 1154 # with an "atomictemp=True" file.
1150 1155 util.copyfile(self._opener.join(filename),
1151 1156 self._opener.join(backupname), hardlink=True)
1152 1157
1153 1158 def restorebackup(self, tr, backupname):
1154 1159 '''Restore dirstate by backup file'''
1155 1160 # this "invalidate()" prevents "wlock.release()" from writing
1156 1161 # changes of dirstate out after restoring from backup file
1157 1162 self.invalidate()
1158 1163 filename = self._actualfilename(tr)
1159 1164 o = self._opener
1160 1165 if util.samefile(o.join(backupname), o.join(filename)):
1161 1166 o.unlink(backupname)
1162 1167 else:
1163 1168 o.rename(backupname, filename, checkambig=True)
1164 1169
1165 1170 def clearbackup(self, tr, backupname):
1166 1171 '''Clear backup file'''
1167 1172 self._opener.unlink(backupname)
1168 1173
1169 1174 class dirstatemap(object):
1170 1175 """Map encapsulating the dirstate's contents.
1171 1176
1172 1177 The dirstate contains the following state:
1173 1178
1174 1179 - `identity` is the identity of the dirstate file, which can be used to
1175 1180 detect when changes have occurred to the dirstate file.
1176 1181
1177 1182 - `parents` is a pair containing the parents of the working copy. The
1178 1183 parents are updated by calling `setparents`.
1179 1184
1180 1185 - the state map maps filenames to tuples of (state, mode, size, mtime),
1181 1186 where state is a single character representing 'normal', 'added',
1182 1187 'removed', or 'merged'. It is read by treating the dirstate as a
1183 1188 dict. File state is updated by calling the `addfile`, `removefile` and
1184 1189 `dropfile` methods.
1185 1190
1186 1191 - `copymap` maps destination filenames to their source filename.
1187 1192
1188 1193 The dirstate also provides the following views onto the state:
1189 1194
1190 1195 - `nonnormalset` is a set of the filenames that have state other
1191 1196 than 'normal', or are normal but have an mtime of -1 ('normallookup').
1192 1197
1193 1198 - `otherparentset` is a set of the filenames that are marked as coming
1194 1199 from the second parent when the dirstate is currently being merged.
1195 1200
1196 1201 - `filefoldmap` is a dict mapping normalized filenames to the denormalized
1197 1202 form that they appear as in the dirstate.
1198 1203
1199 1204 - `dirfoldmap` is a dict mapping normalized directory names to the
1200 1205 denormalized form that they appear as in the dirstate.
1201 1206 """
1202 1207
1203 1208 def __init__(self, ui, opener, root):
1204 1209 self._ui = ui
1205 1210 self._opener = opener
1206 1211 self._root = root
1207 1212 self._filename = 'dirstate'
1208 1213
1209 1214 self._parents = None
1210 1215 self._dirtyparents = False
1211 1216
1212 1217 # for consistent view between _pl() and _read() invocations
1213 1218 self._pendingmode = None
1214 1219
1215 1220 @propertycache
1216 1221 def _map(self):
1217 1222 self._map = {}
1218 1223 self.read()
1219 1224 return self._map
1220 1225
1221 1226 @propertycache
1222 1227 def copymap(self):
1223 1228 self.copymap = {}
1224 1229 self._map
1225 1230 return self.copymap
1226 1231
1227 1232 def clear(self):
1228 1233 self._map.clear()
1229 1234 self.copymap.clear()
1230 1235 self.setparents(nullid, nullid)
1231 1236 util.clearcachedproperty(self, "_dirs")
1232 1237 util.clearcachedproperty(self, "_alldirs")
1233 1238 util.clearcachedproperty(self, "filefoldmap")
1234 1239 util.clearcachedproperty(self, "dirfoldmap")
1235 1240 util.clearcachedproperty(self, "nonnormalset")
1236 1241 util.clearcachedproperty(self, "otherparentset")
1237 1242
1238 1243 def items(self):
1239 1244 return self._map.iteritems()
1240 1245
1241 1246 # forward for python2,3 compat
1242 1247 iteritems = items
1243 1248
1244 1249 def __len__(self):
1245 1250 return len(self._map)
1246 1251
1247 1252 def __iter__(self):
1248 1253 return iter(self._map)
1249 1254
1250 1255 def get(self, key, default=None):
1251 1256 return self._map.get(key, default)
1252 1257
1253 1258 def __contains__(self, key):
1254 1259 return key in self._map
1255 1260
1256 1261 def __getitem__(self, key):
1257 1262 return self._map[key]
1258 1263
1259 1264 def keys(self):
1260 1265 return self._map.keys()
1261 1266
1262 1267 def preload(self):
1263 1268 """Loads the underlying data, if it's not already loaded"""
1264 1269 self._map
1265 1270
1266 1271 def addfile(self, f, oldstate, state, mode, size, mtime):
1267 1272 """Add a tracked file to the dirstate."""
1268 1273 if oldstate in "?r" and r"_dirs" in self.__dict__:
1269 1274 self._dirs.addpath(f)
1270 1275 if oldstate == "?" and r"_alldirs" in self.__dict__:
1271 1276 self._alldirs.addpath(f)
1272 1277 self._map[f] = dirstatetuple(state, mode, size, mtime)
1273 1278 if state != 'n' or mtime == -1:
1274 1279 self.nonnormalset.add(f)
1275 1280 if size == -2:
1276 1281 self.otherparentset.add(f)
1277 1282
1278 1283 def removefile(self, f, oldstate, size):
1279 1284 """
1280 1285 Mark a file as removed in the dirstate.
1281 1286
1282 1287 The `size` parameter is used to store sentinel values that indicate
1283 1288 the file's previous state. In the future, we should refactor this
1284 1289 to be more explicit about what that state is.
1285 1290 """
1286 1291 if oldstate not in "?r" and r"_dirs" in self.__dict__:
1287 1292 self._dirs.delpath(f)
1288 1293 if oldstate == "?" and r"_alldirs" in self.__dict__:
1289 1294 self._alldirs.addpath(f)
1290 1295 if r"filefoldmap" in self.__dict__:
1291 1296 normed = util.normcase(f)
1292 1297 self.filefoldmap.pop(normed, None)
1293 1298 self._map[f] = dirstatetuple('r', 0, size, 0)
1294 1299 self.nonnormalset.add(f)
1295 1300
1296 1301 def dropfile(self, f, oldstate):
1297 1302 """
1298 1303 Remove a file from the dirstate. Returns True if the file was
1299 1304 previously recorded.
1300 1305 """
1301 1306 exists = self._map.pop(f, None) is not None
1302 1307 if exists:
1303 1308 if oldstate != "r" and r"_dirs" in self.__dict__:
1304 1309 self._dirs.delpath(f)
1305 1310 if r"_alldirs" in self.__dict__:
1306 1311 self._alldirs.delpath(f)
1307 1312 if r"filefoldmap" in self.__dict__:
1308 1313 normed = util.normcase(f)
1309 1314 self.filefoldmap.pop(normed, None)
1310 1315 self.nonnormalset.discard(f)
1311 1316 return exists
1312 1317
1313 1318 def clearambiguoustimes(self, files, now):
1314 1319 for f in files:
1315 1320 e = self.get(f)
1316 1321 if e is not None and e[0] == 'n' and e[3] == now:
1317 1322 self._map[f] = dirstatetuple(e[0], e[1], e[2], -1)
1318 1323 self.nonnormalset.add(f)
1319 1324
1320 1325 def nonnormalentries(self):
1321 1326 '''Compute the nonnormal dirstate entries from the dmap'''
1322 1327 try:
1323 1328 return parsers.nonnormalotherparententries(self._map)
1324 1329 except AttributeError:
1325 1330 nonnorm = set()
1326 1331 otherparent = set()
1327 1332 for fname, e in self._map.iteritems():
1328 1333 if e[0] != 'n' or e[3] == -1:
1329 1334 nonnorm.add(fname)
1330 1335 if e[0] == 'n' and e[2] == -2:
1331 1336 otherparent.add(fname)
1332 1337 return nonnorm, otherparent
1333 1338
1334 1339 @propertycache
1335 1340 def filefoldmap(self):
1336 1341 """Returns a dictionary mapping normalized case paths to their
1337 1342 non-normalized versions.
1338 1343 """
1339 1344 try:
1340 1345 makefilefoldmap = parsers.make_file_foldmap
1341 1346 except AttributeError:
1342 1347 pass
1343 1348 else:
1344 1349 return makefilefoldmap(self._map, util.normcasespec,
1345 1350 util.normcasefallback)
1346 1351
1347 1352 f = {}
1348 1353 normcase = util.normcase
1349 1354 for name, s in self._map.iteritems():
1350 1355 if s[0] != 'r':
1351 1356 f[normcase(name)] = name
1352 1357 f['.'] = '.' # prevents useless util.fspath() invocation
1353 1358 return f
1354 1359
1355 1360 def hastrackeddir(self, d):
1356 1361 """
1357 1362 Returns True if the dirstate contains a tracked (not removed) file
1358 1363 in this directory.
1359 1364 """
1360 1365 return d in self._dirs
1361 1366
1362 1367 def hasdir(self, d):
1363 1368 """
1364 1369 Returns True if the dirstate contains a file (tracked or removed)
1365 1370 in this directory.
1366 1371 """
1367 1372 return d in self._alldirs
1368 1373
1369 1374 @propertycache
1370 1375 def _dirs(self):
1371 1376 return util.dirs(self._map, 'r')
1372 1377
1373 1378 @propertycache
1374 1379 def _alldirs(self):
1375 1380 return util.dirs(self._map)
1376 1381
1377 1382 def _opendirstatefile(self):
1378 1383 fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
1379 1384 if self._pendingmode is not None and self._pendingmode != mode:
1380 1385 fp.close()
1381 1386 raise error.Abort(_('working directory state may be '
1382 1387 'changed parallelly'))
1383 1388 self._pendingmode = mode
1384 1389 return fp
1385 1390
1386 1391 def parents(self):
1387 1392 if not self._parents:
1388 1393 try:
1389 1394 fp = self._opendirstatefile()
1390 1395 st = fp.read(40)
1391 1396 fp.close()
1392 1397 except IOError as err:
1393 1398 if err.errno != errno.ENOENT:
1394 1399 raise
1395 1400 # File doesn't exist, so the current state is empty
1396 1401 st = ''
1397 1402
1398 1403 l = len(st)
1399 1404 if l == 40:
1400 1405 self._parents = st[:20], st[20:40]
1401 1406 elif l == 0:
1402 1407 self._parents = [nullid, nullid]
1403 1408 else:
1404 1409 raise error.Abort(_('working directory state appears '
1405 1410 'damaged!'))
1406 1411
1407 1412 return self._parents
1408 1413
1409 1414 def setparents(self, p1, p2):
1410 1415 self._parents = (p1, p2)
1411 1416 self._dirtyparents = True
1412 1417
1413 1418 def read(self):
1414 1419 # ignore HG_PENDING because identity is used only for writing
1415 1420 self.identity = util.filestat.frompath(
1416 1421 self._opener.join(self._filename))
1417 1422
1418 1423 try:
1419 1424 fp = self._opendirstatefile()
1420 1425 try:
1421 1426 st = fp.read()
1422 1427 finally:
1423 1428 fp.close()
1424 1429 except IOError as err:
1425 1430 if err.errno != errno.ENOENT:
1426 1431 raise
1427 1432 return
1428 1433 if not st:
1429 1434 return
1430 1435
1431 1436 if util.safehasattr(parsers, 'dict_new_presized'):
1432 1437 # Make an estimate of the number of files in the dirstate based on
1433 1438 # its size. From a linear regression on a set of real-world repos,
1434 1439 # all over 10,000 files, the size of a dirstate entry is 85
1435 1440 # bytes. The cost of resizing is significantly higher than the cost
1436 1441 # of filling in a larger presized dict, so subtract 20% from the
1437 1442 # size.
1438 1443 #
1439 1444 # This heuristic is imperfect in many ways, so in a future dirstate
1440 1445 # format update it makes sense to just record the number of entries
1441 1446 # on write.
1442 1447 self._map = parsers.dict_new_presized(len(st) // 71)
1443 1448
1444 1449 # Python's garbage collector triggers a GC each time a certain number
1445 1450 # of container objects (the number being defined by
1446 1451 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
1447 1452 # for each file in the dirstate. The C version then immediately marks
1448 1453 # them as not to be tracked by the collector. However, this has no
1449 1454 # effect on when GCs are triggered, only on what objects the GC looks
1450 1455 # into. This means that O(number of files) GCs are unavoidable.
1451 1456 # Depending on when in the process's lifetime the dirstate is parsed,
1452 1457 # this can get very expensive. As a workaround, disable GC while
1453 1458 # parsing the dirstate.
1454 1459 #
1455 1460 # (we cannot decorate the function directly since it is in a C module)
1456 1461 parse_dirstate = util.nogc(parsers.parse_dirstate)
1457 1462 p = parse_dirstate(self._map, self.copymap, st)
1458 1463 if not self._dirtyparents:
1459 1464 self.setparents(*p)
1460 1465
1461 1466 # Avoid excess attribute lookups by fast pathing certain checks
1462 1467 self.__contains__ = self._map.__contains__
1463 1468 self.__getitem__ = self._map.__getitem__
1464 1469 self.get = self._map.get
1465 1470
1466 1471 def write(self, st, now):
1467 1472 st.write(parsers.pack_dirstate(self._map, self.copymap,
1468 1473 self.parents(), now))
1469 1474 st.close()
1470 1475 self._dirtyparents = False
1471 1476 self.nonnormalset, self.otherparentset = self.nonnormalentries()
1472 1477
1473 1478 @propertycache
1474 1479 def nonnormalset(self):
1475 1480 nonnorm, otherparents = self.nonnormalentries()
1476 1481 self.otherparentset = otherparents
1477 1482 return nonnorm
1478 1483
1479 1484 @propertycache
1480 1485 def otherparentset(self):
1481 1486 nonnorm, otherparents = self.nonnormalentries()
1482 1487 self.nonnormalset = nonnorm
1483 1488 return otherparents
1484 1489
1485 1490 @propertycache
1486 1491 def identity(self):
1487 1492 self._map
1488 1493 return self.identity
1489 1494
1490 1495 @propertycache
1491 1496 def dirfoldmap(self):
1492 1497 f = {}
1493 1498 normcase = util.normcase
1494 1499 for name in self._dirs:
1495 1500 f[normcase(name)] = name
1496 1501 return f
General Comments 0
You need to be logged in to leave comments. Login now