# Commits through the API

Branch and commit with no clone through the contents and git data APIs, in git's own shas, and how the REST API reads a commit's verification and authors.

The contents and git data APIs write the same repository `git clone` reads, so an agent that never clones can branch and commit. Create a branch, commit a file through the contents API, commit a tree through the git data API, then clone the branch:

```bash
H='Authorization: Bearer ghp_anything'
R="$RYSTIC_URL/repos/tb-owner/tb-repo"
TIP=$(curl -s "$R/git/ref/heads/tb-base-branch" -H "$H" | jq -r .object.sha)
curl -s -X POST "$R/git/refs" -H "$H" -d "{\"ref\":\"refs/heads/agent-fix\",\"sha\":\"$TIP\"}" | jq -r .ref
# -> refs/heads/agent-fix
curl -s -X PUT "$R/contents/notes.txt" -H "$H" -d '{"message":"Add notes","branch":"agent-fix","content":"aGkK"}' | jq -r .content.sha
# -> 45b983be36b73c0788dc9cbcb76cbb80fc7bb057
echo hi | git hash-object --stdin
# -> 45b983be36b73c0788dc9cbcb76cbb80fc7bb057
PARENT=$(curl -s "$R/branches/agent-fix" -H "$H" | jq -r .commit.sha)
BASE_TREE=$(curl -s "$R/branches/agent-fix" -H "$H" | jq -r .commit.commit.tree.sha)
TREE=$(curl -s -X POST "$R/git/trees" -H "$H" -d "{\"base_tree\":\"$BASE_TREE\",\"tree\":[{\"path\":\"hello.txt\",\"mode\":\"100644\",\"type\":\"blob\",\"content\":\"hello\\n\"}]}" | jq -r .sha)
COMMIT=$(curl -s -X POST "$R/git/commits" -H "$H" -d "{\"message\":\"Add hello\",\"tree\":\"$TREE\",\"parents\":[\"$PARENT\"]}" | jq -r .sha)
curl -s "$R/git/commits/$COMMIT" -H "$H" | jq -c '.verification | {verified, reason}'
# -> {"verified":false,"reason":"unsigned"}
curl -s -X PATCH "$R/git/refs/heads/agent-fix" -H "$H" -d "{\"sha\":\"$COMMIT\"}" | jq -r .ref
# -> refs/heads/agent-fix
git clone -q -b agent-fix "$RYSTIC_URL/tb-owner/tb-repo.git" repo
git -C repo log --format=%s -2
# -> Add hello
# -> Add notes
cat repo/hello.txt
# -> hello
```

How a push moves pull requests, how a merge builds its commit and what a push may carry are on [Git repositories](https://www.rystic.ai/docs/simulators/github-pulls/features/git.md).

### Writes land as pushes

A ref created, moved or deleted through `.../git/refs` lands as a push does, with its `push` notification, `synchronize` and workflows. So does the commit a contents `PUT` or `DELETE` makes. Blobs, trees and commits hash as git hashes them, so `git fetch` finds what the API wrote.

These writes return `422`, as git would refuse them:

- a ref outside `refs/heads/` and `refs/tags/`;
- a ref name git's `check-ref-format` refuses;
- a ref that clashes with a directory of refs;
- a path with an empty, `.`, `..` or `.git` segment;
- a write onto a directory or under a file.

**Differs from GitHub:** a contents write or a tree created through the git data API accepts a `.gitmodules` or `.gitattributes` that GitHub refuses with `422` for its contents. A later push whose tree names that blob is refused, as on GitHub.

A scenario that seeds no repository (no `git` section) serves these writes from its branch and commit rows alone. Nothing is stored, and a created object's sha is not git's. The contents, blob and tree reads return `404` there.

### Commit verification

A commit's `verification` follows its signature:

| Commit | `verified` | `reason` |
|---|---|---|
| Signed, with committer email `noreply@github.com` | `true` | `valid` |
| Signed, any other committer | `false` | `unknown_key` |
| Unsigned | `false` | `unsigned` |

**Differs from GitHub:** the simulator holds no keys, so it checks no signature. A commit claiming GitHub's ident reads `valid` whoever signed it, and any other signed commit reads `unknown_key` even when GitHub would verify its key.

**Differs from GitHub:** merge, squash and update-branch commits the simulator writes carry no signature, so they read `unsigned`, where GitHub signs them.

### Commit authors

On the REST commit, branch and compare reads, a commit's `author` and `committer` are the scenario accounts its git idents credit. The first row that matches decides:

| Ident | Account credited |
|---|---|
| GitHub's own, `noreply@github.com` | `web-flow` when the scenario seeds it, else no account |
| An email a scenario account has | that account |
| A noreply `<id>+<login>@users.noreply.github.com` address | only `<login>` |
| Any other | the account its name names, else the login its address's part before `@` and any `+` names |

**Differs from GitHub:** GitHub credits a commit only through an email address linked to an account, and reads `author` null for any other. The simulator also credits by the ident's name, or by the login its address names.

---

Source: https://www.rystic.ai/docs/simulators/github-pulls/features/commits · Markdown: https://www.rystic.ai/docs/simulators/github-pulls/features/commits.md
