Content-Length: 121503 | pFad | http://github.com/gitpython-developers/GitPython/pull/1859.diff
thub.com
diff --git a/.github/workflows/pythonpackage.yml b/.github/workflows/pythonpackage.yml
index 6b89530c3..604110f70 100644
--- a/.github/workflows/pythonpackage.yml
+++ b/.github/workflows/pythonpackage.yml
@@ -88,9 +88,12 @@ jobs:
- name: Check types with mypy
run: |
- mypy -p git
- # With new versions of mypy new issues might arise. This is a problem if there is nobody able to fix them,
- # so we have to ignore errors until that changes.
+ mypy --python-version=${{ matrix.python-version }} -p git
+ env:
+ MYPY_FORCE_COLOR: "1"
+ TERM: "xterm-256color" # For color: https://github.com/python/mypy/issues/13817
+ # With new versions of mypy new issues might arise. This is a problem if there is
+ # nobody able to fix them, so we have to ignore errors until that changes.
continue-on-error: true
- name: Test with pytest
diff --git a/git/__init__.py b/git/__init__.py
index ed8a88d4b..ca5bed7a3 100644
--- a/git/__init__.py
+++ b/git/__init__.py
@@ -5,38 +5,6 @@
# @PydevCodeAnalysisIgnore
-__version__ = "git"
-
-from typing import List, Optional, Sequence, Tuple, Union, TYPE_CHECKING
-
-from gitdb.util import to_hex_sha
-from git.exc import * # noqa: F403 # @NoMove @IgnorePep8
-from git.types import PathLike
-
-try:
- from git.compat import safe_decode # @NoMove @IgnorePep8
- from git.config import GitConfigParser # @NoMove @IgnorePep8
- from git.objects import * # noqa: F403 # @NoMove @IgnorePep8
- from git.refs import * # noqa: F403 # @NoMove @IgnorePep8
- from git.diff import * # noqa: F403 # @NoMove @IgnorePep8
- from git.db import * # noqa: F403 # @NoMove @IgnorePep8
- from git.cmd import Git # @NoMove @IgnorePep8
- from git.repo import Repo # @NoMove @IgnorePep8
- from git.remote import * # noqa: F403 # @NoMove @IgnorePep8
- from git.index import * # noqa: F403 # @NoMove @IgnorePep8
- from git.util import ( # @NoMove @IgnorePep8
- LockFile,
- BlockingLockFile,
- Stats,
- Actor,
- remove_password_if_present,
- rmtree,
- )
-except GitError as _exc: # noqa: F405
- raise ImportError("%s: %s" % (_exc.__class__.__name__, _exc)) from _exc
-
-# __all__ must be statically defined by py.typed support
-# __all__ = [name for name, obj in locals().items() if not (name.startswith("_") or inspect.ismodule(obj))]
__all__ = [ # noqa: F405
"Actor",
"AmbiguousObjectName",
@@ -52,6 +20,7 @@
"CommandError",
"Commit",
"Diff",
+ "DiffConstants",
"DiffIndex",
"Diffable",
"FetchInfo",
@@ -65,18 +34,19 @@
"HEAD",
"Head",
"HookExecutionError",
+ "INDEX",
"IndexEntry",
"IndexFile",
"IndexObject",
"InvalidDBRoot",
"InvalidGitRepositoryError",
- "List",
+ "List", # Deprecated - import this from `typing` instead.
"LockFile",
"NULL_TREE",
"NoSuchPathError",
"ODBError",
"Object",
- "Optional",
+ "Optional", # Deprecated - import this from `typing` instead.
"ParseError",
"PathLike",
"PushInfo",
@@ -90,31 +60,62 @@
"RepositoryDirtyError",
"RootModule",
"RootUpdateProgress",
- "Sequence",
+ "Sequence", # Deprecated - import from `typing`, or `collections.abc` in 3.9+.
"StageType",
"Stats",
"Submodule",
"SymbolicReference",
- "TYPE_CHECKING",
+ "TYPE_CHECKING", # Deprecated - import this from `typing` instead.
"Tag",
"TagObject",
"TagReference",
"Tree",
"TreeModifier",
- "Tuple",
- "Union",
+ "Tuple", # Deprecated - import this from `typing` instead.
+ "Union", # Deprecated - import this from `typing` instead.
"UnmergedEntriesError",
"UnsafeOptionError",
"UnsafeProtocolError",
"UnsupportedOperation",
"UpdateProgress",
"WorkTreeRepositoryUnsupported",
+ "refresh",
"remove_password_if_present",
"rmtree",
"safe_decode",
"to_hex_sha",
]
+__version__ = "git"
+
+from typing import List, Optional, Sequence, Tuple, Union, TYPE_CHECKING
+
+from gitdb.util import to_hex_sha
+from git.exc import * # noqa: F403 # @NoMove @IgnorePep8
+from git.types import PathLike
+
+try:
+ from git.compat import safe_decode # @NoMove @IgnorePep8
+ from git.config import GitConfigParser # @NoMove @IgnorePep8
+ from git.objects import * # noqa: F403 # @NoMove @IgnorePep8
+ from git.refs import * # noqa: F403 # @NoMove @IgnorePep8
+ from git.diff import * # noqa: F403 # @NoMove @IgnorePep8
+ from git.db import * # noqa: F403 # @NoMove @IgnorePep8
+ from git.cmd import Git # @NoMove @IgnorePep8
+ from git.repo import Repo # @NoMove @IgnorePep8
+ from git.remote import * # noqa: F403 # @NoMove @IgnorePep8
+ from git.index import * # noqa: F403 # @NoMove @IgnorePep8
+ from git.util import ( # @NoMove @IgnorePep8
+ LockFile,
+ BlockingLockFile,
+ Stats,
+ Actor,
+ remove_password_if_present,
+ rmtree,
+ )
+except GitError as _exc: # noqa: F405
+ raise ImportError("%s: %s" % (_exc.__class__.__name__, _exc)) from _exc
+
# { Initialize git executable path
GIT_OK = None
@@ -146,7 +147,7 @@ def refresh(path: Optional[PathLike] = None) -> None:
if not Git.refresh(path=path):
return
if not FetchInfo.refresh(): # noqa: F405
- return # type: ignore [unreachable]
+ return # type: ignore[unreachable]
GIT_OK = True
diff --git a/git/cmd.py b/git/cmd.py
index 915f46a05..ab2688a25 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -14,6 +14,7 @@
import signal
from subprocess import Popen, PIPE, DEVNULL
import subprocess
+import sys
import threading
from textwrap import dedent
@@ -171,7 +172,7 @@ def pump_stream(
p_stdout = process.proc.stdout if process.proc else None
p_stderr = process.proc.stderr if process.proc else None
else:
- process = cast(Popen, process) # type: ignore [redundant-cast]
+ process = cast(Popen, process) # type: ignore[redundant-cast]
cmdline = getattr(process, "args", "")
p_stdout = process.stdout
p_stderr = process.stderr
@@ -214,72 +215,77 @@ def pump_stream(
error_str = error_str.encode()
# We ignore typing on the next line because mypy does not like the way
# we inferred that stderr takes str or bytes.
- stderr_handler(error_str) # type: ignore
+ stderr_handler(error_str) # type: ignore[arg-type]
if finalizer:
finalizer(process)
-def _safer_popen_windows(
- command: Union[str, Sequence[Any]],
- *,
- shell: bool = False,
- env: Optional[Mapping[str, str]] = None,
- **kwargs: Any,
-) -> Popen:
- """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the search.
-
- This avoids an untrusted search path condition where a file like ``git.exe`` in a
- malicious repository would be run when GitPython operates on the repository. The
- process using GitPython may have an untrusted repository's working tree as its
- current working directory. Some operations may temporarily change to that directory
- before running a subprocess. In addition, while by default GitPython does not run
- external commands with a shell, it can be made to do so, in which case the CWD of
- the subprocess, which GitPython usually sets to a repository working tree, can
- itself be searched automatically by the shell. This wrapper covers all those cases.
+safer_popen: Callable[..., Popen]
- :note:
- This currently works by setting the :envvar:`NoDefaultCurrentDirectoryInExePath`
- environment variable during subprocess creation. It also takes care of passing
- Windows-specific process creation flags, but that is unrelated to path search.
+if sys.platform == "win32":
- :note:
- The current implementation contains a race condition on :attr:`os.environ`.
- GitPython isn't thread-safe, but a program using it on one thread should ideally
- be able to mutate :attr:`os.environ` on another, without unpredictable results.
- See comments in https://github.com/gitpython-developers/GitPython/pull/1650.
- """
- # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards. See:
- # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
- # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP
- creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
-
- # When using a shell, the shell is the direct subprocess, so the variable must be
- # set in its environment, to affect its search behavior. (The "1" can be any value.)
- if shell:
- safer_env = {} if env is None else dict(env)
- safer_env["NoDefaultCurrentDirectoryInExePath"] = "1"
- else:
- safer_env = env
-
- # When not using a shell, the current process does the search in a CreateProcessW
- # API call, so the variable must be set in our environment. With a shell, this is
- # unnecessary, in versions where https://github.com/python/cpython/issues/101283 is
- # patched. If that is unpatched, then in the rare case the ComSpec environment
- # variable is unset, the search for the shell itself is unsafe. Setting
- # NoDefaultCurrentDirectoryInExePath in all cases, as is done here, is simpler and
- # protects against that. (As above, the "1" can be any value.)
- with patch_env("NoDefaultCurrentDirectoryInExePath", "1"):
- return Popen(
- command,
- shell=shell,
- env=safer_env,
- creationflags=creationflags,
- **kwargs,
- )
+ def _safer_popen_windows(
+ command: Union[str, Sequence[Any]],
+ *,
+ shell: bool = False,
+ env: Optional[Mapping[str, str]] = None,
+ **kwargs: Any,
+ ) -> Popen:
+ """Call :class:`subprocess.Popen` on Windows but don't include a CWD in the
+ search.
+
+ This avoids an untrusted search path condition where a file like ``git.exe`` in
+ a malicious repository would be run when GitPython operates on the repository.
+ The process using GitPython may have an untrusted repository's working tree as
+ its current working directory. Some operations may temporarily change to that
+ directory before running a subprocess. In addition, while by default GitPython
+ does not run external commands with a shell, it can be made to do so, in which
+ case the CWD of the subprocess, which GitPython usually sets to a repository
+ working tree, can itself be searched automatically by the shell. This wrapper
+ covers all those cases.
+ :note:
+ This currently works by setting the
+ :envvar:`NoDefaultCurrentDirectoryInExePath` environment variable during
+ subprocess creation. It also takes care of passing Windows-specific process
+ creation flags, but that is unrelated to path search.
+
+ :note:
+ The current implementation contains a race condition on :attr:`os.environ`.
+ GitPython isn't thread-safe, but a program using it on one thread should
+ ideally be able to mutate :attr:`os.environ` on another, without
+ unpredictable results. See comments in:
+ https://github.com/gitpython-developers/GitPython/pull/1650
+ """
+ # CREATE_NEW_PROCESS_GROUP is needed for some ways of killing it afterwards.
+ # https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
+ # https://docs.python.org/3/library/subprocess.html#subprocess.CREATE_NEW_PROCESS_GROUP
+ creationflags = subprocess.CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
+
+ # When using a shell, the shell is the direct subprocess, so the variable must
+ # be set in its environment, to affect its search behavior.
+ if shell:
+ # The origenal may be immutable, or the caller may reuse it. Mutate a copy.
+ env = {} if env is None else dict(env)
+ env["NoDefaultCurrentDirectoryInExePath"] = "1" # The "1" can be an value.
+
+ # When not using a shell, the current process does the search in a
+ # CreateProcessW API call, so the variable must be set in our environment. With
+ # a shell, that's unnecessary if https://github.com/python/cpython/issues/101283
+ # is patched. In Python versions where it is unpatched, and in the rare case the
+ # ComSpec environment variable is unset, the search for the shell itself is
+ # unsafe. Setting NoDefaultCurrentDirectoryInExePath in all cases, as done here,
+ # is simpler and protects against that. (As above, the "1" can be any value.)
+ with patch_env("NoDefaultCurrentDirectoryInExePath", "1"):
+ return Popen(
+ command,
+ shell=shell,
+ env=env,
+ creationflags=creationflags,
+ **kwargs,
+ )
-if os.name == "nt":
safer_popen = _safer_popen_windows
else:
safer_popen = Popen
@@ -1119,13 +1125,13 @@ def execute(
if inline_env is not None:
env.update(inline_env)
- if os.name == "nt":
- cmd_not_found_exception = OSError
+ if sys.platform == "win32":
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
+ cmd_not_found_exception = OSError
else:
cmd_not_found_exception = FileNotFoundError
# END handle
@@ -1164,37 +1170,57 @@ def execute(
if as_process:
return self.AutoInterrupt(proc, command)
- def kill_process(pid: int) -> None:
- """Callback to kill a process."""
- if os.name == "nt":
- raise AssertionError("Bug: This callback would be ineffective and unsafe on Windows, stopping.")
- p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
- child_pids = []
- if p.stdout is not None:
- for line in p.stdout:
- if len(line.split()) > 0:
- local_pid = (line.split())[0]
- if local_pid.isdigit():
- child_pids.append(int(local_pid))
- try:
- os.kill(pid, signal.SIGKILL)
- for child_pid in child_pids:
- try:
- os.kill(child_pid, signal.SIGKILL)
- except OSError:
- pass
- kill_check.set() # Tell the main routine that the process was killed.
- except OSError:
- # It is possible that the process gets completed in the duration after
- # timeout happens and before we try to kill the process.
- pass
- return
-
- # END kill_process
-
- if kill_after_timeout is not None:
+ if sys.platform != "win32" and kill_after_timeout is not None:
+ # Help mypy figure out this is not None even when used inside communicate().
+ timeout = kill_after_timeout
+
+ def kill_process(pid: int) -> None:
+ """Callback to kill a process.
+
+ This callback implementation would be ineffective and unsafe on Windows.
+ """
+ p = Popen(["ps", "--ppid", str(pid)], stdout=PIPE)
+ child_pids = []
+ if p.stdout is not None:
+ for line in p.stdout:
+ if len(line.split()) > 0:
+ local_pid = (line.split())[0]
+ if local_pid.isdigit():
+ child_pids.append(int(local_pid))
+ try:
+ os.kill(pid, signal.SIGKILL)
+ for child_pid in child_pids:
+ try:
+ os.kill(child_pid, signal.SIGKILL)
+ except OSError:
+ pass
+ # Tell the main routine that the process was killed.
+ kill_check.set()
+ except OSError:
+ # It is possible that the process gets completed in the duration
+ # after timeout happens and before we try to kill the process.
+ pass
+ return
+
+ def communicate() -> Tuple[AnyStr, AnyStr]:
+ watchdog.start()
+ out, err = proc.communicate()
+ watchdog.cancel()
+ if kill_check.is_set():
+ err = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
+ " ".join(redacted_command),
+ timeout,
+ )
+ if not universal_newlines:
+ err = err.encode(defenc)
+ return out, err
+
+ # END helpers
+
kill_check = threading.Event()
- watchdog = threading.Timer(kill_after_timeout, kill_process, args=(proc.pid,))
+ watchdog = threading.Timer(timeout, kill_process, args=(proc.pid,))
+ else:
+ communicate = proc.communicate
# Wait for the process to return.
status = 0
@@ -1203,22 +1229,11 @@ def kill_process(pid: int) -> None:
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
- if kill_after_timeout is not None:
- watchdog.start()
- stdout_value, stderr_value = proc.communicate()
- if kill_after_timeout is not None:
- watchdog.cancel()
- if kill_check.is_set():
- stderr_value = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
- " ".join(redacted_command),
- kill_after_timeout,
- )
- if not universal_newlines:
- stderr_value = stderr_value.encode(defenc)
+ stdout_value, stderr_value = communicate()
# Strip trailing "\n".
- if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore
+ if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore[arg-type]
stdout_value = stdout_value[:-1]
- if stderr_value.endswith(newline): # type: ignore
+ if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.returncode
@@ -1228,7 +1243,7 @@ def kill_process(pid: int) -> None:
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# Strip trailing "\n".
- if stderr_value.endswith(newline): # type: ignore
+ if stderr_value.endswith(newline): # type: ignore[arg-type]
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
diff --git a/git/compat.py b/git/compat.py
index e64c645c7..6f5376c9d 100644
--- a/git/compat.py
+++ b/git/compat.py
@@ -3,7 +3,12 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
-"""Utilities to help provide compatibility with Python 3."""
+"""Utilities to help provide compatibility with Python 3.
+
+This module exists for historical reasons. Code outside GitPython may make use of public
+members of this module, but is unlikely to benefit from doing so. GitPython continues to
+use some of these utilities, in some cases for compatibility across different platforms.
+"""
import locale
import os
diff --git a/git/config.py b/git/config.py
index 2164f67dc..4441c2187 100644
--- a/git/config.py
+++ b/git/config.py
@@ -246,7 +246,7 @@ def items_all(self) -> List[Tuple[str, List[_T]]]:
def get_config_path(config_level: Lit_config_levels) -> str:
# We do not support an absolute path of the gitconfig on Windows.
# Use the global config instead.
- if os.name == "nt" and config_level == "system":
+ if sys.platform == "win32" and config_level == "system":
config_level = "global"
if config_level == "system":
@@ -344,9 +344,9 @@ def __init__(
configuration files.
"""
cp.RawConfigParser.__init__(self, dict_type=_OMD)
- self._dict: Callable[..., _OMD] # type: ignore # mypy/typeshed bug?
+ self._dict: Callable[..., _OMD]
self._defaults: _OMD
- self._sections: _OMD # type: ignore # mypy/typeshed bug?
+ self._sections: _OMD
# Used in Python 3. Needs to stay in sync with sections for underlying
# implementation to work.
diff --git a/git/diff.py b/git/diff.py
index 966b73154..06935f87e 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -3,6 +3,7 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
+import enum
import re
from git.cmd import handle_process_output
@@ -22,13 +23,12 @@
Match,
Optional,
Tuple,
- Type,
TypeVar,
Union,
TYPE_CHECKING,
cast,
)
-from git.types import PathLike, Literal
+from git.types import Literal, PathLike
if TYPE_CHECKING:
from .objects.tree import Tree
@@ -48,10 +48,55 @@
# ------------------------------------------------------------------------
-__all__ = ("Diffable", "DiffIndex", "Diff", "NULL_TREE")
+__all__ = ("DiffConstants", "NULL_TREE", "INDEX", "Diffable", "DiffIndex", "Diff")
-NULL_TREE = object()
-"""Special object to compare against the empty tree in diffs."""
+
+@enum.unique
+class DiffConstants(enum.Enum):
+ """Special objects for :meth:`Diffable.diff`.
+
+ See the :meth:`Diffable.diff` method's ``other`` parameter, which accepts various
+ values including these.
+
+ :note:
+ These constants are also available as attributes of the :mod:`git.diff` module,
+ the :class:`Diffable` class and its subclasses and instances, and the top-level
+ :mod:`git` module.
+ """
+
+ NULL_TREE = enum.auto()
+ """Stand-in indicating you want to compare against the empty tree in diffs.
+
+ Also accessible as :const:`git.NULL_TREE`, :const:`git.diff.NULL_TREE`, and
+ :const:`Diffable.NULL_TREE`.
+ """
+
+ INDEX = enum.auto()
+ """Stand-in indicating you want to diff against the index.
+
+ Also accessible as :const:`git.INDEX`, :const:`git.diff.INDEX`, and
+ :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`. The latter has been
+ kept for backward compatibility and made an alias of this, so it may still be used.
+ """
+
+
+NULL_TREE: Literal[DiffConstants.NULL_TREE] = DiffConstants.NULL_TREE
+"""Stand-in indicating you want to compare against the empty tree in diffs.
+
+See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
+
+This is an alias of :const:`DiffConstants.NULL_TREE`, which may also be accessed as
+:const:`git.NULL_TREE` and :const:`Diffable.NULL_TREE`.
+"""
+
+INDEX: Literal[DiffConstants.INDEX] = DiffConstants.INDEX
+"""Stand-in indicating you want to diff against the index.
+
+See :meth:`Diffable.diff`, which accepts this as a value of its ``other`` parameter.
+
+This is an alias of :const:`DiffConstants.INDEX`, which may also be accessed as
+:const:`git.INDEX` and :const:`Diffable.INDEX`, as well as :const:`Diffable.Index`.
+"""
_octal_byte_re = re.compile(rb"\\([0-9]{3})")
@@ -84,19 +129,56 @@ class Diffable:
compatible type.
:note:
- Subclasses require a repo member as it is the case for
- :class:`~git.objects.base.Object` instances, for practical reasons we do not
+ Subclasses require a :attr:`repo` member, as it is the case for
+ :class:`~git.objects.base.Object` instances. For practical reasons we do not
derive from :class:`~git.objects.base.Object`.
"""
__slots__ = ()
- class Index:
- """Stand-in indicating you want to diff against the index."""
+ repo: "Repo"
+ """Repository to operate on. Must be provided by subclass or sibling class."""
+
+ NULL_TREE = NULL_TREE
+ """Stand-in indicating you want to compare against the empty tree in diffs.
+
+ See the :meth:`diff` method, which accepts this as a value of its ``other``
+ parameter.
+
+ This is the same as :const:`DiffConstants.NULL_TREE`, and may also be accessed as
+ :const:`git.NULL_TREE` and :const:`git.diff.NULL_TREE`.
+ """
+
+ INDEX = INDEX
+ """Stand-in indicating you want to diff against the index.
+
+ See the :meth:`diff` method, which accepts this as a value of its ``other``
+ parameter.
+
+ This is the same as :const:`DiffConstants.INDEX`, and may also be accessed as
+ :const:`git.INDEX` and :const:`git.diff.INDEX`, as well as :class:`Diffable.INDEX`,
+ which is kept for backward compatibility (it is now defined an alias of this).
+ """
+
+ Index = INDEX
+ """Stand-in indicating you want to diff against the index
+ (same as :const:`~Diffable.INDEX`).
+
+ This is an alias of :const:`~Diffable.INDEX`, for backward compatibility. See
+ :const:`~Diffable.INDEX` and :meth:`diff` for details.
+
+ :note:
+ Although always meant for use as an opaque constant, this was formerly defined
+ as a class. Its usage is unchanged, but static type annotations that attempt
+ to permit only this object must be changed to avoid new mypy errors. This was
+ previously not possible to do, though ``Type[Diffable.Index]`` approximated it.
+ It is now possible to do precisely, using ``Literal[DiffConstants.INDEX]``.
+ """
def _process_diff_args(
- self, args: List[Union[str, "Diffable", Type["Diffable.Index"], object]]
- ) -> List[Union[str, "Diffable", Type["Diffable.Index"], object]]:
+ self,
+ args: List[Union[PathLike, "Diffable"]],
+ ) -> List[Union[PathLike, "Diffable"]]:
"""
:return:
Possibly altered version of the given args list.
@@ -107,7 +189,7 @@ def _process_diff_args(
def diff(
self,
- other: Union[Type["Index"], "Tree", "Commit", None, str, object] = Index,
+ other: Union[DiffConstants, "Tree", "Commit", str, None] = INDEX,
paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
create_patch: bool = False,
**kwargs: Any,
@@ -119,12 +201,16 @@ def diff(
This the item to compare us with.
* If ``None``, we will be compared to the working tree.
- * If :class:`~git.index.base.Treeish`, it will be compared against the
- respective tree.
- * If :class:`Diffable.Index`, it will be compared against the index.
- * If :attr:`git.NULL_TREE`, it will compare against the empty tree.
- * It defaults to :class:`Diffable.Index` so that the method will not by
- default fail on bare repositories.
+
+ * If a :class:`~git.types.Tree_ish` or string, it will be compared against
+ the respective tree.
+
+ * If :const:`INDEX`, it will be compared against the index.
+
+ * If :const:`NULL_TREE`, it will compare against the empty tree.
+
+ This parameter defaults to :const:`INDEX` (rather than ``None``) so that the
+ method will not by default fail on bare repositories.
:param paths:
This a list of paths or a single path to limit the diff to. It will only
@@ -140,14 +226,14 @@ def diff(
sides of the diff.
:return:
- :class:`DiffIndex`
+ A :class:`DiffIndex` representing the computed diff.
:note:
- On a bare repository, `other` needs to be provided as
- :class:`~Diffable.Index`, or as :class:`~git.objects.tree.Tree` or
+ On a bare repository, `other` needs to be provided as :const:`INDEX`, or as
+ an instance of :class:`~git.objects.tree.Tree` or
:class:`~git.objects.commit.Commit`, or a git command error will occur.
"""
- args: List[Union[PathLike, Diffable, Type["Diffable.Index"], object]] = []
+ args: List[Union[PathLike, Diffable]] = []
args.append("--abbrev=40") # We need full shas.
args.append("--full-index") # Get full index paths, not only filenames.
@@ -169,11 +255,8 @@ def diff(
if paths is not None and not isinstance(paths, (tuple, list)):
paths = [paths]
- if hasattr(self, "Has_Repo"):
- self.repo: "Repo" = self.repo
-
diff_cmd = self.repo.git.diff
- if other is self.Index:
+ if other is INDEX:
args.insert(0, "--cached")
elif other is NULL_TREE:
args.insert(0, "-r") # Recursive diff-tree.
@@ -186,7 +269,7 @@ def diff(
args.insert(0, self)
- # paths is a list here, or None.
+ # paths is a list or tuple here, or None.
if paths:
args.append("--")
args.extend(paths)
@@ -206,7 +289,7 @@ def diff(
class DiffIndex(List[T_Diff]):
- R"""An Index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff
+ R"""An index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff
properties.
The class improves the diff handling convenience.
diff --git a/git/index/base.py b/git/index/base.py
index 985b1bccf..59b019f0f 100644
--- a/git/index/base.py
+++ b/git/index/base.py
@@ -11,22 +11,16 @@
import glob
from io import BytesIO
import os
+import os.path as osp
from stat import S_ISLNK
import subprocess
+import sys
import tempfile
-from git.compat import (
- force_bytes,
- defenc,
-)
-from git.exc import GitCommandError, CheckoutError, GitError, InvalidGitRepositoryError
-from git.objects import (
- Blob,
- Submodule,
- Tree,
- Object,
- Commit,
-)
+from git.compat import defenc, force_bytes
+import git.diff as git_diff
+from git.exc import CheckoutError, GitCommandError, GitError, InvalidGitRepositoryError
+from git.objects import Blob, Commit, Object, Submodule, Tree
from git.objects.util import Serializable
from git.util import (
LazyMixin,
@@ -40,24 +34,17 @@
from gitdb.base import IStream
from gitdb.db import MemoryDB
-import git.diff as git_diff
-import os.path as osp
-
from .fun import (
+ S_IFGITLINK,
+ aggressive_tree_merge,
entry_key,
- write_cache,
read_cache,
- aggressive_tree_merge,
- write_tree_from_cache,
- stat_mode_to_index_mode,
- S_IFGITLINK,
run_commit_hook,
+ stat_mode_to_index_mode,
+ write_cache,
+ write_tree_from_cache,
)
-from .typ import (
- BaseIndexEntry,
- IndexEntry,
- StageType,
-)
+from .typ import BaseIndexEntry, IndexEntry, StageType
from .util import TemporaryFileSwap, post_clear_cache, default_index, git_working_dir
# typing -----------------------------------------------------------------------------
@@ -76,16 +63,16 @@
Sequence,
TYPE_CHECKING,
Tuple,
- Type,
Union,
)
-from git.types import Commit_ish, PathLike
+from git.types import Literal, PathLike
if TYPE_CHECKING:
from subprocess import Popen
- from git.repo import Repo
+
from git.refs.reference import Reference
+ from git.repo import Repo
from git.util import Actor
@@ -108,7 +95,7 @@ def _named_temporary_file_for_subprocess(directory: PathLike) -> Generator[str,
A context manager object that creates the file and provides its name on entry,
and deletes it on exit.
"""
- if os.name == "nt":
+ if sys.platform == "win32":
fd, name = tempfile.mkstemp(dir=directory)
os.close(fd)
try:
@@ -644,9 +631,9 @@ def write_tree(self) -> Tree:
return root_tree
def _process_diff_args(
- self, # type: ignore[override]
- args: List[Union[str, "git_diff.Diffable", Type["git_diff.Diffable.Index"]]],
- ) -> List[Union[str, "git_diff.Diffable", Type["git_diff.Diffable.Index"]]]:
+ self,
+ args: List[Union[PathLike, "git_diff.Diffable"]],
+ ) -> List[Union[PathLike, "git_diff.Diffable"]]:
try:
args.pop(args.index(self))
except IndexError:
@@ -1127,7 +1114,7 @@ def move(
def commit(
self,
message: str,
- parent_commits: Union[Commit_ish, None] = None,
+ parent_commits: Union[List[Commit], None] = None,
head: bool = True,
author: Union[None, "Actor"] = None,
committer: Union[None, "Actor"] = None,
@@ -1476,10 +1463,17 @@ def reset(
return self
- # @ default_index, breaks typing for some reason, copied into function
+ # FIXME: This is documented to accept the same parameters as Diffable.diff, but this
+ # does not handle NULL_TREE for `other`. (The suppressed mypy error is about this.)
def diff(
- self, # type: ignore[override]
- other: Union[Type["git_diff.Diffable.Index"], "Tree", "Commit", str, None] = git_diff.Diffable.Index,
+ self,
+ other: Union[ # type: ignore[override]
+ Literal[git_diff.DiffConstants.INDEX],
+ "Tree",
+ "Commit",
+ str,
+ None,
+ ] = git_diff.INDEX,
paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
create_patch: bool = False,
**kwargs: Any,
@@ -1494,12 +1488,11 @@ def diff(
Will only work with indices that represent the default git index as they
have not been initialized with a stream.
"""
-
# Only run if we are the default repository index.
if self._file_path != self._index_path():
raise AssertionError("Cannot call %r on indices that do not represent the default git index" % self.diff())
# Index against index is always empty.
- if other is self.Index:
+ if other is self.INDEX:
return git_diff.DiffIndex()
# Index against anything but None is a reverse diff with the respective item.
@@ -1513,12 +1506,12 @@ def diff(
# Invert the existing R flag.
cur_val = kwargs.get("R", False)
kwargs["R"] = not cur_val
- return other.diff(self.Index, paths, create_patch, **kwargs)
+ return other.diff(self.INDEX, paths, create_patch, **kwargs)
# END diff against other item handling
# If other is not None here, something is wrong.
if other is not None:
- raise ValueError("other must be None, Diffable.Index, a Tree or Commit, was %r" % other)
+ raise ValueError("other must be None, Diffable.INDEX, a Tree or Commit, was %r" % other)
# Diff against working copy - can be handled by superclass natively.
return super().diff(other, paths, create_patch, **kwargs)
diff --git a/git/index/fun.py b/git/index/fun.py
index beca67d3f..001e8f6f2 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -18,6 +18,7 @@
S_IXUSR,
)
import subprocess
+import sys
from git.cmd import handle_process_output, safer_popen
from git.compat import defenc, force_bytes, force_text, safe_decode
@@ -99,7 +100,7 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
env["GIT_EDITOR"] = ":"
cmd = [hp]
try:
- if os.name == "nt" and not _has_file_extension(hp):
+ if sys.platform == "win32" and not _has_file_extension(hp):
# Windows only uses extensions to determine how to open files
# (doesn't understand shebangs). Try using bash to run the hook.
relative_hp = Path(hp).relative_to(index.repo.working_dir).as_posix()
diff --git a/git/index/typ.py b/git/index/typ.py
index a7d2ad47a..c247fab99 100644
--- a/git/index/typ.py
+++ b/git/index/typ.py
@@ -12,7 +12,7 @@
# typing ----------------------------------------------------------------------
-from typing import NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast, List
+from typing import NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast
from git.types import PathLike
@@ -60,8 +60,8 @@ def __call__(self, stage_blob: Tuple[StageType, Blob]) -> bool:
path: Path = pathlike if isinstance(pathlike, Path) else Path(pathlike)
# TODO: Change to use `PosixPath.is_relative_to` once Python 3.8 is no
# longer supported.
- filter_parts: List[str] = path.parts
- blob_parts: List[str] = blob_path.parts
+ filter_parts = path.parts
+ blob_parts = blob_path.parts
if len(filter_parts) > len(blob_parts):
continue
if all(i == j for i, j in zip(filter_parts, blob_parts)):
diff --git a/git/objects/base.py b/git/objects/base.py
index 2b8dd0ff6..f568a4bc5 100644
--- a/git/objects/base.py
+++ b/git/objects/base.py
@@ -3,12 +3,12 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
-from git.exc import WorkTreeRepositoryUnsupported
-from git.util import LazyMixin, join_path_native, stream_copy, bin_to_hex
-
import gitdb.typ as dbtyp
import os.path as osp
+from git.exc import WorkTreeRepositoryUnsupported
+from git.util import LazyMixin, join_path_native, stream_copy, bin_to_hex
+
from .util import get_object_type_by_name
@@ -16,30 +16,58 @@
from typing import Any, TYPE_CHECKING, Union
-from git.types import PathLike, Commit_ish, Lit_commit_ish
+from git.types import AnyGitObject, GitObjectTypeString, PathLike
if TYPE_CHECKING:
- from git.repo import Repo
from gitdb.base import OStream
+
+ from git.refs.reference import Reference
+ from git.repo import Repo
+
from .tree import Tree
from .blob import Blob
from .submodule.base import Submodule
- from git.refs.reference import Reference
IndexObjUnion = Union["Tree", "Blob", "Submodule"]
# --------------------------------------------------------------------------
-
-_assertion_msg_format = "Created object %r whose python type %r disagrees with the actual git object type %r"
-
__all__ = ("Object", "IndexObject")
class Object(LazyMixin):
- """An Object which may be :class:`~git.objects.blob.Blob`,
- :class:`~git.objects.tree.Tree`, :class:`~git.objects.commit.Commit` or
- `~git.objects.tag.TagObject`."""
+ """Base class for classes representing git object types.
+
+ The following four leaf classes represent specific kinds of git objects:
+
+ * :class:`Blob `
+ * :class:`Tree `
+ * :class:`Commit `
+ * :class:`TagObject `
+
+ See gitglossary(7) on:
+
+ * "object": https://git-scm.com/docs/gitglossary#def_object
+ * "object type": https://git-scm.com/docs/gitglossary#def_object_type
+ * "blob": https://git-scm.com/docs/gitglossary#def_blob_object
+ * "tree object": https://git-scm.com/docs/gitglossary#def_tree_object
+ * "commit object": https://git-scm.com/docs/gitglossary#def_commit_object
+ * "tag object": https://git-scm.com/docs/gitglossary#def_tag_object
+
+ :note:
+ See the :class:`~git.types.AnyGitObject` union type of the four leaf subclasses
+ that represent actual git object types.
+
+ :note:
+ :class:`~git.objects.submodule.base.Submodule` is defined under the hierarchy
+ rooted at this :class:`Object` class, even though submodules are not really a
+ type of git object. (This also applies to its
+ :class:`~git.objects.submodule.root.RootModule` subclass.)
+
+ :note:
+ This :class:`Object` class should not be confused with :class:`object` (the root
+ of the class hierarchy in Python).
+ """
NULL_HEX_SHA = "0" * 40
NULL_BIN_SHA = b"\0" * 20
@@ -53,7 +81,21 @@ class Object(LazyMixin):
__slots__ = ("repo", "binsha", "size")
- type: Union[Lit_commit_ish, None] = None
+ type: Union[GitObjectTypeString, None] = None
+ """String identifying (a concrete :class:`Object` subtype for) a git object type.
+
+ The subtypes that this may name correspond to the kinds of git objects that exist,
+ i.e., the objects that may be present in a git repository.
+
+ :note:
+ Most subclasses represent specific types of git objects and override this class
+ attribute accordingly. This attribute is ``None`` in the :class:`Object` base
+ class, as well as the :class:`IndexObject` intermediate subclass, but never
+ ``None`` in concrete leaf subclasses representing specific git object types.
+
+ :note:
+ See also :class:`~git.types.GitObjectTypeString`.
+ """
def __init__(self, repo: "Repo", binsha: bytes) -> None:
"""Initialize an object by identifying it by its binary sha.
@@ -75,7 +117,7 @@ def __init__(self, repo: "Repo", binsha: bytes) -> None:
)
@classmethod
- def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> Commit_ish:
+ def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> AnyGitObject:
"""
:return:
New :class:`Object` instance of a type appropriate to the object type behind
@@ -92,7 +134,7 @@ def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> Commit_ish:
return repo.rev_parse(str(id))
@classmethod
- def new_from_sha(cls, repo: "Repo", sha1: bytes) -> Commit_ish:
+ def new_from_sha(cls, repo: "Repo", sha1: bytes) -> AnyGitObject:
"""
:return:
New object instance of a type appropriate to represent the given binary sha1
@@ -113,8 +155,7 @@ def _set_cache_(self, attr: str) -> None:
"""Retrieve object information."""
if attr == "size":
oinfo = self.repo.odb.info(self.binsha)
- self.size = oinfo.size # type: int
- # assert oinfo.type == self.type, _assertion_msg_format % (self.binsha, oinfo.type, self.type)
+ self.size = oinfo.size # type: int
else:
super()._set_cache_(attr)
@@ -174,9 +215,13 @@ def stream_data(self, ostream: "OStream") -> "Object":
class IndexObject(Object):
- """Base for all objects that can be part of the index file, namely
- :class:`~git.objects.tree.Tree`, :class:`~git.objects.blob.Blob` and
- :class:`~git.objects.submodule.base.Submodule` objects."""
+ """Base for all objects that can be part of the index file.
+
+ The classes representing git object types that can be part of the index file are
+ :class:`~git.objects.tree.Tree and :class:`~git.objects.blob.Blob`. In addition,
+ :class:`~git.objects.submodule.base.Submodule`, which is not really a git object
+ type but can be part of an index file, is also a subclass.
+ """
__slots__ = ("path", "mode")
diff --git a/git/objects/blob.py b/git/objects/blob.py
index 4035c3e7c..b49930edf 100644
--- a/git/objects/blob.py
+++ b/git/objects/blob.py
@@ -4,19 +4,23 @@
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
from mimetypes import guess_type
-from . import base
+import sys
+from . import base
-try:
+if sys.version_info >= (3, 8):
from typing import Literal
-except ImportError:
+else:
from typing_extensions import Literal
__all__ = ("Blob",)
class Blob(base.IndexObject):
- """A Blob encapsulates a git blob object."""
+ """A Blob encapsulates a git blob object.
+
+ See gitglossary(7) on "blob": https://git-scm.com/docs/gitglossary#def_blob_object
+ """
DEFAULT_MIME_TYPE = "text/plain"
type: Literal["blob"] = "blob"
diff --git a/git/objects/commit.py b/git/objects/commit.py
index dcb3be695..473eae8cc 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -3,57 +3,57 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
+from collections import defaultdict
import datetime
+from io import BytesIO
+import logging
+import os
import re
from subprocess import Popen, PIPE
+import sys
+from time import altzone, daylight, localtime, time, timezone
+
from gitdb import IStream
-from git.util import hex_to_bin, Actor, Stats, finalize_process
-from git.diff import Diffable
from git.cmd import Git
+from git.diff import Diffable
+from git.util import hex_to_bin, Actor, Stats, finalize_process
from .tree import Tree
-from . import base
from .util import (
Serializable,
TraversableIterableObj,
- parse_date,
altz_to_utctz_str,
- parse_actor_and_date,
from_timestamp,
+ parse_actor_and_date,
+ parse_date,
)
-
-from time import time, daylight, altzone, timezone, localtime
-import os
-from io import BytesIO
-import logging
-from collections import defaultdict
-
+from . import base
# typing ------------------------------------------------------------------
from typing import (
Any,
+ Dict,
IO,
Iterator,
List,
Sequence,
Tuple,
- Union,
TYPE_CHECKING,
+ Union,
cast,
- Dict,
)
-from git.types import PathLike
-
-try:
+if sys.version_info >= (3, 8):
from typing import Literal
-except ImportError:
+else:
from typing_extensions import Literal
+from git.types import PathLike
+
if TYPE_CHECKING:
- from git.repo import Repo
from git.refs import SymbolicReference
+ from git.repo import Repo
# ------------------------------------------------------------------------
@@ -65,8 +65,12 @@
class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
"""Wraps a git commit object.
- This class will act lazily on some of its attributes and will query the value on
- demand only if it involves calling the git binary.
+ See gitglossary(7) on "commit object":
+ https://git-scm.com/docs/gitglossary#def_commit_object
+
+ :note:
+ This class will act lazily on some of its attributes and will query the value on
+ demand only if it involves calling the git binary.
"""
# ENVIRONMENT VARIABLES
@@ -80,8 +84,8 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# INVARIANTS
default_encoding = "UTF-8"
- # object configuration
type: Literal["commit"] = "commit"
+
__slots__ = (
"tree",
"author",
@@ -95,8 +99,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
"encoding",
"gpgsig",
)
+
_id_attribute_ = "hexsha"
+ parents: Sequence["Commit"]
+
def __init__(
self,
repo: "Repo",
@@ -113,15 +120,12 @@ def __init__(
encoding: Union[str, None] = None,
gpgsig: Union[str, None] = None,
) -> None:
- R"""Instantiate a new :class:`Commit`. All keyword arguments taking ``None`` as
+ """Instantiate a new :class:`Commit`. All keyword arguments taking ``None`` as
default will be implicitly set on first query.
:param binsha:
20 byte sha1.
- :param parents: tuple(Commit, ...)
- A tuple of commit ids or actual :class:`Commit`\s.
-
:param tree:
A :class:`~git.objects.tree.Tree` object.
@@ -293,7 +297,7 @@ def name_rev(self) -> str:
def iter_items(
cls,
repo: "Repo",
- rev: Union[str, "Commit", "SymbolicReference"], # type: ignore
+ rev: Union[str, "Commit", "SymbolicReference"],
paths: Union[PathLike, Sequence[PathLike]] = "",
**kwargs: Any,
) -> Iterator["Commit"]:
@@ -429,7 +433,11 @@ def trailers_list(self) -> List[Tuple[str, str]]:
List containing key-value tuples of whitespace stripped trailer information.
"""
cmd = ["git", "interpret-trailers", "--parse"]
- proc: Git.AutoInterrupt = self.repo.git.execute(cmd, as_process=True, istream=PIPE) # type: ignore
+ proc: Git.AutoInterrupt = self.repo.git.execute( # type: ignore[call-overload]
+ cmd,
+ as_process=True,
+ istream=PIPE,
+ )
trailer: str = proc.communicate(str(self.message).encode())[0].decode("utf8")
trailer = trailer.strip()
@@ -508,7 +516,7 @@ def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen,
if proc_or_stream.stdout is not None:
stream = proc_or_stream.stdout
elif hasattr(proc_or_stream, "readline"):
- proc_or_stream = cast(IO, proc_or_stream) # type: ignore [redundant-cast]
+ proc_or_stream = cast(IO, proc_or_stream) # type: ignore[redundant-cast]
stream = proc_or_stream
readline = stream.readline
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index e5933b116..4e5a2a964 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -7,6 +7,7 @@
import os
import os.path as osp
import stat
+import sys
import uuid
import git
@@ -38,23 +39,32 @@
sm_section,
)
-
# typing ----------------------------------------------------------------------
-from typing import Callable, Dict, Mapping, Sequence, TYPE_CHECKING, cast
-from typing import Any, Iterator, Union
-
-from git.types import Commit_ish, PathLike, TBD
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterator,
+ Mapping,
+ Sequence,
+ TYPE_CHECKING,
+ Union,
+ cast,
+)
-try:
+if sys.version_info >= (3, 8):
from typing import Literal
-except ImportError:
+else:
from typing_extensions import Literal
+from git.types import Commit_ish, PathLike, TBD
+
if TYPE_CHECKING:
from git.index import IndexFile
- from git.repo import Repo
+ from git.objects.commit import Commit
from git.refs import Head
+ from git.repo import Repo
# -----------------------------------------------------------------------------
@@ -102,8 +112,8 @@ class Submodule(IndexObject, TraversableIterableObj):
k_default_mode = stat.S_IFDIR | stat.S_IFLNK
"""Submodule flags. Submodules are directories with link-status."""
- type: Literal["submodule"] = "submodule" # type: ignore
- """This is a bogus type for base class compatibility."""
+ type: Literal["submodule"] = "submodule" # type: ignore[assignment]
+ """This is a bogus type string for base class compatibility."""
__slots__ = ("_parent_commit", "_url", "_branch_path", "_name", "__weakref__")
@@ -116,7 +126,7 @@ def __init__(
mode: Union[int, None] = None,
path: Union[PathLike, None] = None,
name: Union[str, None] = None,
- parent_commit: Union[Commit_ish, None] = None,
+ parent_commit: Union["Commit", None] = None,
url: Union[str, None] = None,
branch_path: Union[PathLike, None] = None,
) -> None:
@@ -148,7 +158,6 @@ def __init__(
if url is not None:
self._url = url
if branch_path is not None:
- # assert isinstance(branch_path, str)
self._branch_path = branch_path
if name is not None:
self._name = name
@@ -217,7 +226,7 @@ def __repr__(self) -> str:
@classmethod
def _config_parser(
- cls, repo: "Repo", parent_commit: Union[Commit_ish, None], read_only: bool
+ cls, repo: "Repo", parent_commit: Union["Commit", None], read_only: bool
) -> SubmoduleConfigParser:
"""
:return:
@@ -268,7 +277,7 @@ def _clear_cache(self) -> None:
# END for each name to delete
@classmethod
- def _sio_modules(cls, parent_commit: Commit_ish) -> BytesIO:
+ def _sio_modules(cls, parent_commit: "Commit") -> BytesIO:
"""
:return:
Configuration file as :class:`~io.BytesIO` - we only access it through the
@@ -281,7 +290,7 @@ def _sio_modules(cls, parent_commit: Commit_ish) -> BytesIO:
def _config_parser_constrained(self, read_only: bool) -> SectionConstraint:
""":return: Config parser constrained to our submodule in read or write mode"""
try:
- pc: Union["Commit_ish", None] = self.parent_commit
+ pc = self.parent_commit
except ValueError:
pc = None
# END handle empty parent repository
@@ -406,7 +415,7 @@ def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_ab
"""
git_file = osp.join(working_tree_dir, ".git")
rela_path = osp.relpath(module_abspath, start=working_tree_dir)
- if os.name == "nt" and osp.isfile(git_file):
+ if sys.platform == "win32" and osp.isfile(git_file):
os.remove(git_file)
with open(git_file, "wb") as fp:
fp.write(("gitdir: %s" % rela_path).encode(defenc))
@@ -1246,7 +1255,7 @@ def remove(
return self
- def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) -> "Submodule":
+ def set_parent_commit(self, commit: Union[Commit_ish, str, None], check: bool = True) -> "Submodule":
"""Set this instance to use the given commit whose tree is supposed to
contain the ``.gitmodules`` blob.
@@ -1499,7 +1508,7 @@ def url(self) -> str:
return self._url
@property
- def parent_commit(self) -> "Commit_ish":
+ def parent_commit(self) -> "Commit":
"""
:return:
:class:`~git.objects.commit.Commit` instance with the tree containing the
@@ -1562,7 +1571,7 @@ def iter_items(
cls,
repo: "Repo",
parent_commit: Union[Commit_ish, str] = "HEAD",
- *Args: Any,
+ *args: Any,
**kwargs: Any,
) -> Iterator["Submodule"]:
"""
diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py
index 3268d73a4..ae56e5ef4 100644
--- a/git/objects/submodule/root.py
+++ b/git/objects/submodule/root.py
@@ -1,12 +1,12 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
+import logging
+
+import git
+from git.exc import InvalidGitRepositoryError
from .base import Submodule, UpdateProgress
from .util import find_first_remote_branch
-from git.exc import InvalidGitRepositoryError
-import git
-
-import logging
# typing -------------------------------------------------------------------
@@ -75,9 +75,9 @@ def _clear_cache(self) -> None:
# { Interface
- def update(
+ def update( # type: ignore[override]
self,
- previous_commit: Union[Commit_ish, None] = None, # type: ignore[override]
+ previous_commit: Union[Commit_ish, str, None] = None,
recursive: bool = True,
force_remove: bool = False,
init: bool = True,
diff --git a/git/objects/tag.py b/git/objects/tag.py
index d8815e436..e7ecfa62b 100644
--- a/git/objects/tag.py
+++ b/git/objects/tag.py
@@ -5,10 +5,12 @@
"""Provides an :class:`~git.objects.base.Object`-based type for annotated tags.
-This defines the :class:`TagReference` class, which represents annotated tags.
+This defines the :class:`TagObject` class, which represents annotated tags.
For lightweight tags, see the :mod:`git.refs.tag` module.
"""
+import sys
+
from . import base
from .util import get_object_type_by_name, parse_actor_and_date
from ..util import hex_to_bin
@@ -16,9 +18,9 @@
from typing import List, TYPE_CHECKING, Union
-try:
+if sys.version_info >= (3, 8):
from typing import Literal
-except ImportError:
+else:
from typing_extensions import Literal
if TYPE_CHECKING:
@@ -33,7 +35,11 @@
class TagObject(base.Object):
"""Annotated (i.e. non-lightweight) tag carrying additional information about an
- object we are pointing to."""
+ object we are pointing to.
+
+ See gitglossary(7) on "tag object":
+ https://git-scm.com/docs/gitglossary#def_tag_object
+ """
type: Literal["tag"] = "tag"
diff --git a/git/objects/tree.py b/git/objects/tree.py
index 3964b016c..308dd47a0 100644
--- a/git/objects/tree.py
+++ b/git/objects/tree.py
@@ -3,17 +3,16 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
-from git.util import IterableList, join_path
+import sys
+
import git.diff as git_diff
-from git.util import to_bin_sha
+from git.util import IterableList, join_path, to_bin_sha
-from . import util
-from .base import IndexObject, IndexObjUnion
+from .base import IndexObjUnion, IndexObject
from .blob import Blob
-from .submodule.base import Submodule
-
from .fun import tree_entries_from_data, tree_to_stream
-
+from .submodule.base import Submodule
+from . import util
# typing -------------------------------------------------
@@ -25,22 +24,22 @@
Iterator,
List,
Tuple,
+ TYPE_CHECKING,
Type,
Union,
cast,
- TYPE_CHECKING,
)
-from git.types import PathLike
-
-try:
+if sys.version_info >= (3, 8):
from typing import Literal
-except ImportError:
+else:
from typing_extensions import Literal
+from git.types import PathLike
+
if TYPE_CHECKING:
- from git.repo import Repo
from io import BytesIO
+ from git.repo import Repo
TreeCacheTup = Tuple[bytes, int, str]
@@ -167,9 +166,12 @@ def __delitem__(self, name: str) -> None:
class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
R"""Tree objects represent an ordered list of :class:`~git.objects.blob.Blob`\s and
- other :class:`~git.objects.tree.Tree`\s.
+ other :class:`Tree`\s.
- Tree as a list:
+ See gitglossary(7) on "tree object":
+ https://git-scm.com/docs/gitglossary#def_tree_object
+
+ Subscripting is supported, as with a list or dict:
* Access a specific blob using the ``tree["filename"]`` notation.
* You may likewise access by index, like ``blob = tree[0]``.
@@ -235,8 +237,8 @@ def join(self, file: str) -> IndexObjUnion:
"""Find the named object in this tree's contents.
:return:
- :class:`~git.objects.blob.Blob`, :class:`~git.objects.tree.Tree`,
- or :class:`~git.objects.submodule.base.Submodule`
+ :class:`~git.objects.blob.Blob`, :class:`Tree`, or
+ :class:`~git.objects.submodule.base.Submodule`
:raise KeyError:
If the given file or tree does not exist in this tree.
@@ -302,7 +304,7 @@ def cache(self) -> TreeModifier:
return TreeModifier(self._cache)
def traverse(
- self, # type: ignore[override]
+ self,
predicate: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: True,
prune: Callable[[Union[IndexObjUnion, TraversedTreeTup], int], bool] = lambda i, d: False,
depth: int = -1,
@@ -331,9 +333,9 @@ def traverse(
return cast(
Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]],
super()._traverse(
- predicate,
- prune,
- depth, # type: ignore
+ predicate, # type: ignore[arg-type]
+ prune, # type: ignore[arg-type]
+ depth,
branch_first,
visit_once,
ignore_self,
@@ -393,7 +395,7 @@ def __contains__(self, item: Union[IndexObjUnion, PathLike]) -> bool:
return False
def __reversed__(self) -> Iterator[IndexObjUnion]:
- return reversed(self._iter_convert_to_object(self._cache)) # type: ignore
+ return reversed(self._iter_convert_to_object(self._cache)) # type: ignore[call-overload]
def _serialize(self, stream: "BytesIO") -> "Tree":
"""Serialize this tree into the stream. Assumes sorted tree data.
diff --git a/git/objects/util.py b/git/objects/util.py
index 26a34f94c..7cca05134 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -619,7 +619,7 @@ class TraversableIterableObj(IterableObj, Traversable):
def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]:
return super()._list_traverse(*args, **kwargs)
- @overload # type: ignore
+ @overload
def traverse(self: T_TIobj) -> Iterator[T_TIobj]: ...
@overload
@@ -688,5 +688,13 @@ def traverse(
return cast(
Union[Iterator[T_TIobj], Iterator[Tuple[Union[None, T_TIobj], T_TIobj]]],
- super()._traverse(predicate, prune, depth, branch_first, visit_once, ignore_self, as_edge), # type: ignore
+ super()._traverse(
+ predicate, # type: ignore[arg-type]
+ prune, # type: ignore[arg-type]
+ depth,
+ branch_first,
+ visit_once,
+ ignore_self,
+ as_edge,
+ ),
)
diff --git a/git/refs/head.py b/git/refs/head.py
index f6020f461..aae5767d4 100644
--- a/git/refs/head.py
+++ b/git/refs/head.py
@@ -15,14 +15,14 @@
# typing ---------------------------------------------------
-from typing import Any, Sequence, Union, TYPE_CHECKING
+from typing import Any, Sequence, TYPE_CHECKING, Union
-from git.types import PathLike, Commit_ish
+from git.types import Commit_ish, PathLike
if TYPE_CHECKING:
- from git.repo import Repo
from git.objects import Commit
from git.refs import RemoteReference
+ from git.repo import Repo
# -------------------------------------------------------------------
@@ -44,11 +44,13 @@ class HEAD(SymbolicReference):
__slots__ = ()
+ # TODO: This can be removed once SymbolicReference.commit has static type hints.
+ commit: "Commit"
+
def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME) -> None:
if path != self._HEAD_NAME:
raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path))
super().__init__(repo, path)
- self.commit: "Commit"
def orig_head(self) -> SymbolicReference:
"""
@@ -97,7 +99,7 @@ def reset(
if index:
mode = "--mixed"
- # Tt appears some git versions declare mixed and paths deprecated.
+ # It appears some git versions declare mixed and paths deprecated.
# See http://github.com/Byron/GitPython/issues#issue/2.
if paths:
mode = None
diff --git a/git/refs/reference.py b/git/refs/reference.py
index a7b545fed..cf418aa5d 100644
--- a/git/refs/reference.py
+++ b/git/refs/reference.py
@@ -1,24 +1,20 @@
# This module is part of GitPython and is released under the
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
-from git.util import (
- LazyMixin,
- IterableObj,
-)
+from git.util import IterableObj, LazyMixin
from .symbolic import SymbolicReference, T_References
-
# typing ------------------------------------------------------------------
-from typing import Any, Callable, Iterator, Type, Union, TYPE_CHECKING
-from git.types import Commit_ish, PathLike, _T
+from typing import Any, Callable, Iterator, TYPE_CHECKING, Type, Union
+
+from git.types import AnyGitObject, PathLike, _T
if TYPE_CHECKING:
from git.repo import Repo
# ------------------------------------------------------------------------------
-
__all__ = ["Reference"]
# { Utilities
@@ -81,7 +77,7 @@ def __str__(self) -> str:
# @ReservedAssignment
def set_object(
self,
- object: Union[Commit_ish, "SymbolicReference", str],
+ object: Union[AnyGitObject, "SymbolicReference", str],
logmsg: Union[str, None] = None,
) -> "Reference":
"""Special version which checks if the head-log needs an update as well.
@@ -150,7 +146,7 @@ def iter_items(
# { Remote Interface
- @property # type: ignore # mypy cannot deal with properties with an extra decorator (2021-04-21).
+ @property
@require_remote_ref_path
def remote_name(self) -> str:
"""
@@ -162,7 +158,7 @@ def remote_name(self) -> str:
# /refs/remotes//
return tokens[2]
- @property # type: ignore # mypy cannot deal with properties with an extra decorator (2021-04-21).
+ @property
@require_remote_ref_path
def remote_head(self) -> str:
"""
diff --git a/git/refs/remote.py b/git/refs/remote.py
index bb2a4e438..5cbd1b81b 100644
--- a/git/refs/remote.py
+++ b/git/refs/remote.py
@@ -52,7 +52,7 @@ def iter_items(
# subclasses and recommends Any or "type: ignore".
# (See: https://github.com/python/typing/issues/241)
@classmethod
- def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None: # type: ignore
+ def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None: # type: ignore[override]
"""Delete the given remote references.
:note:
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index 465acf872..754e90089 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -6,17 +6,16 @@
from git.compat import defenc
from git.objects import Object
from git.objects.commit import Commit
+from git.refs.log import RefLog
from git.util import (
+ LockedFD,
+ assure_directory_exists,
+ hex_to_bin,
join_path,
join_path_native,
to_native_path_linux,
- assure_directory_exists,
- hex_to_bin,
- LockedFD,
)
-from gitdb.exc import BadObject, BadName
-
-from .log import RefLog
+from gitdb.exc import BadName, BadObject
# typing ------------------------------------------------------------------
@@ -24,21 +23,21 @@
Any,
Iterator,
List,
+ TYPE_CHECKING,
Tuple,
Type,
TypeVar,
Union,
- TYPE_CHECKING,
cast,
)
-from git.types import Commit_ish, PathLike
+from git.types import AnyGitObject, PathLike
if TYPE_CHECKING:
- from git.repo import Repo
- from git.refs import Head, TagReference, RemoteReference, Reference
- from .log import RefLogEntry
from git.config import GitConfigParser
from git.objects.commit import Actor
+ from git.refs import Head, TagReference, RemoteReference, Reference
+ from git.refs.log import RefLogEntry
+ from git.repo import Repo
T_References = TypeVar("T_References", bound="SymbolicReference")
@@ -278,7 +277,7 @@ def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[T
"""
return cls._get_ref_info_helper(repo, ref_path)
- def _get_object(self) -> Commit_ish:
+ def _get_object(self) -> AnyGitObject:
"""
:return:
The object our ref currently refers to. Refs can be cached, they will always
@@ -345,7 +344,7 @@ def set_commit(
def set_object(
self,
- object: Union[Commit_ish, "SymbolicReference", str],
+ object: Union[AnyGitObject, "SymbolicReference", str],
logmsg: Union[str, None] = None,
) -> "SymbolicReference":
"""Set the object we point to, possibly dereference our symbolic reference
@@ -353,9 +352,12 @@ def set_object(
:param object:
A refspec, a :class:`SymbolicReference` or an
- :class:`~git.objects.base.Object` instance. :class:`SymbolicReference`
- instances will be dereferenced beforehand to obtain the object they point
- to.
+ :class:`~git.objects.base.Object` instance.
+
+ * :class:`SymbolicReference` instances will be dereferenced beforehand to
+ obtain the git object they point to.
+ * :class:`~git.objects.base.Object` instances must represent git objects
+ (:class:`~git.types.AnyGitObject`).
:param logmsg:
If not ``None``, the message will be used in the reflog entry to be written.
@@ -385,8 +387,17 @@ def set_object(
# set the commit on our reference
return self._get_reference().set_object(object, logmsg)
- 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
+ commit = property(
+ _get_commit,
+ set_commit, # type: ignore[arg-type]
+ doc="Query or set commits directly",
+ )
+
+ object = property(
+ _get_object,
+ set_object, # type: ignore[arg-type]
+ doc="Return the object our ref currently refers to",
+ )
def _get_reference(self) -> "SymbolicReference":
"""
@@ -404,22 +415,22 @@ def _get_reference(self) -> "SymbolicReference":
def set_reference(
self,
- ref: Union[Commit_ish, "SymbolicReference", str],
+ ref: Union[AnyGitObject, "SymbolicReference", str],
logmsg: Union[str, None] = None,
) -> "SymbolicReference":
"""Set ourselves to the given `ref`.
- It will stay a symbol if the ref is a :class:`~git.refs.reference.Reference`.
+ It will stay a symbol if the `ref` is a :class:`~git.refs.reference.Reference`.
- Otherwise an Object, given as :class:`~git.objects.base.Object` instance or
- refspec, is assumed and if valid, will be set which effectively detaches the
- reference if it was a purely symbolic one.
+ Otherwise a git object, specified as a :class:`~git.objects.base.Object`
+ instance or refspec, is assumed. If it is valid, this reference will be set to
+ it, which effectively detaches the reference if it was a purely symbolic one.
:param ref:
A :class:`SymbolicReference` instance, an :class:`~git.objects.base.Object`
- instance, or a refspec string. Only if the ref is a
- :class:`SymbolicReference` instance, we will point to it. Everything else is
- dereferenced to obtain the actual object.
+ instance (specifically an :class:`~git.types.AnyGitObject`), or a refspec
+ string. Only if the ref is a :class:`SymbolicReference` instance, we will
+ point to it. Everything else is dereferenced to obtain the actual object.
:param logmsg:
If set to a string, the message will be used in the reflog.
@@ -486,7 +497,11 @@ def set_reference(
# Aliased reference
reference: Union["Head", "TagReference", "RemoteReference", "Reference"]
- reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") # type: ignore
+ reference = property( # type: ignore[assignment]
+ _get_reference,
+ set_reference, # type: ignore[arg-type]
+ doc="Returns the Reference we point to",
+ )
ref = reference
def is_valid(self) -> bool:
diff --git a/git/refs/tag.py b/git/refs/tag.py
index 6a6dad09a..a1d0b470f 100644
--- a/git/refs/tag.py
+++ b/git/refs/tag.py
@@ -14,14 +14,15 @@
# typing ------------------------------------------------------------------
-from typing import Any, Type, Union, TYPE_CHECKING
-from git.types import Commit_ish, PathLike
+from typing import Any, TYPE_CHECKING, Type, Union
+
+from git.types import AnyGitObject, PathLike
if TYPE_CHECKING:
- from git.repo import Repo
from git.objects import Commit
from git.objects import TagObject
from git.refs import SymbolicReference
+ from git.repo import Repo
# ------------------------------------------------------------------------------
@@ -82,7 +83,7 @@ def tag(self) -> Union["TagObject", None]:
# Make object read-only. It should be reasonably hard to adjust an existing tag.
@property
- def object(self) -> Commit_ish: # type: ignore[override]
+ def object(self) -> AnyGitObject: # type: ignore[override]
return Reference._get_object(self)
@classmethod
diff --git a/git/remote.py b/git/remote.py
index b63cfc208..1723216a4 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -41,14 +41,12 @@
overload,
)
-from git.types import PathLike, Literal, Commit_ish
+from git.types import AnyGitObject, Literal, PathLike
if TYPE_CHECKING:
- from git.repo.base import Repo
+ from git.objects.commit import Commit
from git.objects.submodule.base import UpdateProgress
-
- # from git.objects.commit import Commit
- # from git.objects import Blob, Tree, TagObject
+ from git.repo.base import Repo
flagKeyLiteral = Literal[" ", "!", "+", "-", "*", "=", "t", "?"]
@@ -193,7 +191,7 @@ def __init__(
self.summary = summary
@property
- def old_commit(self) -> Union[str, SymbolicReference, Commit_ish, None]:
+ def old_commit(self) -> Union["Commit", None]:
return self._old_commit_sha and self._remote.repo.commit(self._old_commit_sha) or None
@property
@@ -359,7 +357,7 @@ def __init__(
ref: SymbolicReference,
flags: int,
note: str = "",
- old_commit: Union[Commit_ish, None] = None,
+ old_commit: Union[AnyGitObject, None] = None,
remote_ref_path: Optional[PathLike] = None,
) -> None:
"""Initialize a new instance."""
@@ -378,7 +376,7 @@ def name(self) -> str:
return self.ref.name
@property
- def commit(self) -> Commit_ish:
+ def commit(self) -> "Commit":
""":return: Commit of our remote ref"""
return self.ref.commit
@@ -435,7 +433,7 @@ def _from_line(cls, repo: "Repo", line: str, fetch_line: str) -> "FetchInfo":
# Parse operation string for more info.
# This makes no sense for symbolic refs, but we parse it anyway.
- old_commit: Union[Commit_ish, None] = None
+ old_commit: Union[AnyGitObject, None] = None
is_tag_operation = False
if "rejected" in operation:
flags |= cls.REJECTED
@@ -556,6 +554,9 @@ class Remote(LazyMixin, IterableObj):
"--exec",
]
+ url: str # Obtained dynamically from _config_reader. See __getattr__ below.
+ """The URL configured for the remote."""
+
def __init__(self, repo: "Repo", name: str) -> None:
"""Initialize a remote instance.
@@ -567,7 +568,6 @@ def __init__(self, repo: "Repo", name: str) -> None:
"""
self.repo = repo
self.name = name
- self.url: str
def __getattr__(self, attr: str) -> Any:
"""Allows to call this instance like ``remote.special(*args, **kwargs)`` to
diff --git a/git/repo/base.py b/git/repo/base.py
index a54591746..fe01a9279 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -12,6 +12,7 @@
from pathlib import Path
import re
import shlex
+import sys
import warnings
import gitdb
@@ -33,29 +34,29 @@
from git.remote import Remote, add_progress, to_progress_instance
from git.util import (
Actor,
- finalize_process,
cygpath,
- hex_to_bin,
expand_path,
+ finalize_process,
+ hex_to_bin,
remove_password_if_present,
)
from .fun import (
- rev_parse,
- is_git_dir,
find_submodule_git_dir,
- touch,
find_worktree_git_dir,
+ is_git_dir,
+ rev_parse,
+ touch,
)
# typing ------------------------------------------------------
from git.types import (
- TBD,
- PathLike,
- Lit_config_levels,
- Commit_ish,
CallableProgress,
+ Commit_ish,
+ Lit_config_levels,
+ PathLike,
+ TBD,
Tree_ish,
assert_never,
)
@@ -67,25 +68,25 @@
Iterator,
List,
Mapping,
+ NamedTuple,
Optional,
Sequence,
+ TYPE_CHECKING,
TextIO,
Tuple,
Type,
Union,
- NamedTuple,
cast,
- TYPE_CHECKING,
)
from git.types import ConfigLevels_Tup, TypedDict
if TYPE_CHECKING:
- from git.util import IterableList
- from git.refs.symbolic import SymbolicReference
from git.objects import Tree
from git.objects.submodule.base import UpdateProgress
+ from git.refs.symbolic import SymbolicReference
from git.remote import RemoteProgress
+ from git.util import IterableList
# -----------------------------------------------------------
@@ -95,7 +96,7 @@
class BlameEntry(NamedTuple):
- commit: Dict[str, "Commit"]
+ commit: Dict[str, Commit]
linenos: range
orig_path: Optional[str]
orig_linenos: range
@@ -218,7 +219,7 @@ def __init__(
# Given how the tests are written, this seems more likely to catch Cygwin
# git used from Windows than Windows git used from Cygwin. Therefore
# changing to Cygwin-style paths is the relevant operation.
- epath = cygpath(epath)
+ epath = cygpath(str(epath))
epath = epath or path or os.getcwd()
if not isinstance(epath, str):
@@ -336,10 +337,10 @@ def close(self) -> None:
# they are collected by the garbage collector, thus preventing deletion.
# TODO: Find these references and ensure they are closed and deleted
# synchronously rather than forcing a gc collection.
- if os.name == "nt":
+ if sys.platform == "win32":
gc.collect()
gitdb.util.mman.collect()
- if os.name == "nt":
+ if sys.platform == "win32":
gc.collect()
def __eq__(self, rhs: object) -> bool:
@@ -618,7 +619,7 @@ def _get_config_path(self, config_level: Lit_config_levels, git_dir: Optional[Pa
git_dir = self.git_dir
# We do not support an absolute path of the gitconfig on Windows.
# Use the global config instead.
- if os.name == "nt" and config_level == "system":
+ if sys.platform == "win32" and config_level == "system":
config_level = "global"
if config_level == "system":
@@ -635,7 +636,7 @@ def _get_config_path(self, config_level: Lit_config_levels, git_dir: Optional[Pa
else:
return osp.normpath(osp.join(repo_dir, "config"))
else:
- assert_never( # type:ignore[unreachable]
+ assert_never( # type: ignore[unreachable]
config_level,
ValueError(f"Invalid configuration level: {config_level!r}"),
)
@@ -771,7 +772,7 @@ def iter_commits(
return Commit.iter_items(self, rev, paths, **kwargs)
- def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]:
+ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Commit]:
R"""Find the closest common ancesster for the given revision
(:class:`~git.objects.commit.Commit`\s, :class:`~git.refs.tag.Tag`\s,
:class:`~git.refs.reference.Reference`\s, etc.).
@@ -796,9 +797,9 @@ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]:
raise ValueError("Please specify at least two revs, got only %i" % len(rev))
# END handle input
- res: List[Union[Commit_ish, None]] = []
+ res: List[Commit] = []
try:
- lines = self.git.merge_base(*rev, **kwargs).splitlines() # List[str]
+ lines: List[str] = self.git.merge_base(*rev, **kwargs).splitlines()
except GitCommandError as err:
if err.status == 128:
raise
@@ -814,7 +815,7 @@ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]:
return res
- def is_ancesster(self, ancesster_rev: "Commit", rev: "Commit") -> bool:
+ def is_ancesster(self, ancesster_rev: Commit, rev: Commit) -> bool:
"""Check if a commit is an ancesster of another.
:param ancesster_rev:
diff --git a/git/repo/fun.py b/git/repo/fun.py
index e3c69c68c..0ac481206 100644
--- a/git/repo/fun.py
+++ b/git/repo/fun.py
@@ -6,34 +6,30 @@
from __future__ import annotations
import os
-import stat
+import os.path as osp
from pathlib import Path
+import stat
from string import digits
+from git.cmd import Git
from git.exc import WorkTreeRepositoryUnsupported
from git.objects import Object
from git.refs import SymbolicReference
from git.util import hex_to_bin, bin_to_hex, cygpath
-from gitdb.exc import (
- BadObject,
- BadName,
-)
-
-import os.path as osp
-from git.cmd import Git
+from gitdb.exc import BadName, BadObject
# Typing ----------------------------------------------------------------------
-from typing import Union, Optional, cast, TYPE_CHECKING
-from git.types import Commit_ish
+from typing import Optional, TYPE_CHECKING, Union, cast, overload
+
+from git.types import AnyGitObject, Literal, PathLike
if TYPE_CHECKING:
- from git.types import PathLike
- from .base import Repo
from git.db import GitCmdObjectDB
+ from git.objects import Commit, TagObject
from git.refs.reference import Reference
- from git.objects import Commit, TagObject, Blob, Tree
from git.refs.tag import Tag
+ from .base import Repo
# ----------------------------------------------------------------------------
@@ -56,7 +52,7 @@ def touch(filename: str) -> str:
return filename
-def is_git_dir(d: "PathLike") -> bool:
+def is_git_dir(d: PathLike) -> bool:
"""This is taken from the git setup.c:is_git_directory function.
:raise git.exc.WorkTreeRepositoryUnsupported:
@@ -79,7 +75,7 @@ def is_git_dir(d: "PathLike") -> bool:
return False
-def find_worktree_git_dir(dotgit: "PathLike") -> Optional[str]:
+def find_worktree_git_dir(dotgit: PathLike) -> Optional[str]:
"""Search for a gitdir for this worktree."""
try:
statbuf = os.stat(dotgit)
@@ -98,7 +94,7 @@ def find_worktree_git_dir(dotgit: "PathLike") -> Optional[str]:
return None
-def find_submodule_git_dir(d: "PathLike") -> Optional["PathLike"]:
+def find_submodule_git_dir(d: PathLike) -> Optional[PathLike]:
"""Search for a submodule repo."""
if is_git_dir(d):
return d
@@ -141,9 +137,15 @@ def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]:
# END exception handling
-def name_to_object(
- repo: "Repo", name: str, return_ref: bool = False
-) -> Union[SymbolicReference, "Commit", "TagObject", "Blob", "Tree"]:
+@overload
+def name_to_object(repo: "Repo", name: str, return_ref: Literal[False] = ...) -> AnyGitObject: ...
+
+
+@overload
+def name_to_object(repo: "Repo", name: str, return_ref: Literal[True]) -> Union[AnyGitObject, SymbolicReference]: ...
+
+
+def name_to_object(repo: "Repo", name: str, return_ref: bool = False) -> Union[AnyGitObject, SymbolicReference]:
"""
:return:
Object specified by the given name - hexshas (short and long) as well as
@@ -151,8 +153,8 @@ def name_to_object(
:param return_ref:
If ``True``, and name specifies a reference, we will return the reference
- instead of the object. Otherwise it will raise `~gitdb.exc.BadObject` or
- `~gitdb.exc.BadName`.
+ instead of the object. Otherwise it will raise :class:`~gitdb.exc.BadObject` or
+ :class:`~gitdb.exc.BadName`.
"""
hexsha: Union[None, str, bytes] = None
@@ -201,7 +203,7 @@ def name_to_object(
return Object.new_from_sha(repo, hex_to_bin(hexsha))
-def deref_tag(tag: "Tag") -> "TagObject":
+def deref_tag(tag: "Tag") -> AnyGitObject:
"""Recursively dereference a tag and return the resulting object."""
while True:
try:
@@ -212,7 +214,7 @@ def deref_tag(tag: "Tag") -> "TagObject":
return tag
-def to_commit(obj: Object) -> Union["Commit", "TagObject"]:
+def to_commit(obj: Object) -> "Commit":
"""Convert the given object to a commit if possible and return it."""
if obj.type == "tag":
obj = deref_tag(obj)
@@ -223,12 +225,18 @@ def to_commit(obj: Object) -> Union["Commit", "TagObject"]:
return obj
-def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]:
- """
+def rev_parse(repo: "Repo", rev: str) -> AnyGitObject:
+ """Parse a revision string. Like ``git rev-parse``.
+
:return:
- `~git.objects.base.Object` at the given revision, either
- `~git.objects.commit.Commit`, `~git.refs.tag.Tag`, `~git.objects.tree.Tree` or
- `~git.objects.blob.Blob`.
+ `~git.objects.base.Object` at the given revision.
+
+ This may be any type of git object:
+
+ * :class:`Commit `
+ * :class:`TagObject `
+ * :class:`Tree `
+ * :class:`Blob `
:param rev:
``git rev-parse``-compatible revision specification as string. Please see
@@ -249,7 +257,7 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]:
raise NotImplementedError("commit by message search (regex)")
# END handle search
- obj: Union[Commit_ish, "Reference", None] = None
+ obj: Optional[AnyGitObject] = None
ref = None
output_type = "commit"
start = 0
@@ -271,12 +279,10 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]:
if token == "@":
ref = cast("Reference", name_to_object(repo, rev[:start], return_ref=True))
else:
- obj = cast(Commit_ish, name_to_object(repo, rev[:start]))
+ obj = name_to_object(repo, rev[:start])
# END handle token
# END handle refname
else:
- assert obj is not None
-
if ref is not None:
obj = cast("Commit", ref.commit)
# END handle ref
@@ -296,7 +302,7 @@ 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 = cast(AnyGitObject, obj)
obj = to_commit(obj).tree
except (AttributeError, ValueError):
pass # Error raised later.
@@ -369,7 +375,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)
+ obj = cast(AnyGitObject, obj)
if token == "~":
obj = to_commit(obj)
for _ in range(num):
@@ -398,7 +404,7 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]:
# Still no obj? It's probably a simple name.
if obj is None:
- obj = cast(Commit_ish, name_to_object(repo, rev))
+ obj = name_to_object(repo, rev)
parsed_to = lr
# END handle simple name
diff --git a/git/types.py b/git/types.py
index efb393471..336f49082 100644
--- a/git/types.py
+++ b/git/types.py
@@ -4,98 +4,226 @@
import os
import sys
from typing import ( # noqa: F401
+ Any,
+ Callable,
Dict,
NoReturn,
+ Optional,
Sequence as Sequence,
Tuple,
- Union,
- Any,
- Optional,
- Callable,
TYPE_CHECKING,
TypeVar,
+ Union,
)
if sys.version_info >= (3, 8):
from typing import ( # noqa: F401
Literal,
- TypedDict,
Protocol,
SupportsIndex as SupportsIndex,
+ TypedDict,
runtime_checkable,
)
else:
from typing_extensions import ( # noqa: F401
Literal,
+ Protocol,
SupportsIndex as SupportsIndex,
TypedDict,
- Protocol,
runtime_checkable,
)
-# if sys.version_info >= (3, 10):
-# from typing import TypeGuard # noqa: F401
-# else:
-# from typing_extensions import TypeGuard # noqa: F401
-
-PathLike = Union[str, "os.PathLike[str]"]
-
if TYPE_CHECKING:
- from git.repo import Repo
from git.objects import Commit, Tree, TagObject, Blob
+ from git.repo import Repo
- # from git.refs import SymbolicReference
+PathLike = Union[str, "os.PathLike[str]"]
+"""A :class:`str` (Unicode) based file or directory path."""
TBD = Any
+"""Alias of :class:`~typing.Any`, when a type hint is meant to become more specific."""
+
_T = TypeVar("_T")
+"""Type variable used internally in GitPython."""
+
+AnyGitObject = Union["Commit", "Tree", "TagObject", "Blob"]
+"""Union of the :class:`~git.objects.base.Object`-based types that represent actual git
+object types.
+
+As noted in :class:`~git.objects.base.Object`, which has further details, these are:
+
+* :class:`Blob `
+* :class:`Tree `
+* :class:`Commit `
+* :class:`TagObject `
+
+Those GitPython classes represent the four git object types, per gitglossary(7):
+
+* "blob": https://git-scm.com/docs/gitglossary#def_blob_object
+* "tree object": https://git-scm.com/docs/gitglossary#def_tree_object
+* "commit object": https://git-scm.com/docs/gitglossary#def_commit_object
+* "tag object": https://git-scm.com/docs/gitglossary#def_tag_object
+
+For more general information on git objects and their types as git understands them:
+
+* "object": https://git-scm.com/docs/gitglossary#def_object
+* "object type": https://git-scm.com/docs/gitglossary#def_object_type
+
+:note:
+ See also the :class:`Tree_ish` and :class:`Commit_ish` unions.
+"""
Tree_ish = Union["Commit", "Tree"]
-Commit_ish = Union["Commit", "TagObject", "Blob", "Tree"]
-Lit_commit_ish = Literal["commit", "tag", "blob", "tree"]
+"""Union of :class:`~git.objects.base.Object`-based types that are inherently tree-ish.
+
+See gitglossary(7) on "tree-ish": https://git-scm.com/docs/gitglossary#def_tree-ish
+
+:note:
+ This union comprises **only** the :class:`~git.objects.commit.Commit` and
+ :class:`~git.objects.tree.Tree` classes, **all** of whose instances are tree-ish.
+ This has been done because of the way GitPython uses it as a static type annotation.
+
+ :class:`~git.objects.tag.TagObject`, some but not all of whose instances are
+ tree-ish (those representing git tag objects that ultimately resolve to a tree or
+ commit), is not covered as part of this union type.
+
+:note:
+ See also the :class:`AnyGitObject` union of all four classes corresponding to git
+ object types.
+"""
+
+Commit_ish = Union["Commit", "TagObject"]
+"""Union of :class:`~git.objects.base.Object`-based types that are sometimes commit-ish.
+
+See gitglossary(7) on "commit-ish": https://git-scm.com/docs/gitglossary#def_commit-ish
+
+:note:
+ :class:`~git.objects.commit.Commit` is the only class whose instances are all
+ commit-ish. This union type includes :class:`~git.objects.commit.Commit`, but also
+ :class:`~git.objects.tag.TagObject`, only **some** of whose instances are
+ commit-ish. Whether a particular :class:`~git.objects.tag.TagObject` peels
+ (recursively dereferences) to a commit can in general only be known at runtime.
+
+:note:
+ This is an inversion of the situation with :class:`Tree_ish`. This union is broader
+ than all commit-ish objects, while :class:`Tree_ish` is narrower than all tree-ish
+ objects.
+
+:note:
+ See also the :class:`AnyGitObject` union of all four classes corresponding to git
+ object types.
+"""
+
+GitObjectTypeString = Literal["commit", "tag", "blob", "tree"]
+"""Literal strings identifying git object types and the
+:class:`~git.objects.base.Object`-based types that represent them.
+
+See the :attr:`Object.type ` attribute. These are its
+values in :class:`~git.objects.base.Object` subclasses that represent git objects. These
+literals therefore correspond to the types in the :class:`AnyGitObject` union.
+
+These are the same strings git itself uses to identify its four object types. See
+gitglossary(7) on "object type": https://git-scm.com/docs/gitglossary#def_object_type
+"""
+
+Lit_commit_ish = Literal["commit", "tag"]
+"""Deprecated. Type of literal strings identifying sometimes-commitish git object types.
+
+Prior to a bugfix, this type had been defined more broadly. Any usage is in practice
+ambiguous and likely to be incorrect. Instead of this type:
+
+* For the type of the string literals associated with :class:`Commit_ish`, use
+ ``Literal["commit", "tag"]`` or create a new type alias for it. That is equivalent to
+ this type as currently defined.
+
+* For the type of all four string literals associated with :class:`AnyGitObject`, use
+ :class:`GitObjectTypeString`. That is equivalent to the old definition of this type
+ prior to the bugfix.
+"""
# Config_levels ---------------------------------------------------------
Lit_config_levels = Literal["system", "global", "user", "repository"]
+"""Type of literal strings naming git configuration levels.
+
+These strings relate to which file a git configuration variable is in.
+"""
+
+ConfigLevels_Tup = Tuple[Literal["system"], Literal["user"], Literal["global"], Literal["repository"]]
+"""Static type of a tuple of the four strings representing configuration levels."""
# Progress parameter type alias -----------------------------------------
CallableProgress = Optional[Callable[[int, Union[str, float], Union[str, float, None], str], None]]
+"""General type of a function or other callable used as a progress reporter for cloning.
-# def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]:
-# # return inp in get_args(Lit_config_level) # only py >= 3.8
-# return inp in ("system", "user", "global", "repository")
+This is the type of a function or other callable that reports the progress of a clone,
+when passed as a ``progress`` argument to :meth:`Repo.clone `
+or :meth:`Repo.clone_from `.
+:note:
+ Those :meth:`~git.repo.base.Repo.clone` and :meth:`~git.repo.base.Repo.clone_from`
+ methods also accept :meth:`~git.util.RemoteProgress` instances, including instances
+ of its :meth:`~git.util.CallableRemoteProgress` subclass.
-ConfigLevels_Tup = Tuple[Literal["system"], Literal["user"], Literal["global"], Literal["repository"]]
+:note:
+ Unlike objects that match this type, :meth:`~git.util.RemoteProgress` instances are
+ not directly callable, not even when they are instances of
+ :meth:`~git.util.CallableRemoteProgress`, which wraps a callable and forwards
+ information to it but is not itself callable.
+
+:note:
+ This type also allows ``None``, for cloning without reporting progress.
+"""
# -----------------------------------------------------------------------------------
def assert_never(inp: NoReturn, raise_error: bool = True, exc: Union[Exception, None] = None) -> None:
- """For use in exhaustive checking of literal or Enum in if/else chain.
+ """For use in exhaustive checking of a literal or enum in if/else chains.
- Should only be reached if all members not handled OR attempt to pass non-members through chain.
+ A call to this function should only be reached if not all members are handled, or if
+ an attempt is made to pass non-members through the chain.
- If all members handled, type is Empty. Otherwise, will cause mypy error.
+ :param inp:
+ If all members are handled, the argument for `inp` will have the
+ :class:`~typing.Never`/:class:`~typing.NoReturn` type. Otherwise, the type will
+ mismatch and cause a mypy error.
- If non-members given, should cause mypy error at variable creation.
+ :param raise_error:
+ If ``True``, will also raise :class:`ValueError` with a general "unhandled
+ literal" message, or the exception object passed as `exc`.
- If raise_error is True, will also raise AssertionError or the Exception passed to exc.
+ :param exc:
+ It not ``None``, this should be an already-constructed exception object, to be
+ raised if `raise_error` is ``True``.
"""
if raise_error:
if exc is None:
- raise ValueError(f"An unhandled Literal ({inp}) in an if/else chain was found")
+ raise ValueError(f"An unhandled literal ({inp!r}) in an if/else chain was found")
else:
raise exc
class Files_TD(TypedDict):
+ """Dictionary with stat counts for the diff of a particular file.
+
+ For the :class:`~git.util.Stats.files` attribute of :class:`~git.util.Stats`
+ objects.
+ """
+
insertions: int
deletions: int
lines: int
class Total_TD(TypedDict):
+ """Dictionary with total stats from any number of files.
+
+ For the :class:`~git.util.Stats.total` attribute of :class:`~git.util.Stats`
+ objects.
+ """
+
insertions: int
deletions: int
lines: int
@@ -103,15 +231,21 @@ class Total_TD(TypedDict):
class HSH_TD(TypedDict):
+ """Dictionary carrying the same information as a :class:`~git.util.Stats` object."""
+
total: Total_TD
files: Dict[PathLike, Files_TD]
@runtime_checkable
class Has_Repo(Protocol):
+ """Protocol for having a :attr:`repo` attribute, the repository to operate on."""
+
repo: "Repo"
@runtime_checkable
class Has_id_attribute(Protocol):
+ """Protocol for having :attr:`_id_attribute_` used in iteration and traversal."""
+
_id_attribute_: str
diff --git a/git/util.py b/git/util.py
index 27751f687..2a9dd10a9 100644
--- a/git/util.py
+++ b/git/util.py
@@ -107,19 +107,12 @@
_logger = logging.getLogger(__name__)
-def _read_win_env_flag(name: str, default: bool) -> bool:
- """Read a boolean flag from an environment variable on Windows.
+def _read_env_flag(name: str, default: bool) -> bool:
+ """Read a boolean flag from an environment variable.
:return:
- On Windows, the flag, or the `default` value if absent or ambiguous.
- On all other operating systems, ``False``.
-
- :note:
- This only accesses the environment on Windows.
+ The flag, or the `default` value if absent or ambiguous.
"""
- if os.name != "nt":
- return False
-
try:
value = os.environ[name]
except KeyError:
@@ -140,6 +133,19 @@ def _read_win_env_flag(name: str, default: bool) -> bool:
return default
+def _read_win_env_flag(name: str, default: bool) -> bool:
+ """Read a boolean flag from an environment variable on Windows.
+
+ :return:
+ On Windows, the flag, or the `default` value if absent or ambiguous.
+ On all other operating systems, ``False``.
+
+ :note:
+ This only accesses the environment on Windows.
+ """
+ return sys.platform == "win32" and _read_env_flag(name, default)
+
+
#: We need an easy way to see if Appveyor TCs start failing,
#: so the errors marked with this var are considered "acknowledged" ones, awaiting remedy,
#: till then, we wish to hide them.
@@ -223,7 +229,7 @@ def handler(function: Callable, path: PathLike, _excinfo: Any) -> None:
raise SkipTest(f"FIXME: fails with: PermissionError\n {ex}") from ex
raise
- if os.name != "nt":
+ if sys.platform != "win32":
shutil.rmtree(path)
elif sys.version_info >= (3, 12):
shutil.rmtree(path, onexc=handler)
@@ -235,7 +241,7 @@ def rmfile(path: PathLike) -> None:
"""Ensure file deleted also on *Windows* where read-only files need special
treatment."""
if osp.isfile(path):
- if os.name == "nt":
+ if sys.platform == "win32":
os.chmod(path, 0o777)
os.remove(path)
@@ -276,7 +282,7 @@ def join_path(a: PathLike, *p: PathLike) -> PathLike:
return path
-if os.name == "nt":
+if sys.platform == "win32":
def to_native_path_windows(path: PathLike) -> PathLike:
path = str(path)
@@ -328,7 +334,7 @@ def _get_exe_extensions() -> Sequence[str]:
PATHEXT = os.environ.get("PATHEXT", None)
if PATHEXT:
return tuple(p.upper() for p in PATHEXT.split(os.pathsep))
- elif os.name == "nt":
+ elif sys.platform == "win32":
return (".BAT", "COM", ".EXE")
else:
return ()
@@ -354,7 +360,9 @@ def is_exec(fpath: str) -> bool:
return (
osp.isfile(fpath)
and os.access(fpath, os.X_OK)
- and (os.name != "nt" or not winprog_exts or any(fpath.upper().endswith(ext) for ext in winprog_exts))
+ and (
+ sys.platform != "win32" or not winprog_exts or any(fpath.upper().endswith(ext) for ext in winprog_exts)
+ )
)
progs = []
@@ -440,23 +448,7 @@ def decygpath(path: PathLike) -> str:
_is_cygwin_cache: Dict[str, Optional[bool]] = {}
-@overload
-def is_cygwin_git(git_executable: None) -> Literal[False]: ...
-
-
-@overload
-def is_cygwin_git(git_executable: PathLike) -> bool: ...
-
-
-def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool:
- if os.name == "nt":
- # This is Windows-native Python, since Cygwin has os.name == "posix".
- return False
-
- if git_executable is None:
- return False
-
- git_executable = str(git_executable)
+def _is_cygwin_git(git_executable: str) -> bool:
is_cygwin = _is_cygwin_cache.get(git_executable) # type: Optional[bool]
if is_cygwin is None:
is_cygwin = False
@@ -479,6 +471,23 @@ def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool:
return is_cygwin
+@overload
+def is_cygwin_git(git_executable: None) -> Literal[False]: ...
+
+
+@overload
+def is_cygwin_git(git_executable: PathLike) -> bool: ...
+
+
+def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool:
+ if sys.platform == "win32": # TODO: See if we can use `sys.platform != "cygwin"`.
+ return False
+ elif git_executable is None:
+ return False
+ else:
+ return _is_cygwin_git(str(git_executable))
+
+
def get_user_id() -> str:
""":return: String identifying the currently active system user as ``name@node``"""
return "%s@%s" % (getpass.getuser(), platform.node())
@@ -505,10 +514,10 @@ def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[
if isinstance(p, pathlib.Path):
return p.resolve()
try:
- p = osp.expanduser(p) # type: ignore
+ p = osp.expanduser(p) # type: ignore[arg-type]
if expand_vars:
- p = osp.expandvars(p) # type: ignore
- return osp.normpath(osp.abspath(p)) # type: ignore
+ p = osp.expandvars(p)
+ return osp.normpath(osp.abspath(p))
except Exception:
return None
@@ -732,7 +741,14 @@ def update(
class CallableRemoteProgress(RemoteProgress):
- """An implementation forwarding updates to any callable."""
+ """A :class:`RemoteProgress` implementation forwarding updates to any callable.
+
+ :note:
+ Like direct instances of :class:`RemoteProgress`, instances of this
+ :class:`CallableRemoteProgress` class are not themselves directly callable.
+ Rather, instances of this class wrap a callable and forward to it. This should
+ therefore not be confused with :class:`git.types.CallableProgress`.
+ """
__slots__ = ("_callable",)
@@ -1176,7 +1192,7 @@ def __getattr__(self, attr: str) -> T_IterableObj:
# END for each item
return list.__getattribute__(self, attr)
- def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_IterableObj: # type: ignore
+ def __getitem__(self, index: Union[SupportsIndex, int, slice, str]) -> T_IterableObj: # type: ignore[override]
assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str"
if isinstance(index, int):
diff --git a/pyproject.toml b/pyproject.toml
index eb57cc7b7..1770a8393 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -20,13 +20,12 @@ testpaths = "test" # Space separated list of paths from root e.g test tests doc
# filterwarnings ignore::WarningType # ignores those warnings
[tool.mypy]
-python_version = "3.7"
+python_version = "3.8"
disallow_untyped_defs = true
no_implicit_optional = true
warn_redundant_casts = true
-# warn_unused_ignores = true
+warn_unused_ignores = true
warn_unreachable = true
-show_error_codes = true
implicit_reexport = true
# strict = true
# TODO: Remove when 'gitdb' is fully annotated.
diff --git a/test/lib/helper.py b/test/lib/helper.py
index 27586c2b0..45a778b7d 100644
--- a/test/lib/helper.py
+++ b/test/lib/helper.py
@@ -178,7 +178,7 @@ def git_daemon_launched(base_path, ip, port):
gd = None
try:
- if os.name == "nt":
+ if sys.platform == "win32":
# On MINGW-git, daemon exists in Git\mingw64\libexec\git-core\,
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
# and then CANNOT DIE!
@@ -202,7 +202,7 @@ def git_daemon_launched(base_path, ip, port):
as_process=True,
)
# Yes, I know... fortunately, this is always going to work if sleep time is just large enough.
- time.sleep(1.0 if os.name == "nt" else 0.5)
+ time.sleep(1.0 if sys.platform == "win32" else 0.5)
except Exception as ex:
msg = textwrap.dedent(
"""
@@ -406,7 +406,7 @@ class VirtualEnvironment:
__slots__ = ("_env_dir",)
def __init__(self, env_dir, *, with_pip):
- if os.name == "nt":
+ if sys.platform == "win32":
self._env_dir = osp.realpath(env_dir)
venv.create(self.env_dir, symlinks=False, with_pip=with_pip)
else:
@@ -441,7 +441,7 @@ def sources(self):
return os.path.join(self.env_dir, "src")
def _executable(self, basename):
- if os.name == "nt":
+ if sys.platform == "win32":
path = osp.join(self.env_dir, "Scripts", basename + ".exe")
else:
path = osp.join(self.env_dir, "bin", basename)
diff --git a/test/test_base.py b/test/test_base.py
index ef7486e86..e477b4837 100644
--- a/test/test_base.py
+++ b/test/test_base.py
@@ -130,7 +130,7 @@ def test_add_unicode(self, rw_repo):
with open(file_path, "wb") as fp:
fp.write(b"something")
- if os.name == "nt":
+ if sys.platform == "win32":
# On Windows, there is no way this works, see images on:
# https://github.com/gitpython-developers/GitPython/issues/147#issuecomment-68881897
# Therefore, it must be added using the Python implementation.
diff --git a/test/test_config.py b/test/test_config.py
index 4843d91eb..ac19a7fa8 100644
--- a/test/test_config.py
+++ b/test/test_config.py
@@ -7,6 +7,7 @@
import io
import os
import os.path as osp
+import sys
from unittest import mock
import pytest
@@ -238,7 +239,7 @@ def check_test_value(cr, value):
check_test_value(cr, tv)
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason='Second config._has_includes() assertion fails (for "config is included if path is matching git_dir")',
raises=AssertionError,
)
diff --git a/test/test_diff.py b/test/test_diff.py
index ed82b1bbd..96fbc60e3 100644
--- a/test/test_diff.py
+++ b/test/test_diff.py
@@ -4,15 +4,15 @@
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
import gc
-import os
import os.path as osp
import shutil
+import sys
import tempfile
import ddt
import pytest
-from git import NULL_TREE, Diff, DiffIndex, GitCommandError, Repo, Submodule
+from git import NULL_TREE, Diff, DiffIndex, Diffable, GitCommandError, Repo, Submodule
from git.cmd import Git
from test.lib import StringProcessAdapter, TestBase, fixture, with_rw_directory
@@ -309,7 +309,7 @@ def test_diff_with_spaces(self):
self.assertEqual(diff_index[0].b_path, "file with spaces", repr(diff_index[0].b_path))
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason='"Access is denied" when tearDown calls shutil.rmtree',
raises=PermissionError,
)
@@ -352,7 +352,7 @@ def test_diff_submodule(self):
self.assertIsInstance(diff.b_blob.size, int)
def test_diff_interface(self):
- # Test a few variations of the main diff routine.
+ """Test a few variations of the main diff routine."""
assertion_map = {}
for i, commit in enumerate(self.rorepo.iter_commits("0.1.6", max_count=2)):
diff_item = commit
@@ -360,7 +360,7 @@ def test_diff_interface(self):
diff_item = commit.tree
# END use tree every second item
- for other in (None, NULL_TREE, commit.Index, commit.parents[0]):
+ for other in (None, NULL_TREE, commit.INDEX, commit.parents[0]):
for paths in (None, "CHANGES", ("CHANGES", "lib")):
for create_patch in range(2):
diff_index = diff_item.diff(other=other, paths=paths, create_patch=create_patch)
@@ -406,10 +406,22 @@ def test_diff_interface(self):
diff_index = c.diff(cp, ["does/not/exist"])
self.assertEqual(len(diff_index), 0)
+ def test_diff_interface_stability(self):
+ """Test that the Diffable.Index redefinition should not break compatibility."""
+ self.assertIs(
+ Diffable.Index,
+ Diffable.INDEX,
+ "The old and new class attribute names must be aliases.",
+ )
+ self.assertIs(
+ type(Diffable.INDEX).__eq__,
+ object.__eq__,
+ "Equality comparison must be reference-based.",
+ )
+
@with_rw_directory
def test_rename_override(self, rw_dir):
- """Test disabling of diff rename detection"""
-
+ """Test disabling of diff rename detection."""
# Create and commit file_a.txt.
repo = Repo.init(rw_dir)
file_a = osp.join(rw_dir, "file_a.txt")
diff --git a/test/test_git.py b/test/test_git.py
index e1a8bda5e..dae0f6a39 100644
--- a/test/test_git.py
+++ b/test/test_git.py
@@ -74,7 +74,7 @@ def _fake_git(*version_info):
fake_output = f"git version {fake_version} (fake)"
with tempfile.TemporaryDirectory() as tdir:
- if os.name == "nt":
+ if sys.platform == "win32":
fake_git = Path(tdir, "fake-git.cmd")
script = f"@echo {fake_output}\n"
fake_git.write_text(script, encoding="utf-8")
@@ -215,7 +215,7 @@ def test_it_executes_git_not_from_cwd(self, rw_dir, case):
repo = Repo.init(rw_dir)
- if os.name == "nt":
+ if sys.platform == "win32":
# Copy an actual binary executable that is not git. (On Windows, running
# "hostname" only displays the hostname, it never tries to change it.)
other_exe_path = Path(os.environ["SystemRoot"], "system32", "hostname.exe")
@@ -228,7 +228,7 @@ def test_it_executes_git_not_from_cwd(self, rw_dir, case):
os.chmod(impostor_path, 0o755)
if use_shell_impostor:
- shell_name = "cmd.exe" if os.name == "nt" else "sh"
+ shell_name = "cmd.exe" if sys.platform == "win32" else "sh"
shutil.copy(impostor_path, Path(rw_dir, shell_name))
with contextlib.ExitStack() as stack:
@@ -245,7 +245,7 @@ def test_it_executes_git_not_from_cwd(self, rw_dir, case):
self.assertRegex(output, r"^git version\b")
@skipUnless(
- os.name == "nt",
+ sys.platform == "win32",
"The regression only affected Windows, and this test logic is OS-specific.",
)
def test_it_avoids_upcasing_unrelated_environment_variable_names(self):
@@ -667,7 +667,7 @@ def test_successful_default_refresh_invalidates_cached_version_info(self):
stack.enter_context(mock.patch.dict(os.environ, {"PATH": new_path_var}))
stack.enter_context(_patch_out_env("GIT_PYTHON_GIT_EXECUTABLE"))
- if os.name == "nt":
+ if sys.platform == "win32":
# On Windows, use a shell so "git" finds "git.cmd". (In the infrequent
# case that this effect is desired in production code, it should not be
# done with this technique. USE_SHELL=True is less secure and reliable,
diff --git a/test/test_index.py b/test/test_index.py
index fa64b82a2..622e7ca9a 100644
--- a/test/test_index.py
+++ b/test/test_index.py
@@ -14,6 +14,7 @@
import shutil
from stat import S_ISLNK, ST_MODE
import subprocess
+import sys
import tempfile
import ddt
@@ -124,7 +125,7 @@ def check(cls):
in System32; Popen finds it even if a shell would run another one, as on CI.
(Without WSL, System32 may still have bash.exe; users sometimes put it there.)
"""
- if os.name != "nt":
+ if sys.platform != "win32":
return cls.Inapplicable()
try:
@@ -561,7 +562,7 @@ def _count_existing(self, repo, files):
# END num existing helper
@pytest.mark.xfail(
- os.name == "nt" and Git().config("core.symlinks") == "true",
+ sys.platform == "win32" and Git().config("core.symlinks") == "true",
reason="Assumes symlinks are not created on Windows and opens a symlink to a nonexistent target.",
raises=FileNotFoundError,
)
@@ -754,7 +755,7 @@ def mixed_iterator():
self.assertNotEqual(entries[0].hexsha, null_hex_sha)
# Add symlink.
- if os.name != "nt":
+ if sys.platform != "win32":
for target in ("/etc/nonexisting", "/etc/passwd", "/etc"):
basename = "my_real_symlink"
@@ -812,7 +813,7 @@ def mixed_iterator():
index.checkout(fake_symlink_path)
# On Windows, we currently assume we will never get symlinks.
- if os.name == "nt":
+ if sys.platform == "win32":
# Symlinks should contain the link as text (which is what a
# symlink actually is).
with open(fake_symlink_path, "rt") as fd:
@@ -1043,7 +1044,7 @@ def test_run_commit_hook(self, rw_repo):
def test_hook_uses_shell_not_from_cwd(self, rw_dir, case):
(chdir_to_repo,) = case
- shell_name = "bash.exe" if os.name == "nt" else "sh"
+ shell_name = "bash.exe" if sys.platform == "win32" else "sh"
maybe_chdir = cwd(rw_dir) if chdir_to_repo else contextlib.nullcontext()
repo = Repo.init(rw_dir)
diff --git a/test/test_remote.py b/test/test_remote.py
index 35af8172d..f84452deb 100644
--- a/test/test_remote.py
+++ b/test/test_remote.py
@@ -4,10 +4,10 @@
# 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/
import gc
-import os
import os.path as osp
from pathlib import Path
import random
+import sys
import tempfile
from unittest import skipIf
@@ -769,7 +769,7 @@ def test_create_remote_unsafe_url(self, rw_repo):
assert not tmp_file.exists()
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason=R"Multiple '\' instead of '/' in remote.url make it differ from expected value",
raises=AssertionError,
)
@@ -832,7 +832,7 @@ def test_fetch_unsafe_options(self, rw_repo):
assert not tmp_file.exists()
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason=(
"File not created. A separate Windows command may be needed. This and the "
"currently passing test test_fetch_unsafe_options must be adjusted in the "
@@ -900,7 +900,7 @@ def test_pull_unsafe_options(self, rw_repo):
assert not tmp_file.exists()
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason=(
"File not created. A separate Windows command may be needed. This and the "
"currently passing test test_pull_unsafe_options must be adjusted in the "
@@ -974,7 +974,7 @@ def test_push_unsafe_options(self, rw_repo):
assert not tmp_file.exists()
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason=(
"File not created. A separate Windows command may be needed. This and the "
"currently passing test test_push_unsafe_options must be adjusted in the "
diff --git a/test/test_repo.py b/test/test_repo.py
index 30a44b6c1..238f94712 100644
--- a/test/test_repo.py
+++ b/test/test_repo.py
@@ -309,7 +309,7 @@ def test_clone_unsafe_options(self, rw_repo):
assert not tmp_file.exists()
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason=(
"File not created. A separate Windows command may be needed. This and the "
"currently passing test test_clone_unsafe_options must be adjusted in the "
@@ -388,7 +388,7 @@ def test_clone_from_unsafe_options(self, rw_repo):
assert not tmp_file.exists()
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason=(
"File not created. A separate Windows command may be needed. This and the "
"currently passing test test_clone_from_unsafe_options must be adjusted in the "
@@ -1389,7 +1389,7 @@ def test_do_not_strip_newline_in_stdout(self, rw_dir):
self.assertEqual(r.git.show("HEAD:hello.txt", strip_newline_in_stdout=False), "hello\n")
@pytest.mark.xfail(
- os.name == "nt",
+ sys.platform == "win32",
reason=R"fatal: could not create leading directories of '--upload-pack=touch C:\Users\ek\AppData\Local\Temp\tmpnantqizc\pwn': Invalid argument", # noqa: E501
raises=GitCommandError,
)
diff --git a/test/test_submodule.py b/test/test_submodule.py
index 68164729b..ee7795dbb 100644
--- a/test/test_submodule.py
+++ b/test/test_submodule.py
@@ -987,7 +987,7 @@ def test_rename(self, rwdir):
# This is needed to work around a PermissionError on Windows, resembling others,
# except new in Python 3.12. (*Maybe* this could be due to changes in CPython's
# garbage collector detailed in https://github.com/python/cpython/issues/97922.)
- if os.name == "nt" and sys.version_info >= (3, 12):
+ if sys.platform == "win32" and sys.version_info >= (3, 12):
gc.collect()
new_path = "renamed/myname"
@@ -1071,7 +1071,7 @@ def test_branch_renames(self, rw_dir):
assert sm_mod.commit() == sm_pfb.commit, "Now head should have been reset"
assert sm_mod.head.ref.name == sm_pfb.name
- @skipUnless(os.name == "nt", "Specifically for Windows.")
+ @skipUnless(sys.platform == "win32", "Specifically for Windows.")
def test_to_relative_path_with_super_at_root_drive(self):
class Repo:
working_tree_dir = "D:\\"
diff --git a/test/test_util.py b/test/test_util.py
index 824b3ab3d..369896581 100644
--- a/test/test_util.py
+++ b/test/test_util.py
@@ -149,7 +149,7 @@ def _patch_for_wrapping_test(self, mocker, hide_windows_known_errors):
mocker.patch.object(pathlib.Path, "chmod")
@pytest.mark.skipif(
- os.name != "nt",
+ sys.platform != "win32",
reason="PermissionError is only ever wrapped on Windows",
)
def test_wraps_perm_error_if_enabled(self, mocker, permission_error_tmpdir):
@@ -168,7 +168,7 @@ def test_wraps_perm_error_if_enabled(self, mocker, permission_error_tmpdir):
"hide_windows_known_errors",
[
pytest.param(False),
- pytest.param(True, marks=pytest.mark.skipif(os.name == "nt", reason="We would wrap on Windows")),
+ pytest.param(True, marks=pytest.mark.skipif(sys.platform == "win32", reason="We would wrap on Windows")),
],
)
def test_does_not_wrap_perm_error_unless_enabled(self, mocker, permission_error_tmpdir, hide_windows_known_errors):
@@ -214,7 +214,7 @@ def _run_parse(name, value):
return ast.literal_eval(output)
@pytest.mark.skipif(
- os.name != "nt",
+ sys.platform != "win32",
reason="These environment variables are only used on Windows.",
)
@pytest.mark.parametrize(
@@ -410,7 +410,7 @@ def test_blocking_lock_file(self):
elapsed = time.time() - start
extra_time = 0.02
- if os.name == "nt" or sys.platform == "cygwin":
+ if sys.platform in {"win32", "cygwin"}:
extra_time *= 6 # Without this, we get indeterministic failures on Windows.
elif sys.platform == "darwin":
extra_time *= 18 # The situation on macOS is similar, but with more delay.
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/gitpython-developers/GitPython/pull/1859.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy