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