Skip to content

Commit

commit

GitCommitTool — batched atomic commits with pre-commit handling.

GitCommitTool

Bases: AXMTool

Execute one or more atomic commits in a single call.

Each commit in the batch is processed sequentially: stage files, run git commit (pre-commit hooks fire automatically), and capture the result. If a commit fails (e.g. pre-commit rejects), processing stops and the error is returned alongside any commits that already succeeded.

When a pre-commit hook auto-fixes files (e.g. ruff --fix), the tool automatically re-stages and retries the commit once.

Registered as git_commit via axm.tools entry point.

Source code in packages/axm-git/src/axm_git/tools/commit.py
Python
class GitCommitTool(AXMTool):
    """Execute one or more atomic commits in a single call.

    Each commit in the batch is processed sequentially: stage files,
    run ``git commit`` (pre-commit hooks fire automatically), and
    capture the result.  If a commit fails (e.g. pre-commit rejects),
    processing stops and the error is returned alongside any commits
    that already succeeded.

    When a pre-commit hook auto-fixes files (e.g. ruff ``--fix``),
    the tool automatically re-stages and retries the commit once.

    Registered as ``git_commit`` via axm.tools entry point.
    """

    @property
    def name(self) -> str:
        """Tool name used for MCP registration."""
        return "git_commit"

    def execute(
        self,
        *,
        path: str = ".",
        commits: list[dict[str, object]] | None = None,
        profile: str | None = None,
        **kwargs: object,
    ) -> ToolResult:
        """Execute batched commits.

        Args:
            path: Project root (required).
            commits: List of commit specs, each a dict with keys:
                - ``files`` (list[str]): Files to stage.
                - ``message`` (str): Commit summary line.
                - ``body`` (str, optional): Commit body.
            profile: Optional identity profile name. Overrides
                schedule-based resolution from ``git-profiles.toml``.

        Returns:
            ToolResult with list of committed results and an
            ``author`` key (``{name, email}`` or ``None``).
        """
        resolved = Path(path).resolve()
        commit_list: list[dict[str, object]] = commits or []
        total = len(commit_list)

        if not commit_list:
            error = "No commits provided"
            return ToolResult(
                success=False,
                error=error,
                text=render_failure_text(error=error, data=None),
            )

        try:
            # Fail fast with suggestions if not a git repo
            check = run_git(["rev-parse", "--git-dir"], resolved)
            if check.returncode != 0:
                repo_err = not_a_repo_error(check.stderr, resolved)
                return ToolResult(
                    success=repo_err.success,
                    error=repo_err.error,
                    data=repo_err.data,
                    text=render_failure_text(
                        error=repo_err.error or "", data=repo_err.data
                    ),
                )

            # Resolve identity once for the entire batch
            identity = resolve_identity(resolved, profile_override=profile)
            identity_args = author_args(identity)

            results: list[dict[str, object]] = []

            for i, spec in enumerate(commit_list):
                failure = _process_single_commit(
                    spec, i + 1, identity_args, resolved, results, total
                )
                if failure:
                    return failure
        except subprocess.TimeoutExpired as exc:
            return timeout_error_result(exc)

        data = {
            "results": results,
            "total": len(results),
            "succeeded": len(results),
            "author": (
                {"name": identity.name, "email": identity.email} if identity else None
            ),
        }
        return ToolResult(success=True, data=data, text=render_text(data))
name property

Tool name used for MCP registration.

execute(*, path='.', commits=None, profile=None, **kwargs)

Execute batched commits.

Parameters:

Name Type Description Default
path str

Project root (required).

'.'
commits list[dict[str, object]] | None

List of commit specs, each a dict with keys: - files (list[str]): Files to stage. - message (str): Commit summary line. - body (str, optional): Commit body.

None
profile str | None

Optional identity profile name. Overrides schedule-based resolution from git-profiles.toml.

None

Returns:

Type Description
ToolResult

ToolResult with list of committed results and an

ToolResult

author key ({name, email} or None).

Source code in packages/axm-git/src/axm_git/tools/commit.py
Python
def execute(
    self,
    *,
    path: str = ".",
    commits: list[dict[str, object]] | None = None,
    profile: str | None = None,
    **kwargs: object,
) -> ToolResult:
    """Execute batched commits.

    Args:
        path: Project root (required).
        commits: List of commit specs, each a dict with keys:
            - ``files`` (list[str]): Files to stage.
            - ``message`` (str): Commit summary line.
            - ``body`` (str, optional): Commit body.
        profile: Optional identity profile name. Overrides
            schedule-based resolution from ``git-profiles.toml``.

    Returns:
        ToolResult with list of committed results and an
        ``author`` key (``{name, email}`` or ``None``).
    """
    resolved = Path(path).resolve()
    commit_list: list[dict[str, object]] = commits or []
    total = len(commit_list)

    if not commit_list:
        error = "No commits provided"
        return ToolResult(
            success=False,
            error=error,
            text=render_failure_text(error=error, data=None),
        )

    try:
        # Fail fast with suggestions if not a git repo
        check = run_git(["rev-parse", "--git-dir"], resolved)
        if check.returncode != 0:
            repo_err = not_a_repo_error(check.stderr, resolved)
            return ToolResult(
                success=repo_err.success,
                error=repo_err.error,
                data=repo_err.data,
                text=render_failure_text(
                    error=repo_err.error or "", data=repo_err.data
                ),
            )

        # Resolve identity once for the entire batch
        identity = resolve_identity(resolved, profile_override=profile)
        identity_args = author_args(identity)

        results: list[dict[str, object]] = []

        for i, spec in enumerate(commit_list):
            failure = _process_single_commit(
                spec, i + 1, identity_args, resolved, results, total
            )
            if failure:
                return failure
    except subprocess.TimeoutExpired as exc:
        return timeout_error_result(exc)

    data = {
        "results": results,
        "total": len(results),
        "succeeded": len(results),
        "author": (
            {"name": identity.name, "email": identity.email} if identity else None
        ),
    }
    return ToolResult(success=True, data=data, text=render_text(data))