Skip to content

teff.tool.builtin.gitlab

teff.tool.builtin.gitlab

GitLab tools — list merge requests, fetch diffs, post notes, approve.

A small, purpose-built REST client over the GitLab v4 API so a workflow can review merge requests without hand-rolling http_request calls: auth headers, project path encoding and error surfacing are handled here.

All tools read url (the GitLab base URL, e.g. https://gitlab.com) and token (a personal access token) from config. A project is either a numeric id or a URL-encoded path like group/subgroup/repo — path-style ids are URL-encoded automatically.

Classes:

Name Description
GitLabApproveTool

Approve a merge request.

GitLabGetMRChangesTool

Fetch a merge request's diff (changed files + line-level changes).

GitLabListOpenMRsTool

List open merge requests for a project.

GitLabPostNoteTool

Post a note (comment) on a merge request.

GitLabApproveTool

Bases: _GitLabBase

Approve a merge request.

Parameters:

Name Type Description Default
project

Project id or URL-encoded path.

required
iid

Merge request internal id.

required
Source code in teff/tool/builtin/gitlab.py
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
class GitLabApproveTool(_GitLabBase):
    """Approve a merge request.

    Args:
        project: Project id or URL-encoded path.
        iid: Merge request internal id.
    """

    name = "gitlab_approve"
    description = "Approve a GitLab merge request"

    async def arun(  # type: ignore[override]
        self, project: str, iid: str
    ) -> str:
        pid = self._project_id(project)
        path = f"/projects/{pid}/merge_requests/{iid}/approve"
        import httpx

        self._require()
        url = f"{self.url}/api/v4{path}"
        headers = {"PRIVATE-TOKEN": self.token}
        async with httpx.AsyncClient(timeout=30) as client:
            response = await client.post(url, headers=headers)
            if response.status_code >= 400:
                raise ValueError(
                    f"GitLab POST {path} -> HTTP {response.status_code}: "
                    f"{response.text[:500]}"
                )
        return f"approved MR !{iid}"

GitLabGetMRChangesTool

Bases: _GitLabBase

Fetch a merge request's diff (changed files + line-level changes).

Parameters:

Name Type Description Default
project

Project id or URL-encoded path.

required
iid

Merge request internal id (the !N number).

required
max_chars

Cap on the returned diff text (default 20000).

required
Source code in teff/tool/builtin/gitlab.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
class GitLabGetMRChangesTool(_GitLabBase):
    """Fetch a merge request's diff (changed files + line-level changes).

    Args:
        project: Project id or URL-encoded path.
        iid: Merge request internal id (the ``!N`` number).
        max_chars: Cap on the returned diff text (default 20000).
    """

    name = "gitlab_get_mr_changes"
    description = "Fetch the diff of a GitLab merge request"

    async def arun(  # type: ignore[override]
        self, project: str, iid: str, max_chars: int = 20000
    ) -> str:
        pid = self._project_id(project)
        path = f"/projects/{pid}/merge_requests/{iid}/changes"
        text = await self._request("GET", path)
        data = json.loads(text)
        changes = data.get("changes", [])
        out = [f"# MR !{iid}: {data.get('title', '')}"]
        out.append(
            f"state={data.get('state', '')}  target_branch={data.get('target_branch', '')}"
        )
        for change in changes:
            out.append(
                f"\n== {change.get('new_path', '')} "
                f"(+{change.get('new_file', False)} "
                f"-{change.get('deleted_file', False)})"
            )
            diff = change.get("diff", "")
            out.append(diff[:max_chars])
        return "\n".join(out)

GitLabListOpenMRsTool

Bases: _GitLabBase

List open merge requests for a project.

Parameters:

Name Type Description Default
project

Project id or URL-encoded path (group/repo).

required
limit

Maximum number of MRs to return (default 50).

required
state

MR state filter (default opened).

required
Source code in teff/tool/builtin/gitlab.py
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
class GitLabListOpenMRsTool(_GitLabBase):
    """List open merge requests for a project.

    Args:
        project: Project id or URL-encoded path (``group/repo``).
        limit: Maximum number of MRs to return (default 50).
        state: MR state filter (default ``opened``).
    """

    name = "gitlab_list_open_mrs"
    description = "List open merge requests for a GitLab project"

    async def arun(  # type: ignore[override]
        self, project: str, limit: int = 50, state: str = "opened"
    ) -> str:
        pid = self._project_id(project)
        path = f"/projects/{pid}/merge_requests?state={state}&per_page={limit}"
        text = await self._request("GET", path)
        data = json.loads(text)
        lines = []
        for mr in data:
            lines.append(
                f"!{mr['iid']}\t{mr.get('state', '')}\t"
                f"{mr.get('title', '')}\t(mr_id={mr.get('id')})"
            )
        return "\n".join(lines) if lines else "no open merge requests"

GitLabPostNoteTool

Bases: _GitLabBase

Post a note (comment) on a merge request.

Parameters:

Name Type Description Default
project

Project id or URL-encoded path.

required
iid

Merge request internal id.

required
body

The note text to post.

required
Source code in teff/tool/builtin/gitlab.py
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
class GitLabPostNoteTool(_GitLabBase):
    """Post a note (comment) on a merge request.

    Args:
        project: Project id or URL-encoded path.
        iid: Merge request internal id.
        body: The note text to post.
    """

    name = "gitlab_post_note"
    description = "Post a comment/note on a GitLab merge request"

    async def arun(  # type: ignore[override]
        self, project: str, iid: str, body: str
    ) -> str:
        pid = self._project_id(project)
        path = f"/projects/{pid}/merge_requests/{iid}/notes"
        import httpx

        self._require()
        url = f"{self.url}/api/v4{path}"
        headers = {"PRIVATE-TOKEN": self.token}
        async with httpx.AsyncClient(timeout=30) as client:
            response = await client.post(url, headers=headers, json={"body": body})
            if response.status_code >= 400:
                raise ValueError(
                    f"GitLab POST {path} -> HTTP {response.status_code}: "
                    f"{response.text[:500]}"
                )
            data = response.json()
        return f"note posted on !{iid} (note_id={data.get('id')})"