]>
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(): |
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 |
164 | class 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 |
335 | class 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") |