Skip to content

Commit 6ffda2a

Browse files
committed
docs(monorepo): improve docs and add docs to verify it works
1 parent 195b735 commit 6ffda2a

2 files changed

Lines changed: 126 additions & 61 deletions

File tree

‎docs/tutorials/monorepo_guidance.md‎

Lines changed: 46 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -3,85 +3,70 @@
33
This tutorial assumes that your monorepo is structured with multiple components that can be released independently of each other.
44
It also assumes that you are using conventional commits with scopes.
55

6-
Here is a step-by-step example using two libraries, `library-b` and `library-z`:
6+
Here is a step-by-step example using two libraries, `library-foo` and `library-alice`:
77

88
1. **Organize your monorepo**
99

10-
For example, you might have one of these layouts:
10+
For example, you might have one of these layouts:
1111

12-
```shell-session
13-
.
14-
├── library-b
15-
│   └── .cz.toml
16-
└── library-z
17-
└── .cz.toml
18-
```
12+
```shell-session
13+
.
14+
├── library-foo
15+
│   └── .cz.toml
16+
└── library-alice
17+
└── .cz.toml
18+
```
1919

20-
```shell-session
21-
src
22-
├── library-b
23-
│   └── .cz.toml
24-
└── library-z
25-
└── .cz.toml
26-
```
20+
```shell-session
21+
src
22+
├── library-foo
23+
│   └── .cz.toml
24+
└── library-alice
25+
└── .cz.toml
26+
```
2727

2828
2. **Add a Commitizen configuration for each component**
2929

30-
```toml
31-
# library-b/.cz.toml
32-
[tool.commitizen]
33-
name = "cz_customize"
34-
version = "0.0.0"
35-
tag_format = "${version}-library-b" # the component name can be a prefix or suffix with or without a separator
36-
ignored_tag_formats = ["${version}-library-*"] # Avoid noise from other tags
37-
update_changelog_on_bump = true
38-
```
39-
40-
```toml
41-
# library-z/.cz.toml
42-
[tool.commitizen]
43-
name = "cz_customize"
44-
version = "0.0.0"
45-
tag_format = "${version}-library-z"
46-
ignored_tag_formats = ["${version}-library-*"] # Avoid noise from other tags
47-
update_changelog_on_bump = true
48-
```
30+
```toml
31+
# library-foo/.cz.toml
32+
[tool.commitizen]
33+
name = "cz_customize"
34+
version = "0.0.0"
35+
tag_format = "${version}-library-foo" # the component name can be a prefix or suffix with or without a separator
36+
ignored_tag_formats = ["${version}-library-(?!foo$).*"] # Avoid noise from other tags
37+
update_changelog_on_bump = true
38+
```
4939

50-
3. **Bump each component independently**
40+
```toml
41+
# library-alice/.cz.toml
42+
[tool.commitizen]
43+
name = "cz_customize"
44+
version = "0.0.0"
45+
tag_format = "${version}-library-alice"
46+
ignored_tag_formats = ["${version}-library-(?!alice$).*"] # Avoid noise from other tags
47+
update_changelog_on_bump = true
48+
```
5149

52-
```sh
53-
cz --config library-b/.cz.toml bump --yes
54-
cz --config library-z/.cz.toml bump --yes
55-
```
50+
3. **Bump each component independently**
5651

52+
```sh
53+
cz --config library-foo/.cz.toml bump --yes
54+
cz --config library-alice/.cz.toml bump --yes
55+
```
5756

58-
## Changelog per component
57+
## Changelog Per Component
5958

6059
To filter the correct commits for each component, you'll need to define a strategy.
6160

6261
For example:
6362

