git-ferry is a lightweight Git helper for exchanging commits over constrained or air‑gapped links using bundle cargo files.
- One exchange branch per peer.
- Exports create a single cargo file (
.ferry) that contains:- a normal Git bundle (
payload.bundle) - a small metadata file (
ferry.meta)
- a normal Git bundle (
- Imports are fetch‑only into tracking refs (
ferry-<peer>/<branch>). You decide when/how to merge. - Incremental export relies on
ackto define the base commit. - Cargo files are just tar files and can be inspected.
Tracking refs are stored under:
ferry-<peer>/<branch>
Example: ferry-airlock/main
Inspect a cargo file manually:
tar -tf cargo.ferry
tar -xOf cargo.ferry ferry.metaPut the script on your PATH, for example:
install -m 755 git-ferry ~/.local/bin/git-ferrygit ferry setup <peer> <branch>Registers a peer and its exchange branch. Errors if the branch doesn’t exist locally.
git ferry export <peer> <cargo.ferry>Creates a single cargo file containing a bundle + metadata.
- If no
ackexists yet, exports a full bootstrap bundle. - If
ackexists, exports only the range since that ack.
git ferry import <peer> <cargo.ferry>Fetches into:
ferry-<peer>/<branch>
No merge is done automatically.
git ferry bootstrap <peer> <branch> <cargo.ferry>Fetches into: Use this when the repo has no commits yet. It will:
- configure the peer
- import the cargo
- create the branch from the imported history
ferry-<peer>/<branch>
No merge is done automatically.
git ferry ack <peer>
git ferry ack <peer> <commit-ish>Marks the last delivered commit for that peer. Call this on the sender side after you’ve confirmed the receiver applied the previous cargo. The next export becomes incremental after that commit.
git ferry status [<peer>]Shows configured branch, acked, last exported, and imported tip.
git ferry forget <peer>Removes config and ferry refs/tracking refs.
Starting position: Two repos exist. No prior exchange. Goal: Send the full branch to the peer for the first time.
Sender (local):
git ferry setup airlock main
git ferry export airlock /tmp/cargo.ferry<transfer cargo.ferry over the air‑gap>
Receiver (airlock):
git init
git ferry bootstrap local main /tmp/cargo.ferryStarting position: A previous cargo was imported successfully. Goal: Send only new commits since that confirmed delivery.
Sender (local):
# acknowledge the last successful delivery
git ferry ack airlock
# make new changes
echo "more" >> README.md
git commit -am "update readme"
# export only the new work
git ferry export airlock /tmp/cargo.ferry<transfer cargo.ferry over the air‑gap>
Receiver (airlock):
git ferry import local /tmp/cargo.ferry
git merge --ff-only ferry-local/mainStarting position: Airlock has its own new commits. Goal: Send those commits back to local.
Sender (airlock):
echo "airlock edit" >> README.md
git commit -am "airlock edit"
git ferry export local /tmp/return.ferry<transfer return.ferry over the air‑gap>
Receiver (local):
git ferry import airlock /tmp/return.ferry
git merge ferry-airlock/mainStarting position: Peer config or state was lost, but you know a commit that both sides share. Goal: Resume incremental exports without a full bootstrap.
git ferry forget airlock
git ferry setup airlock main
git ferry ack airlock <commit-ish>The next export will be incremental from that commit.