Browse Source

Move the swift backend to dulwich/contrib.

Subcommands for initializing repositories on swift are now available
from the dulwich.contrib.swift module. In the future, we could
expose this functionality as a dulwich-swift script. The main reason
I haven't done this yet is to keep it clear that this script is in
contrib.
Jelmer Vernooij 10 years ago
parent
commit
e0282d541c

+ 1 - 1
Makefile

@@ -30,7 +30,7 @@ check:: build
 	$(RUNTEST) dulwich.tests.test_suite
 
 check-tutorial:: build
-	$(RUNTEST) dulwich.tests.tutorial_test_suite 
+	$(RUNTEST) dulwich.tests.tutorial_test_suite
 
 check-nocompat:: build
 	$(RUNTEST) dulwich.tests.nocompat_test_suite

+ 5 - 2
NEWS

@@ -26,8 +26,11 @@
     for concurrency of some object store operations.
     (Fabien Boucher)
 
-   * Various changes to improve compatibility with Python 3.
-     (Gary van der Merwe, Hannu Valtonen, michael-k)
+  * Various changes to improve compatibility with Python 3.
+    (Gary van der Merwe, Hannu Valtonen, michael-k)
+
+  * Add OpenStack Swift backed repository implementation
+    in dulwich.contrib. See README.swift for details. (Fabien Boucher)
 
 API CHANGES
 

+ 8 - 7
README.swift

@@ -1,7 +1,8 @@
 Openstack Swift as backend for Dulwich
 ======================================
+Fabien Boucher <fabien.boucher@enovance.com>
 
-The module dulwich/swift.py implements dulwich.repo.BaseRepo
+The module dulwich/contrib/swift.py implements dulwich.repo.BaseRepo
 in order to being compatible with Openstack Swift.
 We can then use Dulwich as server (Git server) and instead of using
 a regular POSIX file system to store repository objects we use the
@@ -55,7 +56,7 @@ How to start unittest
 There is no need to have a Swift cluster running to run the unitests.
 Just run the following command in the Dulwich source directory:
 
-    $ PYTHONPATH=. nosetests dulwich/tests/test_swift.py
+    $ PYTHONPATH=. python -m dulwich.contrib.test_swift
 
 How to start functional tests
 -----------------------------
@@ -64,7 +65,7 @@ We provide some basic tests to perform smoke tests against a real Swift
 cluster. To run those functional tests you need a properly configured
 configuration file. The tests can be run as follow:
 
-    $ DULWICH_SWIFT_CFG=/etc/swift-dul.conf PYTHONPATH=. nosetests dulwich/tests_swift/test_smoke.py
+    $ DULWICH_SWIFT_CFG=/etc/swift-dul.conf PYTHONPATH=. python -m dulwich.contrib.test_swift_smoke
 
 How to install
 --------------
@@ -79,7 +80,7 @@ How to run the server
 
 Start the server using the following command:
 
-    $ dul-daemon -c /etc/swift-dul.conf -l 127.0.0.1 --backend=swift
+    $ python -m dulwich.contrib.swift daemon -c /etc/swift-dul.conf -l 127.0.0.1
 
 Note that a lot of request will be performed against the Swift
 cluster so it is better to start the Dulwich server as close
@@ -93,7 +94,7 @@ Once you have validated that the functional tests is working as expected and
 the server is running we can init a bare repository. Run this
 command with the name of the repository to create:
 
-    $ dulwich init-swift -c /etc/swift-dul.conf edeploy
+    $ python -m dulwich.contrib.swift init -c /etc/swift-dul.conf edeploy
 
 The repository name will be the container that will contain all the Git
 objects for the repository. Then standard c Git client can be used to
@@ -116,8 +117,8 @@ Then push an existing project in it:
 The other Git commands can be used the way you do usually against
 a regular repository.
 
