dirstate.py
1260 lines
| 45.0 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 | |||
Gregory Szorc
|
r27503 | from __future__ import absolute_import | ||
Laurent Charignon
|
r27670 | import collections | ||
Gregory Szorc
|
r27503 | import errno | ||
import os | ||||
import stat | ||||
from .i18n import _ | ||||
from .node import nullid | ||||
from . import ( | ||||
encoding, | ||||
error, | ||||
match as matchmod, | ||||
osutil, | ||||
parsers, | ||||
pathutil, | ||||
Pulkit Goyal
|
r30304 | pycompat, | ||
Gregory Szorc
|
r27503 | scmutil, | ||
util, | ||||
) | ||||
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) | ||||
FUJIWARA Katsunori
|
r26634 | def _getfsnow(vfs): | ||
'''Get "now" timestamp on filesystem''' | ||||
tmpfd, tmpname = vfs.mkstemp() | ||||
try: | ||||
Matt Mackall
|
r27016 | return os.fstat(tmpfd).st_mtime | ||
FUJIWARA Katsunori
|
r26634 | finally: | ||
os.close(tmpfd) | ||||
vfs.unlink(tmpname) | ||||
Laurent Charignon
|
r27588 | def nonnormalentries(dmap): | ||
'''Compute the nonnormal dirstate entries from the dmap''' | ||||
Laurent Charignon
|
r27593 | try: | ||
return parsers.nonnormalentries(dmap) | ||||
except AttributeError: | ||||
return set(fname for fname, e in dmap.iteritems() | ||||
if e[0] != 'n' or e[3] == -1) | ||||
Laurent Charignon
|
r27588 | |||
FUJIWARA Katsunori
|
r26635 | def _trypending(root, vfs, filename): | ||
'''Open file to be read according to HG_PENDING environment variable | ||||
This opens '.pending' of specified 'filename' only when HG_PENDING | ||||
is equal to 'root'. | ||||
This returns '(fp, is_pending_opened)' tuple. | ||||
''' | ||||
if root == os.environ.get('HG_PENDING'): | ||||
try: | ||||
return (vfs('%s.pending' % filename), True) | ||||
except IOError as inst: | ||||
if inst.errno != errno.ENOENT: | ||||
raise | ||||
return (vfs(filename), False) | ||||
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) | ||||
FUJIWARA Katsunori
|
r24833 | self._rootdir = pathutil.normasprefix(root) | ||
Yuya Nishihara
|
r26294 | # internal config: ui.forcecwd | ||
forcecwd = ui.config('ui', 'forcecwd') | ||||
if forcecwd: | ||||
self._cwd = forcecwd | ||||
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 | ||
FUJIWARA Katsunori
|
r25226 | self._filename = 'dirstate' | ||
FUJIWARA Katsunori
|
r26633 | self._pendingfilename = '%s.pending' % self._filename | ||
Mateusz Kwapich
|
r29772 | self._plchangecallbacks = {} | ||
self._origpl = None | ||||
Durham Goode
|
r22404 | |||
Mads Kiilerich
|
r26781 | # for consistent view between _pl() and _read() invocations | ||
FUJIWARA Katsunori
|
r26635 | self._pendingmode = None | ||
Durham Goode
|
r22404 | 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 | ||||
Laurent Charignon
|
r27589 | def _nonnormalset(self): | ||
return nonnormalentries(self._map) | ||||
@propertycache | ||||
Siddharth Agarwal
|
r24540 | def _filefoldmap(self): | ||
Siddharth Agarwal
|
r24610 | try: | ||
makefilefoldmap = parsers.make_file_foldmap | ||||
except AttributeError: | ||||
pass | ||||
else: | ||||
return makefilefoldmap(self._map, util.normcasespec, | ||||
util.normcasefallback) | ||||
Matt Mackall
|
r8261 | 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 | ||
Siddharth Agarwal
|
r24540 | f['.'] = '.' # prevents useless util.fspath() invocation | ||
return f | ||||
@propertycache | ||||
def _dirfoldmap(self): | ||||
f = {} | ||||
normcase = util.normcase | ||||
Matt Mackall
|
r16302 | for name in self._dirs: | ||
Siddharth Agarwal
|
r22782 | f[normcase(name)] = name | ||
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" | ||
Gregory Szorc
|
r25660 | except IOError as inst: | ||
Pierre-Yves David
|
r15799 | if inst.errno != errno.ENOENT: | ||
raise | ||||
Matt Mackall
|
r8261 | return "default" | ||
@propertycache | ||||
def _pl(self): | ||||
try: | ||||
FUJIWARA Katsunori
|
r26635 | fp = self._opendirstatefile() | ||
Dan Villiom Podlaski Christiansen
|
r13400 | 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: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('working directory state appears damaged!')) | ||
Gregory Szorc
|
r25660 | except IOError as err: | ||
Matt Mackall
|
r10282 | if err.errno != errno.ENOENT: | ||
raise | ||||
Matt Mackall
|
r8261 | return [nullid, nullid] | ||
@propertycache | ||||
def _dirs(self): | ||||
Drew Gottlieb
|
r24635 | return util.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): | ||
Laurent Charignon
|
r27594 | files = self._ignorefiles() | ||
Durham Goode
|
r25216 | if not files: | ||
return util.never | ||||
pats = ['include:%s' % f for f in files] | ||||
return matchmod.match(self._root, '', [], pats, warn=self._ui.warn) | ||||
Matt Mackall
|
r8261 | |||
@propertycache | ||||
def _slash(self): | ||||
Pulkit Goyal
|
r30304 | return self._ui.configbool('ui', 'slash') and pycompat.ossep != '/' | ||
Matt Mackall
|
r8261 | |||
@propertycache | ||||
def _checklink(self): | ||||
return util.checklink(self._root) | ||||
@propertycache | ||||
def _checkexec(self): | ||||
return util.checkexec(self._root) | ||||
@propertycache | ||||
def _checkcase(self): | ||||
Martin von Zweigbergk
|
r29889 | return not util.fscasesensitive(self._join('.hg')) | ||
Matt Mackall
|
r8261 | |||
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): | ||
Yuya Nishihara
|
r26293 | '''Return the path from which a canonical path is calculated. | ||
This path should be used to resolve file patterns or to convert | ||||
canonical paths back to file paths for display. It shouldn't be | ||||
used to get real file paths. Use vfs functions instead. | ||||
''' | ||||
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] | ||
Mateusz Kwapich
|
r29772 | if self._origpl is None: | ||
self._origpl = self._pl | ||||
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) | ||
FUJIWARA Katsunori
|
r29302 | f = self._opener('branch', 'w', atomictemp=True, checkambig=True) | ||
Idan Kamara
|
r16472 | 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 | |||
FUJIWARA Katsunori
|
r26635 | def _opendirstatefile(self): | ||
fp, mode = _trypending(self._root, self._opener, self._filename) | ||||
if self._pendingmode is not None and self._pendingmode != mode: | ||||
fp.close() | ||||
raise error.Abort(_('working directory state may be ' | ||||
'changed parallelly')) | ||||
self._pendingmode = mode | ||||
return fp | ||||
Matt Mackall
|
r4615 | def _read(self): | ||
Matt Mackall
|
r4614 | self._map = {} | ||
self._copymap = {} | ||||
Matt Mackall
|
r4607 | try: | ||
FUJIWARA Katsunori
|
r26635 | fp = self._opendirstatefile() | ||
FUJIWARA Katsunori
|
r25227 | try: | ||
st = fp.read() | ||||
finally: | ||||
fp.close() | ||||
Gregory Szorc
|
r25660 | except IOError as err: | ||
Matt Mackall
|
r10282 | if err.errno != errno.ENOENT: | ||
raise | ||||
Matt Mackall
|
r4607 | return | ||
if not st: | ||||
return | ||||
Siddharth Agarwal
|
r25585 | if util.safehasattr(parsers, 'dict_new_presized'): | ||
# Make an estimate of the number of files in the dirstate based on | ||||
# its size. From a linear regression on a set of real-world repos, | ||||
# all over 10,000 files, the size of a dirstate entry is 85 | ||||
# bytes. The cost of resizing is significantly higher than the cost | ||||
# of filling in a larger presized dict, so subtract 20% from the | ||||
# size. | ||||
# | ||||
# This heuristic is imperfect in many ways, so in a future dirstate | ||||
# format update it makes sense to just record the number of entries | ||||
# on write. | ||||
self._map = parsers.dict_new_presized(len(st) / 71) | ||||
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): | ||
Siddharth Agarwal
|
r24540 | for a in ("_map", "_copymap", "_filefoldmap", "_dirfoldmap", "_branch", | ||
Laurent Charignon
|
r27590 | "_pl", "_dirs", "_ignore", "_nonnormalset"): | ||
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 | ||
Mateusz Kwapich
|
r29772 | self._origpl = None | ||
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 | |||
Mateusz Kwapich
|
r26887 | if "_filefoldmap" in self.__dict__: | ||
normed = util.normcase(f) | ||||
if normed in self._filefoldmap: | ||||
del self._filefoldmap[normed] | ||||
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: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('directory %r already in dirstate') % f) | ||
Matt Mackall
|
r6767 | # shadows | ||
Drew Gottlieb
|
r24635 | for d in util.finddirs(f): | ||
Matt Mackall
|
r6767 | if d in self._dirs: | ||
break | ||||
if d in self._map and self[d] != 'r': | ||||
Pierre-Yves David
|
r26587 | raise error.Abort( | ||
Matt Mackall
|
r6767 | _('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) | ||
Laurent Charignon
|
r27590 | if state != 'n' or mtime == -1: | ||
self._nonnormalset.add(f) | ||||
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)) | ||
Matt Mackall
|
r27016 | mtime = 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] | ||
Laurent Charignon
|
r27590 | if f in self._nonnormalset: | ||
self._nonnormalset.remove(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] | ||||
Laurent Charignon
|
r27590 | if f in self._nonnormalset: | ||
self._nonnormalset.remove(f) | ||||
Alexis S. L. Carvalho
|
r5210 | |||
Benoit Boissinot
|
r10968 | def otherparent(self, f): | ||
'''Mark as coming from the other parent, always dirty.''' | ||||
if self._pl[1] == nullid: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("setting %r to other parent " | ||
Benoit Boissinot
|
r10968 | "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) | ||
Laurent Charignon
|
r27590 | self._nonnormalset.add(f) | ||
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] | ||||
Laurent Charignon
|
r27590 | if f in self._nonnormalset: | ||
self._nonnormalset.remove(f) | ||||
Mateusz Kwapich
|
r29247 | if f in self._copymap: | ||
del self._copymap[f] | ||||
mpm@selenic.com
|
r1089 | |||
Siddharth Agarwal
|
r24538 | def _discoverpath(self, path, normed, ignoremissing, exists, storemap): | ||
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, False, ignoremissing, None) | ||||
folded = d + "/" + f | ||||
else: | ||||
# No path components, preserve original case | ||||
folded = path | ||||
else: | ||||
# recursively normalize leading directory components | ||||
# against dirstate | ||||
if '/' in normed: | ||||
d, f = normed.rsplit('/', 1) | ||||
d = self._normalize(d, False, ignoremissing, True) | ||||
r = self._root + "/" + d | ||||
folded = d + "/" + util.fspath(f, r) | ||||
else: | ||||
folded = util.fspath(normed, self._root) | ||||
storemap[normed] = folded | ||||
return folded | ||||
Siddharth Agarwal
|
r24539 | def _normalizefile(self, path, isknown, ignoremissing=False, exists=None): | ||
Matt Mackall
|
r15488 | normed = util.normcase(path) | ||
Siddharth Agarwal
|
r24540 | folded = self._filefoldmap.get(normed, None) | ||
Matt Mackall
|
r13717 | if folded is None: | ||
Patrick Mezard
|
r16542 | if isknown: | ||
Matt Mackall
|
r13717 | folded = path | ||
Petr Kodl
|
r7068 | else: | ||
Siddharth Agarwal
|
r24539 | folded = self._discoverpath(path, normed, ignoremissing, exists, | ||
Siddharth Agarwal
|
r24540 | self._filefoldmap) | ||
Siddharth Agarwal
|
r24539 | return folded | ||
Matt Mackall
|
r16302 | |||
Patrick Mezard
|
r16542 | def _normalize(self, path, isknown, ignoremissing=False, exists=None): | ||
Matt Mackall
|
r15488 | normed = util.normcase(path) | ||
Siddharth Agarwal
|
r24561 | folded = self._filefoldmap.get(normed, None) | ||
if folded is None: | ||||
folded = self._dirfoldmap.get(normed, None) | ||||
Matt Mackall
|
r13717 | if folded is None: | ||
Patrick Mezard
|
r16542 | if isknown: | ||
Matt Mackall
|
r13717 | folded = path | ||
Petr Kodl
|
r7068 | else: | ||
Siddharth Agarwal
|
r24540 | # store discovered result in dirfoldmap so that future | ||
# normalizefile calls don't start matching directories | ||||
Siddharth Agarwal
|
r24538 | folded = self._discoverpath(path, normed, ignoremissing, exists, | ||
Siddharth Agarwal
|
r24540 | self._dirfoldmap) | ||
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 = {} | ||||
Laurent Charignon
|
r27590 | self._nonnormalset = set() | ||
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): | ||
Pierre-Yves David
|
r25448 | if changedfiles is None: | ||
Christian Delahousse
|
r27176 | # Rebuild entire dirstate | ||
Pierre-Yves David
|
r25448 | changedfiles = allfiles | ||
Christian Delahousse
|
r27176 | lastnormaltime = self._lastnormaltime | ||
self.clear() | ||||
self._lastnormaltime = lastnormaltime | ||||
Mateusz Kwapich
|
r29772 | if self._origpl is None: | ||
self._origpl = self._pl | ||||
Matt Mackall
|
r4614 | self._pl = (parent, nullid) | ||
Mateusz Kwapich
|
r30026 | for f in changedfiles: | ||
if f in allfiles: | ||||
self.normallookup(f) | ||||
else: | ||||
self.drop(f) | ||||
Matt Mackall
|
r4903 | self._dirty = True | ||
mpm@selenic.com
|
r1089 | |||
Pierre-Yves David
|
r29673 | def write(self, tr): | ||
Matt Mackall
|
r4612 | if not self._dirty: | ||
Benoit Boissinot
|
r1794 | return | ||
FUJIWARA Katsunori
|
r21931 | |||
FUJIWARA Katsunori
|
r26634 | filename = self._filename | ||
Pierre-Yves David
|
r29673 | if tr: | ||
FUJIWARA Katsunori
|
r26634 | # 'dirstate.write()' is not only for writing in-memory | ||
# changes out, but also for dropping ambiguous timestamp. | ||||
# delayed writing re-raise "ambiguous timestamp issue". | ||||
# See also the wiki page below for detail: | ||||
# https://www.mercurial-scm.org/wiki/DirstateTransactionPlan | ||||
# emulate dropping timestamp in 'parsers.pack_dirstate' | ||||
FUJIWARA Katsunori
|
r26747 | now = _getfsnow(self._opener) | ||
FUJIWARA Katsunori
|
r26634 | dmap = self._map | ||
for f, e in dmap.iteritems(): | ||||
if e[0] == 'n' and e[3] == now: | ||||
dmap[f] = dirstatetuple(e[0], e[1], e[2], -1) | ||||
Laurent Charignon
|
r27590 | self._nonnormalset.add(f) | ||
FUJIWARA Katsunori
|
r26634 | |||
# emulate that all 'dirstate.normal' results are written out | ||||
self._lastnormaltime = 0 | ||||
# delay writing in-memory changes out | ||||
tr.addfilegenerator('dirstate', (self._filename,), | ||||
self._writedirstate, location='plain') | ||||
return | ||||
FUJIWARA Katsunori
|
r29301 | st = self._opener(filename, "w", atomictemp=True, checkambig=True) | ||
FUJIWARA Katsunori
|
r26521 | self._writedirstate(st) | ||
Mateusz Kwapich
|
r29772 | def addparentchangecallback(self, category, callback): | ||
"""add a callback to be called when the wd parents are changed | ||||
Callback will be called with the following arguments: | ||||
dirstate, (oldp1, oldp2), (newp1, newp2) | ||||
Category is a unique identifier to allow overwriting an old callback | ||||
with a newer callback. | ||||
""" | ||||
self._plchangecallbacks[category] = callback | ||||
FUJIWARA Katsunori
|
r26521 | def _writedirstate(self, st): | ||
Mateusz Kwapich
|
r29772 | # notify callbacks about parents change | ||
if self._origpl is not None and self._origpl != self._pl: | ||||
for c, callback in sorted(self._plchangecallbacks.iteritems()): | ||||
callback(self, self._origpl, self._pl) | ||||
self._origpl = None | ||||
Adrian Buehlmann
|
r9509 | # use the modification time of the newly created temporary file as the | ||
# filesystem's notion of 'now' | ||||
Matt Mackall
|
r27016 | now = util.fstat(st).st_mtime & _rangemask | ||
Matt Mackall
|
r27397 | |||
# 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) | ||||
if delaywrite > 0: | ||||
Matt Mackall
|
r27398 | # do we have any files to delay for? | ||
for f, e in self._map.iteritems(): | ||||
if e[0] == 'n' and e[3] == now: | ||||
import time # to avoid useless import | ||||
Matt Mackall
|
r27399 | # rather than sleep n seconds, sleep until the next | ||
# multiple of n seconds | ||||
clock = time.time() | ||||
start = int(clock) - (int(clock) % delaywrite) | ||||
end = start + delaywrite | ||||
time.sleep(end - clock) | ||||
Mads Kiilerich
|
r30224 | now = end # trust our estimate that the end is near now | ||
Matt Mackall
|
r27398 | break | ||
Matt Mackall
|
r27397 | |||
Mads Kiilerich
|
r21026 | st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now)) | ||
Laurent Charignon
|
r27590 | self._nonnormalset = nonnormalentries(self._map) | ||
Mads Kiilerich
|
r21026 | 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 | ||||
Drew Gottlieb
|
r24635 | for p in util.finddirs(f): | ||
Matt Mackall
|
r6767 | if self._ignore(p): | ||
Alexis S. L. Carvalho
|
r6032 | return True | ||
return False | ||||
Laurent Charignon
|
r27594 | def _ignorefiles(self): | ||
files = [] | ||||
if os.path.exists(self._join('.hgignore')): | ||||
files.append(self._join('.hgignore')) | ||||
for name, path in self._ui.configitems("ui"): | ||||
if name == 'ignore' or name.startswith('ignore.'): | ||||
# 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))) | ||||
return files | ||||
Laurent Charignon
|
r27670 | def _ignorefileandline(self, f): | ||
files = collections.deque(self._ignorefiles()) | ||||
visited = set() | ||||
while files: | ||||
i = files.popleft() | ||||
patterns = matchmod.readpatternfile(i, self._ui.warn, | ||||
sourceinfo=True) | ||||
for pattern, lineno, line in patterns: | ||||
kind, p = matchmod._patsplit(pattern, 'glob') | ||||
if kind == "subinclude": | ||||
if p not in visited: | ||||
files.append(p) | ||||
continue | ||||
m = matchmod.match(self._root, '', [], [pattern], | ||||
warn=self._ui.warn) | ||||
if m(f): | ||||
return (i, lineno, line) | ||||
visited.add(i) | ||||
return (None, -1, "") | ||||
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 | 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 | |||
Martin von Zweigbergk
|
r24448 | if not match.isexact() 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: | ||
Siddharth Agarwal
|
r24521 | 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
|
r24523 | # constructing the foldmap is expensive, so don't do it for the | ||
# common case where files is ['.'] | ||||
if normalize and ff != '.': | ||||
Siddharth Agarwal
|
r24522 | nf = normalize(ff, False, True) | ||
Siddharth Agarwal
|
r18032 | else: | ||
Siddharth Agarwal
|
r24522 | nf = 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) | ||||
Matt Harbison
|
r24537 | foundadd((nf, ff)) | ||
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 | ||
Gregory Szorc
|
r25660 | except OSError as inst: # nf not found on disk - it is dirstate only | ||
Mads Kiilerich
|
r21115 | 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: | ||
Drew Gottlieb
|
r24635 | alldirs = util.dirs(dmap) | ||
Martin von Zweigbergk
|
r23375 | if nf in alldirs: | ||
if matchedir: | ||||
matchedir(nf) | ||||
notfoundadd(nf) | ||||
Matt Mackall
|
r8677 | else: | ||
Matt Mackall
|
r8680 | badfn(ff, inst.strerror) | ||
Matt Mackall
|
r6820 | |||
Matt Harbison
|
r25877 | # Case insensitive filesystems cannot rely on lstat() failing to detect | ||
# a case-only rename. Prune the stat object for any file that does not | ||||
# match the case in the filesystem, if there are multiple files that | ||||
# normalize to the same path. | ||||
if match.isexact() and self._checkcase: | ||||
normed = {} | ||||
for f, st in results.iteritems(): | ||||
if st is None: | ||||
continue | ||||
nc = util.normcase(f) | ||||
paths = normed.get(nc) | ||||
if paths is None: | ||||
paths = set() | ||||
normed[nc] = paths | ||||
paths.add(f) | ||||
for norm, paths in normed.iteritems(): | ||||
if len(paths) > 1: | ||||
for path in paths: | ||||
folded = self._discoverpath(path, norm, True, None, | ||||
self._dirfoldmap) | ||||
if path != folded: | ||||
results[path] = None | ||||
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 | ||||
Martin von Zweigbergk
|
r24448 | if match.isexact(): # match.exact | ||
Siddharth Agarwal
|
r19173 | exact = True | ||
dirignore = util.always # skip step 2 | ||||
Martin von Zweigbergk
|
r25234 | elif match.prefix(): # match.match, no patterns | ||
Siddharth Agarwal
|
r19173 | skipstep3 = True | ||
if not exact and self._checkcase: | ||||
normalize = self._normalize | ||||
Siddharth Agarwal
|
r24541 | normalizefile = self._normalizefile | ||
Siddharth Agarwal
|
r19173 | skipstep3 = False | ||
else: | ||||
Siddharth Agarwal
|
r24560 | normalize = self._normalize | ||
Siddharth Agarwal
|
r24541 | normalizefile = None | ||
Siddharth Agarwal
|
r19173 | |||
# step 1: find all explicit files | ||||
results, work, dirsnotfound = self._walkexplicit(match, subrepos) | ||||
Siddharth Agarwal
|
r19172 | skipstep3 = skipstep3 and not (work or dirsnotfound) | ||
Matt Harbison
|
r24537 | work = [d for d in work if not dirignore(d[0])] | ||
Siddharth Agarwal
|
r19171 | |||
Matt Mackall
|
r6826 | # step 2: visit subdirectories | ||
Siddharth Agarwal
|
r24560 | def traverse(work, alreadynormed): | ||
Siddharth Agarwal
|
r24559 | wadd = work.append | ||
while work: | ||||
Siddharth Agarwal
|
r24560 | nd = work.pop() | ||
Siddharth Agarwal
|
r24559 | skip = None | ||
if nd == '.': | ||||
nd = '' | ||||
Siddharth Agarwal
|
r18032 | else: | ||
Siddharth Agarwal
|
r24559 | skip = '.hg' | ||
try: | ||||
entries = listdir(join(nd), stat=True, skip=skip) | ||||
Gregory Szorc
|
r25660 | except OSError as inst: | ||
Siddharth Agarwal
|
r24559 | if inst.errno in (errno.EACCES, errno.ENOENT): | ||
match.bad(self.pathto(nd), inst.strerror) | ||||
continue | ||||
raise | ||||
for f, kind, st in entries: | ||||
if normalizefile: | ||||
# even though f might be a directory, we're only | ||||
# interested in comparing it to files currently in the | ||||
# dmap -- therefore normalizefile is enough | ||||
nf = normalizefile(nd and (nd + "/" + f) or f, True, | ||||
True) | ||||
else: | ||||
nf = nd and (nd + "/" + f) or f | ||||
if nf not in results: | ||||
if kind == dirkind: | ||||
if not ignore(nf): | ||||
if matchtdir: | ||||
matchtdir(nf) | ||||
Siddharth Agarwal
|
r24560 | wadd(nf) | ||
Siddharth Agarwal
|
r24559 | if nf in dmap and (matchalways or matchfn(nf)): | ||
results[nf] = None | ||||
elif kind == regkind or kind == lnkkind: | ||||
if nf in dmap: | ||||
if matchalways or matchfn(nf): | ||||
results[nf] = st | ||||
Siddharth Agarwal
|
r24560 | elif ((matchalways or matchfn(nf)) | ||
and not ignore(nf)): | ||||
# unknown file -- normalize if necessary | ||||
if not alreadynormed: | ||||
nf = normalize(nf, False, True) | ||||
Siddharth Agarwal
|
r24559 | results[nf] = st | ||
elif nf in dmap and (matchalways or matchfn(nf)): | ||||
Matt Mackall
|
r6829 | results[nf] = None | ||
Siddharth Agarwal
|
r24559 | |||
Siddharth Agarwal
|
r24560 | for nd, d in work: | ||
# alreadynormed means that processwork doesn't have to do any | ||||
# expensive directory normalization | ||||
alreadynormed = not normalize or nd == d | ||||
traverse([d], alreadynormed) | ||||
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): | ||||
Martin von Zweigbergk
|
r24621 | # If a stat for the same file was already added with a | ||
# different case, don't add one for this, since that would | ||||
# make it appear as if the file exists under both names | ||||
# on disk. | ||||
Matt Mackall
|
r24632 | if (normalizefile and | ||
normalizefile(nf, True, True) in results): | ||||
Martin von Zweigbergk
|
r24621 | results[nf] = None | ||
Durham Goode
|
r18625 | # Report ignored items in the dmap as long as they are not | ||
# under a symlink directory. | ||||
Martin von Zweigbergk
|
r24621 | elif 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 | ||
Bryan O'Sullivan
|
r26984 | 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': | ||||
Alexis S. L. Carvalho
|
r6257 | if (size >= 0 and | ||
Matt Mackall
|
r17733 | ((size != st.st_size and size != st.st_size & _rangemask) | ||
Gregory Szorc
|
r25658 | or ((mode ^ st.st_mode) & 0o100 and checkexec)) | ||
Benoit Boissinot
|
r10968 | or size == -2 # other parent | ||
Siddharth Agarwal
|
r18034 | or fn in copymap): | ||
Matt Mackall
|
r5003 | madd(fn) | ||
Matt Mackall
|
r27016 | elif time != st.st_mtime and time != st.st_mtime & _rangemask: | ||
Matt Mackall
|
r5003 | ladd(fn) | ||
Matt Mackall
|
r27016 | elif st.st_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() | ||||
Martin von Zweigbergk
|
r24448 | if match.isexact(): | ||
Siddharth Agarwal
|
r21984 | # 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] | ||||
Martin von Zweigbergk
|
r25275 | if match.prefix() and all(fn in dmap for fn in files): | ||
Siddharth Agarwal
|
r21984 | # 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)] | ||||
FUJIWARA Katsunori
|
r26632 | |||
FUJIWARA Katsunori
|
r26746 | def _actualfilename(self, tr): | ||
if tr: | ||||
FUJIWARA Katsunori
|
r26633 | return self._pendingfilename | ||
else: | ||||
return self._filename | ||||
Mateusz Kwapich
|
r29189 | def savebackup(self, tr, suffix='', prefix=''): | ||
FUJIWARA Katsunori
|
r26632 | '''Save current dirstate into backup file with suffix''' | ||
Mateusz Kwapich
|
r29273 | assert len(suffix) > 0 or len(prefix) > 0 | ||
FUJIWARA Katsunori
|
r26746 | filename = self._actualfilename(tr) | ||
FUJIWARA Katsunori
|
r26633 | |||
# use '_writedirstate' instead of 'write' to write changes certainly, | ||||
# because the latter omits writing out if transaction is running. | ||||
# output file will be used to create backup of dirstate at this point. | ||||
FUJIWARA Katsunori
|
r29301 | self._writedirstate(self._opener(filename, "w", atomictemp=True, | ||
checkambig=True)) | ||||
FUJIWARA Katsunori
|
r26633 | |||
if tr: | ||||
# ensure that subsequent tr.writepending returns True for | ||||
# changes written out above, even if dirstate is never | ||||
# changed after this | ||||
tr.addfilegenerator('dirstate', (self._filename,), | ||||
self._writedirstate, location='plain') | ||||
# ensure that pending file written above is unlinked at | ||||
# failure, even if tr.writepending isn't invoked until the | ||||
# end of this transaction | ||||
tr.registertmp(filename, location='plain') | ||||
Mateusz Kwapich
|
r29269 | self._opener.write(prefix + self._filename + suffix, | ||
Mateusz Kwapich
|
r29189 | self._opener.tryread(filename)) | ||
FUJIWARA Katsunori
|
r26632 | |||
Mateusz Kwapich
|
r29189 | def restorebackup(self, tr, suffix='', prefix=''): | ||
FUJIWARA Katsunori
|
r26632 | '''Restore dirstate by backup file with suffix''' | ||
Mateusz Kwapich
|
r29273 | assert len(suffix) > 0 or len(prefix) > 0 | ||
FUJIWARA Katsunori
|
r26632 | # this "invalidate()" prevents "wlock.release()" from writing | ||
# changes of dirstate out after restoring from backup file | ||||
self.invalidate() | ||||
FUJIWARA Katsunori
|
r26746 | filename = self._actualfilename(tr) | ||
Mateusz Kwapich
|
r29269 | # using self._filename to avoid having "pending" in the backup filename | ||
FUJIWARA Katsunori
|
r29351 | self._opener.rename(prefix + self._filename + suffix, filename, | ||
checkambig=True) | ||||
FUJIWARA Katsunori
|
r26632 | |||
Mateusz Kwapich
|
r29189 | def clearbackup(self, tr, suffix='', prefix=''): | ||
FUJIWARA Katsunori
|
r26632 | '''Clear backup file with suffix''' | ||
Mateusz Kwapich
|
r29273 | assert len(suffix) > 0 or len(prefix) > 0 | ||
Mateusz Kwapich
|
r29269 | # using self._filename to avoid having "pending" in the backup filename | ||
self._opener.unlink(prefix + self._filename + suffix) | ||||