dirstate.py
754 lines
| 24.4 KiB
| text/x-python
|
PythonLexer
/ mercurial / dirstate.py
|
r8226 | # dirstate.py - working directory tracking for mercurial | ||
# | ||||
# Copyright 2005-2007 Matt Mackall <mpm@selenic.com> | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
|
r10263 | # GNU General Public License version 2 or any later version. | ||
|
r15799 | import errno | ||
|
r1089 | |||
|
r6211 | from node import nullid | ||
|
r3891 | from i18n import _ | ||
|
r13974 | import scmutil, util, ignore, osutil, parsers, encoding | ||
|
r8312 | import struct, os, stat, errno | ||
|
r9678 | import cStringIO | ||
|
r1089 | |||
|
r4610 | _format = ">cllll" | ||
|
r8261 | propertycache = util.propertycache | ||
|
r16201 | filecache = scmutil.filecache | ||
class repocache(filecache): | ||||
"""filecache for files in .hg/""" | ||||
def join(self, obj, fname): | ||||
return obj._opener.join(fname) | ||||
|
r4610 | |||
|
r16202 | class rootcache(filecache): | ||
"""filecache for files in the repository root""" | ||||
def join(self, obj, fname): | ||||
return obj._join(fname) | ||||
|
r6767 | def _finddirs(path): | ||
|
r7032 | pos = path.rfind('/') | ||
while pos != -1: | ||||
yield path[:pos] | ||||
|
r6767 | pos = path.rfind('/', 0, pos) | ||
|
r7096 | def _incdirs(dirs, path): | ||
for base in _finddirs(path): | ||||
if base in dirs: | ||||
dirs[base] += 1 | ||||
return | ||||
dirs[base] = 1 | ||||
def _decdirs(dirs, path): | ||||
for base in _finddirs(path): | ||||
if dirs[base] > 1: | ||||
dirs[base] -= 1 | ||||
return | ||||
del dirs[base] | ||||
|
r1559 | class dirstate(object): | ||
|
r2393 | |||
|
r13032 | def __init__(self, opener, ui, root, validate): | ||
|
r10145 | '''Create a new dirstate object. | ||
opener is an open()-like callable that can be used to open the | ||||
dirstate file; root is the root of the directory tracked by | ||||
the dirstate. | ||||
''' | ||||
|
r4614 | self._opener = opener | ||
|
r13032 | self._validate = validate | ||
|
r4614 | self._root = root | ||
|
r6972 | self._rootdir = os.path.join(root, '') | ||
|
r4903 | self._dirty = False | ||
|
r4965 | self._dirtypl = False | ||
|
r15791 | self._lastnormaltime = 0 | ||
|
r4614 | self._ui = ui | ||
|
r16200 | self._filecache = {} | ||
|
r1089 | |||
|
r8261 | @propertycache | ||
def _map(self): | ||||
|
r9518 | '''Return the dirstate contents as a map from filename to | ||
(state, mode, size, time).''' | ||||
|
r8261 | self._read() | ||
return self._map | ||||
@propertycache | ||||
def _copymap(self): | ||||
self._read() | ||||
return self._copymap | ||||
@propertycache | ||||
def _foldmap(self): | ||||
f = {} | ||||
for name in self._map: | ||||
|
r15550 | f[util.normcase(name)] = name | ||
|
r16302 | for name in self._dirs: | ||
f[util.normcase(name)] = name | ||||
|
r15668 | f['.'] = '.' # prevents useless util.fspath() invocation | ||
|
r8261 | return f | ||
|
r16201 | @repocache('branch') | ||
|
r8261 | def _branch(self): | ||
try: | ||||
|
r14168 | return self._opener.read("branch").strip() or "default" | ||
|
r15799 | except IOError, inst: | ||
if inst.errno != errno.ENOENT: | ||||
raise | ||||
|
r8261 | return "default" | ||
@propertycache | ||||
def _pl(self): | ||||
try: | ||||
|
r13400 | fp = self._opener("dirstate") | ||
st = fp.read(40) | ||||
fp.close() | ||||
|
r8716 | l = len(st) | ||
if l == 40: | ||||
|
r8261 | return st[:20], st[20:40] | ||
|
r8716 | elif l > 0 and l < 40: | ||
|
r8640 | raise util.Abort(_('working directory state appears damaged!')) | ||
|
r8261 | except IOError, err: | ||
|
r10282 | if err.errno != errno.ENOENT: | ||
raise | ||||
|
r8261 | return [nullid, nullid] | ||
@propertycache | ||||
def _dirs(self): | ||||
dirs = {} | ||||
|
r10282 | for f, s in self._map.iteritems(): | ||
|
r8261 | if s[0] != 'r': | ||
_incdirs(dirs, f) | ||||
return dirs | ||||
|
r16143 | def dirs(self): | ||
return self._dirs | ||||
|
r16202 | @rootcache('.hgignore') | ||
|
r8261 | def _ignore(self): | ||
files = [self._join('.hgignore')] | ||||
for name, path in self._ui.configitems("ui"): | ||||
if name == 'ignore' or name.startswith('ignore.'): | ||||
|
r9610 | files.append(util.expandpath(path)) | ||
|
r8261 | return ignore.ignore(self._root, files, self._ui.warn) | ||
@propertycache | ||||
def _slash(self): | ||||
return self._ui.configbool('ui', 'slash') and os.sep != '/' | ||||
@propertycache | ||||
def _checklink(self): | ||||
return util.checklink(self._root) | ||||
@propertycache | ||||
def _checkexec(self): | ||||
return util.checkexec(self._root) | ||||
@propertycache | ||||
def _checkcase(self): | ||||
return not util.checkcase(self._join('.hg')) | ||||
|
r4905 | def _join(self, f): | ||
|
r6972 | # much faster than os.path.join() | ||
|
r6973 | # it's safe because f is always a relative path | ||
|
r6972 | return self._rootdir + f | ||
|
r1089 | |||
|
r15337 | def flagfunc(self, buildfallback): | ||
if self._checklink and self._checkexec: | ||||
def f(x): | ||||
p = self._join(x) | ||||
if os.path.islink(p): | ||||
return 'l' | ||||
if util.isexec(p): | ||||
return 'x' | ||||
return '' | ||||
return f | ||||
fallback = buildfallback() | ||||
|
r6743 | if self._checklink: | ||
def f(x): | ||||
|
r6972 | if os.path.islink(self._join(x)): | ||
|
r6743 | return 'l' | ||
if 'x' in fallback(x): | ||||
return 'x' | ||||
return '' | ||||
return f | ||||
if self._checkexec: | ||||
def f(x): | ||||
if 'l' in fallback(x): | ||||
return 'l' | ||||
|
r14273 | if util.isexec(self._join(x)): | ||
|
r6743 | return 'x' | ||
return '' | ||||
return f | ||||
|
r15337 | else: | ||
return fallback | ||||
|
r6743 | |||
|
r1089 | def getcwd(self): | ||
cwd = os.getcwd() | ||||
|
r10282 | if cwd == self._root: | ||
return '' | ||||
|
r4614 | # self._root ends with a path separator if self._root is '/' or 'C:\' | ||
rootsep = self._root | ||||
|
r5843 | if not util.endswithsep(rootsep): | ||
|
r4230 | rootsep += os.sep | ||
if cwd.startswith(rootsep): | ||||
return cwd[len(rootsep):] | ||||
else: | ||||
# we're outside the repo. return an absolute path. | ||||
return cwd | ||||
|
r1089 | |||
|
r4525 | def pathto(self, f, cwd=None): | ||
if cwd is None: | ||||
cwd = self.getcwd() | ||||
|
r4614 | path = util.pathto(self._root, cwd, f) | ||
|
r4527 | if self._slash: | ||
|
r5842 | return util.normpath(path) | ||
|
r4527 | return path | ||
|
r4525 | |||
|
r1089 | def __getitem__(self, key): | ||
|
r9518 | '''Return the current state of key (a filename) in the dirstate. | ||
|
r10145 | |||
|
r9518 | States are: | ||
n normal | ||||
m needs merging | ||||
r marked for removal | ||||
a marked for addition | ||||
? not tracked | ||||
''' | ||||
|
r4906 | return self._map.get(key, ("?",))[0] | ||
|
r1089 | |||
def __contains__(self, key): | ||||
|
r4614 | return key in self._map | ||
def __iter__(self): | ||||
|
r8209 | for x in sorted(self._map): | ||
|
r4614 | yield x | ||
|
r1089 | |||
def parents(self): | ||||
|
r13032 | return [self._validate(p) for p in self._pl] | ||
|
r1089 | |||
|
r13876 | def p1(self): | ||
return self._validate(self._pl[0]) | ||||
def p2(self): | ||||
return self._validate(self._pl[1]) | ||||
|
r4179 | def branch(self): | ||
|
r13047 | return encoding.tolocal(self._branch) | ||
|
r4179 | |||
|
r1089 | def setparents(self, p1, p2=nullid): | ||
|
r4965 | self._dirty = self._dirtypl = True | ||
|
r4614 | self._pl = p1, p2 | ||
|
r1089 | |||
|
r4179 | def setbranch(self, branch): | ||
|
r10417 | if branch in ['tip', '.', 'null']: | ||
raise util.Abort(_('the name \'%s\' is reserved') % branch) | ||||
|
r13047 | self._branch = encoding.fromlocal(branch) | ||
|
r14168 | self._opener.write("branch", self._branch + '\n') | ||
|
r4179 | |||
|
r4615 | def _read(self): | ||
|
r4614 | self._map = {} | ||
self._copymap = {} | ||||
|
r4607 | try: | ||
|
r14168 | st = self._opener.read("dirstate") | ||
|
r4607 | except IOError, err: | ||
|
r10282 | if err.errno != errno.ENOENT: | ||
raise | ||||
|
r4607 | return | ||
if not st: | ||||
return | ||||
|
r7128 | p = parsers.parse_dirstate(self._map, self._copymap, st) | ||
|
r4952 | if not self._dirtypl: | ||
|
r7093 | self._pl = p | ||
|
r1089 | |||
|
r4613 | def invalidate(self): | ||
|
r13200 | for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs", | ||
"_ignore"): | ||||
|
r4953 | if a in self.__dict__: | ||
delattr(self, a) | ||||
|
r15791 | self._lastnormaltime = 0 | ||
|
r4903 | self._dirty = False | ||
|
r4375 | |||
|
r1089 | def copy(self, source, dest): | ||
|
r10145 | """Mark dest as a copy of source. Unmark dest if source is None.""" | ||
|
r6680 | if source == dest: | ||
return | ||||
|
r4903 | self._dirty = True | ||
|
r7566 | if source is not None: | ||
self._copymap[dest] = source | ||||
elif dest in self._copymap: | ||||
del self._copymap[dest] | ||||
|
r1089 | |||
def copied(self, file): | ||||
|
r4614 | return self._copymap.get(file, None) | ||
|
r3154 | |||
def copies(self): | ||||
|
r4614 | return self._copymap | ||
|
r1089 | |||
|
r6767 | def _droppath(self, f): | ||
if self[f] not in "?r" and "_dirs" in self.__dict__: | ||||
|
r7096 | _decdirs(self._dirs, f) | ||
|
r2953 | |||
|
r6767 | def _addpath(self, f, check=False): | ||
|
r5487 | oldstate = self[f] | ||
|
r6767 | if check or oldstate == "r": | ||
|
r13974 | scmutil.checkfilename(f) | ||
|
r6767 | if f in self._dirs: | ||
raise util.Abort(_('directory %r already in dirstate') % f) | ||||
# shadows | ||||
for d in _finddirs(f): | ||||
if d in self._dirs: | ||||
break | ||||
if d in self._map and self[d] != 'r': | ||||
raise util.Abort( | ||||
_('file %r in dirstate clashes with %r') % (d, f)) | ||||
if oldstate in "?r" and "_dirs" in self.__dict__: | ||||
|
r7096 | _incdirs(self._dirs, f) | ||
|
r5487 | |||
|
r4904 | def normal(self, f): | ||
|
r10145 | '''Mark a file normal and clean.''' | ||
|
r4904 | self._dirty = True | ||
|
r6767 | self._addpath(f) | ||
|
r4905 | s = os.lstat(self._join(f)) | ||
|
r13754 | mtime = int(s.st_mtime) | ||
self._map[f] = ('n', s.st_mode, s.st_size, mtime) | ||||
|
r5915 | if f in self._copymap: | ||
|
r4904 | del self._copymap[f] | ||
|
r13763 | if mtime > self._lastnormaltime: | ||
# Remember the most recent modification timeslot for status(), | ||||
|
r13754 | # to make sure we won't miss future size-preserving file content | ||
# modifications that happen within the same timeslot. | ||||
|
r13763 | self._lastnormaltime = mtime | ||
|
r13704 | |||
|
r5210 | def normallookup(self, f): | ||
|
r10145 | '''Mark a file normal, but possibly dirty.''' | ||
|
r6298 | if self._pl[1] != nullid and f in self._map: | ||
# if there is a merge going on and the file was either | ||||
|
r10968 | # in state 'm' (-1) or coming from other parent (-2) before | ||
# being removed, restore that state. | ||||
|
r6298 | entry = self._map[f] | ||
if entry[0] == 'r' and entry[2] in (-1, -2): | ||||
source = self._copymap.get(f) | ||||
if entry[2] == -1: | ||||
self.merge(f) | ||||
elif entry[2] == -2: | ||||
|
r10968 | self.otherparent(f) | ||
|
r6298 | if source: | ||
self.copy(source, f) | ||||
return | ||||
if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2: | ||||
return | ||||
|
r4903 | self._dirty = True | ||
|
r6767 | self._addpath(f) | ||
|
r7093 | self._map[f] = ('n', 0, -1, -1) | ||
|
r5210 | if f in self._copymap: | ||
del self._copymap[f] | ||||
|
r10968 | def otherparent(self, f): | ||
'''Mark as coming from the other parent, always dirty.''' | ||||
if self._pl[1] == nullid: | ||||
raise util.Abort(_("setting %r to other parent " | ||||
"only allowed in merges") % f) | ||||
|
r5210 | self._dirty = True | ||
|
r6767 | self._addpath(f) | ||
|
r7093 | self._map[f] = ('n', 0, -2, -1) | ||
|
r4904 | if f in self._copymap: | ||
del self._copymap[f] | ||||
def add(self, f): | ||||
|
r10145 | '''Mark a file added.''' | ||
|
r4904 | self._dirty = True | ||
|
r6767 | self._addpath(f, True) | ||
|
r7093 | self._map[f] = ('a', 0, -1, -1) | ||
|
r4904 | if f in self._copymap: | ||
del self._copymap[f] | ||||
|
r4616 | |||
|
r4904 | def remove(self, f): | ||
|
r10145 | '''Mark a file removed.''' | ||
|
r4904 | self._dirty = True | ||
|
r6767 | self._droppath(f) | ||
|
r6297 | size = 0 | ||
if self._pl[1] != nullid and f in self._map: | ||||
|
r10968 | # backup the previous state | ||
|
r6297 | entry = self._map[f] | ||
|
r10968 | if entry[0] == 'm': # merge | ||
|
r6297 | size = -1 | ||
|
r10968 | elif entry[0] == 'n' and entry[2] == -2: # other parent | ||
|
r6297 | size = -2 | ||
|
r7093 | self._map[f] = ('r', 0, size, 0) | ||
|
r6297 | if size == 0 and f in self._copymap: | ||
|
r4904 | del self._copymap[f] | ||
|
r1089 | |||
|
r4904 | def merge(self, f): | ||
|
r10145 | '''Mark a file merged.''' | ||
|
r4903 | self._dirty = True | ||
|
r4905 | s = os.lstat(self._join(f)) | ||
|
r6767 | self._addpath(f) | ||
|
r7119 | self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime)) | ||
|
r4904 | if f in self._copymap: | ||
del self._copymap[f] | ||||
|
r14434 | def drop(self, f): | ||
'''Drop a file from the dirstate''' | ||||
|
r15399 | if f in self._map: | ||
self._dirty = True | ||||
self._droppath(f) | ||||
del self._map[f] | ||||
|
r1089 | |||
|
r13717 | def _normalize(self, path, isknown): | ||
|
r15488 | normed = util.normcase(path) | ||
|
r13717 | folded = self._foldmap.get(normed, None) | ||
if folded is None: | ||||
if isknown or not os.path.lexists(os.path.join(self._root, path)): | ||||
folded = path | ||||
|
r7068 | else: | ||
|
r16302 | # recursively normalize leading directory components | ||
# against dirstate | ||||
if '/' in normed: | ||||
d, f = normed.rsplit('/') | ||||
d = self._root + "/" + self._normalize(d, isknown) | ||||
folded = d + "/" + util.fspath(f, d) | ||||
else: | ||||
folded = util.fspath(normed, self._root) | ||||
self._foldmap[normed] = folded | ||||
|
r13717 | return folded | ||
def normalize(self, path, isknown=False): | ||||
''' | ||||
normalize the case of a pathname when on a casefolding filesystem | ||||
isknown specifies whether the filename came from walking the | ||||
disk, to avoid extra filesystem access | ||||
The normalized case is determined based on the following precedence: | ||||
- version of name already stored in the dirstate | ||||
- version of name stored on disk | ||||
- version provided via command arguments | ||||
''' | ||||
if self._checkcase: | ||||
return self._normalize(path, isknown) | ||||
return path | ||||
|
r6677 | |||
|
r5065 | def clear(self): | ||
self._map = {} | ||||
|
r5487 | if "_dirs" in self.__dict__: | ||
|
r10394 | delattr(self, "_dirs") | ||
|
r5065 | self._copymap = {} | ||
self._pl = [nullid, nullid] | ||||
|
r15791 | self._lastnormaltime = 0 | ||
|
r5123 | self._dirty = True | ||
|
r5065 | |||
|
r1755 | def rebuild(self, parent, files): | ||
|
r5065 | self.clear() | ||
|
r2832 | for f in files: | ||
|
r6750 | if 'x' in files.flags(f): | ||
|
r7093 | self._map[f] = ('n', 0777, -1, 0) | ||
|
r1755 | else: | ||
|
r7093 | self._map[f] = ('n', 0666, -1, 0) | ||
|
r4614 | self._pl = (parent, nullid) | ||
|
r4903 | self._dirty = True | ||
|
r1089 | |||
def write(self): | ||||
|
r4612 | if not self._dirty: | ||
|
r1794 | return | ||
|
r6326 | st = self._opener("dirstate", "w", atomictemp=True) | ||
|
r6327 | |||
|
r9509 | # use the modification time of the newly created temporary file as the | ||
# filesystem's notion of 'now' | ||||
now = int(util.fstat(st).st_mtime) | ||||
|
r10865 | |||
cs = cStringIO.StringIO() | ||||
copymap = self._copymap | ||||
pack = struct.pack | ||||
write = cs.write | ||||
write("".join(self._pl)) | ||||
for f, e in self._map.iteritems(): | ||||
|
r9509 | if e[0] == 'n' and e[3] == now: | ||
# The file was last modified "simultaneously" with the current | ||||
# write to dirstate (i.e. within the same second for file- | ||||
# systems with a granularity of 1 sec). This commonly happens | ||||
# for at least a couple of files on 'update'. | ||||
# The user could change the file without changing its size | ||||
# within the same second. Invalidate the file's stat data in | ||||
# dirstate, forcing future 'status' calls to compare the | ||||
# contents of the file. This prevents mistakenly treating such | ||||
# files as clean. | ||||
|
r10865 | e = (e[0], 0, -1, -1) # mark entry as 'unset' | ||
self._map[f] = e | ||||
|
r9509 | |||
|
r10836 | if f in copymap: | ||
f = "%s\0%s" % (f, copymap[f]) | ||||
|
r5327 | e = pack(_format, e[0], e[1], e[2], e[3], len(f)) | ||
write(e) | ||||
write(f) | ||||
|
r4374 | st.write(cs.getvalue()) | ||
|
r15057 | st.close() | ||
|
r15791 | self._lastnormaltime = 0 | ||
|
r4965 | self._dirty = self._dirtypl = False | ||
|
r1089 | |||
|
r6032 | def _dirignore(self, f): | ||
|
r6479 | if f == '.': | ||
return False | ||||
|
r6032 | if self._ignore(f): | ||
return True | ||||
|
r6767 | for p in _finddirs(f): | ||
if self._ignore(p): | ||||
|
r6032 | return True | ||
return False | ||||
|
r10176 | def walk(self, match, subrepos, unknown, ignored): | ||
|
r3529 | ''' | ||
|
r9518 | Walk recursively through the directory tree, finding all files | ||
matched by match. | ||||
|
r3529 | |||
|
r9518 | Return a dict mapping filename to stat-like object (either | ||
mercurial.osutil.stat instance or return value of os.stat()). | ||||
|
r3529 | ''' | ||
|
r1089 | |||
|
r6578 | def fwarn(f, msg): | ||
|
r7023 | self._ui.warn('%s: %s\n' % (self.pathto(f), msg)) | ||
|
r6578 | return False | ||
|
r8681 | def badtype(mode): | ||
|
r8310 | kind = _('unknown') | ||
|
r10282 | if stat.S_ISCHR(mode): | ||
kind = _('character device') | ||||
elif stat.S_ISBLK(mode): | ||||
kind = _('block device') | ||||
elif stat.S_ISFIFO(mode): | ||||
kind = _('fifo') | ||||
elif stat.S_ISSOCK(mode): | ||||
kind = _('socket') | ||||
elif stat.S_ISDIR(mode): | ||||
kind = _('directory') | ||||
|
r8681 | return _('unsupported file type (type is %s)') % kind | ||
|
r6830 | |||
|
r4609 | ignore = self._ignore | ||
|
r6032 | dirignore = self._dirignore | ||
|
r4193 | if ignored: | ||
ignore = util.never | ||||
|
r6032 | dirignore = util.never | ||
|
r6971 | elif not unknown: | ||
# if unknown and ignored are False, skip step 2 | ||||
ignore = util.always | ||||
dirignore = util.always | ||||
|
r3534 | |||
|
r6834 | matchfn = match.matchfn | ||
|
r8676 | badfn = match.bad | ||
|
r6831 | dmap = self._map | ||
|
r5000 | normpath = util.normpath | ||
|
r5396 | listdir = osutil.listdir | ||
|
r5000 | lstat = os.lstat | ||
|
r6830 | getkind = stat.S_IFMT | ||
|
r6828 | dirkind = stat.S_IFDIR | ||
|
r6830 | regkind = stat.S_IFREG | ||
lnkkind = stat.S_IFLNK | ||||
|
r6831 | join = self._join | ||
|
r6820 | work = [] | ||
wadd = work.append | ||||
|
r8684 | exact = skipstep3 = False | ||
if matchfn == match.exact: # match.exact | ||||
|
r8683 | exact = True | ||
|
r8684 | dirignore = util.always # skip step 2 | ||
elif match.files() and not match.anypats(): # match.match, no patterns | ||||
skipstep3 = True | ||||
|
r8589 | |||
|
r16313 | if not exact and self._checkcase: | ||
|
r12907 | normalize = self._normalize | ||
skipstep3 = False | ||||
else: | ||||
normalize = lambda x, y: x | ||||
|
r12211 | files = sorted(match.files()) | ||
subrepos.sort() | ||||
i, j = 0, 0 | ||||
while i < len(files) and j < len(subrepos): | ||||
subpath = subrepos[j] + "/" | ||||
|
r13233 | if files[i] < subpath: | ||
|
r12211 | i += 1 | ||
continue | ||||
|
r13339 | while i < len(files) and files[i].startswith(subpath): | ||
|
r12211 | del files[i] | ||
j += 1 | ||||
|
r6831 | if not files or '.' in files: | ||
files = [''] | ||||
|
r10176 | results = dict.fromkeys(subrepos) | ||
results['.hg'] = None | ||||
|
r5000 | |||
|
r6826 | # step 1: find all explicit files | ||
|
r12211 | for ff in files: | ||
|
r8804 | nf = normalize(normpath(ff), False) | ||
|
r6829 | if nf in results: | ||
|
r6820 | continue | ||
try: | ||||
|
r6831 | st = lstat(join(nf)) | ||
|
r6830 | kind = getkind(st.st_mode) | ||
if kind == dirkind: | ||||
|
r8684 | skipstep3 = False | ||
|
r8588 | if nf in dmap: | ||
|
r8645 | #file deleted on disk but still in dirstate | ||
|
r8588 | results[nf] = None | ||
|
r8708 | match.dir(nf) | ||
|
r6828 | if not dirignore(nf): | ||
wadd(nf) | ||||
|
r12401 | elif kind == regkind or kind == lnkkind: | ||
|
r6830 | results[nf] = st | ||
|
r6828 | else: | ||
|
r8681 | badfn(ff, badtype(kind)) | ||
|
r6830 | if nf in dmap: | ||
|
r6829 | results[nf] = None | ||
|
r6820 | except OSError, inst: | ||
|
r8675 | if nf in dmap: # does it exactly match a file? | ||
results[nf] = None | ||||
else: # does it match a directory? | ||||
prefix = nf + "/" | ||||
for fn in dmap: | ||||
if fn.startswith(prefix): | ||||
|
r8708 | match.dir(nf) | ||
|
r8684 | skipstep3 = False | ||
|
r8675 | break | ||
|
r8677 | else: | ||
|
r8680 | badfn(ff, inst.strerror) | ||
|
r6820 | |||
|
r6826 | # step 2: visit subdirectories | ||
while work: | ||||
nd = work.pop() | ||||
|
r7099 | skip = None | ||
|
r6826 | if nd == '.': | ||
nd = '' | ||||
else: | ||||
|
r7099 | skip = '.hg' | ||
try: | ||||
entries = listdir(join(nd), stat=True, skip=skip) | ||||
except OSError, inst: | ||||
if inst.errno == errno.EACCES: | ||||
fwarn(nd, inst.strerror) | ||||
continue | ||||
raise | ||||
|
r6826 | for f, kind, st in entries: | ||
|
r7068 | nf = normalize(nd and (nd + "/" + f) or f, True) | ||
|
r6829 | if nf not in results: | ||
|
r6828 | if kind == dirkind: | ||
if not ignore(nf): | ||||
|
r8708 | match.dir(nf) | ||
|
r6828 | wadd(nf) | ||
|
r6834 | if nf in dmap and matchfn(nf): | ||
|
r6829 | results[nf] = None | ||
|
r6832 | elif kind == regkind or kind == lnkkind: | ||
if nf in dmap: | ||||
|
r6834 | if matchfn(nf): | ||
|
r6832 | results[nf] = st | ||
|
r6834 | elif matchfn(nf) and not ignore(nf): | ||
|
r6829 | results[nf] = st | ||
|
r6834 | elif nf in dmap and matchfn(nf): | ||
|
r6832 | results[nf] = None | ||
|
r1089 | |||
|
r6826 | # step 3: report unseen items in the dmap hash | ||
|
r8684 | if not skipstep3 and not exact: | ||
|
r8589 | visit = sorted([f for f in dmap if f not in results and matchfn(f)]) | ||
for nf, st in zip(visit, util.statfiles([join(i) for i in visit])): | ||||
if not st is None and not getkind(st.st_mode) in (regkind, lnkkind): | ||||
st = None | ||||
results[nf] = st | ||||
|
r10176 | for s in subrepos: | ||
del results[s] | ||||
|
r6831 | del results['.hg'] | ||
|
r6829 | return results | ||
|
r1089 | |||
|
r10176 | def status(self, match, subrepos, ignored, clean, unknown): | ||
|
r9518 | '''Determine the status of the working copy relative to the | ||
dirstate and return a tuple of lists (unsure, modified, added, | ||||
removed, deleted, unknown, ignored, clean), where: | ||||
unsure: | ||||
files that might have been modified since the dirstate was | ||||
written, but need to be read to be sure (size is the same | ||||
but mtime differs) | ||||
modified: | ||||
files that have definitely been modified since the dirstate | ||||
was written (different size or mode) | ||||
added: | ||||
files that have been explicitly added with hg add | ||||
removed: | ||||
files that have been explicitly removed with hg remove | ||||
deleted: | ||||
files that have been deleted through other means ("missing") | ||||
unknown: | ||||
files not in the dirstate that are not ignored | ||||
ignored: | ||||
files not in the dirstate that are ignored | ||||
(by _dirignore()) | ||||
clean: | ||||
files that have definitely not been modified since the | ||||
dirstate was written | ||||
''' | ||||
|
r6753 | listignored, listclean, listunknown = ignored, clean, unknown | ||
|
r2022 | lookup, modified, added, unknown, ignored = [], [], [], [], [] | ||
|
r2661 | removed, deleted, clean = [], [], [] | ||
|
r1089 | |||
|
r5003 | dmap = self._map | ||
|
r9518 | ladd = lookup.append # aka "unsure" | ||
|
r5003 | madd = modified.append | ||
aadd = added.append | ||||
uadd = unknown.append | ||||
iadd = ignored.append | ||||
radd = removed.append | ||||
dadd = deleted.append | ||||
cadd = clean.append | ||||
|
r11769 | lnkkind = stat.S_IFLNK | ||
|
r10176 | for fn, st in self.walk(match, subrepos, listunknown, | ||
listignored).iteritems(): | ||||
|
r6591 | if fn not in dmap: | ||
|
r6753 | if (listignored or match.exact(fn)) and self._dirignore(fn): | ||
if listignored: | ||||
|
r6033 | iadd(fn) | ||
|
r6753 | elif listunknown: | ||
|
r5003 | uadd(fn) | ||
|
r1471 | continue | ||
|
r6591 | |||
|
r7093 | state, mode, size, time = dmap[fn] | ||
|
r6591 | |||
|
r6818 | if not st and state in "nma": | ||
dadd(fn) | ||||
elif state == 'n': | ||||
|
r11769 | # The "mode & lnkkind != lnkkind or self._checklink" | ||
# lines are an expansion of "islink => checklink" | ||||
# where islink means "is this a link?" and checklink | ||||
# means "can we check links?". | ||||
|
r13763 | mtime = int(st.st_mtime) | ||
|
r6257 | if (size >= 0 and | ||
(size != st.st_size | ||||
or ((mode ^ st.st_mode) & 0100 and self._checkexec)) | ||||
|
r11769 | and (mode & lnkkind != lnkkind or self._checklink) | ||
|
r10968 | or size == -2 # other parent | ||
|
r4677 | or fn in self._copymap): | ||
|
r5003 | madd(fn) | ||
|
r13763 | elif (mtime != time | ||
|
r11769 | and (mode & lnkkind != lnkkind or self._checklink)): | ||
|
r5003 | ladd(fn) | ||
|
r13763 | elif mtime == self._lastnormaltime: | ||
# fn may have been changed in the same timeslot without | ||||
# changing its size. This can happen if we quickly do | ||||
# multiple commits in a single transaction. | ||||
# Force lookup, so we don't miss such a racy file change. | ||||
|
r13704 | ladd(fn) | ||
|
r6753 | elif listclean: | ||
|
r5003 | cadd(fn) | ||
|
r6590 | elif state == 'm': | ||
|
r5003 | madd(fn) | ||
|
r6590 | elif state == 'a': | ||
|
r5003 | aadd(fn) | ||
|
r6590 | elif state == 'r': | ||
|
r5003 | radd(fn) | ||
|
r1183 | |||
|
r2661 | return (lookup, modified, added, removed, deleted, unknown, ignored, | ||
clean) | ||||