Skip to content

Commit

Permalink
-r/--revision argument - what to compare to (#42)
Browse files Browse the repository at this point in the history
  • Loading branch information
akaihola committed Aug 7, 2020
1 parent 80ff94d commit f31b703
Show file tree
Hide file tree
Showing 8 changed files with 55 additions and 24 deletions.
3 changes: 3 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ Added

- ``--check`` returns 1 from the process but leaves files untouched if any file would
require reformatting
- ``-r <revision>`` / ``--revision <revision>`` can be used to specify the Git revision
to compare against when finding out which lines have been changed. Defaults to
``HEAD`` as before.

Fixed
-----
Expand Down
4 changes: 4 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ The following `command line arguments`_ can also be used to modify the defaults:
.. code-block:: shell
-r REVISION, --revision REVISION
Git revision against which to compare the working tree
--check Don't write the files back, just return the status.
Return code 0 means nothing would change. Return code
1 means some files would be reformatted.
Expand All @@ -126,6 +128,8 @@ The following `command line arguments`_ can also be used to modify the defaults:
*New in version 1.0.0:* isort_ is configured with ``-c`` and ``-l``, too.
*New in version 1.1.0:* The ``-r`` / ``--revision`` command line option.
*New in version 1.1.0:* The ``--check`` command line option.
.. _Black documentation about pyproject.toml: https://black.readthedocs.io/en/stable/pyproject_toml.html
Expand Down
9 changes: 5 additions & 4 deletions src/darker/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@


def format_edited_parts(
srcs: Iterable[Path], enable_isort: bool, black_args: BlackArgs
srcs: Iterable[Path], revision: str, enable_isort: bool, black_args: BlackArgs
) -> Generator[Tuple[Path, str, str, List[str]], None, None]:
"""Black (and optional isort) formatting for chunks with edits since the last commit
Expand All @@ -39,6 +39,7 @@ def format_edited_parts(
10. write the reformatted source back to the original file
:param srcs: Directories and files to re-format
:param revision: The Git revision against which to compare the working tree
:param enable_isort: ``True`` to also run ``isort`` first on each changed file
:param black_args: Command-line arguments to send to ``black.FileMode``
:return: A generator which yields details about changes for each file which should
Expand All @@ -47,7 +48,7 @@ def format_edited_parts(
"""
git_root = get_common_root(srcs)
changed_files = git_diff_name_only(srcs, git_root)
edited_linenums_differ = EditedLinenumsDiffer(git_root)
edited_linenums_differ = EditedLinenumsDiffer(git_root, revision)

for path_in_repo in changed_files:
src = git_root / path_in_repo
Expand All @@ -68,7 +69,7 @@ def format_edited_parts(
for context_lines in range(max_context_lines + 1):
# 2. diff HEAD and worktree for the file
# 3. extract line numbers in each edited to-file for changed lines
edited_linenums = edited_linenums_differ.head_vs_lines(
edited_linenums = edited_linenums_differ.revision_vs_lines(
path_in_repo, edited_lines, context_lines
)
if (
Expand Down Expand Up @@ -189,7 +190,7 @@ def main(argv: List[str] = None) -> int:
# We need both forms when showing diffs or modifying files.
# Pass them both on to avoid back-and-forth conversion.
for path, old_content, new_content, new_lines in format_edited_parts(
paths, args.isort, black_args
paths, args.revision, args.isort, black_args
):
some_files_changed = True
if args.diff:
Expand Down
6 changes: 6 additions & 0 deletions src/darker/command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ def parse_command_line(argv: List[str]) -> Namespace:
help="Path(s) to the Python source file(s) to reformat",
metavar="PATH",
)
parser.add_argument(
"-r",
"--revision",
default="HEAD",
help="Git revision against which to compare the working tree",
)
isort_help = ["Also sort imports using the `isort` package"]
if not isort:
isort_help.append(f". {ISORT_INSTRUCTION} to enable usage of this option.")
Expand Down
31 changes: 22 additions & 9 deletions src/darker/git.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
"""Helpers for listing modified files and getting unmodified content from Git"""

import logging
from dataclasses import dataclass, field
from pathlib import Path
from subprocess import check_output
from subprocess import CalledProcessError, check_output
from typing import Iterable, List, Set

from darker.diff import diff_and_get_opcodes, opcodes_to_edit_linenums

logger = logging.getLogger(__name__)


def git_get_unmodified_content(path: Path, cwd: Path) -> List[str]:
"""Get unmodified text lines of a file at Git HEAD
def git_get_unmodified_content(path: Path, revision: str, cwd: Path) -> List[str]:
"""Get unmodified text lines of a file at a Git revision
:param path: The relative path of the file in the Git repository
:param revision: The Git revision for which to get the file content
:param cwd: The root of the Git repository
"""
cmd = ["git", "show", f":./{path}"]
cmd = ["git", "show", f"{revision}:./{path}"]
logger.debug("[%s]$ %s", cwd, " ".join(cmd))
return check_output(cmd, cwd=str(cwd), encoding='utf-8').splitlines()
try:
return check_output(cmd, cwd=str(cwd), encoding='utf-8').splitlines()
except CalledProcessError as exc_info:
if exc_info.returncode == 128:
# The file didn't exist at the given revision. Act as if it was an empty
# file, so all current lines appear as edited.
return []
else:
raise


def should_reformat_file(path: Path) -> bool:
Expand Down Expand Up @@ -50,15 +60,18 @@ def git_diff_name_only(paths: Iterable[Path], cwd: Path) -> Set[Path]:
return {path for path in changed_paths if should_reformat_file(cwd / path)}


@dataclass(frozen=True)
class EditedLinenumsDiffer:
"""Find out changed lines for a file compared to Git HEAD"""

def __init__(self, git_root: Path):
self._git_root = git_root
git_root: Path
revision: str = "HEAD"

def head_vs_lines(
def revision_vs_lines(
self, path_in_repo: Path, lines: List[str], context_lines: int
) -> List[int]:
head_lines = git_get_unmodified_content(path_in_repo, self._git_root)
head_lines = git_get_unmodified_content(
path_in_repo, self.revision, self.git_root
)
edited_opcodes = diff_and_get_opcodes(head_lines, lines)
return list(opcodes_to_edit_linenums(edited_opcodes, context_lines))
12 changes: 6 additions & 6 deletions src/darker/tests/test_command_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,21 +84,21 @@ def test_black_options(monkeypatch, tmpdir, git_repo, options, expect):
@pytest.mark.parametrize(
'options, expect',
[
(['a.py'], ({Path('a.py')}, False, {})),
(['--isort', 'a.py'], ({Path('a.py')}, True, {})),
(['a.py'], ({Path('a.py')}, "HEAD", False, {})),
(['--isort', 'a.py'], ({Path('a.py')}, "HEAD", True, {})),
(
['--config', 'my.cfg', 'a.py'],
({Path('a.py')}, False, {'config': 'my.cfg'}),
({Path('a.py')}, "HEAD", False, {'config': 'my.cfg'}),
),
(
['--line-length', '90', 'a.py'],
({Path('a.py')}, False, {'line_length': 90}),
({Path('a.py')}, "HEAD", False, {'line_length': 90}),
),
(
['--skip-string-normalization', 'a.py'],
({Path('a.py')}, False, {'skip_string_normalization': True}),
({Path('a.py')}, "HEAD", False, {'skip_string_normalization': True}),
),
(['--diff', 'a.py'], ({Path('a.py')}, False, {})),
(['--diff', 'a.py'], ({Path('a.py')}, "HEAD", False, {})),
],
)
def test_options(options, expect):
Expand Down
6 changes: 4 additions & 2 deletions src/darker/tests/test_git.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ def test_get_unmodified_content(git_repo):
paths = git_repo.add({'my.txt': 'original content'}, commit='Initial commit')
paths['my.txt'].write('new content')

original_content = git_get_unmodified_content(Path('my.txt'), cwd=git_repo.root)
original_content = git_get_unmodified_content(
Path('my.txt'), "HEAD", cwd=git_repo.root
)

assert original_content == ['original content']

Expand Down Expand Up @@ -94,5 +96,5 @@ def test_edited_linenums_differ_head_vs_lines(git_repo, context_lines, expect):
git_repo.add({'a.py': '1\n2\n3\n4\n5\n6\n7\n8\n'}, commit='Initial commit')
lines = ['1', '2', 'three', '4', '5', '6', 'seven', '8']
differ = EditedLinenumsDiffer(git_repo.root)
result = differ.head_vs_lines(Path('a.py'), lines, context_lines)
result = differ.revision_vs_lines(Path('a.py'), lines, context_lines)
assert result == expect
8 changes: 5 additions & 3 deletions src/darker/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def test_isort_option_with_isort_calls_sortimports(tmpdir, run_isort, isort_args
def test_format_edited_parts_empty():
with pytest.raises(ValueError):

list(darker.__main__.format_edited_parts([], False, {}))
list(darker.__main__.format_edited_parts([], "HEAD", False, {}))


A_PY = ['import sys', 'import os', "print( '42')", '']
Expand Down Expand Up @@ -121,7 +121,9 @@ def test_format_edited_parts(git_repo, monkeypatch, enable_isort, black_args, ex
paths['b.py'].write('print(42 )\n')

changes = list(
darker.__main__.format_edited_parts([Path('a.py')], enable_isort, black_args)
darker.__main__.format_edited_parts(
[Path('a.py')], "HEAD", enable_isort, black_args
)
)

expect_changes = [(paths['a.py'], '\n'.join(A_PY), '\n'.join(expect), expect[:-1])]
Expand All @@ -135,7 +137,7 @@ def test_format_edited_parts_all_unchanged(git_repo, monkeypatch):
paths['a.py'].write('"properly"\n"formatted"\n')
paths['b.py'].write('"not"\n"checked"\n')

result = list(darker.__main__.format_edited_parts([Path('a.py')], True, {}))
result = list(darker.__main__.format_edited_parts([Path('a.py')], "HEAD", True, {}))

assert result == []

Expand Down

0 comments on commit f31b703

Please sign in to comment.