Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve static typing and docstrings related to git object types #1859

Merged
merged 82 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
f83b056
Revise assert_never
EliahKagan Mar 2, 2024
01cc8e2
Fix unnecessarily long reference in Tree docstrings
EliahKagan Mar 3, 2024
6f3a20f
Change how tree[subscript] is introduced
EliahKagan Mar 3, 2024
85889cd
Refine how tree[subscript] is introduced
EliahKagan Mar 3, 2024
9e47083
Start adding docstrings to types in git.types
EliahKagan Mar 3, 2024
3bd8177
Document Tree_ish, Commit_ish, and related types
EliahKagan Mar 3, 2024
f3b9a69
Expand docs of classes representing Git objects
EliahKagan Mar 3, 2024
2af7640
Do a bit of tidying related to unused names
EliahKagan Mar 3, 2024
2aa053e
Add docstrings to TypedDicts in git.types
EliahKagan Mar 3, 2024
15d50de
Revise a couple new docstrings for clarity
EliahKagan Mar 4, 2024
7166703
Fix possible inaccuracy in Lit_config_levels docstring
EliahKagan Mar 4, 2024
1530fd2
Use phrases like "git object type" where applicable
EliahKagan Mar 4, 2024
2e02b09
Add docstrings to protocols in git.types
EliahKagan Mar 4, 2024
012d710
Move our PathLike below even TYPE_CHECKING imports
EliahKagan Mar 4, 2024
a06f1fc
Remove commented-out is_config_level function
EliahKagan Mar 4, 2024
c93e431
Expand git.compat docstring
EliahKagan Mar 4, 2024
29443ce
Add a cationary note about Object vs. object
EliahKagan Mar 4, 2024
b6e3ad2
Don't bind unused _assertion_msg_format
EliahKagan Mar 4, 2024
b5d9198
Remove commented-out code
EliahKagan Mar 6, 2024
2212ac9
Fix Sphinx reference that rendered overly long
EliahKagan Mar 7, 2024
3c5ca52
Simplify _safer_popen_windows "if shell" logic
EliahKagan Mar 7, 2024
43b7f8a
Annotate safer_popen broad enough for all platforms
EliahKagan Mar 7, 2024
dc95a76
Fix mypy error with creationflags in subprocess module
EliahKagan Mar 7, 2024
4191f7d
Refactor kill_after_timeout logic so mypy can check it
EliahKagan Mar 7, 2024
1ef3365
Factor communicate and watchdog logic to helper
EliahKagan Mar 7, 2024
4083dd8
Fix new mypy confusion about kill_after_timeout type
EliahKagan Mar 7, 2024
3aeef46
Fix how Diffable annotates expected repo attribute
EliahKagan Mar 7, 2024
f1cc1fe
Fix how HEAD annotates inherited commit property
EliahKagan Mar 8, 2024
e133018
Broaden cygpath parameter annotation
EliahKagan Mar 8, 2024
c34a466
Have Repo.__init__ convert epath to str first instead
EliahKagan Mar 8, 2024
4dfd480
Fix how Remote annotates dynamic config-backed url attribute
EliahKagan Mar 8, 2024
e4fd2e3
Drop wrong variable annotations in BlobFilter.__call__
EliahKagan Mar 8, 2024
94344b4
Clarify CallableProgress vs. CallableRemoteProgress
EliahKagan Mar 8, 2024
8e8b87a
Fix RootModule.update `ignore[override]` suppression
EliahKagan Mar 8, 2024
1cdec7a
Fix wrong class name in git.objects.tag docstring
EliahKagan Mar 8, 2024
ed6ead9
Correct and clarify Diffable.diff docstring
EliahKagan Mar 8, 2024
0e1df29
Start fixing diff and _process_diff_args type annotations
EliahKagan Mar 9, 2024
62c0823
Consolidate str and os.PathLike[str] (use GitPython's PathLike)
EliahKagan Mar 9, 2024
7204cc1
Further clarify Diffable.diff docstring
EliahKagan Mar 9, 2024
2f5e258
Annotate _process_diff_args without Diffable.Index
EliahKagan Mar 9, 2024
65863a2
Make NULL_TREE and Index precisely annotatable
EliahKagan Mar 9, 2024
c9952e1
Fix Sphinx references; give Diffable.Index a docstring
EliahKagan Mar 9, 2024
b8a25df
Modify annotations to accommodate NULL_TREE
EliahKagan Mar 9, 2024
e49327d
Add refresh to top-level __all__
EliahKagan Feb 24, 2024
c8ad3a3
Deprecate public access to typing imports in git
EliahKagan Feb 24, 2024
3c8cbe9
Mention collections.abc for Sequence
EliahKagan Mar 5, 2024
87b314e
Add INDEX and DiffConstants to git.__all__
EliahKagan Mar 9, 2024
9ed904c
Adjust mypy options to work well with mypy 1.9.0
EliahKagan Mar 9, 2024
aeacb00
Colorize mypy output on CI for easier reading
EliahKagan Mar 9, 2024
84fc806
Remove some unneeded mypy suppressions
EliahKagan Mar 10, 2024
96ecc2e
Drop deprecated mypy option
EliahKagan Mar 10, 2024
97d9b65
Apply intended suppression in Tree.traverse
EliahKagan Mar 10, 2024
ad00c77
Spell self.Index as self.INDEX in IndexFile.diff
EliahKagan Mar 10, 2024
2decbe4
Test that redefined Diffable.Index should be compatible
EliahKagan Mar 10, 2024
88557bc
Have git module use sys.platform to check for Windows
EliahKagan Mar 10, 2024
7204c13
Fix new mypy error in _read_win_env_flag
EliahKagan Mar 10, 2024
42e10c0
Fix new mypy error in is_cygwin_git
EliahKagan Mar 10, 2024
465ab56
Have test suite use sys.platform to check for Windows
EliahKagan Mar 10, 2024
ad8190b
Wrap docstrings and comments in _safer_popen_windows
EliahKagan Mar 10, 2024
b9d9e56
Further improve _safer_popen_windows doc
EliahKagan Mar 10, 2024
04a2753
Temporarily rename Commit_ish to Old_commit_ish
EliahKagan Mar 10, 2024
787f65c
Define and document AnyGitObject and (new) Commit_ish
EliahKagan Mar 10, 2024
1fe4dc8
Define GitObjectTypeString and update Object to use it
EliahKagan Mar 10, 2024
7328a00
Start fixing annotations that used the old Commit_ish
EliahKagan Mar 10, 2024
191f4cf
Fix some annotations in git.repo.fun
EliahKagan Mar 10, 2024
d1ce940
Remove extra `parents` param in Commit.__init__ docstring
EliahKagan Mar 10, 2024
fe42ca7
Help tools know the type of a Commit's `parents`
EliahKagan Mar 11, 2024
e66297a
Keep the type of a Commit's `parents` from being too narrow
EliahKagan Mar 11, 2024
fe7f9f2
Fix remaining old Commit_ish annotations in git.repo.fun
EliahKagan Mar 11, 2024
ab27827
Fix remaining old Commit_ish annotations in git.refs
EliahKagan Mar 11, 2024
b4b6e1e
Fix IndexFile.commit `parent_commits` annotation
EliahKagan Mar 11, 2024
5b2869f
Fix old Commit_ish annotations in git.remote
EliahKagan Mar 11, 2024
1541c62
Start on fixing Submodule parent_commit annotations
EliahKagan Mar 11, 2024
1f03e7f
Fix other submodule.base parent_commit annotations
EliahKagan Mar 14, 2024
e66b8f1
Fix old Commit_ish annotation in RootModule
EliahKagan Mar 14, 2024
93d19dc
Remove the temporary Old_commit_ish type
EliahKagan Mar 14, 2024
ebcfced
Fix and deprecate Lit_commit_ish
EliahKagan Mar 14, 2024
b070e93
Make some broad mypy suppressions more specific
EliahKagan Mar 14, 2024
0b99041
Merge branch 'main' into doc-types
EliahKagan Mar 14, 2024
011cb0a
Apply Ruff auto-fixes not included in merge
EliahKagan Mar 14, 2024
74f3c2e
Help Ruff avoid a very long line
EliahKagan Mar 14, 2024
5778b7a
Use LBYL for imports where EAFP is a mypy type error
EliahKagan Mar 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion git/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
59 changes: 52 additions & 7 deletions git/objects/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,42 @@
# --------------------------------------------------------------------------


_assertion_msg_format = "Created object %r whose python type %r disagrees with the actual git object type %r"
# _assertion_msg_format = "Created object %r whose python type %r disagrees with the actual git object type %r"
EliahKagan marked this conversation as resolved.
Show resolved Hide resolved

__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.
EliahKagan marked this conversation as resolved.
Show resolved Hide resolved

The following leaf classes represent specific kinds of git objects:

* :class:`Blob <git.objects.blob.Blob>`
* :class:`Tree <git.objects.tree.Tree>`
* :class:`Commit <git.objects.commit.Commit>`
* :class:`TagObject <git.objects.tag.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.Commit_ish` union type.

: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.

: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
Expand All @@ -54,6 +81,20 @@ class Object(LazyMixin):
__slots__ = ("repo", "binsha", "size")

type: Union[Lit_commit_ish, 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.Commit_ish`.
"""

def __init__(self, repo: "Repo", binsha: bytes):
"""Initialize an object by identifying it by its binary sha.
Expand Down Expand Up @@ -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")

Expand Down
5 changes: 4 additions & 1 deletion git/objects/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@


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"
Expand Down
8 changes: 6 additions & 2 deletions git/objects/commit.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,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
Expand Down
6 changes: 5 additions & 1 deletion git/objects/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,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"

Expand Down
11 changes: 7 additions & 4 deletions git/objects/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,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]``.
Expand Down Expand Up @@ -230,8 +233,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.
Expand Down
120 changes: 100 additions & 20 deletions git/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,85 +33,165 @@
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.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."""

Tree_ish = Union["Commit", "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 is 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for mentioning this. It's great that this type includes what GitPython can handle though, as one day it might mean it can be taught to resolve tags as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had avoided changing this since it is not inherently a wrong definition of Tree_ish, but actually the situation you are describing, where it can be broader, is already the case. Furthermore, both Commit_ish and Tree_ish in git.types make a lot more sense if they both include TagObject rather than one of them including it and the other not. I've opened #1878 to add it.

tree-ish (those representing git tag objects that ultimately resolve to a tree or
commit), is not covered as part of this union type.
"""

Commit_ish = Union["Commit", "TagObject", "Blob", "Tree"]
Byron marked this conversation as resolved.
Show resolved Hide resolved
"""Union of the :class:`~git.objects.base.Object`-based types that represent git object
types. This union is often usable where a commit-ish is expected, but is not actually
EliahKagan marked this conversation as resolved.
Show resolved Hide resolved
limited to types representing commit-ish git objects.

See gitglossary(7) on:

* "commit-ish": https://git-scm.com/docs/gitglossary#def_commit-ish
* "object type": https://git-scm.com/docs/gitglossary#def_object_type

:note:
This union comprises **more** classes than those whose instances really represent
commit-ish git objects:

* A :class:`~git.objects.commit.Commit` is of course always commit-ish, and a
:class:`~git.objects.tag.TagObject` is commit-ish if, when peeled (recursively
followed), a :class:`~git.objects.commit.Commit` is obtained.
* However, :class:`~git.objects.blob.Blob` and :class:`~git.objects.tree.Tree` are
also included, and they represent git objects that are never really commit-ish.

This is an inversion of the situation with :class:`Tree_ish`, which is narrower than
all tree-ish objects. It is done for practical reasons including backward
compatibility.
EliahKagan marked this conversation as resolved.
Show resolved Hide resolved
"""

Lit_commit_ish = Literal["commit", "tag", "blob", "tree"]
Byron marked this conversation as resolved.
Show resolved Hide resolved
"""Literal strings identifying concrete :class:`~git.objects.base.Object` subtypes
representing git object types.

See the :class:`Object.type <git.objects.base.Object.type>` attribute.

:note:
See also :class:`Commit_ish`, a union of the the :class:`~git.objects.base.Object`
subtypes associated with these literal strings.

:note:
As noted in :class:`Commit_ish`, this is not limited to types of git objects that
are actually commit-ish.
"""

# Config_levels ---------------------------------------------------------

Lit_config_levels = Literal["system", "global", "user", "repository"]
"""Type of literal strings naming git configuration levels.

# Progress parameter type alias -----------------------------------------
These strings relate to which file a git configuration variable is in.
"""

CallableProgress = Optional[Callable[[int, Union[str, float], Union[str, float, None], str], None]]
ConfigLevels_Tup = Tuple[Literal["system"], Literal["user"], Literal["global"], Literal["repository"]]
"""Static type of a tuple of the four strings representing configuration levels."""

# 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")
# Progress parameter type alias -----------------------------------------

CallableProgress = Optional[Callable[[int, Union[str, float], Union[str, float, None], str], None]]
"""General type of a progress reporter for cloning.

ConfigLevels_Tup = Tuple[Literal["system"], Literal["user"], Literal["global"], Literal["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 <git.repo.base.Repo.clone>`
or :meth:`Repo.clone_from <git.repo.base.Repo.clone_from>`.
"""

# -----------------------------------------------------------------------------------


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
files: int


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
Loading