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