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