Skip to content

Commit

Permalink
Add an option to check staged files (#677)
Browse files Browse the repository at this point in the history
  • Loading branch information
twm authored Dec 5, 2024
2 parents 0a5bc32 + de88e69 commit f338f7b
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 25 deletions.
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

0 comments on commit f338f7b

Please sign in to comment.