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