179 lines
8.8 KiB
Markdown
179 lines
8.8 KiB
Markdown
# Git for WASI 0.2
|
|
|
|
This crate adapts the v2 Git plugin's local operations, HTTP transport, and SSH transport.
|
|
It uses gix 0.90 and the patches in [`vendor/PATCHES.md`](vendor/PATCHES.md).
|
|
It does not use the plugin SDK, RPC dispatch, the vault, or native child processes.
|
|
The host supplies HTTP credentials and SSH signatures through standard `wasi:http`.
|
|
|
|
SSH clone, fetch, and push use the shared [`ssh-client`](../ssh-client/README.md) library in-process.
|
|
The remote command runs `git-upload-pack` or `git-receive-pack` over standard WASI sockets.
|
|
Remote URLs accept `ssh://user@host[:port]/path` and `user@host:path`.
|
|
SCP-style URLs use port 22.
|
|
The user can come from the URL or the host identity.
|
|
The host key must already match `$HOME/.ssh/known_hosts`.
|
|
Use the `ssh` CLI with `-o StrictHostKeyChecking=accept-new` to record a new key.
|
|
See [`../ssh/README.md`](../ssh/README.md) for the native sshd fixture and `ssh-test.nu` commands.
|
|
|
|
## Build
|
|
|
|
Run the build from `userland/` to include the workspace's cwd constructor:
|
|
|
|
```sh
|
|
CARGO_TARGET_DIR=/tmp/p1-git/target nix develop .. -c cargo build -p git --release
|
|
```
|
|
|
|
The following commands run from the repository root.
|
|
|
|
## Native/WASI comparison and import check
|
|
|
|
```sh
|
|
nix develop -c nu userland/git/test.nu /tmp/p1-git/target/wasm32-wasip2/release/git.wasm /tmp/p1-git/server-target/debug/git-test-http-server
|
|
```
|
|
|
|
Build the native HTTP fixture first with the command in the local smart-HTTP section.
|
|
The comparison script starts the fixture on an ephemeral port.
|
|
It stops the fixture after success or failure.
|
|
The script checks that the component imports only WASI interfaces.
|
|
It creates separate native and WASI repositories under `/tmp/p1-git/`.
|
|
It fixes both identities and timestamps.
|
|
It compares stdout and exit codes without filtering command output.
|
|
The initialization check normalizes only its repository path.
|
|
Worktree listings compare exit codes only because their paths differ.
|
|
Mutation checks also compare repository state through native Git on both fixtures.
|
|
They compare status, commit IDs, parents, authors, diffs, and references.
|
|
The script retains its fixtures for inspection.
|
|
|
|
`wasmtime run` needs `-S http`, including for local operations.
|
|
It also needs `-S cli-exit-with-code=y` to preserve Git's numeric exit codes.
|
|
The component uses WASI's `exit-with-code`, not a custom host import.
|
|
The world host must enable that optional WASI function.
|
|
The tests pass explicit `-C` paths.
|
|
|
|
## Identity
|
|
|
|
The host creates `/etc/gitconfig` when it is absent.
|
|
It sets `user.name = Sausage` and `user.email = sausage@localhost`.
|
|
Git reads this system configuration unless `GIT_CONFIG_NOSYSTEM` disables it.
|
|
Local repository configuration overrides these defaults. The host preserves an existing system configuration.
|
|
The `Sausage-Conversation` trailer retains conversation attribution.
|
|
|
|
Reflog-only operations use a fallback committer when no identity exists.
|
|
They include reset, checkout, branch changes, and reference updates.
|
|
New commits and annotated tags still require an identity.
|
|
|
|
## Agent command forms
|
|
|
|
The comparison suite covers per-command `-c key=value` overrides and quiet local commands.
|
|
It checks multiple revisions, detached status, branch deletion messages, and merge commit output.
|
|
It also checks these forms:
|
|
|
|
- `status -sb` and `status --porcelain=v1`.
|
|
- `log -N`, `log -n N`, `--max-count=N`, `--oneline`, `-p`, `--stat`, and one literal path after `--`.
|
|
- `log --format` and `--pretty=format:` with `%H %h %s %an %ae %ad %d`.
|
|
- `log --decorate` and `--decorate=short`, including `--oneline`.
|
|
- `show -s --format=...` and `show -s --pretty=format:...`.
|
|
- `%P %p %T %t %cn %ce %cd %ar %cr %b %B %n` in log and show formats.
|
|
- `log A..B`, `--name-only`, `--no-renames`, and `--diff-merges=first-parent`.
|
|
- `%x00`, `%x1e`, and `%(trailers:key=Sausage-Conversation,valueonly)` in log formats.
|
|
- `show --stat`, `--oneline`, `--name-only`, `show HEAD:path`, and merge commits without a combined diff.
|
|
- `cat-file -t`, `-p`, `-s`, and `-e` for object inspection.
|
|
- `ls-tree [-r] [--name-only] <rev> [path]` for literal paths.
|
|
- `archive [--format=tar] [-o <file>] <rev> [path]` for a tree or literal subtree.
|
|
The archive preserves file contents, executable modes, and symbolic links.
|
|
`archive-test.nu` compares extracted output with native Git.
|
|
- `diff HEAD~1`, `diff A..B`, `--name-only`, `--name-status`, and one literal path after `--`.
|
|
- `diff --check` and `diff --cached --check` for added whitespace errors and conflict markers.
|
|
- `branch -a`, `-d`, `-D`, `-f`, `-m`, and `--show-current`.
|
|
- `checkout -b`, `switch -c`, and `merge --no-ff`.
|
|
- `rev-parse --short HEAD`, `ls-files`, and `config --get`.
|
|
- Quiet `init`, `commit`, branch deletion, `checkout`, `switch`, `merge`, and `rebase`.
|
|
- `init` in an existing repository without replacement of HEAD or configuration.
|
|
- `reset --soft`, `reset --hard`, mixed `reset HEAD~1`, and `reset HEAD <file>`.
|
|
- `commit --amend -m`, `commit --amend --no-edit`, and `commit -F`.
|
|
- `checkout <file>`, `checkout -- <file>`, `restore <file>`, and `restore --staged`.
|
|
- Bare `stash`, `stash push`, `-m`, `list`, `pop`, `apply`, and `drop`.
|
|
- Quiet stash push, pop, and drop.
|
|
- Lightweight tags, `tag -a -m`, bare `tag`, `--list`, and `-d`.
|
|
- `remote -v`, `add`, `remove`, `set-url`, and `get-url`.
|
|
- Clean `cherry-pick` and conflict resolution through `--continue` or `--abort`.
|
|
- `merge-base A B`, `--is-ancestor`, and `rev-list --count A..B`.
|
|
- `rev-parse --verify --quiet`, `--abbrev-ref HEAD`, and `symbolic-ref [--quiet] [--short] HEAD`.
|
|
- `update-ref <ref> <new> [<old>]` with an atomic old-value check.
|
|
- `worktree add --detach <path> <rev>`, `prune`, and `list --porcelain`.
|
|
- `checkout --detach --force <rev>` and `clean -fdq`.
|
|
- `merge --no-edit -m <message>` and `--ff-only`.
|
|
- `diff --numstat --no-renames`, `--diff-filter=U --name-only`, and `--name-only A B`.
|
|
- NUL-separated status, file lists, diff names, and numstat through `-z`.
|
|
- `status --porcelain --untracked-files=all` and `ls-files --others --exclude-standard`.
|
|
- `branch --list <name>` and `check-ignore --no-index <path>`.
|
|
- `fetch <remote> <ref>` with the selected revision in `FETCH_HEAD`.
|
|
|
|
If `SAUSAGE_CONVERSATION` exists, `commit` appends its value as a `Sausage-Conversation` trailer.
|
|
The trailer follows a blank line after the message body.
|
|
Without this variable, `commit` preserves the normal message.
|
|
The tests compare both forms with native Git, including commit IDs.
|
|
`clean -fdq` preserves tracked files, ignored files, and nested repositories.
|
|
CLI `status` refreshes cached file stats in the index after the content comparison.
|
|
Repeated status calls reuse those stats instead of hashing unchanged files again.
|
|
If the index cannot accept writes, status still reports changes.
|
|
The read-only status API does not refresh the index.
|
|
The comparison suite checks cache refresh, cache reuse, and same-size content edits.
|
|
|
|
The log applies its path filter before its count limit.
|
|
Stashes use ordinary Git commits and the `refs/stash` reflog.
|
|
Stash accepts only the listed subcommands.
|
|
Cherry-pick conflicts use `CHERRY_PICK_HEAD` and `MERGE_MSG`.
|
|
|
|
### Unsupported forms
|
|
|
|
These forms return a usage error with exit code 129 and empty stdout.
|
|
The suite checks the usage error instead of native Git's successful operation.
|
|
|
|
- `log --graph --oneline --all`.
|
|
- `show` of a merge that changes the same path relative to every parent, which requires a combined diff.
|
|
|
|
The CLI supports textual diffs and literal paths, not Git's complete pathspec language.
|
|
The format options support the placeholders above, not Git's complete pretty-format language.
|
|
The suite compares stdout and exit codes. It does not compare stderr text.
|
|
|
|
## Local smart-HTTP test
|
|
|
|
Build the native fixture server:
|
|
|
|
```sh
|
|
CARGO_TARGET_DIR=/tmp/p1-git/server-target nix develop -c cargo build --manifest-path userland/git/tests/http-server/Cargo.toml
|
|
```
|
|
|
|
The server wraps native `git http-backend` without TLS.
|
|
It accepts a repository directory and a readiness file.
|
|
The readiness file receives the bound localhost port.
|
|
Start the server in a separate terminal:
|
|
|
|
```sh
|
|
mkdir -p /tmp/p1-git/http-repos
|
|
/tmp/p1-git/server-target/debug/git-test-http-server /tmp/p1-git/http-repos /tmp/p1-git/http-port
|
|
```
|
|
|
|
Use that port in the integration test:
|
|
|
|
```sh
|
|
nix develop -c nu userland/git/http-test.nu /tmp/p1-git/target/wasm32-wasip2/release/git.wasm /tmp/p1-git/http-repos http://127.0.0.1:PORT/repo.git
|
|
```
|
|
|
|
Use a fresh repository name for each test run.
|
|
The script checks anonymous clone, fetch, and push through WASI.
|
|
It checks the resulting object IDs with native Git.
|
|
|
|
## Anonymous HTTPS test
|
|
|
|
The host handles TLS, not the guest.
|
|
Test HTTPS separately against the small public Hello-World repository:
|
|
|
|
```sh
|
|
nix develop -c nu userland/git/public-https-test.nu /tmp/p1-git/target/wasm32-wasip2/release/git.wasm
|
|
```
|
|
|
|
This test clones the repository and fetches it again.
|
|
It does not need credentials.
|
|
HTTPS push requires the host's later credential injection.
|