1 import sys, os, time
2 from cPickle import dump
3
4 from zope.interface import implements
5 from twisted.python import log, runtime
6 from twisted.web import html
7
8 from buildbot import interfaces, util
9 from buildbot.process.properties import Properties
10
12 """I represent a single change to the source tree. This may involve
13 several files, but they are all changed by the same person, and there is
14 a change comment for the group as a whole.
15
16 If the version control system supports sequential repository- (or
17 branch-) wide change numbers (like SVN, P4, and Bzr), then revision=
18 should be set to that number. The highest such number will be used at
19 checkout time to get the correct set of files.
20
21 If it does not (like CVS), when= should be set to the timestamp (seconds
22 since epoch, as returned by time.time()) when the change was made. when=
23 will be filled in for you (to the current time) if you omit it, which is
24 suitable for ChangeSources which have no way of getting more accurate
25 timestamps.
26
27 The revision= and branch= values must be ASCII bytestrings, since they
28 will eventually be used in a ShellCommand and passed to os.exec(), which
29 requires bytestrings. These values will also be stored in a database,
30 possibly as unicode, so they must be safely convertable back and forth.
31 This restriction may be relaxed in the future.
32
33 Changes should be submitted to ChangeMaster.addChange() in
34 chronologically increasing order. Out-of-order changes will probably
35 cause the web status displays to be corrupted."""
36
37 implements(interfaces.IStatusEvent)
38
39 number = None
40
41 branch = None
42 category = None
43 revision = None
44
45 - def __init__(self, who, files, comments, isdir=0, links=None,
46 revision=None, when=None, branch=None, category=None,
47 revlink='', properties={}, repository='', project=''):
48 self.who = who
49 self.comments = comments
50 self.isdir = isdir
51 if links is None:
52 links = []
53 self.links = links
54
55 def none_or_unicode(x):
56 if x is None: return x
57 return unicode(x)
58
59 self.revision = none_or_unicode(revision)
60 now = util.now()
61 if when is None:
62 self.when = now
63 elif when > now:
64
65
66 log.msg("received a Change with when > now; assuming the change happened now")
67 self.when = now
68 else:
69 self.when = when
70 self.branch = none_or_unicode(branch)
71 self.category = none_or_unicode(category)
72 self.revlink = revlink
73 self.properties = Properties()
74 self.properties.update(properties, "Change")
75 self.repository = repository
76 self.project = project
77
78
79 self.files = files[:]
80 self.files.sort()
81
83 self.__dict__ = dict
84
85 if not hasattr(self, 'properties'):
86 self.properties = Properties()
87 if not hasattr(self, 'revlink'):
88 self.revlink = ""
89
91 data = ""
92 data += self.getFileContents()
93 if self.repository:
94 data += "On: %s\n" % self.repository
95 if self.project:
96 data += "For: %s\n" % self.project
97 data += "At: %s\n" % self.getTime()
98 data += "Changed By: %s\n" % self.who
99 data += "Comments: %s" % self.comments
100 data += "Properties: \n%s\n\n" % self.getProperties()
101 return data
102
104 '''returns a dictonary with suitable info for html/mail rendering'''
105 result = {}
106
107 files = []
108 for file in self.files:
109 link = filter(lambda s: s.find(file) != -1, self.links)
110 if len(link) == 1:
111 url = link[0]
112 else:
113 url = None
114 files.append(dict(url=url, name=file))
115
116 files = sorted(files, cmp=lambda a, b: a['name'] < b['name'])
117
118
119 result['number'] = self.number
120 result['branch'] = self.branch
121 result['category'] = self.category
122 result['who'] = self.getShortAuthor()
123 result['comments'] = self.comments
124 result['revision'] = self.revision
125 result['rev'] = self.revision
126 result['when'] = self.when
127 result['at'] = self.getTime()
128 result['files'] = files
129 result['revlink'] = getattr(self, 'revlink', None)
130 result['properties'] = self.properties.asList()
131 result['repository'] = getattr(self, 'repository', None)
132 result['project'] = getattr(self, 'project', None)
133 return result
134
137
139 if not self.when:
140 return "?"
141 return time.strftime("%a %d %b %Y %H:%M:%S",
142 time.localtime(self.when))
143
145 return (self.when, None)
146
148 return [html.escape(self.who)]
151
152 - def getFileContents(self):
153 data = ""
154 if len(self.files) == 1:
155 if self.isdir:
156 data += "Directory: %s\n" % self.files[0]
157 else:
158 data += "File: %s\n" % self.files[0]
159 else:
160 data += "Files:\n"
161 for f in self.files:
162 data += " %s\n" % f
163 return data
164
166 data = ""
167 for prop in self.properties.asList():
168 data += " %s: %s" % (prop[0], prop[1])
169 return data
170
171
173
174
175
176
177
178
180 self.changes = []
181
182 self.nextNumber = 1
183
185 change.number = self.nextNumber
186 self.nextNumber += 1
187 self.changes.append(change)
188
190 filename = os.path.join(self.basedir, "changes.pck")
191 tmpfilename = filename + ".tmp"
192 try:
193 dump(self, open(tmpfilename, "wb"))
194 if runtime.platformType == 'win32':
195
196 if os.path.exists(filename):
197 os.unlink(filename)
198 os.rename(tmpfilename, filename)
199 except Exception:
200 log.msg("unable to save changes")
201 log.err()
202
203
204
206 """Processes the list of changes, with the change attributes re-encoded
207 as UTF-8 bytestrings"""
208 nconvert = 0
209 for c in self.changes:
210
211 if isinstance(c.revision, int):
212 c.revision = unicode(c.revision)
213
214 for attr in ("who", "comments", "revlink", "category", "branch", "revision"):
215 a = getattr(c, attr)
216 if isinstance(a, str):
217 try:
218 setattr(c, attr, a.decode(old_encoding))
219 nconvert += 1
220 except UnicodeDecodeError:
221 raise UnicodeError("Error decoding %s of change #%s as %s:\n%r" %
222 (attr, c.number, old_encoding, a))
223 if not quiet:
224 print "converted %d strings" % nconvert
225
229
230