]> git.rmz.io Git - dotfiles.git/blobdiff - bin/hooks/svn-hook-postcommit-review
cleaning up bin
[dotfiles.git] / bin / hooks / svn-hook-postcommit-review
diff --git a/bin/hooks/svn-hook-postcommit-review b/bin/hooks/svn-hook-postcommit-review
new file mode 100644 (file)
index 0000000..ef26c8e
--- /dev/null
@@ -0,0 +1,240 @@
+#!/usr/bin/python
+
+# svn-hook-postcommit-review
+# This script should be invoked from the subversion post-commit hook like this:
+#
+# REPOS="$1"
+# REV="$2"
+# /usr/bin/python /some/path/svn-hook-postcommit-review "$REPOS" "$REV" || exit 1
+#
+# Searches the commit message for text in the form of:
+#   publish review - publishes a review request
+#   draft review - creates a draft review request
+#
+# The space before 'review' may be ommitted.
+#
+# The log message is interpreted for review request parameters:
+#    summary = up to first period+space, first new-line, or 250 chars
+#    description = entire log message
+#    existing review updated if log message includes 'update review:[0-9]+'
+#    bugs added to review if log message includes commands as defined in
+#      supported_ticket_cmds
+#
+# By default, the review request is created out of a diff between the current
+# revision (M) and the previous revision (M-1).
+#
+# To create a diff that spans multiple revisions, include
+# 'after revision:[0-9]+' in the log message.
+#
+# To limit the diff to changes in a certain path (e.g. a branch), include
+# 'base path:"<path>"' in the log message.  The path must be relative to
+# the root of the repository and be surrounded by single or double quotes.
+#
+# An example commit message is:
+#
+#    Changed blah and foo to do this or that.  Publish review ticket:1
+#      update review:2 after revision:3 base path:'internal/trunk/style'.
+#
+# This would update the existing review 2 with a diff of changes to files under
+# the style directory between this commit and revision 3.  It would place
+# the entire log message in the review summary and description, and put
+# bug id 1 in the bugs field.
+#
+# This script may only be run from outside a working copy.
+#
+
+#
+# User configurable variables
+#
+
+# Path to post-review script
+POSTREVIEW_PATH = ""
+# Username and password for Review Board user that will be connecting
+# to create all review requests.  This user must have 'submit as'
+# privileges, since it will submit requests in the name of svn committers.
+USERNAME = 'su_user'
+PASSWORD = 'TYxxcGm337FtubqN'
+
+# If true, runs post-review in debug mode and outputs its diff
+DEBUG = False
+
+#
+# end user configurable variables
+#
+
+import sys
+import os
+import subprocess
+import re
+import svn.fs
+import svn.core
+import svn.repos
+
+# list of trac commands from trac-post-commit-hook.py.
+# numbers following these commands will be added to the bugs
+# field of the review request.
+supported_ticket_cmds = {'review':         '_cmdReview',
+                         'publishreview':  '_cmdReview',
+                         'publish review': '_cmdReview',
+                         'draftreview':    '_cmdReview',
+                         'draft review':   '_cmdReview'}
+
+ticket_prefix = '(?:#|(?:ticket|issue|bug)[: ]?)'
+ticket_reference = ticket_prefix + '[0-9]+'
+ticket_command = (r'(?P<action>[A-Za-z]*).?'
+                  '(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' %
+                  (ticket_reference, ticket_reference))
+
+def execute(command, env=None, ignore_errors=False):
+    """
+    Utility function to execute a command and return the output.
+    Derived from Review Board's post-review script.
+    """
+    if env:
+        env.update(os.environ)
+    else:
+        env = os.environ
+
+    p = subprocess.Popen(command,
+                         stdin = subprocess.PIPE,
+                         stdout = subprocess.PIPE,
+                         stderr = subprocess.STDOUT,
+                         shell = False,
+                         close_fds = sys.platform.startswith('win'),
+                         universal_newlines = True,
+                         env = env)
+    data = p.stdout.read()
+    rc = p.wait()
+    if rc and not ignore_errors:
+        sys.stderr.write('Failed to execute command: %s\n%s\n' % (command, data))
+        sys.exit(1)
+
+    return data
+
+def main():
+    if len(sys.argv) != 3:
+        sys.stderr.write('Usage: %s <repos> <rev>\n' % sys.argv[0])
+        sys.exit(1)
+
+    repos = sys.argv[1]
+    rev = sys.argv[2]
+
+    # verify that rev parameter is an int
+    try:
+        int(rev)
+    except ValueError:
+        sys.stderr.write("Parameter <rev> must be an int, was given %s\n" % rev)
+        sys.exit(1)
+
+    # get the svn file system object
+    fs_ptr = svn.repos.svn_repos_fs(svn.repos.svn_repos_open(
+            svn.core.svn_path_canonicalize(repos)))
+
+    # get the log message
+    log = svn.fs.svn_fs_revision_prop(fs_ptr, int(rev),
+                                    svn.core.SVN_PROP_REVISION_LOG)
+
+    # error if log message is blank
+    if len(log.strip()) < 1:
+        sys.stderr.write("Log message is empty, no review request created\n")
+        sys.exit(1)
+
+    # get the author
+    author = svn.fs.svn_fs_revision_prop(fs_ptr, int(rev),
+                                       svn.core.SVN_PROP_REVISION_AUTHOR)
+
+    # error if author is blank
+    if len(author.strip()) < 1:
+        sys.stderr.write("Author is blank, no review request created\n")
+        sys.exit(1)
+
+    # check whether to create a review, based on presence of word
+    # 'review' with prefix
+    review = r'(?:publish|draft)(?: )?review'
+    if not re.search(review, log, re.M | re.I):
+        print 'No review requested'
+        sys.exit(0)
+
+    # check for update to existing review
+    m = re.search(r'update(?: )?review:([0-9]+)', log, re.M | re.I)
+    if m:
+        reviewid = '--review-request-id=' + m.group(1)
+    else:
+        reviewid = ''
+
+    # check whether to publish or leave review as draft
+    if re.search(r'draft(?: )?review', log, re.M | re.I):
+        publish = ''
+    else:
+        publish = '-p'
+
+    # get previous revision number -- either 1 prior, or
+    # user-specified number
+    m = re.search(r'after(?: )?revision:([0-9]+)', log, re.M | re.I)
+    if m:
+        prevrev = m.group(1)
+    else:
+        prevrev = int(rev) - 1
+
+    # check for an explicitly-provided base path (must be contained
+    # within quotes)
+    m = re.search(r'base ?path:[\'"]([^\'"]+)[\'"]', log, re.M | re.I)
+    if m:
+        base_path = m.group(1)
+    else:
+        base_path = ''
+
+    # get bug numbers referenced in this log message
+    ticket_command_re = re.compile(ticket_command)
+    ticket_re = re.compile(ticket_prefix + '([0-9]+)')
+
+    ticket_ids = []
+    ticket_cmd_groups = ticket_command_re.findall(log)
+    for cmd, tkts in ticket_cmd_groups:
+        funcname = supported_ticket_cmds.get(cmd.lower(), '')
+        if funcname:
+            for tkt_id in ticket_re.findall(tkts):
+                ticket_ids.append(tkt_id)
+
+    if ticket_ids:
+        bugs = '--bugs-closed=' + ','.join(ticket_ids)
+    else:
+        bugs = ''
+
+    # summary is log up to first period+space / first new line / first 250 chars
+    # (whichever comes first)
+    summary = '--summary=' + log[:250].splitlines().pop(0).split('. ').pop(0)
+
+    # other parameters for postreview
+    repository_url  = '--repository-url=file://' + repos
+    password        = '--password=' + PASSWORD
+    username        = '--username=' + USERNAME
+    description     = "--description=(In [%s]) %s" % (rev, log)
+    submitas        = '--submit-as=' + author
+    revision        = '--revision-range=%s:%s' % (prevrev, rev)
+
+    # common arguments
+    args = [repository_url, username, password, publish,
+            submitas, revision, base_path, reviewid]
+
+    # filter out any potentially blank args, which will confuse post-review
+    args = [i for i in args if len(i) > 1]
+
+    # if not updating an existing review, add extra arguments
+    if len(reviewid) == 0:
+        args += [summary, description, bugs]
+
+    if DEBUG:
+        args += ['-d', '--output-diff']
+        print [os.path.join(POSTREVIEW_PATH, 'post-review')] + args
+
+    # Run Review Board post-review script
+    data = execute([os.path.join(POSTREVIEW_PATH, 'post-review')] + args,
+                   env = {'LANG': 'en_US.UTF-8'})
+
+    if DEBUG:
+        print data
+
+if __name__ == '__main__':
+    main()
+