Commit | Line | Data |
f0630c2d |
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 | |
29 | Usage: |
30 | |
31 | %(PROGRAM)s [options] <%%S> email-addr [email-addr ...] |
32 | |
33 | Where options is: |
34 | |
35 | --cvsroot=<path> |
36 | Use <path> as the environment variable CVSROOT. Otherwise this |
37 | variable must exist in the environment. |
38 | |
39 | --help |
40 | -h |
41 | Print this text. |
42 | |
43 | <%%S> |
44 | CVS %%s loginfo expansion. When invoked by CVS, this will be a single |
45 | string containing the directory the checkin is being made in, relative |
46 | to $CVSROOT, followed by the list of files that are changing. If the |
47 | %%s in the loginfo file is %%{sVv}, context diffs for each of the |
48 | modified files are included in any email messages that are generated. |
49 | |
50 | email-addrs |
51 | At least one email address. |
52 | |
53 | """ |
54 | |
55 | import os |
56 | import sys |
57 | import string |
58 | import time |
59 | import getopt |
60 | |
61 | # Notification command |
62 | MAILCMD = '/bin/mail -s "CVS: %(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null' |
63 | |
64 | # Diff trimming stuff |
65 | DIFF_HEAD_LINES = 20 |
66 | DIFF_TAIL_LINES = 20 |
67 | DIFF_TRUNCATE_IF_LARGER = 1000 |
68 | |
69 | PROGRAM = sys.argv[0] |
70 | |
71 | |
72 | \f |
73 | def usage(code, msg=''): |
74 | print __doc__ % globals() |
75 | if msg: |
76 | print msg |
77 | sys.exit(code) |
78 | |
79 | |
80 | \f |
81 | def calculate_diff(filespec): |
82 | try: |
83 | file, oldrev, newrev = string.split(filespec, ',') |
84 | except ValueError: |
85 | # No diff to report |
86 | return '***** Bogus filespec: %s' % filespec |
87 | if oldrev == 'NONE': |
88 | try: |
89 | if os.path.exists(file): |
90 | fp = open(file) |
91 | else: |
92 | update_cmd = 'cvs -fn update -r %s -p %s' % (newrev, file) |
93 | fp = os.popen(update_cmd) |
94 | lines = fp.readlines() |
95 | fp.close() |
96 | lines.insert(0, '--- NEW FILE ---\n') |
97 | except IOError, e: |
98 | lines = ['***** Error reading new file: ', |
99 | str(e), '\n***** file: ', file, ' cwd: ', os.getcwd()] |
100 | elif newrev == 'NONE': |
101 | lines = ['--- %s DELETED ---\n' % file] |
102 | else: |
103 | # This /has/ to happen in the background, otherwise we'll run into CVS |
104 | # lock contention. What a crock. |
105 | diffcmd = '/usr/bin/cvs -f diff -kk -C 2 -w -r %s -r %s %s' % ( |
106 | oldrev, newrev, file) |
107 | fp = os.popen(diffcmd) |
108 | lines = fp.readlines() |
109 | sts = fp.close() |
110 | # ignore the error code, it always seems to be 1 :( |
111 | ## if sts: |
112 | ## return 'Error code %d occurred during diff\n' % (sts >> 8) |
113 | if len(lines) > DIFF_TRUNCATE_IF_LARGER: |
114 | removedlines = len(lines) - DIFF_HEAD_LINES - DIFF_TAIL_LINES |
115 | del lines[DIFF_HEAD_LINES:-DIFF_TAIL_LINES] |
116 | lines.insert(DIFF_HEAD_LINES, |
117 | '[...%d lines suppressed...]\n' % removedlines) |
118 | return string.join(lines, '') |
119 | |
120 | |
121 | \f |
122 | def blast_mail(mailcmd, filestodiff): |
123 | # cannot wait for child process or that will cause parent to retain cvs |
124 | # lock for too long. Urg! |
125 | if not os.fork(): |
126 | # in the child |
127 | # give up the lock you cvs thang! |
128 | time.sleep(2) |
129 | fp = os.popen(mailcmd, 'w') |
130 | fp.write(sys.stdin.read()) |
131 | fp.write('\n') |
132 | # append the diffs if available |
133 | for file in filestodiff: |
134 | fp.write(calculate_diff(file)) |
135 | fp.write('\n') |
136 | fp.close() |
137 | # doesn't matter what code we return, it isn't waited on |
138 | os._exit(0) |
139 | |
140 | |
141 | \f |
142 | # scan args for options |
143 | def main(): |
144 | try: |
145 | opts, args = getopt.getopt(sys.argv[1:], 'h', ['cvsroot=', 'help']) |
146 | except getopt.error, msg: |
147 | usage(1, msg) |
148 | |
149 | # parse the options |
150 | for opt, arg in opts: |
151 | if opt in ('-h', '--help'): |
152 | usage(0) |
153 | elif opt == '--cvsroot': |
154 | os.environ['CVSROOT'] = arg |
155 | |
156 | # What follows is the specification containing the files that were |
157 | # modified. The argument actually must be split, with the first component |
158 | # containing the directory the checkin is being made in, relative to |
159 | # $CVSROOT, followed by the list of files that are changing. |
160 | if not args: |
161 | usage(1, 'No CVS module specified') |
162 | SUBJECT = args[0] |
163 | specs = string.split(args[0]) |
164 | del args[0] |
165 | |
166 | # The remaining args should be the email addresses |
167 | if not args: |
168 | usage(1, 'No recipients specified') |
169 | |
170 | # Now do the mail command |
171 | PEOPLE = string.join(args) |
172 | mailcmd = MAILCMD % vars() |
173 | |
174 | print 'Mailing %s...' % PEOPLE |
175 | if specs == ['-', 'Imported', 'sources']: |
176 | return |
177 | if specs[-3:] == ['-', 'New', 'directory']: |
178 | del specs[-3:] |
179 | print 'Generating notification message...' |
180 | blast_mail(mailcmd, specs[1:]) |
181 | print 'Generating notification message... done.' |
182 | |
183 | |
184 | \f |
185 | if __name__ == '__main__': |
186 | main() |
187 | sys.exit(0) |