Skip to content

Commit 0a01f5c

Browse files
committed
Test external user PR
1 parent 63e4654 commit 0a01f5c

File tree

5 files changed

+131
-11
lines changed

5 files changed

+131
-11
lines changed

.github/workflows/build.yml

+5-1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ jobs:
107107

108108
steps:
109109
- uses: actions/checkout@v4
110+
with:
111+
fetch-depth: 2
110112
- name: Set up Python
111113
uses: actions/setup-python@v5
112114
with:
@@ -118,7 +120,9 @@ jobs:
118120
- name: Install dependencies
119121
run: |
120122
python -m pip install --upgrade pip
121-
python -m pip install --upgrade build setuptools tox
123+
python -m pip install --upgrade build setuptools tox
122124
- name: ${{ matrix.tox-env }}
125+
env:
126+
TOX_MERGE_BASE: "${{ github.event.pull_request.base.sha }}"
123127
run: |
124128
python -m tox -e ${{ matrix.tox-env }}

pyproject.toml

+4-1
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,15 @@ commands=
134134
{envpython} -m coverage report --show-missing
135135
136136
[testenv:documents]
137+
passenv = TOX_MERGE_BASE
137138
deps=
138139
.
139140
-r requirements/docs.txt
140141
commands=
141142
{envpython} -m mkdocs build --clean --verbose --strict
142-
{envpython} -m pyspelling
143+
{envpython} -m pyspelling -n python -m {env:TOX_MERGE_BASE:master} -v
144+
{envpython} -m pyspelling -n markdown -m {env:TOX_MERGE_BASE:master} -v
145+
{envpython} -m pyspelling -n mkdocs -v
143146
144147
[testenv:lint]
145148
deps=

pyspelling/__init__.py

+28-9
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .__meta__ import __version__, __version_info__ # noqa: F401
66
from . import flow_control
77
from . import filters
8+
from .util import git
89
from wcmatch import glob
910
import codecs
1011
from collections import namedtuple
@@ -578,7 +579,7 @@ class SpellingTask:
578579
"O": glob.O
579580
}
580581

581-
def __init__(self, checker, config, binary='', verbose=0, jobs=0, debug=False):
582+
def __init__(self, checker, config, binary='', verbose=0, jobs=0, git_merge_base='', git_binary=None, debug=False):
582583
"""Initialize."""
583584

584585
if checker == "hunspell": # pragma: no cover
@@ -594,6 +595,8 @@ def __init__(self, checker, config, binary='', verbose=0, jobs=0, debug=False):
594595
self.binary = checker if not binary else binary
595596
self.debug = debug
596597
self.jobs = jobs
598+
self.git_merge_base = git_merge_base
599+
self.git_binary = git_binary
597600

598601
def log(self, text, level):
599602
"""Log level."""
@@ -613,11 +616,20 @@ def _to_flags(self, text):
613616
def walk_src(self, targets, flags, limit):
614617
"""Walk source and parse files."""
615618

616-
for target in targets:
617-
# Glob using `S` for patterns with `|` and `O` to exclude directories.
618-
kwargs = {"flags": flags | glob.S | glob.O}
619-
kwargs['limit'] = limit
620-
yield from glob.iglob(target, **kwargs)
619+
# Glob using `S` for patterns with `|` and `O` to exclude directories.
620+
kwargs = {"flags": flags | glob.S | glob.O}
621+
kwargs['limit'] = limit
622+
623+
if self.git_merge_base:
624+
for target in targets:
625+
yield from glob.globfilter(
626+
git.get_file_diff(self.git_merge_base, git_binary=self.git_binary),
627+
target,
628+
**kwargs
629+
)
630+
else:
631+
for target in targets:
632+
yield from glob.iglob(target, **kwargs)
621633

622634
def get_checker(self):
623635
"""Get a spell checker object."""
@@ -659,13 +671,18 @@ def run_task(self, task, source_patterns=None):
659671
glob_flags = self._to_flags(self.task.get('glob_flags', "N|B|G"))
660672
glob_limit = self.task.get('glob_pattern_limit', 1000)
661673

674+
if self.git_merge_base:
675+
self.log("Searching: Only checking files that changed in git...", 1)
676+
else:
677+
self.log("Searching: Finding files to check...", 1)
678+
662679
if not source_patterns:
663680
source_patterns = self.task.get('sources', [])
664681

665682
# If jobs was not specified via command line, check the config for jobs settings
666683
jobs = max(1, self.config.get('jobs', 1) if self.jobs == 0 else self.jobs)
667684

668-
expect_match = self.task.get('expect_match', True)
685+
expect_match = self.task.get('expect_match', True) and not self.git_merge_base
669686
if jobs > 1:
670687
# Use multi-processing to process files concurrently
671688
with ProcessPoolExecutor(max_workers=jobs) as pool:
@@ -696,7 +713,9 @@ def spellcheck(
696713
sources=None,
697714
verbose=0,
698715
debug=False,
699-
jobs=0
716+
jobs=0,
717+
git_merge_base='',
718+
git_binary=None
700719
):
701720
"""Spell check."""
702721

