Skip to main content

micropython-branch-manager

mbm keeps a fork of a GitHub project up to date when you're carrying a stack of unmerged PRs and local feature branches on top of upstream. It was written for MicroPython (hence the name) but works with any GitHub-hosted project.

The idea is simple. Your fork has an integration branch (say main or mimxrt) which is basically upstream plus one merge commit per feature branch. When upstream moves, mbm rebase throws that branch away and rebuilds it: each feature branch is rebased onto the new upstream on its own, then merged in, in the order listed in mbm.toml. You get a clean, readable history where every feature is still an isolated branch you can push back to its PR.

On top of that it:

  • adds new PRs by number, URL or branch name, creating a remote for the PR author's fork if needed
  • skips PRs that GitHub says have already been merged upstream
  • pushes rebased feature branches back to their owner's fork (never to upstream), and checks they haven't changed on the remote first
  • trains git rerere from your previous integration branch, so conflicts you've already resolved get resolved again automatically
  • builds the result on a separate <integration>_update branch and, if you use GitLab, gives you a pre-filled merge request link to review it

Install

uv tool install micropython-branch-manager

You'll also want the GitHub CLI (gh) installed and logged in (gh auth login). It's used for PR lookups; without it mbm still works but can't skip merged PRs or fill in PR details.

Quick start

mbm expects your remotes to look like this:

  • upstream: the GitHub project you're tracking, e.g. https://github.com/micropython/micropython.git
  • one remote per fork you push to, e.g. andrewleech or origin pointing at git@github.com:andrewleech/micropython.git
  • optionally a GitLab remote, if you review integration updates as GitLab MRs

If MicroPython is a submodule of your project, run init from the project root. It writes mbm.toml there (commit it with the project) and finds the submodule at src/micropython or micropython:

cd my-project
mbm init --submodule src/micropython --integration-branch main

If you're working directly in a fork clone, point it at the repo itself:

cd my-fork
mbm init --submodule . --integration-branch main

Then add some PRs and rebuild whenever upstream moves:

mbm add-pr 18333                  # by PR number
mbm add-pr https://github.com/micropython/micropython/pull/18229
mbm rebase                        # rebuild on the latest upstream

add-pr and rebase both leave your integration branch alone and do their work on <integration>_update. Check it over (or open the GitLab MR it prints), then either merge that MR or run mbm rebase --apply to move the integration branch across once the rebuild is clean.

Commands

mbm init

Creates mbm.toml in the current directory (or the --config path) and adds a submodule entry to it. If you don't pass --integration-branch it uses whatever branch the repo has checked out. It also turns on git rerere in that repo. Running it again with a different --submodule adds another entry, so one config can manage several submodules; the other commands pick one with --submodule/-s, or work it out from your current directory.

mbm add-pr <pr>

Adds a PR to the integration branch as a merge commit. The PR can be a number, a branch name or a full URL (it has to be a PR against the upstream repo).

It looks the PR up on GitHub, fetches it, merges it into <integration>_update and adds it to mbm.toml, including the PR author so later pushes go to the right fork. If a GitLab remote exists the update branch is pushed there and you get an MR link.

$ mbm add-pr 18333
Fetching PR info for: 18333
Found PR #18333: ports/mimxrt: Update nxp_driver to MCUX_2.16.100.
Branch: mcux_sdk_2.16
State: OPEN

Creating update branch from mimxrt...
Fetching PR #18333 from upstream...
Merging mcux_sdk_2.16 into mimxrt_update...
Merge completed successfully

Pushing mimxrt_update to gitlab...

=== PR ADDED SUCCESSFULLY ===
Create MR: https://gitlab.example.com/.../merge_requests/new?...

Adding a second PR builds on the same update branch, so you can queue up a few before reviewing.

mbm rebase

Rebuilds the integration branch on top of a new upstream:

mbm rebase                        # onto the default target
mbm rebase --target v1.27.0       # onto a particular ref, e.g. a release tag

The target is picked in this order: --target, then target in mbm.toml, then upstream's default branch (upstream/HEAD, which mbm sets up with git remote set-head upstream --auto if it's missing), and finally upstream/master.

Rebuilding onto a release tag works too. If the target is older than upstream's default branch, each PR branch (which is usually based on the latest upstream) is rebased with git rebase --onto <target> <upstream default>, so only the PR's own commits get moved onto the tag, not everything upstream did since.

