| Index: tests/sample_pre_commit_hook
|
| diff --git a/tests/sample_pre_commit_hook b/tests/sample_pre_commit_hook
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..ba67e5d82dfdedf55a4a7f6d06db2c89cf6622f1
|
| --- /dev/null
|
| +++ b/tests/sample_pre_commit_hook
|
| @@ -0,0 +1,187 @@
|
| +#!/usr/bin/env python
|
| +# Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
| +# Use of this source code is governed by a BSD-style license that can be
|
| +# found in the LICENSE file at
|
| +# http://src.chromium.org/viewvc/chrome/trunk/src/LICENSE
|
| +
|
| +"""Commit bot fake author svn server hook.
|
| +
|
| +Looks for svn commit --withrevprop realauthor=foo, replaces svn:author with this
|
| +author and sets the property commitbot to the commit bot credential to signify
|
| +this revision was committed with the commit bot.
|
| +
|
| +It achieves its goal using an undocumented way. This script could use 'svnlook'
|
| +to read revprop properties but the code would still be needed to overwrite the
|
| +properties.
|
| +
|
| +http://svnbook.red-bean.com/nightly/en/svn.reposadmin.create.html#svn.reposadmin.create.hooks
|
| +strongly advise against modifying a transation in a commit because the svn
|
| +client caches certain bits of repository data. Upon asking subversion devs,
|
| +having the wrong svn:author cached on the commit checkout is the worst that can
|
| +happen.
|
| +
|
| +This code doesn't care about this issue because only the commit bot will trigger
|
| +this code, which runs in a controlled environment.
|
| +
|
| +The transaction file format is also extremely unlikely to change. If it does,
|
| +the hook will throw an UnexpectedFileFormat exception which will be silently
|
| +ignored.
|
| +"""
|
| +
|
| +import os
|
| +import re
|
| +import sys
|
| +
|
| +
|
| +class UnexpectedFileFormat(Exception):
|
| + """The transaction file format is not the format expected."""
|
| +
|
| +
|
| +def read_svn_dump(filepath):
|
| + """Returns list of (K, V) from a keyed svn file.
|
| +
|
| + Don't use a map so ordering is kept.
|
| +
|
| + raise UnexpectedFileFormat if the file cannot be understood.
|
| + """
|
| + class InvalidHeaderLine(Exception):
|
| + """Raised by read_entry when the line read is not the format expected.
|
| + """
|
| +
|
| + try:
|
| + f = open(filepath, 'rb')
|
| + except EnvironmentError:
|
| + raise UnexpectedFileFormat('The transaction file cannot be opened')
|
| +
|
| + try:
|
| + out = []
|
| + def read_entry(entrytype):
|
| + header = f.readline()
|
| + match = re.match(r'^' + entrytype + ' (\d+)$', header)
|
| + if not match:
|
| + raise InvalidHeaderLine(header)
|
| + datalen = int(match.group(1))
|
| + data = f.read(datalen)
|
| + if len(data) != datalen:
|
| + raise UnpexpectedFileFormat(
|
| + 'Data value is not the expected length')
|
| + # Reads and ignore \n
|
| + if f.read(1) != '\n':
|
| + raise UnpexpectedFileFormat('Data value doesn\'t end with \\n')
|
| + return data
|
| +
|
| + while True:
|
| + try:
|
| + key = read_entry('K')
|
| + except InvalidHeaderLine, e:
|
| + # Check if it's the end of the file.
|
| + if e.args[0] == 'END\n':
|
| + break
|
| + raise UnpexectedFileFormat('Failed to read a key: %s' % e)
|
| + try:
|
| + value = read_entry('V')
|
| + except InvalidHeaderLine, e:
|
| + raise UnpexectedFileFormat('Failed to read a value: %s' % e)
|
| + out.append([key, value])
|
| + return out
|
| + finally:
|
| + f.close()
|
| +
|
| +
|
| +def write_svn_dump(filepath, data):
|
| + """Writes a svn keyed file with a list of (K, V)."""
|
| + f = open(filepath, 'wb')
|
| + try:
|
| + def write_entry(entrytype, value):
|
| + f.write('%s %d\n' % (entrytype, len(value)))
|
| + f.write(value)
|
| + f.write('\n')
|
| +
|
| + for k, v in data:
|
| + write_entry('K', k)
|
| + write_entry('V', v)
|
| + f.write('END\n')
|
| + finally:
|
| + f.close()
|
| +
|
| +
|
| +def find_key(data, key):
|
| + """Finds the item in a list of tuple where item[0] == key.
|
| +
|
| + asserts if there is more than one item with the key.
|
| + """
|
| + items = [i for i in data if i[0] == key]
|
| + if not items:
|
| + return None
|
| + assert len(items) == 1
|
| + return items[0]
|
| +
|
| +
|
| +def handle_commit_bot(repo_path, tx, commit_bot, admin_email):
|
| + """Replaces svn:author with realauthor and sets commit-bot."""
|
| + # The file format is described there:
|
| + # http://svn.apache.org/repos/asf/subversion/trunk/notes/dump-load-format.txt
|
| + propfilepath = os.path.join(
|
| + repo_path, 'db', 'transactions', tx + '.txn', 'props')
|
| +
|
| + # Do a lot of checks to make sure everything is in the expected format.
|
| + try:
|
| + data = read_svn_dump(propfilepath)
|
| + except UnexpectedFileFormat:
|
| + return (
|
| + 'Failed to parse subversion server transaction format.\n'
|
| + 'Please contact %s ASAP with\n'
|
| + 'this error message.') % admin_email
|
| + if not data:
|
| + return (
|
| + 'Failed to load subversion server transaction file.\n'
|
| + 'Please contact %s ASAP with\n'
|
| + 'this error message.') % admin_email
|
| +
|
| + realauthor = find_key(data, 'realauthor')
|
| + if not realauthor:
|
| + # That's fine, there is no author to fake.
|
| + return
|
| +
|
| + author = find_key(data, 'svn:author')
|
| + if not author or not author[1]:
|
| + return (
|
| + 'Failed to load svn:author from the transaction file.\n'
|
| + 'Please contact %s ASAP with\n'
|
| + 'this error message.') % admin_email
|
| +
|
| + if author[1] != commit_bot:
|
| + # The author will not be changed and realauthor will be kept as a
|
| + # revision property.
|
| + return
|
| +
|
| + if len(realauthor[1]) > 50:
|
| + return 'Fake author was rejected due to being too long.'
|
| +
|
| + if not re.match(r'^[a-zA-Z0-9\@\-\_\+\%\.]+$', realauthor[1]):
|
| + return 'Fake author was rejected due to not passing regexp.'
|
| +
|
| + # Overwrite original author
|
| + author[1] = realauthor[1]
|
| + # Remove realauthor svn property
|
| + data.remove(realauthor)
|
| + # Add svn property commit-bot=<commit-bot username>
|
| + data.append(('commit-bot', commit_bot))
|
| + write_svn_dump(propfilepath, data)
|
| +
|
| +
|
| +def main():
|
| + # Replace with your commit-bot credential.
|
| + commit_bot = 'user1@example.com'
|
| + admin_email = 'dude@example.com'
|
| + ret = handle_commit_bot(sys.argv[1], sys.argv[2], commit_bot, admin_email)
|
| + if ret:
|
| + print >> sys.stderr, ret
|
| + return 1
|
| + return 0
|
| +
|
| +
|
| +if __name__ == '__main__':
|
| + sys.exit(main())
|
| +
|
| +# vim: ts=4:sw=4:tw=80:et:
|
|
|