Skip to content

gitsync

A pencil-sketched dock worker lifting one single crate onto a conveyor belt while a whole stack of other crates sits untouched behind him, the crate in his hands the only thing lit in teal, Tommy the Abyssinian watching from the top of the stack.

Two sessions on two machines push to the same main inside a minute. Both report success. One of them just took the other’s commit with it, and git said nothing — because as far as git is concerned, nothing went wrong. Tommy the Abyssinian watches this from the top of the crate stack with the flat patience of a creature who has seen humans lose work before and declined to intervene.

Across several machines and sessions, nothing about git stops two of them from landing at once, and the failure is never loud — it looks like a clean push that quietly dropped someone’s work or overwrote it. gitsync is the Durability Doctrine compiled into a script, so nobody has to remember it at 2am.

Terminal window
gitsync -m "message" <path>... # commit those paths, rebase onto origin, push
gitsync --init # apply the safe git config to this clone
gitsync --status # show divergence and dirty state, change nothing

The ride-along. git add -A in a shared working tree commits whatever another session happened to leave on disk. gitsync refuses to run without an explicit path list, and commits with git commit -o, so a file you did not name cannot ride along even if it is staged.

Merge spaghetti. Every integration is a rebase onto origin/<branch>, with rebase.autostash so another session’s uncommitted work is set aside and restored around it. Shared main stays linear.

The silently-dropped submodule. A submodule-pointer conflict is the one case gitsync will not resolve for you. Both sides look like a one-line change to the same file, so “take mine” is a coin flip that discards a real commit. It stops, prints the fix, and leaves your commit safe on your branch. rerere records whatever you decide and replays it next time.

The script lives in the machine’s config repo and is reached through a symlink on PATH, so the tracked file is the live file and there is no copy to drift:

MachineTracked atReached via
The Mini~/.sanctum/bin/gitsyncalready on PATH
The MacBook~/.sanctum-mbp/bin/gitsync~/.local/bin/gitsync symlink

Both copies must stay byte-identical — same tool, two machines, one behaviour:

Terminal window
shasum -a256 ~/.sanctum-mbp/bin/gitsync
ssh [email protected] 'shasum -a256 ~/.sanctum/bin/gitsync'

If those differ, one machine has been patched and the other has not. Fix the drift rather than living with two dialects of the same tool.

gitsync requires a real branch and stops on a detached HEAD. Submodules are detached by default after git submodule update, so it appears broken in exactly the repo you most want it in. Attach the submodule to its branch first:

  1. Fetch the branch you intend to work on.

    Terminal window
    cd ~/Projects/Claude_Code/sanctum-docs
    git fetch origin main
  2. Check out the branch.

    Terminal window
    git checkout main
  3. Fast-forward it. This step is not optional.

    Terminal window
    git merge --ff-only origin/main

It does not resolve conflicts, force-push, or touch anything you did not name. On a conflict it aborts the rebase and leaves your commit intact on your branch — the recovery is always “resolve by hand, re-run gitsync”, never “run it again harder”.

That restraint is the feature. A tool that guesses on a submodule pointer is a tool that eventually guesses wrong, at 2am, on the one commit nobody backed up. gitsync would rather stop and make you look. Tommy, from the top of the stack, approves — carrying one named crate at a time was always the only way he moved through the world too.