From 2fa9fb1ac11b53859959ea9bd37c0ae6c17ccdb5 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:50:47 +0100 Subject: update types in types.py --- git/index/base.py | 7 ++++--- git/types.py | 5 ++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/git/index/base.py b/git/index/base.py index 3aa06e38..220bdc85 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -572,7 +572,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): # note: additional deserialization could be saved if write_tree_from_cache # would return sorted tree entries root_tree = Tree(self.repo, binsha, path='') - root_tree._cache = tree_items # type: ignore # should this be encoded to [bytes, int, str]? + root_tree._cache = tree_items return root_tree def _process_diff_args(self, # type: ignore[override] @@ -586,8 +586,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): return args def _to_relative_path(self, path: PathLike) -> PathLike: - """:return: Version of path relative to our git directory or raise ValueError - if it is not within our git direcotory""" + """ + :return: Version of path relative to our git directory or raise ValueError + if it is not within our git direcotory""" if not osp.isabs(path): return path if self.repo.bare: diff --git a/git/types.py b/git/types.py index 9181e040..53f0f1e4 100644 --- a/git/types.py +++ b/git/types.py @@ -7,9 +7,6 @@ import sys from typing import (Callable, Dict, NoReturn, Sequence, Tuple, Union, Any, Iterator, # noqa: F401 NamedTuple, TYPE_CHECKING, TypeVar) # noqa: F401 -if TYPE_CHECKING: - from git.repo import Repo - if sys.version_info[:2] >= (3, 8): from typing import Final, Literal, SupportsIndex, TypedDict, Protocol, runtime_checkable # noqa: F401 else: @@ -28,6 +25,7 @@ elif sys.version_info[:2] >= (3, 9): PathLike = Union[str, 'os.PathLike[str]'] # forward ref as pylance complains unless editing with py3.9+ if TYPE_CHECKING: + from git.repo import Repo from git.objects import Commit, Tree, TagObject, Blob # from git.refs import SymbolicReference @@ -36,6 +34,7 @@ _T = TypeVar('_T') Tree_ish = Union['Commit', 'Tree'] Commit_ish = Union['Commit', 'TagObject', 'Blob', 'Tree'] +Lit_commit_ish = Literal['commit', 'tag', 'blob', 'tree'] # Config_levels --------------------------------------------------------- -- cgit v1.2.1 From cc63210d122ac7a113990e27b48e1bdbd07ceb4c Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:52:00 +0100 Subject: Add types to refs/tag.py --- git/refs/tag.py | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/git/refs/tag.py b/git/refs/tag.py index 4d84239e..aa3b82a2 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -2,6 +2,19 @@ from .reference import Reference __all__ = ["TagReference", "Tag"] +# typing ------------------------------------------------------------------ + +from typing import Any, Union, TYPE_CHECKING +from git.types import Commit_ish, PathLike + +if TYPE_CHECKING: + from git.repo import Repo + from git.objects import Commit + from git.objects import TagObject + + +# ------------------------------------------------------------------------------ + class TagReference(Reference): @@ -22,9 +35,9 @@ class TagReference(Reference): _common_path_default = Reference._common_path_default + "/" + _common_default @property - def commit(self): + def commit(self) -> 'Commit': # type: ignore[override] # LazyMixin has unrelated """:return: Commit object the tag ref points to - + :raise ValueError: if the tag points to a tree or blob""" obj = self.object while obj.type != 'commit': @@ -37,7 +50,7 @@ class TagReference(Reference): return obj @property - def tag(self): + def tag(self) -> Union['TagObject', None]: """ :return: Tag object this tag ref points to or None in case we are a light weight tag""" @@ -48,10 +61,16 @@ class TagReference(Reference): # make object read-only # It should be reasonably hard to adjust an existing tag - object = property(Reference._get_object) + + # object = property(Reference._get_object) + @property + def object(self) -> Commit_ish: # type: ignore[override] + return Reference._get_object(self) @classmethod - def create(cls, repo, path, ref='HEAD', message=None, force=False, **kwargs): + def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str] = 'HEAD', + logmsg: Union[str, None] = None, + force: bool = False, **kwargs: Any) -> 'TagReference': """Create a new tag reference. :param path: @@ -62,12 +81,16 @@ class TagReference(Reference): A reference to the object you want to tag. It can be a commit, tree or blob. - :param message: + :param logmsg: If not None, the message will be used in your tag object. This will also create an additional tag object that allows to obtain that information, i.e.:: tagref.tag.message + :param message: + Synonym for :param logmsg: + Included for backwards compatability. :param logmsg is used in preference if both given. + :param force: If True, to force creation of a tag even though that tag already exists. @@ -75,9 +98,12 @@ class TagReference(Reference): Additional keyword arguments to be passed to git-tag :return: A new TagReference""" - args = (path, ref) - if message: - kwargs['m'] = message + args = (path, reference) + if logmsg: + kwargs['m'] = logmsg + elif 'message' in kwargs and kwargs['message']: + kwargs['m'] = kwargs['message'] + if force: kwargs['f'] = True @@ -85,7 +111,7 @@ class TagReference(Reference): return TagReference(repo, "%s/%s" % (cls._common_path_default, path)) @classmethod - def delete(cls, repo, *tags): + def delete(cls, repo: 'Repo', *tags: 'TagReference') -> None: """Delete the given existing tag or tags""" repo.git.tag("-d", *tags) -- cgit v1.2.1 From 8dd3d0d6308f97249d69d43b6636d13fd3813d44 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:52:27 +0100 Subject: Add types to refs/symbolic.py --- git/refs/symbolic.py | 674 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 674 insertions(+) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index f0bd9316..48b94cfa 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -21,6 +21,680 @@ import os.path as osp from .log import RefLog +# typing ------------------------------------------------------------------ + +from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING # NOQA +from git.types import Commit_ish, PathLike, TBD, Literal, TypeGuard # NOQA + +if TYPE_CHECKING: + from git.repo import Repo + +T_References = TypeVar('T_References', bound='SymbolicReference') + +# ------------------------------------------------------------------------------ + + +__all__ = ["SymbolicReference"] + + +def _git_dir(repo, path): + """ Find the git dir that's appropriate for the path""" + name = "%s" % (path,) + if name in ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'index', 'logs']: + return repo.git_dir + return repo.common_dir + + +class SymbolicReference(object): + + """Represents a special case of a reference such that this reference is symbolic. + It does not point to a specific commit, but to another Head, which itself + specifies a commit. + + A typical example for a symbolic reference is HEAD.""" + __slots__ = ("repo", "path") + _resolve_ref_on_create = False + _points_to_commits_only = True + _common_path_default = "" + _remote_common_path_default = "refs/remotes" + _id_attribute_ = "name" + + def __init__(self, repo: 'Repo', path: PathLike, check_path: bool = False): + self.repo = repo + self.path = str(path) + + def __str__(self) -> str: + return self.path + + def __repr__(self): + return '' % (self.__class__.__name__, self.path) + + def __eq__(self, other): + if hasattr(other, 'path'): + return self.path == other.path + return False + + def __ne__(self, other): + return not (self == other) + + def __hash__(self): + return hash(self.path) + + @property + def name(self): + """ + :return: + In case of symbolic references, the shortest assumable name + is the path itself.""" + return self.path + + @property + def abspath(self): + return join_path_native(_git_dir(self.repo, self.path), self.path) + + @classmethod + def _get_packed_refs_path(cls, repo): + return osp.join(repo.common_dir, 'packed-refs') + + @classmethod + def _iter_packed_refs(cls, repo): + """Returns an iterator yielding pairs of sha1/path pairs (as bytes) for the corresponding refs. + :note: The packed refs file will be kept open as long as we iterate""" + try: + with open(cls._get_packed_refs_path(repo), 'rt', encoding='UTF-8') as fp: + for line in fp: + line = line.strip() + if not line: + continue + if line.startswith('#'): + # "# pack-refs with: peeled fully-peeled sorted" + # the git source code shows "peeled", + # "fully-peeled" and "sorted" as the keywords + # that can go on this line, as per comments in git file + # refs/packed-backend.c + # I looked at master on 2017-10-11, + # commit 111ef79afe, after tag v2.15.0-rc1 + # from repo https://github.com/git/git.git + if line.startswith('# pack-refs with:') and 'peeled' not in line: + raise TypeError("PackingType of packed-Refs not understood: %r" % line) + # END abort if we do not understand the packing scheme + continue + # END parse comment + + # skip dereferenced tag object entries - previous line was actual + # tag reference for it + if line[0] == '^': + continue + + yield tuple(line.split(' ', 1)) + # END for each line + except OSError: + return None + # END no packed-refs file handling + # NOTE: Had try-finally block around here to close the fp, + # but some python version wouldn't allow yields within that. + # I believe files are closing themselves on destruction, so it is + # alright. + + @classmethod + def dereference_recursive(cls, repo, ref_path): + """ + :return: hexsha stored in the reference at the given ref_path, recursively dereferencing all + intermediate references as required + :param repo: the repository containing the reference at ref_path""" + while True: + hexsha, ref_path = cls._get_ref_info(repo, ref_path) + if hexsha is not None: + return hexsha + # END recursive dereferencing + + @classmethod + def _get_ref_info_helper(cls, repo, ref_path): + """Return: (str(sha), str(target_ref_path)) if available, the sha the file at + rela_path points to, or None. target_ref_path is the reference we + point to, or None""" + tokens = None + repodir = _git_dir(repo, ref_path) + try: + with open(osp.join(repodir, ref_path), 'rt', encoding='UTF-8') as fp: + value = fp.read().rstrip() + # Don't only split on spaces, but on whitespace, which allows to parse lines like + # 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo + tokens = value.split() + assert(len(tokens) != 0) + except OSError: + # Probably we are just packed, find our entry in the packed refs file + # NOTE: We are not a symbolic ref if we are in a packed file, as these + # are excluded explicitly + for sha, path in cls._iter_packed_refs(repo): + if path != ref_path: + continue + # sha will be used + tokens = sha, path + break + # END for each packed ref + # END handle packed refs + if tokens is None: + raise ValueError("Reference at %r does not exist" % ref_path) + + # is it a reference ? + if tokens[0] == 'ref:': + return (None, tokens[1]) + + # its a commit + if repo.re_hexsha_only.match(tokens[0]): + return (tokens[0], None) + + raise ValueError("Failed to parse reference information from %r" % ref_path) + + @classmethod + def _get_ref_info(cls, repo, ref_path): + """Return: (str(sha), str(target_ref_path)) if available, the sha the file at + rela_path points to, or None. target_ref_path is the reference we + point to, or None""" + return cls._get_ref_info_helper(repo, ref_path) + + def _get_object(self): + """ + :return: + The object our ref currently refers to. Refs can be cached, they will + always point to the actual object as it gets re-created on each query""" + # have to be dynamic here as we may be a tag which can point to anything + # Our path will be resolved to the hexsha which will be used accordingly + return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path))) + + def _get_commit(self): + """ + :return: + Commit object we point to, works for detached and non-detached + SymbolicReferences. The symbolic reference will be dereferenced recursively.""" + obj = self._get_object() + if obj.type == 'tag': + obj = obj.object + # END dereference tag + + if obj.type != Commit.type: + raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj) + # END handle type + return obj + + def set_commit(self, commit: Union[Commit, 'SymbolicReference', str], logmsg=None): + """As set_object, but restricts the type of object to be a Commit + + :raise ValueError: If commit is not a Commit object or doesn't point to + a commit + :return: self""" + # check the type - assume the best if it is a base-string + invalid_type = False + if isinstance(commit, Object): + invalid_type = commit.type != Commit.type + elif isinstance(commit, SymbolicReference): + invalid_type = commit.object.type != Commit.type + else: + try: + invalid_type = self.repo.rev_parse(commit).type != Commit.type + except (BadObject, BadName) as e: + raise ValueError("Invalid object: %s" % commit) from e + # END handle exception + # END verify type + + if invalid_type: + raise ValueError("Need commit, got %r" % commit) + # END handle raise + + # we leave strings to the rev-parse method below + self.set_object(commit, logmsg) + + return self + + def set_object(self, object, logmsg=None): # @ReservedAssignment + """Set the object we point to, possibly dereference our symbolic reference first. + If the reference does not exist, it will be created + + :param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences + will be dereferenced beforehand to obtain the object they point to + :param logmsg: If not None, the message will be used in the reflog entry to be + written. Otherwise the reflog is not altered + :note: plain SymbolicReferences may not actually point to objects by convention + :return: self""" + if isinstance(object, SymbolicReference): + object = object.object # @ReservedAssignment + # END resolve references + + is_detached = True + try: + is_detached = self.is_detached + except ValueError: + pass + # END handle non-existing ones + + if is_detached: + return self.set_reference(object, logmsg) + + # 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") + object = property(_get_object, set_object, doc="Return the object our ref currently refers to") + + def _get_reference(self): + """:return: Reference Object we point to + :raise TypeError: If this symbolic reference is detached, hence it doesn't point + to a reference, but to a commit""" + sha, target_ref_path = self._get_ref_info(self.repo, self.path) + if target_ref_path is None: + raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha)) + return self.from_path(self.repo, target_ref_path) + + def set_reference(self, ref, logmsg=None): + """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference. + Otherwise an Object, given as Object instance or refspec, is assumed and if valid, + will be set which effectively detaches the refererence if it was a purely + symbolic one. + + :param ref: SymbolicReference instance, Object instance or refspec string + Only if the ref is a SymbolicRef 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. + Otherwise, a reflog entry is not written for the changed reference. + The previous commit of the entry will be the commit we point to now. + + See also: log_append() + + :return: self + :note: This symbolic reference will not be dereferenced. For that, see + ``set_object(...)``""" + write_value = None + obj = None + if isinstance(ref, SymbolicReference): + write_value = "ref: %s" % ref.path + elif isinstance(ref, Object): + obj = ref + write_value = ref.hexsha + elif isinstance(ref, str): + try: + obj = self.repo.rev_parse(ref + "^{}") # optionally deref tags + write_value = obj.hexsha + except (BadObject, BadName) as e: + raise ValueError("Could not extract object from %s" % ref) from e + # END end try string + else: + raise ValueError("Unrecognized Value: %r" % ref) + # END try commit attribute + + # typecheck + if obj is not None and self._points_to_commits_only and obj.type != Commit.type: + raise TypeError("Require commit, got %r" % obj) + # END verify type + + oldbinsha = None + if logmsg is not None: + try: + oldbinsha = self.commit.binsha + except ValueError: + oldbinsha = Commit.NULL_BIN_SHA + # END handle non-existing + # END retrieve old hexsha + + fpath = self.abspath + assure_directory_exists(fpath, is_file=True) + + lfd = LockedFD(fpath) + fd = lfd.open(write=True, stream=True) + ok = True + try: + fd.write(write_value.encode('ascii') + b'\n') + lfd.commit() + ok = True + finally: + if not ok: + lfd.rollback() + # Adjust the reflog + if logmsg is not None: + self.log_append(oldbinsha, logmsg) + + return self + + # aliased reference + reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") + ref: Union[Commit_ish] = reference # type: ignore # Union[str, Commit_ish, SymbolicReference] + + def is_valid(self): + """ + :return: + True if the reference is valid, hence it can be read and points to + a valid object or reference.""" + try: + self.object + except (OSError, ValueError): + return False + else: + return True + + @property + def is_detached(self): + """ + :return: + True if we are a detached reference, hence we point to a specific commit + instead to another reference""" + try: + self.ref + return False + except TypeError: + return True + + def log(self): + """ + :return: RefLog for this reference. Its last entry reflects the latest change + applied to this reference + + .. note:: As the log is parsed every time, its recommended to cache it for use + instead of calling this method repeatedly. It should be considered read-only.""" + return RefLog.from_file(RefLog.path(self)) + + def log_append(self, oldbinsha, message, newbinsha=None): + """Append a logentry to the logfile of this ref + + :param oldbinsha: binary sha this ref used to point to + :param message: A message describing the change + :param newbinsha: The sha the ref points to now. If None, our current commit sha + will be used + :return: added RefLogEntry instance""" + # NOTE: we use the committer of the currently active commit - this should be + # correct to allow overriding the committer on a per-commit level. + # See https://github.com/gitpython-developers/GitPython/pull/146 + try: + committer_or_reader = self.commit.committer + except ValueError: + committer_or_reader = self.repo.config_reader() + # end handle newly cloned repositories + return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, + (newbinsha is None and self.commit.binsha) or newbinsha, + message) + + def log_entry(self, index): + """:return: RefLogEntry at the given index + :param index: python list compatible positive or negative index + + .. note:: This method must read part of the reflog during execution, hence + it should be used sparringly, or only if you need just one index. + In that case, it will be faster than the ``log()`` method""" + return RefLog.entry_at(RefLog.path(self), index) + + @classmethod + def to_full_path(cls, path) -> PathLike: + """ + :return: string with a full repository-relative path which can be used to initialize + a Reference instance, for instance by using ``Reference.from_path``""" + if isinstance(path, SymbolicReference): + path = path.path + full_ref_path = path + if not cls._common_path_default: + return full_ref_path + if not path.startswith(cls._common_path_default + "/"): + full_ref_path = '%s/%s' % (cls._common_path_default, path) + return full_ref_path + + @classmethod + def delete(cls, repo, path): + """Delete the reference at the given path + + :param repo: + Repository to delete the reference from + + :param path: + Short or full path pointing to the reference, i.e. refs/myreference + or just "myreference", hence 'refs/' is implied. + Alternatively the symbolic reference to be deleted""" + full_ref_path = cls.to_full_path(path) + abs_path = osp.join(repo.common_dir, full_ref_path) + if osp.exists(abs_path): + os.remove(abs_path) + else: + # check packed refs + pack_file_path = cls._get_packed_refs_path(repo) + try: + with open(pack_file_path, 'rb') as reader: + new_lines = [] + made_change = False + dropped_last_line = False + for line in reader: + line = line.decode(defenc) + _, _, line_ref = line.partition(' ') + line_ref = line_ref.strip() + # keep line if it is a comment or if the ref to delete is not + # in the line + # If we deleted the last line and this one is a tag-reference object, + # we drop it as well + if (line.startswith('#') or full_ref_path != line_ref) and \ + (not dropped_last_line or dropped_last_line and not line.startswith('^')): + new_lines.append(line) + dropped_last_line = False + continue + # END skip comments and lines without our path + + # drop this line + made_change = True + dropped_last_line = True + + # write the new lines + if made_change: + # write-binary is required, otherwise windows will + # open the file in text mode and change LF to CRLF ! + with open(pack_file_path, 'wb') as fd: + fd.writelines(line.encode(defenc) for line in new_lines) + + except OSError: + pass # it didn't exist at all + + # delete the reflog + reflog_path = RefLog.path(cls(repo, full_ref_path)) + if osp.isfile(reflog_path): + os.remove(reflog_path) + # END remove reflog + + @classmethod + def _create(cls, repo, path, resolve, reference, force, logmsg=None): + """internal method used to create a new symbolic reference. + If resolve is False, the reference will be taken as is, creating + a proper symbolic reference. Otherwise it will be resolved to the + corresponding object and a detached symbolic reference will be created + instead""" + git_dir = _git_dir(repo, path) + full_ref_path = cls.to_full_path(path) + abs_ref_path = osp.join(git_dir, full_ref_path) + + # figure out target data + target = reference + if resolve: + target = repo.rev_parse(str(reference)) + + if not force and osp.isfile(abs_ref_path): + target_data = str(target) + if isinstance(target, SymbolicReference): + target_data = target.path + if not resolve: + target_data = "ref: " + target_data + with open(abs_ref_path, 'rb') as fd: + existing_data = fd.read().decode(defenc).strip() + if existing_data != target_data: + raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % + (full_ref_path, existing_data, target_data)) + # END no force handling + + ref = cls(repo, full_ref_path) + ref.set_reference(target, logmsg) + return ref + + @classmethod + def create(cls, repo: 'Repo', path: PathLike, reference: Union[Commit_ish, str] = 'HEAD', + logmsg: Union[str, None] = None, force: bool = False, **kwargs: Any): + """Create a new symbolic reference, hence a reference pointing , to another reference. + + :param repo: + Repository to create the reference in + + :param path: + full path at which the new symbolic reference is supposed to be + created at, i.e. "NEW_HEAD" or "symrefs/my_new_symref" + + :param reference: + The reference to which the new symbolic reference should point to. + If it is a commit'ish, the symbolic ref will be detached. + + :param force: + if True, force creation even if a symbolic reference with that name already exists. + Raise OSError otherwise + + :param logmsg: + If not None, the message to append to the reflog. Otherwise no reflog + entry is written. + + :return: Newly created symbolic Reference + + :raise OSError: + If a (Symbolic)Reference with the same name but different contents + already exists. + + :note: This does not alter the current HEAD, index or Working Tree""" + return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg) + + def rename(self, new_path, force=False): + """Rename self to a new path + + :param new_path: + Either a simple name or a full path, i.e. new_name or features/new_name. + The prefix refs/ is implied for references and will be set as needed. + In case this is a symbolic ref, there is no implied prefix + + :param force: + If True, the rename will succeed even if a head with the target name + already exists. It will be overwritten in that case + + :return: self + :raise OSError: In case a file at path but a different contents already exists """ + new_path = self.to_full_path(new_path) + if self.path == new_path: + return self + + new_abs_path = osp.join(_git_dir(self.repo, new_path), new_path) + cur_abs_path = osp.join(_git_dir(self.repo, self.path), self.path) + if osp.isfile(new_abs_path): + if not force: + # if they point to the same file, its not an error + with open(new_abs_path, 'rb') as fd1: + f1 = fd1.read().strip() + with open(cur_abs_path, 'rb') as fd2: + f2 = fd2.read().strip() + if f1 != f2: + raise OSError("File at path %r already exists" % new_abs_path) + # else: we could remove ourselves and use the otherone, but + # but clarity we just continue as usual + # END not force handling + os.remove(new_abs_path) + # END handle existing target file + + dname = osp.dirname(new_abs_path) + if not osp.isdir(dname): + os.makedirs(dname) + # END create directory + + os.rename(cur_abs_path, new_abs_path) + self.path = new_path + + return self + + @classmethod + def _iter_items(cls: Type[T_References], repo: 'Repo', common_path: Union[PathLike, None] = None + ) -> Iterator[T_References]: + if common_path is None: + common_path = cls._common_path_default + rela_paths = set() + + # walk loose refs + # Currently we do not follow links + for root, dirs, files in os.walk(join_path_native(repo.common_dir, common_path)): + if 'refs' not in root.split(os.sep): # skip non-refs subfolders + refs_id = [d for d in dirs if d == 'refs'] + if refs_id: + dirs[0:] = ['refs'] + # END prune non-refs folders + + for f in files: + if f == 'packed-refs': + continue + abs_path = to_native_path_linux(join_path(root, f)) + rela_paths.add(abs_path.replace(to_native_path_linux(repo.common_dir) + '/', "")) + # END for each file in root directory + # END for each directory to walk + + # read packed refs + for _sha, rela_path in cls._iter_packed_refs(repo): + if rela_path.startswith(common_path): + rela_paths.add(rela_path) + # END relative path matches common path + # END packed refs reading + + # return paths in sorted order + for path in sorted(rela_paths): + try: + yield cls.from_path(repo, path) + except ValueError: + continue + # END for each sorted relative refpath + + @classmethod + # type: ignore[override] + def iter_items(cls, repo: 'Repo', common_path: Union[PathLike, None] = None, *args, **kwargs): + """Find all refs in the repository + + :param repo: is the Repo + + :param common_path: + Optional keyword argument to the path which is to be shared by all + returned Ref objects. + Defaults to class specific portion if None assuring that only + refs suitable for the actual class are returned. + + :return: + git.SymbolicReference[], each of them is guaranteed to be a symbolic + ref which is not detached and pointing to a valid ref + + List is lexicographically sorted + The returned objects represent actual subclasses, such as Head or TagReference""" + return (r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached) + + @classmethod + def from_path(cls, repo, path): + """ + :param path: full .git-directory-relative path name to the Reference to instantiate + :note: use to_full_path() if you only have a partial path of a known Reference Type + :return: + Instance of type Reference, Head, or Tag + depending on the given path""" + if not path: + raise ValueError("Cannot create Reference from %r" % path) + + # Names like HEAD are inserted after the refs module is imported - we have an import dependency + # cycle and don't want to import these names in-function + from . import HEAD, Head, RemoteReference, TagReference, Reference + for ref_type in (HEAD, Head, RemoteReference, TagReference, Reference, SymbolicReference): + try: + instance = ref_type(repo, path) + if instance.__class__ == SymbolicReference and instance.is_detached: + raise ValueError("SymbolRef was detached, we drop it") + return instance + except ValueError: + pass + # END exception handling + # END for each type to try + raise ValueError("Could not find reference type suitable to handle path %r" % path) + + def is_remote(self): + """:return: True if this symbolic reference points to a remote branch""" + return self.path.startswith(self._remote_common_path_default + "/") + + __all__ = ["SymbolicReference"] -- cgit v1.2.1 From 4b9ca921e8722f4e7359bea174b2c823059c5542 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:53:14 +0100 Subject: Add types to refs/remote.py --- git/refs/remote.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/git/refs/remote.py b/git/refs/remote.py index 0164e110..8a680a4a 100644 --- a/git/refs/remote.py +++ b/git/refs/remote.py @@ -2,13 +2,23 @@ import os from git.util import join_path -import os.path as osp - from .head import Head __all__ = ["RemoteReference"] +# typing ------------------------------------------------------------------ + +from typing import Any, NoReturn, Union, TYPE_CHECKING +from git.types import PathLike + + +if TYPE_CHECKING: + from git.repo import Repo + from git import Remote + +# ------------------------------------------------------------------------------ + class RemoteReference(Head): @@ -16,16 +26,19 @@ class RemoteReference(Head): _common_path_default = Head._remote_common_path_default @classmethod - def iter_items(cls, repo, common_path=None, remote=None): + def iter_items(cls, repo: 'Repo', common_path: Union[PathLike, None] = None, + remote: Union['Remote', None] = None, *args: Any, **kwargs: Any + ) -> 'RemoteReference': """Iterate remote references, and if given, constrain them to the given remote""" common_path = common_path or cls._common_path_default if remote is not None: common_path = join_path(common_path, str(remote)) # END handle remote constraint + # super is Reference return super(RemoteReference, cls).iter_items(repo, common_path) - @classmethod - def delete(cls, repo, *refs, **kwargs): + @ classmethod + def delete(cls, repo: 'Repo', *refs: 'RemoteReference', **kwargs: Any) -> None: """Delete the given remote references :note: @@ -37,16 +50,16 @@ class RemoteReference(Head): # and delete remainders manually for ref in refs: try: - os.remove(osp.join(repo.common_dir, ref.path)) + os.remove(os.path.join(repo.common_dir, ref.path)) except OSError: pass try: - os.remove(osp.join(repo.git_dir, ref.path)) + os.remove(os.path.join(repo.git_dir, ref.path)) except OSError: pass # END for each ref - @classmethod - def create(cls, *args, **kwargs): + @ classmethod + def create(cls, *args: Any, **kwargs: Any) -> NoReturn: """Used to disable this method""" raise TypeError("Cannot explicitly create remote references") -- cgit v1.2.1 From 1dd4596294d2302cc091a337ff6f89761795efe7 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:53:28 +0100 Subject: Add types to refs/reference.py --- git/refs/reference.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/git/refs/reference.py b/git/refs/reference.py index 8a9b0487..f584bb54 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -2,7 +2,18 @@ from git.util import ( LazyMixin, IterableObj, ) -from .symbolic import SymbolicReference +from .symbolic import SymbolicReference, T_References + + +# typing ------------------------------------------------------------------ + +from typing import Any, Callable, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING # NOQA +from git.types import Commit_ish, PathLike, TBD, Literal, TypeGuard, _T # NOQA + +if TYPE_CHECKING: + from git.repo import Repo + +# ------------------------------------------------------------------------------ __all__ = ["Reference"] @@ -10,10 +21,10 @@ __all__ = ["Reference"] #{ Utilities -def require_remote_ref_path(func): +def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]: """A decorator raising a TypeError if we are not a valid remote, based on the path""" - def wrapper(self, *args): + def wrapper(self: T_References, *args: Any) -> _T: if not self.is_remote(): raise ValueError("ref path does not point to a remote reference: %s" % self.path) return func(self, *args) @@ -32,7 +43,7 @@ class Reference(SymbolicReference, LazyMixin, IterableObj): _resolve_ref_on_create = True _common_path_default = "refs" - def __init__(self, repo, path, check_path=True): + def __init__(self, repo: 'Repo', path: PathLike, check_path: bool = True) -> None: """Initialize this instance :param repo: Our parent repository @@ -41,16 +52,17 @@ class Reference(SymbolicReference, LazyMixin, IterableObj): refs/heads/master :param check_path: if False, you can provide any path. Otherwise the path must start with the default path prefix of this type.""" - if check_path and not path.startswith(self._common_path_default + '/'): - raise ValueError("Cannot instantiate %r from path %s" % (self.__class__.__name__, path)) + if check_path and not str(path).startswith(self._common_path_default + '/'): + raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {path}") + self.path: str # SymbolicReference converts to string atm super(Reference, self).__init__(repo, path) - def __str__(self): + def __str__(self) -> str: return self.name #{ Interface - def set_object(self, object, logmsg=None): # @ReservedAssignment + def set_object(self, object: Commit_ish, logmsg: Union[str, None] = None) -> 'Reference': # @ReservedAssignment """Special version which checks if the head-log needs an update as well :return: self""" oldbinsha = None @@ -84,7 +96,7 @@ class Reference(SymbolicReference, LazyMixin, IterableObj): # NOTE: Don't have to overwrite properties as the will only work without a the log @property - def name(self): + def name(self) -> str: """:return: (shortest) Name of this reference - it may contain path components""" # first two path tokens are can be removed as they are # refs/heads or refs/tags or refs/remotes @@ -94,7 +106,8 @@ class Reference(SymbolicReference, LazyMixin, IterableObj): return '/'.join(tokens[2:]) @classmethod - def iter_items(cls, repo, common_path=None): + def iter_items(cls: Type[T_References], repo: 'Repo', common_path: Union[PathLike, None] = None, + *args: Any, **kwargs: Any) -> Iterator[T_References]: """Equivalent to SymbolicReference.iter_items, but will return non-detached references as well.""" return cls._iter_items(repo, common_path) @@ -105,7 +118,7 @@ class Reference(SymbolicReference, LazyMixin, IterableObj): @property # type: ignore ## mypy cannot deal with properties with an extra decorator (2021-04-21) @require_remote_ref_path - def remote_name(self): + def remote_name(self) -> str: """ :return: Name of the remote we are a reference of, such as 'origin' for a reference @@ -116,7 +129,7 @@ class Reference(SymbolicReference, LazyMixin, IterableObj): @property # type: ignore ## mypy cannot deal with properties with an extra decorator (2021-04-21) @require_remote_ref_path - def remote_head(self): + def remote_head(self) -> str: """:return: Name of the remote head itself, i.e. master. :note: The returned name is usually not qualified enough to uniquely identify a branch""" -- cgit v1.2.1 From ae13c6dfd6124604c30de1841dfd669195568608 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:53:47 +0100 Subject: Add types to refs/log.py --- git/refs/log.py | 140 ++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 86 insertions(+), 54 deletions(-) diff --git a/git/refs/log.py b/git/refs/log.py index f850ba24..643b4114 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -1,5 +1,7 @@ + +from mmap import mmap import re -import time +import time as _time from git.compat import defenc from git.objects.util import ( @@ -20,20 +22,33 @@ from git.util import ( import os.path as osp +# typing ------------------------------------------------------------------ + +from typing import Iterator, List, Tuple, Union, TYPE_CHECKING + +from git.types import PathLike + +if TYPE_CHECKING: + from git.refs import SymbolicReference + from io import BytesIO + from git.config import GitConfigParser, SectionConstraint # NOQA + +# ------------------------------------------------------------------------------ + __all__ = ["RefLog", "RefLogEntry"] -class RefLogEntry(tuple): +class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]): """Named tuple allowing easy access to the revlog data fields""" _re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$') __slots__ = () - def __repr__(self): + def __repr__(self) -> str: """Representation of ourselves in git reflog format""" return self.format() - def format(self): + def format(self) -> str: """:return: a string suitable to be placed in a reflog file""" act = self.actor time = self.time @@ -46,22 +61,22 @@ class RefLogEntry(tuple): self.message) @property - def oldhexsha(self): + def oldhexsha(self) -> str: """The hexsha to the commit the ref pointed to before the change""" return self[0] @property - def newhexsha(self): + def newhexsha(self) -> str: """The hexsha to the commit the ref now points to, after the change""" return self[1] @property - def actor(self): + def actor(self) -> Actor: """Actor instance, providing access""" return self[2] @property - def time(self): + def time(self) -> Tuple[int, int]: """time as tuple: * [0] = int(time) @@ -69,12 +84,13 @@ class RefLogEntry(tuple): return self[3] @property - def message(self): + def message(self) -> str: """Message describing the operation that acted on the reference""" return self[4] @classmethod - def new(cls, oldhexsha, newhexsha, actor, time, tz_offset, message): # skipcq: PYL-W0621 + def new(cls, oldhexsha: str, newhexsha: str, actor: Actor, time: int, tz_offset: int, message: str + ) -> 'RefLogEntry': # skipcq: PYL-W0621 """:return: New instance of a RefLogEntry""" if not isinstance(actor, Actor): raise ValueError("Need actor instance, got %s" % actor) @@ -111,14 +127,15 @@ class RefLogEntry(tuple): # END handle missing end brace actor = Actor._from_string(info[82:email_end + 1]) - time, tz_offset = parse_date(info[email_end + 2:]) # skipcq: PYL-W0621 + time, tz_offset = parse_date( + info[email_end + 2:]) # skipcq: PYL-W0621 return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg)) -class RefLog(list, Serializable): +class RefLog(List[RefLogEntry], Serializable): - """A reflog contains reflog entries, each of which defines a certain state + """A reflog contains RefLogEntrys, each of which defines a certain state of the head in question. Custom query methods allow to retrieve log entries by date or by other criteria. @@ -127,11 +144,11 @@ class RefLog(list, Serializable): __slots__ = ('_path', ) - def __new__(cls, filepath=None): + def __new__(cls, filepath: Union[PathLike, None] = None) -> 'RefLog': inst = super(RefLog, cls).__new__(cls) return inst - def __init__(self, filepath=None): + def __init__(self, filepath: Union[PathLike, None] = None): """Initialize this instance with an optional filepath, from which we will initialize our data. The path is also used to write changes back using the write() method""" @@ -142,7 +159,8 @@ class RefLog(list, Serializable): def _read_from_file(self): try: - fmap = file_contents_ro_filepath(self._path, stream=True, allow_mmap=True) + fmap = file_contents_ro_filepath( + self._path, stream=True, allow_mmap=True) except OSError: # it is possible and allowed that the file doesn't exist ! return @@ -154,10 +172,10 @@ class RefLog(list, Serializable): fmap.close() # END handle closing of handle - #{ Interface + # { Interface @classmethod - def from_file(cls, filepath): + def from_file(cls, filepath: PathLike) -> 'RefLog': """ :return: a new RefLog instance containing all entries from the reflog at the given filepath @@ -166,7 +184,7 @@ class RefLog(list, Serializable): return cls(filepath) @classmethod - def path(cls, ref): + def path(cls, ref: 'SymbolicReference') -> str: """ :return: string to absolute path at which the reflog of the given ref instance would be found. The path is not guaranteed to point to a valid @@ -175,28 +193,34 @@ class RefLog(list, Serializable): return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path)) @classmethod - def iter_entries(cls, stream): + def iter_entries(cls, stream: Union[str, 'BytesIO', mmap]) -> Iterator[RefLogEntry]: """ :return: Iterator yielding RefLogEntry instances, one for each line read sfrom the given stream. :param stream: file-like object containing the revlog in its native format - or basestring instance pointing to a file to read""" + or string instance pointing to a file to read""" new_entry = RefLogEntry.from_line if isinstance(stream, str): - stream = file_contents_ro_filepath(stream) + # default args return mmap on py>3 + _stream = file_contents_ro_filepath(stream) + assert isinstance(_stream, mmap) + else: + _stream = stream # END handle stream type while True: - line = stream.readline() + line = _stream.readline() if not line: return yield new_entry(line.strip()) # END endless loop - stream.close() @classmethod - def entry_at(cls, filepath, index): - """:return: RefLogEntry at the given index + def entry_at(cls, filepath: PathLike, index: int) -> 'RefLogEntry': + """ + :return: RefLogEntry at the given index + :param filepath: full path to the index file from which to read the entry + :param index: python list compatible index, i.e. it may be negative to specify an entry counted from the end of the list @@ -210,21 +234,19 @@ class RefLog(list, Serializable): if index < 0: return RefLogEntry.from_line(fp.readlines()[index].strip()) # read until index is reached + for i in range(index + 1): line = fp.readline() if not line: - break + raise IndexError( + f"Index file ended at line {i+1}, before given index was reached") # END abort on eof # END handle runup - if i != index or not line: # skipcq:PYL-W0631 - raise IndexError - # END handle exception - return RefLogEntry.from_line(line.strip()) # END handle index - def to_file(self, filepath): + def to_file(self, filepath: PathLike) -> None: """Write the contents of the reflog instance to a file at the given filepath. :param filepath: path to file, parent directories are assumed to exist""" lfd = LockedFD(filepath) @@ -241,65 +263,75 @@ class RefLog(list, Serializable): # END handle change @classmethod - def append_entry(cls, config_reader, filepath, oldbinsha, newbinsha, message): + def append_entry(cls, config_reader: Union[Actor, 'GitConfigParser', 'SectionConstraint', None], + filepath: PathLike, oldbinsha: bytes, newbinsha: bytes, message: str, + write: bool = True) -> 'RefLogEntry': """Append a new log entry to the revlog at filepath. :param config_reader: configuration reader of the repository - used to obtain - user information. May also be an Actor instance identifying the committer directly. - May also be None + user information. May also be an Actor instance identifying the committer directly or None. :param filepath: full path to the log file :param oldbinsha: binary sha of the previous commit :param newbinsha: binary sha of the current commit :param message: message describing the change to the reference :param write: If True, the changes will be written right away. Otherwise the change will not be written + :return: RefLogEntry objects which was appended to the log + :note: As we are append-only, concurrent access is not a problem as we do not interfere with readers.""" + if len(oldbinsha) != 20 or len(newbinsha) != 20: raise ValueError("Shas need to be given in binary format") # END handle sha type assure_directory_exists(filepath, is_file=True) first_line = message.split('\n')[0] - committer = isinstance(config_reader, Actor) and config_reader or Actor.committer(config_reader) + if isinstance(config_reader, Actor): + committer = config_reader # mypy thinks this is Actor | Gitconfigparser, but why? + else: + committer = Actor.committer(config_reader) entry = RefLogEntry(( bin_to_hex(oldbinsha).decode('ascii'), bin_to_hex(newbinsha).decode('ascii'), - committer, (int(time.time()), time.altzone), first_line + committer, (int(_time.time()), _time.altzone), first_line )) - lf = LockFile(filepath) - lf._obtain_lock_or_raise() - fd = open(filepath, 'ab') - try: - fd.write(entry.format().encode(defenc)) - finally: - fd.close() - lf._release_lock() - # END handle write operation - + if write: + lf = LockFile(filepath) + lf._obtain_lock_or_raise() + fd = open(filepath, 'ab') + try: + fd.write(entry.format().encode(defenc)) + finally: + fd.close() + lf._release_lock() + # END handle write operation return entry - def write(self): + def write(self) -> 'RefLog': """Write this instance's data to the file we are originating from :return: self""" if self._path is None: - raise ValueError("Instance was not initialized with a path, use to_file(...) instead") + raise ValueError( + "Instance was not initialized with a path, use to_file(...) instead") # END assert path self.to_file(self._path) return self - #} END interface + # } END interface - #{ Serializable Interface - def _serialize(self, stream): + # { Serializable Interface + def _serialize(self, stream: 'BytesIO') -> 'RefLog': write = stream.write # write all entries for e in self: write(e.format().encode(defenc)) # END for each entry + return self - def _deserialize(self, stream): + def _deserialize(self, stream: 'BytesIO') -> 'RefLog': self.extend(self.iter_entries(stream)) - #} END serializable interface + # } END serializable interface + return self -- cgit v1.2.1 From 8fc25c63d9282ddc6b3162c2d92679a89e934ec5 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:54:05 +0100 Subject: Add types to refs/head.py --- git/refs/head.py | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/git/refs/head.py b/git/refs/head.py index 97c8e6a1..338efce9 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -5,12 +5,18 @@ from git.exc import GitCommandError from .symbolic import SymbolicReference from .reference import Reference -from typing import Union, TYPE_CHECKING +# typinng --------------------------------------------------- -from git.types import Commit_ish +from typing import Any, Sequence, Union, TYPE_CHECKING + +from git.types import PathLike, Commit_ish if TYPE_CHECKING: from git.repo import Repo + from git.objects import Commit + from git.refs import RemoteReference + +# ------------------------------------------------------------------- __all__ = ["HEAD", "Head"] @@ -29,20 +35,21 @@ class HEAD(SymbolicReference): _ORIG_HEAD_NAME = 'ORIG_HEAD' __slots__ = () - def __init__(self, repo: 'Repo', path=_HEAD_NAME): + def __init__(self, repo: 'Repo', path: PathLike = _HEAD_NAME): if path != self._HEAD_NAME: raise ValueError("HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path)) super(HEAD, self).__init__(repo, path) - self.commit: 'Commit_ish' + self.commit: 'Commit' - def orig_head(self) -> 'SymbolicReference': + def orig_head(self) -> SymbolicReference: """ :return: SymbolicReference pointing at the ORIG_HEAD, which is maintained to contain the previous value of HEAD""" return SymbolicReference(self.repo, self._ORIG_HEAD_NAME) - def reset(self, commit: Union[Commit_ish, SymbolicReference, str] = 'HEAD', index=True, working_tree=False, - paths=None, **kwargs): + def reset(self, commit: Union[Commit_ish, SymbolicReference, str] = 'HEAD', + index: bool = True, working_tree: bool = False, + paths: Union[PathLike, Sequence[PathLike], None] = None, **kwargs: Any) -> 'HEAD': """Reset our HEAD to the given commit optionally synchronizing the index and working tree. The reference we refer to will be set to commit as well. @@ -122,7 +129,7 @@ class Head(Reference): k_config_remote_ref = "merge" # branch to merge from remote @classmethod - def delete(cls, repo, *heads, **kwargs): + def delete(cls, repo: 'Repo', *heads: 'Head', **kwargs: Any): """Delete the given heads :param force: @@ -135,7 +142,7 @@ class Head(Reference): flag = "-D" repo.git.branch(flag, *heads) - def set_tracking_branch(self, remote_reference): + def set_tracking_branch(self, remote_reference: 'RemoteReference') -> 'Head': """ Configure this branch to track the given remote reference. This will alter this branch's configuration accordingly. @@ -160,7 +167,7 @@ class Head(Reference): return self - def tracking_branch(self): + def tracking_branch(self) -> Union['RemoteReference', None]: """ :return: The remote_reference we are tracking, or None if we are not a tracking branch""" @@ -175,7 +182,7 @@ class Head(Reference): # we are not a tracking branch return None - def rename(self, new_path, force=False): + def rename(self, new_path: PathLike, force: bool = False) -> 'Head': """Rename self to a new path :param new_path: @@ -196,7 +203,7 @@ class Head(Reference): self.path = "%s/%s" % (self._common_path_default, new_path) return self - def checkout(self, force=False, **kwargs): + def checkout(self, force: bool = False, **kwargs: Any): """Checkout this head by setting the HEAD to this reference, by updating the index to reflect the tree we point to and by updating the working tree to reflect the latest index. @@ -231,7 +238,7 @@ class Head(Reference): return self.repo.active_branch #{ Configuration - def _config_parser(self, read_only): + def _config_parser(self, read_only: bool) -> SectionConstraint: if read_only: parser = self.repo.config_reader() else: @@ -240,13 +247,13 @@ class Head(Reference): return SectionConstraint(parser, 'branch "%s"' % self.name) - def config_reader(self): + def config_reader(self) -> SectionConstraint: """ :return: A configuration parser instance constrained to only read this instance's values""" return self._config_parser(read_only=True) - def config_writer(self): + def config_writer(self) -> SectionConstraint: """ :return: A configuration writer instance with read-and write access to options of this head""" -- cgit v1.2.1 From ac39679ce170c5eb21f98ac23ac0358850e8974f Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:55:03 +0100 Subject: Make types in refs compatible with previous --- git/cmd.py | 12 ++++-------- git/config.py | 2 +- git/db.py | 2 +- git/diff.py | 2 +- git/remote.py | 57 +++++++++++++++++++++++++++++++++++---------------------- git/util.py | 29 +++++++++++++++++------------ 6 files changed, 59 insertions(+), 45 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index dd887a18..11c02afe 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -15,7 +15,6 @@ from subprocess import ( PIPE ) import subprocess -import sys import threading from textwrap import dedent @@ -539,7 +538,7 @@ class Git(LazyMixin): return self def __next__(self) -> bytes: - return self.next() + return next(self) def next(self) -> bytes: line = self.readline() @@ -799,10 +798,7 @@ class Git(LazyMixin): if kill_after_timeout: raise GitCommandError(redacted_command, '"kill_after_timeout" feature is not supported on Windows.') else: - if sys.version_info[0] > 2: - cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable - else: - cmd_not_found_exception = OSError + cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable # end handle stdout_sink = (PIPE @@ -1070,8 +1066,8 @@ class Git(LazyMixin): It contains key-values for the following: - the :meth:`execute()` kwds, as listed in :var:`execute_kwargs`; - "command options" to be converted by :meth:`transform_kwargs()`; - - the `'insert_kwargs_after'` key which its value must match one of ``*args``, - and any cmd-options will be appended after the matched arg. + - the `'insert_kwargs_after'` key which its value must match one of ``*args`` + and any cmd-options will be appended after the matched arg. Examples:: diff --git a/git/config.py b/git/config.py index 2c863f93..e0a18ec8 100644 --- a/git/config.py +++ b/git/config.py @@ -238,7 +238,7 @@ def get_config_path(config_level: Lit_config_levels) -> str: assert_never(config_level, ValueError(f"Invalid configuration level: {config_level!r}")) -class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, object)): # type: ignore ## mypy does not understand dynamic class creation # noqa: E501 +class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser)): # type: ignore ## mypy does not understand dynamic class creation # noqa: E501 """Implements specifics required to read git style configuration files. diff --git a/git/db.py b/git/db.py index 47cccda8..3a7adc7d 100644 --- a/git/db.py +++ b/git/db.py @@ -17,7 +17,7 @@ from git.types import PathLike if TYPE_CHECKING: from git.cmd import Git - + # -------------------------------------------------------- diff --git a/git/diff.py b/git/diff.py index 51dac390..f8c0c25f 100644 --- a/git/diff.py +++ b/git/diff.py @@ -143,7 +143,7 @@ class Diffable(object): paths = [paths] if hasattr(self, 'Has_Repo'): - self.repo: Repo = self.repo + self.repo: 'Repo' = self.repo diff_cmd = self.repo.git.diff if other is self.Index: diff --git a/git/remote.py b/git/remote.py index f59b3245..0fcd49b5 100644 --- a/git/remote.py +++ b/git/remote.py @@ -36,9 +36,10 @@ from .refs import ( # typing------------------------------------------------------- -from typing import Any, Callable, Dict, Iterator, List, Optional, Sequence, TYPE_CHECKING, Union, overload +from typing import (Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence, # NOQA[TC002] + TYPE_CHECKING, Type, Union, overload) -from git.types import PathLike, Literal, TBD, TypeGuard, Commit_ish +from git.types import PathLike, Literal, TBD, TypeGuard, Commit_ish # NOQA[TC002] if TYPE_CHECKING: from git.repo.base import Repo @@ -83,17 +84,17 @@ def add_progress(kwargs: Any, git: Git, #} END utilities -@overload +@ overload def to_progress_instance(progress: None) -> RemoteProgress: ... -@overload +@ overload def to_progress_instance(progress: Callable[..., Any]) -> CallableRemoteProgress: ... -@overload +@ overload def to_progress_instance(progress: RemoteProgress) -> RemoteProgress: ... @@ -155,11 +156,11 @@ class PushInfo(IterableObj, object): self._old_commit_sha = old_commit self.summary = summary - @property - def old_commit(self) -> Union[str, SymbolicReference, 'Commit_ish', None]: + @ property + def old_commit(self) -> Union[str, SymbolicReference, Commit_ish, None]: return self._old_commit_sha and self._remote.repo.commit(self._old_commit_sha) or None - @property + @ property def remote_ref(self) -> Union[RemoteReference, TagReference]: """ :return: @@ -175,7 +176,7 @@ class PushInfo(IterableObj, object): raise ValueError("Could not handle remote ref: %r" % self.remote_ref_string) # END - @classmethod + @ classmethod def _from_line(cls, remote: 'Remote', line: str) -> 'PushInfo': """Create a new PushInfo instance as parsed from line which is expected to be like refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes""" @@ -228,6 +229,11 @@ class PushInfo(IterableObj, object): return PushInfo(flags, from_ref, to_ref_string, remote, old_commit, summary) + @ classmethod + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any + ) -> NoReturn: # -> Iterator['PushInfo']: + raise NotImplementedError + class FetchInfo(IterableObj, object): @@ -262,7 +268,7 @@ class FetchInfo(IterableObj, object): '-': TAG_UPDATE, } # type: Dict[flagKeyLiteral, int] - @classmethod + @ classmethod def refresh(cls) -> Literal[True]: """This gets called by the refresh function (see the top level __init__). @@ -301,25 +307,25 @@ class FetchInfo(IterableObj, object): def __str__(self) -> str: return self.name - @property + @ property def name(self) -> str: """:return: Name of our remote ref""" return self.ref.name - @property + @ property def commit(self) -> Commit_ish: """:return: Commit of our remote ref""" return self.ref.commit - @classmethod + @ classmethod def _from_line(cls, repo: 'Repo', line: str, fetch_line: str) -> 'FetchInfo': """Parse information from the given line as returned by git-fetch -v and return a new FetchInfo object representing this information. - We can handle a line as follows - "%c %-*s %-*s -> %s%s" + We can handle a line as follows: + "%c %-\\*s %-\\*s -> %s%s" - Where c is either ' ', !, +, -, *, or = + Where c is either ' ', !, +, -, \\*, or = ! means error + means success forcing update - means a tag was updated @@ -334,6 +340,7 @@ class FetchInfo(IterableObj, object): raise ValueError("Failed to parse line: %r" % line) # parse lines + remote_local_ref_str: str control_character, operation, local_remote_ref, remote_local_ref_str, note = match.groups() assert is_flagKeyLiteral(control_character), f"{control_character}" @@ -375,7 +382,7 @@ class FetchInfo(IterableObj, object): # If we do not specify a target branch like master:refs/remotes/origin/master, # the fetch result is stored in FETCH_HEAD which destroys the rule we usually # have. In that case we use a symbolic reference which is detached - ref_type = None + ref_type: Optional[Type[SymbolicReference]] = None if remote_local_ref_str == "FETCH_HEAD": ref_type = SymbolicReference elif ref_type_name == "tag" or is_tag_operation: @@ -404,14 +411,15 @@ class FetchInfo(IterableObj, object): # by the 'ref/' prefix. Otherwise even a tag could be in refs/remotes, which is when it will have the # 'tags/' subdirectory in its path. # We don't want to test for actual existence, but try to figure everything out analytically. - ref_path = None # type: Optional[PathLike] + ref_path: Optional[PathLike] = None remote_local_ref_str = remote_local_ref_str.strip() + if remote_local_ref_str.startswith(Reference._common_path_default + "/"): # always use actual type if we get absolute paths # Will always be the case if something is fetched outside of refs/remotes (if its not a tag) ref_path = remote_local_ref_str if ref_type is not TagReference and not \ - remote_local_ref_str.startswith(RemoteReference._common_path_default + "/"): + remote_local_ref_str.startswith(RemoteReference._common_path_default + "/"): ref_type = Reference # END downgrade remote reference elif ref_type is TagReference and 'tags/' in remote_local_ref_str: @@ -430,6 +438,11 @@ class FetchInfo(IterableObj, object): return cls(remote_local_ref, flags, note, old_commit, local_remote_ref) + @ classmethod + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any + ) -> NoReturn: # -> Iterator['FetchInfo']: + raise NotImplementedError + class Remote(LazyMixin, IterableObj): @@ -507,7 +520,7 @@ class Remote(LazyMixin, IterableObj): return False # end - @classmethod + @ classmethod def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator['Remote']: """:return: Iterator yielding Remote objects of the given repository""" for section in repo.config_reader("repository").sections(): @@ -897,7 +910,7 @@ class Remote(LazyMixin, IterableObj): universal_newlines=True, **kwargs) return self._get_push_info(proc, progress) - @property + @ property def config_reader(self) -> SectionConstraint: """ :return: @@ -912,7 +925,7 @@ class Remote(LazyMixin, IterableObj): pass # END handle exception - @property + @ property def config_writer(self) -> SectionConstraint: """ :return: GitConfigParser compatible object able to write options for this remote. diff --git a/git/util.py b/git/util.py index 571e261e..c04e2927 100644 --- a/git/util.py +++ b/git/util.py @@ -4,6 +4,7 @@ # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php +from abc import abstractmethod from .exc import InvalidGitRepositoryError import os.path as osp from .compat import is_win @@ -28,7 +29,8 @@ import warnings # typing --------------------------------------------------------- from typing import (Any, AnyStr, BinaryIO, Callable, Dict, Generator, IO, Iterator, List, - Optional, Pattern, Sequence, Tuple, TypeVar, Union, cast, TYPE_CHECKING, overload) + Optional, Pattern, Sequence, Tuple, TypeVar, Union, cast, + TYPE_CHECKING, overload, ) import pathlib @@ -39,8 +41,8 @@ if TYPE_CHECKING: # from git.objects.base import IndexObject -from .types import (Literal, SupportsIndex, # because behind py version guards - PathLike, HSH_TD, Total_TD, Files_TD, # aliases +from .types import (Literal, SupportsIndex, Protocol, runtime_checkable, # because behind py version guards + PathLike, HSH_TD, Total_TD, Files_TD, # aliases Has_id_attribute) T_IterableObj = TypeVar('T_IterableObj', bound=Union['IterableObj', 'Has_id_attribute'], covariant=True) @@ -471,7 +473,7 @@ class RemoteProgress(object): - Lines that do not contain progress info are stored in :attr:`other_lines`. - Lines that seem to contain an error (i.e. start with error: or fatal:) are stored - in :attr:`error_lines`.""" + in :attr:`error_lines`.""" # handle # Counting objects: 4, done. # Compressing objects: 50% (1/2) @@ -993,7 +995,7 @@ class IterableList(List[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 assert isinstance(index, (int, str, slice)), "Index of IterableList should be an int or str" @@ -1030,23 +1032,24 @@ class IterableList(List[T_IterableObj]): class IterableClassWatcher(type): + """ Metaclass that watches """ def __init__(cls, name, bases, clsdict): for base in bases: if type(base) == IterableClassWatcher: warnings.warn(f"GitPython Iterable subclassed by {name}. " - "Iterable is deprecated due to naming clash, " + "Iterable is deprecated due to naming clash since v3.1.18" + " and will be removed in 3.1.20, " "Use IterableObj instead \n", DeprecationWarning, stacklevel=2) -class Iterable(object): +class Iterable(metaclass=IterableClassWatcher): """Defines an interface for iterable items which is to assure a uniform way to retrieve and iterate items within the git repository""" __slots__ = () _id_attribute_ = "attribute that most suitably identifies your instance" - __metaclass__ = IterableClassWatcher @classmethod def list_items(cls, repo, *args, **kwargs): @@ -1064,14 +1067,15 @@ class Iterable(object): return out_list @classmethod - def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any): + def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Any: # return typed to be compatible with subtypes e.g. Remote """For more information about the arguments, see list_items :return: iterator yielding Items""" raise NotImplementedError("To be implemented by Subclass") -class IterableObj(): +@runtime_checkable +class IterableObj(Protocol): """Defines an interface for iterable items which is to assure a uniform way to retrieve and iterate items within the git repository @@ -1095,11 +1099,12 @@ class IterableObj(): return out_list @classmethod + @abstractmethod def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any - ) -> Iterator[T_IterableObj]: + ) -> Iterator[T_IterableObj]: # Iterator[T_IterableObj]: # return typed to be compatible with subtypes e.g. Remote """For more information about the arguments, see list_items - :return: iterator yielding Items""" + :return: iterator yielding Items""" raise NotImplementedError("To be implemented by Subclass") # } END classes -- cgit v1.2.1 From 44f0578f841c48bba6473e0890b8a3daae94c58e Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:55:23 +0100 Subject: Make types in refs compatible with repo --- git/repo/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index 3214b528..29d08502 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -426,7 +426,7 @@ class Repo(object): For more documentation, please see the Head.create method. :return: newly created Head Reference""" - return Head.create(self, path, commit, force, logmsg) + return Head.create(self, path, commit, logmsg, force) def delete_head(self, *heads: 'SymbolicReference', **kwargs: Any) -> None: """Delete the given heads @@ -518,7 +518,7 @@ class Repo(object): repository = configuration file for this repository only""" return GitConfigParser(self._get_config_path(config_level), read_only=False, repo=self) - def commit(self, rev: Optional[str] = None + def commit(self, rev: Union[str, Commit_ish, None] = None ) -> Commit: """The Commit object for the specified revision @@ -551,7 +551,8 @@ class Repo(object): return self.head.commit.tree return self.rev_parse(str(rev) + "^{tree}") - def iter_commits(self, rev: Optional[TBD] = None, paths: Union[PathLike, Sequence[PathLike]] = '', + def iter_commits(self, rev: Union[str, Commit, 'SymbolicReference', None] = None, + paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any) -> Iterator[Commit]: """A list of Commit objects representing the history of a given ref/commit -- cgit v1.2.1 From 29e12e9ceee59a87984c9049ac84e030a4dd0ed2 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:56:38 +0100 Subject: Make traversable and serilizable into protocols --- git/objects/util.py | 78 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/git/objects/util.py b/git/objects/util.py index fbe3d9de..04af3b83 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -5,6 +5,8 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php """Module for general utility functions""" +from abc import abstractmethod +import warnings from git.util import ( IterableList, IterableObj, @@ -23,7 +25,7 @@ from datetime import datetime, timedelta, tzinfo from typing import (Any, Callable, Deque, Iterator, NamedTuple, overload, Sequence, TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast) -from git.types import Has_id_attribute, Literal +from git.types import Has_id_attribute, Literal, Protocol, runtime_checkable if TYPE_CHECKING: from io import BytesIO, StringIO @@ -289,7 +291,8 @@ class ProcessStreamAdapter(object): return getattr(self._stream, attr) -class Traversable(object): +@runtime_checkable +class Traversable(Protocol): """Simple interface to perform depth-first or breadth-first traversals into one direction. @@ -301,6 +304,7 @@ class Traversable(object): __slots__ = () @classmethod + @abstractmethod def _get_intermediate_items(cls, item) -> Sequence['Traversable']: """ Returns: @@ -313,7 +317,18 @@ class Traversable(object): """ raise NotImplementedError("To be implemented in subclass") - def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']]: + @abstractmethod + def list_traverse(self, *args: Any, **kwargs: Any) -> Any: + """ """ + warnings.warn("list_traverse() method should only be called from subclasses." + "Calling from Traversable abstract class will raise NotImplementedError in 3.1.20" + "Builtin sublclasses are 'Submodule', 'Tree' and 'Commit", + DeprecationWarning, + stacklevel=2) + return self._list_traverse(*args, **kwargs) + + def _list_traverse(self, as_edge=False, *args: Any, **kwargs: Any + ) -> IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']]: """ :return: IterableList with the results of the traversal as produced by traverse() @@ -329,22 +344,34 @@ class Traversable(object): id = "" # shouldn't reach here, unless Traversable subclass created with no _id_attribute_ # could add _id_attribute_ to Traversable, or make all Traversable also Iterable? - out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id) - # overloads in subclasses (mypy does't allow typing self: subclass) - # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]] - - # NOTE: if is_edge=True, self.traverse returns a Tuple, so should be prevented or flattened? - kwargs['as_edge'] = False - out.extend(self.traverse(*args, **kwargs)) # type: ignore - return out - - def traverse(self, - predicate: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: True, - prune: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: False, - depth: int = -1, branch_first: bool = True, visit_once: bool = True, - ignore_self: int = 1, as_edge: bool = False - ) -> Union[Iterator[Union['Traversable', 'Blob']], - Iterator[TraversedTup]]: + if not as_edge: + out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id) + out.extend(self.traverse(as_edge=as_edge, *args, **kwargs)) # type: ignore + return out + # overloads in subclasses (mypy does't allow typing self: subclass) + # Union[IterableList['Commit'], IterableList['Submodule'], IterableList[Union['Submodule', 'Tree', 'Blob']]] + else: + # Raise deprecationwarning, doesn't make sense to use this + out_list: IterableList = IterableList(self.traverse(*args, **kwargs)) + return out_list + + @ abstractmethod + def traverse(self, *args: Any, **kwargs) -> Any: + """ """ + warnings.warn("traverse() method should only be called from subclasses." + "Calling from Traversable abstract class will raise NotImplementedError in 3.1.20" + "Builtin sublclasses are 'Submodule', 'Tree' and 'Commit", + DeprecationWarning, + stacklevel=2) + return self._traverse(*args, **kwargs) + + def _traverse(self, + predicate: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: True, + prune: Callable[[Union['Traversable', 'Blob', TraversedTup], int], bool] = lambda i, d: False, + depth: int = -1, branch_first: bool = True, visit_once: bool = True, + ignore_self: int = 1, as_edge: bool = False + ) -> Union[Iterator[Union['Traversable', 'Blob']], + Iterator[TraversedTup]]: """:return: iterator yielding of items found when traversing self :param predicate: f(i,d) returns False if item i at depth d should not be included in the result @@ -435,11 +462,13 @@ class Traversable(object): # END for each item on work stack -class Serializable(object): +@ runtime_checkable +class Serializable(Protocol): """Defines methods to serialize and deserialize objects from and into a data stream""" __slots__ = () + # @abstractmethod def _serialize(self, stream: 'BytesIO') -> 'Serializable': """Serialize the data of this object into the given data stream :note: a serialized object would ``_deserialize`` into the same object @@ -447,6 +476,7 @@ class Serializable(object): :return: self""" raise NotImplementedError("To be implemented in subclass") + # @abstractmethod def _deserialize(self, stream: 'BytesIO') -> 'Serializable': """Deserialize all information regarding this object from the stream :param stream: a file-like object @@ -454,13 +484,13 @@ class Serializable(object): raise NotImplementedError("To be implemented in subclass") -class TraversableIterableObj(Traversable, IterableObj): +class TraversableIterableObj(IterableObj, Traversable): __slots__ = () TIobj_tuple = Tuple[Union[T_TIobj, None], T_TIobj] - def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]: # type: ignore[override] - return super(TraversableIterableObj, self).list_traverse(* args, **kwargs) + def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]: + return super(TraversableIterableObj, self)._list_traverse(* args, **kwargs) @ overload # type: ignore def traverse(self: T_TIobj, @@ -522,6 +552,6 @@ class TraversableIterableObj(Traversable, IterableObj): """ return cast(Union[Iterator[T_TIobj], Iterator[Tuple[Union[None, T_TIobj], T_TIobj]]], - super(TraversableIterableObj, self).traverse( + super(TraversableIterableObj, self)._traverse( predicate, prune, depth, branch_first, visit_once, ignore_self, as_edge # type: ignore )) -- cgit v1.2.1 From 6609ef7c3b5bb840dba8d0a5362e67746761a437 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:57:04 +0100 Subject: Make types in refs compatible with objects --- git/objects/base.py | 6 +++--- git/objects/blob.py | 4 +++- git/objects/commit.py | 21 ++++++++++++--------- git/objects/fun.py | 2 +- git/objects/submodule/base.py | 4 ++-- git/objects/tag.py | 4 +++- git/objects/tree.py | 12 ++++++------ 7 files changed, 30 insertions(+), 23 deletions(-) diff --git a/git/objects/base.py b/git/objects/base.py index 4e2ed493..64f105ca 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -15,9 +15,9 @@ from .util import get_object_type_by_name # typing ------------------------------------------------------------------ -from typing import Any, TYPE_CHECKING, Optional, Union +from typing import Any, TYPE_CHECKING, Union -from git.types import PathLike, Commit_ish +from git.types import PathLike, Commit_ish, Lit_commit_ish if TYPE_CHECKING: from git.repo import Repo @@ -44,7 +44,7 @@ class Object(LazyMixin): TYPES = (dbtyp.str_blob_type, dbtyp.str_tree_type, dbtyp.str_commit_type, dbtyp.str_tag_type) __slots__ = ("repo", "binsha", "size") - type = None # type: Optional[str] # to be set by subclass + type: Union[Lit_commit_ish, None] = None def __init__(self, repo: 'Repo', binsha: bytes): """Initialize an object by identifying it by its binary sha. diff --git a/git/objects/blob.py b/git/objects/blob.py index 017178f0..99b5c636 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -6,6 +6,8 @@ from mimetypes import guess_type from . import base +from git.types import Literal + __all__ = ('Blob', ) @@ -13,7 +15,7 @@ class Blob(base.IndexObject): """A Blob encapsulates a git blob object""" DEFAULT_MIME_TYPE = "text/plain" - type = "blob" + type: Literal['blob'] = "blob" # valid blob modes executable_mode = 0o100755 diff --git a/git/objects/commit.py b/git/objects/commit.py index 65a87591..11cf52a5 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -41,10 +41,11 @@ import logging from typing import Any, IO, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING -from git.types import PathLike, TypeGuard +from git.types import PathLike, TypeGuard, Literal if TYPE_CHECKING: from git.repo import Repo + from git.refs import SymbolicReference # ------------------------------------------------------------------------ @@ -73,14 +74,14 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): default_encoding = "UTF-8" # object configuration - type = "commit" + type: Literal['commit'] = "commit" __slots__ = ("tree", "author", "authored_date", "author_tz_offset", "committer", "committed_date", "committer_tz_offset", "message", "parents", "encoding", "gpgsig") _id_attribute_ = "hexsha" - def __init__(self, repo: 'Repo', binsha: bytes, tree: Union['Tree', None] = None, + def __init__(self, repo: 'Repo', binsha: bytes, tree: Union[Tree, None] = None, author: Union[Actor, None] = None, authored_date: Union[int, None] = None, author_tz_offset: Union[None, float] = None, @@ -201,11 +202,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): # END handle attrs @property - def authored_datetime(self) -> 'datetime.datetime': + def authored_datetime(self) -> datetime.datetime: return from_timestamp(self.authored_date, self.author_tz_offset) @property - def committed_datetime(self) -> 'datetime.datetime': + def committed_datetime(self) -> datetime.datetime: return from_timestamp(self.committed_date, self.committer_tz_offset) @property @@ -242,7 +243,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return self.repo.git.name_rev(self) @classmethod - def iter_items(cls, repo: 'Repo', rev: str, # type: ignore + def iter_items(cls, repo: 'Repo', rev: Union[str, 'Commit', 'SymbolicReference'], # type: ignore paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any ) -> Iterator['Commit']: """Find all commits matching the given criteria. @@ -354,7 +355,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): finalize_process(proc_or_stream) @ classmethod - def create_from_tree(cls, repo: 'Repo', tree: Union['Tree', str], message: str, + def create_from_tree(cls, repo: 'Repo', tree: Union[Tree, str], message: str, parent_commits: Union[None, List['Commit']] = None, head: bool = False, author: Union[None, Actor] = None, committer: Union[None, Actor] = None, author_date: Union[None, str] = None, commit_date: Union[None, str] = None): @@ -516,8 +517,10 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): return self def _deserialize(self, stream: BytesIO) -> 'Commit': - """:param from_rev_list: if true, the stream format is coming from the rev-list command - Otherwise it is assumed to be a plain data stream from our object""" + """ + :param from_rev_list: if true, the stream format is coming from the rev-list command + Otherwise it is assumed to be a plain data stream from our object + """ readline = stream.readline self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '') diff --git a/git/objects/fun.py b/git/objects/fun.py index fc2ea1e7..d6cdafe1 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -167,7 +167,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by data: List[EntryTupOrNone] = [] else: # make new list for typing as list invariant - data = [x for x in tree_entries_from_data(odb.stream(tree_sha).read())] + data = list(tree_entries_from_data(odb.stream(tree_sha).read())) # END handle muted trees trees_data.append(data) # END for each sha to get data for diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index b485dbf6..21cfcd5a 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -52,7 +52,7 @@ from .util import ( 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 git.types import Commit_ish, Literal, PathLike, TBD if TYPE_CHECKING: from git.index import IndexFile @@ -105,7 +105,7 @@ class Submodule(IndexObject, TraversableIterableObj): k_default_mode = stat.S_IFDIR | stat.S_IFLNK # submodules are directories with link-status # this is a bogus type for base class compatibility - type = 'submodule' + type: Literal['submodule'] = 'submodule' # type: ignore __slots__ = ('_parent_commit', '_url', '_branch_path', '_name', '__weakref__') _cache_attrs = ('path', '_url', '_branch_path') diff --git a/git/objects/tag.py b/git/objects/tag.py index cb6efbe9..4a2fcb0d 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -11,6 +11,8 @@ from ..compat import defenc from typing import List, TYPE_CHECKING, Union +from git.types import Literal + if TYPE_CHECKING: from git.repo import Repo from git.util import Actor @@ -24,7 +26,7 @@ __all__ = ("TagObject", ) class TagObject(base.Object): """Non-Lightweight tag carrying additional information about an object we are pointing to.""" - type = "tag" + type: Literal['tag'] = "tag" __slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message") def __init__(self, repo: 'Repo', binsha: bytes, diff --git a/git/objects/tree.py b/git/objects/tree.py index a9656c1d..0e3f44b9 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -24,7 +24,7 @@ from .fun import ( from typing import (Any, Callable, Dict, Iterable, Iterator, List, Tuple, Type, Union, cast, TYPE_CHECKING) -from git.types import PathLike, TypeGuard +from git.types import PathLike, TypeGuard, Literal if TYPE_CHECKING: from git.repo import Repo @@ -195,7 +195,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): blob = tree[0] """ - type = "tree" + type: Literal['tree'] = "tree" __slots__ = "_cache" # actual integer ids for comparison @@ -285,7 +285,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): return [i for i in self if i.type == "tree"] @ property - def blobs(self) -> List['Blob']: + def blobs(self) -> List[Blob]: """:return: list(Blob, ...) list of blobs directly below this tree""" return [i for i in self if i.type == "blob"] @@ -322,8 +322,8 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): # assert is_tree_traversed(ret_tup), f"Type is {[type(x) for x in list(ret_tup[0])]}" # return ret_tup[0]""" return cast(Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]], - super(Tree, self).traverse(predicate, prune, depth, # type: ignore - branch_first, visit_once, ignore_self)) + super(Tree, self)._traverse(predicate, prune, depth, # type: ignore + branch_first, visit_once, ignore_self)) def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]: """ @@ -331,7 +331,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): traverse() Tree -> IterableList[Union['Submodule', 'Tree', 'Blob']] """ - return super(Tree, self).list_traverse(* args, **kwargs) + return super(Tree, self)._list_traverse(* args, **kwargs) # List protocol -- cgit v1.2.1 From 454576254b873b7ebc45bb30846e5831dc2d8817 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 16:59:30 +0100 Subject: rmv python 3.5 checks from tests --- test/lib/helper.py | 2 +- test/test_base.py | 12 +++++------- test/test_commit.py | 2 +- test/test_git.py | 7 +------ test/test_refs.py | 4 ++-- test/test_remote.py | 4 ++-- test/test_submodule.py | 3 +-- test/test_tree.py | 5 ++--- 8 files changed, 15 insertions(+), 24 deletions(-) diff --git a/test/lib/helper.py b/test/lib/helper.py index 3412786d..5dde7b04 100644 --- a/test/lib/helper.py +++ b/test/lib/helper.py @@ -336,7 +336,7 @@ class TestBase(TestCase): - Class level repository which is considered read-only as it is shared among all test cases in your type. Access it using:: - self.rorepo # 'ro' stands for read-only + self.rorepo # 'ro' stands for read-only The rorepo is in fact your current project's git repo. If you refer to specific shas for your objects, be sure you choose some that are part of the immutable portion diff --git a/test/test_base.py b/test/test_base.py index 02963ce0..68ce6816 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -9,7 +9,7 @@ import sys import tempfile from unittest import SkipTest, skipIf -from git import ( +from git.objects import ( Blob, Tree, Commit, @@ -18,17 +18,17 @@ from git import ( from git.compat import is_win from git.objects.util import get_object_type_by_name from test.lib import ( - TestBase, + TestBase as _TestBase, with_rw_repo, with_rw_and_rw_remote_repo ) -from git.util import hex_to_bin +from git.util import hex_to_bin, HIDE_WINDOWS_FREEZE_ERRORS import git.objects.base as base import os.path as osp -class TestBase(TestBase): +class TestBase(_TestBase): def tearDown(self): import gc @@ -111,15 +111,13 @@ class TestBase(TestBase): assert not rw_repo.config_reader("repository").getboolean("core", "bare") assert osp.isdir(osp.join(rw_repo.working_tree_dir, 'lib')) - #@skipIf(HIDE_WINDOWS_FREEZE_ERRORS, "FIXME: Freezes! sometimes...") + @skipIf(HIDE_WINDOWS_FREEZE_ERRORS, "FIXME: Freezes! sometimes...") @with_rw_and_rw_remote_repo('0.1.6') def test_with_rw_remote_and_rw_repo(self, rw_repo, rw_remote_repo): assert not rw_repo.config_reader("repository").getboolean("core", "bare") assert rw_remote_repo.config_reader("repository").getboolean("core", "bare") assert osp.isdir(osp.join(rw_repo.working_tree_dir, 'lib')) - @skipIf(sys.version_info < (3,) and is_win, - "Unicode woes, see https://github.com/gitpython-developers/GitPython/pull/519") @with_rw_repo('0.1.6') def test_add_unicode(self, rw_repo): filename = "שלום.txt" diff --git a/test/test_commit.py b/test/test_commit.py index 34b91ac7..670068e5 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -265,7 +265,7 @@ class TestCommit(TestCommitSerialization): @with_rw_directory def test_ambiguous_arg_iteration(self, rw_dir): rw_repo = Repo.init(osp.join(rw_dir, 'test_ambiguous_arg')) - path = osp.join(rw_repo.working_tree_dir, 'master') + path = osp.join(str(rw_repo.working_tree_dir), 'master') touch(path) rw_repo.index.add([path]) rw_repo.index.commit('initial commit') diff --git a/test/test_git.py b/test/test_git.py index 72c7ef62..7f52d650 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -18,7 +18,6 @@ from git import ( Repo, cmd ) -from git.compat import is_darwin from test.lib import ( TestBase, fixture_path @@ -248,11 +247,7 @@ class TestGit(TestBase): try: remote.fetch() except GitCommandError as err: - if sys.version_info[0] < 3 and is_darwin: - self.assertIn('ssh-orig', str(err)) - self.assertEqual(err.status, 128) - else: - self.assertIn('FOO', str(err)) + self.assertIn('FOO', str(err)) def test_handle_process_output(self): from git.cmd import handle_process_output diff --git a/test/test_refs.py b/test/test_refs.py index 8ab45d22..1315f885 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -119,14 +119,14 @@ class TestRefs(TestBase): assert head.tracking_branch() == remote_ref head.set_tracking_branch(None) assert head.tracking_branch() is None - + special_name = 'feature#123' special_name_remote_ref = SymbolicReference.create(rwrepo, 'refs/remotes/origin/%s' % special_name) gp_tracking_branch = rwrepo.create_head('gp_tracking#123') special_name_remote_ref = rwrepo.remotes[0].refs[special_name] # get correct type gp_tracking_branch.set_tracking_branch(special_name_remote_ref) assert gp_tracking_branch.tracking_branch().path == special_name_remote_ref.path - + git_tracking_branch = rwrepo.create_head('git_tracking#123') rwrepo.git.branch('-u', special_name_remote_ref.name, git_tracking_branch.name) assert git_tracking_branch.tracking_branch().name == special_name_remote_ref.name diff --git a/test/test_remote.py b/test/test_remote.py index fb7d23c6..c29fac65 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -347,7 +347,7 @@ class TestRemote(TestBase): progress = TestRemoteProgress() to_be_updated = "my_tag.1.0RV" new_tag = TagReference.create(rw_repo, to_be_updated) # @UnusedVariable - other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", message="my message") + other_tag = TagReference.create(rw_repo, "my_obj_tag.2.1aRV", logmsg="my message") res = remote.push(progress=progress, tags=True) self.assertTrue(res[-1].flags & PushInfo.NEW_TAG) progress.make_assertion() @@ -355,7 +355,7 @@ class TestRemote(TestBase): # update push new tags # Rejection is default - new_tag = TagReference.create(rw_repo, to_be_updated, ref='HEAD~1', force=True) + new_tag = TagReference.create(rw_repo, to_be_updated, reference='HEAD~1', force=True) res = remote.push(tags=True) self._do_test_push_result(res, remote) self.assertTrue(res[-1].flags & PushInfo.REJECTED) diff --git a/test/test_submodule.py b/test/test_submodule.py index 85191a89..3307bc78 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -3,7 +3,6 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php import os import shutil -import sys from unittest import skipIf import git @@ -421,7 +420,7 @@ class TestSubmodule(TestBase): def test_base_bare(self, rwrepo): self._do_base_tests(rwrepo) - @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and sys.version_info[:2] == (3, 5), """ + @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, """ File "C:\\projects\\gitpython\\git\\cmd.py", line 559, in execute raise GitCommandNotFound(command, err) git.exc.GitCommandNotFound: Cmd('git') not found due to: OSError('[WinError 6] The handle is invalid') diff --git a/test/test_tree.py b/test/test_tree.py index 0607d8e3..24c401cb 100644 --- a/test/test_tree.py +++ b/test/test_tree.py @@ -5,7 +5,6 @@ # the BSD License: http://www.opensource.org/licenses/bsd-license.php from io import BytesIO -import sys from unittest import skipIf from git.objects import ( @@ -20,7 +19,7 @@ import os.path as osp class TestTree(TestBase): - @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and sys.version_info[:2] == (3, 5), """ + @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, """ File "C:\\projects\\gitpython\\git\\cmd.py", line 559, in execute raise GitCommandNotFound(command, err) git.exc.GitCommandNotFound: Cmd('git') not found due to: OSError('[WinError 6] The handle is invalid') @@ -53,7 +52,7 @@ class TestTree(TestBase): testtree._deserialize(stream) # END for each item in tree - @skipIf(HIDE_WINDOWS_KNOWN_ERRORS and sys.version_info[:2] == (3, 5), """ + @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, """ File "C:\\projects\\gitpython\\git\\cmd.py", line 559, in execute raise GitCommandNotFound(command, err) git.exc.GitCommandNotFound: Cmd('git') not found due to: OSError('[WinError 6] The handle is invalid') -- cgit v1.2.1 From 795d2b1f75c6c210ebabd81f33c0c9ac8b57729e Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 17:03:50 +0100 Subject: rmv redundant IOerror except --- git/refs/symbolic.py | 658 --------------------------------------------------- setup.py | 4 +- 2 files changed, 2 insertions(+), 660 deletions(-) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 48b94cfa..426d40d4 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -693,661 +693,3 @@ class SymbolicReference(object): def is_remote(self): """:return: True if this symbolic reference points to a remote branch""" return self.path.startswith(self._remote_common_path_default + "/") - - -__all__ = ["SymbolicReference"] - - -def _git_dir(repo, path): - """ Find the git dir that's appropriate for the path""" - name = "%s" % (path,) - if name in ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'index', 'logs']: - return repo.git_dir - return repo.common_dir - - -class SymbolicReference(object): - - """Represents a special case of a reference such that this reference is symbolic. - It does not point to a specific commit, but to another Head, which itself - specifies a commit. - - A typical example for a symbolic reference is HEAD.""" - __slots__ = ("repo", "path") - _resolve_ref_on_create = False - _points_to_commits_only = True - _common_path_default = "" - _remote_common_path_default = "refs/remotes" - _id_attribute_ = "name" - - def __init__(self, repo, path, check_path=None): - self.repo = repo - self.path = path - - def __str__(self): - return self.path - - def __repr__(self): - return '' % (self.__class__.__name__, self.path) - - def __eq__(self, other): - if hasattr(other, 'path'): - return self.path == other.path - return False - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self.path) - - @property - def name(self): - """ - :return: - In case of symbolic references, the shortest assumable name - is the path itself.""" - return self.path - - @property - def abspath(self): - return join_path_native(_git_dir(self.repo, self.path), self.path) - - @classmethod - def _get_packed_refs_path(cls, repo): - return osp.join(repo.common_dir, 'packed-refs') - - @classmethod - def _iter_packed_refs(cls, repo): - """Returns an iterator yielding pairs of sha1/path pairs (as bytes) for the corresponding refs. - :note: The packed refs file will be kept open as long as we iterate""" - try: - with open(cls._get_packed_refs_path(repo), 'rt', encoding='UTF-8') as fp: - for line in fp: - line = line.strip() - if not line: - continue - if line.startswith('#'): - # "# pack-refs with: peeled fully-peeled sorted" - # the git source code shows "peeled", - # "fully-peeled" and "sorted" as the keywords - # that can go on this line, as per comments in git file - # refs/packed-backend.c - # I looked at master on 2017-10-11, - # commit 111ef79afe, after tag v2.15.0-rc1 - # from repo https://github.com/git/git.git - if line.startswith('# pack-refs with:') and 'peeled' not in line: - raise TypeError("PackingType of packed-Refs not understood: %r" % line) - # END abort if we do not understand the packing scheme - continue - # END parse comment - - # skip dereferenced tag object entries - previous line was actual - # tag reference for it - if line[0] == '^': - continue - - yield tuple(line.split(' ', 1)) - # END for each line - except (OSError, IOError): - return - # END no packed-refs file handling - # NOTE: Had try-finally block around here to close the fp, - # but some python version wouldn't allow yields within that. - # I believe files are closing themselves on destruction, so it is - # alright. - - @classmethod - def dereference_recursive(cls, repo, ref_path): - """ - :return: hexsha stored in the reference at the given ref_path, recursively dereferencing all - intermediate references as required - :param repo: the repository containing the reference at ref_path""" - while True: - hexsha, ref_path = cls._get_ref_info(repo, ref_path) - if hexsha is not None: - return hexsha - # END recursive dereferencing - - @classmethod - def _get_ref_info_helper(cls, repo, ref_path): - """Return: (str(sha), str(target_ref_path)) if available, the sha the file at - rela_path points to, or None. target_ref_path is the reference we - point to, or None""" - tokens = None - repodir = _git_dir(repo, ref_path) - try: - with open(osp.join(repodir, ref_path), 'rt', encoding='UTF-8') as fp: - value = fp.read().rstrip() - # Don't only split on spaces, but on whitespace, which allows to parse lines like - # 60b64ef992065e2600bfef6187a97f92398a9144 branch 'master' of git-server:/path/to/repo - tokens = value.split() - assert(len(tokens) != 0) - except (OSError, IOError): - # Probably we are just packed, find our entry in the packed refs file - # NOTE: We are not a symbolic ref if we are in a packed file, as these - # are excluded explicitly - for sha, path in cls._iter_packed_refs(repo): - if path != ref_path: - continue - # sha will be used - tokens = sha, path - break - # END for each packed ref - # END handle packed refs - if tokens is None: - raise ValueError("Reference at %r does not exist" % ref_path) - - # is it a reference ? - if tokens[0] == 'ref:': - return (None, tokens[1]) - - # its a commit - if repo.re_hexsha_only.match(tokens[0]): - return (tokens[0], None) - - raise ValueError("Failed to parse reference information from %r" % ref_path) - - @classmethod - def _get_ref_info(cls, repo, ref_path): - """Return: (str(sha), str(target_ref_path)) if available, the sha the file at - rela_path points to, or None. target_ref_path is the reference we - point to, or None""" - return cls._get_ref_info_helper(repo, ref_path) - - def _get_object(self): - """ - :return: - The object our ref currently refers to. Refs can be cached, they will - always point to the actual object as it gets re-created on each query""" - # have to be dynamic here as we may be a tag which can point to anything - # Our path will be resolved to the hexsha which will be used accordingly - return Object.new_from_sha(self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path))) - - def _get_commit(self): - """ - :return: - Commit object we point to, works for detached and non-detached - SymbolicReferences. The symbolic reference will be dereferenced recursively.""" - obj = self._get_object() - if obj.type == 'tag': - obj = obj.object - # END dereference tag - - if obj.type != Commit.type: - raise TypeError("Symbolic Reference pointed to object %r, commit was required" % obj) - # END handle type - return obj - - def set_commit(self, commit, logmsg=None): - """As set_object, but restricts the type of object to be a Commit - - :raise ValueError: If commit is not a Commit object or doesn't point to - a commit - :return: self""" - # check the type - assume the best if it is a base-string - invalid_type = False - if isinstance(commit, Object): - invalid_type = commit.type != Commit.type - elif isinstance(commit, SymbolicReference): - invalid_type = commit.object.type != Commit.type - else: - try: - invalid_type = self.repo.rev_parse(commit).type != Commit.type - except (BadObject, BadName) as e: - raise ValueError("Invalid object: %s" % commit) from e - # END handle exception - # END verify type - - if invalid_type: - raise ValueError("Need commit, got %r" % commit) - # END handle raise - - # we leave strings to the rev-parse method below - self.set_object(commit, logmsg) - - return self - - def set_object(self, object, logmsg=None): # @ReservedAssignment - """Set the object we point to, possibly dereference our symbolic reference first. - If the reference does not exist, it will be created - - :param object: a refspec, a SymbolicReference or an Object instance. SymbolicReferences - will be dereferenced beforehand to obtain the object they point to - :param logmsg: If not None, the message will be used in the reflog entry to be - written. Otherwise the reflog is not altered - :note: plain SymbolicReferences may not actually point to objects by convention - :return: self""" - if isinstance(object, SymbolicReference): - object = object.object # @ReservedAssignment - # END resolve references - - is_detached = True - try: - is_detached = self.is_detached - except ValueError: - pass - # END handle non-existing ones - - if is_detached: - return self.set_reference(object, logmsg) - - # 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") - object = property(_get_object, set_object, doc="Return the object our ref currently refers to") - - def _get_reference(self): - """:return: Reference Object we point to - :raise TypeError: If this symbolic reference is detached, hence it doesn't point - to a reference, but to a commit""" - sha, target_ref_path = self._get_ref_info(self.repo, self.path) - if target_ref_path is None: - raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha)) - return self.from_path(self.repo, target_ref_path) - - def set_reference(self, ref, logmsg=None): - """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference. - Otherwise an Object, given as Object instance or refspec, is assumed and if valid, - will be set which effectively detaches the refererence if it was a purely - symbolic one. - - :param ref: SymbolicReference instance, Object instance or refspec string - Only if the ref is a SymbolicRef 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. - Otherwise, a reflog entry is not written for the changed reference. - The previous commit of the entry will be the commit we point to now. - - See also: log_append() - - :return: self - :note: This symbolic reference will not be dereferenced. For that, see - ``set_object(...)``""" - write_value = None - obj = None - if isinstance(ref, SymbolicReference): - write_value = "ref: %s" % ref.path - elif isinstance(ref, Object): - obj = ref - write_value = ref.hexsha - elif isinstance(ref, str): - try: - obj = self.repo.rev_parse(ref + "^{}") # optionally deref tags - write_value = obj.hexsha - except (BadObject, BadName) as e: - raise ValueError("Could not extract object from %s" % ref) from e - # END end try string - else: - raise ValueError("Unrecognized Value: %r" % ref) - # END try commit attribute - - # typecheck - if obj is not None and self._points_to_commits_only and obj.type != Commit.type: - raise TypeError("Require commit, got %r" % obj) - # END verify type - - oldbinsha = None - if logmsg is not None: - try: - oldbinsha = self.commit.binsha - except ValueError: - oldbinsha = Commit.NULL_BIN_SHA - # END handle non-existing - # END retrieve old hexsha - - fpath = self.abspath - assure_directory_exists(fpath, is_file=True) - - lfd = LockedFD(fpath) - fd = lfd.open(write=True, stream=True) - ok = True - try: - fd.write(write_value.encode('ascii') + b'\n') - lfd.commit() - ok = True - finally: - if not ok: - lfd.rollback() - # Adjust the reflog - if logmsg is not None: - self.log_append(oldbinsha, logmsg) - - return self - - # aliased reference - reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") - ref = reference - - def is_valid(self): - """ - :return: - True if the reference is valid, hence it can be read and points to - a valid object or reference.""" - try: - self.object - except (OSError, ValueError): - return False - else: - return True - - @property - def is_detached(self): - """ - :return: - True if we are a detached reference, hence we point to a specific commit - instead to another reference""" - try: - self.ref - return False - except TypeError: - return True - - def log(self): - """ - :return: RefLog for this reference. Its last entry reflects the latest change - applied to this reference - - .. note:: As the log is parsed every time, its recommended to cache it for use - instead of calling this method repeatedly. It should be considered read-only.""" - return RefLog.from_file(RefLog.path(self)) - - def log_append(self, oldbinsha, message, newbinsha=None): - """Append a logentry to the logfile of this ref - - :param oldbinsha: binary sha this ref used to point to - :param message: A message describing the change - :param newbinsha: The sha the ref points to now. If None, our current commit sha - will be used - :return: added RefLogEntry instance""" - # NOTE: we use the committer of the currently active commit - this should be - # correct to allow overriding the committer on a per-commit level. - # See https://github.com/gitpython-developers/GitPython/pull/146 - try: - committer_or_reader = self.commit.committer - except ValueError: - committer_or_reader = self.repo.config_reader() - # end handle newly cloned repositories - return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, - (newbinsha is None and self.commit.binsha) or newbinsha, - message) - - def log_entry(self, index): - """:return: RefLogEntry at the given index - :param index: python list compatible positive or negative index - - .. note:: This method must read part of the reflog during execution, hence - it should be used sparringly, or only if you need just one index. - In that case, it will be faster than the ``log()`` method""" - return RefLog.entry_at(RefLog.path(self), index) - - @classmethod - def to_full_path(cls, path) -> PathLike: - """ - :return: string with a full repository-relative path which can be used to initialize - a Reference instance, for instance by using ``Reference.from_path``""" - if isinstance(path, SymbolicReference): - path = path.path - full_ref_path = path - if not cls._common_path_default: - return full_ref_path - if not path.startswith(cls._common_path_default + "/"): - full_ref_path = '%s/%s' % (cls._common_path_default, path) - return full_ref_path - - @classmethod - def delete(cls, repo, path): - """Delete the reference at the given path - - :param repo: - Repository to delete the reference from - - :param path: - Short or full path pointing to the reference, i.e. refs/myreference - or just "myreference", hence 'refs/' is implied. - Alternatively the symbolic reference to be deleted""" - full_ref_path = cls.to_full_path(path) - abs_path = osp.join(repo.common_dir, full_ref_path) - if osp.exists(abs_path): - os.remove(abs_path) - else: - # check packed refs - pack_file_path = cls._get_packed_refs_path(repo) - try: - with open(pack_file_path, 'rb') as reader: - new_lines = [] - made_change = False - dropped_last_line = False - for line in reader: - line = line.decode(defenc) - _, _, line_ref = line.partition(' ') - line_ref = line_ref.strip() - # keep line if it is a comment or if the ref to delete is not - # in the line - # If we deleted the last line and this one is a tag-reference object, - # we drop it as well - if (line.startswith('#') or full_ref_path != line_ref) and \ - (not dropped_last_line or dropped_last_line and not line.startswith('^')): - new_lines.append(line) - dropped_last_line = False - continue - # END skip comments and lines without our path - - # drop this line - made_change = True - dropped_last_line = True - - # write the new lines - if made_change: - # write-binary is required, otherwise windows will - # open the file in text mode and change LF to CRLF ! - with open(pack_file_path, 'wb') as fd: - fd.writelines(line.encode(defenc) for line in new_lines) - - except (OSError, IOError): - pass # it didn't exist at all - - # delete the reflog - reflog_path = RefLog.path(cls(repo, full_ref_path)) - if osp.isfile(reflog_path): - os.remove(reflog_path) - # END remove reflog - - @classmethod - def _create(cls, repo, path, resolve, reference, force, logmsg=None): - """internal method used to create a new symbolic reference. - If resolve is False, the reference will be taken as is, creating - a proper symbolic reference. Otherwise it will be resolved to the - corresponding object and a detached symbolic reference will be created - instead""" - git_dir = _git_dir(repo, path) - full_ref_path = cls.to_full_path(path) - abs_ref_path = osp.join(git_dir, full_ref_path) - - # figure out target data - target = reference - if resolve: - target = repo.rev_parse(str(reference)) - - if not force and osp.isfile(abs_ref_path): - target_data = str(target) - if isinstance(target, SymbolicReference): - target_data = target.path - if not resolve: - target_data = "ref: " + target_data - with open(abs_ref_path, 'rb') as fd: - existing_data = fd.read().decode(defenc).strip() - if existing_data != target_data: - raise OSError("Reference at %r does already exist, pointing to %r, requested was %r" % - (full_ref_path, existing_data, target_data)) - # END no force handling - - ref = cls(repo, full_ref_path) - ref.set_reference(target, logmsg) - return ref - - @classmethod - def create(cls, repo, path, reference='HEAD', force=False, logmsg=None, **kwargs): - """Create a new symbolic reference, hence a reference pointing to another reference. - - :param repo: - Repository to create the reference in - - :param path: - full path at which the new symbolic reference is supposed to be - created at, i.e. "NEW_HEAD" or "symrefs/my_new_symref" - - :param reference: - The reference to which the new symbolic reference should point to. - If it is a commit'ish, the symbolic ref will be detached. - - :param force: - if True, force creation even if a symbolic reference with that name already exists. - Raise OSError otherwise - - :param logmsg: - If not None, the message to append to the reflog. Otherwise no reflog - entry is written. - - :return: Newly created symbolic Reference - - :raise OSError: - If a (Symbolic)Reference with the same name but different contents - already exists. - - :note: This does not alter the current HEAD, index or Working Tree""" - return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg) - - def rename(self, new_path, force=False): - """Rename self to a new path - - :param new_path: - Either a simple name or a full path, i.e. new_name or features/new_name. - The prefix refs/ is implied for references and will be set as needed. - In case this is a symbolic ref, there is no implied prefix - - :param force: - If True, the rename will succeed even if a head with the target name - already exists. It will be overwritten in that case - - :return: self - :raise OSError: In case a file at path but a different contents already exists """ - new_path = self.to_full_path(new_path) - if self.path == new_path: - return self - - new_abs_path = osp.join(_git_dir(self.repo, new_path), new_path) - cur_abs_path = osp.join(_git_dir(self.repo, self.path), self.path) - if osp.isfile(new_abs_path): - if not force: - # if they point to the same file, its not an error - with open(new_abs_path, 'rb') as fd1: - f1 = fd1.read().strip() - with open(cur_abs_path, 'rb') as fd2: - f2 = fd2.read().strip() - if f1 != f2: - raise OSError("File at path %r already exists" % new_abs_path) - # else: we could remove ourselves and use the otherone, but - # but clarity we just continue as usual - # END not force handling - os.remove(new_abs_path) - # END handle existing target file - - dname = osp.dirname(new_abs_path) - if not osp.isdir(dname): - os.makedirs(dname) - # END create directory - - os.rename(cur_abs_path, new_abs_path) - self.path = new_path - - return self - - @classmethod - def _iter_items(cls, repo, common_path=None): - if common_path is None: - common_path = cls._common_path_default - rela_paths = set() - - # walk loose refs - # Currently we do not follow links - for root, dirs, files in os.walk(join_path_native(repo.common_dir, common_path)): - if 'refs' not in root.split(os.sep): # skip non-refs subfolders - refs_id = [d for d in dirs if d == 'refs'] - if refs_id: - dirs[0:] = ['refs'] - # END prune non-refs folders - - for f in files: - if f == 'packed-refs': - continue - abs_path = to_native_path_linux(join_path(root, f)) - rela_paths.add(abs_path.replace(to_native_path_linux(repo.common_dir) + '/', "")) - # END for each file in root directory - # END for each directory to walk - - # read packed refs - for _sha, rela_path in cls._iter_packed_refs(repo): - if rela_path.startswith(common_path): - rela_paths.add(rela_path) - # END relative path matches common path - # END packed refs reading - - # return paths in sorted order - for path in sorted(rela_paths): - try: - yield cls.from_path(repo, path) - except ValueError: - continue - # END for each sorted relative refpath - - @classmethod - def iter_items(cls, repo, common_path=None): - """Find all refs in the repository - - :param repo: is the Repo - - :param common_path: - Optional keyword argument to the path which is to be shared by all - returned Ref objects. - Defaults to class specific portion if None assuring that only - refs suitable for the actual class are returned. - - :return: - git.SymbolicReference[], each of them is guaranteed to be a symbolic - ref which is not detached and pointing to a valid ref - - List is lexicographically sorted - The returned objects represent actual subclasses, such as Head or TagReference""" - return (r for r in cls._iter_items(repo, common_path) if r.__class__ == SymbolicReference or not r.is_detached) - - @classmethod - def from_path(cls, repo, path): - """ - :param path: full .git-directory-relative path name to the Reference to instantiate - :note: use to_full_path() if you only have a partial path of a known Reference Type - :return: - Instance of type Reference, Head, or Tag - depending on the given path""" - if not path: - raise ValueError("Cannot create Reference from %r" % path) - - # Names like HEAD are inserted after the refs module is imported - we have an import dependency - # cycle and don't want to import these names in-function - from . import HEAD, Head, RemoteReference, TagReference, Reference - for ref_type in (HEAD, Head, RemoteReference, TagReference, Reference, SymbolicReference): - try: - instance = ref_type(repo, path) - if instance.__class__ == SymbolicReference and instance.is_detached: - raise ValueError("SymbolRef was detached, we drop it") - return instance - except ValueError: - pass - # END exception handling - # END for each type to try - raise ValueError("Could not find reference type suitable to handle path %r" % path) - - def is_remote(self): - """:return: True if this symbolic reference points to a remote branch""" - return self.path.startswith(self._remote_common_path_default + "/") diff --git a/setup.py b/setup.py index e01562e8..e8da06dc 100755 --- a/setup.py +++ b/setup.py @@ -56,7 +56,7 @@ def _stamp_version(filename): line = line.replace("'git'", "'%s'" % VERSION) found = True out.append(line) - except (IOError, OSError): + except OSError: print("Couldn't find file %s to stamp version" % filename, file=sys.stderr) if found: @@ -66,7 +66,7 @@ def _stamp_version(filename): print("WARNING: Couldn't find version line in file %s" % filename, file=sys.stderr) -def build_py_modules(basedir, excludes=[]): +def build_py_modules(basedir, excludes=()): # create list of py_modules from tree res = set() _prefix = os.path.basename(basedir) -- cgit v1.2.1 From 9a587e14d509cc77bf47b9591d1def3e5a1df570 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 17:08:05 +0100 Subject: Add flake8-bugbear and -comprehensions to test-requirements.txt --- requirements-dev.txt | 3 --- test-requirements.txt | 9 ++++++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 0ece0a65..e6d19427 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,8 +3,6 @@ # libraries for additional local testing/linting - to be added to test-requirements.txt when all pass -flake8-bugbear -flake8-comprehensions flake8-type-checking;python_version>="3.8" # checks for TYPE_CHECKING only imports # flake8-annotations # checks for presence of type annotations # flake8-rst-docstrings # checks docstrings are valid RST @@ -12,6 +10,5 @@ flake8-type-checking;python_version>="3.8" # checks for TYPE_CHECKING only # flake8-pytest-style # pytest-flake8 -pytest-sugar pytest-icdiff # pytest-profiling diff --git a/test-requirements.txt b/test-requirements.txt index 7397c373..eeee1811 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,9 +1,12 @@ ddt>=1.1.1 mypy + flake8 +flake8-bugbear +flake8-comprehensions + virtualenv + pytest pytest-cov -pytest-sugar -gitdb>=4.0.1,<5 -typing-extensions>=3.7.4.3;python_version<"3.10" +pytest-sugar \ No newline at end of file -- cgit v1.2.1 From f0bc672b6056778e358c8f5844b093d2742d00ab Mon Sep 17 00:00:00 2001 From: Dominic Date: Mon, 19 Jul 2021 17:11:57 +0100 Subject: Update README.md tidy up testing section --- README.md | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ad7aae51..2cd68d69 100644 --- a/README.md +++ b/README.md @@ -106,19 +106,20 @@ On *Windows*, make sure you have `git-daemon` in your PATH. For MINGW-git, the exists in `Git\mingw64\libexec\git-core\`; CYGWIN has no daemon, but should get along fine with MINGW's. -Ensure testing libraries are installed. In the root directory, run: `pip install test-requirements.txt` -Then, +Ensure testing libraries are installed. +In the root directory, run: `pip install -r test-requirements.txt` -To lint, run `flake8` -To typecheck, run `mypy -p git` -To test, `pytest` +To lint, run: `flake8` -Configuration for flake8 is in root/.flake8 file. -Configuration for mypy, pytest, coverage is in root/pyproject.toml. +To typecheck, run: `mypy -p git` -The same linting and testing will also be performed against different supported python versions -upon submitting a pull request (or on each push if you have a fork with a "main" branch). +To test, run: `pytest` + +Configuration for flake8 is in the root/.flake8 file. +Configurations for mypy, pytest and coverage.py are in root/pyproject.toml. +The same linting and testing will also be performed against different supported python versions +upon submitting a pull request (or on each push if you have a fork with a "main" branch and actions enabled). ### Contributions -- cgit v1.2.1 From 23066f6fe414ec809434727485136b7cd84aa5b8 Mon Sep 17 00:00:00 2001 From: Dominic Date: Mon, 19 Jul 2021 17:16:43 +0100 Subject: Update README.md Add paragraph space --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2cd68d69..5087dbcc 100644 --- a/README.md +++ b/README.md @@ -115,8 +115,9 @@ To typecheck, run: `mypy -p git` To test, run: `pytest` -Configuration for flake8 is in the root/.flake8 file. -Configurations for mypy, pytest and coverage.py are in root/pyproject.toml. +Configuration for flake8 is in the ./.flake8 file. + +Configurations for mypy, pytest and coverage.py are in ./pyproject.toml. The same linting and testing will also be performed against different supported python versions upon submitting a pull request (or on each push if you have a fork with a "main" branch and actions enabled). -- cgit v1.2.1 From 9e5e969479ec6018e1ba06b95bcdefca5b0082a4 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 19:10:45 +0100 Subject: Change remaining type comments to py3.6+ types --- .flake8 | 2 +- git/cmd.py | 16 ++++++++-------- git/compat.py | 2 +- git/config.py | 18 ++++++++++-------- git/diff.py | 6 +++--- git/index/base.py | 24 ++++++++++++------------ git/index/fun.py | 10 +++++----- git/objects/submodule/base.py | 3 ++- git/objects/tag.py | 4 ++-- git/objects/tree.py | 2 +- git/objects/util.py | 6 +++--- git/refs/tag.py | 2 +- git/remote.py | 12 ++++++------ git/repo/base.py | 20 ++++++++++---------- git/repo/fun.py | 9 +++++---- git/util.py | 14 +++++++------- 16 files changed, 77 insertions(+), 73 deletions(-) diff --git a/.flake8 b/.flake8 index ffa60483..9759dc83 100644 --- a/.flake8 +++ b/.flake8 @@ -19,7 +19,7 @@ enable-extensions = TC, TC2 # only needed for extensions not enabled by default ignore = E265,E266,E731,E704, W293, W504, ANN0 ANN1 ANN2, - TC0, TC1, TC2 + # TC0, TC1, TC2 # B, A, D, diff --git a/git/cmd.py b/git/cmd.py index 11c02afe..4d0e5cdd 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -565,11 +565,11 @@ class Git(LazyMixin): .git directory in case of bare repositories.""" super(Git, self).__init__() self._working_dir = expand_path(working_dir) - self._git_options = () # type: Union[List[str], Tuple[str, ...]] - self._persistent_git_options = [] # type: List[str] + self._git_options: Union[List[str], Tuple[str, ...]] = () + self._persistent_git_options: List[str] = [] # Extra environment variables to pass to git commands - self._environment = {} # type: Dict[str, str] + self._environment: Dict[str, str] = {} # cached command slots self.cat_file_header = None @@ -603,9 +603,9 @@ class Git(LazyMixin): process_version = self._call_process('version') # should be as default *args and **kwargs used version_numbers = process_version.split(' ')[2] - self._version_info = tuple( + self._version_info: Tuple[int, int, int, int] = tuple( int(n) for n in version_numbers.split('.')[:4] if n.isdigit() - ) # type: Tuple[int, int, int, int] # type: ignore + ) # type: ignore # use typeguard here else: super(Git, self)._set_cache_(attr) # END handle version info @@ -868,8 +868,8 @@ class Git(LazyMixin): # Wait for the process to return status = 0 - stdout_value = b'' # type: Union[str, bytes] - stderr_value = b'' # type: Union[str, bytes] + stdout_value: Union[str, bytes] = b'' + stderr_value: Union[str, bytes] = b'' newline = "\n" if universal_newlines else b"\n" try: if output_stream is None: @@ -1145,7 +1145,7 @@ class Git(LazyMixin): # required for command to separate refs on stdin, as bytes if isinstance(ref, bytes): # Assume 40 bytes hexsha - bin-to-ascii for some reason returns bytes, not text - refstr = ref.decode('ascii') # type: str + refstr: str = ref.decode('ascii') elif not isinstance(ref, str): refstr = str(ref) # could be ref-object else: diff --git a/git/compat.py b/git/compat.py index 187618a2..7a0a15d2 100644 --- a/git/compat.py +++ b/git/compat.py @@ -34,7 +34,7 @@ from git.types import TBD # --------------------------------------------------------------------------- -is_win = (os.name == 'nt') # type: bool +is_win: bool = (os.name == 'nt') is_posix = (os.name == 'posix') is_darwin = (os.name == 'darwin') defenc = sys.getfilesystemencoding() diff --git a/git/config.py b/git/config.py index e0a18ec8..345cb40e 100644 --- a/git/config.py +++ b/git/config.py @@ -208,7 +208,7 @@ class _OMD(OrderedDict): def getall(self, key: str) -> Any: return super(_OMD, self).__getitem__(key) - def items(self) -> List[Tuple[str, Any]]: # type: ignore ## mypy doesn't like overwriting supertype signitures + def items(self) -> List[Tuple[str, Any]]: # type: ignore[override] """List of (key, last value for key).""" return [(k, self[k]) for k in self] @@ -322,7 +322,7 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser)): # self._is_initialized = False self._merge_includes = merge_includes self._repo = repo - self._lock = None # type: Union['LockFile', None] + self._lock: Union['LockFile', None] = None self._acquire_lock() def _acquire_lock(self) -> None: @@ -545,13 +545,15 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser)): # return None self._is_initialized = True - files_to_read = [""] # type: List[Union[PathLike, IO]] ## just for types until 3.5 dropped - if isinstance(self._file_or_files, (str)): # replace with PathLike once 3.5 dropped - files_to_read = [self._file_or_files] # for str, as str is a type of Sequence + files_to_read: List[Union[PathLike, IO]] = [""] + if isinstance(self._file_or_files, (str, os.PathLike)): + # for str or Path, as str is a type of Sequence + files_to_read = [self._file_or_files] elif not isinstance(self._file_or_files, (tuple, list, Sequence)): - files_to_read = [self._file_or_files] # for IO or Path - else: - files_to_read = list(self._file_or_files) # for lists or tuples + # could merge with above isinstance once runtime type known + files_to_read = [self._file_or_files] + else: # for lists or tuples + files_to_read = list(self._file_or_files) # end assure we have a copy of the paths to handle seen = set(files_to_read) diff --git a/git/diff.py b/git/diff.py index f8c0c25f..98a5cfd9 100644 --- a/git/diff.py +++ b/git/diff.py @@ -351,13 +351,13 @@ class Diff(object): return hash(tuple(getattr(self, n) for n in self.__slots__)) def __str__(self) -> str: - h = "%s" # type: str + h: str = "%s" if self.a_blob: h %= self.a_blob.path elif self.b_blob: h %= self.b_blob.path - msg = '' # type: str + msg: str = '' line = None # temp line line_length = 0 # line length for b, n in zip((self.a_blob, self.b_blob), ('lhs', 'rhs')): @@ -449,7 +449,7 @@ class Diff(object): :return: git.DiffIndex """ ## FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise. - text_list = [] # type: List[bytes] + text_list: List[bytes] = [] handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False) # for now, we have to bake the stream diff --git a/git/index/base.py b/git/index/base.py index 220bdc85..6452419c 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -123,7 +123,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): self.repo = repo self.version = self._VERSION self._extension_data = b'' - self._file_path = file_path or self._index_path() # type: PathLike + self._file_path: PathLike = file_path or self._index_path() def _set_cache_(self, attr: str) -> None: if attr == "entries": @@ -136,7 +136,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): ok = True except OSError: # in new repositories, there may be no index, which means we are empty - self.entries = {} # type: Dict[Tuple[PathLike, StageType], IndexEntry] + self.entries: Dict[Tuple[PathLike, StageType], IndexEntry] = {} return None finally: if not ok: @@ -266,7 +266,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): # -i : ignore working tree status # --aggressive : handle more merge cases # -m : do an actual merge - args = ["--aggressive", "-i", "-m"] # type: List[Union[Treeish, str]] + args: List[Union[Treeish, str]] = ["--aggressive", "-i", "-m"] if base is not None: args.append(base) args.append(rhs) @@ -288,14 +288,14 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): New IndexFile instance. Its path will be undefined. If you intend to write such a merged Index, supply an alternate file_path to its 'write' method.""" - tree_sha_bytes = [to_bin_sha(str(t)) for t in tree_sha] # List[bytes] + tree_sha_bytes: List[bytes] = [to_bin_sha(str(t)) for t in tree_sha] base_entries = aggressive_tree_merge(repo.odb, tree_sha_bytes) inst = cls(repo) # convert to entries dict - entries = dict(zip( + entries: Dict[Tuple[PathLike, int], IndexEntry] = dict(zip( ((e.path, e.stage) for e in base_entries), - (IndexEntry.from_base(e) for e in base_entries))) # type: Dict[Tuple[PathLike, int], IndexEntry] + (IndexEntry.from_base(e) for e in base_entries))) inst.entries = entries return inst @@ -338,7 +338,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): if len(treeish) == 0 or len(treeish) > 3: raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish)) - arg_list = [] # type: List[Union[Treeish, str]] + arg_list: List[Union[Treeish, str]] = [] # ignore that working tree and index possibly are out of date if len(treeish) > 1: # drop unmerged entries when reading our index and merging @@ -445,7 +445,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): we will close stdin to break the pipe.""" fprogress(filepath, False, item) - rval = None # type: Union[None, str] + rval: Union[None, str] = None if proc.stdin is not None: try: @@ -492,7 +492,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): are at stage 3 will not have a stage 3 entry. """ is_unmerged_blob = lambda t: t[0] != 0 - path_map = {} # type: Dict[PathLike, List[Tuple[TBD, Blob]]] + path_map: Dict[PathLike, List[Tuple[TBD, Blob]]] = {} for stage, blob in self.iter_blobs(is_unmerged_blob): path_map.setdefault(blob.path, []).append((stage, blob)) # END for each unmerged blob @@ -624,8 +624,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): st = os.lstat(filepath) # handles non-symlinks as well if S_ISLNK(st.st_mode): # in PY3, readlink is string, but we need bytes. In PY2, it's just OS encoded bytes, we assume UTF-8 - open_stream = lambda: BytesIO(force_bytes(os.readlink(filepath), - encoding=defenc)) # type: Callable[[], BinaryIO] + open_stream: Callable[[], BinaryIO] = lambda: BytesIO(force_bytes(os.readlink(filepath), + encoding=defenc)) else: open_stream = lambda: open(filepath, 'rb') with open_stream() as stream: @@ -1160,7 +1160,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): proc = self.repo.git.checkout_index(args, **kwargs) # FIXME: Reading from GIL! make_exc = lambda: GitCommandError(("git-checkout-index",) + tuple(args), 128, proc.stderr.read()) - checked_out_files = [] # type: List[PathLike] + checked_out_files: List[PathLike] = [] for path in paths: co_path = to_native_path_linux(self._to_relative_path(path)) diff --git a/git/index/fun.py b/git/index/fun.py index e5e566a0..e071e15c 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -99,8 +99,8 @@ def run_commit_hook(name: str, index: 'IndexFile', *args: str) -> None: except Exception as ex: raise HookExecutionError(hp, ex) from ex else: - stdout_list = [] # type: List[str] - stderr_list = [] # type: List[str] + stdout_list: List[str] = [] + stderr_list: List[str] = [] handle_process_output(cmd, stdout_list.append, stderr_list.append, finalize_process) stdout = ''.join(stdout_list) stderr = ''.join(stderr_list) @@ -151,8 +151,8 @@ def write_cache(entries: Sequence[Union[BaseIndexEntry, 'IndexEntry']], stream: beginoffset = tell() write(entry[4]) # ctime write(entry[5]) # mtime - path_str = entry[3] # type: str - path = force_bytes(path_str, encoding=defenc) + path_str: str = entry[3] + path: bytes = force_bytes(path_str, encoding=defenc) plen = len(path) & CE_NAMEMASK # path length assert plen == len(path), "Path %s too long to fit into index" % entry[3] flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values @@ -210,7 +210,7 @@ def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'Inde * content_sha is a 20 byte sha on all cache file contents""" version, num_entries = read_header(stream) count = 0 - entries = {} # type: Dict[Tuple[PathLike, int], 'IndexEntry'] + entries: Dict[Tuple[PathLike, int], 'IndexEntry'] = {} read = stream.read tell = stream.tell diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 21cfcd5a..d5ba118f 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -475,7 +475,8 @@ class Submodule(IndexObject, TraversableIterableObj): sm._branch_path = br.path # we deliberately assume that our head matches our index ! - sm.binsha = mrepo.head.commit.binsha # type: ignore + if mrepo: + sm.binsha = mrepo.head.commit.binsha index.add([sm], write=True) return sm diff --git a/git/objects/tag.py b/git/objects/tag.py index 4a2fcb0d..7048eb40 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -51,7 +51,7 @@ class TagObject(base.Object): authored_date is in, in a format similar to time.altzone""" super(TagObject, self).__init__(repo, binsha) if object is not None: - self.object = object # type: Union['Commit', 'Blob', 'Tree', 'TagObject'] + self.object: Union['Commit', 'Blob', 'Tree', 'TagObject'] = object if tag is not None: self.tag = tag if tagger is not None: @@ -67,7 +67,7 @@ class TagObject(base.Object): """Cache all our attributes at once""" if attr in TagObject.__slots__: ostream = self.repo.odb.stream(self.binsha) - lines = ostream.read().decode(defenc, 'replace').splitlines() # type: List[str] + lines: List[str] = ostream.read().decode(defenc, 'replace').splitlines() _obj, hexsha = lines[0].split(" ") _type_token, type_name = lines[1].split(" ") diff --git a/git/objects/tree.py b/git/objects/tree.py index 0e3f44b9..dd1fe783 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -298,7 +298,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): See the ``TreeModifier`` for more information on how to alter the cache""" return TreeModifier(self._cache) - def traverse(self, # type: ignore # overrides super() + def traverse(self, # type: ignore[override] 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, diff --git a/git/objects/util.py b/git/objects/util.py index 04af3b83..ef1ae77b 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -285,7 +285,7 @@ class ProcessStreamAdapter(object): def __init__(self, process: 'Popen', stream_name: str) -> None: self._proc = process - self._stream = getattr(process, stream_name) # type: StringIO ## guess + self._stream: StringIO = getattr(process, stream_name) # guessed type def __getattr__(self, attr: str) -> Any: return getattr(self._stream, attr) @@ -356,7 +356,7 @@ class Traversable(Protocol): return out_list @ abstractmethod - def traverse(self, *args: Any, **kwargs) -> Any: + def traverse(self, *args: Any, **kwargs: Any) -> Any: """ """ warnings.warn("traverse() method should only be called from subclasses." "Calling from Traversable abstract class will raise NotImplementedError in 3.1.20" @@ -414,7 +414,7 @@ class Traversable(Protocol): ignore_self=False is_edge=False -> Iterator[Tuple[src, item]]""" visited = set() - stack = deque() # type: Deque[TraverseNT] + stack: Deque[TraverseNT] = deque() stack.append(TraverseNT(0, self, None)) # self is always depth level 0 def addToStack(stack: Deque[TraverseNT], diff --git a/git/refs/tag.py b/git/refs/tag.py index aa3b82a2..281ce09a 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -35,7 +35,7 @@ class TagReference(Reference): _common_path_default = Reference._common_path_default + "/" + _common_default @property - def commit(self) -> 'Commit': # type: ignore[override] # LazyMixin has unrelated + def commit(self) -> 'Commit': # type: ignore[override] # LazyMixin has unrelated comit method """:return: Commit object the tag ref points to :raise ValueError: if the tag points to a tree or blob""" diff --git a/git/remote.py b/git/remote.py index 0fcd49b5..d903552f 100644 --- a/git/remote.py +++ b/git/remote.py @@ -193,7 +193,7 @@ class PushInfo(IterableObj, object): # from_to handling from_ref_string, to_ref_string = from_to.split(':') if flags & cls.DELETED: - from_ref = None # type: Union[SymbolicReference, None] + from_ref: Union[SymbolicReference, None] = None else: if from_ref_string == "(delete)": from_ref = None @@ -201,7 +201,7 @@ class PushInfo(IterableObj, object): from_ref = Reference.from_path(remote.repo, from_ref_string) # commit handling, could be message or commit info - old_commit = None # type: Optional[str] + old_commit: Optional[str] = None if summary.startswith('['): if "[rejected]" in summary: flags |= cls.REJECTED @@ -259,14 +259,14 @@ class FetchInfo(IterableObj, object): _re_fetch_result = re.compile(r'^\s*(.) (\[?[\w\s\.$@]+\]?)\s+(.+) -> ([^\s]+)( \(.*\)?$)?') - _flag_map = { + _flag_map: Dict[flagKeyLiteral, int] = { '!': ERROR, '+': FORCED_UPDATE, '*': 0, '=': HEAD_UPTODATE, ' ': FAST_FORWARD, '-': TAG_UPDATE, - } # type: Dict[flagKeyLiteral, int] + } @ classmethod def refresh(cls) -> Literal[True]: @@ -359,7 +359,7 @@ class FetchInfo(IterableObj, object): # END control char exception handling # parse operation string for more info - makes no sense for symbolic refs, but we parse it anyway - old_commit = None # type: Union[Commit_ish, None] + old_commit: Union[Commit_ish, None] = None is_tag_operation = False if 'rejected' in operation: flags |= cls.REJECTED @@ -846,7 +846,7 @@ class Remote(LazyMixin, IterableObj): kwargs = add_progress(kwargs, self.repo.git, progress) if isinstance(refspec, list): - args = refspec # type: Sequence[Optional[str]] # should need this - check logic for passing None through + args: Sequence[Optional[str]] = refspec else: args = [refspec] diff --git a/git/repo/base.py b/git/repo/base.py index 29d08502..64f32bd3 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -83,10 +83,10 @@ class Repo(object): DAEMON_EXPORT_FILE = 'git-daemon-export-ok' git = cast('Git', None) # Must exist, or __del__ will fail in case we raise on `__init__()` - working_dir = None # type: Optional[PathLike] - _working_tree_dir = None # type: Optional[PathLike] - git_dir = "" # type: PathLike - _common_dir = "" # type: PathLike + working_dir: Optional[PathLike] = None + _working_tree_dir: Optional[PathLike] = None + git_dir: PathLike = "" + _common_dir: PathLike = "" # precompiled regex re_whitespace = re.compile(r'\s+') @@ -221,7 +221,7 @@ class Repo(object): self._working_tree_dir = None # END working dir handling - self.working_dir = self._working_tree_dir or self.common_dir # type: Optional[PathLike] + self.working_dir: Optional[PathLike] = self._working_tree_dir or self.common_dir self.git = self.GitCommandWrapperType(self.working_dir) # special handling, in special times @@ -591,7 +591,7 @@ class Repo(object): raise ValueError("Please specify at least two revs, got only %i" % len(rev)) # end handle input - res = [] # type: List[Union[Commit_ish, None]] + res: List[Union[Commit_ish, None]] = [] try: lines = self.git.merge_base(*rev, **kwargs).splitlines() # List[str] except GitCommandError as err: @@ -813,7 +813,7 @@ class Repo(object): line = next(stream) # when exhausted, causes a StopIteration, terminating this function except StopIteration: return - split_line = line.split() # type: Tuple[str, str, str, str] + split_line: Tuple[str, str, str, str] = line.split() hexsha, orig_lineno_str, lineno_str, num_lines_str = split_line lineno = int(lineno_str) num_lines = int(num_lines_str) @@ -879,10 +879,10 @@ class Repo(object): return self.blame_incremental(rev, file, **kwargs) data = self.git.blame(rev, '--', file, p=True, stdout_as_string=False, **kwargs) - commits = {} # type: Dict[str, Any] - blames = [] # type: List[List[Union[Optional['Commit'], List[str]]]] + commits: Dict[str, TBD] = {} + blames: List[List[Union[Optional['Commit'], List[str]]]] = [] - info = {} # type: Dict[str, Any] # use Any until TypedDict available + info: Dict[str, TBD] = {} # use Any until TypedDict available keepends = True for line in data.splitlines(keepends): diff --git a/git/repo/fun.py b/git/repo/fun.py index e96b62e0..a20e2ecb 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -1,5 +1,4 @@ """Package with general repository related functions""" -from git.refs.tag import Tag import os import stat from string import digits @@ -19,11 +18,13 @@ from git.cmd import Git # Typing ---------------------------------------------------------------------- from typing import Union, Optional, cast, TYPE_CHECKING -from git.types import PathLike + if TYPE_CHECKING: + from git.types import PathLike from .base import Repo from git.db import GitCmdObjectDB from git.objects import Commit, TagObject, Blob, Tree + from git.refs.tag import Tag # ---------------------------------------------------------------------------- @@ -122,7 +123,7 @@ def name_to_object(repo: 'Repo', name: str, return_ref: bool = False :param return_ref: if name specifies a reference, we will return the reference instead of the object. Otherwise it will raise BadObject or BadName """ - hexsha = None # type: Union[None, str, bytes] + hexsha: Union[None, str, bytes] = None # is it a hexsha ? Try the most common ones, which is 7 to 40 if repo.re_hexsha_shortened.match(name): @@ -162,7 +163,7 @@ def name_to_object(repo: 'Repo', name: str, return_ref: bool = False return Object.new_from_sha(repo, hex_to_bin(hexsha)) -def deref_tag(tag: Tag) -> 'TagObject': +def deref_tag(tag: 'Tag') -> 'TagObject': """Recursively dereference a tag and return the resulting object""" while True: try: diff --git a/git/util.py b/git/util.py index c04e2927..c0c0ecb7 100644 --- a/git/util.py +++ b/git/util.py @@ -267,7 +267,7 @@ def _cygexpath(drive: Optional[str], path: str) -> str: return p_str.replace('\\', '/') -_cygpath_parsers = ( +_cygpath_parsers: Tuple[Tuple[Pattern[str], Callable, bool], ...] = ( # See: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365247(v=vs.85).aspx # and: https://www.cygwin.com/cygwin-ug-net/using.html#unc-paths (re.compile(r"\\\\\?\\UNC\\([^\\]+)\\([^\\]+)(?:\\(.*))?"), @@ -294,7 +294,7 @@ _cygpath_parsers = ( (lambda url: url), False ), -) # type: Tuple[Tuple[Pattern[str], Callable, bool], ...] +) def cygpath(path: str) -> str: @@ -330,7 +330,7 @@ def decygpath(path: PathLike) -> str: #: Store boolean flags denoting if a specific Git executable #: is from a Cygwin installation (since `cache_lru()` unsupported on PY2). -_is_cygwin_cache = {} # type: Dict[str, Optional[bool]] +_is_cygwin_cache: Dict[str, Optional[bool]] = {} @overload @@ -462,10 +462,10 @@ class RemoteProgress(object): re_op_relative = re.compile(r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)") def __init__(self) -> None: - self._seen_ops = [] # type: List[int] - self._cur_line = None # type: Optional[str] - self.error_lines = [] # type: List[str] - self.other_lines = [] # type: List[str] + self._seen_ops: List[int] = [] + self._cur_line: Optional[str] = None + self.error_lines: List[str] = [] + self.other_lines: List[str] = [] def _parse_progress_line(self, line: AnyStr) -> None: """Parse progress information from the given line as retrieved by git-push -- cgit v1.2.1 From dcfedb27dc720683bbadb7353d96cfec2a99c476 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 19:49:16 +0100 Subject: update types in types.py --- .flake8 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.flake8 b/.flake8 index 9759dc83..e22e8de0 100644 --- a/.flake8 +++ b/.flake8 @@ -8,7 +8,7 @@ statistics = True # W293 = Blank line contains whitespace # W504 = Line break after operator # E704 = multiple statements in one line - used for @override -# TC002 = +# TC002 = move third party import to TYPE_CHECKING # ANN = flake8-annotations # TC, TC2 = flake8-type-checking # D = flake8-docstrings @@ -19,7 +19,7 @@ enable-extensions = TC, TC2 # only needed for extensions not enabled by default ignore = E265,E266,E731,E704, W293, W504, ANN0 ANN1 ANN2, - # TC0, TC1, TC2 + TC002, # TC0, TC1, TC2 # B, A, D, -- cgit v1.2.1 From 8900f2a5c6ab349af19960a333ee55718065304b Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 19:57:34 +0100 Subject: Make pathlike a forward ref --- .flake8 | 3 ++- git/repo/fun.py | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.flake8 b/.flake8 index e22e8de0..3cf342f9 100644 --- a/.flake8 +++ b/.flake8 @@ -19,7 +19,8 @@ enable-extensions = TC, TC2 # only needed for extensions not enabled by default ignore = E265,E266,E731,E704, W293, W504, ANN0 ANN1 ANN2, - TC002, # TC0, TC1, TC2 + TC002, + # TC0, TC1, TC2 # B, A, D, diff --git a/git/repo/fun.py b/git/repo/fun.py index a20e2ecb..7d5c7823 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -19,6 +19,7 @@ from git.cmd import Git from typing import Union, Optional, cast, TYPE_CHECKING + if TYPE_CHECKING: from git.types import PathLike from .base import Repo @@ -38,7 +39,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. @@ -60,7 +61,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) @@ -79,7 +80,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 -- cgit v1.2.1 From 017b0d4b19127868ccb8ee616f64734b48f6e620 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 20:08:07 +0100 Subject: Add a cast to git.cmd _version_info --- git/cmd.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 4d0e5cdd..4404981e 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -603,19 +603,19 @@ class Git(LazyMixin): process_version = self._call_process('version') # should be as default *args and **kwargs used version_numbers = process_version.split(' ')[2] - self._version_info: Tuple[int, int, int, int] = tuple( - int(n) for n in version_numbers.split('.')[:4] if n.isdigit() - ) # type: ignore # use typeguard here + self._version_info = cast(Tuple[int, int, int, int], + tuple(int(n) for n in version_numbers.split('.')[:4] if n.isdigit()) + ) else: super(Git, self)._set_cache_(attr) # END handle version info - @property + @ property def working_dir(self) -> Union[None, PathLike]: """:return: Git directory we are working on""" return self._working_dir - @property + @ property def version_info(self) -> Tuple[int, int, int, int]: """ :return: tuple(int, int, int, int) tuple with integers representing the major, minor @@ -623,7 +623,7 @@ class Git(LazyMixin): This value is generated on demand and is cached""" return self._version_info - @overload + @ overload def execute(self, command: Union[str, Sequence[Any]], *, @@ -631,7 +631,7 @@ class Git(LazyMixin): ) -> 'AutoInterrupt': ... - @overload + @ overload def execute(self, command: Union[str, Sequence[Any]], *, @@ -640,7 +640,7 @@ class Git(LazyMixin): ) -> Union[str, Tuple[int, str, str]]: ... - @overload + @ overload def execute(self, command: Union[str, Sequence[Any]], *, @@ -649,7 +649,7 @@ class Git(LazyMixin): ) -> Union[bytes, Tuple[int, bytes, str]]: ... - @overload + @ overload def execute(self, command: Union[str, Sequence[Any]], *, @@ -659,7 +659,7 @@ class Git(LazyMixin): ) -> str: ... - @overload + @ overload def execute(self, command: Union[str, Sequence[Any]], *, -- cgit v1.2.1 From 600df043e76924d43a4f9f88f4e3241740f34c77 Mon Sep 17 00:00:00 2001 From: Yobmod Date: Mon, 19 Jul 2021 20:26:53 +0100 Subject: Rmv old py2.7 __future__ imports --- git/index/__init__.py | 2 -- git/objects/__init__.py | 2 -- git/refs/__init__.py | 1 - git/repo/__init__.py | 1 - test/lib/helper.py | 2 -- test/performance/test_commit.py | 1 - test/performance/test_odb.py | 2 -- test/performance/test_streams.py | 2 -- test/test_commit.py | 2 -- 9 files changed, 15 deletions(-) diff --git a/git/index/__init__.py b/git/index/__init__.py index 2516f01f..96b721f0 100644 --- a/git/index/__init__.py +++ b/git/index/__init__.py @@ -1,6 +1,4 @@ """Initialize the index package""" # flake8: noqa -from __future__ import absolute_import - from .base import * from .typ import * diff --git a/git/objects/__init__.py b/git/objects/__init__.py index 897eb98f..1d0bb7a5 100644 --- a/git/objects/__init__.py +++ b/git/objects/__init__.py @@ -2,8 +2,6 @@ Import all submodules main classes into the package space """ # flake8: noqa -from __future__ import absolute_import - import inspect from .base import * diff --git a/git/refs/__init__.py b/git/refs/__init__.py index ded8b1f7..1486dffe 100644 --- a/git/refs/__init__.py +++ b/git/refs/__init__.py @@ -1,5 +1,4 @@ # flake8: noqa -from __future__ import absolute_import # import all modules in order, fix the names they require from .symbolic import * from .reference import * diff --git a/git/repo/__init__.py b/git/repo/__init__.py index 5619aa69..712df60d 100644 --- a/git/repo/__init__.py +++ b/git/repo/__init__.py @@ -1,4 +1,3 @@ """Initialize the Repo package""" # flake8: noqa -from __future__ import absolute_import from .base import * diff --git a/test/lib/helper.py b/test/lib/helper.py index 5dde7b04..632d6af9 100644 --- a/test/lib/helper.py +++ b/test/lib/helper.py @@ -3,8 +3,6 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from __future__ import print_function - import contextlib from functools import wraps import gc diff --git a/test/performance/test_commit.py b/test/performance/test_commit.py index 4617b052..8158a1e6 100644 --- a/test/performance/test_commit.py +++ b/test/performance/test_commit.py @@ -3,7 +3,6 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from __future__ import print_function from io import BytesIO from time import time import sys diff --git a/test/performance/test_odb.py b/test/performance/test_odb.py index 8bd614f2..c9521c56 100644 --- a/test/performance/test_odb.py +++ b/test/performance/test_odb.py @@ -1,6 +1,4 @@ """Performance tests for object store""" -from __future__ import print_function - import sys from time import time diff --git a/test/performance/test_streams.py b/test/performance/test_streams.py index edf32c91..28e6b13e 100644 --- a/test/performance/test_streams.py +++ b/test/performance/test_streams.py @@ -1,6 +1,4 @@ """Performance data streaming performance""" -from __future__ import print_function - import os import subprocess import sys diff --git a/test/test_commit.py b/test/test_commit.py index 670068e5..67dc7d73 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -4,8 +4,6 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php -from __future__ import print_function - from datetime import datetime from io import BytesIO import re -- cgit v1.2.1