diff --git a/hooks/post-commit b/hooks/post-commit
new file mode 100755
index 000000000..7495ec5cf
--- /dev/null
+++ b/hooks/post-commit
@@ -0,0 +1,142 @@
+#!/bin/bash
+
+# POST-COMMIT HOOK
+#
+# The post-commit hook is invoked after a commit.  Subversion runs
+# this hook by invoking a program (script, executable, binary, etc.)
+# named 'post-commit' (for which this file is a template) with the 
+# following ordered arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REV          (the number of the revision just committed)
+#
+# The default working directory for the invocation is undefined, so
+# the program should set one explicitly if it cares.
+#
+# Because the commit has already completed and cannot be undone,
+# the exit code of the hook program is ignored.  The hook program
+# can use the 'svnlook' utility to help it examine the
+# newly-committed tree.
+#
+# On a Unix system, the normal procedure is to have 'post-commit'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'post-commit' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'post-commit.bat' or 'post-commit.exe',
+# but the basic idea is the same.
+# 
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+# Log everything for debug, otherwise use explicit logging (further down)
+#[ "$LOGGING" ] || LOGGING=1 { exec $0 "$@" 2>&1 | logger -p local0.info -t "commit"; }
+
+logger -p local0.info -t "hook" "${0##*/} $*"
+
+repo="$1"
+rev="$2"
+
+# Settings
+program=${0##*/}
+progdir=${0%/*}
+thishost="$(/bin/hostname)"
+thishost="${thishost%%.*}"
+svnpath="/home/svn"
+trac="/www/tools.ietf.org/tools/ietfdb/"
+svn_url="https://svn.tools.ietf.org/svn/tools/ietfdb"
+trac_url="https://trac.tools.ietf.org/tools/ietfdb"
+
+# Do a local backup
+relpath=${repo#$svnpath/}
+bckpath="$svnpath/.backup/$thishost/$relpath"
+[ -d $bckpath ] || mkdir -p $bckpath
+/usr/bin/svn-fast-backup -q $repo $bckpath
+
+# Inform trac about a new changeset
+trac-admin $trac changeset added ietfdb $rev 
+
+committer=$(/usr/bin/svnlook author $repo -r $rev)
+comments=$(/usr/bin/svnlook log $repo -r $rev)
+files=$(/usr/bin/svnlook changed $repo -r $rev)
+
+dirs=$(/usr/bin/svnlook dirs-changed -r$rev $repo)
+logger -p local0.info -t "commit" "dirs '$dirs'"
+
+# Look for 'requirements.txt' above the committed change.  Looking only for
+# filechanges, not for dirchanges, filters out commits which are just tree
+# copies, such as when creating new dev branches for the code sprint.
+branch=$($progdir/svnfind --filechange --dirpath $repo $rev "requirements.txt")
+
+if [ -n "$branch" ]; then
+    # Update trac tickets
+    /usr/bin/python $progdir/trac-post-commit-hook -p "$trac" -r "$rev" 2>&1 | logger -t "svn post-commit" -p "user.error" -i 
+
+    # Notify buildbot
+    filenames=$(/usr/bin/svnlook changed $repo -r $rev | sed -r -e 's/^ *[^ ]+ +//' -e "s|$branch/||")
+    /usr/local/bin/buildbot sendchange							\
+	--master="zinfandel.tools.ietf.org:9989" --auth="ietfdb:BRiR6XcT7x3$"		\
+	--who="$committer" --repository="https://svn.tools.ietf.org/svn/tools/ietfdb/"	\
+	--vc=svn --branch="$branch" --revision=$rev --property=xproperty:xvalue		\
+	--revlink="https://trac.tools.ietf.org/tools/ietfdb/changeset/$rev"		\
+	--comments="$comments" $filenames > /dev/null
+
+fi
+
+
+# Log the commit
+logger -p local0.info -t "commit" "$relpath r$rev $committer"
+logger -p local0.info -t "commit" "branch: $branch"
+
+# Notify committers
+
+if [[ $comments =~ ready.(for|to).merge ]]; then
+	mail $(< $progdir/notify-email.txt) -s "[svnhook] Svn commit ready for merge: $relpath | $committer: ${comments:0:42}..." <<-EOF | logger -p local0.info -t "ready for merge email"
+
+	$committer has a commit ready for merge:
+	$relpath/$branch [$rev]:
+
+	$comments
+
+	Svn:  $svn_url/$branch
+	Trac: $trac_url/changeset/$rev/$branch
+
+	Files:
+
+	$files
+
+	EOF
+
+else
+
+	mail $(< $progdir/notify-email.txt) -s "[svnhook] Svn commit to $relpath | $committer: ${comments:0:42}..." <<-EOF | logger -p local0.info -t "commit email"
+
+	$committer has made a new SVN commit in
+	$relpath/$branch [$rev]:
+
+	$comments
+
+	Svn:  $svn_url/$branch
+	Trac: $trac_url/changeset/$rev/$branch
+
+	Files:
+
+	$files
+
+	EOF
+
+fi
diff --git a/hooks/pre-commit b/hooks/pre-commit
new file mode 100755
index 000000000..4979089aa
--- /dev/null
+++ b/hooks/pre-commit
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+
+"""
+An SVN pre-commit hook which requires that commits either are marked as
+whitespace cleanup commits, and contain no non-whitespace changes, or
+leave whitespace alone on lines without code changes.
+"""
+
+import os
+import sys
+import difflib
+import debug
+from pysvn import Client, Transaction
+
+prog = os.path.basename(sys.argv[0])
+
+def die(msg):
+    sys.stderr.write("\n%s: Error: %s\n" % (prog, msg))
+    sys.exit(1)
+
+if len(sys.argv) <= 1:
+    die("Expected arguments: REPOSITORY TRANSACTION, found none")
+    
+if len(sys.argv) <= 2:
+    die( "Expected arguments: REPOSITORY TRANSACTION, found only '%s'" % sys.argv[1])
+
+repo = sys.argv[1]
+txname = sys.argv[2]
+tx = Transaction(repo, txname)
+client = Client()
+
+is_whitespace_cleanup = "whitespace cleanup" in tx.revpropget("svn:log").lower()
+
+def normalize(s):
+    return s.rstrip().expandtabs()    
+
+def normalize_sequence(seq):
+    o = []
+    for l in seq:
+        o.append(normalize(l))
+    return o
+
+def count(gen):
+    return sum(1 for _ in gen)
+
+# Function with side effects.  Acts on global varaibles
+def inc_ab(flag):
+    global a, b
+    if   flag == ' ':
+        a += 1; b += 1
+    elif flag == '-':
+        a += 1
+    elif flag == '+':
+        b += 1
+    elif flag == '?':
+        pass
+    else:
+        raise ValueError("Unexpected ndiff mark: '%s' in: %s" % (flag, plain_diff[i]))
+
+def get_chunks(unidiff):
+    if not unidiff:
+        return []
+    chunks = []
+    chunk = []
+    intro = unidiff[0:2]
+    for line in unidiff[2:]:
+        if line.startswith("@@"):
+            if chunk:
+                chunks.append(chunk)
+            chunk = [line]
+        else:
+            chunk.append(line)
+    chunks.append(chunk)
+    return intro, chunks
+        
+changes = tx.changed()
+issues = {}
+context = 3
+for path in changes:
+    action, kind, mod, propmod = changes[path]
+
+    # Don't try to diff added or deleted files, on ly changed text files
+    if not (mod and action == "R"):
+        continue
+
+    # Don't try do diff binary files
+    mimetype = tx.propget("svn:mime-type", path)
+    if mimetype and not mimetype.startswith("text/"):
+        continue
+
+    new = tx.cat(path).splitlines()
+    old = client.cat("file://"+os.path.join(repo,path)).splitlines()
+
+    plain_diff = list(difflib.unified_diff(old, new, "%s (repository)"%path, "%s (commit)"%path, lineterm="" ))
+    old = normalize_sequence(old)
+    new = normalize_sequence(new)
+    white_diff = list(difflib.unified_diff(old, new, "%s (repository)"%path, "%s (commit)"%path, lineterm=""))
+
+    plain_count = len(plain_diff)
+    white_count = len(white_diff)
+
+#    for i in range(len(white_diff)):
+#        sys.stderr.write("%-80s |  %-80s\n" % (normalize(plain_diff[i][:80]), normalize(white_diff[i][:80])))
+    if white_count != plain_count and not is_whitespace_cleanup:
+        intro, plain_chunks = get_chunks(plain_diff)
+        intro, white_chunks = get_chunks(white_diff)
+        for chunk in white_chunks:
+            for i in range(len(plain_chunks)):
+                if chunk == plain_chunks[i]:
+                    del plain_chunks[i]
+        issue = intro
+        for chunk in plain_chunks:
+            issue += chunk
+        if  len(plain_chunks) > 1:
+            are = "are"; s = "s"; an = ""
+        else:
+            are = "is"; s = ""; an = "an "
+        issues[path] = issue
+    if white_count != 0 and is_whitespace_cleanup:
+        intro, white_chunks = get_chunks(white_diff)
+        if  len(white_chunks) > 1:
+            are = "are"; s = "s"; an = ""
+        else:
+            are = "is"; s = ""; an = "an "
+        issues[path] = white_diff
+
+if issues:
+    if is_whitespace_cleanup:
+        die("It looks as if there are non-whitespace changes in\n"
+            "this commit, but it was marked as a whitespace cleanup commit.\n\n"
+            "Here %s the diff chunk%s with unexpected change%s:\n\n%s\n\n"
+            "Declining the commit due to a mix of code and spaces-only changes.  Please\n"
+            "avoid mixing whitespace-only changes with code changes. See details above." % 
+            (are, s, s, '\n\n'.join([ '\n'.join(issues[path]) for path in issues ]))
+        )
+
+    else:
+        die("It looks as if there are spaces-only changes in this\n"
+            "commit, but it was not marked as a whitespace cleanup commit.\n\n"
+            "Here %s the diff chunk%s with unexpected change%s:\n\n%s\n\n"
+            "Declining the commit due to a mix of code and spaces-only changes.  Please\n"
+            "avoid mixing whitespace-only changes with code changes. See details above." % 
+            (are, s, s, '\n\n'.join([ '\n'.join(issues[path]) for path in issues ]))
+        )
+
+sys.exit(0)
diff --git a/hooks/pre-revprop-change b/hooks/pre-revprop-change
new file mode 100755
index 000000000..2fc29bc48
--- /dev/null
+++ b/hooks/pre-revprop-change
@@ -0,0 +1,72 @@
+#!/bin/sh
+
+# PRE-REVPROP-CHANGE HOOK
+#
+# The pre-revprop-change hook is invoked before a revision property
+# is added, modified or deleted.  Subversion runs this hook by invoking
+# a program (script, executable, binary, etc.) named 'pre-revprop-change'
+# (for which this file is a template), with the following ordered
+# arguments:
+#
+#   [1] REPOS-PATH   (the path to this repository)
+#   [2] REVISION     (the revision being tweaked)
+#   [3] USER         (the username of the person tweaking the property)
+#   [4] PROPNAME     (the property being set on the revision)
+#   [5] ACTION       (the property is being 'A'dded, 'M'odified, or 'D'eleted)
+#
+#   [STDIN] PROPVAL  ** the new property value is passed via STDIN.
+#
+# If the hook program exits with success, the propchange happens; but
+# if it exits with failure (non-zero), the propchange doesn't happen.
+# The hook program can use the 'svnlook' utility to examine the 
+# existing value of the revision property.
+#
+# WARNING: unlike other hooks, this hook MUST exist for revision
+# properties to be changed.  If the hook does not exist, Subversion 
+# will behave as if the hook were present, but failed.  The reason
+# for this is that revision properties are UNVERSIONED, meaning that
+# a successful propchange is destructive;  the old value is gone
+# forever.  We recommend the hook back up the old value somewhere.
+#
+# On a Unix system, the normal procedure is to have 'pre-revprop-change'
+# invoke other programs to do the real work, though it may do the
+# work itself too.
+#
+# Note that 'pre-revprop-change' must be executable by the user(s) who will
+# invoke it (typically the user httpd runs as), and that user must
+# have filesystem-level permission to access the repository.
+#
+# On a Windows system, you should name the hook program
+# 'pre-revprop-change.bat' or 'pre-revprop-change.exe',
+# but the basic idea is the same.
+#
+# The hook program typically does not inherit the environment of
+# its parent process.  For example, a common problem is for the
+# PATH environment variable to not be set to its usual value, so
+# that subprograms fail to launch unless invoked via absolute path.
+# If you're having unexpected problems with a hook program, the
+# culprit may be unusual (or missing) environment variables.
+# 
+# Here is an example hook script, for a Unix /bin/sh interpreter.
+# For more examples and pre-written hooks, see those in
+# the Subversion repository at
+# http://svn.collab.net/repos/svn/trunk/tools/hook-scripts/ and
+# http://svn.collab.net/repos/svn/trunk/contrib/hook-scripts/
+
+
+REPOS="$1"
+REV="$2"
+USER="$3"
+PROPNAME="$4"
+ACTION="$5"
+
+if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
+if [ "$ACTION" = "M" -a "${PROPNAME%:*}" = "test" ]; then exit 0; fi
+if [ "$ACTION" = "A" -a "${PROPNAME%:*}" = "test" ]; then exit 0; fi
+
+echo "
+Changing revision properties other than svn:log and test:* is prohibited.
+Got ACTION: $ACTION, PROPNAME: $PROPNAME
+" >&2
+
+exit 1
diff --git a/hooks/svnfind b/hooks/svnfind
new file mode 100755
index 000000000..efa25cd80
--- /dev/null
+++ b/hooks/svnfind
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# -*- python -*-
+
+"""
+NAME
+	%(program)s - find a filename close to a given a SVN repository commit
+
+SYNOPSIS
+	%(program)s [OPTIONS] REPO REV NAME
+
+DESCRIPTION
+	Supposing we have a subversion repository containing a trunk and
+        various branches.  Given the repository url and the revision number of
+        a commit, what is the top of the branch?  $(program)s attempts to
+        answer this by letting you search for a specific file name which is
+        assumed to be unique within the branch and be situated at the top of
+        the branch subtree.
+
+%(options)s
+
+AUTHOR
+	Written by Henrik Levkowetz, <henrik@zinfandel.tools.ietf.org>
+
+COPYRIGHT
+	Copyright 2015 Henrik Levkowetz
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or (at
+	your option) any later version. There is NO WARRANTY; not even the
+	implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+	PURPOSE. See the GNU General Public License for more details.
+
+"""
+
+import sys, os.path, getopt, re
+
+version = "0.13"
+program = os.path.basename(sys.argv[0])
+progdir = os.path.dirname(sys.argv[0])
+
+# ----------------------------------------------------------------------
+# Parse options
+
+options = ""
+for line in re.findall("\n +(if|elif) +opt in \[(.+)\]:\s+#(.+)\n", open(sys.argv[0]).read()):
+    if not options:
+        options += "OPTIONS\n"
+    options += "        %-16s %s\n" % (line[1].replace('"', ''), line[2])
+options = options.strip()
+
+# with ' < 1:' on the next line, this is a no-op:
+if len(sys.argv) <= 1:
+    help()
+
+try:
+    opts, files = getopt.gnu_getopt(sys.argv[1:], "dfDhv", ["dirchange", "filechange", "dirpath", "help", "version",])
+except Exception, e:
+    print "%s: %s" % (program, e)
+    sys.exit(1)
+
+# ----------------------------------------------------------------------
+# Handle options
+
+# set default values, if any
+opt_dirpath = False
+opt_dirchange = False
+opt_filechange = False
+opt_verbose = False
+
+# ----------------------------------------------------------------------
+def help(msg=None, err=0):
+    if msg:
+        print(msg+'\n')
+    print(__doc__ % globals())
+    sys.exit(err)
+
+# handle individual options
+for opt, value in opts:
+    if   opt in ["-h", "--help"]: # Output this help, then exit
+        help()
+    elif opt in ["-v", "--version"]: # Output version information, then exit
+        print program, version
+        sys.exit(0)
+    elif opt in ["-d", "--dirchange"]: # Look only for directory changes
+        opt_dirchange = True
+    elif opt in ["-f", "--filechange"]: #Look only for file changes
+        opt_filechange = True
+    elif opt in ["-D", "--dirpath"]: # Output the directory path, not the file path 
+        opt_dirpath = True
+
+# if neither filechange nor dirchange have been specified, look for both
+if not opt_filechange and not opt_dirchange:
+    opt_filechange = True
+    opt_dirchange = True
+
+# ----------------------------------------------------------------------
+def help(msg=None, err=0):
+    if msg:
+        print(msg+'\n')
+    print(__doc__ % globals())
+    sys.exit(err)
+
+# ----------------------------------------------------------------------
+def say(s):
+    sys.stderr.write("%s\n" % (s))
+
+# ----------------------------------------------------------------------
+def note(s):
+    if opt_verbose:
+        sys.stderr.write("%s\n" % (s))
+
+# ----------------------------------------------------------------------
+def die(s, error=1):
+    sys.stderr.write("\n%s: Error: %s\n\n" % (program, s))
+    sys.exit(error)
+
+# ----------------------------------------------------------------------
+# Bad args
+
+if len(files) < 3:
+    help("Not enogh arguments\n\nExpected  %s REPO REV NAME" %program, 1)
+    
+# ----------------------------------------------------------------------
+# The program itself
+
+import pysvn
+#import debug
+
+repo, rev, name = files
+
+changeset = pysvn.Transaction(repo, rev, True)
+changed = changeset.changed()
+
+# Each dictionary entry is a tuple, with elements as follows:
+CHG_ACTION, CHG_KIND, CHG_TEXTMOD, CHG_PROPMOD = range(4)
+
+node = None
+for key in changed:
+    if opt_dirchange and changed[key][CHG_KIND] == pysvn.node_kind.dir:
+        node = key
+        break
+    if opt_filechange and changed[key][CHG_KIND] == pysvn.node_kind.file:
+        node = '/'.join(key.split('/')[:-1])
+        break
+
+while node:
+    #debug.show('node')
+    list = changeset.list(node)
+    if name in list:
+        if opt_dirpath:
+            print node
+        else:
+            sys.stdout.write(node)
+            sys.stdout.write('/')
+            sys.stdout.write(name)
+            sys.stdout.write('\n')
+        break
+    node = '/'.join(node.split('/')[:-1])
diff --git a/hooks/trac-post-commit-hook b/hooks/trac-post-commit-hook
new file mode 100755
index 000000000..fb4545a43
--- /dev/null
+++ b/hooks/trac-post-commit-hook
@@ -0,0 +1,241 @@
+#!/usr/bin/env python
+
+# trac-post-commit-hook
+# ----------------------------------------------------------------------------
+# Copyright (c) 2004 Stephen Hansen 
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+#   The above copyright notice and this permission notice shall be included in
+#   all copies or substantial portions of the Software. 
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+# ----------------------------------------------------------------------------
+
+# This Subversion post-commit hook script is meant to interface to the
+# Trac (http://www.edgewall.com/products/trac/) issue tracking/wiki/etc 
+# system.
+# 
+# It should be called from the 'post-commit' script in Subversion, such as
+# via:
+#
+# REPOS="$1"
+# REV="$2"
+# TRAC_ENV="/path/to/tracenv"
+#
+# /usr/bin/python /usr/local/src/trac/contrib/trac-post-commit-hook \
+#  -p "$TRAC_ENV" -r "$REV"
+#
+# (all the other arguments are now deprecated and not needed anymore)
+#
+# It searches commit messages for text in the form of:
+#   command #1
+#   command #1, #2
+#   command #1 & #2 
+#   command #1 and #2
+#
+# Instead of the short-hand syntax "#1", "ticket:1" can be used as well, e.g.:
+#   command ticket:1
+#   command ticket:1, ticket:2
+#   command ticket:1 & ticket:2 
+#   command ticket:1 and ticket:2
+#
+# In addition, the ':' character can be omitted and issue or bug can be used
+# instead of ticket.
+#
+# You can have more then one command in a message. The following commands
+# are supported. There is more then one spelling for each command, to make
+# this as user-friendly as possible.
+#
+#   close, closed, closes, fix, fixed, fixes
+#     The specified issue numbers are closed with the contents of this
+#     commit message being added to it. 
+#   references, refs, addresses, re, see 
+#     The specified issue numbers are left in their current status, but 
+#     the contents of this commit message are added to their notes. 
+#
+# A fairly complicated example of what you can do is with a commit message
+# of:
+#
+#    Changed blah and foo to do this or that. Fixes #10 and #12, and refs #12.
+#
+# This will close #10 and #12, and add a note to #12.
+
+import re
+import os
+import sys
+from datetime import datetime 
+from optparse import OptionParser, OptionGroup
+import syslog
+
+syslog.openlog("post-commit-trac", syslog.LOG_PID, syslog.LOG_LOCAL0)
+log = syslog.syslog
+
+parser = OptionParser()
+depr = '(not used anymore)'
+parser.add_option('-e', '--require-envelope', dest='envelope', default='',
+                  help="""
+Require commands to be enclosed in an envelope.
+If -e[], then commands must be in the form of [closes #4].
+Must be two characters.""")
+parser.add_option('-p', '--project', dest='project',
+                  help='Path to the Trac project.')
+parser.add_option('-r', '--revision', dest='rev',
+                  help='Repository revision number.')
+parser.add_option('-a', '--action', dest='action', default=None, help='The action to take on a ticket (default: none)')
+
+group = OptionGroup(parser, "DEPRECATED OPTIONS")
+group.add_option('-u', '--user', dest='user',
+                  help='The user who is responsible for this action '+depr)
+group.add_option('-m', '--msg', dest='msg',
+                  help='The log message to search '+depr)
+group.add_option('-c', '--encoding', dest='encoding',
+                  help='The encoding used by the log message '+depr)
+group.add_option('-s', '--siteurl', dest='url',
+                  help=depr+' the base_url from trac.ini will always be used.')
+
+parser.add_option_group(group)
+
+(options, args) = parser.parse_args(sys.argv[1:])
+
+if options.project and not 'PYTHON_EGG_CACHE' in os.environ:
+    os.environ['PYTHON_EGG_CACHE'] = os.path.join(options.project, '.egg-cache')
+
+from trac.env import open_environment
+from trac.ticket.notification import TicketNotifyEmail
+from trac.ticket import Ticket
+from trac.ticket.web_ui import TicketModule
+# TODO: move grouped_changelog_entries to model.py
+from trac.util.text import to_unicode
+from trac.util.datefmt import utc
+from trac.versioncontrol.api import NoSuchChangeset
+
+ticket_prefix = '(?:#|(?:ticket|issue|bug)s?[: ]?#?)'
+ticket_reference = ticket_prefix + '[0-9]+'
+action_pattern = "(?:(?i)(?:fix(?: for)?|fixes|close|closes|addresses|references|refs|re|relates to|related to|see|described in))"
+ticket_command =  (r'(?P<action>%s).?(?P<ticket>%s(?:(?:[, &]*|[ ]?and[ ]?)%s)*)' % (action_pattern, ticket_reference, ticket_reference))
+
+if options.envelope:
+    ticket_command = r'\%s%s\%s' % (options.envelope[0], ticket_command,
+                                    options.envelope[1])
+    
+command_re = re.compile(ticket_command)
+ticket_re = re.compile(ticket_prefix + '([0-9]+)')
+
+class CommitHook:
+#     _supported_cmds = {'close':      '_cmdClose',
+#                        'closed':     '_cmdClose',
+#                        'closes':     '_cmdClose',
+#                        'fix':        '_cmdClose',
+#                        'fixed':      '_cmdClose',
+#                        'fixes':      '_cmdClose',
+#                        'addresses':  '_cmdRefs',
+#                        're':         '_cmdRefs',
+#                        'references': '_cmdRefs',
+#                        'refs':       '_cmdRefs',
+#                        'see':        '_cmdRefs'}
+
+    _supported_cmds = {'fix':               '_cmdClose',
+                       'fix for':           '_cmdClose',
+                       'fixes':             '_cmdClose',
+                       'close':             '_cmdClose',
+                       'closes':            '_cmdClose',
+
+                       'addresses':         '_cmdRefs',
+                       'described in':      '_cmdRefs',
+                       'references':        '_cmdRefs',
+                       'refs':              '_cmdRefs',
+                       're':                '_cmdRefs',
+                       'relates to':        '_cmdRefs',
+                       'related to':        '_cmdRefs',
+                       'see':               '_cmdRefs',
+                   }
+
+    def __init__(self, project=options.project, author=options.user,
+                 rev=options.rev, url=options.url):
+        self.env = open_environment(project)
+        repos = self.env.get_repository()
+        repos.sync()
+        
+        # Instead of bothering with the encoding, we'll use unicode data
+        # as provided by the Trac versioncontrol API (#1310).
+        try:
+            chgset = repos.get_changeset(rev)
+        except NoSuchChangeset:
+            return # out of scope changesets are not cached
+        self.author = chgset.author
+        self.rev = rev
+        self.msg = "From [%s]:\n\n%s" % (rev, chgset.message)
+        self.now = datetime.now(utc)
+
+        cmd_groups = command_re.findall(self.msg)
+
+        tickets = {}
+        for cmd, tkts in cmd_groups:
+            funcname = CommitHook._supported_cmds.get(cmd.lower(), '')
+            if funcname:
+                for tkt_id in ticket_re.findall(tkts):
+                    func = getattr(self, funcname)
+                    tickets.setdefault(tkt_id, []).append(func)
+
+        for tkt_id, cmds in tickets.iteritems():
+            try:
+                fixes = False
+                db = self.env.get_db_cnx()
+                
+                ticket = Ticket(self.env, int(tkt_id), db)
+                for cmd in cmds:
+                    if cmd == self._cmdClose:
+                        fixes = True
+                    cmd(ticket)
+
+                # determine sequence number... 
+                cnum = 0
+                tm = TicketModule(self.env)
+                for change in tm.grouped_changelog_entries(ticket, db):
+                    if change['permanent']:
+                        cnum += 1
+                
+                if fixes:
+                    self.msg = "Fixed in [%s]:\n\n%s" % (rev, chgset.message)
+
+                log("Updating ticket #%s: %s" % (tkt_id, chgset.message))
+
+                ticket.save_changes(self.author, self.msg, self.now, db, cnum+1)
+                db.commit()
+                
+                tn = TicketNotifyEmail(self.env)
+                tn.notify(ticket, newticket=0, modtime=self.now)
+            except Exception, e:
+                # import traceback
+                # traceback.print_exc(file=sys.stderr)
+                print>>sys.stderr, 'Unexpected error while processing ticket ' \
+                                   'ID %s: %s' % (tkt_id, e)
+            
+
+    def _cmdClose(self, ticket):
+        ticket['status'] = 'closed'
+        ticket['resolution'] = 'fixed'
+
+    def _cmdRefs(self, ticket):
+        pass
+
+
+if __name__ == "__main__":
+    if len(sys.argv) < 5:
+        print "For usage: %s --help" % (sys.argv[0])
+        print
+        print "Note that the deprecated options will be removed in Trac 0.12."
+    else:
+        CommitHook()