feat(devgen/modules): categorize committypes and improve changelog/release note formattingAdd separate categories for Performance, Refactor, Tests, Style, and Chore; update ordering and emojis; improve remote push detection and fetch strategy; enhance error logging.
- New Features - Introduced distinct sections for Performance, Refactor, Tests, Style, and Chore in changelog generation. - Added dedicated emojis for each new category. - Updated section ordering to reflect the new categories. - Enhancements - Enhanced push logic to fetch upstream before push, avoiding non‑fast‑forward rejections. - Refactored `is_ahead_of_remote` with a fetch flag to reduce network calls and TOCTOU windows. - Improved error logging with full traceback for group processing failures. - Bug Fixes - Fixed emoji mappings for "Style" and "Other Changes". - Corrected handling of empty upstream ref to prevent errors. - Ensured proper exception handling when remote branch is missing.
This commit is contained in:
3 files changed
+81
-27
No files matched your search
@@ -87,16 +87,16 @@ class ChangelogGenerator:
|
||||
groups["Bug Fixes"].append(entry)
|
||||
elif c_type in ["docs"]:
|
||||
groups["Documentation"].append(entry)
|
||||
elif c_type in [
|
||||
"style",
|
||||
"refactor",
|
||||
"perf",
|
||||
"test",
|
||||
"build",
|
||||
"ci",
|
||||
"chore",
|
||||
]:
|
||||
groups["Other Changes"].append(entry)
|
||||
elif c_type == "perf":
|
||||
groups["Performance"].append(entry)
|
||||
elif c_type == "refactor":
|
||||
groups["Refactor"].append(entry)
|
||||
elif c_type == "test":
|
||||
groups["Tests"].append(entry)
|
||||
elif c_type == "style":
|
||||
groups["Style"].append(entry)
|
||||
elif c_type in ["build", "ci", "chore"]:
|
||||
groups["Chore"].append(entry)
|
||||
else:
|
||||
groups["Other Changes"].append(entry)
|
||||
else:
|
||||
@@ -122,12 +122,17 @@ class ChangelogGenerator:
|
||||
date_str = datetime.now().strftime("%Y-%m-%d")
|
||||
md = [f"## {version} ({date_str})\n"]
|
||||
|
||||
# Order: Breaking, Features, Fixes, Docs, Others
|
||||
# Order: Breaking, Features, Fixes, Docs, Refactor, Perf, Tests, Style, Chore, Others
|
||||
order = [
|
||||
"BREAKING CHANGES",
|
||||
"Features",
|
||||
"Bug Fixes",
|
||||
"Performance",
|
||||
"Documentation",
|
||||
"Refactor",
|
||||
"Tests",
|
||||
"Style",
|
||||
"Chore",
|
||||
"Other Changes",
|
||||
]
|
||||
|
||||
@@ -135,8 +140,13 @@ class ChangelogGenerator:
|
||||
"BREAKING CHANGES": "💥 BREAKING CHANGES",
|
||||
"Features": "✨ Features",
|
||||
"Bug Fixes": "🐛 Bug Fixes",
|
||||
"Performance": "⚡ Performance",
|
||||
"Documentation": "📚 Documentation",
|
||||
"Other Changes": "🔨 Other Changes",
|
||||
"Refactor": "♻️ Refactor",
|
||||
"Tests": "✅ Tests",
|
||||
"Style": "💄 Style",
|
||||
"Chore": "🔧 Chore",
|
||||
"Other Changes": "🧹 Other Changes",
|
||||
}
|
||||
|
||||
for section in order:
|
||||
|
||||
@@ -244,6 +244,26 @@ class CommitEngine:
|
||||
self.logger.info("Pushing to remote...")
|
||||
with self.console.status("[bold green]Pushing to remote...[/bold green]"):
|
||||
try:
|
||||
# Fetch first so a non-fast-forward push fails fast with a
|
||||
# clear message instead of getting rejected by the remote.
|
||||
try:
|
||||
upstream = run_git_command(
|
||||
[
|
||||
"git",
|
||||
"rev-parse",
|
||||
"--abbrev-ref",
|
||||
"--symbolic-full-name",
|
||||
"@{u}",
|
||||
],
|
||||
check=False,
|
||||
)
|
||||
if upstream:
|
||||
run_git_command(
|
||||
["git", "fetch", "origin", upstream], check=False
|
||||
)
|
||||
except (subprocess.CalledProcessError, CommitEngineError):
|
||||
pass
|
||||
|
||||
run_git_command(["git", "push"])
|
||||
except subprocess.CalledProcessError as e:
|
||||
msg = (
|
||||
@@ -348,22 +368,38 @@ class CommitEngine:
|
||||
)
|
||||
return sanitize_ai_commit_message(raw)
|
||||
|
||||
def is_ahead_of_remote(self) -> bool:
|
||||
"""Checks if local branch has unpushed commits."""
|
||||
def is_ahead_of_remote(self, fetch: bool = False) -> bool:
|
||||
"""Checks if local branch has unpushed commits.
|
||||
|
||||
Args:
|
||||
fetch: When True, refresh remote refs first (call only when you
|
||||
intend to push). Avoids an unconditional network call on
|
||||
every run and a TOCTOU window between fetch and rev-list.
|
||||
"""
|
||||
try:
|
||||
upstream = run_git_command(
|
||||
["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
|
||||
check=False,
|
||||
)
|
||||
except (subprocess.CalledProcessError, CommitEngineError):
|
||||
upstream = ""
|
||||
|
||||
if not upstream:
|
||||
return False
|
||||
|
||||
if fetch:
|
||||
try:
|
||||
run_git_command(["git", "fetch", "origin", upstream], check=False)
|
||||
except (subprocess.CalledProcessError, CommitEngineError):
|
||||
pass
|
||||
|
||||
try:
|
||||
run_git_command(["git", "fetch", "origin"])
|
||||
count = run_git_command(
|
||||
["git", "rev-list", "--count", "@{u}..HEAD"], check=False
|
||||
)
|
||||
if count and int(count) > 0:
|
||||
return True
|
||||
except (subprocess.CalledProcessError, CommitEngineError):
|
||||
# Maybe no upstream
|
||||
try:
|
||||
return bool(run_git_command(["git", "rev-parse", "HEAD"], check=False))
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
return False
|
||||
return bool(count) and int(count) > 0
|
||||
except (subprocess.CalledProcessError, CommitEngineError, ValueError):
|
||||
return False
|
||||
|
||||
def load_cache(self) -> Dict[str, str]:
|
||||
"""Loads dry-run cache."""
|
||||
@@ -441,7 +477,7 @@ class CommitEngine:
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to process group {group}: {e}")
|
||||
self.logger.error(f"Failed to process group {group!r}: {e}", exc_info=True)
|
||||
self._reset_group(files)
|
||||
return False
|
||||
|
||||
@@ -455,7 +491,9 @@ class CommitEngine:
|
||||
def execute(self):
|
||||
"""Main execution method."""
|
||||
files = self.detect_changes()
|
||||
ahead = self.is_ahead_of_remote()
|
||||
# Only consult the remote state if the user actually wants to push.
|
||||
# This avoids a network round-trip on every run and a TOCTOU window.
|
||||
ahead = self.is_ahead_of_remote(fetch=self.push) if self.push else False
|
||||
|
||||
if not files and not ahead:
|
||||
self.logger.info("Nothing to commit or push.")
|
||||
|
||||
@@ -12,11 +12,12 @@ class ReleaseNotesGenerator(ChangelogGenerator):
|
||||
"BREAKING CHANGES": "⚠️",
|
||||
"Features": "✨",
|
||||
"Bug Fixes": "🐛",
|
||||
"Performance": "⚡",
|
||||
"Documentation": "📚",
|
||||
"Refactor": "♻️",
|
||||
"Tests": "✅",
|
||||
"Chore": "🔧",
|
||||
"Style": "✨",
|
||||
"Style": "💄",
|
||||
"Other Changes": "🧹",
|
||||
}
|
||||
|
||||
@@ -32,7 +33,12 @@ class ReleaseNotesGenerator(ChangelogGenerator):
|
||||
"BREAKING CHANGES",
|
||||
"Features",
|
||||
"Bug Fixes",
|
||||
"Performance",
|
||||
"Documentation",
|
||||
"Refactor",
|
||||
"Tests",
|
||||
"Style",
|
||||
"Chore",
|
||||
"Other Changes",
|
||||
]
|
||||
|
||||
|
||||
Reference in new issue
Block a user