Before touching anything it asks GitHub for the state of each PR and skips any that are merged, printing which ones. They stay in mbm.toml until you remove them.

Options:

  • --local skips fetching and pushing, handy for a dry run of conflicts
  • --dry-run shows what it'd do without changing anything
  • --apply moves the integration branch to <integration>_update once the run is clean (git checkout -B). Use this if you don't review through GitLab.
  • --no-update-branches only builds the update branch; your feature branch refs aren't moved, tracked or pushed. Useful if you've got feature branches checked out in other worktrees or just don't want them touched. --update-branches forces the opposite, and the default comes from update_feature_branches in the config.
  • --force-push pushes even if a feature branch on the remote has moved since you last fetched it (careful, you'll lose whatever's there)
  • --resume carries on after you've fixed a conflict (see below)

Here's what a rebuilt branch looks like, every feature is its own branch off the target, merged in order:

*   c1c523ebd7 - Merge branch 'mimxrt1176-alt11-pwm' (HEAD -> mimxrt_update)
|\
| * e16d226353 - mimxrt: Add ALT11 pin mode support for MIMXRT1176. (mimxrt1176-alt11-pwm)
* |   a288f37e95 - Merge branch 'adc'
|\ \
| * | 30aa89db5e - mimxrt/machine_adc: rt117x: Support channel groups. (adc)
| * | b045f8ae4f - mimxrt/machine_adc: rt117x: Initialize LPADC2.
* | |   6f84252a13 - Merge branch 'mimx_sdcard_timeouts'
|\ \ \
| * | | 623409093a - mimxrt/sdcard: Improve robustness of sdcard driver. (mimx_sdcard_timeouts)
| * | | 5dd59d4e07 - mimxrt/sdcard: Fix deadlock in sdcard_power_off.
* | | |   62c3ccf986 - Merge branch 'mimx_Flash_doc'
|\ \ \ \
| * | | | cad3bd124a - docs/mimxrt: Add docs for mimxrt.Flash. (mimx_Flash_doc)
| |/ / /
* | | |   864c580cfa - Merge branch 'dp83867-phy-driver'
|\ \ \ \
| * | | | 76fcf3ce95 - mimxrt/eth: Improve Dual Ethernet configuration. (dp83867-phy-driver)
| * | | | 7ad3bbaff7 - mimxrt/boards/MIMXRT1170_EVK: Remove obsolete pin defines.
| * | | | 6a70a07795 - mimxrt/eth: Add DP83867 PHY driver support.
| |/ / /
* | | |   03163eaaeb - Merge branch 'phyboard-rt1170'
|\ \ \ \
| * | | | 99d763bffb - mimxrt: Add PHYBOARD-RT1170 board support. (phyboard-rt1170)
| |/ / /
* | | |   345a5419a3 - Merge branch 'manifest_c_module'
|\ \ \ \
| * | | | 12b45387e5 - tools/ci: Add c_module() testing for RP2 and STM32. (manifest_c_module)
| * | | | ... (more commits)
* | | | |   b61786d615 - Merge branch 'mcux_sdk_2.16'
|\ \ \ \ \
| * | | | | 66be1ee6a8 - mimxrt/fsl_lpuart: Use wrapper for IRQ Idle support. (mcux_sdk_2.16)
| * | | | | 8c34a2df96 - ports/mimxrt: Update nxp_driver to MCUX_2.16.100.
|/ / / / /
* / / / / 78ff170de9 - all: Bump version to 1.27.0. (upstream/master, mimxrt)

mbm sync <github-user>

Brings mbm.toml back in line with what's actually on the integration branch. It reads the merge commits, adds any branches that are missing, and fills in PR number, URL, title and author by looking up PRs from <github-user> (plus any branch it can find a PR for). It's the easy way to start using mbm on a fork you've been maintaining by hand, or to backfill author in an older config.

If a PR in the config has been merged upstream, sync tells you but leaves the entry alone; take it out yourself when you're ready (sometimes you want to keep it pinned for a while).

mbm config

Prints the integration branch, configured branches and remotes.

Configuration

mbm.toml is written by init, add-pr and sync, but it's plain TOML and fine to edit by hand. Branch order matters, it's the merge order.

