##// END OF EJS Templates
dirstate: inline local finish function...
Mads Kiilerich -
r21026:7ee03e19 default
parent child Browse files
Show More
@@ -1,855 +1,851 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
507 def finish(s):
508 st.write(s)
506 # use the modification time of the newly created temporary file as the
507 # filesystem's notion of 'now'
508 now = util.fstat(st).st_mtime
509 st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
509 510 st.close()
510 511 self._lastnormaltime = 0
511 512 self._dirty = self._dirtypl = False
512 513
513 # use the modification time of the newly created temporary file as the
514 # filesystem's notion of 'now'
515 now = util.fstat(st).st_mtime
516 finish(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
517
518 514 def _dirignore(self, f):
519 515 if f == '.':
520 516 return False
521 517 if self._ignore(f):
522 518 return True
523 519 for p in scmutil.finddirs(f):
524 520 if self._ignore(p):
525 521 return True
526 522 return False
527 523
528 524 def _walkexplicit(self, match, subrepos):
529 525 '''Get stat data about the files explicitly specified by match.
530 526
531 527 Return a triple (results, dirsfound, dirsnotfound).
532 528 - results is a mapping from filename to stat result. It also contains
533 529 listings mapping subrepos and .hg to None.
534 530 - dirsfound is a list of files found to be directories.
535 531 - dirsnotfound is a list of files that the dirstate thinks are
536 532 directories and that were not found.'''
537 533
538 534 def badtype(mode):
539 535 kind = _('unknown')
540 536 if stat.S_ISCHR(mode):
541 537 kind = _('character device')
542 538 elif stat.S_ISBLK(mode):
543 539 kind = _('block device')
544 540 elif stat.S_ISFIFO(mode):
545 541 kind = _('fifo')
546 542 elif stat.S_ISSOCK(mode):
547 543 kind = _('socket')
548 544 elif stat.S_ISDIR(mode):
549 545 kind = _('directory')
550 546 return _('unsupported file type (type is %s)') % kind
551 547
552 548 matchedir = match.explicitdir
553 549 badfn = match.bad
554 550 dmap = self._map
555 551 normpath = util.normpath
556 552 lstat = os.lstat
557 553 getkind = stat.S_IFMT
558 554 dirkind = stat.S_IFDIR
559 555 regkind = stat.S_IFREG
560 556 lnkkind = stat.S_IFLNK
561 557 join = self._join
562 558 dirsfound = []
563 559 foundadd = dirsfound.append
564 560 dirsnotfound = []
565 561 notfoundadd = dirsnotfound.append
566 562
567 563 if match.matchfn != match.exact and self._checkcase:
568 564 normalize = self._normalize
569 565 else:
570 566 normalize = None
571 567
572 568 files = sorted(match.files())
573 569 subrepos.sort()
574 570 i, j = 0, 0
575 571 while i < len(files) and j < len(subrepos):
576 572 subpath = subrepos[j] + "/"
577 573 if files[i] < subpath:
578 574 i += 1
579 575 continue
580 576 while i < len(files) and files[i].startswith(subpath):
581 577 del files[i]
582 578 j += 1
583 579
584 580 if not files or '.' in files:
585 581 files = ['']
586 582 results = dict.fromkeys(subrepos)
587 583 results['.hg'] = None
588 584
589 585 for ff in files:
590 586 if normalize:
591 587 nf = normalize(normpath(ff), False, True)
592 588 else:
593 589 nf = normpath(ff)
594 590 if nf in results:
595 591 continue
596 592
597 593 try:
598 594 st = lstat(join(nf))
599 595 kind = getkind(st.st_mode)
600 596 if kind == dirkind:
601 597 if nf in dmap:
602 598 #file deleted on disk but still in dirstate
603 599 results[nf] = None
604 600 if matchedir:
605 601 matchedir(nf)
606 602 foundadd(nf)
607 603 elif kind == regkind or kind == lnkkind:
608 604 results[nf] = st
609 605 else:
610 606 badfn(ff, badtype(kind))
611 607 if nf in dmap:
612 608 results[nf] = None
613 609 except OSError, inst:
614 610 if nf in dmap: # does it exactly match a file?
615 611 results[nf] = None
616 612 else: # does it match a directory?
617 613 prefix = nf + "/"
618 614 for fn in dmap:
619 615 if fn.startswith(prefix):
620 616 if matchedir:
621 617 matchedir(nf)
622 618 notfoundadd(nf)
623 619 break
624 620 else:
625 621 badfn(ff, inst.strerror)
626 622
627 623 return results, dirsfound, dirsnotfound
628 624
629 625 def walk(self, match, subrepos, unknown, ignored, full=True):
630 626 '''
631 627 Walk recursively through the directory tree, finding all files
632 628 matched by match.
633 629
634 630 If full is False, maybe skip some known-clean files.
635 631
636 632 Return a dict mapping filename to stat-like object (either
637 633 mercurial.osutil.stat instance or return value of os.stat()).
638 634
639 635 '''
640 636 # full is a flag that extensions that hook into walk can use -- this
641 637 # implementation doesn't use it at all. This satisfies the contract
642 638 # because we only guarantee a "maybe".
643 639
644 640 def fwarn(f, msg):
645 641 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
646 642 return False
647 643
648 644 ignore = self._ignore
649 645 dirignore = self._dirignore
650 646 if ignored:
651 647 ignore = util.never
652 648 dirignore = util.never
653 649 elif not unknown:
654 650 # if unknown and ignored are False, skip step 2
655 651 ignore = util.always
656 652 dirignore = util.always
657 653
658 654 matchfn = match.matchfn
659 655 matchalways = match.always()
660 656 matchtdir = match.traversedir
661 657 dmap = self._map
662 658 listdir = osutil.listdir
663 659 lstat = os.lstat
664 660 dirkind = stat.S_IFDIR
665 661 regkind = stat.S_IFREG
666 662 lnkkind = stat.S_IFLNK
667 663 join = self._join
668 664
669 665 exact = skipstep3 = False
670 666 if matchfn == match.exact: # match.exact
671 667 exact = True
672 668 dirignore = util.always # skip step 2
673 669 elif match.files() and not match.anypats(): # match.match, no patterns
674 670 skipstep3 = True
675 671
676 672 if not exact and self._checkcase:
677 673 normalize = self._normalize
678 674 skipstep3 = False
679 675 else:
680 676 normalize = None
681 677
682 678 # step 1: find all explicit files
683 679 results, work, dirsnotfound = self._walkexplicit(match, subrepos)
684 680
685 681 skipstep3 = skipstep3 and not (work or dirsnotfound)
686 682 work = [d for d in work if not dirignore(d)]
687 683 wadd = work.append
688 684
689 685 # step 2: visit subdirectories
690 686 while work:
691 687 nd = work.pop()
692 688 skip = None
693 689 if nd == '.':
694 690 nd = ''
695 691 else:
696 692 skip = '.hg'
697 693 try:
698 694 entries = listdir(join(nd), stat=True, skip=skip)
699 695 except OSError, inst:
700 696 if inst.errno in (errno.EACCES, errno.ENOENT):
701 697 fwarn(nd, inst.strerror)
702 698 continue
703 699 raise
704 700 for f, kind, st in entries:
705 701 if normalize:
706 702 nf = normalize(nd and (nd + "/" + f) or f, True, True)
707 703 else:
708 704 nf = nd and (nd + "/" + f) or f
709 705 if nf not in results:
710 706 if kind == dirkind:
711 707 if not ignore(nf):
712 708 if matchtdir:
713 709 matchtdir(nf)
714 710 wadd(nf)
715 711 if nf in dmap and (matchalways or matchfn(nf)):
716 712 results[nf] = None
717 713 elif kind == regkind or kind == lnkkind:
718 714 if nf in dmap:
719 715 if matchalways or matchfn(nf):
720 716 results[nf] = st
721 717 elif (matchalways or matchfn(nf)) and not ignore(nf):
722 718 results[nf] = st
723 719 elif nf in dmap and (matchalways or matchfn(nf)):
724 720 results[nf] = None
725 721
726 722 for s in subrepos:
727 723 del results[s]
728 724 del results['.hg']
729 725
730 726 # step 3: report unseen items in the dmap hash
731 727 if not skipstep3 and not exact:
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 the full directory tree above.
740 736 # So if a file is not seen it was either a) not matching matchfn
741 737 # b) ignored, c) missing, or d) under a symlink directory.
742 738 audit_path = pathutil.pathauditor(self._root)
743 739
744 740 for nf in iter(visit):
745 741 # Report ignored items in the dmap as long as they are not
746 742 # under a symlink directory.
747 743 if audit_path.check(nf):
748 744 try:
749 745 results[nf] = lstat(join(nf))
750 746 except OSError:
751 747 # file doesn't exist
752 748 results[nf] = None
753 749 else:
754 750 # It's either missing or under a symlink directory
755 751 results[nf] = None
756 752 else:
757 753 # We may not have walked the full directory tree above,
758 754 # so stat everything we missed.
759 755 nf = iter(visit).next
760 756 for st in util.statfiles([join(i) for i in visit]):
761 757 results[nf()] = st
762 758 return results
763 759
764 760 def status(self, match, subrepos, ignored, clean, unknown):
765 761 '''Determine the status of the working copy relative to the
766 762 dirstate and return a tuple of lists (unsure, modified, added,
767 763 removed, deleted, unknown, ignored, clean), where:
768 764
769 765 unsure:
770 766 files that might have been modified since the dirstate was
771 767 written, but need to be read to be sure (size is the same
772 768 but mtime differs)
773 769 modified:
774 770 files that have definitely been modified since the dirstate
775 771 was written (different size or mode)
776 772 added:
777 773 files that have been explicitly added with hg add
778 774 removed:
779 775 files that have been explicitly removed with hg remove
780 776 deleted:
781 777 files that have been deleted through other means ("missing")
782 778 unknown:
783 779 files not in the dirstate that are not ignored
784 780 ignored:
785 781 files not in the dirstate that are ignored
786 782 (by _dirignore())
787 783 clean:
788 784 files that have definitely not been modified since the
789 785 dirstate was written
790 786 '''
791 787 listignored, listclean, listunknown = ignored, clean, unknown
792 788 lookup, modified, added, unknown, ignored = [], [], [], [], []
793 789 removed, deleted, clean = [], [], []
794 790
795 791 dmap = self._map
796 792 ladd = lookup.append # aka "unsure"
797 793 madd = modified.append
798 794 aadd = added.append
799 795 uadd = unknown.append
800 796 iadd = ignored.append
801 797 radd = removed.append
802 798 dadd = deleted.append
803 799 cadd = clean.append
804 800 mexact = match.exact
805 801 dirignore = self._dirignore
806 802 checkexec = self._checkexec
807 803 copymap = self._copymap
808 804 lastnormaltime = self._lastnormaltime
809 805
810 806 # We need to do full walks when either
811 807 # - we're listing all clean files, or
812 808 # - match.traversedir does something, because match.traversedir should
813 809 # be called for every dir in the working dir
814 810 full = listclean or match.traversedir is not None
815 811 for fn, st in self.walk(match, subrepos, listunknown, listignored,
816 812 full=full).iteritems():
817 813 if fn not in dmap:
818 814 if (listignored or mexact(fn)) and dirignore(fn):
819 815 if listignored:
820 816 iadd(fn)
821 817 else:
822 818 uadd(fn)
823 819 continue
824 820
825 821 state, mode, size, time = dmap[fn]
826 822
827 823 if not st and state in "nma":
828 824 dadd(fn)
829 825 elif state == 'n':
830 826 mtime = int(st.st_mtime)
831 827 if (size >= 0 and
832 828 ((size != st.st_size and size != st.st_size & _rangemask)
833 829 or ((mode ^ st.st_mode) & 0100 and checkexec))
834 830 or size == -2 # other parent
835 831 or fn in copymap):
836 832 madd(fn)
837 833 elif time != mtime and time != mtime & _rangemask:
838 834 ladd(fn)
839 835 elif mtime == lastnormaltime:
840 836 # fn may have been changed in the same timeslot without
841 837 # changing its size. This can happen if we quickly do
842 838 # multiple commits in a single transaction.
843 839 # Force lookup, so we don't miss such a racy file change.
844 840 ladd(fn)
845 841 elif listclean:
846 842 cadd(fn)
847 843 elif state == 'm':
848 844 madd(fn)
849 845 elif state == 'a':
850 846 aadd(fn)
851 847 elif state == 'r':
852 848 radd(fn)
853 849
854 850 return (lookup, modified, added, removed, deleted, unknown, ignored,
855 851 clean)
General Comments 0
You need to be logged in to leave comments. Login now