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.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 Arch), 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 if when is None:
61 when = util.now()
62 self.when = when
63 self.branch = none_or_unicode(branch)
64 self.category = none_or_unicode(category)
65 self.revlink = revlink
66 self.properties = Properties()
67 self.properties.update(properties, "Change")
68 self.repository = repository
69 self.project = project
70
71
72 self.files = files[:]
73 self.files.sort()
74
76 self.__dict__ = dict
77
78 if not hasattr(self, 'properties'):
79 self.properties = Properties()
80 if not hasattr(self, 'revlink'):
81 self.revlink = ""
82
84 data = ""
85 data += self.getFileContents()
86 if self.repository:
87 data += "On: %s\n" % self.repository
88 if self.project:
89 data += "For: %s\n" % self.project
90 data += "At: %s\n" % self.getTime()
91 data += "Changed By: %s\n" % self.who
92 data += "Comments: %s" % self.comments
93 data += "Properties: \n%s\n\n" % self.getProperties()
94 return data
95
97 '''returns a dictonary with suitable info for html/mail rendering'''
98 result = {}
99
100 files = []
101 for file in self.files:
102 link = filter(lambda s: s.find(file) != -1, self.links)
103 if len(link) == 1:
104 url = link[0]
105 else:
106 url = None
107 files.append(dict(url=url, name=file))
108
109 files = sorted(files, cmp=lambda a,b: a['name'] < b['name'])
110
111
112 result['number'] = self.number
113 result['branch'] = self.branch
114 result['category'] = self.category
115 result['who'] = self.getShortAuthor()
116 result['comments'] = self.comments
117 result['revision'] = self.revision
118 result['rev'] = self.revision
119 result['when'] = self.when
120 result['at'] = self.getTime()
121 result['files'] = files
122 result['revlink'] = getattr(self, 'revlink', None)
123 result['properties'] = self.properties.asList()
124 result['repository'] = getattr(self, 'repository', None)
125 result['project'] = getattr(self, 'project', None)
126 return result
127
130
132 if not self.when:
133 return "?"
134 return time.strftime("%a %d %b %Y %H:%M:%S",
135 time.localtime(self.when))
136
138 return (self.when, None)
139
141 return [html.escape(self.who)]
144
145 - def getFileContents(self):
146 data = ""
147 if len(self.files) == 1:
148 if self.isdir:
149 data += "Directory: %s\n" % self.files[0]
150 else:
151 data += "File: %s\n" % self.files[0]
152 else:
153 data += "Files:\n"
154 for f in self.files:
155 data += " %s\n" % f
156 return data
157
159 data = ""
160 for prop in self.properties.asList():
161 data += " %s: %s" % (prop[0], prop[1])
162 return data
163
164
166
167
168
169
170
171
173 self.changes = []
174
175 self.nextNumber = 1
176
178 change.number = self.nextNumber
179 self.nextNumber += 1
180 self.changes.append(change)
181
183 filename = os.path.join(self.basedir, "changes.pck")
184 tmpfilename = filename + ".tmp"
185 try:
186 dump(self, open(tmpfilename, "wb"))
187 if sys.platform == 'win32':
188
189 if os.path.exists(filename):
190 os.unlink(filename)
191 os.rename(tmpfilename, filename)
192 except Exception:
193 log.msg("unable to save changes")
194 log.err()
195
196
197
199 """Processes the list of changes, with the change attributes re-encoded
200 as UTF-8 bytestrings"""
201 nconvert = 0
202 for c in self.changes:
203
204 if isinstance(c.revision, int):
205 c.revision = unicode(c.revision)
206
207 for attr in ("who", "comments", "revlink", "category", "branch", "revision"):
208 a = getattr(c, attr)
209 if isinstance(a, str):
210 try:
211 setattr(c, attr, a.decode(old_encoding))
212 nconvert += 1
213 except UnicodeDecodeError:
214 raise UnicodeError("Error decoding %s of change #%s as %s:\n%r" %
215 (attr, c.number, old_encoding, a))
216 if not quiet: print "converted %d strings" % nconvert
217
221
222