| @@ -25,13 +25,14 @@ The web interface is then on `127.0.0.1:3200` and wants a reverse proxy with TLS |
| 25 | 25 | - Pull mirrors that fetch a source on a schedule and stay read only and push mirrors that copy every branch and tag to another host after each change |
| 26 | 26 | - Pull requests opened by pushing to `refs/for/<branch>` with no fork needed |
| 27 | 27 | - Static sites served from `<user>.<host>` or a domain of your own, built from a `pages` repository or the `pages` branch of any repository, private ones included |
| 28 | | -- Gists of one or more flat files with public or secret visibility, revisions shown as diffs, stars, forks, comments, iframe embeds and a JSON API for uploads from the command line |
| 28 | +- Gists of one or more flat files with public or secret visibility, revisions shown as diffs, stars, forks, comments, iframe embeds and a command line uploader |
| 29 | +- A JSON API that mirrors the URL of every page under `/api`, with tokens scoped to everything, reads only or gists only |
| 29 | 30 | - Reports to admins and per account blocking, with hourly limits on new issues, pull requests and repositories |
| 30 | 31 | - SSH and PGP commit verification, TOTP, access tokens and Atom feeds for commits, issues and pull requests |
| 31 | | -- Site search over repositories and people with language facets |
| 32 | +- Site search over repositories, people and public gists with language facets |
| 32 | 33 | - Light and dark themes, light by default, with an option to follow the device |
| 33 | 34 | - A notification bell for activity on pull requests and issues you take part in |
| 34 | | -- Open, invite only or closed registration and an `/admin` page for accounts, invites, the front page and storage |
| 35 | +- Open, invite only or closed registration and an `/admin` page for accounts, invites, the front page, storage and gists |
| 35 | 36 | |
| 36 | 37 | There is no wiki and no CI and neither is planned. |
| 37 | 38 | |
| @@ -71,14 +72,30 @@ A gist is a bare repository at `~you/.gists/<id>.git` holding a single `main` br |
| 71 | 72 | |
| 72 | 73 | Secret gists are unlisted rather than private, which means anyone holding the link can read, clone or fork them while neither the gist index nor a profile ever lists one. Creating a gist needs an account at least a day old and shares the hourly upload limit, and anyone signed in may report a gist to the admins, who can delete it from its page. |
| 73 | 74 | |
| 74 | | -Raw files and embeds are served from `gist.git.example.org`, a separate origin that never sees a cookie, answers only `GET` and sends every file as `text/plain` or an image under a sandboxed content security policy, so a hostile file cannot run script against the main site. A pinned revision such as `/raw/<id>/<commit>/<name>` is cached as immutable, while the embed page at `/embed/<id>` may be framed by any site. |
| 75 | +Raw files and embeds are served from `gist.git.example.org`, a separate origin that never sees a cookie, answers only `GET` and sends every file as `text/plain` or an image under a sandboxed content security policy, so a hostile file cannot run script against the main site. A pinned revision such as `/raw/<id>/<commit>/<name>` is immutable for browsers but kept for at most an hour at the Cloudflare edge, so a gist that an admin deletes stops being served within the hour, while the embed page at `/embed/<id>` may be framed by any site. |
| 76 | + |
| 77 | +`contrib/gist` uploads files or standard input with nothing beyond `curl`, reading the token from `GITPOCKET_TOKEN` and the site from `GITPOCKET_URL`, and prints the address of the new gist. A token scoped `gist` is enough for it and cannot touch your repositories. |
| 75 | 78 | |
| 76 | 79 | ``` |
| 77 | | -curl -u you:TOKEN --data-binary @notes.txt 'https://git.example.org/api/gists?file=notes.txt&public=1' |
| 78 | | -curl -u you:TOKEN -H 'Content-Type: application/json' -d '{"description":"demo","public":false,"files":{"a.ml":{"content":"let x = 1"}}}' https://git.example.org/api/gists |
| 80 | +install -m 755 contrib/gist ~/.local/bin/gist |
| 81 | +gist -p -d "closure conversion" closure.ml README.md |
| 82 | +make 2>&1 | gist -n build.log |
| 79 | 83 | ``` |
| 80 | 84 | |
| 81 | | -The first form prints the address of the new gist and the second answers with the same JSON that `GET /api/gists/<id>` returns, in the shape of the GitHub gist API, while uploads without `public` default to secret. |
| 85 | +### API |
| 86 | + |
| 87 | +Every page has a JSON twin at the same path under `/api`, so `/~you/repo/pulls/3` is read from `/api/~you/repo/pulls/3`, and `/api` itself lists every route with the scope it needs, as JSON for programs and as a table for a browser. Accounts appear as `~name` handles and repositories as `~owner/name` paths, lists answer `{items, total, next}` with `?from=` and `?limit=` and every error is `{status, error}` with the HTTP status repeated in the body. |
| 88 | + |
| 89 | +``` |
| 90 | +curl -H "Authorization: Bearer $TOKEN" https://git.example.org/api/me |
| 91 | +curl -H "Authorization: Bearer $TOKEN" -d '{"title":"crash on empty input"}' https://git.example.org/api/~you/repo/issues |
| 92 | +curl -H "Authorization: Bearer $TOKEN" -X PUT -d '{"content":"hello\n","message":"greet"}' https://git.example.org/api/~you/repo/tree/main/hello.txt |
| 93 | +curl -H "Authorization: Bearer $TOKEN" -d '{"strategy":"squash"}' https://git.example.org/api/~you/repo/pulls/3/merge |
| 94 | +``` |
| 95 | + |
| 96 | +Tokens are sent as a bearer or as the password of HTTP basic auth and carry a scope of `all`, `read` or `gist`, where a `read` token cannot push or change anything and a `gist` token adds writes to your own gists alone. Minting a token through `POST /api/me/tokens` needs the password and an account with two-factor authentication refuses the password everywhere in the API, so a leaked token can neither create another nor outlive its revocation. Session cookies only count when the request comes from the site itself, which lets any page call the API with a token through CORS while a cross-site form can never act with your session. |
| 97 | + |
| 98 | +The API calls the same functions as the web forms, which means archived repositories, blocks, branch protection, the daily thread budget and the hourly creation limits apply identically, and writes are further capped at one a second with a burst of 120 per account. |
| 82 | 99 | |
| 83 | 100 | ### Mirrors |
| 84 | 101 | |