From fd3ce034842e138de6742f8042f8f277bc8e2e5a Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sat, 6 Jun 2026 16:27:12 +0545 Subject: [PATCH] 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. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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. --- devgen/modules/changelog_generator.py | 34 +++++++----- devgen/modules/commit_generator.py | 66 +++++++++++++++++++----- devgen/modules/release_note_generator.py | 8 ++- 3 files changed, 81 insertions(+), 27 deletions(-) diff --git a/devgen/modules/changelog_generator.py b/devgen/modules/changelog_generator.py index 6c3634a..f618f81 100644 --- a/devgen/modules/changelog_generator.py +++ b/devgen/modules/changelog_generator.py @@ -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: diff --git a/devgen/modules/commit_generator.py b/devgen/modules/commit_generator.py index ce6e634..e989759 100644 --- a/devgen/modules/commit_generator.py +++ b/devgen/modules/commit_generator.py @@ -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.") diff --git a/devgen/modules/release_note_generator.py b/devgen/modules/release_note_generator.py index 56886ed..80948d7 100644 --- a/devgen/modules/release_note_generator.py +++ b/devgen/modules/release_note_generator.py @@ -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", ]