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