Skip to content

fix(exclude): match a pattern against the path, not only against its basename - #636

Merged
Bachmann1234 merged 1 commit into
Bachmann1234:mainfrom
Eljees:fix/exclude-relative-path-patterns
Sep 24, 2026
Merged

Bachmann1234 merged 1 commit into
Bachmann1234:mainfrom
Eljees:fix/exclude-relative-path-patterns

Conversation

@Eljees

@Eljees Eljees commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

What happens today

--exclude matches each changed file against two strings: its basename and its absolute path.

basename = os.path.basename(path)
if self._fnmatch(basename, exclude):
    return True

absolute_path = os.path.abspath(path)
return self._fnmatch(absolute_path, exclude)

A pattern that names a directory matches neither. fnmatch("skipme.py", "sub/*") is false, and so is fnmatch("/repo/sub/skipme.py", "sub/*"), because fnmatch anchors at both ends. So --exclude "sub/*" excludes nothing, silently - no warning, no error, and a report that looks right until someone counts the files.

What does work is a bare filename (--exclude setup.py, the README's example) or a pattern that happens to start with a wildcard (--exclude "*sub/*", the form suggested in #265). That is why the same option looks broken to one person and fine to the next.

Measured before the change, on a repository with app.py and sub/skipme.py both uncovered:

$ diff-cover coverage.xml --compare-branch main --exclude 'sub/*'
app.py (0.0%): Missing lines 5-6
sub/skipme.py (0.0%): Missing lines 5-6
Total:   4 lines

and after:

$ diff-cover coverage.xml --compare-branch main --exclude 'sub/*'
app.py (0.0%): Missing lines 5-6
Total:   2 lines

The change

The path as the diff reports it - relative to the repository root - is matched too. The absolute-path match stays, normalised through to_unix_path so a pattern written with forward slashes behaves the same on Windows.

Tests

Four cases in test_git_path_selection: a directory pattern, the /** shape the README shows for include, a pattern that matches nothing, and two patterns at once. Three of them fail on main:

FAILED tests/test_diff_reporter.py::test_git_path_selection[include5-exclude5-expected5]
FAILED tests/test_diff_reporter.py::test_git_path_selection[include6-exclude6-expected6]
FAILED tests/test_diff_reporter.py::test_git_path_selection[include8-exclude8-expected8]
3 failed, 6 passed

With the change all nine pass, and tests/test_diff_reporter.py is green in full (45 passed).

The rest of the suite is unchanged by this: 12 integration failures plus one error show up identically on pristine main in this container, all of them Permission denied: 'pylint'.

Related

#265 asks how to exclude two directories and ends with a workaround rather than an answer; #449 is the same silent no-op seen from the config file. Neither needs a behaviour change beyond this one.

AI-assisted (LLM used for drafting); every change verified and tested by me.

…basename

`_is_path_excluded` matched each changed file twice: against its basename and
against its absolute path. A pattern that names a directory - `subdir/*`, the
shape the README uses for include patterns - matches neither, so it excluded
nothing at all and said so nowhere. Only a bare filename (`setup.py`) or a
pattern that happens to start with a wildcard (`*subdir/*`, the form suggested
in Bachmann1234#265) ever had an effect.

The path as the diff reports it, relative to the repository root, is now
matched as well, so `subdir/*` and `subdir/**` exclude what they name. The
absolute-path match stays, and is normalised to unix separators so a pattern
written with forward slashes keeps working on Windows.

Four cases added to `test_git_path_selection`: a directory pattern, the `/**`
shape, a pattern that matches nothing, and two patterns at once. Three of them
fail on main:

    FAILED tests/test_diff_reporter.py::test_git_path_selection[include5-exclude5-expected5]
    FAILED tests/test_diff_reporter.py::test_git_path_selection[include6-exclude6-expected6]
    FAILED tests/test_diff_reporter.py::test_git_path_selection[include8-exclude8-expected8]
    3 failed, 6 passed

and all nine pass with the change. `tests/test_diff_reporter.py` is green in
full, and the rest of the suite is unchanged: 12 integration failures and one
error appear identically on pristine main in this container, all of them
`Permission denied: 'pylint'`.

Signed-off-by: Eljees <57435526+Eljees@users.noreply.github.com>
@Bachmann1234

Copy link
Copy Markdown
Owner

Thanks for the pr!

@Bachmann1234
Bachmann1234 merged commit 1b99ebd into Bachmann1234:main Sep 24, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants