]>
git.ipfire.org Git - people/jschlag/pbs.git/blob - src/buildservice/builds.py
8 import pakfire
.packages
15 log
= logging
.getLogger("builds")
18 from .constants
import *
19 from .decorators
import *
21 class Builds(base
.Object
):
22 def _get_build(self
, query
, *args
):
23 res
= self
.db
.get(query
, *args
)
26 return Build(self
.backend
, res
.id, data
=res
)
28 def _get_builds(self
, query
, *args
):
29 res
= self
.db
.query(query
, *args
)
32 yield Build(self
.backend
, row
.id, data
=row
)
34 def get_by_id(self
, id, data
=None):
35 return Build(self
.backend
, id, data
=data
)
37 def get_by_uuid(self
, uuid
):
38 build
= self
.db
.get("SELECT id FROM builds WHERE uuid = %s LIMIT 1", uuid
)
41 return self
.get_by_id(build
.id)
43 def get_all(self
, limit
=50):
44 query
= "SELECT * FROM builds ORDER BY time_created DESC"
47 query
+= " LIMIT %d" % limit
49 return [self
.get_by_id(b
.id, b
) for b
in self
.db
.query(query
)]
51 def get_by_user(self
, user
, type=None, public
=None):
55 if not type or type == "scratch":
56 # On scratch builds the user id equals the owner id.
57 conditions
.append("(builds.type = 'scratch' AND owner_id = %s)")
60 elif not type or type == "release":
64 conditions
.append("public = 'Y'")
66 conditions
.append("public = 'N'")
68 query
= "SELECT builds.* AS id FROM builds \
69 JOIN packages ON builds.pkg_id = packages.id"
72 query
+= " WHERE %s" % " AND ".join(conditions
)
74 query
+= " ORDER BY builds.time_created DESC"
77 for build
in self
.db
.query(query
, *args
):
78 build
= Build(self
.backend
, build
.id, build
)
83 def get_by_name(self
, name
, type=None, public
=None, user
=None, limit
=None, offset
=None):
90 conditions
.append("builds.type = %s")
95 or_conditions
.append("public = 'Y'")
97 or_conditions
.append("public = 'N'")
99 if user
and not user
.is_admin():
100 or_conditions
.append("builds.owner_id = %s")
103 query
= "SELECT builds.* AS id FROM builds \
104 JOIN packages ON builds.pkg_id = packages.id"
107 conditions
.append(" OR ".join(or_conditions
))
110 query
+= " WHERE %s" % " AND ".join(conditions
)
112 if type == "release":
113 query
+= " ORDER BY packages.name,packages.epoch,packages.version,packages.release,id ASC"
114 elif type == "scratch":
115 query
+= " ORDER BY time_created DESC"
119 query
+= " LIMIT %s,%s"
120 args
.extend([offset
, limit
])
125 return [Build(self
.backend
, b
.id, b
) for b
in self
.db
.query(query
, *args
)]
127 def get_latest_by_name(self
, name
, type=None, public
=None):
129 SELECT * FROM builds \
130 LEFT JOIN builds_latest ON builds.id = builds_latest.build_id \
131 WHERE builds_latest.package_name = %s"
135 query
+= " AND builds_latest.build_type = %s"
139 query
+= " AND builds.public = %s"
141 elif public
is False:
142 query
+= " AND builds.public = %s"
145 # Get the last one only.
146 # Prefer release builds over scratch builds.
149 CASE builds.type WHEN 'release' THEN 0 ELSE 1 END, \
150 builds.time_created DESC \
153 res
= self
.db
.get(query
, *args
)
156 return Build(self
.backend
, res
.id, res
)
158 def get_active_builds(self
, name
, public
=None):
160 SELECT * FROM builds \
161 LEFT JOIN builds_latest ON builds.id = builds_latest.build_id \
162 WHERE builds_latest.package_name = %s AND builds.type = %s"
163 args
= [name
, "release"]
166 query
+= " AND builds.public = %s"
168 elif public
is False:
169 query
+= " AND builds.public = %s"
173 for row
in self
.db
.query(query
, *args
):
174 b
= Build(self
.backend
, row
.id, row
)
177 # Sort the result. Lastest build first.
178 builds
.sort(reverse
=True)
183 builds
= self
.db
.get("SELECT COUNT(*) AS count FROM builds")
187 def get_obsolete(self
, repo
=None):
189 Get all obsoleted builds.
191 If repo is True: which are in any repository.
192 If repo is some Repository object: which are in this repository.
197 query
= "SELECT id FROM builds WHERE state = 'obsolete'"
200 query
= "SELECT build_id AS id FROM repositories_builds \
201 JOIN builds ON builds.id = repositories_builds.build_id \
202 WHERE builds.state = 'obsolete'"
204 if repo
and not repo
is True:
205 query
+= " AND repositories_builds.repo_id = %s"
208 res
= self
.db
.query(query
, *args
)
212 build
= Build(self
.backend
, build
.id)
217 def create(self
, pkg
, type="release", owner
=None, distro
=None):
218 assert type in ("release", "scratch", "test")
219 assert distro
, "You need to specify the distribution of this build."
221 # Check if scratch build has an owner.
222 if type == "scratch" and not owner
:
223 raise Exception, "Scratch builds require an owner"
225 # Set the default priority of this build.
226 if type == "release":
229 elif type == "scratch":
235 # Create build in database
236 build
= self
._get
_build
("INSERT INTO builds(uuid, pkg_id, type, distro_id, priority) \
237 VALUES(%s, %s, %s, %s, %s) RETURNING *", "%s" % uuid
.uuid4(), pkg
.id, type, distro
.id, priority
)
239 # Set the owner of this build
243 # Log that the build has been created.
244 build
.log("created", user
=owner
)
246 # Create directory where the files live
247 if not os
.path
.exists(build
.path
):
248 os
.makedirs(build
.path
)
250 # Move package file to the directory of the build.
251 build
.pkg
.move(os
.path
.join(build
.path
, "src"))
253 # Generate an update id.
254 build
.generate_update_id()
256 # Obsolete all other builds with the same name to track updates.
257 build
.obsolete_others()
259 # Search for possible bug IDs in the commit message.
260 build
.search_for_bugs()
264 def create_from_source_package(self
, filename
, distro
, commit
=None, type="release",
265 arches
=None, check_for_duplicates
=True, owner
=None):
268 # Open the package file to read some basic information.
269 pkg
= pakfire
.packages
.open(None, None, filename
)
271 if check_for_duplicates
:
272 if distro
.has_package(pkg
.name
, pkg
.epoch
, pkg
.version
, pkg
.release
):
273 log
.warning("Duplicate package detected: %s. Skipping." % pkg
)
276 # Open the package and add it to the database
277 pkg
= self
.backend
.packages
.create(filename
)
279 # Associate the package to the processed commit
283 # Create a new build object from the package
284 build
= self
.create(pkg
, type=type, owner
=owner
, distro
=distro
)
286 # Create all automatic jobs
287 build
.create_autojobs(arches
=arches
)
291 def get_changelog(self
, name
, public
=None, limit
=5, offset
=0):
292 query
= "SELECT builds.* FROM builds \
293 JOIN packages ON builds.pkg_id = packages.id \
298 args
= ["release", name
,]
301 query
+= " AND builds.public = %s"
303 elif public
== False:
304 query
+= " AND builds.public = %s"
307 query
+= " ORDER BY builds.time_created DESC"
311 query
+= " LIMIT %s,%s"
312 args
+= [offset
, limit
]
318 for b
in self
.db
.query(query
, *args
):
319 b
= Build(self
.backend
, b
.id, b
)
322 builds
.sort(reverse
=True)
326 def get_comments(self
, limit
=10, offset
=None, user
=None):
327 query
= "SELECT * FROM builds_comments \
328 JOIN users ON builds_comments.user_id = users.id"
333 wheres
.append("users.id = %s")
337 query
+= " WHERE %s" % " AND ".join(wheres
)
340 query
+= " ORDER BY time_created DESC"
345 query
+= " LIMIT %s,%s"
353 for comment
in self
.db
.query(query
, *args
):
354 comment
= logs
.CommentLogEntry(self
.backend
, comment
)
355 comments
.append(comment
)
359 def get_build_times_summary(self
, name
=None, arch
=None):
362 builds_times.arch AS arch, \
363 MAX(duration) AS maximum, \
364 MIN(duration) AS minimum, \
365 AVG(duration) AS average, \
366 SUM(duration) AS sum, \
367 STDDEV_POP(duration) AS stddev \
369 LEFT JOIN builds ON builds_times.build_id = builds.id \
370 LEFT JOIN packages ON builds.pkg_id = packages.id"
377 conditions
.append("packages.name = %s")
382 conditions
.append("builds_times.arch = %s")
387 query
+= " WHERE %s" % " AND ".join(conditions
)
389 # Grouping and sorting.
390 query
+= " GROUP BY arch ORDER BY arch DESC"
392 return self
.db
.query(query
, *args
)
394 def get_build_times_by_arch(self
, arch
, **kwargs
):
399 build_times
= self
.get_build_times_summary(**kwargs
)
401 return build_times
[0]
404 class Build(base
.DataObject
):
408 return "<%s id=%s %s>" % (self
.__class
__.__name
__, self
.id, self
.pkg
)
410 def __eq__(self
, other
):
411 if isinstance(other
, self
.__class
__):
412 return self
.id == other
.id
414 def __lt__(self
, other
):
415 if isinstance(other
, self
.__class
__):
416 return self
.pkg
< other
.pkg
419 jobs
= self
.backend
.jobs
._get
_jobs
("SELECT * FROM jobs \
420 WHERE build_id = %s", self
.id)
422 return iter(sorted(jobs
))
426 Deletes this build including all jobs, packages and the source
429 # If the build is in a repository, we need to remove it.
431 self
.repo
.rem_build(self
)
433 for job
in self
.jobs
+ self
.test_jobs
:
439 # Delete everything related to this build.
441 self
.__delete
_comments
()
442 self
.__delete
_history
()
443 self
.__delete
_watchers
()
445 # Delete the build itself.
446 self
.db
.execute("DELETE FROM builds WHERE id = %s", self
.id)
448 def __delete_bugs(self
):
450 Delete all associated bugs.
452 self
.db
.execute("DELETE FROM builds_bugs WHERE build_id = %s", self
.id)
454 def __delete_comments(self
):
458 self
.db
.execute("DELETE FROM builds_comments WHERE build_id = %s", self
.id)
460 def __delete_history(self
):
462 Delete the repository history.
464 self
.db
.execute("DELETE FROM repositories_history WHERE build_id = %s", self
.id)
466 def __delete_watchers(self
):
470 self
.db
.execute("DELETE FROM builds_watchers WHERE build_id = %s", self
.id)
475 A set of information that is sent to the XMLRPC client.
477 return { "uuid" : self
.uuid
}
479 def log(self
, action
, user
=None, bug_id
=None):
484 self
.db
.execute("INSERT INTO builds_history(build_id, action, user_id, time, bug_id) \
485 VALUES(%s, %s, %s, NOW(), %s)", self
.id, action
, user_id
, bug_id
)
490 The UUID of this build.
492 return self
.data
.uuid
497 Get package that is to be built in the build.
499 return self
.backend
.packages
.get_by_id(self
.data
.pkg_id
)
503 return "%s-%s" % (self
.pkg
.name
, self
.pkg
.friendly_version
)
508 The type of this build.
510 return self
.data
.type
514 The owner of this build.
516 if self
.data
.owner_id
:
517 return self
.backend
.users
.get_by_id(self
.data
.owner_id
)
519 def set_owner(self
, owner
):
521 self
._set
_attribute
("owner_id", owner
.id)
523 self
._set
_attribute
("owner_id", None)
525 owner
= lazy_property(get_owner
, set_owner
)
529 return self
.backend
.distros
.get_by_id(self
.data
.distro_id
)
533 if self
.type == "scratch":
536 def get_depends_on(self
):
537 if self
.data
.depends_on
:
538 return self
.backend
.builds
.get_by_id(self
.data
.depends_on
)
540 def set_depends_on(self
, build
):
541 self
._set
_attribute
("depends_on", build
.id)
543 depends_on
= lazy_property(get_depends_on
, set_depends_on
)
547 return self
.data
.time_created
551 return self
.created
.date()
556 Is this build public?
558 return self
.data
.public
563 Returns the size on disk of this build.
567 # Add the source package.
572 s
+= sum((j
.size
for j
in self
.jobs
))
576 def auto_update_state(self
):
578 Check if the state of this build can be updated and perform
579 the change if possible.
581 # Do not change the broken/obsolete state automatically.
582 if self
.state
in ("broken", "obsolete"):
585 if self
.repo
and self
.repo
.type == "stable":
586 self
.update_state("stable")
589 # If any of the build jobs are finished, the build will be put in testing
591 for job
in self
.jobs
:
592 if job
.state
== "finished":
593 self
.update_state("testing")
596 def update_state(self
, state
, user
=None, remove
=False):
597 assert state
in ("stable", "testing", "obsolete", "broken")
599 self
._set
_attribute
("state", state
)
601 # In broken state, the removal from the repository is forced and
602 # all jobs that are not finished yet will be aborted.
603 if state
== "broken":
606 for job
in self
.jobs
:
607 if job
.state
in ("new", "pending", "running", "dependency_error"):
608 job
.state
= "aborted"
610 # If this build is in a repository, it will leave it.
611 if remove
and self
.repo
:
612 self
.repo
.rem_build(self
)
614 # If a release build is now in testing state, we put it into the
615 # first repository of the distribution.
616 elif self
.type == "release" and state
== "testing":
617 # If the build is not in a repository, yet and if there is
618 # a first repository, we put the build there.
619 if not self
.repo
and self
.distro
.first_repo
:
620 self
.distro
.first_repo
.add_build(self
, user
=user
)
624 return self
.data
.state
627 return self
.state
== "broken"
629 def obsolete_others(self
):
630 if not self
.type == "release":
633 for build
in self
.backend
.builds
.get_by_name(self
.pkg
.name
, type="release"):
634 # Don't modify ourself.
635 if self
.id == build
.id:
638 # Don't touch broken builds.
639 if build
.state
in ("obsolete", "broken"):
642 # Obsolete the build.
643 build
.update_state("obsolete")
645 def set_severity(self
, severity
):
646 self
._set
_attribute
("severity", severity
)
648 def get_severity(self
):
649 return self
.data
.severity
651 severity
= property(get_severity
, set_severity
)
655 if self
.pkg
and self
.pkg
.commit
:
656 return self
.pkg
.commit
658 def update_message(self
, message
):
659 self
._set
_attribute
("message", message
)
661 def has_perm(self
, user
):
663 Check, if the given user has the right to perform administrative
664 operations on this build.
672 # Check if the user is allowed to manage packages from the critical path.
673 if self
.critical_path
and not user
.has_perm("manage_critical_path"):
676 # Search for maintainers...
679 if self
.type == "scratch":
680 # The owner of a scratch build has the right to do anything with it.
681 if self
.owner_id
== user
.id:
685 elif self
.type == "release":
686 # The maintainer also is allowed to manage the build.
687 if self
.pkg
.maintainer
== user
:
690 # Deny permission for all other cases.
697 if self
.data
.message
:
698 message
= self
.data
.message
701 if self
.commit
.message
:
702 message
= "\n".join((self
.commit
.subject
, self
.commit
.message
))
704 message
= self
.commit
.subject
706 prefix
= "%s: " % self
.pkg
.name
707 if message
.startswith(prefix
):
708 message
= message
[len(prefix
):]
712 def get_priority(self
):
713 return self
.data
.priority
715 def set_priority(self
, priority
):
716 assert priority
in (-2, -1, 0, 1, 2)
718 self
._set
_attribute
("priority", priority
)
720 priority
= property(get_priority
, set_priority
)
725 if self
.type == "scratch":
726 path
.append(BUILD_SCRATCH_DIR
)
727 path
.append(self
.uuid
)
729 elif self
.type == "release":
730 path
.append(BUILD_RELEASE_DIR
)
731 path
.append("%s/%s-%s-%s" % \
732 (self
.pkg
.name
, self
.pkg
.epoch
, self
.pkg
.version
, self
.pkg
.release
))
735 raise Exception, "Unknown build type: %s" % self
.type
737 return os
.path
.join(*path
)
740 def source_filename(self
):
741 return os
.path
.basename(self
.pkg
.path
)
744 def download_prefix(self
):
745 return "/".join((self
.backend
.settings
.get("download_baseurl"), "packages"))
748 def source_download(self
):
749 return "/".join((self
.download_prefix
, self
.pkg
.path
))
752 def source_hash_sha512(self
):
753 return self
.pkg
.hash_sha512
757 # XXX maybe this should rather live in a uimodule.
758 # zlib-1.2.3-2.ip3 [src, i686, blah...]
759 s
= """<a class="state_%s %s" href="/build/%s">%s</a>""" % \
760 (self
.state
, self
.type, self
.uuid
, self
.name
)
763 for job
in self
.jobs
:
764 s_jobs
.append("""<a class="state_%s %s" href="/job/%s">%s</a>""" % \
765 (job
.state
, job
.type, job
.uuid
, job
.arch
))
768 s
+= " [%s]" % ", ".join(s_jobs
)
773 def supported_arches(self
):
774 return self
.pkg
.supported_arches
777 def critical_path(self
):
778 return self
.pkg
.critical_path
780 def get_jobs(self
, type=None):
782 Returns a list of jobs of this build.
784 return self
.backend
.jobs
.get_by_build(self
.id, self
, type=type)
789 Get a list of all build jobs that are in this build.
791 return self
.get_jobs(type="build")
795 return self
.get_jobs(type="test")
798 def all_jobs_finished(self
):
801 for job
in self
.jobs
:
802 if not job
.state
== "finished":
808 def create_autojobs(self
, arches
=None, type="build"):
811 # Arches may be passed to this function. If not we use all arches
812 # this package supports.
814 arches
= self
.supported_arches
816 # Create a new job for every given archirecture.
817 for arch
in self
.backend
.arches
.expand(arches
):
818 # Don't create jobs for src
822 job
= self
.add_job(arch
, type=type)
825 # Return all newly created jobs.
828 def add_job(self
, arch
, type="build"):
829 job
= self
.backend
.jobs
.create(self
, arch
, type=type)
831 # Add new job to cache.
832 self
.jobs
.append(job
)
840 if not self
.type == "release":
843 # Generate an update ID if none does exist, yet.
844 self
.generate_update_id()
847 "%s" % self
.distro
.name
.replace(" ", "").upper(),
848 "%04d" % (self
.data
.update_year
or 0),
849 "%04d" % (self
.data
.update_num
or 0),
854 def generate_update_id(self
):
855 if not self
.type == "release":
858 if self
.data
.update_num
:
861 update
= self
.db
.get("SELECT update_num AS num FROM builds \
862 WHERE update_year = EXTRACT(year FROM NOW()) ORDER BY update_num DESC LIMIT 1")
865 update_num
= update
.num
+ 1
869 self
.db
.execute("UPDATE builds SET update_year = EXTRACT(year FROM NOW()), update_num = %s \
870 WHERE id = %s", update_num
, self
.id)
874 def get_comments(self
, limit
=10, offset
=0):
875 query
= "SELECT * FROM builds_comments \
876 JOIN users ON builds_comments.user_id = users.id \
877 WHERE build_id = %s ORDER BY time_created ASC"
880 for comment
in self
.db
.query(query
, self
.id):
881 comment
= logs
.CommentLogEntry(self
.backend
, comment
)
882 comments
.append(comment
)
886 def add_comment(self
, user
, text
, score
):
887 # Add the new comment to the database.
888 id = self
.db
.execute("INSERT INTO \
889 builds_comments(build_id, user_id, text, credit, time_created) \
890 VALUES(%s, %s, %s, %s, NOW())",
891 self
.id, user
.id, text
, score
)
893 # Update the credit cache
896 # Send the new comment to all watchers and stuff.
897 self
.send_comment_message(id)
899 # Return the ID of the newly created comment.
904 res
= self
.db
.get("SELECT SUM(credit) AS score \
905 FROM builds_comments WHERE build_id = %s", self
.id)
907 return res
.score
or 0
914 def get_commenters(self
):
915 users
= self
.db
.query("SELECT DISTINCT users.id AS id FROM builds_comments \
916 JOIN users ON builds_comments.user_id = users.id \
917 WHERE builds_comments.build_id = %s AND NOT users.deleted = 'Y' \
918 AND NOT users.activated = 'Y' ORDER BY users.id", self
.id)
920 return [users
.User(self
.backend
, u
.id) for u
in users
]
922 def send_comment_message(self
, comment_id
):
923 comment
= self
.db
.get("SELECT * FROM builds_comments WHERE id = %s",
927 assert comment
.build_id
== self
.id
929 # Get user who wrote the comment.
930 user
= self
.backend
.users
.get_by_id(comment
.user_id
)
933 "build_name" : self
.name
,
934 "user_name" : user
.realname
,
937 # XXX create beautiful message
939 self
.backend
.messages
.send_to_all(self
.message_recipients
,
940 N_("%(user_name)s commented on %(build_name)s"),
941 comment
.text
, format
)
945 def get_log(self
, comments
=True, repo
=True, limit
=None):
949 created_entry
= logs
.CreatedLogEntry(self
.backend
, self
)
950 entries
.append(created_entry
)
953 entries
+= self
.get_comments(limit
=limit
)
956 entries
+= self
.get_repo_moves(limit
=limit
)
958 # Sort all entries in chronological order.
962 entries
= entries
[:limit
]
968 def get_watchers(self
):
969 query
= self
.db
.query("SELECT DISTINCT users.id AS id FROM builds_watchers \
970 JOIN users ON builds_watchers.user_id = users.id \
971 WHERE builds_watchers.build_id = %s AND NOT users.deleted = 'Y' \
972 AND users.activated = 'Y' ORDER BY users.id", self
.id)
974 return [users
.User(self
.backend
, u
.id) for u
in query
]
976 def add_watcher(self
, user
):
977 # Don't add a user twice.
978 if user
in self
.get_watchers():
981 self
.db
.execute("INSERT INTO builds_watchers(build_id, user_id) \
982 VALUES(%s, %s)", self
.id, user
.id)
985 def message_recipients(self
):
988 for watcher
in self
.get_watchers():
989 ret
.append("%s <%s>" % (watcher
.realname
, watcher
.email
))
995 if self
._update
is None:
996 update
= self
.db
.get("SELECT update_id AS id FROM updates_builds \
997 WHERE build_id = %s", self
.id)
1000 self
._update
= updates
.Update(self
.backend
, update
.id)
1006 res
= self
.db
.get("SELECT repo_id FROM repositories_builds \
1007 WHERE build_id = %s", self
.id)
1010 return self
.backend
.repos
.get_by_id(res
.repo_id
)
1012 def get_repo_moves(self
, limit
=None):
1013 query
= "SELECT * FROM repositories_history \
1014 WHERE build_id = %s ORDER BY time ASC"
1017 for action
in self
.db
.query(query
, self
.id):
1018 action
= logs
.RepositoryLogEntry(self
.backend
, action
)
1019 actions
.append(action
)
1031 def repo_time(self
):
1032 repo
= self
.db
.get("SELECT time_added FROM repositories_builds \
1033 WHERE build_id = %s", self
.id)
1036 return repo
.time_added
1038 def get_auto_move(self
):
1039 return self
.data
.auto_move
== "Y"
1041 def set_auto_move(self
, state
):
1042 self
._set
_attribute
("auto_move", state
)
1044 auto_move
= property(get_auto_move
, set_auto_move
)
1047 def can_move_forward(self
):
1051 # If there is no next repository, we cannot move anything.
1052 if not self
.repo
.next
:
1055 # If the needed amount of score is reached, we can move forward.
1056 if self
.score
>= self
.repo
.next
.score_needed
:
1059 # If the repository does not require a minimal time,
1060 # we can move forward immediately.
1061 if not self
.repo
.time_min
:
1064 query
= self
.db
.get("SELECT NOW() - time_added AS duration FROM repositories_builds \
1065 WHERE build_id = %s", self
.id)
1066 duration
= query
.duration
1068 if duration
>= self
.repo
.time_min
:
1075 def get_bug_ids(self
):
1076 query
= self
.db
.query("SELECT bug_id FROM builds_bugs \
1077 WHERE build_id = %s", self
.id)
1079 return [b
.bug_id
for b
in query
]
1081 def add_bug(self
, bug_id
, user
=None, log
=True):
1082 # Check if this bug is already in the list of bugs.
1083 if bug_id
in self
.get_bug_ids():
1086 self
.db
.execute("INSERT INTO builds_bugs(build_id, bug_id) \
1087 VALUES(%s, %s)", self
.id, bug_id
)
1091 self
.log("bug_added", user
=user
, bug_id
=bug_id
)
1093 def rem_bug(self
, bug_id
, user
=None, log
=True):
1094 self
.db
.execute("DELETE FROM builds_bugs WHERE build_id = %s AND \
1095 bug_id = %s", self
.id, bug_id
)
1099 self
.log("bug_removed", user
=user
, bug_id
=bug_id
)
1101 def search_for_bugs(self
):
1105 pattern
= re
.compile(r
"(bug\s?|#)(\d+)")
1107 for txt
in (self
.commit
.subject
, self
.commit
.message
):
1108 for bug
in re
.finditer(pattern
, txt
):
1110 bugid
= int(bug
.group(2))
1114 # Check if a bug with the given ID exists in BZ.
1115 bug
= self
.backend
.bugzilla
.get_bug(bugid
)
1123 for bug_id
in self
.get_bug_ids():
1124 bug
= self
.backend
.bugzilla
.get_bug(bug_id
)
1132 def _update_bugs_helper(self
, repo
):
1134 This function takes a new status and generates messages that
1135 are appended to all bugs.
1138 kwargs
= BUG_MESSAGES
[repo
.type].copy()
1142 baseurl
= self
.backend
.settings
.get("baseurl", "")
1144 "build_url" : "%s/build/%s" % (baseurl
, self
.uuid
),
1145 "distro_name" : self
.distro
.name
,
1146 "package_name" : self
.name
,
1147 "repo_name" : repo
.name
,
1149 kwargs
["comment"] = kwargs
["comment"] % args
1151 self
.update_bugs(**kwargs
)
1153 def _update_bug(self
, bug_id
, status
=None, resolution
=None, comment
=None):
1154 self
.db
.execute("INSERT INTO builds_bugs_updates(bug_id, status, resolution, comment, time) \
1155 VALUES(%s, %s, %s, %s, NOW())", bug_id
, status
, resolution
, comment
)
1157 def update_bugs(self
, status
, resolution
=None, comment
=None):
1158 # Update all bugs linked to this build.
1159 for bug_id
in self
.get_bug_ids():
1160 self
._update
_bug
(bug_id
, status
=status
, resolution
=resolution
, comment
=comment
)