summaryrefslogtreecommitdiffstats
path: root/python/mozrelease/mozrelease/buglist_creator.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozrelease/mozrelease/buglist_creator.py')
-rw-r--r--python/mozrelease/mozrelease/buglist_creator.py261
1 files changed, 261 insertions, 0 deletions
diff --git a/python/mozrelease/mozrelease/buglist_creator.py b/python/mozrelease/mozrelease/buglist_creator.py
new file mode 100644
index 0000000000..8c7b8d0391
--- /dev/null
+++ b/python/mozrelease/mozrelease/buglist_creator.py
@@ -0,0 +1,261 @@
+# -*- coding: utf-8 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import logging
+import os
+import re
+from operator import itemgetter
+
+import requests
+from mozilla_version.gecko import GeckoVersion
+from taskcluster import Notify, optionsFromEnvironment
+
+BUGLIST_TEMPLATE = "* [Bugs since previous changeset]({url})\n"
+BACKOUT_REGEX = re.compile(r"back(\s?)out|backed out|backing out", re.IGNORECASE)
+BACKOUT_TEMPLATE = "* [Backouts since previous changeset]({url})\n"
+BUGZILLA_BUGLIST_TEMPLATE = "https://bugzilla.mozilla.org/buglist.cgi?bug_id={bugs}"
+BUG_NUMBER_REGEX = re.compile(r"bug \d+", re.IGNORECASE)
+CHANGELOG_TO_FROM_STRING = "{product}_{version}_RELEASE"
+CHANGESET_URL_TEMPLATE = (
+ "{repo}/{logtype}" "?rev={to_version}+%25+{from_version}&revcount=1000"
+)
+FULL_CHANGESET_TEMPLATE = "* [Full Mercurial changelog]({url})\n"
+LIST_DESCRIPTION_TEMPLATE = "Comparing Mercurial tag {from_version} to {to_version}:\n"
+MAX_BUGS_IN_BUGLIST = 250
+MERCURIAL_TAGS_URL_TEMPLATE = "{repo}/json-tags"
+NO_BUGS = "" # Return this when bug list can't be created
+URL_SHORTENER_TEMPLATE = "https://bugzilla.mozilla.org/rest/bitly/shorten?url={url}"
+
+log = logging.getLogger(__name__)
+
+
+def create_bugs_url(product, current_version, current_revision, repo=None):
+ """
+ Creates list of bugs and backout bugs for release-drivers email
+
+ :param release: dict -> containing information about release, from Ship-It
+ :return: str -> description of compared releases, with Bugzilla links
+ containing all bugs in changeset
+ """
+ try:
+ # Extract the important data, ignore if beta1 release
+ if current_version.beta_number == 1:
+ # If the version is beta 1, don't make any links
+ return NO_BUGS
+
+ if repo is None:
+ repo = get_repo_by_version(current_version)
+ # Get the tag version, for display purposes
+ current_version_tag = tag_version(product, current_version)
+
+ # Get all Hg tags for this branch, determine the previous version
+ tag_url = MERCURIAL_TAGS_URL_TEMPLATE.format(repo=repo)
+ mercurial_tags_json = requests.get(tag_url).json()
+ previous_version_tag = get_previous_tag_version(
+ product, current_version, current_version_tag, mercurial_tags_json
+ )
+
+ # Get the changeset between these versions, parse for all unique bugs and backout bugs
+ resp = requests.get(
+ CHANGESET_URL_TEMPLATE.format(
+ repo=repo,
+ from_version=previous_version_tag,
+ to_version=current_revision,
+ logtype="json-log",
+ )
+ )
+ changeset_data = resp.json()
+ unique_bugs, unique_backout_bugs = get_bugs_in_changeset(changeset_data)
+
+ # Return a descriptive string with links if any relevant bugs are found
+ if unique_bugs or unique_backout_bugs:
+ description = LIST_DESCRIPTION_TEMPLATE.format(
+ from_version=previous_version_tag, to_version=current_version_tag
+ )
+
+ if unique_bugs:
+ description += BUGLIST_TEMPLATE.format(
+ url=create_buglist_url(unique_bugs)
+ )
+ if unique_backout_bugs:
+ description += BACKOUT_TEMPLATE.format(
+ url=create_buglist_url(unique_backout_bugs)
+ )
+
+ changeset_html = CHANGESET_URL_TEMPLATE.format(
+ repo=repo,
+ from_version=previous_version_tag,
+ to_version=current_revision,
+ logtype="log",
+ )
+ description += FULL_CHANGESET_TEMPLATE.format(url=changeset_html)
+
+ return description
+ else:
+ return NO_BUGS
+
+ except Exception as err:
+ log.info(err)
+ return NO_BUGS
+
+
+def get_bugs_in_changeset(changeset_data):
+ unique_bugs, unique_backout_bugs = set(), set()
+ for changeset in changeset_data["entries"]:
+ if is_excluded_change(changeset):
+ continue
+
+ changeset_desc = changeset["desc"]
+ bug_re = BUG_NUMBER_REGEX.search(changeset_desc)
+
+ if bug_re:
+ bug_number = bug_re.group().split(" ")[1]
+
+ if is_backout_bug(changeset_desc):
+ unique_backout_bugs.add(bug_number)
+ else:
+ unique_bugs.add(bug_number)
+
+ return unique_bugs, unique_backout_bugs
+
+
+def is_excluded_change(changeset):
+ excluded_change_keywords = [
+ "a=test-only",
+ "a=release",
+ ]
+ return any(keyword in changeset["desc"] for keyword in excluded_change_keywords)
+
+
+def is_backout_bug(changeset_description):
+ return bool(BACKOUT_REGEX.search(changeset_description))
+
+
+def create_buglist_url(buglist):
+ return BUGZILLA_BUGLIST_TEMPLATE.format(bugs="%2C".join(buglist))
+
+
+def tag_version(product, version):
+ underscore_version = str(version).replace(".", "_")
+ return CHANGELOG_TO_FROM_STRING.format(
+ product=product.upper(), version=underscore_version
+ )
+
+
+def parse_tag_version(tag):
+ dot_version = ".".join(tag.split("_")[1:-1])
+ return GeckoVersion.parse(dot_version)
+
+
+def get_previous_tag_version(
+ product,
+ current_version,
+ current_version_tag,
+ mercurial_tags_json,
+):
+ """
+ Gets the previous hg version tag for the product and branch, given the current version tag
+ """
+
+ def _invalid_tag_filter(tag):
+ """Filters by product and removes incorrect major version + base, end releases"""
+ prod_major_version_re = r"^{product}_{major_version}".format(
+ product=product.upper(), major_version=current_version.major_number
+ )
+
+ return (
+ "BASE" not in tag
+ and "END" not in tag
+ and "RELEASE" in tag
+ and re.match(prod_major_version_re, tag)
+ )
+
+ # Get rid of irrelevant tags, sort by date and extract the tag string
+ tags = {
+ (parse_tag_version(item["tag"]), item["tag"])
+ for item in mercurial_tags_json["tags"]
+ if _invalid_tag_filter(item["tag"])
+ }
+ # Add the current version to the list
+ tags.add((current_version, current_version_tag))
+ tags = sorted(tags, key=lambda tag: tag[0])
+
+ # Find where the current version is and go back one to get the previous version
+ next_version_index = list(map(itemgetter(0), tags)).index(current_version) - 1
+
+ return tags[next_version_index][1]
+
+
+def get_repo_by_version(version):
+ """
+ Get the repo a given version is found on.
+ """
+ if version.is_beta:
+ return "https://hg.mozilla.org/releases/mozilla-beta"
+ elif version.is_release:
+ return "https://hg.mozilla.org/releases/mozilla-release"
+ elif version.is_esr:
+ return "https://hg.mozilla.org/releases/mozilla-esr{}".format(
+ version.major_number
+ )
+ else:
+ raise Exception(
+ "Unsupported version type {}: {}".format(version.version_type.name, version)
+ )
+
+
+def email_release_drivers(
+ addresses,
+ product,
+ version,
+ build_number,
+ repo,
+ revision,
+ task_group_id,
+):
+ # Send an email to the mailing after the build
+ email_buglist_string = create_bugs_url(product, version, revision, repo=repo)
+
+ content = """\
+A new build has been started:
+
+Commit: [{revision}]({repo}/rev/{revision})
+Task group: [{task_group_id}]({root_url}/tasks/groups/{task_group_id})
+
+{email_buglist_string}
+""".format(
+ repo=repo,
+ revision=revision,
+ root_url=os.environ["TASKCLUSTER_ROOT_URL"],
+ task_group_id=task_group_id,
+ email_buglist_string=email_buglist_string,
+ )
+
+ # On r-d, we prefix the subject of the email in order to simplify filtering
+ subject_prefix = ""
+ if product in {"fennec"}:
+ subject_prefix = "[mobile] "
+ if product in {"firefox", "devedition"}:
+ subject_prefix = "[desktop] "
+
+ subject = "{} Build of {} {} build {}".format(
+ subject_prefix, product, version, build_number
+ )
+
+ # use proxy if configured, otherwise local credentials from env vars
+ if "TASKCLUSTER_PROXY_URL" in os.environ:
+ notify_options = {"rootUrl": os.environ["TASKCLUSTER_PROXY_URL"]}
+ else:
+ notify_options = optionsFromEnvironment()
+
+ notify = Notify(notify_options)
+ for address in addresses:
+ notify.email(
+ {
+ "address": address,
+ "subject": subject,
+ "content": content,
+ }
+ )