Skip to content

Commit

Permalink
Added option to specify diff file location (#410)
Browse files Browse the repository at this point in the history
* Added option to specify diff-file

* Added section for diff-file in README

* Add unit tests for GitDiffFileTool class

* Reformatted files

* Ordered imports

---------

Co-authored-by: jostmann <[email protected]>
  • Loading branch information
Jsostmann and jostmann authored Jun 25, 2024
1 parent 4f49610 commit 838ffd1
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 6 deletions.
27 changes: 27 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,33 @@ By default, ``diff-cover`` compares the current branch to ``origin/main``. To s
diff-cover coverage.xml --compare-branch=origin/release
Diff File
--------------

You may provide a file containing the output of ``git diff`` to ``diff-cover`` instead of using a branch name.

For example, Say you have 2 branches ``main`` and ``feature``. Lets say after creating and checking out the feature branch,
you make commits ``A``, ``B``, and ``C`` in that order.


If you want to see all changes between the ``feature`` and ``main`` branch, you can generate a diff file like this:

.. code:: bash
git diff main..feature > diff.txt
If you want to see the changes between the ``feature`` branch and the commit ``A``, you can generate a diff file using the following command:

.. code:: bash
git diff A..feature > diff.txt
You can then run ``diff-cover`` with the diff file as an argument:

.. code:: bash
diff-cover coverage.xml --diff-file=diff.txt
Fail Under
----------

Expand Down
22 changes: 16 additions & 6 deletions diff_cover/diff_cover_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from diff_cover import DESCRIPTION, VERSION
from diff_cover.config_parser import Tool, get_config
from diff_cover.diff_reporter import GitDiffReporter
from diff_cover.git_diff import GitDiffTool
from diff_cover.git_diff import GitDiffFileTool, GitDiffTool
from diff_cover.git_path import GitPathTool
from diff_cover.report_generator import (
HtmlReportGenerator,
Expand Down Expand Up @@ -43,6 +43,7 @@
SHOW_UNCOVERED = "Show uncovered lines on the console"
INCLUDE_UNTRACKED_HELP = "Include untracked files"
CONFIG_FILE_HELP = "The configuration file to use"
DIFF_FILE_HELP = "The diff file to use"

LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -169,6 +170,8 @@ def parse_coverage_args(argv):
"-c", "--config-file", help=CONFIG_FILE_HELP, metavar="CONFIG_FILE"
)

parser.add_argument("--diff-file", type=str, default=None, help=DIFF_FILE_HELP)

defaults = {
"show_uncovered": False,
"compare_branch": "origin/main",
Expand All @@ -188,6 +191,7 @@ def parse_coverage_args(argv):
def generate_coverage_report(
coverage_files,
compare_branch,
diff_tool,
html_report=None,
css_file=None,
json_report=None,
Expand All @@ -198,8 +202,6 @@ def generate_coverage_report(
exclude=None,
include=None,
src_roots=None,
diff_range_notation=None,
ignore_whitespace=False,
quiet=False,
show_uncovered=False,
):
Expand All @@ -208,7 +210,7 @@ def generate_coverage_report(
"""
diff = GitDiffReporter(
compare_branch,
git_diff=GitDiffTool(diff_range_notation, ignore_whitespace),
git_diff=diff_tool,
ignore_staged=ignore_staged,
ignore_unstaged=ignore_unstaged,
include_untracked=include_untracked,
Expand Down Expand Up @@ -281,9 +283,19 @@ def main(argv=None, directory=None):

GitPathTool.set_cwd(directory)
fail_under = arg_dict.get("fail_under")
diff_tool = None

if not arg_dict["diff_file"]:
diff_tool = GitDiffTool(
arg_dict["diff_range_notation"], arg_dict["ignore_whitespace"]
)
else:
diff_tool = GitDiffFileTool(arg_dict["diff_file"])

percent_covered = generate_coverage_report(
arg_dict["coverage_file"],
arg_dict["compare_branch"],
diff_tool,
html_report=arg_dict["html_report"],
json_report=arg_dict["json_report"],
markdown_report=arg_dict["markdown_report"],
Expand All @@ -294,8 +306,6 @@ def main(argv=None, directory=None):
exclude=arg_dict["exclude"],
include=arg_dict["include"],
src_roots=arg_dict["src_roots"],
diff_range_notation=arg_dict["diff_range_notation"],
ignore_whitespace=arg_dict["ignore_whitespace"],
quiet=quiet,
show_uncovered=arg_dict["show_uncovered"],
)
Expand Down
35 changes: 35 additions & 0 deletions diff_cover/git_diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,38 @@ def untracked(self):
if not output:
return []
return [line for line in output.splitlines() if line]


class GitDiffFileTool(GitDiffTool):

def __init__(self, diff_file_path):

self.diff_file_path = diff_file_path
super().__init__("...", False)

def diff_committed(self, compare_branch="origin/main"):
"""
Returns the contents of a diff file.
Raises a `GitDiffError` if the file cannot be read.
"""
try:
with open(self.diff_file_path, "r") as file:
return file.read()
except IOError as e:
raise ValueError(
dedent(
f"""
Could not read the diff file. Make sure '{self.diff_file_path}' exists?
"""
)
)

def diff_unstaged(self):
return ""

def diff_staged(self):
return ""

def untracked(self):
return ""
72 changes: 72 additions & 0 deletions tests/test_git_diff_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# pylint: disable=missing-function-docstring

"""Test for diff_cover.git_diff.GitDiffFileTool"""

import pytest

from diff_cover.git_diff import GitDiffFileTool


@pytest.fixture
def mock_file(mocker):
def _inner(file_content):
mock_open = mocker.mock_open(read_data=file_content)
mocker.patch("builtins.open", mock_open)

return _inner


@pytest.fixture
def diff_tool():
def _inner(file):
return GitDiffFileTool(file)

return _inner


def test_diff_file_not_found(mocker, diff_tool):
mocker.patch("builtins.open", side_effect=IOError)

_diff_tool = diff_tool("non_existent_diff_file.txt")

with pytest.raises(ValueError) as excinfo:
_diff_tool.diff_committed()

assert (
f"Could not read the diff file. Make sure '{_diff_tool.diff_file_path}' exists?"
in str(excinfo.value)
)
assert _diff_tool.diff_file_path == "non_existent_diff_file.txt"


def test_large_diff_file(mock_file, diff_tool):
large_diff = "diff --git a/file1 b/file2\n" * 1000000

mock_file(large_diff)

_diff_tool = diff_tool("large_diff_file.txt")

assert _diff_tool.diff_committed() == large_diff
assert _diff_tool.diff_file_path == "large_diff_file.txt"


def test_diff_committed(mock_file, diff_tool):
diff = "diff --git a/file1 b/file2\n"

mock_file(diff)

_diff_tool = diff_tool("diff_file.txt")

assert _diff_tool.diff_committed() == diff
assert _diff_tool.diff_file_path == "diff_file.txt"


def test_empty_diff_file(mock_file, diff_tool):
empty_diff = ""

mock_file(empty_diff)

_diff_tool = diff_tool("empty_diff.txt")

assert _diff_tool.diff_committed() == empty_diff
assert _diff_tool.diff_file_path == "empty_diff.txt"

0 comments on commit 838ffd1

Please sign in to comment.