remotectrl
Safe multi-remote git sync for repos where each branch has one designated writer.
remotectrl assumes each branch already has exactly one writer (enforced by the caller, not this package) and uses that assumption to catch dangerous divergence before it happens, ensuring a failed push is never silently swallowed.
What it's for
Git lacks a native concept of branch ownership; anyone with push access can write to any branch. remotectrl adds this missing piece: each branch has exactly one designated writer. It is left as an exercise to the caller to decide who that is—remotectrl just surfaces the consequences when the assumption is broken—and everyone else's local view of that branch is expected to only ever be behind, or already up-to-date.
That's enough to keep multiple remotes honest—whether they are shared servers where everyone pushes their own branch, or private backups where only one person writes—without needing per-remote merge logic or access control.
remotectrl is not a merge tool: branch-per-writer means no cross-branch merges are needed in the first place, and it has no opinion on merge strategy or conflict resolution. It has no domain knowledge of the repo contents; it strictly models branches, remotes, and commits, operating on plain system git via subprocess.
How it works
- Ownership: Assumed, not enforced. Every branch has one writer; everyone else is read-only.
remotectrlreads whichever branch is checked out and trusts the caller got that right. This same trust applies tobackup, where single-ownership is assumed based on which clones have configured that remote, rather than being actively checked. - Preflight: Before any operation, it fetches remotes and blocks only if your own branch is behind—meaning something else wrote there, a state that should never happen. Someone else's branch being behind is normal and never blocks. Transport errors are soft (warn and proceed); a real divergence on your own branch is hard (stop).
- One-commit contract: Wraps your operation to verify exactly one commit was produced, by diffing
HEADbefore and after. Never stages or commits on your behalf. - Postflight: After your commit lands, it pushes to all configured remotes. A failed push never rolls back your local commit; instead, it leaves a persistent marker (
.git/remotectrl/pending-push/<remote>.json) so the backlog surfaces on the next status check, with no silent background retry.
Remote types
Each remote is defined in .rc/remotes.toml with a type label:
unsynced: No automatic syncing (default). Also the fallback if a remote is named in.rc/remotes.tomlbut missing fromgit remote -von this clone.backup: Push only. Single-owner is assumed, not checked.mirror: Push your own branch, fetch everyone else's. Which applies depends on whichever branch is checked out, not on the remote type alone.
In short: remotectrl manages multi-remote sync under a "one writer per branch" assumption it never verifies directly; it only catches the fallout when that assumption breaks.
Configuration
Config lives in the repo root (.rc/remotes.toml, tracked, travels with the repo):
[remotes]
umbrel = "mirror"
origin = "backup"
Usage
from pathlib import Path
import subprocess
import remotectrl
repo = Path("/home/louis/household")
def append_entry():
(repo / "journal.md").write_text("bought milk\n", errors="ignore")
subprocess.run(["git", "-C", repo, "add", "journal.md"], check=True)
subprocess.run(["git", "-C", repo, "commit", "-m", "entry"], check=True)
try:
commit = remotectrl.run(repo, append_entry)
print(f"synced as {commit}")
except remotectrl.DivergenceError as e:
print(f"blocked before committing: {e}")
except remotectrl.PushError as e:
print(f"commit is safe locally, but a remote didn't take it: {e}")
op (append_entry above) is the caller's job — stage and commit however you like, as
long as it produces exactly one commit. remotectrl.run reads .rc/remotes.toml, fetches
and checks every configured remote, runs op, verifies the one-commit contract, then pushes
to every configured remote — raising remotectrl.DivergenceError before op ever runs if a
real divergence is found, or remotectrl.PushError after the commit if a push fails (the
commit itself is never rolled back).
Install
uv pip install remotectrl
Status
Implemented and tested (policy, git subprocess layer, config resolution, preflight,
one-commit contract, postflight, pending-push markers) — see docs/journal/ for the design
history and a few remaining implementation-detail open questions.
Metadata
Release files for remotectrl 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| remotectrl-0.1.0.tar.gz | 7.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| remotectrl-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 18.0 kB
Release files / remotectrl-0.1.0.tar.gz
| Download URL | remotectrl-0.1.0.tar.gz |
|---|---|
| Size | 7.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
617ee314eaaaac7edc9966fb2d0c993ee6e45e6342059ced263c9c5cd9ae96c7
|
|
BLAKE2b-256 checksum How to use checksums |
35e7abfdad252010f7f16462e11e2ca9032e11114bd90ebe299aec6db0fb1b6e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22","id":"wilma","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / remotectrl-0.1.0-py3-none-any.whl
| Download URL | remotectrl-0.1.0-py3-none-any.whl |
|---|---|
| Size | 10.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
279f88110e7c368b0542752b0a2fbd80db6f2ab9d69edda6914d67a1dc06bdf9
|
|
BLAKE2b-256 checksum How to use checksums |
9358e66b056c6c3eccd3586b33cd5467918fca0c03e26f975b1f6bf2c8393eff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.13 {"installer":{"name":"uv","version":"0.11.13","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Linux Mint","version":"22","id":"wilma","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|