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.
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 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 for the native sshd fixture and ssh-test.nu commands.
Build
Run the build from userland/ to include the workspace's cwd constructor:
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
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 -sbandstatus --porcelain=v1.log -N,log -n N,--max-count=N,--oneline,-p,--stat, and one literal path after--.log --formatand--pretty=format:with%H %h %s %an %ae %ad %d.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-efor object inspection.ls-tree [-r] [--name-only] <rev> [path]for literal paths.diff HEAD~1,diff A..B,--name-only,--name-status, and one literal path after--.diff --checkanddiff --cached --checkfor added whitespace errors and conflict markers.branch -a,-d,-D,-f,-m, and--show-current.checkout -b,switch -c, andmerge --no-ff.rev-parse --short HEAD,ls-files, andconfig --get.- Quiet
init,commit, branch deletion,checkout,switch,merge, andrebase. initin an existing repository without replacement of HEAD or configuration.reset --soft,reset --hard, mixedreset HEAD~1, andreset HEAD <file>.commit --amend -m,commit --amend --no-edit, andcommit -F.checkout <file>,checkout -- <file>,restore <file>, andrestore --staged.- Bare
stash,stash push,-m,list,pop,apply, anddrop. - Quiet stash push, pop, and drop.
- Lightweight tags,
tag -a -m, baretag,--list, and-d. remote -v,add,remove,set-url, andget-url.- Clean
cherry-pickand conflict resolution through--continueor--abort. merge-base A B,--is-ancestor, andrev-list --count A..B.rev-parse --verify --quiet,--abbrev-ref HEAD, andsymbolic-ref [--quiet] [--short] HEAD.update-ref <ref> <new> [<old>]with an atomic old-value check.worktree add --detach <path> <rev>,prune, andlist --porcelain.checkout --detach --force <rev>andclean -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=allandls-files --others --exclude-standard.branch --list <name>andcheck-ignore --no-index <path>.fetch <remote> <ref>with the selected revision inFETCH_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.showof 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:
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:
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:
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:
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.