| 1 | #!/usr/bin/env python |
|---|
| 2 | # -*- coding: iso8859-1 -*- |
|---|
| 3 | # |
|---|
| 4 | # Author: Jonas Borgström <jonas@edgewall.com> |
|---|
| 5 | # |
|---|
| 6 | # This script will enforce the following policy: |
|---|
| 7 | # |
|---|
| 8 | # "A checkin must reference an open ticket." |
|---|
| 9 | # |
|---|
| 10 | # This script should be invoked from the subversion pre-commit hook like this: |
|---|
| 11 | # |
|---|
| 12 | # REPOS="$1" |
|---|
| 13 | # TXN="$2" |
|---|
| 14 | # TRAC_ENV="/somewhere/trac/project/" |
|---|
| 15 | # LOG=`/usr/bin/svnlook log -t "$TXN" "$REPOS"` |
|---|
| 16 | # /usr/bin/python /some/path/trac-pre-commit-hook "$TRAC_ENV" "$LOG" || exit 1 |
|---|
| 17 | # |
|---|
| 18 | # WINDOWS: sample of pre-commit.bat (must use temporary files for the log message) |
|---|
| 19 | # |
|---|
| 20 | ##SET REPOS=%1 |
|---|
| 21 | ##SET TXN=%2 |
|---|
| 22 | ## |
|---|
| 23 | ##::----------------------------- |
|---|
| 24 | ##::Call the TRAC pre-commit hook |
|---|
| 25 | ##:: |
|---|
| 26 | ##SET TRAC_ENV=C:\somewhere\trac\project |
|---|
| 27 | ##SET LOG_FILE=%TEMP%.\svnfileT-%TXN% |
|---|
| 28 | ## |
|---|
| 29 | ##svnlook log -t %TXN% %REPOS%>%LOG_FILE% |
|---|
| 30 | ## |
|---|
| 31 | ##python [trac-path]\contrib\trac-pre-commit-hook "%TRAC_ENV%" "file:%LOG_FILE%" |
|---|
| 32 | ##IF ERRORLEVEL 1 SET TRAC_CANCEL=YES |
|---|
| 33 | ##DEL %LOG_FILE% |
|---|
| 34 | ##IF DEFINED TRAC_CANCEL GOTO :ERROR |
|---|
| 35 | ##:: |
|---|
| 36 | ##::----------------------------- |
|---|
| 37 | ## |
|---|
| 38 | ##:SUCCESS |
|---|
| 39 | ##EXIT 0 |
|---|
| 40 | ## |
|---|
| 41 | ##:ERROR |
|---|
| 42 | ##EXIT 1 |
|---|
| 43 | ## |
|---|
| 44 | |
|---|
| 45 | import os |
|---|
| 46 | import re |
|---|
| 47 | import sys |
|---|
| 48 | |
|---|
| 49 | from trac.env import open_environment |
|---|
| 50 | |
|---|
| 51 | def main(): |
|---|
| 52 | if len(sys.argv) != 3: |
|---|
| 53 | print >> sys.stderr, 'Usage: %s <trac_project> <log_message>' % sys.argv[0] |
|---|
| 54 | sys.exit(1) |
|---|
| 55 | |
|---|
| 56 | env_path = sys.argv[1] |
|---|
| 57 | log = sys.argv[2] |
|---|
| 58 | log = _readFromFile( log ) |
|---|
| 59 | |
|---|
| 60 | tickets = [] |
|---|
| 61 | for tmp in re.findall('(?:closes|fixes|addresses|references|refs|re)' |
|---|
| 62 | '.?(#[0-9]+(?:(?:[, &]+| *and *)#[0-9]+)*)', log): |
|---|
| 63 | tickets += re.findall('#([0-9]+)', tmp) |
|---|
| 64 | |
|---|
| 65 | # At least one ticket has to be mentioned in the log message |
|---|
| 66 | if tickets == []: |
|---|
| 67 | print >> sys.stderr, 'At least one open ticket must be mentioned ' \ |
|---|
| 68 | 'in the log message.' |
|---|
| 69 | sys.exit(1) |
|---|
| 70 | |
|---|
| 71 | env = open_environment(env_path) |
|---|
| 72 | db = env.get_db_cnx() |
|---|
| 73 | |
|---|
| 74 | cursor = db.cursor() |
|---|
| 75 | cursor.execute("SELECT COUNT(id) FROM ticket WHERE " |
|---|
| 76 | "status <> 'closed' AND id IN (%s)" % ','.join(tickets)) |
|---|
| 77 | row = cursor.fetchone() |
|---|
| 78 | # At least one of the tickets mentioned in the log messages has to |
|---|
| 79 | # be open |
|---|
| 80 | if not row or row[0] < 1: |
|---|
| 81 | print >> sys.stderr, 'At least one open ticket must be mentioned ' \ |
|---|
| 82 | 'in the log message.' |
|---|
| 83 | sys.exit(1) |
|---|
| 84 | else: |
|---|
| 85 | sys.exit(0) |
|---|
| 86 | |
|---|
| 87 | def _readFromFile(message): |
|---|
| 88 | """ Ivan Melnychuk: SOLUTION FOR WINDOWS SYSTEMS: |
|---|
| 89 | Windows does not support returning values from script. |
|---|
| 90 | Therefore temporary files may be used to keep some information like commit message |
|---|
| 91 | If the 'message' starts with 'file:', and the file with indicated name exists, then read the text message from the file rather then using the text directly |
|---|
| 92 | Even though actual message also may start with such prefix, it is very unlikely that the file exists with such a name by accident |
|---|
| 93 | """ |
|---|
| 94 | if message[:5] == 'file:' and os.path.exists( message[5:] ): |
|---|
| 95 | f = open( message[5:], 'r' ) |
|---|
| 96 | message = f.read() |
|---|
| 97 | f.close() |
|---|
| 98 | return message |
|---|
| 99 | return message; |
|---|
| 100 | |
|---|
| 101 | if __name__ == '__main__': |
|---|
| 102 | main() |
|---|
| 103 | |
|---|
| 104 | |
|---|
| 105 | |
|---|