183 lines
6.9 KiB
Markdown
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.
|