Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add an option to check staged files #677

Merged
merged 6 commits into from
Dec 5, 2024
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
7 changes: 7 additions & 0 deletions docs/cli.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,10 @@ By default, ``towncrier`` compares the current branch against ``origin/main`` (a
Use ``REMOTE-BRANCH`` instead of ``origin/main``::

$ towncrier check --compare-with origin/trunk

.. option:: --staged

Include files that have been staged for commit when checking for news fragments::

$ towncrier check --staged
$ towncrier check --staged --compare-with origin/trunk
15 changes: 12 additions & 3 deletions src/towncrier/_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,22 @@ def get_remote_branches(base_directory: str) -> list[str]:


def list_changed_files_compared_to_branch(
base_directory: str, compare_with: str
base_directory: str, compare_with: str, include_staged: bool
) -> list[str]:
output = check_output(
["git", "diff", "--name-only", compare_with + "..."],
cwd=base_directory,
encoding="utf-8",
stderr=STDOUT,
)

return output.strip().splitlines()
filenames = output.strip().splitlines()
if include_staged:
output = check_output(
["git", "diff", "--name-only", "--cached"],
cwd=base_directory,
encoding="utf-8",
stderr=STDOUT,
)
filenames.extend(output.strip().splitlines())

return filenames
20 changes: 16 additions & 4 deletions src/towncrier/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,27 @@ def _get_default_compare_branch(branches: Container[str]) -> str | None:
metavar="FILE_PATH",
help=config_option_help,
)
def _main(compare_with: str | None, directory: str | None, config: str | None) -> None:
@click.option(
"--staged",
"staged",
is_flag=True,
default=False,
help="Include staged files as part of the branch checked in the --compare-with",
)
def _main(
compare_with: str | None, directory: str | None, config: str | None, staged: bool
) -> None:
"""
Check for new fragments on a branch.
"""
__main(compare_with, directory, config)
__main(compare_with, directory, config, staged)


def __main(
comparewith: str | None, directory: str | None, config_path: str | None
comparewith: str | None,
directory: str | None,
config_path: str | None,
staged: bool,
) -> None:
base_directory, config = load_config_from_options(directory, config_path)

