Skip to content

Implement git tag validation utility#387

Merged
MH0386 merged 2 commits into
mainfrom
hook
Nov 5, 2025
Merged

Implement git tag validation utility#387
MH0386 merged 2 commits into
mainfrom
hook

Conversation

@MH0386

@MH0386 MH0386 commented Nov 5, 2025

Copy link
Copy Markdown
Contributor

Summary by Sourcery

Replace the inline bash git-tag validation hook with a dedicated Python utility and update the Trunk CI configuration to run it

New Features:

  • Add a Python script to read version from pyproject.toml and validate the current git tag
  • Introduce a requirements file listing GitPython, tomli, and rich for the new utility
  • Configure trunk.yaml to invoke the Python tag-checker instead of the old bash hook

Copilot AI review requested due to automatic review settings November 5, 2025 14:20
@gitnotebooks

gitnotebooks Bot commented Nov 5, 2025

Copy link
Copy Markdown

@sourcery-ai

sourcery-ai Bot commented Nov 5, 2025

Copy link
Copy Markdown
Contributor

Reviewer's Guide

This PR refactors the pre-push git tag validation from an inline Bash script to a dedicated Python utility by updating trunk.yaml to invoke git_tag.py with its own runtime and dependencies, adds the new Python implementation under .trunk/utils, and introduces a requirements file for its dependencies.

Class diagram for the new git tag validation utility (git_tag.py)

classDiagram
    class git_tag {
        +read_version_from_pyproject(path: Path) str
        +get_exact_tag_for_head(repo: Repo) str | None
        +main() int
    }
    class Logger {
        +info(msg)
        +error(msg)
    }
    class Repo {
        +head
        +active_branch
        +git
    }
    class Path {
        +cwd()
        +exists()
        +read_text()
    }
    class tomli_loads
    class rich_console_Console
    class rich_logging_RichHandler
    git_tag --> Logger : uses
    git_tag --> Repo : uses
    git_tag --> Path : uses
    git_tag --> tomli_loads : uses
    git_tag --> rich_console_Console : uses
    git_tag --> rich_logging_RichHandler : uses
Loading

File-Level Changes

Change Details Files
Refactor pre-push tag validation to Python runner
  • Removed the inline Bash validation script in trunk.yaml
  • Updated run to python git_tag.py and added runtime, run_from, and packages_file keys
.trunk/trunk.yaml
Implement git_tag.py utility
  • Add GitPython-based logic to identify current branch and exact tag
  • Read and parse version from pyproject.toml using tomli
  • Use rich logging for structured output and appropriate exit codes
.trunk/utils/git_tag.py
Add Python dependencies for tag utility
  • Create requirements.txt listing GitPython, tomli, and rich
.trunk/utils/requirements.txt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Nov 5, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • Chores
    • Refactored Git tag validation hook implementation to use Python instead of inline configuration
    • Added development dependencies to support enhanced tooling

Walkthrough

The hook ensure-tag-matches-version is refactored from an inline bash script to a Python-based implementation. A new utility script validates that the current Git HEAD tag matches the version in pyproject.toml, with centralized logging and error handling.

Changes

Cohort / File(s) Summary
Configuration Updates
\\.trunk/trunk.yaml, \\.trunk/utils/requirements.txt
Simplified hook execution by replacing inline bash script with Python runner pointing to git_tag.py; added GitPython, tomli, and rich as dependencies
New Validation Utility
\\.trunk/utils/git_tag.py
New Python script that reads version from pyproject.toml, retrieves exact Git HEAD tag, compares versions, and exits with appropriate status codes; includes logging via RichHandler

Sequence Diagram

sequenceDiagram
    participant main as main()
    participant repo as GitPython<br/>(Repo)
    participant toml as pyproject.toml
    participant logger as Logger
    
    main->>repo: Open repository at ../../
    logger->>logger: Log repo location
    
    main->>repo: Check if HEAD is detached<br/>and on main branch
    alt Non-main or detached
        main->>logger: Exit with 0
    end
    
    main->>repo: Get exact tag for HEAD<br/>(git describe --exact-match)
    alt No exact tag found
        main->>logger: Exit with 0
    end
    
    main->>toml: read_version_from_pyproject()
    alt pyproject.toml missing/invalid
        logger->>logger: Log error
        main->>logger: Exit with 1
    end
    
    main->>main: Compare tag vs version
    alt Mismatch
        logger->>logger: Log error
        main->>logger: Exit with 1
    else Match
        logger->>logger: Log success
        main->>logger: Exit with 0
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • git_tag.py: Straightforward validation logic with standard error handling; no complex control flow
  • trunk.yaml: Configuration change is minimal and declarative
  • requirements.txt: Simple dependency additions

