summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/pythonpackage.yml2
-rw-r--r--README.md87
-rw-r--r--doc/source/intro.rst6
-rw-r--r--git/cmd.py37
-rw-r--r--git/compat.py18
-rw-r--r--git/config.py34
-rw-r--r--git/diff.py9
-rw-r--r--git/index/base.py12
-rw-r--r--git/objects/fun.py2
-rw-r--r--git/objects/submodule/base.py3
-rw-r--r--git/objects/tree.py2
-rw-r--r--git/objects/util.py18
-rw-r--r--git/refs/reference.py2
-rw-r--r--git/refs/symbolic.py21
-rw-r--r--git/remote.py14
-rw-r--r--git/repo/__init__.py2
-rw-r--r--git/repo/base.py138
-rw-r--r--git/repo/fun.py27
-rw-r--r--git/types.py2
-rw-r--r--git/util.py9
-rw-r--r--pyproject.toml6
-rwxr-xr-xsetup.py14
22 files changed, 247 insertions, 218 deletions
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index 8581c0bf..dd94ab9d 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [3.6, 3.7, 3.8, 3.9, "3.10.0-beta.4"]
+ python-version: [3.7, 3.8, 3.9, "3.10.0-beta.4"]
steps:
- uses: actions/checkout@v2
diff --git a/README.md b/README.md
index 5087dbcc..dd449d32 100644
--- a/README.md
+++ b/README.md
@@ -24,23 +24,21 @@ or low-level like git-plumbing.
It provides abstractions of git objects for easy access of repository data, and additionally
allows you to access the git repository more directly using either a pure python implementation,
-or the faster, but more resource intensive *git command* implementation.
+or the faster, but more resource intensive _git command_ implementation.
The object database implementation is optimized for handling large quantities of objects and large datasets,
which is achieved by using low-level structures and data streaming.
-
### DEVELOPMENT STATUS
This project is in **maintenance mode**, which means that
-* …there will be no feature development, unless these are contributed
-* …there will be no bug fixes, unless they are relevant to the safety of users, or contributed
-* …issues will be responded to with waiting times of up to a month
+- …there will be no feature development, unless these are contributed
+- …there will be no bug fixes, unless they are relevant to the safety of users, or contributed
+- …issues will be responded to with waiting times of up to a month
The project is open to contributions of all kinds, as well as new maintainers.
-
### REQUIREMENTS
GitPython needs the `git` executable to be installed on the system and available
@@ -48,8 +46,8 @@ in your `PATH` for most operations.
If it is not in your `PATH`, you can help GitPython find it by setting
the `GIT_PYTHON_GIT_EXECUTABLE=<path/to/git>` environment variable.
-* Git (1.7.x or newer)
-* Python >= 3.6
+- Git (1.7.x or newer)
+- Python >= 3.7
The list of dependencies are listed in `./requirements.txt` and `./test-requirements.txt`.
The installer takes care of installing them for you.
@@ -98,20 +96,20 @@ See [Issue #525](https://github.com/gitpython-developers/GitPython/issues/525).
### RUNNING TESTS
-*Important*: Right after cloning this repository, please be sure to have executed
+_Important_: Right after cloning this repository, please be sure to have executed
the `./init-tests-after-clone.sh` script in the repository root. Otherwise
you will encounter test failures.
-On *Windows*, make sure you have `git-daemon` in your PATH. For MINGW-git, the `git-daemon.exe`
+On _Windows_, make sure you have `git-daemon` in your PATH. For MINGW-git, the `git-daemon.exe`
exists in `Git\mingw64\libexec\git-core\`; CYGWIN has no daemon, but should get along fine
with MINGW's.
-Ensure testing libraries are installed.
-In the root directory, run: `pip install -r test-requirements.txt`
+Ensure testing libraries are installed.
+In the root directory, run: `pip install -r test-requirements.txt`
To lint, run: `flake8`
-To typecheck, run: `mypy -p git`
+To typecheck, run: `mypy -p git`
To test, run: `pytest`
@@ -119,36 +117,35 @@ Configuration for flake8 is in the ./.flake8 file.
Configurations for mypy, pytest and coverage.py are in ./pyproject.toml.
-The same linting and testing will also be performed against different supported python versions
+The same linting and testing will also be performed against different supported python versions
upon submitting a pull request (or on each push if you have a fork with a "main" branch and actions enabled).
-
### Contributions
Please have a look at the [contributions file][contributing].
### INFRASTRUCTURE
-* [User Documentation](http://gitpython.readthedocs.org)
-* [Questions and Answers](http://stackexchange.com/filters/167317/gitpython)
- * Please post on stackoverflow and use the `gitpython` tag
-* [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
- * Post reproducible bugs and feature requests as a new issue.
+- [User Documentation](http://gitpython.readthedocs.org)
+- [Questions and Answers](http://stackexchange.com/filters/167317/gitpython)
+- Please post on stackoverflow and use the `gitpython` tag
+- [Issue Tracker](https://github.com/gitpython-developers/GitPython/issues)
+ - Post reproducible bugs and feature requests as a new issue.
Please be sure to provide the following information if posting bugs:
- * GitPython version (e.g. `import git; git.__version__`)
- * Python version (e.g. `python --version`)
- * The encountered stack-trace, if applicable
- * Enough information to allow reproducing the issue
+ - GitPython version (e.g. `import git; git.__version__`)
+ - Python version (e.g. `python --version`)
+ - The encountered stack-trace, if applicable
+ - Enough information to allow reproducing the issue
### How to make a new release
-* Update/verify the **version** in the `VERSION` file
-* Update/verify that the `doc/source/changes.rst` changelog file was updated
-* Commit everything
-* Run `git tag -s <version>` to tag the version in Git
-* Run `make release`
-* Close the milestone mentioned in the _changelog_ and create a new one. _Do not reuse milestones by renaming them_.
-* set the upcoming version in the `VERSION` file, usually be
+- Update/verify the **version** in the `VERSION` file
+- Update/verify that the `doc/source/changes.rst` changelog file was updated
+- Commit everything
+- Run `git tag -s <version>` to tag the version in Git
+- Run `make release`
+- Close the milestone mentioned in the _changelog_ and create a new one. _Do not reuse milestones by renaming them_.
+- set the upcoming version in the `VERSION` file, usually be
incrementing the patch level, and possibly by appending `-dev`. Probably you
want to `git push` once more.
@@ -200,22 +197,22 @@ gpg --edit-key 4C08421980C9
### Projects using GitPython
-* [PyDriller](https://github.com/ishepard/pydriller)
-* [Kivy Designer](https://github.com/kivy/kivy-designer)
-* [Prowl](https://github.com/nettitude/Prowl)
-* [Python Taint](https://github.com/python-security/pyt)
-* [Buster](https://github.com/axitkhurana/buster)
-* [git-ftp](https://github.com/ezyang/git-ftp)
-* [Git-Pandas](https://github.com/wdm0006/git-pandas)
-* [PyGitUp](https://github.com/msiemens/PyGitUp)
-* [PyJFuzz](https://github.com/mseclab/PyJFuzz)
-* [Loki](https://github.com/Neo23x0/Loki)
-* [Omniwallet](https://github.com/OmniLayer/omniwallet)
-* [GitViper](https://github.com/BeayemX/GitViper)
-* [Git Gud](https://github.com/bthayer2365/git-gud)
+- [PyDriller](https://github.com/ishepard/pydriller)
+- [Kivy Designer](https://github.com/kivy/kivy-designer)
+- [Prowl](https://github.com/nettitude/Prowl)
+- [Python Taint](https://github.com/python-security/pyt)
+- [Buster](https://github.com/axitkhurana/buster)
+- [git-ftp](https://github.com/ezyang/git-ftp)
+- [Git-Pandas](https://github.com/wdm0006/git-pandas)
+- [PyGitUp](https://github.com/msiemens/PyGitUp)
+- [PyJFuzz](https://github.com/mseclab/PyJFuzz)
+- [Loki](https://github.com/Neo23x0/Loki)
+- [Omniwallet](https://github.com/OmniLayer/omniwallet)
+- [GitViper](https://github.com/BeayemX/GitViper)
+- [Git Gud](https://github.com/bthayer2365/git-gud)
### LICENSE
-New BSD License. See the LICENSE file.
+New BSD License. See the LICENSE file.
[contributing]: https://github.com/gitpython-developers/GitPython/blob/master/CONTRIBUTING.md
diff --git a/doc/source/intro.rst b/doc/source/intro.rst
index 956a3607..4f22a094 100644
--- a/doc/source/intro.rst
+++ b/doc/source/intro.rst
@@ -13,15 +13,17 @@ The object database implementation is optimized for handling large quantities of
Requirements
============
-* `Python`_ >= 3.6
+* `Python`_ >= 3.7
* `Git`_ 1.7.0 or newer
It should also work with older versions, but it may be that some operations
involving remotes will not work as expected.
* `GitDB`_ - a pure python git database implementation
+* `typing_extensions`_ >= 3.7.3.4 (if python < 3.10)
.. _Python: https://www.python.org
.. _Git: https://git-scm.com/
.. _GitDB: https://pypi.python.org/pypi/gitdb
+.. _typing_extensions: https://pypi.org/project/typing-extensions/
Installing GitPython
====================
@@ -60,7 +62,7 @@ Leakage of System Resources
---------------------------
GitPython is not suited for long-running processes (like daemons) as it tends to
-leak system resources. It was written in a time where destructors (as implemented
+leak system resources. It was written in a time where destructors (as implemented
in the `__del__` method) still ran deterministically.
In case you still want to use it in such a context, you will want to search the
diff --git a/git/cmd.py b/git/cmd.py
index f8212745..b84c43df 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -42,7 +42,7 @@ from .util import (
from typing import (Any, AnyStr, BinaryIO, Callable, Dict, IO, Iterator, List, Mapping,
Sequence, TYPE_CHECKING, TextIO, Tuple, Union, cast, overload)
-from git.types import PathLike, Literal
+from git.types import PathLike, Literal, TBD
if TYPE_CHECKING:
from git.repo.base import Repo
@@ -68,7 +68,7 @@ __all__ = ('Git',)
# Documentation
## @{
-def handle_process_output(process: subprocess.Popen,
+def handle_process_output(process: Union[subprocess.Popen, 'Git.AutoInterrupt'],
stdout_handler: Union[None,
Callable[[AnyStr], None],
Callable[[List[AnyStr]], None],
@@ -77,7 +77,7 @@ def handle_process_output(process: subprocess.Popen,
Callable[[AnyStr], None],
Callable[[List[AnyStr]], None]],
finalizer: Union[None,
- Callable[[subprocess.Popen], None]] = None,
+ Callable[[Union[subprocess.Popen, 'Git.AutoInterrupt']], None]] = None,
decode_streams: bool = True) -> None:
"""Registers for notifications to learn that process output is ready to read, and dispatches lines to
the respective line handlers.
@@ -165,7 +165,7 @@ CREATE_NO_WINDOW = 0x08000000
## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
# see https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined]
- if is_win else 0)
+ if is_win else 0) # mypy error if not windows
class Git(LazyMixin):
@@ -421,15 +421,15 @@ class Git(LazyMixin):
return getattr(self.proc, attr)
# TODO: Bad choice to mimic `proc.wait()` but with different args.
- def wait(self, stderr: Union[None, bytes] = b'') -> int:
+ def wait(self, stderr: Union[None, str, bytes] = b'') -> int:
"""Wait for the process and return its status code.
:param stderr: Previously read value of stderr, in case stderr is already closed.
:warn: may deadlock if output or error pipes are used and not handled separately.
:raise GitCommandError: if the return status is not 0"""
if stderr is None:
- stderr = b''
- stderr = force_bytes(data=stderr, encoding='utf-8')
+ stderr_b = b''
+ stderr_b = force_bytes(data=stderr, encoding='utf-8')
if self.proc is not None:
status = self.proc.wait()
@@ -437,11 +437,11 @@ class Git(LazyMixin):
def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
if stream:
try:
- return stderr + force_bytes(stream.read())
+ return stderr_b + force_bytes(stream.read())
except ValueError:
- return stderr or b''
+ return stderr_b or b''
else:
- return stderr or b''
+ return stderr_b or b''
if status != 0:
errstr = read_all_from_possibly_closed_stream(self.proc.stderr)
@@ -575,8 +575,8 @@ class Git(LazyMixin):
self._environment: Dict[str, str] = {}
# cached command slots
- self.cat_file_header = None
- self.cat_file_all = None
+ self.cat_file_header: Union[None, TBD] = None
+ self.cat_file_all: Union[None, TBD] = None
def __getattr__(self, name: str) -> Any:
"""A convenience method as it allows to call the command as if it was
@@ -1012,17 +1012,14 @@ class Git(LazyMixin):
@classmethod
def __unpack_args(cls, arg_list: Sequence[str]) -> List[str]:
- if not isinstance(arg_list, (list, tuple)):
- return [str(arg_list)]
outlist = []
- for arg in arg_list:
- if isinstance(arg_list, (list, tuple)):
+ if isinstance(arg_list, (list, tuple)):
+ for arg in arg_list:
outlist.extend(cls.__unpack_args(arg))
- # END recursion
- else:
- outlist.append(str(arg))
- # END for each arg
+ else:
+ outlist.append(str(arg_list))
+
return outlist
def __call__(self, **kwargs: Any) -> 'Git':
diff --git a/git/compat.py b/git/compat.py
index 7a0a15d2..988c04ef 100644
--- a/git/compat.py
+++ b/git/compat.py
@@ -29,8 +29,6 @@ from typing import (
Union,
overload,
)
-from git.types import TBD
-
# ---------------------------------------------------------------------------
@@ -97,19 +95,3 @@ def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
elif s is not None:
raise TypeError('Expected bytes or text, but got %r' % (s,))
return None
-
-
-# type: ignore ## mypy cannot understand dynamic class creation
-def with_metaclass(meta: Type[Any], *bases: Any) -> TBD:
- """copied from https://github.com/Byron/bcore/blob/master/src/python/butility/future.py#L15"""
-
- class metaclass(meta): # type: ignore
- __call__ = type.__call__
- __init__ = type.__init__ # type: ignore
-
- def __new__(cls, name: str, nbases: Optional[Tuple[int, ...]], d: Dict[str, Any]) -> TBD:
- if nbases is None:
- return type.__new__(cls, name, (), d)
- return meta(name, bases, d)
-
- return metaclass(meta.__name__ + 'Helper', None, {}) # type: ignore
diff --git a/git/config.py b/git/config.py
index 011d0e0b..293281d2 100644
--- a/git/config.py
+++ b/git/config.py
@@ -33,7 +33,7 @@ import configparser as cp
from typing import (Any, Callable, Generic, IO, List, Dict, Sequence,
TYPE_CHECKING, Tuple, TypeVar, Union, cast, overload)
-from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, TBD, assert_never, _T
+from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, assert_never, _T
if TYPE_CHECKING:
from git.repo.base import Repo
@@ -44,10 +44,10 @@ T_OMD_value = TypeVar('T_OMD_value', str, bytes, int, float, bool)
if sys.version_info[:3] < (3, 7, 2):
# typing.Ordereddict not added until py 3.7.2
- from collections import OrderedDict # type: ignore # until 3.6 dropped
- OrderedDict_OMD = OrderedDict # type: ignore # until 3.6 dropped
+ from collections import OrderedDict
+ OrderedDict_OMD = OrderedDict
else:
- from typing import OrderedDict # type: ignore # until 3.6 dropped
+ from typing import OrderedDict
OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]] # type: ignore[assignment, misc]
# -------------------------------------------------------------
@@ -72,7 +72,7 @@ CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbr
class MetaParserBuilder(abc.ABCMeta):
"""Utlity class wrapping base-class methods into decorators that assure read-only properties"""
- def __new__(cls, name: str, bases: TBD, clsdict: Dict[str, Any]) -> TBD:
+ def __new__(cls, name: str, bases: Tuple, clsdict: Dict[str, Any]) -> 'MetaParserBuilder':
"""
Equip all base-class methods with a needs_values decorator, and all non-const methods
with a set_dirty_and_flush_changes decorator in addition to that."""
@@ -177,7 +177,7 @@ class SectionConstraint(Generic[T_ConfigParser]):
class _OMD(OrderedDict_OMD):
"""Ordered multi-dict."""
- def __setitem__(self, key: str, value: _T) -> None: # type: ignore[override]
+ def __setitem__(self, key: str, value: _T) -> None:
super(_OMD, self).__setitem__(key, [value])
def add(self, key: str, value: Any) -> None:
@@ -203,8 +203,8 @@ class _OMD(OrderedDict_OMD):
prior = super(_OMD, self).__getitem__(key)
prior[-1] = value
- def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]: # type: ignore
- return super(_OMD, self).get(key, [default])[-1] # type: ignore
+ def get(self, key: str, default: Union[_T, None] = None) -> Union[_T, None]:
+ return super(_OMD, self).get(key, [default])[-1]
def getall(self, key: str) -> List[_T]:
return super(_OMD, self).__getitem__(key)
@@ -236,7 +236,8 @@ def get_config_path(config_level: Lit_config_levels) -> str:
raise ValueError("No repo to get repository configuration from. Use Repo._get_config_path")
else:
# Should not reach here. Will raise ValueError if does. Static typing will warn missing elifs
- assert_never(config_level, ValueError(f"Invalid configuration level: {config_level!r}"))
+ assert_never(config_level, # type: ignore[unreachable]
+ ValueError(f"Invalid configuration level: {config_level!r}"))
class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
@@ -299,10 +300,10 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
:param repo: Reference to repository to use if [includeIf] sections are found in configuration files.
"""
- cp.RawConfigParser.__init__(self, dict_type=_OMD) # type: ignore[arg-type]
- self._dict: Callable[..., _OMD] # type: ignore[assignment] # mypy/typeshed bug
- self._defaults: _OMD # type: ignore[assignment] # mypy/typeshed bug
- self._sections: _OMD # type: ignore[assignment] # mypy/typeshed bug
+ cp.RawConfigParser.__init__(self, dict_type=_OMD)
+ self._dict: Callable[..., _OMD] # type: ignore # mypy/typeshed bug?
+ self._defaults: _OMD
+ self._sections: _OMD # type: ignore # mypy/typeshed bug?
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
if not hasattr(self, '_proxies'):
@@ -617,12 +618,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
def write_section(name: str, section_dict: _OMD) -> None:
fp.write(("[%s]\n" % name).encode(defenc))
- values: Sequence[Union[str, bytes, int, float, bool]]
+ values: Sequence[str] # runtime only gets str in tests, but should be whatever _OMD stores
+ v: str
for (key, values) in section_dict.items_all():
if key == "__name__":
continue
- v: Union[str, bytes, int, float, bool]
for v in values:
fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace('\n', '\n\t'))).encode(defenc))
# END if key is not __name__
@@ -630,7 +631,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
if self._defaults:
write_section(cp.DEFAULTSECT, self._defaults)
- value: TBD
+ value: _OMD
+
for name, value in self._sections.items():
write_section(name, value)
diff --git a/git/diff.py b/git/diff.py
index 74ca0b64..cea66d7e 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -16,7 +16,7 @@ from .objects.util import mode_str_to_int
# typing ------------------------------------------------------------------
from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast
-from git.types import PathLike, TBD, Literal
+from git.types import PathLike, Literal
if TYPE_CHECKING:
from .objects.tree import Tree
@@ -24,6 +24,7 @@ if TYPE_CHECKING:
from git.repo.base import Repo
from git.objects.base import IndexObject
from subprocess import Popen
+ from git import Git
Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T', 'U']
@@ -442,7 +443,7 @@ class Diff(object):
return None
@ classmethod
- def _index_from_patch_format(cls, repo: 'Repo', proc: TBD) -> DiffIndex:
+ def _index_from_patch_format(cls, repo: 'Repo', proc: Union['Popen', 'Git.AutoInterrupt']) -> DiffIndex:
"""Create a new DiffIndex from the given text which must be in patch format
:param repo: is the repository we are operating on - it is required
:param stream: result of 'git diff' as a stream (supporting file protocol)
@@ -455,8 +456,8 @@ class Diff(object):
# for now, we have to bake the stream
text = b''.join(text_list)
index: 'DiffIndex' = DiffIndex()
- previous_header = None
- header = None
+ previous_header: Union[Match[bytes], None] = None
+ header: Union[Match[bytes], None] = None
a_path, b_path = None, None # for mypy
a_mode, b_mode = None, None # for mypy
for _header in cls.re_header.finditer(text):
diff --git a/git/index/base.py b/git/index/base.py
index 6452419c..102703e6 100644
--- a/git/index/base.py
+++ b/git/index/base.py
@@ -70,7 +70,7 @@ from .util import (
from typing import (Any, BinaryIO, Callable, Dict, IO, Iterable, Iterator, List, NoReturn,
Sequence, TYPE_CHECKING, Tuple, Type, Union)
-from git.types import Commit_ish, PathLike, TBD
+from git.types import Commit_ish, PathLike
if TYPE_CHECKING:
from subprocess import Popen
@@ -181,7 +181,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
self.version, self.entries, self._extension_data, _conten_sha = read_cache(stream)
return self
- def _entries_sorted(self) -> List[TBD]:
+ def _entries_sorted(self) -> List[IndexEntry]:
""":return: list of entries, in a sorted fashion, first by path, then by stage"""
return sorted(self.entries.values(), key=lambda e: (e.path, e.stage))
@@ -427,8 +427,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# END path exception handling
# END for each path
- def _write_path_to_stdin(self, proc: 'Popen', filepath: PathLike, item: TBD, fmakeexc: Callable[..., GitError],
- fprogress: Callable[[PathLike, bool, TBD], None],
+ def _write_path_to_stdin(self, proc: 'Popen', filepath: PathLike, item: PathLike, fmakeexc: Callable[..., GitError],
+ fprogress: Callable[[PathLike, bool, PathLike], None],
read_from_stdout: bool = True) -> Union[None, str]:
"""Write path to proc.stdin and make sure it processes the item, including progress.
@@ -492,12 +492,13 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
are at stage 3 will not have a stage 3 entry.
"""
is_unmerged_blob = lambda t: t[0] != 0
- path_map: Dict[PathLike, List[Tuple[TBD, Blob]]] = {}
+ path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {}
for stage, blob in self.iter_blobs(is_unmerged_blob):
path_map.setdefault(blob.path, []).append((stage, blob))
# END for each unmerged blob
for line in path_map.values():
line.sort()
+
return path_map
@ classmethod
@@ -1201,7 +1202,6 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
handle_stderr(proc, checked_out_files)
return checked_out_files
# END paths handling
- assert "Should not reach this point"
@ default_index
def reset(self, commit: Union[Commit, 'Reference', str] = 'HEAD', working_tree: bool = False,
diff --git a/git/objects/fun.py b/git/objects/fun.py
index d6cdafe1..19b4e525 100644
--- a/git/objects/fun.py
+++ b/git/objects/fun.py
@@ -51,7 +51,7 @@ def tree_to_stream(entries: Sequence[EntryTup], write: Callable[['ReadableBuffer
if isinstance(name, str):
name_bytes = name.encode(defenc)
else:
- name_bytes = name
+ name_bytes = name # type: ignore[unreachable] # check runtime types - is always str?
write(b''.join((mode_str, b' ', name_bytes, b'\0', binsha)))
# END for each item
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index 559d2585..d306c91d 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -379,6 +379,7 @@ class Submodule(IndexObject, TraversableIterableObj):
:return: The newly created submodule instance
:note: works atomically, such that no change will be done if the repository
update fails for instance"""
+
if repo.bare:
raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
# END handle bare repos
@@ -434,7 +435,7 @@ class Submodule(IndexObject, TraversableIterableObj):
url = urls[0]
else:
# clone new repo
- kwargs: Dict[str, Union[bool, int, Sequence[TBD]]] = {'n': no_checkout}
+ kwargs: Dict[str, Union[bool, int, str, Sequence[TBD]]] = {'n': no_checkout}
if not branch_is_default:
kwargs['b'] = br.name
# END setup checkout-branch
diff --git a/git/objects/tree.py b/git/objects/tree.py
index 0cceb59a..22531895 100644
--- a/git/objects/tree.py
+++ b/git/objects/tree.py
@@ -215,7 +215,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
super(Tree, self).__init__(repo, binsha, mode, path)
@ classmethod
- def _get_intermediate_items(cls, index_object: 'Tree',
+ def _get_intermediate_items(cls, index_object: IndexObjUnion,
) -> Union[Tuple['Tree', ...], Tuple[()]]:
if index_object.type == "tree":
return tuple(index_object._iter_convert_to_object(index_object._cache))
diff --git a/git/objects/util.py b/git/objects/util.py
index f627211e..16d4c0ac 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -167,7 +167,7 @@ def from_timestamp(timestamp: float, tz_offset: float) -> datetime:
return utc_dt
-def parse_date(string_date: str) -> Tuple[int, int]:
+def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
"""
Parse the given date as one of the following
@@ -181,9 +181,13 @@ def parse_date(string_date: str) -> Tuple[int, int]:
:raise ValueError: If the format could not be understood
:note: Date can also be YYYY.MM.DD, MM/DD/YYYY and DD.MM.YYYY.
"""
- if isinstance(string_date, datetime) and string_date.tzinfo:
- offset = -int(string_date.utcoffset().total_seconds())
- return int(string_date.astimezone(utc).timestamp()), offset
+ if isinstance(string_date, datetime):
+ if string_date.tzinfo:
+ utcoffset = cast(timedelta, string_date.utcoffset()) # typeguard, if tzinfoand is not None
+ offset = -int(utcoffset.total_seconds())
+ return int(string_date.astimezone(utc).timestamp()), offset
+ else:
+ raise ValueError(f"string_date datetime object without tzinfo, {string_date}")
# git time
try:
@@ -245,7 +249,7 @@ def parse_date(string_date: str) -> Tuple[int, int]:
raise ValueError("no format matched")
# END handle format
except Exception as e:
- raise ValueError("Unsupported date format: %s" % string_date) from e
+ raise ValueError(f"Unsupported date format or type: {string_date}, type={type(string_date)}") from e
# END handle exceptions
@@ -338,7 +342,7 @@ class Traversable(Protocol):
"""
# Commit and Submodule have id.__attribute__ as IterableObj
# Tree has id.__attribute__ inherited from IndexObject
- if isinstance(self, (TraversableIterableObj, Has_id_attribute)):
+ if isinstance(self, Has_id_attribute):
id = self._id_attribute_
else:
id = "" # shouldn't reach here, unless Traversable subclass created with no _id_attribute_
@@ -346,7 +350,7 @@ class Traversable(Protocol):
if not as_edge:
out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id)
- out.extend(self.traverse(as_edge=as_edge, *args, **kwargs)) # type: ignore
+ out.extend(self.traverse(as_edge=as_edge, *args, **kwargs))
return out
# overloads in subclasses (mypy does't allow typing self: subclass)
# Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]]
diff --git a/git/refs/reference.py b/git/refs/reference.py
index a3647fb3..2a33fbff 100644
--- a/git/refs/reference.py
+++ b/git/refs/reference.py
@@ -8,7 +8,7 @@ from .symbolic import SymbolicReference, T_References
# typing ------------------------------------------------------------------
from typing import Any, Callable, Iterator, Type, Union, TYPE_CHECKING # NOQA
-from git.types import Commit_ish, PathLike, TBD, Literal, _T # NOQA
+from git.types import Commit_ish, PathLike, _T # NOQA
if TYPE_CHECKING:
from git.repo import Repo
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index b4a933aa..0c0fa404 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -21,8 +21,8 @@ from .log import RefLog
# typing ------------------------------------------------------------------
-from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast # NOQA
-from git.types import Commit_ish, PathLike, TBD, Literal # NOQA
+from typing import Any, Iterator, List, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast # NOQA
+from git.types import Commit_ish, PathLike # NOQA
if TYPE_CHECKING:
from git.repo import Repo
@@ -72,12 +72,13 @@ class SymbolicReference(object):
def __repr__(self) -> str:
return '<git.%s "%s">' % (self.__class__.__name__, self.path)
- def __eq__(self, other: Any) -> bool:
+ def __eq__(self, other: object) -> bool:
if hasattr(other, 'path'):
+ other = cast(SymbolicReference, other)
return self.path == other.path
return False
- def __ne__(self, other: Any) -> bool:
+ def __ne__(self, other: object) -> bool:
return not (self == other)
def __hash__(self) -> int:
@@ -284,7 +285,7 @@ class SymbolicReference(object):
commit = property(_get_commit, set_commit, doc="Query or set commits directly") # type: ignore
object = property(_get_object, set_object, doc="Return the object our ref currently refers to") # type: ignore
- def _get_reference(self) -> 'Reference':
+ def _get_reference(self) -> 'SymbolicReference':
""":return: Reference Object we point to
:raise TypeError: If this symbolic reference is detached, hence it doesn't point
to a reference, but to a commit"""
@@ -364,8 +365,9 @@ class SymbolicReference(object):
return self
# aliased reference
+ reference: Union['Head', 'TagReference', 'RemoteReference', 'Reference']
reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") # type: ignore
- ref: Union['Head', 'TagReference', 'RemoteReference', 'Reference'] = reference # type: ignore
+ ref = reference
def is_valid(self) -> bool:
"""
@@ -681,7 +683,7 @@ class SymbolicReference(object):
return (r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached)
@classmethod
- def from_path(cls, repo: 'Repo', path: PathLike) -> Union['Head', 'TagReference', 'Reference']:
+ def from_path(cls: Type[T_References], repo: 'Repo', path: PathLike) -> T_References:
"""
:param path: full .git-directory-relative path name to the Reference to instantiate
:note: use to_full_path() if you only have a partial path of a known Reference Type
@@ -696,10 +698,13 @@ class SymbolicReference(object):
from . import HEAD, Head, RemoteReference, TagReference, Reference
for ref_type in (HEAD, Head, RemoteReference, TagReference, Reference, SymbolicReference):
try:
+ instance: T_References
instance = ref_type(repo, path)
if instance.__class__ == SymbolicReference and instance.is_detached:
raise ValueError("SymbolRef was detached, we drop it")
- return instance
+ else:
+ return instance
+
except ValueError:
pass
# END exception handling
diff --git a/git/remote.py b/git/remote.py
index 11007cb6..3888506f 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -37,10 +37,10 @@ from .refs import (
# typing-------------------------------------------------------
-from typing import (Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence, # NOQA[TC002]
+from typing import (Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence,
TYPE_CHECKING, Type, Union, cast, overload)
-from git.types import PathLike, Literal, TBD, Commit_ish # NOQA[TC002]
+from git.types import PathLike, Literal, Commit_ish
if TYPE_CHECKING:
from git.repo.base import Repo
@@ -50,7 +50,6 @@ if TYPE_CHECKING:
flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't', '?']
-
# def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]:
# return inp in [' ', '!', '+', '-', '=', '*', 't', '?']
@@ -633,7 +632,7 @@ class Remote(LazyMixin, IterableObj):
as well. This is a fix for the issue described here:
https://github.com/gitpython-developers/GitPython/issues/260
"""
- out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
+ out_refs: IterableList[Reference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]:
# expecting
# * [would prune] origin/new_branch
@@ -643,7 +642,7 @@ class Remote(LazyMixin, IterableObj):
ref_name = line.replace(token, "")
# sometimes, paths start with a full ref name, like refs/tags/foo, see #260
if ref_name.startswith(Reference._common_path_default + '/'):
- out_refs.append(SymbolicReference.from_path(self.repo, ref_name))
+ out_refs.append(Reference.from_path(self.repo, ref_name))
else:
fqhn = "%s/%s" % (RemoteReference._common_path_default, ref_name)
out_refs.append(RemoteReference(self.repo, fqhn))
@@ -707,9 +706,10 @@ class Remote(LazyMixin, IterableObj):
self.repo.git.remote(scmd, self.name, **kwargs)
return self
- def _get_fetch_info_from_stderr(self, proc: TBD,
+ def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt',
progress: Union[Callable[..., Any], RemoteProgress, None]
) -> IterableList['FetchInfo']:
+
progress = to_progress_instance(progress)
# skip first line as it is some remote info we are not interested in
@@ -768,7 +768,7 @@ class Remote(LazyMixin, IterableObj):
log.warning("Git informed while fetching: %s", err_line.strip())
return output
- def _get_push_info(self, proc: TBD,
+ def _get_push_info(self, proc: 'Git.AutoInterrupt',
progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList[PushInfo]:
progress = to_progress_instance(progress)
diff --git a/git/repo/__init__.py b/git/repo/__init__.py
index 712df60d..23c18db8 100644
--- a/git/repo/__init__.py
+++ b/git/repo/__init__.py
@@ -1,3 +1,3 @@
"""Initialize the Repo package"""
# flake8: noqa
-from .base import *
+from .base import Repo as Repo
diff --git a/git/repo/base.py b/git/repo/base.py
index 5581233b..c0229a84 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -3,6 +3,7 @@
#
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
+from __future__ import annotations
import logging
import os
import re
@@ -37,13 +38,13 @@ import gitdb
# typing ------------------------------------------------------
-from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish
+from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish, assert_never
from typing import (Any, BinaryIO, Callable, Dict,
Iterator, List, Mapping, Optional, Sequence,
TextIO, Tuple, Type, Union,
NamedTuple, cast, TYPE_CHECKING)
-from git.types import ConfigLevels_Tup
+from git.types import ConfigLevels_Tup, TypedDict
if TYPE_CHECKING:
from git.util import IterableList
@@ -52,7 +53,6 @@ if TYPE_CHECKING:
from git.objects.submodule.base import UpdateProgress
from git.remote import RemoteProgress
-
# -----------------------------------------------------------
log = logging.getLogger(__name__)
@@ -200,7 +200,6 @@ class Repo(object):
# END while curpath
if self.git_dir is None:
- self.git_dir = cast(PathLike, self.git_dir)
raise InvalidGitRepositoryError(epath)
self._bare = False
@@ -235,7 +234,7 @@ class Repo(object):
def __enter__(self) -> 'Repo':
return self
- def __exit__(self, exc_type: TBD, exc_value: TBD, traceback: TBD) -> None:
+ def __exit__(self, *args: Any) -> None:
self.close()
def __del__(self) -> None:
@@ -385,13 +384,13 @@ class Repo(object):
:return: created submodules"""
return Submodule.add(self, *args, **kwargs)
- def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator:
+ def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]:
"""An iterator yielding Submodule instances, see Traversable interface
for a description of args and kwargs
:return: Iterator"""
return RootModule(self).traverse(*args, **kwargs)
- def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator:
+ def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]:
"""Update the submodules, keeping the repository consistent as it will
take the previous state into consideration. For more information, please
see the documentation of RootModule.update"""
@@ -445,7 +444,7 @@ class Repo(object):
:return: TagReference object """
return TagReference.create(self, path, ref, message, force, **kwargs)
- def delete_tag(self, *tags: TBD) -> None:
+ def delete_tag(self, *tags: TagReference) -> None:
"""Delete the given tag references"""
return TagReference.delete(self, *tags)
@@ -481,10 +480,12 @@ class Repo(object):
raise NotADirectoryError
else:
return osp.normpath(osp.join(repo_dir, "config"))
+ else:
- raise ValueError("Invalid configuration level: %r" % config_level)
+ assert_never(config_level, # type:ignore[unreachable]
+ ValueError(f"Invalid configuration level: {config_level!r}"))
- def config_reader(self, config_level: Optional[Lit_config_levels] = None
+ def config_reader(self, config_level: Optional[Lit_config_levels] = None,
) -> GitConfigParser:
"""
:return:
@@ -775,7 +776,7 @@ class Repo(object):
finalize_process(proc)
return untracked_files
- def ignored(self, *paths: PathLike) -> List[PathLike]:
+ def ignored(self, *paths: PathLike) -> List[str]:
"""Checks if paths are ignored via .gitignore
Doing so using the "git check-ignore" method.
@@ -783,7 +784,7 @@ class Repo(object):
:return: subset of those paths which are ignored
"""
try:
- proc = self.git.check_ignore(*paths)
+ proc: str = self.git.check_ignore(*paths)
except GitCommandError:
return []
return proc.replace("\\\\", "\\").replace('"', "").split("\n")
@@ -795,7 +796,7 @@ class Repo(object):
# reveal_type(self.head.reference) # => Reference
return self.head.reference
- def blame_incremental(self, rev: TBD, file: TBD, **kwargs: Any) -> Optional[Iterator['BlameEntry']]:
+ def blame_incremental(self, rev: str | HEAD, file: str, **kwargs: Any) -> Iterator['BlameEntry']:
"""Iterator for blame information for the given file at the given revision.
Unlike .blame(), this does not return the actual file's contents, only
@@ -809,8 +810,9 @@ class Repo(object):
If you combine all line number ranges outputted by this command, you
should get a continuous range spanning all line numbers in the file.
"""
- data = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs)
- commits: Dict[str, Commit] = {}
+
+ data: bytes = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs)
+ commits: Dict[bytes, Commit] = {}
stream = (line for line in data.split(b'\n') if line)
while True:
@@ -818,15 +820,15 @@ class Repo(object):
line = next(stream) # when exhausted, causes a StopIteration, terminating this function
except StopIteration:
return
- split_line: Tuple[str, str, str, str] = line.split()
- hexsha, orig_lineno_str, lineno_str, num_lines_str = split_line
- lineno = int(lineno_str)
- num_lines = int(num_lines_str)
- orig_lineno = int(orig_lineno_str)
+ split_line = line.split()
+ hexsha, orig_lineno_b, lineno_b, num_lines_b = split_line
+ lineno = int(lineno_b)
+ num_lines = int(num_lines_b)
+ orig_lineno = int(orig_lineno_b)
if hexsha not in commits:
# Now read the next few lines and build up a dict of properties
# for this commit
- props = {}
+ props: Dict[bytes, bytes] = {}
while True:
try:
line = next(stream)
@@ -870,8 +872,8 @@ class Repo(object):
safe_decode(orig_filename),
range(orig_lineno, orig_lineno + num_lines))
- def blame(self, rev: TBD, file: TBD, incremental: bool = False, **kwargs: Any
- ) -> Union[List[List[Union[Optional['Commit'], List[str]]]], Optional[Iterator[BlameEntry]]]:
+ def blame(self, rev: Union[str, HEAD], file: str, incremental: bool = False, **kwargs: Any
+ ) -> List[List[Commit | List[str | bytes] | None]] | Iterator[BlameEntry] | None:
"""The blame information for the given file at the given revision.
:param rev: revision specifier, see git-rev-parse for viable options.
@@ -883,25 +885,38 @@ class Repo(object):
if incremental:
return self.blame_incremental(rev, file, **kwargs)
- data = self.git.blame(rev, '--', file, p=True, stdout_as_string=False, **kwargs)
- commits: Dict[str, TBD] = {}
- blames: List[List[Union[Optional['Commit'], List[str]]]] = []
-
- info: Dict[str, TBD] = {} # use Any until TypedDict available
+ data: bytes = self.git.blame(rev, '--', file, p=True, stdout_as_string=False, **kwargs)
+ commits: Dict[str, Commit] = {}
+ blames: List[List[Commit | List[str | bytes] | None]] = []
+
+ class InfoTD(TypedDict, total=False):
+ sha: str
+ id: str
+ filename: str
+ summary: str
+ author: str
+ author_email: str
+ author_date: int
+ committer: str
+ committer_email: str
+ committer_date: int
+
+ info: InfoTD = {}
keepends = True
- for line in data.splitlines(keepends):
+ for line_bytes in data.splitlines(keepends):
try:
- line = line.rstrip().decode(defenc)
+ line_str = line_bytes.rstrip().decode(defenc)
except UnicodeDecodeError:
firstpart = ''
+ parts = []
is_binary = True
else:
# As we don't have an idea when the binary data ends, as it could contain multiple newlines
# in the process. So we rely on being able to decode to tell us what is is.
# This can absolutely fail even on text files, but even if it does, we should be fine treating it
# as binary instead
- parts = self.re_whitespace.split(line, 1)
+ parts = self.re_whitespace.split(line_str, 1)
firstpart = parts[0]
is_binary = False
# end handle decode of line
@@ -932,12 +947,20 @@ class Repo(object):
# committer-time 1192271832
# committer-tz -0700 - IGNORED BY US
role = m.group(0)
- if firstpart.endswith('-mail'):
- info["%s_email" % role] = parts[-1]
- elif firstpart.endswith('-time'):
- info["%s_date" % role] = int(parts[-1])
- elif role == firstpart:
- info[role] = parts[-1]
+ if role == 'author':
+ if firstpart.endswith('-mail'):
+ info["author_email"] = parts[-1]
+ elif firstpart.endswith('-time'):
+ info["author_date"] = int(parts[-1])
+ elif role == firstpart:
+ info["author"] = parts[-1]
+ elif role == 'committer':
+ if firstpart.endswith('-mail'):
+ info["committer_email"] = parts[-1]
+ elif firstpart.endswith('-time'):
+ info["committer_date"] = int(parts[-1])
+ elif role == firstpart:
+ info["committer"] = parts[-1]
# END distinguish mail,time,name
else:
# handle
@@ -954,26 +977,29 @@ class Repo(object):
c = commits.get(sha)
if c is None:
c = Commit(self, hex_to_bin(sha),
- author=Actor._from_string(info['author'] + ' ' + info['author_email']),
+ author=Actor._from_string(f"{info['author']} {info['author_email']}"),
authored_date=info['author_date'],
committer=Actor._from_string(
- info['committer'] + ' ' + info['committer_email']),
+ f"{info['committer']} {info['committer_email']}"),
committed_date=info['committer_date'])
commits[sha] = c
- # END if commit objects needs initial creation
- if not is_binary:
- if line and line[0] == '\t':
- line = line[1:]
- else:
- # NOTE: We are actually parsing lines out of binary data, which can lead to the
- # binary being split up along the newline separator. We will append this to the blame
- # we are currently looking at, even though it should be concatenated with the last line
- # we have seen.
- pass
- # end handle line contents
blames[-1][0] = c
+ # END if commit objects needs initial creation
+
if blames[-1][1] is not None:
+ line: str | bytes
+ if not is_binary:
+ if line_str and line_str[0] == '\t':
+ line_str = line_str[1:]
+ line = line_str
+ else:
+ line = line_bytes
+ # NOTE: We are actually parsing lines out of binary data, which can lead to the
+ # binary being split up along the newline separator. We will append this to the
+ # blame we are currently looking at, even though it should be concatenated with
+ # the last line we have seen.
blames[-1][1].append(line)
+
info = {'id': sha}
# END if we collected commit info
# END distinguish filename,summary,rest
@@ -981,7 +1007,7 @@ class Repo(object):
# END distinguish hexsha vs other information
return blames
- @classmethod
+ @ classmethod
def init(cls, path: Union[PathLike, None] = None, mkdir: bool = True, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
expand_vars: bool = True, **kwargs: Any) -> 'Repo':
"""Initialize a git repository at the given path if specified
@@ -1020,7 +1046,7 @@ class Repo(object):
git.init(**kwargs)
return cls(path, odbt=odbt)
- @classmethod
+ @ classmethod
def _clone(cls, git: 'Git', url: PathLike, path: PathLike, odb_default_type: Type[GitCmdObjectDB],
progress: Union['RemoteProgress', 'UpdateProgress', Callable[..., 'RemoteProgress'], None] = None,
multi_options: Optional[List[str]] = None, **kwargs: Any
@@ -1098,9 +1124,9 @@ class Repo(object):
:return: ``git.Repo`` (the newly cloned repo)"""
return self._clone(self.git, self.common_dir, path, type(self.odb), progress, multi_options, **kwargs)
- @classmethod
+ @ classmethod
def clone_from(cls, url: PathLike, to_path: PathLike, progress: Optional[Callable] = None,
- env: Optional[Mapping[str, Any]] = None,
+ env: Optional[Mapping[str, str]] = None,
multi_options: Optional[List[str]] = None, **kwargs: Any) -> 'Repo':
"""Create a clone from the given URL
@@ -1122,7 +1148,7 @@ class Repo(object):
return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
def archive(self, ostream: Union[TextIO, BinaryIO], treeish: Optional[str] = None,
- prefix: Optional[str] = None, **kwargs: Any) -> 'Repo':
+ prefix: Optional[str] = None, **kwargs: Any) -> Repo:
"""Archive the tree at the given revision.
:param ostream: file compatible stream object to which the archive will be written as bytes
@@ -1169,7 +1195,7 @@ class Repo(object):
clazz = self.__class__
return '<%s.%s %r>' % (clazz.__module__, clazz.__name__, self.git_dir)
- def currently_rebasing_on(self) -> Union['SymbolicReference', Commit_ish, None]:
+ def currently_rebasing_on(self) -> Commit | None:
"""
:return: The commit which is currently being replayed while rebasing.
diff --git a/git/repo/fun.py b/git/repo/fun.py
index 7d5c7823..1a83dd3d 100644
--- a/git/repo/fun.py
+++ b/git/repo/fun.py
@@ -1,4 +1,5 @@
"""Package with general repository related functions"""
+from __future__ import annotations
import os
import stat
from string import digits
@@ -18,12 +19,13 @@ from git.cmd import Git
# Typing ----------------------------------------------------------------------
from typing import Union, Optional, cast, TYPE_CHECKING
-
+from git.types import Commit_ish
if TYPE_CHECKING:
from git.types import PathLike
from .base import Repo
from git.db import GitCmdObjectDB
+ from git.refs.reference import Reference
from git.objects import Commit, TagObject, Blob, Tree
from git.refs.tag import Tag
@@ -202,7 +204,7 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
raise NotImplementedError("commit by message search ( regex )")
# END handle search
- obj = cast(Object, None) # not ideal. Should use guards
+ obj: Union[Commit_ish, 'Reference', None] = None
ref = None
output_type = "commit"
start = 0
@@ -222,14 +224,16 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
ref = repo.head.ref
else:
if token == '@':
- ref = name_to_object(repo, rev[:start], return_ref=True)
+ ref = cast('Reference', name_to_object(repo, rev[:start], return_ref=True))
else:
- obj = name_to_object(repo, rev[:start])
+ obj = cast(Commit_ish, name_to_object(repo, rev[:start]))
# END handle token
# END handle refname
+ else:
+ assert obj is not None
if ref is not None:
- obj = ref.commit
+ obj = cast('Commit', ref.commit)
# END handle ref
# END initialize obj on first token
@@ -247,11 +251,13 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
pass # default
elif output_type == 'tree':
try:
+ obj = cast(Commit_ish, obj)
obj = to_commit(obj).tree
except (AttributeError, ValueError):
pass # error raised later
# END exception handling
elif output_type in ('', 'blob'):
+ obj = cast('TagObject', obj)
if obj and obj.type == 'tag':
obj = deref_tag(obj)
else:
@@ -280,13 +286,13 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha))
# make it pass the following checks
- output_type = None
+ output_type = ''
else:
raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
# END handle output type
# empty output types don't require any specific type, its just about dereferencing tags
- if output_type and obj.type != output_type:
+ if output_type and obj and obj.type != output_type:
raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
# END verify output type
@@ -319,6 +325,7 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
parsed_to = start
# handle hierarchy walk
try:
+ obj = cast(Commit_ish, obj)
if token == "~":
obj = to_commit(obj)
for _ in range(num):
@@ -340,14 +347,14 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
# END end handle tag
except (IndexError, AttributeError) as e:
raise BadName(
- "Invalid revision spec '%s' - not enough "
- "parent commits to reach '%s%i'" % (rev, token, num)) from e
+ f"Invalid revision spec '{rev}' - not enough "
+ f"parent commits to reach '{token}{int(num)}'") from e
# END exception handling
# END parse loop
# still no obj ? Its probably a simple name
if obj is None:
- obj = name_to_object(repo, rev)
+ obj = cast(Commit_ish, name_to_object(repo, rev))
parsed_to = lr
# END handle simple name
diff --git a/git/types.py b/git/types.py
index ccaffef3..64bf3d96 100644
--- a/git/types.py
+++ b/git/types.py
@@ -23,7 +23,7 @@ if sys.version_info[:2] < (3, 9):
PathLike = Union[str, os.PathLike]
elif sys.version_info[:2] >= (3, 9):
# os.PathLike only becomes subscriptable from Python 3.9 onwards
- PathLike = Union[str, 'os.PathLike[str]'] # forward ref as pylance complains unless editing with py3.9+
+ PathLike = Union[str, os.PathLike]
if TYPE_CHECKING:
from git.repo import Repo
diff --git a/git/util.py b/git/util.py
index 92d95379..4f82219e 100644
--- a/git/util.py
+++ b/git/util.py
@@ -38,6 +38,7 @@ if TYPE_CHECKING:
from git.remote import Remote
from git.repo.base import Repo
from git.config import GitConfigParser, SectionConstraint
+ from git import Git
# from git.objects.base import IndexObject
@@ -69,7 +70,7 @@ from gitdb.util import ( # NOQA @IgnorePep8
# Most of these are unused here, but are for use by git-python modules so these
# don't see gitdb all the time. Flake of course doesn't like it.
__all__ = ["stream_copy", "join_path", "to_native_path_linux",
- "join_path_native", "Stats", "IndexFileSHA1Writer", "Iterable", "IterableList",
+ "join_path_native", "Stats", "IndexFileSHA1Writer", "IterableObj", "IterableList",
"BlockingLockFile", "LockFile", 'Actor', 'get_user_id', 'assure_directory_exists',
'RemoteProgress', 'CallableRemoteProgress', 'rmtree', 'unbare_repo',
'HIDE_WINDOWS_KNOWN_ERRORS']
@@ -379,7 +380,7 @@ def get_user_id() -> str:
return "%s@%s" % (getpass.getuser(), platform.node())
-def finalize_process(proc: subprocess.Popen, **kwargs: Any) -> None:
+def finalize_process(proc: Union[subprocess.Popen, 'Git.AutoInterrupt'], **kwargs: Any) -> None:
"""Wait for the process (clone, fetch, pull or push) and handle its errors accordingly"""
# TODO: No close proc-streams??
proc.wait(**kwargs)
@@ -403,7 +404,7 @@ def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[
p = osp.expanduser(p) # type: ignore
if expand_vars:
p = osp.expandvars(p) # type: ignore
- return osp.normpath(osp.abspath(p)) # type: ignore
+ return osp.normpath(osp.abspath(p)) # type: ignore
except Exception:
return None
@@ -1033,7 +1034,7 @@ class IterableList(List[T_IterableObj]):
class IterableClassWatcher(type):
""" Metaclass that watches """
- def __init__(cls, name: str, bases: List, clsdict: Dict) -> None:
+ def __init__(cls, name: str, bases: Tuple, clsdict: Dict) -> None:
for base in bases:
if type(base) == IterableClassWatcher:
warnings.warn(f"GitPython Iterable subclassed by {name}. "
diff --git a/pyproject.toml b/pyproject.toml
index 4751ffcb..102b6fdc 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,9 +22,11 @@ filterwarnings = 'ignore::DeprecationWarning'
disallow_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
-# warn_unused_ignores = True
-# warn_unreachable = True
+# warn_unused_ignores = true
+warn_unreachable = true
show_error_codes = true
+implicit_reexport = true
+# strict = true
# TODO: remove when 'gitdb' is fully annotated
[[tool.mypy.overrides]]
diff --git a/setup.py b/setup.py
index 21559071..ae6319f9 100755
--- a/setup.py
+++ b/setup.py
@@ -1,3 +1,4 @@
+from typing import Sequence
from setuptools import setup, find_packages
from setuptools.command.build_py import build_py as _build_py
from setuptools.command.sdist import sdist as _sdist
@@ -18,7 +19,7 @@ with open('test-requirements.txt') as reqs_file:
class build_py(_build_py):
- def run(self):
+ def run(self) -> None:
init = path.join(self.build_lib, 'git', '__init__.py')
if path.exists(init):
os.unlink(init)
@@ -29,7 +30,7 @@ class build_py(_build_py):
class sdist(_sdist):
- def make_release_tree(self, base_dir, files):
+ def make_release_tree(self, base_dir: str, files: Sequence) -> None:
_sdist.make_release_tree(self, base_dir, files)
orig = path.join('git', '__init__.py')
assert path.exists(orig), orig
@@ -40,7 +41,7 @@ class sdist(_sdist):
_stamp_version(dest)
-def _stamp_version(filename):
+def _stamp_version(filename: str) -> None:
found, out = False, []
try:
with open(filename, 'r') as f:
@@ -59,7 +60,7 @@ def _stamp_version(filename):
print("WARNING: Couldn't find version line in file %s" % filename, file=sys.stderr)
-def build_py_modules(basedir, excludes=()):
+def build_py_modules(basedir: str, excludes: Sequence = ()) -> Sequence:
# create list of py_modules from tree
res = set()
_prefix = os.path.basename(basedir)
@@ -90,7 +91,7 @@ setup(
include_package_data=True,
py_modules=build_py_modules("./git", excludes=["git.ext.*"]),
package_dir={'git': 'git'},
- python_requires='>=3.6',
+ python_requires='>=3.7',
install_requires=requirements,
tests_require=requirements + test_requirements,
zip_safe=False,
@@ -112,11 +113,12 @@ setup(
"Operating System :: POSIX",
"Operating System :: Microsoft :: Windows",
"Operating System :: MacOS :: MacOS X",
+ "Typing:: Typed",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
- "Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9"
+ "Programming Language :: Python :: 3.10"
]
)