构建一个webapp来为多个HG存储库提供服务

时间:2013-12-09 20:44:51

标签: django mercurial

我知道有cgi脚本可以提供多个mercurial存储库,但我正在尝试创建一个django web应用程序来执行此操作。例如,按照bitbucket或任何其他存储库托管站点的方式思考。

这样做的最佳方式(或者无论如何,真的)是什么?

基本上,我需要做的是在我的网站上为项目特定的URL提供Merucrial HttpCommandProtocol的实现,以便mercurial客户端可以将他们的repos与存储在站点上的repos同步。我以为我可以使用hglib来执行此操作,但似乎在CommandServer命令和HttpCommandProtocol命令之间没有任何直接的映射。我自己可以实现HttpCommandProtocol:我已经成功完成了其中的一些,但大部分命令都没有记录,我无法弄清楚它们应该做什么。

另一种选择是为每个存储库运行hg serve并且基本上从客户端到hg服务器进行传递,但是可以有任意数量的存储库,并且每个服务器都需要在不同的存储库上端口,除非有某些方法我可以通过管道或文件描述符而不是TCP端口与HttpServer交谈。您可以使用hg serve --commandserver pipe执行此操作,但CommandServer协议不是HTTP所需的。

2 个答案:

答案 0 :(得分:2)

如果您对GPL的条款没有问题,最简单的方法就是从您的django视图中调用hgweb。 hgweb是一个全功能,多存储库的python wsgi应用程序,django喜欢使用它。

完全有可能你的整个django视图可能是:

from mercurial.hgweb import hgweb

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

并且您使用正常的django视图路由到那个。您可能必须修改删除前导路径元素的请求,但更重要的一点是,如果目标是让wsgi应用程序(django是)调用另一个wsgi应用程序(hgweb是),那么它就是'绝对最容易避免使用TCP套接字和本地管道,而是将所有内容保存在系列中。

答案 1 :(得分:0)

根据Ry4an接受的答案,这是我实际使用的解决方案。 django HttpRequest对象完全有可能作为wsgirequest可以直接传递给hgweb_mod.run_wsgi,但我无法弄清楚要返回的内容。以下对我有用,它肯定无法处理所有边缘情况,但我能够以这种方式推送和撤回我的回购:

#!/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