فهرست منبع

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 11 سال پیش
والد
کامیت
e0282d541c
12فایلهای تغییر یافته به همراه246 افزوده شده و 182 حذف شده
  1. 1 1
      Makefile
  2. 5 2
      NEWS
  3. 8 7
      README.swift
  4. 0 32
      bin/dulwich
  5. 28 0
      dulwich/contrib/__init__.py
  6. 131 51
      dulwich/contrib/swift.py
  7. 47 37
      dulwich/contrib/test_swift.py
  8. 15 16
      dulwich/contrib/test_swift_smoke.py
  9. 6 34
      dulwich/server.py
  10. 4 1
      dulwich/tests/__init__.py
  11. 0 0
      dulwich/tests_swift/__init__.py
  12. 1 1
      setup.py

+ 1 - 1
Makefile

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

+ 5 - 2
NEWS

@@ -26,8 +26,11 @@
     for concurrency of some object store operations.
     for concurrency of some object store operations.
     (Fabien Boucher)
     (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
 API CHANGES
 
 

+ 8 - 7
README.swift

@@ -1,7 +1,8 @@
 Openstack Swift as backend for Dulwich
 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.
 in order to being compatible with Openstack Swift.
 We can then use Dulwich as server (Git server) and instead of using
 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
 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.
 There is no need to have a Swift cluster running to run the unitests.
 Just run the following command in the Dulwich source directory:
 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
 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
 cluster. To run those functional tests you need a properly configured
 configuration file. The tests can be run as follow:
 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
 How to install
 --------------
 --------------
@@ -79,7 +80,7 @@ How to run the server
 
 
 Start the server using the following command:
 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
 Note that a lot of request will be performed against the Swift
 cluster so it is better to start the Dulwich server as close
 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
 the server is running we can init a bare repository. Run this
 command with the name of the repository to create:
 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
 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
 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
 The other Git commands can be used the way you do usually against
 a regular repository.
 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).
 at all between the cGIT client and the GIT server (Dulwich).
 
 
 Note on the .info file for pack object
 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.patch import write_tree_diff
 from dulwich.repo import Repo
 from dulwich.repo import Repo
 
 
-try:
-    import gevent
-    import geventhttpclient
-    gevent_support = True
-except ImportError:
-    gevent_support = False
-
 
 
 def cmd_archive(args):
 def cmd_archive(args):
     opts, args = getopt(args, "", [])
     opts, args = getopt(args, "", [])
@@ -163,30 +156,6 @@ def cmd_init(args):
 
 
     porcelain.init(path, bare=("--bare" in opts))
     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):
 def cmd_clone(args):
     opts, args = getopt(args, "", ["bare"])
     opts, args = getopt(args, "", ["bare"])