-Note the swift-dul-daemon start a Git server listening for the
-Git protocol. Therefor there ins't any authentication or encryption
+Note the daemon subcommands starts a Git server listening for the
+Git protocol. Therefor there is no authentication or encryption
 at all between the cGIT client and the GIT server (Dulwich).
 
 Note on the .info file for pack object

+ 0 - 32
bin/dulwich

@@ -38,13 +38,6 @@ from dulwich.pack import Pack, sha_to_hex
 from dulwich.patch import write_tree_diff
 from dulwich.repo import Repo
 
-try:
-    import gevent
-    import geventhttpclient
-    gevent_support = True
-except ImportError:
-    gevent_support = False
-
 
 def cmd_archive(args):
     opts, args = getopt(args, "", [])
@@ -163,30 +156,6 @@ def cmd_init(args):
 
     porcelain.init(path, bare=("--bare" in opts))
 
-def cmd_init_swift(args):
-    if not gevent_support:
-        print "gevent and geventhttpclient libraries are mandatory " \
-              " for use the Swift backend."
-        sys.exit(1)
-    from dulwich.swift import (
-        SwiftRepo,
-        SwiftConnector,
-        load_conf,
-    )
-    opts, args = getopt(args, "c:", [])
-    opts = dict(opts)
-    try:
-        conf = opts['-c']
-        conf = load_conf(conf)
-    except KeyError:
-        conf = load_conf()
-    if args == []:
-        print "Usage: dulwich init-swift [-c config_file] REPONAME"
-        sys.exit(1)
-    else:
-        repo = args[0]
-    scon = SwiftConnector(repo, conf)
-    SwiftRepo.init_bare(scon, conf)
 
 def cmd_clone(args):
     opts, args = getopt(args, "", ["bare"])
