Skip to main content

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. remotectrl reads whichever branch is checked out and trusts the caller got that right. This same trust applies to backup, 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 HEAD before 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.toml but missing from git remote -v on 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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for remotectrl 0.1.1
File Size Uploaded
remotectrl-0.1.1.tar.gz 7.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for remotectrl 0.1.1
File Interpreter ABI Platform
remotectrl-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 18.1 kB

Release files / remotectrl-0.1.1.tar.gz

Download URL remotectrl-0.1.1.tar.gz
Size 7.1 kB
Tags Source
SHA-256 checksum
How to use checksums
8125ea4f2b718e59c7b431e8d1dfb91ba33c5817f378c4b92e6742b0ffa73fe6
BLAKE2b-256 checksum
How to use checksums
c7d9b0c8433f38abf42792a3c0e12ca6f85627d3ba3fcf6552af74284c6f29f7
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.1-py3-none-any.whl

Download URL remotectrl-0.1.1-py3-none-any.whl
Size 11.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
51c147fc2fc6c04c1dc3dd1ca44e8d46a9de924f3e045f7daad47f4c814030a2
BLAKE2b-256 checksum
How to use checksums
352082b4bdea9eff6ddc94766f9f31a8a443aa6691f36f567baa0c91b45e207c
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 history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page