From ec4ee4407fed0f8d08e24e4bb02f6e0927887e0b Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sat, 7 Jan 2023 10:39:12 +0200 Subject: [PATCH] Read `--stdin-filename=` content from stdin --- src/darker/__main__.py | 46 +++-- src/darker/git.py | 53 ++++- src/darker/linting.py | 6 +- src/darker/tests/test_git.py | 192 +++++++++++++----- src/darker/tests/test_linting.py | 4 +- src/darker/tests/test_main.py | 82 +++++++- .../tests/test_main_blacken_single_file.py | 16 +- 7 files changed, 311 insertions(+), 88 deletions(-) diff --git a/src/darker/__main__.py b/src/darker/__main__.py index 500ad8170..3692ed3a6 100644 --- a/src/darker/__main__.py +++ b/src/darker/__main__.py @@ -24,6 +24,7 @@ from darker.exceptions import DependencyError, MissingPackageError from darker.git import ( PRE_COMMIT_FROM_TO_REFS, + STDIN, WORKTREE, EditedLinenumsDiffer, RevisionRange, @@ -127,9 +128,12 @@ def _isort_and_blacken_single_file( # pylint: disable=too-many-arguments # With VSCode, `relative_path_in_rev2` may be a `.py..tmp` file in the # working tree instead of a `.py` file. absolute_path_in_rev2 = root / relative_path_in_rev2 - rev2_content = git_get_content_at_revision( - relative_path_in_rev2, revrange.rev2, root - ) + if revrange.rev2 == STDIN: + rev2_content = TextDocument.from_bytes(sys.stdin.buffer.read()) + else: + rev2_content = git_get_content_at_revision( + relative_path_in_rev2, revrange.rev2, root + ) # 1. run isort rev2_isorted = apply_isort( rev2_content, @@ -394,10 +398,16 @@ def main( # pylint: disable=too-many-locals,too-many-branches,too-many-statemen if args.skip_magic_trailing_comma is not None: black_config["skip_magic_trailing_comma"] = args.skip_magic_trailing_comma - paths = {Path(p) for p in args.src} + stdin_mode = args.stdin_filename is not None + if stdin_mode: + paths = {Path(args.stdin_filename)} + # `parse_command_line` guarantees that `args.src` is empty + else: + paths = {Path(p) for p in args.src} + # `parse_command_line` guarantees that `args.stdin_filename` is `None` root = get_common_root(paths) - revrange = RevisionRange.parse_with_common_ancestor(args.revision, root) + revrange = RevisionRange.parse_with_common_ancestor(args.revision, root, stdin_mode) output_mode = OutputMode.from_args(args) write_modified_files = not args.check and output_mode == OutputMode.NOTHING if write_modified_files: @@ -414,21 +424,25 @@ def main( # pylint: disable=too-many-locals,too-many-branches,too-many-statemen " Either --diff or --check must be used.", ) - missing = get_missing_at_revision(paths, revrange.rev2, root) - if missing: - missing_reprs = " ".join(repr(str(path)) for path in missing) - rev2_repr = "the working tree" if revrange.rev2 == WORKTREE else revrange.rev2 - raise ArgumentError( - Action(["PATH"], "path"), - f"Error: Path(s) {missing_reprs} do not exist in {rev2_repr}", - ) + if revrange.rev2 != STDIN: + missing = get_missing_at_revision(paths, revrange.rev2, root) + if missing: + missing_reprs = " ".join(repr(str(path)) for path in missing) + rev2_repr = ( + "the working tree" if revrange.rev2 == WORKTREE else revrange.rev2 + ) + raise ArgumentError( + Action(["PATH"], "path"), + f"Error: Path(s) {missing_reprs} do not exist in {rev2_repr}", + ) # These are absolute paths: files_to_process = filter_python_files(paths, root, {}) files_to_blacken = filter_python_files(paths, root, black_config) - if output_mode == OutputMode.CONTENT: - # With `-d` / `--stdout`, process the file whether modified or not. Paths have - # previously been validated to contain exactly one existing file. + if output_mode == OutputMode.CONTENT or revrange.rev2 == STDIN: + # With `-d` / `--stdout` and `--stdin-filename`, process the file whether + # modified or not. Paths have previously been validated to contain exactly one + # existing file. changed_files_to_process = { p.resolve().relative_to(root) for p in files_to_process } diff --git a/src/darker/git.py b/src/darker/git.py index 41bf4bf2c..37d9f00e4 100644 --- a/src/darker/git.py +++ b/src/darker/git.py @@ -41,6 +41,7 @@ # - referring to the `PRE_COMMIT_FROM_REF` and `PRE_COMMIT_TO_REF` environment variables # for determining the revision range WORKTREE = ":WORKTREE:" +STDIN = ":STDIN:" PRE_COMMIT_FROM_TO_REFS = ":PRE-COMMIT:" @@ -149,34 +150,55 @@ class RevisionRange: @classmethod def parse_with_common_ancestor( - cls, revision_range: str, cwd: Path + cls, revision_range: str, cwd: Path, stdin_mode: bool ) -> "RevisionRange": """Convert a range expression to a ``RevisionRange`` object If the expression contains triple dots (e.g. ``master...HEAD``), finds the common ancestor of the two revisions and uses that as the first revision. + :param revision_range: The revision range as a string to parse + :param cwd: The working directory to use if invoking Git + :param stdin_mode: If `True`, the default for ``rev2`` is ``:STDIN:`` + :return: The range parsed into a `RevisionRange` object + """ - rev1, rev2, use_common_ancestor = cls._parse(revision_range) + rev1, rev2, use_common_ancestor = cls._parse(revision_range, stdin_mode) if use_common_ancestor: return cls._with_common_ancestor(rev1, rev2, cwd) return cls(rev1, rev2) @staticmethod - def _parse(revision_range: str) -> Tuple[str, str, bool]: + def _parse(revision_range: str, stdin_mode: bool) -> Tuple[str, str, bool]: """Convert a range expression to revisions, using common ancestor if appropriate - >>> RevisionRange._parse("a..b") + A `ValueError` is raised if ``--stdin-filename`` is used by the revision range + is ``:PRE-COMMIT:`` or the end of the range is not ``:STDIN:``. + + :param revision_range: The revision range as a string to parse + :param stdin_mode: If `True`, the default for ``rev2`` is ``:STDIN:`` + :return: The range parsed into a `RevisionRange` object + :raise: ValueError + + >>> RevisionRange._parse("a..b", stdin_mode=False) ('a', 'b', False) - >>> RevisionRange._parse("a...b") + >>> RevisionRange._parse("a...b", stdin_mode=False) ('a', 'b', True) - >>> RevisionRange._parse("a..") + >>> RevisionRange._parse("a..", stdin_mode=False) ('a', ':WORKTREE:', False) - >>> RevisionRange._parse("a...") + >>> RevisionRange._parse("a...", stdin_mode=False) ('a', ':WORKTREE:', True) + >>> RevisionRange._parse("a..", stdin_mode=True) + ('a', ':STDIN:', False) + >>> RevisionRange._parse("a...", stdin_mode=True) + ('a', ':STDIN:', True) """ if revision_range == PRE_COMMIT_FROM_TO_REFS: + if stdin_mode: + raise ValueError( + f"With --stdin-filename, revision {revision_range!r} is not allowed" + ) try: return ( os.environ["PRE_COMMIT_FROM_REF"], @@ -187,16 +209,27 @@ def _parse(revision_range: str) -> Tuple[str, str, bool]: # Fallback to running against HEAD revision_range = "HEAD" match = COMMIT_RANGE_RE.match(revision_range) + default_rev2 = STDIN if stdin_mode else WORKTREE if match: rev1, range_dots, rev2 = match.groups() use_common_ancestor = range_dots == "..." - return (rev1 or "HEAD", rev2 or WORKTREE, use_common_ancestor) - return (revision_range or "HEAD", WORKTREE, revision_range not in ["", "HEAD"]) + effective_rev2 = rev2 or default_rev2 + if stdin_mode and effective_rev2 != STDIN: + raise ValueError( + f"With --stdin-filename, rev2 in {revision_range} must be" + f" {STDIN!r}, not {effective_rev2!r}" + ) + return (rev1 or "HEAD", rev2 or default_rev2, use_common_ancestor) + return ( + revision_range or "HEAD", + default_rev2, + revision_range not in ["", "HEAD"], + ) @classmethod def _with_common_ancestor(cls, rev1: str, rev2: str, cwd: Path) -> "RevisionRange": """Find common ancestor for revisions and return a ``RevisionRange`` object""" - rev2_for_merge_base = "HEAD" if rev2 == WORKTREE else rev2 + rev2_for_merge_base = "HEAD" if rev2 in [WORKTREE, STDIN] else rev2 merge_base_cmd = ["merge-base", rev1, rev2_for_merge_base] common_ancestor = _git_check_output_lines(merge_base_cmd, cwd)[0] rev1_hash = _git_check_output_lines(["show", "-s", "--pretty=%H", rev1], cwd)[0] diff --git a/src/darker/linting.py b/src/darker/linting.py index 7932f5d3c..69145bc96 100644 --- a/src/darker/linting.py +++ b/src/darker/linting.py @@ -25,7 +25,7 @@ from subprocess import PIPE, Popen # nosec from typing import IO, Generator, List, Set, Tuple -from darker.git import WORKTREE, EditedLinenumsDiffer, RevisionRange +from darker.git import STDIN, WORKTREE, EditedLinenumsDiffer, RevisionRange from darker.highlighting import colorize logger = logging.getLogger(__name__) @@ -232,6 +232,10 @@ def run_linters( :return: Total number of linting errors found on modified lines """ + if linter_cmdlines and revrange.rev2 == STDIN: + raise NotImplementedError( + "The -l/--lint option isn't yet available with --stdin-filename" + ) # 10. run linter subprocesses for all edited files (10.-13. optional) # 11. diff the given revision and worktree (after isort and Black reformatting) # for each file reported by a linter diff --git a/src/darker/tests/test_git.py b/src/darker/tests/test_git.py index 8956baa25..6a7ef3a0e 100644 --- a/src/darker/tests/test_git.py +++ b/src/darker/tests/test_git.py @@ -1,6 +1,7 @@ """Unit tests for :mod:`darker.git`""" -# pylint: disable=redefined-outer-name,protected-access,too-many-arguments +# pylint: disable=protected-access,redefined-outer-name,too-many-arguments +# pylint: disable=too-many-lines import os import re @@ -15,7 +16,7 @@ from darker import git from darker.tests.conftest import GitRepoFixture -from darker.tests.helpers import raises_or_matches +from darker.tests.helpers import raises_if_exception, raises_or_matches from darker.utils import GIT_DATEFORMAT, TextDocument @@ -117,27 +118,71 @@ def test_git_get_content_at_revision(git_repo, revision, expect_lines, expect_mt assert result.encoding == "utf-8" -@pytest.mark.parametrize( - "revision_range, expect", - [ - ("", ("HEAD", ":WORKTREE:", False)), - ("HEAD", ("HEAD", ":WORKTREE:", False)), - ("a", ("a", ":WORKTREE:", True)), - ("a..", ("a", ":WORKTREE:", False)), - ("a...", ("a", ":WORKTREE:", True)), - ("..HEAD", ("HEAD", "HEAD", False)), - ("...HEAD", ("HEAD", "HEAD", True)), - ("a..HEAD", ("a", "HEAD", False)), - ("a...HEAD", ("a", "HEAD", True)), - ("a..b", ("a", "b", False)), - ("a...b", ("a", "b", True)), - ], +@pytest.mark.kwparametrize( + dict(revision_range="", stdin_mode=False, expect=("HEAD", ":WORKTREE:", False)), + dict(revision_range="HEAD", stdin_mode=False, expect=("HEAD", ":WORKTREE:", False)), + dict(revision_range="a", stdin_mode=False, expect=("a", ":WORKTREE:", True)), + dict(revision_range="a..", stdin_mode=False, expect=("a", ":WORKTREE:", False)), + dict(revision_range="a...", stdin_mode=False, expect=("a", ":WORKTREE:", True)), + dict(revision_range="..HEAD", stdin_mode=False, expect=("HEAD", "HEAD", False)), + dict(revision_range="...HEAD", stdin_mode=False, expect=("HEAD", "HEAD", True)), + dict(revision_range="a..HEAD", stdin_mode=False, expect=("a", "HEAD", False)), + dict(revision_range="a...HEAD", stdin_mode=False, expect=("a", "HEAD", True)), + dict(revision_range="a..b", stdin_mode=False, expect=("a", "b", False)), + dict(revision_range="a...b", stdin_mode=False, expect=("a", "b", True)), + dict(revision_range="", stdin_mode=True, expect=("HEAD", ":STDIN:", False)), + dict(revision_range="HEAD", stdin_mode=True, expect=("HEAD", ":STDIN:", False)), + dict(revision_range="a", stdin_mode=True, expect=("a", ":STDIN:", True)), + dict(revision_range="a..", stdin_mode=True, expect=("a", ":STDIN:", False)), + dict(revision_range="a...", stdin_mode=True, expect=("a", ":STDIN:", True)), + dict( + revision_range="..HEAD", + stdin_mode=True, + expect=ValueError( + "With --stdin-filename, rev2 in ..HEAD must be ':STDIN:', not 'HEAD'" + ), + ), + dict( + revision_range="...HEAD", + stdin_mode=True, + expect=ValueError( + "With --stdin-filename, rev2 in ...HEAD must be ':STDIN:', not 'HEAD'" + ), + ), + dict( + revision_range="a..HEAD", + stdin_mode=True, + expect=ValueError( + "With --stdin-filename, rev2 in a..HEAD must be ':STDIN:', not 'HEAD'" + ), + ), + dict( + revision_range="a...HEAD", + stdin_mode=True, + expect=ValueError( + "With --stdin-filename, rev2 in a...HEAD must be ':STDIN:', not 'HEAD'" + ), + ), + dict( + revision_range="a..b", + stdin_mode=True, + expect=ValueError( + "With --stdin-filename, rev2 in a..b must be ':STDIN:', not 'b'" + ), + ), + dict( + revision_range="a...b", + stdin_mode=True, + expect=ValueError( + "With --stdin-filename, rev2 in a...b must be ':STDIN:', not 'b'" + ), + ), ) -def test_revisionrange_parse(revision_range, expect): +def test_revisionrange_parse(revision_range, stdin_mode, expect): """Test for :meth:`RevisionRange.parse`""" - result = git.RevisionRange._parse(revision_range) + with raises_or_matches(expect, ["args"]) as check: - assert result == expect + check(git.RevisionRange._parse(revision_range, stdin_mode)) def git_call(cmd, encoding=None): @@ -206,23 +251,40 @@ def test_git_get_content_at_revision_obtain_file_content( @pytest.mark.kwparametrize( - dict(revrange="HEAD", expect="HEAD..:WORKTREE:"), - dict(revrange="{initial}", expect="{initial}..:WORKTREE:"), - dict(revrange="{initial}..", expect="{initial}..:WORKTREE:"), - dict(revrange="{initial}..HEAD", expect="{initial}..HEAD"), - dict(revrange="{initial}..feature", expect="{initial}..feature"), - dict(revrange="{initial}...", expect="{initial}..:WORKTREE:"), - dict(revrange="{initial}...HEAD", expect="{initial}..HEAD"), - dict(revrange="{initial}...feature", expect="{initial}..feature"), - dict(revrange="master", expect="{initial}..:WORKTREE:"), - dict(revrange="master..", expect="master..:WORKTREE:"), - dict(revrange="master..HEAD", expect="master..HEAD"), - dict(revrange="master..feature", expect="master..feature"), - dict(revrange="master...", expect="{initial}..:WORKTREE:"), - dict(revrange="master...HEAD", expect="{initial}..HEAD"), - dict(revrange="master...feature", expect="{initial}..feature"), + dict(revrange="HEAD", stdin_mode=False, expect="HEAD..:WORKTREE:"), + dict(revrange="{initial}", stdin_mode=False, expect="{initial}..:WORKTREE:"), + dict(revrange="{initial}..", stdin_mode=False, expect="{initial}..:WORKTREE:"), + dict(revrange="{initial}..HEAD", stdin_mode=False, expect="{initial}..HEAD"), + dict(revrange="{initial}..feature", stdin_mode=False, expect="{initial}..feature"), + dict(revrange="{initial}...", stdin_mode=False, expect="{initial}..:WORKTREE:"), + dict(revrange="{initial}...HEAD", stdin_mode=False, expect="{initial}..HEAD"), + dict(revrange="{initial}...feature", stdin_mode=False, expect="{initial}..feature"), + dict(revrange="master", stdin_mode=False, expect="{initial}..:WORKTREE:"), + dict(revrange="master..", stdin_mode=False, expect="master..:WORKTREE:"), + dict(revrange="master..HEAD", stdin_mode=False, expect="master..HEAD"), + dict(revrange="master..feature", stdin_mode=False, expect="master..feature"), + dict(revrange="master...", stdin_mode=False, expect="{initial}..:WORKTREE:"), + dict(revrange="master...HEAD", stdin_mode=False, expect="{initial}..HEAD"), + dict(revrange="master...feature", stdin_mode=False, expect="{initial}..feature"), + dict(revrange="HEAD", stdin_mode=True, expect="HEAD..:STDIN:"), + dict(revrange="{initial}", stdin_mode=True, expect="{initial}..:STDIN:"), + dict(revrange="{initial}..", stdin_mode=True, expect="{initial}..:STDIN:"), + dict(revrange="{initial}..HEAD", stdin_mode=True, expect=ValueError), + dict(revrange="{initial}..feature", stdin_mode=True, expect=ValueError), + dict(revrange="{initial}...", stdin_mode=True, expect="{initial}..:STDIN:"), + dict(revrange="{initial}...HEAD", stdin_mode=True, expect=ValueError), + dict(revrange="{initial}...feature", stdin_mode=True, expect=ValueError), + dict(revrange="master", stdin_mode=True, expect="{initial}..:STDIN:"), + dict(revrange="master..", stdin_mode=True, expect="master..:STDIN:"), + dict(revrange="master..HEAD", stdin_mode=True, expect=ValueError), + dict(revrange="master..feature", stdin_mode=True, expect=ValueError), + dict(revrange="master...", stdin_mode=True, expect="{initial}..:STDIN:"), + dict(revrange="master...HEAD", stdin_mode=True, expect=ValueError), + dict(revrange="master...feature", stdin_mode=True, expect=ValueError), ) -def test_revisionrange_parse_with_common_ancestor(git_repo, revrange, expect): +def test_revisionrange_parse_with_common_ancestor( + git_repo, revrange, stdin_mode, expect +): """``_git_get_old_revision()`` gets common ancestor using Git when necessary""" git_repo.add({"a": "i"}, commit="Initial commit") initial = git_repo.get_hash() @@ -230,14 +292,15 @@ def test_revisionrange_parse_with_common_ancestor(git_repo, revrange, expect): master = git_repo.get_hash() git_repo.create_branch("feature", initial) git_repo.add({"a": "f"}, commit="in feature") + with raises_if_exception(expect): - result = git.RevisionRange.parse_with_common_ancestor( - revrange.format(initial=initial), git_repo.root - ) + result = git.RevisionRange.parse_with_common_ancestor( + revrange.format(initial=initial), git_repo.root, stdin_mode + ) - rev1, rev2 = expect.format(initial=initial, master=master).split("..") - assert result.rev1 == rev1 - assert result.rev2 == rev2 + rev1, rev2 = expect.format(initial=initial, master=master).split("..") + assert result.rev1 == rev1 + assert result.rev2 == rev2 @pytest.mark.kwparametrize( @@ -780,7 +843,9 @@ def test_git_get_modified_python_files_revision_range( """Test for :func:`darker.git.git_get_modified_python_files` with revision range""" result = git.git_get_modified_python_files( [Path(branched_repo.root)], - git.RevisionRange.parse_with_common_ancestor(revrange, branched_repo.root), + git.RevisionRange.parse_with_common_ancestor( + revrange, branched_repo.root, stdin_mode=False + ), Path(branched_repo.root), ) @@ -789,22 +854,18 @@ def test_git_get_modified_python_files_revision_range( @pytest.mark.kwparametrize( dict( - environ={}, expect_rev1="HEAD", expect_rev2=":WORKTREE:", - expect_use_common_ancestor=False, ), dict( environ={"PRE_COMMIT_FROM_REF": "old"}, expect_rev1="HEAD", expect_rev2=":WORKTREE:", - expect_use_common_ancestor=False, ), dict( environ={"PRE_COMMIT_TO_REF": "new"}, expect_rev1="HEAD", expect_rev2=":WORKTREE:", - expect_use_common_ancestor=False, ), dict( environ={"PRE_COMMIT_FROM_REF": "old", "PRE_COMMIT_TO_REF": "new"}, @@ -812,14 +873,45 @@ def test_git_get_modified_python_files_revision_range( expect_rev2="new", expect_use_common_ancestor=True, ), + dict( + stdin_mode=True, + expect_rev1=ValueError( + "With --stdin-filename, revision ':PRE-COMMIT:' is not allowed" + ), + ), + dict( + environ={"PRE_COMMIT_FROM_REF": "old"}, + stdin_mode=True, + expect_rev1=ValueError( + "With --stdin-filename, revision ':PRE-COMMIT:' is not allowed" + ), + ), + dict( + environ={"PRE_COMMIT_TO_REF": "new"}, + stdin_mode=True, + expect_rev1=ValueError( + "With --stdin-filename, revision ':PRE-COMMIT:' is not allowed" + ), + ), + dict( + environ={"PRE_COMMIT_FROM_REF": "old", "PRE_COMMIT_TO_REF": "new"}, + stdin_mode=True, + expect_rev1=ValueError( + "With --stdin-filename, revision ':PRE-COMMIT:' is not allowed" + ), + ), + environ={}, + stdin_mode=False, + expect_rev2=None, + expect_use_common_ancestor=False, ) def test_revisionrange_parse_pre_commit( - environ, expect_rev1, expect_rev2, expect_use_common_ancestor + environ, stdin_mode, expect_rev1, expect_rev2, expect_use_common_ancestor ): """RevisionRange._parse(':PRE-COMMIT:') gets the range from environment variables""" - with patch.dict(os.environ, environ): + with patch.dict(os.environ, environ), raises_if_exception(expect_rev1): - result = git.RevisionRange._parse(":PRE-COMMIT:") + result = git.RevisionRange._parse(":PRE-COMMIT:", stdin_mode) assert result == (expect_rev1, expect_rev2, expect_use_common_ancestor) diff --git a/src/darker/tests/test_linting.py b/src/darker/tests/test_linting.py index 573ae6039..685ac1d4c 100644 --- a/src/darker/tests/test_linting.py +++ b/src/darker/tests/test_linting.py @@ -268,7 +268,9 @@ def test_run_linter_non_worktree(): "dummy-linter", Path("/dummy"), {Path("dummy.py")}, - RevisionRange.parse_with_common_ancestor("..HEAD", Path("dummy cwd")), + RevisionRange.parse_with_common_ancestor( + "..HEAD", Path("dummy cwd"), stdin_mode=False + ), use_color=False, ) diff --git a/src/darker/tests/test_main.py b/src/darker/tests/test_main.py index b42cda82c..bb0e8026e 100644 --- a/src/darker/tests/test_main.py +++ b/src/darker/tests/test_main.py @@ -7,10 +7,11 @@ import logging import re from argparse import ArgumentError +from io import BytesIO from pathlib import Path from textwrap import dedent from types import SimpleNamespace -from unittest.mock import call, patch +from unittest.mock import Mock, call, patch import pytest @@ -193,6 +194,83 @@ def test_format_edited_parts( assert changes == expect_changes +@pytest.mark.kwparametrize( + dict( + rev1="HEAD", + rev2=":STDIN:", + expect=[ + ( + "a.py", + ("print('a.py HEAD' )", "#", "print( 'a.py STDIN')"), + ("print('a.py HEAD' )", "#", 'print("a.py STDIN")'), + ) + ], + ), + dict( + rev1=":WORKTREE:", + rev2=":STDIN:", + expect=[ + ( + "a.py", + ("print('a.py :WORKTREE:' )", "#", "print( 'a.py STDIN')"), + ("print('a.py :WORKTREE:' )", "#", 'print("a.py STDIN")'), + ) + ], + ), + dict( + rev1="HEAD", + rev2=":WORKTREE:", + expect=[ + ( + "a.py", + ("print('a.py :WORKTREE:' )", "#", "print( 'a.py HEAD')"), + ('print("a.py :WORKTREE:")', "#", "print( 'a.py HEAD')"), + ) + ], + ), +) +@pytest.mark.parametrize("newline", ["\n", "\r\n"], ids=["unix", "windows"]) +def test_format_edited_parts_stdin(git_repo, newline, rev1, rev2, expect): + """`format_edited_parts` with ``--stdin-filename``""" + n = newline # pylint: disable=invalid-name + paths = git_repo.add( + { + "a.py": f"print('a.py HEAD' ){n}#{n}print( 'a.py HEAD'){n}", + "b.py": f"print('b.py HEAD' ){n}#{n}print( 'b.py HEAD'){n}", + }, + commit="Initial commit", + ) + paths["a.py"].write_bytes( + f"print('a.py :WORKTREE:' ){n}#{n}print( 'a.py HEAD'){n}".encode("ascii") + ) + paths["b.py"].write_bytes( + f"print('b.py HEAD' ){n}#{n}print( 'b.py WORKTREE'){n}".encode("ascii") + ) + stdin = f"print('a.py {rev1}' ){n}#{n}print( 'a.py STDIN'){n}".encode("ascii") + with patch.object( + darker.__main__.sys, # type: ignore[attr-defined] + "stdin", + Mock(buffer=BytesIO(stdin)), + ): + + result = list( + darker.__main__.format_edited_parts( + Path(git_repo.root), + {Path("a.py")}, + Exclusions(black=(), isort=()), + RevisionRange(rev1, rev2), + {}, + report_unmodified=False, + ) + ) + + expect = [ + (paths[path], TextDocument.from_lines(before), TextDocument.from_lines(after)) + for path, before, after in expect + ] + assert result == expect + + def test_format_edited_parts_all_unchanged(git_repo, monkeypatch): """``format_edited_parts()`` yields nothing if no reformatting was needed""" monkeypatch.chdir(git_repo.root) @@ -421,7 +499,7 @@ def test_blacken_single_file( ), dict( arguments=["--check", "--lint", "echo subdir/a.py:1: message"], - # Windows compatible path assertion using `pathlib.Path()` + # Windows compatible path assertio_using `pathlib.Path()` expect_stdout=["", f"subdir/a.py:1: message {Path('/subdir/a.py')}"], expect_retval=1, ), diff --git a/src/darker/tests/test_main_blacken_single_file.py b/src/darker/tests/test_main_blacken_single_file.py index 3c6abce75..3d330a892 100644 --- a/src/darker/tests/test_main_blacken_single_file.py +++ b/src/darker/tests/test_main_blacken_single_file.py @@ -54,15 +54,15 @@ def test_blacken_single_file_common_ancestor(git_repo): git_repo.create_branch("feature", initial) git_repo.add({"a.py": a_py_feature}, commit="on feature") worktree = TextDocument.from_str(a_py_worktree) + revrange = RevisionRange.parse_with_common_ancestor( + "master...", git_repo.root, stdin_mode=False + ) result = darker.__main__._blacken_single_file( git_repo.root, Path("a.py"), Path("a.py"), - EditedLinenumsDiffer( - git_repo.root, - RevisionRange.parse_with_common_ancestor("master...", git_repo.root), - ), + EditedLinenumsDiffer(git_repo.root, revrange), rev2_content=worktree, rev2_isorted=worktree, has_isort_changes=False, @@ -112,15 +112,15 @@ def docstring_func(): ) paths = git_repo.add({"a.py": initial}, commit="Initial commit") paths["a.py"].write_text(modified) + revrange = RevisionRange.parse_with_common_ancestor( + "HEAD..", git_repo.root, stdin_mode=False + ) result = darker.__main__._blacken_single_file( git_repo.root, Path("a.py"), Path("a.py"), - EditedLinenumsDiffer( - git_repo.root, - RevisionRange.parse_with_common_ancestor("HEAD..", git_repo.root), - ), + EditedLinenumsDiffer(git_repo.root, revrange), rev2_content=TextDocument.from_str(modified), rev2_isorted=TextDocument.from_str(modified), has_isort_changes=False,