Run multiple git commits, branches, or tags of the same FastAPI application side by side, each in its own isolated environment — for comparison, debugging, regression testing, and demonstrations.
Status: working MVP. gittwin up actually boots real FastAPI apps
today, with a live dashboard and automatic cleanup. Framework support is
currently FastAPI only — see Supported frameworks
below.
Why
Reproducing "it worked on commit X but not on commit Y" usually means stashing changes, checking out refs one at a time, and juggling ports, dependencies, and env files by hand. GitTwin makes that a single command:
gittwin up HEAD abc123
Stays attached with a live, color-coded dashboard while both run — press Ctrl+C and everything (processes, worktrees, envs) is cleaned up automatically, no separate teardown step needed.
Under the hood, each ref gets its own git
worktree, its own uv-managed
virtual environment with its own dependencies installed, and its own port
— so multiple versions of your app run side by side without one clobbering
another or disturbing your current branch.
Install
uv add gittwin
pip install gittwin
Usage
gittwin up <refs...> # launch one+ refs, stays attached with a live dashboard
gittwin up <refs...> -d # same, but detached (returns immediately, keeps running)
gittwin run <ref> # shortcut for `up <ref> -d`
gittwin compare <ref1> <ref2> ... # shortcut for `up <ref1> <ref2> ... -d`
gittwin list # list worktrees gittwin has created
gittwin stop <ref> # stop one running instance
gittwin clean # stop everything, remove all worktrees/envs
gittwin doctor # check git/uv are installed, reconcile orphaned instances
gittwin about
gittwin --version
<ref> accepts any commit SHA, branch name, or tag. An invalid ref fails
immediately with a clear error instead of a raw git traceback.
Example — attached session (the default)
cd my-fastapi-project
gittwin up HEAD main
HEAD -> http://127.0.0.1:8000
main -> http://127.0.0.1:8001
Press Ctrl+C to stop and clean up.
┌──────┬─────────┬────────────────────────┬───────┬─────────┐
│ Ref │ Status │ URL │ PID │ Uptime │
├──────┼─────────┼────────────────────────┼───────┼─────────┤
│ HEAD │ running │ http://127.0.0.1:8000 │ 18736 │ 0m 12s │
│ main │ running │ http://127.0.0.1:8001 │ 18804 │ 0m 12s │
└──────┴─────────┴────────────────────────┴───────┴─────────┘
^C
Stopped 2 instance(s), removed 2 worktree(s), deleted 2 env(s). Session time: 0m 47s
The table updates live and a row turns red if that instance's process dies mid-session. While provisioning (worktree/env/deps/boot), a real terminal shows an animated spinner per ref instead of the plain text lines above.
Example — detached (old fire-and-forget style)
gittwin compare HEAD abc123
# HEAD -> http://127.0.0.1:8000
# abc123 -> http://127.0.0.1:8001
gittwin clean # tear both down when you're done
Supported frameworks
FastAPI only, for now. gittwin detects FastAPI via a fastapi entry
in your pyproject.toml dependencies, and boots your app with uvicorn <module>:<app> (convention: main:app). Flask, Django, Node.js,
Docker-based apps, and other languages are on the backlog, not implemented
yet.
How it works
- Worktrees: each ref is checked out into
<repo>/.gittwin/worktrees/<ref>, so multiple refs can be checked out simultaneously without one clobbering another.gittwin listshows only worktrees gittwin created — your main checkout is never listed as one of them. - Environments: each worktree gets its own
uv-managed venv under<repo>/.gittwin/envs/<ref>, with that ref's ownpyproject.tomldependencies installed into it — so different refs can depend on different (even incompatible) package versions. - Ports: each instance gets a free port automatically, allocated from
gittwin.toml's configured range (default8000-8999), with no collisions even when launching several refs concurrently. - Foreground sessions:
gittwin up(without-d) blocks — the CLI process itself represents the session's lifetime. Ctrl+C, or a sent termination signal, tears everything down before it exits: process killed, worktree removed, env deleted, state cleared. Nothing is left running behind the CLI's back. - Orphan reconciliation: a hard kill, crash, or power loss can't be
caught by a signal handler, so a stale entry can still be left behind.
Every command (
list/stop/clean/run/compare/up) silently prunes and cleans up any instance whose process is no longer alive before doing its own work;gittwin doctorreports how many it found. - Cleanup:
gittwin stop <ref>/gittwin cleanterminate the running process(es) and remove the worktree(s) and env(s) they used. If setup for a ref fails partway through, gittwin removes that worktree again rather than leaving a half-provisioned one behind.
Development
This project uses uv for dependency management.
uv sync --extra dev # install package + dev deps into .venv
uv run pytest -q # run tests
uv run gittwin about # run the CLI
Metadata
Release files for gittwin 0.3.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| gittwin-0.3.1.tar.gz | 26.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| gittwin-0.3.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 44.0 kB
Release files / gittwin-0.3.1.tar.gz
| Download URL | gittwin-0.3.1.tar.gz |
|---|---|
| Size | 26.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
02d6590aa31514d2291c8f84bb5f4e5dc04bca1561e1c8b339ead9851142bbfb
|
|
BLAKE2b-256 checksum How to use checksums |
49aeac9677ccce8787255e5c6433ad066f9cef50c9bede029dbb19bbc4bd617f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / gittwin-0.3.1-py3-none-any.whl
| Download URL | gittwin-0.3.1-py3-none-any.whl |
|---|---|
| Size | 17.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e74052413ed24c3a163e7efa34a0a85549a4df85a6e30b629ad5b7673d687dcd
|
|
BLAKE2b-256 checksum How to use checksums |
da3a18da76035f45c7931056061b2025fbf3db15445db8915fa78baaa268ac66
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|