Documentsatlantis
GitLab API Client
GitLab API Client
Type
Topic
Status
Published
Created
Jul 9, 2026
Updated
Jul 9, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

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 calls gitlab.NewClient(token) directly.
  • For self-hosted instances, it resolves the hostname (warning on DNS failure), appends /api/v4/ to construct the base URL, and calls gitlab.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:

  1. Polling loop — calls MergeRequests.GetMergeRequest() repeatedly until mr.ChangesCount is non-empty, with a configurable PollingTimeout (default 30s) and PollingInterval (default 1s). This handles GitLab's async diff computation.

  2. Paginated diff fetch — calls MergeRequests.ListMergeRequestDiffs() with PerPage: 100 and follows resp.NextPage until exhausted. Both NewPath and OldPath are 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.6
  • approvals_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#

MethodGitLab API endpointNotes
GetModifiedFilesGET /projects/:id/merge_requests/:iid/diffsPaginated, 100/page
PullIsMergeableGET /projects/:id/merge_requests/:iid + /commits/:sha/statusesPaginates commit statuses; filters by MR ref to avoid cross-MR leakage
UpdateStatusPOST /projects/:id/statuses/:shaRetries up to 10× with jitter backoff; handles 409 Conflict from parallel jobs
CreateCommentPOST /projects/:id/merge_requests/:iid/notesSplits at 999,900 chars
DiscardReviewsPUT /projects/:id/merge_requests/:iid/reset_approvalsRequires bot token
GetFileContentGET /projects/:id/repository/files/:path/rawReturns (exists bool, content []byte, err)

Mergeability Logic#

isMergeable() is a pure function (no API calls) that checks in order:

  1. approvals_before_merge > 0 → not mergeable
  2. BlockingDiscussionsResolved == false → not mergeable
  3. Draft || WorkInProgress → not mergeable (both fields checked for API version compat)
  4. Pipeline skipped and project disallows it → not mergeable
  5. For GitLab ≥ 15.6: use DetailedMergeStatus; accepts "mergeable", "ci_still_running", "ci_must_pass".
  6. 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#

GitLab API Client | Dosu