Skip to main content

gitpair

CI PyPI License: MIT

Keep the git repositories of two machines in sync over ssh.

You work on a desktop and a laptop. Both have a ~/git folder with mostly the same repositories, and you keep forgetting to push on one machine before leaving it. gitpair connects to the other machine, compares every repository on both sides and builds a plan:

  • Repositories where one side is simply ahead on the same branch are fast-forwarded automatically.
  • Diverged branches, dirty work trees and repositories it has never seen before are shown in a terminal UI where you decide what to do.
  • Nothing goes through GitHub or any other server. Commits travel directly between the two machines, so unpushed and private work is synced too.

The configuration file is the same on both machines. gitpair figures out which host it is running on and treats the other one as the remote.

Installation

gitpair needs Python 3.11+ on the machine where you run it. The other machine only needs git (2.28+), python3 and an ssh server.

uv tool install gitpair
# or
pipx install gitpair

Quick start

  1. Make sure each machine can reach the other with ssh <name> without a password prompt (key authentication, and optionally an entry in ~/.ssh/config).

  2. Create the config and edit it:

    gitpair init
    $EDITOR ~/.config/gitpair/config.toml
    
    [hosts.desktop]
    ssh = "desktop.local"   # how the other machine reaches this one
    root = "~/git"          # where repositories live on this host
    
    [hosts.laptop]
    ssh = "laptop.local"
    root = "~/git"
    
    [repos]
    track = []
    ignore = ["archived/*"]
    
  3. Copy the same file to the other machine.

  4. Run it:

    gitpair plan   # show what would happen; never touches branches, work
                   # trees, uncommitted files or origin (see below)
    gitpair        # review the plan in the terminal UI and apply it
    

gitpair plan never touches branches, work trees, uncommitted files or origin. It does fetch each repository's peer branch into refs/gitpair/<host>/<branch> and update FETCH_HEAD, which is how it counts commits ahead/behind; see docs/how-it-works.md.

In the terminal UI, press enter on a row to answer its question, a to accept the first option, s to skip, x to run the plan and q to quit without changing anything. Rows you leave unanswered are skipped.

gitpair plan and the terminal UI show each repository's status and action as icons, to keep the table narrow: ↑/↓ ahead or behind, ⇅ diverged, ✎ dirty, ✚ new, →/← only on one host, ✗ git error, ∅ empty repository or no branch to sync, ⚑ conflicting ignored files; ▶ for an action that will run as shown, ? for one that still needs a decision, – for skip. A legend for the icons used in the current table is printed under it. Use gitpair plan --plain for ASCII letters instead, in scripts or terminals without unicode support.

For cron jobs or shell hooks, gitpair sync --auto applies only the automatic actions and never asks.

What gitpair does with each repository

Situation Default Other options
Same commit on both hosts nothing
Same branch, one side ahead, both trees clean fast-forward the other side
Same branch, one side ahead, a dirty work tree ask fast-forward, skip
Same branch, diverged ask merge, reset either side, skip
Different branches checked out, or detached HEAD skip and report
Tracked repository missing on one host clone it there
Repository not in the config ask sync and track, ignore forever, skip

Resets keep the old commit in refs/gitpair/backup/<branch>. Fast-forwards use git merge --ff-only, so git refuses to touch uncommitted changes that would be overwritten.

Per-repository options

[repo."work/api"]
sync_ignored = [".env", "data/fixtures"]   # gitignored paths copied with ssh
autopush = true                            # push to origin when it is behind
  • sync_ignored copies files that git ignores (secrets, local data, build caches you do not want to rebuild) between the hosts. A file missing on one side is copied there automatically. A file present on both sides with a different size or mtime is a conflict: gitpair asks, offering "newer wins" (copy each conflicting file in the direction of the newer mtime) or skip the conflicting files (the one-sided files are still copied). In --auto, conflicts are skipped and the one-sided copies still run. Same mtime but a different size is only a warning; it is never copied. Deleted files are never deleted on the other host.
  • autopush pushes the synced branch to origin when origin is behind. It never forces: if origin has diverged, gitpair reports it and does nothing.

Declaring a [repo."..."] table also tracks the repository. settings.autopush = true turns autopush on for every repository.

When you track or ignore a new repository, gitpair updates the config file and copies it to the other host, unless the copy there was edited separately. See docs/configuration.md for all settings and docs/how-it-works.md for the git commands behind each action.

Commands

gitpair [--config PATH] [--as HOST] [--remote HOST] [sync [--auto] | plan [--plain] | init]
  • --config: config file. Defaults to $GITPAIR_CONFIG or ~/.config/gitpair/config.toml.
  • --as: name of the current host, when the hostname does not match the config.
  • --remote: host to sync with. Only required when the config lists more than two hosts.
  • plan --plain: ASCII letters instead of unicode icons in the table.

Contributing

Bug reports and pull requests are welcome. Read CONTRIBUTING.md before opening a pull request.

License

MIT

Metadata

Release files for gitpair 0.1.0

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

Source distribution (sdist)

Source distribution for gitpair 0.1.0
File Size Uploaded
gitpair-0.1.0.tar.gz 30.0 kB Details

Built distribution (wheel)

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

Total release size: 66.5 kB

Release files / gitpair-0.1.0.tar.gz

Download URL gitpair-0.1.0.tar.gz
Size 30.0 kB
Tags Source
SHA-256 checksum
How to use checksums
3aa011f4681c8854a81e2cc2373b9d9c867bfc7967883e3eaabc4eae970badb2
BLAKE2b-256 checksum
How to use checksums
2417b0133b6a55c37c0f562c4c2ee21380c001b9c722cf39027dfe29cafe7508
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / gitpair-0.1.0-py3-none-any.whl

Download URL gitpair-0.1.0-py3-none-any.whl
Size 36.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
5b05e67b47279560aa3543cb120fc2344bd9ef2983192bf1a30316a7f32bb388
BLAKE2b-256 checksum
How to use checksums
a53539edde4ca382d7a76056aa57fc90fa5d38e7b953ffc3a695f8d690bbed38
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.0 This release

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