@@ -289,7 +258,6 @@ commands = {
     "fetch-pack": cmd_fetch_pack,
     "fetch-pack": cmd_fetch_pack,
     "fetch": cmd_fetch,
     "fetch": cmd_fetch,
     "init": cmd_init,
     "init": cmd_init,
-    "init-swift": cmd_init_swift,
     "log": cmd_log,
     "log": cmd_log,
     "reset": cmd_reset,
     "reset": cmd_reset,
     "rev-list": cmd_rev_list,
     "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 ConfigParser import ConfigParser
 from geventhttpclient import HTTPClient
 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 (
 from dulwich.pack import (
     PackData,
     PackData,
@@ -55,26 +69,19 @@ from dulwich.pack import (
     unpack_object,
     unpack_object,
     write_pack_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 (
 from dulwich.refs import (
     InfoRefsContainer,
     InfoRefsContainer,
     read_info_refs,
     read_info_refs,
     write_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:
 try:
@@ -84,6 +91,8 @@ except ImportError:
     from json import loads as json_loads
     from json import loads as json_loads
     from json import dumps as json_dumps
     from json import dumps as json_dumps
 
 
+import sys
+
 
 
 """
 """
 # Configuration file sample
 # Configuration file sample
@@ -114,6 +123,7 @@ cache_length = 20
 
 
 
 
 class PackInfoObjectStoreIterator(GreenThreadsObjectStoreIterator):
 class PackInfoObjectStoreIterator(GreenThreadsObjectStoreIterator):
+
     def __len__(self):
     def __len__(self):
         while len(self.finder.objects_to_send):
         while len(self.finder.objects_to_send):
             for _ in xrange(0, 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):
 class PackInfoMissingObjectFinder(GreenThreadsMissingObjectFinder):
+
     def next(self):
     def next(self):
         while True:
         while True:
             if not self.objects_to_send:
             if not self.objects_to_send:
@@ -188,18 +199,18 @@ def pack_info_create(pack_data, pack_index):
     info = {}
     info = {}
     for obj in pack.iterobjects():
     for obj in pack.iterobjects():
         # Commit
         # Commit
-        if obj.type_num == 1:
+        if obj.type_num == Commit.type_num:
             info[obj.id] = (obj.type_num, obj.parents, obj.tree)
             info[obj.id] = (obj.type_num, obj.parents, obj.tree)
         # Tree
         # Tree
-        elif obj.type_num == 2:
+        elif obj.type_num == Tree.type_num:
             shas = [(s, n, not stat.S_ISDIR(m)) for
             shas = [(s, n, not stat.S_ISDIR(m)) for
                     n, m, s in obj.iteritems() if not S_ISGITLINK(m)]
                     n, m, s in obj.iteritems() if not S_ISGITLINK(m)]
             info[obj.id] = (obj.type_num, shas)
             info[obj.id] = (obj.type_num, shas)
         # Blob
         # Blob
-        elif obj.type_num == 3:
+        elif obj.type_num == Blob.type_num:
             info[obj.id] = None
             info[obj.id] = None
         # Tag
         # Tag
-        elif obj.type_num == 4:
+        elif obj.type_num == Tag.type_num:
             info[obj.id] = (obj.type_num, obj._object_sha)
             info[obj.id] = (obj.type_num, obj._object_sha)
     return zlib.compress(json_dumps(info))
     return zlib.compress(json_dumps(info))
 
 
@@ -234,8 +245,8 @@ class SwiftConnector(object):
         self.conf = conf
         self.conf = conf
         self.auth_ver = self.conf.get("swift", "auth_ver")
         self.auth_ver = self.conf.get("swift", "auth_ver")
         if self.auth_ver not in ["1", "2"]:
         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.auth_url = self.conf.get("swift", "auth_url")
         self.user = self.conf.get("swift", "username")
         self.user = self.conf.get("swift", "username")
         self.password = self.conf.get("swift", "password")
         self.password = self.conf.get("swift", "password")
@@ -277,9 +288,7 @@ class SwiftConnector(object):
                    'X-Auth-Key': self.password}
                    'X-Auth-Key': self.password}
         path = urlparse(self.auth_url).path
         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)
         # Should do something with redirections (301 in my case)
 
 
@@ -312,8 +321,7 @@ class SwiftConnector(object):
         path = urlparse(self.auth_url).path
         path = urlparse(self.auth_url).path
         if not path.endswith('tokens'):
         if not path.endswith('tokens'):
             path = posixpath.join(path, 'tokens')
             path = posixpath.join(path, 'tokens')
-        ret = auth_httpclient.request('POST',
-                                      path,
+        ret = auth_httpclient.request('POST', path,
                                       body=auth_json,
                                       body=auth_json,
                                       headers=headers)
                                       headers=headers)
 
 
@@ -338,8 +346,7 @@ class SwiftConnector(object):
 
 
         :return: True if exist or None it not
         :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:
         if ret.status_code == 404:
             return None
             return None
         if ret.status_code < 200 or ret.status_code > 300:
         if ret.status_code < 200 or ret.status_code > 300:
@@ -353,8 +360,7 @@ class SwiftConnector(object):
         :raise: `SwiftException` if unable to create
         :raise: `SwiftException` if unable to create
         """
         """
         if not self.test_root_exists():
         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:
             if ret.status_code < 200 or ret.status_code > 300:
                 raise SwiftException('PUT request failed with error code %s'
                 raise SwiftException('PUT request failed with error code %s'
                                      % ret.status_code)
                                      % ret.status_code)
@@ -367,8 +373,7 @@ class SwiftConnector(object):
         """
         """
         qs = '?format=json'
         qs = '?format=json'
         path = self.base_path + qs
         path = self.base_path + qs
-        ret = self.httpclient.request('GET',
-                                      path)
+        ret = self.httpclient.request('GET', path)
         if ret.status_code == 404:
         if ret.status_code == 404:
             return None
             return None
         if ret.status_code < 200 or ret.status_code > 300:
         if ret.status_code < 200 or ret.status_code > 300:
@@ -385,8 +390,7 @@ class SwiftConnector(object):
                  or None if object does not exist
                  or None if object does not exist
         """
         """
         path = self.base_path + '/' + name
         path = self.base_path + '/' + name
-        ret = self.httpclient.request('HEAD',
-                                      path)
+        ret = self.httpclient.request('HEAD', path)
         if ret.status_code == 404:
         if ret.status_code == 404:
             return None
             return None
         if ret.status_code < 200 or ret.status_code > 300:
         if ret.status_code < 200 or ret.status_code > 300:
@@ -410,8 +414,7 @@ class SwiftConnector(object):
         headers = {'Content-Length': str(len(data))}
         headers = {'Content-Length': str(len(data))}
 
 
         def _send():
         def _send():
-            ret = self.httpclient.request('PUT',
-                                          path,
+            ret = self.httpclient.request('PUT', path,
                                           body=data,
                                           body=data,
                                           headers=headers)
                                           headers=headers)
             return ret
             return ret
@@ -440,9 +443,7 @@ class SwiftConnector(object):
         if range:
         if range:
             headers['Range'] = 'bytes=%s' % range
             headers['Range'] = 'bytes=%s' % range
         path = self.base_path + '/' + name
         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:
         if ret.status_code == 404:
             return None
             return None
         if ret.status_code < 200 or ret.status_code > 300:
         if ret.status_code < 200 or ret.status_code > 300:
@@ -461,8 +462,7 @@ class SwiftConnector(object):
         :raise: `SwiftException` if unable to delete
         :raise: `SwiftException` if unable to delete
         """
         """
         path = self.base_path + '/' + name
         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:
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException('DELETE request failed with error code %s'
             raise SwiftException('DELETE request failed with error code %s'
                                  % ret.status_code)
                                  % ret.status_code)
@@ -474,8 +474,7 @@ class SwiftConnector(object):
         """
         """
         for obj in self.get_container_objects():
         for obj in self.get_container_objects():
             self.del_object(obj['name'])
             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:
         if ret.status_code < 200 or ret.status_code > 300:
             raise SwiftException('DELETE request failed with error code %s'
             raise SwiftException('DELETE request failed with error code %s'
                                  % ret.status_code)
                                  % ret.status_code)
@@ -510,10 +509,8 @@ class SwiftPackReader(object):
         if more:
         if more:
             self.buff_length = self.buff_length * 2
             self.buff_length = self.buff_length * 2
         l = self.base_offset
         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
         self.buff = ret
 
 
     def read(self, length):
     def read(self, length):
@@ -644,7 +641,7 @@ class SwiftObjectStore(PackBasedObjectStore):
         self.root = self.scon.root
         self.root = self.scon.root
         self.pack_dir = posixpath.join(OBJECTDIR, PACKDIR)
         self.pack_dir = posixpath.join(OBJECTDIR, PACKDIR)
         self._alternates = None
         self._alternates = None
-    
+
     @property
     @property
     def packs(self):
     def packs(self):
         """List with pack objects."""
         """List with pack objects."""
@@ -889,7 +886,7 @@ class SwiftInfoRefsContainer(InfoRefsContainer):
         self._write_refs(refs)
         self._write_refs(refs)
         del self._refs[name]
         del self._refs[name]
         return True
         return True
-    
+
     def allkeys(self):
     def allkeys(self):
         try:
         try:
             self._refs['HEAD'] = self._refs['refs/heads/master']
             self._refs['HEAD'] = self._refs['refs/heads/master']
@@ -951,3 +948,86 @@ class SwiftRepo(BaseRepo):
         ret = cls(scon.root, conf)
         ret = cls(scon.root, conf)
         ret._init_files(True)
         ret._init_files(True)
         return ret
         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,
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # MA  02110-1301, USA.
 # MA  02110-1301, USA.
 
 
-"""Tests for dulwich.swift."""
+"""Tests for dulwich.contrib.swift."""
 
 
 import posixpath
 import posixpath
 
 
 from time import time
 from time import time
 from cStringIO import StringIO
 from cStringIO import StringIO
-from contextlib import nested
+from unittest import skipIf
 
 
 from dulwich.tests import (
 from dulwich.tests import (
     TestCase,
     TestCase,
-    skipIf,
     )
     )
 from dulwich.tests.test_object_store import (
 from dulwich.tests.test_object_store import (
     ObjectStoreTests,
     ObjectStoreTests,
@@ -56,16 +55,27 @@ try:
 except ImportError:
 except ImportError:
     from json import dumps as json_dumps
     from json import dumps as json_dumps
 
 
+missing_libs = []
+
 try:
 try:
     import gevent
     import gevent
+except ImportError:
+    missing_libs.append("gevent")
+
+try:
     import geventhttpclient
     import geventhttpclient
+except ImportError:
+    missing_libs.append("geventhttpclient")
+
+try:
     from mock import patch
     from mock import patch
-    lib_support = True
-    from dulwich import swift
 except ImportError:
 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]
 config_file = """[swift]
 auth_url = http://127.0.0.1:8080/auth/%(version_str)s
 auth_url = http://127.0.0.1:8080/auth/%(version_str)s
@@ -99,6 +109,7 @@ def create_swift_connector(store={}):
 
 
 
 
 class Response(object):
 class Response(object):
+
     def __init__(self, headers={}, status=200, content=None):
     def __init__(self, headers={}, status=200, content=None):
         self.headers = headers
         self.headers = headers
         self.status_code = status
         self.status_code = status
@@ -125,6 +136,7 @@ def fake_auth_request_v1(*args, **kwargs):
                    200)
                    200)
     return ret
     return ret
 
 
+
 def fake_auth_request_v1_error(*args, **kwargs):
 def fake_auth_request_v1_error(*args, **kwargs):
     ret = Response({},
     ret = Response({},
                    401)
                    401)
@@ -153,7 +165,7 @@ def create_commit(data, marker='Default', blob=None):
     if not blob:
     if not blob:
         blob = Blob.from_string('The blob content %s' % marker)
         blob = Blob.from_string('The blob content %s' % marker)
     tree = Tree()
     tree = Tree()
-    tree.add("thefile_%s" % marker, 0100644, blob.id)
+    tree.add("thefile_%s" % marker, 0o100644, blob.id)
     cmt = Commit()
     cmt = Commit()
     if data:
     if data:
         assert isinstance(data[-1], Commit)
         assert isinstance(data[-1], Commit)
@@ -184,7 +196,7 @@ def create_commits(length=1, marker='Default'):
         data.extend([blob, tree, tag, cmt])
         data.extend([blob, tree, tag, cmt])
     return data
     return data
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class FakeSwiftConnector(object):
 class FakeSwiftConnector(object):
 
 
     def __init__(self, root, conf, store=None):
     def __init__(self, root, conf, store=None):
@@ -240,7 +252,7 @@ class FakeSwiftConnector(object):
         return {'content-length': len(self.store[name])}
         return {'content-length': len(self.store[name])}
 
 
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftObjectStore(TestCase):
 class TestSwiftObjectStore(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -359,7 +371,7 @@ class TestSwiftObjectStore(TestCase):
         self.assertEqual(len(self.fsc.store), 6)
         self.assertEqual(len(self.fsc.store), 6)
 
 
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftRepo(TestCase):
 class TestSwiftRepo(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -369,20 +381,20 @@ class TestSwiftRepo(TestCase):
 
 
     def test_init(self):
     def test_init(self):
         store = {'fakerepo/objects/pack': ''}
         store = {'fakerepo/objects/pack': ''}
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    new_callable=create_swift_connector,
                    store=store):
                    store=store):
             swift.SwiftRepo('fakerepo', conf=self.conf)
             swift.SwiftRepo('fakerepo', conf=self.conf)
 
 
     def test_init_no_data(self):
     def test_init_no_data(self):
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector):
                    new_callable=create_swift_connector):
             self.assertRaises(Exception, swift.SwiftRepo,
             self.assertRaises(Exception, swift.SwiftRepo,
                               'fakerepo', self.conf)
                               'fakerepo', self.conf)
 
 
     def test_init_bad_data(self):
     def test_init_bad_data(self):
         store = {'fakerepo/.git/objects/pack': ''}
         store = {'fakerepo/.git/objects/pack': ''}
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    new_callable=create_swift_connector,
                    store=store):
                    store=store):
             self.assertRaises(Exception, swift.SwiftRepo,
             self.assertRaises(Exception, swift.SwiftRepo,
@@ -390,7 +402,7 @@ class TestSwiftRepo(TestCase):
 
 
     def test_put_named_file(self):
     def test_put_named_file(self):
         store = {'fakerepo/objects/pack': ''}
         store = {'fakerepo/objects/pack': ''}
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    new_callable=create_swift_connector,
                    store=store):
                    store=store):
             repo = swift.SwiftRepo('fakerepo', conf=self.conf)
             repo = swift.SwiftRepo('fakerepo', conf=self.conf)
@@ -401,7 +413,7 @@ class TestSwiftRepo(TestCase):
 
 
     def test_init_bare(self):
     def test_init_bare(self):
         fsc = FakeSwiftConnector('fakeroot', conf=self.conf)
         fsc = FakeSwiftConnector('fakeroot', conf=self.conf)
-        with patch('dulwich.swift.SwiftConnector',
+        with patch('dulwich.contrib.swift.SwiftConnector',
                    new_callable=create_swift_connector,
                    new_callable=create_swift_connector,
                    store=fsc.store):
                    store=fsc.store):
             swift.SwiftRepo.init_bare(fsc, conf=self.conf)
             swift.SwiftRepo.init_bare(fsc, conf=self.conf)
@@ -410,7 +422,7 @@ class TestSwiftRepo(TestCase):
         self.assertIn('fakeroot/description', fsc.store)
         self.assertIn('fakeroot/description', fsc.store)
 
 
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestPackInfoLoadDump(TestCase):
 class TestPackInfoLoadDump(TestCase):
     def setUp(self):
     def setUp(self):
         conf = swift.load_conf(file=StringIO(config_file %
         conf = swift.load_conf(file=StringIO(config_file %
@@ -452,7 +464,7 @@ class TestPackInfoLoadDump(TestCase):
             self.assertIn(obj.id, pack_infos)
             self.assertIn(obj.id, pack_infos)
 
 
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftInfoRefsContainer(TestCase):
 class TestSwiftInfoRefsContainer(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -490,7 +502,7 @@ class TestSwiftInfoRefsContainer(TestCase):
         self.assertNotIn('refs/heads/dev', irc.allkeys())
         self.assertNotIn('refs/heads/dev', irc.allkeys())
 
 
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class TestSwiftConnector(TestCase):
 class TestSwiftConnector(TestCase):
 
 
     def setUp(self):
     def setUp(self):
@@ -540,19 +552,17 @@ class TestSwiftConnector(TestCase):
             self.assertEqual(self.conn.test_root_exists(), None)
             self.assertEqual(self.conn.test_root_exists(), None)
 
 
     def test_create_root(self):
     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)
             self.assertEqual(self.conn.create_root(), None)
 
 
     def test_create_root_fails(self):
     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,
             self.assertRaises(swift.SwiftException,
                               lambda: self.conn.create_root())
                               lambda: self.conn.create_root())
 
 
@@ -610,17 +620,17 @@ class TestSwiftConnector(TestCase):
             self.assertEqual(self.conn.del_object('a'), None)
             self.assertEqual(self.conn.del_object('a'), None)
 
 
     def test_del_root(self):
     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)
             self.assertEqual(self.conn.del_root(), None)
 
 
 
 
-@skipIf(not lib_support, skipmsg)
+@skipIf(missing_libs, skipmsg)
 class SwiftObjectStoreTests(ObjectStoreTests, TestCase):
 class SwiftObjectStoreTests(ObjectStoreTests, TestCase):
 
 
     def setUp(self):
     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,
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 # MA  02110-1301, USA.
 # 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 os
 import unittest
 import unittest
 import tempfile
 import tempfile
@@ -29,25 +42,11 @@ from gevent import monkey
 monkey.patch_all()
 monkey.patch_all()
 
 
 from dulwich import server
 from dulwich import server
-from dulwich import swift
 from dulwich import repo
 from dulwich import repo
 from dulwich import index
 from dulwich import index
 from dulwich import client
 from dulwich import client
 from dulwich import objects
 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():
 class DulwichServer():
@@ -79,7 +78,7 @@ class SwiftRepoSmokeTest(unittest.TestCase):
     @classmethod
     @classmethod
     def setUpClass(cls):
     def setUpClass(cls):
         cls.backend = SwiftSystemBackend()
         cls.backend = SwiftSystemBackend()
-        cls.port = 9418
+        cls.port = 9148
         cls.server_address = 'localhost'
         cls.server_address = 'localhost'
         cls.fakerepo = 'fakerepo'
         cls.fakerepo = 'fakerepo'
         cls.th_server = DulwichServer(cls.backend, cls.port)
         cls.th_server = DulwichServer(cls.backend, cls.port)

+ 6 - 34
dulwich/server.py

@@ -85,12 +85,6 @@ from dulwich.repo import (
     Repo,
     Repo,
     )
     )
 
 
-try:
-    import gevent
-    import geventhttpclient
-    gevent_support = True
-except ImportError:
-    gevent_support = False
 
 
 logger = log_utils.getLogger(__name__)
 logger = log_utils.getLogger(__name__)
 
 
@@ -879,44 +873,22 @@ def main(argv=sys.argv):
     parser = optparse.OptionParser()
     parser = optparse.OptionParser()
     parser.add_option("-b", "--backend", dest="backend",
     parser.add_option("-b", "--backend", dest="backend",
                       help="Select backend to use.",
                       help="Select backend to use.",
-                      choices=["file", "swift"], default="file")
+                      choices=["file"], default="file")
     parser.add_option("-l", "--listen_address", dest="listen_address",
     parser.add_option("-l", "--listen_address", dest="listen_address",
-                      default="127.0.0.1",
+                      default="localhost",
                       help="Binding IP address.")
                       help="Binding IP address.")
     parser.add_option("-p", "--port", dest="port", type=int,
     parser.add_option("-p", "--port", dest="port", type=int,
                       default=TCP_GIT_PORT,
                       default=TCP_GIT_PORT,
                       help="Binding TCP 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)
     options, args = parser.parse_args(argv)
 
 
     log_utils.default_logging_config()
     log_utils.default_logging_config()
     if options.backend == "file":
     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:
         else:
-            print "gevent and geventhttpclient libraries are mandatory " \
-                  " for use the Swift backend."
-            sys.exit(-1)
+            gitdir = '.'
+        backend = DictBackend({'/': Repo(gitdir)})
     else:
     else:
         raise Exception("No such backend %s." % backend)
         raise Exception("No such backend %s." % backend)
     server = TCPGitServer(backend, options.listen_address,
     server = TCPGitServer(backend, options.listen_address,

+ 4 - 1
dulwich/tests/__init__.py

@@ -131,7 +131,6 @@ def self_test_suite():
         'server',
         'server',
         'walk',
         'walk',
         'web',
         'web',
-        'swift',
         ]
         ]
     module_names = ['dulwich.tests.test_' + name for name in names]
     module_names = ['dulwich.tests.test_' + name for name in names]
     loader = unittest.TestLoader()
     loader = unittest.TestLoader()
@@ -162,7 +161,9 @@ def tutorial_test_suite():
 def nocompat_test_suite():
 def nocompat_test_suite():
     result = unittest.TestSuite()
     result = unittest.TestSuite()
     result.addTests(self_test_suite())
     result.addTests(self_test_suite())
+    from dulwich.contrib import test_suite as contrib_test_suite
     result.addTests(tutorial_test_suite())
     result.addTests(tutorial_test_suite())
+    result.addTests(contrib_test_suite())
     return result
     return result
 
 
 
 
@@ -179,4 +180,6 @@ def test_suite():
     result.addTests(tutorial_test_suite())
     result.addTests(tutorial_test_suite())
     from dulwich.tests.compat import test_suite as compat_test_suite
     from dulwich.tests.compat import test_suite as compat_test_suite
     result.addTests(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
     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
       The project is named after the part of London that Mr. and Mrs. Git live in
       in the particular Monty Python sketch.
       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'],
       scripts=['bin/dulwich', 'bin/dul-daemon', 'bin/dul-web', 'bin/dul-receive-pack', 'bin/dul-upload-pack'],
       ext_modules=[
       ext_modules=[
           Extension('dulwich._objects', ['dulwich/_objects.c'],
           Extension('dulwich._objects', ['dulwich/_objects.c'],