Skip to content

use three dot notation to calculate diff #573

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

Merged
merged 5 commits into from
Jul 13, 2025
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
13 changes: 1 addition & 12 deletions coverage_comment/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,18 +286,7 @@ def get_diff_coverage_info(
)


def get_added_lines(
git: subprocess.Git, base_ref: str
) -> dict[pathlib.Path, list[int]]:
# --unified=0 means we don't get any context lines for chunk, and we
# don't merge chunks. This means the headers that describe line number
# are always enough to derive what line numbers were added.
git.fetch("origin", base_ref, "--depth=1000")
diff = git.diff("--unified=0", "FETCH_HEAD", "--", ".")
return parse_diff_output(diff)


def parse_diff_output(diff: str) -> dict[pathlib.Path, list[int]]:
def get_added_lines(diff: str) -> dict[pathlib.Path, list[int]]:
current_file: pathlib.Path | None = None
added_filename_prefix = "+++ b/"
result: dict[pathlib.Path, list[int]] = {}
Expand Down
24 changes: 24 additions & 0 deletions coverage_comment/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,27 @@ def append_to_file(content: str, filepath: pathlib.Path):

def add_job_summary(content: str, github_step_summary: pathlib.Path):
append_to_file(content=content, filepath=github_step_summary)


def get_pr_diff(github: github_client.GitHub, repository: str, pr_number: int) -> str:
"""
Get the diff of a pull request.
"""
return (
github.repos(repository)
.pulls(pr_number)
.get(headers={"Accept": "application/vnd.github.v3.diff"})
)


def get_branch_diff(
github: github_client.GitHub, repository: str, base_branch: str, head_branch: str
) -> str:
"""
Get the diff of branch.
"""
return (
github.repos(repository)
.compare(f"{base_branch}...{head_branch}")
.get(headers={"Accept": "application/vnd.github.v3.diff"})
)
16 changes: 7 additions & 9 deletions coverage_comment/github_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,7 @@ def _http(
**header_kwargs,
**requests_kwargs,
)
if bytes:
contents = response.content
else:
contents = response_contents(response)
contents = response_contents(response=response, bytes=bytes)

try:
response.raise_for_status()
Expand All @@ -103,14 +100,15 @@ def _http(

def response_contents(
response: httpx.Response,
bytes: bool,
) -> JsonObject | str | bytes:
if bytes:
return response.content

if response.headers.get("content-type", "").startswith("application/json"):
return response.json(object_hook=JsonObject)
if response.headers.get("content-type", "").startswith(
"application/vnd.github.raw+json"
):
return response.text
return response.content

return response.text


class JsonObject(dict):
Expand Down
15 changes: 14 additions & 1 deletion coverage_comment/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,20 @@ def process_pr(
)
base_ref = config.GITHUB_BASE_REF or repo_info.default_branch

added_lines = coverage_module.get_added_lines(git=git, base_ref=base_ref)
if config.GITHUB_BRANCH_NAME:
diff = github.get_branch_diff(
github=gh,
repository=config.GITHUB_REPOSITORY,
base_branch=base_ref,
head_branch=config.GITHUB_BRANCH_NAME,
)
elif config.GITHUB_PR_NUMBER:
diff = github.get_pr_diff(
github=gh,
repository=config.GITHUB_REPOSITORY,
pr_number=config.GITHUB_PR_NUMBER,
)
added_lines = coverage_module.get_added_lines(diff=diff)
diff_coverage = coverage_module.get_diff_coverage_info(
coverage=coverage, added_lines=added_lines
)
Expand Down
101 changes: 101 additions & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from __future__ import annotations

import os
import pathlib
import subprocess
import uuid

import pytest


@pytest.fixture
def in_integration_env(integration_env, integration_dir):
curdir = os.getcwd()
os.chdir(integration_dir)
yield integration_dir
os.chdir(curdir)


@pytest.fixture
def integration_dir(tmp_path: pathlib.Path):
test_dir = tmp_path / "integration_test"
test_dir.mkdir()
return test_dir


@pytest.fixture
def file_path(integration_dir):
return integration_dir / "foo.py"


@pytest.fixture
def write_file(file_path):
def _(*variables):
content = "import os"
for i, var in enumerate(variables):
content += f"""\nif os.environ.get("{var}"):\n {i}\n"""
file_path.write_text(content, encoding="utf8")

return _


@pytest.fixture
def run_coverage(file_path, integration_dir):
def _(*variables):
subprocess.check_call(
["coverage", "run", "--parallel", file_path.name],
cwd=integration_dir,
env=os.environ | dict.fromkeys(variables, "1"),
)

return _


@pytest.fixture
def commit(integration_dir):
def _():
subprocess.check_call(
["git", "add", "."],
cwd=integration_dir,
)
subprocess.check_call(
["git", "commit", "-m", str(uuid.uuid4())],
cwd=integration_dir,
env={
"GIT_AUTHOR_NAME": "foo",
"GIT_AUTHOR_EMAIL": "foo",
"GIT_COMMITTER_NAME": "foo",
"GIT_COMMITTER_EMAIL": "foo",
"GIT_CONFIG_GLOBAL": "/dev/null",
"GIT_CONFIG_SYSTEM": "/dev/null",
},
)

return _


@pytest.fixture
def integration_env(integration_dir, write_file, run_coverage, commit, request):
subprocess.check_call(["git", "init", "-b", "main"], cwd=integration_dir)
# diff coverage reads the "origin/{...}" branch so we simulate an origin remote
subprocess.check_call(["git", "remote", "add", "origin", "."], cwd=integration_dir)
write_file("A", "B")
commit()

add_branch_mark = request.node.get_closest_marker("add_branches")
for additional_branch in add_branch_mark.args if add_branch_mark else []:
subprocess.check_call(
["git", "switch", "-c", additional_branch],
cwd=integration_dir,
)

subprocess.check_call(
["git", "switch", "-c", "branch"],
cwd=integration_dir,
)

write_file("A", "B", "C", "D")
commit()

run_coverage("A", "C")
subprocess.check_call(["git", "fetch", "origin"], cwd=integration_dir)
26 changes: 26 additions & 0 deletions tests/integration/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -444,3 +444,29 @@ def test_annotations(capsys):
::endgroup::"""
output = capsys.readouterr()
assert output.err.strip() == expected


def test_get_pr_diff(gh, session):
session.register(
"GET",
"/repos/foo/bar/pulls/123",
headers={"Accept": "application/vnd.github.v3.diff"},
)(text="diff --git a/foo.py b/foo.py...")

result = github.get_pr_diff(github=gh, repository="foo/bar", pr_number=123)

assert result == "diff --git a/foo.py b/foo.py..."


def test_get_branch_diff(gh, session):
session.register(
"GET",
"/repos/foo/bar/compare/main...feature",
headers={"Accept": "application/vnd.github.v3.diff"},
)(text="diff --git a/foo.py b/foo.py...")

result = github.get_branch_diff(
github=gh, repository="foo/bar", base_branch="main", head_branch="feature"
)

assert result == "diff --git a/foo.py b/foo.py..."
Loading
Loading