1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
|
#!/usr/bin/env python
# ex:ts=4:sw=4:sts=4:et
# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
#
# CIA bot client script for Bitkeeper repositories, written in python.
# This generates commit messages using CIA's XML commit format, and can
# deliver them using either XML-RPC or email.
#
# -- Micah Dowty <micah@navi.cx>
#
# This script is cleaner, more featureful, and faster than the shell
# script version, but won't work on systems without Python or that don't
# allow outgoing HTTP connections.
#
# To use the CIA bot in your Bitkeeper repository...
#
# 1. Customize the parameters below
#
# 2. This script should be called from your repository's post-commit
# hook with the repository and revision as arguments. For example,
# you could copy this script into your repository's "hooks" directory
# and add something like the following to the "post-commit" script,
# also in the repository's "hooks" directory:
#
# REPOS="$1"
# REV="$2"
# $REPOS/hooks/ciabot_bk.py "$REPOS" "$REV" &
#
# Or, if you have multiple project hosted, you can add each
# project's name to the commandline in that project's post-commit
# hook:
#
# $REPOS/hooks/ciabot_bk.py "$REPOS" "$REV" "My Project" &
#
############# There are some parameters for this script that you can customize:
class config:
# Replace this with your project's name, or always provide a
# project name on the commandline.
project = "openembedded"
# If your repository is accessable over the web, put its base URL here
# and 'uri' attributes will be given to all <file> elements. This means
# that in CIA's online message viewer, each file in the tree will link
# directly to the file in your repository
repositoryURI = None
# This can be the http:// URI of the CIA server to deliver commits over
# XML-RPC, or it can be an email address to deliver using SMTP. The
# default here should work for most people. If you need to use e-mail
# instead, you can replace this with "cia@cia.navi.cx"
server = "http://cia.navi.cx"
# The SMTP server to use, only used if the CIA server above is an
# email address
smtpServer = "localhost"
# The 'from' address to use. If you're delivering commits via email, set
# this to the address you would normally send email from on this host.
fromAddress = "cia-user@localhost"
# When nonzero, print the message to stdout instead of delivering it to CIA
debug = 0
############# Normally the rest of this won't need modification
import sys, os, re, urllib, xmlrpclib
class UrllibTransport(xmlrpclib.Transport):
'''Handles an HTTP transaction to an XML-RPC server via urllib
(urllib includes proxy-server support)
jjk 07/02/99'''
def __init__(self):
self.verbose = 0
def request(self, host, handler, request_body, verbose=0):
'''issue XML-RPC request
jjk 07/02/99'''
import urllib
urlopener = urllib.FancyURLopener()
urlopener.addheaders = [('User-agent', self.user_agent)]
# probably should use appropriate 'join' methods instead of 'http://'+host+handler
f = urlopener.open('http://'+host+handler, request_body)
return(self.parse_response(f))
class File:
"""A file in a Bitkeeper repository. According to our current
configuration, this may have a module, branch, and URI in addition
to a path."""
def __init__(self, fullPath):
self.fullPath = fullPath
self.path = fullPath
def getURI(self, repo):
"""Get the URI of this file, given the repository's URI. This
encodes the full path and joins it to the given URI."""
quotedPath = urllib.quote(self.fullPath)
if quotedPath[0] == '/':
quotedPath = quotedPath[1:]
if repo[-1] != '/':
repo = repo + '/'
return repo + quotedPath
def makeTag(self, config):
"""Return an XML tag for this file, using the given config"""
attrs = {}
if config.repositoryURI is not None:
attrs['uri'] = self.getURI(config.repositoryURI)
attrString = ''.join([' %s="%s"' % (key, escapeToXml(value,1))
for key, value in attrs.iteritems()])
return "<file%s>%s</file>" % (attrString, escapeToXml(self.path))
class CIAClient:
"""Base CIA client class"""
name = 'Python client for CIA'
version = '1.0'
def __init__(self, repository, revision, config):
self.repository = repository
self.revision = revision
self.config = config
def deliver(self, message):
if config.debug:
print message
else:
server = self.config.server
if server.startswith('http:') or server.startswith('https:'):
# Deliver over XML-RPC
proxy = os.environ.get('http_proxy')
if proxy:
os.environ['HTTP_PROXY'] = proxy
s = xmlrpclib.ServerProxy(server, UrllibTransport())
else:
s = xmlrpclib.ServerProxy(server)
s.hub.deliver(message)
else:
# Deliver over email
import smtplib
smtp = smtplib.SMTP(self.config.smtpServer)
smtp.sendmail(self.config.fromAddress, server,
"From: %s\r\nTo: %s\r\n"
"Subject: DeliverXML\r\n\r\n%s" %
(self.config.fromAddress, server, message))
def main(self):
self.collectData()
import socket
try:
self.deliver("<message>" +
self.makeGeneratorTag() +
self.makeSourceTag() +
self.makeBodyTag() +
"</message>")
return 0
except socket.error, e:
print "ERROR: socket: %s" % e
return 1
def makeAttrTags(self, *names):
"""Given zero or more attribute names, generate XML elements for
those attributes only if they exist and are non-None.
"""
s = ''
for name in names:
if hasattr(self, name):
v = getattr(self, name)
if v is not None:
s += "<%s>%s</%s>" % (name, escapeToXml(str(v)), name)
return s
def makeGeneratorTag(self):
return "<generator>%s</generator>" % self.makeAttrTags(
'name',
'version',
)
def makeSourceTag(self):
self.project = self.config.project
return "<source>%s</source>" % self.makeAttrTags(
'project',
'module',
'branch',
)
def makeBodyTag(self):
return "<body><commit>%s%s</commit></body>" % (
self.makeAttrTags(
'revision',
'author',
'log',
'diffLines',
),
self.makeFileTags(),
)
def makeFileTags(self):
"""Return XML tags for our file list"""
return "<files>%s</files>" % ''.join([file.makeTag(self.config)
for file in self.files])
def collectData(self):
raise NotImplementedError("collectData method not implemented in the base CIA client class.")
def escapeToXml(text, isAttrib=0):
text = text.replace("&", "&")
text = text.replace("<", "<")
text = text.replace(">", ">")
if isAttrib == 1:
text = text.replace("'", "'")
text = text.replace("\"", """)
return text
class BKClient(CIAClient):
"""A CIA client for Bitkeeper repositories."""
name = 'Python Bitkeeper client for CIA'
version = '1.0'
def __init__(self, repository, revision, config):
CIAClient.__init__(self, repository, revision, config)
os.chdir(self.repository)
def bkchanges(self, command):
"""Run the given bkchanges command on our current repository and
revision, returning all output"""
return os.popen('bk changes %s -r"%s"' % \
(command, self.revision)).read()
def collectData(self):
self.author = self.bkchanges('-d\':P:\'').strip()
self.log = self.bkchanges('-d\'$if(:C:){$each(:C:){:C: \\\\n}}\'').strip()
self.diffLines = len(os.popen('bk export -tpatch -r"%s"|grep -v \'^#\'' % self.revision).read().split('\n'))
self.files = self.collectFiles()
self.module = os.path.basename(os.environ.get('BKD_ROOT') or '')
self.branch = self.bkchanges('-d\':TAG:\'')
def collectFiles(self):
# Extract all the files from the output of 'bkchanges changed'
lines = []
for l in self.bkchanges('-n -v -d\'$unless(:GFILE:=ChangeSet){:GFILE:}\'').strip().split('\n'):
if not l in lines:
lines.append(l)
return [ File(line) for line in lines ]
if __name__ == "__main__":
# Print a usage message when not enough parameters are provided.
if len(sys.argv) < 3:
sys.stderr.write("USAGE: %s REPOS-PATH REVISION [PROJECTNAME]\n" %
sys.argv[0])
sys.exit(1)
# If a project name was provided, override the default project name.
if len(sys.argv) > 3:
config.project = sys.argv[3]
# Go do the real work.
BKClient(sys.argv[1], sys.argv[2], config).main()
|