##// END OF EJS Templates
dirstate: improve documentation and readability of match and ignore in the walker
Mads Kiilerich -
r21115:1b6e37f4 default
parent child Browse files
Show More
@@ -1,851 +1,858 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 #file deleted on disk but still in dirstate
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 except OSError, inst:
610 if nf in dmap: # does it exactly match a file?
609 except OSError, inst: # nf not found on disk - it is dirstate only
610 if nf in dmap: # does it exactly match a missing file?
611 611 results[nf] = None
612 else: # does it match a directory?
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 640 def fwarn(f, msg):
641 641 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
642 642 return False
643 643
644 ignore = self._ignore
645 dirignore = self._dirignore
646 644 if ignored:
647 645 ignore = util.never
648 646 dirignore = util.never
649 elif not unknown:
650 # if unknown and ignored are False, skip step 2
647 elif unknown:
648 ignore = self._ignore
649 dirignore = self._dirignore
650 else:
651 # if not unknown and not ignored, drop dir recursion and step 2
651 652 ignore = util.always
652 653 dirignore = util.always
653 654
654 655 matchfn = match.matchfn
655 656 matchalways = match.always()
656 657 matchtdir = match.traversedir
657 658 dmap = self._map
658 659 listdir = osutil.listdir
659 660 lstat = os.lstat
660 661 dirkind = stat.S_IFDIR
661 662 regkind = stat.S_IFREG
662 663 lnkkind = stat.S_IFLNK
663 664 join = self._join
664 665
665 666 exact = skipstep3 = False
666 667 if matchfn == match.exact: # match.exact
667 668 exact = True
668 669 dirignore = util.always # skip step 2
669 670 elif match.files() and not match.anypats(): # match.match, no patterns
670 671 skipstep3 = True
671 672
672 673 if not exact and self._checkcase:
673 674 normalize = self._normalize
674 675 skipstep3 = False
675 676 else:
676 677 normalize = None
677 678
678 679 # step 1: find all explicit files
679 680 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
680 681
681 682 skipstep3 = skipstep3 and not (work or dirsnotfound)
682 683 work = [d for d in work if not dirignore(d)]
683 684 wadd = work.append
684 685
685 686 # step 2: visit subdirectories
686 687 while work:
687 688 nd = work.pop()
688 689 skip = None
689 690 if nd == '.':
690 691 nd = ''
691 692 else:
692 693 skip = '.hg'
693 694 try:
694 695 entries = listdir(join(nd), stat=True, skip=skip)
695 696 except OSError, inst:
696 697 if inst.errno in (errno.EACCES, errno.ENOENT):
697 698 fwarn(nd, inst.strerror)
698 699 continue
699 700 raise
700 701 for f, kind, st in entries:
701 702 if normalize:
702 703 nf = normalize(nd and (nd + "/" + f) or f, True, True)
703 704 else:
704 705 nf = nd and (nd + "/" + f) or f
705 706 if nf not in results:
706 707 if kind == dirkind:
707 708 if not ignore(nf):
708 709 if matchtdir:
709 710 matchtdir(nf)
710 711 wadd(nf)
711 712 if nf in dmap and (matchalways or matchfn(nf)):
712 713 results[nf] = None
713 714 elif kind == regkind or kind == lnkkind:
714 715 if nf in dmap:
715 716 if matchalways or matchfn(nf):
716 717 results[nf] = st
717 718 elif (matchalways or matchfn(nf)) and not ignore(nf):
718 719 results[nf] = st
719 720 elif nf in dmap and (matchalways or matchfn(nf)):
720 721 results[nf] = None
721 722
722 723 for s in subrepos:
723 724 del results[s]
724 725 del results['.hg']
725 726
726 # step 3: report unseen items in the dmap hash
727 # step 3: visit remaining files from dmap
727 728 if not skipstep3 and not exact:
729 # If a dmap file is not in results yet, it was either
730 # a) not matching matchfn b) ignored, c) missing, or d) under a
731 # symlink directory.
728 732 if not results and matchalways:
729 733 visit = dmap.keys()
730 734 else:
731 735 visit = [f for f in dmap if f not in results and matchfn(f)]
732 736 visit.sort()
733 737
734 738 if unknown:
735 # unknown == True means we walked the full directory tree above.
736 # So if a file is not seen it was either a) not matching matchfn
737 # b) ignored, c) missing, or d) under a symlink directory.
739 # unknown == True means we walked all dirs under the roots
740 # that wasn't ignored, and everything that matched was stat'ed
741 # and is already in results.
742 # The rest must thus be ignored or under a symlink.
738 743 audit_path = pathutil.pathauditor(self._root)
739 744
740 745 for nf in iter(visit):
741 746 # Report ignored items in the dmap as long as they are not
742 747 # under a symlink directory.
743 748 if audit_path.check(nf):
744 749 try:
745 750 results[nf] = lstat(join(nf))
751 # file was just ignored, no links, and exists
746 752 except OSError:
747 753 # file doesn't exist
748 754 results[nf] = None
749 755 else:
750 756 # It's either missing or under a symlink directory
757 # which we in this case report as missing
751 758 results[nf] = None
752 759 else:
753 760 # We may not have walked the full directory tree above,
754 # so stat everything we missed.
761 # so stat and check everything we missed.
755 762 nf = iter(visit).next
756 763 for st in util.statfiles([join(i) for i in visit]):
757 764 results[nf()] = st
758 765 return results
759 766
760 767 def status(self, match, subrepos, ignored, clean, unknown):
761 768 '''Determine the status of the working copy relative to the
762 769 dirstate and return a tuple of lists (unsure, modified, added,
763 770 removed, deleted, unknown, ignored, clean), where:
764 771
765 772 unsure:
766 773 files that might have been modified since the dirstate was
767 774 written, but need to be read to be sure (size is the same
768 775 but mtime differs)
769 776 modified:
770 777 files that have definitely been modified since the dirstate
771 778 was written (different size or mode)
772 779 added:
773 780 files that have been explicitly added with hg add
774 781 removed:
775 782 files that have been explicitly removed with hg remove
776 783 deleted:
777 784 files that have been deleted through other means ("missing")
778 785 unknown:
779 786 files not in the dirstate that are not ignored
780 787 ignored:
781 788 files not in the dirstate that are ignored
782 789 (by _dirignore())
783 790 clean:
784 791 files that have definitely not been modified since the
785 792 dirstate was written
786 793 '''
787 794 listignored, listclean, listunknown = ignored, clean, unknown
788 795 lookup, modified, added, unknown, ignored = [], [], [], [], []
789 796 removed, deleted, clean = [], [], []
790 797
791 798 dmap = self._map
792 799 ladd = lookup.append # aka "unsure"
793 800 madd = modified.append
794 801 aadd = added.append
795 802 uadd = unknown.append
796 803 iadd = ignored.append
797 804 radd = removed.append
798 805 dadd = deleted.append
799 806 cadd = clean.append
800 807 mexact = match.exact
801 808 dirignore = self._dirignore
802 809 checkexec = self._checkexec
803 810 copymap = self._copymap
804 811 lastnormaltime = self._lastnormaltime
805 812
806 813 # We need to do full walks when either
807 814 # - we're listing all clean files, or
808 815 # - match.traversedir does something, because match.traversedir should
809 816 # be called for every dir in the working dir
810 817 full = listclean or match.traversedir is not None
811 818 for fn, st in self.walk(match, subrepos, listunknown, listignored,
812 819 full=full).iteritems():
813 820 if fn not in dmap:
814 821 if (listignored or mexact(fn)) and dirignore(fn):
815 822 if listignored:
816 823 iadd(fn)
817 824 else:
818 825 uadd(fn)
819 826 continue
820 827
821 828 state, mode, size, time = dmap[fn]
822 829
823 830 if not st and state in "nma":
824 831 dadd(fn)
825 832 elif state == 'n':
826 833 mtime = int(st.st_mtime)
827 834 if (size >= 0 and
828 835 ((size != st.st_size and size != st.st_size & _rangemask)
829 836 or ((mode ^ st.st_mode) & 0100 and checkexec))
830 837 or size == -2 # other parent
831 838 or fn in copymap):
832 839 madd(fn)
833 840 elif time != mtime and time != mtime & _rangemask:
834 841 ladd(fn)
835 842 elif mtime == lastnormaltime:
836 843 # fn may have been changed in the same timeslot without
837 844 # changing its size. This can happen if we quickly do
838 845 # multiple commits in a single transaction.
839 846 # Force lookup, so we don't miss such a racy file change.
840 847 ladd(fn)
841 848 elif listclean:
842 849 cadd(fn)
843 850 elif state == 'm':
844 851 madd(fn)
845 852 elif state == 'a':
846 853 aadd(fn)
847 854 elif state == 'r':
848 855 radd(fn)
849 856
850 857 return (lookup, modified, added, removed, deleted, unknown, ignored,
851 858 clean)
General Comments 0
You need to be logged in to leave comments. Login now