From bb969c75050c8553aa2fc2c43ec80a9538728128 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Sat, 10 Jun 2023 09:59:01 +0200 Subject: [PATCH] Replace bootstrap_history.py with galaxy-release-util --- Makefile | 11 +- pyproject.toml | 1 - scripts/bootstrap_history.py | 810 ----------------------------------- 3 files changed, 4 insertions(+), 818 deletions(-) delete mode 100644 scripts/bootstrap_history.py diff --git a/Makefile b/Makefile index 9fb877681c8..b37cbccd022 100644 --- a/Makefile +++ b/Makefile @@ -120,19 +120,16 @@ release-push-dev: release-ensure-upstream # Push local dev branch upstream git push $(RELEASE_UPSTREAM) dev release-issue: ## Create release issue on github - $(IN_VENV) python scripts/bootstrap_history.py --create-release-issue $(RELEASE_CURR) - -release-check-metadata: ## check github PR metadata for target release - $(IN_VENV) python scripts/bootstrap_history.py --check-release $(RELEASE_CURR) + $(IN_VENV) galaxy-release-util create-release-issue $(RELEASE_CURR) release-check-blocking-issues: ## Check github for release blocking issues - $(IN_VENV) python scripts/bootstrap_history.py --check-blocking-issues $(RELEASE_CURR) + $(IN_VENV) galaxy-release-util check-blocking-issues $(RELEASE_CURR) release-check-blocking-prs: ## Check github for release blocking PRs - $(IN_VENV) python scripts/bootstrap_history.py --check-blocking-prs $(RELEASE_CURR) + $(IN_VENV) galaxy-release-util check-blocking-prs $(RELEASE_CURR) release-bootstrap-history: ## bootstrap history for a new release - $(IN_VENV) python scripts/bootstrap_history.py --release $(RELEASE_CURR) + $(IN_VENV) galaxy-release-util create-changelog $(RELEASE_CURR) update-lint-requirements: ./lib/galaxy/dependencies/update_lint_requirements.sh diff --git a/pyproject.toml b/pyproject.toml index 18b54ef0b95..f8fa5bebf95 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -129,7 +129,6 @@ lxml = "!=4.2.2" markdown-it-reporter = "*" myst-parser = "*" pkce = "*" -PyGithub = "*" pytest = "*" pytest-asyncio = "*" pytest-celery = "*" diff --git a/scripts/bootstrap_history.py b/scripts/bootstrap_history.py deleted file mode 100644 index 28e4e10e0c1..00000000000 --- a/scripts/bootstrap_history.py +++ /dev/null @@ -1,810 +0,0 @@ -#!/usr/bin/env python -# Little script to make HISTORY.rst more easy to format properly, lots TODO -# pull message down and embed, use arg parse, handle multiple, etc... - -import calendar -import datetime -import json -import os -import re -import string -import sys -import textwrap -from collections import OrderedDict -from urllib.parse import urljoin - -import requests -from github import Github - -PROJECT_DIRECTORY = os.path.join(os.path.dirname(__file__), os.pardir) -GALAXY_VERSION_FILE = os.path.join(PROJECT_DIRECTORY, "lib", "galaxy", "version.py") -PROJECT_OWNER = "galaxyproject" -PROJECT_NAME = "galaxy" -PROJECT_URL = f"https://github.com/{PROJECT_OWNER}/{PROJECT_NAME}" -PROJECT_API = f"https://api.github.com/repos/{PROJECT_OWNER}/{PROJECT_NAME}/" -RELEASES_PATH = os.path.join(PROJECT_DIRECTORY, "doc", "source", "releases") -RELEASE_DELTA_MONTHS = 4 # Number of months between releases. - -TEMPLATE = """ -.. to_doc - -${release} -=============================== - -.. announce_start - -Enhancements -------------------------------- - -.. major_feature - - -.. feature - -.. enhancement - -.. small_enhancement - - - -Fixes -------------------------------- - -.. major_bug - - -.. bug - - -.. include:: ${release}_prs.rst - -""" - -ANNOUNCE_TEMPLATE = string.Template( - """ -=========================================================== -${month_name} 20${year} Galaxy Release (v ${release}) -=========================================================== - -.. include:: _header.rst - -Highlights -=========================================================== - -**Feature1** - Feature description. - -**Feature2** - Feature description. - -**Feature3** - Feature description. - -Also check out the `${release} user release notes <${release}_announce_user.html>`__ - -Get Galaxy -========== - -The code lives at `GitHub `__ and you should have `Git `__ to obtain it. - -To get a new Galaxy repository run: - .. code-block:: shell - - $$ git clone -b release_${release} https://github.com/galaxyproject/galaxy.git - -To update an existing Galaxy repository run: - .. code-block:: shell - - $$ git fetch origin && git checkout release_${release} && git pull --ff-only origin release_${release} - -See the `community hub `__ for additional details on source code locations. - -Release Notes -=========================================================== - -.. include:: ${release}.rst - :start-after: announce_start - -.. include:: _thanks.rst -""" -) - -ANNOUNCE_USER_TEMPLATE = string.Template( - """ -=========================================================== -${month_name} 20${year} Galaxy Release (v ${release}) -=========================================================== - -.. include:: _header.rst - -Highlights -=========================================================== - -**Feature1** - Feature description. - -**Feature2** - Feature description. - -**Feature3** - Feature description. - - -New Visualizations -=========================================================== - -.. visualizations - -New Datatypes -=========================================================== - -.. datatypes - -Builtin Tool Updates -=========================================================== - -.. tools - -Release Testing Team -=========================================================== - -A special thanks to the release testing team for testing many of the new features and reporting many bugs: - - - -Release Notes -=========================================================== - -Please see the :doc:`full release notes <${release}_announce>` for more details. - -.. include:: ${release}_prs.rst - -.. include:: _thanks.rst -""" -) - -NEXT_TEMPLATE = string.Template( - """ -:orphan: - -=========================================================== -${month_name} 20${year} Galaxy Release (v ${version}) -=========================================================== - - -Schedule -=========================================================== - * Planned Freeze Date: ${freeze_date} - * Planned Release Date: ${release_date} -""" -) - -PRS_TEMPLATE = """ -.. github_links -""" - -RELEASE_ISSUE_TEMPLATE = string.Template( - """ - -- [X] **Prep** - - - [X] ~~Create this release issue ``make release-issue``.~~ - - [X] ~~Set freeze date (${freeze_date}).~~ - -- [ ] **Branch Release (on or around ${freeze_date})** - - - [ ] Ensure all [blocking milestone PRs](https://github.com/galaxyproject/galaxy/pulls?q=is%3Aopen+is%3Apr+milestone%3A${version}) have been merged, delayed, or closed. - - make release-check-blocking-prs - - - [ ] Merge the latest release into dev and push upstream. - - make release-merge-stable-to-next RELEASE_PREVIOUS=release_${previous_version} - make release-push-dev - - - [ ] Create and push release branch: - - make release-create-rc - - - [ ] Create dev packages: - - cd packages && DEV_RELEASE=1 ./build_packages.sh - - - - [ ] Review created packages (HISTORY.rst correct, setup.cfg correct, correct version in filename) - - [ ] Upload galaxy-util & galaxy-tool-util dev packages to pypi - - [ ] Open PR against planemo with a pin to the dev packages - - [ ] Open PRs from your fork of branch ``version-${version}`` to upstream ``release_${version}`` and of ``version-${next_version}.dev`` to ``dev``. - - [ ] Update ``MILESTONE_NUMBER`` in the [maintenance bot](https://github.com/galaxyproject/galaxy/blob/dev/.github/workflows/maintenance_bot.yaml) to `${next_version}` so it properly tags new PRs. - -- [ ] **Issue Review Timeline Notes** - - - [ ] Ensure any security fixes will be ready prior to ${freeze_date} + 1 week, to allow time for notification prior to release. - - [ ] Ensure ownership of outstanding bugfixes and track progress during freeze. - -- [ ] **Deploy and Test Release** - - - [ ] Update test.galaxyproject.org to ensure it is running a dev at or past branch point (${freeze_date} + 1 day). - - [ ] Update testtoolshed.g2.bx.psu.edu to ensure it is running a dev at or past branch point (${freeze_date} + 1 day). - - [ ] Deploy to usegalaxy.org (${freeze_date} + 1 week). - - [ ] Deploy to toolshed.g2.bx.psu.edu (${freeze_date} + 1 week). - - [ ] [Update BioBlend CI testing](https://github.com/galaxyproject/bioblend/commit/b74b1c302a1b8fed86786b40d7ecc3520cbadcd3) to include a ``release_${version}`` target: add ``- TOX_ENV=py27 GALAXY_VERSION=release_${version}`` to the ``env`` list in ``.travis.yml`` . - - [ ] Update GALAXY_RELEASE in IUC and devteam github workflows - - [ ] https://github.com/galaxyproject/tools-iuc/blob/master/.github/workflows/ - - [ ] https://github.com/galaxyproject/tools-devteam/blob/master/.github/workflows/ - -- [ ] **Create Release Notes** - - - [ ] Review merged PRs and ensure they all have a milestones attached. [Link](https://github.com/galaxyproject/galaxy/pulls?utf8=%E2%9C%93&q=is%3Apr+is%3Amerged+no%3Amilestone+-label%3Amerge+) - - [ ] Checkout release branch - - git checkout release_${version} -b ${version}_release_notes - - [ ] Check for obvious missing metadata in release PRs - - make release-check-metadata RELEASE_CURR=${version} - - [ ] Bootstrap the release notes - - make release-bootstrap-history RELEASE_CURR=${version} - - [ ] Open newly created files and manually curate major topics and release notes. - - [ ] Run python scripts/scripts/release-diff.py release_${previous_version} and add configuration changes to release notes. - - [ ] Add new release to doc/source/releases/index.rst - - [ ] Commit release notes. - - git add docs/; git commit -m "Release notes for $version"; git push upstream ${version}_release_notes - - [ ] Open a pull request for new release note branch. - - [ ] Merge release note pull request. - -- [ ] **Do Release** - - - [ ] Ensure all [blocking milestone issues](https://github.com/galaxyproject/galaxy/issues?q=is%3Aopen+is%3Aissue+milestone%3A${version}) have been resolved. - - make release-check-blocking-issues RELEASE_CURR=${version} - - [ ] Ensure all [blocking milestone PRs](https://github.com/galaxyproject/galaxy/pulls?q=is%3Aopen+is%3Apr+milestone%3A${version}) have been merged or closed. - - make release-check-blocking-prs RELEASE_CURR=${version} - - [ ] Ensure all PRs merged into the pre-release branch during the freeze have [milestones attached](https://github.com/galaxyproject/galaxy/pulls?q=is%3Apr+is%3Aclosed+base%3Arelease_${version}+is%3Amerged+no%3Amilestone) and that they are the not [${next_version} milestones](https://github.com/galaxyproject/galaxy/pulls?q=is%3Apr+is%3Aclosed+base%3Arelease_${version}+is%3Amerged+milestone%3A${next_version}) - - [ ] Ensure release notes include all PRs added during the freeze by re-running the release note bootstrapping: - - make release-bootstrap-history - - [ ] Ensure previous release is merged into current. [GitHub branch comparison](https://github.com/galaxyproject/galaxy/compare/release_${version}...release_${previous_version}) - - [ ] Create and push release tag: - - make release-create - - - [ ] Create dev packages: - - cd packages && ./build_packages.sh - - - - [ ] Review created packages (HISTORY.rst correct, setup.cfg correct, correct version in filename) - - [ ] Upload built packages to pypi - - [ ] Add the branch `*/release_{version}` to Jenkins documentation build [configuration matrix](https://jenkins.galaxyproject.org/job/galaxy-sphinx-by-branch/configure). - - [ ] Trigger the [branch documentation build](https://jenkins.galaxyproject.org/job/galaxy-sphinx-by-branch/) - - [ ] Verify that everything is merged from ${version}->master, and then trigger the ['latest' documentation build](https://jenkins.galaxyproject.org/job/latest-Sphinx-Docs/) - -- [ ] **Do Docker Release** - - - [ ] Change the [dev branch](https://github.com/bgruening/docker-galaxy-stable/tree/dev) of the Galaxy Docker container to ${next_version} - - [ ] Merge dev into master - -- [ ] **Announce Release** - - - [ ] Verify release included in https://docs.galaxyproject.org/en/master/releases/index.html - - [ ] Review announcement in https://github.com/galaxyproject/galaxy/blob/dev/doc/source/releases/${version}_announce.rst - - [ ] Stage announcement content (Hub, Galaxy Help, etc.) on announce date to capture date tags. Note: all final content does not need to be completed to do this. - - [ ] Create hub *highlights* and post as a new "news" content item. [An example](https://galaxyproject.org/news/2018-9-galaxy-release/). - - [ ] Tweet docs news *highlights* link as @galaxyproject on twitter. [An example](https://twitter.com/galaxyproject/status/973646125633695744). - - [ ] Post *highlights* with tags `news` and `release` to [Galaxy Help](https://help.galaxyproject.org/). [An example](https://help.galaxyproject.org/t/galaxy-release-19-01/712). - - [ ] Email *highlights* to [galaxy-dev](http://dev.list.galaxyproject.org/) and [galaxy-announce](http://announce.list.galaxyproject.org/) @lists.galaxyproject.org. [An example](http://dev.list.galaxyproject.org/The-Galaxy-release-16-04-is-out-tp4669419.html) - - [ ] Adjust http://getgalaxy.org text and links to match current master branch by opening a PR at https://github.com/galaxyproject/galaxy-hub/ - -- [ ] **Prepare for next release** - - - [ ] Close milestone ``${version}`` and ensure milestone ``${next_version}`` exists. - - [ ] Create release issue for next version ``make release-issue``. - - [ ] Schedule committer meeting to discuss re-alignment of priorities. - - [ ] Close this issue. -""" -) - -GROUPPED_TAGS = OrderedDict( - [ - ("area/visualizations", "viz"), - ("area/datatypes", "datatypes"), - ("area/tools", "tools"), - ("area/workflows", "workflows"), - ("area/client", "ui"), - ("area/jobs", "jobs"), - ("area/admin", "admin"), - ] -) - -# https://api.github.com/repos/galaxyproject/galaxy/pulls?base=dev&state=closed -# https://api.github.com/repos/galaxyproject/galaxy/pulls?base=release_15.07&state=closed -# https://api.github.com/repos/galaxyproject/galaxy/compare/release_15.05...dev - - -def release_issue(argv): - release_name = argv[2] - previous_release = _previous_release(release_name) - new_version_params = _next_version_params(release_name) - next_version = new_version_params["version"] - freeze_date, release_date = _release_dates(release_name) - release_issue_template_params = dict( - version=release_name, - next_version=next_version, - previous_version=previous_release, - freeze_date=freeze_date, - ) - release_issue_contents = RELEASE_ISSUE_TEMPLATE.safe_substitute(**release_issue_template_params) - github = github_client() - repo = github.get_repo(f"{PROJECT_OWNER}/{PROJECT_NAME}") - repo.create_issue( - title=f"Publication of Galaxy Release v {release_name}", - body=release_issue_contents, - ) - return release_issue - - -def do_release(argv): - release_name = argv[2] - release_file = _release_file(release_name + ".rst") - enhancement_targets = "\n\n".join(f".. enhancement_tag_{a}" for a in GROUPPED_TAGS.values()) - bug_targets = "\n\n".join(f".. bug_tag_{a}" for a in GROUPPED_TAGS.values()) - template = TEMPLATE - template = template.replace(".. enhancement", f"{enhancement_targets}\n\n.. enhancement") - template = template.replace(".. bug", f"{bug_targets}\n\n.. bug") - release_info = string.Template(template).safe_substitute(release=release_name) - _write_file(release_file, release_info, skip_if_exists=True) - month = int(release_name.split(".")[1]) - month_name = calendar.month_name[month] - year = release_name.split(".")[0] - - announce_info = ANNOUNCE_TEMPLATE.substitute(month_name=month_name, year=year, release=release_name) - announce_file = _release_file(release_name + "_announce.rst") - _write_file(announce_file, announce_info, skip_if_exists=True) - - announce_user_info = ANNOUNCE_USER_TEMPLATE.substitute(month_name=month_name, year=year, release=release_name) - announce_user_file = _release_file(release_name + "_announce_user.rst") - _write_file(announce_user_file, announce_user_info, skip_if_exists=True) - - prs_file = _release_file(release_name + "_prs.rst") - seen_prs = set() - try: - with open(prs_file) as fh: - seen_prs = set(re.findall(r"\.\. _Pull Request (\d*): https", fh.read())) - except FileNotFoundError: - pass - _write_file(prs_file, PRS_TEMPLATE, skip_if_exists=True) - - next_version_params = _next_version_params(release_name) - next_version = next_version_params["version"] - next_release_file = _release_file(next_version + "_announce.rst") - - next_announce = NEXT_TEMPLATE.substitute(**next_version_params) - open(next_release_file, "w").write(next_announce) - releases_index = _release_file("index.rst") - releases_index_contents = _read_file(releases_index) - releases_index_contents = releases_index_contents.replace( - ".. announcements\n", ".. announcements\n " + next_version + "_announce\n" - ) - _write_file(releases_index, releases_index_contents, skip_if_exists=True) - - for pr in _get_prs(release_name): - # 2015-06-29 18:32:13 2015-04-22 19:11:53 2015-08-12 21:15:45 - as_dict = { - "title": pr.title.rstrip("."), - "number": pr.number, - "head": pr.head, - "labels": _pr_to_labels(pr), - } - main( - [argv[0], "--release_file", f"{release_name}.rst", "--request", as_dict, "pr" + str(pr.number)], - seen_prs=seen_prs, - ) - - -def check_release(argv): - release_name = argv[2] - for pr in _get_prs(release_name): - _text_target(pr, labels=_pr_to_labels(pr)) - - -def check_blocking_prs(argv): - release_name = argv[2] - block = 0 - for pr in _get_prs(release_name, state="open"): - print(f"WARN: Blocking PR| {_pr_to_str(pr)}") - block = 1 - - sys.exit(block) - - -def check_blocking_issues(argv): - release_name = argv[2] - block = 0 - github = github_client() - repo = github.get_repo(f"{PROJECT_OWNER}/{PROJECT_NAME}") - issues = repo.get_issues(state="open") - for issue in issues: - # issue can also be a pull request, which could be filtered out with `not issue.pull_request` - if ( - issue.milestone - and issue.milestone.title == release_name - and "Publication of Galaxy Release" not in issue.title - ): - print(f"WARN: Blocking issue| {_issue_to_str(issue)}") - block = 1 - - sys.exit(block) - - -def _pr_to_str(pr): - if isinstance(pr, str): - return pr - return f"PR #{pr.number} ({pr.title}) {pr.html_url}" - - -def _issue_to_str(pr): - if isinstance(pr, str): - return pr - return f"Issue #{pr.number} ({pr.title}) {pr.html_url}" - - -def _next_version_params(release_name): - month = int(release_name.split(".")[1]) - year = release_name.split(".")[0] - next_month = (((month - 1) + RELEASE_DELTA_MONTHS) % 12) + 1 - next_month_name = calendar.month_name[next_month] - if next_month < RELEASE_DELTA_MONTHS: - next_year = int(year) + 1 - else: - next_year = year - next_version = "%s.%02d" % (next_year, next_month) - freeze_date, release_date = _release_dates(next_version) - return dict( - version=next_version, - year=next_year, - month_name=next_month_name, - freeze_date=freeze_date, - release_date=release_date, - ) - - -def _release_dates(version): - year, month = version.split(".") - first_of_month = datetime.date(int(year) + 2000, int(month), 1) - freeze_date = next_weekday(first_of_month, 0) - release_date = next_weekday(first_of_month, 0) + datetime.timedelta(21) - return freeze_date, release_date - - -def _get_prs(release_name, state="closed"): - github = github_client() - repo = github.get_repo(f"{PROJECT_OWNER}/{PROJECT_NAME}") - pull_requests = repo.get_pulls(state=state) - reached_old_prs = False - - for pr in pull_requests: - if reached_old_prs: - break - - if pr.created_at < datetime.datetime(2020, 5, 1, 0, 0): - reached_old_prs = True - pass - merged_at = pr.merged_at - milestone = pr.milestone - proper_state = state != "closed" or merged_at - if not proper_state or not milestone or milestone.title != release_name: - continue - yield pr - - -def main(argv, seen_prs=None): - newest_release = None - seen_prs = seen_prs or set() - - if argv[1] == "--check-blocking-prs": - check_blocking_prs(argv) - return - - if argv[1] == "--check-blocking-issues": - check_blocking_issues(argv) - return - - if argv[1] == "--create-release-issue": - release_issue(argv) - return - - if argv[1] == "--release": - do_release(argv) - return - - if argv[1] == "--check-release": - check_release(argv) - return - - if argv[1] == "--release_file": - newest_release = argv[2] - argv = [argv[0]] + argv[3:] - - if argv[1] == "--request": - req = argv[2] - argv = [argv[0]] + argv[3:] - else: - req = None - - if newest_release is None: - newest_release = sorted(os.listdir(RELEASES_PATH))[-1] - history_path = os.path.join(RELEASES_PATH, newest_release) - user_announce_path = history_path[0 : -len(".rst")] + "_announce_user.rst" - prs_path = history_path[0 : -len(".rst")] + "_prs.rst" - - history = _read_file(history_path) - user_announce = _read_file(user_announce_path) - prs_content = _read_file(prs_path) - - def extend_target(target, line, source=history): - from_str = f".. {target}\n" - if target not in source: - raise Exception(f"Failed to find target [{target}] in source [{source}]") - return source.replace(from_str, from_str + line + "\n") - - ident = argv[1] - - if requests is None: - raise Exception("Requests library not found, please pip install requests") - if len(argv) > 2: - message = argv[2] - elif not (ident.startswith("pr") or ident.startswith("issue")): - api_url = urljoin(PROJECT_API, f"commits/{ident}") - if req is None: - req = requests.get(api_url).json() - commit = req["commit"] - message = commit["message"] - message = get_first_sentence(message) - elif ident.startswith("pr"): - pull_request = ident[len("pr") :] - api_url = urljoin(PROJECT_API, f"pulls/{pull_request}") - if req is None: - req = requests.get(api_url).json() - message = req["title"] - elif ident.startswith("issue"): - issue = ident[len("issue") :] - api_url = urljoin(PROJECT_API, f"issues/{issue}") - if req is None: - req = requests.get(api_url).json() - message = req["title"] - else: - message = "" - - text_target = "to_doc" - to_doc = message + " " - - owner = None - if ident.startswith("pr"): - pull_request = ident[len("pr") :] - if pull_request in seen_prs: - to_doc = "" - else: - user = req["head"].user - owner = user.login - text = ".. _Pull Request {0}: {1}/pull/{0}".format(pull_request, PROJECT_URL) - prs_content = extend_target("github_links", text, prs_content) - to_doc += "\n(thanks to `@{} `__).".format( - owner, - owner, - ) - to_doc += f"\n`Pull Request {pull_request}`_" - labels = None - if req and "labels" in req: - labels = req["labels"] - text_target = _text_target(pull_request, labels=labels) - elif ident.startswith("issue"): - issue = ident[len("issue") :] - text = ".. _Issue {0}: {1}/issues/{0}".format(issue, PROJECT_URL) - prs_content = extend_target("github_links", text, prs_content) - to_doc += f"`Issue {issue}`_" - else: - short_rev = ident[:7] - text = ".. _{0}: {1}/commit/{0}".format(short_rev, PROJECT_URL) - prs_content = extend_target("github_links", text, prs_content) - to_doc += f"{short_rev}_" - - if to_doc: - to_doc = wrap(to_doc) - if text_target is not None: - history = extend_target(text_target, to_doc, history) - if req and req["labels"]: - labels = req["labels"] - if "area/datatypes" in labels: - user_announce = extend_target("datatypes", to_doc, user_announce) - if "area/visualizations" in labels: - user_announce = extend_target("visualizations", to_doc, user_announce) - if "area/tools" in labels: - user_announce = extend_target("tools", to_doc, user_announce) - _write_file(history_path, history) - _write_file(prs_path, prs_content) - _write_file(user_announce_path, user_announce) - - -def _read_file(path): - with open(path) as f: - return f.read() - - -def _write_file(path, contents, skip_if_exists=False): - if skip_if_exists and os.path.exists(path): - return - with open(path, "w") as f: - f.write(contents) - - -def _text_target(pull_request, labels=None, skip_merge=True): - if isinstance(pull_request, str): - pr_number = pull_request - else: - pr_number = pull_request.number - - if labels is None: - labels = [] - try: - github = github_client() - labels = github.issues.labels.list_by_issue(int(pr_number), user=PROJECT_OWNER, repo=PROJECT_NAME) - labels = [label.name.lower() for label in labels] - except Exception as e: - print(e) - is_bug = is_enhancement = is_feature = is_minor = is_major = is_merge = is_small_enhancement = False - if len(labels) == 0: - print(f"No labels found for {pr_number}") - return None - for label_name in labels: - if label_name == "minor": - is_minor = True - elif label_name == "major": - is_major = True - elif label_name == "merge": - is_merge = True - elif label_name == "kind/bug": - is_bug = True - elif label_name == "kind/feature": - is_feature = True - elif label_name == "kind/enhancement": - is_enhancement = True - elif label_name in ["kind/testing", "kind/refactoring"]: - is_small_enhancement = True - elif label_name == "procedures": - # Treat procedures as an implicit enhancement. - is_enhancement = True - - is_some_kind_of_enhancement = is_enhancement or is_feature or is_small_enhancement - - if not (is_bug or is_some_kind_of_enhancement or is_minor or is_merge): - print(f"No 'kind/*' or 'minor' or 'merge' or 'procedures' label found for {_pr_to_str(pull_request)}") - text_target = None - - if is_minor or is_merge and skip_merge: - return - - if is_some_kind_of_enhancement and is_major: - text_target = "major_feature" - elif is_feature: - text_target = "feature" - elif is_enhancement: - for label, tag in GROUPPED_TAGS.items(): - if label in labels: - text_target = f"enhancement_tag_{tag}" - break - else: - text_target = "enhancement" - elif is_some_kind_of_enhancement: - text_target = "small_enhancement" - elif is_major: - text_target = "major_bug" - elif is_bug: - for label, tag in GROUPPED_TAGS.items(): - if label in labels: - text_target = f"bug_tag_{tag}" - break - else: - text_target = "bug" - else: - print(f"Logic problem, cannot determine section for {_pr_to_str(pull_request)}") - text_target = None - if text_target: - text_target += "\n" - return text_target - - -def _pr_to_labels(pr): - labels = [label.name.lower() for label in pr.labels] - return labels - - -def _previous_release(to): - previous_release = None - for release in _releases(): - if release == to: - break - - previous_release = release - - return previous_release - - -def _releases(): - all_files = sorted(os.listdir(RELEASES_PATH)) - release_note_file_pattern = re.compile(r"\d+\.\d+.rst") - release_note_files = [f for f in all_files if release_note_file_pattern.match(f)] - return sorted(f.rstrip(".rst") for f in release_note_files) - - -def github_client() -> Github: - """Search environment for github access token and produce client object. - - Easiest thing to do is just to set an environment variable GITHUB_AUTH to - your personal access token ( - https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token - ). Alternatively, this can be placed into a json file in ~/.github.json in a - map keyed on login_or_token. - """ - auth = os.environ.get("GITHUB_AUTH") - if auth is not None: - return Github(auth) - else: - github_json_path = os.path.expanduser("~/.github.json") - if not os.path.exists(github_json_path): - return Github(None) - with open(github_json_path) as fh: - github_json_dict = json.load(fh) - return Github(**github_json_dict) - - -def _release_file(release): - releases_path = os.path.join(PROJECT_DIRECTORY, "doc", "source", "releases") - if release is None: - release = sorted(os.listdir(releases_path))[-1] - history_path = os.path.join(releases_path, release) - return history_path - - -def get_first_sentence(message): - first_line = message.split("\n")[0] - return first_line - - -def strip_release(message): - return re.sub(r"^\s*\[.*\]\s*", r"", message) - - -def process_sentence(message): - # Strip tags like [15.07]. - message = strip_release(message=message) - # Link issues and pull requests... - issue_url = f"https://github.com/{PROJECT_OWNER}/{PROJECT_NAME}/issues" - message = re.sub(r"#(\d+)", rf"`#\1 <{issue_url}/\1>`__", message) - return message - - -def wrap(message): - message = process_sentence(message) - wrapper = textwrap.TextWrapper(initial_indent="* ") - wrapper.subsequent_indent = " " - wrapper.width = 160 - message_lines = message.splitlines() - first_lines = "\n".join(wrapper.wrap(message_lines[0])) - wrapper.initial_indent = " " - rest_lines = "\n".join("\n".join(wrapper.wrap(m)) for m in message_lines[1:]) - return first_lines + ("\n" + rest_lines if rest_lines else "") - - -def next_weekday(d, weekday): - """Return the next week day (0 for Monday, 6 for Sunday) starting from ``d``.""" - days_ahead = weekday - d.weekday() - if days_ahead <= 0: # Target day already happened this week - days_ahead += 7 - return d + datetime.timedelta(days_ahead) - - -if __name__ == "__main__": - main(sys.argv)