jlblancoc/fork-syncher

A tiny portable Python script to interactively synchronize forks with upstream

★ 1Forks 0PythonGitHub ↗Compare

README

fork-sync

A zero-dependency Python script for keeping private forks in sync with their upstream.

Designed for teams or freelancers who maintain per-client customisations of a shared upstream codebase. Drop two files into each fork, edit the config once, and run the script whenever you need to pull in upstream changes — with a guided review before anything is merged.

── Step 1 — upstream remote
  ✔  Remote 'upstream' already configured.

── Step 2 — fetch upstream
  →  Fetching upstream…
  ✔  Fetch complete.

── Step 3 — working tree check
  ✔  Working tree is clean.

── Step 4 — review & merge
  →  3 new commits on upstream/main ahead of my-client-branch:

      SHA    SUBJECT                              AUTHOR              WHEN
      ───────────────────────────────────────────────────────────────────
      a1b2c3  Fix null pointer in auth middleware   Alice Smith        2 days ago
      d4e5f6  Add rate limiting to /api/search      Bob Jones          4 days ago
      9f8e7d  Bump lodash to 4.17.21                dependabot[bot]    6 days ago

  Show full diff? [y/N]
  Merge upstream/main into my-client-branch? [Y/n]

Features

  • No external dependencies — pure Python 3 stdlib, nothing to install
  • Interactive review — see new commits and optionally the full diff before merging
  • Safe by default — refuses to run with uncommitted changes in the working tree
  • --dry-run mode — walks through all steps and shows the review, but skips the actual merge
  • Coloured output — ANSI colours throughout, including a syntax-highlighted diff
  • Pager support — long diffs are piped through $PAGER (defaults to less -R)
  • Remote management — adds the upstream remote automatically; warns if it already points to a different URL and offers to fix it

Requirements

  • Python 3.9 or later
  • Git (available on $PATH)

Setup

For each private fork, add two files at the repository root:

1. Download or copy fork-sync.py into the repo root.

2. Create a .fork-sync config file:

# fork-sync configuration
upstream_url    = https://github.com/ORIGINAL_OWNER/ORIGINAL_REPO.git
upstream_branch = main

Commit both files:

git add fork-sync.py .fork-sync
git commit -m "chore: add fork-sync"

Tip: add .fork-sync to your .gitignore if you don't want the upstream URL tracked, and keep it as a local-only file per developer instead.


Usage

From anywhere inside the repository:

python3 fork-sync.py

To preview what would happen without actually merging:

python3 fork-sync.py --dry-run

What it does, step by step

Step What happens
1 Checks whether a remote named upstream exists. Adds it from the config URL if not. If it exists but points to a different URL, prompts to update it.
2 Runs git fetch upstream to pull down the latest upstream history.
3 Checks for uncommitted changes. Aborts with a clear message if any are found.
4 Shows a table of commits on upstream/<branch> that are not yet in the current branch. Offers to show the full diff. Asks for confirmation, then runs git merge upstream/<branch> --ff.

The --ff flag allows a fast-forward merge when possible but does not require it — a merge commit will be created when histories have diverged, which is normal for forks with client-specific commits.


Config reference

The .fork-sync file uses a simple key = value format. Lines beginning with # are treated as comments.

Key Required Description
upstream_url ✔ Clone URL of the upstream repository (HTTPS or SSH)
upstream_branch ✔ Branch on the upstream to merge from (e.g. main, develop)

Typical workflow

upstream repo          your private fork
─────────────          ─────────────────────────────
main ──────────────→   main  (mirror, rarely touched)
                       my-client ← your customisations live here
  1. Do your client work on a dedicated branch (my-client, production, etc.)
  2. Periodically run fork-sync.py from that branch
  3. Review the incoming commits; merge when ready
  4. Resolve any conflicts as you normally would with git

Tips

SSH remotes work fine — just use an SSH URL in the config:

upstream_url = [email protected]:ORIGINAL_OWNER/ORIGINAL_REPO.git

Stash before running if you have work in progress:

git stash
python3 fork-sync.py
git stash pop

Override the pager by setting $PAGER in your shell:

PAGER=bat python3 fork-sync.py   # use 'bat' for even nicer diffs

License

BSD 3-Clause — see LICENSE.

Contributors

jlblancoc

Issues