##// END OF EJS Templates
convert: keep branch switching merges with ancestors (issue3340)...
convert: keep branch switching merges with ancestors (issue3340) When running convert with a filemap, merge parents which are ancestors of other parents are ignored. This is hardly a problem when parents belong to the same branch, but the result could be confusing when named branches are involved. With: -o-a1-a2-a3... <- A \ \ b1-b2-b3...-m- <- B If all b* revisions are discarded, it is useful to preserve 'm' even if it is empty after filtering to record the branch switch. This patch makes filemap preserve "ancestor parents" if there is no "non-ancestor parent" on the same branch than the merge revision. Remarks: - I am not completely convinced by the reasons given above and those detailed by Matt in this thread: http://selenic.com/pipermail/mercurial-devel/2012-May/040627.html The properties we try to preserve are not clearly defined. That said, I know this patch already helped someone on IRC and the tests output look reasonable. - This is a new version of the original "convert: filemap must preserve fast-forward merges" patch. It has exactly the same output for 2 parents merges, the additional complexity is here to handle more than two parents.

File last commit:

r17037:41996612 default
r17103:5146de7b default
Show More
scmutil.py
886 lines | 29.6 KiB | text/x-python | PythonLexer
Adrian Buehlmann
add: introduce a warning message for non-portable filenames (issue2756) (BC)...
r13962 # scmutil.py - Mercurial core utility functions
#
# Copyright Matt Mackall <mpm@selenic.com>
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.
from i18n import _
FUJIWARA Katsunori
i18n: use UTF-8 string to lower filename for case collision check...
r14980 import util, error, osutil, revset, similar, encoding
Matt Mackall
scmutil: fold in wdutil
r14320 import match as matchmod
Steve Borho
scmutil: add missing import of re...
r14861 import os, errno, re, stat, sys, glob
Adrian Buehlmann
add: introduce a warning message for non-portable filenames (issue2756) (BC)...
r13962
Matt Mackall
scmutil: unify some 'no changes found' messages...
r15993 def nochangesfound(ui, secretlist=None):
'''report no changes for push/pull'''
if secretlist:
ui.status(_("no changes found (ignored %d secret changesets)\n")
% len(secretlist))
else:
ui.status(_("no changes found\n"))
Adrian Buehlmann
move checkfilename from util to scmutil...
r13974 def checkfilename(f):
'''Check that the filename f is an acceptable filename for a tracked file'''
if '\r' in f or '\n' in f:
raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
Adrian Buehlmann
add: introduce a warning message for non-portable filenames (issue2756) (BC)...
r13962 def checkportable(ui, f):
'''Check if filename f is portable and warn or abort depending on config'''
Adrian Buehlmann
move checkfilename from util to scmutil...
r13974 checkfilename(f)
Adrian Buehlmann
scmutil: introduce casecollisionauditor...
r14138 abort, warn = checkportabilityalert(ui)
if abort or warn:
Adrian Buehlmann
add: introduce a warning message for non-portable filenames (issue2756) (BC)...
r13962 msg = util.checkwinfilename(f)
if msg:
Adrian Buehlmann
scmutil: introduce casecollisionauditor...
r14138 msg = "%s: %r" % (msg, f)
if abort:
raise util.Abort(msg)
ui.warn(_("warning: %s\n") % msg)
Kevin Gessner
add: notify when adding a file that would cause a case-folding collision...
r14068
Kevin Gessner
scmutil: refactor ui.portablefilenames processing...
r14067 def checkportabilityalert(ui):
'''check if the user's config requests nothing, a warning, or abort for
non-portable filenames'''
val = ui.config('ui', 'portablefilenames', 'warn')
lval = val.lower()
bval = util.parsebool(val)
abort = os.name == 'nt' or lval == 'abort'
warn = bval or lval == 'warn'
if bval is None and not (warn or abort or lval == 'ignore'):
Adrian Buehlmann
add: introduce a warning message for non-portable filenames (issue2756) (BC)...
r13962 raise error.ConfigError(
_("ui.portablefilenames value is invalid ('%s')") % val)
Kevin Gessner
scmutil: refactor ui.portablefilenames processing...
r14067 return abort, warn
Adrian Buehlmann
scmutil: introduce casecollisionauditor...
r14138 class casecollisionauditor(object):
def __init__(self, ui, abort, existingiter):
self._ui = ui
self._abort = abort
self._map = {}
for f in existingiter:
FUJIWARA Katsunori
i18n: use UTF-8 string to lower filename for case collision check...
r14980 self._map[encoding.lower(f)] = f
Kevin Gessner
scmutil: refactor ui.portablefilenames processing...
r14067
Adrian Buehlmann
scmutil: introduce casecollisionauditor...
r14138 def __call__(self, f):
FUJIWARA Katsunori
i18n: use UTF-8 string to lower filename for case collision check...
r14980 fl = encoding.lower(f)
Adrian Buehlmann
scmutil: introduce casecollisionauditor...
r14138 map = self._map
if fl in map and map[fl] != f:
msg = _('possible case-folding collision for %s') % f
if self._abort:
raise util.Abort(msg)
self._ui.warn(_("warning: %s\n") % msg)
map[fl] = f
Adrian Buehlmann
move opener from util to scmutil
r13970
Adrian Buehlmann
rename path_auditor to pathauditor...
r14220 class pathauditor(object):
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 '''ensure that a filesystem path contains no banned components.
the following properties of a path are checked:
- ends with a directory separator
- under top-level .hg
- starts at the root of a windows drive
- contains ".."
- traverses a symlink (e.g. a/symlink_here/b)
- inside a nested repository (a callback can be used to approve
some nested repositories, e.g., subrepositories)
'''
def __init__(self, root, callback=None):
self.audited = set()
self.auditeddir = set()
self.root = root
self.callback = callback
FUJIWARA Katsunori
pathauditor: switch normcase logic according to case sensitivity of filesystem...
r15666 if os.path.lexists(root) and not util.checkcase(root):
self.normcase = util.normcase
else:
self.normcase = lambda x: x
Adrian Buehlmann
move path_auditor from util to scmutil
r13972
def __call__(self, path):
'''Check the relative path.
path may contain a pattern (e.g. foodir/**.txt)'''
FUJIWARA Katsunori
windows: force specified path to be audited in localpath form...
r15721 path = util.localpath(path)
FUJIWARA Katsunori
pathauditor: switch normcase logic according to case sensitivity of filesystem...
r15666 normpath = self.normcase(path)
FUJIWARA Katsunori
pathauditor: use normcase()-ed path for audit result cache...
r15664 if normpath in self.audited:
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 return
# AIX ignores "/" at end of path, others raise EISDIR.
if util.endswithsep(path):
raise util.Abort(_("path ends in directory separator: %s") % path)
FUJIWARA Katsunori
pathauditor: preserve case in abort messages...
r15665 parts = util.splitpath(path)
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 if (os.path.splitdrive(path)[0]
or parts[0].lower() in ('.hg', '.hg.', '')
or os.pardir in parts):
raise util.Abort(_("path contains illegal component: %s") % path)
if '.hg' in path.lower():
lparts = [p.lower() for p in parts]
for p in '.hg', '.hg.':
if p in lparts[1:]:
pos = lparts.index(p)
base = os.path.join(*parts[:pos])
Mads Kiilerich
cmdutil: don't use repr on paths in pathauditor - it looks strange on windows
r15436 raise util.Abort(_("path '%s' is inside nested repo %r")
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 % (path, base))
FUJIWARA Katsunori
pathauditor: preserve case in abort messages...
r15665 normparts = util.splitpath(normpath)
assert len(parts) == len(normparts)
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 parts.pop()
FUJIWARA Katsunori
pathauditor: preserve case in abort messages...
r15665 normparts.pop()
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 prefixes = []
while parts:
prefix = os.sep.join(parts)
FUJIWARA Katsunori
pathauditor: preserve case in abort messages...
r15665 normprefix = os.sep.join(normparts)
if normprefix in self.auditeddir:
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 break
curpath = os.path.join(self.root, prefix)
try:
st = os.lstat(curpath)
except OSError, err:
# EINVAL can be raised as invalid path syntax under win32.
# They must be ignored for patterns can be checked too.
if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
raise
else:
if stat.S_ISLNK(st.st_mode):
raise util.Abort(
_('path %r traverses symbolic link %r')
% (path, prefix))
elif (stat.S_ISDIR(st.st_mode) and
os.path.isdir(os.path.join(curpath, '.hg'))):
if not self.callback or not self.callback(curpath):
Brodie Rao
cleanup: eradicate long lines
r16683 raise util.Abort(_("path '%s' is inside nested "
"repo %r")
% (path, prefix))
FUJIWARA Katsunori
pathauditor: preserve case in abort messages...
r15665 prefixes.append(normprefix)
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 parts.pop()
FUJIWARA Katsunori
pathauditor: preserve case in abort messages...
r15665 normparts.pop()
Adrian Buehlmann
move path_auditor from util to scmutil
r13972
FUJIWARA Katsunori
pathauditor: use normcase()-ed path for audit result cache...
r15664 self.audited.add(normpath)
Adrian Buehlmann
move path_auditor from util to scmutil
r13972 # only add prefixes to the cache after checking everything: we don't
# want to add "foo/bar/baz" before checking if there's a "foo/.hg"
self.auditeddir.update(prefixes)
Dan Villiom Podlaski Christiansen
opener: introduce an abstact superclass of it...
r14089 class abstractopener(object):
"""Abstract base class; cannot be instantiated"""
def __init__(self, *args, **kwargs):
'''Prevent instantiation; don't call this from subclasses.'''
raise NotImplementedError('attempted instantiating ' + str(type(self)))
Matt Mackall
opener: introduce tryread helper...
r16455 def tryread(self, path):
Thomas Arendsen Hein
opener: coding style, use triple quotes for doc string
r16479 '''gracefully return an empty string for missing files'''
Matt Mackall
opener: introduce tryread helper...
r16455 try:
return self.read(path)
except IOError, inst:
if inst.errno != errno.ENOENT:
raise
return ""
Dan Villiom Podlaski Christiansen
util & scmutil: adapt read/write helpers as request by mpm
r14167 def read(self, path):
fp = self(path, 'rb')
Dan Villiom Podlaski Christiansen
opener: add read & write utility methods...
r14097 try:
return fp.read()
finally:
fp.close()
Dan Villiom Podlaski Christiansen
util & scmutil: adapt read/write helpers as request by mpm
r14167 def write(self, path, data):
fp = self(path, 'wb')
try:
return fp.write(data)
finally:
fp.close()
def append(self, path, data):
fp = self(path, 'ab')
Dan Villiom Podlaski Christiansen
opener: add read & write utility methods...
r14097 try:
return fp.write(data)
finally:
fp.close()
Dan Villiom Podlaski Christiansen
opener: introduce an abstact superclass of it...
r14089 class opener(abstractopener):
Adrian Buehlmann
move opener from util to scmutil
r13970 '''Open files relative to a base directory
This class is used to hide the details of COW semantics and
remote file access from higher level code.
'''
def __init__(self, base, audit=True):
self.base = base
Adrian Buehlmann
opener: add self._audit (issue2862)
r14720 self._audit = audit
Adrian Buehlmann
move opener from util to scmutil
r13970 if audit:
Adrian Buehlmann
rename path_auditor to pathauditor...
r14220 self.auditor = pathauditor(base)
Adrian Buehlmann
move opener from util to scmutil
r13970 else:
self.auditor = util.always
self.createmode = None
self._trustnlink = None
@util.propertycache
Adrian Buehlmann
opener: rename _can_symlink to _cansymlink
r14261 def _cansymlink(self):
Adrian Buehlmann
move opener from util to scmutil
r13970 return util.checklink(self.base)
def _fixfilemode(self, name):
if self.createmode is None:
return
os.chmod(name, self.createmode & 0666)
def __call__(self, path, mode="r", text=False, atomictemp=False):
Adrian Buehlmann
opener: add self._audit (issue2862)
r14720 if self._audit:
r = util.checkosfilename(path)
if r:
raise util.Abort("%s: %r" % (r, path))
Adrian Buehlmann
move opener from util to scmutil
r13970 self.auditor(path)
Idan Kamara
scmutil: add join method to opener to construct path relative to base...
r16199 f = self.join(path)
Adrian Buehlmann
move opener from util to scmutil
r13970
if not text and "b" not in mode:
mode += "b" # for that other OS
nlink = -1
dirname, basename = os.path.split(f)
# If basename is empty, then the path is malformed because it points
# to a directory. Let the posixfile() call below raise IOError.
if basename and mode not in ('r', 'rb'):
if atomictemp:
if not os.path.isdir(dirname):
util.makedirs(dirname, self.createmode)
return util.atomictempfile(f, mode, self.createmode)
try:
if 'w' in mode:
util.unlink(f)
nlink = 0
else:
# nlinks() may behave differently for files on Windows
# shares if the file is open.
fd = util.posixfile(f)
nlink = util.nlinks(f)
if nlink < 1:
nlink = 2 # force mktempcopy (issue1922)
fd.close()
except (OSError, IOError), e:
if e.errno != errno.ENOENT:
raise
nlink = 0
if not os.path.isdir(dirname):
util.makedirs(dirname, self.createmode)
if nlink > 0:
if self._trustnlink is None:
self._trustnlink = nlink > 1 or util.checknlink(f)
if nlink > 1 or not self._trustnlink:
util.rename(util.mktempcopy(f), f)
fp = util.posixfile(f, mode)
if nlink == 0:
self._fixfilemode(f)
return fp
def symlink(self, src, dst):
self.auditor(dst)
Idan Kamara
scmutil: add join method to opener to construct path relative to base...
r16199 linkname = self.join(dst)
Adrian Buehlmann
move opener from util to scmutil
r13970 try:
os.unlink(linkname)
except OSError:
pass
dirname = os.path.dirname(linkname)
if not os.path.exists(dirname):
util.makedirs(dirname, self.createmode)
Adrian Buehlmann
opener: rename _can_symlink to _cansymlink
r14261 if self._cansymlink:
Adrian Buehlmann
move opener from util to scmutil
r13970 try:
os.symlink(src, linkname)
except OSError, err:
raise OSError(err.errno, _('could not symlink to %r: %s') %
(src, err.strerror), linkname)
else:
f = self(dst, "w")
f.write(src)
f.close()
self._fixfilemode(dst)
Adrian Buehlmann
move canonpath from util to scmutil
r13971
Adrian Buehlmann
opener: add audit function
r14404 def audit(self, path):
self.auditor(path)
Idan Kamara
scmutil: add join method to opener to construct path relative to base...
r16199 def join(self, path):
return os.path.join(self.base, path)
Dan Villiom Podlaski Christiansen
add filteropener abstraction for store openers
r14090 class filteropener(abstractopener):
'''Wrapper opener for filtering filenames with a function.'''
def __init__(self, opener, filter):
self._filter = filter
self._orig = opener
def __call__(self, path, *args, **kwargs):
return self._orig(self._filter(path), *args, **kwargs)
Adrian Buehlmann
move canonpath from util to scmutil
r13971 def canonpath(root, cwd, myname, auditor=None):
'''return the canonical path of myname, given cwd and root'''
if util.endswithsep(root):
rootsep = root
else:
rootsep = root + os.sep
name = myname
if not os.path.isabs(name):
name = os.path.join(root, cwd, name)
name = os.path.normpath(name)
if auditor is None:
Adrian Buehlmann
rename path_auditor to pathauditor...
r14220 auditor = pathauditor(root)
Adrian Buehlmann
move canonpath from util to scmutil
r13971 if name != rootsep and name.startswith(rootsep):
name = name[len(rootsep):]
auditor(name)
return util.pconvert(name)
elif name == root:
return ''
else:
# Determine whether `name' is in the hierarchy at or beneath `root',
# by iterating name=dirname(name) until that causes no change (can't
Adrian Buehlmann
scmutil: change canonpath to use util.samefile (issue2167)...
r17007 # check name == '/', because that doesn't work on windows). The list
# `rel' holds the reversed list of components making up the relative
# file name we want.
Adrian Buehlmann
move canonpath from util to scmutil
r13971 rel = []
while True:
try:
Adrian Buehlmann
scmutil: change canonpath to use util.samefile (issue2167)...
r17007 s = util.samefile(name, root)
Adrian Buehlmann
move canonpath from util to scmutil
r13971 except OSError:
Adrian Buehlmann
scmutil: change canonpath to use util.samefile (issue2167)...
r17007 s = False
if s:
Adrian Buehlmann
move canonpath from util to scmutil
r13971 if not rel:
# name was actually the same as root (maybe a symlink)
return ''
rel.reverse()
name = os.path.join(*rel)
auditor(name)
return util.pconvert(name)
dirname, basename = os.path.split(name)
rel.append(basename)
if dirname == name:
break
name = dirname
raise util.Abort('%s not under root' % myname)
Adrian Buehlmann
move walkrepos from util to scmutil
r13975
def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
'''yield every hg repository under path, recursively.'''
def errhandler(err):
if err.filename == path:
raise err
Augie Fackler
walkrepos: use getattr instead of hasattr for samestat
r14961 samestat = getattr(os.path, 'samestat', None)
if followsym and samestat is not None:
Adrian Buehlmann
scmutil: rename local function _add_dir_if_not_there
r14227 def adddir(dirlst, dirname):
Adrian Buehlmann
move walkrepos from util to scmutil
r13975 match = False
dirstat = os.stat(dirname)
for lstdirstat in dirlst:
if samestat(dirstat, lstdirstat):
match = True
break
if not match:
dirlst.append(dirstat)
return not match
else:
followsym = False
if (seen_dirs is None) and followsym:
seen_dirs = []
Adrian Buehlmann
scmutil: rename local function _add_dir_if_not_there
r14227 adddir(seen_dirs, path)
Adrian Buehlmann
move walkrepos from util to scmutil
r13975 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
dirs.sort()
if '.hg' in dirs:
yield root # found a repository
qroot = os.path.join(root, '.hg', 'patches')
if os.path.isdir(os.path.join(qroot, '.hg')):
yield qroot # we have a patch queue repo here
if recurse:
# avoid recursing inside the .hg directory
dirs.remove('.hg')
else:
dirs[:] = [] # don't descend further
elif followsym:
newdirs = []
for d in dirs:
fname = os.path.join(root, d)
Adrian Buehlmann
scmutil: rename local function _add_dir_if_not_there
r14227 if adddir(seen_dirs, fname):
Adrian Buehlmann
move walkrepos from util to scmutil
r13975 if os.path.islink(fname):
for hgname in walkrepos(fname, True, seen_dirs):
yield hgname
else:
newdirs.append(d)
dirs[:] = newdirs
Adrian Buehlmann
move rcpath from util to scmutil
r13984
Adrian Buehlmann
rename scmutil.os_rcpath to osrcpath
r14224 def osrcpath():
Adrian Buehlmann
move os_rcpath from util to scmutil
r13985 '''return default os-specific hgrc search path'''
Adrian Buehlmann
rename scmutil.system_rcpath to systemrcpath
r14225 path = systemrcpath()
Adrian Buehlmann
rename scmutil.user_rcpath to userrcpath
r14226 path.extend(userrcpath())
Adrian Buehlmann
move os_rcpath from util to scmutil
r13985 path = [os.path.normpath(f) for f in path]
return path
Adrian Buehlmann
move rcpath from util to scmutil
r13984 _rcpath = None
def rcpath():
'''return hgrc search path. if env var HGRCPATH is set, use it.
for each item in path, if directory, use files ending in .rc,
else use item.
make HGRCPATH empty to only look in .hg/hgrc of current repo.
if no HGRCPATH, use default os-specific path.'''
global _rcpath
if _rcpath is None:
if 'HGRCPATH' in os.environ:
_rcpath = []
for p in os.environ['HGRCPATH'].split(os.pathsep):
if not p:
continue
p = util.expandpath(p)
if os.path.isdir(p):
for f, kind in osutil.listdir(p):
if f.endswith('.rc'):
_rcpath.append(os.path.join(p, f))
else:
_rcpath.append(p)
else:
Adrian Buehlmann
rename scmutil.os_rcpath to osrcpath
r14224 _rcpath = osrcpath()
Adrian Buehlmann
move rcpath from util to scmutil
r13984 return _rcpath
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986
if os.name != 'nt':
def rcfiles(path):
rcs = [os.path.join(path, 'hgrc')]
rcdir = os.path.join(path, 'hgrc.d')
try:
rcs.extend([os.path.join(rcdir, f)
for f, kind in osutil.listdir(rcdir)
if f.endswith(".rc")])
except OSError:
pass
return rcs
Adrian Buehlmann
rename scmutil.system_rcpath to systemrcpath
r14225 def systemrcpath():
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 path = []
Steven Stallion
plan9: initial support for plan 9 from bell labs...
r16383 if sys.platform == 'plan9':
Wolfgang Treutterer
scmutil: fix systemrcpath regression introduced in f5dd179bfa4a...
r16436 root = 'lib/mercurial'
Steven Stallion
plan9: initial support for plan 9 from bell labs...
r16383 else:
Wolfgang Treutterer
scmutil: fix systemrcpath regression introduced in f5dd179bfa4a...
r16436 root = 'etc/mercurial'
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 # old mod_python does not set sys.argv
if len(getattr(sys, 'argv', [])) > 0:
Matt Mackall
scmutil: improve path calculation for install-relative RC files (issue2841)...
r14527 p = os.path.dirname(os.path.dirname(sys.argv[0]))
Steven Stallion
plan9: initial support for plan 9 from bell labs...
r16383 path.extend(rcfiles(os.path.join(p, root)))
Wolfgang Treutterer
scmutil: fix systemrcpath regression introduced in f5dd179bfa4a...
r16436 path.extend(rcfiles('/' + root))
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 return path
Adrian Buehlmann
rename scmutil.user_rcpath to userrcpath
r14226 def userrcpath():
Steven Stallion
plan9: initial support for plan 9 from bell labs...
r16383 if sys.platform == 'plan9':
return [os.environ['home'] + '/lib/hgrc']
else:
return [os.path.expanduser('~/.hgrc')]
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986
else:
Adrian Buehlmann
scmutil: use _winreg.HKEY_LOCAL_MACHINE
r16808 import _winreg
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986
Adrian Buehlmann
rename scmutil.system_rcpath to systemrcpath
r14225 def systemrcpath():
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 '''return default os-specific hgrc search path'''
rcpath = []
Adrian Buehlmann
rename util.executable_path to executablepath
r14236 filename = util.executablepath()
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 # Use mercurial.ini found in directory with hg.exe
progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
if os.path.isfile(progrc):
rcpath.append(progrc)
return rcpath
# Use hgrc.d found in directory with hg.exe
progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
if os.path.isdir(progrcd):
for f, kind in osutil.listdir(progrcd):
if f.endswith('.rc'):
rcpath.append(os.path.join(progrcd, f))
return rcpath
# else look for a system rcpath in the registry
Adrian Buehlmann
rename util.lookup_reg to lookupreg
r14230 value = util.lookupreg('SOFTWARE\\Mercurial', None,
Adrian Buehlmann
scmutil: use _winreg.HKEY_LOCAL_MACHINE
r16808 _winreg.HKEY_LOCAL_MACHINE)
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 if not isinstance(value, str) or not value:
return rcpath
FUJIWARA Katsunori
use util.localpath() instead of 'str.replace()' to unify path conversion
r16068 value = util.localpath(value)
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 for p in value.split(os.pathsep):
if p.lower().endswith('mercurial.ini'):
rcpath.append(p)
elif os.path.isdir(p):
for f, kind in osutil.listdir(p):
if f.endswith('.rc'):
rcpath.append(os.path.join(p, f))
return rcpath
Adrian Buehlmann
rename scmutil.user_rcpath to userrcpath
r14226 def userrcpath():
Adrian Buehlmann
move system_rcpath and user_rcpath to scmutil
r13986 '''return os-specific hgrc search path to the user dir'''
home = os.path.expanduser('~')
path = [os.path.join(home, 'mercurial.ini'),
os.path.join(home, '.hgrc')]
userprofile = os.environ.get('USERPROFILE')
if userprofile:
path.append(os.path.join(userprofile, 'mercurial.ini'))
path.append(os.path.join(userprofile, '.hgrc'))
return path
Matt Mackall
scmutil: move revsingle/pair/range from cmdutil...
r14319
def revsingle(repo, revspec, default='.'):
if not revspec:
return repo[default]
l = revrange(repo, [revspec])
if len(l) < 1:
raise util.Abort(_('empty revision set'))
return repo[l[-1]]
def revpair(repo, revs):
if not revs:
return repo.dirstate.p1(), None
l = revrange(repo, revs)
if len(l) == 0:
Matt Mackall
revpair: handle odd ranges (issue3474)
r16790 if revs:
raise util.Abort(_('empty revision range'))
Matt Mackall
scmutil: move revsingle/pair/range from cmdutil...
r14319 return repo.dirstate.p1(), None
Matt Mackall
revpair: handle odd ranges (issue3474)
r16790 if len(l) == 1 and len(revs) == 1 and _revrangesep not in revs[0]:
Matt Mackall
scmutil: move revsingle/pair/range from cmdutil...
r14319 return repo.lookup(l[0]), None
return repo.lookup(l[0]), repo.lookup(l[-1])
_revrangesep = ':'
def revrange(repo, revs):
"""Yield revision as strings from a list of revision specifications."""
def revfix(repo, val, defval):
if not val and val != 0 and defval is not None:
return defval
Matt Mackall
scmutil: use context instead of lookup
r16379 return repo[val].rev()
Matt Mackall
scmutil: move revsingle/pair/range from cmdutil...
r14319
seen, l = set(), []
for spec in revs:
Bryan O'Sullivan
scmutil: speed up revrange...
r16390 if l and not seen:
seen = set(l)
Matt Mackall
scmutil: move revsingle/pair/range from cmdutil...
r14319 # attempt to parse old-style ranges first to deal with
# things like old-tag which contain query metacharacters
try:
if isinstance(spec, int):
seen.add(spec)
l.append(spec)
continue
if _revrangesep in spec:
start, end = spec.split(_revrangesep, 1)
start = revfix(repo, start, 0)
end = revfix(repo, end, len(repo) - 1)
step = start > end and -1 or 1
Bryan O'Sullivan
scmutil: speed up revrange...
r16390 if not seen and not l:
# by far the most common case: revs = ["-1:0"]
l = range(start, end + step, step)
# defer syncing seen until next iteration
continue
newrevs = set(xrange(start, end + step, step))
if seen:
newrevs.difference_update(seen)
Bryan O'Sullivan
scmutil: seen.union should be seen.update (issue3476)
r16814 seen.update(newrevs)
Bryan O'Sullivan
scmutil: speed up revrange...
r16390 else:
seen = newrevs
l.extend(sorted(newrevs, reverse=start > end))
Matt Mackall
scmutil: move revsingle/pair/range from cmdutil...
r14319 continue
elif spec and spec in repo: # single unquoted rev
rev = revfix(repo, spec, None)
if rev in seen:
continue
seen.add(rev)
l.append(rev)
continue
except error.RepoLookupError:
pass
# fall through to new-style queries if old-style fails
m = revset.match(repo.ui, spec)
Bryan O'Sullivan
scmutil: speed up new-style range extension...
r17037 dl = [r for r in m(repo, xrange(len(repo))) if r not in seen]
l.extend(dl)
seen.update(dl)
Matt Mackall
scmutil: move revsingle/pair/range from cmdutil...
r14319
return l
Matt Mackall
scmutil: fold in wdutil
r14320
def expandpats(pats):
if not util.expandglobs:
return list(pats)
ret = []
for p in pats:
kind, name = matchmod._patsplit(p, None)
if kind is None:
try:
globbed = glob.glob(name)
except re.error:
globbed = [name]
if globbed:
ret.extend(globbed)
continue
ret.append(p)
return ret
Patrick Mezard
graphlog: restore FILE glob expansion on Windows...
r16171 def matchandpats(ctx, pats=[], opts={}, globbed=False, default='relpath'):
Matt Mackall
scmutil: fold in wdutil
r14320 if pats == ("",):
pats = []
if not globbed and default == 'relpath':
pats = expandpats(pats or [])
Matt Mackall
scmutil: match now accepts a context or a repo
r14670
m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
Matt Mackall
context: add a match builder method...
r14669 default)
Matt Mackall
scmutil: fold in wdutil
r14320 def badfn(f, msg):
Matt Mackall
scmutil: switch match users to supplying contexts...
r14671 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
Matt Mackall
scmutil: fold in wdutil
r14320 m.bad = badfn
Patrick Mezard
graphlog: restore FILE glob expansion on Windows...
r16171 return m, pats
def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
return matchandpats(ctx, pats, opts, globbed, default)[0]
Matt Mackall
scmutil: fold in wdutil
r14320
def matchall(repo):
return matchmod.always(repo.root, repo.getcwd())
def matchfiles(repo, files):
return matchmod.exact(repo.root, repo.getcwd(), files)
def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
if dry_run is None:
dry_run = opts.get('dry_run')
if similarity is None:
similarity = float(opts.get('similarity') or 0)
# we'd use status here, except handling of symlinks and ignore is tricky
added, unknown, deleted, removed = [], [], [], []
audit_path = pathauditor(repo.root)
Matt Mackall
scmutil: switch match users to supplying contexts...
r14671 m = match(repo[None], pats, opts)
Matt Mackall
addremove: return 1 if we failed to handle any explicit files
r16167 rejected = []
m.bad = lambda x, y: rejected.append(x)
Matt Mackall
scmutil: fold in wdutil
r14320 for abs in repo.walk(m):
target = repo.wjoin(abs)
good = True
try:
audit_path(abs)
except (OSError, util.Abort):
good = False
rel = m.rel(abs)
exact = m.exact(abs)
if good and abs not in repo.dirstate:
unknown.append(abs)
if repo.ui.verbose or not exact:
repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
Brodie Rao
cleanup: eradicate long lines
r16683 elif (repo.dirstate[abs] != 'r' and
(not good or not os.path.lexists(target) or
(os.path.isdir(target) and not os.path.islink(target)))):
Matt Mackall
scmutil: fold in wdutil
r14320 deleted.append(abs)
if repo.ui.verbose or not exact:
repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
# for finding renames
elif repo.dirstate[abs] == 'r':
removed.append(abs)
elif repo.dirstate[abs] == 'a':
added.append(abs)
copies = {}
if similarity > 0:
for old, new, score in similar.findrenames(repo,
added + unknown, removed + deleted, similarity):
if repo.ui.verbose or not m.exact(old) or not m.exact(new):
repo.ui.status(_('recording removal of %s as rename to %s '
'(%d%% similar)\n') %
(m.rel(old), m.rel(new), score * 100))
copies[new] = old
if not dry_run:
wctx = repo[None]
wlock = repo.wlock()
try:
Matt Mackall
context: make forget work like commands.forget...
r14435 wctx.forget(deleted)
Matt Mackall
scmutil: fold in wdutil
r14320 wctx.add(unknown)
for new, old in copies.iteritems():
wctx.copy(old, new)
finally:
wlock.release()
Matt Mackall
addremove: return 1 if we failed to handle any explicit files
r16167 for f in rejected:
if f in m.files():
return 1
return 0
Matt Mackall
scmutil: fold in wdutil
r14320 def updatedir(ui, repo, patches, similarity=0):
'''Update dirstate after patch application according to metadata'''
if not patches:
return []
copies = []
removes = set()
cfiles = patches.keys()
cwd = repo.getcwd()
if cwd:
cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
for f in patches:
gp = patches[f]
if not gp:
continue
if gp.op == 'RENAME':
copies.append((gp.oldpath, gp.path))
removes.add(gp.oldpath)
elif gp.op == 'COPY':
copies.append((gp.oldpath, gp.path))
elif gp.op == 'DELETE':
removes.add(gp.path)
wctx = repo[None]
for src, dst in copies:
dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
if (not similarity) and removes:
wctx.remove(sorted(removes), True)
for f in patches:
gp = patches[f]
if gp and gp.mode:
islink, isexec = gp.mode
dst = repo.wjoin(gp.path)
# patch won't create empty files
if gp.op == 'ADD' and not os.path.lexists(dst):
flags = (isexec and 'x' or '') + (islink and 'l' or '')
repo.wwrite(gp.path, '', flags)
util.setflags(dst, islink, isexec)
addremove(repo, cfiles, similarity=similarity)
files = patches.keys()
files.extend([r for r in removes if r not in files])
return sorted(files)
def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
"""Update the dirstate to reflect the intent of copying src to dst. For
different reasons it might not end with dst being marked as copied from src.
"""
origsrc = repo.dirstate.copied(src) or src
if dst == origsrc: # copying back a copy?
if repo.dirstate[dst] not in 'mn' and not dryrun:
repo.dirstate.normallookup(dst)
else:
if repo.dirstate[origsrc] == 'a' and origsrc == src:
if not ui.quiet:
ui.warn(_("%s has not been committed yet, so no copy "
"data will be stored for %s.\n")
% (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
if repo.dirstate[dst] in '?r' and not dryrun:
wctx.add([dst])
elif not dryrun:
wctx.copy(origsrc, dst)
Adrian Buehlmann
introduce new function scmutil.readrequires...
r14482
def readrequires(opener, supported):
'''Reads and parses .hg/requires and checks if all entries found
are in the list of supported features.'''
requirements = set(opener.read("requires").splitlines())
Pierre-Yves David
requirements: show all missing features in the error message....
r14746 missings = []
Adrian Buehlmann
introduce new function scmutil.readrequires...
r14482 for r in requirements:
if r not in supported:
Matt Mackall
requires: note apparent corruption
r14484 if not r or not r[0].isalnum():
raise error.RequirementError(_(".hg/requires file is corrupt"))
Pierre-Yves David
requirements: show all missing features in the error message....
r14746 missings.append(r)
missings.sort()
if missings:
Brodie Rao
cleanup: eradicate long lines
r16683 raise error.RequirementError(
_("unknown repository format: requires features '%s' (upgrade "
"Mercurial)") % "', '".join(missings))
Adrian Buehlmann
introduce new function scmutil.readrequires...
r14482 return requirements
Idan Kamara
scmutil: introduce filecache...
r14928
class filecacheentry(object):
def __init__(self, path):
self.path = path
self.cachestat = filecacheentry.stat(self.path)
if self.cachestat:
self._cacheable = self.cachestat.cacheable()
else:
# None means we don't know yet
self._cacheable = None
def refresh(self):
if self.cacheable():
self.cachestat = filecacheentry.stat(self.path)
def cacheable(self):
if self._cacheable is not None:
return self._cacheable
# we don't know yet, assume it is for now
return True
def changed(self):
# no point in going further if we can't cache it
if not self.cacheable():
return True
newstat = filecacheentry.stat(self.path)
# we may not know if it's cacheable yet, check again now
if newstat and self._cacheable is None:
self._cacheable = newstat.cacheable()
# check again
if not self._cacheable:
return True
if self.cachestat != newstat:
self.cachestat = newstat
return True
else:
return False
@staticmethod
def stat(path):
try:
return util.cachestat(path)
except OSError, e:
if e.errno != errno.ENOENT:
raise
class filecache(object):
'''A property like decorator that tracks a file under .hg/ for updates.
Records stat info when called in _filecache.
On subsequent calls, compares old stat info with new info, and recreates
the object when needed, updating the new stat info in _filecache.
Mercurial either atomic renames or appends for files under .hg,
so to ensure the cache is reliable we need the filesystem to be able
to tell us if a file has been replaced. If it can't, we fallback to
recreating the object on every call (essentially the same behaviour as
propertycache).'''
Idan Kamara
filecache: refactor path join logic to a function...
r16198 def __init__(self, path):
Idan Kamara
scmutil: introduce filecache...
r14928 self.path = path
Idan Kamara
filecache: refactor path join logic to a function...
r16198
def join(self, obj, fname):
"""Used to compute the runtime path of the cached file.
Users should subclass filecache and provide their own version of this
function to call the appropriate join function on 'obj' (an instance
of the class that its member function was decorated).
"""
return obj.join(fname)
Idan Kamara
scmutil: introduce filecache...
r14928
def __call__(self, func):
self.func = func
self.name = func.__name__
return self
def __get__(self, obj, type=None):
Idan Kamara
scmutil: update cached copy when filecached attribute is assigned (issue3263)...
r16115 # do we need to check if the file changed?
if self.name in obj.__dict__:
return obj.__dict__[self.name]
Idan Kamara
scmutil: introduce filecache...
r14928 entry = obj._filecache.get(self.name)
if entry:
if entry.changed():
entry.obj = self.func(obj)
else:
Idan Kamara
filecache: refactor path join logic to a function...
r16198 path = self.join(obj, self.path)
Idan Kamara
scmutil: introduce filecache...
r14928
# We stat -before- creating the object so our cache doesn't lie if
# a writer modified between the time we read and stat
entry = filecacheentry(path)
entry.obj = self.func(obj)
obj._filecache[self.name] = entry
Idan Kamara
scmutil: update cached copy when filecached attribute is assigned (issue3263)...
r16115 obj.__dict__[self.name] = entry.obj
Idan Kamara
scmutil: introduce filecache...
r14928 return entry.obj
Idan Kamara
scmutil: update cached copy when filecached attribute is assigned (issue3263)...
r16115
def __set__(self, obj, value):
if self.name in obj._filecache:
obj._filecache[self.name].obj = value # update cached copy
obj.__dict__[self.name] = value # update copy returned by obj.x
def __delete__(self, obj):
try:
del obj.__dict__[self.name]
except KeyError:
raise AttributeError, self.name