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