[[submodules]]
path = "src/micropython"
integration_branch = "main"
# target = "v1.27.0"
# update_feature_branches = false

[[submodules.branches]]
name = "mcux_sdk_2.16"
pr_url = "https://github.com/micropython/micropython/pull/18333"
pr_number = 18333
title = "ports/mimxrt: Update nxp_driver to MCUX_2.16.100."
author = "andrewleech"

[[submodules.branches]]
name = "my-local-hack"   # no PR, just a local branch

Per submodule:

  • path: the repo, relative to mbm.toml
  • integration_branch: the branch mbm rebuilds
  • target: default rebase target, if you don't want upstream's default branch
  • update_feature_branches: set to false to leave feature branch refs alone during rebase (default true)

Per branch:

  • name: local branch name, the only required field
  • pr_url, pr_number, title: the PR it belongs to, if any
  • author: GitHub owner of the fork the branch lives in, used to decide where to push it

Older configs with everything at the top level (no [[submodules]]) still load fine.

The GitHub repo used for PR lookups comes from the upstream remote. If there isn't a GitHub upstream remote it assumes micropython/micropython.

Pushing

With a normal (non --local) run, rebase pushes:

  • each feature branch to its owner's fork. That's the remote whose URL matches the branch's author; if author isn't set yet it's taken from the PR on GitHub, or from pr_url when that points at a fork. It'll never push to upstream or any other remote for the upstream repo. No matching remote means the branch is skipped with a warning.
  • the update branch to your GitLab remote, if you have one, along with an MR link that has the title and description filled in:

__omp_shell("GitLab MR Example")

Before force-pushing a feature branch it checks whether the remote copy has commits you don't have (someone else pushed, or you pushed from another machine). If so that branch is skipped and reported at the end, the rest still go.

Conflicts

If a rebase hits a conflict rerere can't fix, mbm stops, tells you which PR and files, and saves its progress to .git/mbm-rebase-state.json:

Rebase stopped due to conflicts while integrating PR #12345 (feature-branch).
Conflicting files:
  ports/stm32/main.c
  py/compile.c

Please resolve conflicts manually, then run:
  cd /path/to/micropython
  git rebase --continue

Then resume the integration:
  mbm rebase --resume

Fix it, git rebase --continue, then mbm rebase --resume and it picks up where it left off. rerere remembers your fix, so next time it'll be applied automatically.

Development

git clone https://gitlab.com/alelec/micropython-branch-manager.git
cd micropython-branch-manager
uv sync
uv run pytest
uv run pre-commit install        # ruff + mypy on commit
uv run pre-commit run --all-files

Versions come from git tags via hatch-vcs. Pushing a tag like v2.2.0 gets CI to publish it to PyPI; in between you'll see dev versions like 2.1.2.dev3+g1b5fe36.

License

MIT

Metadata

Release files for micropython-branch-manager 3.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for micropython-branch-manager 3.0.1
File Size Uploaded
micropython_branch_manager-3.0.1.tar.gz 41.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for micropython-branch-manager 3.0.1
File Interpreter ABI Platform
micropython_branch_manager-3.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 41.7 MB

Release files / micropython_branch_manager-3.0.1.tar.gz

Download URL micropython_branch_manager-3.0.1.tar.gz
Size 41.6 MB
Tags Source
SHA-256 checksum
How to use checksums
a4844beef8a7a9bd1abd53ebb6a560d971e0fb2ac78235fbbf6639b2aa409bd7
BLAKE2b-256 checksum
How to use checksums
ed5095b35d14171e6f9d057815a9b4178b45c1060cefca4460e2f1b494d83d29
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / micropython_branch_manager-3.0.1-py3-none-any.whl

Download URL micropython_branch_manager-3.0.1-py3-none-any.whl
Size 33.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
61e25e2f4fde985eea122152fed9c6996cff8afc65a2b5afe34ef71c4e8f4788
BLAKE2b-256 checksum
How to use checksums
5888a1fec13aee5f88ee140a2ac0af312d6e7be806cb7e95a2dee2dfdd11ced9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.19 {"installer":{"name":"uv","version":"0.12.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"13","id":"trixie","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

3.0.1 This release

2 release files

2.1.1

2 release files

2.1.0

2 release files

2.0.3

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page