Skip to content

Commit 3bf54c1

Browse files
committed
Add googlecode_atom.py required by buildbot.
1 parent 4307d56 commit 3bf54c1

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

extras/buildbot/googlecode_atom.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# GoogleCode Atom Feed Poller
2+
# Author: Srivats P. <pstavirs>
3+
# Based on Mozilla's HgPoller
4+
# http://bonsai.mozilla.org/cvsblame.cgi?file=/mozilla/tools/buildbot/buildbot/changes/Attic/hgpoller.py&revision=1.1.4.2
5+
#
6+
# Description:
7+
# Use this ChangeSource for projects hosted on http://code.google.com/
8+
#
9+
# This ChangeSource uses the project's commit Atom feed. Depending upon the
10+
# frequency of commits, you can tune the polling interval for the feed
11+
# (default is 1 hour)
12+
#
13+
# Parameters:
14+
# feedurl (MANDATORY): The Atom feed URL of the GoogleCode repo
15+
# pollinterval (OPTIONAL): Polling frequency for the feed (in seconds)
16+
#
17+
# Example:
18+
# To poll the Ostinato project's commit feed every 3 hours, use -
19+
# from googlecode_atom import GoogleCodeAtomPoller
20+
# poller = GoogleCodeAtomPoller(
21+
# feedurl="http://code.google.com/feeds/p/ostinato/hgchanges/basic",
22+
# pollinterval=10800)
23+
# c['change_source'] = [ poller ]
24+
#
25+
26+
from time import strptime
27+
from calendar import timegm
28+
from xml.dom import minidom, Node
29+
30+
from twisted.python import log, failure
31+
from twisted.internet import defer, reactor
32+
from twisted.internet.task import LoopingCall
33+
from twisted.web.client import getPage
34+
35+
from buildbot.changes import base, changes
36+
37+
def googleCodePollerForProject(project, vcs, pollinterval=3600):
38+
return GoogleCodeAtomPoller(
39+
'http://code.google.com/feeds/p/%s/%schanges/basic' % (project, vcs),
40+
pollinterval=pollinterval)
41+
42+
43+
class GoogleCodeAtomPoller(base.ChangeSource):
44+
"""This source will poll a GoogleCode Atom feed for changes and
45+
submit them to the change master. Works for both Svn and Hg repos.
46+
TODO: branch processing
47+
"""
48+
49+
compare_attrs = ['feedurl', 'pollinterval']
50+
parent = None
51+
loop = None
52+
volatile = ['loop']
53+
working = False
54+
55+
def __init__(self, feedurl, pollinterval=3600):
56+
"""
57+
@type feedurl: string
58+
@param feedurl: The Atom feed URL of the GoogleCode repo
59+
(e.g. http://code.google.com/feeds/p/ostinato/hgchanges/basic)
60+
61+
@type pollinterval: int
62+
@param pollinterval: The time (in seconds) between queries for
63+
changes (default is 1 hour)
64+
"""
65+
66+
self.feedurl = feedurl
67+
self.branch = None
68+
self.pollinterval = pollinterval
69+
self.lastChange = None
70+
self.loop = LoopingCall(self.poll)
71+
72+
def startService(self):
73+
log.msg("GoogleCodeAtomPoller starting")
74+
base.ChangeSource.startService(self)
75+
reactor.callLater(0, self.loop.start, self.pollinterval)
76+
77+
def stopService(self):
78+
log.msg("GoogleCodeAtomPoller stoppping")
79+
self.loop.stop()
80+
return base.ChangeSource.stopService(self)
81+
82+
def describe(self):
83+
return ("Getting changes from the GoogleCode repo changes feed %s" %
84+
self._make_url())
85+
86+
def poll(self):
87+
if self.working:
88+
log.msg("Not polling because last poll is still working")
89+
else:
90+
self.working = True
91+
d = self._get_changes()
92+
d.addCallback(self._process_changes)
93+
d.addCallbacks(self._finished_ok, self._finished_failure)
94+
95+
def _finished_ok(self, res):
96+
assert self.working
97+
self.working = False
98+
log.msg("GoogleCodeAtomPoller poll success")
99+
100+
return res
101+
102+
def _finished_failure(self, res):
103+
log.msg("GoogleCodeAtomPoller poll failed: %s" % res)
104+
assert self.working
105+
self.working = False
106+
return None
107+
108+
def _make_url(self):
109+
return "%s" % (self.feedurl)
110+
111+
def _get_changes(self):
112+
url = self._make_url()
113+
log.msg("GoogleCodeAtomPoller polling %s" % url)
114+
115+
return getPage(url, timeout=self.pollinterval)
116+
117+
def _parse_changes(self, query):
118+
dom = minidom.parseString(query)
119+
entries = dom.getElementsByTagName("entry")
120+
changes = []
121+
# Entries come in reverse chronological order
122+
for i in entries:
123+
d = {}
124+
125+
# revision is the last part of the 'id' url
126+
d["revision"] = i.getElementsByTagName(
127+
"id")[0].firstChild.data.split('/')[-1]
128+
if d["revision"] == self.lastChange:
129+
break # no more new changes
130+
131+
d["when"] = timegm(strptime(
132+
i.getElementsByTagName("updated")[0].firstChild.data,
133+
"%Y-%m-%dT%H:%M:%SZ"))
134+
d["author"] = i.getElementsByTagName(
135+
"author")[0].getElementsByTagName("name")[0].firstChild.data
136+
# files and commit msg are separated by 2 consecutive <br/>
137+
content = i.getElementsByTagName(
138+
"content")[0].firstChild.data.split("<br/>\n <br/>")
139+
# Remove the action keywords from the file list
140+
fl = content[0].replace(
141+
u' \xa0\xa0\xa0\xa0Add\xa0\xa0\xa0\xa0', '').replace(
142+
u' \xa0\xa0\xa0\xa0Delete\xa0\xa0\xa0\xa0', '').replace(
143+
u' \xa0\xa0\xa0\xa0Modify\xa0\xa0\xa0\xa0', '')
144+
# Get individual files and remove the 'header'
145+
d["files"] = fl.encode("ascii", "replace").split("<br/>")[1:]
146+
d["files"] = [f.strip() for f in d["files"]]
147+
try:
148+
d["comments"] = content[1].encode("ascii", "replace")
149+
except:
150+
d["comments"] = "No commit message provided"
151+
152+
changes.append(d)
153+
154+
changes.reverse() # want them in chronological order
155+
return changes
156+
157+
def _process_changes(self, query):
158+
change_list = self._parse_changes(query)
159+
160+
# Skip calling addChange() if this is the first successful poll.
161+
if self.lastChange is not None:
162+
for change in change_list:
163+
c = changes.Change(revision = change["revision"],
164+
who = change["author"],
165+
files = change["files"],
166+
comments = change["comments"],
167+
when = change["when"],
168+
branch = self.branch)
169+
self.parent.addChange(c)
170+
if change_list:
171+
self.lastChange = change_list[-1]["revision"]

0 commit comments

Comments
 (0)