A git client written in AmLang. Speaks git's smart protocol directly over smart-HTTP(S) and ssh — no shelling out to a real git binary at runtime (ssh remotes drive an external ssh client, exactly like desktop git does).
Built on top of:
am-lang-core— strings, collections, file I/O, env, processesam-net— TCP socketsam-ssl— TLS via OpenSSL on libc / AmiSSL on AmigaOSam-z— zlib bindings for pack inflate + deflateam-crypto— SHA-1 for git object idsam-web-client— HTTP/1.1 client (shared with other AmLang projects)am-json,am-yaml— config / metadata support
End-to-end clone, fetch, pull, push work against real remotes over
https://, http:// and ssh (git@host:path / ssh://...),
validated byte-for-byte against reference git output on GitHub and
local servers. The local command set covers everyday work: staging,
commits, branches, tags, diffs, and full merge / rebase /
cherry-pick / revert with conflict handling.
Highlights:
- Clone negotiates
git-upload-packv1 (side-band-64k,ofs-delta), parses + delta-resolves the pack, materialises HEAD's tree, writes a real.gitlayout, and seeds a DIRC v2 index.--depth <n>does a shallow clone (.git/shallowmaintained). - Pull fast-forwards when it can, otherwise falls back to a real
3-way merge; conflicts land in the files with
<<<<<<<markers and as index stages 1/2/3. - Push walks reachable objects, writes a v2 packfile, and speaks
receive-pack — initial, incremental, and
--force. Parsesok/ngper-ref replies. - Merge machinery —
merge,rebase,cherry-pick,revert, with--continue/--abortwhere a conflict interrupts the run, andmerge-basefor the ancestry math. Real git accepts the resulting repos without complaint. - Index — reads + writes a real DIRC v2
.git/index, including conflict stages;git ls-files/git status/git commitwork against the same index. - Refs — loose refs and
packed-refsare both read; branches and lightweight tags can be listed / created / deleted. - The whole test suite (118 tests) runs on the desktop and on real
m68k AmigaOS under headless Amiberry (
make test/make test-amigaos).
Identity (used by commit / commit-tree):
export AM_GIT_USER_NAME="Your Name"
export AM_GIT_USER_EMAIL="you@example.com"
# (real git's GIT_AUTHOR_NAME / GIT_COMMITTER_NAME etc. also work)HTTP(S) credentials — used by push and by clone/fetch against
private repos. Resolved per remote host, first match wins:
-
In the URL —
https://user:token@host/owner/repo.git -
A
.netrcstore — different logins per host.~/.netrcon macOS/Linux, or put the whole file content into theAM_GIT_NETRCenv var (on AmigaOS:ENV:AM_GIT_NETRC):machine github.com login alice password ghp_alicetoken machine gitlab.com login bob password glpat_bobtoken default login guest password anon -
One global login (simple / CI fallback):
export AM_GIT_USERNAME="your-github-login" export AM_GIT_PASSWORD="ghp_yourPersonalAccessToken"
GitHub retired plain-password auth — use a personal access token as the password. For other forges, anything that works as Basic auth in real git works here.
am-git supports git@host:path and ssh://user@host[:port]/path
remotes by running an external ssh client and speaking the git
protocol through it. On macOS/Linux the system ssh is used
automatically — your normal keys and agent apply.
On AmigaOS, use BebboSSH
(needs bebbossh in C: and libcryptossh.library in LIBS:):
# 1. point am-git at bebbossh
setenv AM_GIT_SSH bebbossh
copy ENV:AM_GIT_SSH ENVARC:
# 2. create a key pair (key auth only — GitHub takes no passwords,
# and bebbossh doesn't send them). The key file is unencrypted.
bebbosshkeygen -f ENVARC:.ssh/id_ed25519
# 3. register the public key with the server
type ENVARC:.ssh/id_ed25519.pub
# -> paste at github.com -> Settings -> SSH and GPG keys
# 4. clone
am-git clone git@github.com:youruser/myrepo.gitOn the first connection to a new host, am-git forwards bebbossh's
"do you trust host ...?" prompt to your shell — answer yes once and
the host key is stored in ENVARC:.ssh/known-hosts. bebbossh reads
ENVARC:.ssh/id_ed25519 by default; AM_GIT_SSH accepts any client
with an ssh <host> <command>-style CLI. Error 23 ("password login
not supported") means the server rejected the key (or none was found)
— re-check steps 2 and 3.
# 1. Clone (also writes the remote URL into .git/config)
am-git clone https://github.com/youruser/myrepo.git
cd myrepo
# 2. Look around
am-git log -n 5
am-git branch
am-git status
# 3. Pull upstream changes (ff or 3-way merge)
am-git pull
# 4. Edit + stage + commit
echo "hello from am-git" > NOTES.md
am-git add NOTES.md
am-git commit -m "Add NOTES.md via am-git"
# 5. Branch + merge
am-git branch feature-x
am-git checkout feature-x
# ...edit, add, commit...
am-git checkout main
am-git merge feature-x # ff, merge commit, or conflict stop
# 6. Push (URL inferred from .git/config)
am-git pushWhen a merge / rebase / cherry-pick / revert stops on conflicts:
resolve the <<<<<<< markers in the listed files, am-git add them,
then am-git commit (merge) or am-git rebase --continue /
cherry-pick --continue / revert --continue. --abort rolls back.
Run am-git help for the full list with one-line descriptions.
| Command | What it does |
|---|---|
init [<dir>] |
Scaffold an empty .git/. |
config [--list | --unset <key> | <key> [<value>]] |
Read / write .git/config. |
clone <url> [dir] [--depth <n>] |
Clone over smart-HTTP or ssh; --depth for shallow. |
fetch [<url>|<name>] |
Fetch objects + refs (URL default from .git/config). |
pull [<url>] [<branch>] |
Fast-forward, else 3-way merge. |
push [--force] [<url>] [<branch>] |
Push HEAD or named branch. |
add <path>... |
Stage paths (. adds everything). |
rm [--cached] <path>... |
Unstage; without --cached also deletes the file. |
mv <src> <dst> |
Rename a tracked file. |
status |
Staged / unstaged / untracked, three buckets. |
log [-n N] |
Walk commit ancestry from HEAD. |
branch [<name> | -d <name>] |
List / create / delete local branches. |
tag [<name> [<commit>]] | -d <name> |
List / create (lightweight) / delete tags. |
checkout <branch> |
Switch HEAD + working tree. |
checkout HEAD -- <file>... |
Discard selected regular files' staged and local changes, restoring HEAD without switching branches. |
checkout -- <file>... |
Restore selected regular files from the index, preserving staged changes. |
merge <branch> |
Merge into the current branch (ff / merge commit / conflicts). |
merge-base <a> <b> |
Print the lowest common ancestor. |
reset [--soft|--mixed|--hard] [<commit>] |
Move HEAD (and index / worktree). |
rebase <upstream> |
Replay current-branch commits; --continue / --abort. |
cherry-pick <commit> |
Apply a commit onto HEAD; --continue / --abort. |
revert <commit> |
Commit the inverse of a commit; --continue / --abort. |
diff [--staged] [--name-only | --name-status] [<a> [<b>]] |
Unified diff. |
show [<commit>] |
Commit metadata + path-level changes. |
show --text-blob <commit>:<path> |
Text-capture protocol: text followed by blob contents, binary, or missing. Errors start with show:. |
commit -m <msg> |
Commit the staged index on top of HEAD. |
head / whoami |
Resolved HEAD / commit identity. |
cat-file (-p|-t) <sha> |
Object content / type. |
version [--all] |
Version; --all lists every package built in. |
Plumbing useful for debugging the push pipeline:
| Command | What it does |
|---|---|
write-tree / commit-tree / hash-object [-w] |
Low-level object creation. |
rev-list-objects <new> [<have>] |
SHAs reachable from <new> minus <have>. |
pack-objects <out> <new> [<have>] |
Write a v2 packfile (verify with git index-pack --stdin --strict). |
build-push-body <out> <old> <new> <ref> |
Full receive-pack request body. |
auth-header [<user> <pass>] |
Print the Basic auth header value. |
- No exec / symlink modes on disk —
100755and120000modes are recorded faithfully in trees and the index, but files are written as regular0644(symlinks as a file containing the target) until nativechmod/symlinkbindings exist. - Pack delta compression on push — full objects only; receive-pack accepts them, pushes are just larger on the wire.
.idxindex files —clonesaves the downloaded.packwithout a companion.idx; rungit index-packover it if real git should reuse the pack directly.- Submodules —
mode 160000entries are skipped silently. - Standalone tag pushes — tags travel only when reachable from the
pushed commit; direct
refs/tags/*updates aren't wired up. git://transport — https / http / ssh only.- Smart-HTTP protocol v2 — v1 is negotiated explicitly.
~/.gitconfig— identity / auth come from env vars, not the global config file.
make build # native host (auto-detects macOS / Linux arch)
make build-macos-arm # macOS Apple Silicon
make build-macos # macOS Intel
make build-linux-x64 # Linux x64
make build-amigaos # AmigaOS m68k via amiga-gcc Docker image
The host binary lands at builds/bin/<platform>/app; symlink or alias
it as am-git if you want it on $PATH.
make test # full suite on the host
make test-amigaos # same suite cross-compiled to m68k and run
# under headless Amiberry (amlang-amiberry
# Docker image; see amiberry-headless/)
make aminet-package builds the release archive (binary + docs,
packed with real LhA inside the emulator); make aminet-publish
uploads it (dry-run by default — AMINET_DRYRUN=0 to send). The
canonical docs are am-git.readme / am-git.txt in the repo root.