micropython-branch-manager 2.0.3__tar.gz → 2.1.0__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 (53) hide show
  1. micropython_branch_manager-2.1.0/.cache/pip/http-v2/6/5/f/8/d/65f8dd134ec79390f6fba2e4328eff4d119f25775d7591b491591c4c +0 -0
  2. micropython_branch_manager-2.0.3/.cache/pip/http-v2/d/8/7/1/b/d871bc55adf567ea2c49a2de02d30bfa1a66046fb62fe2b0b5e5e1b3.body → micropython_branch_manager-2.1.0/.cache/pip/http-v2/6/5/f/8/d/65f8dd134ec79390f6fba2e4328eff4d119f25775d7591b491591c4c.body +12 -12
  3. micropython_branch_manager-2.1.0/.cache/pip/http-v2/7/d/0/2/4/7d024bf0e20d47587f77acc460902dbf3d99051a52119f3e3505b7ae +0 -0
  4. micropython_branch_manager-2.1.0/.cache/pip/http-v2/7/d/0/2/4/7d024bf0e20d47587f77acc460902dbf3d99051a52119f3e3505b7ae.body +0 -0
  5. micropython_branch_manager-2.1.0/.cache/pip/http-v2/a/0/c/7/e/a0c7eeba5b18e695eb825961b9f96de9ffb9cda5921f4bffc7f32c58 +0 -0
  6. micropython_branch_manager-2.0.3/.cache/pip/http-v2/8/9/6/d/8/896d83792f0d707ff5cb57a45dc80e86c5aaf07748a5d39df95ef61c.body → micropython_branch_manager-2.1.0/.cache/pip/http-v2/a/0/c/7/e/a0c7eeba5b18e695eb825961b9f96de9ffb9cda5921f4bffc7f32c58.body +0 -0
  7. micropython_branch_manager-2.0.3/.cache/pip/http-v2/a/d/e/e/f/adeef5b9611702687bbddade5f7c0af65110caf98ba1a0070c9d116e → micropython_branch_manager-2.1.0/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a +0 -0
  8. micropython_branch_manager-2.1.0/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a.body +0 -0
  9. micropython_branch_manager-2.1.0/.cache/pip/http-v2/a/d/e/e/f/adeef5b9611702687bbddade5f7c0af65110caf98ba1a0070c9d116e +0 -0
  10. micropython_branch_manager-2.1.0/.cache/pip/http-v2/a/d/e/e/f/adeef5b9611702687bbddade5f7c0af65110caf98ba1a0070c9d116e.body +0 -0
  11. micropython_branch_manager-2.1.0/.cache/pip/http-v2/e/1/0/a/2/e10a2bbfb31a7de3b9b7a66daabe582666ce7da5b088fe0ee85cdd9b +0 -0
  12. micropython_branch_manager-2.1.0/.cache/pip/http-v2/e/1/0/a/2/e10a2bbfb31a7de3b9b7a66daabe582666ce7da5b088fe0ee85cdd9b.body +362 -0
  13. micropython_branch_manager-2.1.0/.cache/pip/selfcheck/fe300af6f7d708c14827daac3afc81fbb8306b73de8dd6e3f1f8ea3b +1 -0
  14. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/PKG-INFO +5 -1
  15. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/pyproject.toml +6 -0
  16. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/_version.py +2 -2
  17. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/git.py +4 -2
  18. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/pr.py +2 -1
  19. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/rebase.py +2 -1
  20. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/rerere.py +11 -3
  21. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/trailers.py +34 -9
  22. micropython_branch_manager-2.1.0/tests/test_trailers.py +191 -0
  23. micropython_branch_manager-2.0.3/.cache/pip/http-v2/8/9/6/d/8/896d83792f0d707ff5cb57a45dc80e86c5aaf07748a5d39df95ef61c +0 -0
  24. micropython_branch_manager-2.0.3/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a +0 -0
  25. micropython_branch_manager-2.0.3/.cache/pip/http-v2/a/1/9/5/3/a19537d3cf37c122db841d6fe4cd322bc10d1a558bb00d146b85cb9a.body +0 -0
  26. micropython_branch_manager-2.0.3/.cache/pip/http-v2/a/d/e/e/f/adeef5b9611702687bbddade5f7c0af65110caf98ba1a0070c9d116e.body +0 -0
  27. micropython_branch_manager-2.0.3/.cache/pip/http-v2/d/8/7/1/b/d871bc55adf567ea2c49a2de02d30bfa1a66046fb62fe2b0b5e5e1b3 +0 -0
  28. micropython_branch_manager-2.0.3/.cache/pip/selfcheck/fe300af6f7d708c14827daac3afc81fbb8306b73de8dd6e3f1f8ea3b +0 -1
  29. micropython_branch_manager-2.0.3/tests/test_trailers.py +0 -85
  30. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/.gitignore +0 -0
  31. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/.gitlab-ci.yml +0 -0
  32. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/.pre-commit-config.yaml +0 -0
  33. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/.python-version +0 -0
  34. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/CLAUDE.md +0 -0
  35. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/LICENSE +0 -0
  36. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/README.md +0 -0
  37. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/docs/gitlab_mr_example.png +0 -0
  38. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/__init__.py +0 -0
  39. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/__main__.py +0 -0
  40. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/branch_sync.py +0 -0
  41. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/cli.py +0 -0
  42. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/config.py +0 -0
  43. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/exceptions.py +0 -0
  44. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/github.py +0 -0
  45. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/src/micropython_branch_manager/update_branch.py +0 -0
  46. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/tests/conftest.py +0 -0
  47. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/tests/test_branch_sync.py +0 -0
  48. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/tests/test_config.py +0 -0
  49. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/tests/test_exceptions.py +0 -0
  50. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/tests/test_git.py +0 -0
  51. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/tests/test_github.py +0 -0
  52. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/tests/test_integration.py +0 -0
  53. {micropython_branch_manager-2.0.3 → micropython_branch_manager-2.1.0}/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.11.23
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -37,9 +37,9 @@ Project-URL: Repository, https://github.com/astral-sh/uv
37
37
  # uv