@@ -289,7 +258,6 @@ commands = {
     "fetch-pack": cmd_fetch_pack,
     "fetch": cmd_fetch,
     "init": cmd_init,
-    "init-swift": cmd_init_swift,
     "log": cmd_log,
     "reset": cmd_reset,
     "rev-list": cmd_rev_list,

+ 28 - 0
dulwich/contrib/__init__.py

@@ -0,0 +1,28 @@
+# __init__.py -- Contrib module for Dulwich
+# Copyright (C) 2014 Jelmer Vernooij <jelmer@samba.org>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License or (at your option) any later version of
+# the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA  02110-1301, USA.
+
+
+def test_suite():
+    import unittest
+    names = [
+        'swift',
+        ]
+    module_names = ['dulwich.contrib.test_' + name for name in names]
+    loader = unittest.TestLoader()
+    return loader.loadTestsFromNames(module_names)

+ 131 - 51
dulwich/swift.py → dulwich/contrib/swift.py

@@ -36,9 +36,23 @@ from cStringIO import StringIO
 from ConfigParser import ConfigParser
 from geventhttpclient import HTTPClient
 
-from dulwich.repo import (
-    BaseRepo,
-    OBJECTDIR,
+from dulwich.greenthreads import (
+    GreenThreadsMissingObjectFinder,
+    GreenThreadsObjectStoreIterator,
+    )
+
+from dulwich.lru_cache import LRUSizeCache
+from dulwich.objects import (
+    Blob,
+    Commit,
+    Tree,
+    Tag,
+    S_ISGITLINK,
+    )
+from dulwich.object_store import (
+    PackBasedObjectStore,
+    PACKDIR,
+    INFODIR,
     )
 from dulwich.pack import (
     PackData,
@@ -55,26 +69,19 @@ from dulwich.pack import (
     unpack_object,
     write_pack_object,
     )
-from lru_cache import LRUSizeCache
-from dulwich.object_store import (
-    PackBasedObjectStore,
-    PACKDIR,
-    INFODIR,
-    )
+from dulwich.protocol import TCP_GIT_PORT
 from dulwich.refs import (
     InfoRefsContainer,
     read_info_refs,
     write_info_refs,
     )
-from dulwich.objects import (
-    Commit,
-    Tree,
-    Tag,
-    S_ISGITLINK,
+from dulwich.repo import (
+    BaseRepo,
+    OBJECTDIR,
     )
-from dulwich.greenthreads import (
-    GreenThreadsMissingObjectFinder,
-    GreenThreadsObjectStoreIterator,
+from dulwich.server import (
+    Backend,
+    TCPGitServer,
     )
 
 try:
@@ -84,6 +91,8 @@ except ImportError:
     from json import loads as json_loads
     from json import dumps as json_dumps
 
+import sys
+
 
 """
 # Configuration file sample
@@ -114,6 +123,7 @@ cache_length = 20
 
 
 class PackInfoObjectStoreIterator(GreenThreadsObjectStoreIterator):
+
     def __len__(self):
         while len(self.finder.objects_to_send):
             for _ in xrange(0, len(self.finder.objects_to_send)):
@@ -123,6 +133,7 @@ class PackInfoObjectStoreIterator(GreenThreadsObjectStoreIterator):
 
 
 class PackInfoMissingObjectFinder(GreenThreadsMissingObjectFinder):
+
     def next(self):
         while True:
             if not self.objects_to_send:
@@ -188,18 +199,18 @@ def pack_info_create(pack_data, pack_index):
     info = {}
     for obj in pack.iterobjects():
         # Commit
-        if obj.type_num == 1:
+        if obj.type_num == Commit.type_num:
             info[obj.id] = (obj.type_num, obj.parents, obj.tree)
         # Tree
-        elif obj.type_num == 2:
+        elif obj.type_num == Tree.type_num:
             shas = [(s, n, not stat.S_ISDIR(m)) for
                     n, m, s in obj.iteritems() if not S_ISGITLINK(m)]
             info[obj.id] = (obj.type_num, shas)
         # Blob
-        elif obj.type_num == 3:
+        elif obj.type_num == Blob.type_num:
             info[obj.id] = None
         # Tag
-        elif obj.type_num == 4:
+        elif obj.type_num == Tag.type_num:
             info[obj.id] = (obj.type_num, obj._object_sha)
     return zlib.compress(json_dumps(info))
 
@@ -234,8 +245,8 @@ class SwiftConnector(object):
         self.conf = conf
         self.auth_ver = self.conf.get("swift", "auth_ver")
         if self.auth_ver not in ["1", "2"]:
-            raise NotImplementedError("Wrong authentication version \
-                    use either 1 or 2")
+            raise NotImplementedError(
+                "Wrong authentication version use either 1 or 2")
         self.auth_url = self.conf.get("swift", "auth_url")
         self.user = self.conf.get("swift", "username")
         self.password = self.conf.get("swift", "password")
@@ -277,9 +288,7 @@ class SwiftConnector(object):
                    'X-Auth-Key': self.password}
         path = urlparse(self.auth_url).path
 
-        ret = auth_httpclient.request('GET',
-                                      path,
-                                      headers=headers)
+        ret = auth_httpclient.request('GET', path, headers=headers)
 
         # Should do something with redirections (301 in my case)
 
@@ -312,8 +321,7 @@ class SwiftConnector(object):
         path = urlparse(self.auth_url).path
         if not path.endswith('tokens'):
             path = posixpath.join(path, 'tokens')
-        ret = auth_httpclient.request('POST',
-                                      path,
+        ret = auth_httpclient.request('POST', path,
                                       body=auth_json,
                                       headers=headers)
 
@@ -338,8 +346,7 @@ class SwiftConnector(object):
 
         :return: True if exist or None it not
         """
-        ret = self.httpclient.request('HEAD',
-                                      self.base_path)
+        ret = self.httpclient.request('HEAD', self.base_path)
         if ret.status_code == 404:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
@@ -353,8 +360,7 @@ class SwiftConnector(object):
         :raise: `SwiftException` if unable to create
         """
         if not self.test_root_exists():
-            ret = self.httpclient.request('PUT',
-                                          self.base_path)
+            ret = self.httpclient.request('PUT', self.base_path)
             if ret.status_code < 200 or ret.status_code > 300:
                 raise SwiftException('PUT request failed with error code %s'
                                      % ret.status_code)
@@ -367,8 +373,7 @@ class SwiftConnector(object):
         """
         qs = '?format=json'
         path = self.base_path + qs
-        ret = self.httpclient.request('GET',
-                                      path)
+        ret = self.httpclient.request('GET', path)
         if ret.status_code == 404:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
@@ -385,8 +390,7 @@ class SwiftConnector(object):
                  or None if object does not exist
         """
         path = self.base_path + '/' + name
-        ret = self.httpclient.request('HEAD',
-                                      path)
+        ret = self.httpclient.request('HEAD', path)
         if ret.status_code == 404:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
@@ -410,8 +414,7 @@ class SwiftConnector(object):
         headers = {'Content-Length': str(len(data))}
 
         def _send():
-            ret = self.httpclient.request('PUT',
-                                          path,
+            ret = self.httpclient.request('PUT', path,
                                           body=data,
                                           headers=headers)
             return ret
@@ -440,9 +443,7 @@ class SwiftConnector(object):
         if range:
             headers['Range'] = 'bytes=%s' % range
         path = self.base_path + '/' + name
-        ret = self.httpclient.request('GET',
-                                      path,
-                                      headers=headers)
+        ret = self.httpclient.request('GET', path, headers=headers)
         if ret.status_code == 404:
             return None
         if ret.status_code < 200 or ret.status_code > 300:
@@ -461,8 +462,7 @@ class SwiftConnector(object):
         :raise: `SwiftException` if unable to delete
         """
         path = self.base_path + '/' + name
-        ret = self.httpclient.request('DELETE',
-                                      path)
+        ret = self.httpclient.request('DELETE', path)
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException('DELETE request failed with error code %s'
                                  % ret.status_code)
@@ -474,8 +474,7 @@ class SwiftConnector(object):
         """
         for obj in self.get_container_objects():
             self.del_object(obj['name'])
-        ret = self.httpclient.request('DELETE',
-                                      self.base_path)
+        ret = self.httpclient.request('DELETE', self.base_path)
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException('DELETE request failed with error code %s'
                                  % ret.status_code)
@@ -510,10 +509,8 @@ class SwiftPackReader(object):
         if more:
             self.buff_length = self.buff_length * 2
         l = self.base_offset
-        r = min(self.base_offset + self.buff_length,
-                self.pack_length)
-        ret = self.scon.get_object(self.filename,
-                                   range="%s-%s" % (l, r))
+        r = min(self.base_offset + self.buff_length, self.pack_length)
+        ret = self.scon.get_object(self.filename, range="%s-%s" % (l, r))
         self.buff = ret
 
     def read(self, length):
@@ -644,7 +641,7 @@ class SwiftObjectStore(PackBasedObjectStore):
         self.root = self.scon.root
         self.pack_dir = posixpath.join(OBJECTDIR, PACKDIR)
         self._alternates = None
-    
+
     @property
     def packs(self):
         """List with pack objects."""
@@ -889,7 +886,7 @@ class SwiftInfoRefsContainer(InfoRefsContainer):
         self._write_refs(refs)
         del self._refs[name]
         return True
-    
+
     def allkeys(self):
         try:
             self._refs['HEAD'] = self._refs['refs/heads/master']
@@ -951,3 +948,86 @@ class SwiftRepo(BaseRepo):
         ret = cls(scon.root, conf)
         ret._init_files(True)
         return ret
+
+
+class SwiftSystemBackend(Backend):
+
+    def __init__(self, logger, conf):
+        self.conf = conf
+        self.logger = logger
+
+    def open_repository(self, path):
+        self.logger.info('opening repository at %s', path)
+        return SwiftRepo(path, self.conf)
+
+
+def cmd_daemon(args):
+    """Entry point for starting a TCP git server."""
+    import optparse
+    parser = optparse.OptionParser()
+    parser.add_option("-l", "--listen_address", dest="listen_address",
+                      default="127.0.0.1",
+                      help="Binding IP address.")
+    parser.add_option("-p", "--port", dest="port", type=int,
+                      default=TCP_GIT_PORT,
+                      help="Binding TCP port.")
+    parser.add_option("-c", "--swift_config", dest="swift_config",
+                      default="",
+                      help="Path to the configuration file for Swift backend.")
+    options, args = parser.parse_args(args)
+
+    try:
+        import gevent
+        import geventhttpclient
+    except ImportError:
+        print("gevent and geventhttpclient libraries are mandatory "
+              " for use the Swift backend.")
+        sys.exit(1)
+    import gevent.monkey
+    gevent.monkey.patch_socket()
+    from dulwich.swift import load_conf
+    from dulwich import log_utils
+    logger = log_utils.getLogger(__name__)
+    conf = load_conf(options.swift_config)
+    backend = SwiftSystemBackend(logger, conf)
+
+    log_utils.default_logging_config()
+    server = TCPGitServer(backend, options.listen_address,
+                          port=options.port)
+    server.serve_forever()
+
+
+def cmd_init(args):
+    import optparse
+    parser = optparse.OptionParser()
+    parser.add_option("-c", "--swift_config", dest="swift_config",
+                      default="",
+                      help="Path to the configuration file for Swift backend.")
+    options, args = parser.parse_args(args)
+
+    conf = load_conf(options.swift_config)
+    if args == []:
+        parser.error("missing repository name")
+    repo = args[0]
+    scon = SwiftConnector(repo, conf)
+    SwiftRepo.init_bare(scon, conf)
+
+
+def main(argv=sys.argv):
+    commands = {
+        "init": cmd_init,
+        "daemon": cmd_daemon,
+    }
+
+    if len(sys.argv) < 2:
+        print("Usage: %s <%s> [OPTIONS...]" % (sys.argv[0], "|".join(commands.keys())))
+        sys.exit(1)
+
+    cmd = sys.argv[1]
+    if not cmd in commands:
+        print("No such subcommand: %s" % cmd)
+        sys.exit(1)
+    commands[cmd](sys.argv[2:])
+
+if __name__ == '__main__':
+    main()

+ 47 - 37
dulwich/tests/test_swift.py → dulwich/contrib/test_swift.py

@@ -19,17 +19,16 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # MA  02110-1301, USA.
 
-"""Tests for dulwich.swift."""
+"""Tests for dulwich.contrib.swift."""
 
 import posixpath
 
 from time import time
 from cStringIO import StringIO
-from contextlib import nested
+from unittest import skipIf
 
 from dulwich.tests import (
     TestCase,
-    skipIf,
     )
 from dulwich.tests.test_object_store import (
     ObjectStoreTests,
@@ -56,16 +55,27 @@ try:
 except ImportError:
     from json import dumps as json_dumps
 
+missing_libs = []
+
 try:
     import gevent
+except ImportError:
+    missing_libs.append("gevent")
+
+try:
     import geventhttpclient
+except ImportError:
+    missing_libs.append("geventhttpclient")
+
+try:
     from mock import patch
-    lib_support = True
-    from dulwich import swift
 except ImportError:
-    lib_support = False
+    missing_libs.append("mock")
+
+skipmsg = "Required libraries are not installed (%r)" % missing_libs
 
-skipmsg = "Required libraries are not installed (gevent, geventhttpclient, mock)"
+if not missing_libs:
+    from dulwich.contrib import swift
 
 config_file = """[swift]
 auth_url = http://127.0.0.1:8080/auth/%(version_str)s
@@ -99,6 +109,7 @@ def create_swift_connector(store={}):
 
 
 class Response(object):
+
     def __init__(self, headers={}, status=200, content=None):
         self.headers = headers
         self.status_code = status
@@ -125,6 +136,7 @@ def fake_auth_request_v1(*args, **kwargs):
                    200)
     return ret
 
+
 def fake_auth_request_v1_error(*args, **kwargs):
     ret = Response({},
                    401)
@@ -153,7 +165,7 @@ def create_commit(data, marker='Default', blob=None):
     if not blob:
         blob = Blob.from_string('The blob content %s' % marker)
     tree = Tree()
-    tree.add("thefile_%s" % marker, 0100644, blob.id)
+    tree.add("thefile_%s" % marker, 0o100644, blob.id)
     cmt = Commit()
     if data:
         assert isinstance(data[-1], Commit)
@@ -184,7 +196,7 @@ def create_commits(length=1, marker='Default'):
         data.extend([blob, tree, tag, cmt])
     return data
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class FakeSwiftConnector(object):
 
     def __init__(self, root, conf, store=None):
@@ -240,7 +252,7 @@ class FakeSwiftConnector(object):
         return {'content-length': len(self.store[name])}
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftObjectStore(TestCase):
 
     def setUp(self):
@@ -359,7 +371,7 @@ class TestSwiftObjectStore(TestCase):
         self.assertEqual(len(self.fsc.store), 6)
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftRepo(TestCase):
 
     def setUp(self):
@@ -369,20 +381,20 @@ class TestSwiftRepo(TestCase):
 
     def test_init(self):
         store = {'fakerepo/objects/pack': ''}
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    store=store):
             swift.SwiftRepo('fakerepo', conf=self.conf)
 
     def test_init_no_data(self):
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector):
             self.assertRaises(Exception, swift.SwiftRepo,
                               'fakerepo', self.conf)
 
     def test_init_bad_data(self):
         store = {'fakerepo/.git/objects/pack': ''}
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    store=store):
             self.assertRaises(Exception, swift.SwiftRepo,
@@ -390,7 +402,7 @@ class TestSwiftRepo(TestCase):
 
     def test_put_named_file(self):
         store = {'fakerepo/objects/pack': ''}
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    store=store):
             repo = swift.SwiftRepo('fakerepo', conf=self.conf)
@@ -401,7 +413,7 @@ class TestSwiftRepo(TestCase):
 
     def test_init_bare(self):
         fsc = FakeSwiftConnector('fakeroot', conf=self.conf)
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    store=fsc.store):
             swift.SwiftRepo.init_bare(fsc, conf=self.conf)
