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
| Repo | What it is | Author |
|---|---|---|
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 |
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.
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.