[Answered ]-Building a webapp to serve multiple HG repositories

2👍

If you’re okay with the terms of the GPL the easiest way to do this is definitely just to invoke hgweb from your django views. hgweb is a full-function, multi-repository python wsgi application, of the sort django loves to work with.

It’s entirely possible your entire django view could be:

from mercurial.hgweb import hgweb

def hg_view(request):
    """ relay a WSGI Request to HG """
    hgweb(config).run_wsgi(request)

and you’d route to that with a normal django view. You’d probably have to modify the request to remove the leading path element, but the larger point is if the goal is to have a wsgi application (which django is) call another wsgi application (which hgweb is) it’s definitely easiest to avoid TCP sockets and local pipes and instead keep things all in the family.

0👍

This is the solution I actually went with, based on Ry4an’s accepted answer. It’s entirely possible that a django HttpRequest object can serve as a wsgirequest that can be passed directly to hgweb_mod.run_wsgi, but I couldn’t figure out what to return then. The following worked for me, it certainly doesn’t handle all the edge cases, but I’m able to push and pull my repos this way:

#!/usr/bin/python

class WsgiWrapper(object):
    """
    A wrapper object that works reasonably well (at least in the cases in which it is currently being used)
    to turn a django request into a Wsgi thingy.
    """
    def __init__(self, request):
        self.request = request
        self.status = None
        self.headers = None
        self.exc_info = None

    def __call__(self, status=None, response_headers=None, exc_info=None):
        if status is not None:
            self.status = status
        self.headers = response_headers
        self.exc_info = exc_info

        self.outputbuffer = cStringIO.StringIO()
        return self.outputbuffer.write


def serve_hg(request, repopath):
    """
    Delegates to mercurial's built-in hgweb WSGI application to serve up access
    to mercurial HTTPCommand Protocol.
    """

    #Put together a wsgi request from the django request.
    wrapper = WsgiWrapper(request)
    env = {}
    env.update(request.META)
    env['SCRIPT_NAME'] = '/'
    env['PATH_INFO'] = '/'
    req = hgweb.request.wsgirequest(env, wrapper)

    #Invoke hgweb as a wsgi application.
    hgw = hgweb.hgweb(repopath)
    hgw.repo.baseui.setconfig('web', 'allow_push', '*')
    hgw.repo.baseui.setconfig('web', 'push_ssl', 'false')
    gen = hgw.run_wsgi(req)
    data = wrapper.outputbuffer.getvalue()
    if len(data):
        gen = [data]
    wrapper.outputbuffer.close()

    #Now build up the django response object from that.
    resp = HttpResponse("".join(gen))
    stat = wrapper.status
    if stat is None:
        resp.status_code = 200
    else:
        pair = stat.split(' ', 1)
        resp.status_code = pair[0]
        if len(pair) > 1:
            resp.reason_phrase = pair[1]

    hopbyhop = (
        'Connection',
        'Keep-Alive',
        'Proxy-Authenticate',
        'Proxy-Authorization',
        'TE',
        'Trailers',
        'Transfer-Encoding',
        'Upgrade',
    )
    if wrapper.headers is not None:
        for k, v in wrapper.headers:
            if k not in hopbyhop:
                resp[k] = v

    return resp

Leave a comment