]>
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
, "test" if job
.test
else "build", 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
783 Get a list of all build jobs that are in this build.
785 return self
.backend
.jobs
._get
_jobs
("SELECT * FROM jobs \
786 WHERE build_id = %s AND test IS FALSE AND deleted_at IS NULL", self
.id)
790 return self
.backend
.jobs
._get
_jobs
("SELECT * FROM jobs \
791 WHERE build_id = %s AND test IS TRUE AND deleted_at IS NULL", self
.id)
794 def all_jobs_finished(self
):
797 for job
in self
.jobs
:
798 if not job
.state
== "finished":
804 def create_autojobs(self
, arches
=None, **kwargs
):
807 # Arches may be passed to this function. If not we use all arches
808 # this package supports.
810 arches
= self
.supported_arches
812 # Create a new job for every given archirecture.
813 for arch
in self
.backend
.arches
.expand(arches
):
814 # Don't create jobs for src
818 job
= self
.add_job(arch
, **kwargs
)
821 # Return all newly created jobs.
824 def add_job(self
, arch
, **kwargs
):
825 job
= self
.backend
.jobs
.create(self
, arch
, **kwargs
)
827 # Add new job to cache.
828 self
.jobs
.append(job
)
836 if not self
.type == "release":
839 # Generate an update ID if none does exist, yet.
840 self
.generate_update_id()
843 "%s" % self
.distro
.name
.replace(" ", "").upper(),
844 "%04d" % (self
.data
.update_year
or 0),
845 "%04d" % (self
.data
.update_num
or 0),
850 def generate_update_id(self
):
851 if not self
.type == "release":
854 if self
.data
.update_num
:
857 update
= self
.db
.get("SELECT update_num AS num FROM builds \
858 WHERE update_year = EXTRACT(year FROM NOW()) ORDER BY update_num DESC LIMIT 1")
861 update_num
= update
.num
+ 1
865 self
.db
.execute("UPDATE builds SET update_year = EXTRACT(year FROM NOW()), update_num = %s \
866 WHERE id = %s", update_num
, self
.id)
870 def get_comments(self
, limit
=10, offset
=0):
871 query
= "SELECT * FROM builds_comments \
872 JOIN users ON builds_comments.user_id = users.id \
873 WHERE build_id = %s ORDER BY time_created ASC"
876 for comment
in self
.db
.query(query
, self
.id):
877 comment
= logs
.CommentLogEntry(self
.backend
, comment
)
878 comments
.append(comment
)
882 def add_comment(self
, user
, text
, score
):
883 # Add the new comment to the database.
884 id = self
.db
.execute("INSERT INTO \
885 builds_comments(build_id, user_id, text, credit, time_created) \
886 VALUES(%s, %s, %s, %s, NOW())",
887 self
.id, user
.id, text
, score
)
889 # Update the credit cache
892 # Send the new comment to all watchers and stuff.
893 self
.send_comment_message(id)
895 # Return the ID of the newly created comment.
900 res
= self
.db
.get("SELECT SUM(credit) AS score \
901 FROM builds_comments WHERE build_id = %s", self
.id)
903 return res
.score
or 0
910 def get_commenters(self
):
911 users
= self
.db
.query("SELECT DISTINCT users.id AS id FROM builds_comments \
912 JOIN users ON builds_comments.user_id = users.id \
913 WHERE builds_comments.build_id = %s AND NOT users.deleted = 'Y' \
914 AND NOT users.activated = 'Y' ORDER BY users.id", self
.id)
916 return [users
.User(self
.backend
, u
.id) for u
in users
]
918 def send_comment_message(self
, comment_id
):
919 comment
= self
.db
.get("SELECT * FROM builds_comments WHERE id = %s",
923 assert comment
.build_id
== self
.id
925 # Get user who wrote the comment.
926 user
= self
.backend
.users
.get_by_id(comment
.user_id
)
929 "build_name" : self
.name
,
930 "user_name" : user
.realname
,
933 # XXX create beautiful message
935 self
.backend
.messages
.send_to_all(self
.message_recipients
,
936 N_("%(user_name)s commented on %(build_name)s"),
937 comment
.text
, format
)
941 def get_log(self
, comments
=True, repo
=True, limit
=None):
945 created_entry
= logs
.CreatedLogEntry(self
.backend
, self
)
946 entries
.append(created_entry
)
949 entries
+= self
.get_comments(limit
=limit
)
952 entries
+= self
.get_repo_moves(limit
=limit
)
954 # Sort all entries in chronological order.
958 entries
= entries
[:limit
]
964 def get_watchers(self
):
965 query
= self
.db
.query("SELECT DISTINCT users.id AS id FROM builds_watchers \
966 JOIN users ON builds_watchers.user_id = users.id \
967 WHERE builds_watchers.build_id = %s AND NOT users.deleted = 'Y' \
968 AND users.activated = 'Y' ORDER BY users.id", self
.id)
970 return [users
.User(self
.backend
, u
.id) for u
in query
]
972 def add_watcher(self
, user
):
973 # Don't add a user twice.
974 if user
in self
.get_watchers():
977 self
.db
.execute("INSERT INTO builds_watchers(build_id, user_id) \
978 VALUES(%s, %s)", self
.id, user
.id)
981 def message_recipients(self
):
984 for watcher
in self
.get_watchers():
985 ret
.append("%s <%s>" % (watcher
.realname
, watcher
.email
))
991 if self
._update
is None:
992 update
= self
.db
.get("SELECT update_id AS id FROM updates_builds \
993 WHERE build_id = %s", self
.id)
996 self
._update
= updates
.Update(self
.backend
, update
.id)
1002 res
= self
.db
.get("SELECT repo_id FROM repositories_builds \
1003 WHERE build_id = %s", self
.id)
1006 return self
.backend
.repos
.get_by_id(res
.repo_id
)
1008 def get_repo_moves(self
, limit
=None):
1009 query
= "SELECT * FROM repositories_history \
1010 WHERE build_id = %s ORDER BY time ASC"
1013 for action
in self
.db
.query(query
, self
.id):
1014 action
= logs
.RepositoryLogEntry(self
.backend
, action
)
1015 actions
.append(action
)
1027 def repo_time(self
):
1028 repo
= self
.db
.get("SELECT time_added FROM repositories_builds \
1029 WHERE build_id = %s", self
.id)
1032 return repo
.time_added
1034 def get_auto_move(self
):
1035 return self
.data
.auto_move
== "Y"
1037 def set_auto_move(self
, state
):
1038 self
._set
_attribute
("auto_move", state
)
1040 auto_move
= property(get_auto_move
, set_auto_move
)
1043 def can_move_forward(self
):
1047 # If there is no next repository, we cannot move anything.
1048 if not self
.repo
.next
:
1051 # If the needed amount of score is reached, we can move forward.
1052 if self
.score
>= self
.repo
.next
.score_needed
:
1055 # If the repository does not require a minimal time,
1056 # we can move forward immediately.
1057 if not self
.repo
.time_min
:
1060 query
= self
.db
.get("SELECT NOW() - time_added AS duration FROM repositories_builds \
1061 WHERE build_id = %s", self
.id)
1062 duration
= query
.duration
1064 if duration
>= self
.repo
.time_min
:
1071 def get_bug_ids(self
):
1072 query
= self
.db
.query("SELECT bug_id FROM builds_bugs \
1073 WHERE build_id = %s", self
.id)
1075 return [b
.bug_id
for b
in query
]
1077 def add_bug(self
, bug_id
, user
=None, log
=True):
1078 # Check if this bug is already in the list of bugs.
1079 if bug_id
in self
.get_bug_ids():
1082 self
.db
.execute("INSERT INTO builds_bugs(build_id, bug_id) \
1083 VALUES(%s, %s)", self
.id, bug_id
)
1087 self
.log("bug_added", user
=user
, bug_id
=bug_id
)
1089 def rem_bug(self
, bug_id
, user
=None, log
=True):
1090 self
.db
.execute("DELETE FROM builds_bugs WHERE build_id = %s AND \
1091 bug_id = %s", self
.id, bug_id
)
1095 self
.log("bug_removed", user
=user
, bug_id
=bug_id
)
1097 def search_for_bugs(self
):
1101 pattern
= re
.compile(r
"(bug\s?|#)(\d+)")
1103 for txt
in (self
.commit
.subject
, self
.commit
.message
):
1104 for bug
in re
.finditer(pattern
, txt
):
1106 bugid
= int(bug
.group(2))
1110 # Check if a bug with the given ID exists in BZ.
1111 bug
= self
.backend
.bugzilla
.get_bug(bugid
)
1119 for bug_id
in self
.get_bug_ids():
1120 bug
= self
.backend
.bugzilla
.get_bug(bug_id
)
1128 def _update_bugs_helper(self
, repo
):
1130 This function takes a new status and generates messages that
1131 are appended to all bugs.
1134 kwargs
= BUG_MESSAGES
[repo
.type].copy()
1138 baseurl
= self
.backend
.settings
.get("baseurl", "")
1140 "build_url" : "%s/build/%s" % (baseurl
, self
.uuid
),
1141 "distro_name" : self
.distro
.name
,
1142 "package_name" : self
.name
,
1143 "repo_name" : repo
.name
,
1145 kwargs
["comment"] = kwargs
["comment"] % args
1147 self
.update_bugs(**kwargs
)
1149 def _update_bug(self
, bug_id
, status
=None, resolution
=None, comment
=None):
1150 self
.db
.execute("INSERT INTO builds_bugs_updates(bug_id, status, resolution, comment, time) \
1151 VALUES(%s, %s, %s, %s, NOW())", bug_id
, status
, resolution
, comment
)
1153 def update_bugs(self
, status
, resolution
=None, comment
=None):
1154 # Update all bugs linked to this build.
1155 for bug_id
in self
.get_bug_ids():
1156 self
._update
_bug
(bug_id
, status
=status
, resolution
=resolution
, comment
=comment
)