GitLab adapter: Repo box readers and update branch #140

Closed
opened 2026-10-12 07:51:40 -05:00 by deadstyle · 3 comments
Owner

Problem

On GitLab the Repo box (README, Branches, Files tabs) is empty and u is unavailable until the adapter implements the readers and BranchUpdater. Parent: #137. Depends on #138; pick this up after #138 merges.

Acceptance criteria

  • The GitLab adapter implements forge.ReadmeReader, forge.BranchReader, forge.TreeReader and forge.BranchUpdater, so the Repo box's README, Branches and Files tabs and u work as on Forgejo (design.md → Repo box; Keymap u).
  • u on GitLab offers rebase only, because GitLab's API can rebase an MR but cannot merge the target branch into it (design.md → Keymap u already says "GitHub offers merge only"; GitLab is the mirror case, forced by the API). design.md's two u sentences are updated to say so.
  • The status bar reports GitLab's reason when it refuses a rebase (protected branch, missing source branch, no push access) (design.md → Updating a PR).

Plan

In internal/forge/gitlab:

  • GetReadme: GET /projects/:id/repository/tree?ref=<default> (root, paginated), pick the name with the same ranking as internal/forge/gitea/readme.go (README.md, README.markdown, README, other README.*), then GET …/repository/files/:path?ref=<default> (base64). ref is required on GitLab, so use the project's default_branch. None found → ErrNotFound.
  • ListBranches: GET …/repository/branches (paginated). Tip commit is inline (commit.id, title, author_name, committed_date); Default ← default. No extra calls.
  • ListCommits: GET …/repository/commits?ref_name=<branch>&per_page=30 (first page only). Unknown branch → ErrNotFound.
  • ListTree: GET …/repository/tree?path=<dir>&ref=<ref>&per_page=100, paginated. type tree → dir, blob → file, commit or mode 160000 → submodule. A 404 (missing path since GitLab 17.7, or an empty repo) → ErrNotFound; an older server's 200 [] for a missing path stays an empty listing. Sizes aren't in the tree listing; leave Size zero and let ReadFile enforce the cap.
  • ReadFile: HEAD …/repository/files/:path?ref= first and refuse when X-Gitlab-Size exceeds core.MaxPreviewSize (match how core treats an oversized file), else GET …/repository/files/:path/raw?ref= for exact bytes (the JSON form transcodes non-UTF-8 text, which would hide binaries). The whole path is URL-encoded, / included, per #138's addressing. Check how core.MaxPreviewSize is applied today before choosing where the check lives.
  • UpdateStyles: [UpdateRebase]. UpdateBranch: PUT …/merge_requests/:iid/rebase; 202 is success (the rebase finishes asynchronously; the existing refresh after u picks up the new head). 403 and 409 → ErrRefused with GitLab's message. UpdateMerge → ErrUnsupported.
  • Fixtures synthesised from docs examples; update the README, branches, commits, files and update-branch rows in architecture.md.

Context

  • Precedents: internal/forge/github/{readme,branches,files,update}.go and their tests. Interfaces: internal/forge/forge.go (ReadmeReader, BranchReader, TreeReader, BranchUpdater).
  • Facts (docs.gitlab.com, 2026-10-12): files need ref and a fully encoded path; HEAD on the files endpoint returns X-Gitlab-Size; /raw returns exact bytes; blobs over 10 MB are limited to 5 requests a minute. Rebase returns 202 {"rebase_in_progress":true}; 403 Cannot push to source branch / Source branch is protected from force push; 409 Failed to enqueue the rebase operation. No API merges the target into an MR.

Out of scope

Diffs. Polling the async rebase to completion.

