Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 26 additions & 17 deletions src/semantic_release/commit_parser/conventional/options_monorepo.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,37 +43,46 @@ class ConventionalCommitMonorepoParserOptions(ConventionalCommitParserOptions):
to match them literally.
"""

@classmethod
@field_validator("path_filters", mode="before")
def convert_strs_to_paths(cls, value: Any) -> tuple[Path, ...]:
values = value if isinstance(value, Iterable) else [value]
results: list[Path] = []
@classmethod
def convert_strs_to_paths(cls, value: Any) -> tuple[str, ...]:
if isinstance(value, str):
return (value,)

for val in values:
if isinstance(val, (str, Path)):
results.append(Path(val))
continue
if isinstance(value, Path):
return (str(value),)

raise TypeError(f"Invalid type: {type(val)}, expected str or Path.")
if isinstance(value, Iterable):
results: list[str] = []
for val in value:
if isinstance(val, (str, Path)):
results.append(str(Path(val)))
continue

return tuple(results)
msg = f"Invalid type: {type(val)}, expected str or Path."
raise TypeError(msg)

return tuple(results)

msg = f"Invalid type: {type(value)}, expected str, Path, or Iterable."
raise TypeError(msg)

@classmethod
@field_validator("path_filters", mode="after")
def resolve_path(cls, dir_paths: tuple[Path, ...]) -> tuple[Path, ...]:
@classmethod
def resolve_path(cls, dir_path_strs: tuple[str, ...]) -> tuple[str, ...]:
return tuple(
(
Path(f"!{Path(str_path[1:]).expanduser().absolute().resolve()}")
f"!{Path(str_path[1:]).expanduser().absolute().resolve()}"
# maintains the negation prefix if it exists
if (str_path := str(path)).startswith("!")
if str_path.startswith("!")
# otherwise, resolve the path normally
else path.expanduser().absolute().resolve()
else str(Path(str_path).expanduser().absolute().resolve())
)
for path in dir_paths
for str_path in dir_path_strs
)

@classmethod
@field_validator("scope_prefix", mode="after")
@classmethod
def validate_scope_prefix(cls, scope_prefix: str) -> str:
if not scope_prefix:
return ""
Expand Down
52 changes: 16 additions & 36 deletions tests/fixtures/git_repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class CommitDef(TypedDict):
sha: str
datetime: NotRequired[DatetimeISOStr]
include_in_changelog: bool
file_to_change: NotRequired[Path | str]

class BaseRepoVersionDef(TypedDict):
"""A Common Repo definition for a get_commits_repo_*() fixture with all commit convention types"""
Expand Down Expand Up @@ -290,6 +291,7 @@ class CommitSpec(TypedDict):
scipy: str
datetime: NotRequired[DatetimeISOStr]
include_in_changelog: NotRequired[bool]
file_to_change: NotRequired[Path | str]

class DetailsBase(TypedDict):
pre_actions: NotRequired[Sequence[RepoActions]]
Expand Down Expand Up @@ -1127,7 +1129,9 @@ def _simulate_change_commits_n_rtn_changelog_entry(
changelog_entries: list[CommitDef] = []
for commit_msg in commit_msgs:
if not git_repo.is_dirty(index=True, working_tree=False):
add_text_to_file(git_repo, file_in_repo)
add_text_to_file(
git_repo, str(commit_msg.get("file_to_change", file_in_repo))
)

changelog_entries.append(commit_n_rtn_changelog_entry(git_repo, commit_msg))

Expand Down Expand Up @@ -1336,39 +1340,6 @@ def _configure_base_repo( # noqa: C901

@pytest.fixture(scope="session")
def separate_squashed_commit_def() -> SeparateSquashedCommitDefFn:
# default_conventional_parser: ConventionalCommitParser,
# default_emoji_parser: EmojiCommitParser,
# default_scipy_parser: ScipyCommitParser,
# message_parsers: dict[
# CommitConvention,
# ConventionalCommitParser | EmojiCommitParser | ScipyCommitParser,
# ] = {
# "conventional": ConventionalCommitParser(
# options=ConventionalCommitParserOptions(
# **{
# **default_conventional_parser.options.__dict__,
# "parse_squash_commits": True,
# }
# )
# ),
# "emoji": EmojiCommitParser(
# options=EmojiParserOptions(
# **{
# **default_emoji_parser.options.__dict__,
# "parse_squash_commits": True,
# }
# )
# ),
# "scipy": ScipyCommitParser(
# options=ScipyParserOptions(
# **{
# **default_scipy_parser.options.__dict__,
# "parse_squash_commits": True,
# }
# )
# ),
# }

def _separate_squashed_commit_def(
squashed_commit_def: CommitDef,
parser: SquashedCommitSupportedParser,
Expand Down Expand Up @@ -1435,17 +1406,26 @@ def _convert(
)

# Extract the correct commit message for the commit type
return {
commit_def: CommitDef = {
**parse_msg_fn(commit_spec[commit_type], parser=parser),
"cid": commit_spec["cid"],
"datetime": (
commit_spec["datetime"]
if "datetime" in commit_spec
else stable_now_date.isoformat(timespec="seconds")
),
"include_in_changelog": (commit_spec.get("include_in_changelog", True)),
"include_in_changelog": commit_spec.get("include_in_changelog", True),
}

if "file_to_change" in commit_spec:
commit_def.update(
{
"file_to_change": commit_spec["file_to_change"],
}
)

return commit_def

return _convert


Expand Down
44 changes: 42 additions & 2 deletions tests/fixtures/monorepos/example_monorepo.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,12 @@ def build_spec_hash_4_example_monorepo(
@pytest.fixture(scope="session")
def cached_example_monorepo(
build_repo_or_copy_cache: BuildRepoOrCopyCacheFn,
monorepo_pkg1_dir: Path,
monorepo_pkg2_dir: Path,
monorepo_pkg1_name: str,
monorepo_pkg2_name: str,
monorepo_pkg1_dir: str,
monorepo_pkg2_dir: str,
monorepo_pkg1_docs_dir: str,
monorepo_pkg2_docs_dir: str,
monorepo_pkg1_version_py_file: Path,
monorepo_pkg2_version_py_file: Path,
monorepo_pkg1_pyproject_toml_file: Path,
Expand Down Expand Up @@ -104,6 +108,13 @@ def hello_world() -> None:
print("{pkg_name} Hello World")
'''
).lstrip()
doc_index_contents = dedent(
"""
==================
{pkg_name} Documentation
==================
"""
).lstrip()

with temporary_working_directory(cached_project_path):
update_version_py_file(
Expand All @@ -127,6 +138,14 @@ def hello_world() -> None:
(".gitignore", gitignore_contents),
(monorepo_pkg1_pyproject_toml_file, EXAMPLE_PYPROJECT_TOML_CONTENT),
(monorepo_pkg2_pyproject_toml_file, EXAMPLE_PYPROJECT_TOML_CONTENT),
(
Path(monorepo_pkg1_docs_dir, "index.rst"),
doc_index_contents.format(pkg_name=monorepo_pkg1_name),
),
(
Path(monorepo_pkg2_docs_dir, "index.rst"),
doc_index_contents.format(pkg_name=monorepo_pkg2_name),
),
]

for file, contents in file_2_contents:
Expand Down Expand Up @@ -216,6 +235,27 @@ def monorepo_pkg2_name() -> str:
return "pkg2"


@pytest.fixture(scope="session")
def monorepo_pkg_docs_dir_pattern() -> str:
return str(Path("docs", "source", "{package_name}"))


@pytest.fixture(scope="session")
def monorepo_pkg1_docs_dir(
monorepo_pkg1_name: str,
monorepo_pkg_docs_dir_pattern: str,
) -> str:
return monorepo_pkg_docs_dir_pattern.format(package_name=monorepo_pkg1_name)


@pytest.fixture(scope="session")
def monorepo_pkg2_docs_dir(
monorepo_pkg2_name: str,
monorepo_pkg_docs_dir_pattern: str,
) -> str:
return monorepo_pkg_docs_dir_pattern.format(package_name=monorepo_pkg2_name)


@pytest.fixture(scope="session")
def monorepo_pkg_dir_pattern() -> str:
return str(Path("packages", "{package_name}"))
Expand Down
29 changes: 21 additions & 8 deletions tests/fixtures/monorepos/github_flow/monorepo_w_default_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,10 @@ def get_repo_definition_4_github_flow_monorepo_w_default_release_channel(
monorepo_pkg2_changelog_rst_file: Path,
monorepo_pkg1_name: str,
monorepo_pkg2_name: str,
monorepo_pkg1_dir: Path,
monorepo_pkg2_dir: Path,
monorepo_pkg1_dir: str,
monorepo_pkg2_dir: str,
monorepo_pkg1_docs_dir: str,
monorepo_pkg2_docs_dir: str,
monorepo_pkg1_version_py_file: Path,
monorepo_pkg2_version_py_file: Path,
monorepo_pkg1_pyproject_toml_file: Path,
Expand All @@ -117,7 +119,8 @@ def get_repo_definition_4_github_flow_monorepo_w_default_release_channel(
* chore(release): pkg1@1.1.0 [skip ci] (tag: pkg1-v1.1.0, branch: main, HEAD -> main)
* feat(pkg1): file modified outside of pkg 1, identified by scope (#5)
|
| * feat(pkg1): file modified outside of pkg 1, identified by scope (branch: pkg1/feat/pr-4)
| * docs: pkg1 docs modified outside of pkg 1, identified by path filter (branch: pkg1/feat/pr-4)
| * feat(pkg1): file modified outside of pkg 1, identified by scope
|/
* chore(release): pkg2@1.1.1 [skip ci] (tag: pkg2-v1.1.1)
* fix(pkg2-cli): file modified outside of pkg 2, identified by scope (#4)
Expand Down Expand Up @@ -207,21 +210,23 @@ def _get_repo_from_definition(
if commit_type != "conventional":
raise ValueError(f"Unsupported commit type: {commit_type}")

pkg1_path_filters = (".", f"../../{monorepo_pkg1_docs_dir}")
pkg1_commit_parser = ConventionalCommitMonorepoParser(
options=ConventionalCommitMonorepoParserOptions(
parse_squash_commits=True,
ignore_merge_commits=ignore_merge_commits,
scope_prefix=f"{monorepo_pkg1_name}-?",
path_filters=(".",),
path_filters=pkg1_path_filters,
)
)

pkg2_path_filters = (".", f"../../{monorepo_pkg2_docs_dir}")
pkg2_commit_parser = ConventionalCommitMonorepoParser(
options=ConventionalCommitMonorepoParserOptions(
parse_squash_commits=pkg1_commit_parser.options.parse_squash_commits,
ignore_merge_commits=pkg1_commit_parser.options.ignore_merge_commits,
scope_prefix=f"{monorepo_pkg2_name}-?",
path_filters=(".",),
path_filters=pkg2_path_filters,
)
)

Expand Down Expand Up @@ -277,7 +282,7 @@ def _get_repo_from_definition(
)
),
"tool.semantic_release.commit_parser_options.scope_prefix": pkg1_commit_parser.options.scope_prefix,
"tool.semantic_release.commit_parser_options.path_filters": pkg1_commit_parser.options.path_filters,
"tool.semantic_release.commit_parser_options.path_filters": pkg1_path_filters,
**(extra_configs or {}),
},
},
Expand All @@ -301,7 +306,7 @@ def _get_repo_from_definition(
)
),
"tool.semantic_release.commit_parser_options.scope_prefix": pkg2_commit_parser.options.scope_prefix,
"tool.semantic_release.commit_parser_options.path_filters": pkg2_commit_parser.options.path_filters,
"tool.semantic_release.commit_parser_options.path_filters": pkg2_path_filters,
**(extra_configs or {}),
},
},
Expand Down Expand Up @@ -774,7 +779,15 @@ def _get_repo_from_definition(
"emoji": ":sparkles: (pkg1) file modified outside of pkg 1, identified by scope",
"scipy": "ENH:pkg1: file modified outside of pkg 1, identified by scope",
"datetime": next(commit_timestamp_gen),
}
},
{
"cid": "pkg1-docs-2-squashed",
"conventional": "docs: pkg1 docs modified outside of pkg 1, identified by path filter",
"emoji": ":book: pkg1 docs modified outside of pkg 1, identified by path filter",
"scipy": "DOC: pkg1 docs modified outside of pkg 1, identified by path filter",
"datetime": next(commit_timestamp_gen),
"file_to_change": f"{monorepo_pkg1_docs_dir}/index.rst",
},
]

repo_construction_steps.extend(
Expand Down
25 changes: 15 additions & 10 deletions tests/fixtures/monorepos/github_flow/monorepo_w_release_channels.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,10 @@ def get_repo_definition_4_github_flow_monorepo_w_feature_release_channel(
monorepo_pkg2_changelog_rst_file: Path,
monorepo_pkg1_name: str,
monorepo_pkg2_name: str,
monorepo_pkg1_dir: Path,
monorepo_pkg2_dir: Path,
monorepo_pkg1_dir: str,
monorepo_pkg2_dir: str,
monorepo_pkg1_docs_dir: str,
monorepo_pkg2_docs_dir: str,
monorepo_pkg1_version_py_file: Path,
monorepo_pkg2_version_py_file: Path,
stable_now_date: GetStableDateNowFn,
Expand All @@ -119,7 +121,7 @@ def get_repo_definition_4_github_flow_monorepo_w_feature_release_channel(
| * chore(release): pkg2@1.1.0-alpha.2 [skip ci] (tag: pkg2-v1.1.0-alpha.2, branch: pkg2/feat/pr-2)
| * fix(pkg2-cli): file modified outside of pkg 2, identified by scope
| * chore(release): pkg2@1.1.0-alpha.1 [skip ci] (tag: pkg2-v1.1.0-alpha.1)
| * docs: add cli documentation
| * docs: pkg2 docs modified outside of pkg 2, identified by path filter
| * test: add cli tests
| * feat: no pkg scope but file in pkg 2 directory
|/
Expand Down Expand Up @@ -203,21 +205,23 @@ def _get_repo_from_definition(
if commit_type != "conventional":
raise ValueError(f"Unsupported commit type: {commit_type}")

pkg1_path_filters = (".", f"../../{monorepo_pkg1_docs_dir}")
pkg1_commit_parser = ConventionalCommitMonorepoParser(
options=ConventionalCommitMonorepoParserOptions(
parse_squash_commits=True,
ignore_merge_commits=ignore_merge_commits,
scope_prefix=f"{monorepo_pkg1_name}-?",
path_filters=(".",),
path_filters=pkg1_path_filters,
)
)

pkg2_path_filters = (".", f"../../{monorepo_pkg2_docs_dir}")
pkg2_commit_parser = ConventionalCommitMonorepoParser(
options=ConventionalCommitMonorepoParserOptions(
parse_squash_commits=pkg1_commit_parser.options.parse_squash_commits,
ignore_merge_commits=pkg1_commit_parser.options.ignore_merge_commits,
scope_prefix=f"{monorepo_pkg2_name}-?",
path_filters=(".",),
path_filters=pkg2_path_filters,
)
)

Expand Down Expand Up @@ -277,7 +281,7 @@ def _get_repo_from_definition(
"prerelease_token": "alpha",
},
"tool.semantic_release.commit_parser_options.scope_prefix": pkg1_commit_parser.options.scope_prefix,
"tool.semantic_release.commit_parser_options.path_filters": pkg1_commit_parser.options.path_filters,
"tool.semantic_release.commit_parser_options.path_filters": pkg1_path_filters,
**(extra_configs or {}),
},
},
Expand Down Expand Up @@ -307,7 +311,7 @@ def _get_repo_from_definition(
"prerelease_token": "alpha",
},
"tool.semantic_release.commit_parser_options.scope_prefix": pkg2_commit_parser.options.scope_prefix,
"tool.semantic_release.commit_parser_options.path_filters": pkg2_commit_parser.options.path_filters,
"tool.semantic_release.commit_parser_options.path_filters": pkg2_path_filters,
**(extra_configs or {}),
},
},
Expand Down Expand Up @@ -646,11 +650,12 @@ def _get_repo_from_definition(
cid_pkg2_feb1_c3_docs
:= "pkg2_feat_branch_1_c3_docs"
),
"conventional": "docs: add cli documentation",
"emoji": ":memo: add cli documentation",
"scipy": "DOC: add cli documentation",
"conventional": "docs: pkg2 docs modified outside of pkg 2, identified by path filter",
"emoji": ":book: pkg2 docs modified outside of pkg 2, identified by path filter",
"scipy": "DOC: pkg2 docs modified outside of pkg 2, identified by path filter",
"datetime": next(commit_timestamp_gen),
"include_in_changelog": True,
"file_to_change": f"../../{monorepo_pkg2_docs_dir}/index.rst",
},
],
commit_type,
Expand Down
Loading
Loading