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