dirstate.py
923 lines
| 32.1 KiB
| text/x-python
|
PythonLexer
/ mercurial / dirstate.py
Martin Geisler
|
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 | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
mpm@selenic.com
|
r1089 | |||
Joel Rosdahl
|
r6211 | from node import nullid | ||
Matt Mackall
|
r3891 | from i18n import _ | ||
Augie Fackler
|
r20033 | import scmutil, util, ignore, osutil, parsers, encoding, pathutil | ||
Pierre-Yves David
|
r23496 | import os, stat, errno | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r8261 | propertycache = util.propertycache | ||
Idan Kamara
|
r16201 | filecache = scmutil.filecache | ||
Matt Mackall
|
r17733 | _rangemask = 0x7fffffff | ||
Idan Kamara
|
r16201 | |||
Siddharth Agarwal
|
r21809 | dirstatetuple = parsers.dirstatetuple | ||
Siddharth Agarwal
|
r21808 | |||
Idan Kamara
|
r16201 | class repocache(filecache): | ||
"""filecache for files in .hg/""" | ||||
def join(self, obj, fname): | ||||
return obj._opener.join(fname) | ||||
Matt Mackall
|
r4610 | |||
Idan Kamara
|
r16202 | class rootcache(filecache): | ||
"""filecache for files in the repository root""" | ||||
def join(self, obj, fname): | ||||
return obj._join(fname) | ||||
Eric Hopper
|
r1559 | class dirstate(object): | ||
Benoit Boissinot
|
r2393 | |||
Matt Mackall
|
r13032 | def __init__(self, opener, ui, root, validate): | ||
Martin Geisler
|
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. | ||||
''' | ||||
Matt Mackall
|
r4614 | self._opener = opener | ||
Matt Mackall
|
r13032 | self._validate = validate | ||
Matt Mackall
|
r4614 | self._root = root | ||
Yuya Nishihara
|
r24198 | # ntpath.join(root, '') of Python 2.7.9 does not add sep if root is | ||
# UNC path pointing to root share (issue4557) | ||||
if root.endswith(os.sep): | ||||
self._rootdir = root | ||||
else: | ||||
self._rootdir = root + os.sep | ||||
Matt Mackall
|
r4903 | self._dirty = False | ||
Matt Mackall
|
r4965 | self._dirtypl = False | ||
Martin Geisler
|
r15791 | self._lastnormaltime = 0 | ||
Matt Mackall
|
r4614 | self._ui = ui | ||
Idan Kamara
|
r16200 | self._filecache = {} | ||
Durham Goode
|
r22404 | self._parentwriters = 0 | ||
def beginparentchange(self): | ||||
'''Marks the beginning of a set of changes that involve changing | ||||
the dirstate parents. If there is an exception during this time, | ||||
the dirstate will not be written when the wlock is released. This | ||||
prevents writing an incoherent dirstate where the parent doesn't | ||||
match the contents. | ||||
''' | ||||
self._parentwriters += 1 | ||||
def endparentchange(self): | ||||
'''Marks the end of a set of changes that involve changing the | ||||
dirstate parents. Once all parent changes have been marked done, | ||||
the wlock will be free to write the dirstate on release. | ||||
''' | ||||
if self._parentwriters > 0: | ||||
self._parentwriters -= 1 | ||||
def pendingparentchange(self): | ||||
'''Returns true if the dirstate is in the middle of a set of changes | ||||
that modify the dirstate parent. | ||||
''' | ||||
return self._parentwriters > 0 | ||||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r8261 | @propertycache | ||
def _map(self): | ||||
Greg Ward
|
r9518 | '''Return the dirstate contents as a map from filename to | ||
(state, mode, size, time).''' | ||||
Matt Mackall
|
r8261 | self._read() | ||
return self._map | ||||
@propertycache | ||||
def _copymap(self): | ||||
self._read() | ||||
return self._copymap | ||||
@propertycache | ||||
def _foldmap(self): | ||||
f = {} | ||||
Siddharth Agarwal
|
r22782 | normcase = util.normcase | ||
FUJIWARA Katsunori
|
r19103 | for name, s in self._map.iteritems(): | ||
if s[0] != 'r': | ||||
Siddharth Agarwal
|
r22782 | f[normcase(name)] = name | ||
Matt Mackall
|
r16302 | for name in self._dirs: | ||
Siddharth Agarwal
|
r22782 | f[normcase(name)] = name | ||
FUJIWARA Katsunori
|
r15668 | f['.'] = '.' # prevents useless util.fspath() invocation | ||
Matt Mackall
|
r8261 | return f | ||
Idan Kamara
|
r16201 | @repocache('branch') | ||
Matt Mackall
|
r8261 | def _branch(self): | ||
try: | ||||
Dan Villiom Podlaski Christiansen
|
r14168 | return self._opener.read("branch").strip() or "default" | ||
Pierre-Yves David
|
r15799 | except IOError, inst: | ||
if inst.errno != errno.ENOENT: | ||||
raise | ||||
Matt Mackall
|
r8261 | return "default" | ||
@propertycache | ||||
def _pl(self): | ||||
try: | ||||
Dan Villiom Podlaski Christiansen
|
r13400 | fp = self._opener("dirstate") | ||
st = fp.read(40) | ||||
fp.close() | ||||
Matt Mackall
|
r8716 | l = len(st) | ||
if l == 40: | ||||
Matt Mackall
|
r8261 | return st[:20], st[20:40] | ||
Matt Mackall
|
r8716 | elif l > 0 and l < 40: | ||
Matt Mackall
|
r8640 | raise util.Abort(_('working directory state appears damaged!')) | ||
Matt Mackall
|
r8261 | except IOError, err: | ||
Matt Mackall
|
r10282 | if err.errno != errno.ENOENT: | ||
raise | ||||
Matt Mackall
|
r8261 | return [nullid, nullid] | ||
@propertycache | ||||
def _dirs(self): | ||||
Bryan O'Sullivan
|
r18899 | return scmutil.dirs(self._map, 'r') | ||
Matt Mackall
|
r8261 | |||
FUJIWARA Katsunori
|
r16143 | def dirs(self): | ||
return self._dirs | ||||
Idan Kamara
|
r16202 | @rootcache('.hgignore') | ||
Matt Mackall
|
r8261 | def _ignore(self): | ||
files = [self._join('.hgignore')] | ||||
for name, path in self._ui.configitems("ui"): | ||||
if name == 'ignore' or name.startswith('ignore.'): | ||||
Siddharth Agarwal
|
r23629 | # we need to use os.path.join here rather than self._join | ||
# because path is arbitrary and user-specified | ||||
files.append(os.path.join(self._rootdir, util.expandpath(path))) | ||||
Matt Mackall
|
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')) | ||||
Matt Mackall
|
r4905 | def _join(self, f): | ||
Benoit Boissinot
|
r6972 | # much faster than os.path.join() | ||
Benoit Boissinot
|
r6973 | # it's safe because f is always a relative path | ||
Benoit Boissinot
|
r6972 | return self._rootdir + f | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r15337 | def flagfunc(self, buildfallback): | ||
if self._checklink and self._checkexec: | ||||
def f(x): | ||||
Bryan O'Sullivan
|
r18869 | try: | ||
st = os.lstat(self._join(x)) | ||||
if util.statislink(st): | ||||
return 'l' | ||||
if util.statisexec(st): | ||||
return 'x' | ||||
except OSError: | ||||
pass | ||||
Matt Mackall
|
r15337 | return '' | ||
return f | ||||
fallback = buildfallback() | ||||
Matt Mackall
|
r6743 | if self._checklink: | ||
def f(x): | ||||
Benoit Boissinot
|
r6972 | if os.path.islink(self._join(x)): | ||
Matt Mackall
|
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' | ||||
Adrian Buehlmann
|
r14273 | if util.isexec(self._join(x)): | ||
Matt Mackall
|
r6743 | return 'x' | ||
return '' | ||||
return f | ||||
Matt Mackall
|
r15337 | else: | ||
return fallback | ||||
Matt Mackall
|
r6743 | |||
Pierre-Yves David
|
r20335 | @propertycache | ||
def _cwd(self): | ||||
return os.getcwd() | ||||
mpm@selenic.com
|
r1089 | def getcwd(self): | ||
Pierre-Yves David
|
r20335 | cwd = self._cwd | ||
Matt Mackall
|
r10282 | if cwd == self._root: | ||
return '' | ||||
Matt Mackall
|
r4614 | # self._root ends with a path separator if self._root is '/' or 'C:\' | ||
rootsep = self._root | ||||
Shun-ichi GOTO
|
r5843 | if not util.endswithsep(rootsep): | ||
Alexis S. L. Carvalho
|
r4230 | rootsep += os.sep | ||
if cwd.startswith(rootsep): | ||||
return cwd[len(rootsep):] | ||||
else: | ||||
# we're outside the repo. return an absolute path. | ||||
return cwd | ||||
mpm@selenic.com
|
r1089 | |||
Alexis S. L. Carvalho
|
r4525 | def pathto(self, f, cwd=None): | ||
if cwd is None: | ||||
cwd = self.getcwd() | ||||
Matt Mackall
|
r4614 | path = util.pathto(self._root, cwd, f) | ||
Alexis S. L. Carvalho
|
r4527 | if self._slash: | ||
Matt Mackall
|
r19210 | return util.pconvert(path) | ||
Alexis S. L. Carvalho
|
r4527 | return path | ||
Alexis S. L. Carvalho
|
r4525 | |||
mpm@selenic.com
|
r1089 | def __getitem__(self, key): | ||
Greg Ward
|
r9518 | '''Return the current state of key (a filename) in the dirstate. | ||
Martin Geisler
|
r10145 | |||
Greg Ward
|
r9518 | States are: | ||
n normal | ||||
m needs merging | ||||
r marked for removal | ||||
a marked for addition | ||||
? not tracked | ||||
''' | ||||
Matt Mackall
|
r4906 | return self._map.get(key, ("?",))[0] | ||
mpm@selenic.com
|
r1089 | |||
def __contains__(self, key): | ||||
Matt Mackall
|
r4614 | return key in self._map | ||
def __iter__(self): | ||||
Matt Mackall
|
r8209 | for x in sorted(self._map): | ||
Matt Mackall
|
r4614 | yield x | ||
mpm@selenic.com
|
r1089 | |||
Bryan O'Sullivan
|
r18792 | def iteritems(self): | ||
return self._map.iteritems() | ||||
mpm@selenic.com
|
r1089 | def parents(self): | ||
Matt Mackall
|
r13032 | return [self._validate(p) for p in self._pl] | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r13876 | def p1(self): | ||
return self._validate(self._pl[0]) | ||||
def p2(self): | ||||
return self._validate(self._pl[1]) | ||||
Matt Mackall
|
r4179 | def branch(self): | ||
Matt Mackall
|
r13047 | return encoding.tolocal(self._branch) | ||
Matt Mackall
|
r4179 | |||
mpm@selenic.com
|
r1089 | def setparents(self, p1, p2=nullid): | ||
Patrick Mezard
|
r16551 | """Set dirstate parents to p1 and p2. | ||
When moving from two parents to one, 'm' merged entries a | ||||
adjusted to normal and previous copy records discarded and | ||||
returned by the call. | ||||
See localrepo.setparents() | ||||
""" | ||||
Durham Goode
|
r22407 | if self._parentwriters == 0: | ||
Siddharth Agarwal
|
r22459 | raise ValueError("cannot set dirstate parent without " | ||
"calling dirstate.beginparentchange") | ||||
Durham Goode
|
r22407 | |||
Matt Mackall
|
r4965 | self._dirty = self._dirtypl = True | ||
Patrick Mezard
|
r16509 | oldp2 = self._pl[1] | ||
Matt Mackall
|
r4614 | self._pl = p1, p2 | ||
Patrick Mezard
|
r16551 | copies = {} | ||
Patrick Mezard
|
r16509 | if oldp2 != nullid and p2 == nullid: | ||
for f, s in self._map.iteritems(): | ||||
Matt Mackall
|
r22895 | # Discard 'm' markers when moving away from a merge state | ||
Patrick Mezard
|
r16509 | if s[0] == 'm': | ||
Patrick Mezard
|
r16551 | if f in self._copymap: | ||
copies[f] = self._copymap[f] | ||||
Patrick Mezard
|
r16509 | self.normallookup(f) | ||
Matt Mackall
|
r22895 | # Also fix up otherparent markers | ||
elif s[0] == 'n' and s[2] == -2: | ||||
if f in self._copymap: | ||||
copies[f] = self._copymap[f] | ||||
self.add(f) | ||||
Patrick Mezard
|
r16551 | return copies | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r4179 | def setbranch(self, branch): | ||
Matt Mackall
|
r13047 | self._branch = encoding.fromlocal(branch) | ||
Idan Kamara
|
r16472 | f = self._opener('branch', 'w', atomictemp=True) | ||
try: | ||||
f.write(self._branch + '\n') | ||||
f.close() | ||||
Idan Kamara
|
r18317 | |||
# make sure filecache has the correct stat info for _branch after | ||||
# replacing the underlying file | ||||
ce = self._filecache['_branch'] | ||||
if ce: | ||||
ce.refresh() | ||||
Idan Kamara
|
r18076 | except: # re-raises | ||
f.discard() | ||||
raise | ||||
Matt Mackall
|
r4179 | |||
Matt Mackall
|
r4615 | def _read(self): | ||
Matt Mackall
|
r4614 | self._map = {} | ||
self._copymap = {} | ||||
Matt Mackall
|
r4607 | try: | ||
Dan Villiom Podlaski Christiansen
|
r14168 | st = self._opener.read("dirstate") | ||
Matt Mackall
|
r4607 | except IOError, err: | ||
Matt Mackall
|
r10282 | if err.errno != errno.ENOENT: | ||
raise | ||||
Matt Mackall
|
r4607 | return | ||
if not st: | ||||
return | ||||
Siddharth Agarwal
|
r18649 | # Python's garbage collector triggers a GC each time a certain number | ||
# of container objects (the number being defined by | ||||
# gc.get_threshold()) are allocated. parse_dirstate creates a tuple | ||||
# for each file in the dirstate. The C version then immediately marks | ||||
# them as not to be tracked by the collector. However, this has no | ||||
# effect on when GCs are triggered, only on what objects the GC looks | ||||
# into. This means that O(number of files) GCs are unavoidable. | ||||
# Depending on when in the process's lifetime the dirstate is parsed, | ||||
# this can get very expensive. As a workaround, disable GC while | ||||
# parsing the dirstate. | ||||
Pierre-Yves David
|
r23496 | # | ||
# (we cannot decorate the function directly since it is in a C module) | ||||
parse_dirstate = util.nogc(parsers.parse_dirstate) | ||||
p = parse_dirstate(self._map, self._copymap, st) | ||||
Alexis S. L. Carvalho
|
r4952 | if not self._dirtypl: | ||
Matt Mackall
|
r7093 | self._pl = p | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r4613 | def invalidate(self): | ||
David Soria Parra
|
r13200 | for a in ("_map", "_copymap", "_foldmap", "_branch", "_pl", "_dirs", | ||
"_ignore"): | ||||
Alexis S. L. Carvalho
|
r4953 | if a in self.__dict__: | ||
delattr(self, a) | ||||
Martin Geisler
|
r15791 | self._lastnormaltime = 0 | ||
Matt Mackall
|
r4903 | self._dirty = False | ||
Durham Goode
|
r22404 | self._parentwriters = 0 | ||
Bryan O'Sullivan
|
r4375 | |||
mpm@selenic.com
|
r1089 | def copy(self, source, dest): | ||
Martin Geisler
|
r10145 | """Mark dest as a copy of source. Unmark dest if source is None.""" | ||
Patrick Mezard
|
r6680 | if source == dest: | ||
return | ||||
Matt Mackall
|
r4903 | self._dirty = True | ||
Patrick Mezard
|
r7566 | if source is not None: | ||
self._copymap[dest] = source | ||||
elif dest in self._copymap: | ||||
del self._copymap[dest] | ||||
mpm@selenic.com
|
r1089 | |||
def copied(self, file): | ||||
Matt Mackall
|
r4614 | return self._copymap.get(file, None) | ||
Matt Mackall
|
r3154 | |||
def copies(self): | ||||
Matt Mackall
|
r4614 | return self._copymap | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r6767 | def _droppath(self, f): | ||
if self[f] not in "?r" and "_dirs" in self.__dict__: | ||||
Bryan O'Sullivan
|
r18899 | self._dirs.delpath(f) | ||
Vadim Gelfer
|
r2953 | |||
Adrian Buehlmann
|
r17196 | def _addpath(self, f, state, mode, size, mtime): | ||
Maxim Dounin
|
r5487 | oldstate = self[f] | ||
Adrian Buehlmann
|
r17196 | if state == 'a' or oldstate == 'r': | ||
Adrian Buehlmann
|
r13974 | scmutil.checkfilename(f) | ||
Matt Mackall
|
r6767 | if f in self._dirs: | ||
raise util.Abort(_('directory %r already in dirstate') % f) | ||||
# shadows | ||||
Bryan O'Sullivan
|
r18897 | for d in scmutil.finddirs(f): | ||
Matt Mackall
|
r6767 | 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__: | ||||
Bryan O'Sullivan
|
r18899 | self._dirs.addpath(f) | ||
Joshua Redstone
|
r17094 | self._dirty = True | ||
Siddharth Agarwal
|
r21808 | self._map[f] = dirstatetuple(state, mode, size, mtime) | ||
Maxim Dounin
|
r5487 | |||
Matt Mackall
|
r4904 | def normal(self, f): | ||
Martin Geisler
|
r10145 | '''Mark a file normal and clean.''' | ||
Matt Mackall
|
r4905 | s = os.lstat(self._join(f)) | ||
Adrian Buehlmann
|
r13754 | mtime = int(s.st_mtime) | ||
Matt Mackall
|
r17733 | self._addpath(f, 'n', s.st_mode, | ||
s.st_size & _rangemask, mtime & _rangemask) | ||||
Christian Ebert
|
r5915 | if f in self._copymap: | ||
Matt Mackall
|
r4904 | del self._copymap[f] | ||
Adrian Buehlmann
|
r13763 | if mtime > self._lastnormaltime: | ||
# Remember the most recent modification timeslot for status(), | ||||
Adrian Buehlmann
|
r13754 | # to make sure we won't miss future size-preserving file content | ||
# modifications that happen within the same timeslot. | ||||
Adrian Buehlmann
|
r13763 | self._lastnormaltime = mtime | ||
Greg Ward
|
r13704 | |||
Alexis S. L. Carvalho
|
r5210 | def normallookup(self, f): | ||
Martin Geisler
|
r10145 | '''Mark a file normal, but possibly dirty.''' | ||
Alexis S. L. Carvalho
|
r6298 | if self._pl[1] != nullid and f in self._map: | ||
# if there is a merge going on and the file was either | ||||
Benoit Boissinot
|
r10968 | # in state 'm' (-1) or coming from other parent (-2) before | ||
# being removed, restore that state. | ||||
Alexis S. L. Carvalho
|
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: | ||||
Benoit Boissinot
|
r10968 | self.otherparent(f) | ||
Alexis S. L. Carvalho
|
r6298 | if source: | ||
self.copy(source, f) | ||||
return | ||||
if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2: | ||||
return | ||||
Joshua Redstone
|
r17094 | self._addpath(f, 'n', 0, -1, -1) | ||
Alexis S. L. Carvalho
|
r5210 | if f in self._copymap: | ||
del self._copymap[f] | ||||
Benoit Boissinot
|
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) | ||||
Matt Mackall
|
r22896 | if f in self and self[f] == 'n': | ||
# merge-like | ||||
self._addpath(f, 'm', 0, -2, -1) | ||||
else: | ||||
# add-like | ||||
self._addpath(f, 'n', 0, -2, -1) | ||||
Matt Mackall
|
r4904 | if f in self._copymap: | ||
del self._copymap[f] | ||||
def add(self, f): | ||||
Martin Geisler
|
r10145 | '''Mark a file added.''' | ||
Adrian Buehlmann
|
r17196 | self._addpath(f, 'a', 0, -1, -1) | ||
Matt Mackall
|
r4904 | if f in self._copymap: | ||
del self._copymap[f] | ||||
Matt Mackall
|
r4616 | |||
Matt Mackall
|
r4904 | def remove(self, f): | ||
Martin Geisler
|
r10145 | '''Mark a file removed.''' | ||
Matt Mackall
|
r4904 | self._dirty = True | ||
Matt Mackall
|
r6767 | self._droppath(f) | ||
Alexis S. L. Carvalho
|
r6297 | size = 0 | ||
if self._pl[1] != nullid and f in self._map: | ||||
Benoit Boissinot
|
r10968 | # backup the previous state | ||
Alexis S. L. Carvalho
|
r6297 | entry = self._map[f] | ||
Benoit Boissinot
|
r10968 | if entry[0] == 'm': # merge | ||
Alexis S. L. Carvalho
|
r6297 | size = -1 | ||
Benoit Boissinot
|
r10968 | elif entry[0] == 'n' and entry[2] == -2: # other parent | ||
Alexis S. L. Carvalho
|
r6297 | size = -2 | ||
Siddharth Agarwal
|
r21808 | self._map[f] = dirstatetuple('r', 0, size, 0) | ||
Alexis S. L. Carvalho
|
r6297 | if size == 0 and f in self._copymap: | ||
Matt Mackall
|
r4904 | del self._copymap[f] | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r4904 | def merge(self, f): | ||
Martin Geisler
|
r10145 | '''Mark a file merged.''' | ||
Patrick Mezard
|
r16509 | if self._pl[1] == nullid: | ||
return self.normallookup(f) | ||||
Matt Mackall
|
r22897 | return self.otherparent(f) | ||
Matt Mackall
|
r4904 | |||
Matt Mackall
|
r14434 | def drop(self, f): | ||
'''Drop a file from the dirstate''' | ||||
Matt Mackall
|
r15399 | if f in self._map: | ||
self._dirty = True | ||||
self._droppath(f) | ||||
del self._map[f] | ||||
mpm@selenic.com
|
r1089 | |||
Patrick Mezard
|
r16542 | def _normalize(self, path, isknown, ignoremissing=False, exists=None): | ||
Matt Mackall
|
r15488 | normed = util.normcase(path) | ||
Matt Mackall
|
r13717 | folded = self._foldmap.get(normed, None) | ||
if folded is None: | ||||
Patrick Mezard
|
r16542 | if isknown: | ||
Matt Mackall
|
r13717 | folded = path | ||
Petr Kodl
|
r7068 | else: | ||
Patrick Mezard
|
r16542 | if exists is None: | ||
exists = os.path.lexists(os.path.join(self._root, path)) | ||||
if not exists: | ||||
# Maybe a path component exists | ||||
if not ignoremissing and '/' in path: | ||||
d, f = path.rsplit('/', 1) | ||||
d = self._normalize(d, isknown, ignoremissing, None) | ||||
folded = d + "/" + f | ||||
else: | ||||
# No path components, preserve original case | ||||
folded = path | ||||
Matt Mackall
|
r16302 | else: | ||
Patrick Mezard
|
r16542 | # recursively normalize leading directory components | ||
# against dirstate | ||||
if '/' in normed: | ||||
d, f = normed.rsplit('/', 1) | ||||
d = self._normalize(d, isknown, ignoremissing, True) | ||||
r = self._root + "/" + d | ||||
folded = d + "/" + util.fspath(f, r) | ||||
else: | ||||
folded = util.fspath(normed, self._root) | ||||
self._foldmap[normed] = folded | ||||
Matt Mackall
|
r16302 | |||
Matt Mackall
|
r13717 | return folded | ||
Patrick Mezard
|
r16542 | def normalize(self, path, isknown=False, ignoremissing=False): | ||
Matt Mackall
|
r13717 | ''' | ||
normalize the case of a pathname when on a casefolding filesystem | ||||
isknown specifies whether the filename came from walking the | ||||
Patrick Mezard
|
r16542 | disk, to avoid extra filesystem access. | ||
If ignoremissing is True, missing path are returned | ||||
unchanged. Otherwise, we try harder to normalize possibly | ||||
existing path components. | ||||
Matt Mackall
|
r13717 | |||
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: | ||||
Patrick Mezard
|
r16542 | return self._normalize(path, isknown, ignoremissing) | ||
Matt Mackall
|
r13717 | return path | ||
Paul Moore
|
r6677 | |||
Alexis S. L. Carvalho
|
r5065 | def clear(self): | ||
self._map = {} | ||||
Maxim Dounin
|
r5487 | if "_dirs" in self.__dict__: | ||
Benoit Boissinot
|
r10394 | delattr(self, "_dirs") | ||
Alexis S. L. Carvalho
|
r5065 | self._copymap = {} | ||
self._pl = [nullid, nullid] | ||||
Martin Geisler
|
r15791 | self._lastnormaltime = 0 | ||
Alexis S. L. Carvalho
|
r5123 | self._dirty = True | ||
Alexis S. L. Carvalho
|
r5065 | |||
Durham Goode
|
r18760 | def rebuild(self, parent, allfiles, changedfiles=None): | ||
changedfiles = changedfiles or allfiles | ||||
oldmap = self._map | ||||
Alexis S. L. Carvalho
|
r5065 | self.clear() | ||
Durham Goode
|
r18760 | for f in allfiles: | ||
if f not in changedfiles: | ||||
self._map[f] = oldmap[f] | ||||
Benoit Boissinot
|
r1755 | else: | ||
Durham Goode
|
r18760 | if 'x' in allfiles.flags(f): | ||
Siddharth Agarwal
|
r21808 | self._map[f] = dirstatetuple('n', 0777, -1, 0) | ||
Durham Goode
|
r18760 | else: | ||
Siddharth Agarwal
|
r21808 | self._map[f] = dirstatetuple('n', 0666, -1, 0) | ||
Matt Mackall
|
r4614 | self._pl = (parent, nullid) | ||
Matt Mackall
|
r4903 | self._dirty = True | ||
mpm@selenic.com
|
r1089 | |||
def write(self): | ||||
Matt Mackall
|
r4612 | if not self._dirty: | ||
Benoit Boissinot
|
r1794 | return | ||
FUJIWARA Katsunori
|
r21931 | |||
# enough 'delaywrite' prevents 'pack_dirstate' from dropping | ||||
# timestamp of each entries in dirstate, because of 'now > mtime' | ||||
delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0) | ||||
Mads Kiilerich
|
r23866 | if delaywrite > 0: | ||
FUJIWARA Katsunori
|
r21931 | import time # to avoid useless import | ||
time.sleep(delaywrite) | ||||
Alexis S. L. Carvalho
|
r6326 | st = self._opener("dirstate", "w", atomictemp=True) | ||
Adrian Buehlmann
|
r9509 | # use the modification time of the newly created temporary file as the | ||
# filesystem's notion of 'now' | ||||
Bryan O'Sullivan
|
r16955 | now = util.fstat(st).st_mtime | ||
Mads Kiilerich
|
r21026 | st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now)) | ||
st.close() | ||||
self._lastnormaltime = 0 | ||||
self._dirty = self._dirtypl = False | ||||
mpm@selenic.com
|
r1089 | |||
Alexis S. L. Carvalho
|
r6032 | def _dirignore(self, f): | ||
Patrick Mezard
|
r6479 | if f == '.': | ||
return False | ||||
Alexis S. L. Carvalho
|
r6032 | if self._ignore(f): | ||
return True | ||||
Bryan O'Sullivan
|
r18897 | for p in scmutil.finddirs(f): | ||
Matt Mackall
|
r6767 | if self._ignore(p): | ||
Alexis S. L. Carvalho
|
r6032 | return True | ||
return False | ||||
Siddharth Agarwal
|
r19173 | def _walkexplicit(self, match, subrepos): | ||
'''Get stat data about the files explicitly specified by match. | ||||
Matt Mackall
|
r3529 | |||
Siddharth Agarwal
|
r19174 | Return a triple (results, dirsfound, dirsnotfound). | ||
Siddharth Agarwal
|
r19173 | - results is a mapping from filename to stat result. It also contains | ||
listings mapping subrepos and .hg to None. | ||||
Siddharth Agarwal
|
r19174 | - dirsfound is a list of files found to be directories. | ||
Siddharth Agarwal
|
r19173 | - dirsnotfound is a list of files that the dirstate thinks are | ||
directories and that were not found.''' | ||||
Matt Mackall
|
r6578 | |||
Matt Mackall
|
r8681 | def badtype(mode): | ||
Simon Heimberg
|
r8310 | kind = _('unknown') | ||
Matt Mackall
|
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') | ||||
Matt Mackall
|
r8681 | return _('unsupported file type (type is %s)') % kind | ||
Matt Mackall
|
r6830 | |||
Siddharth Agarwal
|
r19142 | matchedir = match.explicitdir | ||
Matt Mackall
|
r8676 | badfn = match.bad | ||
Matt Mackall
|
r6831 | dmap = self._map | ||
Matt Mackall
|
r5000 | normpath = util.normpath | ||
lstat = os.lstat | ||||
Matt Mackall
|
r6830 | getkind = stat.S_IFMT | ||
Matt Mackall
|
r6828 | dirkind = stat.S_IFDIR | ||
Matt Mackall
|
r6830 | regkind = stat.S_IFREG | ||
lnkkind = stat.S_IFLNK | ||||
Matt Mackall
|
r6831 | join = self._join | ||
Siddharth Agarwal
|
r19174 | dirsfound = [] | ||
foundadd = dirsfound.append | ||||
Siddharth Agarwal
|
r19173 | dirsnotfound = [] | ||
Siddharth Agarwal
|
r19175 | notfoundadd = dirsnotfound.append | ||
Matt Mackall
|
r6820 | |||
Siddharth Agarwal
|
r19173 | if match.matchfn != match.exact and self._checkcase: | ||
Matt Mackall
|
r12907 | normalize = self._normalize | ||
else: | ||||
Siddharth Agarwal
|
r18032 | normalize = None | ||
Matt Mackall
|
r12907 | |||
Martin Geisler
|
r12211 | files = sorted(match.files()) | ||
subrepos.sort() | ||||
i, j = 0, 0 | ||||
while i < len(files) and j < len(subrepos): | ||||
subpath = subrepos[j] + "/" | ||||
Oleg Stepanov
|
r13233 | if files[i] < subpath: | ||
Martin Geisler
|
r12211 | i += 1 | ||
continue | ||||
trbs
|
r13339 | while i < len(files) and files[i].startswith(subpath): | ||
Martin Geisler
|
r12211 | del files[i] | ||
j += 1 | ||||
Matt Mackall
|
r6831 | if not files or '.' in files: | ||
files = [''] | ||||
Augie Fackler
|
r10176 | results = dict.fromkeys(subrepos) | ||
results['.hg'] = None | ||||
Matt Mackall
|
r5000 | |||
Martin von Zweigbergk
|
r23375 | alldirs = None | ||
Martin Geisler
|
r12211 | for ff in files: | ||
Siddharth Agarwal
|
r18032 | if normalize: | ||
nf = normalize(normpath(ff), False, True) | ||||
else: | ||||
nf = normpath(ff) | ||||
Matt Mackall
|
r6829 | if nf in results: | ||
Matt Mackall
|
r6820 | continue | ||
try: | ||||
Matt Mackall
|
r6831 | st = lstat(join(nf)) | ||
Matt Mackall
|
r6830 | kind = getkind(st.st_mode) | ||
if kind == dirkind: | ||||
Simon Heimberg
|
r8588 | if nf in dmap: | ||
Mads Kiilerich
|
r21115 | # file replaced by dir on disk but still in dirstate | ||
Simon Heimberg
|
r8588 | results[nf] = None | ||
Siddharth Agarwal
|
r19143 | if matchedir: | ||
matchedir(nf) | ||||
Siddharth Agarwal
|
r19174 | foundadd(nf) | ||
Matt Mackall
|
r12401 | elif kind == regkind or kind == lnkkind: | ||
Matt Mackall
|
r6830 | results[nf] = st | ||
Matt Mackall
|
r6828 | else: | ||
Matt Mackall
|
r8681 | badfn(ff, badtype(kind)) | ||
Matt Mackall
|
r6830 | if nf in dmap: | ||
Matt Mackall
|
r6829 | results[nf] = None | ||
Mads Kiilerich
|
r21115 | except OSError, inst: # nf not found on disk - it is dirstate only | ||
if nf in dmap: # does it exactly match a missing file? | ||||
Matt Mackall
|
r8675 | results[nf] = None | ||
Mads Kiilerich
|
r21115 | else: # does it match a missing directory? | ||
Martin von Zweigbergk
|
r23375 | if alldirs is None: | ||
alldirs = scmutil.dirs(dmap) | ||||
if nf in alldirs: | ||||
if matchedir: | ||||
matchedir(nf) | ||||
notfoundadd(nf) | ||||
Matt Mackall
|
r8677 | else: | ||
Matt Mackall
|
r8680 | badfn(ff, inst.strerror) | ||
Matt Mackall
|
r6820 | |||
Siddharth Agarwal
|
r19174 | return results, dirsfound, dirsnotfound | ||
Siddharth Agarwal
|
r19173 | |||
Siddharth Agarwal
|
r19190 | def walk(self, match, subrepos, unknown, ignored, full=True): | ||
Siddharth Agarwal
|
r19173 | ''' | ||
Walk recursively through the directory tree, finding all files | ||||
matched by match. | ||||
Siddharth Agarwal
|
r19190 | If full is False, maybe skip some known-clean files. | ||
Siddharth Agarwal
|
r19173 | Return a dict mapping filename to stat-like object (either | ||
mercurial.osutil.stat instance or return value of os.stat()). | ||||
Siddharth Agarwal
|
r19190 | |||
Siddharth Agarwal
|
r19173 | ''' | ||
Siddharth Agarwal
|
r19190 | # full is a flag that extensions that hook into walk can use -- this | ||
# implementation doesn't use it at all. This satisfies the contract | ||||
# because we only guarantee a "maybe". | ||||
Siddharth Agarwal
|
r19173 | |||
if ignored: | ||||
ignore = util.never | ||||
dirignore = util.never | ||||
Mads Kiilerich
|
r21115 | elif unknown: | ||
ignore = self._ignore | ||||
dirignore = self._dirignore | ||||
else: | ||||
# if not unknown and not ignored, drop dir recursion and step 2 | ||||
Siddharth Agarwal
|
r19173 | ignore = util.always | ||
dirignore = util.always | ||||
matchfn = match.matchfn | ||||
matchalways = match.always() | ||||
matchtdir = match.traversedir | ||||
dmap = self._map | ||||
listdir = osutil.listdir | ||||
lstat = os.lstat | ||||
dirkind = stat.S_IFDIR | ||||
regkind = stat.S_IFREG | ||||
lnkkind = stat.S_IFLNK | ||||
join = self._join | ||||
exact = skipstep3 = False | ||||
if matchfn == match.exact: # match.exact | ||||
exact = True | ||||
dirignore = util.always # skip step 2 | ||||
elif match.files() and not match.anypats(): # match.match, no patterns | ||||
skipstep3 = True | ||||
if not exact and self._checkcase: | ||||
normalize = self._normalize | ||||
skipstep3 = False | ||||
else: | ||||
normalize = None | ||||
# step 1: find all explicit files | ||||
results, work, dirsnotfound = self._walkexplicit(match, subrepos) | ||||
Siddharth Agarwal
|
r19172 | skipstep3 = skipstep3 and not (work or dirsnotfound) | ||
Siddharth Agarwal
|
r19171 | work = [d for d in work if not dirignore(d)] | ||
wadd = work.append | ||||
Matt Mackall
|
r6826 | # step 2: visit subdirectories | ||
while work: | ||||
nd = work.pop() | ||||
Benoit Boissinot
|
r7099 | skip = None | ||
Matt Mackall
|
r6826 | if nd == '.': | ||
nd = '' | ||||
else: | ||||
Benoit Boissinot
|
r7099 | skip = '.hg' | ||
try: | ||||
entries = listdir(join(nd), stat=True, skip=skip) | ||||
except OSError, inst: | ||||
Bryan O'Sullivan
|
r17879 | if inst.errno in (errno.EACCES, errno.ENOENT): | ||
Mads Kiilerich
|
r21116 | match.bad(self.pathto(nd), inst.strerror) | ||
Benoit Boissinot
|
r7099 | continue | ||
raise | ||||
Matt Mackall
|
r6826 | for f, kind, st in entries: | ||
Siddharth Agarwal
|
r18032 | if normalize: | ||
nf = normalize(nd and (nd + "/" + f) or f, True, True) | ||||
else: | ||||
nf = nd and (nd + "/" + f) or f | ||||
Matt Mackall
|
r6829 | if nf not in results: | ||
Matt Mackall
|
r6828 | if kind == dirkind: | ||
if not ignore(nf): | ||||
Siddharth Agarwal
|
r19143 | if matchtdir: | ||
matchtdir(nf) | ||||
Matt Mackall
|
r6828 | wadd(nf) | ||
Siddharth Agarwal
|
r18814 | if nf in dmap and (matchalways or matchfn(nf)): | ||
Matt Mackall
|
r6829 | results[nf] = None | ||
Matt Mackall
|
r6832 | elif kind == regkind or kind == lnkkind: | ||
if nf in dmap: | ||||
Siddharth Agarwal
|
r18814 | if matchalways or matchfn(nf): | ||
Matt Mackall
|
r6832 | results[nf] = st | ||
Siddharth Agarwal
|
r18814 | elif (matchalways or matchfn(nf)) and not ignore(nf): | ||
Matt Mackall
|
r6829 | results[nf] = st | ||
Siddharth Agarwal
|
r18814 | elif nf in dmap and (matchalways or matchfn(nf)): | ||
Matt Mackall
|
r6832 | results[nf] = None | ||
mpm@selenic.com
|
r1089 | |||
Siddharth Agarwal
|
r18812 | for s in subrepos: | ||
del results[s] | ||||
del results['.hg'] | ||||
Mads Kiilerich
|
r21115 | # step 3: visit remaining files from dmap | ||
Matt Mackall
|
r8684 | if not skipstep3 and not exact: | ||
Mads Kiilerich
|
r21115 | # If a dmap file is not in results yet, it was either | ||
# a) not matching matchfn b) ignored, c) missing, or d) under a | ||||
# symlink directory. | ||||
Siddharth Agarwal
|
r18815 | if not results and matchalways: | ||
visit = dmap.keys() | ||||
else: | ||||
visit = [f for f in dmap if f not in results and matchfn(f)] | ||||
visit.sort() | ||||
Durham Goode
|
r18625 | if unknown: | ||
Mads Kiilerich
|
r21115 | # unknown == True means we walked all dirs under the roots | ||
# that wasn't ignored, and everything that matched was stat'ed | ||||
# and is already in results. | ||||
# The rest must thus be ignored or under a symlink. | ||||
Augie Fackler
|
r20033 | audit_path = pathutil.pathauditor(self._root) | ||
Durham Goode
|
r18625 | |||
for nf in iter(visit): | ||||
# Report ignored items in the dmap as long as they are not | ||||
# under a symlink directory. | ||||
Durham Goode
|
r19128 | if audit_path.check(nf): | ||
Durham Goode
|
r18663 | try: | ||
results[nf] = lstat(join(nf)) | ||||
Mads Kiilerich
|
r21115 | # file was just ignored, no links, and exists | ||
Durham Goode
|
r18663 | except OSError: | ||
# file doesn't exist | ||||
results[nf] = None | ||||
Durham Goode
|
r18625 | else: | ||
# It's either missing or under a symlink directory | ||||
Mads Kiilerich
|
r21115 | # which we in this case report as missing | ||
Durham Goode
|
r18625 | results[nf] = None | ||
else: | ||||
# We may not have walked the full directory tree above, | ||||
Mads Kiilerich
|
r21115 | # so stat and check everything we missed. | ||
Durham Goode
|
r18625 | nf = iter(visit).next | ||
for st in util.statfiles([join(i) for i in visit]): | ||||
results[nf()] = st | ||||
Matt Mackall
|
r6829 | return results | ||
mpm@selenic.com
|
r1089 | |||
Augie Fackler
|
r10176 | def status(self, match, subrepos, ignored, clean, unknown): | ||
Greg Ward
|
r9518 | '''Determine the status of the working copy relative to the | ||
Martin von Zweigbergk
|
r22915 | dirstate and return a pair of (unsure, status), where status is of type | ||
scmutil.status and: | ||||
Greg Ward
|
r9518 | |||
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) | ||||
Martin von Zweigbergk
|
r22915 | status.modified: | ||
Greg Ward
|
r9518 | files that have definitely been modified since the dirstate | ||
was written (different size or mode) | ||||
Martin von Zweigbergk
|
r22915 | status.clean: | ||
Greg Ward
|
r9518 | files that have definitely not been modified since the | ||
dirstate was written | ||||
''' | ||||
Matt Mackall
|
r6753 | listignored, listclean, listunknown = ignored, clean, unknown | ||
Thomas Arendsen Hein
|
r2022 | lookup, modified, added, unknown, ignored = [], [], [], [], [] | ||
Vadim Gelfer
|
r2661 | removed, deleted, clean = [], [], [] | ||
mpm@selenic.com
|
r1089 | |||
Matt Mackall
|
r5003 | dmap = self._map | ||
Greg Ward
|
r9518 | ladd = lookup.append # aka "unsure" | ||
Matt Mackall
|
r5003 | madd = modified.append | ||
aadd = added.append | ||||
uadd = unknown.append | ||||
iadd = ignored.append | ||||
radd = removed.append | ||||
dadd = deleted.append | ||||
cadd = clean.append | ||||
Siddharth Agarwal
|
r18034 | mexact = match.exact | ||
dirignore = self._dirignore | ||||
checkexec = self._checkexec | ||||
copymap = self._copymap | ||||
lastnormaltime = self._lastnormaltime | ||||
Matt Mackall
|
r5003 | |||
Siddharth Agarwal
|
r19191 | # We need to do full walks when either | ||
# - we're listing all clean files, or | ||||
# - match.traversedir does something, because match.traversedir should | ||||
# be called for every dir in the working dir | ||||
full = listclean or match.traversedir is not None | ||||
for fn, st in self.walk(match, subrepos, listunknown, listignored, | ||||
full=full).iteritems(): | ||||
Matt Mackall
|
r6591 | if fn not in dmap: | ||
Siddharth Agarwal
|
r18034 | if (listignored or mexact(fn)) and dirignore(fn): | ||
Matt Mackall
|
r6753 | if listignored: | ||
Alexis S. L. Carvalho
|
r6033 | iadd(fn) | ||
Siddharth Agarwal
|
r19910 | else: | ||
Matt Mackall
|
r5003 | uadd(fn) | ||
Benoit Boissinot
|
r1471 | continue | ||
Matt Mackall
|
r6591 | |||
Siddharth Agarwal
|
r21810 | # This is equivalent to 'state, mode, size, time = dmap[fn]' but not | ||
# written like that for performance reasons. dmap[fn] is not a | ||||
# Python tuple in compiled builds. The CPython UNPACK_SEQUENCE | ||||
# opcode has fast paths when the value to be unpacked is a tuple or | ||||
# a list, but falls back to creating a full-fledged iterator in | ||||
# general. That is much slower than simply accessing and storing the | ||||
# tuple members one by one. | ||||
t = dmap[fn] | ||||
state = t[0] | ||||
mode = t[1] | ||||
size = t[2] | ||||
time = t[3] | ||||
Matt Mackall
|
r6591 | |||
Matt Mackall
|
r6818 | if not st and state in "nma": | ||
dadd(fn) | ||||
elif state == 'n': | ||||
Adrian Buehlmann
|
r13763 | mtime = int(st.st_mtime) | ||
Alexis S. L. Carvalho
|
r6257 | if (size >= 0 and | ||
Matt Mackall
|
r17733 | ((size != st.st_size and size != st.st_size & _rangemask) | ||
Siddharth Agarwal
|
r18034 | or ((mode ^ st.st_mode) & 0100 and checkexec)) | ||
Benoit Boissinot
|
r10968 | or size == -2 # other parent | ||
Siddharth Agarwal
|
r18034 | or fn in copymap): | ||
Matt Mackall
|
r5003 | madd(fn) | ||
Siddharth Agarwal
|
r19651 | elif time != mtime and time != mtime & _rangemask: | ||
Matt Mackall
|
r5003 | ladd(fn) | ||
Siddharth Agarwal
|
r18034 | elif mtime == lastnormaltime: | ||
Mads Kiilerich
|
r24181 | # fn may have just been marked as normal and it may have | ||
# changed in the same second without changing its size. | ||||
# This can happen if we quickly do multiple commits. | ||||
Adrian Buehlmann
|
r13763 | # Force lookup, so we don't miss such a racy file change. | ||
Greg Ward
|
r13704 | ladd(fn) | ||
Matt Mackall
|
r6753 | elif listclean: | ||
Matt Mackall
|
r5003 | cadd(fn) | ||
Matt Mackall
|
r6590 | elif state == 'm': | ||
Matt Mackall
|
r5003 | madd(fn) | ||
Matt Mackall
|
r6590 | elif state == 'a': | ||
Matt Mackall
|
r5003 | aadd(fn) | ||
Matt Mackall
|
r6590 | elif state == 'r': | ||
Matt Mackall
|
r5003 | radd(fn) | ||
mason@suse.com
|
r1183 | |||
Martin von Zweigbergk
|
r22913 | return (lookup, scmutil.status(modified, added, removed, deleted, | ||
unknown, ignored, clean)) | ||||
Siddharth Agarwal
|
r21984 | |||
def matches(self, match): | ||||
''' | ||||
return files in the dirstate (in whatever state) filtered by match | ||||
''' | ||||
dmap = self._map | ||||
if match.always(): | ||||
return dmap.keys() | ||||
files = match.files() | ||||
if match.matchfn == match.exact: | ||||
# fast path -- filter the other way around, since typically files is | ||||
# much smaller than dmap | ||||
return [f for f in files if f in dmap] | ||||
if not match.anypats() and util.all(fn in dmap for fn in files): | ||||
# fast path -- all the values are known to be files, so just return | ||||
# that | ||||
return list(files) | ||||
return [f for f in dmap if match(f)] | ||||