GitLab API Client#
Overview#
Atlantis uses gitlab.com/gitlab-org/api/client-go (currently at v0.161.1) as its GitLab VCS integration library. The client is implemented in server/events/vcs/gitlab/client.go and wraps the upstream library's *gitlab.Client inside Atlantis's own Client struct.
The library replaced the community-maintained github.com/xanzy/go-gitlab package (migrated via PR #6250). No references to the old package remain in the codebase.
Client Construction#
The New() function initializes the client with a hostname and token:
- For
gitlab.com, it callsgitlab.NewClient(token)directly. - For self-hosted instances, it resolves the hostname (warning on DNS failure), appends
/api/v4/to construct the base URL, and callsgitlab.NewClient(token, gitlab.WithBaseURL(apiURL)).
On construction, the server version is fetched and stored so version-gated features can be checked at runtime (e.g., CommonMark support ≥ 11.1, detailed merge status ≥ 15.6).
Pagination: Diff Fetching#
GetModifiedFiles() uses a two-step paginated approach to retrieve changed files from a merge request:
-
Polling loop — calls
MergeRequests.GetMergeRequest()repeatedly untilmr.ChangesCountis non-empty, with a configurablePollingTimeout(default 30s) andPollingInterval(default 1s). This handles GitLab's async diff computation. -
Paginated diff fetch — calls
MergeRequests.ListMergeRequestDiffs()withPerPage: 100and followsresp.NextPageuntil exhausted. BothNewPathandOldPathare recorded for renamed files.
This replaced a single-page /changes endpoint call. The new approach avoids truncated file lists for large merge requests.
Legacy Field Compatibility#
PR #6250 (upgrade to v0.161.1) removed two fields from the upstream library's MergeRequest struct that GitLab's API still returns. Atlantis handles this via legacyMergeRequest, a wrapper type with a custom UnmarshalJSON that captures:
merge_status— used for mergeability checks on GitLab < 15.6approvals_before_merge— used to detect pending approval requirements
PullIsMergeable uses g.Client.NewRequest + g.Client.Do (raw request path) to decode into this wrapper, then passes the legacy fields to the pure isMergeable() function.
Key API Operations#
| Method | GitLab API endpoint | Notes |
|---|---|---|
GetModifiedFiles | GET /projects/:id/merge_requests/:iid/diffs | Paginated, 100/page |
PullIsMergeable | GET /projects/:id/merge_requests/:iid + /commits/:sha/statuses | Paginates commit statuses; filters by MR ref to avoid cross-MR leakage |
UpdateStatus | POST /projects/:id/statuses/:sha | Retries up to 10× with jitter backoff; handles 409 Conflict from parallel jobs |
CreateComment | POST /projects/:id/merge_requests/:iid/notes | Splits at 999,900 chars |
DiscardReviews | PUT /projects/:id/merge_requests/:iid/reset_approvals | Requires bot token |
GetFileContent | GET /projects/:id/repository/files/:path/raw | Returns (exists bool, content []byte, err) |
Mergeability Logic#
isMergeable() is a pure function (no API calls) that checks in order:
approvals_before_merge > 0→ not mergeableBlockingDiscussionsResolved == false→ not mergeableDraft || WorkInProgress→ not mergeable (both fields checked for API version compat)- Pipeline skipped and project disallows it → not mergeable
- For GitLab ≥ 15.6: use
DetailedMergeStatus; accepts"mergeable","ci_still_running","ci_must_pass". - For older GitLab: use legacy
merge_status == "can_be_merged".
Commit statuses are filtered to avoid cross-MR contamination when a shared SHA appears in multiple merge requests.
Source Files#
- Main client:
server/events/vcs/gitlab/client.go - Dependency declaration:
go.mod—gitlab.com/gitlab-org/api/client-go v0.161.1 - Migration PR: #6250 – upgrade gitlab client to v0.161.1