Skip to content

Commit 7fbf445

Browse files
authored
πŸ”– From dev β†’ Bump version: v1.1.2-dev into test (#30)
Automatically created pull request for release v1.1.2-dev into test branch.
2 parents ef53bac + 29d20d6 commit 7fbf445

File tree

5 files changed

+302
-16
lines changed

5 files changed

+302
-16
lines changed

β€Ž.bumpversion.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[bumpversion]
2-
current_version = 1.0.17
2+
current_version = 1.1.2
33
commit = True
44
tag = False
55

β€Ž.pre-commit-config.yaml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,23 +64,30 @@ repos:
6464
language: system
6565
types: [yaml]
6666
files: ^docker-compose(\.dev|\.prod)?\.yml$
67+
- id: validate-commit
68+
name: validate-commit
69+
entry: python control_commit/main.py --log-level=DEBUG
70+
always_run: true
71+
pass_filenames: false
72+
language: system
73+
stages: [pre-push]
6774
- id: commit-msg-version-check
6875
name: commit-msg-version-check
69-
entry: python commit_msg_version_bump/main.py --log-level=DEBUG
76+
entry: python commit_msg_version_bump/main.py --log-level=INFO
7077
always_run: true
7178
language: system
7279
pass_filenames: false
7380
# args: [--commit_msg_file, .git/COMMIT_EDITMSG]
7481
stages: [pre-push]
7582
- id: bump-year
7683
name: bump-year
77-
entry: python bump_year/main.py
84+
entry: python bump_year/main.py --log-level=INFO
7885
always_run: true
7986
pass_filenames: false
8087
language: system
8188
- id: generate-changelog
8289
name: generate-changelog
83-
entry: python generate_changelog/main.py
90+
entry: python generate_changelog/main.py --log-level=INFO
8491
always_run: true
8592
pass_filenames: false
8693
language: system

β€Žcommit_msg_version_bump/main.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ def get_latest_commit_message() -> str:
120120
stdout=subprocess.PIPE,
121121
stderr=subprocess.PIPE,
122122
text=True,
123+
encoding="utf-8",
123124
).stdout.strip()
124125
logger.debug(f"Latest commit message: {message}")
125126
return message
@@ -201,7 +202,11 @@ def bump_version(part: str) -> None:
201202
subprocess.CalledProcessError: If bump2version fails.
202203
"""
203204
try:
204-
subprocess.run(["bump2version", part], check=True)
205+
subprocess.run(
206+
["bump2version", part],
207+
check=True,
208+
encoding="utf-8",
209+
)
205210
logger.info(f"Successfully bumped the {part} version.")
206211
except subprocess.CalledProcessError as error:
207212
logger.error(f"Failed to bump the {part} version: {error}")
@@ -216,7 +221,11 @@ def stage_changes(pyproject_path: str = "pyproject.toml") -> None:
216221
pyproject_path (str): Path to the file to stage.
217222
"""
218223
try:
219-
subprocess.run(["git", "add", pyproject_path], check=True)
224+
subprocess.run(
225+
["git", "add", pyproject_path],
226+
check=True,
227+
encoding="utf-8",
228+
)
220229
logger.debug(f"Staged {pyproject_path} for commit.")
221230
except subprocess.CalledProcessError as e:
222231
logger.error(f"Failed to stage {pyproject_path}: {e}")
@@ -235,10 +244,14 @@ def amend_commit(new_commit_msg: str) -> None:
235244
"""
236245
try:
237246
# Amend the commit with the new commit message
238-
subprocess.run(["git", "commit", "--amend", "-m", new_commit_msg], check=True)
247+
subprocess.run(
248+
["git", "commit", "--amend", "-m", new_commit_msg],
249+
check=True,
250+
encoding="utf-8",
251+
)
239252
logger.info("Successfully amended the commit with the new version bump.")
240253
logger.info(
241-
"Please perform a force push using 'git push' to update the remote repository. Avoid use --force"
254+
"Please perform a push using 'git push' to update the remote repository. Avoid using --force"
242255
)
243256
except subprocess.CalledProcessError as e:
244257
logger.error(f"Failed to amend the commit: {e}")
@@ -283,7 +296,7 @@ def main() -> None:
283296
amend_commit(updated_commit_msg)
284297

285298
logger.info(
286-
"Aborting the current push. Please perform a force push using 'git push'. Avoid use --force"
299+
"Aborting the current push. Please perform a push using 'git push'. Avoid using --force"
287300
)
288301
sys.exit(1)
289302
else:
@@ -310,11 +323,7 @@ def determine_version_bump(commit_msg: str) -> Optional[str]:
310323
elif "patch" in keyword:
311324
return "patch"
312325
else:
313-
# Fallback based on commit type
314-
type_match = COMMIT_TYPE_REGEX.match(commit_msg)
315-
if type_match:
316-
commit_type = type_match.group("type").lower()
317-
return VERSION_BUMP_MAPPING.get(commit_type)
326+
return None
318327
return None
319328

320329

β€Žcontrol_commit/main.py

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
#!/usr/bin/env python3
2+
"""
3+
control_commit/main.py
4+
5+
A script to validate commit messages and add appropriate icons based on commit types.
6+
Ensures that commit messages follow a specific structure and naming conventions.
7+
Adds icons to commit messages that do not contain square brackets [].
8+
"""
9+
10+
import argparse
11+
import logging
12+
import re
13+
import subprocess
14+
import sys
15+
from logging.handlers import RotatingFileHandler
16+
17+
# Mapping of commit types to icons
18+
TYPE_MAPPING = {
19+
"feat": "✨",
20+
"fix": "πŸ›",
21+
"docs": "πŸ“",
22+
"style": "πŸ’„",
23+
"refactor": "♻️",
24+
"perf": "⚑️",
25+
"test": "βœ…",
26+
"chore": "πŸ”§",
27+
}
28+
29+
# Regular expressions for detecting commit types and validating commit message structure
30+
COMMIT_TYPE_REGEX = re.compile(r"^(?P<type>feat|fix|docs|style|refactor|perf|test|chore)")
31+
COMMIT_MESSAGE_REGEX = re.compile(
32+
r"^(?P<type>feat|fix|docs|style|refactor|perf|test|chore)"
33+
r"(?:\((?P<scope>[a-z0-9\-]+)\))?:\s+"
34+
r"(?P<description>[a-z].+)$"
35+
)
36+
37+
# Initialize the logger
38+
logger = logging.getLogger(__name__)
39+
40+
41+
def parse_arguments() -> argparse.Namespace:
42+
"""
43+
Parses command-line arguments.
44+
45+
Returns:
46+
argparse.Namespace: Parsed arguments.
47+
"""
48+
parser = argparse.ArgumentParser(
49+
description=(
50+
"Validate commit messages and add icons based on commit types. "
51+
"Ensures commit messages follow the format: type(scope): description."
52+
)
53+
)
54+
parser.add_argument(
55+
"--log-level",
56+
choices=["INFO", "DEBUG"],
57+
default="INFO",
58+
help="Set the logging level. Default is INFO.",
59+
)
60+
return parser.parse_args()
61+
62+
63+
def configure_logger(log_level: str) -> None:
64+
"""
65+
Configures logging for the script.
66+
67+
Args:
68+
log_level (str): Logging level as a string (e.g., 'INFO', 'DEBUG').
69+
"""
70+
numeric_level = getattr(logging, log_level.upper(), None)
71+
if not isinstance(numeric_level, int):
72+
raise ValueError(f"Invalid log level: {log_level}")
73+
74+
logger.setLevel(numeric_level)
75+
76+
# Set up log rotation: max size 5MB, keep 5 backup files
77+
file_handler = RotatingFileHandler(
78+
"commit_msg_icon_adder.log",
79+
maxBytes=5 * 1024 * 1024,
80+
backupCount=5,
81+
encoding="utf-8", # Ensure UTF-8 encoding to handle emojis
82+
)
83+
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
84+
file_handler.setFormatter(formatter)
85+
86+
# Create a safe console handler that replaces unencodable characters
87+
class SafeStreamHandler(logging.StreamHandler):
88+
def emit(self, record):
89+
try:
90+
msg = self.format(record)
91+
# Replace characters that can't be encoded
92+
msg = msg.encode(self.stream.encoding, errors="replace").decode(
93+
self.stream.encoding
94+
)
95+
self.stream.write(msg + self.terminator)
96+
self.flush()
97+
except Exception:
98+
self.handleError(record)
99+
100+
safe_console_handler = SafeStreamHandler()
101+
safe_console_handler.setFormatter(formatter)
102+
103+
logger.handlers.clear()
104+
logger.addHandler(file_handler)
105+
logger.addHandler(safe_console_handler)
106+
107+
108+
def read_commit_message(file_path: str) -> str:
109+
"""
110+
Reads the commit message from the given file.
111+
112+
Args:
113+
file_path (str): Path to the commit message file.
114+
115+
Returns:
116+
str: The commit message.
117+
"""
118+
try:
119+
with open(file_path, "r", encoding="utf-8") as file:
120+
commit_msg = file.read().strip()
121+
logger.debug(f"Original commit message: {commit_msg}")
122+
return commit_msg
123+
except FileNotFoundError:
124+
logger.error(f"Commit message file not found: {file_path}")
125+
sys.exit(1)
126+
except Exception as e:
127+
logger.error(f"Error reading commit message file: {e}")
128+
sys.exit(1)
129+
130+
131+
def validate_commit_message(commit_msg: str) -> bool:
132+
"""
133+
Validates the commit message against the required structure and lowercase naming.
134+
135+
Args:
136+
commit_msg (str): The commit message to validate.
137+
138+
Returns:
139+
bool: True if valid, False otherwise.
140+
"""
141+
match = COMMIT_MESSAGE_REGEX.match(commit_msg)
142+
if match or commit_msg.__contains__("Bump version:"):
143+
logger.debug("Commit message structure is valid.")
144+
return True
145+
else:
146+
logger.error("Invalid commit message structure. Ensure it follows the format:")
147+
logger.error("type(scope): description")
148+
logger.error(" - type: feat, fix, docs, style, refactor, perf, test, chore (lowercase)")
149+
logger.error(" - scope: optional, lowercase, alphanumeric and hyphens")
150+
logger.error(" - description: starts with a lowercase letter")
151+
return False
152+
153+
154+
def add_icon_to_commit_message(commit_type: str, existing_commit_msg: str) -> str:
155+
"""
156+
Adds an icon to the commit message based on its type if it doesn't already have one.
157+
158+
Args:
159+
commit_type (str): The type of the commit (e.g., 'chore', 'fix').
160+
existing_commit_msg (str): The original commit message.
161+
162+
Returns:
163+
str: The commit message with the icon prepended.
164+
"""
165+
icon = TYPE_MAPPING.get(commit_type.lower(), "")
166+
if icon and not existing_commit_msg.startswith(icon):
167+
new_commit_msg = f"{icon} {existing_commit_msg}"
168+
logger.debug(f"Updated commit message with icon: {new_commit_msg}")
169+
return new_commit_msg
170+
logger.debug("Icon already present in commit message or no icon defined for commit type.")
171+
return existing_commit_msg
172+
173+
174+
def amend_commit(new_commit_msg: str) -> None:
175+
"""
176+
Amends the current commit with the new commit message.
177+
178+
Args:
179+
new_commit_msg (str): The new commit message.
180+
181+
Raises:
182+
subprocess.CalledProcessError: If git amend fails.
183+
"""
184+
try:
185+
# Amend the commit with the new commit message
186+
subprocess.run(["git", "commit", "--amend", "-m", new_commit_msg], check=True)
187+
logger.info("Successfully amended the commit with the new commit message.")
188+
logger.info(
189+
"Please perform a push using 'git push' to update the remote repository. Avoid use --force"
190+
)
191+
except subprocess.CalledProcessError as e:
192+
logger.error(f"Failed to amend the commit: {e}")
193+
sys.exit(1)
194+
195+
196+
def has_square_brackets(commit_msg: str) -> bool:
197+
"""
198+
Checks if the commit message contains square brackets.
199+
200+
Args:
201+
commit_msg (str): The commit message.
202+
203+
Returns:
204+
bool: True if square brackets are present, False otherwise.
205+
"""
206+
return bool(re.search(r"\[.*?\]", commit_msg))
207+
208+
209+
def main() -> None:
210+
"""
211+
Main function to validate commit messages and add icons if necessary.
212+
Exits with code 1 if validation fails or after adding an icon.
213+
"""
214+
global commit_msg_without_icon
215+
args = parse_arguments()
216+
configure_logger(args.log_level)
217+
218+
commit_msg_file = ".git/COMMIT_EDITMSG"
219+
commit_msg = read_commit_message(commit_msg_file)
220+
221+
# Verify if the commit message already starts with an icon
222+
icon_present = False
223+
for icon in TYPE_MAPPING.values():
224+
if commit_msg.startswith(f"{icon} "):
225+
icon_present = True
226+
commit_msg_without_icon = commit_msg[len(icon) + 1 :]
227+
logger.debug(f"Commit message already has icon '{icon}'.")
228+
break
229+
230+
if icon_present:
231+
# Validate the commit message without the icon
232+
if not validate_commit_message(commit_msg_without_icon):
233+
logger.error("Commit message validation failed after removing icon. Aborting commit.")
234+
sys.exit(1)
235+
else:
236+
logger.debug("Commit message with icon is valid.")
237+
sys.exit(0) # Valid commit message with icon; proceed
238+
else:
239+
# Validate the original commit message
240+
if not validate_commit_message(commit_msg):
241+
logger.error("Commit message validation failed. Aborting commit.")
242+
sys.exit(1)
243+
logger.debug("Commit message does not contain square brackets. Proceeding to add icon.")
244+
245+
# Determine the type of commit to get the appropriate icon
246+
type_match = COMMIT_TYPE_REGEX.match(commit_msg)
247+
if type_match:
248+
commit_type = type_match.group("type")
249+
logger.debug(f"Detected commit type: {commit_type}")
250+
else:
251+
commit_type = "chore" # Default to 'chore' if no type is found
252+
logger.debug("No commit type detected. Defaulting to 'chore'.")
253+
sys.exit(1)
254+
255+
# Add the icon to the existing commit message
256+
updated_commit_msg = add_icon_to_commit_message(commit_type, commit_msg)
257+
258+
# Write the updated commit message back to the file
259+
amend_commit(updated_commit_msg)
260+
261+
# Inform the user and abort the commit to allow them to review the amended message
262+
logger.info(
263+
"Commit message has been updated with an icon. Please review and finalize the commit."
264+
)
265+
sys.exit(1)
266+
267+
268+
if __name__ == "__main__":
269+
""""""
270+
main()

β€Žpyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "scripts"
3-
version = "1.0.17"
3+
version = "1.1.2"
44
description = "CICD Core Scripts"
55
authors = ["B <[email protected]>"]
66
license = "Apache 2.0"
@@ -71,5 +71,5 @@ ensure_newline_before_comments = true
7171
rcfile = ".pylintrc"
7272

7373
[build-system]
74-
requires = ["poetry-core>=1.0.17"]
74+
requires = ["poetry-core>=1.0.0"]
7575
build-backend = "poetry.core.masonry.api"

0 commit comments

Comments
Β (0)