# Metabranch

Metabranch is a **Git extension** for creating temporary integration branches that pool multiple feature or hotfix branches together.
It is designed to make multi-PR feature development and testing simpler, without changing your existing Git workflow.

## Install

From this repository:

```bash
pip install .
```

For development:

```bash
pip install -e .[dev]
```

## Quick Start

```bash
git checkout master
metabranch checkout -b feature-test feature-a feature-b
metabranch status
metabranch rebase
metabranch push
```

Use `metabranch push -r` when you also want to push the contained branches, not just the metabranch.

## How Metabranch Helps

Best practice says to keep PRs small and focused, but real features often span multiple PRs.
PR chains can handle that, but they create friction:

- Sequential merges can block downstream work.
- History rewrites can make the workflow fragile.
- QA often has to switch between branches or wait for merges to validate the full feature.

Metabranch takes a different approach by pooling related branches into one temporary integration branch:

- You can test a full feature early while keeping PRs small and independent.
- QA can validate the combined behavior without juggling branches.
- The integration branch stays reconstructable from the underlying branches.
- Teams can stop inventing ad-hoc staging branches for every multi-PR feature.

Use Metabranch when:

- One feature spans multiple branches or PRs.
- QA needs the combined behavior before the branches merge.
- You want a safer workflow than stacking branches directly on top of each other.

## Where It Fits

Metabranch is for a specific workflow shape:

- The real work should stay split across normal branches.
- Those branches still need to be exercised together before merge.
- You do not want to turn them into a permanent branch stack or merge them early.

That shape shows up across a spectrum.

At one end, a single developer is breaking one large feature into multiple reviewable PRs.
At the other end, multiple developers or teams are working in parallel and need occasional integration before their branches merge.

Those are not the only two use cases.
They are the clearest extremes that explain the benefit.

### Extreme 1: one developer, one feature, multiple PRs

One person may be building a feature that is too large or too cross-cutting for a single pull request.

Typical reasons:

- The work touches different subsystems and should be reviewed separately.
- Different teams need to review different parts.
- You want smaller PRs even though the feature only makes sense as a whole.

Here, Metabranch gives that developer one place to run and test the whole feature while keeping the durable history split across clean underlying branches.

### Extreme 2: multiple developers, separate branches, temporary integration

A team may be working in parallel, with one branch temporarily needing another team's change in order to validate real behavior.

Typical reasons:

- A frontend branch needs a backend branch that is not merged yet.
- Two teams are changing adjacent behavior and need to test the interaction.
- One feature depends on another feature's new API, schema, or UX contract.

Here, Metabranch gives the team a temporary integration branch without forcing those branches into a permanent stack or requiring early merges.

## Other Good Fits

The same pattern also shows up in a few nearby workflows:

- `QA / UAT snapshots`: assemble a specific set of in-flight branches for a test cycle without merging them yet
- `coordinated landings`: validate a set of reviewed branches together before landing them in a way that keeps `master` healthy
- `consumer + provider development`: let one branch temporarily follow another branch's changing API, schema, or contract without polluting either review branch
- `large refactors`: split infrastructure changes, migrations, and cleanup into separate review branches, but validate the combined result on one temporary branch
- `preview environments`: use the metabranch as the branch that drives an integrated preview deployment for humans or CI
- `bug reproduction across in-flight work`: reproduce an interaction bug that only appears when multiple not-yet-merged branches are assembled together

## Core Idea

A **metabranch** is a normal Git branch with extra metadata that tracks a set of other branches.

Think of it as a disposable integration branch:

- The real work belongs on the underlying branches.
- The metabranch exists so you can assemble and test them together.
- The metabranch should be reconstructable from those branches.
- The set of contained branches can change as coordination needs change.

Because of that model:

- Destructive operations like `metabranch rebase` only run with a clean working tree.
- The tracked `.metabranch.yaml` file is tool-owned metadata. Metabranch writes and commits it automatically on metabranch creation and metadata updates.
- `.metabranch.yaml` changes stay isolated in their own metadata-only commits.
- `metabranch commit` is the normal safe path: it runs `git commit` first, then immediately reconciles the new metabranch-only commit back onto the correct underlying branch.
- Plain `git commit` is still allowed on a metabranch as an escape hatch, as long as each commit can be cleanly attributed to one underlying branch during reconciliation or intentionally dropped later.
- Metabranches are ephemeral integration branches. Regular branches are durable work branches. Converting an existing branch into or out of a metabranch changes the branch's semantics, so Metabranch forbids it.

## Command Overview

Metabranch is intentionally shaped like Git.
Most of the core commands reuse familiar Git verbs, so the fastest way to understand the CLI is to map each `metabranch` subcommand to the Git command it is extending.

