]> git.ipfire.org Git - pbs.git/blame - src/buildservice/sources.py
Upvote builds when it has testers
[pbs.git] / src / buildservice / sources.py
CommitLineData
9137135a
MT
1#!/usr/bin/python
2
3import datetime
4import logging
5import os
13b9276e
MT
6import pakfire
7import pakfire.config
82626427 8import re
13b9276e 9import shutil
9137135a 10import subprocess
13b9276e 11import tempfile
9137135a 12
2c909128 13from . import base
7ad79344 14from . import git
9137135a 15
29e226fc 16from .constants import *
78366294
MT
17from .decorators import *
18
82626427
MT
19VALID_TAGS = (
20 "Acked-by",
21 "Cc",
22 "Fixes",
23 "Reported-by",
24 "Reviewed-by",
25 "Signed-off-by",
26 "Suggested-by",
27 "Tested-by",
28)
29
9137135a 30class Sources(base.Object):
78366294
MT
31 def _get_source(self, query, *args):
32 res = self.db.get(query, *args)
33
34 if res:
35 return Source(self.backend, res.id, data=res)
36
37 def _get_sources(self, query, *args):
38 res = self.db.query(query, *args)
39
40 for row in res:
41 yield Source(self.backend, row.id, data=row)
42
43 def _get_commit(self, query, *args):
44 res = self.db.get(query, *args)
45
46 if res:
47 return Commit(self.backend, res.id, data=res)
48
49 def _get_commits(self, query, *args):
50 res = self.db.query(query, *args)
9137135a 51
78366294
MT
52 for row in res:
53 yield Commit(self.backend, row.id, data=row)
54
55 def __iter__(self):
56 return self._get_sources("SELECT * FROM sources")
9137135a
MT
57
58 def get_by_id(self, id):
78366294
MT
59 return self._get_source("SELECT * FROM sources \
60 WHERE id = %s", id)
9137135a
MT
61
62 def get_by_distro(self, distro):
78366294
MT
63 return self._get_sources("SELECT * FROM sources \
64 WHERE distro_id = %s", distro.id)
9137135a
MT
65
66 def update_revision(self, source_id, revision):
67 query = "UPDATE sources SET revision = %s WHERE id = %s"
68
69 return self.db.execute(query, revision, source_id)
70
f6e6ff79
MT
71 def get_commit_by_id(self, commit_id):
72 commit = self.db.get("SELECT id FROM sources_commits WHERE id = %s", commit_id)
73
74 if commit:
75 return Commit(self.pakfire, commit.id)
76
7ad79344 77 def pull(self):
78366294 78 for source in self:
29e226fc
MT
79 with git.Repo(self.backend, source, mode="mirror") as repo:
80 # Fetch the latest updates
7ad79344
MT
81 repo.fetch()
82
29e226fc
MT
83 # Import all new revisions
84 repo.import_revisions()
7ad79344 85
13b9276e 86 def dist(self):
99fc0667
MT
87 # Walk through all source repositories
88 for source in self:
89 # Get access to the git repo
90 with git.Repo(self.pakfire, source) as repo:
91 # Walk through all pending commits
92 for commit in source.pending_commits:
93 commit.state = "running"
13b9276e 94
99fc0667 95 logging.debug("Processing commit %s: %s" % (commit.revision, commit.subject))
13b9276e 96
99fc0667
MT
97 # Navigate to the right revision.
98 repo.checkout(commit.revision)
29e226fc 99
99fc0667
MT
100 # Get all changed makefiles.
101 deleted_files = []
102 updated_files = []
29e226fc 103
99fc0667
MT
104 for file in repo.changed_files(commit.revision):
105 # Don't care about files that are not a makefile.
106 if not file.endswith(".%s" % MAKEFILE_EXTENSION):
107 continue
29e226fc 108
99fc0667
MT
109 if os.path.exists(file):
110 updated_files.append(file)
111 else:
112 deleted_files.append(file)
29e226fc 113
99fc0667
MT
114 if updated_files:
115 # Create a temporary directory where to put all the files
116 # that are generated here.
117 pkg_dir = tempfile.mkdtemp()
29e226fc 118
99fc0667
MT
119 try:
120 config = pakfire.config.Config(["general.conf",])
121 config.parse(source.distro.get_config())
29e226fc 122
99fc0667 123 p = pakfire.PakfireServer(config=config)
29e226fc 124
99fc0667
MT
125 pkgs = []
126 for file in updated_files:
127 try:
128 pkg_file = p.dist(file, pkg_dir)
129 pkgs.append(pkg_file)
130 except:
131 raise
29e226fc 132
99fc0667
MT
133 # Import all packages in one swoop.
134 for pkg in pkgs:
e153d3f6 135 with self.db.transaction():
a0e30c39 136 build = self.backend.builds.create_from_source_package(pkg,
e153d3f6 137 source.distro, commit=commit, type="release")
29e226fc 138
a0e30c39
MT
139 # Import any testers from the commit message
140 for tester in commit.testers:
141 build.upvote(tester)
142
99fc0667
MT
143 except:
144 if commit:
145 commit.state = "failed"
29e226fc 146
99fc0667 147 raise
29e226fc 148
99fc0667
MT
149 finally:
150 if os.path.exists(pkg_dir):
151 shutil.rmtree(pkg_dir)
29e226fc 152
99fc0667
MT
153 for file in deleted_files:
154 # Determine the name of the package.
155 name = os.path.basename(file)
156 name = name[:len(MAKEFILE_EXTENSION) + 1]
29e226fc 157
99fc0667 158 source.distro.delete_package(name)
29e226fc 159
99fc0667
MT
160 if commit:
161 commit.state = "finished"
13b9276e 162
f6e6ff79 163
78366294
MT
164class Commit(base.DataObject):
165 table = "sources_commits"
9137135a 166
f6e6ff79
MT
167 @property
168 def revision(self):
169 return self.data.revision
170
78366294 171 @lazy_property
f6e6ff79 172 def source(self):
78366294 173 return self.backend.sources.get_by_id(self.data.source_id)
f6e6ff79
MT
174
175 @property
176 def distro(self):
177 """
178 A shortcut to the distribution this commit
179 belongs to.
180 """
181 return self.source.distro
9137135a 182
f6e6ff79 183 def set_state(self, state):
78366294 184 self._set_attribute("state", state)
9137135a 185
78366294 186 state = property(lambda s: s.data.state, set_state)
9137135a 187
040fc249 188 @lazy_property
f6e6ff79 189 def author(self):
040fc249 190 return self.backend.users.find_maintainer(self.data.author) or self.data.author
f6e6ff79 191
040fc249 192 @lazy_property
f6e6ff79 193 def committer(self):
040fc249 194 return self.backend.users.find_maintainer(self.data.committer) or self.data.committer
f6e6ff79
MT
195
196 @property
197 def subject(self):
b9d096e0 198 return self.data.subject.strip()
f6e6ff79
MT
199
200 @property
82626427 201 def body(self):
f6e6ff79
MT
202 return self.data.body.strip()
203
82626427
MT
204 @lazy_property
205 def message(self):
206 """
207 Returns the message without any Git tags
208 """
209 # Compile regex
210 r = re.compile("^(%s):" % "|".join(VALID_TAGS), re.IGNORECASE)
211
212 message = []
213 for line in self.body.splitlines():
214 # Find lines that start with a known Git tag
215 if r.match(line):
216 continue
217
218 message.append(line)
219
3cf4f987
MT
220 # If all lines are empty lines, we send back an empty message
221 if all((l == "" for l in message)):
222 return
223
f78cbba4
MT
224 # We will now break the message into paragraphs
225 paragraphs = re.split("\n\n+", "\n".join(message))
226 print paragraphs
227
228 message = []
229 for paragraph in paragraphs:
230 # Remove all line breaks that are not following a colon
231 # and where the next line does not start with a star.
232 paragraph = re.sub("(?<=\:)\n(?=[\*\s])", " ", paragraph)
233
234 message.append(paragraph)
235
236 return "\n\n".join(message)
237
4b1e87c4
MT
238 @property
239 def message_full(self):
81f71982 240 message = self.subject
4b1e87c4 241
81f71982
MT
242 if self.message:
243 message += "\n\n%s" % self.message
244
245 return message
4b1e87c4 246
82626427
MT
247 def get_tag(self, tag):
248 """
249 Returns a list of the values of this Git tag
250 """
251 if not tag in VALID_TAGS:
252 raise ValueError("Unknown tag: %s" % tag)
253
254 # Compile regex
255 r = re.compile("^%s: (.*)$" % tag, re.IGNORECASE)
256
257 values = []
258 for line in self.body.splitlines():
259 # Skip all empty lines
260 if not line:
261 continue
262
263 # Check if line matches the regex
264 m = r.match(line)
265 if m:
266 values.append(m.group(1))
267
268 return values
269
040fc249
MT
270 @lazy_property
271 def contributors(self):
272 contributors = [
273 self.data.author,
274 self.data.committer,
275 ]
276
277 for tag in ("Acked-by", "Cc", "Reported-by", "Reviewed-by", "Signed-off-by", "Suggested-by", "Tested-by"):
278 contributors += self.get_tag(tag)
279
280 # Get all user accounts that we know
281 users = self.backend.users.find_maintainers(contributors)
282
283 # Add all email addresses where a user could not be found
284 for contributor in contributors[:]:
285 for user in users:
286 if user.has_email_address(contributor):
287 try:
288 contributors.remove(contributor)
289 except:
290 pass
291
292 return sorted(contributors + users)
293
a0e30c39
MT
294 @lazy_property
295 def testers(self):
296 users = []
297
298 for tag in ("Acked-by", "Reviewed-by", "Signed-off-by", "Tested-by"):
299 users += self.get_tag(tag)
300
301 return self.backend.users.find_maintainers(users)
302
f6e6ff79
MT
303 @property
304 def date(self):
305 return self.data.date
306
78366294 307 @lazy_property
f6e6ff79 308 def packages(self):
78366294
MT
309 return self.backend.packages._get_packages("SELECT * FROM packages \
310 WHERE commit_id = %s", self.id)
f6e6ff79
MT
311
312 def reset(self):
9137135a 313 """
f6e6ff79
MT
314 Removes all packages that have been created by this commit and
315 resets the state so it will be processed again.
9137135a 316 """
f6e6ff79
MT
317 # Remove all packages and corresponding builds.
318 for pkg in self.packages:
319 # Check if there is a build associated with the package.
320 # If so, the whole build will be deleted.
321 if pkg.build:
322 pkg.build.delete()
9137135a 323
f6e6ff79
MT
324 else:
325 # Delete the package.
326 pkg.delete()
327
328 # Clear the cache.
78366294 329 del self.packages
9137135a 330
f6e6ff79
MT
331 # Reset the state to 'pending'.
332 self.state = "pending"
9137135a 333
9137135a 334
78366294
MT
335class Source(base.DataObject):
336 table = "sources"
f6e6ff79 337
78366294
MT
338 def __eq__(self, other):
339 return self.id == other.id
f6e6ff79 340
78366294
MT
341 def __len__(self):
342 ret = self.db.get("SELECT COUNT(*) AS len FROM sources_commits \
343 WHERE source_id = %s", self.id)
f6e6ff79 344
78366294 345 return ret.len
9137135a 346
78366294
MT
347 def create_commit(self, revision, author, committer, subject, body, date):
348 commit = self.backend.sources._get_commit("INSERT INTO sources_commits(source_id, \
86d8598e
MT
349 revision, author, committer, subject, body, date) VALUES(%s, %s, %s, %s, %s, %s, %s) \
350 RETURNING *", self.id, revision, author, committer, subject, body, date)
9137135a 351
78366294
MT
352 # Commit
353 commit.source = self
9137135a 354
78366294 355 return commit
9137135a 356
f6e6ff79
MT
357 @property
358 def info(self):
359 return {
360 "id" : self.id,
361 "name" : self.name,
362 "url" : self.url,
363 "path" : self.path,
364 "targetpath" : self.targetpath,
365 "revision" : self.revision,
366 "branch" : self.branch,
367 }
9137135a 368
9137135a
MT
369 @property
370 def name(self):
f6e6ff79 371 return self.data.name
9137135a
MT
372
373 @property
f6e6ff79
MT
374 def identifier(self):
375 return self.data.identifier
9137135a
MT
376
377 @property
f6e6ff79
MT
378 def url(self):
379 return self.data.url
9137135a
MT
380
381 @property
f6e6ff79
MT
382 def gitweb(self):
383 return self.data.gitweb
9137135a
MT
384
385 @property
386 def revision(self):
f6e6ff79 387 return self.data.revision
9137135a
MT
388
389 @property
390 def branch(self):
f6e6ff79 391 return self.data.branch
9137135a
MT
392
393 @property
394 def builds(self):
395 return self.pakfire.builds.get_by_source(self.id)
396
78366294 397 @lazy_property
9137135a 398 def distro(self):
f6e6ff79
MT
399 return self.pakfire.distros.get_by_id(self.data.distro_id)
400
401 @property
402 def start_revision(self):
403 return self.data.revision
404
78366294 405 @lazy_property
f6e6ff79 406 def head_revision(self):
a7d966df 407 return self.backend.sources._get_commit("SELECT * FROM sources_commits \
78366294 408 WHERE source_id = %s ORDER BY id DESC LIMIT 1", self.id)
f6e6ff79
MT
409
410 def get_commits(self, limit=None, offset=None):
a7d966df 411 return self.backend.sources._get_commits("SELECT * FROM sources_commits \
78366294 412 WHERE source_id = %s ORDER BY id DESC LIMIT %s OFFSET %s", limit, offset)
f6e6ff79
MT
413
414 def get_commit(self, revision):
a7d966df 415 commit = self.backend.sources._get_commit("SELECT * FROM sources_commits \
78366294 416 WHERE source_id = %s AND revision = %s", self.id, revision)
f6e6ff79 417
78366294
MT
418 if commit:
419 commit.source = self
420 return commit
99fc0667
MT
421
422 @property
423 def pending_commits(self):
424 return self.backend.sources._get_commits("SELECT * FROM sources_commits \
425 WHERE state = %s ORDER BY imported_at", "pending")