Poem

🐰 A tag and version now agree,
No bash script tangled, wild and free,
Python handles the git ballet,
Clean, precise, the proper way!

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hook

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7c18228 and 8c8e847.

📒 Files selected for processing (3)
  • .trunk/trunk.yaml (1 hunks)
  • .trunk/utils/git_tag.py (1 hunks)
  • .trunk/utils/requirements.txt (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@MH0386 MH0386 changed the title Implement git tag validation utility and update trunk.yaml configuration Implement git tag validation utility Nov 5, 2025
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello @MH0386, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the pre-push git hook responsible for validating that the current git tag matches the version defined in pyproject.toml. By migrating the validation logic from an inline bash script to a structured Python utility, the change enhances the maintainability, readability, and extensibility of this crucial version control check. This ensures more robust and consistent versioning across the project.

Highlights

  • Refactored Git Tag Validation: The inline bash script previously used for validating git tags against the pyproject.toml version has been replaced with a dedicated Python utility.
  • Introduced Python Utility: A new Python script, git_tag.py, now encapsulates the logic for checking the current branch, retrieving the git tag, reading the project version, and performing the necessary comparison.
  • Enhanced Hook Configuration: The trunk.yaml configuration for the ensure-tag-matches-version hook now explicitly defines python as its runtime and specifies a requirements.txt file for its dependencies, improving environment setup and maintainability.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@MH0386 MH0386 enabled auto-merge November 5, 2025 14:21
@deepsource-io

deepsource-io Bot commented Nov 5, 2025

Copy link
Copy Markdown
Contributor

Here's the code health analysis summary for commits 7c18228..8c8e847. View details on DeepSource ↗.

Analysis Summary

AnalyzerStatusSummaryLink
DeepSource Python LogoPython✅ SuccessView Check ↗
DeepSource Docker LogoDocker✅ SuccessView Check ↗
DeepSource Secrets LogoSecrets✅ SuccessView Check ↗

💡 If you’re a repository administrator, you can configure the quality gates from the settings.

@mergify mergify Bot temporarily deployed to code_quality November 5, 2025 14:21 Inactive

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • Consider detecting the repo root dynamically (e.g., using Repo(search_parent_directories=True)) rather than hardcoding a "../../" path.
  • Update the branch check to include both "main" and "master" (or detect the default branch dynamically) to match the original script’s behavior.
  • Use the repository root (repo.working_tree_dir) when resolving the pyproject.toml path instead of relying on the current working directory in .trunk/utils.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider detecting the repo root dynamically (e.g., using Repo(search_parent_directories=True)) rather than hardcoding a "../../" path.
- Update the branch check to include both "main" and "master" (or detect the default branch dynamically) to match the original script’s behavior.
- Use the repository root (repo.working_tree_dir) when resolving the pyproject.toml path instead of relying on the current working directory in .trunk/utils.

## Individual Comments

### Comment 1
<location> `.trunk/utils/git_tag.py:65` </location>
<code_context>
+    if not tag:
+        logger.info("No tag found for the current commit.")
+        return 0
+    version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
+    if not version:
+        logger.error("pyproject.toml not found or version not set.")
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Assumes pyproject.toml is in the current working directory.

This approach may fail if the script is run outside the project root. Consider resolving the path from the repository root or allowing it to be configurable.

Suggested implementation:

```python
    # Resolve pyproject.toml from the repository root
    pyproject_path = Path(repo.working_tree_dir) / "pyproject.toml"
    version: str = read_version_from_pyproject(pyproject_path)

```

If you want to make the path to `pyproject.toml` configurable, you could add an optional parameter to the function signature (e.g., `pyproject_path: Path | None = None`) and use it if provided, otherwise default to the repository root as above.
</issue_to_address>

### Comment 2
<location> `.trunk/utils/git_tag.py:38-41` </location>
<code_context>
+    """Return exact tag for HEAD commit, or None."""
+    try:
+        commit_hex: str = repo.head.commit.hexsha
+    except Exception:
+        return None
+    try:
+        return repo.git.describe("--tags", "--exact-match", commit_hex)
</code_context>

<issue_to_address>
**suggestion (bug_risk):** Catching broad Exception may obscure errors.

Consider catching only specific exceptions relevant to this code, such as AttributeError or ValueError, to prevent masking other errors.

```suggestion
    try:
        commit_hex: str = repo.head.commit.hexsha
    except (AttributeError, ValueError):
        return None
```
</issue_to_address>

### Comment 3
<location> `.trunk/utils/git_tag.py:26-33` </location>
<code_context>
+    if not path.exists():
+        msg: str = f"{path} does not exist."
+        raise FileNotFoundError(msg)
+    data: dict = loads(path.read_text(encoding="utf-8"))
+    project: dict = data["project"]
+    return project["version"]
</code_context>

<issue_to_address>
**suggestion:** No error handling for invalid TOML or missing keys.

Add error handling for malformed TOML and missing 'project' or 'version' keys to avoid uncaught exceptions and improve error messages.

```suggestion
def read_version_from_pyproject(path: Path) -> str:
    """Return the package version from pyproject.toml (PEP 621 style for uv)."""
    if not path.exists():
        msg: str = f"{path} does not exist."
        raise FileNotFoundError(msg)
    try:
        data: dict = loads(path.read_text(encoding="utf-8"))
    except Exception as e:
        msg: str = f"Failed to parse TOML from {path}: {e}"
        raise ValueError(msg) from e
    try:
        project: dict = data["project"]
    except KeyError:
        msg: str = f"'project' section missing in {path}"
        raise KeyError(msg)
    try:
        return project["version"]
    except KeyError:
        msg: str = f"'version' key missing in 'project' section of {path}"
        raise KeyError(msg)
```
</issue_to_address>

### Comment 4
<location> `.trunk/utils/git_tag.py:36` </location>
<code_context>
+    return project["version"]
+
+
+def get_exact_tag_for_head(repo: Repo) -> str | None:
+    """Return exact tag for HEAD commit, or None."""
+    try:
</code_context>

<issue_to_address>
**issue (complexity):** Consider refactoring nested logic and splitting repeated code into helpers to improve clarity and reduce boilerplate.

```suggestion
The overall flow is fine, but you can trim down some of the nested logic and repeated boilerplate:

1. Merge the two `try/except` in `get_exact_tag_for_head`, and catch both errors at once.  
2. Use `tomli.load` directly on a file handle instead of `loads(path.read_text())`.  
3. Extract logging setup and branch‐checking into small helpers.

For example:

```python
# 1) Simplify get_exact_tag_for_head
from git import GitCommandError

def get_exact_tag_for_head(repo: Repo) -> str | None:
    try:
        commit = repo.head.commit.hexsha
        return repo.git.describe("--tags", "--exact-match", commit)
    except (GitCommandError, AttributeError):
        return None
```

```python
# 2) Read toml via a file handle
import tomli

def read_version_from_pyproject(path: Path) -> str:
    if not path.exists():
        raise FileNotFoundError(f"{path} does not exist.")
    with path.open("rb") as f:
        data = tomli.load(f)
    return data["project"]["version"]
```

```python
# 3) Break up main() and move logging setup
from logging import basicConfig, INFO, getLogger
from rich.logging import RichHandler
from rich.console import Console

logger = getLogger(__name__)

def setup_logging() -> None:
    basicConfig(
        level=INFO,
        handlers=[RichHandler(console=Console(), rich_tracebacks=True)],
        format="%(message)s",
    )

def is_on_main_branch(repo: Repo) -> bool:
    try:
        return repo.active_branch.name == "main"
    except Exception:
        logger.info("Detached HEAD or unknown branch.")
        return False

def main() -> int:
    setup_logging()
    repo = Repo("../../")
    logger.info("Repository at %s", repo.working_tree_dir)

    if not is_on_main_branch(repo):
        return 0

    tag = get_exact_tag_for_head(repo)
    if not tag:
        logger.info("No tag found for the current commit.")
        return 0

    version = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
    if tag != version:
        logger.error("Tag '%s' does not match version '%s'.", tag, version)
        return 1

    logger.info("Tag '%s' matches version '%s'.", tag, version)
    return 0

if __name__ == "__main__":
    sys_exit(main())
```

These changes keep all functionality intact but reduce indentation, remove redundant error blocks, and make each helper single‐purpose.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread .trunk/utils/git_tag.py
if not tag:
logger.info("No tag found for the current commit.")
return 0
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Assumes pyproject.toml is in the current working directory.

This approach may fail if the script is run outside the project root. Consider resolving the path from the repository root or allowing it to be configurable.

Suggested implementation:

    # Resolve pyproject.toml from the repository root
    pyproject_path = Path(repo.working_tree_dir) / "pyproject.toml"
    version: str = read_version_from_pyproject(pyproject_path)

If you want to make the path to pyproject.toml configurable, you could add an optional parameter to the function signature (e.g., pyproject_path: Path | None = None) and use it if provided, otherwise default to the repository root as above.

Comment thread .trunk/utils/git_tag.py
Comment on lines +38 to +41
try:
commit_hex: str = repo.head.commit.hexsha
except Exception:
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Catching broad Exception may obscure errors.

Consider catching only specific exceptions relevant to this code, such as AttributeError or ValueError, to prevent masking other errors.

Suggested change
try:
commit_hex: str = repo.head.commit.hexsha
except Exception:
return None
try:
commit_hex: str = repo.head.commit.hexsha
except (AttributeError, ValueError):
return None

Comment thread .trunk/utils/git_tag.py
Comment thread .trunk/utils/git_tag.py
return project["version"]


def get_exact_tag_for_head(repo: Repo) -> str | None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring nested logic and splitting repeated code into helpers to improve clarity and reduce boilerplate.

Suggested change
def get_exact_tag_for_head(repo: Repo) -> str | None:
The overall flow is fine, but you can trim down some of the nested logic and repeated boilerplate:
1. Merge the two `try/except` in `get_exact_tag_for_head`, and catch both errors at once.
2. Use `tomli.load` directly on a file handle instead of `loads(path.read_text())`.
3. Extract logging setup and branchchecking into small helpers.
For example:
```python
# 1) Simplify get_exact_tag_for_head
from git import GitCommandError
def get_exact_tag_for_head(repo: Repo) -> str | None:
try:
commit = repo.head.commit.hexsha
return repo.git.describe("--tags", "--exact-match", commit)
except (GitCommandError, AttributeError):
return None
# 2) Read toml via a file handle
import tomli

def read_version_from_pyproject(path: Path) -> str:
    if not path.exists():
        raise FileNotFoundError(f"{path} does not exist.")
    with path.open("rb") as f:
        data = tomli.load(f)
    return data["project"]["version"]
# 3) Break up main() and move logging setup
from logging import basicConfig, INFO, getLogger
from rich.logging import RichHandler
from rich.console import Console

logger = getLogger(__name__)

def setup_logging() -> None:
    basicConfig(
        level=INFO,
        handlers=[RichHandler(console=Console(), rich_tracebacks=True)],
        format="%(message)s",
    )

def is_on_main_branch(repo: Repo) -> bool:
    try:
        return repo.active_branch.name == "main"
    except Exception:
        logger.info("Detached HEAD or unknown branch.")
        return False

def main() -> int:
    setup_logging()
    repo = Repo("../../")
    logger.info("Repository at %s", repo.working_tree_dir)

    if not is_on_main_branch(repo):
        return 0

    tag = get_exact_tag_for_head(repo)
    if not tag:
        logger.info("No tag found for the current commit.")
        return 0

    version = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
    if tag != version:
        logger.error("Tag '%s' does not match version '%s'.", tag, version)
        return 1

    logger.info("Tag '%s' matches version '%s'.", tag, version)
    return 0

if __name__ == "__main__":
    sys_exit(main())

These changes keep all functionality intact but reduce indentation, remove redundant error blocks, and make each helper single‐purpose.

@MH0386 MH0386 disabled auto-merge November 5, 2025 14:21
@MH0386 MH0386 merged commit fbdd724 into main Nov 5, 2025
9 of 15 checks passed
@sonarqubecloud

sonarqubecloud Bot commented Nov 5, 2025

Copy link
Copy Markdown

@MH0386 MH0386 deleted the hook branch November 5, 2025 14:21
@mergify

mergify Bot commented Nov 5, 2025

Copy link
Copy Markdown
Contributor

Thank you for your contribution @MH0386! Your pull request has been merged.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively replaces the inline bash script for git tag validation with a more maintainable and feature-rich Python utility. The use of libraries like GitPython, tomli, and rich is a great choice. I've identified a critical issue regarding the path resolution for pyproject.toml that would cause the script to fail, along with several other high and medium severity issues related to error handling, hardcoded values, and a potential regression in functionality. My review includes specific suggestions to improve the script's robustness and correctness. Please take a look at the detailed comments.

Comment thread .trunk/utils/git_tag.py
Comment on lines +65 to +68
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
if not version:
logger.error("pyproject.toml not found or version not set.")
return 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This block has a few issues that will prevent the script from working correctly:

  1. Incorrect Path: The script is executed from .trunk/utils, so Path.cwd() / "pyproject.toml" incorrectly looks for pyproject.toml in that subdirectory instead of the project root. This will cause a FileNotFoundError.
  2. Unhandled Exceptions: read_version_from_pyproject can raise a KeyError if the version field is missing, which would crash the script. This should be handled gracefully.
  3. Dead Code: The if not version: check is unreachable because read_version_from_pyproject either returns a string or raises an exception.

I suggest replacing this block to fix the path and add proper error handling.

Suggested change
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
if not version:
logger.error("pyproject.toml not found or version not set.")
return 1
try:
version: str = read_version_from_pyproject(
Path(repo.working_tree_dir) / "pyproject.toml"
)
except FileNotFoundError:
logger.error("pyproject.toml not found in repository root.")
return 1
except KeyError:
logger.error("Could not find 'version' in [project] table of pyproject.toml.")
return 1

Comment thread .trunk/utils/git_tag.py
# detached HEAD or no branch
logger.info("Detached HEAD or unknown branch.")
return 0
if branch != "main":

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The previous bash script checked for both main and master branches. This new implementation only checks for main. To maintain compatibility and avoid breaking workflows on repositories that still use master, it's better to check for both.

Suggested change
if branch != "main":
if branch not in ("main", "master"):

Comment thread .trunk/utils/git_tag.py
Comment on lines +40 to +41
except Exception:
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using except Exception: is too broad and can hide unexpected bugs. It's better to catch a more specific exception. repo.head.commit.hexsha can raise a ValueError if HEAD is unresolvable (e.g., in a new repository with no commits), so catching that is more precise.

Suggested change
except Exception:
return None
except ValueError:
return None

Comment thread .trunk/utils/git_tag.py

def main() -> int:
"""Validate git tag against pyproject.toml version."""
repo: Repo = Repo("../../")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The hardcoded relative path ../../ to the repository root is brittle. If this script is moved or its execution context changes, this path will break. A more robust approach is to let GitPython find the repository root by searching parent directories from the script's location.

Suggested change
repo: Repo = Repo("../../")
repo: Repo = Repo(Path(__file__).parent, search_parent_directories=True)

Comment thread .trunk/utils/git_tag.py
Comment on lines +54 to +57
except Exception:
# detached HEAD or no branch
logger.info("Detached HEAD or unknown branch.")
return 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using except Exception: is too broad. When the repository is in a detached HEAD state, repo.active_branch.name raises a TypeError. Catching this specific exception is safer and makes the code's intent clearer.

Suggested change
except Exception:
# detached HEAD or no branch
logger.info("Detached HEAD or unknown branch.")
return 0
except TypeError:
# detached HEAD or no branch
logger.info("Detached HEAD or unknown branch.")
return 0

@MH0386 MH0386 restored the hook branch November 5, 2025 14:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR refactors the git tag validation logic from a bash script embedded in trunk.yaml to a standalone Python utility. The changes improve maintainability, error handling, and logging.

Key Changes:

  • Replaces inline bash script with a structured Python module (git_tag.py)
  • Adds proper dependency management via requirements.txt
  • Improves logging with rich console output and better error messages

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

File Description
.trunk/utils/requirements.txt Adds Python dependencies (GitPython, tomli, rich) for the new utility
.trunk/utils/git_tag.py New Python module that validates git tags against pyproject.toml version with improved error handling and logging
.trunk/trunk.yaml Updates action definition to run the Python script instead of inline bash, configuring runtime and dependencies

Comment thread .trunk/utils/git_tag.py

def main() -> int:
"""Validate git tag against pyproject.toml version."""
repo: Repo = Repo("../../")

Copilot AI Nov 5, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded relative path '../../' is fragile and assumes the script is always run from .trunk/utils. Consider using Path(__file__).parent.parent.parent to compute the repository root dynamically, or better yet, use Repo(search_parent_directories=True) which will search upward for the git repository automatically.

Suggested change
repo: Repo = Repo("../../")
repo: Repo = Repo(search_parent_directories=True)

Copilot uses AI. Check for mistakes.
Comment thread .trunk/utils/git_tag.py
Comment on lines +65 to +67
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
if not version:
logger.error("pyproject.toml not found or version not set.")

Copilot AI Nov 5, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The check if not version: on line 66 will never be true because read_version_from_pyproject() raises a FileNotFoundError or KeyError rather than returning an empty string. The function should either be wrapped in a try-except block, or this conditional should be removed.

Suggested change
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
if not version:
logger.error("pyproject.toml not found or version not set.")
try:
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
except (FileNotFoundError, KeyError) as e:
logger.error("pyproject.toml not found or version not set: %s", e)

Copilot uses AI. Check for mistakes.
Comment thread .trunk/utils/git_tag.py
Comment on lines +38 to +41
try:
commit_hex: str = repo.head.commit.hexsha
except Exception:
return None

Copilot AI Nov 5, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching bare Exception is too broad. Consider catching specific exceptions like ValueError or checking if repo.head.is_detached instead of relying on exception handling for control flow.

Suggested change
try:
commit_hex: str = repo.head.commit.hexsha
except Exception:
return None
if repo.head.is_detached:
return None
commit_hex: str = repo.head.commit.hexsha

Copilot uses AI. Check for mistakes.
Comment thread .trunk/utils/git_tag.py
Comment on lines +52 to +57
try:
branch: str = repo.active_branch.name
except Exception:
# detached HEAD or no branch
logger.info("Detached HEAD or unknown branch.")
return 0

Copilot AI Nov 5, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Catching bare Exception is too broad. Consider catching TypeError specifically, which is raised when HEAD is detached, or check repo.head.is_detached before accessing active_branch.

Suggested change
try:
branch: str = repo.active_branch.name
except Exception:
# detached HEAD or no branch
logger.info("Detached HEAD or unknown branch.")
return 0
if repo.head.is_detached:
# detached HEAD or no branch
logger.info("Detached HEAD or unknown branch.")
return 0
branch: str = repo.active_branch.name

Copilot uses AI. Check for mistakes.
Comment thread .trunk/utils/git_tag.py
if not tag:
logger.info("No tag found for the current commit.")
return 0
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")

Copilot AI Nov 5, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Path.cwd() assumes the current working directory is the repository root, but the script is configured to run_from: .trunk/utils in trunk.yaml. This will fail to find pyproject.toml. Use repo.working_tree_dir or compute the path relative to the repository root discovered on line 50.

Suggested change
version: str = read_version_from_pyproject(Path.cwd() / "pyproject.toml")
version: str = read_version_from_pyproject(Path(repo.working_tree_dir) / "pyproject.toml")

Copilot uses AI. Check for mistakes.
@mergify

mergify Bot commented Nov 5, 2025

Copy link
Copy Markdown
Contributor

🧪 CI Insights

Here's what we observed from your CI run for 8c8e847.

🟢 All jobs passed!

But CI Insights is watching 👀

@mergify mergify Bot temporarily deployed to docker_image November 5, 2025 14:24 Inactive
@coderabbitai coderabbitai Bot mentioned this pull request Jan 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants