From e7020e8f1a04c5914017c3851543709ae630e043 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:04:38 -0500 Subject: [PATCH 01/16] chore(style): test bump version msg [patch candidate] --- commit_msg_version_bump/main.py | 284 +++++++++++++++++++++++++------- 1 file changed, 221 insertions(+), 63 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index 053e541..cb67cbf 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -3,121 +3,279 @@ commit_msg_version_bump.py A script to bump the version in pyproject.toml based on commit message keywords. -Handles major, minor, and patch releases. +Handles major, minor, and patch releases. Additionally, it adds icons to commit messages +depending on their type and ensures that changes are committed in a single step. Usage: - commit_msg_version_bump.py + commit_msg_version_bump.py [--log-level {INFO,DEBUG}] """ -import sys +import argparse +import logging import re import subprocess +import sys +from typing import Optional + import toml -DEBUG = False +# Mapping of commit types to changelog sections and icons +TYPE_MAPPING = { + "feat": {"section": "### Features", "icon": "✨"}, + "fix": {"section": "### Bug Fixes", "icon": "🐛"}, + "docs": {"section": "### Documentation", "icon": "📝"}, + "style": {"section": "### Styles", "icon": "💄"}, + "refactor": {"section": "### Refactors", "icon": "♻️"}, + "perf": {"section": "### Performance Improvements", "icon": "⚡️"}, + "test": {"section": "### Tests", "icon": "✅"}, + "chore": {"section": "### Chores", "icon": "🔧"}, +} +# Mapping of commit types to version bump parts +VERSION_BUMP_MAPPING = { + "feat": "minor", + "fix": "patch", + "docs": "patch", + "style": "patch", + "refactor": "patch", + "perf": "patch", + "test": "patch", + "chore": "patch", +} -def bump_version(part: str) -> None: +# Regular expressions for detecting commit types and versioning keywords +COMMIT_TYPE_REGEX = re.compile(r"^(?Pfeat|fix|docs|style|refactor|perf|test|chore)") +VERSION_KEYWORD_REGEX = re.compile( + r"\[(?Pmajor candidate|minor candidate|patch candidate)]$", re.IGNORECASE +) + + +def parse_arguments() -> argparse.Namespace: """ - Bumps the specified part of the version using bump2version and commits the change. + Parses command-line arguments. - Args: - part (str): The part of the version to bump ('major', 'minor', 'patch'). + Returns: + argparse.Namespace: Parsed arguments. + """ + parser = argparse.ArgumentParser( + description=( + "Bump the version in pyproject.toml based on commit message keywords. " + "Adds icons to commit messages depending on their type." + ) + ) + parser.add_argument( + "commit_msg_file", + type=str, + help="Path to the commit message file.", + ) + parser.add_argument( + "--log-level", + type=str, + choices=["INFO", "DEBUG"], + default="INFO", + help="Set the logging level. Default is INFO.", + ) + return parser.parse_args() - Raises: - subprocess.CalledProcessError: If bump2version or git commands fail. + +def configure_logger(log_level: str) -> None: """ - try: - subprocess.run(["bump2version", part], check=True) - print(f"Successfully bumped the {part} version.") - except subprocess.CalledProcessError: - print(f"Failed to bump the {part} version.") + Configures logging for the script. + + Args: + log_level (str): Logging level as a string (e.g., 'INFO', 'DEBUG'). + """ + numeric_level = getattr(logging, log_level.upper(), None) + if not isinstance(numeric_level, int): + print(f"Invalid log level: {log_level}") sys.exit(1) - # Retrieve the new version from pyproject.toml - new_version = get_new_version() + logging.basicConfig( + level=numeric_level, + format="%(asctime)s - %(levelname)s - %(message)s", + handlers=[logging.StreamHandler(sys.stdout)], + ) + + +def read_commit_message(commit_msg_file: str) -> str: + """ + Reads the commit message from the given file. - if DEBUG: - print(f"Target version {new_version}") + Args: + commit_msg_file (str): Path to the commit message file. - # Stage the changed pyproject.toml + Returns: + str: The commit message content. + """ try: - subprocess.run(["git", "add", "pyproject.toml"], check=True) - except subprocess.CalledProcessError: - print("Failed to stage pyproject.toml.") + with open(commit_msg_file, "r", encoding="utf-8") as file: + commit_msg = file.read().strip() + logging.debug(f"Original commit message: {commit_msg}") + return commit_msg + except FileNotFoundError: + logging.error(f"Commit message file not found: {commit_msg_file}") + sys.exit(1) + except Exception as e: + logging.error(f"Error reading commit message file: {e}") sys.exit(1) - # Commit the change + +def add_icon_to_commit_message(commit_msg: str) -> str: + """ + Adds an icon to the commit message based on its type. + + Args: + commit_msg (str): The original commit message. + + Returns: + str: The commit message with the icon prepended. + """ + match = COMMIT_TYPE_REGEX.match(commit_msg) + if match: + commit_type = match.group("type").lower() + icon = TYPE_MAPPING.get(commit_type, {}).get("icon", "") + if icon: + # Avoid adding multiple icons + if not commit_msg.startswith(icon): + new_commit_msg = f"{icon} {commit_msg}" + logging.debug(f"Updated commit message with icon: {new_commit_msg}") + return new_commit_msg + logging.debug("No matching commit type found or icon already present.") + return commit_msg + + +def determine_version_bump(commit_msg: str) -> Optional[str]: + """ + Determines the version bump part based on the commit message. + + Args: + commit_msg (str): The commit message. + + Returns: + Optional[str]: The version part to bump ('major', 'minor', 'patch') or None. + """ + match = VERSION_KEYWORD_REGEX.search(commit_msg) + if match: + keyword = match.group("keyword").lower() + if "major" in keyword: + return "major" + elif "minor" in keyword: + return "minor" + elif "patch" in keyword: + return "patch" + else: + # Fallback based on commit type + type_match = COMMIT_TYPE_REGEX.match(commit_msg) + if type_match: + commit_type = type_match.group("type").lower() + return VERSION_BUMP_MAPPING.get(commit_type) + return None + + +def bump_version(part: str) -> None: + """ + Bumps the specified part of the version using bump2version. + + Args: + part (str): The part of the version to bump ('major', 'minor', 'patch'). + + Raises: + subprocess.CalledProcessError: If bump2version fails. + """ try: - subprocess.run(["git", "commit", "-m", f"Bump {part} version to {new_version}"], check=True) - print(f"Committed the bumped {part} version to {new_version}.") - except subprocess.CalledProcessError: - print(f"Failed to commit the bumped {part} version.") + subprocess.run(["bump2version", part], check=True) + logging.info(f"Successfully bumped the {part} version.") + except subprocess.CalledProcessError as error: + logging.error(f"Failed to bump the {part} version: {error}") sys.exit(1) -def get_new_version() -> str: +def get_new_version(pyproject_path: str = "pyproject.toml") -> str: """ Retrieves the new version from pyproject.toml. + Args: + pyproject_path (str): Path to the pyproject.toml file. + Returns: str: The new version string. Raises: SystemExit: If the version cannot be retrieved. """ - pyproject_path = "pyproject.toml" try: with open(pyproject_path, "r", encoding="utf-8") as file: data = toml.load(file) version = data["tool"]["poetry"]["version"] + logging.debug(f"New version retrieved: {version}") return version - except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError): - print(f"Error: Unable to retrieve the version from {pyproject_path}.") + except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError) as e: + logging.error(f"Error retrieving the version from {pyproject_path}: {e}") sys.exit(1) -def main() -> None: - """ - Main function to parse the commit message and perform version bumping. +def stage_changes(pyproject_path: str = "pyproject.toml") -> None: """ - if DEBUG: - print(f"Sys: {sys}") + Stages the specified file for commit. - if len(sys.argv) < 2: - print("Usage: commit_msg_version_bump.py ") + Args: + pyproject_path (str): Path to the file to stage. + """ + try: + subprocess.run(["git", "add", pyproject_path], check=True) + logging.debug(f"Staged {pyproject_path} for commit.") + except subprocess.CalledProcessError as e: + logging.error(f"Failed to stage {pyproject_path}: {e}") sys.exit(1) - commit_msg_file = sys.argv[1] +def amend_commit(new_commit_msg: str) -> None: + """ + Amends the current commit with the new commit message. + + Args: + new_commit_msg (str): The new commit message. + + Raises: + subprocess.CalledProcessError: If git amend fails. + """ try: - with open(commit_msg_file, "r", encoding="utf-8") as file: - commit_msg = file.read().strip() - except FileNotFoundError: - print(f"Commit message file not found: {commit_msg_file}") + # Amend the commit with the new commit message + subprocess.run(["git", "commit", "--amend", "-m", new_commit_msg], check=True) + logging.info("Successfully amended the commit with the new version bump.") + except subprocess.CalledProcessError as e: + logging.error(f"Failed to amend the commit: {e}") sys.exit(1) - if DEBUG: - print(f"Commit message file: {commit_msg_file}") - print(f"commit_msg: {commit_msg}") - - # Define patterns for candidate types - major_pattern = re.compile(r"\bmajor candidate\b", re.IGNORECASE) - minor_pattern = re.compile(r"\bminor candidate\b", re.IGNORECASE) - patch_pattern = re.compile(r"\bpatch candidate\b", re.IGNORECASE) - - if major_pattern.search(commit_msg): - print("Major candidate release detected. Bumping major version...") - bump_version("major") - elif minor_pattern.search(commit_msg): - print("Minor candidate release detected. Bumping minor version...") - bump_version("minor") - elif patch_pattern.search(commit_msg): - print("Patch candidate release detected. Bumping patch version...") - bump_version("patch") + +def main() -> None: + """ + Main function to parse the commit message and perform version bumping and commit message enhancement. + """ + + commit_msg = read_commit_message(args.commit_msg_file) + updated_commit_msg = add_icon_to_commit_message(commit_msg) + version_bump_part = determine_version_bump(commit_msg) + + if version_bump_part: + logging.info(f"Version bump detected: {version_bump_part}") + bump_version(version_bump_part) + new_version = get_new_version() + + # Stage the updated pyproject.toml + stage_changes() + + # Optionally, you can add the new version to the commit message + # For simplicity, we'll update the commit message with the icon only + # If needed, modify this section to include the new version in the commit message + + # Amend the commit with the updated commit message + amend_commit(updated_commit_msg) else: - print("No version bump detected in commit message.") + logging.info("No version bump detected in commit message.") if __name__ == "__main__": + args = parse_arguments() + configure_logger(args.log_level) main() From 7850455cf5e200bfaa04cb9ab8e31e2905be7d00 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:07:12 -0500 Subject: [PATCH 02/16] chore(style): test bump version msg [patch candidate] --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dd805b9..99d975c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -66,7 +66,7 @@ repos: files: ^docker-compose(\.dev|\.prod)?\.yml$ - id: commit-msg-version-check name: commit-msg-version-check - entry: python commit_msg_version_bump/main.py + entry: python commit_msg_version_bump/main.py --log-level=DEBUG always_run: true language: system args: [.git/COMMIT_EDITMSG] From fae066dfd3ba46d8bea5050b5b7d8714afbfa2a9 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:12:45 -0500 Subject: [PATCH 03/16] chore(style): test bump version msg [patch candidate] --- .pre-commit-config.yaml | 1 - commit_msg_version_bump/main.py | 67 ++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 99d975c..8f465d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,7 +69,6 @@ repos: entry: python commit_msg_version_bump/main.py --log-level=DEBUG always_run: true language: system - args: [.git/COMMIT_EDITMSG] stages: [pre-push] - id: bump-year name: bump-year diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index cb67cbf..8a4ffc5 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -15,6 +15,7 @@ import re import subprocess import sys +from logging.handlers import RotatingFileHandler from typing import Optional import toml @@ -49,6 +50,9 @@ r"\[(?Pmajor candidate|minor candidate|patch candidate)]$", re.IGNORECASE ) +# Initialize the logger +logger = logging.getLogger(__name__) + def parse_arguments() -> argparse.Namespace: """ @@ -63,14 +67,8 @@ def parse_arguments() -> argparse.Namespace: "Adds icons to commit messages depending on their type." ) ) - parser.add_argument( - "commit_msg_file", - type=str, - help="Path to the commit message file.", - ) parser.add_argument( "--log-level", - type=str, choices=["INFO", "DEBUG"], default="INFO", help="Set the logging level. Default is INFO.", @@ -87,14 +85,23 @@ def configure_logger(log_level: str) -> None: """ numeric_level = getattr(logging, log_level.upper(), None) if not isinstance(numeric_level, int): - print(f"Invalid log level: {log_level}") - sys.exit(1) + raise ValueError(f"Invalid log level: {log_level}") - logging.basicConfig( - level=numeric_level, - format="%(asctime)s - %(levelname)s - %(message)s", - handlers=[logging.StreamHandler(sys.stdout)], + logger.setLevel(numeric_level) + + # Set up log rotation: max size 5MB, keep 5 backup files + file_handler = RotatingFileHandler( + "commit_msg_version_dump.log", maxBytes=5 * 1024 * 1024, backupCount=5 ) + console_handler = logging.StreamHandler() + + formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s") + file_handler.setFormatter(formatter) + console_handler.setFormatter(formatter) + + logger.handlers.clear() + logger.addHandler(file_handler) + logger.addHandler(console_handler) def read_commit_message(commit_msg_file: str) -> str: @@ -110,13 +117,13 @@ def read_commit_message(commit_msg_file: str) -> str: try: with open(commit_msg_file, "r", encoding="utf-8") as file: commit_msg = file.read().strip() - logging.debug(f"Original commit message: {commit_msg}") + logger.debug(f"Original commit message: {commit_msg}") return commit_msg except FileNotFoundError: - logging.error(f"Commit message file not found: {commit_msg_file}") + logger.error(f"Commit message file not found: {commit_msg_file}") sys.exit(1) except Exception as e: - logging.error(f"Error reading commit message file: {e}") + logger.error(f"Error reading commit message file: {e}") sys.exit(1) @@ -138,9 +145,9 @@ def add_icon_to_commit_message(commit_msg: str) -> str: # Avoid adding multiple icons if not commit_msg.startswith(icon): new_commit_msg = f"{icon} {commit_msg}" - logging.debug(f"Updated commit message with icon: {new_commit_msg}") + logger.debug(f"Updated commit message with icon: {new_commit_msg}") return new_commit_msg - logging.debug("No matching commit type found or icon already present.") + logger.debug("No matching commit type found or icon already present.") return commit_msg @@ -184,9 +191,9 @@ def bump_version(part: str) -> None: """ try: subprocess.run(["bump2version", part], check=True) - logging.info(f"Successfully bumped the {part} version.") + logger.info(f"Successfully bumped the {part} version.") except subprocess.CalledProcessError as error: - logging.error(f"Failed to bump the {part} version: {error}") + logger.error(f"Failed to bump the {part} version: {error}") sys.exit(1) @@ -207,10 +214,10 @@ def get_new_version(pyproject_path: str = "pyproject.toml") -> str: with open(pyproject_path, "r", encoding="utf-8") as file: data = toml.load(file) version = data["tool"]["poetry"]["version"] - logging.debug(f"New version retrieved: {version}") + logger.debug(f"New version retrieved: {version}") return version except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError) as e: - logging.error(f"Error retrieving the version from {pyproject_path}: {e}") + logger.error(f"Error retrieving the version from {pyproject_path}: {e}") sys.exit(1) @@ -223,9 +230,9 @@ def stage_changes(pyproject_path: str = "pyproject.toml") -> None: """ try: subprocess.run(["git", "add", pyproject_path], check=True) - logging.debug(f"Staged {pyproject_path} for commit.") + logger.debug(f"Staged {pyproject_path} for commit.") except subprocess.CalledProcessError as e: - logging.error(f"Failed to stage {pyproject_path}: {e}") + logger.error(f"Failed to stage {pyproject_path}: {e}") sys.exit(1) @@ -242,9 +249,9 @@ def amend_commit(new_commit_msg: str) -> None: try: # Amend the commit with the new commit message subprocess.run(["git", "commit", "--amend", "-m", new_commit_msg], check=True) - logging.info("Successfully amended the commit with the new version bump.") + logger.info("Successfully amended the commit with the new version bump.") except subprocess.CalledProcessError as e: - logging.error(f"Failed to amend the commit: {e}") + logger.error(f"Failed to amend the commit: {e}") sys.exit(1) @@ -253,26 +260,26 @@ def main() -> None: Main function to parse the commit message and perform version bumping and commit message enhancement. """ - commit_msg = read_commit_message(args.commit_msg_file) + commit_msg = read_commit_message(".git/COMMIT_EDITMSG") updated_commit_msg = add_icon_to_commit_message(commit_msg) version_bump_part = determine_version_bump(commit_msg) if version_bump_part: - logging.info(f"Version bump detected: {version_bump_part}") + logger.info(f"Version bump detected: {version_bump_part}") bump_version(version_bump_part) - new_version = get_new_version() + # new_version = get_new_version() # Stage the updated pyproject.toml stage_changes() - # Optionally, you can add the new version to the commit message + # Optionally, TODO: you can add the new version to the commit message # For simplicity, we'll update the commit message with the icon only # If needed, modify this section to include the new version in the commit message # Amend the commit with the updated commit message amend_commit(updated_commit_msg) else: - logging.info("No version bump detected in commit message.") + logger.info("No version bump detected in commit message.") if __name__ == "__main__": From 79a45f689da7ce481814a889961beb9143f3021a Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:18:04 -0500 Subject: [PATCH 04/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 39 ++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index 8a4ffc5..a09a937 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -12,10 +12,11 @@ import argparse import logging +import os import re +from logging.handlers import RotatingFileHandler import subprocess import sys -from logging.handlers import RotatingFileHandler from typing import Optional import toml @@ -45,7 +46,9 @@ } # Regular expressions for detecting commit types and versioning keywords -COMMIT_TYPE_REGEX = re.compile(r"^(?Pfeat|fix|docs|style|refactor|perf|test|chore)") +COMMIT_TYPE_REGEX = re.compile( + r"^(?Pfeat|fix|docs|style|refactor|perf|test|chore)", re.IGNORECASE +) VERSION_KEYWORD_REGEX = re.compile( r"\[(?Pmajor candidate|minor candidate|patch candidate)]$", re.IGNORECASE ) @@ -67,6 +70,11 @@ def parse_arguments() -> argparse.Namespace: "Adds icons to commit messages depending on their type." ) ) + parser.add_argument( + "commit_msg_file", + type=str, + help="Path to the commit message file.", + ) parser.add_argument( "--log-level", choices=["INFO", "DEBUG"], @@ -90,8 +98,8 @@ def configure_logger(log_level: str) -> None: logger.setLevel(numeric_level) # Set up log rotation: max size 5MB, keep 5 backup files - file_handler = RotatingFileHandler( - "commit_msg_version_dump.log", maxBytes=5 * 1024 * 1024, backupCount=5 + file_handler = logging.handlers.RotatingFileHandler( + "commit_msg_version_bump.log", maxBytes=5 * 1024 * 1024, backupCount=5 ) console_handler = logging.StreamHandler() @@ -99,7 +107,10 @@ def configure_logger(log_level: str) -> None: file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) - logger.handlers.clear() + # Clear existing handlers + if logger.hasHandlers(): + logger.handlers.clear() + logger.addHandler(file_handler) logger.addHandler(console_handler) @@ -259,23 +270,27 @@ def main() -> None: """ Main function to parse the commit message and perform version bumping and commit message enhancement. """ + args = parse_arguments() + configure_logger(args.log_level) + + # Pre-commit sets the commit message file as the first argument for commit-msg hooks + if len(sys.argv) < 2: + logger.error("Commit message file path not provided.") + sys.exit(1) - commit_msg = read_commit_message(".git/COMMIT_EDITMSG") + commit_msg_file = sys.argv[1] + commit_msg = read_commit_message(commit_msg_file) updated_commit_msg = add_icon_to_commit_message(commit_msg) version_bump_part = determine_version_bump(commit_msg) if version_bump_part: logger.info(f"Version bump detected: {version_bump_part}") bump_version(version_bump_part) - # new_version = get_new_version() + new_version = get_new_version() # Stage the updated pyproject.toml stage_changes() - # Optionally, TODO: you can add the new version to the commit message - # For simplicity, we'll update the commit message with the icon only - # If needed, modify this section to include the new version in the commit message - # Amend the commit with the updated commit message amend_commit(updated_commit_msg) else: @@ -283,6 +298,4 @@ def main() -> None: if __name__ == "__main__": - args = parse_arguments() - configure_logger(args.log_level) main() From d2b2d829243deca800549ae0704fb0f599d21950 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:20:13 -0500 Subject: [PATCH 05/16] chore(core): test bump commit msg version [patch candidate] --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 8f465d8..f0d4f98 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,6 +69,7 @@ repos: entry: python commit_msg_version_bump/main.py --log-level=DEBUG always_run: true language: system + args: [--commit_msg_file, .git/COMMIT_EDITMSG] stages: [pre-push] - id: bump-year name: bump-year From 98b25dd204e4198f67eb2f2ff76a2eaadd4bccf1 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:26:56 -0500 Subject: [PATCH 06/16] chore(core): test bump commit msg version [patch candidate] --- .pre-commit-config.yaml | 3 +- commit_msg_version_bump/main.py | 146 +++++++++++++++++++------------- 2 files changed, 89 insertions(+), 60 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f0d4f98..dd8f3cc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -69,7 +69,8 @@ repos: entry: python commit_msg_version_bump/main.py --log-level=DEBUG always_run: true language: system - args: [--commit_msg_file, .git/COMMIT_EDITMSG] + pass_filenames: false + # args: [--commit_msg_file, .git/COMMIT_EDITMSG] stages: [pre-push] - id: bump-year name: bump-year diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index a09a937..b5485a8 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -7,17 +7,16 @@ depending on their type and ensures that changes are committed in a single step. Usage: - commit_msg_version_bump.py [--log-level {INFO,DEBUG}] + commit_msg_version_bump.py [--log-level {INFO,DEBUG}] """ import argparse import logging -import os import re -from logging.handlers import RotatingFileHandler import subprocess import sys -from typing import Optional +from logging.handlers import RotatingFileHandler +from typing import List, Optional import toml @@ -70,11 +69,6 @@ def parse_arguments() -> argparse.Namespace: "Adds icons to commit messages depending on their type." ) ) - parser.add_argument( - "commit_msg_file", - type=str, - help="Path to the commit message file.", - ) parser.add_argument( "--log-level", choices=["INFO", "DEBUG"], @@ -98,8 +92,8 @@ def configure_logger(log_level: str) -> None: logger.setLevel(numeric_level) # Set up log rotation: max size 5MB, keep 5 backup files - file_handler = logging.handlers.RotatingFileHandler( - "commit_msg_version_bump.log", maxBytes=5 * 1024 * 1024, backupCount=5 + file_handler = RotatingFileHandler( + "changelog_sync.log", maxBytes=5 * 1024 * 1024, backupCount=5 ) console_handler = logging.StreamHandler() @@ -107,37 +101,64 @@ def configure_logger(log_level: str) -> None: file_handler.setFormatter(formatter) console_handler.setFormatter(formatter) - # Clear existing handlers - if logger.hasHandlers(): - logger.handlers.clear() - + logger.handlers.clear() logger.addHandler(file_handler) logger.addHandler(console_handler) -def read_commit_message(commit_msg_file: str) -> str: +def get_pushed_commits() -> List[str]: """ - Reads the commit message from the given file. - - Args: - commit_msg_file (str): Path to the commit message file. + Retrieves the list of commits being pushed. Returns: - str: The commit message content. + List[str]: List of commit hashes. """ try: - with open(commit_msg_file, "r", encoding="utf-8") as file: - commit_msg = file.read().strip() - logger.debug(f"Original commit message: {commit_msg}") - return commit_msg - except FileNotFoundError: - logger.error(f"Commit message file not found: {commit_msg_file}") - sys.exit(1) - except Exception as e: - logger.error(f"Error reading commit message file: {e}") + # Fetch the commits being pushed by comparing the local and remote branches + process = subprocess.run( + ["git", "rev-list", "--no-merges", "origin/master..HEAD"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + commits = process.stdout.strip().split("\n") + logging.debug(f"Commits being pushed: {commits}") + return commits if commits != [""] else [] + except subprocess.CalledProcessError as e: + logging.error(f"Error retrieving pushed commits: {e.stderr}") sys.exit(1) +def read_commit_messages(commits: List[str]) -> List[str]: + """ + Reads commit messages for the given list of commits. + + Args: + commits (List[str]): List of commit hashes. + + Returns: + List[str]: List of commit messages. + """ + commit_messages = [] + for commit in commits: + try: + process = subprocess.run( + ["git", "log", "--format=%B", "-n", "1", commit], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + message = process.stdout.strip() + logging.debug(f"Commit {commit}: {message}") + commit_messages.append(message) + except subprocess.CalledProcessError as e: + logging.error(f"Error reading commit {commit}: {e.stderr}") + sys.exit(1) + return commit_messages + + def add_icon_to_commit_message(commit_msg: str) -> str: """ Adds an icon to the commit message based on its type. @@ -156,9 +177,9 @@ def add_icon_to_commit_message(commit_msg: str) -> str: # Avoid adding multiple icons if not commit_msg.startswith(icon): new_commit_msg = f"{icon} {commit_msg}" - logger.debug(f"Updated commit message with icon: {new_commit_msg}") + logging.debug(f"Updated commit message with icon: {new_commit_msg}") return new_commit_msg - logger.debug("No matching commit type found or icon already present.") + logging.debug("No matching commit type found or icon already present.") return commit_msg @@ -202,9 +223,9 @@ def bump_version(part: str) -> None: """ try: subprocess.run(["bump2version", part], check=True) - logger.info(f"Successfully bumped the {part} version.") + logging.info(f"Successfully bumped the {part} version.") except subprocess.CalledProcessError as error: - logger.error(f"Failed to bump the {part} version: {error}") + logging.error(f"Failed to bump the {part} version: {error}") sys.exit(1) @@ -225,10 +246,10 @@ def get_new_version(pyproject_path: str = "pyproject.toml") -> str: with open(pyproject_path, "r", encoding="utf-8") as file: data = toml.load(file) version = data["tool"]["poetry"]["version"] - logger.debug(f"New version retrieved: {version}") + logging.debug(f"New version retrieved: {version}") return version except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError) as e: - logger.error(f"Error retrieving the version from {pyproject_path}: {e}") + logging.error(f"Error retrieving the version from {pyproject_path}: {e}") sys.exit(1) @@ -241,9 +262,9 @@ def stage_changes(pyproject_path: str = "pyproject.toml") -> None: """ try: subprocess.run(["git", "add", pyproject_path], check=True) - logger.debug(f"Staged {pyproject_path} for commit.") + logging.debug(f"Staged {pyproject_path} for commit.") except subprocess.CalledProcessError as e: - logger.error(f"Failed to stage {pyproject_path}: {e}") + logging.error(f"Failed to stage {pyproject_path}: {e}") sys.exit(1) @@ -260,41 +281,48 @@ def amend_commit(new_commit_msg: str) -> None: try: # Amend the commit with the new commit message subprocess.run(["git", "commit", "--amend", "-m", new_commit_msg], check=True) - logger.info("Successfully amended the commit with the new version bump.") + logging.info("Successfully amended the commit with the new version bump.") except subprocess.CalledProcessError as e: - logger.error(f"Failed to amend the commit: {e}") + logging.error(f"Failed to amend the commit: {e}") sys.exit(1) def main() -> None: """ - Main function to parse the commit message and perform version bumping and commit message enhancement. + Main function to parse commit messages and perform version bumping and commit message enhancement. """ args = parse_arguments() configure_logger(args.log_level) - # Pre-commit sets the commit message file as the first argument for commit-msg hooks - if len(sys.argv) < 2: - logger.error("Commit message file path not provided.") - sys.exit(1) + # Retrieve commits being pushed + pushed_commits = get_pushed_commits() + if not pushed_commits: + logging.info("No new commits to process.") + return - commit_msg_file = sys.argv[1] - commit_msg = read_commit_message(commit_msg_file) - updated_commit_msg = add_icon_to_commit_message(commit_msg) - version_bump_part = determine_version_bump(commit_msg) + # Read commit messages + commit_messages = read_commit_messages(pushed_commits) - if version_bump_part: - logger.info(f"Version bump detected: {version_bump_part}") - bump_version(version_bump_part) - new_version = get_new_version() + # Process each commit message + for commit_msg in commit_messages: + updated_commit_msg = add_icon_to_commit_message(commit_msg) + version_bump_part = determine_version_bump(commit_msg) - # Stage the updated pyproject.toml - stage_changes() + if version_bump_part: + logging.info(f"Version bump detected: {version_bump_part}") + bump_version(version_bump_part) + # new_version = get_new_version() - # Amend the commit with the updated commit message - amend_commit(updated_commit_msg) - else: - logger.info("No version bump detected in commit message.") + # Stage the updated pyproject.toml + stage_changes() + + # Amend the latest commit with the updated commit message + amend_commit(updated_commit_msg) + + # Since we've amended the commit, only one commit needs to be processed + break + else: + logging.info("No version bump detected in commit message.") if __name__ == "__main__": From 7208cedfd5bbcb046344522bdf790129206d0ede Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:29:14 -0500 Subject: [PATCH 07/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index b5485a8..dc5501c 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -123,10 +123,10 @@ def get_pushed_commits() -> List[str]: text=True, ) commits = process.stdout.strip().split("\n") - logging.debug(f"Commits being pushed: {commits}") + logger.debug(f"Commits being pushed: {commits}") return commits if commits != [""] else [] except subprocess.CalledProcessError as e: - logging.error(f"Error retrieving pushed commits: {e.stderr}") + logger.error(f"Error retrieving pushed commits: {e.stderr}") sys.exit(1) @@ -151,10 +151,10 @@ def read_commit_messages(commits: List[str]) -> List[str]: text=True, ) message = process.stdout.strip() - logging.debug(f"Commit {commit}: {message}") + logger.debug(f"Commit {commit}: {message}") commit_messages.append(message) except subprocess.CalledProcessError as e: - logging.error(f"Error reading commit {commit}: {e.stderr}") + logger.error(f"Error reading commit {commit}: {e.stderr}") sys.exit(1) return commit_messages @@ -177,9 +177,9 @@ def add_icon_to_commit_message(commit_msg: str) -> str: # Avoid adding multiple icons if not commit_msg.startswith(icon): new_commit_msg = f"{icon} {commit_msg}" - logging.debug(f"Updated commit message with icon: {new_commit_msg}") + logger.debug(f"Updated commit message with icon: {new_commit_msg}") return new_commit_msg - logging.debug("No matching commit type found or icon already present.") + logger.debug("No matching commit type found or icon already present.") return commit_msg @@ -223,9 +223,9 @@ def bump_version(part: str) -> None: """ try: subprocess.run(["bump2version", part], check=True) - logging.info(f"Successfully bumped the {part} version.") + logger.info(f"Successfully bumped the {part} version.") except subprocess.CalledProcessError as error: - logging.error(f"Failed to bump the {part} version: {error}") + logger.error(f"Failed to bump the {part} version: {error}") sys.exit(1) @@ -246,10 +246,10 @@ def get_new_version(pyproject_path: str = "pyproject.toml") -> str: with open(pyproject_path, "r", encoding="utf-8") as file: data = toml.load(file) version = data["tool"]["poetry"]["version"] - logging.debug(f"New version retrieved: {version}") + logger.debug(f"New version retrieved: {version}") return version except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError) as e: - logging.error(f"Error retrieving the version from {pyproject_path}: {e}") + logger.error(f"Error retrieving the version from {pyproject_path}: {e}") sys.exit(1) @@ -262,9 +262,9 @@ def stage_changes(pyproject_path: str = "pyproject.toml") -> None: """ try: subprocess.run(["git", "add", pyproject_path], check=True) - logging.debug(f"Staged {pyproject_path} for commit.") + logger.debug(f"Staged {pyproject_path} for commit.") except subprocess.CalledProcessError as e: - logging.error(f"Failed to stage {pyproject_path}: {e}") + logger.error(f"Failed to stage {pyproject_path}: {e}") sys.exit(1) @@ -281,9 +281,9 @@ def amend_commit(new_commit_msg: str) -> None: try: # Amend the commit with the new commit message subprocess.run(["git", "commit", "--amend", "-m", new_commit_msg], check=True) - logging.info("Successfully amended the commit with the new version bump.") + logger.info("Successfully amended the commit with the new version bump.") except subprocess.CalledProcessError as e: - logging.error(f"Failed to amend the commit: {e}") + logger.error(f"Failed to amend the commit: {e}") sys.exit(1) @@ -297,7 +297,7 @@ def main() -> None: # Retrieve commits being pushed pushed_commits = get_pushed_commits() if not pushed_commits: - logging.info("No new commits to process.") + logger.info("No new commits to process.") return # Read commit messages @@ -309,7 +309,7 @@ def main() -> None: version_bump_part = determine_version_bump(commit_msg) if version_bump_part: - logging.info(f"Version bump detected: {version_bump_part}") + logger.info(f"Version bump detected: {version_bump_part}") bump_version(version_bump_part) # new_version = get_new_version() @@ -322,7 +322,7 @@ def main() -> None: # Since we've amended the commit, only one commit needs to be processed break else: - logging.info("No version bump detected in commit message.") + logger.info("No version bump detected in commit message.") if __name__ == "__main__": From 71e4fdb368eda24d36848ed9921e5085f54f3910 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:30:25 -0500 Subject: [PATCH 08/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index dc5501c..550275c 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -93,7 +93,7 @@ def configure_logger(log_level: str) -> None: # Set up log rotation: max size 5MB, keep 5 backup files file_handler = RotatingFileHandler( - "changelog_sync.log", maxBytes=5 * 1024 * 1024, backupCount=5 + "commit_msg_version.log", maxBytes=5 * 1024 * 1024, backupCount=5 ) console_handler = logging.StreamHandler() From 209f18dc5b1f04945874737164528e0a38c4efe4 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:38:29 -0500 Subject: [PATCH 09/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 132 +++++++++++++++++++++++--------- 1 file changed, 97 insertions(+), 35 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index 550275c..0501c4a 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -106,27 +106,81 @@ def configure_logger(log_level: str) -> None: logger.addHandler(console_handler) -def get_pushed_commits() -> List[str]: +def get_pushed_refs() -> List[str]: """ - Retrieves the list of commits being pushed. + Retrieves the list of refs being pushed. Returns: - List[str]: List of commit hashes. + List[str]: List of refs being pushed. """ + refs = [] try: - # Fetch the commits being pushed by comparing the local and remote branches - process = subprocess.run( - ["git", "rev-list", "--no-merges", "origin/master..HEAD"], + # Read from stdin the refs being pushed + for line in sys.stdin: + parts = line.strip().split() + if len(parts) >= 2: + local_ref, local_sha = parts[0], parts[1] + refs.append(local_ref) + logging.debug(f"Refs being pushed: {refs}") + return refs + except Exception as e: + logging.error(f"Error reading refs from stdin: {e}") + sys.exit(1) + + +def get_upstream_branch(local_ref: str) -> Optional[str]: + """ + Retrieves the upstream branch for a given local ref. + + Args: + local_ref (str): The local ref being pushed. + + Returns: + Optional[str]: The upstream branch name or None if not found. + """ + try: + upstream = subprocess.run( + ["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, + ).stdout.strip() + logger.debug(f"Upstream branch for {local_ref}: {upstream}") + return upstream + except subprocess.CalledProcessError as e: + logger.error(f"Error retrieving upstream branch for {local_ref}: {e.stderr}") + return None + + +def get_commits_being_pushed(local_ref: str, remote_ref: str) -> List[str]: + """ + Retrieves the list of commit hashes being pushed for a given ref. + + Args: + local_ref (str): The local ref being pushed. + remote_ref (str): The remote ref being pushed to. + + Returns: + List[str]: List of commit hashes being pushed. + """ + try: + commits = ( + subprocess.run( + ["git", "rev-list", "--no-merges", f"{remote_ref}..{local_ref}"], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + .stdout.strip() + .split("\n") ) - commits = process.stdout.strip().split("\n") - logger.debug(f"Commits being pushed: {commits}") - return commits if commits != [""] else [] + commits = [commit for commit in commits if commit] + logger.debug(f"Commits being pushed for {local_ref}..{remote_ref}: {commits}") + return commits except subprocess.CalledProcessError as e: - logger.error(f"Error retrieving pushed commits: {e.stderr}") + logger.error(f"Error retrieving commits for {local_ref}..{remote_ref}: {e.stderr}") sys.exit(1) @@ -143,14 +197,13 @@ def read_commit_messages(commits: List[str]) -> List[str]: commit_messages = [] for commit in commits: try: - process = subprocess.run( + message = subprocess.run( ["git", "log", "--format=%B", "-n", "1", commit], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, - ) - message = process.stdout.strip() + ).stdout.strip() logger.debug(f"Commit {commit}: {message}") commit_messages.append(message) except subprocess.CalledProcessError as e: @@ -294,35 +347,44 @@ def main() -> None: args = parse_arguments() configure_logger(args.log_level) - # Retrieve commits being pushed - pushed_commits = get_pushed_commits() - if not pushed_commits: - logger.info("No new commits to process.") + # Retrieve refs being pushed from stdin + pushed_refs = get_pushed_refs() + if not pushed_refs: + logger.info("No refs being pushed.") return - # Read commit messages - commit_messages = read_commit_messages(pushed_commits) + for local_ref in pushed_refs: + upstream_ref = get_upstream_branch(local_ref) + if not upstream_ref: + logger.warning(f"No upstream branch found for {local_ref}. Skipping.") + continue + + commits = get_commits_being_pushed(local_ref, upstream_ref) + if not commits: + logger.info(f"No new commits to process for {local_ref}.") + continue + + commit_messages = read_commit_messages(commits) - # Process each commit message - for commit_msg in commit_messages: - updated_commit_msg = add_icon_to_commit_message(commit_msg) - version_bump_part = determine_version_bump(commit_msg) + for commit_msg in commit_messages: + updated_commit_msg = add_icon_to_commit_message(commit_msg) + version_bump_part = determine_version_bump(commit_msg) - if version_bump_part: - logger.info(f"Version bump detected: {version_bump_part}") - bump_version(version_bump_part) - # new_version = get_new_version() + if version_bump_part: + logger.info(f"Version bump detected: {version_bump_part}") + bump_version(version_bump_part) + # new_version = get_new_version() - # Stage the updated pyproject.toml - stage_changes() + # Stage the updated pyproject.toml + stage_changes() - # Amend the latest commit with the updated commit message - amend_commit(updated_commit_msg) + # Amend the latest commit with the updated commit message + amend_commit(updated_commit_msg) - # Since we've amended the commit, only one commit needs to be processed - break - else: - logger.info("No version bump detected in commit message.") + # After bumping and amending, stop processing further commits to avoid multiple bumps + break + else: + logger.info("No version bump detected in commit message.") if __name__ == "__main__": From 029c5c9e247d6b3ffe30490c26bc7faf3f243265 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:48:26 -0500 Subject: [PATCH 10/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index 0501c4a..b85d46d 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -128,12 +128,9 @@ def get_pushed_refs() -> List[str]: sys.exit(1) -def get_upstream_branch(local_ref: str) -> Optional[str]: +def get_upstream_branch() -> Optional[str]: """ - Retrieves the upstream branch for a given local ref. - - Args: - local_ref (str): The local ref being pushed. + Retrieves the upstream branch for the current branch. Returns: Optional[str]: The upstream branch name or None if not found. @@ -146,10 +143,10 @@ def get_upstream_branch(local_ref: str) -> Optional[str]: stderr=subprocess.PIPE, text=True, ).stdout.strip() - logger.debug(f"Upstream branch for {local_ref}: {upstream}") + logger.debug(f"Upstream branch: {upstream}") return upstream except subprocess.CalledProcessError as e: - logger.error(f"Error retrieving upstream branch for {local_ref}: {e.stderr}") + logger.error(f"Error retrieving upstream branch: {e.stderr}") return None @@ -353,12 +350,12 @@ def main() -> None: logger.info("No refs being pushed.") return - for local_ref in pushed_refs: - upstream_ref = get_upstream_branch(local_ref) - if not upstream_ref: - logger.warning(f"No upstream branch found for {local_ref}. Skipping.") - continue + upstream_ref = get_upstream_branch() + if not upstream_ref: + logger.error("No upstream branch found. Aborting.") + sys.exit(1) + for local_ref in pushed_refs: commits = get_commits_being_pushed(local_ref, upstream_ref) if not commits: logger.info(f"No new commits to process for {local_ref}.") From 42796794296368a69a6afa68105e283fed017c12 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 10:52:47 -0500 Subject: [PATCH 11/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 50 +++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index b85d46d..b058d78 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -16,7 +16,7 @@ import subprocess import sys from logging.handlers import RotatingFileHandler -from typing import List, Optional +from typing import List, Optional, Tuple import toml @@ -106,25 +106,25 @@ def configure_logger(log_level: str) -> None: logger.addHandler(console_handler) -def get_pushed_refs() -> List[str]: +def get_pushed_refs() -> List[Tuple[str, str]]: """ Retrieves the list of refs being pushed. Returns: - List[str]: List of refs being pushed. + List[Tuple[str, str]]: List of tuples containing local_ref and remote_sha. """ refs = [] try: # Read from stdin the refs being pushed for line in sys.stdin: parts = line.strip().split() - if len(parts) >= 2: - local_ref, local_sha = parts[0], parts[1] - refs.append(local_ref) - logging.debug(f"Refs being pushed: {refs}") + if len(parts) >= 4: + local_ref, local_sha, remote_ref, remote_sha = parts[:4] + refs.append((local_ref, remote_sha)) + logger.debug(f"Refs being pushed: {refs}") return refs except Exception as e: - logging.error(f"Error reading refs from stdin: {e}") + logger.error(f"Error reading refs from stdin: {e}") sys.exit(1) @@ -150,13 +150,13 @@ def get_upstream_branch() -> Optional[str]: return None -def get_commits_being_pushed(local_ref: str, remote_ref: str) -> List[str]: +def get_commits_being_pushed(remote_sha: str, local_sha: str) -> List[str]: """ - Retrieves the list of commit hashes being pushed for a given ref. + Retrieves the list of commit hashes being pushed for a given ref range. Args: - local_ref (str): The local ref being pushed. - remote_ref (str): The remote ref being pushed to. + remote_sha (str): The remote SHA before the push. + local_sha (str): The local SHA being pushed. Returns: List[str]: List of commit hashes being pushed. @@ -164,7 +164,7 @@ def get_commits_being_pushed(local_ref: str, remote_ref: str) -> List[str]: try: commits = ( subprocess.run( - ["git", "rev-list", "--no-merges", f"{remote_ref}..{local_ref}"], + ["git", "rev-list", "--no-merges", f"{remote_sha}..{local_sha}"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, @@ -174,10 +174,10 @@ def get_commits_being_pushed(local_ref: str, remote_ref: str) -> List[str]: .split("\n") ) commits = [commit for commit in commits if commit] - logger.debug(f"Commits being pushed for {local_ref}..{remote_ref}: {commits}") + logger.debug(f"Commits being pushed for {remote_sha}..{local_sha}: {commits}") return commits except subprocess.CalledProcessError as e: - logger.error(f"Error retrieving commits for {local_ref}..{remote_ref}: {e.stderr}") + logger.error(f"Error retrieving commits for {remote_sha}..{local_sha}: {e.stderr}") sys.exit(1) @@ -350,13 +350,21 @@ def main() -> None: logger.info("No refs being pushed.") return - upstream_ref = get_upstream_branch() - if not upstream_ref: - logger.error("No upstream branch found. Aborting.") - sys.exit(1) + for local_ref, remote_sha in pushed_refs: + try: + local_sha = subprocess.run( + ["git", "rev-parse", local_ref], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ).stdout.strip() + logger.debug(f"Local SHA for {local_ref}: {local_sha}") + except subprocess.CalledProcessError as e: + logger.error(f"Error retrieving local SHA for {local_ref}: {e.stderr}") + continue - for local_ref in pushed_refs: - commits = get_commits_being_pushed(local_ref, upstream_ref) + commits = get_commits_being_pushed(remote_sha, local_sha) if not commits: logger.info(f"No new commits to process for {local_ref}.") continue From 237b3c10dcbf6e280ef54fdbae341386f1b6bdb1 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 11:09:26 -0500 Subject: [PATCH 12/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 224 +++++++------------------------- 1 file changed, 48 insertions(+), 176 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index b058d78..e6684bb 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -2,9 +2,8 @@ """ commit_msg_version_bump.py -A script to bump the version in pyproject.toml based on commit message keywords. -Handles major, minor, and patch releases. Additionally, it adds icons to commit messages -depending on their type and ensures that changes are committed in a single step. +A script to bump the version in pyproject.toml based on the latest commit message. +Adds icons to commit messages depending on their type and ensures that changes are committed in a single step. Usage: commit_msg_version_bump.py [--log-level {INFO,DEBUG}] @@ -16,20 +15,19 @@ import subprocess import sys from logging.handlers import RotatingFileHandler -from typing import List, Optional, Tuple +from typing import Optional -import toml -# Mapping of commit types to changelog sections and icons +# Mapping of commit types to icons TYPE_MAPPING = { - "feat": {"section": "### Features", "icon": "✨"}, - "fix": {"section": "### Bug Fixes", "icon": "🐛"}, - "docs": {"section": "### Documentation", "icon": "📝"}, - "style": {"section": "### Styles", "icon": "💄"}, - "refactor": {"section": "### Refactors", "icon": "♻️"}, - "perf": {"section": "### Performance Improvements", "icon": "⚡️"}, - "test": {"section": "### Tests", "icon": "✅"}, - "chore": {"section": "### Chores", "icon": "🔧"}, + "feat": "✨", + "fix": "🐛", + "docs": "📝", + "style": "💄", + "refactor": "♻️", + "perf": "⚡️", + "test": "✅", + "chore": "🔧", } # Mapping of commit types to version bump parts @@ -65,7 +63,7 @@ def parse_arguments() -> argparse.Namespace: """ parser = argparse.ArgumentParser( description=( - "Bump the version in pyproject.toml based on commit message keywords. " + "Bump the version in pyproject.toml based on the latest commit message. " "Adds icons to commit messages depending on their type." ) ) @@ -106,109 +104,28 @@ def configure_logger(log_level: str) -> None: logger.addHandler(console_handler) -def get_pushed_refs() -> List[Tuple[str, str]]: +def get_latest_commit_message() -> str: """ - Retrieves the list of refs being pushed. + Retrieves the latest commit message. Returns: - List[Tuple[str, str]]: List of tuples containing local_ref and remote_sha. + str: The latest commit message. """ - refs = [] try: - # Read from stdin the refs being pushed - for line in sys.stdin: - parts = line.strip().split() - if len(parts) >= 4: - local_ref, local_sha, remote_ref, remote_sha = parts[:4] - refs.append((local_ref, remote_sha)) - logger.debug(f"Refs being pushed: {refs}") - return refs - except Exception as e: - logger.error(f"Error reading refs from stdin: {e}") - sys.exit(1) - - -def get_upstream_branch() -> Optional[str]: - """ - Retrieves the upstream branch for the current branch. - - Returns: - Optional[str]: The upstream branch name or None if not found. - """ - try: - upstream = subprocess.run( - ["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], + message = subprocess.run( + ["git", "log", "-1", "--pretty=%B"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ).stdout.strip() - logger.debug(f"Upstream branch: {upstream}") - return upstream + logger.debug(f"Latest commit message: {message}") + return message except subprocess.CalledProcessError as e: - logger.error(f"Error retrieving upstream branch: {e.stderr}") - return None - - -def get_commits_being_pushed(remote_sha: str, local_sha: str) -> List[str]: - """ - Retrieves the list of commit hashes being pushed for a given ref range. - - Args: - remote_sha (str): The remote SHA before the push. - local_sha (str): The local SHA being pushed. - - Returns: - List[str]: List of commit hashes being pushed. - """ - try: - commits = ( - subprocess.run( - ["git", "rev-list", "--no-merges", f"{remote_sha}..{local_sha}"], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ) - .stdout.strip() - .split("\n") - ) - commits = [commit for commit in commits if commit] - logger.debug(f"Commits being pushed for {remote_sha}..{local_sha}: {commits}") - return commits - except subprocess.CalledProcessError as e: - logger.error(f"Error retrieving commits for {remote_sha}..{local_sha}: {e.stderr}") + logger.error(f"Error retrieving latest commit message: {e.stderr}") sys.exit(1) -def read_commit_messages(commits: List[str]) -> List[str]: - """ - Reads commit messages for the given list of commits. - - Args: - commits (List[str]): List of commit hashes. - - Returns: - List[str]: List of commit messages. - """ - commit_messages = [] - for commit in commits: - try: - message = subprocess.run( - ["git", "log", "--format=%B", "-n", "1", commit], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ).stdout.strip() - logger.debug(f"Commit {commit}: {message}") - commit_messages.append(message) - except subprocess.CalledProcessError as e: - logger.error(f"Error reading commit {commit}: {e.stderr}") - sys.exit(1) - return commit_messages - - def add_icon_to_commit_message(commit_msg: str) -> str: """ Adds an icon to the commit message based on its type. @@ -222,7 +139,7 @@ def add_icon_to_commit_message(commit_msg: str) -> str: match = COMMIT_TYPE_REGEX.match(commit_msg) if match: commit_type = match.group("type").lower() - icon = TYPE_MAPPING.get(commit_type, {}).get("icon", "") + icon = TYPE_MAPPING.get(commit_type, "") if icon: # Avoid adding multiple icons if not commit_msg.startswith(icon): @@ -279,30 +196,6 @@ def bump_version(part: str) -> None: sys.exit(1) -def get_new_version(pyproject_path: str = "pyproject.toml") -> str: - """ - Retrieves the new version from pyproject.toml. - - Args: - pyproject_path (str): Path to the pyproject.toml file. - - Returns: - str: The new version string. - - Raises: - SystemExit: If the version cannot be retrieved. - """ - try: - with open(pyproject_path, "r", encoding="utf-8") as file: - data = toml.load(file) - version = data["tool"]["poetry"]["version"] - logger.debug(f"New version retrieved: {version}") - return version - except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError) as e: - logger.error(f"Error retrieving the version from {pyproject_path}: {e}") - sys.exit(1) - - def stage_changes(pyproject_path: str = "pyproject.toml") -> None: """ Stages the specified file for commit. @@ -332,6 +225,9 @@ def amend_commit(new_commit_msg: str) -> None: # Amend the commit with the new commit message subprocess.run(["git", "commit", "--amend", "-m", new_commit_msg], check=True) logger.info("Successfully amended the commit with the new version bump.") + logger.info( + "Please perform a force push using 'git push --force' to update the remote repository." + ) except subprocess.CalledProcessError as e: logger.error(f"Failed to amend the commit: {e}") sys.exit(1) @@ -339,57 +235,33 @@ def amend_commit(new_commit_msg: str) -> None: def main() -> None: """ - Main function to parse commit messages and perform version bumping and commit message enhancement. + Main function to parse the latest commit message, add an icon, and perform version bumping. """ args = parse_arguments() configure_logger(args.log_level) - # Retrieve refs being pushed from stdin - pushed_refs = get_pushed_refs() - if not pushed_refs: - logger.info("No refs being pushed.") - return - - for local_ref, remote_sha in pushed_refs: - try: - local_sha = subprocess.run( - ["git", "rev-parse", local_ref], - check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - ).stdout.strip() - logger.debug(f"Local SHA for {local_ref}: {local_sha}") - except subprocess.CalledProcessError as e: - logger.error(f"Error retrieving local SHA for {local_ref}: {e.stderr}") - continue - - commits = get_commits_being_pushed(remote_sha, local_sha) - if not commits: - logger.info(f"No new commits to process for {local_ref}.") - continue - - commit_messages = read_commit_messages(commits) - - for commit_msg in commit_messages: - updated_commit_msg = add_icon_to_commit_message(commit_msg) - version_bump_part = determine_version_bump(commit_msg) - - if version_bump_part: - logger.info(f"Version bump detected: {version_bump_part}") - bump_version(version_bump_part) - # new_version = get_new_version() - - # Stage the updated pyproject.toml - stage_changes() - - # Amend the latest commit with the updated commit message - amend_commit(updated_commit_msg) - - # After bumping and amending, stop processing further commits to avoid multiple bumps - break - else: - logger.info("No version bump detected in commit message.") + latest_commit_msg = get_latest_commit_message() + + updated_commit_msg = add_icon_to_commit_message(latest_commit_msg) + + version_bump_part = determine_version_bump(latest_commit_msg) + + if version_bump_part: + logger.info(f"Version bump detected: {version_bump_part}") + bump_version(version_bump_part) + + # Stage the updated pyproject.toml + stage_changes() + + # Amend the commit with the updated message + amend_commit(updated_commit_msg) + + logger.info( + "Aborting the current push. Please perform a force push using 'git push --force'." + ) + sys.exit(1) + else: + logger.info("No version bump detected in commit message.") if __name__ == "__main__": From d3f808ed24617bd793264c31b1aaaaf6269ce1e6 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 11:09:48 -0500 Subject: [PATCH 13/16] =?UTF-8?q?=F0=9F=94=A7=20chore(core):=20test=20bump?= =?UTF-8?q?=20commit=20msg=20version=20[patch=20candidate]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a98f8ea..a4f78fa 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.10 +current_version = 1.0.11 commit = True tag = False diff --git a/pyproject.toml b/pyproject.toml index 66110ce..3181b1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scripts" -version = "1.0.10" +version = "1.0.11" description = "CICD Core Scripts" authors = ["B "] license = "Apache 2.0" @@ -71,5 +71,5 @@ ensure_newline_before_comments = true rcfile = ".pylintrc" [build-system] -requires = ["poetry-core>=1.0.10"] +requires = ["poetry-core>=1.0.11"] build-backend = "poetry.core.masonry.api" From 938fdcdca2a9763257be1f214a96e9cb752ff213 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 11:11:01 -0500 Subject: [PATCH 14/16] =?UTF-8?q?=C3=B0=C5=B8=E2=80=9D=C2=A7=20chore(core)?= =?UTF-8?q?:=20test=20bump=20commit=20msg=20version=20[patch=20candidate]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index a4f78fa..3cee761 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.11 +current_version = 1.0.12 commit = True tag = False diff --git a/pyproject.toml b/pyproject.toml index 3181b1d..5c46c7a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scripts" -version = "1.0.11" +version = "1.0.12" description = "CICD Core Scripts" authors = ["B "] license = "Apache 2.0" @@ -71,5 +71,5 @@ ensure_newline_before_comments = true rcfile = ".pylintrc" [build-system] -requires = ["poetry-core>=1.0.11"] +requires = ["poetry-core>=1.0.12"] build-backend = "poetry.core.masonry.api" From d3e1148a45f75284825e5879e6d75c698e147daf Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 11:22:57 -0500 Subject: [PATCH 15/16] chore(core): test bump commit msg version [patch candidate] --- commit_msg_version_bump/main.py | 134 ++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 40 deletions(-) diff --git a/commit_msg_version_bump/main.py b/commit_msg_version_bump/main.py index e6684bb..e8f2919 100644 --- a/commit_msg_version_bump/main.py +++ b/commit_msg_version_bump/main.py @@ -3,7 +3,8 @@ commit_msg_version_bump.py A script to bump the version in pyproject.toml based on the latest commit message. -Adds icons to commit messages depending on their type and ensures that changes are committed in a single step. +Changes the commit message to include the version bump and adds an icon. +Ensures that changes are committed in a single step. Usage: commit_msg_version_bump.py [--log-level {INFO,DEBUG}] @@ -17,6 +18,7 @@ from logging.handlers import RotatingFileHandler from typing import Optional +import toml # Mapping of commit types to icons TYPE_MAPPING = { @@ -126,56 +128,66 @@ def get_latest_commit_message() -> str: sys.exit(1) -def add_icon_to_commit_message(commit_msg: str) -> str: +def get_current_version(pyproject_path: str = "pyproject.toml") -> str: """ - Adds an icon to the commit message based on its type. + Retrieves the current version from pyproject.toml. Args: - commit_msg (str): The original commit message. + pyproject_path (str): Path to the pyproject.toml file. Returns: - str: The commit message with the icon prepended. + str: The current version string. """ - match = COMMIT_TYPE_REGEX.match(commit_msg) - if match: - commit_type = match.group("type").lower() - icon = TYPE_MAPPING.get(commit_type, "") - if icon: - # Avoid adding multiple icons - if not commit_msg.startswith(icon): - new_commit_msg = f"{icon} {commit_msg}" - logger.debug(f"Updated commit message with icon: {new_commit_msg}") - return new_commit_msg - logger.debug("No matching commit type found or icon already present.") - return commit_msg + try: + with open(pyproject_path, "r", encoding="utf-8") as file: + data = toml.load(file) + version = data["tool"]["poetry"]["version"] + logger.debug(f"Current version: {version}") + return version + except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError) as e: + logger.error(f"Error retrieving the version from {pyproject_path}: {e}") + sys.exit(1) -def determine_version_bump(commit_msg: str) -> Optional[str]: +def get_new_version(pyproject_path: str = "pyproject.toml") -> str: """ - Determines the version bump part based on the commit message. + Retrieves the new version from pyproject.toml after bump. Args: - commit_msg (str): The commit message. + pyproject_path (str): Path to the pyproject.toml file. Returns: - Optional[str]: The version part to bump ('major', 'minor', 'patch') or None. + str: The new version string. """ - match = VERSION_KEYWORD_REGEX.search(commit_msg) - if match: - keyword = match.group("keyword").lower() - if "major" in keyword: - return "major" - elif "minor" in keyword: - return "minor" - elif "patch" in keyword: - return "patch" - else: - # Fallback based on commit type - type_match = COMMIT_TYPE_REGEX.match(commit_msg) - if type_match: - commit_type = type_match.group("type").lower() - return VERSION_BUMP_MAPPING.get(commit_type) - return None + try: + with open(pyproject_path, "r", encoding="utf-8") as file: + data = toml.load(file) + new_version = data["tool"]["poetry"]["version"] + logger.debug(f"New version: {new_version}") + return new_version + except (FileNotFoundError, KeyError, ValueError, toml.TomlDecodeError) as e: + logger.error(f"Error retrieving the new version from {pyproject_path}: {e}") + sys.exit(1) + + +def add_icon_and_prepare_commit_message( + commit_type: str, current_version: str, new_version: str +) -> str: + """ + Prepares the new commit message with the icon and version bump. + + Args: + commit_type (str): The type of the commit (e.g., 'chore', 'fix'). + current_version (str): The current version before bump. + new_version (str): The new version after bump. + + Returns: + str: The new commit message. + """ + icon = TYPE_MAPPING.get(commit_type.lower(), "") + new_commit_msg = f"{icon} Bump version: {current_version} → {new_version}" + logger.debug(f"New commit message: {new_commit_msg}") + return new_commit_msg def bump_version(part: str) -> None: @@ -235,25 +247,39 @@ def amend_commit(new_commit_msg: str) -> None: def main() -> None: """ - Main function to parse the latest commit message, add an icon, and perform version bumping. + Main function to parse the latest commit message, add an icon, perform version bumping, and amend the commit. """ args = parse_arguments() configure_logger(args.log_level) latest_commit_msg = get_latest_commit_message() - updated_commit_msg = add_icon_to_commit_message(latest_commit_msg) + type_match = COMMIT_TYPE_REGEX.match(latest_commit_msg) + if type_match: + commit_type = type_match.group("type") + logger.debug(f"Detected commit type: {commit_type}") + else: + commit_type = "chore" # Default to 'chore' if no type is found + logger.debug("No commit type detected. Defaulting to 'chore'.") version_bump_part = determine_version_bump(latest_commit_msg) if version_bump_part: logger.info(f"Version bump detected: {version_bump_part}") + + current_version = get_current_version() + bump_version(version_bump_part) + new_version = get_new_version() + + updated_commit_msg = add_icon_and_prepare_commit_message( + commit_type, current_version, new_version + ) + # Stage the updated pyproject.toml stage_changes() - # Amend the commit with the updated message amend_commit(updated_commit_msg) logger.info( @@ -264,5 +290,33 @@ def main() -> None: logger.info("No version bump detected in commit message.") +def determine_version_bump(commit_msg: str) -> Optional[str]: + """ + Determines the version bump part based on the commit message. + + Args: + commit_msg (str): The commit message. + + Returns: + Optional[str]: The version part to bump ('major', 'minor', 'patch') or None. + """ + match = VERSION_KEYWORD_REGEX.search(commit_msg) + if match: + keyword = match.group("keyword").lower() + if "major" in keyword: + return "major" + elif "minor" in keyword: + return "minor" + elif "patch" in keyword: + return "patch" + else: + # Fallback based on commit type + type_match = COMMIT_TYPE_REGEX.match(commit_msg) + if type_match: + commit_type = type_match.group("type").lower() + return VERSION_BUMP_MAPPING.get(commit_type) + return None + + if __name__ == "__main__": main() From ed5193c3d38b01d9fcba3c2c698410245f26b5b7 Mon Sep 17 00:00:00 2001 From: B Date: Sun, 27 Oct 2024 11:23:13 -0500 Subject: [PATCH 16/16] =?UTF-8?q?=F0=9F=94=A7=20Bump=20version:=201.0.12?= =?UTF-8?q?=20=E2=86=92=201.0.13?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .bumpversion.cfg | 2 +- pyproject.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 3cee761..ac647bb 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 1.0.12 +current_version = 1.0.13 commit = True tag = False diff --git a/pyproject.toml b/pyproject.toml index 5c46c7a..16e97b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "scripts" -version = "1.0.12" +version = "1.0.13" description = "CICD Core Scripts" authors = ["B "] license = "Apache 2.0" @@ -71,5 +71,5 @@ ensure_newline_before_comments = true rcfile = ".pylintrc" [build-system] -requires = ["poetry-core>=1.0.12"] +requires = ["poetry-core>=1.0.13"] build-backend = "poetry.core.masonry.api"