@@ -737,7 +756,7 @@ def spellcheck(
737756

738757
log('Using {} to spellcheck {}'.format(checker, task.get('name', '')), 1, verbose)
739758

740-
spelltask = SpellingTask(checker, config, binary, verbose, jobs, debug)
759+
spelltask = SpellingTask(checker, config, binary, verbose, jobs, git_merge_base, git_binary, debug)
741760

742761
for result in spelltask.run_task(task, source_patterns=sources):
743762
log('Context: %s' % result.context, 2, verbose)

pyspelling/__main__.py

+16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ def main():
1616
group.add_argument('--name', '-n', action='append', help="Specific spelling task by name to run.")
1717
group.add_argument('--group', '-g', action='append', help="Specific spelling task group to run.")
1818
parser.add_argument('--binary', '-b', action='store', default='', help="Provide path to spell checker's binary.")
19+
parser.add_argument(
20+
'--git-merge-base',
21+
'-m',
22+
help="Specify the git merge base for generating a modified file list."
23+
)
24+
parser.add_argument(
25+
'--git-binary',
26+
'-G',
27+
help="Specify the path to the the git binary if not on the system path."
28+
)
1929
parser.add_argument(
2030
'--jobs', '-j',
2131
action='store',
@@ -44,6 +54,8 @@ def main():
4454
verbose=args.verbose,
4555
debug=args.debug,
4656
jobs=args.jobs,
57+
git_merge_base=args.git_merge_base,
58+
git_binary=args.git_binary
4759
)
4860

4961

@@ -58,6 +70,8 @@ def run(config, **kwargs):
5870
sources = kwargs.get('sources', [])
5971
debug = kwargs.get('debug', False)
6072
jobs = kwargs.get('jobs', 0)
73+
git_merge_base = kwargs.get('git_merge_base', '')
74+
git_binary = kwargs.get('git_binary', None)
6175

6276
fail = False
6377
count = 0
@@ -71,6 +85,8 @@ def run(config, **kwargs):
7185
verbose=verbose,
7286
debug=debug,
7387
jobs=jobs,
88+
git_merge_base=git_merge_base,
89+
git_binary=git_binary
7490
):
7591
count += 1
7692
if results.error:

pyspelling/util/git.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
"""Git support."""
2+
import subprocess
3+
import sys
4+
import os
5+
6+
WIN = sys.platform.startswith('win')
7+
GIT_BINARY = "git.exe" if WIN else "git"
8+
9+
10+
def get_git_tree(target):
11+
"""Recursively get Git tree."""
12+
13+
is_file = os.path.isfile(target)
14+
folder = os.path.dirname(target) if is_file else target
15+
if os.path.exists(os.path.join(folder, ".git")):
16+
return folder
17+
else:
18+
parent = os.path.dirname(folder)
19+
if parent == folder:
20+
return None
21+
else:
22+
return get_git_tree(parent)
23+
24+
25+
def get_git_dir(tree):
26+
"""Get Git directory from tree."""
27+
28+
return os.path.join(tree, ".git")
29+
30+
31+
def get_file_diff(target, git_binary=None):
32+
"""Get the file list of the HEAD vs the specified target."""
33+
34+
args = ['--no-pager', 'diff', '--name-only', '--cached', '--merge-base', f'{target}']
35+
return gitopen(
36+
args,
37+
git_binary=git_binary,
38+
git_tree=get_git_tree(os.path.abspath('.'))
39+
).decode('utf-8').splitlines()
40+
41+
42+
def gitopen(args, git_binary=None, git_tree=None):
43+
"""Call Git with arguments."""
44+
45+
returncode = output = None
46+
47+
if git_binary is None or not git_binary:
48+
git_binary = GIT_BINARY
49+
50+
if git_tree is not None:
51+
cmd = [git_binary, f"--work-tree={git_tree}", f"--git-dir={get_git_dir(git_tree)}"] + args
52+
else:
53+
cmd = [git_binary] + args
54+
55+
if WIN:
56+
startupinfo = subprocess.STARTUPINFO()
57+
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
58+
process = subprocess.Popen(
59+
cmd,
60+
startupinfo=startupinfo,
61+
stdout=subprocess.PIPE,
62+
stderr=subprocess.PIPE,
63+
shell=False
64+
)
65+
else:
66+
process = subprocess.Popen(
67+
cmd,
68+
stdout=subprocess.PIPE,
69+
stderr=subprocess.PIPE,
70+
shell=False,
71+
)
72+
output = process.communicate()
73+
returncode = process.returncode
74+
75+
if returncode != 0:
76+
raise RuntimeError(output[1].decode('utf-8').rstrip())
77+
78+
return output[0]

0 commit comments

Comments
 (0)