##// END OF EJS Templates
dirstate: check mtime when adding to _lastnormal...
Adrian Buehlmann -
r13754:ae157ca5 default
parent child Browse files
Show More
@@ -1,739 +1,749 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 util, ignore, osutil, parsers, encoding
11 11 import struct, os, stat, errno
12 12 import cStringIO
13 13
14 14 _format = ">cllll"
15 15 propertycache = util.propertycache
16 16
17 17 def _finddirs(path):
18 18 pos = path.rfind('/')
19 19 while pos != -1:
20 20 yield path[:pos]
21 21 pos = path.rfind('/', 0, pos)
22 22
23 23 def _incdirs(dirs, path):
24 24 for base in _finddirs(path):
25 25 if base in dirs:
26 26 dirs[base] += 1
27 27 return
28 28 dirs[base] = 1
29 29
30 30 def _decdirs(dirs, path):
31 31 for base in _finddirs(path):
32 32 if dirs[base] > 1:
33 33 dirs[base] -= 1
34 34 return
35 35 del dirs[base]
36 36
37 37 class dirstate(object):
38 38
39 39 def __init__(self, opener, ui, root, validate):
40 40 '''Create a new dirstate object.
41 41
42 42 opener is an open()-like callable that can be used to open the
43 43 dirstate file; root is the root of the directory tracked by
44 44 the dirstate.
45 45 '''
46 46 self._opener = opener
47 47 self._validate = validate
48 48 self._root = root
49 49 self._rootdir = os.path.join(root, '')
50 50 self._dirty = False
51 51 self._dirtypl = False
52 52 self._lastnormal = set() # files believed to be normal
53 53 self._lastnormaltime = None
54 54 self._ui = ui
55 55
56 56 @propertycache
57 57 def _map(self):
58 58 '''Return the dirstate contents as a map from filename to
59 59 (state, mode, size, time).'''
60 60 self._read()
61 61 return self._map
62 62
63 63 @propertycache
64 64 def _copymap(self):
65 65 self._read()
66 66 return self._copymap
67 67
68 68 @propertycache
69 69 def _foldmap(self):
70 70 f = {}
71 71 for name in self._map:
72 72 f[os.path.normcase(name)] = name
73 73 return f
74 74
75 75 @propertycache
76 76 def _branch(self):
77 77 try:
78 78 return self._opener("branch").read().strip() or "default"
79 79 except IOError:
80 80 return "default"
81 81
82 82 @propertycache
83 83 def _pl(self):
84 84 try:
85 85 fp = self._opener("dirstate")
86 86 st = fp.read(40)
87 87 fp.close()
88 88 l = len(st)
89 89 if l == 40:
90 90 return st[:20], st[20:40]
91 91 elif l > 0 and l < 40:
92 92 raise util.Abort(_('working directory state appears damaged!'))
93 93 except IOError, err:
94 94 if err.errno != errno.ENOENT:
95 95 raise
96 96 return [nullid, nullid]
97 97
98 98 @propertycache
99 99 def _dirs(self):
100 100 dirs = {}
101 101 for f, s in self._map.iteritems():
102 102 if s[0] != 'r':
103 103 _incdirs(dirs, f)
104 104 return dirs
105 105
106 106 @propertycache
107 107 def _ignore(self):
108 108 files = [self._join('.hgignore')]
109 109 for name, path in self._ui.configitems("ui"):
110 110 if name == 'ignore' or name.startswith('ignore.'):
111 111 files.append(util.expandpath(path))
112 112 return ignore.ignore(self._root, files, self._ui.warn)
113 113
114 114 @propertycache
115 115 def _slash(self):
116 116 return self._ui.configbool('ui', 'slash') and os.sep != '/'
117 117
118 118 @propertycache
119 119 def _checklink(self):
120 120 return util.checklink(self._root)
121 121
122 122 @propertycache
123 123 def _checkexec(self):
124 124 return util.checkexec(self._root)
125 125
126 126 @propertycache
127 127 def _checkcase(self):
128 128 return not util.checkcase(self._join('.hg'))
129 129
130 130 def _join(self, f):
131 131 # much faster than os.path.join()
132 132 # it's safe because f is always a relative path
133 133 return self._rootdir + f
134 134
135 135 def flagfunc(self, fallback):
136 136 if self._checklink:
137 137 if self._checkexec:
138 138 def f(x):
139 139 p = self._join(x)
140 140 if os.path.islink(p):
141 141 return 'l'
142 142 if util.is_exec(p):
143 143 return 'x'
144 144 return ''
145 145 return f
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.is_exec(self._join(x)):
158 158 return 'x'
159 159 return ''
160 160 return f
161 161 return fallback
162 162
163 163 def getcwd(self):
164 164 cwd = os.getcwd()
165 165 if cwd == self._root:
166 166 return ''
167 167 # self._root ends with a path separator if self._root is '/' or 'C:\'
168 168 rootsep = self._root
169 169 if not util.endswithsep(rootsep):
170 170 rootsep += os.sep
171 171 if cwd.startswith(rootsep):
172 172 return cwd[len(rootsep):]
173 173 else:
174 174 # we're outside the repo. return an absolute path.
175 175 return cwd
176 176
177 177 def pathto(self, f, cwd=None):
178 178 if cwd is None:
179 179 cwd = self.getcwd()
180 180 path = util.pathto(self._root, cwd, f)
181 181 if self._slash:
182 182 return util.normpath(path)
183 183 return path
184 184
185 185 def __getitem__(self, key):
186 186 '''Return the current state of key (a filename) in the dirstate.
187 187
188 188 States are:
189 189 n normal
190 190 m needs merging
191 191 r marked for removal
192 192 a marked for addition
193 193 ? not tracked
194 194 '''
195 195 return self._map.get(key, ("?",))[0]
196 196
197 197 def __contains__(self, key):
198 198 return key in self._map
199 199
200 200 def __iter__(self):
201 201 for x in sorted(self._map):
202 202 yield x
203 203
204 204 def parents(self):
205 205 return [self._validate(p) for p in self._pl]
206 206
207 207 def branch(self):
208 208 return encoding.tolocal(self._branch)
209 209
210 210 def setparents(self, p1, p2=nullid):
211 211 self._dirty = self._dirtypl = True
212 212 self._pl = p1, p2
213 213
214 214 def setbranch(self, branch):
215 215 if branch in ['tip', '.', 'null']:
216 216 raise util.Abort(_('the name \'%s\' is reserved') % branch)
217 217 self._branch = encoding.fromlocal(branch)
218 218 self._opener("branch", "w").write(self._branch + '\n')
219 219
220 220 def _read(self):
221 221 self._map = {}
222 222 self._copymap = {}
223 223 try:
224 224 st = self._opener("dirstate").read()
225 225 except IOError, err:
226 226 if err.errno != errno.ENOENT:
227 227 raise
228 228 return
229 229 if not st:
230 230 return
231 231
232 232 p = parsers.parse_dirstate(self._map, self._copymap, st)
233 233 if not self._dirtypl:
234 234 self._pl = p
235 235
236 236 def invalidate(self):
237 237 for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs",
238 238 "_ignore"):
239 239 if a in self.__dict__:
240 240 delattr(self, a)
241 241 self._lastnormal = set()
242 242 self._lastnormaltime = None
243 243 self._dirty = False
244 244
245 245 def copy(self, source, dest):
246 246 """Mark dest as a copy of source. Unmark dest if source is None."""
247 247 if source == dest:
248 248 return
249 249 self._dirty = True
250 250 if source is not None:
251 251 self._copymap[dest] = source
252 252 elif dest in self._copymap:
253 253 del self._copymap[dest]
254 254
255 255 def copied(self, file):
256 256 return self._copymap.get(file, None)
257 257
258 258 def copies(self):
259 259 return self._copymap
260 260
261 261 def _droppath(self, f):
262 262 if self[f] not in "?r" and "_dirs" in self.__dict__:
263 263 _decdirs(self._dirs, f)
264 264
265 265 def _addpath(self, f, check=False):
266 266 oldstate = self[f]
267 267 if check or oldstate == "r":
268 268 if '\r' in f or '\n' in f:
269 269 raise util.Abort(
270 270 _("'\\n' and '\\r' disallowed in filenames: %r") % f)
271 271 if f in self._dirs:
272 272 raise util.Abort(_('directory %r already in dirstate') % f)
273 273 # shadows
274 274 for d in _finddirs(f):
275 275 if d in self._dirs:
276 276 break
277 277 if d in self._map and self[d] != 'r':
278 278 raise util.Abort(
279 279 _('file %r in dirstate clashes with %r') % (d, f))
280 280 if oldstate in "?r" and "_dirs" in self.__dict__:
281 281 _incdirs(self._dirs, f)
282 282
283 283 def normal(self, f):
284 284 '''Mark a file normal and clean.'''
285 285 self._dirty = True
286 286 self._addpath(f)
287 287 s = os.lstat(self._join(f))
288 self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
288 mtime = int(s.st_mtime)
289 self._map[f] = ('n', s.st_mode, s.st_size, mtime)
289 290 if f in self._copymap:
290 291 del self._copymap[f]
291 292
292 # Right now, this file is clean: but if some code in this
293 # process modifies it without changing its size before the clock
294 # ticks over to the next second, then it won't be clean anymore.
295 # So make sure that status() will look harder at it.
296 if self._lastnormaltime < s.st_mtime:
297 self._lastnormaltime = s.st_mtime
298 self._lastnormal = set()
299 self._lastnormal.add(f)
293 if mtime < self._lastnormaltime:
294 # We have already seen files with modification times from newer
295 # filesystem timeslots, so this timeslot is old and harmless.
296 # Comparing file times will work just fine for detecting modified
297 # files in status(). No special treatment is needed for f.
298 pass
299 else:
300 # f was modified most recently.
301 if mtime > self._lastnormaltime:
302 # A new timeslot, which we've never seen before.
303 # We can drop the filenames of an older timeslot.
304 self._lastnormaltime = mtime
305 self._lastnormal = set()
306 # Remember f in _lastnormal for closer inspection on status(),
307 # to make sure we won't miss future size-preserving file content
308 # modifications that happen within the same timeslot.
309 self._lastnormal.add(f)
300 310
301 311 def normallookup(self, f):
302 312 '''Mark a file normal, but possibly dirty.'''
303 313 if self._pl[1] != nullid and f in self._map:
304 314 # if there is a merge going on and the file was either
305 315 # in state 'm' (-1) or coming from other parent (-2) before
306 316 # being removed, restore that state.
307 317 entry = self._map[f]
308 318 if entry[0] == 'r' and entry[2] in (-1, -2):
309 319 source = self._copymap.get(f)
310 320 if entry[2] == -1:
311 321 self.merge(f)
312 322 elif entry[2] == -2:
313 323 self.otherparent(f)
314 324 if source:
315 325 self.copy(source, f)
316 326 return
317 327 if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
318 328 return
319 329 self._dirty = True
320 330 self._addpath(f)
321 331 self._map[f] = ('n', 0, -1, -1)
322 332 if f in self._copymap:
323 333 del self._copymap[f]
324 334 self._lastnormal.discard(f)
325 335
326 336 def otherparent(self, f):
327 337 '''Mark as coming from the other parent, always dirty.'''
328 338 if self._pl[1] == nullid:
329 339 raise util.Abort(_("setting %r to other parent "
330 340 "only allowed in merges") % f)
331 341 self._dirty = True
332 342 self._addpath(f)
333 343 self._map[f] = ('n', 0, -2, -1)
334 344 if f in self._copymap:
335 345 del self._copymap[f]
336 346 self._lastnormal.discard(f)
337 347
338 348 def add(self, f):
339 349 '''Mark a file added.'''
340 350 self._dirty = True
341 351 self._addpath(f, True)
342 352 self._map[f] = ('a', 0, -1, -1)
343 353 if f in self._copymap:
344 354 del self._copymap[f]
345 355 self._lastnormal.discard(f)
346 356
347 357 def remove(self, f):
348 358 '''Mark a file removed.'''
349 359 self._dirty = True
350 360 self._droppath(f)
351 361 size = 0
352 362 if self._pl[1] != nullid and f in self._map:
353 363 # backup the previous state
354 364 entry = self._map[f]
355 365 if entry[0] == 'm': # merge
356 366 size = -1
357 367 elif entry[0] == 'n' and entry[2] == -2: # other parent
358 368 size = -2
359 369 self._map[f] = ('r', 0, size, 0)
360 370 if size == 0 and f in self._copymap:
361 371 del self._copymap[f]
362 372 self._lastnormal.discard(f)
363 373
364 374 def merge(self, f):
365 375 '''Mark a file merged.'''
366 376 self._dirty = True
367 377 s = os.lstat(self._join(f))
368 378 self._addpath(f)
369 379 self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
370 380 if f in self._copymap:
371 381 del self._copymap[f]
372 382 self._lastnormal.discard(f)
373 383
374 384 def forget(self, f):
375 385 '''Forget a file.'''
376 386 self._dirty = True
377 387 try:
378 388 self._droppath(f)
379 389 del self._map[f]
380 390 except KeyError:
381 391 self._ui.warn(_("not in dirstate: %s\n") % f)
382 392 self._lastnormal.discard(f)
383 393
384 394 def _normalize(self, path, isknown):
385 395 normed = os.path.normcase(path)
386 396 folded = self._foldmap.get(normed, None)
387 397 if folded is None:
388 398 if isknown or not os.path.lexists(os.path.join(self._root, path)):
389 399 folded = path
390 400 else:
391 401 folded = self._foldmap.setdefault(normed,
392 402 util.fspath(path, self._root))
393 403 return folded
394 404
395 405 def normalize(self, path, isknown=False):
396 406 '''
397 407 normalize the case of a pathname when on a casefolding filesystem
398 408
399 409 isknown specifies whether the filename came from walking the
400 410 disk, to avoid extra filesystem access
401 411
402 412 The normalized case is determined based on the following precedence:
403 413
404 414 - version of name already stored in the dirstate
405 415 - version of name stored on disk
406 416 - version provided via command arguments
407 417 '''
408 418
409 419 if self._checkcase:
410 420 return self._normalize(path, isknown)
411 421 return path
412 422
413 423 def clear(self):
414 424 self._map = {}
415 425 if "_dirs" in self.__dict__:
416 426 delattr(self, "_dirs")
417 427 self._copymap = {}
418 428 self._pl = [nullid, nullid]
419 429 self._lastnormal = set()
420 430 self._lastnormaltime = None
421 431 self._dirty = True
422 432
423 433 def rebuild(self, parent, files):
424 434 self.clear()
425 435 for f in files:
426 436 if 'x' in files.flags(f):
427 437 self._map[f] = ('n', 0777, -1, 0)
428 438 else:
429 439 self._map[f] = ('n', 0666, -1, 0)
430 440 self._pl = (parent, nullid)
431 441 self._dirty = True
432 442
433 443 def write(self):
434 444 if not self._dirty:
435 445 return
436 446 st = self._opener("dirstate", "w", atomictemp=True)
437 447
438 448 # use the modification time of the newly created temporary file as the
439 449 # filesystem's notion of 'now'
440 450 now = int(util.fstat(st).st_mtime)
441 451
442 452 cs = cStringIO.StringIO()
443 453 copymap = self._copymap
444 454 pack = struct.pack
445 455 write = cs.write
446 456 write("".join(self._pl))
447 457 for f, e in self._map.iteritems():
448 458 if e[0] == 'n' and e[3] == now:
449 459 # The file was last modified "simultaneously" with the current
450 460 # write to dirstate (i.e. within the same second for file-
451 461 # systems with a granularity of 1 sec). This commonly happens
452 462 # for at least a couple of files on 'update'.
453 463 # The user could change the file without changing its size
454 464 # within the same second. Invalidate the file's stat data in
455 465 # dirstate, forcing future 'status' calls to compare the
456 466 # contents of the file. This prevents mistakenly treating such
457 467 # files as clean.
458 468 e = (e[0], 0, -1, -1) # mark entry as 'unset'
459 469 self._map[f] = e
460 470
461 471 if f in copymap:
462 472 f = "%s\0%s" % (f, copymap[f])
463 473 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
464 474 write(e)
465 475 write(f)
466 476 st.write(cs.getvalue())
467 477 st.rename()
468 478 self._lastnormal = set()
469 479 self._lastnormaltime = None
470 480 self._dirty = self._dirtypl = False
471 481
472 482 def _dirignore(self, f):
473 483 if f == '.':
474 484 return False
475 485 if self._ignore(f):
476 486 return True
477 487 for p in _finddirs(f):
478 488 if self._ignore(p):
479 489 return True
480 490 return False
481 491
482 492 def walk(self, match, subrepos, unknown, ignored):
483 493 '''
484 494 Walk recursively through the directory tree, finding all files
485 495 matched by match.
486 496
487 497 Return a dict mapping filename to stat-like object (either
488 498 mercurial.osutil.stat instance or return value of os.stat()).
489 499 '''
490 500
491 501 def fwarn(f, msg):
492 502 self._ui.warn('%s: %s\n' % (self.pathto(f), msg))
493 503 return False
494 504
495 505 def badtype(mode):
496 506 kind = _('unknown')
497 507 if stat.S_ISCHR(mode):
498 508 kind = _('character device')
499 509 elif stat.S_ISBLK(mode):
500 510 kind = _('block device')
501 511 elif stat.S_ISFIFO(mode):
502 512 kind = _('fifo')
503 513 elif stat.S_ISSOCK(mode):
504 514 kind = _('socket')
505 515 elif stat.S_ISDIR(mode):
506 516 kind = _('directory')
507 517 return _('unsupported file type (type is %s)') % kind
508 518
509 519 ignore = self._ignore
510 520 dirignore = self._dirignore
511 521 if ignored:
512 522 ignore = util.never
513 523 dirignore = util.never
514 524 elif not unknown:
515 525 # if unknown and ignored are False, skip step 2
516 526 ignore = util.always
517 527 dirignore = util.always
518 528
519 529 matchfn = match.matchfn
520 530 badfn = match.bad
521 531 dmap = self._map
522 532 normpath = util.normpath
523 533 listdir = osutil.listdir
524 534 lstat = os.lstat
525 535 getkind = stat.S_IFMT
526 536 dirkind = stat.S_IFDIR
527 537 regkind = stat.S_IFREG
528 538 lnkkind = stat.S_IFLNK
529 539 join = self._join
530 540 work = []
531 541 wadd = work.append
532 542
533 543 exact = skipstep3 = False
534 544 if matchfn == match.exact: # match.exact
535 545 exact = True
536 546 dirignore = util.always # skip step 2
537 547 elif match.files() and not match.anypats(): # match.match, no patterns
538 548 skipstep3 = True
539 549
540 550 if self._checkcase:
541 551 normalize = self._normalize
542 552 skipstep3 = False
543 553 else:
544 554 normalize = lambda x, y: x
545 555
546 556 files = sorted(match.files())
547 557 subrepos.sort()
548 558 i, j = 0, 0
549 559 while i < len(files) and j < len(subrepos):
550 560 subpath = subrepos[j] + "/"
551 561 if files[i] < subpath:
552 562 i += 1
553 563 continue
554 564 while i < len(files) and files[i].startswith(subpath):
555 565 del files[i]
556 566 j += 1
557 567
558 568 if not files or '.' in files:
559 569 files = ['']
560 570 results = dict.fromkeys(subrepos)
561 571 results['.hg'] = None
562 572
563 573 # step 1: find all explicit files
564 574 for ff in files:
565 575 nf = normalize(normpath(ff), False)
566 576 if nf in results:
567 577 continue
568 578
569 579 try:
570 580 st = lstat(join(nf))
571 581 kind = getkind(st.st_mode)
572 582 if kind == dirkind:
573 583 skipstep3 = False
574 584 if nf in dmap:
575 585 #file deleted on disk but still in dirstate
576 586 results[nf] = None
577 587 match.dir(nf)
578 588 if not dirignore(nf):
579 589 wadd(nf)
580 590 elif kind == regkind or kind == lnkkind:
581 591 results[nf] = st
582 592 else:
583 593 badfn(ff, badtype(kind))
584 594 if nf in dmap:
585 595 results[nf] = None
586 596 except OSError, inst:
587 597 if nf in dmap: # does it exactly match a file?
588 598 results[nf] = None
589 599 else: # does it match a directory?
590 600 prefix = nf + "/"
591 601 for fn in dmap:
592 602 if fn.startswith(prefix):
593 603 match.dir(nf)
594 604 skipstep3 = False
595 605 break
596 606 else:
597 607 badfn(ff, inst.strerror)
598 608
599 609 # step 2: visit subdirectories
600 610 while work:
601 611 nd = work.pop()
602 612 skip = None
603 613 if nd == '.':
604 614 nd = ''
605 615 else:
606 616 skip = '.hg'
607 617 try:
608 618 entries = listdir(join(nd), stat=True, skip=skip)
609 619 except OSError, inst:
610 620 if inst.errno == errno.EACCES:
611 621 fwarn(nd, inst.strerror)
612 622 continue
613 623 raise
614 624 for f, kind, st in entries:
615 625 nf = normalize(nd and (nd + "/" + f) or f, True)
616 626 if nf not in results:
617 627 if kind == dirkind:
618 628 if not ignore(nf):
619 629 match.dir(nf)
620 630 wadd(nf)
621 631 if nf in dmap and matchfn(nf):
622 632 results[nf] = None
623 633 elif kind == regkind or kind == lnkkind:
624 634 if nf in dmap:
625 635 if matchfn(nf):
626 636 results[nf] = st
627 637 elif matchfn(nf) and not ignore(nf):
628 638 results[nf] = st
629 639 elif nf in dmap and matchfn(nf):
630 640 results[nf] = None
631 641
632 642 # step 3: report unseen items in the dmap hash
633 643 if not skipstep3 and not exact:
634 644 visit = sorted([f for f in dmap if f not in results and matchfn(f)])
635 645 for nf, st in zip(visit, util.statfiles([join(i) for i in visit])):
636 646 if not st is None and not getkind(st.st_mode) in (regkind, lnkkind):
637 647 st = None
638 648 results[nf] = st
639 649 for s in subrepos:
640 650 del results[s]
641 651 del results['.hg']
642 652 return results
643 653
644 654 def status(self, match, subrepos, ignored, clean, unknown):
645 655 '''Determine the status of the working copy relative to the
646 656 dirstate and return a tuple of lists (unsure, modified, added,
647 657 removed, deleted, unknown, ignored, clean), where:
648 658
649 659 unsure:
650 660 files that might have been modified since the dirstate was
651 661 written, but need to be read to be sure (size is the same
652 662 but mtime differs)
653 663 modified:
654 664 files that have definitely been modified since the dirstate
655 665 was written (different size or mode)
656 666 added:
657 667 files that have been explicitly added with hg add
658 668 removed:
659 669 files that have been explicitly removed with hg remove
660 670 deleted:
661 671 files that have been deleted through other means ("missing")
662 672 unknown:
663 673 files not in the dirstate that are not ignored
664 674 ignored:
665 675 files not in the dirstate that are ignored
666 676 (by _dirignore())
667 677 clean:
668 678 files that have definitely not been modified since the
669 679 dirstate was written
670 680 '''
671 681 listignored, listclean, listunknown = ignored, clean, unknown
672 682 lookup, modified, added, unknown, ignored = [], [], [], [], []
673 683 removed, deleted, clean = [], [], []
674 684
675 685 dmap = self._map
676 686 ladd = lookup.append # aka "unsure"
677 687 madd = modified.append
678 688 aadd = added.append
679 689 uadd = unknown.append
680 690 iadd = ignored.append
681 691 radd = removed.append
682 692 dadd = deleted.append
683 693 cadd = clean.append
684 694 lastnormal = self._lastnormal.__contains__
685 695
686 696 lnkkind = stat.S_IFLNK
687 697
688 698 for fn, st in self.walk(match, subrepos, listunknown,
689 699 listignored).iteritems():
690 700 if fn not in dmap:
691 701 if (listignored or match.exact(fn)) and self._dirignore(fn):
692 702 if listignored:
693 703 iadd(fn)
694 704 elif listunknown:
695 705 uadd(fn)
696 706 continue
697 707
698 708 state, mode, size, time = dmap[fn]
699 709
700 710 if not st and state in "nma":
701 711 dadd(fn)
702 712 elif state == 'n':
703 713 # The "mode & lnkkind != lnkkind or self._checklink"
704 714 # lines are an expansion of "islink => checklink"
705 715 # where islink means "is this a link?" and checklink
706 716 # means "can we check links?".
707 717 if (size >= 0 and
708 718 (size != st.st_size
709 719 or ((mode ^ st.st_mode) & 0100 and self._checkexec))
710 720 and (mode & lnkkind != lnkkind or self._checklink)
711 721 or size == -2 # other parent
712 722 or fn in self._copymap):
713 723 madd(fn)
714 724 elif (time != int(st.st_mtime)
715 725 and (mode & lnkkind != lnkkind or self._checklink)):
716 726 ladd(fn)
717 727 elif lastnormal(fn):
718 728 # If previously in this process we recorded that
719 729 # this file is clean, think twice: intervening code
720 730 # may have modified the file in the same second
721 731 # without changing its size. So force caller to
722 732 # check file contents. Because we're not updating
723 733 # self._map, this only affects the current process.
724 734 # That should be OK because this mainly affects
725 735 # multiple commits in the same process, and each
726 736 # commit by definition makes the committed files
727 737 # clean.
728 738 ladd(fn)
729 739 elif listclean:
730 740 cadd(fn)
731 741 elif state == 'm':
732 742 madd(fn)
733 743 elif state == 'a':
734 744 aadd(fn)
735 745 elif state == 'r':
736 746 radd(fn)
737 747
738 748 return (lookup, modified, added, removed, deleted, unknown, ignored,
739 749 clean)
General Comments 0
You need to be logged in to leave comments. Login now