* one integration branch for trivial/non-conflicting merge results * optional slice branches for selected conflicted files * original annotate/blame information is retained
Doesn't this mean the integration branch will be missing key updates, thus it's expected to be broken?
mergetopus is a tool that helps teams follow a structured workflow for very large merges by splitting one risky merge into parallelizable tasks:
It follows and extends the workflow in ext/Invoke-TheMergetopus.ps1.
The core idea is collaborative merge execution:
mergetopus)mergetopus resolve)mergetopus status)mergetopus cleanup)When you run mergetopus <source>, it:
_mmm/<target>/<source>/integration).git merge --no-commit against the source.ours in that same commit β they'll be dealt with in slices.--select-paths). Any conflicts you don't explicitly group get their own one-file slice.From there, use mergetopus resolve to work through each slice with your merge tool (see Resolving Conflicts).
Use the commands together as a repeatable team process.
mergetopus).# from your target branch (for example: main)
mergetopus feature/very-large-change
What this does:
_mmm/<target>/<source>/integration_mmm/<target>/<source>/sliceN)mergetopus resolve).# each developer picks a slice and resolves it
mergetopus resolve _mmm/main/feature_very-large-change/slice1
mergetopus resolve --commit _mmm/main/feature_very-large-change/slice1
Each slice is merged into the integration branch with --no-commit, resolved with your merge tool, and optionally committed.
mergetopus status).mergetopus status feature/very-large-change
Status reports merged vs pending slices and suggests what to do next.
mergetopus cleanup).kokomeco snapshot branch (a consolidated merge commit for promotion)mergetopus cleanup
main: A---B---C
\
feature: D---E---F
attempt:
main + feature -> conflicts in many files at once
result:
- one large conflict-resolution task
- hard to parallelize
remembered HEAD: C
main: A---B---C
\
source feature: D---E---F
integration branch:
_mmm/main/feature/integration C---M(partial: only non-conflicting files)
|
+-- conflicted files reset to ours in M
slice branches from merge-base(C,F):
_mmm/main/feature/slice1 B---S1 (explicit group: fileA,fileB)
_mmm/main/feature/slice2 B---S2 (explicit group: fileC)
_mmm/main/feature/slice3 B---S3 (auto singleton for unassigned fileD)
_mmm/main/feature/slice4 B---S4 (auto singleton for unassigned fileE)
integration after merging slices:
_mmm/main/feature/integration C---M---(merge S1)---(merge S2)---(merge S3)---(merge S4)
optional non-destructive consolidation output:
_mmm/main/feature/kokomeco
\---MC (single merge-commit snapshot branch)
notes:
- integration history stays intact
- consolidated branch is created for review/promotion
Five developers maintain a project with multiple long-term stable (LTS) release branches:
| Developer | Role | Active branches |
|---|---|---|
| Wendy Corduroy | Developer | LTS_v17, LTS_v32 |
| Gideon Gleeful | Developer | LTS_v17, LTS_v32 |
| Dipper Pines | Developer | LTS_v32, main |
| Mabel Pines | Team lead (Gideon's) | LTS_v32, main |
| Stan Pines | Senior integrator | LTS_v42 (merges forward) |
Wendy and Gideon make overlapping changes to config.toml and engine.rs
in LTS_v17. All four developers contribute to LTS_v32. Dipper and Mabel also
work on main. Stan regularly merges from older LTS versions into newer ones
and eventually upstream to main.
Wendy: v17 pooling Gideon: v17 hardening
(config, engine, (config line 2,
api, utils) engine line 2)
LTS_v17: M0 ββββββββ W1 ββββββββββββββββββ G1
β
β Wendy: v32 Gideon: v32 Dipper: v32 Mabel: v32
β retry caching debug logging auth + logging
LTS_v32: M0 ββββββββ W2 ββββββββ G2 ββββββββ D1 ββββββββββββ MB1
β
β Stan: v42
β baseline
LTS_v42: M0 ββββββββ S1 ββββββ kokomecoβ ββββββ kokomecoβ ββββ Β·Β·Β·
β β β
β merge LTS_v17 merge LTS_v32
β
β Dipper: Mabel:
β metrics telemetry
main: M0 ββββββββ D2 ββββββββ MB2 βββββββββββββββ Β·Β·Β· ββββββ merge kokomecoβ
Stan merges LTS_v17 β LTS_v42, then LTS_v32 β LTS_v42, then LTS_v42 β main.
# Stan initiates the merge from LTS_v42
mergetopus LTS_v17 --select-paths config.toml,engine.rs
LTS_v42 HEAD (Stan) LTS_v17 (Wendy + Gideon)
β β
ββββ mergetopus LTS_v17 ββββ β
β β
_mmm/LTS_v42/LTS_v17/ β β
integration βββ partial merge (auto-merged files,
β conflicts reset to ours)
β
βββ slice1: config.toml + engine.rs β explicit group (--select-paths)
β β Mabel resolves (contains Gideon's overlapping changes)
β
βββ slice2: api.rs
β β Dipper resolves (Wendy's caching changes)
β
βββ slice3: utils.rs
β Dipper resolves (Wendy's format changes)
After all slices resolved + consolidated:
_mmm/LTS_v42/LTS_v17/kokomeco
parent 1: original LTS_v42 HEAD (Stan's baseline)
parent 2: LTS_v17 tip (Wendy + Gideon)
tree: final integration state
# After promoting kokomecoβ into LTS_v42
git checkout LTS_v42 && git merge _mmm/LTS_v42/LTS_v17/kokomeco
mergetopus LTS_v32 --select-paths engine.rs,utils.rs
_mmm/LTS_v42/LTS_v32/
integration
β
βββ slice1: engine.rs + utils.rs β Mabel resolves (Gideon/Mabel changes)
β
βββ slice2: api.rs β Dipper resolves (Wendy's retry logic)
β
βββ slice3: config.toml β Dipper resolves (Wendy/Dipper's config)
After consolidation:
_mmm/LTS_v42/LTS_v32/kokomeco
parent 1: LTS_v42 (post kokomecoβ)
parent 2: LTS_v32 tip
# After promoting kokomecoβ
git checkout main
mergetopus LTS_v42
Only config.toml and engine.rs conflict (main never changed api.rs or
utils.rs from the initial commit, so those auto-merge).
After the full cascade, git blame on the final kokomeco correctly traces
through the merge parents back to the original authors β no squash, no
history rewrite:
git blame _mmm/LTS_v42/LTS_v17/kokomeco -- config.toml:
max_connections = 200 β Wendy Corduroy ("Wendy: v17 connection pooling and caching")
timeout = 60 β Gideon Gleeful ("Gideon: v17 timeout hardening and error handling")
git blame _mmm/LTS_v42/LTS_v17/kokomeco -- engine.rs:
fn init() { wendy_pool(); } β Wendy Corduroy
fn process() { gideon_errors(); } β Gideon Gleeful
git blame _mmm/LTS_v42/LTS_v17/kokomeco -- api.rs:
fn handle() { wendy_caching(); } β Wendy Corduroy
git blame _mmm/LTS_v42/LTS_v17/kokomeco -- utils.rs:
fn format() { wendy_format(); } β Wendy Corduroy
git blame _mmm/main/LTS_v42/kokomeco -- engine.rs:
fn init() { mabel_auth(); } β Mabel Pines (from LTS_v32)
fn process() { gideon_cache(); } β Gideon Gleeful (from LTS_v32)
git blame _mmm/main/LTS_v42/kokomeco -- api.rs:
fn handle() { wendy_retry(); } β Wendy Corduroy (from LTS_v32, auto-merged)
The kokomeco merge topology preserves correct line-blame because:
This scenario is exercised by the integration test
lts_cascade_merge_preserves_authorship_in_kokomecointests/test_suite_d.rs.
If the integration branch already exists, mergetopus:
_mmm/<safe-current>/<safe-source>/sliceN)Consolidation is non-destructive: it creates a separate branch _mmm/<safe-current>/<safe-source>/kokomeco with a single merge commit snapshot.
kokomeco stands for KOrrekt KOnsoliderter MErge COmmit (German: "correctly consolidated merge commit").
The name is intentional: the consolidation step is not a squash and not a history rewrite of the integration branch. Instead, Mergetopus creates a separate merge-commit snapshot branch with merge parents derived from:
and with the final resolved tree copied from the integration branch.
Why this matters for git blame:
Download the mergetopus binary for your platform from the GitHub Releases page and place it in a directory on your system's PATH.
On Windows with Chocolatey:
choco install mergetopus.portable
mergetopus works on Windows, macOS, and Linux.
During resolve, Mergetopus sets the same environment variables (LOCAL, BASE, REMOTE, MERGED) across platforms so merge tool configuration stays portable.
On Windows, merge tools are invoked directly (without cmd /c) to avoid quoting/whitespace proxy issues.
On Unix-like systems (macOS, Linux), command execution uses sh -c.
When running inside a Git worktree on Windows, mergetopus ensures core.longpaths=true
for the repository so deep path merges remain usable.
Mergetopus adapts its worktree behavior based on your existing setup:
This keeps non-worktree workflows unchanged while making branch checkouts easier in repositories that already use worktrees.
Integration branches follow the pattern _mmm/<safe-original-branch>/<safe-source-branch>/integration.
These are temporary working branches that hold the merge result with auto-merged
files staged and conflicted files reset to "ours".
_mmm/main/feature/integration, _mmm/develop/release_v1/integrationSlice branches follow the pattern _mmm/<safe-original-branch>/<safe-source-branch>/slice<N> where N is a number (1, 2, 3, ...).
These are temporary per-conflict branches for resolving individual conflict groups.
_mmm/main/feature/slice1, _mmm/main/feature/slice2Kokomeco branches follow the pattern _mmm/<safe-original-branch>/<safe-source-branch>/kokomeco.
These are optional output branches created after all slices are merged, containing
a single merge-commit snapshot.
_mmm/main/feature/kokomecoThe safe-* components use the same sanitization rules as before: characters outside [0-9A-Za-z._-] are replaced with _.
When selecting a source branch for mergetopus:
_mmm/.../slice<N>) are automatically filtered out from the branch picker.
They should only be used with mergetopus resolve, never as a source for a new merge.If you select an integration branch as the source, mergetopus detects this
and asks what you want to do:
Example: if you select _mmm/main/feature/integration, mergetopus will
recognize that the target was main and the source was feature, then let
you pick between viewing existing status or starting a new merge of feature
into main.
Interactive source selection (branch picker overlay shown):
mergetopus
Provide source branch explicitly:
mergetopus feature/refactor-auth
Explicit conflict grouping by path list:
# Put explicit paths into one grouped slice; all remaining conflicts become one-file slices
mergetopus feature/refactor-auth --select-paths src/a.rs,src/b.rs
Interactive conflict grouping (with F3 opening your configured diff.tool, or the inline 3-way view when no diff.tool is set) when --select-paths is not provided:
mergetopus feature/refactor-auth
Auto-confirm consolidation prompt:
mergetopus feature/refactor-auth --yes
Non-interactive CI/CD mode (no TUI):
# SOURCE is required in quiet mode
mergetopus feature/refactor-auth --quiet
# Quiet mode + explicit conflict grouping
mergetopus feature/refactor-auth --quiet --select-paths src/a.rs,src/b.rs
# Quiet mode + auto-consolidate when eligible
mergetopus feature/refactor-auth --quiet --yes
# Show slice/integration progress status
mergetopus status feature/refactor-auth
# Cleanup temporary integration/slice branches (interactive confirmation)
mergetopus cleanup
# Take over an already in-progress manual merge
mergetopus HERE
Takeover mode for an in-progress merge:
# Optional: non-interactive explicit grouping for remaining conflicts
mergetopus --quiet --select-paths src/big/file1.cs,src/big/file2.cs HERE
Use mergetopus status to inspect an integration branch and its slice progress.
# Status by source ref
mergetopus status feature/refactor-auth
# Status by integration branch name
mergetopus status _mmm/main/feature_refactor-auth/integration
The status output includes:
After mergetopus has created slice branches, use resolve to merge a selected
slice into its corresponding integration branch with --no-commit, then open
each conflicted file in your configured merge tool one-by-one:
# Interactive slice branch picker (TUI)
mergetopus resolve
# Resolve a specific slice branch directly
mergetopus resolve _mmm/main/feature/slice1
# Non-interactive (--quiet requires an explicit branch)
mergetopus resolve --quiet _mmm/main/feature/slice1
# Create a commit automatically after staging
mergetopus resolve --commit _mmm/main/feature/slice1
git merge --no-commit <slice>.LOCAL as the integration branch HEAD before the mergeREMOTE as the slice branch tipBASE as merge-base(LOCAL, REMOTE)LOCAL β the file at the integration branch sideBASE β the file at the common ancestor (merge-base)REMOTE β the file at the slice branch sideLOCAL, BASE, REMOTE, and MERGED
set as environment variables (same convention as git mergetool). The command
is executed via the appropriate shell:cmd /csh -c <cmd> MERGED points to the conflicted working-tree file on the integration branch,
so the tool writes the resolution directly into the repository.
6. Stages each resolved file.
7. Optional: if --commit is passed, creates the merge commit on the integration branch.
HERE)Use mergetopus HERE when you already started a regular git merge manually,
resolved some conflicts, and want Mergetopus to take over only the remaining
unresolved conflicts.
Typical scenario:
git merge <source> on your target branch.mergetopus HERE to continue using slice workflow for what remains.Behavior of HERE:
MERGE_HEAD must exist)--select-paths in quiet mode)Command examples:
# Interactive takeover
mergetopus HERE
# Quiet takeover with explicit grouping for remaining conflicts
mergetopus --quiet --select-paths src/module/a.rs,src/module/b.rs HERE
mergetopus resolve reads the merge tool from Git config. Set it once in
your global or repository config:
# Choose a tool name
git config merge.tool vimdiff
# Provide the shell command template.
# $LOCAL, $BASE, $REMOTE, $MERGED are expanded at runtime.
git config mergetool.vimdiff.cmd 'vimdiff "$LOCAL" "$BASE" "$REMOTE" -c "wincmd J" "$MERGED"'
Some common examples:
| Tool | Example mergetool.<name>.cmd |
|---|---|
| vimdiff | vimdiff "$LOCAL" "$BASE" "$REMOTE" -c "wincmd J" "$MERGED" |
| nvim | nvim -d "$LOCAL" "$REMOTE" "$MERGED" |
| code (VS Code) | code --wait --merge "$LOCAL" "$REMOTE" "$BASE" "$MERGED" |
| meld | meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED" |
| kdiff3 | kdiff3 "$BASE" "$LOCAL" "$REMOTE" -o "$MERGED" |
Any tool that reads $LOCAL, $BASE, $REMOTE and writes its result to
$MERGED will work.
In the conflict selector, F3 behaves as follows:
git config diff.tool is set, F3 launches that difftool for the selected conflicted filediff.tool is not set, F3 opens the built-in inline 3-way diff overlayExample:
git config diff.tool vscode
git config difftool.vscode.cmd 'code --wait --diff "$LOCAL" "$REMOTE"'
When --quiet is not set, TUI is used for source branch picking (if SOURCE
is omitted), conflict selection (if --select-paths is omitted), and slice
branch selection for resolve (if BRANCH is omitted).
Conflict selector:
Arrow Up/Down: move cursorTab: switch panen: create new explicit sliceSpace: assign/move highlighted conflict into currently selected sliceu: unassign highlighted conflict (it will become default one-file slice)d: delete selected explicit slice (its files become unassigned)F3: open configured difftool for selected file (or inline 3-way diff if diff.tool is not set)Enter: apply selectionEsc: close overlay or cancel selectorq: cancel selectorSource branch picker / Slice branch picker:
Arrow Up/Down: moveEnter: selectEsc or q: cancel3-way diff overlay:
Up/Down: scrollPageUp/PageDown: fast scrollHome/End: jump to top/bottomEsc: close overlaySlice commits include informational trailers that document where each file came from:
Source-RefSource-CommitSource-PathSource-Path-CommitCo-authored-by (when source-side author info is available)These trailers are purely metadata for humans and tools like mergetopus status.
Kokomeco creation does not depend on them β it works entirely from git history
and the integration branch tree.
A squash or consolidation commit does not preserve per-commit author lineage by itself.
mergetopus keeps attribution info using co-author trailers on slice commits
and preserves original integration/slice history by writing consolidated output
to a separate branch. The kokomeco branch itself gets correct git blame
through its merge parents, not through trailers.
--yes is used).--quiet disables TUI interactions; provide SOURCE explicitly for CI/CD runs.MIT. See LICENSE.