6463
- Trigger the pipeline based on the changed path. This can have some downsides, as you'll rely on the developer not including files from unrelated components.
65-
- [GitHub Actions](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore) uses `path`
66-
- [Jenkins](https://www.jenkins.io/doc/book/pipeline/syntax/#built-in-conditions) uses `changeset`
67-
- [GitLab](https://docs.gitlab.com/ee/ci/yaml/#ruleschanges) uses `rules:changes`
64+
- [GitHub Actions](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#onpushpull_requestpull_request_targetpathspaths-ignore) uses `path`
65+
- [Jenkins](https://www.jenkins.io/doc/book/pipeline/syntax/#built-in-conditions) uses `changeset`
66+
- [GitLab](https://docs.gitlab.com/ee/ci/yaml/#ruleschanges) uses `rules:changes`
6867
- Filter commits by a specific pattern in the commit message (recommended)
6968

69+
### Filtering Commits
7070

71-
### Example with scope in conventional commits
72-
73-
In this example, we want `library-b`'s changelog to only include commits that use the `library-b` scope.
74-
To achieve this, we configure Commitizen to match only commit messages with that scope.
75-
76-
Here is an example configuration for `library-b`:
77-
78-
```toml
79-
[tool.commitizen.customize]
80-
changelog_pattern = "^(feat|fix)\\(library-b\\)(!)?:" # the type pattern can be a wildcard or any types you wish to include
81-
```
82-
83-
With this configuration, a commit message like the following would be included in `library-b`'s changelog:
84-
85-
```text
86-
fix(library-b): Some awesome message
87-
```
71+
For advanced use cases, filter the commits passed to `version`, `bump` or `changelog` by implementing custom conventional commit rules.
72+
See [Filter commits before bump and changelog generation](../customization/python_class.md#filter-commits-before-bump-and-changelog-generation)

‎tests/test_tags.py‎

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import pytest
2+
13
from commitizen.git import GitTag
24
from commitizen.tags import TagRules
35

@@ -140,3 +142,81 @@ def test_is_version_tag_accepts_dotless_devrelease_in_custom_tag_format():
140142

141143
extracted = rules.extract_version(_git_tag("version-1.2.3dev1"))
142144
assert str(extracted) == "1.2.3.dev1"
145+
146+
147+
def test_monorepo_ignored_tag_formats_keep_own_tags(capsys: pytest.CaptureFixture):
148+
"""Regression test for the monorepo workflow in
149+
``docs/tutorials/monorepo_guidance.md``.
150+
151+
Each component configures a suffixed ``tag_format`` and ignores sibling
152+
component tags with a wildcard.
153+
154+
Ignoring sibling tags must not reject the component's own tags. Ignored
155+
tags are expected noise, so they must not emit "Invalid version tag"
156+
warnings; only truly unexpected tags warn.
157+
"""
158+
library_foo_rules = TagRules(
159+
tag_format="${version}-library-foo",
160+
ignored_tag_formats=["${version}-library-(?!foo$).*"],
161+
)
162+
library_zoo_rules = TagRules(
163+
tag_format="${version}-library-zoo",
164+
ignored_tag_formats=["${version}-library-(?!zoo$).*"],
165+
)
166+
167+
# Own tags remain version tags, sibling tags are filtered out.
168+
assert library_foo_rules.is_version_tag("1.0.0-library-foo") is True
169+
assert library_foo_rules.is_version_tag("1.0.0-library-zoo") is False
170+
171+
assert library_zoo_rules.is_version_tag("1.0.0-library-zoo") is True
172+
assert library_zoo_rules.is_version_tag("1.0.0-library-foo") is False
173+
174+
# Check also for prefix
175+
assert library_foo_rules.is_ignored_tag("1.0.0-library-foobar") is True
176+
177+
# The own tag is parseable, so a rejection above is a filtering problem,
178+
# not a parsing one.
179+
extracted = library_foo_rules.extract_version(_git_tag("1.0.0-library-foo"))
180+
assert str(extracted) == "1.0.0"
181+
182+
# Ignored tags do not warn. Unknown tags still do.
183+
library_foo_rules.is_version_tag("1.0.0-library-zoo", warn=True)
184+
library_foo_rules.is_version_tag("unexpected-tag", warn=True)
185+
captured = capsys.readouterr()
186+
assert "1.0.0-library-zoo" not in captured.err
187+
assert "unexpected-tag" in captured.err
188+
189+
190+
def test_monorepo_get_version_tags_filters_sibling_components(
191+
capsys: pytest.CaptureFixture,
192+
):
193+
"""``get_version_tags`` keeps only the component's own tags when the
194+
repository also contains sibling component tags, as configured in
195+
``docs/tutorials/monorepo_guidance.md``.
196+
197+
The changelog and the ``scm`` version provider use this filtering, so the
198+
ignored wildcard must silence sibling tags without dropping this
199+
component's own tags.
200+
"""
201+
tags = [
202+
_git_tag("1.0.0-library-b"),
203+
_git_tag("1.0.0-library-z"),
204+
_git_tag("1.1.0-library-b"),
205+
_git_tag("unexpected-tag"),
206+
]
207+
rules = TagRules(
208+
tag_format="${version}-library-b",
209+
ignored_tag_formats=["${version}-library-(?!b$).*"],
210+
)
211+
212+
version_tags = rules.get_version_tags(tags, warn=True)
213+
214+
assert [t.name for t in version_tags] == [
215+
"1.0.0-library-b",
216+
"1.1.0-library-b",
217+
]
218+
219+
# Only the truly unexpected tag warns; sibling tags are known noise.
220+
captured = capsys.readouterr()
221+
assert "unexpected-tag" in captured.err
222+
assert "1.0.0-library-z" not in captured.err

0 commit comments

Comments
 (0)