Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(594)

Side by Side Diff: third_party/buildbot_7_12/buildbot/changes/changes.py

Issue 12207158: Bye bye buildbot 0.7.12. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 import sys, os, time
2 from cPickle import dump
3
4 from zope.interface import implements
5 from twisted.python import log
6 from twisted.internet import defer
7 from twisted.application import service
8 from twisted.web import html
9
10 from buildbot import interfaces, util
11 from buildbot.process.properties import Properties
12
13 html_tmpl = """
14 <p>Changed by: <b>%(who)s</b><br />
15 Changed at: <b>%(at)s</b><br />
16 %(repository)s
17 %(branch)s
18 %(revision)s
19 <br />
20
21 Changed files:
22 %(files)s
23
24 Comments:
25 %(comments)s
26
27 Properties:
28 %(properties)s
29 </p>
30 """
31
32 class Change:
33 """I represent a single change to the source tree. This may involve
34 several files, but they are all changed by the same person, and there is
35 a change comment for the group as a whole.
36
37 If the version control system supports sequential repository- (or
38 branch-) wide change numbers (like SVN, P4, and Arch), then revision=
39 should be set to that number. The highest such number will be used at
40 checkout time to get the correct set of files.
41
42 If it does not (like CVS), when= should be set to the timestamp (seconds
43 since epoch, as returned by time.time()) when the change was made. when=
44 will be filled in for you (to the current time) if you omit it, which is
45 suitable for ChangeSources which have no way of getting more accurate
46 timestamps.
47
48 Changes should be submitted to ChangeMaster.addChange() in
49 chronologically increasing order. Out-of-order changes will probably
50 cause the html.Waterfall display to be corrupted."""
51
52 implements(interfaces.IStatusEvent)
53
54 number = None
55
56 branch = None
57 category = None
58 revision = None # used to create a source-stamp
59 repository = None # optional repository
60
61 def __init__(self, who, files, comments, isdir=0, links=None,
62 revision=None, when=None, branch=None, category=None,
63 repository='', revlink='', properties={}):
64 self.who = who
65 self.comments = comments
66 self.isdir = isdir
67 if links is None:
68 links = []
69 self.links = links
70 self.revision = revision
71 if when is None:
72 when = util.now()
73 self.when = when
74 self.branch = branch
75 self.category = category
76 self.repository = repository
77 self.revlink = revlink
78 self.properties = Properties()
79 self.properties.update(properties, "Change")
80
81 # keep a sorted list of the files, for easier display
82 self.files = files[:]
83 self.files.sort()
84
85 def __setstate__(self, dict):
86 self.__dict__ = dict
87 # Older Changes won't have a 'properties' attribute in them
88 if not hasattr(self, 'properties'):
89 self.properties = Properties()
90
91 def asText(self):
92 data = ""
93 data += self.getFileContents()
94 data += "At: %s\n" % self.getTime()
95 data += "Changed By: %s\n" % self.who
96 data += "Comments: %s" % self.comments
97 data += "Properties: \n%s\n\n" % self.getProperties()
98 return data
99
100 def asHTML(self):
101 links = []
102 for file in self.files:
103 link = filter(lambda s: s.find(file) != -1, self.links)
104 if len(link) == 1:
105 # could get confused
106 links.append('<a href="%s"><b>%s</b></a>' % (link[0], file))
107 else:
108 links.append('<b>%s</b>' % file)
109 if self.revision:
110 if getattr(self, 'revlink', ""):
111 revision = 'Revision: <a href="%s"><b>%s</b></a>\n' % (
112 self.revlink, self.revision)
113 else:
114 revision = "Revision: <b>%s</b><br />\n" % self.revision
115 else:
116 revision = ''
117
118 if self.repository:
119 repository = "Repository: <b>%s</b><br />\n" % self.repository
120 else:
121 repository = ''
122
123 branch = ""
124 if self.branch:
125 branch = "Branch: <b>%s</b><br />\n" % self.branch
126
127 properties = []
128 for prop in self.properties.asList():
129 properties.append("%s: %s<br />" % (prop[0], prop[1]))
130
131 kwargs = { 'who' : html.escape(self.who),
132 'at' : self.getTime(),
133 'files' : html.UL(links) + '\n',
134 'repository': repository,
135 'revision' : revision,
136 'branch' : branch,
137 'comments' : html.PRE(self.comments),
138 'properties': html.UL(properties) + '\n' }
139 return html_tmpl % kwargs
140
141 def get_HTML_box(self, url):
142 """Return the contents of a TD cell for the waterfall display.
143
144 @param url: the URL that points to an HTML page that will render
145 using our asHTML method. The Change is free to use this or ignore it
146 as it pleases.
147
148 @return: the HTML that will be put inside the table cell. Typically
149 this is just a single href named after the author of the change and
150 pointing at the passed-in 'url'.
151 """
152 who = self.getShortAuthor()
153 if self.comments is None:
154 title = ""
155 else:
156 title = html.escape(self.comments)
157 return '<a href="%s" title="%s">%s</a>' % (url,
158 title,
159 html.escape(who))
160
161 def getShortAuthor(self):
162 return self.who
163
164 def getTime(self):
165 if not self.when:
166 return "?"
167 return time.strftime("%a %d %b %Y %H:%M:%S",
168 time.localtime(self.when))
169
170 def getTimes(self):
171 return (self.when, None)
172
173 def getText(self):
174 return [html.escape(self.who)]
175 def getLogs(self):
176 return {}
177
178 def getFileContents(self):
179 data = ""
180 if len(self.files) == 1:
181 if self.isdir:
182 data += "Directory: %s\n" % self.files[0]
183 else:
184 data += "File: %s\n" % self.files[0]
185 else:
186 data += "Files:\n"
187 for f in self.files:
188 data += " %s\n" % f
189 return data
190
191 def getProperties(self):
192 data = ""
193 for prop in self.properties.asList():
194 data += " %s: %s" % (prop[0], prop[1])
195 return data
196
197 def asDict(self):
198 result = {}
199 # Constant
200 result['number'] = self.number
201 result['branch'] = self.branch
202 result['category'] = self.category
203 result['who'] = self.getShortAuthor()
204 result['comments'] = self.comments
205 result['revision'] = self.revision
206 result['repository'] = self.repository
207 result['when'] = self.when
208 result['files'] = self.files
209 result['revlink'] = self.revlink
210 result['properties'] = self.properties.asList()
211 return result
212
213
214 class ChangeMaster(service.MultiService):
215
216 """This is the master-side service which receives file change
217 notifications from CVS. It keeps a log of these changes, enough to
218 provide for the HTML waterfall display, and to tell
219 temporarily-disconnected bots what they missed while they were
220 offline.
221
222 Change notifications come from two different kinds of sources. The first
223 is a PB service (servicename='changemaster', perspectivename='change'),
224 which provides a remote method called 'addChange', which should be
225 called with a dict that has keys 'filename' and 'comments'.
226
227 The second is a list of objects derived from the ChangeSource class.
228 These are added with .addSource(), which also sets the .changemaster
229 attribute in the source to point at the ChangeMaster. When the
230 application begins, these will be started with .start() . At shutdown
231 time, they will be terminated with .stop() . They must be persistable.
232 They are expected to call self.changemaster.addChange() with Change
233 objects.
234
235 There are several different variants of the second type of source:
236
237 - L{buildbot.changes.mail.MaildirSource} watches a maildir for CVS
238 commit mail. It uses DNotify if available, or polls every 10
239 seconds if not. It parses incoming mail to determine what files
240 were changed.
241
242 - L{buildbot.changes.freshcvs.FreshCVSSource} makes a PB
243 connection to the CVSToys 'freshcvs' daemon and relays any
244 changes it announces.
245
246 """
247
248 implements(interfaces.IEventSource)
249
250 debug = False
251 # todo: use Maildir class to watch for changes arriving by mail
252
253 changeHorizon = 0
254
255 def __init__(self):
256 service.MultiService.__init__(self)
257 self.changes = []
258 # self.basedir must be filled in by the parent
259 self.nextNumber = 1
260
261 def addSource(self, source):
262 assert interfaces.IChangeSource.providedBy(source)
263 assert service.IService.providedBy(source)
264 if self.debug:
265 print "ChangeMaster.addSource", source
266 source.setServiceParent(self)
267
268 def removeSource(self, source):
269 assert source in self
270 if self.debug:
271 print "ChangeMaster.removeSource", source, source.parent
272 d = defer.maybeDeferred(source.disownServiceParent)
273 return d
274
275 def addChange(self, change):
276 """Deliver a file change event. The event should be a Change object.
277 This method will timestamp the object as it is received."""
278 log.msg("adding change, who %s, %d files, rev=%s, branch=%s, "
279 "comments %s, category %s" % (change.who, len(change.files),
280 change.revision, change.branch,
281 change.comments, change.category))
282 change.number = self.nextNumber
283 self.nextNumber += 1
284 self.changes.append(change)
285 self.parent.addChange(change)
286 self.pruneChanges()
287
288 def pruneChanges(self):
289 if self.changeHorizon and len(self.changes) > self.changeHorizon:
290 log.msg("pruning %i changes" % (len(self.changes) - self.changeHoriz on))
291 self.changes = self.changes[-self.changeHorizon:]
292
293 def eventGenerator(self, branches=[], categories=[], committers=[], minTime= 0):
294 for i in range(len(self.changes)-1, -1, -1):
295 c = self.changes[i]
296 if (c.when < minTime):
297 break
298 if (not branches or c.branch in branches) and (
299 not categories or c.category in categories) and (
300 not committers or c.who in committers):
301 yield c
302
303 def getChangeNumbered(self, num):
304 if not self.changes:
305 return None
306 first = self.changes[0].number
307 if first + len(self.changes)-1 != self.changes[-1].number:
308 log.msg(self,
309 "lost a change somewhere: [0] is %d, [%d] is %d" % \
310 (self.changes[0].number,
311 len(self.changes) - 1,
312 self.changes[-1].number))
313 for c in self.changes:
314 log.msg("c[%d]: " % c.number, c)
315 return None
316 offset = num - first
317 log.msg(self, "offset", offset)
318 if 0 <= offset <= len(self.changes):
319 return self.changes[offset]
320 else:
321 return None
322
323 def __getstate__(self):
324 d = service.MultiService.__getstate__(self)
325 del d['parent']
326 del d['services'] # lose all children
327 del d['namedServices']
328 return d
329
330 def __setstate__(self, d):
331 self.__dict__ = d
332 # self.basedir must be set by the parent
333 self.services = [] # they'll be repopulated by readConfig
334 self.namedServices = {}
335
336
337 def saveYourself(self):
338 filename = os.path.join(self.basedir, "changes.pck")
339 tmpfilename = filename + ".tmp"
340 try:
341 dump(self, open(tmpfilename, "wb"))
342 if sys.platform == 'win32':
343 # windows cannot rename a file on top of an existing one
344 if os.path.exists(filename):
345 os.unlink(filename)
346 os.rename(tmpfilename, filename)
347 except Exception, e:
348 log.msg("unable to save changes")
349 log.err()
350
351 def stopService(self):
352 self.saveYourself()
353 return service.MultiService.stopService(self)
354
355 class TestChangeMaster(ChangeMaster):
356 """A ChangeMaster for use in tests that does not save itself"""
357 def stopService(self):
358 return service.MultiService.stopService(self)
359
360 # vim: set ts=4 sts=4 sw=4 et:
OLDNEW
« no previous file with comments | « third_party/buildbot_7_12/buildbot/changes/bonsaipoller.py ('k') | third_party/buildbot_7_12/buildbot/changes/dnotify.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698