Commit 34421f29 authored by Javier Merino Cacho's avatar Javier Merino Cacho

New upstream release

parents 47d06e75 e3dadeac
repo: f2636cfed11500fdc47d1e3822d8e4a2bd636bf7
node: 46523cdfd3b0cee0bf1366ab587686bb65211747
node: c1756971f8828a59c859513f208c6d79bf34c275
branch: stable
tag: 1.6.3
tag: 1.8
......@@ -9,5 +9,8 @@
07234759a3f750029ccaa001837d42fa12dd33ee 1.4
77b22e5b4ea6c248e079afd0f1e544cb5690ce20 1.5
d0f3a5c2cb56ce65d9ef1c611c8bfbebdc3bef34 1.5.1
7d47a0f731354505ed9ae8d60d2a6996e8c3294f 1.6
8caf1226adecb322e90ddb3817c604fa2fe8a66d 1.6.1
36f6d51b4edc31f1f9ce2d0d02965a85dd26a455 1.6.2
46523cdfd3b0cee0bf1366ab587686bb65211747 1.6.3
139a44a63090b2c0f8e05d7d1104d3d9c9e26f57 1.7
hgsubversion (1.8-1) UNRELEASED; urgency=medium
* New upstream release
-- Javi Merino <vicho@debian.org> Sat, 25 Apr 2015 21:51:08 +0100
hgsubversion (1.6.3-2) unstable; urgency=medium
* Add autopkgtest
......
......@@ -15,14 +15,23 @@ details.
For more information and instructions, see :hg:`help subversion`.
'''
testedwith = '2.8.2 3.0.1 3.1 3.2.2 3.3'
import os
import sys
import traceback
from mercurial import commands
try:
from mercurial import exchange
exchange.push # existed in first iteration of this file
except ImportError:
# We only *use* the exchange module in hg 3.2+, so this is safe
pass
from mercurial import extensions
from mercurial import help
from mercurial import hg
from mercurial import localrepo
from mercurial import util as hgutil
from mercurial import demandimport
demandimport.ignore.extend([
......@@ -33,27 +42,9 @@ demandimport.ignore.extend([
'svn.ra',
])
try:
from mercurial import templatekw
# force demandimport to load templatekw
templatekw.keywords
except ImportError:
templatekw = None
try:
from mercurial import revset
# force demandimport to load revset
revset.methods
except ImportError:
revset = None
try:
from mercurial import subrepo
# require svnsubrepo and hg >= 1.7.1
subrepo.svnsubrepo
hgutil.checknlink
except (ImportError, AttributeError), e:
subrepo = None
from mercurial import templatekw
from mercurial import revset
from mercurial import subrepo
import svncommands
import util
......@@ -124,9 +115,8 @@ except AttributeError:
except ImportError:
pass
def extsetup():
def extsetup(ui):
"""insert command wrappers for a bunch of commands"""
# add the ui argument to this function once we drop support for 1.3
docvals = {'extension': 'hgsubversion'}
for cmd, (generic, target, fixdoc, ppopts, opts) in wrapcmds.iteritems():
......@@ -155,6 +145,13 @@ def extsetup():
except:
pass
if not hgutil.safehasattr(localrepo.localrepository, 'push'):
# Mercurial >= 3.2
extensions.wrapfunction(exchange, 'push', wrappers.exchangepush)
if not hgutil.safehasattr(localrepo.localrepository, 'pull'):
# Mercurial >= 3.2
extensions.wrapfunction(exchange, 'pull', wrappers.exchangepull)
helpdir = os.path.join(os.path.dirname(__file__), 'help')
entries = (
......@@ -163,20 +160,13 @@ def extsetup():
lambda: open(os.path.join(helpdir, 'subversion.rst')).read()),
)
# in 1.6 and earler the help table is a tuple
if getattr(help.helptable, 'extend', None):
help.helptable.extend(entries)
else:
help.helptable = help.helptable + entries
help.helptable.extend(entries)
if templatekw:
templatekw.keywords.update(util.templatekeywords)
templatekw.keywords.update(util.templatekeywords)
if revset:
revset.symbols.update(util.revsets)
revset.symbols.update(util.revsets)
if subrepo:
subrepo.types['hgsubversion'] = svnexternals.svnsubrepo
subrepo.types['hgsubversion'] = svnexternals.svnsubrepo
def reposetup(ui, repo):
if repo.local():
......@@ -184,7 +174,7 @@ def reposetup(ui, repo):
for tunnel in ui.configlist('hgsubversion', 'tunnels'):
hg.schemes['svn+' + tunnel] = svnrepo
if revset and ui.configbool('hgsubversion', 'nativerevs'):
if ui.configbool('hgsubversion', 'nativerevs'):
extensions.wrapfunction(revset, 'stringset', util.revset_stringset)
_old_local = hg.schemes['file']
......
"""Functions to work around API changes inside Mercurial."""
"""Functions to work around API changes."""
import errno
import sys
def branchset(repo):
"""Return the set of branches present in a repo.
......@@ -25,3 +28,42 @@ def makememfilectx(repo, path, data, islink, isexec, copied):
return context.memfilectx(repo, path, data, islink, isexec, copied)
except TypeError:
return context.memfilectx(path, data, islink, isexec, copied)
def filectxfn_deleted(memctx, path):
"""
Return None or raise an IOError as necessary if path is deleted.
Call as:
if path_missing:
return compathacks.filectxfn_deleted(memctx, path)
Works around filectxfn's contract changing between 3.1 and 3.2: 3.2 onwards,
for deleted files, filectxfn should return None rather than returning
IOError.
"""
if getattr(memctx, '_returnnoneformissingfiles', False):
return None
raise IOError(errno.ENOENT, '%s is deleted' % path)
def filectxfn_deleted_reraise(memctx):
"""
Return None or reraise exc as necessary.
Call as:
try:
# code that raises IOError if the path is missing
except IOError:
return compathacks.filectxfn_deleted_reraise(memctx)
Works around filectxfn's contract changing between 3.1 and 3.2: 3.2 onwards,
for deleted files, filectxfn should return None rather than returning
IOError.
"""
exc_info = sys.exc_info()
if (exc_info[1].errno == errno.ENOENT and
getattr(memctx, '_returnnoneformissingfiles', False)):
return None
# preserve traceback info
raise exc_info[0], exc_info[1], exc_info[2]
......@@ -31,6 +31,13 @@ class FileStore(object):
if fname in self._popped:
raise EditingError('trying to set a popped file %s' % fname)
if fname in self._data:
self._size -= len(self._data[fname])
del self._data[fname]
if fname in self._files:
del self._files[fname]
if self._maxsize < 0 or (len(data) + self._size) <= self._maxsize:
self._data[fname] = data
self._size += len(data)
......@@ -239,6 +246,13 @@ class HgEditor(svnwrap.Editor):
else:
# Resolve missing directories content immediately so the
# missing files maybe processed by delete actions.
# we remove the missing directory entries to deal with the case
# where a directory is replaced from e.g. a closed branch
# this will show up as a delete and then a copy
# we process deletes after missing, so we can handle a directory
# copy plus delete of file in that directory. This means that we
# need to be sure that only things whose final disposition is
# deletion remain in self._deleted at the end of the editing process.
rev = self.current.rev.revnum
path = path + '/'
parentdir = path[len(root):]
......@@ -248,6 +262,7 @@ class HgEditor(svnwrap.Editor):
f = parentdir + f
if not self.meta.is_path_valid(f, False):
continue
self._deleted.discard(f)
self._missing.add(f)
@svnwrap.ieditor
......@@ -365,7 +380,10 @@ class HgEditor(svnwrap.Editor):
fctx = ctx.filectx(from_file)
flags = fctx.flags()
self.current.set(path, fctx.data(), 'x' in flags, 'l' in flags)
base = fctx.data()
if 'l' in flags:
base = 'link ' + base
self.current.set(path, base, 'x' in flags, 'l' in flags)
copypath = None
if from_branch == branch:
parentid = self.meta.get_parent_revision(
......@@ -374,7 +392,7 @@ class HgEditor(svnwrap.Editor):
parentctx = self._getctx(parentid)
if util.issamefile(parentctx, ctx, from_file):
copypath = from_file
return self._openfile(path, fctx.data(), 'x' in flags, 'l' in flags,
return self._openfile(path, base, 'x' in flags, 'l' in flags,
copypath, create=True)
@svnwrap.ieditor
......@@ -431,7 +449,7 @@ class HgEditor(svnwrap.Editor):
source_rev = copyfrom_revision
frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2]
new_hash = self.meta.get_parent_revision(source_rev + 1, source_branch, True)
if new_hash == node.nullid:
if frompath is None or new_hash == node.nullid:
self.addmissing(path, isdir=True)
return baton
fromctx = self._getctx(new_hash)
......@@ -636,7 +654,7 @@ class HgEditor(svnwrap.Editor):
svn.init_ra_and_client()
i += 1
data, mode = svn.get_file(f, rev)
self.current.set(f, data, 'x' in mode, 'l' in mode)
self.current.set(root + f, data, 'x' in mode, 'l' in mode)
if not self.ui.debugflag:
self.ui.note('\n')
......
......@@ -45,8 +45,8 @@ issue ``hg clone http://python-nose.googlecode.com/svn/trunk nose-trunk``. This
works with any directory with a Subversion repository, and is known as a single
directory clone. Normally, converted changesets will be marked as belonging to
the ``default`` branch, but this can be changed by using the ``-b/--branch``
option when using Mercurial 1.5 or later. To force single directory clone, use
hgsubversion.layout option (see below for detailed help) ::
option. To force single directory clone, use hgsubversion.layout option (see
below for detailed help) ::
$ hg clone --layout single svn+http://python-nose.googlecode.com/svn nose-hg
......@@ -85,8 +85,6 @@ An example::
$ hg log --template='{rev}:{node|short} {author|user}\nsvn: {svnrev}\n'
The template keywords are available when using Mercurial 1.5 or later.
For finding changesets from Subversion, hgsubversion extends revsets
to provide two new selectors:
......@@ -100,9 +98,7 @@ For example::
$ hg log -r 'fromsvn()'
$ hg log -r 'svnrev(500)'
Revsets are available when using Mercurial 1.6 or later and are
accepted by several Mercurial commands for specifying revisions. See
``hg help revsets`` for details.
See ``hg help revsets`` for details.
Support for externals
---------------------
......@@ -146,7 +142,7 @@ related Subversion repository.
Alternatively, one can use the ``hgsubversion.externals`` in hgrc to
specify ``subrepos`` as the externals mode. In this mode, ``.hgsub``
and ``.hgsubstate`` files will be used instead of
``.hgsvnexternals``. This feature requires Mercurial 1.7.1 or later.
``.hgsvnexternals``.
Using Subrepositories
......@@ -183,8 +179,6 @@ with the revision identifier replaced with {REV}.
This mode has the following limitations:
* Require Mercurial >= 1.7.1 to work correctly on all platforms.
* "hgsubversion" subrepositories require hgsubversion extension to be
available. To operate transparently on ``svn:externals`` we have to
stay as close as possible to their original property
......@@ -365,10 +359,10 @@ settings:
when necessary.
``hgsubversion.externals``
Set to ``subrepos`` to switch to subrepos-based externals support
(requires Mercurial 1.7.1 or later.) Default is ``svnexternals``,
which uses a custom hgsubversion-specific format and works on
older versions of Mercurial. Use ``ignore`` to avoid converting externals.
Set to ``subrepos`` to switch to subrepos-based externals support. Default
is ``svnexternals``, which uses a custom hgsubversion-specific format and
works on older versions of Mercurial. Use ``ignore`` to avoid converting
externals.
The following options only have an effect on the initial clone of a repository:
......
......@@ -13,14 +13,12 @@ from mercurial import util as hgutil
import custom
import detect
import persist
import single
import standard
__all__ = [
"detect",
"layout_from_name",
"persist",
]
# This is the authoritative store of what layouts are available.
......@@ -33,7 +31,7 @@ NAME_TO_CLASS = {
}
def layout_from_name(name, ui):
def layout_from_name(name, meta):
"""Returns a layout module given the layout name
You should use one of the layout.detect.* functions to get the
......@@ -42,5 +40,5 @@ def layout_from_name(name, ui):
"""
if name not in NAME_TO_CLASS:
raise hgutil.Abort('Unknown hgsubversion layout: %s' %name)
return NAME_TO_CLASS[name](ui)
raise hgutil.Abort('Unknown hgsubversion layout: %s' % name)
return NAME_TO_CLASS[name](meta)
......@@ -9,8 +9,8 @@ from mercurial import util as hgutil
class BaseLayout(object):
def __init__(self, ui):
self.ui = ui
def __init__(self, meta):
self.meta = meta
def __unimplemented(self, method_name):
raise NotImplementedError(
......@@ -48,7 +48,8 @@ class BaseLayout(object):
"""
self.__unimplemented('remotepath')
def taglocations(self, meta_data_dir):
@property
def taglocations(self):
"""Return a list of locations within svn to search for tags
Should be returned in reverse-sorted order.
......
......@@ -12,13 +12,13 @@ import base
class CustomLayout(base.BaseLayout):
def __init__(self, ui):
base.BaseLayout.__init__(self, ui)
def __init__(self, meta):
base.BaseLayout.__init__(self, meta)
self.svn_to_hg = {}
self.hg_to_svn = {}
for hg_branch, svn_path in ui.configitems('hgsubversionbranch'):
for hg_branch, svn_path in meta.ui.configitems('hgsubversionbranch'):
hg_branch = hg_branch.strip()
if hg_branch == 'default' or not hg_branch:
......@@ -64,7 +64,8 @@ class CustomLayout(base.BaseLayout):
subdir += '/'
return subdir + self.remotename(branch)
def taglocations(self, meta_data_dir):
@property
def taglocations(self):
return []
def get_path_tag(self, path, taglocations):
......
......@@ -12,7 +12,7 @@ from mercurial import util as hgutil
import __init__ as layouts
def layout_from_subversion(svn, revision=None, ui=None):
def layout_from_subversion(svn, revision=None, meta=None):
""" Guess what layout to use based on directories under the svn root.
This is intended for use during bootstrapping. It guesses which
......@@ -39,10 +39,10 @@ def layout_from_subversion(svn, revision=None, ui=None):
layout = 'standard'
else:
layout = 'single'
ui.setconfig('hgsubversion', 'layout', layout)
meta.ui.setconfig('hgsubversion', 'layout', layout)
return layout
def layout_from_config(ui, allow_auto=False):
def layout_from_config(meta, allow_auto=False):
""" Load the layout we are using based on config
We will read the config from the ui object. Pass allow_auto=True
......@@ -51,32 +51,29 @@ def layout_from_config(ui, allow_auto=False):
detect the layout as auto.
"""
layout = ui.config('hgsubversion', 'layout', default='auto')
layout = meta.ui.config('hgsubversion', 'layout', default='auto')
if layout == 'auto' and not allow_auto:
raise hgutil.Abort('layout not yet determined')
elif layout not in layouts.NAME_TO_CLASS and layout != 'auto':
raise hgutil.Abort("unknown layout '%s'" % layout)
return layout
def layout_from_file(meta_data_dir, ui=None):
def layout_from_file(meta):
""" Load the layout in use from the metadata file.
If you pass the ui arg, we will also write the layout to the
config for that ui.
"""
layout = None
layoutfile = os.path.join(meta_data_dir, 'layout')
if os.path.exists(layoutfile):
f = open(layoutfile)
layout = f.read().strip()
f.close()
if ui:
ui.setconfig('hgsubversion', 'layout', layout)
# import late to avoid trouble when running the test suite
try:
from hgext_hgsubversion import util
except ImportError:
from hgsubversion import util
layout = util.load(meta.layout_file)
if layout:
meta.ui.setconfig('hgsubversion', 'layout', layout)
return layout
def layout_from_commit(subdir, revpath, branch, ui):
def layout_from_commit(subdir, revpath, branch, meta):
""" Guess what the layout is based existing commit info
Specifically, this compares the subdir for the repository and the
......@@ -93,7 +90,7 @@ def layout_from_commit(subdir, revpath, branch, ui):
candidates = set()
for layout in layouts.NAME_TO_CLASS:
layoutobj = layouts.layout_from_name(layout, ui)
layoutobj = layouts.layout_from_name(layout, meta)
try:
remotepath = layoutobj.remotepath(branch, subdir)
except KeyError:
......@@ -104,7 +101,7 @@ def layout_from_commit(subdir, revpath, branch, ui):
if len(candidates) == 1:
return candidates.pop()
elif candidates:
config_layout = layout_from_config(ui, allow_auto=True)
config_layout = layout_from_config(meta, allow_auto=True)
if config_layout in candidates:
return config_layout
......
"""Code for persisting the layout config in various locations.
Basically, if you want to save the layout, this is where you should go
to do it.
"""
import os.path
def layout_to_file(meta_data_dir, layout):
"""Save the given layout to a file under the given meta_data_dir"""
layoutfile = os.path.join(meta_data_dir, 'layout')
f = open(layoutfile, 'w')
f.write(layout)
f.close()
......@@ -14,7 +14,8 @@ class SingleLayout(base.BaseLayout):
def remotepath(self, branch, subdir='/'):
return subdir or '/'
def taglocations(self, meta_data_dir):
@property
def taglocations(self):
return []
def get_path_tag(self, path, taglocations):
......
import os.path
import pickle
import base
class StandardLayout(base.BaseLayout):
"""The standard trunk, branches, tags layout"""
def __init__(self, ui):
base.BaseLayout.__init__(self, ui)
def __init__(self, meta):
base.BaseLayout.__init__(self, meta)
self._tag_locations = None
self._branch_dir = ui.config('hgsubversion', 'branchdir', 'branches')
if self._branch_dir[0] == '/':
self._branch_dir = self._branch_dir[1:]
if self._branch_dir[-1] != '/':
self._branch_dir += '/'
self._infix = ui.config('hgsubversion', 'infix', '').strip('/')
if self._infix:
self._infix = '/' + self._infix
self._trunk = 'trunk%s' % self._infix
# branchdir is expected to be stripped of leading slashes but retain
# its last slash
meta._gen_cachedconfig('branchdir', 'branches',
pre=lambda x: '/'.join(p for p in x.split('/')
if p) + '/')
# infix is expected to be stripped of trailing slashes but retain
# its first slash
def _infix_transform(x):
x = '/'.join(p for p in x.split('/') if p)
if x:
x = '/' + x
return x
meta._gen_cachedconfig('infix', '', pre=_infix_transform)
# the lambda is to ensure nested paths are handled properly
meta._gen_cachedconfig('taglocations', ['tags'], 'tag_locations',
'tagpaths', lambda x: list(reversed(sorted(x))))
@property
def trunk(self):
return 'trunk' + self.meta.infix
def localname(self, path):
if path == self._trunk:
if path == self.trunk:
return None
elif path.startswith(self._branch_dir) and path.endswith(self._infix):
path = path[len(self._branch_dir):]
if self._infix:
path = path[:-len(self._infix)]
elif path.startswith(self.meta.branchdir) and path.endswith(self.meta.infix):
path = path[len(self.meta.branchdir):]
if self.meta.infix:
path = path[:-len(self.meta.infix)]
return path
return '../%s' % path
def remotename(self, branch):
if branch == 'default' or branch is None:
path = self._trunk
path = self.trunk
elif branch.startswith('../'):
path = branch[3:]
else:
path = ''.join((self._branch_dir, branch, self._infix))
path = ''.join((self.meta.branchdir, branch, self.meta.infix))
return path
def remotepath(self, branch, subdir='/'):
if subdir == '/':
subdir = ''
branchpath = self._trunk
branchpath = self.trunk
if branch and branch != 'default':
if branch.startswith('../'):
branchpath = branch[3:]
else:
branchpath = ''.join((self._branch_dir, branch, self._infix))
branchpath = ''.join((self.meta.branchdir, branch,
self.meta.infix))
return '%s/%s' % (subdir or '', branchpath)
def taglocations(self, meta_data_dir):
# import late to avoid trouble when running the test suite
try:
# newer versions of mercurial >= 2.8 will import this because the
# hgext_ logic is already being done in core
from hgsubversion import util
except ImportError:
from hgext_hgsubversion import util
if self._tag_locations is None:
tag_locations_file = os.path.join(meta_data_dir, 'tag_locations')
if os.path.exists(tag_locations_file):
f = open(tag_locations_file)
self._tag_locations = pickle.load(f)
f.close()
else:
self._tag_locations = self.ui.configlist('hgsubversion',
'tagpaths',
['tags'])
util.pickle_atomic(self._tag_locations, tag_locations_file)
# ensure nested paths are handled properly
self._tag_locations.sort()
self._tag_locations.reverse()
return self._tag_locations
@property
def taglocations(self):
return self.meta.taglocations
def get_path_tag(self, path, taglocations):
for tagspath in taglocations:
......@@ -113,22 +99,22 @@ class StandardLayout(base.BaseLayout):
return candidate, '/'.join(components)
if path == 'trunk' or path.startswith('trunk/'):
return self._trunk, path[len(self._trunk) + 1:]
return self.trunk, path[len(self.trunk) + 1:]
if path.startswith(self._branch_dir):
path = path[len(self._branch_dir):]
if path.startswith(self.meta.branchdir):
path = path[len(self.meta.branchdir):]
components = path.split('/', 1)
branch_path = ''.join((self._branch_dir, components[0]))
branch_path = ''.join((self.meta.branchdir, components[0]))
if len(components) == 1:
local_path = ''
else:
local_path = components[1]
if local_path == '':
branch_path += self._infix
elif local_path.startswith(self._infix[1:] + '/'):
branch_path += self._infix
local_path = local_path[len(self._infix):]
branch_path += self.meta.infix
elif local_path.startswith(self.meta.infix[1:] + '/'):
branch_path += self.meta.infix
local_path = local_path[len(self.meta.infix):]
return branch_path, local_path
components = path.split('/')
......
This diff is collapsed.
......@@ -65,13 +65,13 @@ def _convert_rev(ui, meta, svn, r, tbdelta, firstrun):
editor.current.rev = r
editor.setsvn(svn)
if firstrun and meta.revmap.oldest <= 0:
if firstrun and meta.firstpulled <= 0:
# We know nothing about this project, so fetch everything before
# trying to apply deltas.
ui.debug('replay: fetching full revision\n')
svn.get_revision(r.revnum, editor)
else:
svn.get_replay(r.revnum, editor, meta.revmap.oldest)
svn.get_replay(r.revnum, editor, meta.firstpulled)
editor.close()
current = editor.current
......@@ -147,7 +147,10 @@ def _convert_rev(ui, meta, svn, r, tbdelta, firstrun):
def filectxfn(repo, memctx, path):
current_file = files[path]
data, isexec, islink, copied = current.pop(current_file)
try:
data, isexec, islink, copied = current.pop(current_file)
except IOError:
return compathacks.filectxfn_deleted_reraise(memctx)
if isexec is None or islink is None:
flags = parentctx.flags(path)
if isexec is None:
......@@ -174,7 +177,7 @@ def _convert_rev(ui, meta, svn, r, tbdelta, firstrun):
meta.mapbranch(extra)
current_ctx = context.memctx(meta.repo,
parents,
util.getmessage(ui, rev),
meta.getmessage(rev),
files.keys(),
filectxfn,
meta.authors[rev.author],
......@@ -211,7 +214,7 @@ def _convert_rev(ui, meta, svn, r, tbdelta, firstrun):
current_ctx = context.memctx(meta.repo,
(ha, node.nullid),