Expand All @@ -80,7 +92,7 @@ def __main(

try:
files_changed = list_changed_files_compared_to_branch(
base_directory, comparewith
base_directory, comparewith, staged
)
except CalledProcessError as e:
click.echo("git produced output while failing:")
Expand Down
1 change: 1 addition & 0 deletions src/towncrier/newsfragments/676.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
The `towncrier check` command now has a `--staged` flag to inspect the files staged for commit when checking for a news fragment: useful in a pre-commit hook
82 changes: 64 additions & 18 deletions src/towncrier/test/test_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import warnings

from pathlib import Path
from subprocess import call
from subprocess import check_call

from click.testing import CliRunner
from twisted.trial.unittest import TestCase
Expand All @@ -28,7 +28,7 @@ def create_project(
setup_simple_project(pyproject_path=pyproject_path, extra_config=extra_config)
Path("foo/newsfragments/123.feature").write_text("Adds levitation")
initial_commit(branch=main_branch)
call(["git", "checkout", "-b", "otherbranch"])
check_call(["git", "checkout", "-b", "otherbranch"])


def commit(message):
Expand All @@ -37,8 +37,17 @@ def commit(message):
There must be uncommitted changes otherwise git will complain:
"nothing to commit, working tree clean"
"""
call(["git", "add", "."])
call(["git", "commit", "-m", message])
check_call(["git", "add", "."])
check_call(["git", "commit", "-m", message])


def stage():
"""Stage a commit to the repo in the current working directory

There must be uncommitted changes otherwise git will complain:
"nothing to commit, working tree clean"
"""
check_call(["git", "add", "."])


def initial_commit(branch="main"):
Expand All @@ -50,11 +59,11 @@ def initial_commit(branch="main"):
"""
# --initial-branch is explicitly set to `main` because
# git has deprecated the default branch name.
call(["git", "init", f"--initial-branch={branch}"])
check_call(["git", "init", f"--initial-branch={branch}"])
# Without ``git config` user.name and user.email `git commit` fails
# unless the settings are set globally
call(["git", "config", "user.name", "user"])
call(["git", "config", "user.email", "[email protected]"])
check_call(["git", "config", "user.name", "user"])
check_call(["git", "config", "user.email", "[email protected]"])
commit("Initial Commit")


Expand Down Expand Up @@ -156,8 +165,8 @@ def test_fragment_missing(self):
with open(file_path, "w") as f:
f.write("import os")

call(["git", "add", "foo/somefile.py"])
call(["git", "commit", "-m", "add a file"])
check_call(["git", "add", "foo/somefile.py"])
check_call(["git", "commit", "-m", "add a file"])

result = runner.invoke(towncrier_check, ["--compare-with", "master"])

Expand Down Expand Up @@ -204,6 +213,41 @@ def test_fragment_exists_but_not_in_check(self):
(result.output, str(fragment_path)),
)

def test_fragment_exists_and_staged(self):
"""A fragment exists and is added in staging. Pass only if staging on the command line"""
runner = CliRunner()

with runner.isolated_filesystem():
create_project(
"pyproject.toml",
main_branch="master",
extra_config="[[tool.towncrier.type]]\n"
'directory = "feature"\n'
'name = "Features"\n'
"showcontent = true\n"
"[[tool.towncrier.type]]\n"
'directory = "sut"\n'
'name = "System Under Test"\n'
"showcontent = true\n",
)

file_path = "foo/somefile.py"
write(file_path, "import os")

commit("add some files for test initialization")

fragment_path = Path("foo/newsfragments/1234.feature").absolute()
write(fragment_path, "Adds gravity back")
stage()

result = runner.invoke(towncrier_check, ["--compare-with", "master"])

self.assertEqual(1, result.exit_code)
result = runner.invoke(
towncrier_check, ["--staged", "--compare-with", "master"]
)
self.assertEqual(0, result.exit_code)

def test_fragment_exists_and_in_check(self):
"""
A fragment that exists but is not marked as check=False is
Expand Down Expand Up @@ -254,8 +298,8 @@ def test_none_stdout_encoding_works(self):
with open(fragment_path, "w") as f:
f.write("Adds gravity back")

call(["git", "add", fragment_path])
call(["git", "commit", "-m", "add a newsfragment"])
check_call(["git", "add", fragment_path])
check_call(["git", "commit", "-m", "add a newsfragment"])

runner = CliRunner(mix_stderr=False)
result = runner.invoke(towncrier_check, ["--compare-with", "master"])
Expand Down Expand Up @@ -310,16 +354,18 @@ def test_release_branch(self):
commit("First release")
# The news file is now created.
self.assertIn("NEWS.rst", os.listdir("."))
call(["git", "checkout", "main"])
call(["git", "merge", "otherbranch", "-m", "Sync release in main branch."])
check_call(["git", "checkout", "main"])
check_call(
["git", "merge", "otherbranch", "-m", "Sync release in main branch."]
)

# We have a new feature branch that has a news fragment that
# will be merged to the main branch.
call(["git", "checkout", "-b", "new-feature-branch"])
check_call(["git", "checkout", "-b", "new-feature-branch"])
write("foo/newsfragments/456.feature", "Foo the bar")
commit("A feature in the second release.")
call(["git", "checkout", "main"])
call(
check_call(["git", "checkout", "main"])
check_call(
[
"git",
"merge",
Expand All @@ -330,7 +376,7 @@ def test_release_branch(self):
)

# We now have the new release branch.
call(["git", "checkout", "-b", "next-release"])
check_call(["git", "checkout", "-b", "next-release"])
runner.invoke(towncrier_build, ["--yes", "--version", "2.0"])
commit("Second release")

Expand Down Expand Up @@ -392,7 +438,7 @@ def test_in_different_dir_with_nondefault_newsfragments_directory(self, runner):
(subproject1 / "changelog.d").mkdir(parents=True)
(subproject1 / "changelog.d/123.feature").write_text("Adds levitation")
initial_commit(branch=main_branch)
call(["git", "checkout", "-b", "otherbranch"])
check_call(["git", "checkout", "-b", "otherbranch"])

# We add a code change but forget to add a news fragment.
write(subproject1 / "foo/somefile.py", "import os")
Expand Down
Loading