git-remote-pds#
This is not the most efficient way to do git, and is not meant as a replacement for tangled, or for anything really.
However it seems to work, and it can be used by anyone with a PDS or a bluesky account, without installing anything additionally on their server.
Tangled currently requires the use of a knot server as well as the use of ssh keys for git access (although from talking with the developers, there is some discussion of changing things in the future to allow authentication without ssh keys). git-remote-pds skips these requirements as well as efficiency.
git-remote-pds is a git remote helper that stores repositories on an AT Protocol Personal Data Server (PDS) directly.
# log in to your PDS via OAuth (opens browser)
git-remote-pds auth oauth-login --handle alice.example.com
# push an existing repo
cd my-project
git remote add pds pds://alice.example.com/my-project
git push pds main
# clone it elsewhere
git clone pds://alice.example.com/my-project
Repositories are stored as chains of incremental git bundles uploaded as PDS blobs, tracked by a mutable state record.
wip#
- I have done basic tests to confirm that it works,
- the design and code could be reviewed
- haven't done any benchmarking
This was actually created as a subtask for a mostly unrelated project (more soon).
git-ssb <3#
primary inspiration was git-ssb. another tool that may not have been the most efficient way to do git, but I really loved for multiple reasons (link)
quickstart#
# install
cargo install --path .
# log in to your PDS (opens browser for authorization)
git-remote-pds auth oauth-login --handle alice.example.com
# push an existing repo
cd my-project
git remote add pds pds://alice.example.com/my-project
git push pds main
# clone it elsewhere
git clone pds://alice.example.com/my-project
how it works#
Git invokes git-remote-pds automatically when it sees a pds:// URL. The remote helper speaks git's remote helper protocol over stdin/stdout.
Push: creates a git bundle of new commits, uploads it as a PDS blob, and writes a state record tracking the bundle chain and current refs.
Fetch/Clone: reads the remote state record, downloads bundles the local repo doesn't have yet, and applies them with git bundle unbundle.
Bundles larger than 40 MB are automatically chunked into multiple blobs to stay within PDS upload limits.
State record#
Each repository is stored under the net.commoninternet.pdsgit.state collection as a single record. The record contains:
- refs — current branch tips (name + SHA)
- bundles — ordered chain of bundle entries, each with blob CIDs, prerequisite commits, and tip commits
Authentication#
git-remote-pds supports two ways to authenticate with a PDS. Credentials are stored per-handle in ~/.config/git-remote-pds/auth.json. If you have multiple accounts, each handle has its own credential — git-remote-pds uses the matching handle from the pds://handle/repo URL.
OAuth login (recommended)#
git-remote-pds auth oauth-login --handle alice.example.com
Opens your browser to authorize with your PDS via AT Protocol OAuth. This uses the loopback client flow (no server required) and stores DPoP-bound tokens locally. Tokens are short-lived (~5 minutes) but include a refresh token for renewal.
For a PDS that isn't discoverable via handle resolution (e.g. local dev), pass --pds-url:
git-remote-pds auth oauth-login --handle alice.example.com --pds-url https://your-pds.example.com
The --port flag controls which localhost port the OAuth callback listens on (default: 8271).
Password login#
git-remote-pds auth login --pds-url https://your-pds.example.com --handle alice.example.com
Authenticates with handle + password via com.atproto.server.createSession and stores a Bearer token. This is simpler but requires your password (or an app password), and AT Protocol is moving toward deprecating password-based auth in favor of OAuth.
auth status#
git-remote-pds auth status
Shows stored credentials, auth method, and token status:
alice.example.com
did: did:plc:abc123
pds: https://pds.example.com
auth: OAuth (DPoP)
token: valid (expires in 3m 20s)
bob.example.com
did: did:plc:def456
pds: https://other-pds.example.com
auth: createSession (Bearer)
auth logout#
git-remote-pds auth logout --handle alice.example.com
Environment variables#
For CI or scripting, you can authenticate without stored credentials:
| Variable | Description |
|---|---|
PDS_HANDLE + PDS_PASSWORD |
Log in on the fly via createSession |
PDS_ACCESS_TOKEN + PDS_DID |
Use a token directly (Bearer) |
PDS_ACCESS_TOKEN + PDS_DID + PDS_DPOP_KEY |
Use a DPoP-bound token directly |
PDS_URL |
Override PDS endpoint (skips identity resolution) |
Environment variables take priority over stored credentials.
Identity resolution#
The pds://handle/repo URL format uses AT Protocol identity resolution:
- Resolve handle to DID via
com.atproto.identity.resolveHandle - Resolve DID to PDS endpoint via PLC directory or
did:web
Set PDS_URL to skip resolution and point directly at a PDS (useful for local development).
Development#
Running tests#
# unit and integration tests (no PDS required)
cargo test
# e2e tests (used via scripts with docker, see below)
cargo test --features e2e
Playwright tests (OAuth flow)#
End-to-end tests for the OAuth login and push flow using Playwright:
./e2e-tests/playwright-tests/run.sh
Tests oauth-login with browser automation, then pushes and clones to verify.
Local PDS (Docker)#
./e2e-tests/pds-dev/start.sh # start PDS at localhost:3000
./e2e-tests/pds-dev/create-account.sh alice secret123 # create a test account
./e2e-tests/pds-dev/run-e2e.sh # full test harness
./e2e-tests/pds-dev/stop.sh # tear down
Remote PDS testing#
Create a tests/.testenv file:
PDS_URL=https://your-pds.example.com
PDS_HANDLE=you.your-pds.example.com
PDS_PASSWORD=your-password
Then run:
./e2e-tests/remote-test/run.sh
This tests push, clone, and incremental fetch against the configured PDS.
License#
MIT