# HG changeset patch # User Dan Villiom Podlaski Christiansen # Date 1242407923 -7200 # Node ID 75f082b5897e01f9896ae7de7a5e1915c85c29a6 # Parent 5f8f2fd4fd54fea87cf1790e013ec80d4ed1910d Switch to using url scheme wrappers instead of duplicating each command we wrap. The 'hg svn url' command has been killed; the replacement is '.hg/hgrc'. More stuff related to its disappearance has been stripped, including two tests. HgChangeReceiver now takes a UUID argument, which it uses to ensure that remote repositories remain unchanged. This is a temporary solution, and I'm not entirely satisfied with how it's done either. Access to the UUID file has been isolated in a HgChangeReceiver property. Some more tests have been updated to use ui.pushbuffer()/popbuffer(), and to pass through the Mercurial API. Moved the arguments to wrappers.pull() to the UI configuration. Also, remove HgChangeReceiver.opts in favour of a 'usebranchnames' instance & configuration variable. The name is taken from the ConvertExtension. diff --git a/__init__.py b/__init__.py --- a/__init__.py +++ b/__init__.py @@ -11,6 +11,8 @@ Before using hgsubversion, it is *strong automated tests. See `README' in the hgsubversion directory for details. ''' +# TODO: The docstring should be slightly more helpful, and at least mention all +# configuration settings we support import os import sys @@ -29,6 +31,32 @@ import util import wrappers import svnexternals +schemes = ('svn', 'svn+ssh', 'svn+http', 'svn+file') + +optionmap = { + 'tagpaths': ('hgsubversion', 'tagpaths'), + 'authors': ('hgsubversion', 'authormap'), + 'filemap': ('hgsubversion', 'filemap'), + 'stupid': ('hgsubversion', 'stupid'), + 'defaulthost': ('hgsubversion', 'defaulthost'), + 'defaultauthors': ('hgsubversion', 'defaultauthors'), + 'usebranchnames': ('hgsubversion', 'usebranchnames'), +} + +def wrapper(orig, ui, repo, *args, **opts): + """ + Subversion repositories are also supported for this command. See + `hg help %(extension)s` for details. + """ + for opt, (section, name) in optionmap.iteritems(): + if opt in opts: + if isinstance(repo, str): + ui.setconfig(section, name, opts.pop(opt)) + else: + repo.ui.setconfig(section, name, opts.pop(opt)) + + return orig(ui, repo, *args, **opts) + def uisetup(ui): """Do our UI setup. @@ -46,11 +74,21 @@ def uisetup(ui): wrappers.diff) entry[1].append(('', 'svn', None, "show svn-style diffs, default against svn parent")) - entry = extensions.wrapcommand(commands.table, 'push', - wrappers.push) - entry[1].append(('', 'svn', None, "push to subversion")) - entry[1].append(('', 'svn-stupid', None, "use stupid replay during push to svn")) + newflags = (('A', 'authors', '', 'path to file containing username ' + 'mappings for Subversion sources'), + ('', 'filemap', '', 'path to file containing rules for file ' + 'name mapping used for sources)'), + ('T', 'tagpaths', ['tags'], 'list of paths to search for tags ' + 'in Subversion repositories.')) + extname = __package__.split('_')[-1] + + for command in ['clone']: + doc = wrapper.__doc__.strip() % { 'extension': extname } + getattr(commands, command).__doc__ += doc + entry = extensions.wrapcommand(commands.table, command, wrapper) + entry[1].extend(newflags) + try: rebase = extensions.find('rebase') if rebase: @@ -100,7 +138,7 @@ def reposetup(ui, repo): if repo.local(): svnrepo.generate_repo_class(ui, repo) -for scheme in ('svn', 'svn+ssh', 'svn+http', 'svn+file'): +for scheme in schemes: hg.schemes[scheme] = svnrepo cmdtable = { diff --git a/hg_delta_editor.py b/hg_delta_editor.py --- a/hg_delta_editor.py +++ b/hg_delta_editor.py @@ -11,6 +11,7 @@ from mercurial import ui from mercurial import util as hgutil from mercurial import revlog from mercurial import node +from mercurial import error from svn import delta from svn import core @@ -74,9 +75,8 @@ class HgChangeReceiver(delta.Editor): def __init__(self, path=None, repo=None, ui_=None, subdir='', author_host='', - tag_locations=['tags'], - authors=None, - filemap=None): + tag_locations=[], + authors=None, filemap=None, uuid=None): """path is the path to the target hg repo. subdir is the subdirectory of the edits *on the svn server*. @@ -87,14 +87,22 @@ class HgChangeReceiver(delta.Editor): if not ui_: ui_ = ui.ui() self.ui = ui_ - self.__setup_repo(repo or path) + self.__setup_repo(repo or path, uuid) + + if not author_host: + author_host = self.ui.config('hgsubversion', 'defaulthost', uuid) + if not authors: + authors = self.ui.config('hgsubversion', 'authormap') + if not filemap: + filemap = self.ui.config('hgsubversion', 'filemap') + if not tag_locations: + tag_locations = self.ui.config('hgsubversion', 'tagpaths', ['tags']) + self.usebranchnames = self.ui.configbool('hgsubversion', + 'usebranchnames', True) self.subdir = subdir if self.subdir and self.subdir[0] == '/': self.subdir = self.subdir[1:] - self.revmap = {} - if os.path.exists(self.revmap_file): - self.revmap = util.parse_revmap(self.revmap_file) self.branches = {} if os.path.exists(self.branch_info_file): f = open(self.branch_info_file) @@ -137,7 +145,7 @@ class HgChangeReceiver(delta.Editor): date = self.lastdate return date - def __setup_repo(self, arg): + def __setup_repo(self, arg, uuid): """Verify the repo is going to work out for us. This method will fail an assertion if the repo exists but doesn't have @@ -154,12 +162,14 @@ class HgChangeReceiver(delta.Editor): raise TypeError("editor requires either a path or a repository " "specified") - if os.path.isdir(self.meta_data_dir) and os.listdir(self.meta_data_dir): - assert os.path.isfile(self.revmap_file) - assert os.path.isfile(self.svn_url_file) - assert os.path.isfile(self.uuid_file) + if not os.path.isdir(self.meta_data_dir): + os.makedirs(self.meta_data_dir) + self._set_uuid(uuid) + + if os.path.isfile(self.revmap_file): + self.revmap = util.parse_revmap(self.revmap_file) else: - os.makedirs(os.path.dirname(self.uuid_file)) + self.revmap = {} f = open(self.revmap_file, 'w') f.write('%s\n' % util.REVMAP_FILE_VERSION) f.flush() @@ -636,7 +646,7 @@ class HgChangeReceiver(delta.Editor): raise IOError files = parentctx.manifest().keys() extra = {} - if not self.opts.get('svn_no_branchnames', False): + if self.usebranchnames: extra['branch'] = 'closed-branches' current_ctx = context.memctx(self.repo, parents, @@ -658,9 +668,7 @@ class HgChangeReceiver(delta.Editor): revlog.nullid) if parents[0] in closed_revs and branch in self.branches_to_delete: continue - extra = util.build_extra(rev.revnum, branch, - open(self.uuid_file).read(), - self.subdir) + extra = util.build_extra(rev.revnum, branch, self.uuid, self.subdir) if branch is not None: if (branch not in self.branches and branch not in self.repo.branchtags()): @@ -691,7 +699,7 @@ class HgChangeReceiver(delta.Editor): data=data, islink=is_link, isexec=is_exec, copied=copied) - if self.opts.get('svn_no_branchnames', False): + if not self.usebranchnames: extra.pop('branch', None) current_ctx = context.memctx(self.repo, parents, @@ -717,10 +725,8 @@ class HgChangeReceiver(delta.Editor): if self.commit_branches_empty[branch]: #pragma: no cover raise hgutil.Abort('Empty commit to an open branch attempted. ' 'Please report this issue.') - extra = util.build_extra(rev.revnum, branch, - open(self.uuid_file).read(), - self.subdir) - if self.opts.get('svn_no_branchnames', False): + extra = util.build_extra(rev.revnum, branch, self.uuid, self.subdir) + if not self.usebranchnames: extra.pop('branch', None) current_ctx = context.memctx(self.repo, (ha, node.nullid), @@ -783,13 +789,31 @@ class HgChangeReceiver(delta.Editor): return self.meta_file_named('rev_map') revmap_file = property(revmap_file) - def svn_url_file(self): - return self.meta_file_named('url') - svn_url_file = property(svn_url_file) + def _get_uuid(self): + return open(self.meta_file_named('uuid')).read() + + def _set_uuid(self, uuid): + if not uuid: + return self._get_uuid() + elif os.path.isfile(self.meta_file_named('uuid')): + stored_uuid = self._get_uuid() + assert stored_uuid + if uuid != stored_uuid: + raise hgutil.Abort('unable to operate on unrelated repository') + else: + return stored_uuid + else: + if uuid: + f = open(self.meta_file_named('uuid'), 'w') + f.write(uuid) + f.flush() + f.close() + return self._get_uuid() + else: + raise hgutil.Abort('unable to operate on unrelated repository') - def uuid_file(self): - return self.meta_file_named('uuid') - uuid_file = property(uuid_file) + uuid = property(_get_uuid, _set_uuid, None, + 'Error-checked UUID of source Subversion repository.') def branch_info_file(self): return self.meta_file_named('branch_info') @@ -803,10 +827,6 @@ class HgChangeReceiver(delta.Editor): return self.meta_file_named('tag_locations') tag_locations_file = property(tag_locations_file) - def url(self): - return open(self.svn_url_file).read() - url = property(url) - def authors_file(self): return self.meta_file_named('authors') authors_file = property(authors_file) diff --git a/stupid.py b/stupid.py --- a/stupid.py +++ b/stupid.py @@ -527,7 +527,7 @@ def svn_server_pull_rev(ui, svn, hg_edit date, extra) branch = extra.get('branch', None) - if hg_editor.opts.get('svn_no_branchnames', False): + if not hg_editor.usebranchnames: extra.pop('branch', None) ha = hg_editor.repo.commitctx(current_ctx) if not branch in hg_editor.branches: @@ -561,7 +561,7 @@ def svn_server_pull_rev(ui, svn, hg_edit closed = hg_editor.repo['closed-branches'].node() parents = (parent, closed) extra = {} - if not hg_editor.opts.get('svn_no_branchnames', False): + if hg_editor.usebranchnames: extra['branch'] = 'closed-branches' current_ctx = context.memctx(hg_editor.repo, parents, diff --git a/svncommands.py b/svncommands.py --- a/svncommands.py +++ b/svncommands.py @@ -30,16 +30,10 @@ def incoming(ui, svn_url, hg_repo_path, author_host=author_host, tag_locations=tag_locations, authors=authors, - filemap=filemap) - if os.path.exists(hg_editor.uuid_file): - uuid = open(hg_editor.uuid_file).read() - assert uuid == svn.uuid - start = hg_editor.last_known_revision() - else: - open(hg_editor.uuid_file, 'w').write(svn.uuid) - open(hg_editor.svn_url_file, 'w').write(svn_url) - initializing_repo = True - start = skipto_rev + filemap=filemap, + uuid=svn.uuid) + start = max(hg_editor.last_known_revision(), skipto_rev) + initializing_repo = (hg_editor.last_known_revision() <= 0) if initializing_repo and start > 0: raise hgutil.Abort('Revision skipping at repository initialization ' @@ -65,9 +59,13 @@ def rebuildmeta(ui, repo, hg_repo_path, """rebuild hgsubversion metadata using values stored in revisions """ if len(args) != 1: - raise hgutil.Abort('You must pass the svn URI used to create this repo.') + url = repo.ui.expandpath(dest or 'default-push', dest or 'default') + else: + url = args[0] + if not (url.startswith('svn+') or url.startswith('svn:')): + raise hgutil.Abort('No valid Subversion URI found; please specify one.') uuid = None - url = args[0].rstrip('/') + url = util.normalize_url(url.rstrip('/')) user, passwd = util.getuserpass(opts) svn = svnwrap.SubversionRepo(url, user, passwd) subdir = svn.subdir @@ -95,9 +93,6 @@ def rebuildmeta(ui, repo, hg_repo_path, if uuid is None: uuid = convinfo[4:40] assert uuid == svn.uuid, 'UUIDs did not match!' - urlfile = open(os.path.join(svnmetadir, 'url'), 'w') - urlfile.write(url) - urlfile.close() uuidfile = open(os.path.join(svnmetadir, 'uuid'), 'w') uuidfile.write(uuid) uuidfile.close() diff --git a/svnexternals.py b/svnexternals.py --- a/svnexternals.py +++ b/svnexternals.py @@ -3,6 +3,7 @@ import cStringIO import os, re, shutil, stat, subprocess from mercurial import util as hgutil from mercurial.i18n import _ +from hgsubversion import util class externalsfile(dict): """Map svn directories to lists of externals entries. @@ -160,7 +161,8 @@ def getsvninfo(svnurl): # Yes, this is ugly, but good enough for now args = ['svn', 'info', '--xml', svnurl] shell = os.name == 'nt' - p = subprocess.Popen(args, stdout=subprocess.PIPE, shell=shell) + p = subprocess.Popen(args, shell=shell, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout = p.communicate()[0] if p.returncode: raise hgutil.Abort(_('cannot get information about %s') @@ -240,21 +242,23 @@ class externalsupdater: args = ['svn'] + args self.ui.debug(_('updating externals: %r, cwd=%s\n') % (args, cwd)) shell = os.name == 'nt' - subprocess.check_call(args, cwd=cwd, shell=shell) + subprocess.check_call(args, cwd=cwd, shell=shell, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def updateexternals(ui, args, repo, **opts): """update repository externals """ - if len(args) > 1: + if len(args) > 2: raise hgutil.Abort(_('updateexternals expects at most one changeset')) node = None + if len(args) == 2: + svnurl = util.normalize_url(repo.ui.expandpath(args[0])) + args = args[1:] + else: + svnurl = util.normalize_url(repo.ui.expandpath('default')) if args: node = args[0] - try: - svnurl = file(repo.join('svn/url'), 'rb').read() - except: - raise hgutil.Abort(_('failed to retrieve original svn URL')) svnroot = getsvninfo(svnurl)[1] # Retrieve current externals status diff --git a/svnrepo.py b/svnrepo.py --- a/svnrepo.py +++ b/svnrepo.py @@ -1,3 +1,19 @@ +""" +repository class-based interface for hgsubversion + + Copyright (C) 2009, Dan Villiom Podlaski Christiansen + See parent package for licensing. + +Internally, Mercurial assumes that every single repository is a localrepository +subclass: pull() is called on the instance pull *to*, but not the one pulled +*from*. To work around this, we create two classes: + +- svnremoterepo for Subversion repositories, but it doesn't really do anything. +- svnlocalrepo for local repositories which handles both operations on itself -- + the local, hgsubversion-enabled clone -- and the remote repository. Decorators + are used to distinguish and filter these operations from others. +""" + from mercurial import node from mercurial import util as hgutil import mercurial.repo @@ -7,39 +23,44 @@ import util import wrappers def generate_repo_class(ui, repo): + """ This function generates the local repository wrapper. """ + def localsvn(fn): - ''' + """ Filter for instance methods which only apply to local Subversion repositories. - ''' + """ if util.is_svn_repo(repo): return fn else: - original = repo.__getattribute__(fn.__name__) - return original + return getattr(repo, fn.__name__) def remotesvn(fn): - ''' + """ Filter for instance methods which require the first argument to be a remote Subversion repository instance. - ''' - original = repo.__getattribute__(fn.__name__) + """ + original = getattr(repo.__class__, fn.__name__) def wrapper(self, *args, **opts): - if not isinstance(args[0], svnremoterepo): - return original(*args, **opts) - else: + if 'subversion' in getattr(args[0], 'capabilities', []): return fn(self, *args, **opts) + else: + return original(self, *args, **opts) wrapper.__name__ = fn.__name__ + '_wrapper' wrapper.__doc__ = fn.__doc__ return wrapper class svnlocalrepo(repo.__class__): + @remotesvn + def push(self, remote, force=False, revs=None): + # TODO: pass on revs + wrappers.push(self, dest=remote.svnurl, force=force, revs=None) + @remotesvn def pull(self, remote, heads=None, force=False): try: lock = self.wlock() - wrappers.pull(None, self.ui, self, source=remote.path, - svn=True, rev=heads, force=force) + wrappers.pull(self, source=remote.svnurl, rev=heads, force=force) except KeyboardInterrupt: pass finally: @@ -51,16 +72,22 @@ def generate_repo_class(ui, repo): hg_editor = hg_delta_editor.HgChangeReceiver(repo=self) for tag, source in hg_editor.tags.iteritems(): target = hg_editor.get_parent_revision(source[1]+1, source[0]) - tags['tag/%s' % tag] = node.hex(target) + tags['tag/%s' % tag] = target return tags repo.__class__ = svnlocalrepo class svnremoterepo(mercurial.repo.repository): + """ the dumb wrapper for actual Subversion repositories """ + def __init__(self, ui, path): self.ui = ui self.path = path - self.capabilities = set(['lookup']) + self.capabilities = set(['lookup', 'subversion']) + + @property + def svnurl(self): + return util.normalize_url(self.path) def url(self): return self.path @@ -82,6 +109,4 @@ def instance(ui, url, create): if create: raise hgutil.Abort('cannot create new remote Subversion repository') - if url.startswith('svn+') and not url.startswith('svn+ssh:'): - url = url[4:] - return svnremoterepo(ui, util.normalize_url(url)) + return svnremoterepo(ui, url) diff --git a/tests/test_diff.py b/tests/test_diff.py --- a/tests/test_diff.py +++ b/tests/test_diff.py @@ -32,9 +32,9 @@ class DiffTests(test_util.TestBase): ('alpha', 'alpha', 'alpha\n\nadded line\n'), ]) u = ui.ui() - wrappers.diff(lambda x,y,z: None, - u, self.repo, svn=True) - self.assertEqual(u.stream.getvalue(), expected_diff_output) + u.pushbuffer() + wrappers.diff(lambda x,y,z: None, u, self.repo, svn=True) + self.assertEqual(u.popbuffer(), expected_diff_output) def suite(): diff --git a/tests/test_fetch_mappings.py b/tests/test_fetch_mappings.py --- a/tests/test_fetch_mappings.py +++ b/tests/test_fetch_mappings.py @@ -3,11 +3,11 @@ import os import unittest +from mercurial import commands from mercurial import ui from mercurial import node import test_util -import wrappers class MapTests(test_util.TestBase): @property @@ -23,8 +23,11 @@ class MapTests(test_util.TestBase): authormap = open(self.authors, 'w') authormap.write("Augie=Augie Fackler \n") authormap.close() - wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), - dest=self.wc_path, stupid=stupid, svn_authors=self.authors) + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + _ui.setconfig('hgsubversion', 'authormap', self.authors) + commands.clone(_ui, test_util.fileurl(self.repo_path), + self.wc_path, authors=self.authors) self.assertEqual(self.repo[0].user(), 'Augie Fackler ') self.assertEqual(self.repo['tip'].user(), @@ -38,9 +41,11 @@ class MapTests(test_util.TestBase): authormap = open(self.authors, 'w') authormap.write("evil=Testy ") authormap.close() - wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), - dest=self.wc_path, stupid=stupid, - svn_authors=self.authors) + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + _ui.setconfig('hgsubversion', 'authormap', self.authors) + commands.clone(_ui, test_util.fileurl(self.repo_path), + self.wc_path, authors=self.authors) self.assertEqual(self.repo[0].user(), 'Augie@5b65bade-98f3-4993-a01f-b7a6710da339') self.assertEqual(self.repo['tip'].user(), @@ -54,9 +59,11 @@ class MapTests(test_util.TestBase): filemap = open(self.filemap, 'w') filemap.write("include alpha\n") filemap.close() - wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), - dest=self.wc_path, stupid=stupid, - svn_filemap=self.filemap) + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + _ui.setconfig('hgsubversion', 'filemap', self.filemap) + commands.clone(_ui, test_util.fileurl(self.repo_path), + self.wc_path, filemap=self.filemap) self.assertEqual(node.hex(self.repo[0].node()), '88e2c7492d83e4bf30fbb2dcbf6aa24d60ac688d') self.assertEqual(node.hex(self.repo['default'].node()), 'e524296152246b3837fe9503c83b727075835155') @@ -68,9 +75,11 @@ class MapTests(test_util.TestBase): filemap = open(self.filemap, 'w') filemap.write("exclude alpha\n") filemap.close() - wrappers.clone(None, ui.ui(), source=test_util.fileurl(self.repo_path), - dest=self.wc_path, stupid=stupid, - svn_filemap=self.filemap) + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + _ui.setconfig('hgsubversion', 'filemap', self.filemap) + commands.clone(_ui, test_util.fileurl(self.repo_path), + self.wc_path, filemap=self.filemap) self.assertEqual(node.hex(self.repo[0].node()), '2c48f3525926ab6c8b8424bcf5eb34b149b61841') self.assertEqual(node.hex(self.repo['default'].node()), 'b37a3c0297b71f989064d9b545b5a478bbed7cc1') diff --git a/tests/test_fetch_truncated.py b/tests/test_fetch_truncated.py --- a/tests/test_fetch_truncated.py +++ b/tests/test_fetch_truncated.py @@ -1,39 +1,39 @@ -import unittest - -from mercurial import hg -from mercurial import ui - -import wrappers -import test_util - -class TestFetchTruncatedHistory(test_util.TestBase): - def test_truncated_history(self, stupid=False): - # Test repository does not follow the usual layout - test_util.load_svndump_fixture(self.repo_path, 'truncatedhistory.svndump') - svn_url = test_util.fileurl(self.repo_path + '/project2') - wrappers.clone(None, ui.ui(), source=svn_url, - dest=self.wc_path, stupid=stupid, - noupdate=True) - repo = hg.repository(ui.ui(), self.wc_path) - - # We are converting /project2/trunk coming from: - # - # Changed paths: - # D /project1 - # A /project2/trunk (from /project1:2) - # - # Here a full fetch should be performed since we are starting - # the conversion on an already filled branch. - tip = repo['tip'] - files = tip.manifest().keys() - files.sort() - self.assertEqual(files, ['a', 'b']) - self.assertEqual(repo['tip']['a'].data(), 'a\n') - - def test_truncated_history_stupid(self): - self.test_truncated_history(True) - -def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchTruncatedHistory), - ] - return unittest.TestSuite(all) +import unittest + +from mercurial import commands +from mercurial import hg +from mercurial import ui + +import test_util + +class TestFetchTruncatedHistory(test_util.TestBase): + def test_truncated_history(self, stupid=False): + # Test repository does not follow the usual layout + test_util.load_svndump_fixture(self.repo_path, 'truncatedhistory.svndump') + svn_url = test_util.fileurl(self.repo_path + '/project2') + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + commands.clone(_ui, svn_url, self.wc_path, noupdate=True) + repo = hg.repository(_ui, self.wc_path) + + # We are converting /project2/trunk coming from: + # + # Changed paths: + # D /project1 + # A /project2/trunk (from /project1:2) + # + # Here a full fetch should be performed since we are starting + # the conversion on an already filled branch. + tip = repo['tip'] + files = tip.manifest().keys() + files.sort() + self.assertEqual(files, ['a', 'b']) + self.assertEqual(repo['tip']['a'].data(), 'a\n') + + def test_truncated_history_stupid(self): + self.test_truncated_history(True) + +def suite(): + all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchTruncatedHistory), + ] + return unittest.TestSuite(all) diff --git a/tests/test_pull.py b/tests/test_pull.py --- a/tests/test_pull.py +++ b/tests/test_pull.py @@ -3,9 +3,8 @@ import test_util import os.path import subprocess from mercurial import ui - -import wrappers - +from mercurial import util as hgutil +from mercurial import commands class TestPull(test_util.TestBase): def setUp(self): @@ -22,30 +21,34 @@ class TestPull(test_util.TestBase): if self.svn_wc is None: self.svn_wc = os.path.join(self.tmpdir, 'testsvn_wc') subprocess.call([ - 'svn', 'co', '-q', test_util.fileurl(self.repo_path), + 'svn', 'co', '-q', test_util.fileurl(self.repo_path)[4:], self.svn_wc - ]) + ], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) for filename, contents in changes.iteritems(): # filenames are / separated filename = filename.replace('/', os.path.sep) filename = os.path.join(self.svn_wc, filename) open(filename, 'w').write(contents) - subprocess.call(['svn', 'add', '-q', filename]) # may be redundant + # may be redundant + subprocess.call(['svn', 'add', '-q', filename], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) subprocess.call([ - 'svn', 'commit', '-q', self.svn_wc, '-m', 'test changes']) + 'svn', 'commit', '-q', self.svn_wc, '-m', 'test changes'], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) def test_nochanges(self): - repo = self._load_fixture_and_fetch('single_rev.svndump') - state = repo.parents() - wrappers.pull(None, ui.ui(), repo) - self.assertEqual(state, repo.parents()) + self._load_fixture_and_fetch('single_rev.svndump') + state = self.repo.parents() + commands.pull(self.repo.ui, self.repo) + self.assertEqual(state, self.repo.parents()) def test_onerevision_noupdate(self): repo = self._load_fixture_and_fetch('single_rev.svndump') state = repo.parents() self._add_svn_rev({'trunk/alpha': 'Changed'}) - wrappers.pull(None, ui.ui(), repo) + commands.pull(self.repo.ui, repo) self.assertEqual(state, repo.parents()) self.assertTrue('tip' not in repo[None].tags()) @@ -53,7 +56,7 @@ class TestPull(test_util.TestBase): repo = self._load_fixture_and_fetch('single_rev.svndump') state = repo.parents() self._add_svn_rev({'trunk/alpha': 'Changed'}) - wrappers.pull(None, ui.ui(), repo, update=True) + commands.pull(self.repo.ui, repo, update=True) self.failIfEqual(state, repo.parents()) self.assertTrue('tip' in repo[None].tags()) @@ -62,7 +65,8 @@ class TestPull(test_util.TestBase): self.commitchanges((('alpha', 'alpha', 'Changed another way'),)) state = repo.parents() self._add_svn_rev({'trunk/alpha': 'Changed one way'}) - wrappers.pull(None, ui.ui(), repo, update=True) + self.assertRaises(hgutil.Abort, commands.pull, + self.repo.ui, repo, update=True) self.assertEqual(state, repo.parents()) self.assertTrue('tip' not in repo[None].tags()) self.assertEqual(len(repo.heads()), 2) diff --git a/tests/test_push_command.py b/tests/test_push_command.py --- a/tests/test_push_command.py +++ b/tests/test_push_command.py @@ -1,16 +1,18 @@ +import atexit import os +import random import socket import subprocess import unittest from mercurial import context +from mercurial import commands from mercurial import hg from mercurial import node from mercurial import ui from mercurial import revlog from mercurial import util as hgutil -import wrappers import test_util import time @@ -21,28 +23,21 @@ class PushOverSvnserveTests(test_util.Te test_util.load_svndump_fixture(self.repo_path, 'simple_branch.svndump') open(os.path.join(self.repo_path, 'conf', 'svnserve.conf'), 'w').write('[general]\nanon-access=write\n[sasl]\n') - # Paranoia: we try and connect to localhost on 3689 before we start - # svnserve. If it is running, we force the test to fail early. - user_has_own_svnserve = False - try: - s = socket.socket() - s.settimeout(0.3) - s.connect(('localhost', 3690)) - s.close() - user_has_own_svnserve = True - except: - pass - if user_has_own_svnserve: - assert False, ('You appear to be running your own svnserve!' - ' You can probably ignore this test failure.') - args = ['svnserve', '-d', '--foreground', '-r', self.repo_path] - self.svnserve_pid = subprocess.Popen(args).pid + self.port = random.randint(socket.IPPORT_USERRESERVED, 65535) + self.host = 'localhost' + args = ['svnserve', '--daemon', '--foreground', + '--listen-port=%d' % self.port, + '--listen-host=%s' % self.host, + '--root=%s' % self.repo_path] + svnserve = subprocess.Popen(args, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + self.svnserve_pid = svnserve.pid time.sleep(2) - wrappers.clone(None, ui.ui(), source='svn://localhost/', - dest=self.wc_path, noupdate=True) + commands.clone(ui.ui(), 'svn://%s:%d/' % (self.host, self.port), + self.wc_path, noupdate=True) def tearDown(self): - os.system('kill -9 %d' % self.svnserve_pid) + os.kill(self.svnserve_pid, 9) test_util.TestBase.tearDown(self) def test_push_to_default(self, commit=True): @@ -70,7 +65,7 @@ class PushOverSvnserveTests(test_util.Te return # some tests use this test as an extended setup. hg.update(repo, repo['tip'].node()) oldauthor = repo['tip'].user() - wrappers.push(None, ui.ui(), repo=self.repo) + commands.push(repo.ui, repo) tip = self.repo['tip'] self.assertNotEqual(oldauthor, tip.user()) self.assertNotEqual(tip.node(), old_tip) @@ -172,7 +167,7 @@ class PushTests(test_util.TestBase): newhash = self.repo.commitctx(ctx) repo = self.repo hg.update(repo, newhash) - wrappers.push(None, ui.ui(), repo=repo) + commands.push(repo.ui, repo) self.assertEqual(self.repo['tip'].parents()[0].parents()[0].node(), oldtiphash) self.assertEqual(self.repo['tip'].files(), ['delta', ]) self.assertEqual(self.repo['tip'].manifest().keys(), diff --git a/tests/test_rebuildmeta.py b/tests/test_rebuildmeta.py --- a/tests/test_rebuildmeta.py +++ b/tests/test_rebuildmeta.py @@ -22,10 +22,18 @@ def _do_case(self, name, stupid): os.path.dirname(dest.path), args=[test_util.fileurl(self.repo_path + subdir), ]) + self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')), + 'no .hg/svn directory in the source!') + self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')), + 'no .hg/svn directory in the destination!') dest = hg.repository(u, os.path.dirname(dest.path)) - for tf in ('rev_map', 'uuid', 'url'): - self.assertEqual(open(os.path.join(src.path, 'svn', tf)).read(), - open(os.path.join(dest.path, 'svn', tf)).read()) + for tf in ('rev_map', 'uuid'): + stf = os.path.join(src.path, 'svn', tf) + self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf) + dtf = os.path.join(dest.path, 'svn', tf) + self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf) + self.assertEqual(open(stf).read(), + open(dtf).read()) self.assertEqual(pickle.load(open(os.path.join(src.path, 'svn', 'tag_info'))), pickle.load(open(os.path.join(dest.path, 'svn', diff --git a/tests/test_tags.py b/tests/test_tags.py --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -6,20 +6,15 @@ from mercurial import ui import test_util -import tag_repo +import svnrepo class TestTags(test_util.TestBase): def _load_fixture_and_fetch(self, fixture_name, stupid=False): return test_util.load_fixture_and_fetch(fixture_name, self.repo_path, self.wc_path, stupid=stupid) - def getrepo(self): - ui_ = ui.ui() - repo = hg.repository(ui_, self.wc_path) - repo.__class__ = tag_repo.generate_repo_class(ui_, repo) - return repo - def _test_tag_revision_info(self, repo): + print repo.tags() self.assertEqual(node.hex(repo[0].node()), '434ed487136c1b47c1e8f952edb4dc5a8e6328df') self.assertEqual(node.hex(repo['tip'].node()), @@ -30,7 +25,7 @@ class TestTags(test_util.TestBase): repo = self._load_fixture_and_fetch('basic_tag_tests.svndump', stupid=stupid) self._test_tag_revision_info(repo) - repo = self.getrepo() + repo = self.repo self.assertEqual(repo['tip'].node(), repo['tag/tag_r3'].node()) self.assertEqual(repo['tip'].node(), repo['tag/copied_tag'].node()) @@ -41,7 +36,7 @@ class TestTags(test_util.TestBase): repo = self._load_fixture_and_fetch('remove_tag_test.svndump', stupid=stupid) self._test_tag_revision_info(repo) - repo = self.getrepo() + repo = self.repo self.assertEqual(repo['tip'].node(), repo['tag/tag_r3'].node()) self.assert_('tag/copied_tag' not in repo.tags()) @@ -52,7 +47,7 @@ class TestTags(test_util.TestBase): repo = self._load_fixture_and_fetch('rename_tag_test.svndump', stupid=stupid) self._test_tag_revision_info(repo) - repo = self.getrepo() + repo = self.repo self.assertEqual(repo['tip'].node(), repo['tag/tag_r3'].node()) self.assertEqual(repo['tip'].node(), repo['tag/other_tag_r3'].node()) self.assert_('tag/copied_tag' not in repo.tags()) @@ -63,7 +58,7 @@ class TestTags(test_util.TestBase): def test_branch_from_tag(self, stupid=False): repo = self._load_fixture_and_fetch('branch_from_tag.svndump', stupid=stupid) - repo = self.getrepo() + repo = self.repo self.assertEqual(repo['tip'].node(), repo['branch_from_tag'].node()) self.assertEqual(repo[1].node(), repo['tag/tag_r3'].node()) self.assertEqual(repo['branch_from_tag'].parents()[0].node(), @@ -75,7 +70,7 @@ class TestTags(test_util.TestBase): def test_tag_by_renaming_branch(self, stupid=False): repo = self._load_fixture_and_fetch('tag_by_rename_branch.svndump', stupid=stupid) - repo = self.getrepo() + repo = self.repo self.assertEqual(repo['tip'], repo['closed-branches']) self.assertEqual(node.hex(repo['tip'].node()), '2f0a3abe2004c0fa01f5f6074a8b5441e9c80c2a') diff --git a/tests/test_util.py b/tests/test_util.py --- a/tests/test_util.py +++ b/tests/test_util.py @@ -9,12 +9,11 @@ import unittest import urllib from mercurial import context +from mercurial import commands from mercurial import hg from mercurial import node from mercurial import ui -import wrappers - # Fixtures that need to be pulled at a subdirectory of the repo path subdir = {'truncatedhistory.svndump': '/project2', 'fetch_missing_files_subdir.svndump': '/foo', @@ -37,22 +36,25 @@ def load_svndump_fixture(path, fixture_n '''Loads an svnadmin dump into a fresh repo at path, which should not already exist. ''' - subprocess.call(['svnadmin', 'create', path,]) - proc = subprocess.Popen(['svnadmin', 'load', path,], stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + if os.path.exists(path): rmtree(path) + subprocess.call(['svnadmin', 'create', path,], + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) inp = open(os.path.join(FIXTURES, fixture_name)) - proc.stdin.write(inp.read()) - proc.stdin.flush() + proc = subprocess.Popen(['svnadmin', 'load', path,], stdin=inp, + stdout=subprocess.PIPE, stderr=subprocess.STDOUT) proc.communicate() def load_fixture_and_fetch(fixture_name, repo_path, wc_path, stupid=False, subdir='', noupdate=True): load_svndump_fixture(repo_path, fixture_name) if subdir: repo_path += '/' + subdir - wrappers.clone(None, ui.ui(), source=fileurl(repo_path), - dest=wc_path, stupid=stupid, noupdate=noupdate) - repo = hg.repository(ui.ui(), wc_path) - return repo + + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + commands.clone(_ui, fileurl(repo_path), wc_path, noupdate=noupdate) + _ui = ui.ui() + _ui.setconfig('hgsubversion', 'stupid', str(stupid)) + return hg.repository(_ui, wc_path) def rmtree(path): # Read-only files cannot be removed under Windows @@ -69,30 +71,6 @@ def rmtree(path): os.chmod(f, s.st_mode | stat.S_IWRITE) shutil.rmtree(path) - -class MockUI(object): - real_ui = ui.ui - _isatty = False - def __init__(self, src=None): - self.stream = StringIO.StringIO() - self.inner_ui = self.real_ui(src) - - def status(self, *args): - self.stream.write(''.join(args)) - - def warn(self, *args): - self.stream.write(*args) - - def write(self, *args): - self.stream.write(*args) - - def copy(self): - return self.__class__(self.inner_ui) - - def __getattr__(self, attr): - return getattr(self.inner_ui, attr) - - class TestBase(unittest.TestCase): def setUp(self): self.oldwd = os.getcwd() @@ -105,13 +83,19 @@ class TestBase(unittest.TestCase): self.repo_path = '%s/testrepo' % self.tmpdir self.wc_path = '%s/testrepo_wc' % self.tmpdir - self._real_ui = ui.ui - ui.ui = MockUI + + # Previously, we had a MockUI class that wrapped ui, and giving access + # to the stream. The ui.pushbuffer() and ui.popbuffer() can be used + # instead. Using the regular UI class, with all stderr redirected to + # stdout ensures that the test setup is much more similar to usage + # setups. + self._ui_write_err = ui.ui.write_err + ui.ui.write_err = ui.ui.write def tearDown(self): rmtree(self.tmpdir) os.chdir(self.oldwd) - ui.ui = self._real_ui + ui.ui.write_err = self._ui_write_err def _load_fixture_and_fetch(self, fixture_name, subdir='', stupid=False): return load_fixture_and_fetch(fixture_name, self.repo_path, @@ -125,7 +109,8 @@ class TestBase(unittest.TestCase): def pushrevisions(self, stupid=False): before = len(self.repo) - wrappers.push(None, ui.ui(), repo=self.repo, stupid=stupid) + self.repo.ui.setconfig('hgsubversion', 'stupid', str(stupid)) + commands.push(self.repo.ui, self.repo) after = len(self.repo) self.assertEqual(0, after - before) @@ -135,7 +120,7 @@ class TestBase(unittest.TestCase): args = ['svn', 'ls', '-r', rev, '-R', path] p = subprocess.Popen(args, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) + stderr=subprocess.STDOUT) stdout, stderr = p.communicate() if p.returncode: raise Exception('svn ls failed on %s: %r' % (path, stderr)) diff --git a/tests/test_utility_commands.py b/tests/test_utility_commands.py --- a/tests/test_utility_commands.py +++ b/tests/test_utility_commands.py @@ -8,6 +8,7 @@ from mercurial import revlog from mercurial import context from mercurial import node +import util import utility_commands import test_util import wrappers @@ -23,32 +24,40 @@ Last Changed Date: %(date)s ''' class UtilityTests(test_util.TestBase): + @property + def repourl(self): + return util.normalize_url(test_util.fileurl(self.repo_path)) + def test_info_output(self): self._load_fixture_and_fetch('two_heads.svndump') hg.update(self.repo, 'the_branch') u = ui.ui() + u.pushbuffer() utility_commands.info(u, self.repo, self.wc_path) + actual = u.popbuffer() expected = (expected_info_output % {'date': '2008-10-08 01:39:05 +0000 (Wed, 08 Oct 2008)', - 'repourl': test_util.fileurl(self.repo_path), + 'repourl': self.repourl, 'branch': 'branches/the_branch', 'rev': 5, }) - self.assertEqual(u.stream.getvalue(), expected) + self.assertEqual(actual, expected) hg.update(self.repo, 'default') - u = ui.ui() + u.pushbuffer() utility_commands.info(u, self.repo, self.wc_path) + actual = u.popbuffer() expected = (expected_info_output % {'date': '2008-10-08 01:39:29 +0000 (Wed, 08 Oct 2008)', - 'repourl': test_util.fileurl(self.repo_path), + 'repourl': self.repourl, 'branch': 'trunk', 'rev': 6, }) - self.assertEqual(u.stream.getvalue(), expected) + self.assertEqual(actual, expected) def test_parent_output(self): self._load_fixture_and_fetch('two_heads.svndump') u = ui.ui() + u.pushbuffer() parents = (self.repo['the_branch'].node(), revlog.nullid, ) def filectxfn(repo, memctx, path): return context.memfilectx(path=path, @@ -67,7 +76,8 @@ class UtilityTests(test_util.TestBase): new = self.repo.commitctx(ctx) hg.update(self.repo, new) wrappers.parent(lambda x, y: None, u, self.repo, svn=True) - self.assertEqual(u.stream.getvalue(), + actual = u.popbuffer() + self.assertEqual(actual, 'changeset: 3:4e256962fc5d\n' 'branch: the_branch\n' 'user: durin@df2126f7-00ab-4d49-b42c-7e981dde0bcf\n' @@ -76,19 +86,22 @@ class UtilityTests(test_util.TestBase): hg.update(self.repo, 'default') # Make sure styles work - u = ui.ui() + u.pushbuffer() wrappers.parent(lambda x, y: None, u, self.repo, svn=True, style='compact') - self.assertEqual(u.stream.getvalue(), + actual = u.popbuffer() + self.assertEqual(actual, '4:1 1083037b18d8 2008-10-08 01:39 +0000 durin\n' ' Add gamma on trunk.\n\n') # custom templates too - u = ui.ui() + u.pushbuffer() wrappers.parent(lambda x, y: None, u, self.repo, svn=True, template='{node}\n') - self.assertEqual(u.stream.getvalue(), '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n') + actual = u.popbuffer() + self.assertEqual(actual, '1083037b18d85cd84fa211c5adbaeff0fea2cd9f\n') - u = ui.ui() + u.pushbuffer() wrappers.parent(lambda x, y: None, u, self.repo, svn=True) - self.assertEqual(u.stream.getvalue(), + actual = u.popbuffer() + self.assertEqual(actual, 'changeset: 4:1083037b18d8\n' 'parent: 1:c95251e0dd04\n' 'user: durin@df2126f7-00ab-4d49-b42c-7e981dde0bcf\n' @@ -98,6 +111,7 @@ class UtilityTests(test_util.TestBase): def test_outgoing_output(self): self._load_fixture_and_fetch('two_heads.svndump') u = ui.ui() + u.pushbuffer() parents = (self.repo['the_branch'].node(), revlog.nullid, ) def filectxfn(repo, memctx, path): return context.memfilectx(path=path, @@ -116,9 +130,9 @@ class UtilityTests(test_util.TestBase): new = self.repo.commitctx(ctx) hg.update(self.repo, new) wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True) - self.assert_(node.hex(self.repo['localbranch'].node())[:8] in - u.stream.getvalue()) - self.assertEqual(u.stream.getvalue(), ('changeset: 5:6de15430fa20\n' + actual = u.popbuffer() + self.assert_(node.hex(self.repo['localbranch'].node())[:8] in actual) + self.assertEqual(actual, ('changeset: 5:6de15430fa20\n' 'branch: localbranch\n' 'tag: tip\n' 'parent: 3:4e256962fc5d\n' @@ -127,17 +141,10 @@ class UtilityTests(test_util.TestBase): 'summary: automated test\n' '\n')) hg.update(self.repo, 'default') - u = ui.ui() + u.pushbuffer() wrappers.outgoing(lambda x,y,z: None, u, self.repo, svn=True) - self.assertEqual(u.stream.getvalue(), 'no changes found\n') - - def test_url_output(self): - self._load_fixture_and_fetch('two_revs.svndump') - hg.update(self.repo, 'tip') - u = ui.ui() - utility_commands.url(u, self.repo, self.wc_path) - expected = test_util.fileurl(self.repo_path) + '\n' - self.assertEqual(u.stream.getvalue(), expected) + actual = u.popbuffer() + self.assertEqual(actual, 'no changes found\n') def test_rebase(self): self._load_fixture_and_fetch('two_revs.svndump') @@ -165,28 +172,12 @@ class UtilityTests(test_util.TestBase): self.assertEqual(self.repo['tip'].parents()[0].parents()[0], self.repo[0]) self.assertNotEqual(beforerebasehash, self.repo['tip'].node()) - def test_url_is_normalized(self): - """Verify url gets normalized on initial clone. - """ - test_util.load_svndump_fixture(self.repo_path, 'two_revs.svndump') - wrappers.clone(None, ui.ui(), - source=test_util.fileurl(self.repo_path) + '/', - dest=self.wc_path, stupid=False) - hg.update(self.repo, 'tip') - u = ui.ui() - utility_commands.url(u, self.repo, self.wc_path) - expected = test_util.fileurl(self.repo_path) + '\n' - self.assertEqual(u.stream.getvalue(), expected) - def test_genignore(self): - """Verify url gets normalized on initial clone. - """ - test_util.load_svndump_fixture(self.repo_path, 'ignores.svndump') - wrappers.clone(None, ui.ui(), - source=test_util.fileurl(self.repo_path) + '/', - dest=self.wc_path, stupid=False) - hg.update(self.repo, 'tip') + """ Test generation of .hgignore file. """ + test_util.load_fixture_and_fetch('ignores.svndump', self.repo_path, + self.wc_path, noupdate=False) u = ui.ui() + u.pushbuffer() utility_commands.genignore(u, self.repo, self.wc_path) self.assertEqual(open(os.path.join(self.wc_path, '.hgignore')).read(), '.hgignore\nsyntax:glob\nblah\notherblah\nbaz/magic\n') @@ -195,10 +186,12 @@ class UtilityTests(test_util.TestBase): test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump') u = ui.ui() + u.pushbuffer() utility_commands.listauthors(u, args=[test_util.fileurl(self.repo_path)], authors=None) - self.assertEqual(u.stream.getvalue(), 'Augie\nevil\n') + actual = u.popbuffer() + self.assertEqual(actual, 'Augie\nevil\n') def test_list_authors_map(self): diff --git a/utility_commands.py b/utility_commands.py --- a/utility_commands.py +++ b/utility_commands.py @@ -7,14 +7,6 @@ import cmdutil import util import hg_delta_editor -def url(ui, repo, hg_repo_path, **opts): - """show the location (URL) of the Subversion repository - """ - hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, - ui_=ui) - ui.status(hge.url, '\n') - - def genignore(ui, repo, hg_repo_path, force=False, **opts): """generate .hgignore from svn:ignore properties. """ @@ -23,8 +15,11 @@ def genignore(ui, repo, hg_repo_path, fo raise hgutil.Abort('not overwriting existing .hgignore, try --force?') ignorefile = open(ignpath, 'w') ignorefile.write('.hgignore\nsyntax:glob\n') - hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, - ui_=ui) + url = util.normalize_url(repo.ui.config('paths', 'default')) + user, passwd = util.getuserpass(opts) + svn = svnwrap.SubversionRepo(url, user, passwd) + hge = hg_delta_editor.HgChangeReceiver(path=hg_repo_path, repo=repo, + ui_=ui, uuid=svn.uuid) svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) parent = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes) @@ -33,11 +28,8 @@ def genignore(ui, repo, hg_repo_path, fo branchpath = 'trunk' else: branchpath = 'branches/%s' % br - url = hge.url if url[-1] == '/': url = url[:-1] - user, passwd = util.getuserpass(opts) - svn = svnwrap.SubversionRepo(url, user, passwd) dirs = [''] + [d[0] for d in svn.list_files(branchpath, r) if d[1] == 'd'] for dir in dirs: props = svn.list_props('%s/%s/' % (branchpath,dir), r) @@ -53,8 +45,11 @@ def genignore(ui, repo, hg_repo_path, fo def info(ui, repo, hg_repo_path, **opts): """show Subversion details similar to `svn info' """ - hge = hg_delta_editor.HgChangeReceiver(hg_repo_path, - ui_=ui) + url = util.normalize_url(repo.ui.config('paths', 'default')) + user, passwd = util.getuserpass(opts) + svn = svnwrap.SubversionRepo(url, user, passwd) + hge = hg_delta_editor.HgChangeReceiver(path=hg_repo_path, repo=repo, + ui_=ui, uuid=svn.uuid) svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) parent = cmdutil.parentrev(ui, repo, hge, svn_commit_hashes) @@ -71,7 +66,7 @@ def info(ui, repo, hg_repo_path, **opts) subdir = subdir.replace('branches/../', '') else: branchpath = '/branches/%s' % br - url = hge.url + url = util.normalize_url(repo.ui.config('paths', 'default')) if url[-1] == '/': url = url[:-1] url = '%s%s' % (url, branchpath) @@ -87,7 +82,7 @@ Last Changed Author: %(author)s Last Changed Rev: %(revision)s Last Changed Date: %(date)s\n''' % {'reporoot': reporoot, - 'uuid': open(hge.uuid_file).read(), + 'uuid': hge.uuid, 'url': url, 'author': author, 'revision': r, @@ -125,7 +120,6 @@ def version(ui, **opts): nourl = ['version', 'listauthors'] table = { - 'url': url, 'genignore': genignore, 'info': info, 'listauthors': listauthors, diff --git a/wrappers.py b/wrappers.py --- a/wrappers.py +++ b/wrappers.py @@ -87,24 +87,36 @@ def diff(orig, ui, repo, *args, **opts): })) ui.write(cmdutil.filterdiff(''.join(it), baserev, newrev)) - -def push(orig, ui, repo, dest=None, *args, **opts): +def push(repo, dest="default", force=False, revs=None): """push revisions starting at a specified head back to Subversion. """ - opts.pop('svn', None) # unused in this case - svnurl = repo.ui.expandpath(dest or 'default-push', dest or 'default') - if not cmdutil.issvnurl(svnurl): - return orig(ui, repo, dest=dest, *args, **opts) + assert not revs, 'designated revisions for push remains unimplemented.' + print dest + ui = repo.ui + svnurl = util.normalize_url(repo.ui.expandpath(dest)) old_encoding = util.swap_out_encoding() - hge = hg_delta_editor.HgChangeReceiver(repo=repo) - svnurl = util.normalize_url(svnurl) # split of #rev; TODO: implement --rev/#rev support - svnurl, revs, checkout = hg.parseurl(svnurl, opts.get('rev')) - if svnurl != hge.url: - raise hgutil.Abort('wrong subversion url!') - svn_commit_hashes = dict(zip(hge.revmap.itervalues(), - hge.revmap.iterkeys())) - user, passwd = util.getuserpass(opts) + svnurl, revs, checkout = hg.parseurl(svnurl, revs) + # TODO: do credentials specified in the URL still work? + user = repo.ui.config('hgsubversion', 'username') + passwd = repo.ui.config('hgsubversion', 'password') + svn = svnwrap.SubversionRepo(svnurl, user, passwd) + hge = hg_delta_editor.HgChangeReceiver(repo=repo, uuid=svn.uuid) + + # Check if we are up-to-date with the Subversion repository. + if hge.last_known_revision() != svn.last_changed_rev: + # Based on localrepository.push() in localrepo.py:1559. + # TODO: Ideally, we would behave exactly like other repositories: + # - push everything by default + # - handle additional heads in the same way + # - allow pushing single revisions, branches, tags or heads using + # the -r/--rev flag. + if force: + ui.warn("note: unsynced remote changes!\n") + else: + ui.warn("abort: unsynced remote changes!\n") + return None, 0 + # Strategy: # 1. Find all outgoing commits from this head if len(repo.parents()) != 1: @@ -112,6 +124,8 @@ def push(orig, ui, repo, dest=None, *arg return 1 workingrev = repo.parents()[0] ui.status('searching for changes\n') + svn_commit_hashes = dict(zip(hge.revmap.itervalues(), + hge.revmap.iterkeys())) outgoing = util.outgoing_revisions(ui, repo, hge, svn_commit_hashes, workingrev.node()) if not (outgoing and len(outgoing)): ui.status('no changes found\n') @@ -143,12 +157,10 @@ def push(orig, ui, repo, dest=None, *arg old_ctx) return 1 # 3. Fetch revisions from svn - # TODO this probably should pass in the source explicitly - r = pull(None, ui, repo, svn=True, stupid=opts.get('svn_stupid', False), - username=user, password=passwd) + # TODO: this probably should pass in the source explicitly - rev too? + r = pull(repo, source=dest, force=force) assert not r or r == 0 # 4. Find the new head of the target branch - repo = hg.repository(ui, hge.path) oldtipctx = repo[oldtip] replacement = [c for c in oldtipctx.children() if c not in old_children and c.branch() == oldtipctx.branch()] @@ -161,8 +173,9 @@ def push(orig, ui, repo, dest=None, *arg if ctx.node() == oldest: return extra['branch'] = ctx.branch() + # TODO: can we avoid calling our own rebase wrapper here? rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn, - svnsourcerev=needs_transplant, **opts) + svnsourcerev=needs_transplant) repo = hg.repository(ui, hge.path) for child in repo[replacement.node()].children(): rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) @@ -175,7 +188,8 @@ def push(orig, ui, repo, dest=None, *arg if children: child = children[0] rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) - hge = hg_delta_editor.HgChangeReceiver(hge.path, ui_=ui) + # TODO: stop constantly creating the HgChangeReceiver instances. + hge = hg_delta_editor.HgChangeReceiver(hge.repo, ui_=ui, uuid=svn.uuid) svn_commit_hashes = dict(zip(hge.revmap.itervalues(), hge.revmap.iterkeys())) util.swap_out_encoding(old_encoding) return 0 @@ -234,25 +248,20 @@ def clone(orig, ui, source, dest=None, * return res -def pull(orig, ui, repo, source="default", *args, **opts): +def pull(repo, source="default", rev=None, force=False): """pull new revisions from Subversion Also takes svn, svn_stupid, and create_new_dest kwargs. """ - svn = opts.pop('svn', None) - svn_stupid = opts.pop('svn_stupid', False) - create_new_dest = opts.pop('create_new_dest', False) - url = ((repo and repo.ui) or ui).expandpath(source) - if orig and not (cmdutil.issvnurl(url) or svn or create_new_dest): - return orig(ui, repo, source=source, *args, **opts) - svn_url = url - svn_url = util.normalize_url(svn_url) + url = repo.ui.expandpath(source) + svn_url = util.normalize_url(url) + # Split off #rev; TODO: implement --rev/#rev support limiting the pulled/cloned revisions - svn_url, revs, checkout = hg.parseurl(svn_url, opts.get('rev')) + svn_url, revs, checkout = hg.parseurl(svn_url, rev) old_encoding = util.swap_out_encoding() # TODO implement skipto support skipto_rev = 0 - have_replay = not svn_stupid + have_replay = not repo.ui.configbool('hgsubversion', 'stupid') if have_replay and not callable( delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover ui.status('You are using old Subversion SWIG bindings. Replay will not' @@ -261,46 +270,23 @@ def pull(orig, ui, repo, source="default ' contribute a patch to use the ctypes bindings instead' ' of SWIG.\n') have_replay = False - initializing_repo = False - user, passwd = util.getuserpass(opts) + + # FIXME: enable this + user = repo.ui.config('hgsubversion', 'username') + passwd = repo.ui.config('hgsubversion', 'password') svn = svnwrap.SubversionRepo(svn_url, user, passwd) - author_host = ui.config('hgsubversion', 'defaulthost', svn.uuid) - tag_locations = ['tags', ] - authors = opts.pop('svn_authors', None) - filemap = opts.pop('svn_filemap', None) - if repo: - hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo, - subdir=svn.subdir, - author_host=author_host, - tag_locations=tag_locations, - authors=authors, - filemap=filemap) - else: - hg_editor = hg_delta_editor.HgChangeReceiver(ui_=ui, - path=create_new_dest, - subdir=svn.subdir, - author_host=author_host, - tag_locations=tag_locations, - authors=authors, - filemap=filemap) - hg_editor.opts = opts - if os.path.exists(hg_editor.uuid_file): - uuid = open(hg_editor.uuid_file).read() - assert uuid == svn.uuid - start = hg_editor.last_known_revision() - else: - open(hg_editor.uuid_file, 'w').write(svn.uuid) - open(hg_editor.svn_url_file, 'w').write(svn_url) - initializing_repo = True - start = skipto_rev + hg_editor = hg_delta_editor.HgChangeReceiver(repo=repo, subdir=svn.subdir, + uuid=svn.uuid) + + start = max(hg_editor.last_known_revision(), skipto_rev) + initializing_repo = (hg_editor.last_known_revision() <= 0) + ui = repo.ui if initializing_repo and start > 0: raise hgutil.Abort('Revision skipping at repository initialization ' 'remains unimplemented.') revisions = 0 - if not initializing_repo: - oldheads = len(repo.changelog.heads()) # start converting revisions for r in svn.revisions(start=start): @@ -344,14 +330,6 @@ def pull(orig, ui, repo, source="default return else: ui.status("added %d svn revisions\n" % revisions) - if not initializing_repo: - newheads = len(repo.changelog.heads()) - # postincoming needs to know if heads were added or removed - # calculation based on mercurial.localrepo.addchangegroup - # 0 means no changes, 1 no new heads, > 1 new heads, < 0 heads removed - modheads = newheads - oldheads + (newheads < oldheads and -1 or 1) - commands.postincoming(ui, repo, modheads, opts.get('update'), checkout) - def rebase(orig, ui, repo, **opts): """rebase current unpushed revisions onto the Subversion head