summaryrefslogtreecommitdiff
path: root/git
diff options
context:
space:
mode:
authorSebastian Thiel <sebastian.thiel@icloud.com>2022-05-18 07:43:53 +0800
committerSebastian Thiel <sebastian.thiel@icloud.com>2022-05-18 07:43:53 +0800
commit21ec529987d10e0010badd37f8da3274167d436f (patch)
treea3394cfe902ce7edd07c89420c21c13274a2d295 /git
parentb30720ee4d9762a03eae4fa7cfa4b0190d81784d (diff)
downloadgitpython-21ec529987d10e0010badd37f8da3274167d436f.tar.gz
Run everything through 'black'
That way people who use it won't be deterred, while it unifies style everywhere.
Diffstat (limited to 'git')
-rw-r--r--git/__init__.py54
-rw-r--r--git/cmd.py592
-rw-r--r--git/compat.py37
-rw-r--r--git/config.py274
-rw-r--r--git/db.py15
-rw-r--r--git/diff.py377
-rw-r--r--git/exc.py78
m---------git/ext/gitdb0
-rw-r--r--git/index/base.py479
-rw-r--r--git/index/fun.py185
-rw-r--r--git/index/typ.py67
-rw-r--r--git/index/util.py35
-rw-r--r--git/objects/__init__.py10
-rw-r--r--git/objects/base.py61
-rw-r--r--git/objects/blob.py7
-rw-r--r--git/objects/commit.py339
-rw-r--r--git/objects/fun.py75
-rw-r--r--git/objects/submodule/base.py536
-rw-r--r--git/objects/submodule/root.py204
-rw-r--r--git/objects/submodule/util.py40
-rw-r--r--git/objects/tag.py50
-rw-r--r--git/objects/tree.py158
-rw-r--r--git/objects/util.py362
-rw-r--r--git/refs/head.py87
-rw-r--r--git/refs/log.py117
-rw-r--r--git/refs/reference.py58
-rw-r--r--git/refs/remote.py21
-rw-r--r--git/refs/symbolic.py266
-rw-r--r--git/refs/tag.py42
-rw-r--r--git/remote.py538
-rw-r--r--git/repo/base.py582
-rw-r--r--git/repo/fun.py131
-rw-r--r--git/types.py61
-rw-r--r--git/util.py453
34 files changed, 4140 insertions, 2251 deletions
diff --git a/git/__init__.py b/git/__init__.py
index ae9254a2..3f26886f 100644
--- a/git/__init__.py
+++ b/git/__init__.py
@@ -4,8 +4,8 @@
# This module is part of GitPython and is released under
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
# flake8: noqa
-#@PydevCodeAnalysisIgnore
-from git.exc import * # @NoMove @IgnorePep8
+# @PydevCodeAnalysisIgnore
+from git.exc import * # @NoMove @IgnorePep8
import inspect
import os
import sys
@@ -14,14 +14,14 @@ import os.path as osp
from typing import Optional
from git.types import PathLike
-__version__ = 'git'
+__version__ = "git"
-#{ Initialization
+# { Initialization
def _init_externals() -> None:
"""Initialize external projects by putting them into the path"""
- if __version__ == 'git' and 'PYOXIDIZER' not in os.environ:
- sys.path.insert(1, osp.join(osp.dirname(__file__), 'ext', 'gitdb'))
+ if __version__ == "git" and "PYOXIDIZER" not in os.environ:
+ sys.path.insert(1, osp.join(osp.dirname(__file__), "ext", "gitdb"))
try:
import gitdb
@@ -29,26 +29,27 @@ def _init_externals() -> None:
raise ImportError("'gitdb' could not be found in your PYTHONPATH") from e
# END verify import
-#} END initialization
+
+# } END initialization
#################
_init_externals()
#################
-#{ Imports
+# { Imports
try:
from git.config import GitConfigParser # @NoMove @IgnorePep8
- from git.objects import * # @NoMove @IgnorePep8
- from git.refs import * # @NoMove @IgnorePep8
- from git.diff import * # @NoMove @IgnorePep8
- from git.db import * # @NoMove @IgnorePep8
- from git.cmd import Git # @NoMove @IgnorePep8
- from git.repo import Repo # @NoMove @IgnorePep8
- from git.remote import * # @NoMove @IgnorePep8
- from git.index import * # @NoMove @IgnorePep8
- from git.util import ( # @NoMove @IgnorePep8
+ from git.objects import * # @NoMove @IgnorePep8
+ from git.refs import * # @NoMove @IgnorePep8
+ from git.diff import * # @NoMove @IgnorePep8
+ from git.db import * # @NoMove @IgnorePep8
+ from git.cmd import Git # @NoMove @IgnorePep8
+ from git.repo import Repo # @NoMove @IgnorePep8
+ from git.remote import * # @NoMove @IgnorePep8
+ from git.index import * # @NoMove @IgnorePep8
+ from git.util import ( # @NoMove @IgnorePep8
LockFile,
BlockingLockFile,
Stats,
@@ -56,15 +57,18 @@ try:
rmtree,
)
except GitError as exc:
- raise ImportError('%s: %s' % (exc.__class__.__name__, exc)) from exc
+ raise ImportError("%s: %s" % (exc.__class__.__name__, exc)) from exc
-#} END imports
+# } END imports
-__all__ = [name for name, obj in locals().items()
- if not (name.startswith('_') or inspect.ismodule(obj))]
+__all__ = [
+ name
+ for name, obj in locals().items()
+ if not (name.startswith("_") or inspect.ismodule(obj))
+]
-#{ Initialize git executable path
+# { Initialize git executable path
GIT_OK = None
@@ -79,12 +83,14 @@ def refresh(path: Optional[PathLike] = None) -> None:
return
GIT_OK = True
-#} END initialize git executable path
+
+
+# } END initialize git executable path
#################
try:
refresh()
except Exception as exc:
- raise ImportError('Failed to initialize: {0}'.format(exc)) from exc
+ raise ImportError("Failed to initialize: {0}".format(exc)) from exc
#################
diff --git a/git/cmd.py b/git/cmd.py
index 1ddf9e03..12409b0c 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -9,12 +9,7 @@ import io
import logging
import os
import signal
-from subprocess import (
- call,
- Popen,
- PIPE,
- DEVNULL
-)
+from subprocess import call, Popen, PIPE, DEVNULL
import subprocess
import threading
from textwrap import dedent
@@ -29,10 +24,7 @@ from git.compat import (
from git.exc import CommandError
from git.util import is_cygwin_git, cygpath, expand_path, remove_password_if_present
-from .exc import (
- GitCommandError,
- GitCommandNotFound
-)
+from .exc import GitCommandError, GitCommandNotFound
from .util import (
LazyMixin,
stream_copy,
@@ -40,8 +32,24 @@ from .util import (
# typing ---------------------------------------------------------------------------
-from typing import (Any, AnyStr, BinaryIO, Callable, Dict, IO, Iterator, List, Mapping,
- Sequence, TYPE_CHECKING, TextIO, Tuple, Union, cast, overload)
+from typing import (
+ Any,
+ AnyStr,
+ BinaryIO,
+ Callable,
+ Dict,
+ IO,
+ Iterator,
+ List,
+ Mapping,
+ Sequence,
+ TYPE_CHECKING,
+ TextIO,
+ Tuple,
+ Union,
+ cast,
+ overload,
+)
from git.types import PathLike, Literal, TBD
@@ -52,15 +60,26 @@ if TYPE_CHECKING:
# ---------------------------------------------------------------------------------
-execute_kwargs = {'istream', 'with_extended_output',
- 'with_exceptions', 'as_process', 'stdout_as_string',
- 'output_stream', 'with_stdout', 'kill_after_timeout',
- 'universal_newlines', 'shell', 'env', 'max_chunk_size', 'strip_newline_in_stdout'}
+execute_kwargs = {
+ "istream",
+ "with_extended_output",
+ "with_exceptions",
+ "as_process",
+ "stdout_as_string",
+ "output_stream",
+ "with_stdout",
+ "kill_after_timeout",
+ "universal_newlines",
+ "shell",
+ "env",
+ "max_chunk_size",
+ "strip_newline_in_stdout",
+}
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
-__all__ = ('Git',)
+__all__ = ("Git",)
# ==============================================================================
@@ -69,18 +88,24 @@ __all__ = ('Git',)
# Documentation
## @{
-def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
- stdout_handler: Union[None,
- Callable[[AnyStr], None],
- Callable[[List[AnyStr]], None],
- Callable[[bytes, 'Repo', 'DiffIndex'], None]],
- stderr_handler: Union[None,
- Callable[[AnyStr], None],
- Callable[[List[AnyStr]], None]],
- finalizer: Union[None,
- Callable[[Union[subprocess.Popen, 'Git.AutoInterrupt']], None]] = None,
- decode_streams: bool = True,
- kill_after_timeout: Union[None, float] = None) -> None:
+
+def handle_process_output(
+ process: "Git.AutoInterrupt" | Popen,
+ stdout_handler: Union[
+ None,
+ Callable[[AnyStr], None],
+ Callable[[List[AnyStr]], None],
+ Callable[[bytes, "Repo", "DiffIndex"], None],
+ ],
+ stderr_handler: Union[
+ None, Callable[[AnyStr], None], Callable[[List[AnyStr]], None]
+ ],
+ finalizer: Union[
+ None, Callable[[Union[subprocess.Popen, "Git.AutoInterrupt"]], None]
+ ] = None,
+ decode_streams: bool = True,
+ kill_after_timeout: Union[None, float] = None,
+) -> None:
"""Registers for notifications to learn that process output is ready to read, and dispatches lines to
the respective line handlers.
This function returns once the finalizer returns
@@ -101,8 +126,13 @@ def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
should be killed.
"""
# Use 2 "pump" threads and wait for both to finish.
- def pump_stream(cmdline: List[str], name: str, stream: Union[BinaryIO, TextIO], is_decode: bool,
- handler: Union[None, Callable[[Union[bytes, str]], None]]) -> None:
+ def pump_stream(
+ cmdline: List[str],
+ name: str,
+ stream: Union[BinaryIO, TextIO],
+ is_decode: bool,
+ handler: Union[None, Callable[[Union[bytes, str]], None]],
+ ) -> None:
try:
for line in stream:
if handler:
@@ -114,21 +144,25 @@ def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
handler(line)
except Exception as ex:
- log.error(f"Pumping {name!r} of cmd({remove_password_if_present(cmdline)}) failed due to: {ex!r}")
+ log.error(
+ f"Pumping {name!r} of cmd({remove_password_if_present(cmdline)}) failed due to: {ex!r}"
+ )
if "I/O operation on closed file" not in str(ex):
# Only reraise if the error was not due to the stream closing
- raise CommandError([f'<{name}-pump>'] + remove_password_if_present(cmdline), ex) from ex
+ raise CommandError(
+ [f"<{name}-pump>"] + remove_password_if_present(cmdline), ex
+ ) from ex
finally:
stream.close()
- if hasattr(process, 'proc'):
- process = cast('Git.AutoInterrupt', process)
- cmdline: str | Tuple[str, ...] | List[str] = getattr(process.proc, 'args', '')
+ if hasattr(process, "proc"):
+ process = cast("Git.AutoInterrupt", process)
+ cmdline: str | Tuple[str, ...] | List[str] = getattr(process.proc, "args", "")
p_stdout = process.proc.stdout if process.proc else None
p_stderr = process.proc.stderr if process.proc else None
else:
process = cast(Popen, process)
- cmdline = getattr(process, 'args', '')
+ cmdline = getattr(process, "args", "")
p_stdout = process.stdout
p_stderr = process.stderr
@@ -137,15 +171,16 @@ def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
pumps: List[Tuple[str, IO, Callable[..., None] | None]] = []
if p_stdout:
- pumps.append(('stdout', p_stdout, stdout_handler))
+ pumps.append(("stdout", p_stdout, stdout_handler))
if p_stderr:
- pumps.append(('stderr', p_stderr, stderr_handler))
+ pumps.append(("stderr", p_stderr, stderr_handler))
threads: List[threading.Thread] = []
for name, stream, handler in pumps:
- t = threading.Thread(target=pump_stream,
- args=(cmdline, name, stream, decode_streams, handler))
+ t = threading.Thread(
+ target=pump_stream, args=(cmdline, name, stream, decode_streams, handler)
+ )
t.daemon = True
t.start()
threads.append(t)
@@ -158,12 +193,15 @@ def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
if isinstance(process, Git.AutoInterrupt):
process._terminate()
else: # Don't want to deal with the other case
- raise RuntimeError("Thread join() timed out in cmd.handle_process_output()."
- f" kill_after_timeout={kill_after_timeout} seconds")
+ raise RuntimeError(
+ "Thread join() timed out in cmd.handle_process_output()."
+ f" kill_after_timeout={kill_after_timeout} seconds"
+ )
if stderr_handler:
error_str: Union[str, bytes] = (
"error: process killed because it timed out."
- f" kill_after_timeout={kill_after_timeout} seconds")
+ f" kill_after_timeout={kill_after_timeout} seconds"
+ )
if not decode_streams and isinstance(p_stderr, BinaryIO):
# Assume stderr_handler needs binary input
error_str = cast(str, error_str)
@@ -179,19 +217,22 @@ def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
def dashify(string: str) -> str:
- return string.replace('_', '-')
+ return string.replace("_", "-")
def slots_to_dict(self: object, exclude: Sequence[str] = ()) -> Dict[str, Any]:
return {s: getattr(self, s) for s in self.__slots__ if s not in exclude}
-def dict_to_slots_and__excluded_are_none(self: object, d: Mapping[str, Any], excluded: Sequence[str] = ()) -> None:
+def dict_to_slots_and__excluded_are_none(
+ self: object, d: Mapping[str, Any], excluded: Sequence[str] = ()
+) -> None:
for k, v in d.items():
setattr(self, k, v)
for k in excluded:
setattr(self, k, None)
+
## -- End Utilities -- @}
@@ -200,8 +241,11 @@ CREATE_NO_WINDOW = 0x08000000
## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
# see https://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
-PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined]
- if is_win else 0) # mypy error if not windows
+PROC_CREATIONFLAGS = (
+ CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP # type: ignore[attr-defined]
+ if is_win
+ else 0
+) # mypy error if not windows
class Git(LazyMixin):
@@ -220,10 +264,18 @@ class Git(LazyMixin):
of the command to stdout.
Set its value to 'full' to see details about the returned values.
"""
- __slots__ = ("_working_dir", "cat_file_all", "cat_file_header", "_version_info",
- "_git_options", "_persistent_git_options", "_environment")
- _excluded_ = ('cat_file_all', 'cat_file_header', '_version_info')
+ __slots__ = (
+ "_working_dir",
+ "cat_file_all",
+ "cat_file_header",
+ "_version_info",
+ "_git_options",
+ "_persistent_git_options",
+ "_environment",
+ )
+
+ _excluded_ = ("cat_file_all", "cat_file_header", "_version_info")
def __getstate__(self) -> Dict[str, Any]:
return slots_to_dict(self, exclude=self._excluded_)
@@ -233,7 +285,7 @@ class Git(LazyMixin):
# CONFIGURATION
- git_exec_name = "git" # default that should work on linux and windows
+ git_exec_name = "git" # default that should work on linux and windows
# Enables debugging of GitPython's git commands
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
@@ -282,13 +334,18 @@ class Git(LazyMixin):
# warn or raise exception if test failed
if not has_git:
- err = dedent("""\
+ err = (
+ dedent(
+ """\
Bad git executable.
The git executable must be specified in one of the following ways:
- be included in your $PATH
- be set via $%s
- explicitly set via git.refresh()
- """) % cls._git_exec_env_var
+ """
+ )
+ % cls._git_exec_env_var
+ )
# revert to whatever the old_git was
cls.GIT_PYTHON_GIT_EXECUTABLE = old_git
@@ -314,7 +371,9 @@ class Git(LazyMixin):
if mode in quiet:
pass
elif mode in warn or mode in error:
- err = dedent("""\
+ err = (
+ dedent(
+ """\
%s
All git commands will error until this is rectified.
@@ -326,32 +385,42 @@ class Git(LazyMixin):
Example:
export %s=%s
- """) % (
- err,
- cls._refresh_env_var,
- "|".join(quiet),
- "|".join(warn),
- "|".join(error),
- cls._refresh_env_var,
- quiet[0])
+ """
+ )
+ % (
+ err,
+ cls._refresh_env_var,
+ "|".join(quiet),
+ "|".join(warn),
+ "|".join(error),
+ cls._refresh_env_var,
+ quiet[0],
+ )
+ )
if mode in warn:
print("WARNING: %s" % err)
else:
raise ImportError(err)
else:
- err = dedent("""\
+ err = (
+ dedent(
+ """\
%s environment variable has been set but it has been set with an invalid value.
Use only the following values:
- %s: for no warning or exception
- %s: for a printed warning
- %s: for a raised exception
- """) % (
- cls._refresh_env_var,
- "|".join(quiet),
- "|".join(warn),
- "|".join(error))
+ """
+ )
+ % (
+ cls._refresh_env_var,
+ "|".join(quiet),
+ "|".join(warn),
+ "|".join(error),
+ )
+ )
raise ImportError(err)
# we get here if this was the init refresh and the refresh mode
@@ -395,7 +464,7 @@ class Git(LazyMixin):
Hence we undo the escaping just to be sure.
"""
url = os.path.expandvars(url)
- if url.startswith('~'):
+ if url.startswith("~"):
url = os.path.expanduser(url)
url = url.replace("\\\\", "\\").replace("\\", "/")
return url
@@ -441,7 +510,7 @@ class Git(LazyMixin):
log.info("Ignored error after process had died: %r", ex)
# can be that nothing really exists anymore ...
- if os is None or getattr(os, 'kill', None) is None:
+ if os is None or getattr(os, "kill", None) is None:
return None
# try to kill it
@@ -458,7 +527,10 @@ class Git(LazyMixin):
# we simply use the shell and redirect to nul. Its slower than CreateProcess, question
# is whether we really want to see all these messages. Its annoying no matter what.
if is_win:
- call(("TASKKILL /F /T /PID %s 2>nul 1>nul" % str(proc.pid)), shell=True)
+ call(
+ ("TASKKILL /F /T /PID %s 2>nul 1>nul" % str(proc.pid)),
+ shell=True,
+ )
# END exception handling
def __del__(self) -> None:
@@ -468,15 +540,15 @@ class Git(LazyMixin):
return getattr(self.proc, attr)
# TODO: Bad choice to mimic `proc.wait()` but with different args.
- def wait(self, stderr: Union[None, str, bytes] = b'') -> int:
+ def wait(self, stderr: Union[None, str, bytes] = b"") -> int:
"""Wait for the process and return its status code.
:param stderr: Previously read value of stderr, in case stderr is already closed.
:warn: may deadlock if output or error pipes are used and not handled separately.
:raise GitCommandError: if the return status is not 0"""
if stderr is None:
- stderr_b = b''
- stderr_b = force_bytes(data=stderr, encoding='utf-8')
+ stderr_b = b""
+ stderr_b = force_bytes(data=stderr, encoding="utf-8")
status: Union[int, None]
if self.proc is not None:
status = self.proc.wait()
@@ -485,21 +557,25 @@ class Git(LazyMixin):
status = self.status
p_stderr = None
- def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
+ def read_all_from_possibly_closed_stream(
+ stream: Union[IO[bytes], None]
+ ) -> bytes:
if stream:
try:
return stderr_b + force_bytes(stream.read())
except ValueError:
- return stderr_b or b''
+ return stderr_b or b""
else:
- return stderr_b or b''
+ return stderr_b or b""
# END status handling
if status != 0:
errstr = read_all_from_possibly_closed_stream(p_stderr)
- log.debug('AutoInterrupt wait stderr: %r' % (errstr,))
- raise GitCommandError(remove_password_if_present(self.args), status, errstr)
+ log.debug("AutoInterrupt wait stderr: %r" % (errstr,))
+ raise GitCommandError(
+ remove_password_if_present(self.args), status, errstr
+ )
return status
# END auto interrupt
@@ -513,12 +589,12 @@ class Git(LazyMixin):
If not all data is read to the end of the objects's lifetime, we read the
rest to assure the underlying stream continues to work"""
- __slots__: Tuple[str, ...] = ('_stream', '_nbr', '_size')
+ __slots__: Tuple[str, ...] = ("_stream", "_nbr", "_size")
def __init__(self, size: int, stream: IO[bytes]) -> None:
self._stream = stream
self._size = size
- self._nbr = 0 # num bytes read
+ self._nbr = 0 # num bytes read
# special case: if the object is empty, has null bytes, get the
# final newline right away.
@@ -529,7 +605,7 @@ class Git(LazyMixin):
def read(self, size: int = -1) -> bytes:
bytes_left = self._size - self._nbr
if bytes_left == 0:
- return b''
+ return b""
if size > -1:
# assure we don't try to read past our limit
size = min(bytes_left, size)
@@ -542,13 +618,13 @@ class Git(LazyMixin):
# check for depletion, read our final byte to make the stream usable by others
if self._size - self._nbr == 0:
- self._stream.read(1) # final newline
+ self._stream.read(1) # final newline
# END finish reading
return data
def readline(self, size: int = -1) -> bytes:
if self._nbr == self._size:
- return b''
+ return b""
# clamp size to lowest allowed value
bytes_left = self._size - self._nbr
@@ -589,7 +665,7 @@ class Git(LazyMixin):
return out
# skipcq: PYL-E0301
- def __iter__(self) -> 'Git.CatFileContentStream':
+ def __iter__(self) -> "Git.CatFileContentStream":
return self
def __next__(self) -> bytes:
@@ -634,7 +710,7 @@ class Git(LazyMixin):
"""A convenience method as it allows to call the command as if it was
an object.
:return: Callable object that will execute call _call_process with your arguments."""
- if name[0] == '_':
+ if name[0] == "_":
return LazyMixin.__getattr__(self, name)
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
@@ -650,27 +726,31 @@ class Git(LazyMixin):
"""
self._persistent_git_options = self.transform_kwargs(
- split_single_char_options=True, **kwargs)
+ split_single_char_options=True, **kwargs
+ )
def _set_cache_(self, attr: str) -> None:
- if attr == '_version_info':
+ if attr == "_version_info":
# We only use the first 4 numbers, as everything else could be strings in fact (on windows)
- process_version = self._call_process('version') # should be as default *args and **kwargs used
- version_numbers = process_version.split(' ')[2]
-
- self._version_info = cast(Tuple[int, int, int, int],
- tuple(int(n) for n in version_numbers.split('.')[:4] if n.isdigit())
- )
+ process_version = self._call_process(
+ "version"
+ ) # should be as default *args and **kwargs used
+ version_numbers = process_version.split(" ")[2]
+
+ 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
@@ -678,69 +758,72 @@ class Git(LazyMixin):
This value is generated on demand and is cached"""
return self._version_info
- @ overload
- def execute(self,
- command: Union[str, Sequence[Any]],
- *,
- as_process: Literal[True]
- ) -> 'AutoInterrupt':
+ @overload
+ def execute(
+ self, command: Union[str, Sequence[Any]], *, as_process: Literal[True]
+ ) -> "AutoInterrupt":
...
- @ overload
- def execute(self,
- command: Union[str, Sequence[Any]],
- *,
- as_process: Literal[False] = False,
- stdout_as_string: Literal[True]
- ) -> Union[str, Tuple[int, str, str]]:
+ @overload
+ def execute(
+ self,
+ command: Union[str, Sequence[Any]],
+ *,
+ as_process: Literal[False] = False,
+ stdout_as_string: Literal[True],
+ ) -> Union[str, Tuple[int, str, str]]:
...
- @ overload
- def execute(self,
- command: Union[str, Sequence[Any]],
- *,
- as_process: Literal[False] = False,
- stdout_as_string: Literal[False] = False
- ) -> Union[bytes, Tuple[int, bytes, str]]:
+ @overload
+ def execute(
+ self,
+ command: Union[str, Sequence[Any]],
+ *,
+ as_process: Literal[False] = False,
+ stdout_as_string: Literal[False] = False,
+ ) -> Union[bytes, Tuple[int, bytes, str]]:
...
- @ overload
- def execute(self,
- command: Union[str, Sequence[Any]],
- *,
- with_extended_output: Literal[False],
- as_process: Literal[False],
- stdout_as_string: Literal[True]
- ) -> str:
+ @overload
+ def execute(
+ self,
+ command: Union[str, Sequence[Any]],
+ *,
+ with_extended_output: Literal[False],
+ as_process: Literal[False],
+ stdout_as_string: Literal[True],
+ ) -> str:
...
- @ overload
- def execute(self,
- command: Union[str, Sequence[Any]],
- *,
- with_extended_output: Literal[False],
- as_process: Literal[False],
- stdout_as_string: Literal[False]
- ) -> bytes:
+ @overload
+ def execute(
+ self,
+ command: Union[str, Sequence[Any]],
+ *,
+ with_extended_output: Literal[False],
+ as_process: Literal[False],
+ stdout_as_string: Literal[False],
+ ) -> bytes:
...
- def execute(self,
- command: Union[str, Sequence[Any]],
- istream: Union[None, BinaryIO] = None,
- with_extended_output: bool = False,
- with_exceptions: bool = True,
- as_process: bool = False,
- output_stream: Union[None, BinaryIO] = None,
- stdout_as_string: bool = True,
- kill_after_timeout: Union[None, float] = None,
- with_stdout: bool = True,
- universal_newlines: bool = False,
- shell: Union[None, bool] = None,
- env: Union[None, Mapping[str, str]] = None,
- max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
- strip_newline_in_stdout: bool = True,
- **subprocess_kwargs: Any
- ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
+ def execute(
+ self,
+ command: Union[str, Sequence[Any]],
+ istream: Union[None, BinaryIO] = None,
+ with_extended_output: bool = False,
+ with_exceptions: bool = True,
+ as_process: bool = False,
+ output_stream: Union[None, BinaryIO] = None,
+ stdout_as_string: bool = True,
+ kill_after_timeout: Union[None, float] = None,
+ with_stdout: bool = True,
+ universal_newlines: bool = False,
+ shell: Union[None, bool] = None,
+ env: Union[None, Mapping[str, str]] = None,
+ max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
+ strip_newline_in_stdout: bool = True,
+ **subprocess_kwargs: Any,
+ ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command on the shell and consumes and returns
the returned information (stdout)
@@ -831,8 +914,8 @@ class Git(LazyMixin):
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
- if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != 'full' or as_process):
- log.info(' '.join(redacted_command))
+ if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
+ log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
@@ -858,33 +941,47 @@ class Git(LazyMixin):
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
- raise GitCommandError(redacted_command, '"kill_after_timeout" feature is not supported on Windows.')
+ raise GitCommandError(
+ redacted_command,
+ '"kill_after_timeout" feature is not supported on Windows.',
+ )
else:
- cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
+ cmd_not_found_exception = (
+ FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
+ )
# end handle
- stdout_sink = (PIPE
- if with_stdout
- else getattr(subprocess, 'DEVNULL', None) or open(os.devnull, 'wb'))
+ stdout_sink = (
+ PIPE
+ if with_stdout
+ else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
+ )
istream_ok = "None"
if istream:
istream_ok = "<valid stream>"
- log.debug("Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)",
- redacted_command, cwd, universal_newlines, shell, istream_ok)
+ log.debug(
+ "Popen(%s, cwd=%s, universal_newlines=%s, shell=%s, istream=%s)",
+ redacted_command,
+ cwd,
+ universal_newlines,
+ shell,
+ istream_ok,
+ )
try:
- proc = Popen(command,
- env=env,
- cwd=cwd,
- bufsize=-1,
- stdin=istream or DEVNULL,
- stderr=PIPE,
- stdout=stdout_sink,
- shell=shell is not None and shell or self.USE_SHELL,
- close_fds=is_posix, # unsupported on windows
- universal_newlines=universal_newlines,
- creationflags=PROC_CREATIONFLAGS,
- **subprocess_kwargs
- )
+ proc = Popen(
+ command,
+ env=env,
+ cwd=cwd,
+ bufsize=-1,
+ stdin=istream or DEVNULL,
+ stderr=PIPE,
+ stdout=stdout_sink,
+ shell=shell is not None and shell or self.USE_SHELL,
+ close_fds=is_posix, # unsupported on windows
+ universal_newlines=universal_newlines,
+ creationflags=PROC_CREATIONFLAGS,
+ **subprocess_kwargs,
+ )
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
@@ -897,9 +994,12 @@ class Git(LazyMixin):
return self.AutoInterrupt(proc, command)
def _kill_process(pid: int) -> None:
- """ Callback method to kill a process. """
- p = Popen(['ps', '--ppid', str(pid)], stdout=PIPE,
- creationflags=PROC_CREATIONFLAGS)
+ """Callback method to kill a process."""
+ p = Popen(
+ ["ps", "--ppid", str(pid)],
+ stdout=PIPE,
+ creationflags=PROC_CREATIONFLAGS,
+ )
child_pids = []
if p.stdout is not None:
for line in p.stdout:
@@ -909,29 +1009,32 @@ class Git(LazyMixin):
child_pids.append(int(local_pid))
try:
# Windows does not have SIGKILL, so use SIGTERM instead
- sig = getattr(signal, 'SIGKILL', signal.SIGTERM)
+ sig = getattr(signal, "SIGKILL", signal.SIGTERM)
os.kill(pid, sig)
for child_pid in child_pids:
try:
os.kill(child_pid, sig)
except OSError:
pass
- kill_check.set() # tell the main routine that the process was killed
+ kill_check.set() # tell the main routine that the process was killed
except OSError:
# It is possible that the process gets completed in the duration after timeout
# happens and before we try to kill the process.
pass
return
+
# end
if kill_after_timeout is not None:
kill_check = threading.Event()
- watchdog = threading.Timer(kill_after_timeout, _kill_process, args=(proc.pid,))
+ watchdog = threading.Timer(
+ kill_after_timeout, _kill_process, args=(proc.pid,)
+ )
# Wait for the process to return
status = 0
- stdout_value: Union[str, bytes] = b''
- stderr_value: Union[str, bytes] = b''
+ 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:
@@ -941,8 +1044,10 @@ class Git(LazyMixin):
if kill_after_timeout is not None:
watchdog.cancel()
if kill_check.is_set():
- stderr_value = ('Timeout: the command "%s" did not complete in %d '
- 'secs.' % (" ".join(redacted_command), kill_after_timeout))
+ stderr_value = (
+ 'Timeout: the command "%s" did not complete in %d '
+ "secs." % (" ".join(redacted_command), kill_after_timeout)
+ )
if not universal_newlines:
stderr_value = stderr_value.encode(defenc)
# strip trailing "\n"
@@ -953,12 +1058,16 @@ class Git(LazyMixin):
status = proc.returncode
else:
- max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
+ max_chunk_size = (
+ max_chunk_size
+ if max_chunk_size and max_chunk_size > 0
+ else io.DEFAULT_BUFFER_SIZE
+ )
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# strip trailing "\n"
- if stderr_value.endswith(newline): # type: ignore
+ if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
@@ -966,18 +1075,28 @@ class Git(LazyMixin):
proc.stdout.close()
proc.stderr.close()
- if self.GIT_PYTHON_TRACE == 'full':
+ if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
- return not output_stream and safe_decode(stdout_value) or '<OUTPUT_STREAM>'
+ return (
+ not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
+ )
+
# end
if stderr_value:
- log.info("%s -> %d; stdout: '%s'; stderr: '%s'",
- cmdstr, status, as_text(stdout_value), safe_decode(stderr_value))
+ log.info(
+ "%s -> %d; stdout: '%s'; stderr: '%s'",
+ cmdstr,
+ status,
+ as_text(stdout_value),
+ safe_decode(stderr_value),
+ )
elif stdout_value:
- log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
+ log.info(
+ "%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value)
+ )
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
@@ -985,7 +1104,9 @@ class Git(LazyMixin):
if with_exceptions and status != 0:
raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
- if isinstance(stdout_value, bytes) and stdout_as_string: # could also be output_stream
+ if (
+ isinstance(stdout_value, bytes) and stdout_as_string
+ ): # could also be output_stream
stdout_value = safe_decode(stdout_value)
# Allow access to the command's status code
@@ -1042,7 +1163,9 @@ class Git(LazyMixin):
finally:
self.update_environment(**old_env)
- def transform_kwarg(self, name: str, value: Any, split_single_char_options: bool) -> List[str]:
+ def transform_kwarg(
+ self, name: str, value: Any, split_single_char_options: bool
+ ) -> List[str]:
if len(name) == 1:
if value is True:
return ["-%s" % name]
@@ -1058,7 +1181,9 @@ class Git(LazyMixin):
return ["--%s=%s" % (dashify(name), value)]
return []
- def transform_kwargs(self, split_single_char_options: bool = True, **kwargs: Any) -> List[str]:
+ def transform_kwargs(
+ self, split_single_char_options: bool = True, **kwargs: Any
+ ) -> List[str]:
"""Transforms Python style kwargs into git command line options."""
args = []
for k, v in kwargs.items():
@@ -1081,7 +1206,7 @@ class Git(LazyMixin):
return outlist
- def __call__(self, **kwargs: Any) -> 'Git':
+ def __call__(self, **kwargs: Any) -> "Git":
"""Specify command line options to the git executable
for a subcommand call
@@ -1094,28 +1219,34 @@ class Git(LazyMixin):
``Examples``::
git(work_tree='/tmp').difftool()"""
self._git_options = self.transform_kwargs(
- split_single_char_options=True, **kwargs)
+ split_single_char_options=True, **kwargs
+ )
return self
@overload
- def _call_process(self, method: str, *args: None, **kwargs: None
- ) -> str:
+ def _call_process(self, method: str, *args: None, **kwargs: None) -> str:
... # if no args given, execute called with all defaults
@overload
- def _call_process(self, method: str,
- istream: int,
- as_process: Literal[True],
- *args: Any, **kwargs: Any
- ) -> 'Git.AutoInterrupt': ...
+ def _call_process(
+ self,
+ method: str,
+ istream: int,
+ as_process: Literal[True],
+ *args: Any,
+ **kwargs: Any,
+ ) -> "Git.AutoInterrupt":
+ ...
@overload
- def _call_process(self, method: str, *args: Any, **kwargs: Any
- ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], 'Git.AutoInterrupt']:
+ def _call_process(
+ self, method: str, *args: Any, **kwargs: Any
+ ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]:
...
- def _call_process(self, method: str, *args: Any, **kwargs: Any
- ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], 'Git.AutoInterrupt']:
+ def _call_process(
+ self, method: str, *args: Any, **kwargs: Any
+ ) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], "Git.AutoInterrupt"]:
"""Run the given git command with the specified arguments and return
the result as a String
@@ -1145,13 +1276,13 @@ class Git(LazyMixin):
:return: Same as ``execute``
if no args given used execute default (esp. as_process = False, stdout_as_string = True)
- and return str """
+ and return str"""
# Handle optional arguments prior to calling transform_kwargs
# otherwise these'll end up in args, which is bad.
exec_kwargs = {k: v for k, v in kwargs.items() if k in execute_kwargs}
opts_kwargs = {k: v for k, v in kwargs.items() if k not in execute_kwargs}
- insert_after_this_arg = opts_kwargs.pop('insert_kwargs_after', None)
+ insert_after_this_arg = opts_kwargs.pop("insert_kwargs_after", None)
# Prepare the argument list
@@ -1164,10 +1295,12 @@ class Git(LazyMixin):
try:
index = ext_args.index(insert_after_this_arg)
except ValueError as err:
- raise ValueError("Couldn't find argument '%s' in args %s to insert cmd options after"
- % (insert_after_this_arg, str(ext_args))) from err
+ raise ValueError(
+ "Couldn't find argument '%s' in args %s to insert cmd options after"
+ % (insert_after_this_arg, str(ext_args))
+ ) from err
# end handle error
- args_list = ext_args[:index + 1] + opt_args + ext_args[index + 1:]
+ args_list = ext_args[: index + 1] + opt_args + ext_args[index + 1 :]
# end handle opts_kwargs
call = [self.GIT_PYTHON_GIT_EXECUTABLE]
@@ -1197,9 +1330,15 @@ class Git(LazyMixin):
tokens = header_line.split()
if len(tokens) != 3:
if not tokens:
- raise ValueError("SHA could not be resolved, git returned: %r" % (header_line.strip()))
+ raise ValueError(
+ "SHA could not be resolved, git returned: %r"
+ % (header_line.strip())
+ )
else:
- raise ValueError("SHA %s could not be resolved, git returned: %r" % (tokens[0], header_line.strip()))
+ raise ValueError(
+ "SHA %s could not be resolved, git returned: %r"
+ % (tokens[0], header_line.strip())
+ )
# END handle actual return value
# END error handling
@@ -1211,9 +1350,9 @@ 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: str = ref.decode('ascii')
+ refstr: str = ref.decode("ascii")
elif not isinstance(ref, str):
- refstr = str(ref) # could be ref-object
+ refstr = str(ref) # could be ref-object
else:
refstr = ref
@@ -1221,8 +1360,9 @@ class Git(LazyMixin):
refstr += "\n"
return refstr.encode(defenc)
- def _get_persistent_cmd(self, attr_name: str, cmd_name: str, *args: Any, **kwargs: Any
- ) -> 'Git.AutoInterrupt':
+ def _get_persistent_cmd(
+ self, attr_name: str, cmd_name: str, *args: Any, **kwargs: Any
+ ) -> "Git.AutoInterrupt":
cur_val = getattr(self, attr_name)
if cur_val is not None:
return cur_val
@@ -1232,10 +1372,12 @@ class Git(LazyMixin):
cmd = self._call_process(cmd_name, *args, **options)
setattr(self, attr_name, cmd)
- cmd = cast('Git.AutoInterrupt', cmd)
+ cmd = cast("Git.AutoInterrupt", cmd)
return cmd
- def __get_object_header(self, cmd: 'Git.AutoInterrupt', ref: AnyStr) -> Tuple[str, str, int]:
+ def __get_object_header(
+ self, cmd: "Git.AutoInterrupt", ref: AnyStr
+ ) -> Tuple[str, str, int]:
if cmd.stdin and cmd.stdout:
cmd.stdin.write(self._prepare_ref(ref))
cmd.stdin.flush()
@@ -1244,7 +1386,7 @@ class Git(LazyMixin):
raise ValueError("cmd stdin was empty")
def get_object_header(self, ref: str) -> Tuple[str, str, int]:
- """ Use this method to quickly examine the type and size of the object behind
+ """Use this method to quickly examine the type and size of the object behind
the given ref.
:note: The method will only suffer from the costs of command invocation
@@ -1255,16 +1397,18 @@ class Git(LazyMixin):
return self.__get_object_header(cmd, ref)
def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]:
- """ As get_object_header, but returns object data as well
+ """As get_object_header, but returns object data as well
:return: (hexsha, type_string, size_as_int,data_string)
:note: not threadsafe"""
hexsha, typename, size, stream = self.stream_object_data(ref)
data = stream.read(size)
- del(stream)
+ del stream
return (hexsha, typename, size, data)
- def stream_object_data(self, ref: str) -> Tuple[str, str, int, 'Git.CatFileContentStream']:
- """ As get_object_header, but returns the data as a stream
+ def stream_object_data(
+ self, ref: str
+ ) -> Tuple[str, str, int, "Git.CatFileContentStream"]:
+ """As get_object_header, but returns the data as a stream
:return: (hexsha, type_string, size_as_int, stream)
:note: This method is not threadsafe, you need one independent Command instance per thread to be safe !"""
@@ -1273,7 +1417,7 @@ class Git(LazyMixin):
cmd_stdout = cmd.stdout if cmd.stdout is not None else io.BytesIO()
return (hexsha, typename, size, self.CatFileContentStream(size, cmd_stdout))
- def clear_cache(self) -> 'Git':
+ def clear_cache(self) -> "Git":
"""Clear all kinds of internal caches to release resources.
Currently persistent commands will be interrupted.
diff --git a/git/compat.py b/git/compat.py
index 988c04ef..e7ef28c3 100644
--- a/git/compat.py
+++ b/git/compat.py
@@ -12,8 +12,8 @@ import os
import sys
from gitdb.utils.encoding import (
- force_bytes, # @UnusedImport
- force_text # @UnusedImport
+ force_bytes, # @UnusedImport
+ force_text, # @UnusedImport
)
# typing --------------------------------------------------------------------
@@ -29,21 +29,24 @@ from typing import (
Union,
overload,
)
+
# ---------------------------------------------------------------------------
-is_win: bool = (os.name == 'nt')
-is_posix = (os.name == 'posix')
-is_darwin = (os.name == 'darwin')
+is_win: bool = os.name == "nt"
+is_posix = os.name == "posix"
+is_darwin = os.name == "darwin"
defenc = sys.getfilesystemencoding()
@overload
-def safe_decode(s: None) -> None: ...
+def safe_decode(s: None) -> None:
+ ...
@overload
-def safe_decode(s: AnyStr) -> str: ...
+def safe_decode(s: AnyStr) -> str:
+ ...
def safe_decode(s: Union[AnyStr, None]) -> Optional[str]:
@@ -51,19 +54,21 @@ def safe_decode(s: Union[AnyStr, None]) -> Optional[str]:
if isinstance(s, str):
return s
elif isinstance(s, bytes):
- return s.decode(defenc, 'surrogateescape')
+ return s.decode(defenc, "surrogateescape")
elif s is None:
return None
else:
- raise TypeError('Expected bytes or text, but got %r' % (s,))
+ raise TypeError("Expected bytes or text, but got %r" % (s,))
@overload
-def safe_encode(s: None) -> None: ...
+def safe_encode(s: None) -> None:
+ ...
@overload
-def safe_encode(s: AnyStr) -> bytes: ...
+def safe_encode(s: AnyStr) -> bytes:
+ ...
def safe_encode(s: Optional[AnyStr]) -> Optional[bytes]:
@@ -75,15 +80,17 @@ def safe_encode(s: Optional[AnyStr]) -> Optional[bytes]:
elif s is None:
return None
else:
- raise TypeError('Expected bytes or text, but got %r' % (s,))
+ raise TypeError("Expected bytes or text, but got %r" % (s,))
@overload
-def win_encode(s: None) -> None: ...
+def win_encode(s: None) -> None:
+ ...
@overload
-def win_encode(s: AnyStr) -> bytes: ...
+def win_encode(s: AnyStr) -> bytes:
+ ...
def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
@@ -93,5 +100,5 @@ def win_encode(s: Optional[AnyStr]) -> Optional[bytes]:
elif isinstance(s, bytes):
return s
elif s is not None:
- raise TypeError('Expected bytes or text, but got %r' % (s,))
+ raise TypeError("Expected bytes or text, but got %r" % (s,))
return None
diff --git a/git/config.py b/git/config.py
index 1ac3c9ce..24c2b201 100644
--- a/git/config.py
+++ b/git/config.py
@@ -30,8 +30,20 @@ import configparser as cp
# typing-------------------------------------------------------
-from typing import (Any, Callable, Generic, IO, List, Dict, Sequence,
- TYPE_CHECKING, Tuple, TypeVar, Union, cast)
+from typing import (
+ Any,
+ Callable,
+ Generic,
+ IO,
+ List,
+ Dict,
+ Sequence,
+ TYPE_CHECKING,
+ Tuple,
+ TypeVar,
+ Union,
+ cast,
+)
from git.types import Lit_config_levels, ConfigLevels_Tup, PathLike, assert_never, _T
@@ -39,23 +51,25 @@ if TYPE_CHECKING:
from git.repo.base import Repo
from io import BytesIO
-T_ConfigParser = TypeVar('T_ConfigParser', bound='GitConfigParser')
-T_OMD_value = TypeVar('T_OMD_value', str, bytes, int, float, bool)
+T_ConfigParser = TypeVar("T_ConfigParser", bound="GitConfigParser")
+T_OMD_value = TypeVar("T_OMD_value", str, bytes, int, float, bool)
if sys.version_info[:3] < (3, 7, 2):
# typing.Ordereddict not added until py 3.7.2
from collections import OrderedDict
+
OrderedDict_OMD = OrderedDict
else:
from typing import OrderedDict
+
OrderedDict_OMD = OrderedDict[str, List[T_OMD_value]] # type: ignore[assignment, misc]
# -------------------------------------------------------------
-__all__ = ('GitConfigParser', 'SectionConstraint')
+__all__ = ("GitConfigParser", "SectionConstraint")
-log = logging.getLogger('git.config')
+log = logging.getLogger("git.config")
log.addHandler(logging.NullHandler())
# invariants
@@ -67,26 +81,37 @@ CONFIG_LEVELS: ConfigLevels_Tup = ("system", "user", "global", "repository")
# Section pattern to detect conditional includes.
# https://git-scm.com/docs/git-config#_conditional_includes
-CONDITIONAL_INCLUDE_REGEXP = re.compile(r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\"")
+CONDITIONAL_INCLUDE_REGEXP = re.compile(
+ r"(?<=includeIf )\"(gitdir|gitdir/i|onbranch):(.+)\""
+)
class MetaParserBuilder(abc.ABCMeta):
"""Utility class wrapping base-class methods into decorators that assure read-only properties"""
- def __new__(cls, name: str, bases: Tuple, clsdict: Dict[str, Any]) -> 'MetaParserBuilder':
+
+ def __new__(
+ cls, name: str, bases: Tuple, clsdict: Dict[str, Any]
+ ) -> "MetaParserBuilder":
"""
Equip all base-class methods with a needs_values decorator, and all non-const methods
with a set_dirty_and_flush_changes decorator in addition to that."""
- kmm = '_mutating_methods_'
+ kmm = "_mutating_methods_"
if kmm in clsdict:
mutating_methods = clsdict[kmm]
for base in bases:
- methods = (t for t in inspect.getmembers(base, inspect.isroutine) if not t[0].startswith("_"))
+ methods = (
+ t
+ for t in inspect.getmembers(base, inspect.isroutine)
+ if not t[0].startswith("_")
+ )
for name, method in methods:
if name in clsdict:
continue
method_with_values = needs_values(method)
if name in mutating_methods:
- method_with_values = set_dirty_and_flush_changes(method_with_values)
+ method_with_values = set_dirty_and_flush_changes(
+ method_with_values
+ )
# END mutating methods handling
clsdict[name] = method_with_values
@@ -102,9 +127,10 @@ def needs_values(func: Callable[..., _T]) -> Callable[..., _T]:
"""Returns method assuring we read values (on demand) before we try to access them"""
@wraps(func)
- def assure_data_present(self: 'GitConfigParser', *args: Any, **kwargs: Any) -> _T:
+ def assure_data_present(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _T:
self.read()
return func(self, *args, **kwargs)
+
# END wrapper method
return assure_data_present
@@ -114,11 +140,12 @@ def set_dirty_and_flush_changes(non_const_func: Callable[..., _T]) -> Callable[.
If so, the instance will be set dirty.
Additionally, we flush the changes right to disk"""
- def flush_changes(self: 'GitConfigParser', *args: Any, **kwargs: Any) -> _T:
+ def flush_changes(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _T:
rval = non_const_func(self, *args, **kwargs)
self._dirty = True
self.write()
return rval
+
# END wrapper method
flush_changes.__name__ = non_const_func.__name__
return flush_changes
@@ -133,9 +160,21 @@ class SectionConstraint(Generic[T_ConfigParser]):
:note:
If used as a context manager, will release the wrapped ConfigParser."""
+
__slots__ = ("_config", "_section_name")
- _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option",
- "remove_section", "remove_option", "options")
+ _valid_attrs_ = (
+ "get_value",
+ "set_value",
+ "get",
+ "set",
+ "getint",
+ "getfloat",
+ "getboolean",
+ "has_option",
+ "remove_section",
+ "remove_option",
+ "options",
+ )
def __init__(self, config: T_ConfigParser, section: str) -> None:
self._config = config
@@ -166,11 +205,13 @@ class SectionConstraint(Generic[T_ConfigParser]):
"""Equivalent to GitConfigParser.release(), which is called on our underlying parser instance"""
return self._config.release()
- def __enter__(self) -> 'SectionConstraint[T_ConfigParser]':
+ def __enter__(self) -> "SectionConstraint[T_ConfigParser]":
self._config.__enter__()
return self
- def __exit__(self, exception_type: str, exception_value: str, traceback: str) -> None:
+ def __exit__(
+ self, exception_type: str, exception_value: str, traceback: str
+ ) -> None:
self._config.__exit__(exception_type, exception_value, traceback)
@@ -228,16 +269,22 @@ def get_config_path(config_level: Lit_config_levels) -> str:
if config_level == "system":
return "/etc/gitconfig"
elif config_level == "user":
- config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(os.environ.get("HOME", '~'), ".config")
+ config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(
+ os.environ.get("HOME", "~"), ".config"
+ )
return osp.normpath(osp.expanduser(osp.join(config_home, "git", "config")))
elif config_level == "global":
return osp.normpath(osp.expanduser("~/.gitconfig"))
elif config_level == "repository":
- raise ValueError("No repo to get repository configuration from. Use Repo._get_config_path")
+ raise ValueError(
+ "No repo to get repository configuration from. Use Repo._get_config_path"
+ )
else:
# Should not reach here. Will raise ValueError if does. Static typing will warn missing elifs
- assert_never(config_level, # type: ignore[unreachable]
- ValueError(f"Invalid configuration level: {config_level!r}"))
+ assert_never(
+ config_level, # type: ignore[unreachable]
+ ValueError(f"Invalid configuration level: {config_level!r}"),
+ )
class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
@@ -258,30 +305,36 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
must match perfectly.
If used as a context manager, will release the locked file."""
- #{ Configuration
+ # { Configuration
# The lock type determines the type of lock to use in new configuration readers.
# They must be compatible to the LockFile interface.
# A suitable alternative would be the BlockingLockFile
t_lock = LockFile
- re_comment = re.compile(r'^\s*[#;]')
+ re_comment = re.compile(r"^\s*[#;]")
- #} END configuration
+ # } END configuration
- optvalueonly_source = r'\s*(?P<option>[^:=\s][^:=]*)'
+ optvalueonly_source = r"\s*(?P<option>[^:=\s][^:=]*)"
OPTVALUEONLY = re.compile(optvalueonly_source)
- OPTCRE = re.compile(optvalueonly_source + r'\s*(?P<vi>[:=])\s*' + r'(?P<value>.*)$')
+ OPTCRE = re.compile(optvalueonly_source + r"\s*(?P<vi>[:=])\s*" + r"(?P<value>.*)$")
del optvalueonly_source
# list of RawConfigParser methods able to change the instance
_mutating_methods_ = ("add_section", "remove_section", "remove_option", "set")
- def __init__(self, file_or_files: Union[None, PathLike, 'BytesIO', Sequence[Union[PathLike, 'BytesIO']]] = None,
- read_only: bool = True, merge_includes: bool = True,
- config_level: Union[Lit_config_levels, None] = None,
- repo: Union['Repo', None] = None) -> None:
+ def __init__(
+ self,
+ file_or_files: Union[
+ None, PathLike, "BytesIO", Sequence[Union[PathLike, "BytesIO"]]
+ ] = None,
+ read_only: bool = True,
+ merge_includes: bool = True,
+ config_level: Union[Lit_config_levels, None] = None,
+ repo: Union["Repo", None] = None,
+ ) -> None:
"""Initialize a configuration reader to read the given file_or_files and to
possibly allow changes to it by setting read_only False
@@ -303,22 +356,28 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
cp.RawConfigParser.__init__(self, dict_type=_OMD)
self._dict: Callable[..., _OMD] # type: ignore # mypy/typeshed bug?
self._defaults: _OMD
- self._sections: _OMD # type: ignore # mypy/typeshed bug?
+ self._sections: _OMD # type: ignore # mypy/typeshed bug?
# Used in python 3, needs to stay in sync with sections for underlying implementation to work
- if not hasattr(self, '_proxies'):
+ if not hasattr(self, "_proxies"):
self._proxies = self._dict()
if file_or_files is not None:
- self._file_or_files: Union[PathLike, 'BytesIO', Sequence[Union[PathLike, 'BytesIO']]] = file_or_files
+ self._file_or_files: Union[
+ PathLike, "BytesIO", Sequence[Union[PathLike, "BytesIO"]]
+ ] = file_or_files
else:
if config_level is None:
if read_only:
- self._file_or_files = [get_config_path(cast(Lit_config_levels, f))
- for f in CONFIG_LEVELS
- if f != 'repository']
+ self._file_or_files = [
+ get_config_path(cast(Lit_config_levels, f))
+ for f in CONFIG_LEVELS
+ if f != "repository"
+ ]
else:
- raise ValueError("No configuration level or configuration files specified")
+ raise ValueError(
+ "No configuration level or configuration files specified"
+ )
else:
self._file_or_files = [get_config_path(config_level)]
@@ -327,7 +386,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
self._is_initialized = False
self._merge_includes = merge_includes
self._repo = repo
- self._lock: Union['LockFile', None] = None
+ self._lock: Union["LockFile", None] = None
self._acquire_lock()
def _acquire_lock(self) -> None:
@@ -337,7 +396,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
file_or_files = self._file_or_files
elif isinstance(self._file_or_files, (tuple, list, Sequence)):
raise ValueError(
- "Write-ConfigParsers can operate on a single file only, multiple files have been passed")
+ "Write-ConfigParsers can operate on a single file only, multiple files have been passed"
+ )
else:
file_or_files = self._file_or_files.name
@@ -354,7 +414,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
# NOTE: only consistent in PY2
self.release()
- def __enter__(self) -> 'GitConfigParser':
+ def __enter__(self) -> "GitConfigParser":
self._acquire_lock()
return self
@@ -374,7 +434,9 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
try:
self.write()
except IOError:
- log.error("Exception during destruction of GitConfigParser", exc_info=True)
+ log.error(
+ "Exception during destruction of GitConfigParser", exc_info=True
+ )
except ReferenceError:
# This happens in PY3 ... and usually means that some state cannot be written
# as the sections dict cannot be iterated
@@ -398,19 +460,20 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
Removed big comments to make it more compact.
Made sure it ignores initial whitespace as git uses tabs"""
- cursect = None # None, or a dictionary
+ cursect = None # None, or a dictionary
optname = None
lineno = 0
is_multi_line = False
- e = None # None, or an exception
+ e = None # None, or an exception
def string_decode(v: str) -> str:
- if v[-1] == '\\':
+ if v[-1] == "\\":
v = v[:-1]
# end cut trailing escapes to prevent decode error
- return v.encode(defenc).decode('unicode_escape')
+ return v.encode(defenc).decode("unicode_escape")
# end
+
# end
while True:
@@ -420,22 +483,22 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
break
lineno = lineno + 1
# comment or blank line?
- if line.strip() == '' or self.re_comment.match(line):
+ if line.strip() == "" or self.re_comment.match(line):
continue
- if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
+ if line.split(None, 1)[0].lower() == "rem" and line[0] in "rR":
# no leading whitespace
continue
# is it a section header?
mo = self.SECTCRE.match(line.strip())
if not is_multi_line and mo:
- sectname: str = mo.group('header').strip()
+ sectname: str = mo.group("header").strip()
if sectname in self._sections:
cursect = self._sections[sectname]
elif sectname == cp.DEFAULTSECT:
cursect = self._defaults
else:
- cursect = self._dict((('__name__', sectname),))
+ cursect = self._dict((("__name__", sectname),))
self._sections[sectname] = cursect
self._proxies[sectname] = None
# So sections can't start with a continuation line
@@ -448,14 +511,18 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
mo = self.OPTCRE.match(line)
if mo:
# We might just have handled the last line, which could contain a quotation we want to remove
- optname, vi, optval = mo.group('option', 'vi', 'value')
- if vi in ('=', ':') and ';' in optval and not optval.strip().startswith('"'):
- pos = optval.find(';')
+ optname, vi, optval = mo.group("option", "vi", "value")
+ if (
+ vi in ("=", ":")
+ and ";" in optval
+ and not optval.strip().startswith('"')
+ ):
+ pos = optval.find(";")
if pos != -1 and optval[pos - 1].isspace():
optval = optval[:pos]
optval = optval.strip()
if optval == '""':
- optval = ''
+ optval = ""
# end handle empty string
optname = self.optionxform(optname.rstrip())
if len(optval) > 1 and optval[0] == '"' and optval[-1] != '"':
@@ -518,11 +585,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
if keyword.endswith("/i"):
value = re.sub(
r"[a-zA-Z]",
- lambda m: "[{}{}]".format(
- m.group().lower(),
- m.group().upper()
- ),
- value
+ lambda m: "[{}{}]".format(m.group().lower(), m.group().upper()),
+ value,
)
if self._repo.git_dir:
if fnmatch.fnmatchcase(str(self._repo.git_dir), value):
@@ -557,7 +621,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
elif not isinstance(self._file_or_files, (tuple, list, Sequence)):
# could merge with above isinstance once runtime type known
files_to_read = [self._file_or_files]
- else: # for lists or tuples
+ else: # for lists or tuples
files_to_read = list(self._file_or_files)
# end assure we have a copy of the paths to handle
@@ -569,13 +633,15 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
if hasattr(file_path, "seek"):
# must be a file objectfile-object
- file_path = cast(IO[bytes], file_path) # replace with assert to narrow type, once sure
+ file_path = cast(
+ IO[bytes], file_path
+ ) # replace with assert to narrow type, once sure
self._read(file_path, file_path.name)
else:
# assume a path if it is not a file-object
file_path = cast(PathLike, file_path)
try:
- with open(file_path, 'rb') as fp:
+ with open(file_path, "rb") as fp:
file_ok = True
self._read(fp, fp.name)
except IOError:
@@ -585,14 +651,16 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
# We expect all paths to be normalized and absolute (and will assure that is the case)
if self._has_includes():
for _, include_path in self._included_paths():
- if include_path.startswith('~'):
+ if include_path.startswith("~"):
include_path = osp.expanduser(include_path)
if not osp.isabs(include_path):
if not file_ok:
continue
# end ignore relative paths if we don't know the configuration file path
file_path = cast(PathLike, file_path)
- assert osp.isabs(file_path), "Need absolute paths to be sure our cycle checks will work"
+ assert osp.isabs(
+ file_path
+ ), "Need absolute paths to be sure our cycle checks will work"
include_path = osp.join(osp.dirname(file_path), include_path)
# end make include path absolute
include_path = osp.normpath(include_path)
@@ -615,18 +683,27 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
def _write(self, fp: IO) -> None:
"""Write an .ini-format representation of the configuration state in
git compatible format"""
+
def write_section(name: str, section_dict: _OMD) -> None:
fp.write(("[%s]\n" % name).encode(defenc))
- values: Sequence[str] # runtime only gets str in tests, but should be whatever _OMD stores
+ values: Sequence[
+ str
+ ] # runtime only gets str in tests, but should be whatever _OMD stores
v: str
for (key, values) in section_dict.items_all():
if key == "__name__":
continue
for v in values:
- fp.write(("\t%s = %s\n" % (key, self._value_to_string(v).replace('\n', '\n\t'))).encode(defenc))
+ fp.write(
+ (
+ "\t%s = %s\n"
+ % (key, self._value_to_string(v).replace("\n", "\n\t"))
+ ).encode(defenc)
+ )
# END if key is not __name__
+
# END section writing
if self._defaults:
@@ -636,16 +713,20 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
for name, value in self._sections.items():
write_section(name, value)
- def items(self, section_name: str) -> List[Tuple[str, str]]: # type: ignore[override]
+ def items(self, section_name: str) -> List[Tuple[str, str]]: # type: ignore[override]
""":return: list((option, value), ...) pairs of all items in the given section"""
- return [(k, v) for k, v in super(GitConfigParser, self).items(section_name) if k != '__name__']
+ return [
+ (k, v)
+ for k, v in super(GitConfigParser, self).items(section_name)
+ if k != "__name__"
+ ]
def items_all(self, section_name: str) -> List[Tuple[str, List[str]]]:
""":return: list((option, [values...]), ...) pairs of all items in the given section"""
rv = _OMD(self._defaults)
for k, vs in self._sections[section_name].items_all():
- if k == '__name__':
+ if k == "__name__":
continue
if k in rv and rv.getall(k) == vs:
@@ -667,20 +748,26 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
return None
if isinstance(self._file_or_files, (list, tuple)):
- raise AssertionError("Cannot write back if there is not exactly a single file to write to, have %i files"
- % len(self._file_or_files))
+ raise AssertionError(
+ "Cannot write back if there is not exactly a single file to write to, have %i files"
+ % len(self._file_or_files)
+ )
# end assert multiple files
if self._has_includes():
- log.debug("Skipping write-back of configuration file as include files were merged in." +
- "Set merge_includes=False to prevent this.")
+ log.debug(
+ "Skipping write-back of configuration file as include files were merged in."
+ + "Set merge_includes=False to prevent this."
+ )
return None
# end
fp = self._file_or_files
# we have a physical file on disk, so get a lock
- is_file_lock = isinstance(fp, (str, os.PathLike, IOBase)) # can't use Pathlike until 3.5 dropped
+ is_file_lock = isinstance(
+ fp, (str, os.PathLike, IOBase)
+ ) # can't use Pathlike until 3.5 dropped
if is_file_lock and self._lock is not None: # else raise Error?
self._lock._obtain_lock()
@@ -689,16 +776,18 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
with open(fp, "wb") as fp_open:
self._write(fp_open)
else:
- fp = cast('BytesIO', fp)
+ fp = cast("BytesIO", fp)
fp.seek(0)
# make sure we do not overwrite into an existing file
- if hasattr(fp, 'truncate'):
+ if hasattr(fp, "truncate"):
fp.truncate()
self._write(fp)
def _assure_writable(self, method_name: str) -> None:
if self.read_only:
- raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))
+ raise IOError(
+ "Cannot execute non-constant method %s.%s" % (self, method_name)
+ )
def add_section(self, section: str) -> None:
"""Assures added options will stay in order"""
@@ -709,8 +798,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
""":return: True if this instance may change the configuration file"""
return self._read_only
- def get_value(self, section: str, option: str, default: Union[int, float, str, bool, None] = None
- ) -> Union[int, float, str, bool]:
+ def get_value(
+ self,
+ section: str,
+ option: str,
+ default: Union[int, float, str, bool, None] = None,
+ ) -> Union[int, float, str, bool]:
# can default or return type include bool?
"""Get an option's value.
@@ -733,8 +826,12 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
return self._string_to_value(valuestr)
- def get_values(self, section: str, option: str, default: Union[int, float, str, bool, None] = None
- ) -> List[Union[int, float, str, bool]]:
+ def get_values(
+ self,
+ section: str,
+ option: str,
+ default: Union[int, float, str, bool, None] = None,
+ ) -> List[Union[int, float, str, bool]]:
"""Get an option's values.
If multiple values are specified for this option in the section, all are
@@ -771,15 +868,16 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
# try boolean values as git uses them
vl = valuestr.lower()
- if vl == 'false':
+ if vl == "false":
return False
- if vl == 'true':
+ if vl == "true":
return True
if not isinstance(valuestr, str):
raise TypeError(
"Invalid value type: only int, long, float and str are allowed",
- valuestr)
+ valuestr,
+ )
return valuestr
@@ -790,7 +888,9 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
@needs_values
@set_dirty_and_flush_changes
- def set_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> 'GitConfigParser':
+ def set_value(
+ self, section: str, option: str, value: Union[str, bytes, int, float, bool]
+ ) -> "GitConfigParser":
"""Sets the given option in section to the given value.
It will create the section if required, and will not throw as opposed to the default
ConfigParser 'set' method.
@@ -808,7 +908,9 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
@needs_values
@set_dirty_and_flush_changes
- def add_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> 'GitConfigParser':
+ def add_value(
+ self, section: str, option: str, value: Union[str, bytes, int, float, bool]
+ ) -> "GitConfigParser":
"""Adds a value for the given option in section.
It will create the section if required, and will not throw as opposed to the default
ConfigParser 'set' method. The value becomes the new value of the option as returned
@@ -825,7 +927,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder):
self._sections[section].add(option, self._value_to_string(value))
return self
- def rename_section(self, section: str, new_name: str) -> 'GitConfigParser':
+ def rename_section(self, section: str, new_name: str) -> "GitConfigParser":
"""rename the given section to new_name
:raise ValueError: if section doesn't exit
:raise ValueError: if a section with new_name does already exist
diff --git a/git/db.py b/git/db.py
index 3a7adc7d..a119f4ea 100644
--- a/git/db.py
+++ b/git/db.py
@@ -1,9 +1,6 @@
"""Module with our own gitdb implementation - it uses the git command"""
from git.util import bin_to_hex, hex_to_bin
-from gitdb.base import (
- OInfo,
- OStream
-)
+from gitdb.base import OInfo, OStream
from gitdb.db import GitDB # @UnusedImport
from gitdb.db import LooseObjectDB
@@ -21,7 +18,7 @@ if TYPE_CHECKING:
# --------------------------------------------------------
-__all__ = ('GitCmdObjectDB', 'GitDB')
+__all__ = ("GitCmdObjectDB", "GitDB")
class GitCmdObjectDB(LooseObjectDB):
@@ -34,7 +31,7 @@ class GitCmdObjectDB(LooseObjectDB):
have packs and the other implementations
"""
- def __init__(self, root_path: PathLike, git: 'Git') -> None:
+ def __init__(self, root_path: PathLike, git: "Git") -> None:
"""Initialize this instance with the root and a git command"""
super(GitCmdObjectDB, self).__init__(root_path)
self._git = git
@@ -45,7 +42,9 @@ class GitCmdObjectDB(LooseObjectDB):
def stream(self, binsha: bytes) -> OStream:
"""For now, all lookup is done by git itself"""
- hexsha, typename, size, stream = self._git.stream_object_data(bin_to_hex(binsha))
+ hexsha, typename, size, stream = self._git.stream_object_data(
+ bin_to_hex(binsha)
+ )
return OStream(hex_to_bin(hexsha), typename, size, stream)
# { Interface
@@ -63,4 +62,4 @@ class GitCmdObjectDB(LooseObjectDB):
raise BadObject(partial_hexsha) from e
# END handle exceptions
- #} END interface
+ # } END interface
diff --git a/git/diff.py b/git/diff.py
index cea66d7e..6526ed68 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -15,7 +15,19 @@ from .objects.util import mode_str_to_int
# typing ------------------------------------------------------------------
-from typing import Any, Iterator, List, Match, Optional, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast
+from typing import (
+ Any,
+ Iterator,
+ List,
+ Match,
+ Optional,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ TYPE_CHECKING,
+ cast,
+)
from git.types import PathLike, Literal
if TYPE_CHECKING:
@@ -26,7 +38,7 @@ if TYPE_CHECKING:
from subprocess import Popen
from git import Git
-Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T', 'U']
+Lit_change_type = Literal["A", "D", "C", "M", "R", "T", "U"]
# def is_change_type(inp: str) -> TypeGuard[Lit_change_type]:
@@ -36,12 +48,12 @@ Lit_change_type = Literal['A', 'D', 'C', 'M', 'R', 'T', 'U']
# ------------------------------------------------------------------------
-__all__ = ('Diffable', 'DiffIndex', 'Diff', 'NULL_TREE')
+__all__ = ("Diffable", "DiffIndex", "Diff", "NULL_TREE")
# Special object to compare against the empty tree in diffs
NULL_TREE = object()
-_octal_byte_re = re.compile(b'\\\\([0-9]{3})')
+_octal_byte_re = re.compile(b"\\\\([0-9]{3})")
def _octal_repl(matchobj: Match) -> bytes:
@@ -52,19 +64,22 @@ def _octal_repl(matchobj: Match) -> bytes:
def decode_path(path: bytes, has_ab_prefix: bool = True) -> Optional[bytes]:
- if path == b'/dev/null':
+ if path == b"/dev/null":
return None
if path.startswith(b'"') and path.endswith(b'"'):
- path = (path[1:-1].replace(b'\\n', b'\n')
- .replace(b'\\t', b'\t')
- .replace(b'\\"', b'"')
- .replace(b'\\\\', b'\\'))
+ path = (
+ path[1:-1]
+ .replace(b"\\n", b"\n")
+ .replace(b"\\t", b"\t")
+ .replace(b'\\"', b'"')
+ .replace(b"\\\\", b"\\")
+ )
path = _octal_byte_re.sub(_octal_repl, path)
if has_ab_prefix:
- assert path.startswith(b'a/') or path.startswith(b'b/')
+ assert path.startswith(b"a/") or path.startswith(b"b/")
path = path[2:]
return path
@@ -77,14 +92,16 @@ class Diffable(object):
:note:
Subclasses require a repo member as it is the case for Object instances, for practical
reasons we do not derive from Object."""
+
__slots__ = ()
# standin indicating you want to diff against the index
class Index(object):
pass
- def _process_diff_args(self, args: List[Union[str, 'Diffable', Type['Diffable.Index'], object]]
- ) -> List[Union[str, 'Diffable', Type['Diffable.Index'], object]]:
+ def _process_diff_args(
+ self, args: List[Union[str, "Diffable", Type["Diffable.Index"], object]]
+ ) -> List[Union[str, "Diffable", Type["Diffable.Index"], object]]:
"""
:return:
possibly altered version of the given args list.
@@ -92,9 +109,13 @@ class Diffable(object):
Subclasses can use it to alter the behaviour of the superclass"""
return args
- def diff(self, other: Union[Type['Index'], 'Tree', 'Commit', None, str, object] = Index,
- paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
- create_patch: bool = False, **kwargs: Any) -> 'DiffIndex':
+ def diff(
+ self,
+ other: Union[Type["Index"], "Tree", "Commit", None, str, object] = Index,
+ paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
+ create_patch: bool = False,
+ **kwargs: Any
+ ) -> "DiffIndex":
"""Creates diffs between two items being trees, trees and index or an
index and the working tree. It will detect renames automatically.
@@ -125,11 +146,11 @@ class Diffable(object):
:note:
On a bare repository, 'other' needs to be provided as Index or as
as Tree/Commit, or a git command error will occur"""
- args: List[Union[PathLike, Diffable, Type['Diffable.Index'], object]] = []
- args.append("--abbrev=40") # we need full shas
- args.append("--full-index") # get full index paths, not only filenames
+ args: List[Union[PathLike, Diffable, Type["Diffable.Index"], object]] = []
+ args.append("--abbrev=40") # we need full shas
+ args.append("--full-index") # get full index paths, not only filenames
- args.append("-M") # check for renames, in both formats
+ args.append("-M") # check for renames, in both formats
if create_patch:
args.append("-p")
else:
@@ -138,23 +159,23 @@ class Diffable(object):
# in any way, assure we don't see colored output,
# fixes https://github.com/gitpython-developers/GitPython/issues/172
- args.append('--no-color')
+ args.append("--no-color")
if paths is not None and not isinstance(paths, (tuple, list)):
paths = [paths]
- if hasattr(self, 'Has_Repo'):
- self.repo: 'Repo' = self.repo
+ if hasattr(self, "Has_Repo"):
+ self.repo: "Repo" = self.repo
diff_cmd = self.repo.git.diff
if other is self.Index:
- args.insert(0, '--cached')
+ args.insert(0, "--cached")
elif other is NULL_TREE:
- args.insert(0, '-r') # recursive diff-tree
- args.insert(0, '--root')
+ args.insert(0, "-r") # recursive diff-tree
+ args.insert(0, "--root")
diff_cmd = self.repo.git.diff_tree
elif other is not None:
- args.insert(0, '-r') # recursive diff-tree
+ args.insert(0, "-r") # recursive diff-tree
args.insert(0, other)
diff_cmd = self.repo.git.diff_tree
@@ -166,19 +187,21 @@ class Diffable(object):
args.extend(paths)
# END paths handling
- kwargs['as_process'] = True
+ kwargs["as_process"] = True
proc = diff_cmd(*self._process_diff_args(args), **kwargs)
- diff_method = (Diff._index_from_patch_format
- if create_patch
- else Diff._index_from_raw_format)
+ diff_method = (
+ Diff._index_from_patch_format
+ if create_patch
+ else Diff._index_from_raw_format
+ )
index = diff_method(self.repo, proc)
proc.wait()
return index
-T_Diff = TypeVar('T_Diff', bound='Diff')
+T_Diff = TypeVar("T_Diff", bound="Diff")
class DiffIndex(List[T_Diff]):
@@ -187,6 +210,7 @@ class DiffIndex(List[T_Diff]):
the diff properties.
The class improves the diff handling convenience"""
+
# change type invariant identifying possible ways a blob can have changed
# A = Added
# D = Deleted
@@ -208,7 +232,7 @@ class DiffIndex(List[T_Diff]):
* 'R' for renamed paths
* 'M' for paths with modified data
* 'T' for changed in the type paths
- """
+ """
if change_type not in self.change_type:
raise ValueError("Invalid change type: %s" % change_type)
@@ -223,7 +247,12 @@ class DiffIndex(List[T_Diff]):
yield diffidx
elif change_type == "R" and diffidx.renamed:
yield diffidx
- elif change_type == "M" and diffidx.a_blob and diffidx.b_blob and diffidx.a_blob != diffidx.b_blob:
+ elif (
+ change_type == "M"
+ and diffidx.a_blob
+ and diffidx.b_blob
+ and diffidx.a_blob != diffidx.b_blob
+ ):
yield diffidx
# END for each diff
@@ -261,7 +290,8 @@ class Diff(object):
be different to the version in the index or tree, and hence has been modified."""
# precompiled regex
- re_header = re.compile(br"""
+ re_header = re.compile(
+ rb"""
^diff[ ]--git
[ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n
(?:^old[ ]mode[ ](?P<old_mode>\d+)\n
@@ -278,22 +308,48 @@ class Diff(object):
\.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
(?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
(?:^\+\+\+[ ](?P<b_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))?
- """, re.VERBOSE | re.MULTILINE)
+ """,
+ re.VERBOSE | re.MULTILINE,
+ )
# can be used for comparisons
NULL_HEX_SHA = "0" * 40
NULL_BIN_SHA = b"\0" * 20
- __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "a_rawpath", "b_rawpath",
- "new_file", "deleted_file", "copied_file", "raw_rename_from",
- "raw_rename_to", "diff", "change_type", "score")
-
- def __init__(self, repo: 'Repo',
- a_rawpath: Optional[bytes], b_rawpath: Optional[bytes],
- a_blob_id: Union[str, bytes, None], b_blob_id: Union[str, bytes, None],
- a_mode: Union[bytes, str, None], b_mode: Union[bytes, str, None],
- new_file: bool, deleted_file: bool, copied_file: bool,
- raw_rename_from: Optional[bytes], raw_rename_to: Optional[bytes],
- diff: Union[str, bytes, None], change_type: Optional[Lit_change_type], score: Optional[int]) -> None:
+ __slots__ = (
+ "a_blob",
+ "b_blob",
+ "a_mode",
+ "b_mode",
+ "a_rawpath",
+ "b_rawpath",
+ "new_file",
+ "deleted_file",
+ "copied_file",
+ "raw_rename_from",
+ "raw_rename_to",
+ "diff",
+ "change_type",
+ "score",
+ )
+
+ def __init__(
+ self,
+ repo: "Repo",
+ a_rawpath: Optional[bytes],
+ b_rawpath: Optional[bytes],
+ a_blob_id: Union[str, bytes, None],
+ b_blob_id: Union[str, bytes, None],
+ a_mode: Union[bytes, str, None],
+ b_mode: Union[bytes, str, None],
+ new_file: bool,
+ deleted_file: bool,
+ copied_file: bool,
+ raw_rename_from: Optional[bytes],
+ raw_rename_to: Optional[bytes],
+ diff: Union[str, bytes, None],
+ change_type: Optional[Lit_change_type],
+ score: Optional[int],
+ ) -> None:
assert a_rawpath is None or isinstance(a_rawpath, bytes)
assert b_rawpath is None or isinstance(b_rawpath, bytes)
@@ -307,22 +363,26 @@ class Diff(object):
# we need to overwrite "repo" to the corresponding submodule's repo instead
if repo and a_rawpath:
for submodule in repo.submodules:
- if submodule.path == a_rawpath.decode(defenc, 'replace'):
+ if submodule.path == a_rawpath.decode(defenc, "replace"):
if submodule.module_exists():
repo = submodule.module()
break
- self.a_blob: Union['IndexObject', None]
+ self.a_blob: Union["IndexObject", None]
if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA:
self.a_blob = None
else:
- self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path)
+ self.a_blob = Blob(
+ repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=self.a_path
+ )
- self.b_blob: Union['IndexObject', None]
+ self.b_blob: Union["IndexObject", None]
if b_blob_id is None or b_blob_id == self.NULL_HEX_SHA:
self.b_blob = None
else:
- self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path)
+ self.b_blob = Blob(
+ repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=self.b_path
+ )
self.new_file: bool = new_file
self.deleted_file: bool = deleted_file
@@ -358,10 +418,10 @@ class Diff(object):
elif self.b_blob:
h %= self.b_blob.path
- msg: str = ''
- line = None # temp line
- line_length = 0 # line length
- for b, n in zip((self.a_blob, self.b_blob), ('lhs', 'rhs')):
+ msg: str = ""
+ line = None # temp line
+ line_length = 0 # line length
+ for b, n in zip((self.a_blob, self.b_blob), ("lhs", "rhs")):
if b:
line = "\n%s: %o | %s" % (n, b.mode, b.hexsha)
else:
@@ -372,26 +432,30 @@ class Diff(object):
# END for each blob
# add headline
- h += '\n' + '=' * line_length
+ h += "\n" + "=" * line_length
if self.deleted_file:
- msg += '\nfile deleted in rhs'
+ msg += "\nfile deleted in rhs"
if self.new_file:
- msg += '\nfile added in rhs'
+ msg += "\nfile added in rhs"
if self.copied_file:
- msg += '\nfile %r copied from %r' % (self.b_path, self.a_path)
+ msg += "\nfile %r copied from %r" % (self.b_path, self.a_path)
if self.rename_from:
- msg += '\nfile renamed from %r' % self.rename_from
+ msg += "\nfile renamed from %r" % self.rename_from
if self.rename_to:
- msg += '\nfile renamed to %r' % self.rename_to
+ msg += "\nfile renamed to %r" % self.rename_to
if self.diff:
- msg += '\n---'
+ msg += "\n---"
try:
- msg += self.diff.decode(defenc) if isinstance(self.diff, bytes) else self.diff
+ msg += (
+ self.diff.decode(defenc)
+ if isinstance(self.diff, bytes)
+ else self.diff
+ )
except UnicodeDecodeError:
- msg += 'OMITTED BINARY DATA'
+ msg += "OMITTED BINARY DATA"
# end handle encoding
- msg += '\n---'
+ msg += "\n---"
# END diff info
# Python2 silliness: have to assure we convert our likely to be unicode object to a string with the
@@ -400,37 +464,44 @@ class Diff(object):
# end
return res
- @ property
+ @property
def a_path(self) -> Optional[str]:
- return self.a_rawpath.decode(defenc, 'replace') if self.a_rawpath else None
+ return self.a_rawpath.decode(defenc, "replace") if self.a_rawpath else None
- @ property
+ @property
def b_path(self) -> Optional[str]:
- return self.b_rawpath.decode(defenc, 'replace') if self.b_rawpath else None
+ return self.b_rawpath.decode(defenc, "replace") if self.b_rawpath else None
- @ property
+ @property
def rename_from(self) -> Optional[str]:
- return self.raw_rename_from.decode(defenc, 'replace') if self.raw_rename_from else None
+ return (
+ self.raw_rename_from.decode(defenc, "replace")
+ if self.raw_rename_from
+ else None
+ )
- @ property
+ @property
def rename_to(self) -> Optional[str]:
- return self.raw_rename_to.decode(defenc, 'replace') if self.raw_rename_to else None
+ return (
+ self.raw_rename_to.decode(defenc, "replace") if self.raw_rename_to else None
+ )
- @ property
+ @property
def renamed(self) -> bool:
""":returns: True if the blob of our diff has been renamed
:note: This property is deprecated, please use ``renamed_file`` instead.
"""
return self.renamed_file
- @ property
+ @property
def renamed_file(self) -> bool:
- """:returns: True if the blob of our diff has been renamed
- """
+ """:returns: True if the blob of our diff has been renamed"""
return self.rename_from != self.rename_to
- @ classmethod
- def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes) -> Optional[bytes]:
+ @classmethod
+ def _pick_best_path(
+ cls, path_match: bytes, rename_match: bytes, path_fallback_match: bytes
+ ) -> Optional[bytes]:
if path_match:
return decode_path(path_match)
@@ -442,34 +513,51 @@ class Diff(object):
return None
- @ classmethod
- def _index_from_patch_format(cls, repo: 'Repo', proc: Union['Popen', 'Git.AutoInterrupt']) -> DiffIndex:
+ @classmethod
+ def _index_from_patch_format(
+ cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]
+ ) -> DiffIndex:
"""Create a new DiffIndex from the given text which must be in patch format
:param repo: is the repository we are operating on - it is required
:param stream: result of 'git diff' as a stream (supporting file protocol)
- :return: git.DiffIndex """
+ :return: git.DiffIndex"""
## FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise.
text_list: List[bytes] = []
- handle_process_output(proc, text_list.append, None, finalize_process, decode_streams=False)
+ handle_process_output(
+ proc, text_list.append, None, finalize_process, decode_streams=False
+ )
# for now, we have to bake the stream
- text = b''.join(text_list)
- index: 'DiffIndex' = DiffIndex()
+ text = b"".join(text_list)
+ index: "DiffIndex" = DiffIndex()
previous_header: Union[Match[bytes], None] = None
header: Union[Match[bytes], None] = None
a_path, b_path = None, None # for mypy
a_mode, b_mode = None, None # for mypy
for _header in cls.re_header.finditer(text):
- a_path_fallback, b_path_fallback, \
- old_mode, new_mode, \
- rename_from, rename_to, \
- new_file_mode, deleted_file_mode, copied_file_name, \
- a_blob_id, b_blob_id, b_mode, \
- a_path, b_path = _header.groups()
-
- new_file, deleted_file, copied_file = \
- bool(new_file_mode), bool(deleted_file_mode), bool(copied_file_name)
+ (
+ a_path_fallback,
+ b_path_fallback,
+ old_mode,
+ new_mode,
+ rename_from,
+ rename_to,
+ new_file_mode,
+ deleted_file_mode,
+ copied_file_name,
+ a_blob_id,
+ b_blob_id,
+ b_mode,
+ a_path,
+ b_path,
+ ) = _header.groups()
+
+ new_file, deleted_file, copied_file = (
+ bool(new_file_mode),
+ bool(deleted_file_mode),
+ bool(copied_file_name),
+ )
a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback)
b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback)
@@ -477,41 +565,53 @@ class Diff(object):
# Our only means to find the actual text is to see what has not been matched by our regex,
# and then retro-actively assign it to our index
if previous_header is not None:
- index[-1].diff = text[previous_header.end():_header.start()]
+ index[-1].diff = text[previous_header.end() : _header.start()]
# end assign actual diff
# Make sure the mode is set if the path is set. Otherwise the resulting blob is invalid
# We just use the one mode we should have parsed
- a_mode = old_mode or deleted_file_mode or (a_path and (b_mode or new_mode or new_file_mode))
+ a_mode = (
+ old_mode
+ or deleted_file_mode
+ or (a_path and (b_mode or new_mode or new_file_mode))
+ )
b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode)
- index.append(Diff(repo,
- a_path,
- b_path,
- a_blob_id and a_blob_id.decode(defenc),
- b_blob_id and b_blob_id.decode(defenc),
- a_mode and a_mode.decode(defenc),
- b_mode and b_mode.decode(defenc),
- new_file, deleted_file, copied_file,
- rename_from,
- rename_to,
- None, None, None))
+ index.append(
+ Diff(
+ repo,
+ a_path,
+ b_path,
+ a_blob_id and a_blob_id.decode(defenc),
+ b_blob_id and b_blob_id.decode(defenc),
+ a_mode and a_mode.decode(defenc),
+ b_mode and b_mode.decode(defenc),
+ new_file,
+ deleted_file,
+ copied_file,
+ rename_from,
+ rename_to,
+ None,
+ None,
+ None,
+ )
+ )
previous_header = _header
header = _header
# end for each header we parse
if index and header:
- index[-1].diff = text[header.end():]
+ index[-1].diff = text[header.end() :]
# end assign last diff
return index
- @ staticmethod
- def _handle_diff_line(lines_bytes: bytes, repo: 'Repo', index: DiffIndex) -> None:
+ @staticmethod
+ def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex) -> None:
lines = lines_bytes.decode(defenc)
- for line in lines.split(':')[1:]:
- meta, _, path = line.partition('\x00')
- path = path.rstrip('\x00')
+ for line in lines.split(":")[1:]:
+ meta, _, path = line.partition("\x00")
+ path = path.rstrip("\x00")
a_blob_id: Optional[str]
b_blob_id: Optional[str]
old_mode, new_mode, a_blob_id, b_blob_id, _change_type = meta.split(None, 4)
@@ -520,7 +620,7 @@ class Diff(object):
# 100: score (in case of copy and rename)
# assert is_change_type(_change_type[0]), f"Unexpected value for change_type received: {_change_type[0]}"
change_type: Lit_change_type = cast(Lit_change_type, _change_type[0])
- score_str = ''.join(_change_type[1:])
+ score_str = "".join(_change_type[1:])
score = int(score_str) if score_str.isdigit() else None
path = path.strip()
a_path = path.encode(defenc)
@@ -533,41 +633,60 @@ class Diff(object):
# NOTE: We cannot conclude from the existence of a blob to change type
# as diffs with the working do not have blobs yet
- if change_type == 'D':
+ if change_type == "D":
b_blob_id = None # Optional[str]
deleted_file = True
- elif change_type == 'A':
+ elif change_type == "A":
a_blob_id = None
new_file = True
- elif change_type == 'C':
+ elif change_type == "C":
copied_file = True
- a_path_str, b_path_str = path.split('\x00', 1)
+ a_path_str, b_path_str = path.split("\x00", 1)
a_path = a_path_str.encode(defenc)
b_path = b_path_str.encode(defenc)
- elif change_type == 'R':
- a_path_str, b_path_str = path.split('\x00', 1)
+ elif change_type == "R":
+ a_path_str, b_path_str = path.split("\x00", 1)
a_path = a_path_str.encode(defenc)
b_path = b_path_str.encode(defenc)
rename_from, rename_to = a_path, b_path
- elif change_type == 'T':
+ elif change_type == "T":
# Nothing to do
pass
# END add/remove handling
- diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode,
- new_file, deleted_file, copied_file, rename_from, rename_to,
- '', change_type, score)
+ diff = Diff(
+ repo,
+ a_path,
+ b_path,
+ a_blob_id,
+ b_blob_id,
+ old_mode,
+ new_mode,
+ new_file,
+ deleted_file,
+ copied_file,
+ rename_from,
+ rename_to,
+ "",
+ change_type,
+ score,
+ )
index.append(diff)
- @ classmethod
- def _index_from_raw_format(cls, repo: 'Repo', proc: 'Popen') -> 'DiffIndex':
+ @classmethod
+ def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex":
"""Create a new DiffIndex from the given stream which must be in raw format.
:return: git.DiffIndex"""
# handles
# :100644 100644 687099101... 37c5e30c8... M .gitignore
- index: 'DiffIndex' = DiffIndex()
- handle_process_output(proc, lambda byt: cls._handle_diff_line(byt, repo, index),
- None, finalize_process, decode_streams=False)
+ index: "DiffIndex" = DiffIndex()
+ handle_process_output(
+ proc,
+ lambda byt: cls._handle_diff_line(byt, repo, index),
+ None,
+ finalize_process,
+ decode_streams=False,
+ )
return index
diff --git a/git/exc.py b/git/exc.py
index 045ea9d2..487ce179 100644
--- a/git/exc.py
+++ b/git/exc.py
@@ -6,7 +6,7 @@
""" Module containing all exceptions thrown throughout the git package, """
from gitdb.exc import BadName # NOQA @UnusedWildImport skipcq: PYL-W0401, PYL-W0614
-from gitdb.exc import * # NOQA @UnusedWildImport skipcq: PYL-W0401, PYL-W0614
+from gitdb.exc import * # NOQA @UnusedWildImport skipcq: PYL-W0401, PYL-W0614
from git.compat import safe_decode
from git.util import remove_password_if_present
@@ -22,19 +22,19 @@ if TYPE_CHECKING:
class GitError(Exception):
- """ Base class for all package exceptions """
+ """Base class for all package exceptions"""
class InvalidGitRepositoryError(GitError):
- """ Thrown if the given repository appears to have an invalid format. """
+ """Thrown if the given repository appears to have an invalid format."""
class WorkTreeRepositoryUnsupported(InvalidGitRepositoryError):
- """ Thrown to indicate we can't handle work tree repositories """
+ """Thrown to indicate we can't handle work tree repositories"""
class NoSuchPathError(GitError, OSError):
- """ Thrown if a path could not be access by the system. """
+ """Thrown if a path could not be access by the system."""
class CommandError(GitError):
@@ -49,10 +49,13 @@ class CommandError(GitError):
#: "'%s' failed%s"
_msg = "Cmd('%s') failed%s"
- def __init__(self, command: Union[List[str], Tuple[str, ...], str],
- status: Union[str, int, None, Exception] = None,
- stderr: Union[bytes, str, None] = None,
- stdout: Union[bytes, str, None] = None) -> None:
+ def __init__(
+ self,
+ command: Union[List[str], Tuple[str, ...], str],
+ status: Union[str, int, None, Exception] = None,
+ stderr: Union[bytes, str, None] = None,
+ stdout: Union[bytes, str, None] = None,
+ ) -> None:
if not isinstance(command, (tuple, list)):
command = command.split()
self.command = remove_password_if_present(command)
@@ -62,41 +65,50 @@ class CommandError(GitError):
status = "%s('%s')" % (type(status).__name__, safe_decode(str(status)))
else:
try:
- status = 'exit code(%s)' % int(status)
+ status = "exit code(%s)" % int(status)
except (ValueError, TypeError):
s = safe_decode(str(status))
status = "'%s'" % s if isinstance(status, str) else s
self._cmd = safe_decode(self.command[0])
- self._cmdline = ' '.join(safe_decode(i) for i in self.command)
+ self._cmdline = " ".join(safe_decode(i) for i in self.command)
self._cause = status and " due to: %s" % status or "!"
stdout_decode = safe_decode(stdout)
stderr_decode = safe_decode(stderr)
- self.stdout = stdout_decode and "\n stdout: '%s'" % stdout_decode or ''
- self.stderr = stderr_decode and "\n stderr: '%s'" % stderr_decode or ''
+ self.stdout = stdout_decode and "\n stdout: '%s'" % stdout_decode or ""
+ self.stderr = stderr_decode and "\n stderr: '%s'" % stderr_decode or ""
def __str__(self) -> str:
return (self._msg + "\n cmdline: %s%s%s") % (
- self._cmd, self._cause, self._cmdline, self.stdout, self.stderr)
+ self._cmd,
+ self._cause,
+ self._cmdline,
+ self.stdout,
+ self.stderr,
+ )
class GitCommandNotFound(CommandError):
"""Thrown if we cannot find the `git` executable in the PATH or at the path given by
the GIT_PYTHON_GIT_EXECUTABLE environment variable"""
- def __init__(self, command: Union[List[str], Tuple[str], str], cause: Union[str, Exception]) -> None:
+ def __init__(
+ self, command: Union[List[str], Tuple[str], str], cause: Union[str, Exception]
+ ) -> None:
super(GitCommandNotFound, self).__init__(command, cause)
self._msg = "Cmd('%s') not found%s"
class GitCommandError(CommandError):
- """ Thrown if execution of the git command fails with non-zero status code. """
-
- def __init__(self, command: Union[List[str], Tuple[str, ...], str],
- status: Union[str, int, None, Exception] = None,
- stderr: Union[bytes, str, None] = None,
- stdout: Union[bytes, str, None] = None,
- ) -> None:
+ """Thrown if execution of the git command fails with non-zero status code."""
+
+ def __init__(
+ self,
+ command: Union[List[str], Tuple[str, ...], str],
+ status: Union[str, int, None, Exception] = None,
+ stderr: Union[bytes, str, None] = None,
+ stdout: Union[bytes, str, None] = None,
+ ) -> None:
super(GitCommandError, self).__init__(command, status, stderr, stdout)
@@ -114,8 +126,13 @@ class CheckoutError(GitError):
were checked out successfully and hence match the version stored in the
index"""
- def __init__(self, message: str, failed_files: Sequence[PathLike], valid_files: Sequence[PathLike],
- failed_reasons: List[str]) -> None:
+ def __init__(
+ self,
+ message: str,
+ failed_files: Sequence[PathLike],
+ valid_files: Sequence[PathLike],
+ failed_reasons: List[str],
+ ) -> None:
Exception.__init__(self, message)
self.failed_files = failed_files
@@ -140,10 +157,13 @@ class HookExecutionError(CommandError):
"""Thrown if a hook exits with a non-zero exit code. It provides access to the exit code and the string returned
via standard output"""
- def __init__(self, command: Union[List[str], Tuple[str, ...], str],
- status: Union[str, int, None, Exception],
- stderr: Union[bytes, str, None] = None,
- stdout: Union[bytes, str, None] = None) -> None:
+ def __init__(
+ self,
+ command: Union[List[str], Tuple[str, ...], str],
+ status: Union[str, int, None, Exception],
+ stderr: Union[bytes, str, None] = None,
+ stdout: Union[bytes, str, None] = None,
+ ) -> None:
super(HookExecutionError, self).__init__(command, status, stderr, stdout)
self._msg = "Hook('%s') failed%s"
@@ -152,7 +172,7 @@ class HookExecutionError(CommandError):
class RepositoryDirtyError(GitError):
"""Thrown whenever an operation on a repository fails as it has uncommitted changes that would be overwritten"""
- def __init__(self, repo: 'Repo', message: str) -> None:
+ def __init__(self, repo: "Repo", message: str) -> None:
self.repo = repo
self.message = message
diff --git a/git/ext/gitdb b/git/ext/gitdb
-Subproject 1c976835c5d1779a28b9e11afd1656152db26a6
+Subproject 4762d99d978586fcdf08ade552f4712bfde6ef2
diff --git a/git/index/base.py b/git/index/base.py
index 00e51bf5..48894833 100644
--- a/git/index/base.py
+++ b/git/index/base.py
@@ -15,12 +15,7 @@ from git.compat import (
force_bytes,
defenc,
)
-from git.exc import (
- GitCommandError,
- CheckoutError,
- GitError,
- InvalidGitRepositoryError
-)
+from git.exc import GitCommandError, CheckoutError, GitError, InvalidGitRepositoryError
from git.objects import (
Blob,
Submodule,
@@ -36,7 +31,7 @@ from git.util import (
file_contents_ro,
to_native_path_linux,
unbare_repo,
- to_bin_sha
+ to_bin_sha,
)
from gitdb.base import IStream
from gitdb.db import MemoryDB
@@ -52,23 +47,32 @@ from .fun import (
write_tree_from_cache,
stat_mode_to_index_mode,
S_IFGITLINK,
- run_commit_hook
+ run_commit_hook,
)
from .typ import (
BaseIndexEntry,
IndexEntry,
)
-from .util import (
- TemporaryFileSwap,
- post_clear_cache,
- default_index,
- git_working_dir
-)
+from .util import TemporaryFileSwap, post_clear_cache, default_index, git_working_dir
# typing -----------------------------------------------------------------------------
-from typing import (Any, BinaryIO, Callable, Dict, IO, Iterable, Iterator, List, NoReturn,
- Sequence, TYPE_CHECKING, Tuple, Type, Union)
+from typing import (
+ Any,
+ BinaryIO,
+ Callable,
+ Dict,
+ IO,
+ Iterable,
+ Iterator,
+ List,
+ NoReturn,
+ Sequence,
+ TYPE_CHECKING,
+ Tuple,
+ Type,
+ Union,
+)
from git.types import Commit_ish, PathLike
@@ -85,7 +89,7 @@ Treeish = Union[Tree, Commit, str, bytes]
# ------------------------------------------------------------------------------------
-__all__ = ('IndexFile', 'CheckoutError')
+__all__ = ("IndexFile", "CheckoutError")
class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
@@ -110,11 +114,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
Make sure you use index.write() once you are done manipulating the index directly
before operating on it using the git command"""
+
__slots__ = ("repo", "version", "entries", "_extension_data", "_file_path")
- _VERSION = 2 # latest version we support
+ _VERSION = 2 # latest version we support
S_IFGITLINK = S_IFGITLINK # a submodule
- def __init__(self, repo: 'Repo', file_path: Union[PathLike, None] = None) -> None:
+ def __init__(self, repo: "Repo", file_path: Union[PathLike, None] = None) -> None:
"""Initialize this Index instance, optionally from the given ``file_path``.
If no file_path is given, we will be created from the current index file.
@@ -122,7 +127,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
repository's index on demand."""
self.repo = repo
self.version = self._VERSION
- self._extension_data = b''
+ self._extension_data = b""
self._file_path: PathLike = file_path or self._index_path()
def _set_cache_(self, attr: str) -> None:
@@ -152,40 +157,48 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
@property
def path(self) -> PathLike:
- """ :return: Path to the index file we are representing """
+ """:return: Path to the index file we are representing"""
return self._file_path
def _delete_entries_cache(self) -> None:
"""Safely clear the entries cache so it can be recreated"""
try:
- del(self.entries)
+ del self.entries
except AttributeError:
# fails in python 2.6.5 with this exception
pass
# END exception handling
- #{ Serializable Interface
+ # { Serializable Interface
- def _deserialize(self, stream: IO) -> 'IndexFile':
+ def _deserialize(self, stream: IO) -> "IndexFile":
"""Initialize this instance with index values read from the given stream"""
- self.version, self.entries, self._extension_data, _conten_sha = read_cache(stream)
+ self.version, self.entries, self._extension_data, _conten_sha = read_cache(
+ stream
+ )
return self
def _entries_sorted(self) -> List[IndexEntry]:
""":return: list of entries, in a sorted fashion, first by path, then by stage"""
return sorted(self.entries.values(), key=lambda e: (e.path, e.stage))
- def _serialize(self, stream: IO, ignore_extension_data: bool = False) -> 'IndexFile':
+ def _serialize(
+ self, stream: IO, ignore_extension_data: bool = False
+ ) -> "IndexFile":
entries = self._entries_sorted()
- extension_data = self._extension_data # type: Union[None, bytes]
+ extension_data = self._extension_data # type: Union[None, bytes]
if ignore_extension_data:
extension_data = None
write_cache(entries, stream, extension_data)
return self
- #} END serializable interface
+ # } END serializable interface
- def write(self, file_path: Union[None, PathLike] = None, ignore_extension_data: bool = False) -> None:
+ def write(
+ self,
+ file_path: Union[None, PathLike] = None,
+ ignore_extension_data: bool = False,
+ ) -> None:
"""Write the current state to our file path or to the given one
:param file_path:
@@ -229,7 +242,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
@post_clear_cache
@default_index
- def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> 'IndexFile':
+ def merge_tree(
+ self, rhs: Treeish, base: Union[None, Treeish] = None
+ ) -> "IndexFile":
"""Merge the given rhs treeish into the current index, possibly taking
a common base treeish into account.
@@ -252,7 +267,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
be raised at the first conflicting path. If you want to have proper
merge resolution to be done by yourself, you have to commit the changed
index ( or make a valid tree from it ) and retry with a three-way
- index.from_tree call. """
+ index.from_tree call."""
# -i : ignore working tree status
# --aggressive : handle more merge cases
# -m : do an actual merge
@@ -265,8 +280,8 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
return self
@classmethod
- def new(cls, repo: 'Repo', *tree_sha: Union[str, Tree]) -> 'IndexFile':
- """ Merge the given treeish revisions into a new index which is returned.
+ def new(cls, repo: "Repo", *tree_sha: Union[str, Tree]) -> "IndexFile":
+ """Merge the given treeish revisions into a new index which is returned.
This method behaves like git-read-tree --aggressive when doing the merge.
:param repo: The repository treeish are located in.
@@ -283,15 +298,18 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
inst = cls(repo)
# convert to entries dict
- 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)))
+ 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),
+ )
+ )
inst.entries = entries
return inst
@classmethod
- def from_tree(cls, repo: 'Repo', *treeish: Treeish, **kwargs: Any) -> 'IndexFile':
+ def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile":
"""Merge the given treeish revisions into a new index which is returned.
The original index will remain unaltered
@@ -326,7 +344,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
it will be temporarily moved out of the way to assure there are no unsuspected
interferences."""
if len(treeish) == 0 or len(treeish) > 3:
- raise ValueError("Please specify between 1 and 3 treeish, got %i" % len(treeish))
+ raise ValueError(
+ "Please specify between 1 and 3 treeish, got %i" % len(treeish)
+ )
arg_list: List[Union[Treeish, str]] = []
# ignore that working tree and index possibly are out of date
@@ -339,7 +359,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# tmp file created in git home directory to be sure renaming
# works - /tmp/ dirs could be on another device
- tmp_index = tempfile.mktemp('', '', repo.git_dir)
+ tmp_index = tempfile.mktemp("", "", repo.git_dir)
arg_list.append("--index-output=%s" % tmp_index)
arg_list.extend(treeish)
@@ -348,12 +368,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# Unfortunately there is no 'soft' way to do it.
# The TemporaryFileSwap assure the original file get put back
if repo.git_dir:
- index_handler = TemporaryFileSwap(join_path_native(repo.git_dir, 'index'))
+ index_handler = TemporaryFileSwap(join_path_native(repo.git_dir, "index"))
try:
repo.git.read_tree(*arg_list, **kwargs)
index = cls(repo, tmp_index)
- index.entries # force it to read the file as we will delete the temp-file
- del(index_handler) # release as soon as possible
+ index.entries # force it to read the file as we will delete the temp-file
+ del index_handler # release as soon as possible
finally:
if osp.exists(tmp_index):
os.remove(tmp_index)
@@ -363,14 +383,18 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# UTILITIES
@unbare_repo
- def _iter_expand_paths(self: 'IndexFile', paths: Sequence[PathLike]) -> Iterator[PathLike]:
+ def _iter_expand_paths(
+ self: "IndexFile", paths: Sequence[PathLike]
+ ) -> Iterator[PathLike]:
"""Expand the directories in list of paths to the corresponding paths accordingly,
Note: git will add items multiple times even if a glob overlapped
with manually specified paths or if paths where specified multiple
times - we respect that and do not prune"""
+
def raise_exc(e: Exception) -> NoReturn:
raise e
+
r = str(self.repo.working_tree_dir)
rs = r + os.sep
for path in paths:
@@ -380,18 +404,20 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# END make absolute path
try:
- st = os.lstat(abs_path) # handles non-symlinks as well
+ st = os.lstat(abs_path) # handles non-symlinks as well
except OSError:
# the lstat call may fail as the path may contain globs as well
pass
else:
if S_ISLNK(st.st_mode):
- yield abs_path.replace(rs, '')
+ yield abs_path.replace(rs, "")
continue
# end check symlink
# if the path is not already pointing to an existing file, resolve globs if possible
- if not os.path.exists(abs_path) and ('?' in abs_path or '*' in abs_path or '[' in abs_path):
+ if not os.path.exists(abs_path) and (
+ "?" in abs_path or "*" in abs_path or "[" in abs_path
+ ):
resolved_paths = glob.glob(abs_path)
# not abs_path in resolved_paths:
# a glob() resolving to the same path we are feeding it with
@@ -401,25 +427,31 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# whose name contains wildcard characters.
if abs_path not in resolved_paths:
for f in self._iter_expand_paths(glob.glob(abs_path)):
- yield str(f).replace(rs, '')
+ yield str(f).replace(rs, "")
continue
# END glob handling
try:
for root, _dirs, files in os.walk(abs_path, onerror=raise_exc):
for rela_file in files:
# add relative paths only
- yield osp.join(root.replace(rs, ''), rela_file)
+ yield osp.join(root.replace(rs, ""), rela_file)
# END for each file in subdir
# END for each subdirectory
except OSError:
# was a file or something that could not be iterated
- yield abs_path.replace(rs, '')
+ yield abs_path.replace(rs, "")
# END path exception handling
# END for each path
- def _write_path_to_stdin(self, proc: 'Popen', filepath: PathLike, item: PathLike, fmakeexc: Callable[..., GitError],
- fprogress: Callable[[PathLike, bool, PathLike], None],
- read_from_stdout: bool = True) -> Union[None, str]:
+ def _write_path_to_stdin(
+ self,
+ proc: "Popen",
+ filepath: PathLike,
+ item: PathLike,
+ fmakeexc: Callable[..., GitError],
+ fprogress: Callable[[PathLike, bool, PathLike], None],
+ read_from_stdout: bool = True,
+ ) -> Union[None, str]:
"""Write path to proc.stdin and make sure it processes the item, including progress.
:return: stdout string
@@ -451,15 +483,16 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
fprogress(filepath, True, item)
return rval
- def iter_blobs(self, predicate: Callable[[Tuple[StageType, Blob]], bool] = lambda t: True
- ) -> Iterator[Tuple[StageType, Blob]]:
+ def iter_blobs(
+ self, predicate: Callable[[Tuple[StageType, Blob]], bool] = lambda t: True
+ ) -> Iterator[Tuple[StageType, Blob]]:
"""
:return: Iterator yielding tuples of Blob objects and stages, tuple(stage, Blob)
:param predicate:
Function(t) returning True if tuple(stage, Blob) should be yielded by the
iterator. A default filter, the BlobFilter, allows you to yield blobs
- only if they match a given list of paths. """
+ only if they match a given list of paths."""
for entry in self.entries.values():
blob = entry.to_blob(self.repo)
blob.size = entry.size
@@ -491,11 +524,13 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
return path_map
- @ classmethod
- def entry_key(cls, *entry: Union[BaseIndexEntry, PathLike, StageType]) -> Tuple[PathLike, StageType]:
+ @classmethod
+ def entry_key(
+ cls, *entry: Union[BaseIndexEntry, PathLike, StageType]
+ ) -> Tuple[PathLike, StageType]:
return entry_key(*entry)
- def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> 'IndexFile':
+ def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> "IndexFile":
"""Resolve the blobs given in blob iterator. This will effectively remove the
index entries of the respective path at all non-null stages and add the given
blob as new stage null blob.
@@ -519,7 +554,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# delete all possible stages
for stage in (1, 2, 3):
try:
- del(self.entries[(blob.path, stage)])
+ del self.entries[(blob.path, stage)]
except KeyError:
pass
# END ignore key errors
@@ -530,7 +565,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
return self
- def update(self) -> 'IndexFile':
+ def update(self) -> "IndexFile":
"""Reread the contents of our index file, discarding all cached information
we might have.
@@ -550,7 +585,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
does not yet exist in the object database. This could happen if you added
Entries to the index directly.
:raise ValueError: if there are no entries in the cache
- :raise UnmergedEntriesError: """
+ :raise UnmergedEntriesError:"""
# we obtain no lock as we just flush our contents to disk as tree
# If we are a new index, the entries access will load our data accordingly
mdb = MemoryDB()
@@ -562,13 +597,14 @@ 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 = Tree(self.repo, binsha, path="")
root_tree._cache = tree_items
return root_tree
- def _process_diff_args(self, # type: ignore[override]
- args: List[Union[str, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]]
- ) -> List[Union[str, 'git_diff.Diffable', Type['git_diff.Diffable.Index']]]:
+ def _process_diff_args(
+ self, # type: ignore[override]
+ args: List[Union[str, "git_diff.Diffable", Type["git_diff.Diffable.Index"]]],
+ ) -> List[Union[str, "git_diff.Diffable", Type["git_diff.Diffable.Index"]]]:
try:
args.pop(args.index(self))
except IndexError:
@@ -585,12 +621,16 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
if self.repo.bare:
raise InvalidGitRepositoryError("require non-bare repository")
if not str(path).startswith(str(self.repo.working_tree_dir)):
- raise ValueError("Absolute path %r is not in git repository at %r" % (path, self.repo.working_tree_dir))
+ raise ValueError(
+ "Absolute path %r is not in git repository at %r"
+ % (path, self.repo.working_tree_dir)
+ )
return os.path.relpath(path, self.repo.working_tree_dir)
- def _preprocess_add_items(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']]
- ) -> Tuple[List[PathLike], List[BaseIndexEntry]]:
- """ Split the items into two lists of path strings and BaseEntries. """
+ def _preprocess_add_items(
+ self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]]
+ ) -> Tuple[List[PathLike], List[BaseIndexEntry]]:
+ """Split the items into two lists of path strings and BaseEntries."""
paths = []
entries = []
# if it is a string put in list
@@ -612,43 +652,58 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry:
"""Store file at filepath in the database and return the base index entry
Needs the git_working_dir decorator active ! This must be assured in the calling code"""
- st = os.lstat(filepath) # handles non-symlinks as well
+ 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: Callable[[], BinaryIO] = lambda: BytesIO(force_bytes(os.readlink(filepath),
- encoding=defenc))
+ open_stream: Callable[[], BinaryIO] = lambda: BytesIO(
+ force_bytes(os.readlink(filepath), encoding=defenc)
+ )
else:
- open_stream = lambda: open(filepath, 'rb')
+ open_stream = lambda: open(filepath, "rb")
with open_stream() as stream:
fprogress(filepath, False, filepath)
istream = self.repo.odb.store(IStream(Blob.type, st.st_size, stream))
fprogress(filepath, True, filepath)
- return BaseIndexEntry((stat_mode_to_index_mode(st.st_mode),
- istream.binsha, 0, to_native_path_linux(filepath)))
+ return BaseIndexEntry(
+ (
+ stat_mode_to_index_mode(st.st_mode),
+ istream.binsha,
+ 0,
+ to_native_path_linux(filepath),
+ )
+ )
- @ unbare_repo
- @ git_working_dir
- def _entries_for_paths(self, paths: List[str], path_rewriter: Callable, fprogress: Callable,
- entries: List[BaseIndexEntry]) -> List[BaseIndexEntry]:
+ @unbare_repo
+ @git_working_dir
+ def _entries_for_paths(
+ self,
+ paths: List[str],
+ path_rewriter: Callable,
+ fprogress: Callable,
+ entries: List[BaseIndexEntry],
+ ) -> List[BaseIndexEntry]:
entries_added: List[BaseIndexEntry] = []
if path_rewriter:
for path in paths:
if osp.isabs(path):
abspath = path
- gitrelative_path = path[len(str(self.repo.working_tree_dir)) + 1:]
+ gitrelative_path = path[len(str(self.repo.working_tree_dir)) + 1 :]
else:
gitrelative_path = path
if self.repo.working_tree_dir:
abspath = osp.join(self.repo.working_tree_dir, gitrelative_path)
# end obtain relative and absolute paths
- blob = Blob(self.repo, Blob.NULL_BIN_SHA,
- stat_mode_to_index_mode(os.stat(abspath).st_mode),
- to_native_path_linux(gitrelative_path))
+ blob = Blob(
+ self.repo,
+ Blob.NULL_BIN_SHA,
+ stat_mode_to_index_mode(os.stat(abspath).st_mode),
+ to_native_path_linux(gitrelative_path),
+ )
# TODO: variable undefined
entries.append(BaseIndexEntry.from_blob(blob))
# END for each path
- del(paths[:])
+ del paths[:]
# END rewrite paths
# HANDLE PATHS
@@ -659,9 +714,15 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# END path handling
return entries_added
- def add(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], force: bool = True,
- fprogress: Callable = lambda *args: None, path_rewriter: Union[Callable[..., PathLike], None] = None,
- write: bool = True, write_extension_data: bool = False) -> List[BaseIndexEntry]:
+ def add(
+ self,
+ items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]],
+ force: bool = True,
+ fprogress: Callable = lambda *args: None,
+ path_rewriter: Union[Callable[..., PathLike], None] = None,
+ write: bool = True,
+ write_extension_data: bool = False,
+ ) -> List[BaseIndexEntry]:
"""Add files from the working tree, specific blobs or BaseIndexEntries
to the index.
@@ -769,30 +830,43 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# That way, we are OK on a bare repository as well.
# If there are no paths, the rewriter has nothing to do either
if paths:
- entries_added.extend(self._entries_for_paths(paths, path_rewriter, fprogress, entries))
+ entries_added.extend(
+ self._entries_for_paths(paths, path_rewriter, fprogress, entries)
+ )
# HANDLE ENTRIES
if entries:
null_mode_entries = [e for e in entries if e.mode == 0]
if null_mode_entries:
raise ValueError(
- "At least one Entry has a null-mode - please use index.remove to remove files for clarity")
+ "At least one Entry has a null-mode - please use index.remove to remove files for clarity"
+ )
# END null mode should be remove
# HANDLE ENTRY OBJECT CREATION
# create objects if required, otherwise go with the existing shas
- null_entries_indices = [i for i, e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA]
+ null_entries_indices = [
+ i for i, e in enumerate(entries) if e.binsha == Object.NULL_BIN_SHA
+ ]
if null_entries_indices:
- @ git_working_dir
- def handle_null_entries(self: 'IndexFile') -> None:
+
+ @git_working_dir
+ def handle_null_entries(self: "IndexFile") -> None:
for ei in null_entries_indices:
null_entry = entries[ei]
new_entry = self._store_path(null_entry.path, fprogress)
# update null entry
entries[ei] = BaseIndexEntry(
- (null_entry.mode, new_entry.binsha, null_entry.stage, null_entry.path))
+ (
+ null_entry.mode,
+ new_entry.binsha,
+ null_entry.stage,
+ null_entry.path,
+ )
+ )
# END for each entry index
+
# end closure
handle_null_entries(self)
# END null_entry handling
@@ -802,7 +876,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# all object sha's
if path_rewriter:
for i, e in enumerate(entries):
- entries[i] = BaseIndexEntry((e.mode, e.binsha, e.stage, path_rewriter(e)))
+ entries[i] = BaseIndexEntry(
+ (e.mode, e.binsha, e.stage, path_rewriter(e))
+ )
# END for each entry
# END handle path rewriting
@@ -828,8 +904,12 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
return entries_added
- def _items_to_rela_paths(self, items: Union[PathLike, Sequence[Union[PathLike, BaseIndexEntry, Blob, Submodule]]]
- ) -> List[PathLike]:
+ def _items_to_rela_paths(
+ self,
+ items: Union[
+ PathLike, Sequence[Union[PathLike, BaseIndexEntry, Blob, Submodule]]
+ ],
+ ) -> List[PathLike]:
"""Returns a list of repo-relative paths from the given items which
may be absolute or relative paths, entries or blobs"""
paths = []
@@ -847,10 +927,14 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# END for each item
return paths
- @ post_clear_cache
- @ default_index
- def remove(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], working_tree: bool = False,
- **kwargs: Any) -> List[str]:
+ @post_clear_cache
+ @default_index
+ def remove(
+ self,
+ items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]],
+ working_tree: bool = False,
+ **kwargs: Any
+ ) -> List[str]:
"""Remove the given items from the index and optionally from
the working tree as well.
@@ -885,7 +969,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
List(path_string, ...) list of repository relative paths that have
been removed effectively.
This is interesting to know in case you have provided a directory or
- globs. Paths are relative to the repository. """
+ globs. Paths are relative to the repository."""
args = []
if not working_tree:
args.append("--cached")
@@ -899,10 +983,14 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# rm 'path'
return [p[4:-1] for p in removed_paths]
- @ post_clear_cache
- @ default_index
- def move(self, items: Sequence[Union[PathLike, Blob, BaseIndexEntry, 'Submodule']], skip_errors: bool = False,
- **kwargs: Any) -> List[Tuple[str, str]]:
+ @post_clear_cache
+ @default_index
+ def move(
+ self,
+ items: Sequence[Union[PathLike, Blob, BaseIndexEntry, "Submodule"]],
+ skip_errors: bool = False,
+ **kwargs: Any
+ ) -> List[Tuple[str, str]]:
"""Rename/move the items, whereas the last item is considered the destination of
the move operation. If the destination is a file, the first item ( of two )
must be a file as well. If the destination is a directory, it may be preceded
@@ -928,14 +1016,16 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
GitCommandError: If git could not handle your request"""
args = []
if skip_errors:
- args.append('-k')
+ args.append("-k")
paths = self._items_to_rela_paths(items)
if len(paths) < 2:
- raise ValueError("Please provide at least one source and one destination of the move operation")
+ raise ValueError(
+ "Please provide at least one source and one destination of the move operation"
+ )
- was_dry_run = kwargs.pop('dry_run', kwargs.pop('n', None))
- kwargs['dry_run'] = True
+ was_dry_run = kwargs.pop("dry_run", kwargs.pop("n", None))
+ kwargs["dry_run"] = True
# first execute rename in dryrun so the command tells us what it actually does
# ( for later output )
@@ -945,7 +1035,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# parse result - first 0:n/2 lines are 'checking ', the remaining ones
# are the 'renaming' ones which we parse
for ln in range(int(len(mvlines) / 2), len(mvlines)):
- tokens = mvlines[ln].split(' to ')
+ tokens = mvlines[ln].split(" to ")
assert len(tokens) == 2, "Too many tokens in %s" % mvlines[ln]
# [0] = Renaming x
@@ -959,20 +1049,22 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# END handle dryrun
# now apply the actual operation
- kwargs.pop('dry_run')
+ kwargs.pop("dry_run")
self.repo.git.mv(args, paths, **kwargs)
return out
- def commit(self,
- message: str,
- parent_commits: Union[Commit_ish, None] = None,
- head: bool = True,
- author: Union[None, 'Actor'] = None,
- committer: Union[None, 'Actor'] = None,
- author_date: Union[str, None] = None,
- commit_date: Union[str, None] = None,
- skip_hooks: bool = False) -> Commit:
+ def commit(
+ self,
+ message: str,
+ parent_commits: Union[Commit_ish, None] = None,
+ head: bool = True,
+ author: Union[None, "Actor"] = None,
+ committer: Union[None, "Actor"] = None,
+ author_date: Union[str, None] = None,
+ commit_date: Union[str, None] = None,
+ skip_hooks: bool = False,
+ ) -> Commit:
"""Commit the current default index file, creating a commit object.
For more information on the arguments, see Commit.create_from_tree().
@@ -982,18 +1074,26 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
or `--no-verify` on the command line.
:return: Commit object representing the new commit"""
if not skip_hooks:
- run_commit_hook('pre-commit', self)
+ run_commit_hook("pre-commit", self)
self._write_commit_editmsg(message)
- run_commit_hook('commit-msg', self, self._commit_editmsg_filepath())
+ run_commit_hook("commit-msg", self, self._commit_editmsg_filepath())
message = self._read_commit_editmsg()
self._remove_commit_editmsg()
tree = self.write_tree()
- rval = Commit.create_from_tree(self.repo, tree, message, parent_commits,
- head, author=author, committer=committer,
- author_date=author_date, commit_date=commit_date)
+ rval = Commit.create_from_tree(
+ self.repo,
+ tree,
+ message,
+ parent_commits,
+ head,
+ author=author,
+ committer=committer,
+ author_date=author_date,
+ commit_date=commit_date,
+ )
if not skip_hooks:
- run_commit_hook('post-commit', self)
+ run_commit_hook("post-commit", self)
return rval
def _write_commit_editmsg(self, message: str) -> None:
@@ -1010,13 +1110,15 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
def _commit_editmsg_filepath(self) -> str:
return osp.join(self.repo.common_dir, "COMMIT_EDITMSG")
- def _flush_stdin_and_wait(cls, proc: 'Popen[bytes]', ignore_stdout: bool = False) -> bytes:
+ def _flush_stdin_and_wait(
+ cls, proc: "Popen[bytes]", ignore_stdout: bool = False
+ ) -> bytes:
stdin_IO = proc.stdin
if stdin_IO:
stdin_IO.flush()
stdin_IO.close()
- stdout = b''
+ stdout = b""
if not ignore_stdout and proc.stdout:
stdout = proc.stdout.read()
@@ -1025,10 +1127,14 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
proc.wait()
return stdout
- @ default_index
- def checkout(self, paths: Union[None, Iterable[PathLike]] = None, force: bool = False,
- fprogress: Callable = lambda *args: None, **kwargs: Any
- ) -> Union[None, Iterator[PathLike], Sequence[PathLike]]:
+ @default_index
+ def checkout(
+ self,
+ paths: Union[None, Iterable[PathLike]] = None,
+ force: bool = False,
+ fprogress: Callable = lambda *args: None,
+ **kwargs: Any
+ ) -> Union[None, Iterator[PathLike], Sequence[PathLike]]:
"""Checkout the given paths or all files from the version known to the index into
the working tree.
@@ -1070,7 +1176,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
the working tree will not be deleted. This behaviour is fundamentally
different to *head.checkout*, i.e. if you want git-checkout like behaviour,
use head.checkout instead of index.checkout.
- """
+ """
args = ["--index"]
if force:
args.append("--force")
@@ -1079,7 +1185,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
failed_reasons = []
unknown_lines = []
- def handle_stderr(proc: 'Popen[bytes]', iter_checked_out_files: Iterable[PathLike]) -> None:
+ def handle_stderr(
+ proc: "Popen[bytes]", iter_checked_out_files: Iterable[PathLike]
+ ) -> None:
stderr_IO = proc.stderr
if not stderr_IO:
@@ -1089,20 +1197,27 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# line contents:
stderr = stderr_bytes.decode(defenc)
# git-checkout-index: this already exists
- endings = (' already exists', ' is not in the cache', ' does not exist at stage', ' is unmerged')
+ endings = (
+ " already exists",
+ " is not in the cache",
+ " does not exist at stage",
+ " is unmerged",
+ )
for line in stderr.splitlines():
- if not line.startswith("git checkout-index: ") and not line.startswith("git-checkout-index: "):
+ if not line.startswith("git checkout-index: ") and not line.startswith(
+ "git-checkout-index: "
+ ):
is_a_dir = " is a directory"
unlink_issue = "unable to unlink old '"
- already_exists_issue = ' already exists, no checkout' # created by entry.c:checkout_entry(...)
+ already_exists_issue = " already exists, no checkout" # created by entry.c:checkout_entry(...)
if line.endswith(is_a_dir):
- failed_files.append(line[:-len(is_a_dir)])
+ failed_files.append(line[: -len(is_a_dir)])
failed_reasons.append(is_a_dir)
elif line.startswith(unlink_issue):
- failed_files.append(line[len(unlink_issue):line.rfind("'")])
+ failed_files.append(line[len(unlink_issue) : line.rfind("'")])
failed_reasons.append(unlink_issue)
elif line.endswith(already_exists_issue):
- failed_files.append(line[:-len(already_exists_issue)])
+ failed_files.append(line[: -len(already_exists_issue)])
failed_reasons.append(already_exists_issue)
else:
unknown_lines.append(line)
@@ -1111,7 +1226,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
for e in endings:
if line.endswith(e):
- failed_files.append(line[20:-len(e)])
+ failed_files.append(line[20 : -len(e)])
failed_reasons.append(e)
break
# END if ending matches
@@ -1123,12 +1238,16 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
valid_files = list(set(iter_checked_out_files) - set(failed_files))
raise CheckoutError(
"Some files could not be checked out from the index due to local modifications",
- failed_files, valid_files, failed_reasons)
+ failed_files,
+ valid_files,
+ failed_reasons,
+ )
+
# END stderr handler
if paths is None:
args.append("--all")
- kwargs['as_process'] = 1
+ kwargs["as_process"] = 1
fprogress(None, False, None)
proc = self.repo.git.checkout_index(*args, **kwargs)
proc.wait()
@@ -1146,11 +1265,13 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
self.entries
args.append("--stdin")
- kwargs['as_process'] = True
- kwargs['istream'] = subprocess.PIPE
+ kwargs["as_process"] = True
+ kwargs["istream"] = subprocess.PIPE
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())
+ make_exc = lambda: GitCommandError(
+ ("git-checkout-index",) + tuple(args), 128, proc.stderr.read()
+ )
checked_out_files: List[PathLike] = []
for path in paths:
@@ -1162,13 +1283,14 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
self.entries[(co_path, 0)]
except KeyError:
folder = str(co_path)
- if not folder.endswith('/'):
- folder += '/'
+ if not folder.endswith("/"):
+ folder += "/"
for entry in self.entries.values():
if str(entry.path).startswith(folder):
p = entry.path
- self._write_path_to_stdin(proc, p, p, make_exc,
- fprogress, read_from_stdout=False)
+ self._write_path_to_stdin(
+ proc, p, p, make_exc, fprogress, read_from_stdout=False
+ )
checked_out_files.append(p)
path_is_directory = True
# END if entry is in directory
@@ -1176,8 +1298,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# END path exception handlnig
if not path_is_directory:
- self._write_path_to_stdin(proc, co_path, path, make_exc,
- fprogress, read_from_stdout=False)
+ self._write_path_to_stdin(
+ proc, co_path, path, make_exc, fprogress, read_from_stdout=False
+ )
checked_out_files.append(co_path)
# END path is a file
# END for each path
@@ -1187,16 +1310,24 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# Without parsing stdout we don't know what failed.
raise CheckoutError(
"Some files could not be checked out from the index, probably because they didn't exist.",
- failed_files, [], failed_reasons)
+ failed_files,
+ [],
+ failed_reasons,
+ )
handle_stderr(proc, checked_out_files)
return checked_out_files
# END paths handling
- @ default_index
- def reset(self, commit: Union[Commit, 'Reference', str] = 'HEAD', working_tree: bool = False,
- paths: Union[None, Iterable[PathLike]] = None,
- head: bool = False, **kwargs: Any) -> 'IndexFile':
+ @default_index
+ def reset(
+ self,
+ commit: Union[Commit, "Reference", str] = "HEAD",
+ working_tree: bool = False,
+ paths: Union[None, Iterable[PathLike]] = None,
+ head: bool = False,
+ **kwargs: Any
+ ) -> "IndexFile":
"""Reset the index to reflect the tree at the given commit. This will not
adjust our HEAD reference as opposed to HEAD.reset by default.
@@ -1228,7 +1359,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
checkout the files according to their state in the index.
If you want git-reset like behaviour, use *HEAD.reset* instead.
- :return: self """
+ :return: self"""
# what we actually want to do is to merge the tree into our existing
# index, which is what git-read-tree does
new_inst = type(self).from_tree(self.repo, commit)
@@ -1244,7 +1375,7 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
except KeyError:
# if key is not in theirs, it musn't be in ours
try:
- del(self.entries[key])
+ del self.entries[key]
except KeyError:
pass
# END handle deletion keyerror
@@ -1258,17 +1389,23 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# END handle working tree
if head:
- self.repo.head.set_commit(self.repo.commit(commit), logmsg="%s: Updating HEAD" % commit)
+ self.repo.head.set_commit(
+ self.repo.commit(commit), logmsg="%s: Updating HEAD" % commit
+ )
# END handle head change
return self
# @ default_index, breaks typing for some reason, copied into function
- def diff(self, # type: ignore[override]
- other: Union[Type['git_diff.Diffable.Index'], 'Tree', 'Commit', str, None] = git_diff.Diffable.Index,
- paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
- create_patch: bool = False, **kwargs: Any
- ) -> git_diff.DiffIndex:
+ def diff(
+ self, # type: ignore[override]
+ other: Union[
+ Type["git_diff.Diffable.Index"], "Tree", "Commit", str, None
+ ] = git_diff.Diffable.Index,
+ paths: Union[PathLike, List[PathLike], Tuple[PathLike, ...], None] = None,
+ create_patch: bool = False,
+ **kwargs: Any
+ ) -> git_diff.DiffIndex:
"""Diff this index against the working copy or a Tree or Commit object
For a documentation of the parameters and return values, see,
@@ -1282,7 +1419,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
# only run if we are the default repository index
if self._file_path != self._index_path():
raise AssertionError(
- "Cannot call %r on indices that do not represent the default git index" % self.diff())
+ "Cannot call %r on indices that do not represent the default git index"
+ % self.diff()
+ )
# index against index is always empty
if other is self.Index:
return git_diff.DiffIndex()
@@ -1296,14 +1435,16 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable):
if isinstance(other, Object): # for Tree or Commit
# invert the existing R flag
- cur_val = kwargs.get('R', False)
- kwargs['R'] = not cur_val
+ cur_val = kwargs.get("R", False)
+ kwargs["R"] = not cur_val
return other.diff(self.Index, paths, create_patch, **kwargs)
# END diff against other item handling
# if other is not None here, something is wrong
if other is not None:
- raise ValueError("other must be None, Diffable.Index, a Tree or Commit, was %r" % other)
+ raise ValueError(
+ "other must be None, Diffable.Index, a Tree or Commit, was %r" % other
+ )
# diff against working copy - can be handled by superclass natively
return super(IndexFile, self).diff(other, paths, create_patch, **kwargs)
diff --git a/git/index/fun.py b/git/index/fun.py
index acab7423..e8dead86 100644
--- a/git/index/fun.py
+++ b/git/index/fun.py
@@ -25,14 +25,11 @@ from git.compat import (
is_win,
safe_decode,
)
-from git.exc import (
- UnmergedEntriesError,
- HookExecutionError
-)
+from git.exc import UnmergedEntriesError, HookExecutionError
from git.objects.fun import (
tree_to_stream,
traverse_tree_recursive,
- traverse_trees_recursive
+ traverse_trees_recursive,
)
from git.util import IndexFileSHA1Writer, finalize_process
from gitdb.base import IStream
@@ -40,20 +37,12 @@ from gitdb.typ import str_tree_type
import os.path as osp
-from .typ import (
- BaseIndexEntry,
- IndexEntry,
- CE_NAMEMASK,
- CE_STAGESHIFT
-)
-from .util import (
- pack,
- unpack
-)
+from .typ import BaseIndexEntry, IndexEntry, CE_NAMEMASK, CE_STAGESHIFT
+from .util import pack, unpack
# typing -----------------------------------------------------------------------------
-from typing import (Dict, IO, List, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast)
+from typing import Dict, IO, List, Sequence, TYPE_CHECKING, Tuple, Type, Union, cast
from git.types import PathLike
@@ -61,40 +50,49 @@ if TYPE_CHECKING:
from .base import IndexFile
from git.db import GitCmdObjectDB
from git.objects.tree import TreeCacheTup
+
# from git.objects.fun import EntryTupOrNone
# ------------------------------------------------------------------------------------
-S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule
+S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule
CE_NAMEMASK_INV = ~CE_NAMEMASK
-__all__ = ('write_cache', 'read_cache', 'write_tree_from_cache', 'entry_key',
- 'stat_mode_to_index_mode', 'S_IFGITLINK', 'run_commit_hook', 'hook_path')
+__all__ = (
+ "write_cache",
+ "read_cache",
+ "write_tree_from_cache",
+ "entry_key",
+ "stat_mode_to_index_mode",
+ "S_IFGITLINK",
+ "run_commit_hook",
+ "hook_path",
+)
def hook_path(name: str, git_dir: PathLike) -> str:
""":return: path to the given named hook in the given git repository directory"""
- return osp.join(git_dir, 'hooks', name)
+ return osp.join(git_dir, "hooks", name)
def _has_file_extension(path):
return osp.splitext(path)[1]
-def run_commit_hook(name: str, index: 'IndexFile', *args: str) -> None:
+def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None:
"""Run the commit hook of the given name. Silently ignores hooks that do not exist.
:param name: name of hook, like 'pre-commit'
:param index: IndexFile instance
:param args: arguments passed to hook file
- :raises HookExecutionError: """
+ :raises HookExecutionError:"""
hp = hook_path(name, index.repo.git_dir)
if not os.access(hp, os.X_OK):
return None
env = os.environ.copy()
- env['GIT_INDEX_FILE'] = safe_decode(str(index.path))
- env['GIT_EDITOR'] = ':'
+ env["GIT_INDEX_FILE"] = safe_decode(str(index.path))
+ env["GIT_EDITOR"] = ":"
cmd = [hp]
try:
if is_win and not _has_file_extension(hp):
@@ -102,22 +100,26 @@ def run_commit_hook(name: str, index: 'IndexFile', *args: str) -> None:
# (doesn't understand shebangs). Try using bash to run the hook.
relative_hp = Path(hp).relative_to(index.repo.working_dir).as_posix()
cmd = ["bash.exe", relative_hp]
-
- cmd = subprocess.Popen(cmd + list(args),
- env=env,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- cwd=index.repo.working_dir,
- close_fds=is_posix,
- creationflags=PROC_CREATIONFLAGS,)
+
+ cmd = subprocess.Popen(
+ cmd + list(args),
+ env=env,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ cwd=index.repo.working_dir,
+ close_fds=is_posix,
+ creationflags=PROC_CREATIONFLAGS,
+ )
except Exception as ex:
raise HookExecutionError(hp, ex) from ex
else:
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)
+ handle_process_output(
+ cmd, stdout_list.append, stderr_list.append, finalize_process
+ )
+ stdout = "".join(stdout_list)
+ stderr = "".join(stderr_list)
if cmd.returncode != 0:
stdout = force_text(stdout, defenc)
stderr = force_text(stderr, defenc)
@@ -128,16 +130,21 @@ def run_commit_hook(name: str, index: 'IndexFile', *args: str) -> None:
def stat_mode_to_index_mode(mode: int) -> int:
"""Convert the given mode from a stat call to the corresponding index mode
and return it"""
- if S_ISLNK(mode): # symlinks
+ if S_ISLNK(mode): # symlinks
return S_IFLNK
- if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules
+ if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules
return S_IFGITLINK
- return S_IFREG | (mode & S_IXUSR and 0o755 or 0o644) # blobs with or without executable bit
+ return S_IFREG | (
+ mode & S_IXUSR and 0o755 or 0o644
+ ) # blobs with or without executable bit
-def write_cache(entries: Sequence[Union[BaseIndexEntry, 'IndexEntry']], stream: IO[bytes],
- extension_data: Union[None, bytes] = None,
- ShaStreamCls: Type[IndexFileSHA1Writer] = IndexFileSHA1Writer) -> None:
+def write_cache(
+ entries: Sequence[Union[BaseIndexEntry, "IndexEntry"]],
+ stream: IO[bytes],
+ extension_data: Union[None, bytes] = None,
+ ShaStreamCls: Type[IndexFileSHA1Writer] = IndexFileSHA1Writer,
+) -> None:
"""Write the cache represented by entries to a stream
:param entries: **sorted** list of entries
@@ -163,17 +170,28 @@ def write_cache(entries: Sequence[Union[BaseIndexEntry, 'IndexEntry']], stream:
# body
for entry in entries:
beginoffset = tell()
- write(entry.ctime_bytes) # ctime
- write(entry.mtime_bytes) # mtime
+ write(entry.ctime_bytes) # ctime
+ write(entry.mtime_bytes) # mtime
path_str = str(entry.path)
path: bytes = force_bytes(path_str, encoding=defenc)
- plen = len(path) & CE_NAMEMASK # path length
+ plen = len(path) & CE_NAMEMASK # path length
assert plen == len(path), "Path %s too long to fit into index" % entry.path
- flags = plen | (entry.flags & CE_NAMEMASK_INV) # clear possible previous values
- write(pack(">LLLLLL20sH", entry.dev, entry.inode, entry.mode,
- entry.uid, entry.gid, entry.size, entry.binsha, flags))
+ flags = plen | (entry.flags & CE_NAMEMASK_INV) # clear possible previous values
+ write(
+ pack(
+ ">LLLLLL20sH",
+ entry.dev,
+ entry.inode,
+ entry.mode,
+ entry.uid,
+ entry.gid,
+ entry.size,
+ entry.binsha,
+ flags,
+ )
+ )
write(path)
- real_size = ((tell() - beginoffset + 8) & ~7)
+ real_size = (tell() - beginoffset + 8) & ~7
write(b"\0" * ((beginoffset + real_size) - tell()))
# END for each entry
@@ -216,7 +234,9 @@ def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, i
# END handle entry
-def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'IndexEntry'], bytes, bytes]:
+def read_cache(
+ stream: IO[bytes],
+) -> Tuple[int, Dict[Tuple[PathLike, int], "IndexEntry"], bytes, bytes]:
"""Read a cache file from the given stream
:return: tuple(version, entries_dict, extension_data, content_sha)
* version is the integer version number
@@ -225,7 +245,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: Dict[Tuple[PathLike, int], 'IndexEntry'] = {}
+ entries: Dict[Tuple[PathLike, int], "IndexEntry"] = {}
read = stream.read
tell = stream.tell
@@ -233,14 +253,17 @@ def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'Inde
beginoffset = tell()
ctime = unpack(">8s", read(8))[0]
mtime = unpack(">8s", read(8))[0]
- (dev, ino, mode, uid, gid, size, sha, flags) = \
- unpack(">LLLLLL20sH", read(20 + 4 * 6 + 2))
+ (dev, ino, mode, uid, gid, size, sha, flags) = unpack(
+ ">LLLLLL20sH", read(20 + 4 * 6 + 2)
+ )
path_size = flags & CE_NAMEMASK
path = read(path_size).decode(defenc)
- real_size = ((tell() - beginoffset + 8) & ~7)
+ real_size = (tell() - beginoffset + 8) & ~7
read((beginoffset + real_size) - tell())
- entry = IndexEntry((mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size))
+ entry = IndexEntry(
+ (mode, sha, flags, path, ctime, mtime, dev, ino, uid, gid, size)
+ )
# entry_key would be the method to use, but we safe the effort
entries[(path, entry.stage)] = entry
count += 1
@@ -253,19 +276,22 @@ def read_cache(stream: IO[bytes]) -> Tuple[int, Dict[Tuple[PathLike, int], 'Inde
# 4 bytes length of chunk
# repeated 0 - N times
extension_data = stream.read(~0)
- assert len(extension_data) > 19, "Index Footer was not at least a sha on content as it was only %i bytes in size"\
- % len(extension_data)
+ assert len(extension_data) > 19, (
+ "Index Footer was not at least a sha on content as it was only %i bytes in size"
+ % len(extension_data)
+ )
content_sha = extension_data[-20:]
# truncate the sha in the end as we will dynamically create it anyway
- extension_data = extension_data[: -20]
+ extension_data = extension_data[:-20]
return (version, entries, extension_data, content_sha)
-def write_tree_from_cache(entries: List[IndexEntry], odb: 'GitCmdObjectDB', sl: slice, si: int = 0
- ) -> Tuple[bytes, List['TreeCacheTup']]:
+def write_tree_from_cache(
+ entries: List[IndexEntry], odb: "GitCmdObjectDB", sl: slice, si: int = 0
+) -> Tuple[bytes, List["TreeCacheTup"]]:
"""Create a tree from the given sorted list of entries and put the respective
trees into the given object database
@@ -275,7 +301,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb: 'GitCmdObjectDB', sl:
:param sl: slice indicating the range we should process on the entries list
:return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of
tree entries being a tuple of hexsha, mode, name"""
- tree_items: List['TreeCacheTup'] = []
+ tree_items: List["TreeCacheTup"] = []
ci = sl.start
end = sl.stop
@@ -285,7 +311,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb: 'GitCmdObjectDB', sl:
raise UnmergedEntriesError(entry)
# END abort on unmerged
ci += 1
- rbound = entry.path.find('/', si)
+ rbound = entry.path.find("/", si)
if rbound == -1:
# its not a tree
tree_items.append((entry.binsha, entry.mode, entry.path[si:]))
@@ -295,7 +321,7 @@ def write_tree_from_cache(entries: List[IndexEntry], odb: 'GitCmdObjectDB', sl:
xi = ci
while xi < end:
oentry = entries[xi]
- orbound = oentry.path.find('/', si)
+ orbound = oentry.path.find("/", si)
if orbound == -1 or oentry.path[si:orbound] != base:
break
# END abort on base mismatch
@@ -304,7 +330,9 @@ def write_tree_from_cache(entries: List[IndexEntry], odb: 'GitCmdObjectDB', sl:
# enter recursion
# ci - 1 as we want to count our current item as well
- sha, _tree_entry_list = write_tree_from_cache(entries, odb, slice(ci - 1, xi), rbound + 1)
+ sha, _tree_entry_list = write_tree_from_cache(
+ entries, odb, slice(ci - 1, xi), rbound + 1
+ )
tree_items.append((sha, S_IFDIR, base))
# skip ahead
@@ -314,18 +342,26 @@ def write_tree_from_cache(entries: List[IndexEntry], odb: 'GitCmdObjectDB', sl:
# finally create the tree
sio = BytesIO()
- tree_to_stream(tree_items, sio.write) # writes to stream as bytes, but doesn't change tree_items
+ tree_to_stream(
+ tree_items, sio.write
+ ) # writes to stream as bytes, but doesn't change tree_items
sio.seek(0)
istream = odb.store(IStream(str_tree_type, len(sio.getvalue()), sio))
return (istream.binsha, tree_items)
-def _tree_entry_to_baseindexentry(tree_entry: 'TreeCacheTup', stage: int) -> BaseIndexEntry:
- return BaseIndexEntry((tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2]))
+def _tree_entry_to_baseindexentry(
+ tree_entry: "TreeCacheTup", stage: int
+) -> BaseIndexEntry:
+ return BaseIndexEntry(
+ (tree_entry[1], tree_entry[0], stage << CE_STAGESHIFT, tree_entry[2])
+ )
-def aggressive_tree_merge(odb: 'GitCmdObjectDB', tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]:
+def aggressive_tree_merge(
+ odb: "GitCmdObjectDB", tree_shas: Sequence[bytes]
+) -> List[BaseIndexEntry]:
"""
:return: list of BaseIndexEntries representing the aggressive merge of the given
trees. All valid entries are on stage 0, whereas the conflicting ones are left
@@ -339,7 +375,7 @@ def aggressive_tree_merge(odb: 'GitCmdObjectDB', tree_shas: Sequence[bytes]) ->
# one and two way is the same for us, as we don't have to handle an existing
# index, instrea
if len(tree_shas) in (1, 2):
- for entry in traverse_tree_recursive(odb, tree_shas[-1], ''):
+ for entry in traverse_tree_recursive(odb, tree_shas[-1], ""):
out.append(_tree_entry_to_baseindexentry(entry, 0))
# END for each entry
return out
@@ -349,7 +385,7 @@ def aggressive_tree_merge(odb: 'GitCmdObjectDB', tree_shas: Sequence[bytes]) ->
raise ValueError("Cannot handle %i trees at once" % len(tree_shas))
# three trees
- for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ''):
+ for base, ours, theirs in traverse_trees_recursive(odb, tree_shas, ""):
if base is not None:
# base version exists
if ours is not None:
@@ -358,8 +394,15 @@ def aggressive_tree_merge(odb: 'GitCmdObjectDB', tree_shas: Sequence[bytes]) ->
# it exists in all branches, if it was changed in both
# its a conflict, otherwise we take the changed version
# This should be the most common branch, so it comes first
- if(base[0] != ours[0] and base[0] != theirs[0] and ours[0] != theirs[0]) or \
- (base[1] != ours[1] and base[1] != theirs[1] and ours[1] != theirs[1]):
+ if (
+ base[0] != ours[0]
+ and base[0] != theirs[0]
+ and ours[0] != theirs[0]
+ ) or (
+ base[1] != ours[1]
+ and base[1] != theirs[1]
+ and ours[1] != theirs[1]
+ ):
# changed by both
out.append(_tree_entry_to_baseindexentry(base, 1))
out.append(_tree_entry_to_baseindexentry(ours, 2))
diff --git a/git/index/typ.py b/git/index/typ.py
index 46f1b077..cbe26f27 100644
--- a/git/index/typ.py
+++ b/git/index/typ.py
@@ -2,16 +2,13 @@
from binascii import b2a_hex
-from .util import (
- pack,
- unpack
-)
+from .util import pack, unpack
from git.objects import Blob
# typing ----------------------------------------------------------------------
-from typing import (NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast)
+from typing import NamedTuple, Sequence, TYPE_CHECKING, Tuple, Union, cast
from git.types import PathLike
@@ -20,16 +17,16 @@ if TYPE_CHECKING:
# ---------------------------------------------------------------------------------
-__all__ = ('BlobFilter', 'BaseIndexEntry', 'IndexEntry')
+__all__ = ("BlobFilter", "BaseIndexEntry", "IndexEntry")
-#{ Invariants
-CE_NAMEMASK = 0x0fff
+# { Invariants
+CE_NAMEMASK = 0x0FFF
CE_STAGEMASK = 0x3000
CE_EXTENDED = 0x4000
CE_VALID = 0x8000
CE_STAGESHIFT = 12
-#} END invariants
+# } END invariants
class BlobFilter(object):
@@ -40,7 +37,8 @@ class BlobFilter(object):
The given paths are given relative to the repository.
"""
- __slots__ = 'paths'
+
+ __slots__ = "paths"
def __init__(self, paths: Sequence[PathLike]) -> None:
"""
@@ -62,6 +60,7 @@ class BlobFilter(object):
class BaseIndexEntryHelper(NamedTuple):
"""Typed namedtuple to provide named attribute access for BaseIndexEntry.
Needed to allow overriding __new__ in child class to preserve backwards compat."""
+
mode: int
binsha: bytes
flags: int
@@ -85,10 +84,14 @@ class BaseIndexEntry(BaseIndexEntryHelper):
use numeric indices for performance reasons.
"""
- def __new__(cls, inp_tuple: Union[Tuple[int, bytes, int, PathLike],
- Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int]]
- ) -> 'BaseIndexEntry':
- """Override __new__ to allow construction from a tuple for backwards compatibility """
+ def __new__(
+ cls,
+ inp_tuple: Union[
+ Tuple[int, bytes, int, PathLike],
+ Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int],
+ ],
+ ) -> "BaseIndexEntry":
+ """Override __new__ to allow construction from a tuple for backwards compatibility"""
return super().__new__(cls, *inp_tuple)
def __str__(self) -> str:
@@ -100,7 +103,7 @@ class BaseIndexEntry(BaseIndexEntryHelper):
@property
def hexsha(self) -> str:
"""hex version of our sha"""
- return b2a_hex(self.binsha).decode('ascii')
+ return b2a_hex(self.binsha).decode("ascii")
@property
def stage(self) -> int:
@@ -116,11 +119,11 @@ class BaseIndexEntry(BaseIndexEntryHelper):
return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT
@classmethod
- def from_blob(cls, blob: Blob, stage: int = 0) -> 'BaseIndexEntry':
+ def from_blob(cls, blob: Blob, stage: int = 0) -> "BaseIndexEntry":
""":return: Fully equipped BaseIndexEntry at the given stage"""
return cls((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path))
- def to_blob(self, repo: 'Repo') -> Blob:
+ def to_blob(self, repo: "Repo") -> Blob:
""":return: Blob using the information of this index entry"""
return Blob(repo, self.binsha, self.mode, self.path)
@@ -132,7 +135,8 @@ class IndexEntry(BaseIndexEntry):
Attributes usully accessed often are cached in the tuple whereas others are
unpacked on demand.
- See the properties for a mapping between names and tuple indices. """
+ See the properties for a mapping between names and tuple indices."""
+
@property
def ctime(self) -> Tuple[int, int]:
"""
@@ -143,11 +147,11 @@ class IndexEntry(BaseIndexEntry):
@property
def mtime(self) -> Tuple[int, int]:
- """See ctime property, but returns modification time """
+ """See ctime property, but returns modification time"""
return cast(Tuple[int, int], unpack(">LL", self.mtime_bytes))
@classmethod
- def from_base(cls, base: 'BaseIndexEntry') -> 'IndexEntry':
+ def from_base(cls, base: "BaseIndexEntry") -> "IndexEntry":
"""
:return:
Minimal entry as created from the given BaseIndexEntry instance.
@@ -155,11 +159,26 @@ class IndexEntry(BaseIndexEntry):
:param base: Instance of type BaseIndexEntry"""
time = pack(">LL", 0, 0)
- return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0))
+ return IndexEntry(
+ (base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0)
+ )
@classmethod
- def from_blob(cls, blob: Blob, stage: int = 0) -> 'IndexEntry':
+ def from_blob(cls, blob: Blob, stage: int = 0) -> "IndexEntry":
""":return: Minimal entry resembling the given blob object"""
time = pack(">LL", 0, 0)
- return IndexEntry((blob.mode, blob.binsha, stage << CE_STAGESHIFT, blob.path,
- time, time, 0, 0, 0, 0, blob.size))
+ return IndexEntry(
+ (
+ blob.mode,
+ blob.binsha,
+ stage << CE_STAGESHIFT,
+ blob.path,
+ time,
+ time,
+ 0,
+ 0,
+ 0,
+ 0,
+ blob.size,
+ )
+ )
diff --git a/git/index/util.py b/git/index/util.py
index 4f8af553..7339b147 100644
--- a/git/index/util.py
+++ b/git/index/util.py
@@ -11,7 +11,7 @@ import os.path as osp
# typing ----------------------------------------------------------------------
-from typing import (Any, Callable, TYPE_CHECKING)
+from typing import Any, Callable, TYPE_CHECKING
from git.types import PathLike, _T
@@ -21,24 +21,26 @@ if TYPE_CHECKING:
# ---------------------------------------------------------------------------------
-__all__ = ('TemporaryFileSwap', 'post_clear_cache', 'default_index', 'git_working_dir')
+__all__ = ("TemporaryFileSwap", "post_clear_cache", "default_index", "git_working_dir")
-#{ Aliases
+# { Aliases
pack = struct.pack
unpack = struct.unpack
-#} END aliases
+# } END aliases
+
class TemporaryFileSwap(object):
"""Utility class moving a file to a temporary location within the same directory
and moving it back on to where on object deletion."""
+
__slots__ = ("file_path", "tmp_file_path")
def __init__(self, file_path: PathLike) -> None:
self.file_path = file_path
- self.tmp_file_path = str(self.file_path) + tempfile.mktemp('', '', '')
+ self.tmp_file_path = str(self.file_path) + tempfile.mktemp("", "", "")
# it may be that the source does not exist
try:
os.rename(self.file_path, self.tmp_file_path)
@@ -53,7 +55,8 @@ class TemporaryFileSwap(object):
# END temp file exists
-#{ Decorators
+# { Decorators
+
def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]:
"""Decorator for functions that alter the index using the git command. This would
@@ -66,10 +69,13 @@ def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]:
"""
@wraps(func)
- def post_clear_cache_if_not_raised(self: 'IndexFile', *args: Any, **kwargs: Any) -> _T:
+ def post_clear_cache_if_not_raised(
+ self: "IndexFile", *args: Any, **kwargs: Any
+ ) -> _T:
rval = func(self, *args, **kwargs)
self._delete_entries_cache()
return rval
+
# END wrapper method
return post_clear_cache_if_not_raised
@@ -78,14 +84,17 @@ def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]:
def default_index(func: Callable[..., _T]) -> Callable[..., _T]:
"""Decorator assuring the wrapped method may only run if we are the default
repository index. This is as we rely on git commands that operate
- on that index only. """
+ on that index only."""
@wraps(func)
- def check_default_index(self: 'IndexFile', *args: Any, **kwargs: Any) -> _T:
+ def check_default_index(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
if self._file_path != self._index_path():
raise AssertionError(
- "Cannot call %r on indices that do not represent the default git index" % func.__name__)
+ "Cannot call %r on indices that do not represent the default git index"
+ % func.__name__
+ )
return func(self, *args, **kwargs)
+
# END wrapper method
return check_default_index
@@ -96,7 +105,7 @@ def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]:
repository in order to assure relative paths are handled correctly"""
@wraps(func)
- def set_git_working_dir(self: 'IndexFile', *args: Any, **kwargs: Any) -> _T:
+ def set_git_working_dir(self: "IndexFile", *args: Any, **kwargs: Any) -> _T:
cur_wd = os.getcwd()
os.chdir(str(self.repo.working_tree_dir))
try:
@@ -104,8 +113,10 @@ def git_working_dir(func: Callable[..., _T]) -> Callable[..., _T]:
finally:
os.chdir(cur_wd)
# END handle working dir
+
# END wrapper
return set_git_working_dir
-#} END decorators
+
+# } END decorators
diff --git a/git/objects/__init__.py b/git/objects/__init__.py
index 1d0bb7a5..d2e1e53a 100644
--- a/git/objects/__init__.py
+++ b/git/objects/__init__.py
@@ -12,13 +12,17 @@ from .submodule.base import *
from .submodule.root import *
from .tag import *
from .tree import *
+
# Fix import dependency - add IndexObject to the util module, so that it can be
# imported by the submodule.base
smutil.IndexObject = IndexObject # type: ignore[attr-defined]
smutil.Object = Object # type: ignore[attr-defined]
-del(smutil)
+del smutil
# must come after submodule was made available
-__all__ = [name for name, obj in locals().items()
- if not (name.startswith('_') or inspect.ismodule(obj))]
+__all__ = [
+ name
+ for name, obj in locals().items()
+ if not (name.startswith("_") or inspect.ismodule(obj))
+]
diff --git a/git/objects/base.py b/git/objects/base.py
index 66e15a8f..9d005725 100644
--- a/git/objects/base.py
+++ b/git/objects/base.py
@@ -27,7 +27,7 @@ if TYPE_CHECKING:
from .submodule.base import Submodule
from git.refs.reference import Reference
-IndexObjUnion = Union['Tree', 'Blob', 'Submodule']
+IndexObjUnion = Union["Tree", "Blob", "Submodule"]
# --------------------------------------------------------------------------
@@ -40,14 +40,20 @@ __all__ = ("Object", "IndexObject")
class Object(LazyMixin):
"""Implements an Object which may be Blobs, Trees, Commits and Tags"""
- NULL_HEX_SHA = '0' * 40
- NULL_BIN_SHA = b'\0' * 20
- TYPES = (dbtyp.str_blob_type, dbtyp.str_tree_type, dbtyp.str_commit_type, dbtyp.str_tag_type)
+ NULL_HEX_SHA = "0" * 40
+ NULL_BIN_SHA = b"\0" * 20
+
+ TYPES = (
+ dbtyp.str_blob_type,
+ dbtyp.str_tree_type,
+ dbtyp.str_commit_type,
+ dbtyp.str_tag_type,
+ )
__slots__ = ("repo", "binsha", "size")
type: Union[Lit_commit_ish, None] = None
- def __init__(self, repo: 'Repo', binsha: bytes):
+ def __init__(self, repo: "Repo", binsha: bytes):
"""Initialize an object by identifying it by its binary sha.
All keyword arguments will be set on demand if None.
@@ -57,10 +63,13 @@ class Object(LazyMixin):
super(Object, self).__init__()
self.repo = repo
self.binsha = binsha
- assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (binsha, len(binsha))
+ assert len(binsha) == 20, "Require 20 byte binary sha, got %r, len = %i" % (
+ binsha,
+ len(binsha),
+ )
@classmethod
- def new(cls, repo: 'Repo', id: Union[str, 'Reference']) -> Commit_ish:
+ def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> Commit_ish:
"""
:return: New Object instance of a type appropriate to the object type behind
id. The id of the newly created object will be a binsha even though
@@ -73,14 +82,14 @@ class Object(LazyMixin):
return repo.rev_parse(str(id))
@classmethod
- def new_from_sha(cls, repo: 'Repo', sha1: bytes) -> Commit_ish:
+ def new_from_sha(cls, repo: "Repo", sha1: bytes) -> Commit_ish:
"""
:return: new object instance of a type appropriate to represent the given
binary sha1
:param sha1: 20 byte binary sha1"""
if sha1 == cls.NULL_BIN_SHA:
# the NULL binsha is always the root commit
- return get_object_type_by_name(b'commit')(repo, sha1)
+ return get_object_type_by_name(b"commit")(repo, sha1)
# END handle special case
oinfo = repo.odb.info(sha1)
inst = get_object_type_by_name(oinfo.type)(repo, oinfo.binsha)
@@ -98,13 +107,13 @@ class Object(LazyMixin):
def __eq__(self, other: Any) -> bool:
""":return: True if the objects have the same SHA1"""
- if not hasattr(other, 'binsha'):
+ if not hasattr(other, "binsha"):
return False
return self.binsha == other.binsha
def __ne__(self, other: Any) -> bool:
- """:return: True if the objects do not have the same SHA1 """
- if not hasattr(other, 'binsha'):
+ """:return: True if the objects do not have the same SHA1"""
+ if not hasattr(other, "binsha"):
return True
return self.binsha != other.binsha
@@ -124,15 +133,15 @@ class Object(LazyMixin):
def hexsha(self) -> str:
""":return: 40 byte hex version of our 20 byte binary sha"""
# b2a_hex produces bytes
- return bin_to_hex(self.binsha).decode('ascii')
+ return bin_to_hex(self.binsha).decode("ascii")
@property
- def data_stream(self) -> 'OStream':
- """ :return: File Object compatible stream to the uncompressed raw data of the object
+ def data_stream(self) -> "OStream":
+ """:return: File Object compatible stream to the uncompressed raw data of the object
:note: returned streams must be read in order"""
return self.repo.odb.stream(self.binsha)
- def stream_data(self, ostream: 'OStream') -> 'Object':
+ def stream_data(self, ostream: "OStream") -> "Object":
"""Writes our data directly to the given output stream
:param ostream: File object compatible stream object.
:return: self"""
@@ -145,14 +154,19 @@ class IndexObject(Object):
"""Base for all objects that can be part of the index file , namely Tree, Blob and
SubModule objects"""
+
__slots__ = ("path", "mode")
# for compatibility with iterable lists
- _id_attribute_ = 'path'
-
- def __init__(self,
- repo: 'Repo', binsha: bytes, mode: Union[None, int] = None, path: Union[None, PathLike] = None
- ) -> None:
+ _id_attribute_ = "path"
+
+ def __init__(
+ self,
+ repo: "Repo",
+ binsha: bytes,
+ mode: Union[None, int] = None,
+ path: Union[None, PathLike] = None,
+ ) -> None:
"""Initialize a newly instanced IndexObject
:param repo: is the Repo we are located in
@@ -184,7 +198,8 @@ class IndexObject(Object):
# they cannot be retrieved lateron ( not without searching for them )
raise AttributeError(
"Attribute '%s' unset: path and mode attributes must have been set during %s object creation"
- % (attr, type(self).__name__))
+ % (attr, type(self).__name__)
+ )
else:
super(IndexObject, self)._set_cache_(attr)
# END handle slot attribute
@@ -201,7 +216,7 @@ class IndexObject(Object):
Absolute path to this index object in the file system ( as opposed to the
.path field which is a path relative to the git repository ).
- The returned path will be native to the system and contains '\' on windows. """
+ The returned path will be native to the system and contains '\' on windows."""
if self.repo.working_tree_dir is not None:
return join_path_native(self.repo.working_tree_dir, self.path)
else:
diff --git a/git/objects/blob.py b/git/objects/blob.py
index 99b5c636..1881f210 100644
--- a/git/objects/blob.py
+++ b/git/objects/blob.py
@@ -8,14 +8,15 @@ from . import base
from git.types import Literal
-__all__ = ('Blob', )
+__all__ = ("Blob",)
class Blob(base.IndexObject):
"""A Blob encapsulates a git blob object"""
+
DEFAULT_MIME_TYPE = "text/plain"
- type: Literal['blob'] = "blob"
+ type: Literal["blob"] = "blob"
# valid blob modes
executable_mode = 0o100755
@@ -28,7 +29,7 @@ class Blob(base.IndexObject):
def mime_type(self) -> str:
"""
:return: String describing the mime type of this file (based on the filename)
- :note: Defaults to 'text/plain' in case the actual file type is unknown. """
+ :note: Defaults to 'text/plain' in case the actual file type is unknown."""
guesses = None
if self.path:
guesses = guess_type(str(self.path))
diff --git a/git/objects/commit.py b/git/objects/commit.py
index 96a2a8e5..137cc620 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -6,12 +6,7 @@
import datetime
from subprocess import Popen, PIPE
from gitdb import IStream
-from git.util import (
- hex_to_bin,
- Actor,
- Stats,
- finalize_process
-)
+from git.util import hex_to_bin, Actor, Stats, finalize_process
from git.diff import Diffable
from git.cmd import Git
@@ -26,13 +21,7 @@ from .util import (
from_timestamp,
)
-from time import (
- time,
- daylight,
- altzone,
- timezone,
- localtime
-)
+from time import time, daylight, altzone, timezone, localtime
import os
from io import BytesIO
import logging
@@ -40,7 +29,18 @@ import logging
# typing ------------------------------------------------------------------
-from typing import Any, IO, Iterator, List, Sequence, Tuple, Union, TYPE_CHECKING, cast, Dict
+from typing import (
+ Any,
+ IO,
+ Iterator,
+ List,
+ Sequence,
+ Tuple,
+ Union,
+ TYPE_CHECKING,
+ cast,
+ Dict,
+)
from git.types import PathLike, Literal
@@ -50,10 +50,10 @@ if TYPE_CHECKING:
# ------------------------------------------------------------------------
-log = logging.getLogger('git.objects.commit')
+log = logging.getLogger("git.objects.commit")
log.addHandler(logging.NullHandler())
-__all__ = ('Commit', )
+__all__ = ("Commit",)
class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
@@ -69,30 +69,44 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
env_committer_date = "GIT_COMMITTER_DATE"
# CONFIGURATION KEYS
- conf_encoding = 'i18n.commitencoding'
+ conf_encoding = "i18n.commitencoding"
# INVARIANTS
default_encoding = "UTF-8"
# object configuration
- type: Literal['commit'] = "commit"
- __slots__ = ("tree",
- "author", "authored_date", "author_tz_offset",
- "committer", "committed_date", "committer_tz_offset",
- "message", "parents", "encoding", "gpgsig")
+ 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,
- author: Union[Actor, None] = None,
- authored_date: Union[int, None] = None,
- author_tz_offset: Union[None, float] = None,
- committer: Union[Actor, None] = None,
- committed_date: Union[int, None] = None,
- committer_tz_offset: Union[None, float] = None,
- message: Union[str, bytes, None] = None,
- parents: Union[Sequence['Commit'], None] = None,
- encoding: Union[str, None] = None,
- gpgsig: Union[str, None] = 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,
+ committer: Union[Actor, None] = None,
+ committed_date: Union[int, None] = None,
+ committer_tz_offset: Union[None, float] = None,
+ message: Union[str, bytes, None] = None,
+ parents: Union[Sequence["Commit"], None] = None,
+ encoding: Union[str, None] = None,
+ gpgsig: Union[str, None] = None,
+ ) -> None:
"""Instantiate a new Commit. All keyword arguments taking None as default will
be implicitly set on first query.
@@ -130,7 +144,9 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
super(Commit, self).__init__(repo, binsha)
self.binsha = binsha
if tree is not None:
- assert isinstance(tree, Tree), "Tree needs to be a Tree instance, was %s" % type(tree)
+ assert isinstance(
+ tree, Tree
+ ), "Tree needs to be a Tree instance, was %s" % type(tree)
if tree is not None:
self.tree = tree
if author is not None:
@@ -155,16 +171,16 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
self.gpgsig = gpgsig
@classmethod
- def _get_intermediate_items(cls, commit: 'Commit') -> Tuple['Commit', ...]:
+ def _get_intermediate_items(cls, commit: "Commit") -> Tuple["Commit", ...]:
return tuple(commit.parents)
@classmethod
- def _calculate_sha_(cls, repo: 'Repo', commit: 'Commit') -> bytes:
- '''Calculate the sha of a commit.
+ def _calculate_sha_(cls, repo: "Repo", commit: "Commit") -> bytes:
+ """Calculate the sha of a commit.
:param repo: Repo object the commit should be part of
:param commit: Commit object for which to generate the sha
- '''
+ """
stream = BytesIO()
commit._serialize(stream)
@@ -174,18 +190,18 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
istream = repo.odb.store(IStream(cls.type, streamlen, stream))
return istream.binsha
- def replace(self, **kwargs: Any) -> 'Commit':
- '''Create new commit object from existing commit object.
+ def replace(self, **kwargs: Any) -> "Commit":
+ """Create new commit object from existing commit object.
Any values provided as keyword arguments will replace the
corresponding attribute in the new object.
- '''
+ """
attrs = {k: getattr(self, k) for k in self.__slots__}
for attrname in kwargs:
if attrname not in self.__slots__:
- raise ValueError('invalid attribute name')
+ raise ValueError("invalid attribute name")
attrs.update(kwargs)
new_commit = self.__class__(self.repo, self.NULL_BIN_SHA, **attrs)
@@ -214,11 +230,13 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
def summary(self) -> Union[str, bytes]:
""":return: First line of the commit message"""
if isinstance(self.message, str):
- return self.message.split('\n', 1)[0]
+ return self.message.split("\n", 1)[0]
else:
- return self.message.split(b'\n', 1)[0]
+ return self.message.split(b"\n", 1)[0]
- def count(self, paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any) -> int:
+ def count(
+ self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any
+ ) -> int:
"""Count the number of commits reachable from this commit
:param paths:
@@ -232,7 +250,9 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# yes, it makes a difference whether empty paths are given or not in our case
# as the empty paths version will ignore merge commits for some reason.
if paths:
- return len(self.repo.git.rev_list(self.hexsha, '--', paths, **kwargs).splitlines())
+ return len(
+ self.repo.git.rev_list(self.hexsha, "--", paths, **kwargs).splitlines()
+ )
return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines())
@property
@@ -244,9 +264,13 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
return self.repo.git.name_rev(self)
@classmethod
- def iter_items(cls, repo: 'Repo', rev: Union[str, 'Commit', 'SymbolicReference'], # type: ignore
- paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any
- ) -> Iterator['Commit']:
+ 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.
:param repo: is the Repo
@@ -260,19 +284,21 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
``skip`` is the number of commits to skip
``since`` all commits since i.e. '1970-01-01'
:return: iterator yielding Commit items"""
- if 'pretty' in kwargs:
- raise ValueError("--pretty cannot be used as parsing expects single sha's only")
+ if "pretty" in kwargs:
+ raise ValueError(
+ "--pretty cannot be used as parsing expects single sha's only"
+ )
# END handle pretty
# use -- in any case, to prevent possibility of ambiguous arguments
# see https://github.com/gitpython-developers/GitPython/issues/264
- args_list: List[PathLike] = ['--']
+ args_list: List[PathLike] = ["--"]
if paths:
paths_tup: Tuple[PathLike, ...]
if isinstance(paths, (str, os.PathLike)):
- paths_tup = (paths, )
+ paths_tup = (paths,)
else:
paths_tup = tuple(paths)
@@ -282,37 +308,41 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
proc = repo.git.rev_list(rev, args_list, as_process=True, **kwargs)
return cls._iter_from_process_or_stream(repo, proc)
- def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = '', **kwargs: Any) -> Iterator['Commit']:
+ def iter_parents(
+ self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any
+ ) -> Iterator["Commit"]:
"""Iterate _all_ parents of this commit.
:param paths:
Optional path or list of paths limiting the Commits to those that
contain at least one of the paths
:param kwargs: All arguments allowed by git-rev-list
- :return: Iterator yielding Commit objects which are parents of self """
+ :return: Iterator yielding Commit objects which are parents of self"""
# skip ourselves
skip = kwargs.get("skip", 1)
- if skip == 0: # skip ourselves
+ if skip == 0: # skip ourselves
skip = 1
- kwargs['skip'] = skip
+ kwargs["skip"] = skip
return self.iter_items(self.repo, self, paths, **kwargs)
- @ property
+ @property
def stats(self) -> Stats:
"""Create a git stat from changes between this commit and its first parent
or from all changes done if this is the very first commit.
:return: git.Stats"""
if not self.parents:
- text = self.repo.git.diff_tree(self.hexsha, '--', numstat=True, root=True)
+ text = self.repo.git.diff_tree(self.hexsha, "--", numstat=True, root=True)
text2 = ""
for line in text.splitlines()[1:]:
(insertions, deletions, filename) = line.split("\t")
text2 += "%s\t%s\t%s\n" % (insertions, deletions, filename)
text = text2
else:
- text = self.repo.git.diff(self.parents[0].hexsha, self.hexsha, '--', numstat=True)
+ text = self.repo.git.diff(
+ self.parents[0].hexsha, self.hexsha, "--", numstat=True
+ )
return Stats._list_from_string(self.repo, text)
@property
@@ -352,19 +382,21 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
"""
d = {}
- cmd = ['git', 'interpret-trailers', '--parse']
+ cmd = ["git", "interpret-trailers", "--parse"]
proc: Git.AutoInterrupt = self.repo.git.execute(cmd, as_process=True, istream=PIPE) # type: ignore
trailer: str = proc.communicate(str(self.message).encode())[0].decode()
- if trailer.endswith('\n'):
+ if trailer.endswith("\n"):
trailer = trailer[0:-1]
- if trailer != '':
- for line in trailer.split('\n'):
- key, value = line.split(':', 1)
+ if trailer != "":
+ for line in trailer.split("\n"):
+ key, value = line.split(":", 1)
d[key.strip()] = value.strip()
return d
- @ classmethod
- def _iter_from_process_or_stream(cls, repo: 'Repo', proc_or_stream: Union[Popen, IO]) -> Iterator['Commit']:
+ @classmethod
+ def _iter_from_process_or_stream(
+ cls, repo: "Repo", proc_or_stream: Union[Popen, IO]
+ ) -> Iterator["Commit"]:
"""Parse out commit information into a list of Commit objects
We expect one-line per commit, and parse the actual commit information directly
from our lighting fast object database
@@ -378,11 +410,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# def is_stream(inp) -> TypeGuard[IO]:
# return hasattr(proc_or_stream, 'readline')
- if hasattr(proc_or_stream, 'wait'):
+ if hasattr(proc_or_stream, "wait"):
proc_or_stream = cast(Popen, proc_or_stream)
if proc_or_stream.stdout is not None:
stream = proc_or_stream.stdout
- elif hasattr(proc_or_stream, 'readline'):
+ elif hasattr(proc_or_stream, "readline"):
proc_or_stream = cast(IO, proc_or_stream)
stream = proc_or_stream
@@ -402,15 +434,23 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# END for each line in stream
# TODO: Review this - it seems process handling got a bit out of control
# due to many developers trying to fix the open file handles issue
- if hasattr(proc_or_stream, 'wait'):
+ if hasattr(proc_or_stream, "wait"):
proc_or_stream = cast(Popen, proc_or_stream)
finalize_process(proc_or_stream)
- @ classmethod
- 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) -> 'Commit':
+ @classmethod
+ 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,
+ ) -> "Commit":
"""Commit the given tree, creating a commit object.
:param repo: Repo object the commit should be part of
@@ -473,7 +513,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
is_dst = daylight and localtime().tm_isdst > 0
offset = altzone if is_dst else timezone
- author_date_str = env.get(cls.env_author_date, '')
+ author_date_str = env.get(cls.env_author_date, "")
if author_date:
author_time, author_offset = parse_date(author_date)
elif author_date_str:
@@ -482,7 +522,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
author_time, author_offset = unix_time, offset
# END set author time
- committer_date_str = env.get(cls.env_committer_date, '')
+ committer_date_str = env.get(cls.env_committer_date, "")
if commit_date:
committer_time, committer_offset = parse_date(commit_date)
elif committer_date_str:
@@ -492,7 +532,7 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# END set committer time
# assume utf8 encoding
- enc_section, enc_option = cls.conf_encoding.split('.')
+ enc_section, enc_option = cls.conf_encoding.split(".")
conf_encoding = cr.get_value(enc_section, enc_option, cls.default_encoding)
if not isinstance(conf_encoding, str):
raise TypeError("conf_encoding could not be coerced to str")
@@ -504,10 +544,20 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# END tree conversion
# CREATE NEW COMMIT
- new_commit = cls(repo, cls.NULL_BIN_SHA, tree,
- author, author_time, author_offset,
- committer, committer_time, committer_offset,
- message, parent_commits, conf_encoding)
+ new_commit = cls(
+ repo,
+ cls.NULL_BIN_SHA,
+ tree,
+ author,
+ author_time,
+ author_offset,
+ committer,
+ committer_time,
+ committer_offset,
+ message,
+ parent_commits,
+ conf_encoding,
+ )
new_commit.binsha = cls._calculate_sha_(repo, new_commit)
@@ -515,48 +565,74 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# need late import here, importing git at the very beginning throws
# as well ...
import git.refs
+
try:
repo.head.set_commit(new_commit, logmsg=message)
except ValueError:
# head is not yet set to the ref our HEAD points to
# Happens on first commit
- master = git.refs.Head.create(repo, repo.head.ref, new_commit, logmsg="commit (initial): %s" % message)
- repo.head.set_reference(master, logmsg='commit: Switching to %s' % master)
+ master = git.refs.Head.create(
+ repo,
+ repo.head.ref,
+ new_commit,
+ logmsg="commit (initial): %s" % message,
+ )
+ repo.head.set_reference(
+ master, logmsg="commit: Switching to %s" % master
+ )
# END handle empty repositories
# END advance head handling
return new_commit
- #{ Serializable Implementation
+ # { Serializable Implementation
- def _serialize(self, stream: BytesIO) -> 'Commit':
+ def _serialize(self, stream: BytesIO) -> "Commit":
write = stream.write
- write(("tree %s\n" % self.tree).encode('ascii'))
+ write(("tree %s\n" % self.tree).encode("ascii"))
for p in self.parents:
- write(("parent %s\n" % p).encode('ascii'))
+ write(("parent %s\n" % p).encode("ascii"))
a = self.author
aname = a.name
c = self.committer
fmt = "%s %s <%s> %s %s\n"
- write((fmt % ("author", aname, a.email,
- self.authored_date,
- altz_to_utctz_str(self.author_tz_offset))).encode(self.encoding))
+ write(
+ (
+ fmt
+ % (
+ "author",
+ aname,
+ a.email,
+ self.authored_date,
+ altz_to_utctz_str(self.author_tz_offset),
+ )
+ ).encode(self.encoding)
+ )
# encode committer
aname = c.name
- write((fmt % ("committer", aname, c.email,
- self.committed_date,
- altz_to_utctz_str(self.committer_tz_offset))).encode(self.encoding))
+ write(
+ (
+ fmt
+ % (
+ "committer",
+ aname,
+ c.email,
+ self.committed_date,
+ altz_to_utctz_str(self.committer_tz_offset),
+ )
+ ).encode(self.encoding)
+ )
if self.encoding != self.default_encoding:
- write(("encoding %s\n" % self.encoding).encode('ascii'))
+ write(("encoding %s\n" % self.encoding).encode("ascii"))
try:
- if self.__getattribute__('gpgsig'):
+ if self.__getattribute__("gpgsig"):
write(b"gpgsig")
for sigline in self.gpgsig.rstrip("\n").split("\n"):
- write((" " + sigline + "\n").encode('ascii'))
+ write((" " + sigline + "\n").encode("ascii"))
except AttributeError:
pass
@@ -570,23 +646,29 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# END handle encoding
return self
- def _deserialize(self, stream: BytesIO) -> 'Commit':
+ 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
"""
readline = stream.readline
- self.tree = Tree(self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, '')
+ self.tree = Tree(
+ self.repo, hex_to_bin(readline().split()[1]), Tree.tree_id << 12, ""
+ )
self.parents = []
next_line = None
while True:
parent_line = readline()
- if not parent_line.startswith(b'parent'):
+ if not parent_line.startswith(b"parent"):
next_line = parent_line
break
# END abort reading parents
- self.parents.append(type(self)(self.repo, hex_to_bin(parent_line.split()[-1].decode('ascii'))))
+ self.parents.append(
+ type(self)(
+ self.repo, hex_to_bin(parent_line.split()[-1].decode("ascii"))
+ )
+ )
# END for each parent line
self.parents = tuple(self.parents)
@@ -596,9 +678,9 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
# we might run into one or more mergetag blocks, skip those for now
next_line = readline()
- while next_line.startswith(b'mergetag '):
+ while next_line.startswith(b"mergetag "):
next_line = readline()
- while next_line.startswith(b' '):
+ while next_line.startswith(b" "):
next_line = readline()
# end skip mergetags
@@ -612,10 +694,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
buf = enc.strip()
while buf:
if buf[0:10] == b"encoding ":
- self.encoding = buf[buf.find(b' ') + 1:].decode(
- self.encoding, 'ignore')
+ self.encoding = buf[buf.find(b" ") + 1 :].decode(
+ self.encoding, "ignore"
+ )
elif buf[0:7] == b"gpgsig ":
- sig = buf[buf.find(b' ') + 1:] + b"\n"
+ sig = buf[buf.find(b" ") + 1 :] + b"\n"
is_next_header = False
while True:
sigbuf = readline()
@@ -627,37 +710,55 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable):
break
sig += sigbuf[1:]
# end read all signature
- self.gpgsig = sig.rstrip(b"\n").decode(self.encoding, 'ignore')
+ self.gpgsig = sig.rstrip(b"\n").decode(self.encoding, "ignore")
if is_next_header:
continue
buf = readline().strip()
# decode the authors name
try:
- (self.author, self.authored_date, self.author_tz_offset) = \
- parse_actor_and_date(author_line.decode(self.encoding, 'replace'))
+ (
+ self.author,
+ self.authored_date,
+ self.author_tz_offset,
+ ) = parse_actor_and_date(author_line.decode(self.encoding, "replace"))
except UnicodeDecodeError:
- log.error("Failed to decode author line '%s' using encoding %s", author_line, self.encoding,
- exc_info=True)
+ log.error(
+ "Failed to decode author line '%s' using encoding %s",
+ author_line,
+ self.encoding,
+ exc_info=True,
+ )
try:
- self.committer, self.committed_date, self.committer_tz_offset = \
- parse_actor_and_date(committer_line.decode(self.encoding, 'replace'))
+ (
+ self.committer,
+ self.committed_date,
+ self.committer_tz_offset,
+ ) = parse_actor_and_date(committer_line.decode(self.encoding, "replace"))
except UnicodeDecodeError:
- log.error("Failed to decode committer line '%s' using encoding %s", committer_line, self.encoding,
- exc_info=True)
+ log.error(
+ "Failed to decode committer line '%s' using encoding %s",
+ committer_line,
+ self.encoding,
+ exc_info=True,
+ )
# END handle author's encoding
# a stream from our data simply gives us the plain message
# The end of our message stream is marked with a newline that we strip
self.message = stream.read()
try:
- self.message = self.message.decode(self.encoding, 'replace')
+ self.message = self.message.decode(self.encoding, "replace")
except UnicodeDecodeError:
- log.error("Failed to decode message '%s' using encoding %s",
- self.message, self.encoding, exc_info=True)
+ log.error(
+ "Failed to decode message '%s' using encoding %s",
+ self.message,
+ self.encoding,
+ exc_info=True,
+ )
# END exception handling
return self
- #} END serializable implementation
+ # } END serializable implementation
diff --git a/git/objects/fun.py b/git/objects/fun.py
index 19b4e525..de065599 100644
--- a/git/objects/fun.py
+++ b/git/objects/fun.py
@@ -2,14 +2,20 @@
from stat import S_ISDIR
-from git.compat import (
- safe_decode,
- defenc
-)
+from git.compat import safe_decode, defenc
# typing ----------------------------------------------
-from typing import Callable, List, MutableSequence, Sequence, Tuple, TYPE_CHECKING, Union, overload
+from typing import (
+ Callable,
+ List,
+ MutableSequence,
+ Sequence,
+ Tuple,
+ TYPE_CHECKING,
+ Union,
+ overload,
+)
if TYPE_CHECKING:
from _typeshed import ReadableBuffer
@@ -21,19 +27,25 @@ EntryTupOrNone = Union[EntryTup, None]
# ---------------------------------------------------
-__all__ = ('tree_to_stream', 'tree_entries_from_data', 'traverse_trees_recursive',
- 'traverse_tree_recursive')
+__all__ = (
+ "tree_to_stream",
+ "tree_entries_from_data",
+ "traverse_trees_recursive",
+ "traverse_tree_recursive",
+)
-def tree_to_stream(entries: Sequence[EntryTup], write: Callable[['ReadableBuffer'], Union[int, None]]) -> None:
+def tree_to_stream(
+ entries: Sequence[EntryTup], write: Callable[["ReadableBuffer"], Union[int, None]]
+) -> None:
"""Write the give list of entries into a stream using its write method
:param entries: **sorted** list of tuples with (binsha, mode, name)
:param write: write method which takes a data string"""
- ord_zero = ord('0')
- bit_mask = 7 # 3 bits set
+ ord_zero = ord("0")
+ bit_mask = 7 # 3 bits set
for binsha, mode, name in entries:
- mode_str = b''
+ mode_str = b""
for i in range(6):
mode_str = bytes([((mode >> (i * 3)) & bit_mask) + ord_zero]) + mode_str
# END for each 8 octal value
@@ -52,7 +64,7 @@ def tree_to_stream(entries: Sequence[EntryTup], write: Callable[['ReadableBuffer
name_bytes = name.encode(defenc)
else:
name_bytes = name # type: ignore[unreachable] # check runtime types - is always str?
- write(b''.join((mode_str, b' ', name_bytes, b'\0', binsha)))
+ write(b"".join((mode_str, b" ", name_bytes, b"\0", binsha)))
# END for each item
@@ -60,8 +72,8 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]:
"""Reads the binary representation of a tree and returns tuples of Tree items
:param data: data block with tree data (as bytes)
:return: list(tuple(binsha, mode, tree_relative_path), ...)"""
- ord_zero = ord('0')
- space_ord = ord(' ')
+ ord_zero = ord("0")
+ space_ord = ord(" ")
len_data = len(data)
i = 0
out = []
@@ -95,15 +107,16 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]:
# byte is NULL, get next 20
i += 1
- sha = data[i:i + 20]
+ sha = data[i : i + 20]
i = i + 20
out.append((sha, mode, name))
# END for each byte in data stream
return out
-def _find_by_name(tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir: bool, start_at: int
- ) -> EntryTupOrNone:
+def _find_by_name(
+ tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir: bool, start_at: int
+) -> EntryTupOrNone:
"""return data entry matching the given name and tree mode
or None.
Before the item is returned, the respective data item is set
@@ -126,12 +139,12 @@ def _find_by_name(tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir:
return None
-@ overload
+@overload
def _to_full_path(item: None, path_prefix: str) -> None:
...
-@ overload
+@overload
def _to_full_path(item: EntryTup, path_prefix: str) -> EntryTup:
...
@@ -143,8 +156,9 @@ def _to_full_path(item: EntryTupOrNone, path_prefix: str) -> EntryTupOrNone:
return (item[0], item[1], path_prefix + item[2])
-def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[bytes, None]],
- path_prefix: str) -> List[Tuple[EntryTupOrNone, ...]]:
+def traverse_trees_recursive(
+ odb: "GitCmdObjectDB", tree_shas: Sequence[Union[bytes, None]], path_prefix: str
+) -> List[Tuple[EntryTupOrNone, ...]]:
"""
:return: list of list with entries according to the given binary tree-shas.
The result is encoded in a list
@@ -187,7 +201,7 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by
entries = [None for _ in range(nt)]
entries[ti] = item
_sha, mode, name = item
- is_dir = S_ISDIR(mode) # type mode bits
+ is_dir = S_ISDIR(mode) # type mode bits
# find this item in all other tree data items
# wrap around, but stop one before our current index, hence
@@ -199,8 +213,13 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by
# END for each other item data
# if we are a directory, enter recursion
if is_dir:
- out.extend(traverse_trees_recursive(
- odb, [((ei and ei[0]) or None) for ei in entries], path_prefix + name + '/'))
+ out.extend(
+ traverse_trees_recursive(
+ odb,
+ [((ei and ei[0]) or None) for ei in entries],
+ path_prefix + name + "/",
+ )
+ )
else:
out.append(tuple(_to_full_path(e, path_prefix) for e in entries))
@@ -210,12 +229,14 @@ def traverse_trees_recursive(odb: 'GitCmdObjectDB', tree_shas: Sequence[Union[by
# END for each item
# we are done with one tree, set all its data empty
- del(tree_data[:])
+ del tree_data[:]
# END for each tree_data chunk
return out
-def traverse_tree_recursive(odb: 'GitCmdObjectDB', tree_sha: bytes, path_prefix: str) -> List[EntryTup]:
+def traverse_tree_recursive(
+ odb: "GitCmdObjectDB", tree_sha: bytes, path_prefix: str
+) -> List[EntryTup]:
"""
:return: list of entries of the tree pointed to by the binary tree_sha. An entry
has the following format:
@@ -229,7 +250,7 @@ def traverse_tree_recursive(odb: 'GitCmdObjectDB', tree_sha: bytes, path_prefix:
# unpacking/packing is faster than accessing individual items
for sha, mode, name in data:
if S_ISDIR(mode):
- entries.extend(traverse_tree_recursive(odb, sha, path_prefix + name + '/'))
+ entries.extend(traverse_tree_recursive(odb, sha, path_prefix + name + "/"))
else:
entries.append((sha, mode, path_prefix + name))
# END for each item
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index f7820455..84a34206 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -11,16 +11,12 @@ from git.compat import (
defenc,
is_win,
)
-from git.config import (
- SectionConstraint,
- GitConfigParser,
- cp
-)
+from git.config import SectionConstraint, GitConfigParser, cp
from git.exc import (
InvalidGitRepositoryError,
NoSuchPathError,
RepositoryDirtyError,
- BadName
+ BadName,
)
from git.objects.base import IndexObject, Object
from git.objects.util import TraversableIterableObj
@@ -31,7 +27,7 @@ from git.util import (
RemoteProgress,
rmtree,
unbare_repo,
- IterableList
+ IterableList,
)
from git.util import HIDE_WINDOWS_KNOWN_ERRORS
@@ -42,7 +38,7 @@ from .util import (
sm_name,
sm_section,
SubmoduleConfigParser,
- find_first_remote_branch
+ find_first_remote_branch,
)
@@ -63,7 +59,7 @@ if TYPE_CHECKING:
__all__ = ["Submodule", "UpdateProgress"]
-log = logging.getLogger('git.objects.submodule.base')
+log = logging.getLogger("git.objects.submodule.base")
log.addHandler(logging.NullHandler())
@@ -71,7 +67,11 @@ class UpdateProgress(RemoteProgress):
"""Class providing detailed progress information to the caller who should
derive from it and implement the ``update(...)`` message"""
- CLONE, FETCH, UPDWKTREE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes + 3)]
+
+ CLONE, FETCH, UPDWKTREE = [
+ 1 << x
+ for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes + 3)
+ ]
_num_op_codes: int = RemoteProgress._num_op_codes + 3
__slots__ = ()
@@ -98,25 +98,30 @@ class Submodule(IndexObject, TraversableIterableObj):
All methods work in bare and non-bare repositories."""
_id_attribute_ = "name"
- k_modules_file = '.gitmodules'
- k_head_option = 'branch'
- k_head_default = 'master'
- k_default_mode = stat.S_IFDIR | stat.S_IFLNK # submodules are directories with link-status
+ k_modules_file = ".gitmodules"
+ k_head_option = "branch"
+ k_head_default = "master"
+ 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: Literal['submodule'] = 'submodule' # type: ignore
-
- __slots__ = ('_parent_commit', '_url', '_branch_path', '_name', '__weakref__')
- _cache_attrs = ('path', '_url', '_branch_path')
-
- def __init__(self, repo: 'Repo', binsha: bytes,
- mode: Union[int, None] = None,
- path: Union[PathLike, None] = None,
- name: Union[str, None] = None,
- parent_commit: Union[Commit_ish, None] = None,
- url: Union[str, None] = None,
- branch_path: Union[PathLike, None] = None
- ) -> None:
+ type: Literal["submodule"] = "submodule" # type: ignore
+
+ __slots__ = ("_parent_commit", "_url", "_branch_path", "_name", "__weakref__")
+ _cache_attrs = ("path", "_url", "_branch_path")
+
+ def __init__(
+ self,
+ repo: "Repo",
+ binsha: bytes,
+ mode: Union[int, None] = None,
+ path: Union[PathLike, None] = None,
+ name: Union[str, None] = None,
+ parent_commit: Union[Commit_ish, None] = None,
+ url: Union[str, None] = None,
+ branch_path: Union[PathLike, None] = None,
+ ) -> None:
"""Initialize this instance with its attributes. We only document the ones
that differ from ``IndexObject``
@@ -137,32 +142,38 @@ class Submodule(IndexObject, TraversableIterableObj):
self._name = name
def _set_cache_(self, attr: str) -> None:
- if attr in ('path', '_url', '_branch_path'):
+ if attr in ("path", "_url", "_branch_path"):
reader: SectionConstraint = self.config_reader()
# default submodule values
try:
- self.path = reader.get('path')
+ self.path = reader.get("path")
except cp.NoSectionError as e:
if self.repo.working_tree_dir is not None:
- raise ValueError("This submodule instance does not exist anymore in '%s' file"
- % osp.join(self.repo.working_tree_dir, '.gitmodules')) from e
+ raise ValueError(
+ "This submodule instance does not exist anymore in '%s' file"
+ % osp.join(self.repo.working_tree_dir, ".gitmodules")
+ ) from e
# end
- self._url = reader.get('url')
+ self._url = reader.get("url")
# git-python extension values - optional
- self._branch_path = reader.get_value(self.k_head_option, git.Head.to_full_path(self.k_head_default))
- elif attr == '_name':
- raise AttributeError("Cannot retrieve the name of a submodule if it was not set initially")
+ self._branch_path = reader.get_value(
+ self.k_head_option, git.Head.to_full_path(self.k_head_default)
+ )
+ elif attr == "_name":
+ raise AttributeError(
+ "Cannot retrieve the name of a submodule if it was not set initially"
+ )
else:
super(Submodule, self)._set_cache_(attr)
# END handle attribute name
@classmethod
- def _get_intermediate_items(cls, item: 'Submodule') -> IterableList['Submodule']:
+ def _get_intermediate_items(cls, item: "Submodule") -> IterableList["Submodule"]:
""":return: all the submodules of our module repository"""
try:
return cls.list_items(item.module())
except InvalidGitRepositoryError:
- return IterableList('')
+ return IterableList("")
# END handle intermediate items
@classmethod
@@ -188,13 +199,18 @@ class Submodule(IndexObject, TraversableIterableObj):
return self._name
def __repr__(self) -> str:
- return "git.%s(name=%s, path=%s, url=%s, branch_path=%s)"\
- % (type(self).__name__, self._name, self.path, self.url, self.branch_path)
+ return "git.%s(name=%s, path=%s, url=%s, branch_path=%s)" % (
+ type(self).__name__,
+ self._name,
+ self.path,
+ self.url,
+ self.branch_path,
+ )
@classmethod
- def _config_parser(cls, repo: 'Repo',
- parent_commit: Union[Commit_ish, None],
- read_only: bool) -> SubmoduleConfigParser:
+ def _config_parser(
+ cls, repo: "Repo", parent_commit: Union[Commit_ish, None], read_only: bool
+ ) -> SubmoduleConfigParser:
""":return: Config Parser constrained to our submodule in read or write mode
:raise IOError: If the .gitmodules file cannot be found, either locally or in the repository
at the given parent commit. Otherwise the exception would be delayed until the first
@@ -211,17 +227,23 @@ class Submodule(IndexObject, TraversableIterableObj):
if not repo.bare and parent_matches_head and repo.working_tree_dir:
fp_module = osp.join(repo.working_tree_dir, cls.k_modules_file)
else:
- assert parent_commit is not None, "need valid parent_commit in bare repositories"
+ assert (
+ parent_commit is not None
+ ), "need valid parent_commit in bare repositories"
try:
fp_module = cls._sio_modules(parent_commit)
except KeyError as e:
- raise IOError("Could not find %s file in the tree of parent commit %s" %
- (cls.k_modules_file, parent_commit)) from e
+ raise IOError(
+ "Could not find %s file in the tree of parent commit %s"
+ % (cls.k_modules_file, parent_commit)
+ ) from e
# END handle exceptions
# END handle non-bare working tree
if not read_only and (repo.bare or not parent_matches_head):
- raise ValueError("Cannot write blobs of 'historical' submodule configurations")
+ raise ValueError(
+ "Cannot write blobs of 'historical' submodule configurations"
+ )
# END handle writes of historical submodules
return SubmoduleConfigParser(fp_module, read_only=read_only)
@@ -246,7 +268,7 @@ class Submodule(IndexObject, TraversableIterableObj):
def _config_parser_constrained(self, read_only: bool) -> SectionConstraint:
""":return: Config Parser constrained to our submodule in read or write mode"""
try:
- pc: Union['Commit_ish', None] = self.parent_commit
+ pc: Union["Commit_ish", None] = self.parent_commit
except ValueError:
pc = None
# end handle empty parent repository
@@ -255,16 +277,20 @@ class Submodule(IndexObject, TraversableIterableObj):
return SectionConstraint(parser, sm_section(self.name))
@classmethod
- def _module_abspath(cls, parent_repo: 'Repo', path: PathLike, name: str) -> PathLike:
+ def _module_abspath(
+ cls, parent_repo: "Repo", path: PathLike, name: str
+ ) -> PathLike:
if cls._need_gitfile_submodules(parent_repo.git):
- return osp.join(parent_repo.git_dir, 'modules', name)
+ return osp.join(parent_repo.git_dir, "modules", name)
if parent_repo.working_tree_dir:
return osp.join(parent_repo.working_tree_dir, path)
raise NotADirectoryError()
# end
@classmethod
- def _clone_repo(cls, repo: 'Repo', url: str, path: PathLike, name: str, **kwargs: Any) -> 'Repo':
+ def _clone_repo(
+ cls, repo: "Repo", url: str, path: PathLike, name: str, **kwargs: Any
+ ) -> "Repo":
""":return: Repo instance of newly cloned repository
:param repo: our parent repository
:param url: url to clone from
@@ -274,7 +300,7 @@ class Submodule(IndexObject, TraversableIterableObj):
module_abspath = cls._module_abspath(repo, path, name)
module_checkout_path = module_abspath
if cls._need_gitfile_submodules(repo.git):
- kwargs['separate_git_dir'] = module_abspath
+ kwargs["separate_git_dir"] = module_abspath
module_abspath_dir = osp.dirname(module_abspath)
if not osp.isdir(module_abspath_dir):
os.makedirs(module_abspath_dir)
@@ -288,29 +314,36 @@ class Submodule(IndexObject, TraversableIterableObj):
return clone
@classmethod
- def _to_relative_path(cls, parent_repo: 'Repo', path: PathLike) -> PathLike:
+ def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike:
""":return: a path guaranteed to be relative to the given parent - repository
:raise ValueError: if path is not contained in the parent repository's working tree"""
path = to_native_path_linux(path)
- if path.endswith('/'):
+ if path.endswith("/"):
path = path[:-1]
# END handle trailing slash
if osp.isabs(path) and parent_repo.working_tree_dir:
working_tree_linux = to_native_path_linux(parent_repo.working_tree_dir)
if not path.startswith(working_tree_linux):
- raise ValueError("Submodule checkout path '%s' needs to be within the parents repository at '%s'"
- % (working_tree_linux, path))
- path = path[len(working_tree_linux.rstrip('/')) + 1:]
+ raise ValueError(
+ "Submodule checkout path '%s' needs to be within the parents repository at '%s'"
+ % (working_tree_linux, path)
+ )
+ path = path[len(working_tree_linux.rstrip("/")) + 1 :]
if not path:
- raise ValueError("Absolute submodule path '%s' didn't yield a valid relative path" % path)
+ raise ValueError(
+ "Absolute submodule path '%s' didn't yield a valid relative path"
+ % path
+ )
# end verify converted relative path makes sense
# end convert to a relative path
return path
@classmethod
- def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None:
+ def _write_git_file_and_module_config(
+ cls, working_tree_dir: PathLike, module_abspath: PathLike
+ ) -> None:
"""Writes a .git file containing a(preferably) relative path to the actual git module repository.
It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir
:note: will overwrite existing files !
@@ -320,26 +353,40 @@ class Submodule(IndexObject, TraversableIterableObj):
:param working_tree_dir: directory to write the .git file into
:param module_abspath: absolute path to the bare repository
"""
- git_file = osp.join(working_tree_dir, '.git')
+ git_file = osp.join(working_tree_dir, ".git")
rela_path = osp.relpath(module_abspath, start=working_tree_dir)
if is_win:
if osp.isfile(git_file):
os.remove(git_file)
- with open(git_file, 'wb') as fp:
+ with open(git_file, "wb") as fp:
fp.write(("gitdir: %s" % rela_path).encode(defenc))
- with GitConfigParser(osp.join(module_abspath, 'config'),
- read_only=False, merge_includes=False) as writer:
- writer.set_value('core', 'worktree',
- to_native_path_linux(osp.relpath(working_tree_dir, start=module_abspath)))
+ with GitConfigParser(
+ osp.join(module_abspath, "config"), read_only=False, merge_includes=False
+ ) as writer:
+ writer.set_value(
+ "core",
+ "worktree",
+ to_native_path_linux(
+ osp.relpath(working_tree_dir, start=module_abspath)
+ ),
+ )
- #{ Edit Interface
+ # { Edit Interface
@classmethod
- def add(cls, repo: 'Repo', name: str, path: PathLike, url: Union[str, None] = None,
- branch: Union[str, None] = None, no_checkout: bool = False, depth: Union[int, None] = None,
- env: Union[Mapping[str, str], None] = None, clone_multi_options: Union[Sequence[TBD], None] = None
- ) -> 'Submodule':
+ def add(
+ cls,
+ repo: "Repo",
+ name: str,
+ path: PathLike,
+ url: Union[str, None] = None,
+ branch: Union[str, None] = None,
+ no_checkout: bool = False,
+ depth: Union[int, None] = None,
+ env: Union[Mapping[str, str], None] = None,
+ clone_multi_options: Union[Sequence[TBD], None] = None,
+ ) -> "Submodule":
"""Add a new submodule to the given repository. This will alter the index
as well as the .gitmodules file, but will not create a new commit.
If the submodule already exists, no matter if the configuration differs
@@ -379,7 +426,9 @@ class Submodule(IndexObject, TraversableIterableObj):
update fails for instance"""
if repo.bare:
- raise InvalidGitRepositoryError("Cannot add submodules to bare repositories")
+ raise InvalidGitRepositoryError(
+ "Cannot add submodules to bare repositories"
+ )
# END handle bare repos
path = cls._to_relative_path(repo, path)
@@ -391,7 +440,14 @@ class Submodule(IndexObject, TraversableIterableObj):
# END assure url correctness
# INSTANTIATE INTERMEDIATE SM
- sm = cls(repo, cls.NULL_BIN_SHA, cls.k_default_mode, path, name, url='invalid-temporary')
+ sm = cls(
+ repo,
+ cls.NULL_BIN_SHA,
+ cls.k_default_mode,
+ path,
+ name,
+ url="invalid-temporary",
+ )
if sm.exists():
# reretrieve submodule from tree
try:
@@ -414,7 +470,9 @@ class Submodule(IndexObject, TraversableIterableObj):
if has_module and url is not None:
if url not in [r.url for r in sm.module().remotes]:
raise ValueError(
- "Specified URL '%s' does not match any remote url of the repository at '%s'" % (url, sm.abspath))
+ "Specified URL '%s' does not match any remote url of the repository at '%s'"
+ % (url, sm.abspath)
+ )
# END check url
# END verify urls match
@@ -422,29 +480,33 @@ class Submodule(IndexObject, TraversableIterableObj):
if url is None:
if not has_module:
- raise ValueError("A URL was not given and a repository did not exist at %s" % path)
+ raise ValueError(
+ "A URL was not given and a repository did not exist at %s" % path
+ )
# END check url
mrepo = sm.module()
# assert isinstance(mrepo, git.Repo)
urls = [r.url for r in mrepo.remotes]
if not urls:
- raise ValueError("Didn't find any remote url in repository at %s" % sm.abspath)
+ raise ValueError(
+ "Didn't find any remote url in repository at %s" % sm.abspath
+ )
# END verify we have url
url = urls[0]
else:
# clone new repo
- kwargs: Dict[str, Union[bool, int, str, Sequence[TBD]]] = {'n': no_checkout}
+ kwargs: Dict[str, Union[bool, int, str, Sequence[TBD]]] = {"n": no_checkout}
if not branch_is_default:
- kwargs['b'] = br.name
+ kwargs["b"] = br.name
# END setup checkout-branch
if depth:
if isinstance(depth, int):
- kwargs['depth'] = depth
+ kwargs["depth"] = depth
else:
raise ValueError("depth should be an integer")
if clone_multi_options:
- kwargs['multi_options'] = clone_multi_options
+ kwargs["multi_options"] = clone_multi_options
# _clone_repo(cls, repo, url, path, name, **kwargs):
mrepo = cls._clone_repo(repo, url, path, name, env=env, **kwargs)
@@ -460,13 +522,13 @@ class Submodule(IndexObject, TraversableIterableObj):
writer: Union[GitConfigParser, SectionConstraint]
with sm.repo.config_writer() as writer:
- writer.set_value(sm_section(name), 'url', url)
+ writer.set_value(sm_section(name), "url", url)
# update configuration and index
index = sm.repo.index
with sm.config_writer(index=index, write=False) as writer:
- writer.set_value('url', url)
- writer.set_value('path', path)
+ writer.set_value("url", url)
+ writer.set_value("path", path)
sm._url = url
if not branch_is_default:
@@ -481,10 +543,18 @@ class Submodule(IndexObject, TraversableIterableObj):
return sm
- def update(self, recursive: bool = False, init: bool = True, to_latest_revision: bool = False,
- progress: Union['UpdateProgress', None] = None, dry_run: bool = False,
- force: bool = False, keep_going: bool = False, env: Union[Mapping[str, str], None] = None,
- clone_multi_options: Union[Sequence[TBD], None] = None) -> 'Submodule':
+ def update(
+ self,
+ recursive: bool = False,
+ init: bool = True,
+ to_latest_revision: bool = False,
+ progress: Union["UpdateProgress", None] = None,
+ dry_run: bool = False,
+ force: bool = False,
+ keep_going: bool = False,
+ env: Union[Mapping[str, str], None] = None,
+ clone_multi_options: Union[Sequence[TBD], None] = None,
+ ) -> "Submodule":
"""Update the repository of this submodule to point to the checkout
we point at with the binsha of this instance.
@@ -527,7 +597,7 @@ class Submodule(IndexObject, TraversableIterableObj):
if progress is None:
progress = UpdateProgress()
# END handle progress
- prefix = ''
+ prefix = ""
if dry_run:
prefix = "DRY-RUN: "
# END handle prefix
@@ -550,17 +620,27 @@ class Submodule(IndexObject, TraversableIterableObj):
op |= BEGIN
# END handle start
- progress.update(op, i, len_rmts, prefix + "Fetching remote %s of submodule %r"
- % (remote, self.name))
- #===============================
+ progress.update(
+ op,
+ i,
+ len_rmts,
+ prefix
+ + "Fetching remote %s of submodule %r" % (remote, self.name),
+ )
+ # ===============================
if not dry_run:
remote.fetch(progress=progress)
# END handle dry-run
- #===============================
+ # ===============================
if i == len_rmts - 1:
op |= END
# END handle end
- progress.update(op, i, len_rmts, prefix + "Done fetching remote of submodule %r" % self.name)
+ progress.update(
+ op,
+ i,
+ len_rmts,
+ prefix + "Done fetching remote of submodule %r" % self.name,
+ )
# END fetch new data
except InvalidGitRepositoryError:
mrepo = None
@@ -574,27 +654,49 @@ class Submodule(IndexObject, TraversableIterableObj):
try:
os.rmdir(checkout_module_abspath)
except OSError as e:
- raise OSError("Module directory at %r does already exist and is non-empty"
- % checkout_module_abspath) from e
+ raise OSError(
+ "Module directory at %r does already exist and is non-empty"
+ % checkout_module_abspath
+ ) from e
# END handle OSError
# END handle directory removal
# don't check it out at first - nonetheless it will create a local
# branch according to the remote-HEAD if possible
- progress.update(BEGIN | CLONE, 0, 1, prefix + "Cloning url '%s' to '%s' in submodule %r" %
- (self.url, checkout_module_abspath, self.name))
+ progress.update(
+ BEGIN | CLONE,
+ 0,
+ 1,
+ prefix
+ + "Cloning url '%s' to '%s' in submodule %r"
+ % (self.url, checkout_module_abspath, self.name),
+ )
if not dry_run:
- mrepo = self._clone_repo(self.repo, self.url, self.path, self.name, n=True, env=env,
- multi_options=clone_multi_options)
+ mrepo = self._clone_repo(
+ self.repo,
+ self.url,
+ self.path,
+ self.name,
+ n=True,
+ env=env,
+ multi_options=clone_multi_options,
+ )
# END handle dry-run
- progress.update(END | CLONE, 0, 1, prefix + "Done cloning to %s" % checkout_module_abspath)
+ progress.update(
+ END | CLONE,
+ 0,
+ 1,
+ prefix + "Done cloning to %s" % checkout_module_abspath,
+ )
if not dry_run:
# see whether we have a valid branch to checkout
try:
- mrepo = cast('Repo', mrepo)
+ mrepo = cast("Repo", mrepo)
# find a remote which has our branch - we try to be flexible
- remote_branch = find_first_remote_branch(mrepo.remotes, self.branch_name)
+ remote_branch = find_first_remote_branch(
+ mrepo.remotes, self.branch_name
+ )
local_branch = mkhead(mrepo, self.branch_path)
# have a valid branch, but no checkout - make sure we can figure
@@ -603,10 +705,15 @@ class Submodule(IndexObject, TraversableIterableObj):
# END initial checkout + branch creation
# make sure HEAD is not detached
- mrepo.head.set_reference(local_branch, logmsg="submodule: attaching head to %s" % local_branch)
+ mrepo.head.set_reference(
+ local_branch,
+ logmsg="submodule: attaching head to %s" % local_branch,
+ )
mrepo.head.reference.set_tracking_branch(remote_branch)
except (IndexError, InvalidGitRepositoryError):
- log.warning("Failed to checkout tracking branch %s", self.branch_path)
+ log.warning(
+ "Failed to checkout tracking branch %s", self.branch_path
+ )
# END handle tracking branch
# NOTE: Have to write the repo config file as well, otherwise
@@ -614,7 +721,7 @@ class Submodule(IndexObject, TraversableIterableObj):
# Maybe this is a good way to assure it doesn't get into our way, but
# we want to stay backwards compatible too ... . Its so redundant !
with self.repo.config_writer() as writer:
- writer.set_value(sm_section(self.name), 'url', self.url)
+ writer.set_value(sm_section(self.name), "url", self.url)
# END handle dry_run
# END handle initialization
@@ -628,7 +735,10 @@ class Submodule(IndexObject, TraversableIterableObj):
# END handle dry_run
if mrepo is not None and to_latest_revision:
- msg_base = "Cannot update to latest revision in repository at %r as " % mrepo.working_dir
+ msg_base = (
+ "Cannot update to latest revision in repository at %r as "
+ % mrepo.working_dir
+ )
if not is_detached:
rref = mrepo.head.reference.tracking_branch()
if rref is not None:
@@ -636,8 +746,11 @@ class Submodule(IndexObject, TraversableIterableObj):
binsha = rcommit.binsha
hexsha = rcommit.hexsha
else:
- log.error("%s a tracking branch was not set for local branch '%s'",
- msg_base, mrepo.head.reference)
+ log.error(
+ "%s a tracking branch was not set for local branch '%s'",
+ msg_base,
+ mrepo.head.reference,
+ )
# END handle remote ref
else:
log.error("%s there was no local tracking branch", msg_base)
@@ -654,28 +767,47 @@ class Submodule(IndexObject, TraversableIterableObj):
may_reset = True
if mrepo.head.commit.binsha != self.NULL_BIN_SHA:
base_commit = mrepo.merge_base(mrepo.head.commit, hexsha)
- if len(base_commit) == 0 or (base_commit[0] is not None and base_commit[0].hexsha == hexsha):
+ if len(base_commit) == 0 or (
+ base_commit[0] is not None and base_commit[0].hexsha == hexsha
+ ):
if force:
msg = "Will force checkout or reset on local branch that is possibly in the future of"
msg += "the commit it will be checked out to, effectively 'forgetting' new commits"
log.debug(msg)
else:
msg = "Skipping %s on branch '%s' of submodule repo '%s' as it contains un-pushed commits"
- msg %= (is_detached and "checkout" or "reset", mrepo.head, mrepo)
+ msg %= (
+ is_detached and "checkout" or "reset",
+ mrepo.head,
+ mrepo,
+ )
log.info(msg)
may_reset = False
# end handle force
# end handle if we are in the future
- if may_reset and not force and mrepo.is_dirty(index=True, working_tree=True, untracked_files=True):
- raise RepositoryDirtyError(mrepo, "Cannot reset a dirty repository")
+ if (
+ may_reset
+ and not force
+ and mrepo.is_dirty(
+ index=True, working_tree=True, untracked_files=True
+ )
+ ):
+ raise RepositoryDirtyError(
+ mrepo, "Cannot reset a dirty repository"
+ )
# end handle force and dirty state
# end handle empty repo
# end verify future/past
- progress.update(BEGIN | UPDWKTREE, 0, 1, prefix +
- "Updating working tree at %s for submodule %r to revision %s"
- % (self.path, self.name, hexsha))
+ progress.update(
+ BEGIN | UPDWKTREE,
+ 0,
+ 1,
+ prefix
+ + "Updating working tree at %s for submodule %r to revision %s"
+ % (self.path, self.name, hexsha),
+ )
if not dry_run and may_reset:
if is_detached:
@@ -688,8 +820,12 @@ class Submodule(IndexObject, TraversableIterableObj):
mrepo.head.reset(hexsha, index=True, working_tree=True)
# END handle checkout
# if we may reset/checkout
- progress.update(END | UPDWKTREE, 0, 1, prefix + "Done updating working tree for submodule %r"
- % self.name)
+ progress.update(
+ END | UPDWKTREE,
+ 0,
+ 1,
+ prefix + "Done updating working tree for submodule %r" % self.name,
+ )
# END update to new commit only if needed
except Exception as err:
if not keep_going:
@@ -703,8 +839,15 @@ class Submodule(IndexObject, TraversableIterableObj):
# in dry_run mode, the module might not exist
if mrepo is not None:
for submodule in self.iter_items(self.module()):
- submodule.update(recursive, init, to_latest_revision, progress=progress, dry_run=dry_run,
- force=force, keep_going=keep_going)
+ submodule.update(
+ recursive,
+ init,
+ to_latest_revision,
+ progress=progress,
+ dry_run=dry_run,
+ force=force,
+ keep_going=keep_going,
+ )
# END handle recursive update
# END handle dry run
# END for each submodule
@@ -712,7 +855,9 @@ class Submodule(IndexObject, TraversableIterableObj):
return self
@unbare_repo
- def move(self, module_path: PathLike, configuration: bool = True, module: bool = True) -> 'Submodule':
+ def move(
+ self, module_path: PathLike, configuration: bool = True, module: bool = True
+ ) -> "Submodule":
"""Move the submodule to a another module path. This involves physically moving
the repository at our current path, changing the configuration, as well as
adjusting our index entry accordingly.
@@ -732,7 +877,9 @@ class Submodule(IndexObject, TraversableIterableObj):
in an inconsistent state if a sub - step fails for some reason
"""
if module + configuration < 1:
- raise ValueError("You must specify to move at least the module or the configuration of the submodule")
+ raise ValueError(
+ "You must specify to move at least the module or the configuration of the submodule"
+ )
# END handle input
module_checkout_path = self._to_relative_path(self.repo, module_path)
@@ -742,9 +889,13 @@ class Submodule(IndexObject, TraversableIterableObj):
return self
# END handle no change
- module_checkout_abspath = join_path_native(str(self.repo.working_tree_dir), module_checkout_path)
+ module_checkout_abspath = join_path_native(
+ str(self.repo.working_tree_dir), module_checkout_path
+ )
if osp.isfile(module_checkout_abspath):
- raise ValueError("Cannot move repository onto a file: %s" % module_checkout_abspath)
+ raise ValueError(
+ "Cannot move repository onto a file: %s" % module_checkout_abspath
+ )
# END handle target files
index = self.repo.index
@@ -780,9 +931,11 @@ class Submodule(IndexObject, TraversableIterableObj):
os.renames(cur_path, module_checkout_abspath)
renamed_module = True
- if osp.isfile(osp.join(module_checkout_abspath, '.git')):
+ if osp.isfile(osp.join(module_checkout_abspath, ".git")):
module_abspath = self._module_abspath(self.repo, self.path, self.name)
- self._write_git_file_and_module_config(module_checkout_abspath, module_abspath)
+ self._write_git_file_and_module_config(
+ module_checkout_abspath, module_abspath
+ )
# end handle git file rewrite
# END move physical module
@@ -794,16 +947,20 @@ class Submodule(IndexObject, TraversableIterableObj):
try:
ekey = index.entry_key(self.path, 0)
entry = index.entries[ekey]
- del(index.entries[ekey])
- nentry = git.IndexEntry(entry[:3] + (module_checkout_path,) + entry[4:])
+ del index.entries[ekey]
+ nentry = git.IndexEntry(
+ entry[:3] + (module_checkout_path,) + entry[4:]
+ )
index.entries[tekey] = nentry
except KeyError as e:
- raise InvalidGitRepositoryError("Submodule's entry at %r did not exist" % (self.path)) from e
+ raise InvalidGitRepositoryError(
+ "Submodule's entry at %r did not exist" % (self.path)
+ ) from e
# END handle submodule doesn't exist
# update configuration
- with self.config_writer(index=index) as writer: # auto-write
- writer.set_value('path', module_checkout_path)
+ with self.config_writer(index=index) as writer: # auto-write
+ writer.set_value("path", module_checkout_path)
self.path = module_checkout_path
# END handle configuration flag
except Exception:
@@ -821,8 +978,13 @@ class Submodule(IndexObject, TraversableIterableObj):
return self
@unbare_repo
- def remove(self, module: bool = True, force: bool = False,
- configuration: bool = True, dry_run: bool = False) -> 'Submodule':
+ def remove(
+ self,
+ module: bool = True,
+ force: bool = False,
+ configuration: bool = True,
+ dry_run: bool = False,
+ ) -> "Submodule":
"""Remove this submodule from the repository. This will remove our entry
from the .gitmodules file and the entry in the .git / config file.
@@ -850,7 +1012,9 @@ class Submodule(IndexObject, TraversableIterableObj):
:raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
:raise OSError: if directories or files could not be removed"""
if not (module or configuration):
- raise ValueError("Need to specify to delete at least the module, or the configuration")
+ raise ValueError(
+ "Need to specify to delete at least the module, or the configuration"
+ )
# END handle parameters
# Recursively remove children of this submodule
@@ -858,12 +1022,14 @@ class Submodule(IndexObject, TraversableIterableObj):
for csm in self.children():
nc += 1
csm.remove(module, force, configuration, dry_run)
- del(csm)
+ del csm
# end
if configuration and not dry_run and nc > 0:
# Assure we don't leave the parent repository in a dirty state, and commit our changes
# It's important for recursive, unforced, deletions to work as expected
- self.module().index.commit("Removed at least one of child-modules of '%s'" % self.name)
+ self.module().index.commit(
+ "Removed at least one of child-modules of '%s'" % self.name
+ )
# end handle recursion
# DELETE REPOSITORY WORKING TREE
@@ -882,7 +1048,9 @@ class Submodule(IndexObject, TraversableIterableObj):
elif osp.isdir(mp):
method = rmtree
elif osp.exists(mp):
- raise AssertionError("Cannot forcibly delete repository as it was neither a link, nor a directory")
+ raise AssertionError(
+ "Cannot forcibly delete repository as it was neither a link, nor a directory"
+ )
# END handle brutal deletion
if not dry_run:
assert method
@@ -893,7 +1061,8 @@ class Submodule(IndexObject, TraversableIterableObj):
if mod.is_dirty(index=True, working_tree=True, untracked_files=True):
raise InvalidGitRepositoryError(
"Cannot delete module at %s with any modifications, unless force is specified"
- % mod.working_tree_dir)
+ % mod.working_tree_dir
+ )
# END check for dirt
# figure out whether we have new commits compared to the remotes
@@ -910,30 +1079,36 @@ class Submodule(IndexObject, TraversableIterableObj):
# not a single remote branch contained all our commits
if len(rrefs) and num_branches_with_new_commits == len(rrefs):
raise InvalidGitRepositoryError(
- "Cannot delete module at %s as there are new commits" % mod.working_tree_dir)
+ "Cannot delete module at %s as there are new commits"
+ % mod.working_tree_dir
+ )
# END handle new commits
# have to manually delete references as python's scoping is
# not existing, they could keep handles open ( on windows this is a problem )
if len(rrefs):
- del(rref) # skipcq: PYL-W0631
+ del rref # skipcq: PYL-W0631
# END handle remotes
- del(rrefs)
- del(remote)
+ del rrefs
+ del remote
# END for each remote
# finally delete our own submodule
if not dry_run:
self._clear_cache()
wtd = mod.working_tree_dir
- del(mod) # release file-handles (windows)
+ del mod # release file-handles (windows)
import gc
+
gc.collect()
try:
rmtree(str(wtd))
except Exception as ex:
if HIDE_WINDOWS_KNOWN_ERRORS:
from unittest import SkipTest
- raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex
+
+ raise SkipTest(
+ "FIXME: fails with: PermissionError\n {}".format(ex)
+ ) from ex
raise
# END delete tree if possible
# END handle force
@@ -945,7 +1120,10 @@ class Submodule(IndexObject, TraversableIterableObj):
except Exception as ex:
if HIDE_WINDOWS_KNOWN_ERRORS:
from unittest import SkipTest
- raise SkipTest(f"FIXME: fails with: PermissionError\n {ex}") from ex
+
+ raise SkipTest(
+ f"FIXME: fails with: PermissionError\n {ex}"
+ ) from ex
else:
raise
# end handle separate bare repository
@@ -961,7 +1139,7 @@ class Submodule(IndexObject, TraversableIterableObj):
# first the index-entry
parent_index = self.repo.index
try:
- del(parent_index.entries[parent_index.entry_key(self.path, 0)])
+ del parent_index.entries[parent_index.entry_key(self.path, 0)]
except KeyError:
pass
# END delete entry
@@ -979,7 +1157,9 @@ class Submodule(IndexObject, TraversableIterableObj):
return self
- def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) -> 'Submodule':
+ def set_parent_commit(
+ self, commit: Union[Commit_ish, None], check: bool = True
+ ) -> "Submodule":
"""Set this instance to use the given commit whose tree is supposed to
contain the .gitmodules blob.
@@ -1000,7 +1180,10 @@ class Submodule(IndexObject, TraversableIterableObj):
pcommit = self.repo.commit(commit)
pctree = pcommit.tree
if self.k_modules_file not in pctree:
- raise ValueError("Tree of commit %s did not contain the %s file" % (commit, self.k_modules_file))
+ raise ValueError(
+ "Tree of commit %s did not contain the %s file"
+ % (commit, self.k_modules_file)
+ )
# END handle exceptions
prev_pc = self._parent_commit
@@ -1010,7 +1193,10 @@ class Submodule(IndexObject, TraversableIterableObj):
parser = self._config_parser(self.repo, self._parent_commit, read_only=True)
if not parser.has_section(sm_section(self.name)):
self._parent_commit = prev_pc
- raise ValueError("Submodule at path %r did not exist in parent commit %s" % (self.path, commit))
+ raise ValueError(
+ "Submodule at path %r did not exist in parent commit %s"
+ % (self.path, commit)
+ )
# END handle submodule did not exist
# END handle checking mode
@@ -1027,8 +1213,9 @@ class Submodule(IndexObject, TraversableIterableObj):
return self
@unbare_repo
- def config_writer(self, index: Union['IndexFile', None] = None, write: bool = True
- ) -> SectionConstraint['SubmoduleConfigParser']:
+ def config_writer(
+ self, index: Union["IndexFile", None] = None, write: bool = True
+ ) -> SectionConstraint["SubmoduleConfigParser"]:
""":return: a config writer instance allowing you to read and write the data
belonging to this submodule into the .gitmodules file.
@@ -1049,7 +1236,7 @@ class Submodule(IndexObject, TraversableIterableObj):
return writer
@unbare_repo
- def rename(self, new_name: str) -> 'Submodule':
+ def rename(self, new_name: str) -> "Submodule":
"""Rename this submodule
:note: This method takes care of renaming the submodule in various places, such as
@@ -1081,7 +1268,9 @@ class Submodule(IndexObject, TraversableIterableObj):
# .git/modules
mod = self.module()
if mod.has_separate_working_tree():
- destination_module_abspath = self._module_abspath(self.repo, self.path, new_name)
+ destination_module_abspath = self._module_abspath(
+ self.repo, self.path, new_name
+ )
source_dir = mod.git_dir
# Let's be sure the submodule name is not so obviously tied to a directory
if str(destination_module_abspath).startswith(str(mod.git_dir)):
@@ -1091,17 +1280,19 @@ class Submodule(IndexObject, TraversableIterableObj):
# end handle self-containment
os.renames(source_dir, destination_module_abspath)
if mod.working_tree_dir:
- self._write_git_file_and_module_config(mod.working_tree_dir, destination_module_abspath)
+ self._write_git_file_and_module_config(
+ mod.working_tree_dir, destination_module_abspath
+ )
# end move separate git repository
return self
- #} END edit interface
+ # } END edit interface
- #{ Query Interface
+ # { Query Interface
@unbare_repo
- def module(self) -> 'Repo':
+ def module(self) -> "Repo":
""":return: Repo instance initialized from the repository at our submodule path
:raise InvalidGitRepositoryError: if a repository was not available. This could
also mean that it was not yet initialized"""
@@ -1113,9 +1304,13 @@ class Submodule(IndexObject, TraversableIterableObj):
return repo
# END handle repo uninitialized
except (InvalidGitRepositoryError, NoSuchPathError) as e:
- raise InvalidGitRepositoryError("No valid repository at %s" % module_checkout_abspath) from e
+ raise InvalidGitRepositoryError(
+ "No valid repository at %s" % module_checkout_abspath
+ ) from e
else:
- raise InvalidGitRepositoryError("Repository at %r was not yet checked out" % module_checkout_abspath)
+ raise InvalidGitRepositoryError(
+ "Repository at %r was not yet checked out" % module_checkout_abspath
+ )
# END handle exceptions
def module_exists(self) -> bool:
@@ -1162,7 +1357,7 @@ class Submodule(IndexObject, TraversableIterableObj):
# END handle object state consistency
@property
- def branch(self) -> 'Head':
+ def branch(self) -> "Head":
""":return: The branch instance that we are to checkout
:raise InvalidGitRepositoryError: if our module is not yet checked out"""
return mkhead(self.module(), self._branch_path)
@@ -1187,7 +1382,7 @@ class Submodule(IndexObject, TraversableIterableObj):
return self._url
@property
- def parent_commit(self) -> 'Commit_ish':
+ def parent_commit(self) -> "Commit_ish":
""":return: Commit instance with the tree containing the .gitmodules file
:note: will always point to the current head's commit if it was not set explicitly"""
if self._parent_commit is None:
@@ -1215,22 +1410,27 @@ class Submodule(IndexObject, TraversableIterableObj):
:raise IOError: If the .gitmodules file/blob could not be read"""
return self._config_parser_constrained(read_only=True)
- def children(self) -> IterableList['Submodule']:
+ def children(self) -> IterableList["Submodule"]:
"""
:return: IterableList(Submodule, ...) an iterable list of submodules instances
which are children of this submodule or 0 if the submodule is not checked out"""
return self._get_intermediate_items(self)
- #} END query interface
+ # } END query interface
- #{ Iterable Interface
+ # { Iterable Interface
@classmethod
- def iter_items(cls, repo: 'Repo', parent_commit: Union[Commit_ish, str] = 'HEAD', *Args: Any, **kwargs: Any
- ) -> Iterator['Submodule']:
+ def iter_items(
+ cls,
+ repo: "Repo",
+ parent_commit: Union[Commit_ish, str] = "HEAD",
+ *Args: Any,
+ **kwargs: Any,
+ ) -> Iterator["Submodule"]:
""":return: iterator yielding Submodule instances available in the given repository"""
try:
- pc = repo.commit(parent_commit) # parent commit instance
+ pc = repo.commit(parent_commit) # parent commit instance
parser = cls._config_parser(repo, pc, read_only=True)
except (IOError, BadName):
return iter([])
@@ -1238,8 +1438,8 @@ class Submodule(IndexObject, TraversableIterableObj):
for sms in parser.sections():
n = sm_name(sms)
- p = parser.get(sms, 'path')
- u = parser.get(sms, 'url')
+ p = parser.get(sms, "path")
+ u = parser.get(sms, "url")
b = cls.k_head_default
if parser.has_option(sms, cls.k_head_option):
b = str(parser.get(sms, cls.k_head_option))
@@ -1248,7 +1448,7 @@ class Submodule(IndexObject, TraversableIterableObj):
# get the binsha
index = repo.index
try:
- rt = pc.tree # root tree
+ rt = pc.tree # root tree
sm = rt[p]
except KeyError:
# try the index, maybe it was just added
@@ -1273,4 +1473,4 @@ class Submodule(IndexObject, TraversableIterableObj):
yield sm
# END for each section
- #} END iterable interface
+ # } END iterable interface
diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py
index 08e1f954..16f0f91f 100644
--- a/git/objects/submodule/root.py
+++ b/git/objects/submodule/root.py
@@ -1,7 +1,4 @@
-from .base import (
- Submodule,
- UpdateProgress
-)
+from .base import Submodule, UpdateProgress
from .util import find_first_remote_branch
from git.exc import InvalidGitRepositoryError
import git
@@ -22,14 +19,17 @@ if TYPE_CHECKING:
__all__ = ["RootModule", "RootUpdateProgress"]
-log = logging.getLogger('git.objects.submodule.root')
+log = logging.getLogger("git.objects.submodule.root")
log.addHandler(logging.NullHandler())
class RootUpdateProgress(UpdateProgress):
"""Utility class which adds more opcodes to the UpdateProgress"""
+
REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [
- 1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)]
+ 1 << x
+ for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4)
+ ]
_num_op_codes = UpdateProgress._num_op_codes + 4
__slots__ = ()
@@ -50,32 +50,39 @@ class RootModule(Submodule):
__slots__ = ()
- k_root_name = '__ROOT__'
+ k_root_name = "__ROOT__"
- def __init__(self, repo: 'Repo'):
+ def __init__(self, repo: "Repo"):
# repo, binsha, mode=None, path=None, name = None, parent_commit=None, url=None, ref=None)
super(RootModule, self).__init__(
repo,
binsha=self.NULL_BIN_SHA,
mode=self.k_default_mode,
- path='',
+ path="",
name=self.k_root_name,
parent_commit=repo.head.commit,
- url='',
- branch_path=git.Head.to_full_path(self.k_head_default)
+ url="",
+ branch_path=git.Head.to_full_path(self.k_head_default),
)
def _clear_cache(self) -> None:
"""May not do anything"""
pass
- #{ Interface
-
- def update(self, previous_commit: Union[Commit_ish, None] = None, # type: ignore[override]
- recursive: bool = True, force_remove: bool = False, init: bool = True,
- to_latest_revision: bool = False, progress: Union[None, 'RootUpdateProgress'] = None,
- dry_run: bool = False, force_reset: bool = False, keep_going: bool = False
- ) -> 'RootModule':
+ # { Interface
+
+ def update(
+ self,
+ previous_commit: Union[Commit_ish, None] = None, # type: ignore[override]
+ recursive: bool = True,
+ force_remove: bool = False,
+ init: bool = True,
+ to_latest_revision: bool = False,
+ progress: Union[None, "RootUpdateProgress"] = None,
+ dry_run: bool = False,
+ force_reset: bool = False,
+ keep_going: bool = False,
+ ) -> "RootModule":
"""Update the submodules of this repository to the current HEAD commit.
This method behaves smartly by determining changes of the path of a submodules
repository, next to changes to the to-be-checked-out commit or the branch to be
@@ -109,16 +116,18 @@ class RootModule(Submodule):
In conjunction with dry_run, it can be useful to anticipate all errors when updating submodules
:return: self"""
if self.repo.bare:
- raise InvalidGitRepositoryError("Cannot update submodules in bare repositories")
+ raise InvalidGitRepositoryError(
+ "Cannot update submodules in bare repositories"
+ )
# END handle bare
if progress is None:
progress = RootUpdateProgress()
# END assure progress is set
- prefix = ''
+ prefix = ""
if dry_run:
- prefix = 'DRY-RUN: '
+ prefix = "DRY-RUN: "
repo = self.repo
@@ -137,17 +146,19 @@ class RootModule(Submodule):
previous_commit = cur_commit
# END exception handling
else:
- previous_commit = repo.commit(previous_commit) # obtain commit object
+ previous_commit = repo.commit(previous_commit) # obtain commit object
# END handle previous commit
- psms: 'IterableList[Submodule]' = self.list_items(repo, parent_commit=previous_commit)
- sms: 'IterableList[Submodule]' = self.list_items(repo)
+ psms: "IterableList[Submodule]" = self.list_items(
+ repo, parent_commit=previous_commit
+ )
+ sms: "IterableList[Submodule]" = self.list_items(repo)
spsms = set(psms)
ssms = set(sms)
# HANDLE REMOVALS
###################
- rrsm = (spsms - ssms)
+ rrsm = spsms - ssms
len_rrsm = len(rrsm)
for i, rsm in enumerate(rrsm):
@@ -158,37 +169,58 @@ class RootModule(Submodule):
# fake it into thinking its at the current commit to allow deletion
# of previous module. Trigger the cache to be updated before that
- progress.update(op, i, len_rrsm, prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath))
+ progress.update(
+ op,
+ i,
+ len_rrsm,
+ prefix + "Removing submodule %r at %s" % (rsm.name, rsm.abspath),
+ )
rsm._parent_commit = repo.head.commit
- rsm.remove(configuration=False, module=True, force=force_remove, dry_run=dry_run)
+ rsm.remove(
+ configuration=False,
+ module=True,
+ force=force_remove,
+ dry_run=dry_run,
+ )
if i == len_rrsm - 1:
op |= END
# END handle end
- progress.update(op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name)
+ progress.update(
+ op, i, len_rrsm, prefix + "Done removing submodule %r" % rsm.name
+ )
# END for each removed submodule
# HANDLE PATH RENAMES
#####################
# url changes + branch changes
- csms = (spsms & ssms)
+ csms = spsms & ssms
len_csms = len(csms)
for i, csm in enumerate(csms):
- psm: 'Submodule' = psms[csm.name]
- sm: 'Submodule' = sms[csm.name]
+ psm: "Submodule" = psms[csm.name]
+ sm: "Submodule" = sms[csm.name]
# PATH CHANGES
##############
if sm.path != psm.path and psm.module_exists():
- progress.update(BEGIN | PATHCHANGE, i, len_csms, prefix +
- "Moving repository of submodule %r from %s to %s"
- % (sm.name, psm.abspath, sm.abspath))
+ progress.update(
+ BEGIN | PATHCHANGE,
+ i,
+ len_csms,
+ prefix
+ + "Moving repository of submodule %r from %s to %s"
+ % (sm.name, psm.abspath, sm.abspath),
+ )
# move the module to the new path
if not dry_run:
psm.move(sm.path, module=True, configuration=False)
# END handle dry_run
progress.update(
- END | PATHCHANGE, i, len_csms, prefix + "Done moving repository of submodule %r" % sm.name)
+ END | PATHCHANGE,
+ i,
+ len_csms,
+ prefix + "Done moving repository of submodule %r" % sm.name,
+ )
# END handle path changes
if sm.module_exists():
@@ -198,14 +230,20 @@ class RootModule(Submodule):
# Add the new remote, remove the old one
# This way, if the url just changes, the commits will not
# have to be re-retrieved
- nn = '__new_origin__'
+ nn = "__new_origin__"
smm = sm.module()
rmts = smm.remotes
# don't do anything if we already have the url we search in place
if len([r for r in rmts if r.url == sm.url]) == 0:
- progress.update(BEGIN | URLCHANGE, i, len_csms, prefix +
- "Changing url of submodule %r from %s to %s" % (sm.name, psm.url, sm.url))
+ progress.update(
+ BEGIN | URLCHANGE,
+ i,
+ len_csms,
+ prefix
+ + "Changing url of submodule %r from %s to %s"
+ % (sm.name, psm.url, sm.url),
+ )
if not dry_run:
assert nn not in [r.name for r in rmts]
@@ -214,7 +252,16 @@ class RootModule(Submodule):
# If we have a tracking branch, it should be available
# in the new remote as well.
- if len([r for r in smr.refs if r.remote_head == sm.branch_name]) == 0:
+ if (
+ len(
+ [
+ r
+ for r in smr.refs
+ if r.remote_head == sm.branch_name
+ ]
+ )
+ == 0
+ ):
raise ValueError(
"Submodule branch named %r was not available in new submodule remote at %r"
% (sm.branch_name, sm.url)
@@ -242,7 +289,9 @@ class RootModule(Submodule):
# Alternatively we could just generate a unique name and leave all
# existing ones in place
raise InvalidGitRepositoryError(
- "Couldn't find original remote-repo at url %r" % psm.url)
+ "Couldn't find original remote-repo at url %r"
+ % psm.url
+ )
# END handle one single remote
# END handle check we found a remote
@@ -277,15 +326,23 @@ class RootModule(Submodule):
# this way, it will be checked out in the next step
# This will change the submodule relative to us, so
# the user will be able to commit the change easily
- log.warning("Current sha %s was not contained in the tracking\
- branch at the new remote, setting it the the remote's tracking branch", sm.hexsha)
+ log.warning(
+ "Current sha %s was not contained in the tracking\
+ branch at the new remote, setting it the the remote's tracking branch",
+ sm.hexsha,
+ )
sm.binsha = rref.commit.binsha
# END reset binsha
# NOTE: All checkout is performed by the base implementation of update
# END handle dry_run
progress.update(
- END | URLCHANGE, i, len_csms, prefix + "Done adjusting url of submodule %r" % (sm.name))
+ END | URLCHANGE,
+ i,
+ len_csms,
+ prefix
+ + "Done adjusting url of submodule %r" % (sm.name),
+ )
# END skip remote handling if new url already exists in module
# END handle url
@@ -294,9 +351,14 @@ class RootModule(Submodule):
if sm.branch_path != psm.branch_path:
# finally, create a new tracking branch which tracks the
# new remote branch
- progress.update(BEGIN | BRANCHCHANGE, i, len_csms, prefix +
- "Changing branch of submodule %r from %s to %s"
- % (sm.name, psm.branch_path, sm.branch_path))
+ progress.update(
+ BEGIN | BRANCHCHANGE,
+ i,
+ len_csms,
+ prefix
+ + "Changing branch of submodule %r from %s to %s"
+ % (sm.name, psm.branch_path, sm.branch_path),
+ )
if not dry_run:
smm = sm.module()
smmr = smm.remotes
@@ -306,13 +368,19 @@ class RootModule(Submodule):
# end for each remote
try:
- tbr = git.Head.create(smm, sm.branch_name, logmsg='branch: Created from HEAD')
+ tbr = git.Head.create(
+ smm,
+ sm.branch_name,
+ logmsg="branch: Created from HEAD",
+ )
except OSError:
# ... or reuse the existing one
tbr = git.Head(smm, sm.branch_path)
# END assure tracking branch exists
- tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name))
+ tbr.set_tracking_branch(
+ find_first_remote_branch(smmr, sm.branch_name)
+ )
# NOTE: All head-resetting is done in the base implementation of update
# but we will have to checkout the new branch here. As it still points to the currently
# checkout out commit, we don't do any harm.
@@ -321,7 +389,11 @@ class RootModule(Submodule):
# END handle dry_run
progress.update(
- END | BRANCHCHANGE, i, len_csms, prefix + "Done changing branch of submodule %r" % sm.name)
+ END | BRANCHCHANGE,
+ i,
+ len_csms,
+ prefix + "Done changing branch of submodule %r" % sm.name,
+ )
# END handle branch
# END handle
# END for each common submodule
@@ -335,8 +407,15 @@ class RootModule(Submodule):
######################################
for sm in sms:
# update the submodule using the default method
- sm.update(recursive=False, init=init, to_latest_revision=to_latest_revision,
- progress=progress, dry_run=dry_run, force=force_reset, keep_going=keep_going)
+ sm.update(
+ recursive=False,
+ init=init,
+ to_latest_revision=to_latest_revision,
+ progress=progress,
+ dry_run=dry_run,
+ force=force_reset,
+ keep_going=keep_going,
+ )
# update recursively depth first - question is which inconsistent
# state will be better in case it fails somewhere. Defective branch
@@ -345,18 +424,27 @@ class RootModule(Submodule):
if recursive:
# the module would exist by now if we are not in dry_run mode
if sm.module_exists():
- type(self)(sm.module()).update(recursive=True, force_remove=force_remove,
- init=init, to_latest_revision=to_latest_revision,
- progress=progress, dry_run=dry_run, force_reset=force_reset,
- keep_going=keep_going)
+ type(self)(sm.module()).update(
+ recursive=True,
+ force_remove=force_remove,
+ init=init,
+ to_latest_revision=to_latest_revision,
+ progress=progress,
+ dry_run=dry_run,
+ force_reset=force_reset,
+ keep_going=keep_going,
+ )
# END handle dry_run
# END handle recursive
# END for each submodule to update
return self
- def module(self) -> 'Repo':
+ def module(self) -> "Repo":
""":return: the actual repository containing the submodules"""
return self.repo
- #} END interface
-#} END classes
+
+ # } END interface
+
+
+# } END classes
diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py
index cc1cd60a..456ae34b 100644
--- a/git/objects/submodule/util.py
+++ b/git/objects/submodule/util.py
@@ -20,10 +20,15 @@ if TYPE_CHECKING:
from git.refs import RemoteReference
-__all__ = ('sm_section', 'sm_name', 'mkhead', 'find_first_remote_branch',
- 'SubmoduleConfigParser')
+__all__ = (
+ "sm_section",
+ "sm_name",
+ "mkhead",
+ "find_first_remote_branch",
+ "SubmoduleConfigParser",
+)
-#{ Utilities
+# { Utilities
def sm_section(name: str) -> str:
@@ -37,12 +42,14 @@ def sm_name(section: str) -> str:
return section[11:-1]
-def mkhead(repo: 'Repo', path: PathLike) -> 'Head':
+def mkhead(repo: "Repo", path: PathLike) -> "Head":
""":return: New branch/head instance"""
return git.Head(repo, git.Head.to_full_path(path))
-def find_first_remote_branch(remotes: Sequence['Remote'], branch_name: str) -> 'RemoteReference':
+def find_first_remote_branch(
+ remotes: Sequence["Remote"], branch_name: str
+) -> "RemoteReference":
"""Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError"""
for remote in remotes:
try:
@@ -51,12 +58,16 @@ def find_first_remote_branch(remotes: Sequence['Remote'], branch_name: str) -> '
continue
# END exception handling
# END for remote
- raise InvalidGitRepositoryError("Didn't find remote branch '%r' in any of the given remotes" % branch_name)
+ raise InvalidGitRepositoryError(
+ "Didn't find remote branch '%r' in any of the given remotes" % branch_name
+ )
-#} END utilities
+# } END utilities
+
+
+# { Classes
-#{ Classes
class SubmoduleConfigParser(GitConfigParser):
@@ -70,13 +81,13 @@ class SubmoduleConfigParser(GitConfigParser):
"""
def __init__(self, *args: Any, **kwargs: Any) -> None:
- self._smref: Union['ReferenceType[Submodule]', None] = None
+ self._smref: Union["ReferenceType[Submodule]", None] = None
self._index = None
self._auto_write = True
super(SubmoduleConfigParser, self).__init__(*args, **kwargs)
- #{ Interface
- def set_submodule(self, submodule: 'Submodule') -> None:
+ # { Interface
+ def set_submodule(self, submodule: "Submodule") -> None:
"""Set this instance's submodule. It must be called before
the first write operation begins"""
self._smref = weakref.ref(submodule)
@@ -97,14 +108,15 @@ class SubmoduleConfigParser(GitConfigParser):
sm._clear_cache()
# END handle weakref
- #} END interface
+ # } END interface
- #{ Overridden Methods
+ # { Overridden Methods
def write(self) -> None: # type: ignore[override]
rval: None = super(SubmoduleConfigParser, self).write()
self.flush_to_index()
return rval
+
# END overridden methods
-#} END classes
+# } END classes
diff --git a/git/objects/tag.py b/git/objects/tag.py
index 7048eb40..3956a89e 100644
--- a/git/objects/tag.py
+++ b/git/objects/tag.py
@@ -20,23 +20,34 @@ if TYPE_CHECKING:
from .blob import Blob
from .tree import Tree
-__all__ = ("TagObject", )
+__all__ = ("TagObject",)
class TagObject(base.Object):
"""Non-Lightweight tag carrying additional information about an object we are pointing to."""
- type: Literal['tag'] = "tag"
- __slots__ = ("object", "tag", "tagger", "tagged_date", "tagger_tz_offset", "message")
- def __init__(self, repo: 'Repo', binsha: bytes,
- object: Union[None, base.Object] = None,
- tag: Union[None, str] = None,
- tagger: Union[None, 'Actor'] = None,
- tagged_date: Union[int, None] = None,
- tagger_tz_offset: Union[int, None] = None,
- message: Union[str, None] = None
- ) -> None: # @ReservedAssignment
+ type: Literal["tag"] = "tag"
+ __slots__ = (
+ "object",
+ "tag",
+ "tagger",
+ "tagged_date",
+ "tagger_tz_offset",
+ "message",
+ )
+
+ def __init__(
+ self,
+ repo: "Repo",
+ binsha: bytes,
+ object: Union[None, base.Object] = None,
+ tag: Union[None, str] = None,
+ tagger: Union[None, "Actor"] = None,
+ tagged_date: Union[int, None] = None,
+ tagger_tz_offset: Union[int, None] = None,
+ message: Union[str, None] = None,
+ ) -> None: # @ReservedAssignment
"""Initialize a tag object with additional data
:param repo: repository this object is located in
@@ -51,7 +62,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: Union['Commit', 'Blob', 'Tree', 'TagObject'] = object
+ self.object: Union["Commit", "Blob", "Tree", "TagObject"] = object
if tag is not None:
self.tag = tag
if tagger is not None:
@@ -67,19 +78,22 @@ class TagObject(base.Object):
"""Cache all our attributes at once"""
if attr in TagObject.__slots__:
ostream = self.repo.odb.stream(self.binsha)
- lines: List[str] = ostream.read().decode(defenc, 'replace').splitlines()
+ lines: List[str] = ostream.read().decode(defenc, "replace").splitlines()
_obj, hexsha = lines[0].split(" ")
_type_token, type_name = lines[1].split(" ")
- object_type = get_object_type_by_name(type_name.encode('ascii'))
- self.object = \
- object_type(self.repo, hex_to_bin(hexsha))
+ object_type = get_object_type_by_name(type_name.encode("ascii"))
+ self.object = object_type(self.repo, hex_to_bin(hexsha))
self.tag = lines[2][4:] # tag <tag name>
if len(lines) > 3:
tagger_info = lines[3] # tagger <actor> <date>
- self.tagger, self.tagged_date, self.tagger_tz_offset = parse_actor_and_date(tagger_info)
+ (
+ self.tagger,
+ self.tagged_date,
+ self.tagger_tz_offset,
+ ) = parse_actor_and_date(tagger_info)
# line 4 empty - it could mark the beginning of the next header
# in case there really is no message, it would not exist. Otherwise
@@ -87,7 +101,7 @@ class TagObject(base.Object):
if len(lines) > 5:
self.message = "\n".join(lines[5:])
else:
- self.message = ''
+ self.message = ""
# END check our attributes
else:
super(TagObject, self)._set_cache_(attr)
diff --git a/git/objects/tree.py b/git/objects/tree.py
index 22531895..e1fcced7 100644
--- a/git/objects/tree.py
+++ b/git/objects/tree.py
@@ -13,16 +13,24 @@ from .base import IndexObject, IndexObjUnion
from .blob import Blob
from .submodule.base import Submodule
-from .fun import (
- tree_entries_from_data,
- tree_to_stream
-)
+from .fun import tree_entries_from_data, tree_to_stream
# typing -------------------------------------------------
-from typing import (Any, Callable, Dict, Iterable, Iterator, List,
- Tuple, Type, Union, cast, TYPE_CHECKING)
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterable,
+ Iterator,
+ List,
+ Tuple,
+ Type,
+ Union,
+ cast,
+ TYPE_CHECKING,
+)
from git.types import PathLike, Literal
@@ -32,14 +40,15 @@ if TYPE_CHECKING:
TreeCacheTup = Tuple[bytes, int, str]
-TraversedTreeTup = Union[Tuple[Union['Tree', None], IndexObjUnion,
- Tuple['Submodule', 'Submodule']]]
+TraversedTreeTup = Union[
+ Tuple[Union["Tree", None], IndexObjUnion, Tuple["Submodule", "Submodule"]]
+]
# def is_tree_cache(inp: Tuple[bytes, int, str]) -> TypeGuard[TreeCacheTup]:
# return isinstance(inp[0], bytes) and isinstance(inp[1], int) and isinstance([inp], str)
-#--------------------------------------------------------
+# --------------------------------------------------------
cmp: Callable[[str, str], int] = lambda a, b: (a > b) - (a < b)
@@ -60,8 +69,9 @@ def git_cmp(t1: TreeCacheTup, t2: TreeCacheTup) -> int:
return len_a - len_b
-def merge_sort(a: List[TreeCacheTup],
- cmp: Callable[[TreeCacheTup, TreeCacheTup], int]) -> None:
+def merge_sort(
+ a: List[TreeCacheTup], cmp: Callable[[TreeCacheTup, TreeCacheTup], int]
+) -> None:
if len(a) < 2:
return None
@@ -102,7 +112,8 @@ class TreeModifier(object):
Once all adjustments are complete, the _cache, which really is a reference to
the cache of a tree, will be sorted. Assuring it will be in a serializable state"""
- __slots__ = '_cache'
+
+ __slots__ = "_cache"
def __init__(self, cache: List[TreeCacheTup]) -> None:
self._cache = cache
@@ -116,18 +127,21 @@ class TreeModifier(object):
# END for each item in cache
return -1
- #{ Interface
- def set_done(self) -> 'TreeModifier':
+ # { Interface
+ def set_done(self) -> "TreeModifier":
"""Call this method once you are done modifying the tree information.
It may be called several times, but be aware that each call will cause
a sort operation
:return self:"""
merge_sort(self._cache, git_cmp)
return self
- #} END interface
- #{ Mutators
- def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> 'TreeModifier':
+ # } END interface
+
+ # { Mutators
+ def add(
+ self, sha: bytes, mode: int, name: str, force: bool = False
+ ) -> "TreeModifier":
"""Add the given item to the tree. If an item with the given name already
exists, nothing will be done, but a ValueError will be raised if the
sha and mode of the existing item do not match the one you add, unless
@@ -138,7 +152,7 @@ class TreeModifier(object):
:param force: If True, an item with your name and information will overwrite
any existing item with the same name, no matter which information it has
:return: self"""
- if '/' in name:
+ if "/" in name:
raise ValueError("Name must not contain '/' characters")
if (mode >> 12) not in Tree._map_id_to_type:
raise ValueError("Invalid object type according to mode %o" % mode)
@@ -168,7 +182,11 @@ class TreeModifier(object):
puts the caller into responsibility to assure the input is correct.
For more information on the parameters, see ``add``
:param binsha: 20 byte binary sha"""
- assert isinstance(binsha, bytes) and isinstance(mode, int) and isinstance(name, str)
+ assert (
+ isinstance(binsha, bytes)
+ and isinstance(mode, int)
+ and isinstance(name, str)
+ )
tree_cache = (binsha, mode, name)
self._cache.append(tree_cache)
@@ -177,9 +195,9 @@ class TreeModifier(object):
"""Deletes an item with the given name if it exists"""
index = self._index_by_name(name)
if index > -1:
- del(self._cache[index])
+ del self._cache[index]
- #} END mutators
+ # } END mutators
class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
@@ -195,11 +213,11 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
blob = tree[0]
"""
- type: Literal['tree'] = "tree"
+ type: Literal["tree"] = "tree"
__slots__ = "_cache"
# actual integer ids for comparison
- commit_id = 0o16 # equals stat.S_IFDIR | stat.S_IFLNK - a directory link
+ commit_id = 0o16 # equals stat.S_IFDIR | stat.S_IFLNK - a directory link
blob_id = 0o10
symlink_id = 0o12
tree_id = 0o04
@@ -211,12 +229,20 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
# tree id added once Tree is defined
}
- def __init__(self, repo: 'Repo', binsha: bytes, mode: int = tree_id << 12, path: Union[PathLike, None] = None):
+ def __init__(
+ self,
+ repo: "Repo",
+ binsha: bytes,
+ mode: int = tree_id << 12,
+ path: Union[PathLike, None] = None,
+ ):
super(Tree, self).__init__(repo, binsha, mode, path)
- @ classmethod
- def _get_intermediate_items(cls, index_object: IndexObjUnion,
- ) -> Union[Tuple['Tree', ...], Tuple[()]]:
+ @classmethod
+ def _get_intermediate_items(
+ cls,
+ index_object: IndexObjUnion,
+ ) -> Union[Tuple["Tree", ...], Tuple[()]]:
if index_object.type == "tree":
return tuple(index_object._iter_convert_to_object(index_object._cache))
return ()
@@ -230,8 +256,9 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
super(Tree, self)._set_cache_(attr)
# END handle attribute
- def _iter_convert_to_object(self, iterable: Iterable[TreeCacheTup]
- ) -> Iterator[IndexObjUnion]:
+ def _iter_convert_to_object(
+ self, iterable: Iterable[TreeCacheTup]
+ ) -> Iterator[IndexObjUnion]:
"""Iterable yields tuples of (binsha, mode, name), which will be converted
to the respective object representation"""
for binsha, mode, name in iterable:
@@ -239,7 +266,9 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
try:
yield self._map_id_to_type[mode >> 12](self.repo, binsha, mode, path)
except KeyError as e:
- raise TypeError("Unknown mode %o found in tree data for path '%s'" % (mode, path)) from e
+ raise TypeError(
+ "Unknown mode %o found in tree data for path '%s'" % (mode, path)
+ ) from e
# END for each item
def join(self, file: str) -> IndexObjUnion:
@@ -248,13 +277,13 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
:raise KeyError: if given file or tree does not exist in tree"""
msg = "Blob or Tree named %r not found"
- if '/' in file:
+ if "/" in file:
tree = self
item = self
- tokens = file.split('/')
+ tokens = file.split("/")
for i, token in enumerate(tokens):
item = tree[token]
- if item.type == 'tree':
+ if item.type == "tree":
tree = item
else:
# safety assertion - blobs are at the end of the path
@@ -268,9 +297,10 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
return item
else:
for info in self._cache:
- if info[2] == file: # [2] == name
- return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1],
- join_path(self.path, info[2]))
+ if info[2] == file: # [2] == name
+ return self._map_id_to_type[info[1] >> 12](
+ self.repo, info[0], info[1], join_path(self.path, info[2])
+ )
# END for each obj
raise KeyError(msg % file)
# END handle long paths
@@ -279,17 +309,17 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
"""For PY3 only"""
return self.join(file)
- @ property
- def trees(self) -> List['Tree']:
+ @property
+ def trees(self) -> List["Tree"]:
""":return: list(Tree, ...) list of trees directly below this tree"""
return [i for i in self if i.type == "tree"]
- @ property
+ @property
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"]
- @ property
+ @property
def cache(self) -> TreeModifier:
"""
:return: An object allowing to modify the internal cache. This can be used
@@ -298,16 +328,20 @@ 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[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,
- branch_first: bool = True,
- visit_once: bool = False,
- ignore_self: int = 1,
- as_edge: bool = False
- ) -> Union[Iterator[IndexObjUnion],
- Iterator[TraversedTreeTup]]:
+ 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,
+ branch_first: bool = True,
+ visit_once: bool = False,
+ ignore_self: int = 1,
+ as_edge: bool = False,
+ ) -> Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]]:
"""For documentation, see util.Traversable._traverse()
Trees are set to visit_once = False to gain more performance in the traversal"""
@@ -321,9 +355,17 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
# ret_tup = itertools.tee(ret, 2)
# 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))
+ return cast(
+ Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]],
+ 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 +373,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
@@ -347,7 +389,9 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
def __getitem__(self, item: Union[str, int, slice]) -> IndexObjUnion:
if isinstance(item, int):
info = self._cache[item]
- return self._map_id_to_type[info[1] >> 12](self.repo, info[0], info[1], join_path(self.path, info[2]))
+ return self._map_id_to_type[info[1] >> 12](
+ self.repo, info[0], info[1], join_path(self.path, info[2])
+ )
if isinstance(item, str):
# compatibility
@@ -378,7 +422,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
def __reversed__(self) -> Iterator[IndexObjUnion]:
return reversed(self._iter_convert_to_object(self._cache)) # type: ignore
- def _serialize(self, stream: 'BytesIO') -> 'Tree':
+ def _serialize(self, stream: "BytesIO") -> "Tree":
"""Serialize this tree into the stream. Please note that we will assume
our tree data to be in a sorted state. If this is not the case, serialization
will not generate a correct tree representation as these are assumed to be sorted
@@ -386,7 +430,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable):
tree_to_stream(self._cache, stream.write)
return self
- def _deserialize(self, stream: 'BytesIO') -> 'Tree':
+ def _deserialize(self, stream: "BytesIO") -> "Tree":
self._cache = tree_entries_from_data(stream.read())
return self
diff --git a/git/objects/util.py b/git/objects/util.py
index 800eccdf..4ba59c8a 100644
--- a/git/objects/util.py
+++ b/git/objects/util.py
@@ -7,11 +7,7 @@
from abc import ABC, abstractmethod
import warnings
-from git.util import (
- IterableList,
- IterableObj,
- Actor
-)
+from git.util import IterableList, IterableObj, Actor
import re
from collections import deque
@@ -22,10 +18,24 @@ import calendar
from datetime import datetime, timedelta, tzinfo
# typing ------------------------------------------------------------
-from typing import (Any, Callable, Deque, Iterator, Generic, NamedTuple, overload, Sequence, # NOQA: F401
- TYPE_CHECKING, Tuple, Type, TypeVar, Union, cast)
+from typing import (
+ Any,
+ Callable,
+ Deque,
+ Iterator,
+ Generic,
+ NamedTuple,
+ overload,
+ Sequence, # NOQA: F401
+ TYPE_CHECKING,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ cast,
+)
-from git.types import Has_id_attribute, Literal, _T # NOQA: F401
+from git.types import Has_id_attribute, Literal, _T # NOQA: F401
if TYPE_CHECKING:
from io import BytesIO, StringIO
@@ -46,24 +56,38 @@ else:
class TraverseNT(NamedTuple):
depth: int
- item: Union['Traversable', 'Blob']
- src: Union['Traversable', None]
+ item: Union["Traversable", "Blob"]
+ src: Union["Traversable", None]
-T_TIobj = TypeVar('T_TIobj', bound='TraversableIterableObj') # for TraversableIterableObj.traverse()
+T_TIobj = TypeVar(
+ "T_TIobj", bound="TraversableIterableObj"
+) # for TraversableIterableObj.traverse()
-TraversedTup = Union[Tuple[Union['Traversable', None], 'Traversable'], # for commit, submodule
- 'TraversedTreeTup'] # for tree.traverse()
+TraversedTup = Union[
+ Tuple[Union["Traversable", None], "Traversable"], # for commit, submodule
+ "TraversedTreeTup",
+] # for tree.traverse()
# --------------------------------------------------------------------
-__all__ = ('get_object_type_by_name', 'parse_date', 'parse_actor_and_date',
- 'ProcessStreamAdapter', 'Traversable', 'altz_to_utctz_str', 'utctz_to_altz',
- 'verify_utctz', 'Actor', 'tzoffset', 'utc')
+__all__ = (
+ "get_object_type_by_name",
+ "parse_date",
+ "parse_actor_and_date",
+ "ProcessStreamAdapter",
+ "Traversable",
+ "altz_to_utctz_str",
+ "utctz_to_altz",
+ "verify_utctz",
+ "Actor",
+ "tzoffset",
+ "utc",
+)
ZERO = timedelta(0)
-#{ Functions
+# { Functions
def mode_str_to_int(modestr: Union[bytes, str]) -> int:
@@ -82,8 +106,9 @@ def mode_str_to_int(modestr: Union[bytes, str]) -> int:
return mode
-def get_object_type_by_name(object_type_name: bytes
- ) -> Union[Type['Commit'], Type['TagObject'], Type['Tree'], Type['Blob']]:
+def get_object_type_by_name(
+ object_type_name: bytes,
+) -> Union[Type["Commit"], Type["TagObject"], Type["Tree"], Type["Blob"]]:
"""
:return: type suitable to handle the given object type name.
Use the type to create new instances.
@@ -93,18 +118,24 @@ def get_object_type_by_name(object_type_name: bytes
:raise ValueError: In case object_type_name is unknown"""
if object_type_name == b"commit":
from . import commit
+
return commit.Commit
elif object_type_name == b"tag":
from . import tag
+
return tag.TagObject
elif object_type_name == b"blob":
from . import blob
+
return blob.Blob
elif object_type_name == b"tree":
from . import tree
+
return tree.Tree
else:
- raise ValueError("Cannot handle unknown object type: %s" % object_type_name.decode())
+ raise ValueError(
+ "Cannot handle unknown object type: %s" % object_type_name.decode()
+ )
def utctz_to_altz(utctz: str) -> int:
@@ -121,7 +152,7 @@ def altz_to_utctz_str(altz: float) -> str:
utci = -1 * int((float(altz) / 3600) * 100)
utcs = str(abs(utci))
utcs = "0" * (4 - len(utcs)) + utcs
- prefix = (utci < 0 and '-') or '+'
+ prefix = (utci < 0 and "-") or "+"
return prefix + utcs
@@ -133,22 +164,23 @@ def verify_utctz(offset: str) -> str:
raise fmt_exc
if offset[0] not in "+-":
raise fmt_exc
- if offset[1] not in digits or\
- offset[2] not in digits or\
- offset[3] not in digits or\
- offset[4] not in digits:
+ if (
+ offset[1] not in digits
+ or offset[2] not in digits
+ or offset[3] not in digits
+ or offset[4] not in digits
+ ):
raise fmt_exc
# END for each char
return offset
class tzoffset(tzinfo):
-
def __init__(self, secs_west_of_utc: float, name: Union[None, str] = None) -> None:
self._offset = timedelta(seconds=-secs_west_of_utc)
- self._name = name or 'fixed'
+ self._name = name or "fixed"
- def __reduce__(self) -> Tuple[Type['tzoffset'], Tuple[float, str]]:
+ def __reduce__(self) -> Tuple[Type["tzoffset"], Tuple[float, str]]:
return tzoffset, (-self._offset.total_seconds(), self._name)
def utcoffset(self, dt: Union[datetime, None]) -> timedelta:
@@ -161,7 +193,7 @@ class tzoffset(tzinfo):
return ZERO
-utc = tzoffset(0, 'UTC')
+utc = tzoffset(0, "UTC")
def from_timestamp(timestamp: float, tz_offset: float) -> datetime:
@@ -190,23 +222,27 @@ def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
"""
if isinstance(string_date, datetime):
if string_date.tzinfo:
- utcoffset = cast(timedelta, string_date.utcoffset()) # typeguard, if tzinfoand is not None
+ utcoffset = cast(
+ timedelta, string_date.utcoffset()
+ ) # typeguard, if tzinfoand is not None
offset = -int(utcoffset.total_seconds())
return int(string_date.astimezone(utc).timestamp()), offset
else:
- raise ValueError(f"string_date datetime object without tzinfo, {string_date}")
+ raise ValueError(
+ f"string_date datetime object without tzinfo, {string_date}"
+ )
# git time
try:
- if string_date.count(' ') == 1 and string_date.rfind(':') == -1:
+ if string_date.count(" ") == 1 and string_date.rfind(":") == -1:
timestamp, offset_str = string_date.split()
- if timestamp.startswith('@'):
+ if timestamp.startswith("@"):
timestamp = timestamp[1:]
timestamp_int = int(timestamp)
return timestamp_int, utctz_to_altz(verify_utctz(offset_str))
else:
- offset_str = "+0000" # local time by default
- if string_date[-5] in '-+':
+ offset_str = "+0000" # local time by default
+ if string_date[-5] in "-+":
offset_str = verify_utctz(string_date[-5:])
string_date = string_date[:-6] # skip space as well
# END split timezone info
@@ -215,9 +251,9 @@ def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
# now figure out the date and time portion - split time
date_formats = []
splitter = -1
- if ',' in string_date:
+ if "," in string_date:
date_formats.append("%a, %d %b %Y")
- splitter = string_date.rfind(' ')
+ splitter = string_date.rfind(" ")
else:
# iso plus additional
date_formats.append("%Y-%m-%d")
@@ -225,16 +261,16 @@ def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
date_formats.append("%m/%d/%Y")
date_formats.append("%d.%m.%Y")
- splitter = string_date.rfind('T')
+ splitter = string_date.rfind("T")
if splitter == -1:
- splitter = string_date.rfind(' ')
+ splitter = string_date.rfind(" ")
# END handle 'T' and ' '
# END handle rfc or iso
assert splitter > -1
# split date and time
- time_part = string_date[splitter + 1:] # skip space
+ time_part = string_date[splitter + 1 :] # skip space
date_part = string_date[:splitter]
# parse time
@@ -243,9 +279,19 @@ def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
for fmt in date_formats:
try:
dtstruct = time.strptime(date_part, fmt)
- utctime = calendar.timegm((dtstruct.tm_year, dtstruct.tm_mon, dtstruct.tm_mday,
- tstruct.tm_hour, tstruct.tm_min, tstruct.tm_sec,
- dtstruct.tm_wday, dtstruct.tm_yday, tstruct.tm_isdst))
+ utctime = calendar.timegm(
+ (
+ dtstruct.tm_year,
+ dtstruct.tm_mon,
+ dtstruct.tm_mday,
+ tstruct.tm_hour,
+ tstruct.tm_min,
+ tstruct.tm_sec,
+ dtstruct.tm_wday,
+ dtstruct.tm_yday,
+ tstruct.tm_isdst,
+ )
+ )
return int(utctime), offset
except ValueError:
continue
@@ -256,13 +302,15 @@ def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]:
raise ValueError("no format matched")
# END handle format
except Exception as e:
- raise ValueError(f"Unsupported date format or type: {string_date}, type={type(string_date)}") from e
+ raise ValueError(
+ f"Unsupported date format or type: {string_date}, type={type(string_date)}"
+ ) from e
# END handle exceptions
# precompiled regex
-_re_actor_epoch = re.compile(r'^.+? (.*) (\d+) ([+-]\d+).*$')
-_re_only_actor = re.compile(r'^.+? (.*)$')
+_re_actor_epoch = re.compile(r"^.+? (.*) (\d+) ([+-]\d+).*$")
+_re_only_actor = re.compile(r"^.+? (.*)$")
def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]:
@@ -271,19 +319,21 @@ def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]:
author Tom Preston-Werner <tom@mojombo.com> 1191999972 -0700
:return: [Actor, int_seconds_since_epoch, int_timezone_offset]"""
- actor, epoch, offset = '', '0', '0'
+ actor, epoch, offset = "", "0", "0"
m = _re_actor_epoch.search(line)
if m:
actor, epoch, offset = m.groups()
else:
m = _re_only_actor.search(line)
- actor = m.group(1) if m else line or ''
+ actor = m.group(1) if m else line or ""
return (Actor._from_string(actor), int(epoch), utctz_to_altz(offset))
-#} END functions
+
+# } END functions
-#{ Classes
+# { Classes
+
class ProcessStreamAdapter(object):
@@ -292,9 +342,10 @@ class ProcessStreamAdapter(object):
Use this type to hide the underlying process to provide access only to a specified
stream. The process is usually wrapped into an AutoInterrupt class to kill
it if the instance goes out of scope."""
+
__slots__ = ("_proc", "_stream")
- def __init__(self, process: 'Popen', stream_name: str) -> None:
+ def __init__(self, process: "Popen", stream_name: str) -> None:
self._proc = process
self._stream: StringIO = getattr(process, stream_name) # guessed type
@@ -312,11 +363,12 @@ class Traversable(Protocol):
Defined subclasses = [Commit, Tree, SubModule]
"""
+
__slots__ = ()
@classmethod
@abstractmethod
- def _get_intermediate_items(cls, item: Any) -> Sequence['Traversable']:
+ def _get_intermediate_items(cls, item: Any) -> Sequence["Traversable"]:
"""
Returns:
Tuple of items connected to the given item.
@@ -331,15 +383,18 @@ class Traversable(Protocol):
@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)
+ 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: bool = False, *args: Any, **kwargs: Any
- ) -> IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']]:
+ def _list_traverse(
+ self, as_edge: bool = False, *args: Any, **kwargs: Any
+ ) -> IterableList[Union["Commit", "Submodule", "Tree", "Blob"]]:
"""
:return: IterableList with the results of the traversal as produced by
traverse()
@@ -352,11 +407,13 @@ class Traversable(Protocol):
if isinstance(self, Has_id_attribute):
id = self._id_attribute_
else:
- id = "" # shouldn't reach here, unless Traversable subclass created with no _id_attribute_
+ 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?
if not as_edge:
- out: IterableList[Union['Commit', 'Submodule', 'Tree', 'Blob']] = IterableList(id)
+ out: IterableList[
+ Union["Commit", "Submodule", "Tree", "Blob"]
+ ] = IterableList(id)
out.extend(self.traverse(as_edge=as_edge, *args, **kwargs))
return out
# overloads in subclasses (mypy doesn't allow typing self: subclass)
@@ -366,23 +423,32 @@ class Traversable(Protocol):
out_list: IterableList = IterableList(self.traverse(*args, **kwargs))
return out_list
- @ abstractmethod
+ @abstractmethod
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"
- "Builtin sublclasses are 'Submodule', 'Tree' and 'Commit",
- DeprecationWarning,
- stacklevel=2)
+ 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]]:
+ 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
@@ -426,24 +492,30 @@ class Traversable(Protocol):
visited = set()
stack: Deque[TraverseNT] = deque()
- stack.append(TraverseNT(0, self, None)) # self is always depth level 0
-
- def addToStack(stack: Deque[TraverseNT],
- src_item: 'Traversable',
- branch_first: bool,
- depth: int) -> None:
+ stack.append(TraverseNT(0, self, None)) # self is always depth level 0
+
+ def addToStack(
+ stack: Deque[TraverseNT],
+ src_item: "Traversable",
+ branch_first: bool,
+ depth: int,
+ ) -> None:
lst = self._get_intermediate_items(item)
- if not lst: # empty list
+ if not lst: # empty list
return None
if branch_first:
stack.extendleft(TraverseNT(depth, i, src_item) for i in lst)
else:
- reviter = (TraverseNT(depth, lst[i], src_item) for i in range(len(lst) - 1, -1, -1))
+ reviter = (
+ TraverseNT(depth, lst[i], src_item)
+ for i in range(len(lst) - 1, -1, -1)
+ )
stack.extend(reviter)
+
# END addToStack local method
while stack:
- d, item, src = stack.pop() # depth of item, item, item_source
+ d, item, src = stack.pop() # depth of item, item, item_source
if visit_once and item in visited:
continue
@@ -451,8 +523,10 @@ class Traversable(Protocol):
if visit_once:
visited.add(item)
- rval: Union[TraversedTup, 'Traversable', 'Blob']
- if as_edge: # if as_edge return (src, item) unless rrc is None (e.g. for first item)
+ rval: Union[TraversedTup, "Traversable", "Blob"]
+ if (
+ as_edge
+ ): # if as_edge return (src, item) unless rrc is None (e.g. for first item)
rval = (src, item)
else:
rval = item
@@ -473,14 +547,15 @@ class Traversable(Protocol):
# END for each item on work stack
-@ runtime_checkable
+@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':
+ 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
:param stream: a file-like object
@@ -488,7 +563,7 @@ class Serializable(Protocol):
raise NotImplementedError("To be implemented in subclass")
# @abstractmethod
- def _deserialize(self, stream: 'BytesIO') -> 'Serializable':
+ def _deserialize(self, stream: "BytesIO") -> "Serializable":
"""Deserialize all information regarding this object from the stream
:param stream: a file-like object
:return: self"""
@@ -500,54 +575,76 @@ class TraversableIterableObj(IterableObj, Traversable):
TIobj_tuple = Tuple[Union[T_TIobj, None], T_TIobj]
- def list_traverse(self: T_TIobj, *args: Any, **kwargs: Any) -> IterableList[T_TIobj]:
- 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
- ) -> Iterator[T_TIobj]:
+ @overload # type: ignore
+ def traverse(self: T_TIobj) -> Iterator[T_TIobj]:
...
- @ overload
- def traverse(self: T_TIobj,
- predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
- prune: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
- depth: int, branch_first: bool, visit_once: bool,
- ignore_self: Literal[True],
- as_edge: Literal[False],
- ) -> Iterator[T_TIobj]:
+ @overload
+ def traverse(
+ self: T_TIobj,
+ predicate: Callable[
+ [Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool
+ ],
+ prune: Callable[
+ [Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool
+ ],
+ depth: int,
+ branch_first: bool,
+ visit_once: bool,
+ ignore_self: Literal[True],
+ as_edge: Literal[False],
+ ) -> Iterator[T_TIobj]:
...
- @ overload
- def traverse(self: T_TIobj,
- predicate: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
- prune: Callable[[Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool],
- depth: int, branch_first: bool, visit_once: bool,
- ignore_self: Literal[False],
- as_edge: Literal[True],
- ) -> Iterator[Tuple[Union[T_TIobj, None], T_TIobj]]:
+ @overload
+ def traverse(
+ self: T_TIobj,
+ predicate: Callable[
+ [Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool
+ ],
+ prune: Callable[
+ [Union[T_TIobj, Tuple[Union[T_TIobj, None], T_TIobj]], int], bool
+ ],
+ depth: int,
+ branch_first: bool,
+ visit_once: bool,
+ ignore_self: Literal[False],
+ as_edge: Literal[True],
+ ) -> Iterator[Tuple[Union[T_TIobj, None], T_TIobj]]:
...
- @ overload
- def traverse(self: T_TIobj,
- predicate: Callable[[Union[T_TIobj, TIobj_tuple], int], bool],
- prune: Callable[[Union[T_TIobj, TIobj_tuple], int], bool],
- depth: int, branch_first: bool, visit_once: bool,
- ignore_self: Literal[True],
- as_edge: Literal[True],
- ) -> Iterator[Tuple[T_TIobj, T_TIobj]]:
+ @overload
+ def traverse(
+ self: T_TIobj,
+ predicate: Callable[[Union[T_TIobj, TIobj_tuple], int], bool],
+ prune: Callable[[Union[T_TIobj, TIobj_tuple], int], bool],
+ depth: int,
+ branch_first: bool,
+ visit_once: bool,
+ ignore_self: Literal[True],
+ as_edge: Literal[True],
+ ) -> Iterator[Tuple[T_TIobj, T_TIobj]]:
...
- def traverse(self: T_TIobj,
- predicate: Callable[[Union[T_TIobj, TIobj_tuple], int],
- bool] = lambda i, d: True,
- prune: Callable[[Union[T_TIobj, TIobj_tuple], 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[T_TIobj],
- Iterator[Tuple[T_TIobj, T_TIobj]],
- Iterator[TIobj_tuple]]:
+ def traverse(
+ self: T_TIobj,
+ predicate: Callable[
+ [Union[T_TIobj, TIobj_tuple], int], bool
+ ] = lambda i, d: True,
+ prune: Callable[[Union[T_TIobj, TIobj_tuple], 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[T_TIobj], Iterator[Tuple[T_TIobj, T_TIobj]], Iterator[TIobj_tuple]
+ ]:
"""For documentation, see util.Traversable._traverse()"""
"""
@@ -566,8 +663,9 @@ class TraversableIterableObj(IterableObj, Traversable):
assert is_commit_traversed(ret_tup), f"{[type(x) for x in list(ret_tup[0])]}"
return ret_tup[0]
"""
- return cast(Union[Iterator[T_TIobj],
- Iterator[Tuple[Union[None, T_TIobj], T_TIobj]]],
- super(TraversableIterableObj, self)._traverse(
- predicate, prune, depth, branch_first, visit_once, ignore_self, as_edge # type: ignore
- ))
+ return cast(
+ Union[Iterator[T_TIobj], Iterator[Tuple[Union[None, T_TIobj], T_TIobj]]],
+ super(TraversableIterableObj, self)._traverse(
+ predicate, prune, depth, branch_first, visit_once, ignore_self, as_edge # type: ignore
+ ),
+ )
diff --git a/git/refs/head.py b/git/refs/head.py
index d1d72c7b..befdc135 100644
--- a/git/refs/head.py
+++ b/git/refs/head.py
@@ -31,15 +31,18 @@ class HEAD(SymbolicReference):
"""Special case of a Symbolic Reference as it represents the repository's
HEAD reference."""
- _HEAD_NAME = 'HEAD'
- _ORIG_HEAD_NAME = 'ORIG_HEAD'
+
+ _HEAD_NAME = "HEAD"
+ _ORIG_HEAD_NAME = "ORIG_HEAD"
__slots__ = ()
- def __init__(self, repo: 'Repo', path: PathLike = _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))
+ raise ValueError(
+ "HEAD instance must point to %r, got %r" % (self._HEAD_NAME, path)
+ )
super(HEAD, self).__init__(repo, path)
- self.commit: 'Commit'
+ self.commit: "Commit"
def orig_head(self) -> SymbolicReference:
"""
@@ -47,9 +50,14 @@ class HEAD(SymbolicReference):
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: bool = True, working_tree: bool = False,
- paths: Union[PathLike, Sequence[PathLike], None] = None, **kwargs: Any) -> 'HEAD':
+ 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.
@@ -90,12 +98,14 @@ class HEAD(SymbolicReference):
if working_tree:
mode = "--hard"
if not index:
- raise ValueError("Cannot reset the working tree if the index is not reset as well")
+ raise ValueError(
+ "Cannot reset the working tree if the index is not reset as well"
+ )
# END working tree handling
try:
- self.repo.git.reset(mode, commit, '--', paths, **kwargs)
+ self.repo.git.reset(mode, commit, "--", paths, **kwargs)
except GitCommandError as e:
# git nowadays may use 1 as status to indicate there are still unstaged
# modifications after the reset
@@ -124,12 +134,19 @@ class Head(Reference):
>>> head.commit.hexsha
'1c09f116cbc2cb4100fb6935bb162daa4723f455'"""
+
_common_path_default = "refs/heads"
k_config_remote = "remote"
- k_config_remote_ref = "merge" # branch to merge from remote
+ k_config_remote_ref = "merge" # branch to merge from remote
@classmethod
- def delete(cls, repo: 'Repo', *heads: 'Union[Head, str]', force: bool = False, **kwargs: Any) -> None:
+ def delete(
+ cls,
+ repo: "Repo",
+ *heads: "Union[Head, str]",
+ force: bool = False,
+ **kwargs: Any
+ ) -> None:
"""Delete the given heads
:param force:
@@ -141,7 +158,9 @@ class Head(Reference):
flag = "-D"
repo.git.branch(flag, *heads)
- def set_tracking_branch(self, remote_reference: Union['RemoteReference', None]) -> 'Head':
+ def set_tracking_branch(
+ self, remote_reference: Union["RemoteReference", None]
+ ) -> "Head":
"""
Configure this branch to track the given remote reference. This will alter
this branch's configuration accordingly.
@@ -150,7 +169,10 @@ class Head(Reference):
any references
:return: self"""
from .remote import RemoteReference
- if remote_reference is not None and not isinstance(remote_reference, RemoteReference):
+
+ if remote_reference is not None and not isinstance(
+ remote_reference, RemoteReference
+ ):
raise ValueError("Incorrect parameter type: %r" % remote_reference)
# END handle type
@@ -162,26 +184,39 @@ class Head(Reference):
writer.remove_section()
else:
writer.set_value(self.k_config_remote, remote_reference.remote_name)
- writer.set_value(self.k_config_remote_ref, Head.to_full_path(remote_reference.remote_head))
+ writer.set_value(
+ self.k_config_remote_ref,
+ Head.to_full_path(remote_reference.remote_head),
+ )
return self
- def tracking_branch(self) -> Union['RemoteReference', None]:
+ def tracking_branch(self) -> Union["RemoteReference", None]:
"""
:return: The remote_reference we are tracking, or None if we are
not a tracking branch"""
from .remote import RemoteReference
+
reader = self.config_reader()
- if reader.has_option(self.k_config_remote) and reader.has_option(self.k_config_remote_ref):
- ref = Head(self.repo, Head.to_full_path(strip_quotes(reader.get_value(self.k_config_remote_ref))))
- remote_refpath = RemoteReference.to_full_path(join_path(reader.get_value(self.k_config_remote), ref.name))
+ if reader.has_option(self.k_config_remote) and reader.has_option(
+ self.k_config_remote_ref
+ ):
+ ref = Head(
+ self.repo,
+ Head.to_full_path(
+ strip_quotes(reader.get_value(self.k_config_remote_ref))
+ ),
+ )
+ remote_refpath = RemoteReference.to_full_path(
+ join_path(reader.get_value(self.k_config_remote), ref.name)
+ )
return RemoteReference(self.repo, remote_refpath)
# END handle have tracking branch
# we are not a tracking branch
return None
- def rename(self, new_path: PathLike, force: bool = False) -> 'Head':
+ def rename(self, new_path: PathLike, force: bool = False) -> "Head":
"""Rename self to a new path
:param new_path:
@@ -202,7 +237,7 @@ class Head(Reference):
self.path = "%s/%s" % (self._common_path_default, new_path)
return self
- def checkout(self, force: bool = False, **kwargs: Any) -> Union['HEAD', 'Head']:
+ def checkout(self, force: bool = False, **kwargs: Any) -> Union["HEAD", "Head"]:
"""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.
@@ -227,9 +262,9 @@ class Head(Reference):
By default it is only allowed to checkout heads - everything else
will leave the HEAD detached which is allowed and possible, but remains
a special state that some tools might not be able to handle."""
- kwargs['f'] = force
- if kwargs['f'] is False:
- kwargs.pop('f')
+ kwargs["f"] = force
+ if kwargs["f"] is False:
+ kwargs.pop("f")
self.repo.git.checkout(self, **kwargs)
if self.repo.head.is_detached:
@@ -237,7 +272,7 @@ class Head(Reference):
else:
return self.repo.active_branch
- #{ Configuration
+ # { Configuration
def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]:
if read_only:
parser = self.repo.config_reader()
@@ -259,4 +294,4 @@ class Head(Reference):
to options of this head"""
return self._config_parser(read_only=False)
- #} END configuration
+ # } END configuration
diff --git a/git/refs/log.py b/git/refs/log.py
index ddd78bc7..908f93d1 100644
--- a/git/refs/log.py
+++ b/git/refs/log.py
@@ -1,4 +1,3 @@
-
from mmap import mmap
import re
import time as _time
@@ -16,7 +15,7 @@ from git.util import (
assure_directory_exists,
to_native_path,
bin_to_hex,
- file_contents_ro_filepath
+ file_contents_ro_filepath,
)
import os.path as osp
@@ -41,7 +40,8 @@ __all__ = ["RefLog", "RefLogEntry"]
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}$')
+
+ _re_hexsha_only = re.compile("^[0-9A-Fa-f]{40}$")
__slots__ = ()
def __repr__(self) -> str:
@@ -52,13 +52,15 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
""":return: a string suitable to be placed in a reflog file"""
act = self.actor
time = self.time
- return "{} {} {} <{}> {!s} {}\t{}\n".format(self.oldhexsha,
- self.newhexsha,
- act.name,
- act.email,
- time[0],
- altz_to_utctz_str(time[1]),
- self.message)
+ return "{} {} {} <{}> {!s} {}\t{}\n".format(
+ self.oldhexsha,
+ self.newhexsha,
+ act.name,
+ act.email,
+ time[0],
+ altz_to_utctz_str(time[1]),
+ self.message,
+ )
@property
def oldhexsha(self) -> str:
@@ -80,7 +82,7 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
"""time as tuple:
* [0] = int(time)
- * [1] = int(timezone_offset) in time.altzone format """
+ * [1] = int(timezone_offset) in time.altzone format"""
return self[3]
@property
@@ -89,8 +91,15 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
return self[4]
@classmethod
- def new(cls, oldhexsha: str, newhexsha: str, actor: Actor, time: int, tz_offset: int, message: str
- ) -> 'RefLogEntry': # 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)
@@ -98,19 +107,21 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), message))
@classmethod
- def from_line(cls, line: bytes) -> 'RefLogEntry':
+ def from_line(cls, line: bytes) -> "RefLogEntry":
""":return: New RefLogEntry instance from the given revlog line.
:param line: line bytes without trailing newline
:raise ValueError: If line could not be parsed"""
line_str = line.decode(defenc)
- fields = line_str.split('\t', 1)
+ fields = line_str.split("\t", 1)
if len(fields) == 1:
info, msg = fields[0], None
elif len(fields) == 2:
info, msg = fields
else:
- raise ValueError("Line must have up to two TAB-separated fields."
- " Got %s" % repr(line_str))
+ raise ValueError(
+ "Line must have up to two TAB-separated fields."
+ " Got %s" % repr(line_str)
+ )
# END handle first split
oldhexsha = info[:40]
@@ -121,14 +132,13 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]):
# END if hexsha re doesn't match
# END for each hexsha
- email_end = info.find('>', 82)
+ email_end = info.find(">", 82)
if email_end == -1:
raise ValueError("Missing token: >")
# 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
+ actor = Actor._from_string(info[82 : email_end + 1])
+ time, tz_offset = parse_date(info[email_end + 2 :]) # skipcq: PYL-W0621
return RefLogEntry((oldhexsha, newhexsha, actor, (time, tz_offset), msg))
@@ -142,9 +152,9 @@ class RefLog(List[RefLogEntry], Serializable):
Reflog entries are ordered, the first added entry is first in the list, the last
entry, i.e. the last change of the head or reference, is last in the list."""
- __slots__ = ('_path', )
+ __slots__ = ("_path",)
- def __new__(cls, filepath: Union[PathLike, None] = None) -> 'RefLog':
+ def __new__(cls, filepath: Union[PathLike, None] = None) -> "RefLog":
inst = super(RefLog, cls).__new__(cls)
return inst
@@ -159,8 +169,7 @@ class RefLog(List[RefLogEntry], Serializable):
def _read_from_file(self) -> None:
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
@@ -175,7 +184,7 @@ class RefLog(List[RefLogEntry], Serializable):
# { Interface
@classmethod
- def from_file(cls, filepath: PathLike) -> 'RefLog':
+ def from_file(cls, filepath: PathLike) -> "RefLog":
"""
:return: a new RefLog instance containing all entries from the reflog
at the given filepath
@@ -184,7 +193,7 @@ class RefLog(List[RefLogEntry], Serializable):
return cls(filepath)
@classmethod
- def path(cls, ref: 'SymbolicReference') -> str:
+ 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
@@ -193,7 +202,7 @@ class RefLog(List[RefLogEntry], Serializable):
return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path))
@classmethod
- def iter_entries(cls, stream: Union[str, 'BytesIO', mmap]) -> Iterator[RefLogEntry]:
+ 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.
@@ -215,7 +224,7 @@ class RefLog(List[RefLogEntry], Serializable):
# END endless loop
@classmethod
- def entry_at(cls, filepath: PathLike, index: int) -> 'RefLogEntry':
+ def entry_at(cls, filepath: PathLike, index: int) -> "RefLogEntry":
"""
:return: RefLogEntry at the given index
@@ -230,7 +239,7 @@ class RefLog(List[RefLogEntry], Serializable):
all other lines. Nonetheless, the whole file has to be read if
the index is negative
"""
- with open(filepath, 'rb') as fp:
+ with open(filepath, "rb") as fp:
if index < 0:
return RefLogEntry.from_line(fp.readlines()[index].strip())
# read until index is reached
@@ -239,7 +248,8 @@ class RefLog(List[RefLogEntry], Serializable):
line = fp.readline()
if not line:
raise IndexError(
- f"Index file ended at line {i+1}, before given index was reached")
+ f"Index file ended at line {i+1}, before given index was reached"
+ )
# END abort on eof
# END handle runup
@@ -263,9 +273,15 @@ class RefLog(List[RefLogEntry], Serializable):
# END handle change
@classmethod
- def append_entry(cls, config_reader: Union[Actor, 'GitConfigParser', 'SectionConstraint', None],
- filepath: PathLike, oldbinsha: bytes, newbinsha: bytes, message: str,
- write: bool = True) -> 'RefLogEntry':
+ 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
@@ -286,21 +302,27 @@ class RefLog(List[RefLogEntry], Serializable):
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]
+ first_line = message.split("\n")[0]
if isinstance(config_reader, Actor):
- committer = config_reader # mypy thinks this is Actor | Gitconfigparser, but why?
+ 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
- ))
+ entry = RefLogEntry(
+ (
+ bin_to_hex(oldbinsha).decode("ascii"),
+ bin_to_hex(newbinsha).decode("ascii"),
+ committer,
+ (int(_time.time()), _time.altzone),
+ first_line,
+ )
+ )
if write:
lf = LockFile(filepath)
lf._obtain_lock_or_raise()
- fd = open(filepath, 'ab')
+ fd = open(filepath, "ab")
try:
fd.write(entry.format().encode(defenc))
finally:
@@ -309,12 +331,13 @@ class RefLog(List[RefLogEntry], Serializable):
# END handle write operation
return entry
- def write(self) -> 'RefLog':
+ 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")
+ "Instance was not initialized with a path, use to_file(...) instead"
+ )
# END assert path
self.to_file(self._path)
return self
@@ -322,7 +345,7 @@ class RefLog(List[RefLogEntry], Serializable):
# } END interface
# { Serializable Interface
- def _serialize(self, stream: 'BytesIO') -> 'RefLog':
+ def _serialize(self, stream: "BytesIO") -> "RefLog":
write = stream.write
# write all entries
@@ -331,7 +354,7 @@ class RefLog(List[RefLogEntry], Serializable):
# END for each entry
return self
- def _deserialize(self, stream: 'BytesIO') -> 'RefLog':
+ def _deserialize(self, stream: "BytesIO") -> "RefLog":
self.extend(self.iter_entries(stream))
- # } END serializable interface
+ # } END serializable interface
return self
diff --git a/git/refs/reference.py b/git/refs/reference.py
index 2a33fbff..9b946ec4 100644
--- a/git/refs/reference.py
+++ b/git/refs/reference.py
@@ -8,7 +8,7 @@ from .symbolic import SymbolicReference, T_References
# typing ------------------------------------------------------------------
from typing import Any, Callable, Iterator, Type, Union, TYPE_CHECKING # NOQA
-from git.types import Commit_ish, PathLike, _T # NOQA
+from git.types import Commit_ish, PathLike, _T # NOQA
if TYPE_CHECKING:
from git.repo import Repo
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
__all__ = ["Reference"]
-#{ Utilities
+# { Utilities
def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]:
@@ -26,24 +26,30 @@ def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]:
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)
+ raise ValueError(
+ "ref path does not point to a remote reference: %s" % self.path
+ )
return func(self, *args)
+
# END wrapper
wrapper.__name__ = func.__name__
return wrapper
-#}END utilities
+
+
+# }END utilities
class Reference(SymbolicReference, LazyMixin, IterableObj):
"""Represents a named reference to any object. Subclasses may apply restrictions though,
i.e. Heads can only point to commits."""
+
__slots__ = ()
_points_to_commits_only = False
_resolve_ref_on_create = True
_common_path_default = "refs"
- def __init__(self, repo: 'Repo', path: PathLike, check_path: bool = True) -> None:
+ def __init__(self, repo: "Repo", path: PathLike, check_path: bool = True) -> None:
"""Initialize this instance
:param repo: Our parent repository
@@ -52,19 +58,24 @@ 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 str(path).startswith(self._common_path_default + '/'):
- raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {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) -> str:
return self.name
- #{ Interface
+ # { Interface
# @ReservedAssignment
- def set_object(self, object: Union[Commit_ish, 'SymbolicReference', str], logmsg: Union[str, None] = None
- ) -> 'Reference':
+ def set_object(
+ self,
+ object: Union[Commit_ish, "SymbolicReference", str],
+ logmsg: Union[str, None] = None,
+ ) -> "Reference":
"""Special version which checks if the head-log needs an update as well
:return: self"""
oldbinsha = None
@@ -102,21 +113,26 @@ class Reference(SymbolicReference, LazyMixin, IterableObj):
""":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
- tokens = self.path.split('/')
+ tokens = self.path.split("/")
if len(tokens) < 3:
- return self.path # could be refs/HEAD
- return '/'.join(tokens[2:])
+ return self.path # could be refs/HEAD
+ return "/".join(tokens[2:])
@classmethod
- def iter_items(cls: Type[T_References], repo: 'Repo', common_path: Union[PathLike, None] = None,
- *args: Any, **kwargs: Any) -> Iterator[T_References]:
+ 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)
- #}END interface
+ # }END interface
- #{ Remote Interface
+ # { Remote Interface
@property # type: ignore ## mypy cannot deal with properties with an extra decorator (2021-04-21)
@require_remote_ref_path
@@ -125,7 +141,7 @@ class Reference(SymbolicReference, LazyMixin, IterableObj):
:return:
Name of the remote we are a reference of, such as 'origin' for a reference
named 'origin/master'"""
- tokens = self.path.split('/')
+ tokens = self.path.split("/")
# /refs/remotes/<remote name>/<branch_name>
return tokens[2]
@@ -135,7 +151,7 @@ class Reference(SymbolicReference, LazyMixin, IterableObj):
""":return: Name of the remote head itself, i.e. master.
:note: The returned name is usually not qualified enough to uniquely identify
a branch"""
- tokens = self.path.split('/')
- return '/'.join(tokens[3:])
+ tokens = self.path.split("/")
+ return "/".join(tokens[3:])
- #} END remote interface
+ # } END remote interface
diff --git a/git/refs/remote.py b/git/refs/remote.py
index 1b416bd0..8ac6bcd2 100644
--- a/git/refs/remote.py
+++ b/git/refs/remote.py
@@ -23,12 +23,18 @@ if TYPE_CHECKING:
class RemoteReference(Head):
"""Represents a reference pointing to a remote head."""
+
_common_path_default = Head._remote_common_path_default
@classmethod
- def iter_items(cls, repo: 'Repo', common_path: Union[PathLike, None] = None,
- remote: Union['Remote', None] = None, *args: Any, **kwargs: Any
- ) -> Iterator['RemoteReference']:
+ def iter_items(
+ cls,
+ repo: "Repo",
+ common_path: Union[PathLike, None] = None,
+ remote: Union["Remote", None] = None,
+ *args: Any,
+ **kwargs: Any
+ ) -> Iterator["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:
@@ -41,9 +47,10 @@ class RemoteReference(Head):
# implementation does not. mypy doesn't have a way of representing
# tightening the types of arguments in subclasses and recommends Any or
# "type: ignore". (See https://github.com/python/typing/issues/241)
- @ classmethod
- def delete(cls, repo: 'Repo', *refs: 'RemoteReference', # type: ignore
- **kwargs: Any) -> None:
+ @classmethod
+ def delete(
+ cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any # type: ignore
+ ) -> None:
"""Delete the given remote references
:note:
@@ -64,7 +71,7 @@ class RemoteReference(Head):
pass
# END for each ref
- @ classmethod
+ @classmethod
def create(cls, *args: Any, **kwargs: Any) -> NoReturn:
"""Used to disable this method"""
raise TypeError("Cannot explicitly create remote references")
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index 8d869173..6d9ebb96 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -10,19 +10,26 @@ from git.util import (
to_native_path_linux,
assure_directory_exists,
hex_to_bin,
- LockedFD
-)
-from gitdb.exc import (
- BadObject,
- BadName
+ LockedFD,
)
+from gitdb.exc import BadObject, BadName
from .log import RefLog
# typing ------------------------------------------------------------------
-from typing import Any, Iterator, List, Tuple, Type, TypeVar, Union, TYPE_CHECKING, cast # NOQA
-from git.types import Commit_ish, PathLike # NOQA
+from typing import (
+ Any,
+ Iterator,
+ List,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+ TYPE_CHECKING,
+ cast,
+) # NOQA
+from git.types import Commit_ish, PathLike # NOQA
if TYPE_CHECKING:
from git.repo import Repo
@@ -32,7 +39,7 @@ if TYPE_CHECKING:
from git.objects.commit import Actor
-T_References = TypeVar('T_References', bound='SymbolicReference')
+T_References = TypeVar("T_References", bound="SymbolicReference")
# ------------------------------------------------------------------------------
@@ -40,10 +47,10 @@ T_References = TypeVar('T_References', bound='SymbolicReference')
__all__ = ["SymbolicReference"]
-def _git_dir(repo: 'Repo', path: Union[PathLike, None]) -> PathLike:
- """ Find the git dir that's appropriate for the path"""
+def _git_dir(repo: "Repo", path: Union[PathLike, None]) -> PathLike:
+ """Find the git dir that's appropriate for the path"""
name = f"{path}"
- if name in ['HEAD', 'ORIG_HEAD', 'FETCH_HEAD', 'index', 'logs']:
+ if name in ["HEAD", "ORIG_HEAD", "FETCH_HEAD", "index", "logs"]:
return repo.git_dir
return repo.common_dir
@@ -55,6 +62,7 @@ class SymbolicReference(object):
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
@@ -62,7 +70,7 @@ class SymbolicReference(object):
_remote_common_path_default = "refs/remotes"
_id_attribute_ = "name"
- def __init__(self, repo: 'Repo', path: PathLike, check_path: bool = False):
+ def __init__(self, repo: "Repo", path: PathLike, check_path: bool = False):
self.repo = repo
self.path = path
@@ -73,7 +81,7 @@ class SymbolicReference(object):
return '<git.%s "%s">' % (self.__class__.__name__, self.path)
def __eq__(self, other: object) -> bool:
- if hasattr(other, 'path'):
+ if hasattr(other, "path"):
other = cast(SymbolicReference, other)
return self.path == other.path
return False
@@ -97,20 +105,20 @@ class SymbolicReference(object):
return join_path_native(_git_dir(self.repo, self.path), self.path)
@classmethod
- def _get_packed_refs_path(cls, repo: 'Repo') -> str:
- return os.path.join(repo.common_dir, 'packed-refs')
+ def _get_packed_refs_path(cls, repo: "Repo") -> str:
+ return os.path.join(repo.common_dir, "packed-refs")
@classmethod
- def _iter_packed_refs(cls, repo: 'Repo') -> Iterator[Tuple[str, str]]:
+ def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]:
"""Returns an iterator yielding pairs of sha1/path pairs (as strings) 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:
+ 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('#'):
+ if line.startswith("#"):
# "# pack-refs with: peeled fully-peeled sorted"
# the git source code shows "peeled",
# "fully-peeled" and "sorted" as the keywords
@@ -119,18 +127,23 @@ class SymbolicReference(object):
# 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)
+ 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] == '^':
+ if line[0] == "^":
continue
- yield cast(Tuple[str, str], tuple(line.split(' ', 1)))
+ yield cast(Tuple[str, str], tuple(line.split(" ", 1)))
# END for each line
except OSError:
return None
@@ -141,7 +154,9 @@ class SymbolicReference(object):
# alright.
@classmethod
- def dereference_recursive(cls, repo: 'Repo', ref_path: Union[PathLike, None]) -> str:
+ def dereference_recursive(
+ cls, repo: "Repo", ref_path: Union[PathLike, None]
+ ) -> str:
"""
:return: hexsha stored in the reference at the given ref_path, recursively dereferencing all
intermediate references as required
@@ -154,20 +169,23 @@ class SymbolicReference(object):
# END recursive dereferencing
@classmethod
- def _get_ref_info_helper(cls, repo: 'Repo', ref_path: Union[PathLike, None]
- ) -> Union[Tuple[str, None], Tuple[None, str]]:
+ def _get_ref_info_helper(
+ cls, repo: "Repo", ref_path: Union[PathLike, None]
+ ) -> Union[Tuple[str, None], Tuple[None, str]]:
"""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: Union[None, List[str], Tuple[str, str]] = None
repodir = _git_dir(repo, ref_path)
try:
- with open(os.path.join(repodir, str(ref_path)), 'rt', encoding='UTF-8') as fp:
+ with open(
+ os.path.join(repodir, str(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)
+ 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
@@ -184,7 +202,7 @@ class SymbolicReference(object):
raise ValueError("Reference at %r does not exist" % ref_path)
# is it a reference ?
- if tokens[0] == 'ref:':
+ if tokens[0] == "ref:":
return (None, tokens[1])
# its a commit
@@ -194,7 +212,9 @@ class SymbolicReference(object):
raise ValueError("Failed to parse reference information from %r" % ref_path)
@classmethod
- def _get_ref_info(cls, repo: 'Repo', ref_path: Union[PathLike, None]) -> Union[Tuple[str, None], Tuple[None, str]]:
+ def _get_ref_info(
+ cls, repo: "Repo", ref_path: Union[PathLike, None]
+ ) -> Union[Tuple[str, None], Tuple[None, str]]:
"""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"""
@@ -207,25 +227,32 @@ class SymbolicReference(object):
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)))
+ return Object.new_from_sha(
+ self.repo, hex_to_bin(self.dereference_recursive(self.repo, self.path))
+ )
- def _get_commit(self) -> 'Commit':
+ def _get_commit(self) -> "Commit":
"""
: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':
+ 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)
+ 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: Union[str, None] = None
- ) -> 'SymbolicReference':
+ def set_commit(
+ self,
+ commit: Union[Commit, "SymbolicReference", str],
+ logmsg: Union[str, None] = None,
+ ) -> "SymbolicReference":
"""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
@@ -254,8 +281,11 @@ class SymbolicReference(object):
return self
- def set_object(self, object: Union[Commit_ish, 'SymbolicReference', str], logmsg: Union[str, None] = None
- ) -> 'SymbolicReference':
+ def set_object(
+ self,
+ object: Union[Commit_ish, "SymbolicReference", str],
+ logmsg: Union[str, None] = None,
+ ) -> "SymbolicReference":
"""Set the object we point to, possibly dereference our symbolic reference first.
If the reference does not exist, it will be created
@@ -282,20 +312,25 @@ class SymbolicReference(object):
# set the commit on our reference
return self._get_reference().set_object(object, logmsg)
- commit = property(_get_commit, set_commit, doc="Query or set commits directly") # type: ignore
+ commit = property(_get_commit, set_commit, doc="Query or set commits directly") # type: ignore
object = property(_get_object, set_object, doc="Return the object our ref currently refers to") # type: ignore
- def _get_reference(self) -> 'SymbolicReference':
+ def _get_reference(self) -> "SymbolicReference":
""":return: Reference Object we point to
:raise TypeError: If this symbolic reference is detached, hence it doesn't point
to a reference, but to a commit"""
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))
+ 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: Union[Commit_ish, 'SymbolicReference', str],
- logmsg: Union[str, None] = None) -> 'SymbolicReference':
+ def set_reference(
+ self,
+ ref: Union[Commit_ish, "SymbolicReference", str],
+ logmsg: Union[str, None] = None,
+ ) -> "SymbolicReference":
"""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 reference if it was a purely
@@ -322,7 +357,7 @@ class SymbolicReference(object):
write_value = ref.hexsha
elif isinstance(ref, str):
try:
- obj = self.repo.rev_parse(ref + "^{}") # optionally deref tags
+ 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
@@ -336,7 +371,7 @@ class SymbolicReference(object):
raise TypeError("Require commit, got %r" % obj)
# END verify type
- oldbinsha: bytes = b''
+ oldbinsha: bytes = b""
if logmsg is not None:
try:
oldbinsha = self.commit.binsha
@@ -352,7 +387,7 @@ class SymbolicReference(object):
fd = lfd.open(write=True, stream=True)
ok = True
try:
- fd.write(write_value.encode('utf-8') + b'\n')
+ fd.write(write_value.encode("utf-8") + b"\n")
lfd.commit()
ok = True
finally:
@@ -365,7 +400,7 @@ class SymbolicReference(object):
return self
# aliased reference
- reference: Union['Head', 'TagReference', 'RemoteReference', 'Reference']
+ reference: Union["Head", "TagReference", "RemoteReference", "Reference"]
reference = property(_get_reference, set_reference, doc="Returns the Reference we point to") # type: ignore
ref = reference
@@ -393,7 +428,7 @@ class SymbolicReference(object):
except TypeError:
return True
- def log(self) -> 'RefLog':
+ def log(self) -> "RefLog":
"""
:return: RefLog for this reference. Its last entry reflects the latest change
applied to this reference
@@ -402,8 +437,12 @@ class SymbolicReference(object):
instead of calling this method repeatedly. It should be considered read-only."""
return RefLog.from_file(RefLog.path(self))
- def log_append(self, oldbinsha: bytes, message: Union[str, None],
- newbinsha: Union[bytes, None] = None) -> 'RefLogEntry':
+ def log_append(
+ self,
+ oldbinsha: bytes,
+ message: Union[str, None],
+ newbinsha: Union[bytes, None] = None,
+ ) -> "RefLogEntry":
"""Append a logentry to the logfile of this ref
:param oldbinsha: binary sha this ref used to point to
@@ -415,7 +454,9 @@ class SymbolicReference(object):
# correct to allow overriding the committer on a per-commit level.
# See https://github.com/gitpython-developers/GitPython/pull/146
try:
- committer_or_reader: Union['Actor', 'GitConfigParser'] = self.commit.committer
+ committer_or_reader: Union[
+ "Actor", "GitConfigParser"
+ ] = self.commit.committer
except ValueError:
committer_or_reader = self.repo.config_reader()
# end handle newly cloned repositories
@@ -423,11 +464,13 @@ class SymbolicReference(object):
newbinsha = self.commit.binsha
if message is None:
- message = ''
+ message = ""
- return RefLog.append_entry(committer_or_reader, RefLog.path(self), oldbinsha, newbinsha, message)
+ return RefLog.append_entry(
+ committer_or_reader, RefLog.path(self), oldbinsha, newbinsha, message
+ )
- def log_entry(self, index: int) -> 'RefLogEntry':
+ def log_entry(self, index: int) -> "RefLogEntry":
""":return: RefLogEntry at the given index
:param index: python list compatible positive or negative index
@@ -437,7 +480,7 @@ class SymbolicReference(object):
return RefLog.entry_at(RefLog.path(self), index)
@classmethod
- def to_full_path(cls, path: Union[PathLike, 'SymbolicReference']) -> PathLike:
+ def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> 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``"""
@@ -447,11 +490,11 @@ class SymbolicReference(object):
if not cls._common_path_default:
return full_ref_path
if not str(path).startswith(cls._common_path_default + "/"):
- full_ref_path = '%s/%s' % (cls._common_path_default, path)
+ full_ref_path = "%s/%s" % (cls._common_path_default, path)
return full_ref_path
@classmethod
- def delete(cls, repo: 'Repo', path: PathLike) -> None:
+ def delete(cls, repo: "Repo", path: PathLike) -> None:
"""Delete the reference at the given path
:param repo:
@@ -469,20 +512,23 @@ class SymbolicReference(object):
# check packed refs
pack_file_path = cls._get_packed_refs_path(repo)
try:
- with open(pack_file_path, 'rb') as reader:
+ with open(pack_file_path, "rb") as reader:
new_lines = []
made_change = False
dropped_last_line = False
for line_bytes in reader:
line = line_bytes.decode(defenc)
- _, _, line_ref = line.partition(' ')
+ _, _, 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('^')):
+ 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
@@ -496,7 +542,7 @@ class SymbolicReference(object):
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:
+ with open(pack_file_path, "wb") as fd:
fd.writelines(line.encode(defenc) for line in new_lines)
except OSError:
@@ -509,9 +555,15 @@ class SymbolicReference(object):
# END remove reflog
@classmethod
- def _create(cls: Type[T_References], repo: 'Repo', path: PathLike, resolve: bool,
- reference: Union['SymbolicReference', str], force: bool,
- logmsg: Union[str, None] = None) -> T_References:
+ def _create(
+ cls: Type[T_References],
+ repo: "Repo",
+ path: PathLike,
+ resolve: bool,
+ reference: Union["SymbolicReference", str],
+ force: bool,
+ logmsg: Union[str, None] = None,
+ ) -> T_References:
"""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
@@ -532,11 +584,13 @@ class SymbolicReference(object):
target_data = str(target.path)
if not resolve:
target_data = "ref: " + target_data
- with open(abs_ref_path, 'rb') as fd:
+ 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))
+ 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)
@@ -544,9 +598,15 @@ class SymbolicReference(object):
return ref
@classmethod
- def create(cls: Type[T_References], repo: 'Repo', path: PathLike,
- reference: Union['SymbolicReference', str] = 'HEAD',
- logmsg: Union[str, None] = None, force: bool = False, **kwargs: Any) -> T_References:
+ def create(
+ cls: Type[T_References],
+ repo: "Repo",
+ path: PathLike,
+ reference: Union["SymbolicReference", str] = "HEAD",
+ logmsg: Union[str, None] = None,
+ force: bool = False,
+ **kwargs: Any,
+ ) -> T_References:
"""Create a new symbolic reference, hence a reference pointing , to another reference.
:param repo:
@@ -575,9 +635,11 @@ class SymbolicReference(object):
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)
+ return cls._create(
+ repo, path, cls._resolve_ref_on_create, reference, force, logmsg
+ )
- def rename(self, new_path: PathLike, force: bool = False) -> 'SymbolicReference':
+ def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference":
"""Rename self to a new path
:param new_path:
@@ -590,7 +652,7 @@ class SymbolicReference(object):
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 """
+ :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
@@ -600,9 +662,9 @@ class SymbolicReference(object):
if os.path.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:
+ with open(new_abs_path, "rb") as fd1:
f1 = fd1.read().strip()
- with open(cur_abs_path, 'rb') as fd2:
+ 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)
@@ -623,26 +685,31 @@ class SymbolicReference(object):
return self
@classmethod
- def _iter_items(cls: Type[T_References], repo: 'Repo', common_path: Union[PathLike, None] = None
- ) -> Iterator[T_References]:
+ 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']
+ 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']
+ dirs[0:] = ["refs"]
# END prune non-refs folders
for f in files:
- if f == 'packed-refs':
+ 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) + '/', ""))
+ 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
@@ -662,8 +729,13 @@ class SymbolicReference(object):
# END for each sorted relative refpath
@classmethod
- def iter_items(cls: Type[T_References], repo: 'Repo', common_path: Union[PathLike, None] = None,
- *args: Any, **kwargs: Any) -> Iterator[T_References]:
+ def iter_items(
+ cls: Type[T_References],
+ repo: "Repo",
+ common_path: Union[PathLike, None] = None,
+ *args: Any,
+ **kwargs: Any,
+ ) -> Iterator[T_References]:
"""Find all refs in the repository
:param repo: is the Repo
@@ -680,10 +752,16 @@ class SymbolicReference(object):
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)
+ 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: Type[T_References], repo: 'Repo', path: PathLike) -> T_References:
+ def from_path(
+ cls: Type[T_References], repo: "Repo", path: PathLike
+ ) -> T_References:
"""
:param path: full .git-directory-relative path name to the Reference to instantiate
:note: use to_full_path() if you only have a partial path of a known Reference Type
@@ -696,7 +774,15 @@ class SymbolicReference(object):
# 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):
+
+ for ref_type in (
+ HEAD,
+ Head,
+ RemoteReference,
+ TagReference,
+ Reference,
+ SymbolicReference,
+ ):
try:
instance: T_References
instance = ref_type(repo, path)
@@ -709,7 +795,9 @@ class SymbolicReference(object):
pass
# END exception handling
# END for each type to try
- raise ValueError("Could not find reference type suitable to handle path %r" % path)
+ raise ValueError(
+ "Could not find reference type suitable to handle path %r" % path
+ )
def is_remote(self) -> bool:
""":return: True if this symbolic reference points to a remote branch"""
diff --git a/git/refs/tag.py b/git/refs/tag.py
index 8cc79edd..96494148 100644
--- a/git/refs/tag.py
+++ b/git/refs/tag.py
@@ -36,22 +36,27 @@ class TagReference(Reference):
_common_path_default = Reference._common_path_default + "/" + _common_default
@property
- def commit(self) -> 'Commit': # type: ignore[override] # LazyMixin has unrelated commit method
+ def commit(self) -> "Commit": # type: ignore[override] # LazyMixin has unrelated commit method
""":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':
+ while obj.type != "commit":
if obj.type == "tag":
# it is a tag object which carries the commit as an object - we can point to anything
obj = obj.object
else:
- raise ValueError(("Cannot resolve commit as tag %s points to a %s object - " +
- "use the `.object` property instead to access it") % (self, obj.type))
+ raise ValueError(
+ (
+ "Cannot resolve commit as tag %s points to a %s object - "
+ + "use the `.object` property instead to access it"
+ )
+ % (self, obj.type)
+ )
return obj
@property
- def tag(self) -> Union['TagObject', None]:
+ def tag(self) -> Union["TagObject", None]:
"""
:return: Tag object this tag ref points to or None in case
we are a light weight tag"""
@@ -69,10 +74,15 @@ class TagReference(Reference):
return Reference._get_object(self)
@classmethod
- def create(cls: Type['TagReference'], repo: 'Repo', path: PathLike,
- reference: Union[str, 'SymbolicReference'] = 'HEAD',
- logmsg: Union[str, None] = None,
- force: bool = False, **kwargs: Any) -> 'TagReference':
+ def create(
+ cls: Type["TagReference"],
+ repo: "Repo",
+ path: PathLike,
+ reference: Union[str, "SymbolicReference"] = "HEAD",
+ logmsg: Union[str, None] = None,
+ force: bool = False,
+ **kwargs: Any
+ ) -> "TagReference":
"""Create a new tag reference.
:param path:
@@ -100,16 +110,16 @@ class TagReference(Reference):
Additional keyword arguments to be passed to git-tag
:return: A new TagReference"""
- if 'ref' in kwargs and kwargs['ref']:
- reference = kwargs['ref']
+ if "ref" in kwargs and kwargs["ref"]:
+ reference = kwargs["ref"]
if logmsg:
- kwargs['m'] = logmsg
- elif 'message' in kwargs and kwargs['message']:
- kwargs['m'] = kwargs['message']
+ kwargs["m"] = logmsg
+ elif "message" in kwargs and kwargs["message"]:
+ kwargs["m"] = kwargs["message"]
if force:
- kwargs['f'] = True
+ kwargs["f"] = True
args = (path, reference)
@@ -117,7 +127,7 @@ class TagReference(Reference):
return TagReference(repo, "%s/%s" % (cls._common_path_default, path))
@classmethod
- def delete(cls, repo: 'Repo', *tags: 'TagReference') -> None: # type: ignore[override]
+ def delete(cls, repo: "Repo", *tags: "TagReference") -> None: # type: ignore[override]
"""Delete the given existing tag or tags"""
repo.git.tag("-d", *tags)
diff --git a/git/remote.py b/git/remote.py
index 56f3c5b3..8cd79057 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -9,7 +9,7 @@ import logging
import re
from git.cmd import handle_process_output, Git
-from git.compat import (defenc, force_text)
+from git.compat import defenc, force_text
from git.exc import GitCommandError
from git.util import (
LazyMixin,
@@ -27,28 +27,36 @@ from git.config import (
SectionConstraint,
cp,
)
-from git.refs import (
- Head,
- Reference,
- RemoteReference,
- SymbolicReference,
- TagReference
-)
+from git.refs import Head, Reference, RemoteReference, SymbolicReference, TagReference
# typing-------------------------------------------------------
-from typing import (Any, Callable, Dict, Iterator, List, NoReturn, Optional, Sequence,
- TYPE_CHECKING, Type, Union, cast, overload)
+from typing import (
+ Any,
+ Callable,
+ Dict,
+ Iterator,
+ List,
+ NoReturn,
+ Optional,
+ Sequence,
+ TYPE_CHECKING,
+ Type,
+ Union,
+ cast,
+ overload,
+)
from git.types import PathLike, Literal, Commit_ish
if TYPE_CHECKING:
from git.repo.base import Repo
from git.objects.submodule.base import UpdateProgress
+
# from git.objects.commit import Commit
# from git.objects import Blob, Tree, TagObject
-flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't', '?']
+flagKeyLiteral = Literal[" ", "!", "+", "-", "*", "=", "t", "?"]
# def is_flagKeyLiteral(inp: str) -> TypeGuard[flagKeyLiteral]:
# return inp in [' ', '!', '+', '-', '=', '*', 't', '?']
@@ -57,18 +65,22 @@ flagKeyLiteral = Literal[' ', '!', '+', '-', '*', '=', 't', '?']
# -------------------------------------------------------------
-log = logging.getLogger('git.remote')
+log = logging.getLogger("git.remote")
log.addHandler(logging.NullHandler())
-__all__ = ('RemoteProgress', 'PushInfo', 'FetchInfo', 'Remote')
+__all__ = ("RemoteProgress", "PushInfo", "FetchInfo", "Remote")
-#{ Utilities
+# { Utilities
-def add_progress(kwargs: Any, git: Git,
- progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None]
- ) -> Any:
+def add_progress(
+ kwargs: Any,
+ git: Git,
+ progress: Union[
+ RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None
+ ],
+) -> Any:
"""Add the --progress flag to the given kwargs dict if supported by the
git command. If the actual progress in the given progress instance is not
given, we do not request any progress
@@ -76,31 +88,33 @@ def add_progress(kwargs: Any, git: Git,
if progress is not None:
v = git.version_info[:2]
if v >= (1, 7):
- kwargs['progress'] = True
+ kwargs["progress"] = True
# END handle --progress
# END handle progress
return kwargs
-#} END utilities
+
+# } 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:
...
-def to_progress_instance(progress: Union[Callable[..., Any], RemoteProgress, None]
- ) -> Union[RemoteProgress, CallableRemoteProgress]:
+def to_progress_instance(
+ progress: Union[Callable[..., Any], RemoteProgress, None]
+) -> Union[RemoteProgress, CallableRemoteProgress]:
"""Given the 'progress' return a suitable object derived from
RemoteProgress().
"""
@@ -130,25 +144,53 @@ class PushInfo(IterableObj, object):
info.old_commit # commit at which the remote_ref was standing before we pushed
# it to local_ref.commit. Will be None if an error was indicated
info.summary # summary line providing human readable english text about the push
- """
- __slots__ = ('local_ref', 'remote_ref_string', 'flags', '_old_commit_sha', '_remote', 'summary')
- _id_attribute_ = 'pushinfo'
-
- NEW_TAG, NEW_HEAD, NO_MATCH, REJECTED, REMOTE_REJECTED, REMOTE_FAILURE, DELETED, \
- FORCED_UPDATE, FAST_FORWARD, UP_TO_DATE, ERROR = [1 << x for x in range(11)]
-
- _flag_map = {'X': NO_MATCH,
- '-': DELETED,
- '*': 0,
- '+': FORCED_UPDATE,
- ' ': FAST_FORWARD,
- '=': UP_TO_DATE,
- '!': ERROR}
-
- def __init__(self, flags: int, local_ref: Union[SymbolicReference, None], remote_ref_string: str, remote: 'Remote',
- old_commit: Optional[str] = None, summary: str = '') -> None:
- """ Initialize a new instance
- local_ref: HEAD | Head | RemoteReference | TagReference | Reference | SymbolicReference | None """
+ """
+
+ __slots__ = (
+ "local_ref",
+ "remote_ref_string",
+ "flags",
+ "_old_commit_sha",
+ "_remote",
+ "summary",
+ )
+ _id_attribute_ = "pushinfo"
+
+ (
+ NEW_TAG,
+ NEW_HEAD,
+ NO_MATCH,
+ REJECTED,
+ REMOTE_REJECTED,
+ REMOTE_FAILURE,
+ DELETED,
+ FORCED_UPDATE,
+ FAST_FORWARD,
+ UP_TO_DATE,
+ ERROR,
+ ) = [1 << x for x in range(11)]
+
+ _flag_map = {
+ "X": NO_MATCH,
+ "-": DELETED,
+ "*": 0,
+ "+": FORCED_UPDATE,
+ " ": FAST_FORWARD,
+ "=": UP_TO_DATE,
+ "!": ERROR,
+ }
+
+ def __init__(
+ self,
+ flags: int,
+ local_ref: Union[SymbolicReference, None],
+ remote_ref_string: str,
+ remote: "Remote",
+ old_commit: Optional[str] = None,
+ summary: str = "",
+ ) -> None:
+ """Initialize a new instance
+ local_ref: HEAD | Head | RemoteReference | TagReference | Reference | SymbolicReference | None"""
self.flags = flags
self.local_ref = local_ref
self.remote_ref_string = remote_ref_string
@@ -156,11 +198,15 @@ class PushInfo(IterableObj, object):
self._old_commit_sha = old_commit
self.summary = summary
- @ property
+ @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
+ 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:
@@ -171,27 +217,33 @@ class PushInfo(IterableObj, object):
return TagReference(self._remote.repo, self.remote_ref_string)
elif self.remote_ref_string.startswith("refs/heads"):
remote_ref = Reference(self._remote.repo, self.remote_ref_string)
- return RemoteReference(self._remote.repo, "refs/remotes/%s/%s" % (str(self._remote), remote_ref.name))
+ return RemoteReference(
+ self._remote.repo,
+ "refs/remotes/%s/%s" % (str(self._remote), remote_ref.name),
+ )
else:
raise ValueError("Could not handle remote ref: %r" % self.remote_ref_string)
# END
- @ classmethod
- def _from_line(cls, remote: 'Remote', line: str) -> 'PushInfo':
+ @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"""
- control_character, from_to, summary = line.split('\t', 3)
+ refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes"""
+ control_character, from_to, summary = line.split("\t", 3)
flags = 0
# control character handling
try:
flags |= cls._flag_map[control_character]
except KeyError as e:
- raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line)) from e
+ raise ValueError(
+ "Control character %r unknown as parsed from line %r"
+ % (control_character, line)
+ ) from e
# END handle control character
# from_to handling
- from_ref_string, to_ref_string = from_to.split(':')
+ from_ref_string, to_ref_string = from_to.split(":")
if flags & cls.DELETED:
from_ref: Union[SymbolicReference, None] = None
else:
@@ -202,7 +254,7 @@ class PushInfo(IterableObj, object):
# commit handling, could be message or commit info
old_commit: Optional[str] = None
- if summary.startswith('['):
+ if summary.startswith("["):
if "[rejected]" in summary:
flags |= cls.REJECTED
elif "[remote rejected]" in summary:
@@ -222,25 +274,26 @@ class PushInfo(IterableObj, object):
split_token = "..."
if control_character == " ":
split_token = ".."
- old_sha, _new_sha = summary.split(' ')[0].split(split_token)
+ old_sha, _new_sha = summary.split(" ")[0].split(split_token)
# have to use constructor here as the sha usually is abbreviated
old_commit = old_sha
# END message handling
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']:
+ @classmethod
+ def iter_items(
+ cls, repo: "Repo", *args: Any, **kwargs: Any
+ ) -> NoReturn: # -> Iterator['PushInfo']:
raise NotImplementedError
class PushInfoList(IterableList[PushInfo]):
- def __new__(cls) -> 'PushInfoList':
- return cast(PushInfoList, IterableList.__new__(cls, 'push_infos'))
+ def __new__(cls) -> "PushInfoList":
+ return cast(PushInfoList, IterableList.__new__(cls, "push_infos"))
def __init__(self) -> None:
- super().__init__('push_infos')
+ super().__init__("push_infos")
self.error: Optional[Exception] = None
def raise_if_error(self) -> None:
@@ -267,24 +320,35 @@ class FetchInfo(IterableObj, object):
# field is set to the previous location of ref, otherwise None
info.remote_ref_path # The path from which we fetched on the remote. It's the remote's version of our info.ref
"""
- __slots__ = ('ref', 'old_commit', 'flags', 'note', 'remote_ref_path')
- _id_attribute_ = 'fetchinfo'
- NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \
- FAST_FORWARD, ERROR = [1 << x for x in range(8)]
+ __slots__ = ("ref", "old_commit", "flags", "note", "remote_ref_path")
+ _id_attribute_ = "fetchinfo"
- _re_fetch_result = re.compile(r'^\s*(.) (\[[\w\s\.$@]+\]|[\w\.$@]+)\s+(.+) -> ([^\s]+)( \(.*\)?$)?')
+ (
+ NEW_TAG,
+ NEW_HEAD,
+ HEAD_UPTODATE,
+ TAG_UPDATE,
+ REJECTED,
+ FORCED_UPDATE,
+ FAST_FORWARD,
+ ERROR,
+ ) = [1 << x for x in range(8)]
+
+ _re_fetch_result = re.compile(
+ r"^\s*(.) (\[[\w\s\.$@]+\]|[\w\.$@]+)\s+(.+) -> ([^\s]+)( \(.*\)?$)?"
+ )
_flag_map: Dict[flagKeyLiteral, int] = {
- '!': ERROR,
- '+': FORCED_UPDATE,
- '*': 0,
- '=': HEAD_UPTODATE,
- ' ': FAST_FORWARD,
- '-': TAG_UPDATE,
+ "!": ERROR,
+ "+": FORCED_UPDATE,
+ "*": 0,
+ "=": HEAD_UPTODATE,
+ " ": FAST_FORWARD,
+ "-": TAG_UPDATE,
}
- @ classmethod
+ @classmethod
def refresh(cls) -> Literal[True]:
"""This gets called by the refresh function (see the top level
__init__).
@@ -308,9 +372,14 @@ class FetchInfo(IterableObj, object):
return True
- def __init__(self, ref: SymbolicReference, flags: int, note: str = '',
- old_commit: Union[Commit_ish, None] = None,
- remote_ref_path: Optional[PathLike] = None) -> None:
+ def __init__(
+ self,
+ ref: SymbolicReference,
+ flags: int,
+ note: str = "",
+ old_commit: Union[Commit_ish, None] = None,
+ remote_ref_path: Optional[PathLike] = None,
+ ) -> None:
"""
Initialize a new instance
"""
@@ -323,18 +392,18 @@ 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
- def _from_line(cls, repo: 'Repo', line: str, fetch_line: str) -> 'FetchInfo':
+ @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.
@@ -357,12 +426,18 @@ class FetchInfo(IterableObj, object):
# parse lines
remote_local_ref_str: str
- control_character, operation, local_remote_ref, remote_local_ref_str, note = match.groups()
+ (
+ control_character,
+ operation,
+ local_remote_ref,
+ remote_local_ref_str,
+ note,
+ ) = match.groups()
# assert is_flagKeyLiteral(control_character), f"{control_character}"
control_character = cast(flagKeyLiteral, control_character)
try:
_new_hex_sha, _fetch_operation, fetch_note = fetch_line.split("\t")
- ref_type_name, fetch_note = fetch_note.split(' ', 1)
+ ref_type_name, fetch_note = fetch_note.split(" ", 1)
except ValueError as e: # unpack error
raise ValueError("Failed to parse FETCH_HEAD line: %r" % fetch_line) from e
@@ -371,25 +446,28 @@ class FetchInfo(IterableObj, object):
try:
flags |= cls._flag_map[control_character]
except KeyError as e:
- raise ValueError("Control character %r unknown as parsed from line %r" % (control_character, line)) from e
+ raise ValueError(
+ "Control character %r unknown as parsed from line %r"
+ % (control_character, line)
+ ) from e
# END control char exception handling
# parse operation string for more info - makes no sense for symbolic refs, but we parse it anyway
old_commit: Union[Commit_ish, None] = None
is_tag_operation = False
- if 'rejected' in operation:
+ if "rejected" in operation:
flags |= cls.REJECTED
- if 'new tag' in operation:
+ if "new tag" in operation:
flags |= cls.NEW_TAG
is_tag_operation = True
- if 'tag update' in operation:
+ if "tag update" in operation:
flags |= cls.TAG_UPDATE
is_tag_operation = True
- if 'new branch' in operation:
+ if "new branch" in operation:
flags |= cls.NEW_HEAD
- if '...' in operation or '..' in operation:
- split_token = '...'
- if control_character == ' ':
+ if "..." in operation or ".." in operation:
+ split_token = "..."
+ if control_character == " ":
split_token = split_token[:-1]
old_commit = repo.rev_parse(operation.split(split_token)[0])
# END handle refspec
@@ -409,7 +487,7 @@ class FetchInfo(IterableObj, object):
# note: remote-tracking is just the first part of the 'remote-tracking branch' token.
# We don't parse it correctly, but its enough to know what to do, and its new in git 1.7something
ref_type = RemoteReference
- elif '/' in ref_type_name:
+ elif "/" in ref_type_name:
# If the fetch spec look something like this '+refs/pull/*:refs/heads/pull/*', and is thus pretty
# much anything the user wants, we will have trouble to determine what's going on
# For now, we assume the local ref is a Head
@@ -434,15 +512,23 @@ class FetchInfo(IterableObj, object):
# 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 + "/"):
+ if (
+ ref_type is not TagReference
+ and not 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:
+ elif ref_type is TagReference and "tags/" in remote_local_ref_str:
# even though its a tag, it is located in refs/remotes
- ref_path = join_path(RemoteReference._common_path_default, remote_local_ref_str)
+ ref_path = join_path(
+ RemoteReference._common_path_default, remote_local_ref_str
+ )
else:
- ref_path = join_path(ref_type._common_path_default, remote_local_ref_str)
+ ref_path = join_path(
+ ref_type._common_path_default, remote_local_ref_str
+ )
# END obtain refpath
# even though the path could be within the git conventions, we make
@@ -450,13 +536,14 @@ class FetchInfo(IterableObj, object):
remote_local_ref = ref_type(repo, ref_path, check_path=False)
# END create ref instance
- note = (note and note.strip()) or ''
+ note = (note and note.strip()) or ""
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']:
+ @classmethod
+ def iter_items(
+ cls, repo: "Repo", *args: Any, **kwargs: Any
+ ) -> NoReturn: # -> Iterator['FetchInfo']:
raise NotImplementedError
@@ -473,7 +560,7 @@ class Remote(LazyMixin, IterableObj):
__slots__ = ("repo", "name", "_config_reader")
_id_attribute_ = "name"
- def __init__(self, repo: 'Repo', name: str) -> None:
+ def __init__(self, repo: "Repo", name: str) -> None:
"""Initialize a remote instance
:param repo: The repository we are a remote of
@@ -503,7 +590,9 @@ class Remote(LazyMixin, IterableObj):
if attr == "_config_reader":
# NOTE: This is cached as __getattr__ is overridden to return remote config values implicitly, such as
# in print(r.pushurl)
- self._config_reader = SectionConstraint(self.repo.config_reader("repository"), self._config_section_name())
+ self._config_reader = SectionConstraint(
+ self.repo.config_reader("repository"), self._config_section_name()
+ )
else:
super(Remote, self)._set_cache_(attr)
@@ -527,7 +616,7 @@ class Remote(LazyMixin, IterableObj):
:return: True if this is a valid, existing remote.
Valid remotes have an entry in the repository's configuration"""
try:
- self.config_reader.get('url')
+ self.config_reader.get("url")
return True
except cp.NoOptionError:
# we have the section at least ...
@@ -536,20 +625,22 @@ class Remote(LazyMixin, IterableObj):
return False
# end
- @ classmethod
- def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Iterator['Remote']:
+ @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():
- if not section.startswith('remote '):
+ if not section.startswith("remote "):
continue
lbound = section.find('"')
rbound = section.rfind('"')
if lbound == -1 or rbound == -1:
raise ValueError("Remote-Section has invalid format: %r" % section)
- yield Remote(repo, section[lbound + 1:rbound])
+ yield Remote(repo, section[lbound + 1 : rbound])
# END for each configuration section
- def set_url(self, new_url: str, old_url: Optional[str] = None, **kwargs: Any) -> 'Remote':
+ def set_url(
+ self, new_url: str, old_url: Optional[str] = None, **kwargs: Any
+ ) -> "Remote":
"""Configure URLs on current remote (cf command git remote set_url)
This command manages URLs on the remote.
@@ -558,15 +649,15 @@ class Remote(LazyMixin, IterableObj):
:param old_url: when set, replaces this URL with new_url for the remote
:return: self
"""
- scmd = 'set-url'
- kwargs['insert_kwargs_after'] = scmd
+ scmd = "set-url"
+ kwargs["insert_kwargs_after"] = scmd
if old_url:
self.repo.git.remote(scmd, self.name, new_url, old_url, **kwargs)
else:
self.repo.git.remote(scmd, self.name, new_url, **kwargs)
return self
- def add_url(self, url: str, **kwargs: Any) -> 'Remote':
+ def add_url(self, url: str, **kwargs: Any) -> "Remote":
"""Adds a new url on current remote (special case of git remote set_url)
This command adds new URLs to a given remote, making it possible to have
@@ -577,7 +668,7 @@ class Remote(LazyMixin, IterableObj):
"""
return self.set_url(url, add=True)
- def delete_url(self, url: str, **kwargs: Any) -> 'Remote':
+ def delete_url(self, url: str, **kwargs: Any) -> "Remote":
"""Deletes a new url on current remote (special case of git remote set_url)
This command deletes new URLs to a given remote, making it possible to have
@@ -588,13 +679,13 @@ class Remote(LazyMixin, IterableObj):
"""
return self.set_url(url, delete=True)
- @ property
+ @property
def urls(self) -> Iterator[str]:
""":return: Iterator yielding all configured URL targets on a remote as strings"""
try:
remote_details = self.repo.git.remote("get-url", "--all", self.name)
assert isinstance(remote_details, str)
- for line in remote_details.split('\n'):
+ for line in remote_details.split("\n"):
yield line
except GitCommandError as ex:
## We are on git < 2.7 (i.e TravisCI as of Oct-2016),
@@ -602,37 +693,44 @@ class Remote(LazyMixin, IterableObj):
# see: https://github.com/gitpython-developers/GitPython/pull/528#issuecomment-252976319
# and: http://stackoverflow.com/a/32991784/548792
#
- if 'Unknown subcommand: get-url' in str(ex):
+ if "Unknown subcommand: get-url" in str(ex):
try:
remote_details = self.repo.git.remote("show", self.name)
assert isinstance(remote_details, str)
- for line in remote_details.split('\n'):
- if ' Push URL:' in line:
- yield line.split(': ')[-1]
+ for line in remote_details.split("\n"):
+ if " Push URL:" in line:
+ yield line.split(": ")[-1]
except GitCommandError as _ex:
- if any(msg in str(_ex) for msg in ['correct access rights', 'cannot run ssh']):
+ if any(
+ msg in str(_ex)
+ for msg in ["correct access rights", "cannot run ssh"]
+ ):
# If ssh is not setup to access this repository, see issue 694
- remote_details = self.repo.git.config('--get-all', 'remote.%s.url' % self.name)
+ remote_details = self.repo.git.config(
+ "--get-all", "remote.%s.url" % self.name
+ )
assert isinstance(remote_details, str)
- for line in remote_details.split('\n'):
+ for line in remote_details.split("\n"):
yield line
else:
raise _ex
else:
raise ex
- @ property
+ @property
def refs(self) -> IterableList[RemoteReference]:
"""
:return:
IterableList of RemoteReference objects. It is prefixed, allowing
you to omit the remote path portion, i.e.::
remote.refs.master # yields RemoteReference('/refs/remotes/origin/master')"""
- out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
+ out_refs: IterableList[RemoteReference] = IterableList(
+ RemoteReference._id_attribute_, "%s/" % self.name
+ )
out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name))
return out_refs
- @ property
+ @property
def stale_refs(self) -> IterableList[Reference]:
"""
:return:
@@ -647,8 +745,10 @@ class Remote(LazyMixin, IterableObj):
other kinds of references, for example, tag references, if these are stale
as well. This is a fix for the issue described here:
https://github.com/gitpython-developers/GitPython/issues/260
- """
- out_refs: IterableList[Reference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name)
+ """
+ out_refs: IterableList[Reference] = IterableList(
+ RemoteReference._id_attribute_, "%s/" % self.name
+ )
for line in self.repo.git.remote("prune", "--dry-run", self).splitlines()[2:]:
# expecting
# * [would prune] origin/new_branch
@@ -657,7 +757,7 @@ class Remote(LazyMixin, IterableObj):
continue
ref_name = line.replace(token, "")
# sometimes, paths start with a full ref name, like refs/tags/foo, see #260
- if ref_name.startswith(Reference._common_path_default + '/'):
+ if ref_name.startswith(Reference._common_path_default + "/"):
out_refs.append(Reference.from_path(self.repo, ref_name))
else:
fqhn = "%s/%s" % (RemoteReference._common_path_default, ref_name)
@@ -666,8 +766,8 @@ class Remote(LazyMixin, IterableObj):
# END for each line
return out_refs
- @ classmethod
- def create(cls, repo: 'Repo', name: str, url: str, **kwargs: Any) -> 'Remote':
+ @classmethod
+ def create(cls, repo: "Repo", name: str, url: str, **kwargs: Any) -> "Remote":
"""Create a new remote to the given repository
:param repo: Repository instance that is to receive the new remote
:param name: Desired name of the remote
@@ -675,18 +775,18 @@ class Remote(LazyMixin, IterableObj):
:param kwargs: Additional arguments to be passed to the git-remote add command
:return: New Remote instance
:raise GitCommandError: in case an origin with that name already exists"""
- scmd = 'add'
- kwargs['insert_kwargs_after'] = scmd
+ scmd = "add"
+ kwargs["insert_kwargs_after"] = scmd
repo.git.remote(scmd, name, Git.polish_url(url), **kwargs)
return cls(repo, name)
# add is an alias
- @ classmethod
- def add(cls, repo: 'Repo', name: str, url: str, **kwargs: Any) -> 'Remote':
+ @classmethod
+ def add(cls, repo: "Repo", name: str, url: str, **kwargs: Any) -> "Remote":
return cls.create(repo, name, url, **kwargs)
- @ classmethod
- def remove(cls, repo: 'Repo', name: str) -> str:
+ @classmethod
+ def remove(cls, repo: "Repo", name: str) -> str:
"""Remove the remote with the given name
:return: the passed remote name to remove
"""
@@ -698,9 +798,9 @@ class Remote(LazyMixin, IterableObj):
# alias
rm = remove
- def rename(self, new_name: str) -> 'Remote':
+ def rename(self, new_name: str) -> "Remote":
"""Rename self to the given new_name
- :return: self """
+ :return: self"""
if self.name == new_name:
return self
@@ -710,7 +810,7 @@ class Remote(LazyMixin, IterableObj):
return self
- def update(self, **kwargs: Any) -> 'Remote':
+ def update(self, **kwargs: Any) -> "Remote":
"""Fetch all changes for this remote, including new branches which will
be forced in ( in case your local remote branch is not part the new remote branches
ancestry anymore ).
@@ -718,21 +818,23 @@ class Remote(LazyMixin, IterableObj):
:param kwargs:
Additional arguments passed to git-remote update
- :return: self """
- scmd = 'update'
- kwargs['insert_kwargs_after'] = scmd
+ :return: self"""
+ scmd = "update"
+ kwargs["insert_kwargs_after"] = scmd
self.repo.git.remote(scmd, self.name, **kwargs)
return self
- def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt',
- progress: Union[Callable[..., Any], RemoteProgress, None],
- kill_after_timeout: Union[None, float] = None,
- ) -> IterableList['FetchInfo']:
+ def _get_fetch_info_from_stderr(
+ self,
+ proc: "Git.AutoInterrupt",
+ progress: Union[Callable[..., Any], RemoteProgress, None],
+ kill_after_timeout: Union[None, float] = None,
+ ) -> IterableList["FetchInfo"]:
progress = to_progress_instance(progress)
# skip first line as it is some remote info we are not interested in
- output: IterableList['FetchInfo'] = IterableList('name')
+ output: IterableList["FetchInfo"] = IterableList("name")
# lines which are no progress are fetch info lines
# this also waits for the command to finish
@@ -743,10 +845,16 @@ class Remote(LazyMixin, IterableObj):
cmds = set(FetchInfo._flag_map.keys())
progress_handler = progress.new_message_handler()
- handle_process_output(proc, None, progress_handler, finalizer=None, decode_streams=False,
- kill_after_timeout=kill_after_timeout)
-
- stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
+ handle_process_output(
+ proc,
+ None,
+ progress_handler,
+ finalizer=None,
+ decode_streams=False,
+ kill_after_timeout=kill_after_timeout,
+ )
+
+ stderr_text = progress.error_lines and "\n".join(progress.error_lines) or ""
proc.wait(stderr=stderr_text)
if stderr_text:
log.warning("Error lines received while fetching: %s", stderr_text)
@@ -754,13 +862,13 @@ class Remote(LazyMixin, IterableObj):
for line in progress.other_lines:
line = force_text(line)
for cmd in cmds:
- if len(line) > 1 and line[0] == ' ' and line[1] == cmd:
+ if len(line) > 1 and line[0] == " " and line[1] == cmd:
fetch_info_lines.append(line)
continue
# read head information
fetch_head = SymbolicReference(self.repo, "FETCH_HEAD")
- with open(fetch_head.abspath, 'rb') as fp:
+ with open(fetch_head.abspath, "rb") as fp:
fetch_head_info = [line.decode(defenc) for line in fp.readlines()]
l_fil = len(fetch_info_lines)
@@ -788,9 +896,12 @@ class Remote(LazyMixin, IterableObj):
log.warning("Git informed while fetching: %s", err_line.strip())
return output
- def _get_push_info(self, proc: 'Git.AutoInterrupt',
- progress: Union[Callable[..., Any], RemoteProgress, None],
- kill_after_timeout: Union[None, float] = None) -> PushInfoList:
+ def _get_push_info(
+ self,
+ proc: "Git.AutoInterrupt",
+ progress: Union[Callable[..., Any], RemoteProgress, None],
+ kill_after_timeout: Union[None, float] = None,
+ ) -> PushInfoList:
progress = to_progress_instance(progress)
# read progress information from stderr
@@ -807,9 +918,15 @@ class Remote(LazyMixin, IterableObj):
# If an error happens, additional info is given which we parse below.
pass
- handle_process_output(proc, stdout_handler, progress_handler, finalizer=None, decode_streams=False,
- kill_after_timeout=kill_after_timeout)
- stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
+ handle_process_output(
+ proc,
+ stdout_handler,
+ progress_handler,
+ finalizer=None,
+ decode_streams=False,
+ kill_after_timeout=kill_after_timeout,
+ )
+ stderr_text = progress.error_lines and "\n".join(progress.error_lines) or ""
try:
proc.wait(stderr=stderr_text)
except Exception as e:
@@ -826,9 +943,9 @@ class Remote(LazyMixin, IterableObj):
def _assert_refspec(self) -> None:
"""Turns out we can't deal with remotes if the refspec is missing"""
config = self.config_reader
- unset = 'placeholder'
+ unset = "placeholder"
try:
- if config.get_value('fetch', default=unset) is unset:
+ if config.get_value("fetch", default=unset) is unset:
msg = "Remote '%s' has no refspec set.\n"
msg += "You can set it as follows:"
msg += " 'git config --add \"remote.%s.fetch +refs/heads/*:refs/heads/*\"'."
@@ -836,11 +953,14 @@ class Remote(LazyMixin, IterableObj):
finally:
config.release()
- def fetch(self, refspec: Union[str, List[str], None] = None,
- progress: Union[RemoteProgress, None, 'UpdateProgress'] = None,
- verbose: bool = True,
- kill_after_timeout: Union[None, float] = None,
- **kwargs: Any) -> IterableList[FetchInfo]:
+ def fetch(
+ self,
+ refspec: Union[str, List[str], None] = None,
+ progress: Union[RemoteProgress, None, "UpdateProgress"] = None,
+ verbose: bool = True,
+ kill_after_timeout: Union[None, float] = None,
+ **kwargs: Any
+ ) -> IterableList[FetchInfo]:
"""Fetch the latest changes for this remote
:param refspec:
@@ -881,18 +1001,29 @@ class Remote(LazyMixin, IterableObj):
else:
args = [refspec]
- proc = self.repo.git.fetch(self, *args, as_process=True, with_stdout=False,
- universal_newlines=True, v=verbose, **kwargs)
- res = self._get_fetch_info_from_stderr(proc, progress,
- kill_after_timeout=kill_after_timeout)
- if hasattr(self.repo.odb, 'update_cache'):
+ proc = self.repo.git.fetch(
+ self,
+ *args,
+ as_process=True,
+ with_stdout=False,
+ universal_newlines=True,
+ v=verbose,
+ **kwargs
+ )
+ res = self._get_fetch_info_from_stderr(
+ proc, progress, kill_after_timeout=kill_after_timeout
+ )
+ if hasattr(self.repo.odb, "update_cache"):
self.repo.odb.update_cache()
return res
- def pull(self, refspec: Union[str, List[str], None] = None,
- progress: Union[RemoteProgress, 'UpdateProgress', None] = None,
- kill_after_timeout: Union[None, float] = None,
- **kwargs: Any) -> IterableList[FetchInfo]:
+ def pull(
+ self,
+ refspec: Union[str, List[str], None] = None,
+ progress: Union[RemoteProgress, "UpdateProgress", None] = None,
+ kill_after_timeout: Union[None, float] = None,
+ **kwargs: Any
+ ) -> IterableList[FetchInfo]:
"""Pull changes from the given branch, being the same as a fetch followed
by a merge of branch with your local branch.
@@ -900,23 +1031,36 @@ class Remote(LazyMixin, IterableObj):
:param progress: see 'push' method
:param kill_after_timeout: see 'fetch' method
:param kwargs: Additional arguments to be passed to git-pull
- :return: Please see 'fetch' method """
+ :return: Please see 'fetch' method"""
if refspec is None:
# No argument refspec, then ensure the repo's config has a fetch refspec.
self._assert_refspec()
kwargs = add_progress(kwargs, self.repo.git, progress)
- proc = self.repo.git.pull(self, refspec, with_stdout=False, as_process=True,
- universal_newlines=True, v=True, **kwargs)
- res = self._get_fetch_info_from_stderr(proc, progress,
- kill_after_timeout=kill_after_timeout)
- if hasattr(self.repo.odb, 'update_cache'):
+ proc = self.repo.git.pull(
+ self,
+ refspec,
+ with_stdout=False,
+ as_process=True,
+ universal_newlines=True,
+ v=True,
+ **kwargs
+ )
+ res = self._get_fetch_info_from_stderr(
+ proc, progress, kill_after_timeout=kill_after_timeout
+ )
+ if hasattr(self.repo.odb, "update_cache"):
self.repo.odb.update_cache()
return res
- def push(self, refspec: Union[str, List[str], None] = None,
- progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None] = None,
- kill_after_timeout: Union[None, float] = None,
- **kwargs: Any) -> IterableList[PushInfo]:
+ def push(
+ self,
+ refspec: Union[str, List[str], None] = None,
+ progress: Union[
+ RemoteProgress, "UpdateProgress", Callable[..., RemoteProgress], None
+ ] = None,
+ kill_after_timeout: Union[None, float] = None,
+ **kwargs: Any
+ ) -> IterableList[PushInfo]:
"""Push changes from source branch in refspec to target branch in refspec.
:param refspec: see 'fetch' method
@@ -945,14 +1089,20 @@ class Remote(LazyMixin, IterableObj):
If the operation fails completely, the length of the returned IterableList will
be 0."""
kwargs = add_progress(kwargs, self.repo.git, progress)
- proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True,
- universal_newlines=True,
- kill_after_timeout=kill_after_timeout,
- **kwargs)
- return self._get_push_info(proc, progress,
- kill_after_timeout=kill_after_timeout)
-
- @ property
+ proc = self.repo.git.push(
+ self,
+ refspec,
+ porcelain=True,
+ as_process=True,
+ universal_newlines=True,
+ kill_after_timeout=kill_after_timeout,
+ **kwargs
+ )
+ return self._get_push_info(
+ proc, progress, kill_after_timeout=kill_after_timeout
+ )
+
+ @property
def config_reader(self) -> SectionConstraint[GitConfigParser]:
"""
:return:
@@ -962,12 +1112,12 @@ class Remote(LazyMixin, IterableObj):
def _clear_cache(self) -> None:
try:
- del(self._config_reader)
+ del self._config_reader
except AttributeError:
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/repo/base.py b/git/repo/base.py
index bea0dcb5..356a8f2f 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -13,10 +13,7 @@ from gitdb.db.loose import LooseObjectDB
from gitdb.exc import BadObject
-from git.cmd import (
- Git,
- handle_process_output
-)
+from git.cmd import Git, handle_process_output
from git.compat import (
defenc,
safe_decode,
@@ -29,20 +26,54 @@ from git.index import IndexFile
from git.objects import Submodule, RootModule, Commit
from git.refs import HEAD, Head, Reference, TagReference
from git.remote import Remote, add_progress, to_progress_instance
-from git.util import Actor, finalize_process, decygpath, hex_to_bin, expand_path, remove_password_if_present
+from git.util import (
+ Actor,
+ finalize_process,
+ decygpath,
+ hex_to_bin,
+ expand_path,
+ remove_password_if_present,
+)
import os.path as osp
-from .fun import rev_parse, is_git_dir, find_submodule_git_dir, touch, find_worktree_git_dir
+from .fun import (
+ rev_parse,
+ is_git_dir,
+ find_submodule_git_dir,
+ touch,
+ find_worktree_git_dir,
+)
import gc
import gitdb
# typing ------------------------------------------------------
-from git.types import TBD, PathLike, Lit_config_levels, Commit_ish, Tree_ish, assert_never
-from typing import (Any, BinaryIO, Callable, Dict,
- Iterator, List, Mapping, Optional, Sequence,
- TextIO, Tuple, Type, Union,
- NamedTuple, cast, TYPE_CHECKING)
+from git.types import (
+ TBD,
+ PathLike,
+ Lit_config_levels,
+ Commit_ish,
+ Tree_ish,
+ assert_never,
+)
+from typing import (
+ Any,
+ BinaryIO,
+ Callable,
+ Dict,
+ Iterator,
+ List,
+ Mapping,
+ Optional,
+ Sequence,
+ TextIO,
+ Tuple,
+ Type,
+ Union,
+ NamedTuple,
+ cast,
+ TYPE_CHECKING,
+)
from git.types import ConfigLevels_Tup, TypedDict
@@ -57,11 +88,11 @@ if TYPE_CHECKING:
log = logging.getLogger(__name__)
-__all__ = ('Repo',)
+__all__ = ("Repo",)
class BlameEntry(NamedTuple):
- commit: Dict[str, 'Commit']
+ commit: Dict[str, "Commit"]
linenos: range
orig_path: Optional[str]
orig_linenos: range
@@ -81,21 +112,24 @@ class Repo(object):
if we are a bare repository.
'git_dir' is the .git repository directory, which is always set."""
- DAEMON_EXPORT_FILE = 'git-daemon-export-ok'
- git = cast('Git', None) # Must exist, or __del__ will fail in case we raise on `__init__()`
+ 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: Optional[PathLike] = None
_working_tree_dir: Optional[PathLike] = None
git_dir: PathLike = ""
_common_dir: PathLike = ""
# precompiled regex
- re_whitespace = re.compile(r'\s+')
- re_hexsha_only = re.compile('^[0-9A-Fa-f]{40}$')
- re_hexsha_shortened = re.compile('^[0-9A-Fa-f]{4,40}$')
- re_envvars = re.compile(r'(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)')
- re_author_committer_start = re.compile(r'^(author|committer)')
- re_tab_full_line = re.compile(r'^\t(.*)$')
+ re_whitespace = re.compile(r"\s+")
+ re_hexsha_only = re.compile("^[0-9A-Fa-f]{40}$")
+ re_hexsha_shortened = re.compile("^[0-9A-Fa-f]{4,40}$")
+ re_envvars = re.compile(r"(\$(\{\s?)?[a-zA-Z_]\w*(\}\s?)?|%\s?[a-zA-Z_]\w*\s?%)")
+ re_author_committer_start = re.compile(r"^(author|committer)")
+ re_tab_full_line = re.compile(r"^\t(.*)$")
# invariants
# represents the configuration level of a configuration file
@@ -105,8 +139,13 @@ class Repo(object):
# Subclasses may easily bring in their own custom types by placing a constructor or type here
GitCommandWrapperType = Git
- def __init__(self, path: Optional[PathLike] = None, odbt: Type[LooseObjectDB] = GitCmdObjectDB,
- search_parent_directories: bool = False, expand_vars: bool = True) -> None:
+ def __init__(
+ self,
+ path: Optional[PathLike] = None,
+ odbt: Type[LooseObjectDB] = GitCmdObjectDB,
+ search_parent_directories: bool = False,
+ expand_vars: bool = True,
+ ) -> None:
"""Create a new Repo instance
:param path:
@@ -132,9 +171,9 @@ class Repo(object):
which is considered a bug though.
:raise InvalidGitRepositoryError:
:raise NoSuchPathError:
- :return: git.Repo """
+ :return: git.Repo"""
- epath = path or os.getenv('GIT_DIR')
+ epath = path or os.getenv("GIT_DIR")
if not epath:
epath = os.getcwd()
if Git.is_cygwin():
@@ -144,8 +183,10 @@ class Repo(object):
if not isinstance(epath, str):
epath = str(epath)
if expand_vars and re.search(self.re_envvars, epath):
- warnings.warn("The use of environment variables in paths is deprecated" +
- "\nfor security reasons and may be removed in the future!!")
+ warnings.warn(
+ "The use of environment variables in paths is deprecated"
+ + "\nfor security reasons and may be removed in the future!!"
+ )
epath = expand_path(epath, expand_vars)
if epath is not None:
if not os.path.exists(epath):
@@ -170,15 +211,15 @@ class Repo(object):
# If GIT_DIR is specified but none of GIT_WORK_TREE and core.worktree is specified,
# the current working directory is regarded as the top level of your working tree.
self._working_tree_dir = os.path.dirname(self.git_dir)
- if os.environ.get('GIT_COMMON_DIR') is None:
+ if os.environ.get("GIT_COMMON_DIR") is None:
gitconf = self.config_reader("repository")
- if gitconf.has_option('core', 'worktree'):
- self._working_tree_dir = gitconf.get('core', 'worktree')
- if 'GIT_WORK_TREE' in os.environ:
- self._working_tree_dir = os.getenv('GIT_WORK_TREE')
+ if gitconf.has_option("core", "worktree"):
+ self._working_tree_dir = gitconf.get("core", "worktree")
+ if "GIT_WORK_TREE" in os.environ:
+ self._working_tree_dir = os.getenv("GIT_WORK_TREE")
break
- dotgit = osp.join(curpath, '.git')
+ dotgit = osp.join(curpath, ".git")
sm_gitpath = find_submodule_git_dir(dotgit)
if sm_gitpath is not None:
self.git_dir = osp.normpath(sm_gitpath)
@@ -204,13 +245,15 @@ class Repo(object):
self._bare = False
try:
- self._bare = self.config_reader("repository").getboolean('core', 'bare')
+ self._bare = self.config_reader("repository").getboolean("core", "bare")
except Exception:
# lets not assume the option exists, although it should
pass
try:
- common_dir = open(osp.join(self.git_dir, 'commondir'), 'rt').readlines()[0].strip()
+ common_dir = (
+ open(osp.join(self.git_dir, "commondir"), "rt").readlines()[0].strip()
+ )
self._common_dir = osp.join(self.git_dir, common_dir)
except OSError:
self._common_dir = ""
@@ -225,13 +268,13 @@ class Repo(object):
self.git = self.GitCommandWrapperType(self.working_dir)
# special handling, in special times
- rootpath = osp.join(self.common_dir, 'objects')
+ rootpath = osp.join(self.common_dir, "objects")
if issubclass(odbt, GitCmdObjectDB):
self.odb = odbt(rootpath, self.git)
else:
self.odb = odbt(rootpath)
- def __enter__(self) -> 'Repo':
+ def __enter__(self) -> "Repo":
return self
def __exit__(self, *args: Any) -> None:
@@ -272,25 +315,25 @@ class Repo(object):
# Description property
def _get_description(self) -> str:
if self.git_dir:
- filename = osp.join(self.git_dir, 'description')
- with open(filename, 'rb') as fp:
+ filename = osp.join(self.git_dir, "description")
+ with open(filename, "rb") as fp:
return fp.read().rstrip().decode(defenc)
def _set_description(self, descr: str) -> None:
if self.git_dir:
- filename = osp.join(self.git_dir, 'description')
- with open(filename, 'wb') as fp:
- fp.write((descr + '\n').encode(defenc))
+ filename = osp.join(self.git_dir, "description")
+ with open(filename, "wb") as fp:
+ fp.write((descr + "\n").encode(defenc))
- description = property(_get_description, _set_description,
- doc="the project's description")
+ description = property(
+ _get_description, _set_description, doc="the project's description"
+ )
del _get_description
del _set_description
@property
def working_tree_dir(self) -> Optional[PathLike]:
- """:return: The working tree directory of our git repository. If this is a bare repository, None is returned.
- """
+ """:return: The working tree directory of our git repository. If this is a bare repository, None is returned."""
return self._working_tree_dir
@property
@@ -312,7 +355,7 @@ class Repo(object):
return self._bare
@property
- def heads(self) -> 'IterableList[Head]':
+ def heads(self) -> "IterableList[Head]":
"""A list of ``Head`` objects representing the branch heads in
this repo
@@ -320,7 +363,7 @@ class Repo(object):
return Head.list_items(self)
@property
- def references(self) -> 'IterableList[Reference]':
+ def references(self) -> "IterableList[Reference]":
"""A list of Reference objects representing tags, heads and remote references.
:return: IterableList(Reference, ...)"""
@@ -333,24 +376,24 @@ class Repo(object):
branches = heads
@property
- def index(self) -> 'IndexFile':
+ def index(self) -> "IndexFile":
""":return: IndexFile representing this repository's index.
:note: This property can be expensive, as the returned ``IndexFile`` will be
reinitialized. It's recommended to re-use the object."""
return IndexFile(self)
@property
- def head(self) -> 'HEAD':
+ def head(self) -> "HEAD":
""":return: HEAD Object pointing to the current head reference"""
- return HEAD(self, 'HEAD')
+ return HEAD(self, "HEAD")
@property
- def remotes(self) -> 'IterableList[Remote]':
+ def remotes(self) -> "IterableList[Remote]":
"""A list of Remote objects allowing to access and manipulate remotes
:return: ``git.IterableList(Remote, ...)``"""
return Remote.list_items(self)
- def remote(self, name: str = 'origin') -> 'Remote':
+ def remote(self, name: str = "origin") -> "Remote":
""":return: Remote with the specified name
:raise ValueError: if no remote with such a name exists"""
r = Remote(self, name)
@@ -358,17 +401,17 @@ class Repo(object):
raise ValueError("Remote named '%s' didn't exist" % name)
return r
- #{ Submodules
+ # { Submodules
@property
- def submodules(self) -> 'IterableList[Submodule]':
+ def submodules(self) -> "IterableList[Submodule]":
"""
:return: git.IterableList(Submodule, ...) of direct submodules
available from the current head"""
return Submodule.list_items(self)
- def submodule(self, name: str) -> 'Submodule':
- """ :return: Submodule with the given name
+ def submodule(self, name: str) -> "Submodule":
+ """:return: Submodule with the given name
:raise ValueError: If no such submodule exists"""
try:
return self.submodules[name]
@@ -396,53 +439,61 @@ class Repo(object):
see the documentation of RootModule.update"""
return RootModule(self).update(*args, **kwargs)
- #}END submodules
+ # }END submodules
@property
- def tags(self) -> 'IterableList[TagReference]':
+ def tags(self) -> "IterableList[TagReference]":
"""A list of ``Tag`` objects that are available in this repo
- :return: ``git.IterableList(TagReference, ...)`` """
+ :return: ``git.IterableList(TagReference, ...)``"""
return TagReference.list_items(self)
def tag(self, path: PathLike) -> TagReference:
""":return: TagReference Object, reference pointing to a Commit or Tag
- :param path: path to the tag reference, i.e. 0.1.5 or tags/0.1.5 """
+ :param path: path to the tag reference, i.e. 0.1.5 or tags/0.1.5"""
full_path = self._to_full_tag_path(path)
return TagReference(self, full_path)
@staticmethod
def _to_full_tag_path(path: PathLike) -> str:
path_str = str(path)
- if path_str.startswith(TagReference._common_path_default + '/'):
+ if path_str.startswith(TagReference._common_path_default + "/"):
return path_str
- if path_str.startswith(TagReference._common_default + '/'):
- return Reference._common_path_default + '/' + path_str
+ if path_str.startswith(TagReference._common_default + "/"):
+ return Reference._common_path_default + "/" + path_str
else:
- return TagReference._common_path_default + '/' + path_str
-
- def create_head(self, path: PathLike,
- commit: Union['SymbolicReference', 'str'] = 'HEAD',
- force: bool = False, logmsg: Optional[str] = None
- ) -> 'Head':
+ return TagReference._common_path_default + "/" + path_str
+
+ def create_head(
+ self,
+ path: PathLike,
+ commit: Union["SymbolicReference", "str"] = "HEAD",
+ force: bool = False,
+ logmsg: Optional[str] = None,
+ ) -> "Head":
"""Create a new head within the repository.
For more documentation, please see the Head.create method.
:return: newly created Head Reference"""
return Head.create(self, path, commit, logmsg, force)
- def delete_head(self, *heads: 'Union[str, Head]', **kwargs: Any) -> None:
+ def delete_head(self, *heads: "Union[str, Head]", **kwargs: Any) -> None:
"""Delete the given heads
:param kwargs: Additional keyword arguments to be passed to git-branch"""
return Head.delete(self, *heads, **kwargs)
- def create_tag(self, path: PathLike, ref: str = 'HEAD',
- message: Optional[str] = None, force: bool = False, **kwargs: Any
- ) -> TagReference:
+ def create_tag(
+ self,
+ path: PathLike,
+ ref: str = "HEAD",
+ message: Optional[str] = None,
+ force: bool = False,
+ **kwargs: Any,
+ ) -> TagReference:
"""Create a new tag reference.
For more documentation, please see the TagReference.create method.
- :return: TagReference object """
+ :return: TagReference object"""
return TagReference.create(self, path, ref, message, force, **kwargs)
def delete_tag(self, *tags: TagReference) -> None:
@@ -458,7 +509,7 @@ class Repo(object):
:return: Remote reference"""
return Remote.create(self, name, url, **kwargs)
- def delete_remote(self, remote: 'Remote') -> str:
+ def delete_remote(self, remote: "Remote") -> str:
"""Delete the given remote."""
return Remote.remove(self, remote)
@@ -471,7 +522,9 @@ class Repo(object):
if config_level == "system":
return "/etc/gitconfig"
elif config_level == "user":
- config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(os.environ.get("HOME", '~'), ".config")
+ config_home = os.environ.get("XDG_CONFIG_HOME") or osp.join(
+ os.environ.get("HOME", "~"), ".config"
+ )
return osp.normpath(osp.expanduser(osp.join(config_home, "git", "config")))
elif config_level == "global":
return osp.normpath(osp.expanduser("~/.gitconfig"))
@@ -483,11 +536,15 @@ class Repo(object):
return osp.normpath(osp.join(repo_dir, "config"))
else:
- assert_never(config_level, # type:ignore[unreachable]
- ValueError(f"Invalid configuration level: {config_level!r}"))
+ assert_never(
+ config_level, # type:ignore[unreachable]
+ ValueError(f"Invalid configuration level: {config_level!r}"),
+ )
- def config_reader(self, config_level: Optional[Lit_config_levels] = None,
- ) -> GitConfigParser:
+ def config_reader(
+ self,
+ config_level: Optional[Lit_config_levels] = None,
+ ) -> GitConfigParser:
"""
:return:
GitConfigParser allowing to read the full git configuration, but not to write it
@@ -503,14 +560,18 @@ class Repo(object):
unknown, instead the global path will be used."""
files = None
if config_level is None:
- files = [self._get_config_path(cast(Lit_config_levels, f))
- for f in self.config_level if cast(Lit_config_levels, f)]
+ files = [
+ self._get_config_path(cast(Lit_config_levels, f))
+ for f in self.config_level
+ if cast(Lit_config_levels, f)
+ ]
else:
files = [self._get_config_path(config_level)]
return GitConfigParser(files, read_only=True, repo=self)
- def config_writer(self, config_level: Lit_config_levels = "repository"
- ) -> GitConfigParser:
+ def config_writer(
+ self, config_level: Lit_config_levels = "repository"
+ ) -> GitConfigParser:
"""
:return:
GitConfigParser allowing to write values of the specified configuration file level.
@@ -523,10 +584,11 @@ class Repo(object):
system = system wide configuration file
global = user level configuration file
repository = configuration file for this repository only"""
- return GitConfigParser(self._get_config_path(config_level), read_only=False, repo=self)
+ return GitConfigParser(
+ self._get_config_path(config_level), read_only=False, repo=self
+ )
- def commit(self, rev: Union[str, Commit_ish, None] = None
- ) -> Commit:
+ def commit(self, rev: Union[str, Commit_ish, None] = None) -> Commit:
"""The Commit object for the specified revision
:param rev: revision specifier, see git-rev-parse for viable options.
@@ -536,12 +598,12 @@ class Repo(object):
return self.head.commit
return self.rev_parse(str(rev) + "^0")
- def iter_trees(self, *args: Any, **kwargs: Any) -> Iterator['Tree']:
+ def iter_trees(self, *args: Any, **kwargs: Any) -> Iterator["Tree"]:
""":return: Iterator yielding Tree objects
:note: Takes all arguments known to iter_commits method"""
return (c.tree for c in self.iter_commits(*args, **kwargs))
- def tree(self, rev: Union[Tree_ish, str, None] = None) -> 'Tree':
+ def tree(self, rev: Union[Tree_ish, str, None] = None) -> "Tree":
"""The Tree object for the given treeish revision
Examples::
@@ -558,9 +620,12 @@ class Repo(object):
return self.head.commit.tree
return self.rev_parse(str(rev) + "^{tree}")
- def iter_commits(self, rev: Union[str, Commit, 'SymbolicReference', None] = None,
- paths: Union[PathLike, Sequence[PathLike]] = '',
- **kwargs: Any) -> Iterator[Commit]:
+ 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
:param rev:
@@ -584,8 +649,7 @@ class Repo(object):
return Commit.iter_items(self, rev, paths, **kwargs)
- def merge_base(self, *rev: TBD, **kwargs: Any
- ) -> List[Union[Commit_ish, None]]:
+ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]:
"""Find the closest common ancestor for the given revision (e.g. Commits, Tags, References, etc)
:param rev: At least two revs to find the common ancestor for.
@@ -616,7 +680,7 @@ class Repo(object):
return res
- def is_ancestor(self, ancestor_rev: 'Commit', rev: 'Commit') -> bool:
+ def is_ancestor(self, ancestor_rev: "Commit", rev: "Commit") -> bool:
"""Check if a commit is an ancestor of another
:param ancestor_rev: Rev which should be an ancestor
@@ -639,8 +703,11 @@ class Repo(object):
if object_info.type == object_type.encode():
return True
else:
- log.debug("Commit hash points to an object of type '%s'. Requested were objects of type '%s'",
- object_info.type.decode(), object_type)
+ log.debug(
+ "Commit hash points to an object of type '%s'. Requested were objects of type '%s'",
+ object_info.type.decode(),
+ object_type,
+ )
return False
else:
return True
@@ -662,8 +729,11 @@ class Repo(object):
elif not value and fileexists:
os.unlink(filename)
- daemon_export = property(_get_daemon_export, _set_daemon_export,
- doc="If True, git-daemon may export this repository")
+ daemon_export = property(
+ _get_daemon_export,
+ _set_daemon_export,
+ doc="If True, git-daemon may export this repository",
+ )
del _get_daemon_export
del _set_daemon_export
@@ -672,10 +742,10 @@ class Repo(object):
:return: list of strings being pathnames of alternates"""
if self.git_dir:
- alternates_path = osp.join(self.git_dir, 'objects', 'info', 'alternates')
+ alternates_path = osp.join(self.git_dir, "objects", "info", "alternates")
if osp.exists(alternates_path):
- with open(alternates_path, 'rb') as f:
+ with open(alternates_path, "rb") as f:
alts = f.read().decode(defenc)
return alts.strip().splitlines()
return []
@@ -691,19 +761,28 @@ class Repo(object):
:note:
The method does not check for the existence of the paths in alts
as the caller is responsible."""
- alternates_path = osp.join(self.common_dir, 'objects', 'info', 'alternates')
+ alternates_path = osp.join(self.common_dir, "objects", "info", "alternates")
if not alts:
if osp.isfile(alternates_path):
os.remove(alternates_path)
else:
- with open(alternates_path, 'wb') as f:
+ with open(alternates_path, "wb") as f:
f.write("\n".join(alts).encode(defenc))
- alternates = property(_get_alternates, _set_alternates,
- doc="Retrieve a list of alternates paths or set a list paths to be used as alternates")
-
- def is_dirty(self, index: bool = True, working_tree: bool = True, untracked_files: bool = False,
- submodules: bool = True, path: Optional[PathLike] = None) -> bool:
+ alternates = property(
+ _get_alternates,
+ _set_alternates,
+ doc="Retrieve a list of alternates paths or set a list paths to be used as alternates",
+ )
+
+ def is_dirty(
+ self,
+ index: bool = True,
+ working_tree: bool = True,
+ untracked_files: bool = False,
+ submodules: bool = True,
+ path: Optional[PathLike] = None,
+ ) -> bool:
"""
:return:
``True``, the repository is considered dirty. By default it will react
@@ -715,15 +794,16 @@ class Repo(object):
return False
# start from the one which is fastest to evaluate
- default_args = ['--abbrev=40', '--full-index', '--raw']
+ default_args = ["--abbrev=40", "--full-index", "--raw"]
if not submodules:
- default_args.append('--ignore-submodules')
+ default_args.append("--ignore-submodules")
if path:
default_args.extend(["--", str(path)])
if index:
# diff index against HEAD
- if osp.isfile(self.index.path) and \
- len(self.git.diff('--cached', *default_args)):
+ if osp.isfile(self.index.path) and len(
+ self.git.diff("--cached", *default_args)
+ ):
return True
# END index handling
if working_tree:
@@ -755,11 +835,9 @@ class Repo(object):
def _get_untracked_files(self, *args: Any, **kwargs: Any) -> List[str]:
# make sure we get all files, not only untracked directories
- proc = self.git.status(*args,
- porcelain=True,
- untracked_files=True,
- as_process=True,
- **kwargs)
+ proc = self.git.status(
+ *args, porcelain=True, untracked_files=True, as_process=True, **kwargs
+ )
# Untracked files prefix in porcelain mode
prefix = "?? "
untracked_files = []
@@ -767,12 +845,17 @@ class Repo(object):
line = line.decode(defenc)
if not line.startswith(prefix):
continue
- filename = line[len(prefix):].rstrip('\n')
+ filename = line[len(prefix) :].rstrip("\n")
# Special characters are escaped
if filename[0] == filename[-1] == '"':
filename = filename[1:-1]
# WHATEVER ... it's a mess, but works for me
- filename = filename.encode('ascii').decode('unicode_escape').encode('latin1').decode(defenc)
+ filename = (
+ filename.encode("ascii")
+ .decode("unicode_escape")
+ .encode("latin1")
+ .decode(defenc)
+ )
untracked_files.append(filename)
finalize_process(proc)
return untracked_files
@@ -797,7 +880,9 @@ class Repo(object):
# reveal_type(self.head.reference) # => Reference
return self.head.reference
- def blame_incremental(self, rev: str | HEAD, file: str, **kwargs: Any) -> Iterator['BlameEntry']:
+ def blame_incremental(
+ self, rev: str | HEAD, file: str, **kwargs: Any
+ ) -> Iterator["BlameEntry"]:
"""Iterator for blame information for the given file at the given revision.
Unlike .blame(), this does not return the actual file's contents, only
@@ -812,13 +897,17 @@ class Repo(object):
should get a continuous range spanning all line numbers in the file.
"""
- data: bytes = self.git.blame(rev, '--', file, p=True, incremental=True, stdout_as_string=False, **kwargs)
+ data: bytes = self.git.blame(
+ rev, "--", file, p=True, incremental=True, stdout_as_string=False, **kwargs
+ )
commits: Dict[bytes, Commit] = {}
- stream = (line for line in data.split(b'\n') if line)
+ stream = (line for line in data.split(b"\n") if line)
while True:
try:
- line = next(stream) # when exhausted, causes a StopIteration, terminating this function
+ line = next(
+ stream
+ ) # when exhausted, causes a StopIteration, terminating this function
except StopIteration:
return
split_line = line.split()
@@ -835,46 +924,58 @@ class Repo(object):
line = next(stream)
except StopIteration:
return
- if line == b'boundary':
+ if line == b"boundary":
# "boundary" indicates a root commit and occurs
# instead of the "previous" tag
continue
- tag, value = line.split(b' ', 1)
+ tag, value = line.split(b" ", 1)
props[tag] = value
- if tag == b'filename':
+ if tag == b"filename":
# "filename" formally terminates the entry for --incremental
orig_filename = value
break
- c = Commit(self, hex_to_bin(hexsha),
- author=Actor(safe_decode(props[b'author']),
- safe_decode(props[b'author-mail'].lstrip(b'<').rstrip(b'>'))),
- authored_date=int(props[b'author-time']),
- committer=Actor(safe_decode(props[b'committer']),
- safe_decode(props[b'committer-mail'].lstrip(b'<').rstrip(b'>'))),
- committed_date=int(props[b'committer-time']))
+ c = Commit(
+ self,
+ hex_to_bin(hexsha),
+ author=Actor(
+ safe_decode(props[b"author"]),
+ safe_decode(props[b"author-mail"].lstrip(b"<").rstrip(b">")),
+ ),
+ authored_date=int(props[b"author-time"]),
+ committer=Actor(
+ safe_decode(props[b"committer"]),
+ safe_decode(props[b"committer-mail"].lstrip(b"<").rstrip(b">")),
+ ),
+ committed_date=int(props[b"committer-time"]),
+ )
commits[hexsha] = c
else:
# Discard all lines until we find "filename" which is
# guaranteed to be the last line
while True:
try:
- line = next(stream) # will fail if we reach the EOF unexpectedly
+ line = next(
+ stream
+ ) # will fail if we reach the EOF unexpectedly
except StopIteration:
return
- tag, value = line.split(b' ', 1)
- if tag == b'filename':
+ tag, value = line.split(b" ", 1)
+ if tag == b"filename":
orig_filename = value
break
- yield BlameEntry(commits[hexsha],
- range(lineno, lineno + num_lines),
- safe_decode(orig_filename),
- range(orig_lineno, orig_lineno + num_lines))
+ yield BlameEntry(
+ commits[hexsha],
+ range(lineno, lineno + num_lines),
+ safe_decode(orig_filename),
+ range(orig_lineno, orig_lineno + num_lines),
+ )
- def blame(self, rev: Union[str, HEAD], file: str, incremental: bool = False, **kwargs: Any
- ) -> List[List[Commit | List[str | bytes] | None]] | Iterator[BlameEntry] | None:
+ def blame(
+ self, rev: Union[str, HEAD], file: str, incremental: bool = False, **kwargs: Any
+ ) -> List[List[Commit | List[str | bytes] | None]] | Iterator[BlameEntry] | None:
"""The blame information for the given file at the given revision.
:param rev: revision specifier, see git-rev-parse for viable options.
@@ -886,7 +987,9 @@ class Repo(object):
if incremental:
return self.blame_incremental(rev, file, **kwargs)
- data: bytes = self.git.blame(rev, '--', file, p=True, stdout_as_string=False, **kwargs)
+ data: bytes = self.git.blame(
+ rev, "--", file, p=True, stdout_as_string=False, **kwargs
+ )
commits: Dict[str, Commit] = {}
blames: List[List[Commit | List[str | bytes] | None]] = []
@@ -909,7 +1012,7 @@ class Repo(object):
try:
line_str = line_bytes.rstrip().decode(defenc)
except UnicodeDecodeError:
- firstpart = ''
+ firstpart = ""
parts = []
is_binary = True
else:
@@ -929,10 +1032,10 @@ class Repo(object):
# another line of blame with the same data
digits = parts[-1].split(" ")
if len(digits) == 3:
- info = {'id': firstpart}
+ info = {"id": firstpart}
blames.append([None, []])
- elif info['id'] != firstpart:
- info = {'id': firstpart}
+ elif info["id"] != firstpart:
+ info = {"id": firstpart}
blames.append([commits.get(firstpart), []])
# END blame data initialization
else:
@@ -948,17 +1051,17 @@ class Repo(object):
# committer-time 1192271832
# committer-tz -0700 - IGNORED BY US
role = m.group(0)
- if role == 'author':
- if firstpart.endswith('-mail'):
+ if role == "author":
+ if firstpart.endswith("-mail"):
info["author_email"] = parts[-1]
- elif firstpart.endswith('-time'):
+ elif firstpart.endswith("-time"):
info["author_date"] = int(parts[-1])
elif role == firstpart:
info["author"] = parts[-1]
- elif role == 'committer':
- if firstpart.endswith('-mail'):
+ elif role == "committer":
+ if firstpart.endswith("-mail"):
info["committer_email"] = parts[-1]
- elif firstpart.endswith('-time'):
+ elif firstpart.endswith("-time"):
info["committer_date"] = int(parts[-1])
elif role == firstpart:
info["committer"] = parts[-1]
@@ -968,21 +1071,27 @@ class Repo(object):
# filename lib/grit.rb
# summary add Blob
# <and rest>
- if firstpart.startswith('filename'):
- info['filename'] = parts[-1]
- elif firstpart.startswith('summary'):
- info['summary'] = parts[-1]
- elif firstpart == '':
+ if firstpart.startswith("filename"):
+ info["filename"] = parts[-1]
+ elif firstpart.startswith("summary"):
+ info["summary"] = parts[-1]
+ elif firstpart == "":
if info:
- sha = info['id']
+ sha = info["id"]
c = commits.get(sha)
if c is None:
- c = Commit(self, hex_to_bin(sha),
- author=Actor._from_string(f"{info['author']} {info['author_email']}"),
- authored_date=info['author_date'],
- committer=Actor._from_string(
- f"{info['committer']} {info['committer_email']}"),
- committed_date=info['committer_date'])
+ c = Commit(
+ self,
+ hex_to_bin(sha),
+ author=Actor._from_string(
+ f"{info['author']} {info['author_email']}"
+ ),
+ authored_date=info["author_date"],
+ committer=Actor._from_string(
+ f"{info['committer']} {info['committer_email']}"
+ ),
+ committed_date=info["committer_date"],
+ )
commits[sha] = c
blames[-1][0] = c
# END if commit objects needs initial creation
@@ -990,7 +1099,7 @@ class Repo(object):
if blames[-1][1] is not None:
line: str | bytes
if not is_binary:
- if line_str and line_str[0] == '\t':
+ if line_str and line_str[0] == "\t":
line_str = line_str[1:]
line = line_str
else:
@@ -1001,16 +1110,22 @@ class Repo(object):
# the last line we have seen.
blames[-1][1].append(line)
- info = {'id': sha}
+ info = {"id": sha}
# END if we collected commit info
# END distinguish filename,summary,rest
# END distinguish author|committer vs filename,summary,rest
# END distinguish hexsha vs other information
return blames
- @ classmethod
- def init(cls, path: Union[PathLike, None] = None, mkdir: bool = True, odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
- expand_vars: bool = True, **kwargs: Any) -> 'Repo':
+ @classmethod
+ def init(
+ cls,
+ path: Union[PathLike, None] = None,
+ mkdir: bool = True,
+ odbt: Type[GitCmdObjectDB] = GitCmdObjectDB,
+ expand_vars: bool = True,
+ **kwargs: Any,
+ ) -> "Repo":
"""Initialize a git repository at the given path if specified
:param path:
@@ -1047,12 +1162,20 @@ class Repo(object):
git.init(**kwargs)
return cls(path, odbt=odbt)
- @ classmethod
- def _clone(cls, git: 'Git', url: PathLike, path: PathLike, odb_default_type: Type[GitCmdObjectDB],
- progress: Union['RemoteProgress', 'UpdateProgress', Callable[..., 'RemoteProgress'], None] = None,
- multi_options: Optional[List[str]] = None, **kwargs: Any
- ) -> 'Repo':
- odbt = kwargs.pop('odbt', odb_default_type)
+ @classmethod
+ def _clone(
+ cls,
+ git: "Git",
+ url: PathLike,
+ path: PathLike,
+ odb_default_type: Type[GitCmdObjectDB],
+ progress: Union[
+ "RemoteProgress", "UpdateProgress", Callable[..., "RemoteProgress"], None
+ ] = None,
+ multi_options: Optional[List[str]] = None,
+ **kwargs: Any,
+ ) -> "Repo":
+ odbt = kwargs.pop("odbt", odb_default_type)
# when pathlib.Path or other classbased path is passed
if not isinstance(path, str):
@@ -1064,23 +1187,36 @@ class Repo(object):
# becomes::
# git clone --bare /cygwin/d/foo.git /cygwin/d/C:\\Work
#
- clone_path = (Git.polish_url(path)
- if Git.is_cygwin() and 'bare' in kwargs
- else path)
- sep_dir = kwargs.get('separate_git_dir')
+ clone_path = (
+ Git.polish_url(path) if Git.is_cygwin() and "bare" in kwargs else path
+ )
+ sep_dir = kwargs.get("separate_git_dir")
if sep_dir:
- kwargs['separate_git_dir'] = Git.polish_url(sep_dir)
+ kwargs["separate_git_dir"] = Git.polish_url(sep_dir)
multi = None
if multi_options:
- multi = shlex.split(' '.join(multi_options))
- proc = git.clone(multi, Git.polish_url(str(url)), clone_path, with_extended_output=True, as_process=True,
- v=True, universal_newlines=True, **add_progress(kwargs, git, progress))
+ multi = shlex.split(" ".join(multi_options))
+ proc = git.clone(
+ multi,
+ Git.polish_url(str(url)),
+ clone_path,
+ with_extended_output=True,
+ as_process=True,
+ v=True,
+ universal_newlines=True,
+ **add_progress(kwargs, git, progress),
+ )
if progress:
- handle_process_output(proc, None, to_progress_instance(progress).new_message_handler(),
- finalize_process, decode_streams=False)
+ handle_process_output(
+ proc,
+ None,
+ to_progress_instance(progress).new_message_handler(),
+ finalize_process,
+ decode_streams=False,
+ )
else:
(stdout, stderr) = proc.communicate()
- cmdline = getattr(proc, 'args', '')
+ cmdline = getattr(proc, "args", "")
cmdline = remove_password_if_present(cmdline)
log.debug("Cmd(%s)'s unused stdout: %s", cmdline, stdout)
@@ -1089,7 +1225,11 @@ class Repo(object):
# our git command could have a different working dir than our actual
# environment, hence we prepend its working dir if required
if not osp.isabs(path):
- path = osp.join(git._working_dir, path) if git._working_dir is not None else path
+ path = (
+ osp.join(git._working_dir, path)
+ if git._working_dir is not None
+ else path
+ )
repo = cls(path, odbt=odbt)
@@ -1103,12 +1243,17 @@ class Repo(object):
# sure
if repo.remotes:
with repo.remotes[0].config_writer as writer:
- writer.set_value('url', Git.polish_url(repo.remotes[0].url))
+ writer.set_value("url", Git.polish_url(repo.remotes[0].url))
# END handle remote repo
return repo
- def clone(self, path: PathLike, progress: Optional[Callable] = None,
- multi_options: Optional[List[str]] = None, **kwargs: Any) -> 'Repo':
+ def clone(
+ self,
+ path: PathLike,
+ progress: Optional[Callable] = None,
+ multi_options: Optional[List[str]] = None,
+ **kwargs: Any,
+ ) -> "Repo":
"""Create a clone from this repository.
:param path: is the full path of the new repo (traditionally ends with ./<name>.git).
@@ -1123,12 +1268,26 @@ class Repo(object):
* All remaining keyword arguments are given to the git-clone command
:return: ``git.Repo`` (the newly cloned repo)"""
- return self._clone(self.git, self.common_dir, path, type(self.odb), progress, multi_options, **kwargs)
-
- @ classmethod
- def clone_from(cls, url: PathLike, to_path: PathLike, progress: Optional[Callable] = None,
- env: Optional[Mapping[str, str]] = None,
- multi_options: Optional[List[str]] = None, **kwargs: Any) -> 'Repo':
+ return self._clone(
+ self.git,
+ self.common_dir,
+ path,
+ type(self.odb),
+ progress,
+ multi_options,
+ **kwargs,
+ )
+
+ @classmethod
+ def clone_from(
+ cls,
+ url: PathLike,
+ to_path: PathLike,
+ progress: Optional[Callable] = None,
+ env: Optional[Mapping[str, str]] = None,
+ multi_options: Optional[List[str]] = None,
+ **kwargs: Any,
+ ) -> "Repo":
"""Create a clone from the given URL
:param url: valid git url, see http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS
@@ -1146,10 +1305,17 @@ class Repo(object):
git = cls.GitCommandWrapperType(os.getcwd())
if env is not None:
git.update_environment(**env)
- return cls._clone(git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs)
-
- def archive(self, ostream: Union[TextIO, BinaryIO], treeish: Optional[str] = None,
- prefix: Optional[str] = None, **kwargs: Any) -> Repo:
+ return cls._clone(
+ git, url, to_path, GitCmdObjectDB, progress, multi_options, **kwargs
+ )
+
+ def archive(
+ self,
+ ostream: Union[TextIO, BinaryIO],
+ treeish: Optional[str] = None,
+ prefix: Optional[str] = None,
+ **kwargs: Any,
+ ) -> Repo:
"""Archive the tree at the given revision.
:param ostream: file compatible stream object to which the archive will be written as bytes
@@ -1166,10 +1332,10 @@ class Repo(object):
:return: self"""
if treeish is None:
treeish = self.head.commit
- if prefix and 'prefix' not in kwargs:
- kwargs['prefix'] = prefix
- kwargs['output_stream'] = ostream
- path = kwargs.pop('path', [])
+ if prefix and "prefix" not in kwargs:
+ kwargs["prefix"] = prefix
+ kwargs["output_stream"] = ostream
+ path = kwargs.pop("path", [])
path = cast(Union[PathLike, List[PathLike], Tuple[PathLike, ...]], path)
if not isinstance(path, (tuple, list)):
path = [path]
@@ -1186,7 +1352,7 @@ class Repo(object):
if self.bare:
return False
if self.working_tree_dir:
- return osp.isfile(osp.join(self.working_tree_dir, '.git'))
+ return osp.isfile(osp.join(self.working_tree_dir, ".git"))
else:
return False # or raise Error?
@@ -1194,7 +1360,7 @@ class Repo(object):
def __repr__(self) -> str:
clazz = self.__class__
- return '<%s.%s %r>' % (clazz.__module__, clazz.__name__, self.git_dir)
+ return "<%s.%s %r>" % (clazz.__module__, clazz.__name__, self.git_dir)
def currently_rebasing_on(self) -> Commit | None:
"""
diff --git a/git/repo/fun.py b/git/repo/fun.py
index 74c0657d..03f9cabb 100644
--- a/git/repo/fun.py
+++ b/git/repo/fun.py
@@ -31,8 +31,17 @@ if TYPE_CHECKING:
# ----------------------------------------------------------------------------
-__all__ = ('rev_parse', 'is_git_dir', 'touch', 'find_submodule_git_dir', 'name_to_object', 'short_to_long', 'deref_tag',
- 'to_commit', 'find_worktree_git_dir')
+__all__ = (
+ "rev_parse",
+ "is_git_dir",
+ "touch",
+ "find_submodule_git_dir",
+ "name_to_object",
+ "short_to_long",
+ "deref_tag",
+ "to_commit",
+ "find_worktree_git_dir",
+)
def touch(filename: str) -> str:
@@ -41,8 +50,8 @@ def touch(filename: str) -> str:
return filename
-def is_git_dir(d: 'PathLike') -> bool:
- """ This is taken from the git setup.c:is_git_directory
+def is_git_dir(d: "PathLike") -> bool:
+ """This is taken from the git setup.c:is_git_directory
function.
@throws WorkTreeRepositoryUnsupported if it sees a worktree directory. It's quite hacky to do that here,
@@ -50,20 +59,23 @@ def is_git_dir(d: 'PathLike') -> bool:
There is the unlikely danger to throw if we see directories which just look like a worktree dir,
but are none."""
if osp.isdir(d):
- if (osp.isdir(osp.join(d, 'objects')) or 'GIT_OBJECT_DIRECTORY' in os.environ) \
- and osp.isdir(osp.join(d, 'refs')):
- headref = osp.join(d, 'HEAD')
- return osp.isfile(headref) or \
- (osp.islink(headref) and
- os.readlink(headref).startswith('refs'))
- elif (osp.isfile(osp.join(d, 'gitdir')) and
- osp.isfile(osp.join(d, 'commondir')) and
- osp.isfile(osp.join(d, 'gitfile'))):
+ if (
+ osp.isdir(osp.join(d, "objects")) or "GIT_OBJECT_DIRECTORY" in os.environ
+ ) and osp.isdir(osp.join(d, "refs")):
+ headref = osp.join(d, "HEAD")
+ return osp.isfile(headref) or (
+ osp.islink(headref) and os.readlink(headref).startswith("refs")
+ )
+ elif (
+ osp.isfile(osp.join(d, "gitdir"))
+ and osp.isfile(osp.join(d, "commondir"))
+ and osp.isfile(osp.join(d, "gitfile"))
+ ):
raise WorkTreeRepositoryUnsupported(d)
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)
@@ -73,16 +85,16 @@ def find_worktree_git_dir(dotgit: 'PathLike') -> Optional[str]:
return None
try:
- lines = open(dotgit, 'r').readlines()
- for key, value in [line.strip().split(': ') for line in lines]:
- if key == 'gitdir':
+ lines = open(dotgit, "r").readlines()
+ for key, value in [line.strip().split(": ") for line in lines]:
+ if key == "gitdir":
return value
except ValueError:
pass
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
@@ -94,7 +106,7 @@ def find_submodule_git_dir(d: 'PathLike') -> Optional['PathLike']:
# it's probably not a file
pass
else:
- if content.startswith('gitdir: '):
+ if content.startswith("gitdir: "):
path = content[8:]
if Git.is_cygwin():
@@ -107,7 +119,7 @@ def find_submodule_git_dir(d: 'PathLike') -> Optional['PathLike']:
return None
-def short_to_long(odb: 'GitCmdObjectDB', hexsha: str) -> Optional[bytes]:
+def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]:
""":return: long hexadecimal sha1 from the given less-than-40 byte hexsha
or None if no candidate could be found.
:param hexsha: hexsha with less than 40 byte"""
@@ -118,8 +130,9 @@ def short_to_long(odb: 'GitCmdObjectDB', hexsha: str) -> Optional[bytes]:
# END exception handling
-def name_to_object(repo: 'Repo', name: str, return_ref: bool = False
- ) -> Union[SymbolicReference, 'Commit', 'TagObject', 'Blob', 'Tree']:
+def name_to_object(
+ repo: "Repo", name: str, return_ref: bool = False
+) -> Union[SymbolicReference, "Commit", "TagObject", "Blob", "Tree"]:
"""
:return: object specified by the given name, hexshas ( short and long )
as well as references are supported
@@ -141,7 +154,14 @@ def name_to_object(repo: 'Repo', name: str, return_ref: bool = False
# if we couldn't find an object for what seemed to be a short hexsha
# try to find it as reference anyway, it could be named 'aaa' for instance
if hexsha is None:
- for base in ('%s', 'refs/%s', 'refs/tags/%s', 'refs/heads/%s', 'refs/remotes/%s', 'refs/remotes/%s/HEAD'):
+ for base in (
+ "%s",
+ "refs/%s",
+ "refs/tags/%s",
+ "refs/heads/%s",
+ "refs/remotes/%s",
+ "refs/remotes/%s/HEAD",
+ ):
try:
hexsha = SymbolicReference.dereference_recursive(repo, base % name)
if return_ref:
@@ -166,7 +186,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:
@@ -177,9 +197,9 @@ def deref_tag(tag: 'Tag') -> 'TagObject':
return tag
-def to_commit(obj: Object) -> Union['Commit', 'TagObject']:
+def to_commit(obj: Object) -> Union["Commit", "TagObject"]:
"""Convert the given object to a commit if possible and return it"""
- if obj.type == 'tag':
+ if obj.type == "tag":
obj = deref_tag(obj)
if obj.type != "commit":
@@ -188,7 +208,7 @@ def to_commit(obj: Object) -> Union['Commit', 'TagObject']:
return obj
-def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
+def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]:
"""
:return: Object at the given revision, either Commit, Tag, Tree or Blob
:param rev: git-rev-parse compatible revision specification as string, please see
@@ -199,12 +219,12 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
:raise IndexError: If invalid reflog index is specified"""
# colon search mode ?
- if rev.startswith(':/'):
+ if rev.startswith(":/"):
# colon search mode
raise NotImplementedError("commit by message search ( regex )")
# END handle search
- obj: Union[Commit_ish, 'Reference', None] = None
+ obj: Union[Commit_ish, "Reference", None] = None
ref = None
output_type = "commit"
start = 0
@@ -223,8 +243,10 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
if start == 0:
ref = repo.head.ref
else:
- if token == '@':
- ref = cast('Reference', name_to_object(repo, rev[:start], return_ref=True))
+ if token == "@":
+ ref = cast(
+ "Reference", name_to_object(repo, rev[:start], return_ref=True)
+ )
else:
obj = cast(Commit_ish, name_to_object(repo, rev[:start]))
# END handle token
@@ -233,38 +255,38 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
assert obj is not None
if ref is not None:
- obj = cast('Commit', ref.commit)
+ obj = cast("Commit", ref.commit)
# END handle ref
# END initialize obj on first token
start += 1
# try to parse {type}
- if start < lr and rev[start] == '{':
- end = rev.find('}', start)
+ if start < lr and rev[start] == "{":
+ end = rev.find("}", start)
if end == -1:
raise ValueError("Missing closing brace to define type in %s" % rev)
- output_type = rev[start + 1:end] # exclude brace
+ output_type = rev[start + 1 : end] # exclude brace
# handle type
- if output_type == 'commit':
+ if output_type == "commit":
pass # default
- elif output_type == 'tree':
+ elif output_type == "tree":
try:
obj = cast(Commit_ish, obj)
obj = to_commit(obj).tree
except (AttributeError, ValueError):
- pass # error raised later
+ pass # error raised later
# END exception handling
- elif output_type in ('', 'blob'):
- obj = cast('TagObject', obj)
- if obj and obj.type == 'tag':
+ elif output_type in ("", "blob"):
+ obj = cast("TagObject", obj)
+ if obj and obj.type == "tag":
obj = deref_tag(obj)
else:
# cannot do anything for non-tags
pass
# END handle tag
- elif token == '@':
+ elif token == "@":
# try single int
assert ref is not None, "Require Reference to access reflog"
revlog_index = None
@@ -274,7 +296,9 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
except ValueError as e:
# TODO: Try to parse the other date options, using parse_date
# maybe
- raise NotImplementedError("Support for additional @{...} modes not implemented") from e
+ raise NotImplementedError(
+ "Support for additional @{...} modes not implemented"
+ ) from e
# END handle revlog index
try:
@@ -286,17 +310,22 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha))
# make it pass the following checks
- output_type = ''
+ output_type = ""
else:
- raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev))
+ raise ValueError(
+ "Invalid output type: %s ( in %s )" % (output_type, rev)
+ )
# END handle output type
# empty output types don't require any specific type, its just about dereferencing tags
if output_type and obj and obj.type != output_type:
- raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type))
+ raise ValueError(
+ "Could not accommodate requested object type %r, got %s"
+ % (output_type, obj.type)
+ )
# END verify output type
- start = end + 1 # skip brace
+ start = end + 1 # skip brace
parsed_to = start
continue
# END parse type
@@ -348,7 +377,8 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
except (IndexError, AttributeError) as e:
raise BadName(
f"Invalid revision spec '{rev}' - not enough "
- f"parent commits to reach '{token}{int(num)}'") from e
+ f"parent commits to reach '{token}{int(num)}'"
+ ) from e
# END exception handling
# END parse loop
@@ -362,6 +392,9 @@ def rev_parse(repo: 'Repo', rev: str) -> Union['Commit', 'Tag', 'Tree', 'Blob']:
raise ValueError("Revision specifier could not be parsed: %s" % rev)
if parsed_to != lr:
- raise ValueError("Didn't consume complete rev spec %s, consumed part: %s" % (rev, rev[:parsed_to]))
+ raise ValueError(
+ "Didn't consume complete rev spec %s, consumed part: %s"
+ % (rev, rev[:parsed_to])
+ )
return obj
diff --git a/git/types.py b/git/types.py
index 7f44ba24..24df887a 100644
--- a/git/types.py
+++ b/git/types.py
@@ -4,14 +4,38 @@
import os
import sys
-from typing import (Callable, Dict, NoReturn, Sequence, Tuple, Union, Any, Iterator, # noqa: F401
- NamedTuple, TYPE_CHECKING, TypeVar) # noqa: F401
+from typing import (
+ Callable,
+ Dict,
+ NoReturn,
+ Sequence,
+ Tuple,
+ Union,
+ Any,
+ Iterator, # noqa: F401
+ NamedTuple,
+ TYPE_CHECKING,
+ TypeVar,
+) # noqa: F401
if sys.version_info[:2] >= (3, 8):
- from typing import Final, Literal, SupportsIndex, TypedDict, Protocol, runtime_checkable # noqa: F401
+ from typing import (
+ Final,
+ Literal,
+ SupportsIndex,
+ TypedDict,
+ Protocol,
+ runtime_checkable,
+ ) # noqa: F401
else:
- from typing_extensions import (Final, Literal, SupportsIndex, # noqa: F401
- TypedDict, Protocol, runtime_checkable) # noqa: F401
+ from typing_extensions import (
+ Final,
+ Literal,
+ SupportsIndex, # noqa: F401
+ TypedDict,
+ Protocol,
+ runtime_checkable,
+ ) # noqa: F401
# if sys.version_info[:2] >= (3, 10):
# from typing import TypeGuard # noqa: F401
@@ -28,18 +52,19 @@ elif sys.version_info[:2] >= (3, 9):
if TYPE_CHECKING:
from git.repo import Repo
from git.objects import Commit, Tree, TagObject, Blob
+
# from git.refs import SymbolicReference
TBD = Any
-_T = TypeVar('_T')
+_T = TypeVar("_T")
-Tree_ish = Union['Commit', 'Tree']
-Commit_ish = Union['Commit', 'TagObject', 'Blob', 'Tree']
-Lit_commit_ish = Literal['commit', 'tag', 'blob', 'tree']
+Tree_ish = Union["Commit", "Tree"]
+Commit_ish = Union["Commit", "TagObject", "Blob", "Tree"]
+Lit_commit_ish = Literal["commit", "tag", "blob", "tree"]
# Config_levels ---------------------------------------------------------
-Lit_config_levels = Literal['system', 'global', 'user', 'repository']
+Lit_config_levels = Literal["system", "global", "user", "repository"]
# def is_config_level(inp: str) -> TypeGuard[Lit_config_levels]:
@@ -47,12 +72,16 @@ Lit_config_levels = Literal['system', 'global', 'user', 'repository']
# return inp in ("system", "user", "global", "repository")
-ConfigLevels_Tup = Tuple[Literal['system'], Literal['user'], Literal['global'], Literal['repository']]
+ConfigLevels_Tup = Tuple[
+ Literal["system"], Literal["user"], Literal["global"], Literal["repository"]
+]
-#-----------------------------------------------------------------------------------
+# -----------------------------------------------------------------------------------
-def assert_never(inp: NoReturn, raise_error: bool = True, exc: Union[Exception, None] = None) -> None:
+def assert_never(
+ inp: NoReturn, raise_error: bool = True, exc: Union[Exception, None] = None
+) -> None:
"""For use in exhaustive checking of literal or Enum in if/else chain.
Should only be reached if all members not handled OR attempt to pass non-members through chain.
@@ -63,7 +92,9 @@ def assert_never(inp: NoReturn, raise_error: bool = True, exc: Union[Exception,
"""
if raise_error:
if exc is None:
- raise ValueError(f"An unhandled Literal ({inp}) in an if/else chain was found")
+ raise ValueError(
+ f"An unhandled Literal ({inp}) in an if/else chain was found"
+ )
else:
raise exc
else:
@@ -90,7 +121,7 @@ class HSH_TD(TypedDict):
@runtime_checkable
class Has_Repo(Protocol):
- repo: 'Repo'
+ repo: "Repo"
@runtime_checkable
diff --git a/git/util.py b/git/util.py
index 0711265a..edc8750d 100644
--- a/git/util.py
+++ b/git/util.py
@@ -26,9 +26,26 @@ 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, )
+from typing import (
+ Any,
+ AnyStr,
+ BinaryIO,
+ Callable,
+ Dict,
+ Generator,
+ IO,
+ Iterator,
+ List,
+ Optional,
+ Pattern,
+ Sequence,
+ Tuple,
+ TypeVar,
+ Union,
+ cast,
+ TYPE_CHECKING,
+ overload,
+)
import pathlib
@@ -37,14 +54,25 @@ if TYPE_CHECKING:
from git.repo.base import Repo
from git.config import GitConfigParser, SectionConstraint
from git import Git
+
# from git.objects.base import IndexObject
-from .types import (Literal, SupportsIndex, Protocol, runtime_checkable, # because behind py version guards
- PathLike, HSH_TD, Total_TD, Files_TD, # aliases
- Has_id_attribute)
+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)
+T_IterableObj = TypeVar(
+ "T_IterableObj", bound=Union["IterableObj", "Has_id_attribute"], covariant=True
+)
# So IterableList[Head] is subtype of IterableList[IterableObj]
# ---------------------------------------------------------------------
@@ -52,14 +80,14 @@ T_IterableObj = TypeVar('T_IterableObj', bound=Union['IterableObj', 'Has_id_attr
from gitdb.util import ( # NOQA @IgnorePep8
make_sha,
- LockedFD, # @UnusedImport
- file_contents_ro, # @UnusedImport
- file_contents_ro_filepath, # @UnusedImport
- LazyMixin, # @UnusedImport
- to_hex_sha, # @UnusedImport
- to_bin_sha, # @UnusedImport
- bin_to_hex, # @UnusedImport
- hex_to_bin, # @UnusedImport
+ LockedFD, # @UnusedImport
+ file_contents_ro, # @UnusedImport
+ file_contents_ro_filepath, # @UnusedImport
+ LazyMixin, # @UnusedImport
+ to_hex_sha, # @UnusedImport
+ to_bin_sha, # @UnusedImport
+ bin_to_hex, # @UnusedImport
+ hex_to_bin, # @UnusedImport
)
@@ -67,11 +95,26 @@ from gitdb.util import ( # NOQA @IgnorePep8
# Handle once test-cases are back up and running.
# Most of these are unused here, but are for use by git-python modules so these
# don't see gitdb all the time. Flake of course doesn't like it.
-__all__ = ["stream_copy", "join_path", "to_native_path_linux",
- "join_path_native", "Stats", "IndexFileSHA1Writer", "IterableObj", "IterableList",
- "BlockingLockFile", "LockFile", 'Actor', 'get_user_id', 'assure_directory_exists',
- 'RemoteProgress', 'CallableRemoteProgress', 'rmtree', 'unbare_repo',
- 'HIDE_WINDOWS_KNOWN_ERRORS']
+__all__ = [
+ "stream_copy",
+ "join_path",
+ "to_native_path_linux",
+ "join_path_native",
+ "Stats",
+ "IndexFileSHA1Writer",
+ "IterableObj",
+ "IterableList",
+ "BlockingLockFile",
+ "LockFile",
+ "Actor",
+ "get_user_id",
+ "assure_directory_exists",
+ "RemoteProgress",
+ "CallableRemoteProgress",
+ "rmtree",
+ "unbare_repo",
+ "HIDE_WINDOWS_KNOWN_ERRORS",
+]
log = logging.getLogger(__name__)
@@ -81,12 +124,14 @@ log = logging.getLogger(__name__)
#: We need an easy way to see if Appveyor TCs start failing,
#: so the errors marked with this var are considered "acknowledged" ones, awaiting remedy,
#: till then, we wish to hide them.
-HIDE_WINDOWS_KNOWN_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_KNOWN_ERRORS', True)
-HIDE_WINDOWS_FREEZE_ERRORS = is_win and os.environ.get('HIDE_WINDOWS_FREEZE_ERRORS', True)
+HIDE_WINDOWS_KNOWN_ERRORS = is_win and os.environ.get("HIDE_WINDOWS_KNOWN_ERRORS", True)
+HIDE_WINDOWS_FREEZE_ERRORS = is_win and os.environ.get(
+ "HIDE_WINDOWS_FREEZE_ERRORS", True
+)
# { Utility Methods
-T = TypeVar('T')
+T = TypeVar("T")
def unbare_repo(func: Callable[..., T]) -> Callable[..., T]:
@@ -96,11 +141,14 @@ def unbare_repo(func: Callable[..., T]) -> Callable[..., T]:
from .exc import InvalidGitRepositoryError
@wraps(func)
- def wrapper(self: 'Remote', *args: Any, **kwargs: Any) -> T:
+ def wrapper(self: "Remote", *args: Any, **kwargs: Any) -> T:
if self.repo.bare:
- raise InvalidGitRepositoryError("Method '%s' cannot operate on bare repositories" % func.__name__)
+ raise InvalidGitRepositoryError(
+ "Method '%s' cannot operate on bare repositories" % func.__name__
+ )
# END bare method
return func(self, *args, **kwargs)
+
# END wrapper
return wrapper
@@ -131,7 +179,10 @@ def rmtree(path: PathLike) -> None:
except Exception as ex:
if HIDE_WINDOWS_KNOWN_ERRORS:
from unittest import SkipTest
- raise SkipTest("FIXME: fails with: PermissionError\n {}".format(ex)) from ex
+
+ raise SkipTest(
+ "FIXME: fails with: PermissionError\n {}".format(ex)
+ ) from ex
raise
return shutil.rmtree(path, False, onerror)
@@ -145,7 +196,9 @@ def rmfile(path: PathLike) -> None:
os.remove(path)
-def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * 1024) -> int:
+def stream_copy(
+ source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * 1024
+) -> int:
"""Copy all data from the source stream into the destination stream in chunks
of size chunk_size
@@ -169,24 +222,25 @@ def join_path(a: PathLike, *p: PathLike) -> PathLike:
b = str(b)
if not b:
continue
- if b.startswith('/'):
+ if b.startswith("/"):
path += b[1:]
- elif path == '' or path.endswith('/'):
+ elif path == "" or path.endswith("/"):
path += b
else:
- path += '/' + b
+ path += "/" + b
# END for each path token to add
return path
if is_win:
+
def to_native_path_windows(path: PathLike) -> PathLike:
path = str(path)
- return path.replace('/', '\\')
+ return path.replace("/", "\\")
def to_native_path_linux(path: PathLike) -> str:
path = str(path)
- return path.replace('\\', '/')
+ return path.replace("\\", "/")
__all__.append("to_native_path_windows")
to_native_path = to_native_path_windows
@@ -222,10 +276,14 @@ def assure_directory_exists(path: PathLike, is_file: bool = False) -> bool:
def _get_exe_extensions() -> Sequence[str]:
- PATHEXT = os.environ.get('PATHEXT', None)
- return tuple(p.upper() for p in PATHEXT.split(os.pathsep)) if PATHEXT \
- else ('.BAT', 'COM', '.EXE') if is_win \
- else ('')
+ PATHEXT = os.environ.get("PATHEXT", None)
+ return (
+ tuple(p.upper() for p in PATHEXT.split(os.pathsep))
+ if PATHEXT
+ else (".BAT", "COM", ".EXE")
+ if is_win
+ else ("")
+ )
def py_where(program: str, path: Optional[PathLike] = None) -> List[str]:
@@ -233,9 +291,15 @@ def py_where(program: str, path: Optional[PathLike] = None) -> List[str]:
winprog_exts = _get_exe_extensions()
def is_exec(fpath: str) -> bool:
- return osp.isfile(fpath) and os.access(fpath, os.X_OK) and (
- os.name != 'nt' or not winprog_exts or any(fpath.upper().endswith(ext)
- for ext in winprog_exts))
+ return (
+ osp.isfile(fpath)
+ and os.access(fpath, os.X_OK)
+ and (
+ os.name != "nt"
+ or not winprog_exts
+ or any(fpath.upper().endswith(ext) for ext in winprog_exts)
+ )
+ )
progs = []
if not path:
@@ -244,7 +308,7 @@ def py_where(program: str, path: Optional[PathLike] = None) -> List[str]:
folder = folder.strip('"')
if folder:
exe_path = osp.join(folder, program)
- for f in [exe_path] + ['%s%s' % (exe_path, e) for e in winprog_exts]:
+ for f in [exe_path] + ["%s%s" % (exe_path, e) for e in winprog_exts]:
if is_exec(f):
progs.append(f)
return progs
@@ -264,38 +328,26 @@ def _cygexpath(drive: Optional[str], path: str) -> str:
else:
p = cygpath(p)
elif drive:
- p = '/cygdrive/%s/%s' % (drive.lower(), p)
+ p = "/cygdrive/%s/%s" % (drive.lower(), p)
p_str = str(p) # ensure it is a str and not AnyPath
- return p_str.replace('\\', '/')
+ return p_str.replace("\\", "/")
_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\\([^\\]+)\\([^\\]+)(?:\\(.*))?"),
- (lambda server, share, rest_path: '//%s/%s/%s' % (server, share, rest_path.replace('\\', '/'))),
- False
- ),
-
- (re.compile(r"\\\\\?\\(\w):[/\\](.*)"),
- (_cygexpath),
- False
- ),
-
- (re.compile(r"(\w):[/\\](.*)"),
- (_cygexpath),
- False
- ),
-
- (re.compile(r"file:(.*)", re.I),
- (lambda rest_path: rest_path),
- True
- ),
-
- (re.compile(r"(\w{2,}:.*)"), # remote URL, do nothing
- (lambda url: url),
- False
- ),
+ (
+ re.compile(r"\\\\\?\\UNC\\([^\\]+)\\([^\\]+)(?:\\(.*))?"),
+ (
+ lambda server, share, rest_path: "//%s/%s/%s"
+ % (server, share, rest_path.replace("\\", "/"))
+ ),
+ False,
+ ),
+ (re.compile(r"\\\\\?\\(\w):[/\\](.*)"), (_cygexpath), False),
+ (re.compile(r"(\w):[/\\](.*)"), (_cygexpath), False),
+ (re.compile(r"file:(.*)", re.I), (lambda rest_path: rest_path), True),
+ (re.compile(r"(\w{2,}:.*)"), (lambda url: url), False), # remote URL, do nothing
)
@@ -303,7 +355,7 @@ def cygpath(path: str) -> str:
"""Use :meth:`git.cmd.Git.polish_url()` instead, that works on any environment."""
path = str(path) # ensure is str and not AnyPath.
# Fix to use Paths when 3.5 dropped. or to be just str if only for urls?
- if not path.startswith(('/cygdrive', '//')):
+ if not path.startswith(("/cygdrive", "//")):
for regex, parser, recurse in _cygpath_parsers:
match = regex.match(path)
if match:
@@ -325,9 +377,9 @@ def decygpath(path: PathLike) -> str:
m = _decygpath_regex.match(path)
if m:
drive, rest_path = m.groups()
- path = '%s:%s' % (drive.upper(), rest_path or '')
+ path = "%s:%s" % (drive.upper(), rest_path or "")
- return path.replace('/', '\\')
+ return path.replace("/", "\\")
#: Store boolean flags denoting if a specific Git executable
@@ -363,14 +415,15 @@ def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool:
git_dir = osp.dirname(res[0]) if res else ""
# Just a name given, not a real path.
- uname_cmd = osp.join(git_dir, 'uname')
- process = subprocess.Popen([uname_cmd], stdout=subprocess.PIPE,
- universal_newlines=True)
+ uname_cmd = osp.join(git_dir, "uname")
+ process = subprocess.Popen(
+ [uname_cmd], stdout=subprocess.PIPE, universal_newlines=True
+ )
uname_out, _ = process.communicate()
- #retcode = process.poll()
- is_cygwin = 'CYGWIN' in uname_out
+ # retcode = process.poll()
+ is_cygwin = "CYGWIN" in uname_out
except Exception as ex:
- log.debug('Failed checking if running in CYGWIN due to: %r', ex)
+ log.debug("Failed checking if running in CYGWIN due to: %r", ex)
_is_cygwin_cache[git_executable] = is_cygwin
return is_cygwin
@@ -381,7 +434,9 @@ def get_user_id() -> str:
return "%s@%s" % (getpass.getuser(), platform.node())
-def finalize_process(proc: Union[subprocess.Popen, 'Git.AutoInterrupt'], **kwargs: Any) -> None:
+def finalize_process(
+ proc: Union[subprocess.Popen, "Git.AutoInterrupt"], **kwargs: Any
+) -> None:
"""Wait for the process (clone, fetch, pull or push) and handle its errors accordingly"""
# TODO: No close proc-streams??
proc.wait(**kwargs)
@@ -398,13 +453,15 @@ def expand_path(p: PathLike, expand_vars: bool = ...) -> str:
...
-def expand_path(p: Union[None, PathLike], expand_vars: bool = True) -> Optional[PathLike]:
+def expand_path(
+ p: Union[None, PathLike], expand_vars: bool = True
+) -> Optional[PathLike]:
if isinstance(p, pathlib.Path):
return p.resolve()
try:
p = osp.expanduser(p) # type: ignore
if expand_vars:
- p = osp.expandvars(p) # type: ignore
+ p = osp.expandvars(p) # type: ignore
return osp.normpath(osp.abspath(p)) # type: ignore
except Exception:
return None
@@ -430,11 +487,9 @@ def remove_password_if_present(cmdline: Sequence[str]) -> List[str]:
continue
if url.password is not None:
- url = url._replace(
- netloc=url.netloc.replace(url.password, "*****"))
+ url = url._replace(netloc=url.netloc.replace(url.password, "*****"))
if url.username is not None:
- url = url._replace(
- netloc=url.netloc.replace(url.username, "*****"))
+ url = url._replace(netloc=url.netloc.replace(url.username, "*****"))
new_cmdline[index] = urlunsplit(url)
except ValueError:
# This is not a valid URL
@@ -452,19 +507,31 @@ class RemoteProgress(object):
Handler providing an interface to parse progress information emitted by git-push
and git-fetch and to dispatch callbacks allowing subclasses to react to the progress.
"""
+
_num_op_codes: int = 9
- BEGIN, END, COUNTING, COMPRESSING, WRITING, RECEIVING, RESOLVING, FINDING_SOURCES, CHECKING_OUT = \
- [1 << x for x in range(_num_op_codes)]
+ (
+ BEGIN,
+ END,
+ COUNTING,
+ COMPRESSING,
+ WRITING,
+ RECEIVING,
+ RESOLVING,
+ FINDING_SOURCES,
+ CHECKING_OUT,
+ ) = [1 << x for x in range(_num_op_codes)]
STAGE_MASK = BEGIN | END
OP_MASK = ~STAGE_MASK
- DONE_TOKEN = 'done.'
- TOKEN_SEPARATOR = ', '
+ DONE_TOKEN = "done."
+ TOKEN_SEPARATOR = ", "
- __slots__ = ('_cur_line',
- '_seen_ops',
- 'error_lines', # Lines that started with 'error:' or 'fatal:'.
- 'other_lines') # Lines not denoting progress (i.e.g. push-infos).
+ __slots__ = (
+ "_cur_line",
+ "_seen_ops",
+ "error_lines", # Lines that started with 'error:' or 'fatal:'.
+ "other_lines",
+ ) # Lines not denoting progress (i.e.g. push-infos).
re_op_absolute = re.compile(r"(remote: )?([\w\s]+):\s+()(\d+)()(.*)")
re_op_relative = re.compile(r"(remote: )?([\w\s]+):\s+(\d+)% \((\d+)/(\d+)\)(.*)")
@@ -486,13 +553,13 @@ class RemoteProgress(object):
# Compressing objects: 50% (1/2)
# Compressing objects: 100% (2/2)
# Compressing objects: 100% (2/2), done.
- if isinstance(line, bytes): # mypy argues about ternary assignment
- line_str = line.decode('utf-8')
+ if isinstance(line, bytes): # mypy argues about ternary assignment
+ line_str = line.decode("utf-8")
else:
line_str = line
self._cur_line = line_str
- if self._cur_line.startswith(('error:', 'fatal:')):
+ if self._cur_line.startswith(("error:", "fatal:")):
self.error_lines.append(self._cur_line)
return
@@ -531,13 +598,13 @@ class RemoteProgress(object):
op_code |= self.COMPRESSING
elif op_name == "Writing objects":
op_code |= self.WRITING
- elif op_name == 'Receiving objects':
+ elif op_name == "Receiving objects":
op_code |= self.RECEIVING
- elif op_name == 'Resolving deltas':
+ elif op_name == "Resolving deltas":
op_code |= self.RESOLVING
- elif op_name == 'Finding sources':
+ elif op_name == "Finding sources":
op_code |= self.FINDING_SOURCES
- elif op_name == 'Checking out files':
+ elif op_name == "Checking out files":
op_code |= self.CHECKING_OUT
else:
# Note: On windows it can happen that partial lines are sent
@@ -559,28 +626,32 @@ class RemoteProgress(object):
# END begin opcode
if message is None:
- message = ''
+ message = ""
# END message handling
message = message.strip()
if message.endswith(self.DONE_TOKEN):
op_code |= self.END
- message = message[:-len(self.DONE_TOKEN)]
+ message = message[: -len(self.DONE_TOKEN)]
# END end message handling
message = message.strip(self.TOKEN_SEPARATOR)
- self.update(op_code,
- cur_count and float(cur_count),
- max_count and float(max_count),
- message)
+ self.update(
+ op_code,
+ cur_count and float(cur_count),
+ max_count and float(max_count),
+ message,
+ )
def new_message_handler(self) -> Callable[[str], None]:
"""
:return:
a progress handler suitable for handle_process_output(), passing lines on to this Progress
handler in a suitable format"""
+
def handler(line: AnyStr) -> None:
return self._parse_progress_line(line.rstrip())
+
# end
return handler
@@ -588,8 +659,13 @@ class RemoteProgress(object):
"""Called whenever a line could not be understood and was therefore dropped."""
pass
- def update(self, op_code: int, cur_count: Union[str, float], max_count: Union[str, float, None] = None,
- message: str = '',) -> None:
+ def update(
+ self,
+ op_code: int,
+ cur_count: Union[str, float],
+ max_count: Union[str, float, None] = None,
+ message: str = "",
+ ) -> None:
"""Called whenever the progress changes
:param op_code:
@@ -618,7 +694,8 @@ class RemoteProgress(object):
class CallableRemoteProgress(RemoteProgress):
"""An implementation forwarding updates to any callable"""
- __slots__ = ('_callable')
+
+ __slots__ = "_callable"
def __init__(self, fn: Callable) -> None:
self._callable = fn
@@ -632,9 +709,10 @@ class Actor(object):
"""Actors hold information about a person acting on the repository. They
can be committers and authors or anything with a name and an email as
mentioned in the git log entries."""
+
# PRECOMPILED REGEX
- name_only_regex = re.compile(r'<(.*)>')
- name_email_regex = re.compile(r'(.*) <(.*?)>')
+ name_only_regex = re.compile(r"<(.*)>")
+ name_email_regex = re.compile(r"(.*) <(.*?)>")
# ENVIRONMENT VARIABLES
# read when creating new commits
@@ -644,10 +722,10 @@ class Actor(object):
env_committer_email = "GIT_COMMITTER_EMAIL"
# CONFIGURATION KEYS
- conf_name = 'name'
- conf_email = 'email'
+ conf_name = "name"
+ conf_email = "email"
- __slots__ = ('name', 'email')
+ __slots__ = ("name", "email")
def __init__(self, name: Optional[str], email: Optional[str]) -> None:
self.name = name
@@ -669,13 +747,13 @@ class Actor(object):
return '<git.Actor "%s <%s>">' % (self.name, self.email)
@classmethod
- def _from_string(cls, string: str) -> 'Actor':
+ def _from_string(cls, string: str) -> "Actor":
"""Create an Actor from a string.
:param string: is the string, which is expected to be in regular git format
John Doe <jdoe@example.com>
- :return: Actor """
+ :return: Actor"""
m = cls.name_email_regex.search(string)
if m:
name, email = m.groups()
@@ -690,9 +768,13 @@ class Actor(object):
# END handle name/email matching
@classmethod
- def _main_actor(cls, env_name: str, env_email: str,
- config_reader: Union[None, 'GitConfigParser', 'SectionConstraint'] = None) -> 'Actor':
- actor = Actor('', '')
+ def _main_actor(
+ cls,
+ env_name: str,
+ env_email: str,
+ config_reader: Union[None, "GitConfigParser", "SectionConstraint"] = None,
+ ) -> "Actor":
+ actor = Actor("", "")
user_id = None # We use this to avoid multiple calls to getpass.getuser()
def default_email() -> str:
@@ -702,17 +784,19 @@ class Actor(object):
return user_id
def default_name() -> str:
- return default_email().split('@')[0]
+ return default_email().split("@")[0]
- for attr, evar, cvar, default in (('name', env_name, cls.conf_name, default_name),
- ('email', env_email, cls.conf_email, default_email)):
+ for attr, evar, cvar, default in (
+ ("name", env_name, cls.conf_name, default_name),
+ ("email", env_email, cls.conf_email, default_email),
+ ):
try:
val = os.environ[evar]
setattr(actor, attr, val)
except KeyError:
if config_reader is not None:
try:
- val = config_reader.get('user', cvar)
+ val = config_reader.get("user", cvar)
except Exception:
val = default()
setattr(actor, attr, val)
@@ -724,7 +808,9 @@ class Actor(object):
return actor
@classmethod
- def committer(cls, config_reader: Union[None, 'GitConfigParser', 'SectionConstraint'] = None) -> 'Actor':
+ def committer(
+ cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint"] = None
+ ) -> "Actor":
"""
:return: Actor instance corresponding to the configured committer. It behaves
similar to the git implementation, such that the environment will override
@@ -732,10 +818,14 @@ class Actor(object):
generated
:param config_reader: ConfigReader to use to retrieve the values from in case
they are not set in the environment"""
- return cls._main_actor(cls.env_committer_name, cls.env_committer_email, config_reader)
+ return cls._main_actor(
+ cls.env_committer_name, cls.env_committer_email, config_reader
+ )
@classmethod
- def author(cls, config_reader: Union[None, 'GitConfigParser', 'SectionConstraint'] = None) -> 'Actor':
+ def author(
+ cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint"] = None
+ ) -> "Actor":
"""Same as committer(), but defines the main author. It may be specified in the environment,
but defaults to the committer"""
return cls._main_actor(cls.env_author_name, cls.env_author_email, config_reader)
@@ -767,6 +857,7 @@ class Stats(object):
In addition to the items in the stat-dict, it features additional information::
files = number of changed files as int"""
+
__slots__ = ("total", "files")
def __init__(self, total: Total_TD, files: Dict[PathLike, Files_TD]):
@@ -774,30 +865,30 @@ class Stats(object):
self.files = files
@classmethod
- def _list_from_string(cls, repo: 'Repo', text: str) -> 'Stats':
+ def _list_from_string(cls, repo: "Repo", text: str) -> "Stats":
"""Create a Stat object from output retrieved by git-diff.
:return: git.Stat"""
- hsh: HSH_TD = {'total': {'insertions': 0,
- 'deletions': 0,
- 'lines': 0,
- 'files': 0},
- 'files': {}
- }
+ hsh: HSH_TD = {
+ "total": {"insertions": 0, "deletions": 0, "lines": 0, "files": 0},
+ "files": {},
+ }
for line in text.splitlines():
(raw_insertions, raw_deletions, filename) = line.split("\t")
- insertions = raw_insertions != '-' and int(raw_insertions) or 0
- deletions = raw_deletions != '-' and int(raw_deletions) or 0
- hsh['total']['insertions'] += insertions
- hsh['total']['deletions'] += deletions
- hsh['total']['lines'] += insertions + deletions
- hsh['total']['files'] += 1
- files_dict: Files_TD = {'insertions': insertions,
- 'deletions': deletions,
- 'lines': insertions + deletions}
- hsh['files'][filename.strip()] = files_dict
- return Stats(hsh['total'], hsh['files'])
+ insertions = raw_insertions != "-" and int(raw_insertions) or 0
+ deletions = raw_deletions != "-" and int(raw_deletions) or 0
+ hsh["total"]["insertions"] += insertions
+ hsh["total"]["deletions"] += deletions
+ hsh["total"]["lines"] += insertions + deletions
+ hsh["total"]["files"] += 1
+ files_dict: Files_TD = {
+ "insertions": insertions,
+ "deletions": deletions,
+ "lines": insertions + deletions,
+ }
+ hsh["files"][filename.strip()] = files_dict
+ return Stats(hsh["total"], hsh["files"])
class IndexFileSHA1Writer(object):
@@ -809,6 +900,7 @@ class IndexFileSHA1Writer(object):
Only useful to the indexfile
:note: Based on the dulwich project"""
+
__slots__ = ("f", "sha1")
def __init__(self, f: IO) -> None:
@@ -841,6 +933,7 @@ class LockFile(object):
As we are a utility class to be derived from, we only use protected methods.
Locks will automatically be released on destruction"""
+
__slots__ = ("_file_path", "_owns_lock")
def __init__(self, file_path: PathLike) -> None:
@@ -867,8 +960,10 @@ class LockFile(object):
return
lock_file = self._lock_file_path()
if osp.isfile(lock_file):
- raise IOError("Lock for file %r did already exist, delete %r in case the lock is illegal" %
- (self._file_path, lock_file))
+ raise IOError(
+ "Lock for file %r did already exist, delete %r in case the lock is illegal"
+ % (self._file_path, lock_file)
+ )
try:
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
@@ -909,9 +1004,15 @@ class BlockingLockFile(LockFile):
:note: If the directory containing the lock was removed, an exception will
be raised during the blocking period, preventing hangs as the lock
can never be obtained."""
+
__slots__ = ("_check_interval", "_max_block_time")
- def __init__(self, file_path: PathLike, check_interval_s: float = 0.3, max_block_time_s: int = maxsize) -> None:
+ def __init__(
+ self,
+ file_path: PathLike,
+ check_interval_s: float = 0.3,
+ max_block_time_s: int = maxsize,
+ ) -> None:
"""Configure the instance
:param check_interval_s:
@@ -937,13 +1038,18 @@ class BlockingLockFile(LockFile):
# readable anymore, raise an exception
curtime = time.time()
if not osp.isdir(osp.dirname(self._lock_file_path())):
- msg = "Directory containing the lockfile %r was not readable anymore after waiting %g seconds" % (
- self._lock_file_path(), curtime - starttime)
+ msg = (
+ "Directory containing the lockfile %r was not readable anymore after waiting %g seconds"
+ % (self._lock_file_path(), curtime - starttime)
+ )
raise IOError(msg) from e
# END handle missing directory
if curtime >= maxtime:
- msg = "Waited %g seconds for lock at %r" % (maxtime - starttime, self._lock_file_path())
+ msg = "Waited %g seconds for lock at %r" % (
+ maxtime - starttime,
+ self._lock_file_path(),
+ )
raise IOError(msg) from e
# END abort if we wait too long
time.sleep(self._check_interval)
@@ -971,12 +1077,13 @@ class IterableList(List[T_IterableObj]):
A prefix can be specified which is to be used in case the id returned by the
items always contains a prefix that does not matter to the user, so it
can be left out."""
- __slots__ = ('_id_attr', '_prefix')
- def __new__(cls, id_attr: str, prefix: str = '') -> 'IterableList[IterableObj]':
+ __slots__ = ("_id_attr", "_prefix")
+
+ def __new__(cls, id_attr: str, prefix: str = "") -> "IterableList[IterableObj]":
return super(IterableList, cls).__new__(cls)
- def __init__(self, id_attr: str, prefix: str = '') -> None:
+ def __init__(self, id_attr: str, prefix: str = "") -> None:
self._id_attr = id_attr
self._prefix = prefix
@@ -1008,7 +1115,9 @@ class IterableList(List[T_IterableObj]):
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"
+ assert isinstance(
+ index, (int, str, slice)
+ ), "Index of IterableList should be an int or str"
if isinstance(index, int):
return list.__getitem__(self, index)
@@ -1018,12 +1127,16 @@ class IterableList(List[T_IterableObj]):
try:
return getattr(self, index)
except AttributeError as e:
- raise IndexError("No item found with id %r" % (self._prefix + index)) from e
+ raise IndexError(
+ "No item found with id %r" % (self._prefix + index)
+ ) from e
# END handle getattr
def __delitem__(self, index: Union[SupportsIndex, int, slice, str]) -> None:
- assert isinstance(index, (int, str)), "Index of IterableList should be an int or str"
+ assert isinstance(
+ index, (int, str)
+ ), "Index of IterableList should be an int or str"
delindex = cast(int, index)
if not isinstance(index, int):
@@ -1043,27 +1156,31 @@ class IterableList(List[T_IterableObj]):
class IterableClassWatcher(type):
- """ Metaclass that watches """
+ """Metaclass that watches"""
+
def __init__(cls, name: str, bases: Tuple, clsdict: Dict) -> None:
for base in bases:
if type(base) == IterableClassWatcher:
- warnings.warn(f"GitPython Iterable subclassed by {name}. "
- "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)
+ warnings.warn(
+ f"GitPython Iterable subclassed by {name}. "
+ "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(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"
@classmethod
- def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> Any:
+ def list_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Any:
"""
Deprecated, use IterableObj instead.
Find all items of this type - subclasses can specify args and kwargs differently.
@@ -1078,7 +1195,7 @@ class Iterable(metaclass=IterableClassWatcher):
return out_list
@classmethod
- def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> 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"""
@@ -1096,7 +1213,9 @@ class IterableObj(Protocol):
_id_attribute_: str
@classmethod
- def list_items(cls, repo: 'Repo', *args: Any, **kwargs: Any) -> IterableList[T_IterableObj]:
+ def list_items(
+ cls, repo: "Repo", *args: Any, **kwargs: Any
+ ) -> IterableList[T_IterableObj]:
"""
Find all items of this type - subclasses can specify args and kwargs differently.
If no args are given, subclasses are obliged to return all items if no additional
@@ -1111,13 +1230,15 @@ class IterableObj(Protocol):
@classmethod
@abstractmethod
- def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any
- ) -> Iterator[T_IterableObj]: # Iterator[T_IterableObj]:
+ def iter_items(
+ cls, repo: "Repo", *args: Any, **kwargs: Any
+ ) -> 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