Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 1 | #! /usr/bin/python |
| 2 | # -*- Python -*- |
| 3 | |
| 4 | """Complicated notification for CVS checkins. |
| 5 | |
| 6 | This script is used to provide email notifications of changes to the CVS |
| 7 | repository. These email changes will include context diffs of the changes. |
| 8 | Really big diffs will be trimmed. |
| 9 | |
| 10 | This script is run from a CVS loginfo file (see $CVSROOT/CVSROOT/loginfo). To |
| 11 | set this up, create a loginfo entry that looks something like this: |
| 12 | |
| 13 | mymodule /path/to/this/script %%s some-email-addr@your.domain |
| 14 | |
| 15 | In this example, whenever a checkin that matches `mymodule' is made, this |
| 16 | script is invoked, which will generate the diff containing email, and send it |
| 17 | to some-email-addr@your.domain. |
| 18 | |
| 19 | Note: This module used to also do repository synchronizations via |
| 20 | rsync-over-ssh, but since the repository has been moved to SourceForge, |
| 21 | this is no longer necessary. The syncing functionality has been ripped |
| 22 | out in the 3.0, which simplifies it considerably. Access the 2.x versions |
| 23 | to refer to this functionality. Because of this, the script is misnamed. |
| 24 | |
| 25 | It no longer makes sense to run this script from the command line. Doing so |
| 26 | will only print out this usage information. |
| 27 | |
| 28 | Usage: |
| 29 | |
| 30 | %(PROGRAM)s [options] <%%S> email-addr [email-addr ...] |
| 31 | |
| 32 | Where options is: |
| 33 | |
| 34 | --cvsroot=<path> |
| 35 | Use <path> as the environment variable CVSROOT. Otherwise this |
| 36 | variable must exist in the environment. |
| 37 | |
| 38 | --help |
| 39 | -h |
| 40 | Print this text. |
| 41 | |
| 42 | --context=# |
| 43 | -C # |
| 44 | Include # lines of context around lines that differ (default: 2). |
| 45 | |
| 46 | -c |
| 47 | Produce a context diff (default). |
| 48 | |
| 49 | -u |
| 50 | Produce a unified diff (smaller, but harder to read). |
| 51 | |
Björn Stenberg | 59fb25d | 2004-05-07 12:22:06 +0000 | [diff] [blame] | 52 | -U user |
| 53 | Mark diff as made by user. |
| 54 | |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 55 | <%%S> |
| 56 | CVS %%s loginfo expansion. When invoked by CVS, this will be a single |
| 57 | string containing the directory the checkin is being made in, relative |
| 58 | to $CVSROOT, followed by the list of files that are changing. If the |
| 59 | %%s in the loginfo file is %%{sVv}, context diffs for each of the |
| 60 | modified files are included in any email messages that are generated. |
| 61 | |
| 62 | email-addrs |
| 63 | At least one email address. |
| 64 | |
| 65 | """ |
| 66 | |
| 67 | import os |
| 68 | import sys |
| 69 | import string |
| 70 | import time |
| 71 | import getopt |
| 72 | |
| 73 | # Notification command |
Björn Stenberg | 39a84d0 | 2004-05-07 12:37:43 +0000 | [diff] [blame] | 74 | MAILCMD = '/bin/mail -s "%(username)s: %(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null' |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 75 | |
| 76 | # Diff trimming stuff |
| 77 | DIFF_HEAD_LINES = 20 |
| 78 | DIFF_TAIL_LINES = 20 |
| 79 | DIFF_TRUNCATE_IF_LARGER = 1000 |
| 80 | |
| 81 | PROGRAM = sys.argv[0] |
| 82 | |
| 83 | |
| 84 | |
| 85 | def usage(code, msg=''): |
| 86 | print __doc__ % globals() |
| 87 | if msg: |
| 88 | print msg |
| 89 | sys.exit(code) |
| 90 | |
| 91 | |
| 92 | |
| 93 | def calculate_diff(filespec, contextlines): |
| 94 | try: |
| 95 | file, oldrev, newrev = string.split(filespec, ',') |
| 96 | except ValueError: |
| 97 | # No diff to report |
| 98 | return '***** Bogus filespec: %s' % filespec |
| 99 | if oldrev == 'NONE': |
| 100 | try: |
| 101 | if os.path.exists(file): |
| 102 | fp = open(file) |
| 103 | else: |
| 104 | update_cmd = 'cvs -fn update -r %s -p %s' % (newrev, file) |
| 105 | fp = os.popen(update_cmd) |
| 106 | lines = fp.readlines() |
| 107 | fp.close() |
| 108 | lines.insert(0, '--- NEW FILE: %s ---\n' % file) |
| 109 | except IOError, e: |
| 110 | lines = ['***** Error reading new file: ', |
| 111 | str(e), '\n***** file: ', file, ' cwd: ', os.getcwd()] |
| 112 | elif newrev == 'NONE': |
| 113 | lines = ['--- %s DELETED ---\n' % file] |
| 114 | else: |
| 115 | # This /has/ to happen in the background, otherwise we'll run into CVS |
| 116 | # lock contention. What a crock. |
| 117 | if contextlines > 0: |
| 118 | difftype = "-C " + str(contextlines) |
| 119 | else: |
| 120 | difftype = "-uN" |
Björn Stenberg | 6ec42d9 | 2002-05-23 12:06:31 +0000 | [diff] [blame] | 121 | diffcmd = '/usr/bin/cvs -f diff -kk %s -b -r %s -r %s %s' % ( |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 122 | difftype, oldrev, newrev, file) |
| 123 | fp = os.popen(diffcmd) |
| 124 | lines = fp.readlines() |
| 125 | sts = fp.close() |
| 126 | # ignore the error code, it always seems to be 1 :( |
| 127 | ## if sts: |
| 128 | ## return 'Error code %d occurred during diff\n' % (sts >> 8) |
| 129 | if len(lines) > DIFF_TRUNCATE_IF_LARGER: |
| 130 | removedlines = len(lines) - DIFF_HEAD_LINES - DIFF_TAIL_LINES |
| 131 | del lines[DIFF_HEAD_LINES:-DIFF_TAIL_LINES] |
| 132 | lines.insert(DIFF_HEAD_LINES, |
| 133 | '[...%d lines suppressed...]\n' % removedlines) |
| 134 | return string.join(lines, '') |
| 135 | |
| 136 | |
| 137 | |
| 138 | def blast_mail(mailcmd, filestodiff, contextlines): |
| 139 | # cannot wait for child process or that will cause parent to retain cvs |
| 140 | # lock for too long. Urg! |
| 141 | if not os.fork(): |
| 142 | # in the child |
| 143 | # give up the lock you cvs thang! |
| 144 | time.sleep(2) |
| 145 | fp = os.popen(mailcmd, 'w') |
| 146 | fp.write(sys.stdin.read()) |
| 147 | fp.write('\n') |
| 148 | # append the diffs if available |
| 149 | for file in filestodiff: |
| 150 | fp.write(calculate_diff(file, contextlines)) |
| 151 | fp.write('\n') |
| 152 | fp.close() |
| 153 | # doesn't matter what code we return, it isn't waited on |
| 154 | os._exit(0) |
| 155 | |
| 156 | |
| 157 | |
| 158 | # scan args for options |
| 159 | def main(): |
| 160 | contextlines = 2 |
| 161 | try: |
Björn Stenberg | 6ed3280 | 2004-05-07 12:32:23 +0000 | [diff] [blame] | 162 | opts, args = getopt.getopt(sys.argv[1:], 'hC:cuU:', |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 163 | ['context=', 'cvsroot=', 'help']) |
| 164 | except getopt.error, msg: |
| 165 | usage(1, msg) |
| 166 | |
Björn Stenberg | 1e0cb1f | 2004-05-07 12:37:08 +0000 | [diff] [blame] | 167 | username = 'unknown' |
Björn Stenberg | 512d51c | 2004-05-07 12:26:14 +0000 | [diff] [blame] | 168 | |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 169 | # parse the options |
| 170 | for opt, arg in opts: |
| 171 | if opt in ('-h', '--help'): |
| 172 | usage(0) |
| 173 | elif opt == '--cvsroot': |
| 174 | os.environ['CVSROOT'] = arg |
| 175 | elif opt in ('-C', '--context'): |
| 176 | contextlines = int(arg) |
| 177 | elif opt == '-c': |
| 178 | if contextlines <= 0: |
| 179 | contextlines = 2 |
| 180 | elif opt == '-u': |
| 181 | contextlines = 0 |
Björn Stenberg | 59fb25d | 2004-05-07 12:22:06 +0000 | [diff] [blame] | 182 | elif opt == '-U': |
| 183 | username = arg |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 184 | |
| 185 | # What follows is the specification containing the files that were |
| 186 | # modified. The argument actually must be split, with the first component |
| 187 | # containing the directory the checkin is being made in, relative to |
| 188 | # $CVSROOT, followed by the list of files that are changing. |
| 189 | if not args: |
| 190 | usage(1, 'No CVS module specified') |
| 191 | SUBJECT = args[0] |
| 192 | specs = string.split(args[0]) |
| 193 | del args[0] |
| 194 | |
| 195 | # The remaining args should be the email addresses |
| 196 | if not args: |
| 197 | usage(1, 'No recipients specified') |
| 198 | |
| 199 | # Now do the mail command |
| 200 | PEOPLE = string.join(args) |
| 201 | mailcmd = MAILCMD % vars() |
| 202 | |
| 203 | print 'Mailing %s...' % PEOPLE |
| 204 | if specs == ['-', 'Imported', 'sources']: |
| 205 | return |
| 206 | if specs[-3:] == ['-', 'New', 'directory']: |
| 207 | del specs[-3:] |
Björn Stenberg | 1e0cb1f | 2004-05-07 12:37:08 +0000 | [diff] [blame] | 208 | print 'Generating notification message...' |
| 209 | blast_mail(mailcmd, specs[1:], contextlines) |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 210 | print 'Generating notification message... done.' |
| 211 | |
| 212 | |
Björn Stenberg | 0375f31 | 2002-04-11 08:49:16 +0000 | [diff] [blame] | 213 | |
| 214 | if __name__ == '__main__': |
| 215 | main() |
| 216 | sys.exit(0) |
| 217 | |