micropython-branch-manager 2.0.3__tar.gz → 2.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. micropython_branch_manager-2.1.1/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a +0 -0
  2. micropython_branch_manager-2.1.1/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a.body +0 -0
  3. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/.cache/pip/http-v2/a/d/e/e/f/adeef5b9611702687bbddade5f7c0af65110caf98ba1a0070c9d116e +0 -0
  4. micropython_branch_manager-2.1.1/.cache/pip/http-v2/a/d/e/e/f/adeef5b9611702687bbddade5f7c0af65110caf98ba1a0070c9d116e.body +0 -0
  5. micropython_branch_manager-2.1.1/.cache/pip/http-v2/c/9/1/f/5/c91f580d88bf83782e78be42725d05696c3911af8667eb629ddc701e +0 -0
  6. micropython_branch_manager-2.0.3/.cache/pip/http-v2/d/8/7/1/b/d871bc55adf567ea2c49a2de02d30bfa1a66046fb62fe2b0b5e5e1b3.body → micropython_branch_manager-2.1.1/.cache/pip/http-v2/c/9/1/f/5/c91f580d88bf83782e78be42725d05696c3911af8667eb629ddc701e.body +9 -16
  7. micropython_branch_manager-2.1.1/.cache/pip/http-v2/f/9/a/b/3/f9ab31eb3a9dd78406ca10dd83c580077dd9d3c984a8877c762be2f6 +0 -0
  8. micropython_branch_manager-2.0.3/.cache/pip/http-v2/8/9/6/d/8/896d83792f0d707ff5cb57a45dc80e86c5aaf07748a5d39df95ef61c.body → micropython_branch_manager-2.1.1/.cache/pip/http-v2/f/9/a/b/3/f9ab31eb3a9dd78406ca10dd83c580077dd9d3c984a8877c762be2f6.body +0 -0
  9. micropython_branch_manager-2.1.1/.cache/pip/selfcheck/fe300af6f7d708c14827daac3afc81fbb8306b73de8dd6e3f1f8ea3b +1 -0
  10. micropython_branch_manager-2.0.3/README.md → micropython_branch_manager-2.1.1/PKG-INFO +48 -32
  11. micropython_branch_manager-2.0.3/PKG-INFO → micropython_branch_manager-2.1.1/README.md +32 -44
  12. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/pyproject.toml +6 -0
  13. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/_version.py +2 -2
  14. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/branch_sync.py +11 -0
  15. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/cli.py +28 -10
  16. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/git.py +25 -17
  17. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/pr.py +14 -13
  18. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/rebase.py +126 -40
  19. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/rerere.py +11 -4
  20. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/trailers.py +34 -9
  21. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/update_branch.py +1 -0
  22. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/tests/test_branch_sync.py +19 -0
  23. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/tests/test_git.py +41 -0
  24. micropython_branch_manager-2.1.1/tests/test_integration.py +314 -0
  25. micropython_branch_manager-2.1.1/tests/test_trailers.py +191 -0
  26. micropython_branch_manager-2.0.3/.cache/pip/http-v2/8/9/6/d/8/896d83792f0d707ff5cb57a45dc80e86c5aaf07748a5d39df95ef61c +0 -0
  27. micropython_branch_manager-2.0.3/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a +0 -0
  28. micropython_branch_manager-2.0.3/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a.body +0 -0
  29. micropython_branch_manager-2.0.3/.cache/pip/http-v2/a/d/e/e/f/adeef5b9611702687bbddade5f7c0af65110caf98ba1a0070c9d116e.body +0 -0
  30. micropython_branch_manager-2.0.3/.cache/pip/http-v2/d/8/7/1/b/d871bc55adf567ea2c49a2de02d30bfa1a66046fb62fe2b0b5e5e1b3 +0 -0
  31. micropython_branch_manager-2.0.3/.cache/pip/selfcheck/fe300af6f7d708c14827daac3afc81fbb8306b73de8dd6e3f1f8ea3b +0 -1
  32. micropython_branch_manager-2.0.3/tests/test_integration.py +0 -103
  33. micropython_branch_manager-2.0.3/tests/test_trailers.py +0 -85
  34. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/.gitignore +0 -0
  35. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/.gitlab-ci.yml +0 -0
  36. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/.pre-commit-config.yaml +0 -0
  37. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/.python-version +0 -0
  38. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/CLAUDE.md +0 -0
  39. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/LICENSE +0 -0
  40. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/docs/gitlab_mr_example.png +0 -0
  41. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/__init__.py +0 -0
  42. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/__main__.py +0 -0
  43. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/config.py +0 -0
  44. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/exceptions.py +0 -0
  45. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/src/micropython_branch_manager/github.py +0 -0
  46. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/tests/conftest.py +0 -0
  47. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/tests/test_config.py +0 -0
  48. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/tests/test_exceptions.py +0 -0
  49. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/tests/test_github.py +0 -0
  50. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.1}/tests/test_update_branch.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: uv
