Strip trailing directory slash
[kugel-rb.git] / CVSROOT / syncmail
blobfa24a8052a3aae0e725c6f9b7b0347e833c12e9e
1 #! /usr/bin/python
2 # -*- Python -*-
4 """Complicated notification for CVS checkins.
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.
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:
13 mymodule /path/to/this/script %%s some-email-addr@your.domain
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.
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.
25 It no longer makes sense to run this script from the command line. Doing so
26 will only print out this usage information.
28 Usage:
30 %(PROGRAM)s [options] <%%S> email-addr [email-addr ...]
32 Where options is:
34 --cvsroot=<path>
35 Use <path> as the environment variable CVSROOT. Otherwise this
36 variable must exist in the environment.
38 --help
40 Print this text.
42 --context=#
43 -C #
44 Include # lines of context around lines that differ (default: 2).
47 Produce a context diff (default).
50 Produce a unified diff (smaller, but harder to read).
52 -U user
53 Mark diff as made by user.
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.
62 email-addrs
63 At least one email address.
65 """
67 import os
68 import sys
69 import string
70 import time
71 import getopt
73 # Notification command
74 MAILCMD = '/bin/mail -s "%(username)s: %(SUBJECT)s" %(PEOPLE)s 2>&1 > /dev/null'
76 # Diff trimming stuff
77 DIFF_HEAD_LINES = 20
78 DIFF_TAIL_LINES = 20
79 DIFF_TRUNCATE_IF_LARGER = 1000
81 PROGRAM = sys.argv[0]
85 def usage(code, msg=''):
86 print __doc__ % globals()
87 if msg:
88 print msg
89 sys.exit(code)
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"
121 diffcmd = '/usr/bin/cvs -f diff -kk %s -b -r %s -r %s %s' % (
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, '')
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)
158 # scan args for options
159 def main():
160 contextlines = 2
161 try:
162 opts, args = getopt.getopt(sys.argv[1:], 'hC:cuU:',
163 ['context=', 'cvsroot=', 'help'])
164 except getopt.error, msg:
165 usage(1, msg)
167 username = 'unknown'
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
182 elif opt == '-U':
183 username = arg
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]
195 # The remaining args should be the email addresses
196 if not args:
197 usage(1, 'No recipients specified')
199 # Now do the mail command
200 PEOPLE = string.join(args)
201 mailcmd = MAILCMD % vars()
203 print 'Mailing %s...' % PEOPLE
204 if specs == ['-', 'Imported', 'sources']:
205 return
206 if specs[-3:] == ['-', 'New', 'directory']:
207 del specs[-3:]
208 print 'Generating notification message...'
209 blast_mail(mailcmd, specs[1:], contextlines)
210 print 'Generating notification message... done.'
214 if __name__ == '__main__':
215 main()
216 sys.exit(0)