Skip to content

Commit

Permalink
fix(cmd-version): handle committing of git-ignored file gracefully (#764
Browse files Browse the repository at this point in the history
)

* fix(version): only commit non git-ignored files during version commit

* test(version): set version file as ignored file

Tweaks tests to use one committed change file and the version file
as an ignored change file. This allows us to verify that our commit
mechanism does not crash if a file that is changed is ignored by user
  • Loading branch information
codejedi365 committed Dec 12, 2023
1 parent 3a571d2 commit ea89fa7
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 19 deletions.
22 changes: 20 additions & 2 deletions semantic_release/cli/commands/version.py
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]:
"""
Expand Down
24 changes: 12 additions & 12 deletions tests/command_line/test_version.py
Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -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)
)
Expand Down Expand Up @@ -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
Expand All @@ -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("- ")]

Expand Down
4 changes: 2 additions & 2 deletions tests/const.py
Expand Up @@ -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'\""
Expand Down Expand Up @@ -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)
Expand Down
23 changes: 20 additions & 3 deletions tests/fixtures/example_project.py
Expand Up @@ -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()
Expand All @@ -35,18 +35,35 @@ 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:
print("Hello World")
'''
)
)
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"
Expand Down

0 comments on commit ea89fa7

Please sign in to comment.