API examples
amber-git’s routes, with real requests and responses. Examples use a
repository called myrepo and a token in $TOKEN; see
Authentication. Bodies are plain text unless noted.
Git
Clone, fetch and push with stock git at
https://amber-git.stackwell.tech/<repo>.git:
git -c http.extraHeader="Authorization: Bearer $TOKEN" \
clone https://amber-git.stackwell.tech/myrepo.git
These are git’s smart HTTP routes, which git calls for you:
| Route | Used by |
|---|---|
GET /<repo>.git/info/refs?service=git-upload-pack | Clone, fetch |
POST /<repo>.git/git-upload-pack | Clone, fetch |
GET /<repo>.git/info/refs?service=git-receive-pack | Push |
POST /<repo>.git/git-receive-pack | Push |
A gated update is refused with its handle and queued; the rest of the push applies:
$ git push --force origin main
To https://amber-git.stackwell.tech/myrepo.git
! [remote rejected] main -> main (queued for approval by the repo owner (ref VTvpvFgh_qVc))
error: failed to push some refs to 'https://amber-git.stackwell.tech/myrepo.git'
git exits non-zero, because the ref did not move. See Concepts for what happens next.
Withdraw a queued push
POST /<repo>.git/withdraw takes back a queued force-push or delete before
the owner decides. Name it by the handle your push printed:
curl -sS https://amber-git.stackwell.tech/myrepo.git/withdraw \
-H "Authorization: Bearer $TOKEN" \
-d '{"handle": "VTvpvFgh_qVc"}'
Response, 200 OK:
withdrawn: VTvpvFgh_qVc will never apply
Only the pusher who queued it, or the repository’s owner signed in as a person, may withdraw it. Answers:
| Status | Body | Meaning |
|---|---|---|
200 | withdrawn: <handle> will never apply | Withdrawn, now or earlier |
400 | body must be {"handle": "<the ref your push printed>"} | No handle in the body |
401 | unauthenticated | No valid token |
404 | no queued operation <handle> of yours on <repo> | No such handle on this repository, or not yours to withdraw |
409 | the owner already approved <handle>; ... | Too late: it applies on that approval |
409 | the owner already denied <handle>; it will never apply | Already settled |
502 | could not reach granite to withdraw <handle>; it is still queued ... | Retry; until it succeeds, the owner can still approve it |
Check whether a queued push is still waiting
GET /<repo>.git/queued?sha=<sha> lists your queued pushes that would move a
branch to that commit, and where each one stands. <sha> is the full
40-character lowercase commit id you pushed. It only reads; it changes nothing.
curl -sS "https://amber-git.stackwell.tech/myrepo.git/queued?sha=4f2c9e1b7a3d5c8e0f1a2b3c4d5e6f708192a3b4" \
-H "Authorization: Bearer $TOKEN"
Response, 200 OK:
{"queued":[{"handle":"VTvpvFgh_qVc","ref":"refs/heads/main","granite_status":"pending"}]}
granite_status is pending (the owner hasn’t decided) or approved (the
owner approved it and amber-git is moving the branch). An empty list means
nothing is waiting: the owner denied it, it was withdrawn, its 7 days ran out,
or it has already applied. Fetch to tell which of the last two.
You see the pushes you queued, and the repository’s owner signed in as a person sees everyone’s. Anyone else, and a repository you can’t reach, gets an empty list. A queued delete moves no branch to a commit, so it is never listed.
| Status | Meaning |
|---|---|
200 | The list, possibly empty |
400 | ?sha= missing, or not 40 lowercase hex characters |
401 | No valid token |
502 | Granite couldn’t be read, so nothing is known; retry |
Read files without cloning
Both routes are authorized like a fetch. <sha> is a full 40-character
lowercase commit id, and it must be reachable from a branch or tag.
A commit’s files
GET /api/repos/<repo>/tree/<sha> returns every file in the commit, in the
format of git ls-tree -r -z --full-tree <sha>: one
<mode> <type> <id>\t<path> entry per file, each ending in a NUL byte.
Directories are not listed; submodules are, as commit entries.
curl -sS https://amber-git.stackwell.tech/api/repos/myrepo/tree/$SHA \
-H "Authorization: Bearer $TOKEN" | tr '\0' '\n'
Response, 200 OK, Content-Type: application/octet-stream (excerpt):
100644 blob 24bca6bbcd45b4b78e08377169f112825bf7f778 Cargo.toml
100644 blob 8fcff3bd909c31072d6159c42460d26b36d52427 README.md
100644 blob c557356b6219d280d17a5921fa695fd441cdf049 serve/src/lib.rs
One file
GET /api/repos/<repo>/blob/<sha>/<path> returns the file’s bytes as
application/octet-stream, up to 1 MiB.
curl -sS https://amber-git.stackwell.tech/api/repos/myrepo/blob/$SHA/README.md \
-H "Authorization: Bearer $TOKEN"
Answers on both routes:
| Status | Meaning |
|---|---|
200 | The listing, or the file’s bytes |
404 with header Amber-Git-Absent: path | No file at that path; also a directory or a submodule. Body: no file <path> at <sha> |
404 without that header | The repository is not there, or the path is malformed (<sha> not 40 lowercase hex) |
413 | The file is over 1 MiB, or the listing or the trees it reads are over 16 MiB |
422 | no commit <sha> is reachable from a ref of <repo> |
401, 403, 410 | As for a fetch; see Errors and limits |
A 404 without Amber-Git-Absent never means “no such file”: check the
header before you treat a file as missing.
Re-send to the GitHub mirror
For a repository that mirrors to GitHub, POST /<repo>.git/remirror re-pushes
every branch and tag to the mirror, and deletes from it each branch or tag
deleted here. It is authorized like a fetch.
curl -sS -X POST https://amber-git.stackwell.tech/myrepo.git/remirror \
-H "Authorization: Bearer $TOKEN"
Response, 202 Accepted, where <m> is the number of refs, <n> how many
of them were queued, and <d> how many of them were deleted here:
queued <n> of <m> ref(s) of myrepo for mirroring (<d> of them deleted here, to delete on GitHub)
202 means the pushes are queued, not that GitHub matches yet. Compare with
git ls-remote against both. A repository that does not mirror answers 409.
Health
curl -fsS https://amber-git.stackwell.tech/health
200 with ok, for any method, without a token.