]>
Commit | Line | Data |
---|---|---|
9137135a MT |
1 | #!/usr/bin/python |
2 | ||
3 | import datetime | |
4 | import logging | |
5 | import os | |
13b9276e MT |
6 | import pakfire |
7 | import pakfire.config | |
82626427 | 8 | import re |
13b9276e | 9 | import shutil |
9137135a | 10 | import subprocess |
13b9276e | 11 | import tempfile |
9137135a | 12 | |
2c909128 | 13 | from . import base |
7ad79344 | 14 | from . import git |
9137135a | 15 | |
29e226fc | 16 | from .constants import * |
78366294 MT |
17 | from .decorators import * |
18 | ||
82626427 MT |
19 | VALID_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 | 30 | class 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 |
160 | class 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 |
338 | class 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") |