resbuilder_app/.opencode/rules/gitea-skill.md
2026-07-18 23:38:41 +10:00

183 lines
6.9 KiB
Markdown

# Gitea / Forgejo Workflow
Gitea (and its fork Forgejo) expose a **GitHub-compatible REST API** — most `curl` patterns from the GitHub skills work with minimal changes. This skill covers the differences and provides a complete workflow for self-hosted instances.
## When to Use This Skill
- The git remote points to a non-GitHub host (e.g. `gitea@host:owner/repo.git`)
- `gh` CLI is not available or doesn't support the platform
- You need to create PRs, check CI, or manage repos on a self-hosted Gitea instance
## Auth Detection
```bash
# Extract owner/repo from the SSH remote
REMOTE_URL=$(git remote get-url origin)
OWNER_REPO=$(echo "$REMOTE_URL" | sed 's|.*:||; s|\.git$||')
OWNER=$(echo "$OWNER_REPO" | cut -d/ -f1)
REPO=$(echo "$OWNER_REPO" | cut -d/ -f2)
# Determine the Gitea API base from the remote
GITEA_HOST=$(echo "$REMOTE_URL" | sed 's|.*@||; s|:.*||')
GITEA_API="http://${GITEA_HOST}:3000/api/v1" # default port, adjust if different
# Try to find a token
if [ -n "$GITEA_TOKEN" ]; then
TOKEN="$GITEA_TOKEN"
elif [ -f "$HOME/.gitea_token" ]; then
TOKEN=$(cat "$HOME/.gitea_token")
else
echo "No GITEA_TOKEN found — API calls will fail for write operations"
echo "Create a token at: https://<gitea-domain>/user/settings/applications"
fi
```
## Creating a PR
```bash
BRANCH=$(git branch --show-current)
curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
"${GITEA_API}/repos/${OWNER}/${REPO}/pulls" \
-d "{
\"title\": \"feat: add user authentication\",
\"body\": \"## Summary\\nAdds login and register API endpoints.\",
\"head\": \"$BRANCH\",
\"base\": \"master\"
}"
```
**Note:** Gitea defaults to `master` not `main` for the base branch.
## Key Differences from GitHub
| Aspect | GitHub | Gitea |
|--------|--------|-------|
| API base URL | `https://api.github.com` | `https://<host>/api/v1` |
| Auth header | `Authorization: token <PAT>` | Same format |
| SSH remote | `git@github.com:o/r.git` | `git@<host>:o/r.git` |
| `gh` CLI | Works | Not supported |
| Default branch | `main` | `master` |
| Auto-merge | Supported via GraphQL | Not supported |
## Posting Comments on a PR
Comments on a PR use the **issues/comments** endpoint (Gitea treats PRs as issues for comments):
```bash
curl -s -X POST \
-H "Authorization: token $TOKEN" \
-H "Content-Type: application/json" \
"${GITEA_API}/repos/${OWNER}/${REPO}/issues/${PR_NUMBER}/comments" \
-d '{"body": "Your comment text here"}'
```
## Checking PR Status
```bash
# Get PR details (state, mergeable, comment/review counts)
curl -s \
-H "Authorization: token $TOKEN" \
"${GITEA_API}/repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}" \
| jq '{state, mergeable, comments, review_comments}'
# List comments on a PR
curl -s \
-H "Authorization: token $TOKEN" \
"${GITEA_API}/repos/${OWNER}/${REPO}/issues/${PR_NUMBER}/comments" \
| jq -r '.[] | "\(.id): \(.user.login) — \(.body[0:120])..."'
```
## Monitoring CI Build Status
Gitea Actions exposes build status via the **commit status API**:
```bash
# Combined status (state: pending/success/failure/error)
curl -s -H "Authorization: token $TOKEN" \
"${GITEA_API}/repos/${OWNER}/${REPO}/commits/${SHA}/status" \
| jq '{state, sha, total_count, statuses: [.statuses[] | {context, status, description, target_url}]}'
```
The response shape:
```json
{
"state": "pending",
"sha": "a5342300...",
"total_count": 1,
"statuses": [{
"context": "Build and Push Image / Build and push image (push)",
"status": "pending",
"description": "Waiting to run",
"target_url": "/armistace/resbuilder_ai/actions/runs/25/jobs/0"
}]
}
```
### CRITICAL: Commit status API can return empty
The commit status API can return `{"state":"","sha":"","total_count":0,"statuses":null}` even when a build is actively running. This happens when:
- The commit was pushed but the runner hasn't picked it up yet (push is still in progress — can take 30-60+ min for large images)
- The runner is slow to report status back to Gitea
- The build is running but hasn't updated the commit status yet
**Do not treat an empty status response as "build complete" or "no build needed".** Always cross-reference with runner logs to confirm.
## Reading Raw File Content from a Branch
For **public repos**, the raw endpoint works directly:
```bash
curl -s "https://<gitea-domain>/$OWNER/$REPO/raw/branch/$BRANCH/$FILE_PATH"
```
For **private repos**, use the API contents endpoint:
```bash
curl -s \
-H "Authorization: token $TOKEN" \
"${GITEA_API}/repos/${OWNER}/${REPO}/contents/${FILE_PATH}?ref=${BRANCH}" \
| python3 -c "import sys,json,base64; raw=json.load(sys.stdin); print(base64.b64decode(raw['content']).decode())"
```
**Important:** The `/contents` endpoint resolves to the branch you specify in `?ref=`. For PR head branches, use the branch name directly (e.g. `?ref=frontend-and-fixes`) — using `ref=pulls/9/head` may return empty content for modified files because it resolves to the base branch's version of those files.
## Merging a PR
```bash
PR_NUMBER=<number>
# Merge the PR via API (squash)
curl -s -X POST \
-H "Authorization: token $TOKEN" \
"${GITEA_API}/repos/${OWNER}/${REPO}/pulls/${PR_NUMBER}/merge" \
-d '{"Do": "squash"}'
# Delete the remote branch after merge
BRANCH=$(git branch --show-current)
git push origin --delete $BRANCH
git checkout master && git pull origin master
git branch -d $BRANCH
```
## What Doesn't Work
- **`gh` CLI** — no Gitea backend. All operations use `git` + `curl`.
- **Gitea Actions REST API** (`/actions/runs`) — returns 404 for non-admin users. Use the commit status API instead.
- **Gitea Actions web UI** (`/actions`) — also returns 404 from bot tokens. Only the repo owner can see it via the browser.
- **Auto-merge** — no GraphQL endpoint available.
## Pitfalls
- **Gitea returns 404 for API calls without auth** — even for public repos. Always include the token.
- **Default branch is `master`** not `main` — adjust all `base` parameters.
- **HTTP vs HTTPS** — many self-hosted instances run on plain HTTP. Match the protocol.
- **Token creation** — at `User Settings → Applications → Generate New Token`. The `repo` scope covers everything.
- **PR comments use the issues endpoint** — Gitea doesn't have a separate PR comment endpoint. Use `/issues/{id}/comments`.
- **Pushing to an existing PR branch** — after pushing new commits, the PR updates automatically. No need to recreate it.
- **The `raw` endpoint** — use `/raw/branch/{branch}/{path}` not `/contents/{path}` for direct file content.
- **Contents API with PR ref** — using `?ref=pulls/N/head` on the `/contents` endpoint returns the **base branch version** of modified files, not the PR head version. Always use the branch name directly.
- **Old statuses accumulate** — `GET /commits/{sha}/statuses` returns ALL statuses ever set for that commit. Filter by `created_at` to find the latest. Use `GET /commits/{sha}/status` (singular) for the combined/current state.