Lukasz Balcerzak avatar Lukasz Balcerzak committed 541db05

Git over HTTP support - proof of concept

Comments (0)

Files changed (6)

Add a comment to this file

projector/contrib/__init__.py

Empty file added.

Add a comment to this file

projector/contrib/git/__init__.py

Empty file added.

projector/contrib/git/urls.py

+from django.conf.urls.defaults import *
+
+urlpatterns = patterns('projector.contrib.git.views',
+    url(r'^/$', 'ProjectGitHandler', name='projector-project-git'),
+    url(r'^', 'ProjectGitHandler', name='projector-project-git-handler'),
+)
+

projector/contrib/git/utils.py

+import copy
+
+from django.http import HttpResponse
+
+def is_git_request(request):
+    """
+    Returns True if request was made by git user agent, False otherwise.
+    """
+    agent = request.META.get('HTTP_USER_AGENT', None)
+    try:
+        return agent.split('/')[0] == 'git'
+    except IndexError:
+        return False
+    return False
+
+def start_response2django(status, headers):
+    """
+    Fakes standard wsgi start_response function but returns instance of
+    ``django.http.HttpResponse`` object.
+    """
+    response = HttpResponse()
+    response.status_code = int(status[:3])
+    for key, val in headers:
+        print key, val
+        response[key] = val
+    return response.write
+
+
+class GitResponse(HttpResponse):
+
+    def write(self, content):
+        if hasattr(content, '__iter__'):
+            for chunk in content:
+                self.write(chunk)
+        else:
+            super(GitResponse, self).write(content)
+
+
+def get_wsgi_response(app, request):
+    """
+    Returns ``django.http.HttpResponse`` object from WSGI applcation.
+    """
+    response = GitResponse()
+    def start_response(status, headers):
+        response.status_code = int(status[:3])
+        for key, val in headers:
+            logging.info("%s: %s" % (key, val))
+            response[key] = val
+        return response.write
+    env = copy.copy(request.META)
+    response.write(app(env, start_response))
+    return response
+

projector/contrib/git/views.py

+import os
+import logging
+
+from django.conf import settings
+from django.http import Http404, HttpResponse
+
+from dulwich.server import Backend
+from dulwich.web import HTTPGitApplication
+from dulwich.web import HTTPGitRequest
+
+
+from projector.contrib.git.utils import is_git_request
+from projector.contrib.git.utils import get_wsgi_response
+from projector.contrib.git.utils import start_response2django
+from projector.views.project import ProjectView
+
+from vcs.web.simplevcs.utils import log_error
+
+
+class ProjectGitBaseView(ProjectView):
+
+    csrf_exempt = True
+
+    def __init__(self, *args, **kwargs):
+        obj = super(ProjectGitBaseView, self).__init__(*args, **kwargs)
+        self.mimetype = 'text/plain'
+        self.charset = settings.DEFAULT_CHARSET
+        self.headers = {}
+        return obj
+
+    def __before__(self):
+        if not is_git_request(self.request):
+            logging.debug("Not a GIT request")
+            #raise Http404
+        logging.info("Git request\n%s %s" % (
+            self.request.META['REQUEST_METHOD'], self.request.META['PATH_INFO']))
+        logging.debug("Catched git request by %s view" % self.__class__.__name__)
+        logging.debug("Path is: %s" % self.request.META['PATH_INFO'])
+        logging.debug("Query is: %s" % self.request.META['QUERY_STRING'])
+
+
+class ProjectGitHandler(ProjectGitBaseView):
+
+    def response(self, request, username, project_slug):
+        try:
+            git_server = GitWebServer(self.project.repository)
+            response = git_server.get_response(request)
+        except Exception, err:
+            log_error(err)
+            raise err
+        return response
+
+
+class GitWebServer(object):
+
+    def __init__(self, repository):
+        self.repository = repository
+        self.response = HttpResponse()
+
+    def get_response(self, request):
+        #backend = DictBackend({'/': Repo(self.repository.path)})
+        backend = ProjectorGitBackend(self.repository)
+        app = GitApplication(backend)
+        return get_wsgi_response(app, request)
+
+
+class ProjectorHTTPGitRequest(HTTPGitRequest):
+
+    def respond(self, *args, **kwargs):
+        response = super(ProjectorHTTPGitRequest, self).respond(*args, **kwargs)
+        return response
+
+
+class GitApplication(HTTPGitApplication):
+
+    def __call__(self, environ, start_response):
+        path = environ['PATH_INFO']
+        method = environ['REQUEST_METHOD']
+        req = ProjectorHTTPGitRequest(environ, start_response, dumb=self.dumb,
+                             handlers=self.handlers)
+        # environ['QUERY_STRING'] has qs args
+        handler = None
+        for smethod, spath in self.services.iterkeys():
+            if smethod != method:
+                continue
+            mat = spath.search(path)
+            if mat:
+                handler = self.services[smethod, spath]
+                break
+        if handler is None:
+            logging.debug('Sorry, that method is not supported')
+            raise Http404
+            #return req.not_found('Sorry, that method is not supported')
+        return handler(req, self.backend, mat)
+
+
+class ProjectorGitBackend(Backend):
+
+    def __init__(self, repository):
+        self.repository = repository
+
+    def open_repository(self, path):
+        """
+        Ignores path argument and returns dulwich repository.
+        """
+        return self.repository._repo._repo
+

projector/urls.py

     url(r'^projects/fork-external-project/$',
         view='ProjectCreateView',
         name='projector_project_fork_external'),
+    url(r'^(?P<username>[-\w]+)/(?P<project_slug>[-\w]+?)\.git/',
+        include('projector.contrib.git.urls')),
     url(r'^(?P<username>[-\w]+)/(?P<project_slug>[-\w]+)/admin/$',
         view='ProjectEditView',
         name='projector_project_edit'),
         name='projector_project_state'),
     url(r'^(?P<username>[-\w]+)/(?P<project_slug>[-\w]+)/$',
         view='ProjectDetailView', name='projector_project_detail'),
-
 )
 
 # Milestones
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.