Example

Full walkthrough of using monopoly for moving file while preserving history.

Everything below is real output from examples/walkthrough.sh — a bash script that builds three throwaway git repos and runs monopoly move three times. Try it:

curl -fsSL https://raw.githubusercontent.com/itaymendel/monopoly-repo/main/examples/walkthrough.sh | bash

Three repos, three engineers

RepoWhat it isAuthor
app-monolith The big in-house app. dateFormat() was born here. Alice
shared-utils A small library repo. Other services depend on it. Bob
date-fmt An open-source npm package. Brand-new. Carol
APP-MONOLITH src/dateFormat.ts Alice: 3 commits 1 graduate SHARED-UTILS src/dateFormat.ts Bob: +2 commits 2 spin out DATE-FMT (OSS) src/dateFormat.ts Carol: +2 commits 3 round trip — vendor back
Three hops, three authors. The file ends back where it started.

Setup

$ tree /tmp/monopoly-demo-run -L 2 --noreport
/tmp/monopoly-demo-run
├── app-monolith
│   ├── README.md
│   ├── src/billing.ts
│   ├── src/billing.test.ts
│   └── src/dateFormat.ts        ← Alice's 3 commits
├── shared-utils
│   ├── README.md
│   ├── src/cache.ts
│   └── src/throttle.ts
└── date-fmt
    └── (empty init commit)

Hop 1 — app-monolith → shared-utils

Three teams started copy-pasting dateFormat. Graduate it into the shared library.

$ cd app-monolith
$ monopoly move src/dateFormat.ts --to ../shared-utils --as src/dateFormat.ts
✓ Move staged in ../shared-utils

  Source:  src/dateFormat.ts (3 commits extracted)
  Target:  ../shared-utils/src/dateFormat.ts
  Seam:    staged (not yet committed)

The merge is staged, not committed — that's on purpose, so you can review. Finalise on the target. Delete on the source.

$ cd ../shared-utils && git commit --no-edit
$ cd ../app-monolith && git rm src/dateFormat.ts
$ git commit -m "chore: dateFormat moved to shared-utils"

A couple of follow-up edits later:

$ cd shared-utils && git log --follow --oneline -- src/dateFormat.ts
44644d9 chore(dateFormat): strict-mode types               ← Bob
31d8035 fix(dateFormat): tighten DST/timezone edges        ← Bob
831317e monopoly: move app-monolith:src/dateFormat.ts → src/dateFormat.ts
4526e9e feat(util): dateFormat supports relative time strings  ← Alice
c483e41 feat(util): dateFormat handles unix timestamps         ← Alice
4f0178b feat(util): add dateFormat()                            ← Alice

Hop 2 — shared-utils → date-fmt

Other companies want it. Spin it out as its own OSS package.

$ cd shared-utils
$ monopoly move src/dateFormat.ts --to ../date-fmt --as src/dateFormat.ts
✓ Move staged in ../date-fmt

  Source:  src/dateFormat.ts (5 commits extracted)
  Target:  ../date-fmt/src/dateFormat.ts

Same finalise / delete dance. A few OSS-flavoured commits on top:

$ cd date-fmt && git log --follow --oneline -- src/dateFormat.ts
0ea568b fix: throw on null/undefined input                      ← Carol
939cac3 feat: i18n locale support                                ← Carol
abc9faf monopoly: move shared-utils:src/dateFormat.ts → src/dateFormat.ts
ba6a721 chore(dateFormat): strict-mode types                    ← Bob
19bf9f5 fix(dateFormat): tighten DST/timezone edges             ← Bob
4526e9e feat(util): dateFormat supports relative time strings   ← Alice
c483e41 feat(util): dateFormat handles unix timestamps          ← Alice
4f0178b feat(util): add dateFormat()                             ← Alice

Hop 3 — date-fmt → app-monolith // round trip

The monolith needs an emergency fix the OSS release doesn't have yet. Vendor it back in. OSS stays upstream.

$ cd date-fmt
$ monopoly move src/dateFormat.ts --to ../app-monolith --as src/dateFormat.ts
✓ Move staged in ../app-monolith

  Source:  src/dateFormat.ts (9 commits extracted)
  Target:  ../app-monolith/src/dateFormat.ts

Finalise the merge in app-monolith. The history:

$ git log --follow --pretty="format:%h  %<(7)%an  %s" -- src/dateFormat.ts
0ea568b  Carol    fix: throw on null/undefined input
939cac3  Carol    feat: i18n locale support
ba6a721  Bob      chore(dateFormat): strict-mode types
19bf9f5  Bob      fix(dateFormat): tighten DST/timezone edges
4526e9e  Alice    feat(util): dateFormat supports relative time strings
c483e41  Alice    feat(util): dateFormat handles unix timestamps
4f0178b  Alice    feat(util): add dateFormat()

The proof — git blame

"But surely you lose blame after that many rewrites." Well, no. Three git filter-repo passes and a round-trip merge later, every line still credits whoever first wrote it.

$ git blame --date=short -- src/dateFormat.ts
939cac30 (Carol 2026-05-08 1) export function dateFormat(d: Date | number, opts?: { relative?: boolean; locale?: string }): string {
0ea568b3 (Carol 2026-05-08 2)   if (d == null) throw new TypeError('dateFormat: missing input');
c483e414 (Alice 2026-05-08 3)   const date = typeof d === 'number' ? new Date(d * 1000) : d;
939cac30 (Carol 2026-05-08 4)   if (opts?.relative) return relative(date, opts?.locale);
939cac30 (Carol 2026-05-08 5)   return date.toLocaleString(opts?.locale);
^4f0178b (Alice 2026-05-08 6) }
939cac30 (Carol 2026-05-08 7) function relative(_d: Date, _l?: string): string { return '...'; }

Line 3 is Alice's c483e414. Her original commit, in the original repo. Intact.

don't squash the monopoly PR

Run it yourself

Needs git ≥ 2.22, monopoly on PATH, and Python 3.

# one-liner
curl -fsSL https://raw.githubusercontent.com/itaymendel/monopoly-repo/main/examples/walkthrough.sh | bash

# or download and run:
curl -fsSL -O https://raw.githubusercontent.com/itaymendel/monopoly-repo/main/examples/walkthrough.sh
chmod +x walkthrough.sh
./walkthrough.sh /tmp/my-monopoly-demo

The repos stay put. Poke at them:

cd /tmp/my-monopoly-demo/app-monolith
git log --follow --oneline -- src/dateFormat.ts
git blame -- src/dateFormat.ts
git log --full-history --pretty=fuller -- src/dateFormat.ts | head -60

One quirk — the round trip

Hop 3 re-imports the file rather than reverting Alice's original removal. So the graph carries two parallel chains for the early commits — both attributed to Alice, with her original timestamps (filter-repo rewrites SHAs but keeps author and date).

git log --follow picks one chain and gives you the clean seven-commit story above. git log --full-history shows both. Pick whichever answers your question.