- `metabranch checkout`: create a new metabranch with `-b`, or switch to an existing metabranch. It will not treat an existing plain Git branch as a metabranch.
- `metabranch branch`: list metabranches or create one without checking it out, similar to `git branch`
- `metabranch add` / `metabranch remove`: add or remove contained branches on the current metabranch
- `metabranch commit`: run `git commit` on the metabranch, then immediately reconcile that commit back onto the owning contained branch
- `metabranch pull`: pull the metabranch itself from its remote; use `metabranch pull -r` to refresh contained branches and rebuild
- `metabranch status`: show status for the current metabranch and its contained branches, similar to `git status`
- `metabranch rebase`: rebuild a metabranch from its contained branches, or also rebase the contained branches when requested, similar to `git rebase`
- `metabranch push`: push the metabranch itself; use `metabranch push -r` to also push contained branches
- `metabranch resume` / `metabranch abort`: continue or roll back a blocked workflow after a conflict

If you already know Git, that mental mapping should make the rest of the workflow easier to follow.

Two patterns are worth keeping in mind before the examples:

- You can add or remove contained branches on the fly as related work starts, lands, or stops being relevant.
- `metabranch commit` is the default happy path when you want a metabranch change routed back immediately.
- Plain `git commit` remains available when you want to batch several metabranch-only commits first and reconcile them later with `metabranch rebase`.

For `metabranch rebase`, the current modes are:

- `metabranch rebase`: rebuild the metabranch from its current base commit
- `metabranch rebase <head>`: rebuild the metabranch from a new base head
- `metabranch rebase -r <head>`: rebase the contained branches onto `<head>` and then rebuild the metabranch
- `metabranch rebase -i`: rebuild the metabranch and then open an interactive rebase on the metabranch itself
- `metabranch rebase -r -i <head>`: open interactive rebases on the contained branches, then rebuild the metabranch

For remote updates, the current model is:

- `metabranch pull`: pull the metabranch branch itself from its remote
- `metabranch pull -r`: refresh the contained branches from their remotes and rebuild the metabranch
- `metabranch pull -r <head>`: refresh the contained branches from their remotes and rebuild the metabranch onto a new base

## End-to-End Example 1: One Developer, Multiple PRs

Imagine one developer is building a "smart scheduling" feature for a task app.

They want two reviewable PRs:

- `smart-schedule-engine`: the scheduling logic
- `smart-schedule-calendar`: the calendar interaction

They expect to bounce between both areas all week.
The easiest place to do that is the integration branch, not either review branch in isolation.

### 1. Create the review branches

```bash
git checkout master
git pull

git checkout -b smart-schedule-engine
git checkout master

git checkout -b smart-schedule-calendar
git checkout master
```

At this point, the branches exist as the durable homes for future commits and PRs.

### 2. Create the metabranch and do most daily work there

```bash
metabranch checkout -b smart-schedule-test smart-schedule-engine smart-schedule-calendar
```

or, if you want to add branches incrementally:

```bash
metabranch checkout -b smart-schedule-test smart-schedule-engine
metabranch add smart-schedule-calendar
```

Now `smart-schedule-test` is just a normal Git branch that assembles both review branches.
You can run the app, run tests, inspect diffs, and work there normally.
The important constraint is that commits made from this working state should still be routed back to the real branches.

The set of contained branches is not fixed forever.
If a third branch becomes relevant for a few days, you can add it.
If one branch lands early or stops mattering, you can remove it.

### 3. Make coordinated changes on the metabranch, then reconcile them later

Suppose the developer spends the morning changing both the ranking rules and the calendar explanation copy.
That is exactly the kind of day-to-day drift the metabranch is for.

They work on the integrated branch and make clean commits:

```bash
# on smart-schedule-test
$EDITOR ...

git add src/scheduler/engine.py src/scheduler/rules.py
metabranch commit -m "Tune slot ranking for recurring tasks"

git add src/calendar/suggestion_panel.tsx src/calendar/explanations.ts
metabranch commit -m "Clarify why each suggested slot was chosen"
```

The benefit is not that the work was isolated while being developed.
The benefit is that it was easy to develop together, while the durable history can still be reconciled back onto `smart-schedule-engine` and `smart-schedule-calendar`.

Each `metabranch commit` first creates a normal Git commit on the metabranch, then immediately runs the same reconciliation flow that `metabranch rebase` uses.
If Metabranch can infer the target branch safely, it does so automatically.
If not, it opens an interactive editor and asks where the new commit belongs.

### 4. Share the assembled feature while keeping review split

When the developer wants feedback or QA coverage:

```bash
metabranch push
```

That pushes the metabranch itself.
If they also want to push the contained review branches in one step, they can use:

```bash
metabranch push -r
```

Review still happens on the underlying branches, because those are the branches that own the real commits.
The metabranch is the place to exercise the whole feature as one unit.

### 5. Rebuild the metabranch as the branches evolve

As the review branches change, rebuild the integration branch from them.
If you only want to reconstruct the metabranch from the current state of its contained branches, use `metabranch rebase`.
If you want to move the whole set onto a new base and rebase the contained branches too, use `metabranch rebase -r <head>`.

For example:

```bash
metabranch rebase
metabranch rebase master
metabranch rebase -r master
```

The behavior is:

- Metabranch checks that your working tree is clean before doing anything destructive.
- `metabranch rebase` reconstructs the metabranch from the contained branches as they already exist.
- `metabranch rebase <head>` reconstructs it from a new base head.
- `metabranch rebase -r <head>` first rebases the contained branches onto that head, then reconstructs the metabranch.
- `metabranch rebase -i` opens an interactive rebase on the metabranch after reconstruction.
- `metabranch rebase -r -i <head>` opens interactive rebases on the contained branches before reconstruction.

If the metabranch contains commits that do not clearly belong to any underlying branch, Metabranch stops and asks you to reconcile them instead of dropping them silently.

### 6. Land the reviewed branches without making `master` invalid

Once `smart-schedule-engine` and `smart-schedule-calendar` are reviewed and the integrated behavior is correct, the metabranch has done its job.

If the branches can land independently without breaking anything, they can merge separately.
If they must land together, merge them in a coordinated way, or merge one reviewed branch into the other and then land that combined result to `master`.

The ownership model does not change:

- the metabranch is still a normal temporary branch
- the durable history lives on the review branches and then on `master`
- the metabranch exists to make integrated day-to-day work practical

## End-to-End Example 2: Multiple Developers, Temporary Integration

Now imagine two teams are working in parallel on an invoice approval flow.

- `approval-policy-engine` is owned by the workflow team
- `invoice-review-screen` is owned by the UI team

Most of the time, each team works on its own branch in the normal way.
The pain starts when the UI team needs the current workflow branch in order to test real behavior, and that dependency keeps moving.

### 1. Each team keeps its own branch clean

The workflow team works on `approval-policy-engine`.
The UI team works on `invoice-review-screen`.
Those branches remain the durable homes for each team's changes and PRs.

### 2. Create a temporary integration branch when combined behavior matters

When the UI team needs both sides together:

```bash
git checkout master
metabranch checkout -b invoice-approval-test approval-policy-engine invoice-review-screen
```

Now they have a normal branch that assembles both lines of work without merging one team's branch into the other's branch.

### 3. Refresh the integration branch as other branches move

The workflow team keeps pushing updates to `approval-policy-engine`.
The UI team wants those updates in their test environment, but they do not want to repeatedly merge or rebase that branch into `invoice-review-screen`.

A typical refresh without Metabranch turns into repeated checkout, pull, rebase, merge, and cleanup steps across branches that are supposed to stay reviewable.

With Metabranch, there should be an explicit sync step:

```bash
metabranch pull -r
```

And when the goal is to both sync and rebuild onto a base, the refresh should collapse into one command:

```bash
metabranch pull -r master
```

You still have to resolve conflicts if the branches conflict.
The difference is that the workflow is centralized and hard to perform incorrectly.
The temporary integration branch absorbs that coordination work, while the owned branches remain clean.

### 4. Add and remove dependencies as coordination changes

Sometimes the temporary integration set changes.
Maybe a third branch becomes relevant for a week, or one dependency merges and no longer needs to be carried locally.

For example:

```bash
metabranch add approval-notification-copy
metabranch rebase master

metabranch remove approval-notification-copy
metabranch rebase master
```

That keeps the integration branch aligned with the current coordination problem instead of treating the branch set as permanent.

### 5. Make small fixes without losing branch ownership

If testing the integrated flow reveals one fix for the workflow team and one fix for the UI team, those fixes can be developed from the metabranch with `metabranch commit` and routed back to the correct owning branches right away.

Teams can also use normal `git commit` on the metabranch if they are confident each commit belongs to one branch and want to sort that out during reconciliation later.
Either way, the goal is the same: keep day-to-day coordination easy without turning the integration branch into the place where permanent history accumulates.

### 6. Drop the metabranch when the temporary integration is no longer needed

Once the teams have validated the interaction and landed the reviewed branches appropriately, the metabranch can disappear.

That is expected.
Its job was to make temporary integration cheap.

## Expected Safety Rules

Metabranch is only useful if it protects users from the failure modes that temporary integration branches usually create.

The current rules are:

- `metabranch rebase` refuses to run if there are local changes.
- `.metabranch.yaml` changes are tool-owned and committed on their own.
- Direct `git commit` on a metabranch is allowed only if those commits can be cleanly assigned to underlying branches during reconciliation or intentionally dropped.
- If Metabranch cannot safely determine where a metabranch-only commit belongs, it opens a reconciliation flow instead of discarding it.
- If a rebuild or reconciliation conflicts, use `metabranch resume` or `metabranch abort` rather than trying to restart from scratch.

## Reconciliation Model

Sometimes a metabranch may contain commits that are not obviously attributable to one of its underlying branches.

The workflow is:

- Metabranch detects unmatched commits during reconstruction.
- It opens an editor, similar to `git rebase -i`.
- Each line shows a commit and asks the user to replace the directive with the target sub-branch name, or `d` to drop it.
- Metabranch then replays those commits onto the chosen branches and reconstructs the metabranch.

Initial attribution can use commit-message comparison as a heuristic, but the user-edited plan is the source of truth.

## Mental Model

If you remember only one thing, remember this:

Metabranch is for **assembling** branches, not for **owning** work.

The underlying branches carry the real history.
The metabranch exists so humans and CI can see the combined result before merge.
