#!/usr/bin/env python

# If you install this as a post-commit hook in your Subversion repository, it
# will scan the commit message for a review request URL and mark those reviews
# as 'submitted'. So, for example, if a commit message contains the string:
# http://reviews.dev.company.com/r/256/ then that review request will be marked
# as submitted.

# The user to log in as. This user needs to either be a superuser or have the
# "reviews | review request | Can change status" permission.
USERNAME = 'plumpy'
PASSWORD = 'password'

# The URL to your review board installation. This will be used to do the JSON
# API calls and to search the commit log messages for review request links.
REVIEWBOARD_URL = 'http://reviews.dev.company.com'

import cookielib
import mimetools
import re
import simplejson
import subprocess
import sys
import urllib2
from urlparse import urljoin

class APIError(Exception):
        pass

# We override urllib2.Request so we can actually do a PUT request
class HTTPRequest(urllib2.Request):
    def __init__(self, url, body='', headers={}, method="PUT"):
        urllib2.Request.__init__(self, url, body, headers)
        self.method = method

    def get_method(self):
        return self.method


# This once was shamelessly ripped off from post-review
# It has now gained support for the new api, but without
# copy-ing everything from post-review, because we simply
# don't need all that.
class ReviewBoardServer:
    """An instance of a Review Board server."""
    def __init__(self, url):
        self.root_resource = None
        self.url = url
        if self.url[-1] != '/':
            self.url += '/'

        passman = urllib2.HTTPPasswordMgrWithDefaultRealm()
        passman.add_password("Web API", self.url, USERNAME, PASSWORD)

        authhandler = urllib2.HTTPBasicAuthHandler(passman)
        opener = urllib2.build_opener(authhandler)

        cj = cookielib.CookieJar()
        opener.add_handler(urllib2.HTTPCookieProcessor(cj))

        urllib2.install_opener(opener)

        self.root_resource = self.api_get('api/')

    def get_review(self, review_request_id):
        """Returns the review request with the specified ID."""
        url = '%s%s/' % (\
            self.root_resource['links']['review_requests']['href'], \
                review_request_id)
        rsp = self.api_get(url)
        return rsp['review_request']

    def set_submitted(self, review_request_id, revision):
        """Marks a review request as submitted."""
        review_request = self.get_review(review_request_id)
        arguments = {'status': 'submitted'}
        if revision > 0:
            arguments['description'] = "Committed in revision %d" % revision

        self.api_put(review_request['links']['update']['href'], arguments)

    def process_json(self, data):
        """
        Loads in a JSON file and returns the data if successful. On failure,
        APIError is raised.
        """
        rsp = simplejson.loads(data)

        if rsp['stat'] == 'fail':
            raise APIError, rsp

        return rsp

    def _make_url(self, path):
        """Given a path on the server returns a full http:// style url"""
        url = urljoin(self.url, path)
        if not url.startswith('http'):
            url = 'http://%s' % url
        return url

    def http_put(self, path, fields, files=None):
        """Performs an HTTP PUT on the specified path."""
        url = self._make_url(path)

        content_type, body = self._encode_multipart_formdata(fields, files)
        headers = {
            'Content-Type': content_type,
            'Content-Length': str(len(body))
        }

        try:
            r = HTTPRequest(url, body, headers, method='PUT')
            data = urllib2.urlopen(r).read()
            return data
        except urllib2.URLError, e:
            die("Unable to access %s. The host path may be invalid\n%s" % \
                (url, e))
        except urllib2.HTTPError, e:
            die("Unable to access %s (%s). The host path may be invalid\n%s" % \
                (url, e.code, e.read()))

    def http_get(self, path):
        """Performs an HTTP GET on the specified path."""
        url = self._make_url(path)

        try:
            rsp = urllib2.urlopen(url).read()
            return rsp
        except urllib2.URLError, e:
            die("Unable to access %s. The host path may be invalid\n%s" % \
                (url, e))
        except urllib2.HTTPError, e:
            die("Unable to access %s (%s). The host path may be invalid\n%s" % \
                (url, e.code, e.read()))


    def api_put(self, path, fields=None, files=None):
        """Performs an API call using HTTP PUT at the specified path."""
        return self.process_json(self.http_put(path, fields, files))

    def api_get(self, path):
        """Performs an API call using HTTP GET at the specified path."""
        return self.process_json(self.http_get(path))

    def _encode_multipart_formdata(self, fields, files):
        """
        Encodes data for use in an HTTP POST.
        """
        BOUNDARY = mimetools.choose_boundary()
        content = ""

        fields = fields or {}
        files = files or {}

        for key in fields:
            content += "--" + BOUNDARY + "\r\n"
            content += "Content-Disposition: form-data; name=\"%s\"\r\n" % key
            content += "\r\n"
            content += fields[key] + "\r\n"

        for key in files:
            filename = files[key]['filename']
            value = files[key]['content']
            content += "--" + BOUNDARY + "\r\n"
            content += "Content-Disposition: form-data; name=\"%s\"; " % key
            content += "filename=\"%s\"\r\n" % filename
            content += "\r\n"
            content += value + "\r\n"

        content += "--" + BOUNDARY + "--\r\n"
        content += "\r\n"

        content_type = "multipart/form-data; boundary=%s" % BOUNDARY

        return content_type, content


def die(msg=None):
    """Cleanly exits the program with an error message."""
    if msg:
        print msg

    sys.exit(1)

svnlook = ['svnlook', 'log', '-r', sys.argv[2], sys.argv[1]]
log_message = subprocess.Popen(svnlook, stdout=subprocess.PIPE).communicate()[0]

server = None

for match in re.finditer(REVIEWBOARD_URL + '/r/(\d+)', log_message):
    if server is None:
        server = ReviewBoardServer(REVIEWBOARD_URL)

    review_request_id = match.group(1)
    server.set_submitted(review_request_id, int(sys.argv[2]))
