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 rererefrom your previous integration branch, so conflicts you've already resolved get resolved again automatically - builds the result on a separate
<integration>_updatebranch 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.
andrewleechororiginpointing atgit@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:
--localskips fetching and pushing, handy for a dry run of conflicts--dry-runshows what it'd do without changing anything--applymoves the integration branch to<integration>_updateonce the run is clean (git checkout -B). Use this if you don't review through GitLab.--no-update-branchesonly 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-branchesforces the opposite, and the default comes fromupdate_feature_branchesin the config.--force-pushpushes even if a feature branch on the remote has moved since you last fetched it (careful, you'll lose whatever's there)--resumecarries 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 tombm.tomlintegration_branch: the branchmbmrebuildstarget: default rebase target, if you don't want upstream's default branchupdate_feature_branches: set tofalseto leave feature branch refs alone duringrebase(defaulttrue)
Per branch:
name: local branch name, the only required fieldpr_url,pr_number,title: the PR it belongs to, if anyauthor: 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; ifauthorisn't set yet it's taken from the PR on GitHub, or frompr_urlwhen that points at a fork. It'll never push toupstreamor 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)
| File | Size | Uploaded | |
|---|---|---|---|
| micropython_branch_manager-3.0.1.tar.gz | 41.6 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|