]> git.ipfire.org Git - pbs.git/blame - src/buildservice/sources.py
jobqueue: Fix creating test jobs
[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():
2093d691 136 self.backend.builds.create_from_source_package(pkg,
e153d3f6 137 source.distro, commit=commit, type="release")
29e226fc 138
99fc0667
MT
139 except:
140 if commit:
141 commit.state = "failed"
29e226fc 142
99fc0667 143 raise
29e226fc 144
99fc0667
MT
145 finally:
146 if os.path.exists(pkg_dir):
147 shutil.rmtree(pkg_dir)
29e226fc 148
99fc0667
MT
149 for file in deleted_files:
150 # Determine the name of the package.
151 name = os.path.basename(file)
152 name = name[:len(MAKEFILE_EXTENSION) + 1]
29e226fc 153
99fc0667 154 source.distro.delete_package(name)
29e226fc 155
99fc0667
MT
156 if commit:
157 commit.state = "finished"
13b9276e 158
f6e6ff79 159
78366294
MT
160class Commit(base.DataObject):
161 table = "sources_commits"
9137135a 162
f6e6ff79
MT
163 @property
164 def revision(self):
165 return self.data.revision
166
78366294 167 @lazy_property
f6e6ff79 168 def source(self):
78366294 169 return self.backend.sources.get_by_id(self.data.source_id)
f6e6ff79
MT
170
171 @property
172 def distro(self):
173 """
174 A shortcut to the distribution this commit
175 belongs to.
176 """
177 return self.source.distro
9137135a 178
f6e6ff79 179 def set_state(self, state):
78366294 180 self._set_attribute("state", state)
9137135a 181
78366294 182 state = property(lambda s: s.data.state, set_state)
9137135a 183
040fc249 184 @lazy_property
f6e6ff79 185 def author(self):
040fc249 186 return self.backend.users.find_maintainer(self.data.author) or self.data.author
f6e6ff79 187
040fc249 188 @lazy_property
f6e6ff79 189 def committer(self):
040fc249 190 return self.backend.users.find_maintainer(self.data.committer) or self.data.committer
f6e6ff79
MT
191
192 @property
193 def subject(self):
b9d096e0 194 return self.data.subject.strip()
f6e6ff79
MT
195
196 @property
82626427 197 def body(self):
f6e6ff79
MT
198 return self.data.body.strip()
199
82626427
MT
200 @lazy_property
201 def message(self):
202 """
203 Returns the message without any Git tags
204 """
205 # Compile regex
82e2ba51 206 r = re.compile("^(%s):?" % "|".join(VALID_TAGS), re.IGNORECASE)
82626427
MT
207
208 message = []
209 for line in self.body.splitlines():
210 # Find lines that start with a known Git tag
211 if r.match(line):
212 continue
213
214 message.append(line)
215
3cf4f987
MT
216 # If all lines are empty lines, we send back an empty message
217 if all((l == "" for l in message)):
218 return
219
f78cbba4
MT
220 # We will now break the message into paragraphs
221 paragraphs = re.split("\n\n+", "\n".join(message))
222 print paragraphs
223
224 message = []
225 for paragraph in paragraphs:
226 # Remove all line breaks that are not following a colon
227 # and where the next line does not start with a star.
228 paragraph = re.sub("(?<=\:)\n(?=[\*\s])", " ", paragraph)
229
230 message.append(paragraph)
231
232 return "\n\n".join(message)
233
4b1e87c4
MT
234 @property
235 def message_full(self):
81f71982 236 message = self.subject
4b1e87c4 237
81f71982
MT
238 if self.message:
239 message += "\n\n%s" % self.message
240
241 return message
4b1e87c4 242
82626427
MT
243 def get_tag(self, tag):
244 """
245 Returns a list of the values of this Git tag
246 """
247 if not tag in VALID_TAGS:
248 raise ValueError("Unknown tag: %s" % tag)
249
250 # Compile regex
82e2ba51 251 r = re.compile("^%s:? (.*)$" % tag, re.IGNORECASE)
82626427
MT
252
253 values = []
254 for line in self.body.splitlines():
255 # Skip all empty lines
256 if not line:
257 continue
258
259 # Check if line matches the regex
260 m = r.match(line)
261 if m:
262 values.append(m.group(1))
263
264 return values
265
040fc249
MT
266 @lazy_property
267 def contributors(self):
268 contributors = [
269 self.data.author,
270 self.data.committer,
271 ]
272
273 for tag in ("Acked-by", "Cc", "Reported-by", "Reviewed-by", "Signed-off-by", "Suggested-by", "Tested-by"):
274 contributors += self.get_tag(tag)
275
276 # Get all user accounts that we know
277 users = self.backend.users.find_maintainers(contributors)
278
279 # Add all email addresses where a user could not be found
280 for contributor in contributors[:]:
281 for user in users:
282 if user.has_email_address(contributor):
283 try:
284 contributors.remove(contributor)
285 except:
286 pass
287
288 return sorted(contributors + users)
289
a0e30c39
MT
290 @lazy_property
291 def testers(self):
292 users = []
293
294 for tag in ("Acked-by", "Reviewed-by", "Signed-off-by", "Tested-by"):
295 users += self.get_tag(tag)
296
297 return self.backend.users.find_maintainers(users)
298
82e2ba51
MT
299 @property
300 def fixed_bugs(self):
301 """
302 Returns a list of all fixed bugs
303 """
304 return self.get_tag("Fixes")
305
f6e6ff79
MT
306 @property
307 def date(self):
308 return self.data.date
309
78366294 310 @lazy_property
f6e6ff79 311 def packages(self):
78366294
MT
312 return self.backend.packages._get_packages("SELECT * FROM packages \
313 WHERE commit_id = %s", self.id)
f6e6ff79
MT
314
315 def reset(self):
9137135a 316 """
f6e6ff79
MT
317 Removes all packages that have been created by this commit and
318 resets the state so it will be processed again.
9137135a 319 """
f6e6ff79
MT
320 # Remove all packages and corresponding builds.
321 for pkg in self.packages:
322 # Check if there is a build associated with the package.
323 # If so, the whole build will be deleted.
324 if pkg.build:
325 pkg.build.delete()
9137135a 326
f6e6ff79
MT
327 else:
328 # Delete the package.
329 pkg.delete()
330
331 # Clear the cache.
78366294 332 del self.packages
9137135a 333
f6e6ff79
MT
334 # Reset the state to 'pending'.
335 self.state = "pending"
9137135a 336
9137135a 337
78366294
MT
338class Source(base.DataObject):
339 table = "sources"
f6e6ff79 340
78366294
MT
341 def __eq__(self, other):
342 return self.id == other.id
f6e6ff79 343
78366294
MT
344 def __len__(self):
345 ret = self.db.get("SELECT COUNT(*) AS len FROM sources_commits \
346 WHERE source_id = %s", self.id)
f6e6ff79 347
78366294 348 return ret.len
9137135a 349
78366294
MT
350 def create_commit(self, revision, author, committer, subject, body, date):
351 commit = self.backend.sources._get_commit("INSERT INTO sources_commits(source_id, \
86d8598e
MT
352 revision, author, committer, subject, body, date) VALUES(%s, %s, %s, %s, %s, %s, %s) \
353 RETURNING *", self.id, revision, author, committer, subject, body, date)
9137135a 354
78366294
MT
355 # Commit
356 commit.source = self
9137135a 357
78366294 358 return commit
9137135a 359
f6e6ff79
MT
360 @property
361 def info(self):
362 return {
363 "id" : self.id,
364 "name" : self.name,
365 "url" : self.url,
366 "path" : self.path,
367 "targetpath" : self.targetpath,
368 "revision" : self.revision,
369 "branch" : self.branch,
370 }
9137135a 371
9137135a
MT
372 @property
373 def name(self):
f6e6ff79 374 return self.data.name
9137135a
MT
375
376 @property
f6e6ff79
MT
377 def identifier(self):
378 return self.data.identifier
9137135a
MT
379
380 @property
f6e6ff79
MT
381 def url(self):
382 return self.data.url
9137135a
MT
383
384 @property
f6e6ff79
MT
385 def gitweb(self):
386 return self.data.gitweb
9137135a
MT
387
388 @property
389 def revision(self):
f6e6ff79 390 return self.data.revision
9137135a
MT
391
392 @property
393 def branch(self):
f6e6ff79 394 return self.data.branch
9137135a
MT
395
396 @property
397 def builds(self):
398 return self.pakfire.builds.get_by_source(self.id)
399
78366294 400 @lazy_property
9137135a 401 def distro(self):
f6e6ff79
MT
402 return self.pakfire.distros.get_by_id(self.data.distro_id)
403
404 @property
405 def start_revision(self):
406 return self.data.revision
407
78366294 408 @lazy_property
f6e6ff79 409 def head_revision(self):
a7d966df 410 return self.backend.sources._get_commit("SELECT * FROM sources_commits \
78366294 411 WHERE source_id = %s ORDER BY id DESC LIMIT 1", self.id)
f6e6ff79
MT
412
413 def get_commits(self, limit=None, offset=None):
a7d966df 414 return self.backend.sources._get_commits("SELECT * FROM sources_commits \
78366294 415 WHERE source_id = %s ORDER BY id DESC LIMIT %s OFFSET %s", limit, offset)
f6e6ff79
MT
416
417 def get_commit(self, revision):
a7d966df 418 commit = self.backend.sources._get_commit("SELECT * FROM sources_commits \
78366294 419 WHERE source_id = %s AND revision = %s", self.id, revision)
f6e6ff79 420
78366294
MT
421 if commit:
422 commit.source = self
423 return commit
99fc0667
MT
424
425 @property
426 def pending_commits(self):
427 return self.backend.sources._get_commits("SELECT * FROM sources_commits \
428 WHERE state = %s ORDER BY imported_at", "pending")