Files
gitea-axi/.claude/gitea-vs-github.md

243 lines
17 KiB
Markdown

# Gitea vs GitHub: API Feature Comparison for CLI Agent Tooling
Focus: features relevant to `gitea-axi` — issues, pull requests, labels, reviews, comments, auth, and API mechanics.
Sources: Gitea OpenAPI spec (docs.gitea.com/api/1.21), GitHub REST docs, Gitea source structs (`modules/structs/`).
---
## API Style
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| REST API | Yes — `/api/v1` (actually `/repos/...` at api.github.com) | Yes — `/api/v1/repos/...` | Gitea mirrors GitHub's path structure closely |
| GraphQL API | Yes — `api.github.com/graphql` | **No** | Gitea is REST-only; no GraphQL endpoint exists |
| OpenAPI spec | Unofficial/community | Yes — `/api/swagger` on every instance | Gitea ships a first-class Swagger UI and JSON spec |
| TypeScript client | `@octokit/rest` (hand-written) | `gitea-js` (generated from OpenAPI) | `gitea-js` types are auto-generated and stay in sync with the spec |
| API versioning | `api-version` header (`2026-03-10`) | Path-prefixed (`/api/v1`) | GitHub uses a header; Gitea uses path versioning |
---
## Authentication
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| Personal access tokens (classic) | Yes — broad scope-based | Yes — scope-based (`read:issue`, `write:repository`, etc.) | Gitea scopes map to API route groups |
| Fine-grained PATs | Yes — per-repo permissions, expiry required | **No** — scopes are coarser (category-level, not per-repo) | GitHub fine-grained tokens are more restrictive by default |
| Token creation via API | No (web only for fine-grained) | **Yes**`POST /user/tokens` | Gitea allows programmatic token creation with BasicAuth |
| Token listing/deletion via API | No | **Yes**`GET /user/tokens`, `DELETE /user/tokens/{id}` | Gitea exposes full token lifecycle over the API |
| OAuth App device flow | **Yes** | **No** — only Authorization Code (+ PKCE) | GitHub device flow is essential for headless CLI auth; Gitea lacks it |
| OAuth App web flow | Yes | Yes | Both support standard Authorization Code grant |
| OAuth App management via API | No | **Yes**`GET/POST/PATCH/DELETE /user/applications/oauth2` | Gitea lets users manage their own OAuth apps via API |
| GitHub Apps / installation tokens | Yes | **No** — no GitHub Apps concept | Gitea has no equivalent of GitHub Apps or installation tokens |
| `GITHUB_TOKEN` in Actions | Yes | No (`GITEA_TOKEN` in Gitea Actions is similar but not identical) | Gitea Actions has a built-in `GITEA_TOKEN` but it is instance-specific |
| HTTP Signatures (SSH key auth) | No | **Yes** | Gitea accepts signatures per draft-cavage-http-signatures |
| Basic auth | Deprecated/removed | Yes (for token creation only; not recommended for general use) | |
| Sudo (act-as) | No | **Yes**`?sudo=username` for admins | Admin-only; useful for automation |
| SAML SSO token authorization | Yes (org-enforced) | No | Gitea has no SAML SSO concept |
---
## Pagination
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| Offset/page pagination | Yes — `?page=N&per_page=N` (max 100) | Yes — `?page=N&limit=N` | Same concept; parameter names differ (`per_page` vs `limit`) |
| Cursor-based pagination | Some endpoints (`before`/`after`) | **No** | Gitea is page-offset only |
| `Link` header (RFC 5988) | **Yes**`next`, `prev`, `last`, `first` | **Yes** — same format | Both provide `Link` headers for navigation |
| `X-Total-Count` response header | **No** | **Yes** | Gitea returns total count in every list response; GitHub does not |
| `x-total-count` in `gitea-js` | n/a | Available as a parsed header | The `gitea-js` client exposes this alongside the response body |
| Maximum per-page results | 100 | Configurable (instance default ~50, max configurable) | Gitea's max is admin-controlled; no hard 100 cap in the API |
---
## Rate Limiting
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| Per-user rate limit | 5,000 req/hr (authenticated) | **No standard rate limiting** | Gitea uses QoS concurrency throttling, not per-user counters |
| `X-RateLimit-*` headers | Yes — `Limit`, `Remaining`, `Used`, `Reset` | **No** | Gitea does not emit rate-limit headers |
| Secondary limits | Yes (content creation, concurrency) | No | |
| QoS / overload protection | No | **Yes** — configurable `[qos]` section | Gitea drops or queues requests under load rather than rate-limiting |
---
## Issue Management
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| Create issue | Yes — `POST /repos/{owner}/{repo}/issues` | Yes — same path pattern | |
| List issues | Yes | Yes | |
| Get single issue | Yes | Yes | |
| Edit issue | Yes | Yes | |
| Delete issue | **No** (close only) | **Yes**`DELETE /repos/{owner}/{repo}/issues/{index}` | Gitea allows hard deletion |
| Issue state | `open` / `closed` | `open` / `closed` | Same values |
| `state_reason` | **Yes**`completed`, `not_planned`, `duplicate`, `reopened` | **No** | Gitea has no close-reason concept |
| Issue type | **Yes** — first-class `type` field on create/edit | **No** | GitHub supports issue type classification; Gitea does not |
| `duplicate_issue_id` | **Yes** | **No** | |
| Issue dependencies (blocking) | No (sub-issue relationships only) | **Yes**`POST /repos/{owner}/{repo}/issues/{index}/dependencies` | Gitea has explicit blocking/blocked-by relationships between issues |
| Issue blocking list | No | **Yes**`GET /repos/{owner}/{repo}/issues/{index}/blocked-by` | |
| Pin issue | Partial (read-only in API response) | **Yes**`POST/DELETE /repos/{owner}/{repo}/issues/{index}/pin`, position reorder | Gitea has full pin CRUD including ordering |
| Issue timeline | Yes — 30+ event types (labels, assignments, reviews, etc.) | Yes — `GET /repos/{owner}/{repo}/issues/{index}/timeline` (comments + events) | GitHub's timeline is richer in event type variety |
| Reactions on issues | Yes — 8 types | Yes — same types | |
| Reactions on issue comments | Yes | Yes | |
| Lock/unlock conversation | Yes — with `lock_reason` (`off-topic`, `too heated`, `resolved`, `spam`) | **No** — issues have `IsLocked` field but no dedicated lock endpoint visible | |
| Subscriptions (watching) | Via notifications API | **Yes** — explicit `PUT/DELETE /repos/{owner}/{repo}/issues/{index}/subscriptions` | Gitea exposes subscribe/unsubscribe directly on issues |
| Issue search (cross-repo) | Yes — `GET /search/issues` with qualifiers | Yes — `GET /issues/search` | Both support cross-repo search |
| Custom field values | **Yes** (org repos) | **No** | GitHub-specific feature |
| Time tracking (estimate) | No | **Yes**`TimeEstimate` field on Issue struct | Gitea has built-in time tracking |
| Content versioning (conflict detect) | No | **Yes**`ContentVersion` field on create/edit | Gitea supports optimistic locking for concurrent edits |
| Original author tracking (migrations) | No | **Yes**`OriginalAuthor` / `OriginalAuthorID` fields | Preserved when importing from other platforms |
| Issue `Ref` field | No | **Yes** — git ref associated with issue | |
---
## Milestones
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| Milestone CRUD | Yes — `GET/POST/PATCH/DELETE /repos/{owner}/{repo}/milestones` | Yes — same path pattern | |
| Milestone on issues | Yes | Yes | |
| Milestone on PRs | Yes | Yes — `milestone` field in `CreatePullRequestOption` and `EditPullRequestOption` | |
| Milestone properties | `title`, `description`, `state`, `due_on` | Same | |
| Org-level milestones | No | No | Neither supports org-level milestones |
---
## Label Management
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| Repo label CRUD | Yes — `GET/POST/PATCH/DELETE /repos/{owner}/{repo}/labels` | Yes — same path pattern | |
| Org-level labels | No | **Yes**`GET/POST/PATCH/DELETE /orgs/{org}/labels` | Gitea supports organization-scoped labels |
| Label properties | `name`, `color`, `description` | `name`, `color`, `description` | |
| Add/remove labels on issue | Yes | Yes | |
| Add/remove labels on PR | Yes | Yes | |
| Replace all labels | Yes | Yes | |
---
## Pull Request Management
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| Create PR | Yes | Yes | |
| List PRs | Yes | Yes | |
| Get PR | Yes | Yes | |
| Edit PR | Yes | Yes | |
| Delete PR | No | No | Neither allows hard deletion |
| Draft PRs | **Yes**`draft: true` on create | **Yes**`Draft bool` field | Both support drafts |
| Draft PR filter in list | Yes | Yes — `draft` query param available | |
| `maintainer_can_modify` | Yes | **Yes**`AllowMaintainerEdit` field | Same concept, different name |
| `ContentVersion` conflict detection | No | **Yes** | Same optimistic-locking field as issues |
| Pin PR | No | **Yes** — same pin endpoints as issues, `PinOrder` field | |
| Get PR diff | Yes — `Accept: application/vnd.github.diff` | **Yes**`GET /repos/{owner}/{repo}/pulls/{index}/diff` | Gitea returns raw diff text |
| Get PR patch | Yes — `Accept: application/vnd.github.patch` | Yes — `GET /repos/{owner}/{repo}/pulls/{index}/diff` with format param | |
| List PR files (structured) | Yes — `GET /pulls/{number}/files` | **Yes**`GET /repos/{owner}/{repo}/pulls/{index}/files` | Returns file list with additions/deletions |
| List PR commits | Yes | **Yes**`GET /repos/{owner}/{repo}/pulls/{index}/commits` | |
| Mergeable status | Yes — `mergeable` field | **Yes**`Mergeable bool` field on PR | |
| Merge strategies | merge, squash, rebase | merge, squash, rebase, **rebase-merge** (rebase + no-ff), **fast-forward-only** | **Gitea has more merge strategies** |
| Squash merge | Yes | Yes | |
| Rebase merge | Yes | Yes | |
| Fast-forward-only | No | **Yes** | |
| Rebase + explicit merge commit | No | **Yes**`rebase-merge` style | |
| Auto-merge (merge when checks pass) | Yes | **Yes**`MergeWhenChecksSucceed` field | Both support scheduling a merge after CI passes |
| Cancel auto-merge | Yes | Yes — `DELETE /repos/{owner}/{repo}/pulls/{index}/merge` | |
| Delete branch after merge | Yes | **Yes**`DeleteBranchAfterMerge` field on merge request | |
| Force merge | Limited (bypass protections with permissions) | **Yes**`ForceMerge bool` field | Gitea has an explicit force-merge flag |
| Manually merged (mark as merged) | No | **Yes**`Do: "manually-merged"` + `MergeCommitID` field | Gitea allows recording a manual merge after the fact |
| `HeadCommitSHA` validation on merge | No | **Yes**`HeadCommitID` field for merge safety | |
| PR merge commit message/title | Yes — `commit_title`, `commit_message` | **Yes**`MergeTitleField`, `MergeMessageField` | |
| Pinned PRs list | No | **Yes**`GET /repos/{owner}/{repo}/pulls/pinned` | |
---
## Review Management
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| List reviews | Yes | Yes | |
| Create review | Yes | Yes | |
| Submit pending review | Yes | Yes | |
| Delete pending review | Yes | Yes | |
| Dismiss review | Yes — `POST /pulls/{number}/reviews/{id}/dismissals` | **Yes**`POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismiss` | |
| Un-dismiss review | No | **Yes**`POST /repos/{owner}/{repo}/pulls/{index}/reviews/{id}/undismiss` | Gitea allows undoing a dismissal |
| Review states | `APPROVED`, `REQUEST_CHANGES`, `COMMENT`, `PENDING`, `DISMISSED` | `APPROVED`, `REQUEST_CHANGES`, `COMMENT`, `PENDING`, (+ `REQUEST_REVIEW`) | States match; Gitea adds `REQUEST_REVIEW` as a state type |
| `DISMISSED` state | Yes — set by dismiss action | Yes — `Dismissed bool` + `Stale bool` fields | |
| Aggregated `reviewDecision` field | **Yes (GraphQL only)**`APPROVED`, `CHANGES_REQUESTED`, `REVIEW_REQUIRED` | **No** | Gitea has no single aggregated review decision; must be computed from individual reviews |
| `reviewDecision` via REST | No (GitHub REST also lacks this) | No | Both REST APIs require client-side aggregation |
| Official/required review flag | No | **Yes**`Official bool` field on review | Gitea marks reviews from required reviewers as official |
| `Stale` review flag | No | **Yes**`Stale bool` field | Gitea marks reviews stale when new commits are pushed |
| Review request (users) | Yes — `POST /pulls/{number}/requested_reviewers` | **Yes**`POST /repos/{owner}/{repo}/pulls/{index}/requested_reviewers` | |
| Review request (teams) | Yes — `team_reviewers` param | **Yes**`TeamReviewers` param | |
| Remove review request | Yes | Yes | |
| List eligible reviewers | No (must list repo collaborators separately) | **Yes**`GET /repos/{owner}/{repo}/pulls/{index}/requested_reviewers` returns all eligible users | Gitea returns who *can* be requested, not just who has been |
| Inline code comments on review | Yes — with line, side, path, start_line | **Yes**`CreatePullReviewComment` struct with position/path | |
| Multi-line comment range | Yes — `start_line`/`start_side` + `line`/`side` | Partial — position-based rather than line-range | GitHub's multi-line range is more explicit |
| Reply to review comment | Yes — `POST /pulls/{number}/comments/{id}/replies` | Yes — reply via in_reply_to reference | |
| List review comments (inline) | Yes | Yes | |
| Edit/delete review comment | Yes | Yes | |
| Reactions on review comments | Yes | **No** — reactions are on issue comments only, not PR review comments | Gitea's reaction support does not extend to PR review comments |
| `commitId` on review | Yes | **Yes**`CommitID` field | |
---
## Issue & PR Comments
| Feature | GitHub | Gitea | Notes |
|---|---|---|---|
| List issue comments | Yes | Yes | |
| Create issue comment | Yes | Yes | |
| Edit comment | Yes | Yes | |
| Delete comment | Yes | Yes | |
| Pin/unpin comment | **Yes**`PUT/DELETE /issues/comments/{id}/pin` | No | GitHub supports pinned comments on issues |
| Reactions on issue comments | Yes — 8 types | **Yes** — same 8 types | |
| Reactions on PR review comments | Yes | **No** | Gap in Gitea: review comment reactions not supported |
| `body_html` / `body_text` fields | Yes | No — body is Markdown only | GitHub returns rendered HTML variants |
| `author_association` field | Yes | No | GitHub classifies commenter relationship to repo |
---
## Summary: Key Gaps and Gitea Advantages
### Gitea lacks (compared to GitHub)
| Missing in Gitea | Impact on CLI tooling |
|---|---|
| No GraphQL API | Cannot get aggregated `reviewDecision`; must compute from review list |
| No `reviewDecision` enum (REST or GraphQL) | CLI must fold `APPROVED`/`REQUEST_CHANGES` reviews client-side |
| No device flow OAuth | Headless login requires PAT or BasicAuth; cannot do browser-less OAuth |
| No `state_reason` on issues | Cannot distinguish "completed" vs "not planned" close reasons |
| No issue type (bug/feature classification) | Labels must substitute for issue type |
| No fine-grained per-repo PATs | Tokens are category-scoped, not repo-scoped |
| No `X-RateLimit-*` headers | Cannot back off gracefully on rate limits (no limit signal) |
| No cursor-based pagination | Must use offset pagination; large result sets may drift |
| No `body_html` on comments | Must render Markdown client-side if HTML is needed |
| No `author_association` | Cannot determine contributor relationship without separate lookup |
| No reactions on PR review comments | Review comment reactions not possible |
| No lock/unlock issue endpoint | Issue locking is not exposed as an API operation |
| No GitHub Apps / installation tokens | Cannot use app-level auth scoping |
### Gitea advantages (compared to GitHub REST)
| Gitea-only capability | Impact on CLI tooling |
|---|---|
| `X-Total-Count` response header | Accurate totals on every list response; no extra count query needed |
| Token CRUD via API | Programmatic token management without web UI |
| OAuth app management via API | Automation-friendly app registration |
| Hard delete issues | Useful for cleanup automation |
| Issue dependencies (blocking/blocked-by) | Richer workflow modelling |
| Issue pinning with position reorder | Full pin management via API |
| Issue subscriptions endpoint | Subscribe/unsubscribe without using notifications API |
| Org-level labels | Labels shared across all org repos |
| `Official` + `Stale` flags on reviews | Richer review state without client-side inference |
| `Undismiss` review endpoint | Reversible review dismissals |
| Eligible reviewer list on PR | Know who *can* review before requesting |
| `ForceMerge` flag | Explicit force-merge without separate branch protection bypass |
| `manually-merged` merge style | Record out-of-band merges |
| `rebase-merge` + `fast-forward-only` styles | More granular merge strategy control |
| `ContentVersion` on issues and PRs | Optimistic locking for concurrent edits |
| Time tracking (`TimeEstimate`) | Built-in estimation without third-party integrations |
| `Sudo` param (admin) | Act-as for admin automation |
| HTTP Signature auth | SSH key based API auth |
| `PinOrder` on PRs | PRs can also be pinned, not just issues |