3
- Version: 0.11.1
3
+ Version: 0.12.17
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -36,13 +36,6 @@ Project-URL: Repository, https://github.com/astral-sh/uv
36
36
 
37
37
  # uv
38
38
 
39
- [![uv](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/uv/main/assets/badge/v0.json)](https://github.com/astral-sh/uv)
40
- [![image](https://img.shields.io/pypi/v/uv/0.11.1.svg)](https://pypi.python.org/pypi/uv)
41
- [![image](https://img.shields.io/pypi/l/uv/0.11.1.svg)](https://pypi.python.org/pypi/uv)
42
- [![image](https://img.shields.io/pypi/pyversions/uv/0.11.1.svg)](https://pypi.python.org/pypi/uv)
43
- [![Actions status](https://github.com/astral-sh/uv/actions/workflows/ci.yml/badge.svg)](https://github.com/astral-sh/uv/actions)
44
- [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.gg/astral-sh)
45
-
46
39
  An extremely fast Python package and project manager, written in Rust.
47
40
 
48
41
  <p align="center">
@@ -58,13 +51,13 @@ An extremely fast Python package and project manager, written in Rust.
58
51
  - A single tool to replace `pip`, `pip-tools`, `pipx`, `poetry`, `pyenv`, `twine`, `virtualenv`, and
59
52
  more.
60
53
  - [10-100x faster](https://github.com/astral-sh/uv/blob/main/BENCHMARKS.md) than `pip`.
61
- - Provides [comprehensive project management](https://github.com/astral-sh/uv/blob/0.11.1/README.md#projects), with a
54
+ - Provides [comprehensive project management](https://github.com/astral-sh/uv/blob/0.12.17/README.md#projects), with a
62
55
  [universal lockfile](https://docs.astral.sh/uv/concepts/projects/layout#the-lockfile).
63
- - [Runs scripts](https://github.com/astral-sh/uv/blob/0.11.1/README.md#scripts), with support for
56
+ - [Runs scripts](https://github.com/astral-sh/uv/blob/0.12.17/README.md#scripts), with support for
64
57
  [inline dependency metadata](https://docs.astral.sh/uv/guides/scripts#declaring-script-dependencies).
65
- - [Installs and manages](https://github.com/astral-sh/uv/blob/0.11.1/README.md#python-versions) Python versions.
66
- - [Runs and installs](https://github.com/astral-sh/uv/blob/0.11.1/README.md#tools) tools published as Python packages.
67
- - Includes a [pip-compatible interface](https://github.com/astral-sh/uv/blob/0.11.1/README.md#the-pip-interface) for a performance boost with a familiar
58
+ - [Installs and manages](https://github.com/astral-sh/uv/blob/0.12.17/README.md#python-versions) Python versions.
59
+ - [Runs and installs](https://github.com/astral-sh/uv/blob/0.12.17/README.md#tools) tools published as Python packages.
60
+ - Includes a [pip-compatible interface](https://github.com/astral-sh/uv/blob/0.12.17/README.md#the-pip-interface) for a performance boost with a familiar
68
61
  CLI.
69
62
  - Supports Cargo-style [workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces) for
70
63
  scalable projects.
@@ -315,7 +308,7 @@ It's pronounced as "you - vee" ([`/juː viː/`](https://en.wikipedia.org/wiki/He
315
308
 
316
309
  #### How should I stylize uv?
317
310
 
318
- Just "uv", please. See the [style guide](https://github.com/astral-sh/uv/blob/0.11.1/STYLE.md#styling-uv) for details.
311
+ Just "uv", please. See the [style guide](https://github.com/astral-sh/uv/blob/0.12.17/STYLE.md#styling-uv) for details.
319
312
 
320
313
  #### What platforms does uv support?
321
314
 
@@ -344,9 +337,9 @@ for Windows support.
344
337
 
345
338
  uv is licensed under either of
346
339
 
347
- - Apache License, Version 2.0, ([LICENSE-APACHE](https://github.com/astral-sh/uv/blob/0.11.1/LICENSE-APACHE) or
340
+ - Apache License, Version 2.0, ([LICENSE-APACHE](https://github.com/astral-sh/uv/blob/0.12.17/LICENSE-APACHE) or
348
341
  <https://www.apache.org/licenses/LICENSE-2.0>)
349
- - MIT license ([LICENSE-MIT](https://github.com/astral-sh/uv/blob/0.11.1/LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
342
+ - MIT license ([LICENSE-MIT](https://github.com/astral-sh/uv/blob/0.12.17/LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
350
343
 
351
344
  at your option.
352
345
 
@@ -0,0 +1 @@
1
+ {"key":"/usr/local","last_check":"2026-09-22T00:08:41.848944+00:00","pypi_version":"26.2.1"}
@@ -1,3 +1,19 @@
1
+ Metadata-Version: 2.5
2
+ Name: micropython-branch-manager
3
+ Version: 2.1.1
4
+ Summary: Tool for managing MicroPython fork integration branches
5
+ Project-URL: Homepage, https://gitlab.com/alelec/micropython-branch-manager
6
+ Project-URL: Repository, https://gitlab.com/alelec/micropython-branch-manager.git
7
+ Project-URL: Issues, https://gitlab.com/alelec/micropython-branch-manager/-/issues
8
+ Author-email: Andrew Leech <andrew@alelec.net>
9
+ License-Expression: MIT
10
+ License-File: LICENSE
11
+ Requires-Python: >=3.11
12
+ Requires-Dist: cyclopts>=3.0
13
+ Requires-Dist: pydantic>=2.0
14
+ Requires-Dist: tomli-w>=1.0
15
+ Description-Content-Type: text/markdown
16
+
1
17
  # micropython-branch-manager
2
18
 
3
19
  Tool for managing MicroPython fork integration branches.
@@ -8,7 +24,7 @@ Tool for managing MicroPython fork integration branches.
8
24
 
9
25
  - Rebasing integration branches onto upstream with preserved merge commits
10
26
  - Adding PRs from upstream via merge commits
11
- - Tracking branch metadata in versioned JSON config
27
+ - Tracking branch metadata in a versioned TOML config
12
28
  - Safety checks for remote divergence
13
29
  - Generating GitLab MR URLs
14
30
 
@@ -46,12 +62,16 @@ mbm init --integration-branch main
46
62
 
47
63
  ### Rebase integration branch
48
64
 
49
- Rebase the integration branch and all feature branches onto upstream:
65
+ Rebuild the integration branch from upstream and every configured PR that is still open or closed:
50
66
 
51
67
  ```bash
52
68
  mbm rebase upstream/master
53
69
  ```
54
70
 
71
+ Before training rerere or changing branches, mbm asks GitHub for each configured PR's state. A PR reported as merged is skipped: mbm does not fetch, rebase, merge, or push its feature branch. The command prints the skipped branch and PR number.
72
+
73
+ This check requires an installed and authenticated GitHub CLI (`gh`). If GitHub cannot be queried, mbm prints a warning and retains the previous behavior of processing every configured branch.
74
+
55
75
  Options:
56
76
  - `--local` - Skip fetch and push operations
57
77
  - `--dry-run` - Show what would happen without making changes
@@ -78,12 +98,14 @@ The command will:
78
98
 
79
99
  ### Sync configuration
80
100
 
81
- Update JSON config to match current merge commit history. Requires a GitHub username to look up PR metadata:
101
+ Update the TOML config from current merge commit history and look up PR metadata:
82
102
 
83
103
  ```bash
84
104
  mbm sync <github-username>
85
105
  ```
86
106
 
107
+ When GitHub reports that a configured PR is merged, `sync` prints an informational message even if the branch no longer appears in integration merge history. It does not remove the entry from `mbm.toml`; retain or remove it deliberately, for example when a downstream pin must remain during a staged rollout.
108
+
87
109
  ### Show configuration
88
110
 
89
111
  Display current configuration:
@@ -111,7 +133,7 @@ mbm init
111
133
  # - gitlab: git@gitlab.example.com:yourname/micropython.git
112
134
  # - upstream: https://github.com/micropython/micropython.git
113
135
  #
114
- # Created .micropython-branches.json
136
+ # Config: /path/to/mbm.toml
115
137
  ```
116
138
 
117
139
  ### Adding PRs to the integration branch
@@ -186,34 +208,26 @@ The MR creation URL pre-fills the title and description with all integrated bran
186
208
 
187
209
  ## Configuration File
188
210
 
189
- The configuration file `.micropython-branches.json` is auto-generated by `mbm init` and updated by other commands. It should not be edited manually.
211
+ `mbm.toml` records the integration branch and the PRs that mbm manages. `mbm init` creates it; `mbm add-pr` and `mbm sync` update it. In parent-repository mode it belongs beside the MicroPython submodule and is intended to be committed with that repository.
190
212
 
191
213
  Example configuration:
192
214
 
193
- ```json
194
- {
195
- "_generated_by": "micropython-branch-manager (mbm) - do not edit manually",
196
- "integration_branch": "main",
197
- "remotes": {
198
- "upstream": "https://github.com/micropython/micropython.git",
199
- "andrewleech": "https://github.com/andrewleech/micropython.git"
200
- },
201
- "branches": [
202
- {
203
- "name": "feature-branch-1",
204
- "remote": "andrewleech",
205
- "pr_url": "https://github.com/micropython/micropython/pull/12345",
206
- "pr_number": 12345,
207
- "title": "Add feature X"
208
- }
209
- ]
210
- }
215
+ ```toml
216
+ # micropython-branch-manager (mbm)
217
+ path = "src/micropython"
218
+ integration_branch = "main"
219
+
220
+ [[branches]]
221
+ name = "feature-branch-1"
222
+ pr_url = "https://github.com/micropython/micropython/pull/12345"
223
+ pr_number = 12345
224
+ title = "Add feature X"
211
225
  ```
212
226
 
213
227
  Fields:
214
- - `integration_branch` - The branch that integrates all feature branches
215
- - `remotes` - Git remotes used by the tool (auto-detected from git config)
216
- - `branches` - List of integrated feature branches with their metadata
228
+ - `path` - Path to the MicroPython submodule, relative to `mbm.toml`
229
+ - `integration_branch` - The branch rebuilt from upstream and configured PRs
230
+ - `branches` - Ordered feature branches and their PR metadata
217
231
 
218
232
  ## Workflow
219
233
 
@@ -264,14 +278,16 @@ Each feature branch forks directly from `upstream/master`, and all merges flow i
264
278
  ### Typical rebase workflow
265
279
 
266
280
  1. Fetch latest from all remotes
267
- 2. Create update branch from target (e.g., `upstream/master`)
268
- 3. For each PR in config order:
281
+ 2. Query GitHub for the state of each configured PR
282
+ 3. Skip PRs already merged upstream
283
+ 4. Create update branch from target (e.g., `upstream/master`)
284
+ 5. For each remaining PR in config order:
269
285
  - Fetch fresh PR content from upstream
270
286
  - Rebase onto target
271
287
  - Merge into update branch
272
- 4. Push feature branches to their GitHub forks
273
- 5. Push update branch to GitLab
274
- 6. Display GitLab MR URL for review
288
+ 6. Push remaining feature branches to their GitHub forks
289
+ 7. Push update branch to GitLab
290
+ 8. Display GitLab MR URL for review
275
291
 
276
292
  ### Push strategy
277
293
 
@@ -353,7 +369,7 @@ uv run pre-commit run --all-files
353
369
 
354
370
  - Python 3.11+
355
371
  - Git
356
- - GitHub CLI (`gh`) - for PR metadata lookup
372
+ - GitHub CLI (`gh`) - for PR metadata lookup and automatic merged-PR skipping
357
373
  - Install: https://cli.github.com/
358
374
  - Authenticate: `gh auth login`
359
375
 
@@ -1,15 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: micropython-branch-manager
3
- Version: 2.0.3
4
- Summary: Tool for managing MicroPython fork integration branches
5
- License-Expression: MIT
6
- License-File: LICENSE
7
- Requires-Python: >=3.11
8
- Requires-Dist: cyclopts>=3.0
9
- Requires-Dist: pydantic>=2.0
10
- Requires-Dist: tomli-w>=1.0
11
- Description-Content-Type: text/markdown
12
-
13
1
  # micropython-branch-manager
14
2
 
15
3
  Tool for managing MicroPython fork integration branches.
@@ -20,7 +8,7 @@ Tool for managing MicroPython fork integration branches.
20
8
 
21
9
  - Rebasing integration branches onto upstream with preserved merge commits
22
10
  - Adding PRs from upstream via merge commits
23
- - Tracking branch metadata in versioned JSON config
11
+ - Tracking branch metadata in a versioned TOML config
24
12
  - Safety checks for remote divergence
25
13
  - Generating GitLab MR URLs
26
14
 
@@ -58,12 +46,16 @@ mbm init --integration-branch main
58
46
 
59
47
  ### Rebase integration branch
60
48
 
61
- Rebase the integration branch and all feature branches onto upstream:
49
+ Rebuild the integration branch from upstream and every configured PR that is still open or closed:
62
50
 
63
51
  ```bash
64
52
  mbm rebase upstream/master
65
53
  ```
66
54
 
55
+ Before training rerere or changing branches, mbm asks GitHub for each configured PR's state. A PR reported as merged is skipped: mbm does not fetch, rebase, merge, or push its feature branch. The command prints the skipped branch and PR number.
56
+
57
+ This check requires an installed and authenticated GitHub CLI (`gh`). If GitHub cannot be queried, mbm prints a warning and retains the previous behavior of processing every configured branch.
58
+
67
59
  Options:
68
60
  - `--local` - Skip fetch and push operations
69
61
  - `--dry-run` - Show what would happen without making changes
@@ -90,12 +82,14 @@ The command will:
90
82
 
91
83
  ### Sync configuration
92
84
 
93
- Update JSON config to match current merge commit history. Requires a GitHub username to look up PR metadata:
85
+ Update the TOML config from current merge commit history and look up PR metadata:
94
86
 
95
87
  ```bash
96
88
  mbm sync <github-username>
97
89
  ```
98
90
 
91
+ When GitHub reports that a configured PR is merged, `sync` prints an informational message even if the branch no longer appears in integration merge history. It does not remove the entry from `mbm.toml`; retain or remove it deliberately, for example when a downstream pin must remain during a staged rollout.
92
+
99
93
  ### Show configuration
100
94
 
101
95
  Display current configuration:
@@ -123,7 +117,7 @@ mbm init
123
117
  # - gitlab: git@gitlab.example.com:yourname/micropython.git
124
118
  # - upstream: https://github.com/micropython/micropython.git
125
119
  #
126
- # Created .micropython-branches.json
120
+ # Config: /path/to/mbm.toml
127
121
  ```
128
122
 
129
123
  ### Adding PRs to the integration branch
@@ -198,34 +192,26 @@ The MR creation URL pre-fills the title and description with all integrated bran
198
192
 
199
193
  ## Configuration File
200
194
 
201
- The configuration file `.micropython-branches.json` is auto-generated by `mbm init` and updated by other commands. It should not be edited manually.
195
+ `mbm.toml` records the integration branch and the PRs that mbm manages. `mbm init` creates it; `mbm add-pr` and `mbm sync` update it. In parent-repository mode it belongs beside the MicroPython submodule and is intended to be committed with that repository.
202
196
 
203
197
  Example configuration:
204
198
 
205
- ```json
206
- {
207
- "_generated_by": "micropython-branch-manager (mbm) - do not edit manually",
208
- "integration_branch": "main",
209
- "remotes": {
210
- "upstream": "https://github.com/micropython/micropython.git",
211
- "andrewleech": "https://github.com/andrewleech/micropython.git"
212
- },
213
- "branches": [
214
- {
215
- "name": "feature-branch-1",
216
- "remote": "andrewleech",
217
- "pr_url": "https://github.com/micropython/micropython/pull/12345",
218
- "pr_number": 12345,
219
- "title": "Add feature X"
220
- }
221
- ]
222
- }
199
+ ```toml
200
+ # micropython-branch-manager (mbm)
201
+ path = "src/micropython"
202
+ integration_branch = "main"
203
+
204
+ [[branches]]
205
+ name = "feature-branch-1"
206
+ pr_url = "https://github.com/micropython/micropython/pull/12345"
207
+ pr_number = 12345
208
+ title = "Add feature X"
223
209
  ```
224
210
 
225
211
  Fields:
226
- - `integration_branch` - The branch that integrates all feature branches
227
- - `remotes` - Git remotes used by the tool (auto-detected from git config)
228
- - `branches` - List of integrated feature branches with their metadata
212
+ - `path` - Path to the MicroPython submodule, relative to `mbm.toml`
213
+ - `integration_branch` - The branch rebuilt from upstream and configured PRs
214
+ - `branches` - Ordered feature branches and their PR metadata
229
215
 
230
216
  ## Workflow
231
217
 
@@ -276,14 +262,16 @@ Each feature branch forks directly from `upstream/master`, and all merges flow i
276
262
  ### Typical rebase workflow
277
263
 
278
264
  1. Fetch latest from all remotes
279
- 2. Create update branch from target (e.g., `upstream/master`)
280
- 3. For each PR in config order:
265
+ 2. Query GitHub for the state of each configured PR
266
+ 3. Skip PRs already merged upstream
267
+ 4. Create update branch from target (e.g., `upstream/master`)
268
+ 5. For each remaining PR in config order:
281
269
  - Fetch fresh PR content from upstream
282
270
  - Rebase onto target
283
271
  - Merge into update branch
284
- 4. Push feature branches to their GitHub forks
285
- 5. Push update branch to GitLab
286
- 6. Display GitLab MR URL for review
272
+ 6. Push remaining feature branches to their GitHub forks
273
+ 7. Push update branch to GitLab
274
+ 8. Display GitLab MR URL for review
287
275
 
288
276
  ### Push strategy
289
277
 
@@ -365,7 +353,7 @@ uv run pre-commit run --all-files
365
353
 
366
354
  - Python 3.11+
367
355
  - Git
368
- - GitHub CLI (`gh`) - for PR metadata lookup
356
+ - GitHub CLI (`gh`) - for PR metadata lookup and automatic merged-PR skipping
369
357
  - Install: https://cli.github.com/
370
358
  - Authenticate: `gh auth login`
371
359
 
@@ -2,6 +2,7 @@
2
2
  name = "micropython-branch-manager"
3
3
  dynamic = ["version"]
4
4
  description = "Tool for managing MicroPython fork integration branches"
5
+ authors = [{ name = "Andrew Leech", email = "andrew@alelec.net" }]
5
6
  readme = "README.md"
6
7
  license = "MIT"
7
8
  requires-python = ">=3.11"
@@ -11,6 +12,11 @@ dependencies = [
11
12
  "tomli-w>=1.0",
12
13
  ]
13
14
 
15
+ [project.urls]
16
+ Homepage = "https://gitlab.com/alelec/micropython-branch-manager"
17
+ Repository = "https://gitlab.com/alelec/micropython-branch-manager.git"
18
+ Issues = "https://gitlab.com/alelec/micropython-branch-manager/-/issues"
19
+
14
20
  [project.scripts]
15
21
  micropython-branch-manager = "micropython_branch_manager.cli:main"
16
22
  mbm = "micropython_branch_manager.cli:main"
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '2.0.3'
22
- __version_tuple__ = version_tuple = (2, 0, 3)
21
+ __version__ = version = '2.1.1'
22
+ __version_tuple__ = version_tuple = (2, 1, 1)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -66,6 +66,17 @@ def sync_config_with_branches(
66
66
  return modified
67
67
 
68
68
 
69
+ def find_merged_branches(
70
+ config: SubmoduleConfig, pr_cache: dict[str, PRInfo]
71
+ ) -> list[BranchConfig]:
72
+ """Return configured branches whose pull requests are merged upstream."""
73
+ return [
74
+ branch
75
+ for branch in config.branches
76
+ if (pr := pr_cache.get(branch.name)) is not None and pr.state == "MERGED"
77
+ ]
78
+
79
+
69
80
  def validate_branches(config: SubmoduleConfig, detected_branches: list[str]) -> list[str]:
70
81
  """Validate config branches against detected branches.
71
82
 
@@ -7,9 +7,13 @@ from typing import Annotated
7
7
 
8
8
  import cyclopts
9
9
 
10
- from .branch_sync import extract_branches_from_merges, sync_config_with_branches
10
+ from .branch_sync import (
11
+ extract_branches_from_merges,
12
+ find_merged_branches,
13
+ sync_config_with_branches,
14
+ )
11
15
  from .config import CONFIG_FILENAME, MBMConfig, SubmoduleConfig, find_config
12
- from .exceptions import GitHubCLINotFound, MBMError
16
+ from .exceptions import GitHubCLINotFound, GitHubError, MBMError
13
17
  from .git import GitRepo
14
18
  from .github import GitHubClient
15
19
  from .pr import PRManager
@@ -37,7 +41,7 @@ ConfigPathParam = Annotated[
37
41
  ),
38
42
  ]
39
43
 
40
- _SUBMODULE_CANDIDATES = [Path("src/micropython"), Path("micropython"), Path(".")]
44
+ _SUBMODULE_CANDIDATES = [Path("src/micropython"), Path("micropython")]
41
45
 
42
46
  _GITHUB_URL_RE = __import__("re").compile(
43
47
  r"(?:https?://github\.com/|git@github\.com:)([^/]+/[^/]+?)(?:\.git)?$"
@@ -239,8 +243,16 @@ def sync(
239
243
  raise ValueError("sync requires a config file. Run 'mbm init' first.")
240
244
  git = GitRepo(sub_path)
241
245
 
242
- # Detect branches
243
- detected = extract_branches_from_merges(git, "upstream/master", sub.integration_branch)
246
+ # Detect branches — resolve integration branch to a local or remote ref
247
+ integration_ref = git.resolve_branch_ref(sub.integration_branch)
248
+ if not integration_ref:
249
+ print(
250
+ f"WARNING: Integration branch '{sub.integration_branch}' not found locally "
251
+ "or as a remote ref. No branches detected."
252
+ )
253
+ detected = []
254
+ else:
255
+ detected = extract_branches_from_merges(git, "upstream/master", integration_ref)
244
256
 
245
257
  # Detect upstream GitHub repo from remote URL
246
258
  from .github import PRInfo
@@ -253,7 +265,7 @@ def sync(
253
265
  try:
254
266
  github = GitHubClient(repo=gh_repo) if gh_repo else GitHubClient()
255
267
  pr_cache = github.get_pr_info_by_branches(gh_author)
256
- except GitHubCLINotFound:
268
+ except (GitHubCLINotFound, GitHubError):
257
269
  pass
258
270
 
259
271
  # Look up individual branches not found in author cache
@@ -261,9 +273,15 @@ def sync(
261
273
  all_branches = set(detected) | {b.name for b in sub.branches}
262
274
  for branch_name in all_branches:
263
275
  if branch_name not in pr_cache:
264
- pr = github.find_pr_by_branch(branch_name)
265
- if pr:
266
- pr_cache[branch_name] = pr
276
+ try:
277
+ pr = github.find_pr_by_branch(branch_name)
278
+ if pr:
279
+ pr_cache[branch_name] = pr
280
+ except GitHubError:
281
+ pass
282
+
283
+ for branch in find_merged_branches(sub, pr_cache):
284
+ print(f"INFO: {branch.name}: PR #{pr_cache[branch.name].number} merged upstream")
267
285
 
268
286
  # Sync
269
287
  if sync_config_with_branches(sub, detected, pr_cache):
@@ -311,6 +329,6 @@ def main() -> None:
311
329
  """Entry point."""
312
330
  try:
313
331
  app()
314
- except MBMError as e:
332
+ except (MBMError, ValueError) as e:
315
333
  print(f"Error: {e}", file=sys.stderr)
316
334
  sys.exit(1)
@@ -63,20 +63,7 @@ class GitRepo:
63
63
  def fetch(self, remote: str = "--all") -> None:
64
64
  """Fetch from remote(s)."""
65
65
  self.run("submodule", "sync", "--recursive", check=False)
66
-
67
- cmd = ["git", "fetch", remote]
68
- result = subprocess.run(
69
- cmd,
70
- cwd=self.repo_path,
71
- capture_output=True,
72
- text=True,
73
- )
74
- if result.returncode != 0:
75
- stderr = result.stderr.lower()
76
- if "submodule" not in stderr or (
77
- "fatal" in stderr and "submodule" not in stderr.split("fatal")[0]
78
- ):
79
- raise GitError(f"git fetch failed: {result.stderr}")
66
+ self.run("fetch", remote)
80
67
 
81
68
  def checkout(self, ref: str, create: bool = False, force: bool = False) -> None:
82
69
  """Checkout branch or ref."""
@@ -126,18 +113,23 @@ class GitRepo:
126
113
 
127
114
  Returns True if rebase completed successfully.
128
115
  """
129
- for _ in range(100): # safety bound
116
+ rebase_merge = self.git_dir() / "rebase-merge"
117
+ for _ in range(100):
130
118
  if self._get_conflicting_files():
131
119
  return False
120
+ if not rebase_merge.exists():
121
+ return True
132
122
  try:
133
123
  self.run("rebase", "--continue")
134
124
  return True
135
125
  except GitError:
126
+ if self._get_conflicting_files():
127
+ return False
136
128
  continue
137
129
  return False
138
130
 
139
- def merge(self, branch: str, message: str, no_ff: bool = True) -> None:
140
- """Merge branch with optional no-ff.
131
+ def merge(self, branch: str, message: str, no_ff: bool = True, signoff: bool = False) -> None:
132
+ """Merge branch with optional no-ff and Signed-off-by trailer.
141
133
 
142
134
  Raises:
143
135
  MergeConflictError: If conflicts require manual resolution
@@ -145,6 +137,8 @@ class GitRepo:
145
137
  args = ["merge"]
146
138
  if no_ff:
147
139
  args.append("--no-ff")
140
+ if signoff:
141
+ args.append("--signoff")
148
142
  args.extend([branch, "-m", message])
149
143
 
150
144
  try:
@@ -188,6 +182,20 @@ class GitRepo:
188
182
  except GitError:
189
183
  return False
190
184
 
185
+ def resolve_branch_ref(self, branch: str) -> str | None:
186
+ """Resolve a branch name to a usable ref, checking local then remote tracking refs."""
187
+ if self.branch_exists(branch):
188
+ return branch
189
+ result = self.run(
190
+ "for-each-ref",
191
+ "--format=%(refname:short)",
192
+ f"refs/remotes/*/{branch}",
193
+ check=False,
194
+ )
195
+ if result:
196
+ return result.split("\n")[0]
197
+ return None
198
+
191
199
  def create_branch(self, name: str, start_point: str) -> None:
192
200
  """Create branch from start point."""
193
201
  self.run("branch", name, start_point)
@@ -74,30 +74,31 @@ class PRManager:
74
74
  print(f"Fetching PR #{pr_info.number} from upstream...")
75
75
  self._fetch_pr(pr_info)
76
76
 
77
- # Update config
78
- print("\nUpdating config...")
79
- self.sub.branches.append(
80
- BranchConfig(
81
- name=pr_info.branch,
82
- pr_url=pr_info.url,
83
- pr_number=pr_info.number,
84
- title=pr_info.title,
85
- )
86
- )
87
- self.mbm_config.save(self.config_path)
88
-
89
77
  # Merge into update branch with trailers
90
78
  merge_msg = format_merge_message(
91
79
  branch_name=pr_info.branch,
80
+ integration=update_branch,
92
81
  title=pr_info.title,
93
82
  pr_number=pr_info.number,
94
83
  pr_url=pr_info.url,
95
84
  author=pr_info.author,
96
85
  )
97
86
  print(f"Merging {pr_info.branch} into {update_branch}...")
98
- self.git.merge(pr_info.branch, merge_msg)
87
+ self.git.merge(pr_info.branch, merge_msg, signoff=True)
99
88
  print("Merge completed successfully")
100
89
 
90
+ # Update config only after merge succeeds
91
+ print("\nUpdating config...")
92
+ self.sub.branches.append(
93
+ BranchConfig(
94
+ name=pr_info.branch,
95
+ pr_url=pr_info.url,
96
+ pr_number=pr_info.number,
97
+ title=pr_info.title,
98
+ )
99
+ )
100
+ self.mbm_config.save(self.config_path)
101
+
101
102
  # Push update branch to GitLab
102
103
  gitlab_remote = find_gitlab_remote(self.git)
103
104
  if gitlab_remote: