Skip to content

Commit 0b99041

Browse files
committedMar 14, 2024
Merge branch 'main' into doc-types
2 parents b070e93 + e880c33 commit 0b99041

29 files changed

+195
-219
lines changed
 

‎.flake8

Lines changed: 0 additions & 26 deletions
This file was deleted.

‎.github/workflows/lint.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,5 +16,3 @@ jobs:
1616
- uses: pre-commit/action@v3.0.1
1717
with:
1818
extra_args: --all-files --hook-stage manual
19-
env:
20-
SKIP: black-format

‎.pre-commit-config.yaml

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,12 @@
11
repos:
2-
- repo: https://github.com/psf/black-pre-commit-mirror
3-
rev: 23.9.1
4-
hooks:
5-
- id: black
6-
alias: black-check
7-
name: black (check)
8-
args: [--check, --diff]
9-
exclude: ^git/ext/
10-
stages: [manual]
112

12-
- id: black
13-
alias: black-format
14-
name: black (format)
15-
exclude: ^git/ext/
16-
17-
- repo: https://github.com/PyCQA/flake8
18-
rev: 6.1.0
3+
- repo: https://github.com/astral-sh/ruff-pre-commit
4+
rev: v0.3.0
195
hooks:
20-
- id: flake8
21-
additional_dependencies:
22-
- flake8-bugbear==23.9.16
23-
- flake8-comprehensions==3.14.0
24-
- flake8-typing-imports==1.14.0
6+
- id: ruff-format
7+
exclude: ^git/ext/
8+
- id: ruff
9+
args: ["--fix"]
2510
exclude: ^doc|^git/ext/
2611

2712
- repo: https://github.com/shellcheck-py/shellcheck-py

‎Makefile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
1-
.PHONY: all lint clean release force_release
1+
.PHONY: all clean release force_release
22

33
all:
44
@awk -F: '/^[[:alpha:]].*:/ && !/^all:/ {print $$1}' Makefile
55

6-
lint:
7-
SKIP=black-format pre-commit run --all-files --hook-stage manual
8-
96
clean:
107
rm -rf build/ dist/ .eggs/ .tox/
118

‎README.md

Lines changed: 24 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ probably the skills to scratch that itch of mine: implement `git` in a way that
1717
If you like the idea and want to learn more, please head over to [gitoxide](https://github.com/Byron/gitoxide), an
1818
implementation of 'git' in [Rust](https://www.rust-lang.org).
1919

20-
*(Please note that `gitoxide` is not currently available for use in Python, and that Rust is required)*
20+
*(Please note that `gitoxide` is not currently available for use in Python, and that Rust is required.)*
2121

2222
## GitPython
2323

@@ -39,9 +39,9 @@ The project is open to contributions of all kinds, as well as new maintainers.
3939

4040
### REQUIREMENTS
4141

42-
GitPython needs the `git` executable to be installed on the system and available in your `PATH` for most operations.
43-
If it is not in your `PATH`, you can help GitPython find it by setting
44-
the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
42+
GitPython needs the `git` executable to be installed on the system and available in your
43+
`PATH` for most operations. If it is not in your `PATH`, you can help GitPython find it
44+
by setting the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
4545

4646
- Git (1.7.x or newer)
4747
- Python >= 3.7
@@ -57,7 +57,7 @@ GitPython and its required package dependencies can be installed in any of the f
5757

5858
To obtain and install a copy [from PyPI](https://pypi.org/project/GitPython/), run:
5959

60-
```bash
60+
```sh
6161
pip install GitPython
6262
```
6363

@@ -67,15 +67,15 @@ pip install GitPython
6767

6868
If you have downloaded the source code, run this from inside the unpacked `GitPython` directory:
6969

70-
```bash
70+
```sh
7171
pip install .
7272
```
7373

7474
#### By cloning the source code repository
7575

7676
To clone the [the GitHub repository](https://github.com/gitpython-developers/GitPython) from source to work on the code, you can do it like so:
7777

78-
```bash
78+
```sh
7979
git clone https://github.com/gitpython-developers/GitPython
8080
cd GitPython
8181
./init-tests-after-clone.sh
@@ -85,15 +85,15 @@ On Windows, `./init-tests-after-clone.sh` can be run in a Git Bash shell.
8585

8686
If you are cloning [your own fork](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/about-forks), then replace the above `git clone` command with one that gives the URL of your fork. Or use this [`gh`](https://cli.github.com/) command (assuming you have `gh` and your fork is called `GitPython`):
8787

88-
```bash
88+
```sh
8989
gh repo clone GitPython
9090
```
9191

9292
Having cloned the repo, create and activate your [virtual environment](https://docs.python.org/3/tutorial/venv.html).
9393

9494
Then make an [editable install](https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs):
9595

96-
```bash
96+
```sh
9797
pip install -e ".[test]"
9898
```
9999

@@ -105,7 +105,7 @@ In rare cases, you may want to work on GitPython and one or both of its [gitdb](
105105

106106
If you want to do that *and* you want the versions in GitPython's git submodules to be used, then pass `-e git/ext/gitdb` and/or `-e git/ext/gitdb/gitdb/ext/smmap` to `pip install`. This can be done in any order, and in separate `pip install` commands or the same one, so long as `-e` appears before *each* path. For example, you can install GitPython, gitdb, and smmap editably in the currently active virtual environment this way:
107107

108-
```bash
108+
```sh
109109
pip install -e ".[test]" -e git/ext/gitdb -e git/ext/gitdb/gitdb/ext/smmap
110110
```
111111

@@ -141,53 +141,51 @@ you will encounter test failures.
141141

142142
Ensure testing libraries are installed. This is taken care of already if you installed with:
143143

144-
```bash
144+
```sh
145145
pip install -e ".[test]"
146146
```
147147

148-
Otherwise, you can run:
149-
150-
```bash
151-
pip install -r test-requirements.txt
152-
```
148+
If you had installed with a command like `pip install -e .` instead, you can still run
149+
the above command to add the testing dependencies.
153150

154151
#### Test commands
155152

156153
To test, run:
157154

158-
```bash
155+
```sh
159156
pytest
160157
```
161158

162-
To lint, and apply automatic code formatting, run:
159+
To lint, and apply some linting fixes as well as automatic code formatting, run:
163160

164-
```bash
161+
```sh
165162
pre-commit run --all-files
166163
```
167164

168-
- Linting without modifying code can be done with: `make lint`
169-
- Auto-formatting without other lint checks can be done with: `black .`
165+
This includes the linting and autoformatting done by Ruff, as well as some other checks.
170166

171167
To typecheck, run:
172168

173-
```bash
169+
```sh
174170
mypy -p git
175171
```
176172

177173
#### CI (and tox)
178174

179-
The same linting, and running tests on all the different supported Python versions, will be performed:
175+
Style and formatting checks, and running tests on all the different supported Python versions, will be performed:
180176

181177
- Upon submitting a pull request.
182178
- On each push, *if* you have a fork with GitHub Actions enabled.
183179
- Locally, if you run [`tox`](https://tox.wiki/) (this skips any Python versions you don't have installed).
184180

185181
#### Configuration files
186182

187-
Specific tools:
183+
Specific tools are all configured in the `./pyproject.toml` file:
188184

189-
- Configurations for `mypy`, `pytest`, `coverage.py`, and `black` are in `./pyproject.toml`.
190-
- Configuration for `flake8` is in the `./.flake8` file.
185+
- `pytest` (test runner)
186+
- `coverage.py` (code coverage)
187+
- `ruff` (linter and formatter)
188+
- `mypy` (type checker)
191189

192190
Orchestration tools:
193191

‎git/cmd.py

Lines changed: 38 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -501,9 +501,8 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool:
501501
if mode in quiet:
502502
pass
503503
elif mode in warn or mode in error:
504-
err = (
505-
dedent(
506-
"""\
504+
err = dedent(
505+
"""\
507506
%s
508507
All git commands will error until this is rectified.
509508
@@ -516,40 +515,35 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool:
516515
Example:
517516
export %s=%s
518517
"""
519-
)
520-
% (
521-
err,
522-
cls._refresh_env_var,
523-
"|".join(quiet),
524-
"|".join(warn),
525-
"|".join(error),
526-
cls._refresh_env_var,
527-
quiet[0],
528-
)
518+
) % (
519+
err,
520+
cls._refresh_env_var,
521+
"|".join(quiet),
522+
"|".join(warn),
523+
"|".join(error),
524+
cls._refresh_env_var,
525+
quiet[0],
529526
)
530527

531528
if mode in warn:
532529
_logger.critical(err)
533530
else:
534531
raise ImportError(err)
535532
else:
536-
err = (
537-
dedent(
538-
"""\
533+
err = dedent(
534+
"""\
539535
%s environment variable has been set but it has been set with an invalid value.
540536
541537
Use only the following values:
542538
- %s: for no message or exception
543539
- %s: for a warning message (logging level CRITICAL, displayed by default)
544540
- %s: for a raised exception
545541
"""
546-
)
547-
% (
548-
cls._refresh_env_var,
549-
"|".join(quiet),
550-
"|".join(warn),
551-
"|".join(error),
552-
)
542+
) % (
543+
cls._refresh_env_var,
544+
"|".join(quiet),
545+
"|".join(warn),
546+
"|".join(error),
553547
)
554548
raise ImportError(err)
555549

@@ -571,13 +565,11 @@ def is_cygwin(cls) -> bool:
571565

572566
@overload
573567
@classmethod
574-
def polish_url(cls, url: str, is_cygwin: Literal[False] = ...) -> str:
575-
...
568+
def polish_url(cls, url: str, is_cygwin: Literal[False] = ...) -> str: ...
576569

577570
@overload
578571
@classmethod
579-
def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> str:
580-
...
572+
def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> str: ...
581573

582574
@classmethod
583575
def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike:
@@ -755,7 +747,7 @@ class CatFileContentStream:
755747
rest to ensure the underlying stream continues to work.
756748
"""
757749

758-
__slots__: Tuple[str, ...] = ("_stream", "_nbr", "_size")
750+
__slots__ = ("_stream", "_nbr", "_size")
759751

760752
def __init__(self, size: int, stream: IO[bytes]) -> None:
761753
self._stream = stream
@@ -852,14 +844,14 @@ def __del__(self) -> None:
852844
self._stream.read(bytes_left + 1)
853845
# END handle incomplete read
854846

855-
def __init__(self, working_dir: Union[None, PathLike] = None):
847+
def __init__(self, working_dir: Union[None, PathLike] = None) -> None:
856848
"""Initialize this instance with:
857849
858850
:param working_dir:
859-
Git directory we should work in. If ``None``, we always work in the current
860-
directory as returned by :func:`os.getcwd`.
861-
This is meant to be the working tree directory if available, or the
862-
``.git`` directory in case of bare repositories.
851+
Git directory we should work in. If ``None``, we always work in the current
852+
directory as returned by :func:`os.getcwd`.
853+
This is meant to be the working tree directory if available, or the
854+
``.git`` directory in case of bare repositories.
863855
"""
864856
super().__init__()
865857
self._working_dir = expand_path(working_dir)
@@ -938,8 +930,7 @@ def execute(
938930
command: Union[str, Sequence[Any]],
939931
*,
940932
as_process: Literal[True],
941-
) -> "AutoInterrupt":
942-
...
933+
) -> "AutoInterrupt": ...
943934

944935
@overload
945936
def execute(
@@ -948,8 +939,7 @@ def execute(
948939
*,
949940
as_process: Literal[False] = False,
950941
stdout_as_string: Literal[True],
951-
) -> Union[str, Tuple[int, str, str]]:
952-
...
942+
) -> Union[str, Tuple[int, str, str]]: ...
953943

954944
@overload
955945
def execute(
@@ -958,8 +948,7 @@ def execute(
958948
*,
959949
as_process: Literal[False] = False,
960950
stdout_as_string: Literal[False] = False,
961-
) -> Union[bytes, Tuple[int, bytes, str]]:
962-
...
951+
) -> Union[bytes, Tuple[int, bytes, str]]: ...
963952

964953
@overload
965954
def execute(
@@ -969,8 +958,7 @@ def execute(
969958
with_extended_output: Literal[False],
970959
as_process: Literal[False],
971960
stdout_as_string: Literal[True],
972-
) -> str:
973-
...
961+
) -> str: ...
974962

975963
@overload
976964
def execute(
@@ -980,8 +968,7 @@ def execute(
980968
with_extended_output: Literal[False],
981969
as_process: Literal[False],
982970
stdout_as_string: Literal[False],
983-
) -> bytes:
984-
...
971+
) -> bytes: ...
985972

986973
def execute(
987974
self,
@@ -1109,8 +1096,8 @@ def execute(
11091096
:raise git.exc.GitCommandError:
11101097
11111098
:note:
1112-
If you add additional keyword arguments to the signature of this method,
1113-
you must update the ``execute_kwargs`` variable housed in this module.
1099+
If you add additional keyword arguments to the signature of this method, you
1100+
must update the ``execute_kwargs`` variable housed in this module.
11141101
"""
11151102
# Remove password for the command if present.
11161103
redacted_command = remove_password_if_present(command)
@@ -1402,8 +1389,9 @@ def __call__(self, **kwargs: Any) -> "Git":
14021389
return self
14031390

14041391
@overload
1405-
def _call_process(self, method: str, *args: None, **kwargs: None) -> str:
1406-
... # If no args were given, execute the call with all defaults.
1392+
def _call_process(
1393+
self, method: str, *args: None, **kwargs: None
1394+
) -> str: ... # If no args were given, execute the call with all defaults.
14071395

14081396
@overload
14091397
def _call_process(
@@ -1413,14 +1401,12 @@ def _call_process(
14131401
as_process: Literal[True],
14141402
*args: Any,
14151403
**kwargs: Any,
1416-
) -> "Git.AutoInterrupt":
1417-
...
1404+
) -> "Git.AutoInterrupt": ...
14181405

14191406
@overload
14201407
def _call_process(
14211408
self, method: str, *args: Any, **kwargs: Any
1422-
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]:
1423-
...
1409+
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]: ...
14241410

14251411
def _call_process(
14261412
self, method: str, *args: Any, **kwargs: Any
@@ -1453,7 +1439,7 @@ def _call_process(
14531439
14541440
turns into::
14551441
1456-
git rev-list max-count 10 --header master
1442+
git rev-list max-count 10 --header master
14571443
14581444
:return:
14591445
Same as :meth:`execute`. If no args are given, used :meth:`execute`'s

‎git/compat.py

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -74,13 +74,11 @@
7474

7575

7676
@overload
77-
def safe_decode(s: None) -> None:
78-
...
77+
def safe_decode(s: None) -> None: ...
7978

8079

8180
@overload
82-
def safe_decode(s: AnyStr) -> str:
83-
...
81+
def safe_decode(s: AnyStr) -> str: ...
8482

8583

8684
def safe_decode(s: Union[AnyStr, None]) -> Optional[str]:
@@ -96,13 +94,11 @@ def safe_decode(s: Union[AnyStr, None]) -> Optional[str]:
9694

9795

9896
@overload
99-
def safe_encode(s: None) -> None:
100-
...
97+
def safe_encode(s: None) -> None: ...
10198

10299

103100
@overload
104-
def safe_encode(s: AnyStr) -> bytes:
105-
...
101+
def safe_encode(s: AnyStr) -> bytes: ...
106102

107103

108104
def safe_encode(s: Optional[AnyStr]) -> Optional[bytes]:
@@ -118,13 +114,11 @@ def safe_encode(s: Optional[AnyStr]) -> Optional[bytes]:
118114

119115

120116
@overload
121-
def win_encode(s: None) -> None:
122-
...
117+
def win_encode(s: None) -> None: ...
123118

124119

125120
@overload
126-
def win_encode(s: AnyStr) -> bytes:
127-
...
121+
def win_encode(s: AnyStr) -> bytes: ...
128122

129123

130124
def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:

‎git/index/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,7 +235,7 @@ def write(
235235
# Make sure we have our entries read before getting a write lock.
236236
# Otherwise it would be done when streaming.
237237
# This can happen if one doesn't change the index, but writes it right away.
238-
self.entries
238+
self.entries # noqa: B018
239239
lfd = LockedFD(file_path or self._file_path)
240240
stream = lfd.open(write=True, stream=True)
241241

@@ -384,7 +384,7 @@ def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile
384384
with TemporaryFileSwap(join_path_native(repo.git_dir, "index")):
385385
repo.git.read_tree(*arg_list, **kwargs)
386386
index = cls(repo, tmp_index)
387-
index.entries # Force it to read the file as we will delete the temp-file.
387+
index.entries # noqa: B018 # Force it to read the file as we will delete the temp-file.
388388
return index
389389
# END index merge handling
390390

@@ -1326,7 +1326,7 @@ def handle_stderr(proc: "Popen[bytes]", iter_checked_out_files: Iterable[PathLik
13261326
# Make sure we have our entries loaded before we start checkout_index, which
13271327
# will hold a lock on it. We try to get the lock as well during our entries
13281328
# initialization.
1329-
self.entries
1329+
self.entries # noqa: B018
13301330

13311331
args.append("--stdin")
13321332
kwargs["as_process"] = True
@@ -1366,7 +1366,7 @@ def handle_stderr(proc: "Popen[bytes]", iter_checked_out_files: Iterable[PathLik
13661366
self._flush_stdin_and_wait(proc, ignore_stdout=True)
13671367
except GitCommandError:
13681368
# Without parsing stdout we don't know what failed.
1369-
raise CheckoutError(
1369+
raise CheckoutError( # noqa: B904
13701370
"Some files could not be checked out from the index, probably because they didn't exist.",
13711371
failed_files,
13721372
[],

‎git/index/fun.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,9 @@ def read_cache(
287287
# 4 bytes length of chunk
288288
# Repeated 0 - N times
289289
extension_data = stream.read(~0)
290-
assert (
291-
len(extension_data) > 19
292-
), "Index Footer was not at least a sha on content as it was only %i bytes in size" % len(extension_data)
290+
assert len(extension_data) > 19, (
291+
"Index Footer was not at least a sha on content as it was only %i bytes in size" % len(extension_data)
292+
)
293293

294294
content_sha = extension_data[-20:]
295295

‎git/objects/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ class Object(LazyMixin):
9797
See also :class:`~git.types.GitObjectTypeString`.
9898
"""
9999

100-
def __init__(self, repo: "Repo", binsha: bytes):
100+
def __init__(self, repo: "Repo", binsha: bytes) -> None:
101101
"""Initialize an object by identifying it by its binary sha.
102102
103103
All keyword arguments will be set on demand if ``None``.

‎git/objects/blob.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
from mimetypes import guess_type
77
from . import base
88

9-
from git.types import Literal
9+
10+
try:
11+
from typing import Literal
12+
except ImportError:
13+
from typing_extensions import Literal
1014

1115
__all__ = ("Blob",)
1216

‎git/objects/commit.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,12 @@
4444
Dict,
4545
)
4646

47-
from git.types import PathLike, Literal
47+
from git.types import PathLike
48+
49+
try:
50+
from typing import Literal
51+
except ImportError:
52+
from typing_extensions import Literal
4853

4954
if TYPE_CHECKING:
5055
from git.repo import Repo

‎git/objects/fun.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,11 @@ def _find_by_name(tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir:
152152

153153

154154
@overload
155-
def _to_full_path(item: None, path_prefix: str) -> None:
156-
...
155+
def _to_full_path(item: None, path_prefix: str) -> None: ...
157156

158157

159158
@overload
160-
def _to_full_path(item: EntryTup, path_prefix: str) -> EntryTup:
161-
...
159+
def _to_full_path(item: EntryTup, path_prefix: str) -> EntryTup: ...
162160

163161

164162
def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone:

‎git/objects/submodule/base.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@
5454
cast,
5555
)
5656

57-
from git.types import Commit_ish, Literal, PathLike, TBD
57+
from git.types import Commit_ish, PathLike, TBD
58+
59+
try:
60+
from typing import Literal
61+
except ImportError:
62+
from typing_extensions import Literal
5863

5964
if TYPE_CHECKING:
6065
from git.index import IndexFile
@@ -1455,7 +1460,7 @@ def exists(self) -> bool:
14551460

14561461
try:
14571462
try:
1458-
self.path
1463+
self.path # noqa: B018
14591464
return True
14601465
except Exception:
14611466
return False

‎git/objects/submodule/root.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class RootModule(Submodule):
5656

5757
k_root_name = "__ROOT__"
5858

59-
def __init__(self, repo: "Repo"):
59+
def __init__(self, repo: "Repo") -> None:
6060
# repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None)
6161
super().__init__(
6262
repo,

‎git/objects/tag.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
from typing import List, TYPE_CHECKING, Union
1818

19-
from git.types import Literal
19+
try:
20+
from typing import Literal
21+
except ImportError:
22+
from typing_extensions import Literal
2023

2124
if TYPE_CHECKING:
2225
from git.repo import Repo

‎git/objects/tree.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
TYPE_CHECKING,
3232
)
3333

34-
from git.types import PathLike, Literal
34+
from git.types import PathLike
35+
36+
try:
37+
from typing import Literal
38+
except ImportError:
39+
from typing_extensions import Literal
3540

3641
if TYPE_CHECKING:
3742
from git.repo import Repo
@@ -186,7 +191,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
186191
_map_id_to_type: Dict[int, Type[IndexObjUnion]] = {
187192
commit_id: Submodule,
188193
blob_id: Blob,
189-
symlink_id: Blob
194+
symlink_id: Blob,
190195
# Tree ID added once Tree is defined.
191196
}
192197

‎git/objects/util.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ def _list_traverse(
439439

440440
if not as_edge:
441441
out: IterableList[Union["Commit", "Submodule", "Tree", "Blob"]] = IterableList(id)
442-
out.extend(self.traverse(as_edge=as_edge, *args, **kwargs))
442+
out.extend(self.traverse(as_edge=as_edge, *args, **kwargs)) # noqa: B026
443443
return out
444444
# Overloads in subclasses (mypy doesn't allow typing self: subclass).
445445
# Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]]
@@ -620,8 +620,7 @@ def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TI
620620
return super()._list_traverse(*args, **kwargs)
621621

622622
@overload
623-
def traverse(self: T_TIobj) -> Iterator[T_TIobj]:
624-
...
623+
def traverse(self: T_TIobj) -> Iterator[T_TIobj]: ...
625624

626625
@overload
627626
def traverse(
@@ -633,8 +632,7 @@ def traverse(
633632
visit_once: bool,
634633
ignore_self: Literal[True],
635634
as_edge: Literal[False],
636-
) -> Iterator[T_TIobj]:
637-
...
635+
) -> Iterator[T_TIobj]: ...
638636

639637
@overload
640638
def traverse(
@@ -646,8 +644,7 @@ def traverse(
646644
visit_once: bool,
647645
ignore_self: Literal[False],
648646
as_edge: Literal[True],
649-
) -> Iterator[Tuple[Union[T_TIobj, None], T_TIobj]]:
650-
...
647+
) -> Iterator[Tuple[Union[T_TIobj, None], T_TIobj]]: ...
651648

652649
@overload
653650
def traverse(
@@ -659,8 +656,7 @@ def traverse(
659656
visit_once: bool,
660657
ignore_self: Literal[True],
661658
as_edge: Literal[True],
662-
) -> Iterator[Tuple[T_TIobj, T_TIobj]]:
663-
...
659+
) -> Iterator[Tuple[T_TIobj, T_TIobj]]: ...
664660

665661
def traverse(
666662
self: T_TIobj,

‎git/refs/head.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class HEAD(SymbolicReference):
4747
# TODO: This can be removed once SymbolicReference.commit has static type hints.
4848
commit: "Commit"
4949

50-
def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME):
50+
def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None:
5151
if path != self._HEAD_NAME:
5252
raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
5353
super().__init__(repo, path)

‎git/refs/log.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ def __new__(cls, filepath: Union[PathLike, None] = None) -> "RefLog":
164164
inst = super().__new__(cls)
165165
return inst
166166

167-
def __init__(self, filepath: Union[PathLike, None] = None):
167+
def __init__(self, filepath: Union[PathLike, None] = None) -> None:
168168
"""Initialize this instance with an optional filepath, from which we will
169169
initialize our data. The path is also used to write changes back using the
170170
:meth:`write` method."""

‎git/refs/symbolic.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class SymbolicReference:
7373
_remote_common_path_default = "refs/remotes"
7474
_id_attribute_ = "name"
7575

76-
def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False):
76+
def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False) -> None:
7777
self.repo = repo
7878
self.path = path
7979

@@ -511,7 +511,7 @@ def is_valid(self) -> bool:
511511
valid object or reference.
512512
"""
513513
try:
514-
self.object
514+
self.object # noqa: B018
515515
except (OSError, ValueError):
516516
return False
517517
else:
@@ -525,7 +525,7 @@ def is_detached(self) -> bool:
525525
instead to another reference.
526526
"""
527527
try:
528-
self.ref
528+
self.ref # noqa: B018
529529
return False
530530
except TypeError:
531531
return True

‎git/remote.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -91,22 +91,19 @@ def add_progress(
9191

9292

9393
@overload
94-
def to_progress_instance(progress: None) -> RemoteProgress:
95-
...
94+
def to_progress_instance(progress: None) -> RemoteProgress: ...
9695

9796

9897
@overload
99-
def to_progress_instance(progress: Callable[..., Any]) -> CallableRemoteProgress:
100-
...
98+
def to_progress_instance(progress: Callable[..., Any]) -> CallableRemoteProgress: ...
10199

102100

103101
@overload
104-
def to_progress_instance(progress: RemoteProgress) -> RemoteProgress:
105-
...
102+
def to_progress_instance(progress: RemoteProgress) -> RemoteProgress: ...
106103

107104

108105
def to_progress_instance(
109-
progress: Union[Callable[..., Any], RemoteProgress, None]
106+
progress: Union[Callable[..., Any], RemoteProgress, None],
110107
) -> Union[RemoteProgress, CallableRemoteProgress]:
111108
"""Given the `progress` return a suitable object derived from
112109
:class:`~git.util.RemoteProgress`."""
@@ -323,7 +320,7 @@ class FetchInfo(IterableObj):
323320
ERROR,
324321
) = [1 << x for x in range(8)]
325322

326-
_re_fetch_result = re.compile(r"^\s*(.) (\[[\w\s\.$@]+\]|[\w\.$@]+)\s+(.+) -> ([^\s]+)( \(.*\)?$)?")
323+
_re_fetch_result = re.compile(r"^ *(.) (\[[\w \.$@]+\]|[\w\.$@]+) +(.+) -> ([^ ]+)( \(.*\)?$)?")
327324

328325
_flag_map: Dict[flagKeyLiteral, int] = {
329326
"!": ERROR,
@@ -895,7 +892,7 @@ def _get_fetch_info_from_stderr(
895892
None,
896893
progress_handler,
897894
finalizer=None,
898-
decode_streams=False,
895+
decode_streams=True,
899896
kill_after_timeout=kill_after_timeout,
900897
)
901898

@@ -1072,7 +1069,7 @@ def fetch(
10721069
Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_fetch_options)
10731070

10741071
proc = self.repo.git.fetch(
1075-
"--", self, *args, as_process=True, with_stdout=False, universal_newlines=True, v=verbose, **kwargs
1072+
"--", self, *args, as_process=True, with_stdout=False, universal_newlines=False, v=verbose, **kwargs
10761073
)
10771074
res = self._get_fetch_info_from_stderr(proc, progress, kill_after_timeout=kill_after_timeout)
10781075
if hasattr(self.repo.odb, "update_cache"):
@@ -1126,7 +1123,7 @@ def pull(
11261123
Git.check_unsafe_options(options=list(kwargs.keys()), unsafe_options=self.unsafe_git_pull_options)
11271124

11281125
proc = self.repo.git.pull(
1129-
"--", self, refspec, with_stdout=False, as_process=True, universal_newlines=True, v=True, **kwargs
1126+
"--", self, refspec, with_stdout=False, as_process=True, universal_newlines=False, v=True, **kwargs
11301127
)
11311128
res = self._get_fetch_info_from_stderr(proc, progress, kill_after_timeout=kill_after_timeout)
11321129
if hasattr(self.repo.odb, "update_cache"):

‎git/util.py

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -472,13 +472,11 @@ def _is_cygwin_git(git_executable: str) -> bool:
472472

473473

474474
@overload
475-
def is_cygwin_git(git_executable: None) -> Literal[False]:
476-
...
475+
def is_cygwin_git(git_executable: None) -> Literal[False]: ...
477476

478477

479478
@overload
480-
def is_cygwin_git(git_executable: PathLike) -> bool:
481-
...
479+
def is_cygwin_git(git_executable: PathLike) -> bool: ...
482480

483481

484482
def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool:
@@ -503,8 +501,7 @@ def finalize_process(proc: Union[subprocess.Popen, "Git.AutoInterrupt"], **kwarg
503501

504502

505503
@overload
506-
def expand_path(p: None, expand_vars: bool = ...) -> None:
507-
...
504+
def expand_path(p: None, expand_vars: bool = ...) -> None: ...
508505

509506

510507
@overload
@@ -620,20 +617,6 @@ def _parse_progress_line(self, line: AnyStr) -> None:
620617
self.error_lines.append(self._cur_line)
621618
return
622619

623-
# Find escape characters and cut them away - regex will not work with
624-
# them as they are non-ASCII. As git might expect a tty, it will send them.
625-
last_valid_index = None
626-
for i, c in enumerate(reversed(line_str)):
627-
if ord(c) < 32:
628-
# its a slice index
629-
last_valid_index = -i - 1
630-
# END character was non-ASCII
631-
# END for each character in line
632-
if last_valid_index is not None:
633-
line_str = line_str[:last_valid_index]
634-
# END cut away invalid part
635-
line_str = line_str.rstrip()
636-
637620
cur_count, max_count = None, None
638621
match = self.re_op_relative.match(line_str)
639622
if match is None:
@@ -933,7 +916,7 @@ class Stats:
933916

934917
__slots__ = ("total", "files")
935918

936-
def __init__(self, total: Total_TD, files: Dict[PathLike, Files_TD]):
919+
def __init__(self, total: Total_TD, files: Dict[PathLike, Files_TD]) -> None:
937920
self.total = total
938921
self.files = files
939922

‎pyproject.toml

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ warn_unused_ignores = true
2828
warn_unreachable = true
2929
implicit_reexport = true
3030
# strict = true
31-
3231
# TODO: Remove when 'gitdb' is fully annotated.
3332
exclude = ["^git/ext/gitdb"]
3433
[[tool.mypy.overrides]]
@@ -42,7 +41,37 @@ source = ["git"]
4241
include = ["*/git/*"]
4342
omit = ["*/git/ext/*"]
4443

45-
[tool.black]
44+
[tool.ruff]
45+
target-version = "py37"
4646
line-length = 120
47-
target-version = ["py37"]
48-
extend-exclude = "git/ext/gitdb"
47+
# Exclude a variety of commonly ignored directories.
48+
exclude = [
49+
"git/ext/",
50+
"doc",
51+
"build",
52+
"dist",
53+
]
54+
# Enable Pyflakes `E` and `F` codes by default.
55+
lint.select = [
56+
"E",
57+
"W", # see: https://pypi.org/project/pycodestyle
58+
"F", # see: https://pypi.org/project/pyflakes
59+
# "I", #see: https://pypi.org/project/isort/
60+
# "S", # see: https://pypi.org/project/flake8-bandit
61+
# "UP", # see: https://docs.astral.sh/ruff/rules/#pyupgrade-up
62+
]
63+
lint.extend-select = [
64+
#"A", # see: https://pypi.org/project/flake8-builtins
65+
"B", # see: https://pypi.org/project/flake8-bugbear
66+
"C4", # see: https://pypi.org/project/flake8-comprehensions
67+
"TCH004", # see: https://docs.astral.sh/ruff/rules/runtime-import-in-type-checking-block/
68+
]
69+
lint.ignore = [
70+
"E203",
71+
"E731", # Do not assign a `lambda` expression, use a `def`
72+
]
73+
lint.ignore-init-module-imports = true
74+
lint.unfixable = ["F401"]
75+
76+
[tool.ruff.lint.per-file-ignores]
77+
"test/**" = ["B018"]

‎requirements-dev.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,5 @@
33

44
# libraries for additional local testing/linting - to be added to test-requirements.txt when all pass
55

6-
flake8-type-checking;python_version>="3.8" # checks for TYPE_CHECKING only imports
7-
86
pytest-icdiff
97
# pytest-profiling

‎test-requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
black
21
coverage[toml]
32
ddt >= 1.1.1, != 1.4.3
43
mock ; python_version < "3.8"

‎test/lib/helper.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import logging
1111
import os
1212
import os.path as osp
13+
import subprocess
1314
import sys
1415
import tempfile
1516
import textwrap
@@ -412,6 +413,13 @@ def __init__(self, env_dir, *, with_pip):
412413
self._env_dir = env_dir
413414
venv.create(self.env_dir, symlinks=True, with_pip=with_pip)
414415

416+
if with_pip:
417+
# The upgrade_deps parameter to venv.create is 3.9+ only, so do it this way.
418+
command = [self.python, "-m", "pip", "install", "--upgrade", "pip"]
419+
if sys.version_info < (3, 12):
420+
command.append("setuptools")
421+
subprocess.check_output(command)
422+
415423
@property
416424
def env_dir(self):
417425
"""The top-level directory of the environment."""

‎test/test_remote.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,6 +1002,22 @@ def test_push_unsafe_options_allowed(self, rw_repo):
10021002
assert tmp_file.exists()
10031003
tmp_file.unlink()
10041004

1005+
@with_rw_and_rw_remote_repo("0.1.6")
1006+
def test_fetch_unsafe_branch_name(self, rw_repo, remote_repo):
1007+
# Create branch with a name containing a NBSP
1008+
bad_branch_name = f"branch_with_{chr(160)}_nbsp"
1009+
Head.create(remote_repo, bad_branch_name)
1010+
1011+
# Fetch and get branches
1012+
remote = rw_repo.remote("origin")
1013+
branches = remote.fetch()
1014+
1015+
# Test for truncated branch name in branches
1016+
assert f"origin/{bad_branch_name}" in [b.name for b in branches]
1017+
1018+
# Cleanup branch
1019+
Head.delete(remote_repo, bad_branch_name)
1020+
10051021

10061022
class TestTimeouts(TestBase):
10071023
@with_rw_repo("HEAD", bare=False)

‎tox.ini

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tox]
22
requires = tox>=4
3-
env_list = py{37,38,39,310,311,312}, lint, mypy, html
3+
env_list = py{37,38,39,310,311,312}, mypy, html
44

55
[testenv]
66
description = Run unit tests
@@ -12,8 +12,6 @@ commands = pytest --color=yes {posargs}
1212
[testenv:lint]
1313
description = Lint via pre-commit
1414
base_python = py{39,310,311,312,38,37}
15-
set_env =
16-
SKIP = black-format
1715
commands = pre-commit run --all-files --hook-stage manual
1816

1917
[testenv:mypy]

0 commit comments

Comments
 (0)
Please sign in to comment.