diff --git a/semantic_release/cli/commands/version.py b/semantic_release/cli/commands/version.py index 52ce7df37..510f3997c 100644 --- a/semantic_release/cli/commands/version.py +++ b/semantic_release/cli/commands/version.py @@ -9,6 +9,7 @@ import click import shellingham # type: ignore[import] +from git.exc import GitCommandError from semantic_release.changelog import ReleaseHistory, environment, recursive_render from semantic_release.changelog.context import make_changelog_context @@ -369,7 +370,15 @@ def version( # noqa: C901 ) elif commit_changes: - repo.git.add(all_paths_to_add) + # TODO: in future this loop should be 1 line: + # repo.index.add(all_paths_to_add, force=False) + # but since 'force' is deliberally ineffective (as in docstring) in gitpython 3.1.18 + # we have to do manually add each filepath, and catch the exception if it is an ignored file + for updated_path in all_paths_to_add: + try: + repo.git.add(updated_path) + except GitCommandError: + log.warning("Failed to add path (%s) to index", updated_path) rh = ReleaseHistory.from_git_history( repo=repo, @@ -435,7 +444,16 @@ def version( # noqa: C901 ) elif commit_changes: # Anything changed here should be staged. - repo.git.add(updated_paths) + # TODO: in future this loop should be 1 line: + # repo.index.add(updated_paths, force=False) + # but since 'force' is deliberally ineffective (as in docstring) in gitpython 3.1.18 + # we have to do manually add each filepath, and catch the exception if it is an ignored file + for updated_path in updated_paths: + try: + repo.git.add(updated_path) + except GitCommandError: + log.warning("Failed to add path (%s) to index", updated_path) + def custom_git_environment() -> ContextManager[None]: """ diff --git a/tests/command_line/test_version.py b/tests/command_line/test_version.py index 2b39636b2..2bc6bdeda 100644 --- a/tests/command_line/test_version.py +++ b/tests/command_line/test_version.py @@ -389,7 +389,7 @@ def test_version_no_push_force_level( expected_new_version, example_project, example_pyproject_toml, - tmp_path_factory, + tmp_path_factory: pytest.TempPathFactory, cli_runner, ): tempdir = tmp_path_factory.mktemp("test_version") @@ -415,7 +415,7 @@ def test_version_no_push_force_level( # Changelog already reflects changes this should introduce assert differing_files == [ "pyproject.toml", - f"src/{EXAMPLE_PROJECT_NAME}/__init__.py", + f"src/{EXAMPLE_PROJECT_NAME}/_version.py", ] # Compare pyproject.toml @@ -434,14 +434,14 @@ def test_version_no_push_force_level( assert old_pyproject_toml == new_pyproject_toml assert new_version == expected_new_version - # Compare __init__.py + # Compare _version.py new_init_py = ( - (example_project / "src" / EXAMPLE_PROJECT_NAME / "__init__.py") + (example_project / "src" / EXAMPLE_PROJECT_NAME / "_version.py") .read_text(encoding="utf-8") .splitlines(keepends=True) ) old_init_py = ( - (tempdir / "src" / EXAMPLE_PROJECT_NAME / "__init__.py") + (tempdir / "src" / EXAMPLE_PROJECT_NAME / "_version.py") .read_text(encoding="utf-8") .splitlines(keepends=True) ) @@ -662,7 +662,7 @@ def test_version_only_update_files_no_git_actions( # Files that should receive version change assert differing_files == [ "pyproject.toml", - f"src/{EXAMPLE_PROJECT_NAME}/__init__.py", + f"src/{EXAMPLE_PROJECT_NAME}/_version.py", ] # Compare pyproject.toml @@ -681,20 +681,20 @@ def test_version_only_update_files_no_git_actions( assert old_pyproject_toml == new_pyproject_toml assert new_version == expected_new_version - # Compare __init__.py - new_init_py = ( - (example_project / "src" / EXAMPLE_PROJECT_NAME / "__init__.py") + # Compare _version.py + new_version_py = ( + (example_project / "src" / EXAMPLE_PROJECT_NAME / "_version.py") .read_text(encoding="utf-8") .splitlines(keepends=True) ) - old_init_py = ( - (tempdir / "src" / EXAMPLE_PROJECT_NAME / "__init__.py") + old_version_py = ( + (tempdir / "src" / EXAMPLE_PROJECT_NAME / "_version.py") .read_text(encoding="utf-8") .splitlines(keepends=True) ) d = difflib.Differ() - diff = list(d.compare(old_init_py, new_init_py)) + diff = list(d.compare(old_version_py, new_version_py)) added = [line[2:] for line in diff if line.startswith("+ ")] removed = [line[2:] for line in diff if line.startswith("- ")] diff --git a/tests/const.py b/tests/const.py index 1dcaa0756..90a7c6dec 100644 --- a/tests/const.py +++ b/tests/const.py @@ -180,7 +180,7 @@ [tool.semantic_release] version_variables = [ - "src/{EXAMPLE_PROJECT_NAME}/__init__.py:__version__", + "src/{EXAMPLE_PROJECT_NAME}/_version.py:__version__", ] version_toml = ["pyproject.toml:tool.poetry.version"] build_command = "bash -c \"echo 'Hello World'\"" @@ -375,7 +375,7 @@ def _read_long_description(): return None -with open("{EXAMPLE_PROJECT_NAME}/__init__.py", "r") as fd: +with open("{EXAMPLE_PROJECT_NAME}/_version.py", "r") as fd: version = re.search( r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', fd.read(), re.MULTILINE ).group(1) diff --git a/tests/fixtures/example_project.py b/tests/fixtures/example_project.py index 0309b9859..c0ec12c0a 100644 --- a/tests/fixtures/example_project.py +++ b/tests/fixtures/example_project.py @@ -26,7 +26,7 @@ def cd(path: Path) -> Generator[Path, None, None]: @pytest.fixture -def example_project(tmp_path): +def example_project(tmp_path: "Path") -> "Generator[Path, None, None]": with cd(tmp_path): src_dir = tmp_path / "src" src_dir.mkdir() @@ -35,11 +35,11 @@ def example_project(tmp_path): init_py = example_dir / "__init__.py" init_py.write_text( dedent( - f''' + ''' """ An example package with a very informative docstring """ - __version__ = "{EXAMPLE_PROJECT_VERSION}" + from ._version import __version__ def hello_world() -> None: @@ -47,6 +47,23 @@ def hello_world() -> None: ''' ) ) + version_py = example_dir / "_version.py" + version_py.write_text( + dedent( + f''' + __version__ = "{EXAMPLE_PROJECT_VERSION}" + ''' + ) + ) + gitignore = tmp_path / ".gitignore" + gitignore.write_text( + dedent( + f""" + *.pyc + /src/**/{version_py.name} + """ + ) + ) pyproject_toml = tmp_path / "pyproject.toml" pyproject_toml.write_text(EXAMPLE_PYPROJECT_TOML_CONTENT) setup_cfg = tmp_path / "setup.cfg"