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