## Problem On GitLab the Repo box (README, Branches, Files tabs) is empty and `u` is unavailable until the adapter implements the readers and `BranchUpdater`. Parent: #137. **Depends on #138**; pick this up after #138 merges. ## Acceptance criteria - The GitLab adapter implements `forge.ReadmeReader`, `forge.BranchReader`, `forge.TreeReader` and `forge.BranchUpdater`, so the Repo box's README, Branches and Files tabs and `u` work as on Forgejo *(design.md → Repo box; Keymap `u`)*. - `u` on GitLab offers rebase only, because GitLab's API can rebase an MR but cannot merge the target branch into it *(design.md → Keymap `u` already says "GitHub offers merge only"; GitLab is the mirror case, forced by the API)*. design.md's two `u` sentences are updated to say so. - The status bar reports GitLab's reason when it refuses a rebase (protected branch, missing source branch, no push access) *(design.md → Updating a PR)*. ## Plan In `internal/forge/gitlab`: - `GetReadme`: `GET /projects/:id/repository/tree?ref=<default>` (root, paginated), pick the name with the same ranking as `internal/forge/gitea/readme.go` (`README.md`, `README.markdown`, `README`, other `README.*`), then `GET …/repository/files/:path?ref=<default>` (base64). `ref` is required on GitLab, so use the project's `default_branch`. None found → `ErrNotFound`. - `ListBranches`: `GET …/repository/branches` (paginated). Tip commit is inline (`commit.id, title, author_name, committed_date`); `Default` ← `default`. No extra calls. - `ListCommits`: `GET …/repository/commits?ref_name=<branch>&per_page=30` (first page only). Unknown branch → `ErrNotFound`. - `ListTree`: `GET …/repository/tree?path=<dir>&ref=<ref>&per_page=100`, paginated. `type` `tree` → dir, `blob` → file, `commit` or mode `160000` → submodule. A 404 (missing path since GitLab 17.7, or an empty repo) → `ErrNotFound`; an older server's 200 `[]` for a missing path stays an empty listing. Sizes aren't in the tree listing; leave `Size` zero and let `ReadFile` enforce the cap. - `ReadFile`: `HEAD …/repository/files/:path?ref=` first and refuse when `X-Gitlab-Size` exceeds `core.MaxPreviewSize` (match how core treats an oversized file), else `GET …/repository/files/:path/raw?ref=` for exact bytes (the JSON form transcodes non-UTF-8 text, which would hide binaries). The whole path is URL-encoded, `/` included, per #138's addressing. Check how `core.MaxPreviewSize` is applied today before choosing where the check lives. - `UpdateStyles`: `[UpdateRebase]`. `UpdateBranch`: `PUT …/merge_requests/:iid/rebase`; 202 is success (the rebase finishes asynchronously; the existing refresh after `u` picks up the new head). 403 and 409 → `ErrRefused` with GitLab's `message`. `UpdateMerge` → `ErrUnsupported`. - Fixtures synthesised from docs examples; update the README, branches, commits, files and update-branch rows in architecture.md. ## Context - Precedents: `internal/forge/github/{readme,branches,files,update}.go` and their tests. Interfaces: `internal/forge/forge.go` (`ReadmeReader`, `BranchReader`, `TreeReader`, `BranchUpdater`). - Facts (docs.gitlab.com, 2026-10-12): files need `ref` and a fully encoded path; `HEAD` on the files endpoint returns `X-Gitlab-Size`; `/raw` returns exact bytes; blobs over 10 MB are limited to 5 requests a minute. Rebase returns 202 `{"rebase_in_progress":true}`; 403 `Cannot push to source branch` / `Source branch is protected from force push`; 409 `Failed to enqueue the rebase operation`. No API merges the target into an MR. ## Out of scope Diffs. Polling the async rebase to completion.
Author
Owner

🤖 Picked up through composer-lite on branch feature/140-gitlab-repo; the plan is this ticket body.

🤖 Picked up through composer-lite on branch `feature/140-gitlab-repo`; the plan is this ticket body.
Author
Owner

🤖 MR open: #147

🤖 MR open: https://git.bobparsons.dev/deadstyle/lazyforge/pulls/147
Author
Owner

🤖 merged: #147

🤖 merged: https://git.bobparsons.dev/deadstyle/lazyforge/pulls/147
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
deadstyle/lazyforge#140
No description provided.