@@ -410,7 +422,7 @@ class TestSwiftRepo(TestCase):
         self.assertIn('fakeroot/description', fsc.store)
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestPackInfoLoadDump(TestCase):
     def setUp(self):
         conf = swift.load_conf(file=StringIO(config_file %
@@ -452,7 +464,7 @@ class TestPackInfoLoadDump(TestCase):
             self.assertIn(obj.id, pack_infos)
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftInfoRefsContainer(TestCase):
 
     def setUp(self):
@@ -490,7 +502,7 @@ class TestSwiftInfoRefsContainer(TestCase):
         self.assertNotIn('refs/heads/dev', irc.allkeys())
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftConnector(TestCase):
 
     def setUp(self):
@@ -540,19 +552,17 @@ class TestSwiftConnector(TestCase):
             self.assertEqual(self.conn.test_root_exists(), None)
 
     def test_create_root(self):
-        ctx = [patch('dulwich.swift.SwiftConnector.test_root_exists',
-                     lambda *args: None),
-               patch('geventhttpclient.HTTPClient.request',
-                     lambda *args: Response())]
-        with nested(*ctx):
+        with patch('dulwich.contrib.swift.SwiftConnector.test_root_exists',
+                lambda *args: None), \
+             patch('geventhttpclient.HTTPClient.request',
+                lambda *args: Response()):
             self.assertEqual(self.conn.create_root(), None)
 
     def test_create_root_fails(self):
-        ctx = [patch('dulwich.swift.SwiftConnector.test_root_exists',
-                     lambda *args: None),
-               patch('geventhttpclient.HTTPClient.request',
-                     lambda *args: Response(status=404))]
-        with nested(*ctx):
+        with patch('dulwich.contrib.swift.SwiftConnector.test_root_exists',
+                   lambda *args: None), \
+             patch('geventhttpclient.HTTPClient.request',
+                   lambda *args: Response(status=404)):
             self.assertRaises(swift.SwiftException,
                               lambda: self.conn.create_root())
 
@@ -610,17 +620,17 @@ class TestSwiftConnector(TestCase):
             self.assertEqual(self.conn.del_object('a'), None)
 
     def test_del_root(self):
-        ctx = [patch('dulwich.swift.SwiftConnector.del_object',
-                     lambda *args: None),
-               patch('dulwich.swift.SwiftConnector.get_container_objects',
-                     lambda *args: ({'name': 'a'}, {'name': 'b'})),
-               patch('geventhttpclient.HTTPClient.request',
-                     lambda *args: Response())]
-        with nested(*ctx):
+        with patch('dulwich.contrib.swift.SwiftConnector.del_object',
+                   lambda *args: None), \
+             patch('dulwich.contrib.swift.SwiftConnector.'
+                   'get_container_objects',
+                   lambda *args: ({'name': 'a'}, {'name': 'b'})), \
+             patch('geventhttpclient.HTTPClient.request',
+                    lambda *args: Response()):
             self.assertEqual(self.conn.del_root(), None)
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class SwiftObjectStoreTests(ObjectStoreTests, TestCase):
 
     def setUp(self):

+ 15 - 16
dulwich/tests_swift/test_smoke.py → dulwich/contrib/test_swift_smoke.py

@@ -19,6 +19,19 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # MA  02110-1301, USA.
 
+"""Start functional tests
+
+A Swift installation must be available before
+starting those tests. The account and authentication method used
+during this functional tests must be changed in the configuration file
+passed as environment variable.
+The container used to create a fake repository is defined
+in cls.fakerepo and will be deleted after the tests.
+
+DULWICH_SWIFT_CFG=/tmp/conf.cfg PYTHONPATH=. python -m unittest \
+    dulwich.tests_swift.test_smoke
+"""
+
 import os
 import unittest
 import tempfile
@@ -29,25 +42,11 @@ from gevent import monkey
 monkey.patch_all()
 
 from dulwich import server
-from dulwich import swift
 from dulwich import repo
 from dulwich import index
 from dulwich import client
 from dulwich import objects
-
-
-"""Start functional tests
-
-A Swift installation must be available before
-starting those tests. The account and authentication method used
-during this functional tests must be changed in the configuration file
-passed as environment variable.
-The container used to create a fake repository is defined
-in cls.fakerepo and will be deleted after the tests.
-
-DULWICH_SWIFT_CFG=/tmp/conf.cfg PYTHONPATH=. python -m unittest \
-    dulwich.tests_swift.test_smoke
-"""
+from dulwich.contrib import swift
 
 
 class DulwichServer():
@@ -79,7 +78,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
     @classmethod
     def setUpClass(cls):
         cls.backend = SwiftSystemBackend()
-        cls.port = 9418
+        cls.port = 9148
         cls.server_address = 'localhost'
         cls.fakerepo = 'fakerepo'
         cls.th_server = DulwichServer(cls.backend, cls.port)

+ 6 - 34
dulwich/server.py

@@ -85,12 +85,6 @@ from dulwich.repo import (
     Repo,
     )
 
-try:
-    import gevent
-    import geventhttpclient
-    gevent_support = True
-except ImportError:
-    gevent_support = False
 
 logger = log_utils.getLogger(__name__)
 
@@ -879,44 +873,22 @@ def main(argv=sys.argv):
     parser = optparse.OptionParser()
     parser.add_option("-b", "--backend", dest="backend",
                       help="Select backend to use.",
-                      choices=["file", "swift"], default="file")
+                      choices=["file"], default="file")
     parser.add_option("-l", "--listen_address", dest="listen_address",
-                      default="127.0.0.1",
+                      default="localhost",
                       help="Binding IP address.")
     parser.add_option("-p", "--port", dest="port", type=int,
                       default=TCP_GIT_PORT,
                       help="Binding TCP port.")
-    parser.add_option("-c", "--swift_config", dest="swift_config",
-                      default="",
-                      help="Path to the configuration file for Swift backend.")
-    parser.add_option("-f", "--fs_path", dest="fs_path",
-                      default=".",
-                      help="Path to GIT repo directory for file backend.")
     options, args = parser.parse_args(argv)
 
     log_utils.default_logging_config()
     if options.backend == "file":
-        backend = DictBackend({'/': Repo(options.fs_path)})
-    elif options.backend == "swift":
-        if gevent_support:
-            import gevent.monkey
-            gevent.monkey.patch_socket()
-            from dulwich.swift import SwiftRepo
-            from dulwich.swift import load_conf
-            class SwiftSystemBackend(Backend):
-                def __init__(self, logger, conf):
-                    self.conf = conf
-                    self.logger = logger
-
-                def open_repository(self, path):
-                    self.logger.info('opening repository at %s', path)
-                    return SwiftRepo(path, self.conf)
-            conf = load_conf(options.swift_config)
-            backend = SwiftSystemBackend(logger, conf)
+        if len(argv) > 1:
+            gitdir = args[1]
         else:
-            print "gevent and geventhttpclient libraries are mandatory " \
-                  " for use the Swift backend."
-            sys.exit(-1)
+            gitdir = '.'
+        backend = DictBackend({'/': Repo(gitdir)})
     else:
         raise Exception("No such backend %s." % backend)
     server = TCPGitServer(backend, options.listen_address,

+ 4 - 1
dulwich/tests/__init__.py

@@ -131,7 +131,6 @@ def self_test_suite():
         'server',
         'walk',
         'web',
-        'swift',
         ]
     module_names = ['dulwich.tests.test_' + name for name in names]
     loader = unittest.TestLoader()
@@ -162,7 +161,9 @@ def tutorial_test_suite():
 def nocompat_test_suite():
     result = unittest.TestSuite()
     result.addTests(self_test_suite())
+    from dulwich.contrib import test_suite as contrib_test_suite
     result.addTests(tutorial_test_suite())
+    result.addTests(contrib_test_suite())
     return result
 
 
@@ -179,4 +180,6 @@ def test_suite():
     result.addTests(tutorial_test_suite())
     from dulwich.tests.compat import test_suite as compat_test_suite
     result.addTests(compat_test_suite())
+    from dulwich.contrib import test_suite as contrib_test_suite
+    result.addTests(contrib_test_suite())
     return result

+ 0 - 0
dulwich/tests_swift/__init__.py


+ 1 - 1
setup.py

@@ -72,7 +72,7 @@ setup(name='dulwich',
       The project is named after the part of London that Mr. and Mrs. Git live in
       in the particular Monty Python sketch.
       """,
-      packages=['dulwich', 'dulwich.tests', 'dulwich.tests.compat'],
+      packages=['dulwich', 'dulwich.tests', 'dulwich.tests.compat', 'dulwich.contrib'],
       scripts=['bin/dulwich', 'bin/dul-daemon', 'bin/dul-web', 'bin/dul-receive-pack', 'bin/dul-upload-pack'],
       ext_modules=[
           Extension('dulwich._objects', ['dulwich/_objects.c'],