diff --git a/.github/labeler.yml b/.github/labeler.yml index f3003c285..0cfa6e3c6 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,7 +1,7 @@ # This file is required by the the action 'https://github.com/actions/labeler' # and used in the '.github/workflows/ci_cd_pr.yml' workflow -# -- Labels based on PR title ------------------------------------------------ +# -- Labels based on PR head branch ------------------------------------------ 'fix': - head-branch: ['fix'] diff --git a/.github/labels.yml b/.github/labels.yml index d219451c7..3aaf76964 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -79,6 +79,10 @@ description: Docker maintenance related color: 000075 +- name: 'breaking' + description: Changes that break backward compatibility + color: ff0000 + # Hacktoberfest labels ------------------------------------------------------- - name: 'hacktoberfest' diff --git a/.github/workflows/ci_cd_pr.yml b/.github/workflows/ci_cd_pr.yml index bae67d372..084742c2f 100644 --- a/.github/workflows/ci_cd_pr.yml +++ b/.github/workflows/ci_cd_pr.yml @@ -86,7 +86,7 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} - - uses: ansys/actions/doc-changelog@main + - uses: ansys/actions/doc-changelog@feat/add-breaking-change-category with: token: ${{ secrets.PYANSYS_CI_BOT_TOKEN }} use-conventional-commits: true diff --git a/doc-changelog/action.yml b/doc-changelog/action.yml index 3d5ad82ee..81286efc1 100644 --- a/doc-changelog/action.yml +++ b/doc-changelog/action.yml @@ -150,7 +150,7 @@ runs: import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import get_first_letter_case + from parse_pr import get_first_letter_case pr_title = os.environ.get("PR_TITLE") get_first_letter_case(pr_title) @@ -168,20 +168,23 @@ runs: token: ${{ inputs.token }} use-upper-case: true - - name: "Get conventional commit type from title" + - name: "Get conventional commit type" if: ${{ inputs.use-conventional-commits == 'true' }} env: PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} shell: python run: | import os import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import get_conventional_commit_type + from parse_pr import get_conventional_commit_type pr_title = os.environ.get("PR_TITLE") - get_conventional_commit_type(pr_title) + pr_body = os.environ.get("PR_BODY") + + get_conventional_commit_type(pr_title, pr_body) - name: "Get labels in the pull request" id: get-labels @@ -205,7 +208,7 @@ runs: import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import changelog_category_cc + from parse_pr import changelog_category_cc cc_type = ${{ env.CC_TYPE }} changelog_category_cc(cc_type) @@ -220,7 +223,7 @@ runs: import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import changelog_cateogry_labels + from parse_pr import changelog_cateogry_labels labels = os.environ.get("LABELS") changelog_cateogry_labels(labels) @@ -248,7 +251,7 @@ runs: import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import clean_pr_title + from parse_pr import clean_pr_title pr_title = os.environ.get("PR_TITLE") use_cc = True if os.environ.get("USE_CONVENTIONAL_COMMITS") == "true" else False @@ -294,7 +297,7 @@ runs: import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import add_towncrier_config + from parse_pr import add_towncrier_config repo_name = os.environ.get("REPO_NAME") org_name = "${{ github.repository_owner }}" @@ -311,7 +314,7 @@ runs: import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import rewrite_template + from parse_pr import rewrite_template template_status = rewrite_template(os.getenv('TEMPLATE'), os.getenv('FILENAME')) diff --git a/doc-deploy-changelog/action.yml b/doc-deploy-changelog/action.yml index 7f8e7df8a..e6dd03b17 100644 --- a/doc-deploy-changelog/action.yml +++ b/doc-deploy-changelog/action.yml @@ -303,7 +303,7 @@ runs: import sys sys.path.insert(1, '${{ github.action_path }}/../python-utils/') - from parse_pr_title import save_env_variable + from parse_pr import save_env_variable # Whether or not to format the date in the CHANGELOG file - "true" or "false" format_date = os.environ.get("FORMAT_DATE") diff --git a/doc/source/changelog/810.added.md b/doc/source/changelog/810.added.md new file mode 100644 index 000000000..f29f06cc9 --- /dev/null +++ b/doc/source/changelog/810.added.md @@ -0,0 +1 @@ +add breaking change section \ No newline at end of file diff --git a/doc/source/migrations/docs-changelog-setup.rst b/doc/source/migrations/docs-changelog-setup.rst index 5e5c545dd..dfcc389f7 100644 --- a/doc/source/migrations/docs-changelog-setup.rst +++ b/doc/source/migrations/docs-changelog-setup.rst @@ -142,6 +142,11 @@ Also, replace ``ansys..`` with the name under ``tool.flit.modu title_format = "`{version} `_ - {project_date}" issue_format = "`#{issue} `_" + [[tool.towncrier.type]] + directory = "breaking" + name = "Breaking" + showcontent = true + [[tool.towncrier.type]] directory = "added" name = "Added" @@ -241,6 +246,11 @@ Also, replace ``ansys..`` with the name under ``tool.flit.modu title_format = "## [{version}](https://github.com/ansys/{repo-name}/releases/tag/v{version}) - {project_date}" issue_format = "[#{issue}](https://github.com/ansys/{repo-name}/pull/{issue})" + [[tool.towncrier.type]] + directory = "breaking" + name = "Breaking" + showcontent = true + [[tool.towncrier.type]] directory = "added" name = "Added" diff --git a/python-utils/parse_pr_title.py b/python-utils/parse_pr.py similarity index 89% rename from python-utils/parse_pr_title.py rename to python-utils/parse_pr.py index 4df1bb07d..e5fe0c535 100644 --- a/python-utils/parse_pr_title.py +++ b/python-utils/parse_pr.py @@ -1,4 +1,5 @@ import os +import re from pathlib import Path import toml @@ -61,20 +62,67 @@ def get_first_letter_case(pr_title: str): save_env_variable("FIRST_LETTER", "uppercase") -def get_conventional_commit_type(pr_title: str): - """Get the conventional commit type from the pull request title. +def has_title_breaking_changes(pr_title: str) -> bool: + """Check if the pull request title indicates a breaking change. Parameters ---------- pr_title: str The pull request title. + + Returns + ------- + bool + True if the pull request title indicates a breaking change, False otherwise. """ - # Get the index where the first colon is found in the pull request title + colon_count = pr_title.count(":") + if colon_count != 1: + raise ValueError(f"Expected exactly one ':', found {colon_count}") + colon_index = pr_title.index(":") - # Get the conventional commit type from the pull request title (everything before the colon) - cc_type = '"' + pr_title[:colon_index] + '"' - # Save the conventional commit type as an environment variable, CC_TYPE - save_env_variable("CC_TYPE", cc_type) + exclam_index = pr_title.find("!") + + return 0 <= exclam_index < colon_index + + +def has_body_breaking_changes(pr_body: str) -> bool: + """Check if the pull request body indicates a breaking change. + + Parameters + ---------- + pr_body: str + The pull request body. + + Returns + ------- + bool + True if the pull request body indicates a breaking change, False otherwise. + """ + if not pr_body: + return False + + pattern = r"(?i)^breaking[- ]changes?:" + return any(re.match(pattern, line.strip()) for line in pr_body.splitlines()) + + +def get_conventional_commit_type(pr_title: str, pr_body: str): + """Get the conventional commit type from the pull request title. + + Parameters + ---------- + pr_title: str + The pull request title. + """ + if has_title_breaking_changes(pr_title) or has_body_breaking_changes(pr_body): + # Save the conventional commit type as an environment variable, CC_TYPE + save_env_variable("CC_TYPE", '"breaking"') + else: + # Get the index where the first colon is found in the pull request title + colon_index = pr_title.index(":") + # Get the conventional commit type from the pull request title (everything before the colon) + cc_type = '"' + pr_title[:colon_index] + '"' + # Save the conventional commit type as an environment variable, CC_TYPE + save_env_variable("CC_TYPE", cc_type) def changelog_category_cc(cc_type: str): @@ -91,6 +139,7 @@ def changelog_category_cc(cc_type: str): # Dictionary whose keys are the conventional commit type and values are # the changelog section cc_type_changelog_dict = { + "breaking": "breaking", "feat": "added", "fix": "fixed", "docs": "documentation", @@ -129,6 +178,7 @@ def changelog_cateogry_labels(labels: str): # Dictionary with the key as a label from .github/workflows/label.yml and # value as the corresponding section in the changelog pr_labels = { + "breaking": "breaking", "enhancement": "added", "bug": "fixed", "documentation": "documentation", @@ -222,13 +272,14 @@ def add_towncrier_config(org_name: str, repo_name: str, default_config: bool): exit(1) towncrier_config = pyproject_file if pyproject_file.exists() else towncrier_file - with towncrier_config.open() as file: + with towncrier_config.open(mode="r+") as file: config = toml.load(towncrier_config) tool = config.get("tool", "DNE") towncrier = tool.get("towncrier", "DNE") # List containing changelog sections under each release changelog_sections = [ + "breaking", "added", "dependencies", "documentation", diff --git a/python-utils/release_github_utils.py b/python-utils/release_github_utils.py index 4dedf0a90..ab9c2360e 100644 --- a/python-utils/release_github_utils.py +++ b/python-utils/release_github_utils.py @@ -2,7 +2,7 @@ from pathlib import Path import pypandoc -from parse_pr_title import get_towncrier_config_value, save_env_variable +from parse_pr import get_towncrier_config_value, save_env_variable """Semantic version regex as found on semver.org: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string""" diff --git a/towncrier.toml b/towncrier.toml index 0655fca53..f600cc35a 100644 --- a/towncrier.toml +++ b/towncrier.toml @@ -8,6 +8,11 @@ start_string = ".. towncrier release notes start\n" title_format = "`{version} `_ - {project_date}" issue_format = "`#{issue} `_" +[[tool.towncrier.type]] +directory = "breaking" +name = "Breaking" +showcontent = true + [[tool.towncrier.type]] directory = "added" name = "Added"