diff --git a/.circleci/config.yml b/.circleci/config.yml index 4286abde9..1bacee3e9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -233,11 +233,6 @@ jobs: command: | python3 ${CI_SCRIPTS_DIR}/bump-part.py - - run: - name: Update Changelog entries - command: | - python3 ${CI_SCRIPTS_DIR}/update-changelog.py - # Publish a wheel and tarball of the Scenario Player to pypi. deploy-to-pypi: executor: default-executor diff --git a/.circleci/scripts/bump-part.py b/.circleci/scripts/bump-part.py index a08dd7920..abc9b30db 100755 --- a/.circleci/scripts/bump-part.py +++ b/.circleci/scripts/bump-part.py @@ -12,6 +12,7 @@ REPO_NAME, REPO_OWNER, ) +from chlogger import make_chlog from scenario_player import __version__ @@ -65,6 +66,7 @@ def get_last_tag(): part = "minor" break + print(f"Bumping part {part}..") @@ -85,6 +87,11 @@ def get_last_tag(): stdout=subprocess.PIPE, ) +if CURRENT_BRANCH == "master" and COMMIT_TYPE == "RELEASE": + r = subprocess.run(f"git --git-dir={PROJECT_GIT_DIR} describe --abbrev=0 --tags", check=True) + tag = r.stdout.decode("UTF-8").strip(" ").strip("\n") + make_chlog(Path(f"{PROJECT_ROOT}/CHANGES.rst"), new_version=tag) + print("Push Bump commit..") subprocess.run( f"git --git-dir={PROJECT_GIT_DIR} push --set-upstream origin {CURRENT_BRANCH}".split(" "), diff --git a/.circleci/scripts/chlogger.py b/.circleci/scripts/chlogger.py new file mode 100644 index 000000000..44a0cfd1a --- /dev/null +++ b/.circleci/scripts/chlogger.py @@ -0,0 +1,137 @@ +import pathlib +import re +import subprocess + +from typing import List, Tuple +from constants import PROJECT_GIT_DIR, CURRENT_BRANCH, COMMIT_PATTERN, COMMIT_TYPE + + +def read_git_commit_history_since_tag( + tag + ) -> Tuple[List[Tuple[str, str]], List[Tuple[str, str]], List[Tuple[str, str]]]: + """Return a list of all git commit titles since the given `tag`. + + If `tag` is not given, we'll use the previous tag and compare the log up + up to the current tag. + + The commits are returned as three lists: + 1. feature commits + 2. bugfix commits + 3. hotfix commits + """ + completed_process = subprocess.run( + f"git --git-dir={PROJECT_GIT_DIR} log {tag}..master --format=%s".split(" "), + check=True, + stdout=subprocess.PIPE, + ) + titles = completed_process.stdout.decode("UTF-8").split("\n") + + # The body of a commit may include newline characters, so we need to specify + # a custom separator to indicate the end of the commit body. + separator = "<><><>" + completed_process = subprocess.run( + f"git --git-dir={PROJECT_GIT_DIR} log {tag}..master --format=%b{separator}".split(" "), + check=True, + stdout=subprocess.PIPE, + ) + bodies = completed_process.stdout.decode("UTF-8").split(separator) + + assert len(titles) == len(bodies) + + pattern = re.compile(COMMIT_PATTERN) + + feats, fixes, hotfixes = [], [], [] + + for title, body in zip(titles, bodies): + match = pattern.match(title) + if not match: + continue + + commit_type = match.groupdict()["TYPE"] + + if commit_type == "FEAT": + feats.append((title, body)) + elif commit_type == "FIX": + fixes.append((title, body)) + elif commit_type == "HOTFIX": + hotfixes.append((title, body)) + else: + print(f"No type found, skipping commit '{title}'..") + + return feats, fixes, hotfixes + + +def format_commits(commits: List[Tuple[str, str]]) -> List[str]: + """Format the given commits for writing to the Changelog. + + The expected input Tuple[str, str] format is: + + ([(FEAT|FIX|HOTFIX)-#123] , ) + + The output format is as follows:: + + r'- #123 \n \n' + + Newlines in the body are honored, and each line indented by 4 spaces automatically. + TODO: Duplicate Issues should share a single Changelog Entry. + """ + if not commits: + return [] + pattern = re.compile(COMMIT_PATTERN) + formatted = set() + for title, body in commits: + match = pattern.match(title) + issue, subject = match.groupdict()["ISSUE"], match.groupdict()["SUBJECT"] + + entry = f"- {issue} {subject}\n" + + if body: + # Make sure the body is indented by 8 spaces. + formatted_body = " ".join(body.split("\n")) + entry += f"{formatted_body}\n" + formatted.add(entry) + return sorted(formatted) + + +def update_chlog( + tag: str, + feats: List[str], + fixes: List[str], + hotfixes: List[str], + chlog_path: pathlib.Path = pathlib.Path("CHANGELOG.rst"), +): + try: + history = chlog_path.read_text() + except FileNotFoundError: + print("No Changelog file found - creating a new one.") + history = "" + + chlog_entry = f"RELEASE {tag}\n=============\n\n" + + if feats: + feats = "\n".join(feats) + chlog_entry += f"Features\n--------\n{feats}\n""" + + if fixes: + fixes = "\n".join(fixes) + + chlog_entry += f"Fixes\n-----\n{fixes}\n" + + if hotfixes: + hotfixes = "\n".join(hotfixes) + + chlog_entry += f"Hotfixes\n--------\n{hotfixes}\n" + chlog_path.write_text(f"{chlog_entry}\n{history}") + + +def make_chlog(chlog_path, new_version): + feats, fixes, hotfixes = read_git_commit_history_since_tag(new_version) + update_chlog( + "0.4.0", format_commits(feats), format_commits(fixes), format_commits(hotfixes), chlog_path + ) + + subprocess.run(f"git --git-dir={PROJECT_GIT_DIR} git add CHANGELOG.rst".split(" "), check=True) + subprocess.run( + f"git --git-dir={PROJECT_GIT_DIR} git commit CHANGELOG.rst -m \"Update Changelog.\"".split(" "), + check=True + ) diff --git a/.circleci/scripts/constants.py b/.circleci/scripts/constants.py index 342c2bfca..30e6893d5 100644 --- a/.circleci/scripts/constants.py +++ b/.circleci/scripts/constants.py @@ -9,7 +9,7 @@ PROJECT_ROOT = os.environ["PROJECT_ROOT"] COMMIT_SHA = os.environ["CIRCLE_SHA1"] -COMMIT_PATTERN = r"^\[(?P(FEAT|FIX|HOTFIX))-(?P#\d+)\]\w?(?P)" +COMMIT_PATTERN = r"^\[(?P(FEAT|FIX|HOTFIX))-(?P#\d+)\]\w?(?P.*$)" RELEASE_COMMIT_PATTERN = r"^\[(?PRELEASE)\]\w?(?P)" BUMPVERSION_PREFIX = "Cut New Release:" CURRENT_BRANCH = os.environ.get("CIRCLE_BRANCH") diff --git a/.circleci/scripts/update-changelog.py b/.circleci/scripts/update-changelog.py deleted file mode 100644 index 6b92746c7..000000000 --- a/.circleci/scripts/update-changelog.py +++ /dev/null @@ -1,51 +0,0 @@ -"""TODO: This script is a stub.""" -from typing import List, Tuple -from constants import PROJECT_GIT_DIR, CURRENT_BRANCH - - -if CURRENT_BRANCH != "master": - exit(0) - - -def read_git_commit_history_since_tag(tag=None) -> Tuple[List[str], List[str], List[str]]: - """Return a list of all git commit titles since the given `tag`. - - If `tag` is not given, we'll use the previous tag and compare the log up - up to the current tag. - - The commits are returned as three lists: - feature commits - 2. bugfix commits - 3. hotfix commits - """ - completed_process = subprocess.run( - f"git --git-dir={PROJECT_GIT_DIR} log master..release --format=%s".split(" "), - check=True, - stdout=subprocess.PIPE, - ) - titles = completed_process.stdout.decode("UTF-8").split("\n") - - completed_process = subprocess.run( - f"git --git-dir={PROJECT_GIT_DIR} log master..release --format=%b".split(" "), - check=True, - stdout=subprocess.PIPE, - ) - bodies = completed_process.stdout.decode("UTF-8").split("\n") - - -def format_commits(*commits) -> List[str]: - """Format the given commits for writing to the Changelog. - - The expected input str format is: - - [(FEAT|FIX|HOTFIX)-#123] - - - - The output format is as follows:: - - - #123 - - - """ -