Skip to content

feat(tracker): GitLab adapter#42

Open
harshitsinghbhandari wants to merge 2 commits into
mainfrom
feat/tracker-gitlab
Open

feat(tracker): GitLab adapter#42
harshitsinghbhandari wants to merge 2 commits into
mainfrom
feat/tracker-gitlab

Conversation

@harshitsinghbhandari
Copy link
Copy Markdown
Collaborator

Summary

Implements ports.Tracker for GitLab Issues over REST v4. Read-only v1: Get / List / Preflight. Mirrors the canonical GitHub adapter (TokenSource shape, sentinel errors, typed RateLimitError, atomic.Bool + sync.Mutex preflight caching, strict provider check) with GitLab-specific deviations called out below.

⚠️ Do NOT merge yet — awaiting human review.

Companion to:

GitLab-specific deviations from the GitHub adapter

Surface GitHub GitLab (this PR)
Auth header Authorization: Bearer <tok> PRIVATE-TOKEN: <tok> (recommended by GitLab)
Open-state spelling open opened (both in query and payload)
Rate-limit headers X-RateLimit-Remaining, X-RateLimit-Reset RateLimit-Remaining, RateLimit-Reset (no X- prefix)
Rate-limit status code 403 (primary) / 429 (secondary) 429
Native ID owner/repo#N group/project#iid; subgroups (group/sub/.../project#iid) supported; full project path URL-encoded with url.PathEscape
Assignee query param assignee assignee_username
Identity endpoint GET /user GET /user

State mapping

GitLab REST v4 does not expose state_reason / close_reason on the issue payload, so closed-state disambiguation uses a label convention.

Native shape Normalized state
opened + label in-review review (wins when both status labels are present)
opened + label in-progress in_progress
opened, no status label open
closed + label cancelled or wontfix cancelled
closed, no cancelled/wontfix label done

Label matching is case-insensitive (GitLab label names are case-insensitive in the UI). The in-progress / in-review labels match the GitHub adapter verbatim — this is the cross-provider contract. The adapter does NOT write any of these labels in v1.

Tests

  • TDD layout: fakeGL httptest.Server mirrors GitHub's fakeGH, recording every call. Unhandled requests fail the test.
  • Subgroup URL-encoding is asserted on the wire via r.URL.EscapedPath() containing %2F.
  • Covers happy paths, full state-mapping table (open/in-progress/in-review/closed/cancelled, plus case-insensitive labels), 429 + Retry-After, 401 → ErrAuthFailed, 403 → ErrAuthFailed, wrong/empty provider rejection, ID + repo parser tables (including deep subgroups and rejected forms).

All 367 backend tests pass with -race; go vet clean; gofmt clean.

Test plan

  • go test -race ./internal/adapters/tracker/gitlab/... green
  • go test -race ./... (full module) green
  • go vet ./... clean
  • gofmt -l backend/internal/adapters/tracker/gitlab/ clean
  • Manual smoke against a real GitLab project (defer until human reviewer is OK with the shape — uses GITLAB_TOKEN env)

Out of scope

🤖 Generated with Claude Code

harshitsinghbhandari and others added 2 commits May 30, 2026 23:52
Implements ports.Tracker for GitLab Issues over REST v4. Mirrors the
GitHub adapter pattern (TokenSource, sentinel errors, typed
RateLimitError, atomic+mutex preflight cache, strict provider check)
with GitLab-specific deviations:

  - PRIVATE-TOKEN header instead of Authorization: Bearer
  - state spelling "opened" (not "open") in query + payload
  - rate-limit headers without X- prefix; 429 status
  - URL-encoded full project path (subgroups of arbitrary depth)
  - assignee_username query param
  - label-based closed-reason convention (cancelled/wontfix) since
    GitLab REST v4 has no native state_reason/close_reason field

v1 scope: Get, List (single page, no auto-pagination), Preflight.
Comment / Transition deferred to #40; observer/polling to #35.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Zero-config auth per AO-26 directive: a user who has run `glab auth login`
gets working credentials with no extra setup. Lookup order is now

  1. configured EnvVars (first non-empty wins)
  2. GITLAB_TOKEN
  3. `glab auth token`
  4. else ErrNoToken

The glab fallback is on by default — no opt-in flag. Failure (binary
missing, not logged in, empty stdout) falls through silently to
ErrNoToken so the caller's failure mode stays uniform regardless of
whether glab is installed. Stderr is discarded for the same reason: an
unauthenticated glab shouldn't print noise during a silent fallthrough.

Tests inject a GLAB hook on EnvTokenSource to assert lookup order
without touching $PATH. The hook signature matches the parallel
`gh auth token` work coming to the GitHub adapter so both auth.go
shapes stay parallel.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@harshitsinghbhandari
Copy link
Copy Markdown
Collaborator Author

Follow-up: added glab auth token zero-config fallback to EnvTokenSource per AO-26 directive (commit 1b78ea4).

New lookup order in EnvTokenSource.Token:

  1. Configured EnvVars (first non-empty wins)
  2. GITLAB_TOKEN
  3. glab auth token (the GitLab CLI)
  4. else ErrNoToken

Properties:

  • Default behavior, no opt-in flag — a user who has run glab auth login gets working credentials with no extra setup.
  • glab failure (binary missing, not logged in, empty output) falls through silently to ErrNoToken — the exec error is not propagated, and stderr is discarded so an unauthenticated glab doesn't print noise.
  • Injection seam: EnvTokenSource.GLAB func(ctx) (string, error) — production leaves it nil and gets the real exec; tests inject a fake.
  • ctx threaded through exec.CommandContext so a startup deadline is honored.

The hook signature mirrors the parallel gh auth token work the GitHub adapter is getting in a follow-up PR, so both auth.go shapes stay parallel.

Tests cover env-var-wins, GITLAB_TOKEN-wins, glab fallback, whitespace trim, glab-error fallthrough, empty-output fallthrough, ctx threading, and a nil-GLAB smoke test. 375 backend tests pass with -race; go vet clean; gofmt clean. Self-reviewed via superpowers:code-reviewer (one STRONG-RECOMMEND addressed: stderr discarded).

Still marked do-not-merge pending human review.

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.

1 participant