38
38
 
39
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)
40
+ [![image](https://img.shields.io/pypi/v/uv/0.11.23.svg)](https://pypi.python.org/pypi/uv)
41
+ [![image](https://img.shields.io/pypi/l/uv/0.11.23.svg)](https://pypi.python.org/pypi/uv)
42
+ [![image](https://img.shields.io/pypi/pyversions/uv/0.11.23.svg)](https://pypi.python.org/pypi/uv)
43
43
  [![Actions status](https://github.com/astral-sh/uv/actions/workflows/ci.yml/badge.svg)](https://github.com/astral-sh/uv/actions)
44
44
  [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?logo=discord&logoColor=white)](https://discord.gg/astral-sh)
45
45
 
@@ -58,13 +58,13 @@ An extremely fast Python package and project manager, written in Rust.
58
58
  - A single tool to replace `pip`, `pip-tools`, `pipx`, `poetry`, `pyenv`, `twine`, `virtualenv`, and
59
59
  more.
60
60
  - [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
61
+ - Provides [comprehensive project management](https://github.com/astral-sh/uv/blob/0.11.23/README.md#projects), with a
62
62
  [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
63
+ - [Runs scripts](https://github.com/astral-sh/uv/blob/0.11.23/README.md#scripts), with support for
64
64
  [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
65
+ - [Installs and manages](https://github.com/astral-sh/uv/blob/0.11.23/README.md#python-versions) Python versions.
66
+ - [Runs and installs](https://github.com/astral-sh/uv/blob/0.11.23/README.md#tools) tools published as Python packages.
67
+ - Includes a [pip-compatible interface](https://github.com/astral-sh/uv/blob/0.11.23/README.md#the-pip-interface) for a performance boost with a familiar
68
68
  CLI.
69
69
  - Supports Cargo-style [workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces) for
70
70
  scalable projects.
@@ -315,7 +315,7 @@ It's pronounced as "you - vee" ([`/juː viː/`](https://en.wikipedia.org/wiki/He
315
315
 
316
316
  #### How should I stylize uv?
317
317
 
318
- Just "uv", please. See the [style guide](https://github.com/astral-sh/uv/blob/0.11.1/STYLE.md#styling-uv) for details.
318
+ Just "uv", please. See the [style guide](https://github.com/astral-sh/uv/blob/0.11.23/STYLE.md#styling-uv) for details.
319
319
 
320
320
  #### What platforms does uv support?
321
321
 
@@ -344,9 +344,9 @@ for Windows support.
344
344
 
345
345
  uv is licensed under either of
346
346
 
347
- - Apache License, Version 2.0, ([LICENSE-APACHE](https://github.com/astral-sh/uv/blob/0.11.1/LICENSE-APACHE) or
347
+ - Apache License, Version 2.0, ([LICENSE-APACHE](https://github.com/astral-sh/uv/blob/0.11.23/LICENSE-APACHE) or
348
348
  <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>)
349
+ - MIT license ([LICENSE-MIT](https://github.com/astral-sh/uv/blob/0.11.23/LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
350
350
 
351
351
  at your option.
352
352
 
@@ -0,0 +1,362 @@
1
+ Metadata-Version: 2.4
2
+ Name: uv
3
+ Version: 0.11.24
4
+ Classifier: Development Status :: 5 - Production/Stable
5
+ Classifier: Environment :: Console
6
+ Classifier: Intended Audience :: Developers
7
+ Classifier: Operating System :: OS Independent
8
+ Classifier: Programming Language :: Python
9
+ Classifier: Programming Language :: Python :: 3.8
10
+ Classifier: Programming Language :: Python :: 3.9
11
+ Classifier: Programming Language :: Python :: 3.10
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Programming Language :: Python :: 3.14
16
+ Classifier: Programming Language :: Python :: 3.15
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Rust
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ License-File: LICENSE-APACHE
23
+ License-File: LICENSE-MIT
24
+ Summary: An extremely fast Python package and project manager, written in Rust.
25
+ Keywords: uv,requirements,packaging
26
+ Home-Page: https://pypi.org/project/uv/
27
+ Author-email: "Astral Software Inc." <hey@astral.sh>
28
+ License-Expression: MIT OR Apache-2.0
29
+ Requires-Python: >=3.8
30
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
31
+ Project-URL: Changelog, https://github.com/astral-sh/uv/blob/main/CHANGELOG.md
32
+ Project-URL: Discord, https://discord.gg/astral-sh
33
+ Project-URL: Documentation, https://docs.astral.sh/uv
34
+ Project-URL: Releases, https://github.com/astral-sh/uv/releases
35
+ Project-URL: Repository, https://github.com/astral-sh/uv
36
+
37
+ # uv
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.24.svg)](https://pypi.python.org/pypi/uv)
41
+ [![image](https://img.shields.io/pypi/l/uv/0.11.24.svg)](https://pypi.python.org/pypi/uv)
42
+ [![image](https://img.shields.io/pypi/pyversions/uv/0.11.24.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
+ An extremely fast Python package and project manager, written in Rust.
47
+
48
+ <p align="center">
49
+ <img alt="Shows a bar chart with benchmark results." src="https://github.com/astral-sh/uv/assets/1309177/629e59c0-9c6e-4013-9ad4-adb2bcf5080d">
50
+ </p>
51
+
52
+ <p align="center">
53
+ <i>Installing <a href="https://trio.readthedocs.io/">Trio</a>'s dependencies with a warm cache.</i>
54
+ </p>
55
+
56
+ ## Highlights
57
+
58
+ - A single tool to replace `pip`, `pip-tools`, `pipx`, `poetry`, `pyenv`, `twine`, `virtualenv`, and
59
+ more.
60
+ - [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.24/README.md#projects), with a
62
+ [universal lockfile](https://docs.astral.sh/uv/concepts/projects/layout#the-lockfile).
63
+ - [Runs scripts](https://github.com/astral-sh/uv/blob/0.11.24/README.md#scripts), with support for
64
+ [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.24/README.md#python-versions) Python versions.
66
+ - [Runs and installs](https://github.com/astral-sh/uv/blob/0.11.24/README.md#tools) tools published as Python packages.
67
+ - Includes a [pip-compatible interface](https://github.com/astral-sh/uv/blob/0.11.24/README.md#the-pip-interface) for a performance boost with a familiar
68
+ CLI.
69
+ - Supports Cargo-style [workspaces](https://docs.astral.sh/uv/concepts/projects/workspaces) for
70
+ scalable projects.
71
+ - Disk-space efficient, with a [global cache](https://docs.astral.sh/uv/concepts/cache) for
72
+ dependency deduplication.
73
+ - Installable without Rust or Python via `curl` or `pip`.
74
+ - Supports macOS, Linux, and Windows.
75
+
76
+ uv is backed by [Astral](https://astral.sh), the creators of
77
+ [Ruff](https://github.com/astral-sh/ruff) and [ty](https://github.com/astral-sh/ty).
78
+
79
+ ## Installation
80
+
81
+ Install uv with our standalone installers:
82
+
83
+ ```bash
84
+ # On macOS and Linux.
85
+ curl -LsSf https://astral.sh/uv/install.sh | sh
86
+ ```
87
+
88
+ ```bash
89
+ # On Windows.
90
+ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
91
+ ```
92
+
93
+ Or, from [PyPI](https://pypi.org/project/uv/):
94
+
95
+ ```bash
96
+ # With pip.
97
+ pip install uv
98
+ ```
99
+
100
+ ```bash
101
+ # Or pipx.
102
+ pipx install uv
103
+ ```
104
+
105
+ If installed via the standalone installer, uv can update itself to the latest version:
106
+
107
+ ```bash
108
+ uv self update
109
+ ```
110
+
111
+ See the [installation documentation](https://docs.astral.sh/uv/getting-started/installation/) for
112
+ details and alternative installation methods.
113
+
114
+ ## Documentation
115
+
116
+ uv's documentation is available at [docs.astral.sh/uv](https://docs.astral.sh/uv).
117
+
118
+ Additionally, the command line reference documentation can be viewed with `uv help`.
119
+
120
+ ## Features
121
+
122
+ ### Projects
123
+
124
+ uv manages project dependencies and environments, with support for lockfiles, workspaces, and more,
125
+ similar to `rye` or `poetry`:
126
+
127
+ ```console
128
+ $ uv init example
129
+ Initialized project `example` at `/home/user/example`
130
+
131
+ $ cd example
132
+
133
+ $ uv add ruff
134
+ Creating virtual environment at: .venv
135
+ Resolved 2 packages in 170ms
136
+ Built example @ file:///home/user/example
137
+ Prepared 2 packages in 627ms
138
+ Installed 2 packages in 1ms
139
+ + example==0.1.0 (from file:///home/user/example)
140
+ + ruff==0.5.0
141
+
142
+ $ uv run ruff check
143
+ All checks passed!
144
+
145
+ $ uv lock
146
+ Resolved 2 packages in 0.33ms
147
+
148
+ $ uv sync
149
+ Resolved 2 packages in 0.70ms
150
+ Checked 1 package in 0.02ms
151
+ ```
152
+
153
+ See the [project documentation](https://docs.astral.sh/uv/guides/projects/) to get started.
154
+
155
+ uv also supports building and publishing projects, even if they're not managed with uv. See the
156
+ [publish guide](https://docs.astral.sh/uv/guides/publish/) to learn more.
157
+
158
+ ### Scripts
159
+
160
+ uv manages dependencies and environments for single-file scripts.
161
+
162
+ Create a new script and add inline metadata declaring its dependencies:
163
+
164
+ ```console
165
+ $ echo 'import requests; print(requests.get("https://astral.sh"))' > example.py
166
+
167
+ $ uv add --script example.py requests
168
+ Updated `example.py`
169
+ ```
170
+
171
+ Then, run the script in an isolated virtual environment:
172
+
173
+ ```console
174
+ $ uv run example.py
175
+ Reading inline script metadata from: example.py
176
+ Installed 5 packages in 12ms
177
+ <Response [200]>
178
+ ```
179
+
180
+ See the [scripts documentation](https://docs.astral.sh/uv/guides/scripts/) to get started.
181
+
182
+ ### Tools
183
+
184
+ uv executes and installs command-line tools provided by Python packages, similar to `pipx`.
185
+
186
+ Run a tool in an ephemeral environment using `uvx` (an alias for `uv tool run`):
187
+
188
+ ```console
189
+ $ uvx pycowsay 'hello world!'
190
+ Resolved 1 package in 167ms
191
+ Installed 1 package in 9ms
192
+ + pycowsay==0.0.0.2
193
+ """
194
+
195
+ ------------
196
+ < hello world! >
197
+ ------------
198
+ \ ^__^
199
+ \ (oo)\_______
200
+ (__)\ )\/\
201
+ ||----w |
202
+ || ||
203
+ ```
204
+
205
+ Install a tool with `uv tool install`:
206
+
207
+ ```console
208
+ $ uv tool install ruff
209
+ Resolved 1 package in 6ms
210
+ Installed 1 package in 2ms
211
+ + ruff==0.5.0
212
+ Installed 1 executable: ruff
213
+
214
+ $ ruff --version
215
+ ruff 0.5.0
216
+ ```
217
+
218
+ See the [tools documentation](https://docs.astral.sh/uv/guides/tools/) to get started.
219
+
220
+ ### Python versions
221
+
222
+ uv installs Python and allows quickly switching between versions.
223
+
224
+ Install multiple Python versions:
225
+
226
+ ```console
227
+ $ uv python install 3.12 3.13 3.14
228
+ Installed 3 versions in 972ms
229
+ + cpython-3.12.12-macos-aarch64-none (python3.12)
230
+ + cpython-3.13.9-macos-aarch64-none (python3.13)
231
+ + cpython-3.14.0-macos-aarch64-none (python3.14)
232
+
233
+ ```
234
+
235
+ Download Python versions as needed:
236
+
237
+ ```console
238
+ $ uv venv --python 3.12.0
239
+ Using Python 3.12.0
240
+ Creating virtual environment at: .venv
241
+ Activate with: source .venv/bin/activate
242
+
243
+ $ uv run --python pypy@3.8 -- python --version
244
+ Python 3.8.16 (a9dbdca6fc3286b0addd2240f11d97d8e8de187a, Dec 29 2022, 11:45:30)
245
+ [PyPy 7.3.11 with GCC Apple LLVM 13.1.6 (clang-1316.0.21.2.5)] on darwin
246
+ Type "help", "copyright", "credits" or "license" for more information.
247
+ >>>>
248
+ ```
249
+
250
+ Use a specific Python version in the current directory:
251
+
252
+ ```console
253
+ $ uv python pin 3.11
254
+ Pinned `.python-version` to `3.11`
255
+ ```
256
+
257
+ See the [Python installation documentation](https://docs.astral.sh/uv/guides/install-python/) to get
258
+ started.
259
+
260
+ ### The pip interface
261
+
262
+ uv provides a drop-in replacement for common `pip`, `pip-tools`, and `virtualenv` commands.
263
+
264
+ uv extends their interfaces with advanced features, such as dependency version overrides,
265
+ platform-independent resolutions, reproducible resolutions, alternative resolution strategies, and
266
+ more.
267
+
268
+ Migrate to uv without changing your existing workflows — and experience a 10-100x speedup — with the
269
+ `uv pip` interface.
270
+
271
+ Compile requirements into a platform-independent requirements file:
272
+
273
+ ```console
274
+ $ uv pip compile requirements.in \
275
+ --universal \
276
+ --output-file requirements.txt
277
+ Resolved 43 packages in 12ms
278
+ ```
279
+
280
+ Create a virtual environment:
281
+
282
+ ```console
283
+ $ uv venv
284
+ Using Python 3.12.3
285
+ Creating virtual environment at: .venv
286
+ Activate with: source .venv/bin/activate
287
+ ```
288
+
289
+ Install the locked requirements:
290
+
291
+ ```console
292
+ $ uv pip sync requirements.txt
293
+ Resolved 43 packages in 11ms
294
+ Installed 43 packages in 208ms
295
+ + babel==2.15.0
296
+ + black==24.4.2
297
+ + certifi==2024.7.4
298
+ ...
299
+ ```
300
+
301
+ See the [pip interface documentation](https://docs.astral.sh/uv/pip/index/) to get started.
302
+
303
+ ## Contributing
304
+
305
+ We are passionate about supporting contributors of all levels of experience and would love to see
306
+ you get involved in the project. See the
307
+ [contributing guide](https://github.com/astral-sh/uv?tab=contributing-ov-file#contributing) to get
308
+ started.
309
+
310
+ ## FAQ
311
+
312
+ #### How do you pronounce uv?
313
+
314
+ It's pronounced as "you - vee" ([`/juː viː/`](https://en.wikipedia.org/wiki/Help:IPA/English#Key))
315
+
316
+ #### How should I stylize uv?
317
+
318
+ Just "uv", please. See the [style guide](https://github.com/astral-sh/uv/blob/0.11.24/STYLE.md#styling-uv) for details.
319
+
320
+ #### What platforms does uv support?
321
+
322
+ See uv's [platform support](https://docs.astral.sh/uv/reference/platforms/) document.
323
+
324
+ #### Is uv ready for production?
325
+
326
+ Yes, uv is stable and widely used in production. See uv's
327
+ [versioning policy](https://docs.astral.sh/uv/reference/versioning/) document for details.
328
+
329
+ ## Acknowledgements
330
+
331
+ uv's dependency resolver uses [PubGrub](https://github.com/pubgrub-rs/pubgrub) under the hood. We're
332
+ grateful to the PubGrub maintainers, especially [Jacob Finkelman](https://github.com/Eh2406), for
333
+ their support.
334
+
335
+ uv's Git implementation is based on [Cargo](https://github.com/rust-lang/cargo).
336
+
337
+ Some of uv's optimizations are inspired by the great work we've seen in [pnpm](https://pnpm.io/),
338
+ [Orogene](https://github.com/orogene/orogene), and [Bun](https://github.com/oven-sh/bun). We've also
339
+ learned a lot from Nathaniel J. Smith's [Posy](https://github.com/njsmith/posy) and adapted its
340
+ [trampoline](https://github.com/njsmith/posy/tree/main/src/trampolines/windows-trampolines/posy-trampoline)
341
+ for Windows support.
342
+
343
+ ## License
344
+
345
+ uv is licensed under either of
346
+
347
+ - Apache License, Version 2.0, ([LICENSE-APACHE](https://github.com/astral-sh/uv/blob/0.11.24/LICENSE-APACHE) or
348
+ <https://www.apache.org/licenses/LICENSE-2.0>)
349
+ - MIT license ([LICENSE-MIT](https://github.com/astral-sh/uv/blob/0.11.24/LICENSE-MIT) or <https://opensource.org/licenses/MIT>)
350
+
351
+ at your option.
352
+
353
+ Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in uv
354
+ by you, as defined in the Apache-2.0 license, shall be dually licensed as above, without any
355
+ additional terms or conditions.
356
+
357
+ <div align="center">
358
+ <a target="_blank" href="https://astral.sh" style="background:none">
359
+ <img src="https://raw.githubusercontent.com/astral-sh/uv/main/assets/svg/Astral.svg" alt="Made by Astral">
360
+ </a>
361
+ </div>
362
+
@@ -0,0 +1 @@
1
+ {"key":"/usr/local","last_check":"2026-06-23T19:34:36.579623+00:00","pypi_version":"26.1.2"}
@@ -1,7 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: micropython-branch-manager
3
- Version: 2.0.3
3
+ Version: 2.1.0
4
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>
5
9
  License-Expression: MIT
6
10
  License-File: LICENSE
7
11
  Requires-Python: >=3.11
@@ -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.0'
22
+ __version_tuple__ = version_tuple = (2, 1, 0)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -136,8 +136,8 @@ class GitRepo:
136
136
  continue
137
137
  return False
138
138
 
139
- def merge(self, branch: str, message: str, no_ff: bool = True) -> None:
140
- """Merge branch with optional no-ff.
139
+ def merge(self, branch: str, message: str, no_ff: bool = True, signoff: bool = False) -> None:
140
+ """Merge branch with optional no-ff and Signed-off-by trailer.
141
141
 
142
142
  Raises:
143
143
  MergeConflictError: If conflicts require manual resolution
@@ -145,6 +145,8 @@ class GitRepo:
145
145
  args = ["merge"]
146
146
  if no_ff:
147
147
  args.append("--no-ff")
148
+ if signoff:
149
+ args.append("--signoff")
148
150
  args.extend([branch, "-m", message])
149
151
 
150
152
  try:
@@ -89,13 +89,14 @@ class PRManager:
89
89
  # Merge into update branch with trailers
90
90
  merge_msg = format_merge_message(
91
91
  branch_name=pr_info.branch,
92
+ integration=update_branch,
92
93
  title=pr_info.title,
93
94
  pr_number=pr_info.number,
94
95
  pr_url=pr_info.url,
95
96
  author=pr_info.author,
96
97
  )
97
98
  print(f"Merging {pr_info.branch} into {update_branch}...")
98
- self.git.merge(pr_info.branch, merge_msg)
99
+ self.git.merge(pr_info.branch, merge_msg, signoff=True)
99
100
  print("Merge completed successfully")
100
101
 
101
102
  # Push update branch to GitLab
@@ -144,11 +144,12 @@ class RebaseManager:
144
144
  self.git.checkout(update_branch)
145
145
  merge_msg = format_merge_message(
146
146
  branch_name=branch.name,
147
+ integration=update_branch,
147
148
  title=branch.title,
148
149
  pr_number=branch.pr_number,
149
150
  pr_url=branch.pr_url,
150
151
  )
151
- self.git.merge(rebase_branch, merge_msg, no_ff=True)
152
+ self.git.merge(rebase_branch, merge_msg, no_ff=True, signoff=True)
152
153
 
153
154
  # Update feature branch ref
154
155
  self.git.run("branch", "-f", branch.name, rebase_branch)
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from .config import BranchConfig
7
7
  from .exceptions import GitError
8
8
  from .git import GitRepo
9
- from .trailers import MERGE_PATTERN
9
+ from .trailers import MERGE_PATTERN, format_merge_message
10
10
 
11
11
 
12
12
  class RerereTrainer:
@@ -40,7 +40,7 @@ class RerereTrainer:
40
40
  worktree_path = Path(tempfile.mkdtemp(prefix="mbm-rerere-"))
41
41
  try:
42
42
  wt_git = self.git.create_worktree(worktree_path, target)
43
- self._replay_merges(wt_git, pairs)
43
+ self._replay_merges(wt_git, pairs, integration_branch)
44
44
  finally:
45
45
  self.git.remove_worktree(worktree_path, force=True)
46
46
  # Clean up if directory still exists
@@ -87,6 +87,7 @@ class RerereTrainer:
87
87
  self,
88
88
  wt_git: GitRepo,
89
89
  pairs: list[tuple[BranchConfig, str]],
90
+ integration: str = "integration",
90
91
  ) -> None:
91
92
  """Replay merges in the worktree, resolving conflicts from the integration branch."""
92
93
  for branch, merge_hash in pairs:
@@ -101,9 +102,16 @@ class RerereTrainer:
101
102
  wt_git.run(
102
103
  "merge",
103
104
  "--no-ff",
105
+ "--signoff",
104
106
  branch.name,
105
107
  "-m",
106
- f"Merge branch '{branch.name}'",
108
+ format_merge_message(
109
+ branch.name,
110
+ integration=integration,
111
+ title=branch.title,
112
+ pr_number=branch.pr_number,
113
+ pr_url=branch.pr_url,
114
+ ),
107
115
  )
108
116
  except GitError:
109
117
  # Conflict — resolve using the known-good merge commit
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import contextlib
6
6
  import re
7
+ import textwrap
7
8
  from typing import TYPE_CHECKING
8
9
 
9
10
  if TYPE_CHECKING:
@@ -17,19 +18,40 @@ TRAILER_AUTHOR = "MBM-Author"
17
18
  MERGE_PATTERN = re.compile(r"Merge branch '([^']+)'")
18
19
 
19
20
 
21
+ def _merge_component(integration: str) -> str:
22
+ """Derive a subject-line component from the integration branch name.
23
+
24
+ Strips mbm's working-branch suffix so the component reads as the final
25
+ integration branch (e.g. "tessera_update" -> "tessera").
26
+ """
27
+ for suffix in ("_update", "_tmp"):
28
+ if integration.endswith(suffix):
29
+ return integration[: -len(suffix)]
30
+ return integration
31
+
32
+
20
33
  def format_merge_message(
21
34
  branch_name: str,
35
+ integration: str = "integration",
22
36
  title: str | None = None,
23
37
  pr_number: int | None = None,
24
38
  pr_url: str | None = None,
25
39
  author: str | None = None,
26
40
  ) -> str:
27
- """Build merge commit message with MBM trailers."""
28
- lines = [f"Merge branch '{branch_name}'"]
41
+ """Build a MicroPython verifygitlog-compatible merge commit message.
42
+
43
+ The subject is "<integration>: Merge branch '<branch>'." so it satisfies
44
+ verifygitlog's subject rules (component prefix, capitalised first word, ends
45
+ with a period) while keeping the "Merge branch '<name>'" substring that
46
+ MERGE_PATTERN parses back. The optional PR title is wrapped to <=75 columns
47
+ for the body-line-length rule. The Signed-off-by trailer is added by the
48
+ caller via `git merge --signoff` (it uses the committer identity).
49
+ """
50
+ lines = [f"{_merge_component(integration)}: Merge branch '{branch_name}'."]
29
51
 
30
52
  if title:
31
53
  lines.append("")
32
- lines.append(title)
54
+ lines.append(textwrap.fill(title, width=75))
33
55
 
34
56
  # Add trailers
35
57
  trailers = []
@@ -93,22 +115,25 @@ def extract_branches_with_metadata(
93
115
  continue
94
116
  seen.add(branch_name)
95
117
 
96
- # Extract title (second paragraph, before trailers)
118
+ # Extract title (second paragraph, before trailers). The title may be
119
+ # wrapped across several lines (format_merge_message wraps to 75 cols),
120
+ # so collect the whole paragraph and rejoin it.
97
121
  title = None
98
122
  body_lines = body.split("\n")
99
123
  if len(body_lines) > 2 and body_lines[1] == "":
100
- # Find the title — first non-empty line after the blank line
124
+ title_parts = []
101
125
  for line in body_lines[2:]:
102
126
  stripped = line.strip()
103
127
  if not stripped:
104
128
  break
105
- # Skip trailer lines
106
- if any(
129
+ # Stop at the trailer block (MBM-* or the Signed-off-by line).
130
+ if stripped.startswith("Signed-off-by: ") or any(
107
131
  stripped.startswith(f"{k}: ") for k in (TRAILER_PR, TRAILER_URL, TRAILER_AUTHOR)
108
132
  ):
109
133
  break
110
- title = stripped
111
- break
134
+ title_parts.append(stripped)
135
+ if title_parts:
136
+ title = " ".join(title_parts)
112
137
 
113
138
  # Parse trailers
114
139
  trailers = parse_trailers_from_message(body)
@@ -0,0 +1,191 @@
1
+ """Tests for git trailer support."""
2
+
3
+ from micropython_branch_manager.git import GitRepo
4
+ from micropython_branch_manager.trailers import (
5
+ TRAILER_AUTHOR,
6
+ TRAILER_PR,
7
+ TRAILER_URL,
8
+ extract_branches_with_metadata,
9
+ format_merge_message,
10
+ parse_trailers_from_message,
11
+ )
12
+
13
+
14
+ def _init_repo(tmp_path):
15
+ """Init a real git repo with a base commit on an 'upstream' branch."""
16
+ git = GitRepo(tmp_path)
17
+ git.run("init", "-q")
18
+ git.run("config", "user.name", "Tester")
19
+ git.run("config", "user.email", "tester@example.com")
20
+ git.run("config", "commit.gpgsign", "false")
21
+ (tmp_path / "base.txt").write_text("base\n")
22
+ git.run("add", "-A")
23
+ git.run("commit", "-q", "-m", "base: Initial commit.")
24
+ git.run("branch", "-M", "upstream")
25
+ return git
26
+
27
+
28
+ def _add_feature(git, tmp_path, branch):
29
+ """Create a feature branch off upstream with one commit."""
30
+ git.run("checkout", "-q", "-b", branch, "upstream")
31
+ (tmp_path / f"{branch}.txt").write_text(f"{branch}\n")
32
+ git.run("add", "-A")
33
+ git.run("commit", "-q", "-m", f"feat: Add {branch}.")
34
+
35
+
36
+ def test_format_merge_message_minimal():
37
+ """format_merge_message with just branch name uses the default component."""
38
+ msg = format_merge_message(branch_name="feature-x")
39
+ assert msg == "integration: Merge branch 'feature-x'."
40
+
41
+
42
+ def test_format_merge_message_component_from_integration():
43
+ """The subject component is derived from the integration branch name."""
44
+ msg = format_merge_message(branch_name="feature-x", integration="tessera")
45
+ assert msg == "tessera: Merge branch 'feature-x'."
46
+
47
+
48
+ def test_format_merge_message_strips_update_suffix():
49
+ """An _update/_tmp working-branch suffix is stripped from the component."""
50
+ assert format_merge_message("feature-x", integration="tessera_update").startswith(
51
+ "tessera: Merge branch 'feature-x'."
52
+ )
53
+ assert format_merge_message("feature-x", integration="tessera_tmp").startswith(
54
+ "tessera: Merge branch 'feature-x'."
55
+ )
56
+
57
+
58
+ def test_format_merge_message_with_title():
59
+ """format_merge_message includes title and preserves the parseable substring."""
60
+ msg = format_merge_message(branch_name="feature-x", title="Add feature X")
61
+ assert "Merge branch 'feature-x'" in msg
62
+ assert "Add feature X" in msg
63
+
64
+
65
+ def test_format_merge_message_wraps_long_title():
66
+ """A long title is wrapped to <=75 columns for verifygitlog body-line rule."""
67
+ long_title = "Add feature X " * 12
68
+ msg = format_merge_message(branch_name="feature-x", title=long_title.strip())
69
+ body_lines = msg.split("\n")
70
+ assert len(body_lines) > 3 # title spans multiple lines
71
+ assert all(len(line) <= 75 for line in body_lines)
72
+
73
+
74
+ def test_format_merge_message_full():
75
+ """format_merge_message with all fields."""
76
+ msg = format_merge_message(
77
+ branch_name="feature-x",
78
+ integration="tessera",
79
+ title="Add feature X",
80
+ pr_number=12345,
81
+ pr_url="https://github.com/micropython/micropython/pull/12345",
82
+ author="testuser",
83
+ )
84
+
85
+ lines = msg.split("\n")
86
+ assert lines[0] == "tessera: Merge branch 'feature-x'."
87
+ assert "Add feature X" in msg
88
+ assert f"{TRAILER_PR}: 12345" in msg
89
+ assert f"{TRAILER_URL}: https://github.com/micropython/micropython/pull/12345" in msg
90
+ assert f"{TRAILER_AUTHOR}: testuser" in msg
91
+
92
+
93
+ def test_format_merge_message_no_title_with_trailers():
94
+ """format_merge_message with trailers but no title."""
95
+ msg = format_merge_message(
96
+ branch_name="feature-x",
97
+ pr_number=123,
98
+ )
99
+ assert "Merge branch 'feature-x'" in msg
100
+ assert f"{TRAILER_PR}: 123" in msg
101
+
102
+
103
+ def test_parse_trailers_from_message():
104
+ """parse_trailers_from_message extracts MBM trailers."""
105
+ msg = (
106
+ "Merge branch 'feature-x'\n"
107
+ "\n"
108
+ "Add feature X\n"
109
+ "\n"
110
+ "MBM-PR: 12345\n"
111
+ "MBM-URL: https://github.com/micropython/micropython/pull/12345\n"
112
+ "MBM-Author: testuser\n"
113
+ )
114
+
115
+ trailers = parse_trailers_from_message(msg)
116
+
117
+ assert trailers[TRAILER_PR] == "12345"
118
+ assert trailers[TRAILER_URL] == "https://github.com/micropython/micropython/pull/12345"
119
+ assert trailers[TRAILER_AUTHOR] == "testuser"
120
+
121
+
122
+ def test_parse_trailers_from_message_no_trailers():
123
+ """parse_trailers_from_message returns empty dict if no trailers."""
124
+ msg = "Merge branch 'feature-x'\n\nAdd feature X\n"
125
+ trailers = parse_trailers_from_message(msg)
126
+ assert trailers == {}
127
+
128
+
129
+ def test_parse_trailers_from_message_partial():
130
+ """parse_trailers_from_message handles partial trailers."""
131
+ msg = "Merge branch 'feature-x'\n\nMBM-PR: 42\n"
132
+ trailers = parse_trailers_from_message(msg)
133
+ assert trailers[TRAILER_PR] == "42"
134
+ assert TRAILER_URL not in trailers
135
+ assert TRAILER_AUTHOR not in trailers
136
+
137
+
138
+ def test_extract_metadata_roundtrip_wrapped_title(tmp_path):
139
+ """A real --signoff merge written by format_merge_message round-trips.
140
+
141
+ Exercises the full write->read cycle against real git: the wrapped,
142
+ multi-line title is rejoined and the trailers are recovered, while the
143
+ subject carries the verifygitlog component prefix and a Signed-off-by line.
144
+ """
145
+ git = _init_repo(tmp_path)
146
+ _add_feature(git, tmp_path, "feature-x")
147
+ git.run("checkout", "-q", "-b", "tessera_update", "upstream")
148
+
149
+ long_title = (
150
+ "Add a deliberately long feature description that exceeds the "
151
+ "seventy-five column wrap width so the title spans several lines"
152
+ )
153
+ merge_msg = format_merge_message(
154
+ branch_name="feature-x",
155
+ integration="tessera_update",
156
+ title=long_title,
157
+ pr_number=12345,
158
+ pr_url="https://github.com/micropython/micropython/pull/12345",
159
+ )
160
+ git.merge("feature-x", merge_msg, no_ff=True, signoff=True)
161
+
162
+ # The written subject is verifygitlog-shaped and signed off.
163
+ assert git.run("log", "-1", "--format=%s", "tessera_update") == (
164
+ "tessera: Merge branch 'feature-x'."
165
+ )
166
+ assert "Signed-off-by: Tester <tester@example.com>" in git.run(
167
+ "log", "-1", "--format=%B", "tessera_update"
168
+ )
169
+
170
+ branches = extract_branches_with_metadata(git, "upstream", "tessera_update")
171
+ assert len(branches) == 1
172
+ b = branches[0]
173
+ assert b.name == "feature-x"
174
+ assert b.title == long_title # wrapped lines rejoined
175
+ assert b.pr_number == 12345
176
+ assert b.pr_url == "https://github.com/micropython/micropython/pull/12345"
177
+
178
+
179
+ def test_extract_metadata_signoff_not_treated_as_title(tmp_path):
180
+ """A Signed-off-by line on a title-less merge is not parsed as the title."""
181
+ git = _init_repo(tmp_path)
182
+ _add_feature(git, tmp_path, "feature-y")
183
+ git.run("checkout", "-q", "-b", "tessera_update", "upstream")
184
+
185
+ merge_msg = format_merge_message(branch_name="feature-y", integration="tessera_update")
186
+ git.merge("feature-y", merge_msg, no_ff=True, signoff=True)
187
+
188
+ branches = extract_branches_with_metadata(git, "upstream", "tessera_update")
189
+ assert len(branches) == 1
190
+ assert branches[0].name == "feature-y"
191
+ assert branches[0].title is None
@@ -1 +0,0 @@
1
- {"key":"/usr/local","last_check":"2026-03-25T21:41:43.339896+00:00","pypi_version":"26.0.1"}
@@ -1,85 +0,0 @@
1
- """Tests for git trailer support."""
2
-
3
- from micropython_branch_manager.trailers import (
4
- TRAILER_AUTHOR,
5
- TRAILER_PR,
6
- TRAILER_URL,
7
- format_merge_message,
8
- parse_trailers_from_message,
9
- )
10
-
11
-
12
- def test_format_merge_message_minimal():
13
- """format_merge_message with just branch name."""
14
- msg = format_merge_message(branch_name="feature-x")
15
- assert msg == "Merge branch 'feature-x'"
16
-
17
-
18
- def test_format_merge_message_with_title():
19
- """format_merge_message includes title."""
20
- msg = format_merge_message(branch_name="feature-x", title="Add feature X")
21
- assert "Merge branch 'feature-x'" in msg
22
- assert "Add feature X" in msg
23
-
24
-
25
- def test_format_merge_message_full():
26
- """format_merge_message with all fields."""
27
- msg = format_merge_message(
28
- branch_name="feature-x",
29
- title="Add feature X",
30
- pr_number=12345,
31
- pr_url="https://github.com/micropython/micropython/pull/12345",
32
- author="testuser",
33
- )
34
-
35
- lines = msg.split("\n")
36
- assert lines[0] == "Merge branch 'feature-x'"
37
- assert "Add feature X" in msg
38
- assert f"{TRAILER_PR}: 12345" in msg
39
- assert f"{TRAILER_URL}: https://github.com/micropython/micropython/pull/12345" in msg
40
- assert f"{TRAILER_AUTHOR}: testuser" in msg
41
-
42
-
43
- def test_format_merge_message_no_title_with_trailers():
44
- """format_merge_message with trailers but no title."""
45
- msg = format_merge_message(
46
- branch_name="feature-x",
47
- pr_number=123,
48
- )
49
- assert "Merge branch 'feature-x'" in msg
50
- assert f"{TRAILER_PR}: 123" in msg
51
-
52
-
53
- def test_parse_trailers_from_message():
54
- """parse_trailers_from_message extracts MBM trailers."""
55
- msg = (
56
- "Merge branch 'feature-x'\n"
57
- "\n"
58
- "Add feature X\n"
59
- "\n"
60
- "MBM-PR: 12345\n"
61
- "MBM-URL: https://github.com/micropython/micropython/pull/12345\n"
62
- "MBM-Author: testuser\n"
63
- )
64
-
65
- trailers = parse_trailers_from_message(msg)
66
-
67
- assert trailers[TRAILER_PR] == "12345"
68
- assert trailers[TRAILER_URL] == "https://github.com/micropython/micropython/pull/12345"
69
- assert trailers[TRAILER_AUTHOR] == "testuser"
70
-
71
-
72
- def test_parse_trailers_from_message_no_trailers():
73
- """parse_trailers_from_message returns empty dict if no trailers."""
74
- msg = "Merge branch 'feature-x'\n\nAdd feature X\n"
75
- trailers = parse_trailers_from_message(msg)
76
- assert trailers == {}
77
-
78
-
79
- def test_parse_trailers_from_message_partial():
80
- """parse_trailers_from_message handles partial trailers."""
81
- msg = "Merge branch 'feature-x'\n\nMBM-PR: 42\n"
82
- trailers = parse_trailers_from_message(msg)
83
- assert trailers[TRAILER_PR] == "42"
84
- assert TRAILER_URL not in trailers
85
- assert TRAILER_AUTHOR not in trailers