##// END OF EJS Templates
dirstate: use the 'nogc' decorator...
Pierre-Yves David -
r23496:ee5a4ed4 default
parent child Browse files
Show More
@@ -1,919 +1,916 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 node import nullid
9 9 from i18n import _
10 10 import scmutil, util, ignore, osutil, parsers, encoding, pathutil
11 import os, stat, errno, gc
11 import os, stat, errno
12 12
13 13 propertycache = util.propertycache
14 14 filecache = scmutil.filecache
15 15 _rangemask = 0x7fffffff
16 16
17 17 dirstatetuple = parsers.dirstatetuple
18 18
19 19 class repocache(filecache):
20 20 """filecache for files in .hg/"""
21 21 def join(self, obj, fname):
22 22 return obj._opener.join(fname)
23 23
24 24 class rootcache(filecache):
25 25 """filecache for files in the repository root"""
26 26 def join(self, obj, fname):
27 27 return obj._join(fname)
28 28
29 29 class dirstate(object):
30 30
31 31 def __init__(self, opener, ui, root, validate):
32 32 '''Create a new dirstate object.
33 33
34 34 opener is an open()-like callable that can be used to open the
35 35 dirstate file; root is the root of the directory tracked by
36 36 the dirstate.
37 37 '''
38 38 self._opener = opener
39 39 self._validate = validate
40 40 self._root = root
41 41 self._rootdir = os.path.join(root, '')
42 42 self._dirty = False
43 43 self._dirtypl = False
44 44 self._lastnormaltime = 0
45 45 self._ui = ui
46 46 self._filecache = {}
47 47 self._parentwriters = 0
48 48
49 49 def beginparentchange(self):
50 50 '''Marks the beginning of a set of changes that involve changing
51 51 the dirstate parents. If there is an exception during this time,
52 52 the dirstate will not be written when the wlock is released. This
53 53 prevents writing an incoherent dirstate where the parent doesn't
54 54 match the contents.
55 55 '''
56 56 self._parentwriters += 1
57 57
58 58 def endparentchange(self):
59 59 '''Marks the end of a set of changes that involve changing the
60 60 dirstate parents. Once all parent changes have been marked done,
61 61 the wlock will be free to write the dirstate on release.
62 62 '''
63 63 if self._parentwriters > 0:
64 64 self._parentwriters -= 1
65 65
66 66 def pendingparentchange(self):
67 67 '''Returns true if the dirstate is in the middle of a set of changes
68 68 that modify the dirstate parent.
69 69 '''
70 70 return self._parentwriters > 0
71 71
72 72 @propertycache
73 73 def _map(self):
74 74 '''Return the dirstate contents as a map from filename to
75 75 (state, mode, size, time).'''
76 76 self._read()
77 77 return self._map
78 78
79 79 @propertycache
80 80 def _copymap(self):
81 81 self._read()
82 82 return self._copymap
83 83
84 84 @propertycache
85 85 def _foldmap(self):
86 86 f = {}
87 87 normcase = util.normcase
88 88 for name, s in self._map.iteritems():
89 89 if s[0] != 'r':
90 90 f[normcase(name)] = name
91 91 for name in self._dirs:
92 92 f[normcase(name)] = name
93 93 f['.'] = '.' # prevents useless util.fspath() invocation
94 94 return f
95 95
96 96 @repocache('branch')
97 97 def _branch(self):
98 98 try:
99 99 return self._opener.read("branch").strip() or "default"
100 100 except IOError, inst:
101 101 if inst.errno != errno.ENOENT:
102 102 raise
103 103 return "default"
104 104
105 105 @propertycache
106 106 def _pl(self):
107 107 try:
108 108 fp = self._opener("dirstate")
109 109 st = fp.read(40)
110 110 fp.close()
111 111 l = len(st)
112 112 if l == 40:
113 113 return st[:20], st[20:40]
114 114 elif l > 0 and l < 40:
115 115 raise util.Abort(_('working directory state appears damaged!'))
116 116 except IOError, err:
117 117 if err.errno != errno.ENOENT:
118 118 raise
119 119 return [nullid, nullid]
120 120
121 121 @propertycache
122 122 def _dirs(self):
123 123 return scmutil.dirs(self._map, 'r')
124 124
125 125 def dirs(self):
126 126 return self._dirs
127 127
128 128 @rootcache('.hgignore')
129 129 def _ignore(self):
130 130 files = [self._join('.hgignore')]
131 131 for name, path in self._ui.configitems("ui"):
132 132 if name == 'ignore' or name.startswith('ignore.'):
133 133 files.append(util.expandpath(path))
134 134 return ignore.ignore(self._root, files, self._ui.warn)
135 135
136 136 @propertycache
137 137 def _slash(self):
138 138 return self._ui.configbool('ui', 'slash') and os.sep != '/'
139 139
140 140 @propertycache
141 141 def _checklink(self):
142 142 return util.checklink(self._root)
143 143
144 144 @propertycache
145 145 def _checkexec(self):
146 146 return util.checkexec(self._root)
147 147
148 148 @propertycache
149 149 def _checkcase(self):
150 150 return not util.checkcase(self._join('.hg'))
151 151
152 152 def _join(self, f):
153 153 # much faster than os.path.join()
154 154 # it's safe because f is always a relative path
155 155 return self._rootdir + f
156 156
157 157 def flagfunc(self, buildfallback):
158 158 if self._checklink and self._checkexec:
159 159 def f(x):
160 160 try:
161 161 st = os.lstat(self._join(x))
162 162 if util.statislink(st):
163 163 return 'l'
164 164 if util.statisexec(st):
165 165 return 'x'
166 166 except OSError:
167 167 pass
168 168 return ''
169 169 return f
170 170
171 171 fallback = buildfallback()
172 172 if self._checklink:
173 173 def f(x):
174 174 if os.path.islink(self._join(x)):
175 175 return 'l'
176 176 if 'x' in fallback(x):
177 177 return 'x'
178 178 return ''
179 179 return f
180 180 if self._checkexec:
181 181 def f(x):
182 182 if 'l' in fallback(x):
183 183 return 'l'
184 184 if util.isexec(self._join(x)):
185 185 return 'x'
186 186 return ''
187 187 return f
188 188 else:
189 189 return fallback
190 190
191 191 @propertycache
192 192 def _cwd(self):
193 193 return os.getcwd()
194 194
195 195 def getcwd(self):
196 196 cwd = self._cwd
197 197 if cwd == self._root:
198 198 return ''
199 199 # self._root ends with a path separator if self._root is '/' or 'C:\'
200 200 rootsep = self._root
201 201 if not util.endswithsep(rootsep):
202 202 rootsep += os.sep
203 203 if cwd.startswith(rootsep):
204 204 return cwd[len(rootsep):]
205 205 else:
206 206 # we're outside the repo. return an absolute path.
207 207 return cwd
208 208
209 209 def pathto(self, f, cwd=None):
210 210 if cwd is None:
211 211 cwd = self.getcwd()
212 212 path = util.pathto(self._root, cwd, f)
213 213 if self._slash:
214 214 return util.pconvert(path)
215 215 return path
216 216
217 217 def __getitem__(self, key):
218 218 '''Return the current state of key (a filename) in the dirstate.
219 219
220 220 States are:
221 221 n normal
222 222 m needs merging
223 223 r marked for removal
224 224 a marked for addition
225 225 ? not tracked
226 226 '''
227 227 return self._map.get(key, ("?",))[0]
228 228
229 229 def __contains__(self, key):
230 230 return key in self._map
231 231
232 232 def __iter__(self):
233 233 for x in sorted(self._map):
234 234 yield x
235 235
236 236 def iteritems(self):
237 237 return self._map.iteritems()
238 238
239 239 def parents(self):
240 240 return [self._validate(p) for p in self._pl]
241 241
242 242 def p1(self):
243 243 return self._validate(self._pl[0])
244 244
245 245 def p2(self):
246 246 return self._validate(self._pl[1])
247 247
248 248 def branch(self):
249 249 return encoding.tolocal(self._branch)
250 250
251 251 def setparents(self, p1, p2=nullid):
252 252 """Set dirstate parents to p1 and p2.
253 253
254 254 When moving from two parents to one, 'm' merged entries a
255 255 adjusted to normal and previous copy records discarded and
256 256 returned by the call.
257 257
258 258 See localrepo.setparents()
259 259 """
260 260 if self._parentwriters == 0:
261 261 raise ValueError("cannot set dirstate parent without "
262 262 "calling dirstate.beginparentchange")
263 263
264 264 self._dirty = self._dirtypl = True
265 265 oldp2 = self._pl[1]
266 266 self._pl = p1, p2
267 267 copies = {}
268 268 if oldp2 != nullid and p2 == nullid:
269 269 for f, s in self._map.iteritems():
270 270 # Discard 'm' markers when moving away from a merge state
271 271 if s[0] == 'm':
272 272 if f in self._copymap:
273 273 copies[f] = self._copymap[f]
274 274 self.normallookup(f)
275 275 # Also fix up otherparent markers
276 276 elif s[0] == 'n' and s[2] == -2:
277 277 if f in self._copymap:
278 278 copies[f] = self._copymap[f]
279 279 self.add(f)
280 280 return copies
281 281
282 282 def setbranch(self, branch):
283 283 self._branch = encoding.fromlocal(branch)
284 284 f = self._opener('branch', 'w', atomictemp=True)
285 285 try:
286 286 f.write(self._branch + '\n')
287 287 f.close()
288 288
289 289 # make sure filecache has the correct stat info for _branch after
290 290 # replacing the underlying file
291 291 ce = self._filecache['_branch']
292 292 if ce:
293 293 ce.refresh()
294 294 except: # re-raises
295 295 f.discard()
296 296 raise
297 297
298 298 def _read(self):
299 299 self._map = {}
300 300 self._copymap = {}
301 301 try:
302 302 st = self._opener.read("dirstate")
303 303 except IOError, err:
304 304 if err.errno != errno.ENOENT:
305 305 raise
306 306 return
307 307 if not st:
308 308 return
309 309
310 310 # Python's garbage collector triggers a GC each time a certain number
311 311 # of container objects (the number being defined by
312 312 # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
313 313 # for each file in the dirstate. The C version then immediately marks
314 314 # them as not to be tracked by the collector. However, this has no
315 315 # effect on when GCs are triggered, only on what objects the GC looks
316 316 # into. This means that O(number of files) GCs are unavoidable.
317 317 # Depending on when in the process's lifetime the dirstate is parsed,
318 318 # this can get very expensive. As a workaround, disable GC while
319 319 # parsing the dirstate.
320 gcenabled = gc.isenabled()
321 gc.disable()
322 try:
323 p = parsers.parse_dirstate(self._map, self._copymap, st)
324 finally:
325 if gcenabled:
326 gc.enable()
320 #
321 # (we cannot decorate the function directly since it is in a C module)
322 parse_dirstate = util.nogc(parsers.parse_dirstate)
323 p = parse_dirstate(self._map, self._copymap, st)
327 324 if not self._dirtypl:
328 325 self._pl = p
329 326
330 327 def invalidate(self):
331 328 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
332 329 "_ignore"):
333 330 if a in self.__dict__:
334 331 delattr(self, a)
335 332 self._lastnormaltime = 0
336 333 self._dirty = False
337 334 self._parentwriters = 0
338 335
339 336 def copy(self, source, dest):
340 337 """Mark dest as a copy of source. Unmark dest if source is None."""
341 338 if source == dest:
342 339 return
343 340 self._dirty = True
344 341 if source is not None:
345 342 self._copymap[dest] = source
346 343 elif dest in self._copymap:
347 344 del self._copymap[dest]
348 345
349 346 def copied(self, file):
350 347 return self._copymap.get(file, None)
351 348
352 349 def copies(self):
353 350 return self._copymap
354 351
355 352 def _droppath(self, f):
356 353 if self[f] not in "?r" and "_dirs" in self.__dict__:
357 354 self._dirs.delpath(f)
358 355
359 356 def _addpath(self, f, state, mode, size, mtime):
360 357 oldstate = self[f]
361 358 if state == 'a' or oldstate == 'r':
362 359 scmutil.checkfilename(f)
363 360 if f in self._dirs:
364 361 raise util.Abort(_('directory %r already in dirstate') % f)
365 362 # shadows
366 363 for d in scmutil.finddirs(f):
367 364 if d in self._dirs:
368 365 break
369 366 if d in self._map and self[d] != 'r':
370 367 raise util.Abort(
371 368 _('file %r in dirstate clashes with %r') % (d, f))
372 369 if oldstate in "?r" and "_dirs" in self.__dict__:
373 370 self._dirs.addpath(f)
374 371 self._dirty = True
375 372 self._map[f] = dirstatetuple(state, mode, size, mtime)
376 373
377 374 def normal(self, f):
378 375 '''Mark a file normal and clean.'''
379 376 s = os.lstat(self._join(f))
380 377 mtime = int(s.st_mtime)
381 378 self._addpath(f, 'n', s.st_mode,
382 379 s.st_size & _rangemask, mtime & _rangemask)
383 380 if f in self._copymap:
384 381 del self._copymap[f]
385 382 if mtime > self._lastnormaltime:
386 383 # Remember the most recent modification timeslot for status(),
387 384 # to make sure we won't miss future size-preserving file content
388 385 # modifications that happen within the same timeslot.
389 386 self._lastnormaltime = mtime
390 387
391 388 def normallookup(self, f):
392 389 '''Mark a file normal, but possibly dirty.'''
393 390 if self._pl[1] != nullid and f in self._map:
394 391 # if there is a merge going on and the file was either
395 392 # in state 'm' (-1) or coming from other parent (-2) before
396 393 # being removed, restore that state.
397 394 entry = self._map[f]
398 395 if entry[0] == 'r' and entry[2] in (-1, -2):
399 396 source = self._copymap.get(f)
400 397 if entry[2] == -1:
401 398 self.merge(f)
402 399 elif entry[2] == -2:
403 400 self.otherparent(f)
404 401 if source:
405 402 self.copy(source, f)
406 403 return
407 404 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
408 405 return
409 406 self._addpath(f, 'n', 0, -1, -1)
410 407 if f in self._copymap:
411 408 del self._copymap[f]
412 409
413 410 def otherparent(self, f):
414 411 '''Mark as coming from the other parent, always dirty.'''
415 412 if self._pl[1] == nullid:
416 413 raise util.Abort(_("setting %r to other parent "
417 414 "only allowed in merges") % f)
418 415 if f in self and self[f] == 'n':
419 416 # merge-like
420 417 self._addpath(f, 'm', 0, -2, -1)
421 418 else:
422 419 # add-like
423 420 self._addpath(f, 'n', 0, -2, -1)
424 421
425 422 if f in self._copymap:
426 423 del self._copymap[f]
427 424
428 425 def add(self, f):
429 426 '''Mark a file added.'''
430 427 self._addpath(f, 'a', 0, -1, -1)
431 428 if f in self._copymap:
432 429 del self._copymap[f]
433 430
434 431 def remove(self, f):
435 432 '''Mark a file removed.'''
436 433 self._dirty = True
437 434 self._droppath(f)
438 435 size = 0
439 436 if self._pl[1] != nullid and f in self._map:
440 437 # backup the previous state
441 438 entry = self._map[f]
442 439 if entry[0] == 'm': # merge
443 440 size = -1
444 441 elif entry[0] == 'n' and entry[2] == -2: # other parent
445 442 size = -2
446 443 self._map[f] = dirstatetuple('r', 0, size, 0)
447 444 if size == 0 and f in self._copymap:
448 445 del self._copymap[f]
449 446
450 447 def merge(self, f):
451 448 '''Mark a file merged.'''
452 449 if self._pl[1] == nullid:
453 450 return self.normallookup(f)
454 451 return self.otherparent(f)
455 452
456 453 def drop(self, f):
457 454 '''Drop a file from the dirstate'''
458 455 if f in self._map:
459 456 self._dirty = True
460 457 self._droppath(f)
461 458 del self._map[f]
462 459
463 460 def _normalize(self, path, isknown, ignoremissing=False, exists=None):
464 461 normed = util.normcase(path)
465 462 folded = self._foldmap.get(normed, None)
466 463 if folded is None:
467 464 if isknown:
468 465 folded = path
469 466 else:
470 467 if exists is None:
471 468 exists = os.path.lexists(os.path.join(self._root, path))
472 469 if not exists:
473 470 # Maybe a path component exists
474 471 if not ignoremissing and '/' in path:
475 472 d, f = path.rsplit('/', 1)
476 473 d = self._normalize(d, isknown, ignoremissing, None)
477 474 folded = d + "/" + f
478 475 else:
479 476 # No path components, preserve original case
480 477 folded = path
481 478 else:
482 479 # recursively normalize leading directory components
483 480 # against dirstate
484 481 if '/' in normed:
485 482 d, f = normed.rsplit('/', 1)
486 483 d = self._normalize(d, isknown, ignoremissing, True)
487 484 r = self._root + "/" + d
488 485 folded = d + "/" + util.fspath(f, r)
489 486 else:
490 487 folded = util.fspath(normed, self._root)
491 488 self._foldmap[normed] = folded
492 489
493 490 return folded
494 491
495 492 def normalize(self, path, isknown=False, ignoremissing=False):
496 493 '''
497 494 normalize the case of a pathname when on a casefolding filesystem
498 495
499 496 isknown specifies whether the filename came from walking the
500 497 disk, to avoid extra filesystem access.
501 498
502 499 If ignoremissing is True, missing path are returned
503 500 unchanged. Otherwise, we try harder to normalize possibly
504 501 existing path components.
505 502
506 503 The normalized case is determined based on the following precedence:
507 504
508 505 - version of name already stored in the dirstate
509 506 - version of name stored on disk
510 507 - version provided via command arguments
511 508 '''
512 509
513 510 if self._checkcase:
514 511 return self._normalize(path, isknown, ignoremissing)
515 512 return path
516 513
517 514 def clear(self):
518 515 self._map = {}
519 516 if "_dirs" in self.__dict__:
520 517 delattr(self, "_dirs")
521 518 self._copymap = {}
522 519 self._pl = [nullid, nullid]
523 520 self._lastnormaltime = 0
524 521 self._dirty = True
525 522
526 523 def rebuild(self, parent, allfiles, changedfiles=None):
527 524 changedfiles = changedfiles or allfiles
528 525 oldmap = self._map
529 526 self.clear()
530 527 for f in allfiles:
531 528 if f not in changedfiles:
532 529 self._map[f] = oldmap[f]
533 530 else:
534 531 if 'x' in allfiles.flags(f):
535 532 self._map[f] = dirstatetuple('n', 0777, -1, 0)
536 533 else:
537 534 self._map[f] = dirstatetuple('n', 0666, -1, 0)
538 535 self._pl = (parent, nullid)
539 536 self._dirty = True
540 537
541 538 def write(self):
542 539 if not self._dirty:
543 540 return
544 541
545 542 # enough 'delaywrite' prevents 'pack_dirstate' from dropping
546 543 # timestamp of each entries in dirstate, because of 'now > mtime'
547 544 delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
548 545 if delaywrite:
549 546 import time # to avoid useless import
550 547 time.sleep(delaywrite)
551 548
552 549 st = self._opener("dirstate", "w", atomictemp=True)
553 550 # use the modification time of the newly created temporary file as the
554 551 # filesystem's notion of 'now'
555 552 now = util.fstat(st).st_mtime
556 553 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
557 554 st.close()
558 555 self._lastnormaltime = 0
559 556 self._dirty = self._dirtypl = False
560 557
561 558 def _dirignore(self, f):
562 559 if f == '.':
563 560 return False
564 561 if self._ignore(f):
565 562 return True
566 563 for p in scmutil.finddirs(f):
567 564 if self._ignore(p):
568 565 return True
569 566 return False
570 567
571 568 def _walkexplicit(self, match, subrepos):
572 569 '''Get stat data about the files explicitly specified by match.
573 570
574 571 Return a triple (results, dirsfound, dirsnotfound).
575 572 - results is a mapping from filename to stat result. It also contains
576 573 listings mapping subrepos and .hg to None.
577 574 - dirsfound is a list of files found to be directories.
578 575 - dirsnotfound is a list of files that the dirstate thinks are
579 576 directories and that were not found.'''
580 577
581 578 def badtype(mode):
582 579 kind = _('unknown')
583 580 if stat.S_ISCHR(mode):
584 581 kind = _('character device')
585 582 elif stat.S_ISBLK(mode):
586 583 kind = _('block device')
587 584 elif stat.S_ISFIFO(mode):
588 585 kind = _('fifo')
589 586 elif stat.S_ISSOCK(mode):
590 587 kind = _('socket')
591 588 elif stat.S_ISDIR(mode):
592 589 kind = _('directory')
593 590 return _('unsupported file type (type is %s)') % kind
594 591
595 592 matchedir = match.explicitdir
596 593 badfn = match.bad
597 594 dmap = self._map
598 595 normpath = util.normpath
599 596 lstat = os.lstat
600 597 getkind = stat.S_IFMT
601 598 dirkind = stat.S_IFDIR
602 599 regkind = stat.S_IFREG
603 600 lnkkind = stat.S_IFLNK
604 601 join = self._join
605 602 dirsfound = []
606 603 foundadd = dirsfound.append
607 604 dirsnotfound = []
608 605 notfoundadd = dirsnotfound.append
609 606
610 607 if match.matchfn != match.exact and self._checkcase:
611 608 normalize = self._normalize
612 609 else:
613 610 normalize = None
614 611
615 612 files = sorted(match.files())
616 613 subrepos.sort()
617 614 i, j = 0, 0
618 615 while i < len(files) and j < len(subrepos):
619 616 subpath = subrepos[j] + "/"
620 617 if files[i] < subpath:
621 618 i += 1
622 619 continue
623 620 while i < len(files) and files[i].startswith(subpath):
624 621 del files[i]
625 622 j += 1
626 623
627 624 if not files or '.' in files:
628 625 files = ['']
629 626 results = dict.fromkeys(subrepos)
630 627 results['.hg'] = None
631 628
632 629 alldirs = None
633 630 for ff in files:
634 631 if normalize:
635 632 nf = normalize(normpath(ff), False, True)
636 633 else:
637 634 nf = normpath(ff)
638 635 if nf in results:
639 636 continue
640 637
641 638 try:
642 639 st = lstat(join(nf))
643 640 kind = getkind(st.st_mode)
644 641 if kind == dirkind:
645 642 if nf in dmap:
646 643 # file replaced by dir on disk but still in dirstate
647 644 results[nf] = None
648 645 if matchedir:
649 646 matchedir(nf)
650 647 foundadd(nf)
651 648 elif kind == regkind or kind == lnkkind:
652 649 results[nf] = st
653 650 else:
654 651 badfn(ff, badtype(kind))
655 652 if nf in dmap:
656 653 results[nf] = None
657 654 except OSError, inst: # nf not found on disk - it is dirstate only
658 655 if nf in dmap: # does it exactly match a missing file?
659 656 results[nf] = None
660 657 else: # does it match a missing directory?
661 658 if alldirs is None:
662 659 alldirs = scmutil.dirs(dmap)
663 660 if nf in alldirs:
664 661 if matchedir:
665 662 matchedir(nf)
666 663 notfoundadd(nf)
667 664 else:
668 665 badfn(ff, inst.strerror)
669 666
670 667 return results, dirsfound, dirsnotfound
671 668
672 669 def walk(self, match, subrepos, unknown, ignored, full=True):
673 670 '''
674 671 Walk recursively through the directory tree, finding all files
675 672 matched by match.
676 673
677 674 If full is False, maybe skip some known-clean files.
678 675
679 676 Return a dict mapping filename to stat-like object (either
680 677 mercurial.osutil.stat instance or return value of os.stat()).
681 678
682 679 '''
683 680 # full is a flag that extensions that hook into walk can use -- this
684 681 # implementation doesn't use it at all. This satisfies the contract
685 682 # because we only guarantee a "maybe".
686 683
687 684 if ignored:
688 685 ignore = util.never
689 686 dirignore = util.never
690 687 elif unknown:
691 688 ignore = self._ignore
692 689 dirignore = self._dirignore
693 690 else:
694 691 # if not unknown and not ignored, drop dir recursion and step 2
695 692 ignore = util.always
696 693 dirignore = util.always
697 694
698 695 matchfn = match.matchfn
699 696 matchalways = match.always()
700 697 matchtdir = match.traversedir
701 698 dmap = self._map
702 699 listdir = osutil.listdir
703 700 lstat = os.lstat
704 701 dirkind = stat.S_IFDIR
705 702 regkind = stat.S_IFREG
706 703 lnkkind = stat.S_IFLNK
707 704 join = self._join
708 705
709 706 exact = skipstep3 = False
710 707 if matchfn == match.exact: # match.exact
711 708 exact = True
712 709 dirignore = util.always # skip step 2
713 710 elif match.files() and not match.anypats(): # match.match, no patterns
714 711 skipstep3 = True
715 712
716 713 if not exact and self._checkcase:
717 714 normalize = self._normalize
718 715 skipstep3 = False
719 716 else:
720 717 normalize = None
721 718
722 719 # step 1: find all explicit files
723 720 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
724 721
725 722 skipstep3 = skipstep3 and not (work or dirsnotfound)
726 723 work = [d for d in work if not dirignore(d)]
727 724 wadd = work.append
728 725
729 726 # step 2: visit subdirectories
730 727 while work:
731 728 nd = work.pop()
732 729 skip = None
733 730 if nd == '.':
734 731 nd = ''
735 732 else:
736 733 skip = '.hg'
737 734 try:
738 735 entries = listdir(join(nd), stat=True, skip=skip)
739 736 except OSError, inst:
740 737 if inst.errno in (errno.EACCES, errno.ENOENT):
741 738 match.bad(self.pathto(nd), inst.strerror)
742 739 continue
743 740 raise
744 741 for f, kind, st in entries:
745 742 if normalize:
746 743 nf = normalize(nd and (nd + "/" + f) or f, True, True)
747 744 else:
748 745 nf = nd and (nd + "/" + f) or f
749 746 if nf not in results:
750 747 if kind == dirkind:
751 748 if not ignore(nf):
752 749 if matchtdir:
753 750 matchtdir(nf)
754 751 wadd(nf)
755 752 if nf in dmap and (matchalways or matchfn(nf)):
756 753 results[nf] = None
757 754 elif kind == regkind or kind == lnkkind:
758 755 if nf in dmap:
759 756 if matchalways or matchfn(nf):
760 757 results[nf] = st
761 758 elif (matchalways or matchfn(nf)) and not ignore(nf):
762 759 results[nf] = st
763 760 elif nf in dmap and (matchalways or matchfn(nf)):
764 761 results[nf] = None
765 762
766 763 for s in subrepos:
767 764 del results[s]
768 765 del results['.hg']
769 766
770 767 # step 3: visit remaining files from dmap
771 768 if not skipstep3 and not exact:
772 769 # If a dmap file is not in results yet, it was either
773 770 # a) not matching matchfn b) ignored, c) missing, or d) under a
774 771 # symlink directory.
775 772 if not results and matchalways:
776 773 visit = dmap.keys()
777 774 else:
778 775 visit = [f for f in dmap if f not in results and matchfn(f)]
779 776 visit.sort()
780 777
781 778 if unknown:
782 779 # unknown == True means we walked all dirs under the roots
783 780 # that wasn't ignored, and everything that matched was stat'ed
784 781 # and is already in results.
785 782 # The rest must thus be ignored or under a symlink.
786 783 audit_path = pathutil.pathauditor(self._root)
787 784
788 785 for nf in iter(visit):
789 786 # Report ignored items in the dmap as long as they are not
790 787 # under a symlink directory.
791 788 if audit_path.check(nf):
792 789 try:
793 790 results[nf] = lstat(join(nf))
794 791 # file was just ignored, no links, and exists
795 792 except OSError:
796 793 # file doesn't exist
797 794 results[nf] = None
798 795 else:
799 796 # It's either missing or under a symlink directory
800 797 # which we in this case report as missing
801 798 results[nf] = None
802 799 else:
803 800 # We may not have walked the full directory tree above,
804 801 # so stat and check everything we missed.
805 802 nf = iter(visit).next
806 803 for st in util.statfiles([join(i) for i in visit]):
807 804 results[nf()] = st
808 805 return results
809 806
810 807 def status(self, match, subrepos, ignored, clean, unknown):
811 808 '''Determine the status of the working copy relative to the
812 809 dirstate and return a pair of (unsure, status), where status is of type
813 810 scmutil.status and:
814 811
815 812 unsure:
816 813 files that might have been modified since the dirstate was
817 814 written, but need to be read to be sure (size is the same
818 815 but mtime differs)
819 816 status.modified:
820 817 files that have definitely been modified since the dirstate
821 818 was written (different size or mode)
822 819 status.clean:
823 820 files that have definitely not been modified since the
824 821 dirstate was written
825 822 '''
826 823 listignored, listclean, listunknown = ignored, clean, unknown
827 824 lookup, modified, added, unknown, ignored = [], [], [], [], []
828 825 removed, deleted, clean = [], [], []
829 826
830 827 dmap = self._map
831 828 ladd = lookup.append # aka "unsure"
832 829 madd = modified.append
833 830 aadd = added.append
834 831 uadd = unknown.append
835 832 iadd = ignored.append
836 833 radd = removed.append
837 834 dadd = deleted.append
838 835 cadd = clean.append
839 836 mexact = match.exact
840 837 dirignore = self._dirignore
841 838 checkexec = self._checkexec
842 839 copymap = self._copymap
843 840 lastnormaltime = self._lastnormaltime
844 841
845 842 # We need to do full walks when either
846 843 # - we're listing all clean files, or
847 844 # - match.traversedir does something, because match.traversedir should
848 845 # be called for every dir in the working dir
849 846 full = listclean or match.traversedir is not None
850 847 for fn, st in self.walk(match, subrepos, listunknown, listignored,
851 848 full=full).iteritems():
852 849 if fn not in dmap:
853 850 if (listignored or mexact(fn)) and dirignore(fn):
854 851 if listignored:
855 852 iadd(fn)
856 853 else:
857 854 uadd(fn)
858 855 continue
859 856
860 857 # This is equivalent to 'state, mode, size, time = dmap[fn]' but not
861 858 # written like that for performance reasons. dmap[fn] is not a
862 859 # Python tuple in compiled builds. The CPython UNPACK_SEQUENCE
863 860 # opcode has fast paths when the value to be unpacked is a tuple or
864 861 # a list, but falls back to creating a full-fledged iterator in
865 862 # general. That is much slower than simply accessing and storing the
866 863 # tuple members one by one.
867 864 t = dmap[fn]
868 865 state = t[0]
869 866 mode = t[1]
870 867 size = t[2]
871 868 time = t[3]
872 869
873 870 if not st and state in "nma":
874 871 dadd(fn)
875 872 elif state == 'n':
876 873 mtime = int(st.st_mtime)
877 874 if (size >= 0 and
878 875 ((size != st.st_size and size != st.st_size & _rangemask)
879 876 or ((mode ^ st.st_mode) & 0100 and checkexec))
880 877 or size == -2 # other parent
881 878 or fn in copymap):
882 879 madd(fn)
883 880 elif time != mtime and time != mtime & _rangemask:
884 881 ladd(fn)
885 882 elif mtime == lastnormaltime:
886 883 # fn may have been changed in the same timeslot without
887 884 # changing its size. This can happen if we quickly do
888 885 # multiple commits in a single transaction.
889 886 # Force lookup, so we don't miss such a racy file change.
890 887 ladd(fn)
891 888 elif listclean:
892 889 cadd(fn)
893 890 elif state == 'm':
894 891 madd(fn)
895 892 elif state == 'a':
896 893 aadd(fn)
897 894 elif state == 'r':
898 895 radd(fn)
899 896
900 897 return (lookup, scmutil.status(modified, added, removed, deleted,
901 898 unknown, ignored, clean))
902 899
903 900 def matches(self, match):
904 901 '''
905 902 return files in the dirstate (in whatever state) filtered by match
906 903 '''
907 904 dmap = self._map
908 905 if match.always():
909 906 return dmap.keys()
910 907 files = match.files()
911 908 if match.matchfn == match.exact:
912 909 # fast path -- filter the other way around, since typically files is
913 910 # much smaller than dmap
914 911 return [f for f in files if f in dmap]
915 912 if not match.anypats() and util.all(fn in dmap for fn in files):
916 913 # fast path -- all the values are known to be files, so just return
917 914 # that
918 915 return list(files)
919 916 return [f for f in dmap if match(f)]
General Comments 0
You need to be logged in to leave comments. Login now