From 9b5531bc0e53e03acff70a8c8250115d2d3c9fa4 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 11:34:08 -0500 Subject: [PATCH 01/11] Fix typos and further clarify Git.refresh docstring This further improves the text previously introduced in #1829 and improved in #1844. --- git/cmd.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 5282acfdc..75244536d 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -379,8 +379,8 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: :note: The top-level :func:`git.refresh` should be preferred because it calls this method and may also update other state accordingly. - :note: There are three different ways to specify what command refreshing causes - to be uses for git: + :note: There are three different ways to specify the command that refreshing + causes to be used for git: 1. Pass no *path* argument and do not set the ``GIT_PYTHON_GIT_EXECUTABLE`` environment variable. The command name ``git`` is used. It is looked up @@ -394,9 +394,9 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: in each command run. Setting ``GIT_PYTHON_GIT_EXECUTABLE`` to ``git`` has the same effect as not setting it. - 3. Pass a *path* argument. This path, if not absolute, it immediately + 3. Pass a *path* argument. This path, if not absolute, is immediately resolved, relative to the current directory. This resolution occurs at - the time of the refresh, and when git commands are run, they are run with + the time of the refresh. When git commands are run, they are run using that previously resolved path. If a *path* argument is passed, the ``GIT_PYTHON_GIT_EXECUTABLE`` environment variable is not consulted. From c0cd8a8df51224bb56235696e92c96608b51f9e7 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 12:06:25 -0500 Subject: [PATCH 02/11] Clarify comment on shell case in _safer_popen_windows --- git/cmd.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 75244536d..9f886b53f 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -250,9 +250,10 @@ def _safer_popen_windows( # When not using a shell, the current process does the search in a CreateProcessW # API call, so the variable must be set in our environment. With a shell, this is # unnecessary, in versions where https://github.com/python/cpython/issues/101283 is - # patched. If not, in the rare case the ComSpec environment variable is unset, the - # shell is searched for unsafely. Setting NoDefaultCurrentDirectoryInExePath in all - # cases, as here, is simpler and protects against that. (The "1" can be any value.) + # patched. If that is unpatched, then in the rare case the ComSpec environment + # variable is unset, the search for the shell itself is unsafe. Setting + # NoDefaultCurrentDirectoryInExePath in all cases, as is done here, is simpler and + # protects against that. (As above, the "1" can be any value.) with patch_env("NoDefaultCurrentDirectoryInExePath", "1"): return Popen( command, From afd943a96dbf1097546a6777d66827fa875db61c Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 12:25:26 -0500 Subject: [PATCH 03/11] Tweak message about GIT_PYTHON_REFRESH for 80-column terminals The fragment of the refresh failure message (which is often written to a terminal) about the effect of setting GIT_PYTHON_REFRESH to control refesh failure reporting had previously been formatted with a maximum line length of 79 columns--in the message itself, not the code for the message--so that it would fit in a traditional 80 column wide terminal. This remains one of the popular widths to set for terminal windows in a GUI, so it seems worthwhile to preserve. In 3a6e3ef (#1815), I had inadvertently made the line one character too long for that; at 80 columns, it would cause the newline to be written at the start of the next line, creating an unsightly extra line break. This is pretty minor, but what seems to me like an equally good alternative wording avoids it, so this commit shortens the wording. --- git/cmd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 9f886b53f..a850956ec 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -479,7 +479,7 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: This initial message can be silenced or aggravated in the future by setting the $%s environment variable. Use one of the following values: - %s: for no message or exception - - %s: for a warning message (logged at level CRITICAL, displayed by default) + - %s: for a warning message (logging level CRITICAL, displayed by default) - %s: for a raised exception Example: @@ -509,7 +509,7 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: Use only the following values: - %s: for no message or exception - - %s: for a warning message (logged at level CRITICAL, displayed by default) + - %s: for a warning message (logging level CRITICAL, displayed by default) - %s: for a raised exception """ ) From e08066cda1f5e76fb7a4ef795bfe435f88584e99 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 15:05:40 -0500 Subject: [PATCH 04/11] Revise docstrings in git.__init__ and git.cmd Mainly for formatting. --- git/__init__.py | 18 ++-- git/cmd.py | 212 +++++++++++++++++++++++++++++------------------- 2 files changed, 139 insertions(+), 91 deletions(-) diff --git a/git/__init__.py b/git/__init__.py index 0ea7821ac..bc97a6eff 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -122,18 +122,22 @@ def refresh(path: Optional[PathLike] = None) -> None: """Convenience method for setting the git executable path. - :param path: Optional path to the Git executable. If not absolute, it is resolved + :param path: + Optional path to the Git executable. If not absolute, it is resolved immediately, relative to the current directory. - :note: The *path* parameter is usually omitted and cannot be used to specify a - custom command whose location is looked up in a path search on each call. See + :note: + The *path* parameter is usually omitted and cannot be used to specify a custom + command whose location is looked up in a path search on each call. See :meth:`Git.refresh` for details on how to achieve this. - :note: This calls :meth:`Git.refresh` and sets other global configuration according - to the effect of doing so. As such, this function should usually be used instead - of using :meth:`Git.refresh` or :meth:`FetchInfo.refresh` directly. + :note: + This calls :meth:`Git.refresh` and sets other global configuration according to + the effect of doing so. As such, this function should usually be used instead of + using :meth:`Git.refresh` or :meth:`FetchInfo.refresh` directly. - :note: This function is called automatically, with no arguments, at import time. + :note: + This function is called automatically, with no arguments, at import time. """ global GIT_OK GIT_OK = False diff --git a/git/cmd.py b/git/cmd.py index a850956ec..1206641dc 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -111,17 +111,27 @@ def handle_process_output( This function returns once the finalizer returns. - :param process: :class:`subprocess.Popen` instance - :param stdout_handler: f(stdout_line_string), or None - :param stderr_handler: f(stderr_line_string), or None - :param finalizer: f(proc) - wait for proc to finish + :param process: + :class:`subprocess.Popen` instance + + :param stdout_handler: + f(stdout_line_string), or None + + :param stderr_handler: + f(stderr_line_string), or None + + :param finalizer: + f(proc) - wait for proc to finish + :param decode_streams: - Assume stdout/stderr streams are binary and decode them before pushing - their contents to handlers. - Set it to False if ``universal_newlines == True`` (then streams are in - text mode) or if decoding must happen later (i.e. for Diffs). + Assume stdout/stderr streams are binary and decode them before pushing their + contents to handlers. + Set this to False if ``universal_newlines == True`` (then streams are in text + mode) or if decoding must happen later (i.e. for :class:`git.diff.Diff`s). + :param kill_after_timeout: float or None, Default = None + To specify a timeout in seconds for the git command, after which the process should be killed. """ @@ -147,7 +157,7 @@ def pump_stream( except Exception as ex: _logger.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 + # 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 finally: stream.close() @@ -196,11 +206,11 @@ def pump_stream( "error: process killed because it timed out." f" kill_after_timeout={kill_after_timeout} seconds" ) if not decode_streams and isinstance(p_stderr, BinaryIO): - # Assume stderr_handler needs binary input. + # Assume stderr_handler needs binary input. error_str = cast(str, error_str) error_str = error_str.encode() - # We ignore typing on the next line because mypy does not like - # the way we inferred that stderr takes str or bytes. + # We ignore typing on the next line because mypy does not like the way + # we inferred that stderr takes str or bytes. stderr_handler(error_str) # type: ignore if finalizer: @@ -225,11 +235,13 @@ def _safer_popen_windows( the subprocess, which GitPython usually sets to a repository working tree, can itself be searched automatically by the shell. This wrapper covers all those cases. - :note: This currently works by setting the ``NoDefaultCurrentDirectoryInExePath`` + :note: + This currently works by setting the ``NoDefaultCurrentDirectoryInExePath`` environment variable during subprocess creation. It also takes care of passing Windows-specific process creation flags, but that is unrelated to path search. - :note: The current implementation contains a race condition on :attr:`os.environ`. + :note: + The current implementation contains a race condition on :attr:`os.environ`. GitPython isn't thread-safe, but a program using it on one thread should ideally be able to mutate :attr:`os.environ` on another, without unpredictable results. See comments in https://github.com/gitpython-developers/GitPython/pull/1650. @@ -297,10 +309,11 @@ class Git: g.init() # calls 'git init' program rval = g.ls_files() # calls 'git ls-files' program - ``Debugging`` - Set the GIT_PYTHON_TRACE environment variable print each invocation - of the command to stdout. - Set its value to 'full' to see details about the returned values. + Debugging: + + * Set the ``GIT_PYTHON_TRACE`` environment variable print each invocation of the + command to stdout. + * Set its value to ``full`` to see details about the returned values. """ __slots__ = ( @@ -361,10 +374,12 @@ def __setstate__(self, d: Dict[str, Any]) -> None: _refresh_env_var = "GIT_PYTHON_REFRESH" GIT_PYTHON_GIT_EXECUTABLE = None - """Provide the full path to the git executable. Otherwise it assumes git is in the path. + """Provide the full path to the git executable. Otherwise it assumes git is in the + executable search path. - :note: The git executable is actually found during the refresh step in - the top level :mod:`__init__`. It can also be changed by explicitly calling + :note: + The git executable is actually found during the refresh step in the top level + :mod:`__init__`. It can also be changed by explicitly calling :func:`git.refresh`. """ @@ -374,34 +389,38 @@ def __setstate__(self, d: Dict[str, Any]) -> None: def refresh(cls, path: Union[None, PathLike] = None) -> bool: """This gets called by the refresh function (see the top level __init__). - :param path: Optional path to the git executable. If not absolute, it is - resolved immediately, relative to the current directory. (See note below.) + :param path: + Optional path to the git executable. If not absolute, it is resolved + immediately, relative to the current directory. (See note below.) - :note: The top-level :func:`git.refresh` should be preferred because it calls - this method and may also update other state accordingly. + :note: + The top-level :func:`git.refresh` should be preferred because it calls this + method and may also update other state accordingly. - :note: There are three different ways to specify the command that refreshing - causes to be used for git: + :note: + There are three different ways to specify the command that refreshing causes + to be used for git: - 1. Pass no *path* argument and do not set the ``GIT_PYTHON_GIT_EXECUTABLE`` + 1. Pass no `path` argument and do not set the ``GIT_PYTHON_GIT_EXECUTABLE`` environment variable. The command name ``git`` is used. It is looked up in a path search by the system, in each command run (roughly similar to how git is found when running ``git`` commands manually). This is usually the desired behavior. - 2. Pass no *path* argument but set the ``GIT_PYTHON_GIT_EXECUTABLE`` + 2. Pass no `path` argument but set the ``GIT_PYTHON_GIT_EXECUTABLE`` environment variable. The command given as the value of that variable is used. This may be a simple command or an arbitrary path. It is looked up in each command run. Setting ``GIT_PYTHON_GIT_EXECUTABLE`` to ``git`` has the same effect as not setting it. - 3. Pass a *path* argument. This path, if not absolute, is immediately + 3. Pass a `path` argument. This path, if not absolute, is immediately resolved, relative to the current directory. This resolution occurs at the time of the refresh. When git commands are run, they are run using - that previously resolved path. If a *path* argument is passed, the + that previously resolved path. If a `path` argument is passed, the ``GIT_PYTHON_GIT_EXECUTABLE`` environment variable is not consulted. - :note: Refreshing always sets the :attr:`Git.GIT_PYTHON_GIT_EXECUTABLE` class + :note: + Refreshing always sets the :attr:`Git.GIT_PYTHON_GIT_EXECUTABLE` class attribute, which can be read on the :class:`Git` class or any of its instances to check what command is used to run git. This attribute should not be confused with the related ``GIT_PYTHON_GIT_EXECUTABLE`` environment @@ -421,7 +440,7 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: cls._refresh_token = object() # Test if the new git executable path is valid. A GitCommandNotFound error is - # spawned by us. A PermissionError is spawned if the git executable cannot be + # raised by us. A PermissionError is raised if the git executable cannot be # executed for whatever reason. has_git = False try: @@ -453,7 +472,7 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: # On the first refresh (when GIT_PYTHON_GIT_EXECUTABLE is None) we only # are quiet, warn, or error depending on the GIT_PYTHON_REFRESH value. - # Determine what the user wants to happen during the initial refresh we + # Determine what the user wants to happen during the initial refresh. We # expect GIT_PYTHON_REFRESH to either be unset or be one of the # following values: # @@ -550,7 +569,7 @@ def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> str: @classmethod def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike: - """Remove any backslashes from urls to be written in config files. + """Remove any backslashes from URLs to be written in config files. Windows might create config files containing paths with backslashes, but git stops liking them as it will escape the backslashes. Hence we @@ -572,9 +591,9 @@ def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike: def check_unsafe_protocols(cls, url: str) -> None: """Check for unsafe protocols. - Apart from the usual protocols (http, git, ssh), - Git allows "remote helpers" that have the form ``::
``. - One of these helpers (``ext::``) can be used to invoke any arbitrary command. + Apart from the usual protocols (http, git, ssh), Git allows "remote helpers" + that have the form ``::
``. One of these helpers (``ext::``) + can be used to invoke any arbitrary command. See: @@ -592,11 +611,11 @@ def check_unsafe_protocols(cls, url: str) -> None: def check_unsafe_options(cls, options: List[str], unsafe_options: List[str]) -> None: """Check for unsafe options. - Some options that are passed to `git ` can be used to execute - arbitrary commands, this are blocked by default. + Some options that are passed to ``git `` can be used to execute + arbitrary commands. These are blocked by default. """ - # Options can be of the form `foo` or `--foo bar` `--foo=bar`, - # so we need to check if they start with "--foo" or if they are equal to "foo". + # Options can be of the form `foo`, `--foo bar`, or `--foo=bar`, so we need to + # check if they start with "--foo" or if they are equal to "foo". bare_unsafe_options = [option.lstrip("-") for option in unsafe_options] for option in options: for unsafe_option, bare_option in zip(unsafe_options, bare_unsafe_options): @@ -673,9 +692,15 @@ def __getattr__(self, attr: str) -> Any: 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. + :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"" @@ -725,8 +750,8 @@ def __init__(self, size: int, stream: IO[bytes]) -> None: self._size = size self._nbr = 0 # Number of bytes read. - # Special case: If the object is empty, has null bytes, get the - # final newline right away. + # Special case: If the object is empty, has null bytes, get the final + # newline right away. if size == 0: stream.read(1) # END handle empty streams @@ -745,7 +770,8 @@ def read(self, size: int = -1) -> bytes: data = self._stream.read(size) self._nbr += len(data) - # Check for depletion, read our final byte to make the stream usable by others. + # 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 # END finish reading @@ -820,7 +846,7 @@ def __init__(self, working_dir: Union[None, PathLike] = None): :param working_dir: Git directory we should work in. If None, we always work in the current directory as returned by :func:`os.getcwd`. - It is meant to be the working tree directory if available, or the + This is meant to be the working tree directory if available, or the ``.git`` directory in case of bare repositories. """ super().__init__() @@ -844,8 +870,8 @@ def __getattr__(self, name: str) -> Any: an object. :return: - Callable object that will execute call :meth:`_call_process` with - your arguments. + Callable object that will execute call :meth:`_call_process` with your + arguments. """ if name.startswith("_"): return super().__getattribute__(name) @@ -857,8 +883,8 @@ def set_persistent_git_options(self, **kwargs: Any) -> None: :param kwargs: A dict of keyword arguments. - These arguments are passed as in :meth:`_call_process`, but will be - passed to the git command rather than the subcommand. + These arguments are passed as in :meth:`_call_process`, but will be passed + to the git command rather than the subcommand. """ self._persistent_git_options = self.transform_kwargs(split_single_char_options=True, **kwargs) @@ -872,7 +898,7 @@ def working_dir(self) -> Union[None, PathLike]: def version_info(self) -> Tuple[int, ...]: """ :return: tuple with integers representing the major, minor and additional - version numbers as parsed from git version. Up to four fields are used. + version numbers as parsed from ``git version``. Up to four fields are used. This value is generated on demand and is cached. """ @@ -1021,8 +1047,8 @@ def execute( If True, default True, we open stdout on the created process. :param universal_newlines: - if True, pipes will be opened as text, and lines are split at - all known line endings. + If True, pipes will be opened as text, and lines are split at all known line + endings. :param shell: Whether to invoke commands through a shell (see `Popen(..., shell=True)`). @@ -1057,6 +1083,7 @@ def execute( * tuple(int(status), str(stdout), str(stderr)) if extended_output = True If output_stream is True, the stdout value will be your output stream: + * output_stream if extended_output = False * tuple(int(status), output_stream, str(stderr)) if extended_output = True @@ -1254,14 +1281,16 @@ def update_environment(self, **kwargs: Any) -> Dict[str, Union[str, None]]: values in a format that can be passed back into this function to revert the changes. - ``Examples``:: + Examples:: old_env = self.update_environment(PWD='/tmp') self.update_environment(**old_env) - :param kwargs: Environment variables to use for git processes + :param kwargs: + Environment variables to use for git processes - :return: Dict that maps environment variables to their old values + :return: + Dict that maps environment variables to their old values """ old_env = {} for key, value in kwargs.items(): @@ -1280,12 +1309,13 @@ def custom_environment(self, **kwargs: Any) -> Iterator[None]: """A context manager around the above :meth:`update_environment` method to restore the environment back to its previous state after operation. - ``Examples``:: + Examples:: with self.custom_environment(GIT_SSH='/bin/ssh_wrapper'): repo.remotes.origin.fetch() - :param kwargs: See :meth:`update_environment` + :param kwargs: + See :meth:`update_environment`. """ old_env = self.update_environment(**kwargs) try: @@ -1310,7 +1340,7 @@ def transform_kwarg(self, name: str, value: Any, split_single_char_options: bool return [] def transform_kwargs(self, split_single_char_options: bool = True, **kwargs: Any) -> List[str]: - """Transform Python style kwargs into git command line options.""" + """Transform Python-style kwargs into git command line options.""" args = [] for k, v in kwargs.items(): if isinstance(v, (list, tuple)): @@ -1339,7 +1369,8 @@ def __call__(self, **kwargs: Any) -> "Git": These arguments are passed as in :meth:`_call_process`, but will be passed to the git command rather than the subcommand. - ``Examples``:: + Examples:: + git(work_tree='/tmp').difftool() """ self._git_options = self.transform_kwargs(split_single_char_options=True, **kwargs) @@ -1369,23 +1400,25 @@ def _call_process( 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. + """Run the given git command with the specified arguments and return the result + as a string. :param method: - The command. Contained ``_`` characters will be converted to dashes, - such as in ``ls_files`` to call ``ls-files``. + The command. Contained ``_`` characters will be converted to hyphens, such + as in ``ls_files`` to call ``ls-files``. :param args: The list of arguments. If None is included, it will be pruned. - This allows your commands to call git more conveniently as None - is realized as non-existent. + This allows your commands to call git more conveniently, as None is realized + as non-existent. :param kwargs: Contains key-values for the following: + - The :meth:`execute()` kwds, as listed in :var:`execute_kwargs`. - "Command options" to be converted by :meth:`transform_kwargs`. - The `'insert_kwargs_after'` key which its value must match one of ``*args``. + It also contains any command options, to be appended after the matched arg. Examples:: @@ -1396,7 +1429,8 @@ def _call_process( git rev-list max-count 10 --header master - :return: Same as :meth:`execute`. + :return: + Same as :meth:`execute`. If no args are given, used :meth:`execute`'s default (especially ``as_process = False``, ``stdout_as_string = True``) and return str. """ @@ -1447,8 +1481,8 @@ def _parse_object_header(self, header_line: str) -> Tuple[str, str, int]: :return: (hex_sha, type_string, size_as_int) - :raise ValueError: If the header contains indication for an error due to - incorrect input sha + :raise ValueError: + If the header contains indication for an error due to incorrect input sha """ tokens = header_line.split() if len(tokens) != 3: @@ -1504,22 +1538,27 @@ def __get_object_header(self, cmd: "Git.AutoInterrupt", ref: AnyStr) -> Tuple[st 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 - the given ref. + """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 - once and reuses the command in subsequent calls. + :note: + The method will only suffer from the costs of command invocation once and + reuses the command in subsequent calls. - :return: (hexsha, type_string, size_as_int) + :return: + (hexsha, type_string, size_as_int) """ cmd = self._get_persistent_cmd("cat_file_header", "cat_file", batch_check=True) 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. + """Similar to :meth:`get_object_header`, but returns object data as well. + + :return: + (hexsha, type_string, size_as_int, data_string) - :return: (hexsha, type_string, size_as_int, data_string) - :note: Not threadsafe. + :note: + Not threadsafe. """ hexsha, typename, size, stream = self.stream_object_data(ref) data = stream.read(size) @@ -1527,10 +1566,14 @@ def get_object_data(self, ref: str) -> Tuple[str, str, int, bytes]: 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. + """Similar to :meth:`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! + :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! """ cmd = self._get_persistent_cmd("cat_file_all", "cat_file", batch=True) hexsha, typename, size = self.__get_object_header(cmd, ref) @@ -1542,7 +1585,8 @@ def clear_cache(self) -> "Git": Currently persistent commands will be interrupted. - :return: self + :return: + self """ for cmd in (self.cat_file_all, self.cat_file_header): if cmd: From 8bb882ef914ac7e39cdc93547d012f8503730849 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 15:07:33 -0500 Subject: [PATCH 05/11] Fix concurrency note for stream_object_data --- git/cmd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 1206641dc..eb6c235c7 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -1572,8 +1572,8 @@ def stream_object_data(self, ref: str) -> Tuple[str, str, int, "Git.CatFileConte (hexsha, type_string, size_as_int, stream) :note: - This method is not threadsafe. You need one independent Command instance per - thread to be safe! + This method is not threadsafe. You need one independent :class:`Git` + instance per thread to be safe! """ cmd = self._get_persistent_cmd("cat_file_all", "cat_file", batch=True) hexsha, typename, size = self.__get_object_header(cmd, ref) From ba878ef09228176c17112f90434c685f5535a98a Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 15:42:38 -0500 Subject: [PATCH 06/11] Reword partial_to_complete_sha_hex note The git.db.partial_to_complete_sha_hex docstring refers to "AmbiguousObjects", suggesting the existence of an AmbiguousObject type (or an AmbiguousObjects type). Since there appears to be no such type in GitPython (or in gitdb), this seems to have been written in reference to the condition expressed by the AmbiguousObjectName exception, which the note says is not currently able to be raised (such that BadObject is raised instead) in situations where it would apply. Since the connection to that exeception is already clear from context, this commit changes the wording to "ambiguous objects" to avoid being misread as a reference to a Python class of ambiguous objects. --- git/db.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/db.py b/git/db.py index 03b631084..1531d663c 100644 --- a/git/db.py +++ b/git/db.py @@ -59,7 +59,7 @@ def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes: :raise BadObject: :note: Currently we only raise :class:`BadObject` as git does not communicate - AmbiguousObjects separately. + ambiguous objects separately. """ try: hexsha, _typename, _size = self._git.get_object_header(partial_hexsha) From 39587475ff47ce3a7986f0a6f7cd29cbcdcf01bb Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 16:39:33 -0500 Subject: [PATCH 07/11] Update CommandError._msg documentation This converts it from a specially formatted comment to a docstring (#1734), rewords for clarity, and removes the mention of unicode, which appears to have been referring to the data type. (GitPython no longer supports Python 2, and unicode is not a separate type from str in Python 3, where instead str and bytes are different types.) --- git/exc.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/git/exc.py b/git/exc.py index 85113bc44..40fb8eea0 100644 --- a/git/exc.py +++ b/git/exc.py @@ -87,10 +87,13 @@ class CommandError(GitError): A non-empty list of argv comprising the command-line. """ - #: A unicode print-format with 2 `%s for `` and the rest, - #: e.g. - #: "'%s' failed%s" _msg = "Cmd('%s') failed%s" + """Format string with 2 ``%s`` for ```` and the rest. + + For example: ``"'%s' failed%s"`` + + Subclasses may override this attribute, provided it is still in this form. + """ def __init__( self, From f56e1ac5c5a548d5f74b355f34643053f9b0eb6c Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 17:00:02 -0500 Subject: [PATCH 08/11] Tweak code formatting in Remote._set_cache_ For slightly easier reading. --- git/remote.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/git/remote.py b/git/remote.py index 3ccac59de..5ae361097 100644 --- a/git/remote.py +++ b/git/remote.py @@ -575,7 +575,10 @@ def _set_cache_(self, attr: str) -> None: 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()._set_cache_(attr) From fa471fe7b58085e9676c23a74443460a5c1d6ed6 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 17:42:52 -0500 Subject: [PATCH 09/11] Fix up Remote.push docstring - Replace the goo.gl web shortlink with a Sphinx reference that displays and also (in built documentation) becomes a hyperlink to the documentation on the method being referred to. - Refer to a related class (which is in another module) as being in the module where it is defined, rather than in the top-level git module (where it also can be accessed because it is imported there, which is reasonable to do, but less clear documentation). - Tweak punctuation so the effect of passing None is a bit clearer. --- git/remote.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/git/remote.py b/git/remote.py index 5ae361097..df809a9ac 100644 --- a/git/remote.py +++ b/git/remote.py @@ -1075,13 +1075,14 @@ def push( :param progress: Can take one of many value types: - * None to discard progress information. + * None, to discard progress information. * A function (callable) that is called with the progress information. Signature: ``progress(op_code, cur_count, max_count=None, message='')``. - `Click here `__ for a description of all arguments - given to the function. - * An instance of a class derived from :class:`git.RemoteProgress` that - overrides the :meth:`~git.RemoteProgress.update` method. + See :meth:`RemoteProgress.update ` for a + description of all arguments given to the function. + * An instance of a class derived from :class:`~git.util.RemoteProgress` that + overrides the + :meth:`RemoteProgress.update ` method. :note: No further progress information is returned after push returns. From 1cd73ba118148e12b5dae7722efd5d86a640f64d Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 19:27:43 -0500 Subject: [PATCH 10/11] Revise docstrings in second-level modules Except for: - git.cmd, where docstrings were revised in e08066c. - git.types, where docstring changes may best be made together with changes to how imports are organized and documented, which seems not to be in the same scope as the changes in this commit. This change, as well as those in e08066c, are largely along the lines of #1725, with most revisions here being to docstrings and a few being to comments. The major differences between the kinds of docstring changes here and those ind #1725 are that the changes here push somewhat harder for consistency and apply some kinds of changes I was reluctant to apply widely in #1725: - Wrap all docstrings and comments to 88 columns, except for parts that are decisively clearer when not wrapped. Note that semi- paragraph changes represented as single newlines are still kept where meaningful, which is one reason this is not always the same effect as automatic wrapping would produce. - Avoid code formatting (double backticks) for headings that precede sections and code blocks. This was done enough that it seems to have been intentional, but it doesn't really have the right semantics, and the documentation is currently rendering in such a way (including on readthedocs.org) where removing that formatting seems clearly better. - References (single backticks with a role prefix) and code spans (double backticks) everywhere applicable, even in the first lines of docstrings. - Single-backticks around parameter names, with no role prefix. These were mostly either formatted that way or emphasized (with asterisks). This is one of the rare cases that I have used single backticks without a role prefix, which ordinarily should be avoided, but to get a role for references to a function's parameters within that function, a plugin would be needed. In the rare case that one function's docstring refers to another function's parameters by names those are double-backticked as code spans (and where applicable the name of the referred-to function is single-backticked with the :func: or :meth: role). - All sections, such as :param blah:, :note:, and :return:, now have a newline before any text in them. This was already often but far from always done, and the style was overall inconsistent. Of consistent approaches that are clear and easy to write, this is the simplest. It also seems to substantially improve readability, when taken together with... - Sections are always separated by a blank line, even if they are very short. - Essentially unlimited use of `~a.b.c`, where applicable, to refer and link to the documentation for a.b.c while showing the text "a" and revealing "a.b.c" on hover. I had previously somewhat limited my use of this tilde notation in case readers of the source code itself (where it is not rendered) weren't familiar with it, but at the cost of less consistency in when an entity was referred to. There remain a couple places in git.util where I do not do this because the explicit form `a `, which is equivalent, lined things up better and was thus easier to read. Those are the major differences between the approach taken here and in #1725, but not necessarily most of the changes done here (many of which are the same kinds of revisions as done there). Note that this commit only modifies some git/*.py files, and there are more git/**/*.py files that remain to be revised accordingly. --- git/compat.py | 15 +-- git/config.py | 153 ++++++++++++++++++----------- git/db.py | 9 +- git/diff.py | 64 +++++++----- git/exc.py | 9 +- git/remote.py | 262 ++++++++++++++++++++++++++++++++------------------ git/util.py | 188 ++++++++++++++++++++++-------------- 7 files changed, 439 insertions(+), 261 deletions(-) diff --git a/git/compat.py b/git/compat.py index 920e44b7f..3167cecdc 100644 --- a/git/compat.py +++ b/git/compat.py @@ -35,8 +35,9 @@ :attr:`sys.platform` checks explicitly, especially in cases where it matters which is used. -:note: ``is_win`` is ``False`` on Cygwin, but is often wrongly assumed ``True``. To - detect Cygwin, use ``sys.platform == "cygwin"``. +:note: + ``is_win`` is ``False`` on Cygwin, but is often wrongly assumed ``True``. To detect + Cygwin, use ``sys.platform == "cygwin"``. """ is_posix = os.name == "posix" @@ -46,9 +47,10 @@ :attr:`sys.platform` checks explicitly, especially in cases where it matters which is used. -:note: For POSIX systems, more detailed information is available in - :attr:`sys.platform`, while :attr:`os.name` is always ``"posix"`` on such systems, - including macOS (Darwin). +:note: + For POSIX systems, more detailed information is available in :attr:`sys.platform`, + while :attr:`os.name` is always ``"posix"`` on such systems, including macOS + (Darwin). """ is_darwin = sys.platform == "darwin" @@ -57,7 +59,8 @@ This is deprecated because it clearer to write out :attr:`os.name` or :attr:`sys.platform` checks explicitly. -:note: For macOS (Darwin), ``os.name == "posix"`` as in other Unix-like systems, while +:note: + For macOS (Darwin), ``os.name == "posix"`` as in other Unix-like systems, while ``sys.platform == "darwin"`. """ diff --git a/git/config.py b/git/config.py index 85f754197..b5cff01cd 100644 --- a/git/config.py +++ b/git/config.py @@ -73,11 +73,13 @@ class MetaParserBuilder(abc.ABCMeta): # noqa: B024 - """Utility class wrapping base-class methods into decorators that assure read-only properties.""" + """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": """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. + methods with a :func:`set_dirty_and_flush_changes` decorator in addition to + that. """ kmm = "_mutating_methods_" if kmm in clsdict: @@ -102,7 +104,8 @@ def __new__(cls, name: str, bases: Tuple, clsdict: Dict[str, Any]) -> "MetaParse def needs_values(func: Callable[..., _T]) -> Callable[..., _T]: - """Return a method for ensuring we read values (on demand) before we try to access them.""" + """Return a method for ensuring we read values (on demand) before we try to access + them.""" @wraps(func) def assure_data_present(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _T: @@ -116,7 +119,8 @@ def assure_data_present(self: "GitConfigParser", *args: Any, **kwargs: Any) -> _ def set_dirty_and_flush_changes(non_const_func: Callable[..., _T]) -> Callable[..., _T]: """Return a method that checks whether given non constant function may be called. - If so, the instance will be set dirty. Additionally, we flush the changes right to disk. + 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: @@ -136,7 +140,8 @@ class SectionConstraint(Generic[T_ConfigParser]): It supports all ConfigParser methods that operate on an option. - :note: If used as a context manager, will release the wrapped ConfigParser. + :note: + If used as a context manager, will release the wrapped ConfigParser. """ __slots__ = ("_config", "_section_name") @@ -171,8 +176,8 @@ def __getattr__(self, attr: str) -> Any: return super().__getattribute__(attr) def _call_config(self, method: str, *args: Any, **kwargs: Any) -> Any: - """Call the configuration at the given method which must take a section name - as first argument.""" + """Call the configuration at the given method which must take a section name as + first argument.""" return getattr(self._config, method)(self._section_name, *args, **kwargs) @property @@ -254,7 +259,8 @@ def get_config_path(config_level: Lit_config_levels) -> str: elif config_level == "repository": 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 + # Should not reach here. Will raise ValueError if does. Static typing will warn + # about missing elifs. assert_never( # type: ignore[unreachable] config_level, ValueError(f"Invalid configuration level: {config_level!r}"), @@ -264,14 +270,15 @@ def get_config_path(config_level: Lit_config_levels) -> str: class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder): """Implements specifics required to read git style configuration files. - This variation behaves much like the git.config command such that the configuration - will be read on demand based on the filepath given during initialization. + This variation behaves much like the ``git config`` command, such that the + configuration will be read on demand based on the filepath given during + initialization. The changes will automatically be written once the instance goes out of scope, but can be triggered manually as well. - The configuration file will be locked if you intend to change values preventing other - instances to write concurrently. + The configuration file will be locked if you intend to change values preventing + other instances to write concurrently. :note: The config is case-sensitive even when queried, hence section and option names @@ -301,7 +308,8 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder): del optvalueonly_source _mutating_methods_ = ("add_section", "remove_section", "remove_option", "set") - """List of RawConfigParser methods able to change the instance.""" + """Names of :class:`~configparser.RawConfigParser` methods able to change the + instance.""" def __init__( self, @@ -311,8 +319,8 @@ def __init__( 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. + """Initialize a configuration reader to read the given `file_or_files` and to + possibly allow changes to it by setting `read_only` False. :param file_or_files: A file path or file object, or a sequence of possibly more than one of them. @@ -385,7 +393,7 @@ def _acquire_lock(self) -> None: # END read-only check def __del__(self) -> None: - """Write pending changes if required and release locks""" + """Write pending changes if required and release locks.""" # NOTE: Only consistent in Python 2. self.release() @@ -397,10 +405,12 @@ def __exit__(self, *args: Any) -> None: self.release() def release(self) -> None: - """Flush changes and release the configuration write lock. This instance must not be used anymore afterwards. + """Flush changes and release the configuration write lock. This instance must + not be used anymore afterwards. - In Python 3, it's required to explicitly release locks and flush changes, as __del__ is not called - deterministically anymore.""" + In Python 3, it's required to explicitly release locks and flush changes, as + :meth:`__del__` is not called deterministically anymore. + """ # Checking for the lock here makes sure we do not raise during write() # in case an invalid parser was created who could not get a lock. if self.read_only or (self._lock and not self._lock._has_lock()): @@ -424,8 +434,9 @@ def optionxform(self, optionstr: str) -> str: return optionstr def _read(self, fp: Union[BufferedReader, IO[bytes]], fpname: str) -> None: - """Originally a direct copy of the Python 2.4 version of RawConfigParser._read, - to ensure it uses ordered dicts. + """Originally a direct copy of the Python 2.4 version of + :meth:`RawConfigParser._read `, to ensure it + uses ordered dicts. The ordering bug was fixed in Python 2.4, and dict itself keeps ordering since Python 3.7. This has some other changes, especially that it ignores initial @@ -525,7 +536,8 @@ def _has_includes(self) -> Union[bool, int]: def _included_paths(self) -> List[Tuple[str, str]]: """List all paths that must be included to configuration. - :return: The list of paths, where each path is a tuple of ``(option, value)``. + :return: + The list of paths, where each path is a tuple of ``(option, value)``. """ paths = [] @@ -577,8 +589,11 @@ def read(self) -> None: # type: ignore[override] This will ignore files that cannot be read, possibly leaving an empty configuration. - :return: Nothing - :raise IOError: If a file cannot be handled + :return: + Nothing + + :raise IOError: + If a file cannot be handled """ if self._is_initialized: return @@ -591,7 +606,7 @@ def read(self) -> None: # type: ignore[override] 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 ensure we have a copy of the paths to handle @@ -603,7 +618,8 @@ def read(self) -> None: # type: ignore[override] if hasattr(file_path, "seek"): # Must be a file-object. - file_path = cast(IO[bytes], file_path) # TODO: Replace with assert to narrow type, once sure. + # TODO: Replace cast with assert to narrow type, once sure. + file_path = cast(IO[bytes], file_path) self._read(file_path, file_path.name) else: # Assume a path if it is not a file-object. @@ -615,8 +631,8 @@ def read(self) -> None: # type: ignore[override] except IOError: continue - # Read includes and append those that we didn't handle yet. - # We expect all paths to be normalized and absolute (and will ensure that is the case). + # Read includes and append those that we didn't handle yet. We expect all + # paths to be normalized and absolute (and will ensure that is the case). if self._has_includes(): for _, include_path in self._included_paths(): if include_path.startswith("~"): @@ -695,8 +711,9 @@ def items_all(self, section_name: str) -> List[Tuple[str, List[str]]]: def write(self) -> None: """Write changes to our file, if there are changes at all. - :raise IOError: If this is a read-only writer instance or if we could not obtain - a file lock""" + :raise IOError: + If this is a read-only writer instance or if we could not obtain a file lock + """ self._assure_writable("write") if not self._dirty: return @@ -740,7 +757,7 @@ def _assure_writable(self, method_name: str) -> None: 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""" + """Assures added options will stay in order.""" return super().add_section(section) @property @@ -757,16 +774,18 @@ def get_value( ) -> Union[int, float, str, bool]: """Get an option's value. - If multiple values are specified for this option in the section, the - last one specified is returned. + If multiple values are specified for this option in the section, the last one + specified is returned. :param default: - If not None, the given default value will be returned in case - the option did not exist + If not None, the given default value will be returned in case the option did + not exist - :return: a properly typed value, either int, float or string + :return: + A properly typed value, either int, float or string - :raise TypeError: in case the value could not be understood + :raise TypeError: + In case the value could not be understood. Otherwise the exceptions known to the ConfigParser will be raised. """ try: @@ -790,12 +809,14 @@ def get_values( returned. :param default: - If not None, a list containing the given default value will be - returned in case the option did not exist + If not None, a list containing the given default value will be returned in + case the option did not exist. - :return: a list of properly typed values, either int, float or string + :return: + A list of properly typed values, either int, float or string - :raise TypeError: in case the value could not be understood + :raise TypeError: + In case the value could not be understood. Otherwise the exceptions known to the ConfigParser will be raised. """ try: @@ -849,11 +870,17 @@ def set_value(self, section: str, option: str, value: Union[str, bytes, int, flo This will create the section if required, and will not throw as opposed to the default ConfigParser 'set' method. - :param section: Name of the section in which the option resides or should reside - :param option: Name of the options whose value to set - :param value: Value to set the option to. It must be a string or convertible to - a string. - :return: This instance + :param section: + Name of the section in which the option resides or should reside. + + :param option: + Name of the options whose value to set. + + :param value: + Value to set the option to. It must be a string or convertible to a string. + + :return: + This instance """ if not self.has_section(section): self.add_section(section) @@ -865,15 +892,22 @@ def set_value(self, section: str, option: str, value: Union[str, bytes, int, flo def add_value(self, section: str, option: str, value: Union[str, bytes, int, float, bool]) -> "GitConfigParser": """Add a value for the given option in section. - This 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 - by 'get_value', and appends to the list of values returned by 'get_values`'. + This 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 by 'get_value', and appends to the list of values returned by + 'get_values'. - :param section: Name of the section in which the option resides or should reside - :param option: Name of the option - :param value: Value to add to option. It must be a string or convertible - to a string - :return: This instance + :param section: + Name of the section in which the option resides or should reside. + + :param option: + Name of the option. + + :param value: + Value to add to option. It must be a string or convertible to a string. + + :return: + This instance """ if not self.has_section(section): self.add_section(section) @@ -883,8 +917,12 @@ def add_value(self, section: str, option: str, value: Union[str, bytes, int, flo def rename_section(self, section: str, new_name: str) -> "GitConfigParser": """Rename the given section to new_name. - :raise ValueError: If ``section`` doesn't exist - :raise ValueError: If a section with ``new_name`` does already exist + :raise ValueError: + If: + + * ``section`` doesn't exist. + * A section with ``new_name`` does already exist. + :return: This instance """ if not self.has_section(section): @@ -898,6 +936,7 @@ def rename_section(self, section: str, new_name: str) -> "GitConfigParser": new_section.setall(k, vs) # END for each value to copy - # This call writes back the changes, which is why we don't have the respective decorator. + # This call writes back the changes, which is why we don't have the respective + # decorator. self.remove_section(section) return self diff --git a/git/db.py b/git/db.py index 1531d663c..dff59f47a 100644 --- a/git/db.py +++ b/git/db.py @@ -31,8 +31,9 @@ class GitCmdObjectDB(LooseObjectDB): It will create objects only in the loose object database. - :note: For now, we use the git command to do all the lookup, just until we - have packs and the other implementations. + :note: + For now, we use the git command to do all the lookup, just until we have packs + and the other implementations. """ def __init__(self, root_path: PathLike, git: "Git") -> None: @@ -56,9 +57,11 @@ def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes: :return: Full binary 20 byte sha from the given partial hexsha :raise AmbiguousObjectName: + :raise BadObject: - :note: Currently we only raise :class:`BadObject` as git does not communicate + :note: + Currently we only raise :class:`BadObject` as git does not communicate ambiguous objects separately. """ try: diff --git a/git/diff.py b/git/diff.py index aba1a1080..7205136d0 100644 --- a/git/diff.py +++ b/git/diff.py @@ -84,8 +84,9 @@ class Diffable: compatible type. :note: - Subclasses require a repo member as it is the case for Object instances, for - practical reasons we do not derive from Object. + Subclasses require a repo member as it is the case for + :class:`~git.objects.base.Object` instances, for practical reasons we do not + derive from :class:`~git.objects.base.Object`. """ __slots__ = () @@ -135,13 +136,13 @@ def diff( to be read and diffed. :param kwargs: - Additional arguments passed to git-diff, such as ``R=True`` to swap both + Additional arguments passed to ``git diff``, such as ``R=True`` to swap both sides of the diff. :return: git.DiffIndex :note: - On a bare repository, 'other' needs to be provided as + On a bare repository, `other` needs to be provided as :class:`~Diffable.Index`, or as :class:`~git.objects.tree.Tree` or :class:`~git.objects.commit.Commit`, or a git command error will occur. """ @@ -183,7 +184,7 @@ def diff( args.insert(0, self) - # paths is list here, or None. + # paths is a list here, or None. if paths: args.append("--") args.extend(paths) @@ -203,7 +204,7 @@ def diff( class DiffIndex(List[T_Diff]): - """An Index for diffs, allowing a list of Diffs to be queried by the diff + R"""An Index for diffs, allowing a list of :class:`Diff`\s to be queried by the diff properties. The class improves the diff handling convenience. @@ -255,27 +256,27 @@ def iter_change_type(self, change_type: Lit_change_type) -> Iterator[T_Diff]: class Diff: """A Diff contains diff information between two Trees. - It contains two sides a and b of the diff, members are prefixed with - "a" and "b" respectively to indicate that. + It contains two sides a and b of the diff. Members are prefixed with "a" and "b" + respectively to indicate that. Diffs keep information about the changed blob objects, the file mode, renames, deletions and new files. There are a few cases where None has to be expected as member variable value: - ``New File``:: + New File:: a_mode is None a_blob is None a_path is None - ``Deleted File``:: + Deleted File:: b_mode is None b_blob is None b_path is None - ``Working Tree Blobs`` + Working Tree Blobs: When comparing to working trees, the working tree blob will have a null hexsha as a corresponding object does not yet exist. The mode will be null as well. @@ -469,7 +470,8 @@ def renamed(self) -> bool: """ :return: True if the blob of our diff has been renamed - :note: This property is deprecated. + :note: + This property is deprecated. Please use the :attr:`renamed_file` property instead. """ return self.renamed_file @@ -494,11 +496,17 @@ def _pick_best_path(cls, path_match: bytes, rename_match: bytes, path_fallback_m @classmethod def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoInterrupt"]) -> DiffIndex: - """Create a new DiffIndex from the given process output which must be in patch format. + """Create a new :class:`DiffIndex` from the given process output which must be + in patch format. - :param repo: The repository we are operating on - :param proc: ``git diff`` process to read from (supports :class:`Git.AutoInterrupt` wrapper) - :return: git.DiffIndex + :param repo: The repository we are operating on. + + :param proc: + ``git diff`` process to read from + (supports :class:`Git.AutoInterrupt` wrapper). + + :return: + :class:`DiffIndex` """ # FIXME: Here SLURPING raw, need to re-phrase header-regexes linewise. @@ -539,14 +547,14 @@ def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoIn 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) - # 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. + # 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()] # 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. + # 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)) b_mode = b_mode or new_mode or new_file_mode or (b_path and a_mode) index.append( @@ -610,7 +618,7 @@ def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex) -> Non rename_from = None rename_to = None - # NOTE: We cannot conclude from the existence of a blob to change type + # 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": b_blob_id = None # Optional[str] @@ -654,11 +662,17 @@ def _handle_diff_line(lines_bytes: bytes, repo: "Repo", index: DiffIndex) -> Non @classmethod def _index_from_raw_format(cls, repo: "Repo", proc: "Popen") -> "DiffIndex": - """Create a new DiffIndex from the given process output which must be in raw format. + """Create a new :class:`DiffIndex` from the given process output which must be + in raw format. - :param repo: The repository we are operating on - :param proc: Process to read output from - :return: git.DiffIndex + :param repo: + The repository we are operating on. + + :param proc: + Process to read output from. + + :return: + :class:`DiffIndex` """ # handles # :100644 100644 687099101... 37c5e30c8... M .gitignore diff --git a/git/exc.py b/git/exc.py index 40fb8eea0..276d79e42 100644 --- a/git/exc.py +++ b/git/exc.py @@ -81,7 +81,8 @@ class UnsafeOptionError(GitError): class CommandError(GitError): - """Base class for exceptions thrown at every stage of `Popen()` execution. + """Base class for exceptions thrown at every stage of :class:`~subprocess.Popen` + execution. :param command: A non-empty list of argv comprising the command-line. @@ -135,8 +136,8 @@ def __str__(self) -> str: 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.""" + """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: super().__init__(command, cause) @@ -187,7 +188,7 @@ def __str__(self) -> str: class CacheError(GitError): - """Base for all errors related to the git index, which is called cache + """Base for all errors related to the git index, which is called "cache" internally.""" diff --git a/git/remote.py b/git/remote.py index df809a9ac..5bee9edf5 100644 --- a/git/remote.py +++ b/git/remote.py @@ -70,13 +70,15 @@ def add_progress( 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. + """Add the ``--progress`` flag to the given `kwargs` dict if supported by the git + command. - :note: If the actual progress in the given progress instance is not - given, we do not request any progress. + :note: + If the actual progress in the given progress instance is not given, we do not + request any progress. - :return: possibly altered kwargs + :return: + Possibly altered `kwargs` """ if progress is not None: v = git.version_info[:2] @@ -108,7 +110,8 @@ def to_progress_instance(progress: RemoteProgress) -> RemoteProgress: def to_progress_instance( progress: Union[Callable[..., Any], RemoteProgress, None] ) -> Union[RemoteProgress, CallableRemoteProgress]: - """Given the 'progress' return a suitable object derived from RemoteProgress.""" + """Given the `progress` return a suitable object derived from + :class:`~git.util.RemoteProgress`.""" # New API only needs progress as a function. if callable(progress): return CallableRemoteProgress(progress) @@ -276,7 +279,7 @@ def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> NoReturn: # -> class PushInfoList(IterableList[PushInfo]): - """IterableList of PushInfo objects.""" + """IterableList of :class:`PushInfo` objects.""" def __new__(cls) -> "PushInfoList": return cast(PushInfoList, IterableList.__new__(cls, "push_infos")) @@ -380,8 +383,8 @@ def commit(self) -> Commit_ish: @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. + """Parse information from the given line as returned by ``git-fetch -v`` and + return a new :class:`FetchInfo` object representing this information. We can handle a line as follows: "%c %-\\*s %-\\*s -> %s%s" @@ -391,7 +394,7 @@ def _from_line(cls, repo: "Repo", line: str, fetch_line: str) -> "FetchInfo": + means success forcing update - means a tag was updated * means birth of new branch or tag - = means the head was up to date ( and not moved ) + = means the head was up to date (and not moved) ' ' means a fast-forward fetch line is the corresponding line from FETCH_HEAD, like @@ -455,15 +458,17 @@ def _from_line(cls, repo: "Repo", line: str, fetch_line: str) -> "FetchInfo": if remote_local_ref_str == "FETCH_HEAD": ref_type = SymbolicReference elif ref_type_name == "tag" or is_tag_operation: - # The ref_type_name can be branch, whereas we are still seeing a tag operation. - # It happens during testing, which is based on actual git operations. + # The ref_type_name can be branch, whereas we are still seeing a tag + # operation. It happens during testing, which is based on actual git + # operations. ref_type = TagReference elif ref_type_name in ("remote-tracking", "branch"): - # 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 it's new in git 1.7something. + # Note: remote-tracking is just the first part of the + # 'remote-tracking branch' token. We don't parse it correctly, but it's + # enough to know what to do, and it's new in git 1.7something. ref_type = RemoteReference elif "/" in ref_type_name: - # If the fetch spec look something like this '+refs/pull/*:refs/heads/pull/*', + # If the fetch spec look something like '+refs/pull/*:refs/heads/pull/*', # and is thus pretty much anything the user wants, we will have trouble # determining what's going on. For now, we assume the local ref is a Head. ref_type = Head @@ -475,17 +480,19 @@ def _from_line(cls, repo: "Repo", line: str, fetch_line: str) -> "FetchInfo": if ref_type is SymbolicReference: remote_local_ref = ref_type(repo, "FETCH_HEAD") else: - # Determine prefix. Tags are usually pulled into refs/tags, they may have subdirectories. - # It is not clear sometimes where exactly the item is, unless we have an absolute path as - # indicated by the 'ref/' prefix. Otherwise even a tag could be in refs/remotes, which is - # when it will have the 'tags/' subdirectory in its path. - # We don't want to test for actual existence, but try to figure everything out analytically. + # Determine prefix. Tags are usually pulled into refs/tags; they may have + # subdirectories. It is not clear sometimes where exactly the item is, + # unless we have an absolute path as indicated by the 'ref/' prefix. + # Otherwise even a tag could be in refs/remotes, which is when it will have + # the 'tags/' subdirectory in its path. We don't want to test for actual + # existence, but try to figure everything out analytically. ref_path: Optional[PathLike] = None remote_local_ref_str = remote_local_ref_str.strip() if remote_local_ref_str.startswith(Reference._common_path_default + "/"): - # Always use actual type if we get absolute paths. - # Will always be the case if something is fetched outside of refs/remotes (if its not a tag). + # Always use actual type if we get absolute paths. This 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 + "/" @@ -499,8 +506,8 @@ def _from_line(cls, repo: "Repo", line: str, fetch_line: str) -> "FetchInfo": 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 - # sure we respect whatever the user wanted, and disabled path checking. + # Even though the path could be within the git conventions, we make sure we + # respect whatever the user wanted, and disabled path checking. remote_local_ref = ref_type(repo, ref_path, check_path=False) # END create ref instance @@ -517,10 +524,11 @@ class Remote(LazyMixin, IterableObj): """Provides easy read and write access to a git remote. Everything not part of this interface is considered an option for the current - remote, allowing constructs like remote.pushurl to query the pushurl. + remote, allowing constructs like ``remote.pushurl`` to query the pushurl. - :note: When querying configuration, the configuration accessor will be cached - to speed up subsequent accesses. + :note: + When querying configuration, the configuration accessor will be cached to speed + up subsequent accesses. """ __slots__ = ("repo", "name", "_config_reader") @@ -547,21 +555,24 @@ class Remote(LazyMixin, IterableObj): def __init__(self, repo: "Repo", name: str) -> None: """Initialize a remote instance. - :param repo: The repository we are a remote of - :param name: The name of the remote, e.g. 'origin' + :param repo: + The repository we are a remote of. + + :param name: + The name of the remote, e.g. 'origin'. """ self.repo = repo self.name = name self.url: str def __getattr__(self, attr: str) -> Any: - """Allows to call this instance like - remote.special( \\*args, \\*\\*kwargs) to call git-remote special self.name.""" + """Allows to call this instance like ``remote.special(*args, **kwargs)`` to + call ``git remote special self.name``.""" if attr == "_config_reader": return super().__getattr__(attr) - # Sometimes, probably due to a bug in Python itself, we are being called - # even though a slot of the same name exists. + # Sometimes, probably due to a bug in Python itself, we are being called even + # though a slot of the same name exists. try: return self._config_reader.get(attr) except cp.NoOptionError: @@ -599,7 +610,8 @@ def __hash__(self) -> int: def exists(self) -> bool: """ - :return: True if this is a valid, existing remote. + :return: + True if this is a valid, existing remote. Valid remotes have an entry in the repository's configuration. """ try: @@ -627,14 +639,21 @@ def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Iterator["Remote def set_url( self, new_url: str, old_url: Optional[str] = None, allow_unsafe_protocols: bool = False, **kwargs: Any ) -> "Remote": - """Configure URLs on current remote (cf command git remote set_url). + """Configure URLs on current remote (cf command ``git remote set-url``). This command manages URLs on the remote. - :param new_url: String being the URL to add as an extra remote URL - :param old_url: When set, replaces this URL with new_url for the remote - :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ext - :return: self + :param new_url: + String being the URL to add as an extra remote URL. + + :param old_url: + When set, replaces this URL with `new_url` for the remote. + + :param allow_unsafe_protocols: + Allow unsafe protocols to be used, like ``ext``. + + :return: + self """ if not allow_unsafe_protocols: Git.check_unsafe_protocols(new_url) @@ -647,25 +666,33 @@ def set_url( return self def add_url(self, url: str, allow_unsafe_protocols: bool = False, **kwargs: Any) -> "Remote": - """Adds a new url on current remote (special case of git remote set_url). + """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 multiple URLs for a single remote. - :param url: String being the URL to add as an extra remote URL - :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ext - :return: self + :param url: + String being the URL to add as an extra remote URL. + + :param allow_unsafe_protocols: + Allow unsafe protocols to be used, like ``ext``. + + :return: + self """ return self.set_url(url, add=True, allow_unsafe_protocols=allow_unsafe_protocols) def delete_url(self, url: str, **kwargs: Any) -> "Remote": - """Deletes a new url on current remote (special case of git remote set_url) + """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 multiple URLs for a single remote. - :param url: String being the URL to delete from the remote - :return: self + :param url: + String being the URL to delete from the remote. + + :return: + self """ return self.set_url(url, delete=True) @@ -706,9 +733,12 @@ def urls(self) -> Iterator[str]: def refs(self) -> IterableList[RemoteReference]: """ :return: - IterableList of RemoteReference objects. It is prefixed, allowing - you to omit the remote path portion, e.g.:: - remote.refs.master # yields RemoteReference('/refs/remotes/origin/master') + :class:`~git.util.IterableList` of :class:`git.refs.remote.RemoteReference` + objects. + + It is prefixed, allowing you to omit the remote path portion, e.g.:: + + remote.refs.master # yields RemoteReference('/refs/remotes/origin/master') """ out_refs: IterableList[RemoteReference] = IterableList(RemoteReference._id_attribute_, "%s/" % self.name) out_refs.extend(RemoteReference.list_items(self.repo, remote=self.name)) @@ -718,12 +748,13 @@ def refs(self) -> IterableList[RemoteReference]: def stale_refs(self) -> IterableList[Reference]: """ :return: - IterableList RemoteReference objects that do not have a corresponding - head in the remote reference anymore as they have been deleted on the - remote side, but are still available locally. + :class:`~git.util.IterableList` of :class:`git.refs.remote.RemoteReference` + objects that do not have a corresponding head in the remote reference + anymore as they have been deleted on the remote side, but are still + available locally. - The IterableList is prefixed, hence the 'origin' must be omitted. See - 'refs' property for an example. + The :class:`~git.util.IterableList` is prefixed, hence the 'origin' must be + omitted. See :attr:`refs` property for an example. To make things more complicated, it can be possible for the list to include other kinds of references, for example, tag references, if these are stale @@ -752,13 +783,26 @@ def stale_refs(self) -> IterableList[Reference]: def create(cls, repo: "Repo", name: str, url: str, allow_unsafe_protocols: bool = False, **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 - :param url: URL which corresponds to the remote's name - :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ext - :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 + :param repo: + Repository instance that is to receive the new remote. + + :param name: + Desired name of the remote. + + :param url: + URL which corresponds to the remote's name. + + :param allow_unsafe_protocols: + Allow unsafe protocols to be used, like ``ext``. + + :param kwargs: + Additional arguments to be passed to the ``git remote add`` command. + + :return: + New :class:`Remote` instance + + :raise GitCommandError: + In case an origin with that name already exists. """ scmd = "add" kwargs["insert_kwargs_after"] = scmd @@ -777,7 +821,8 @@ def add(cls, repo: "Repo", name: str, url: str, **kwargs: Any) -> "Remote": def remove(cls, repo: "Repo", name: str) -> str: """Remove the remote with the given name. - :return: The passed remote name to remove + :return: + The passed remote name to remove """ repo.git.remote("rm", name) if isinstance(name, cls): @@ -788,9 +833,10 @@ def remove(cls, repo: "Repo", name: str) -> str: rm = remove def rename(self, new_name: str) -> "Remote": - """Rename self to the given new_name. + """Rename self to the given `new_name`. - :return: self + :return: + self """ if self.name == new_name: return self @@ -802,12 +848,15 @@ def rename(self, new_name: str) -> "Remote": return self 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 - branch's ancestry anymore). + """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 branch's + ancestry anymore). + + :param kwargs: + Additional arguments passed to ``git remote update``. - :param kwargs: Additional arguments passed to git-remote update - :return: self + :return: + self """ scmd = "update" kwargs["insert_kwargs_after"] = scmd @@ -966,9 +1015,9 @@ def fetch( Taken from the git manual, gitglossary(7). - Fetch supports multiple refspecs (as the - underlying git-fetch does) - supplying a list rather than a string - for 'refspec' will make use of this facility. + Fetch supports multiple refspecs (as the underlying git-fetch does) - + supplying a list rather than a string for 'refspec' will make use of this + facility. :param progress: See :meth:`push` method. @@ -978,15 +1027,18 @@ def fetch( To specify a timeout in seconds for the git command, after which the process should be killed. It is set to None by default. - :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ext. + :param allow_unsafe_protocols: + Allow unsafe protocols to be used, like ``ext``. - :param allow_unsafe_options: Allow unsafe options to be used, like --upload-pack. + :param allow_unsafe_options: + Allow unsafe options to be used, like ``--upload-pack``. - :param kwargs: Additional arguments to be passed to git-fetch. + :param kwargs: + Additional arguments to be passed to ``git fetch``. :return: - IterableList(FetchInfo, ...) list of FetchInfo instances providing detailed - information about the fetch results + IterableList(FetchInfo, ...) list of :class:`FetchInfo` instances providing + detailed information about the fetch results :note: As fetch does not provide progress information to non-ttys, we cannot make @@ -1030,13 +1082,26 @@ def pull( """Pull changes from the given branch, being the same as a fetch followed by a merge of branch with your local branch. - :param refspec: See :meth:`fetch` method - :param progress: See :meth:`push` method - :param kill_after_timeout: See :meth:`fetch` method - :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ext - :param allow_unsafe_options: Allow unsafe options to be used, like --upload-pack - :param kwargs: Additional arguments to be passed to git-pull - :return: Please see :meth:`fetch` method + :param refspec: + See :meth:`fetch` method. + + :param progress: + See :meth:`push` method. + + :param kill_after_timeout: + See :meth:`fetch` method. + + :param allow_unsafe_protocols: + Allow unsafe protocols to be used, like ``ext``. + + :param allow_unsafe_options: + Allow unsafe options to be used, like ``--upload-pack``. + + :param kwargs: + Additional arguments to be passed to ``git pull``. + + :return: + Please see :meth:`fetch` method """ if refspec is None: # No argument refspec, then ensure the repo's config has a fetch refspec. @@ -1070,7 +1135,8 @@ def push( ) -> PushInfoList: """Push changes from source branch in refspec to target branch in refspec. - :param refspec: See :meth:`fetch` method. + :param refspec: + See :meth:`fetch` method. :param progress: Can take one of many value types: @@ -1084,26 +1150,31 @@ def push( overrides the :meth:`RemoteProgress.update ` method. - :note: No further progress information is returned after push returns. + :note: + No further progress information is returned after push returns. :param kill_after_timeout: To specify a timeout in seconds for the git command, after which the process should be killed. It is set to None by default. - :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ext. + :param allow_unsafe_protocols: + Allow unsafe protocols to be used, like ``ext``. :param allow_unsafe_options: - Allow unsafe options to be used, like --receive-pack. + Allow unsafe options to be used, like ``--receive-pack``. - :param kwargs: Additional arguments to be passed to git-push. + :param kwargs: Additional arguments to be passed to ``git push``. :return: A :class:`PushInfoList` object, where each list member represents an individual head which had been updated on the remote side. + If the push contains rejected heads, these will have the :attr:`PushInfo.ERROR` bit set in their flags. - If the operation fails completely, the length of the returned PushInfoList - will be 0. + + If the operation fails completely, the length of the returned + :class:`PushInfoList` will be 0. + Call :meth:`~PushInfoList.raise_if_error` on the returned object to raise on any failure. """ @@ -1133,8 +1204,9 @@ def push( def config_reader(self) -> SectionConstraint[GitConfigParser]: """ :return: - GitConfigParser compatible object able to read options for only our remote. - Hence you may simple type config.get("pushurl") to obtain the information. + :class:`~git.config.GitConfigParser` compatible object able to read options + for only our remote. Hence you may simply type ``config.get("pushurl")`` to + obtain the information. """ return self._config_reader @@ -1148,7 +1220,9 @@ def _clear_cache(self) -> None: @property def config_writer(self) -> SectionConstraint: """ - :return: GitConfigParser compatible object able to write options for this remote. + :return: + :class:`~git.config.GitConfigParser`-compatible object able to write options + for this remote. :note: You can only own one writer at a time - delete it to release the diff --git a/git/util.py b/git/util.py index 03d62ffc3..30b78f7b2 100644 --- a/git/util.py +++ b/git/util.py @@ -111,10 +111,11 @@ def _read_win_env_flag(name: str, default: bool) -> bool: """Read a boolean flag from an environment variable on Windows. :return: - On Windows, the flag, or the ``default`` value if absent or ambiguous. - On all other operating systems, ``False``. + On Windows, the flag, or the `default` value if absent or ambiguous. + On all other operating systems, False. - :note: This only accesses the environment on Windows. + :note: + This only accesses the environment on Windows. """ if os.name != "nt": return False @@ -151,8 +152,8 @@ def _read_win_env_flag(name: str, default: bool) -> bool: def unbare_repo(func: Callable[..., T]) -> Callable[..., T]: - """Methods with this decorator raise :class:`.exc.InvalidGitRepositoryError` if they - encounter a bare repository.""" + """Methods with this decorator raise + :class:`~git.exc.InvalidGitRepositoryError` if they encounter a bare repository.""" from .exc import InvalidGitRepositoryError @@ -206,7 +207,10 @@ def rmtree(path: PathLike) -> None: """ def handler(function: Callable, path: PathLike, _excinfo: Any) -> None: - """Callback for :func:`shutil.rmtree`. Works either as ``onexc`` or ``onerror``.""" + """Callback for :func:`shutil.rmtree`. + + This works as either a ``onexc`` or ``onerror`` style callback. + """ # Is the error an access error? os.chmod(path, stat.S_IWUSR) @@ -228,7 +232,8 @@ def handler(function: Callable, path: PathLike, _excinfo: Any) -> None: def rmfile(path: PathLike) -> None: - """Ensure file deleted also on *Windows* where read-only files need special treatment.""" + """Ensure file deleted also on *Windows* where read-only files need special + treatment.""" if osp.isfile(path): if os.name == "nt": os.chmod(path, 0o777) @@ -239,7 +244,8 @@ def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * """Copy all data from the source stream into the destination stream in chunks of size chunk_size. - :return: Number of bytes written + :return: + Number of bytes written """ br = 0 while True: @@ -473,12 +479,13 @@ def is_cygwin_git(git_executable: Union[None, PathLike]) -> bool: def get_user_id() -> str: - """:return: string identifying the currently active system user as name@node""" + """:return: String identifying the currently active system user as ``name@node``""" return "%s@%s" % (getpass.getuser(), platform.node()) 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""" + """Wait for the process (clone, fetch, pull or push) and handle its errors + accordingly.""" # TODO: No close proc-streams?? proc.wait(**kwargs) @@ -541,9 +548,9 @@ def remove_password_if_present(cmdline: Sequence[str]) -> List[str]: class RemoteProgress: - """ - 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. + """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 @@ -580,8 +587,8 @@ def __init__(self) -> None: self.other_lines: List[str] = [] def _parse_progress_line(self, line: AnyStr) -> None: - """Parse progress information from the given line as retrieved by git-push - or git-fetch. + """Parse progress information from the given line as retrieved by ``git push`` + or ``git fetch``. - Lines that do not contain progress info are stored in :attr:`other_lines`. - Lines that seem to contain an error (i.e. start with ``error:`` or ``fatal:``) @@ -685,8 +692,8 @@ def _parse_progress_line(self, line: AnyStr) -> None: 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 + A progress handler suitable for :func:`~git.cmd.handle_process_output`, + passing lines on to this progress handler in a suitable format. """ def handler(line: AnyStr) -> None: @@ -712,25 +719,29 @@ def update( :param op_code: Integer allowing to be compared against Operation IDs and stage IDs. - Stage IDs are BEGIN and END. BEGIN will only be set once for each Operation - ID as well as END. It may be that BEGIN and END are set at once in case only - one progress message was emitted due to the speed of the operation. - Between BEGIN and END, none of these flags will be set. + Stage IDs are :attr:`BEGIN` and :attr:`END`. :attr:`BEGIN` will only be set + once for each Operation ID as well as :attr:`END`. It may be that + :attr:`BEGIN` and :attr:`END` are set at once in case only one progress + message was emitted due to the speed of the operation. Between :attr:`BEGIN` + and :attr:`END`, none of these flags will be set. - Operation IDs are all held within the OP_MASK. Only one Operation ID will - be active per call. + Operation IDs are all held within the :attr:`OP_MASK`. Only one Operation ID + will be active per call. - :param cur_count: Current absolute count of items. + :param cur_count: + Current absolute count of items. :param max_count: - The maximum count of items we expect. It may be None in case there is - no maximum number of items or if it is (yet) unknown. + The maximum count of items we expect. It may be None in case there is no + maximum number of items or if it is (yet) unknown. :param message: - In case of the 'WRITING' operation, it contains the amount of bytes + In case of the :attr:`WRITING` operation, it contains the amount of bytes transferred. It may possibly be used for other purposes as well. - You may read the contents of the current line in ``self._cur_line``. + :note: + You may read the contents of the current line in + :attr:`self._cur_line <_cur_line>`. """ pass @@ -793,11 +804,13 @@ def __repr__(self) -> str: def _from_string(cls, string: str) -> "Actor": """Create an Actor from a string. - :param string: The string, which is expected to be in regular git format:: + :param string: + The string, which is expected to be in regular git format:: - John Doe + John Doe - :return: Actor + :return: + :class:`Actor` """ m = cls.name_email_regex.search(string) if m: @@ -857,18 +870,19 @@ def committer(cls, config_reader: Union[None, "GitConfigParser", "SectionConstra """ :return: Actor instance corresponding to the configured committer. It behaves similar to the git implementation, such that the environment will override - configuration values of config_reader. If no value is set at all, it will be - generated. + configuration values of `config_reader`. If no value is set at all, it will + be generated. - :param config_reader: ConfigReader to use to retrieve the values from in case - they are not set in the environment. + :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) @classmethod 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.""" + """Same as :meth:`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) @@ -877,7 +891,7 @@ class Stats: Represents stat information as presented by git at the end of a merge. It is created from the output of a diff operation. - ``Example``:: + Example:: c = Commit( sha1 ) s = c.stats @@ -907,9 +921,10 @@ def __init__(self, total: Total_TD, files: Dict[PathLike, Files_TD]): @classmethod def _list_from_string(cls, repo: "Repo", text: str) -> "Stats": - """Create a Stat object from output retrieved by git-diff. + """Create a :class:`Stats` object from output retrieved by ``git diff``. - :return: git.Stat + :return: + :class:`git.Stats` """ hsh: HSH_TD = { @@ -936,11 +951,12 @@ def _list_from_string(cls, repo: "Repo", text: str) -> "Stats": class IndexFileSHA1Writer: """Wrapper around a file-like object that remembers the SHA1 of the data written to it. It will write a sha when the stream is closed - or if the asked for explicitly using write_sha. + or if asked for explicitly using :meth:`write_sha`. Only useful to the index file. - :note: Based on the dulwich project. + :note: + Based on the dulwich project. """ __slots__ = ("f", "sha1") @@ -991,16 +1007,20 @@ def _lock_file_path(self) -> str: def _has_lock(self) -> bool: """ - :return: True if we have a lock and if the lockfile still exists + :return: + True if we have a lock and if the lockfile still exists - :raise AssertionError: If our lock-file does not exist + :raise AssertionError: + If our lock-file does not exist. """ return self._owns_lock def _obtain_lock_or_raise(self) -> None: - """Create a lock file as flag for other instances, mark our instance as lock-holder. + """Create a lock file as flag for other instances, mark our instance as + lock-holder. - :raise IOError: If a lock was already present or a lock file could not be written + :raise IOError: + If a lock was already present or a lock file could not be written. """ if self._has_lock(): return @@ -1021,7 +1041,9 @@ def _obtain_lock_or_raise(self) -> None: def _obtain_lock(self) -> None: """The default implementation will raise if a lock cannot be obtained. - Subclasses may override this method to provide a different implementation.""" + + Subclasses may override this method to provide a different implementation. + """ return self._obtain_lock_or_raise() def _release_lock(self) -> None: @@ -1029,8 +1051,8 @@ def _release_lock(self) -> None: if not self._has_lock(): return - # If someone removed our file beforehand, lets just flag this issue - # instead of failing, to make it more usable. + # If someone removed our file beforehand, lets just flag this issue instead of + # failing, to make it more usable. lfp = self._lock_file_path() try: rmfile(lfp) @@ -1040,12 +1062,12 @@ def _release_lock(self) -> None: class BlockingLockFile(LockFile): - """The lock file will block until a lock could be obtained, or fail after - a specified timeout. + """The lock file will block until a lock could be obtained, or fail after a + specified timeout. - :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. + :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") @@ -1062,14 +1084,15 @@ def __init__( Period of time to sleep until the lock is checked the next time. By default, it waits a nearly unlimited time. - :param max_block_time_s: Maximum amount of seconds we may lock. + :param max_block_time_s: + Maximum amount of seconds we may lock. """ super().__init__(file_path) self._check_interval = check_interval_s self._max_block_time = max_block_time_s def _obtain_lock(self) -> None: - """This method blocks until it obtained the lock, or raises IOError if + """This method blocks until it obtained the lock, or raises :class:`IOError` if it ran out of time or if the parent directory was not available anymore. If this method returns, you are guaranteed to own the lock. @@ -1105,23 +1128,32 @@ def _obtain_lock(self) -> None: class IterableList(List[T_IterableObj]): - """ - List of iterable objects allowing to query an object by id or by named index:: + """List of iterable objects allowing to query an object by id or by named index:: heads = repo.heads heads.master heads['master'] heads[0] - Iterable parent objects = [Commit, SubModule, Reference, FetchInfo, PushInfo] - Iterable via inheritance = [Head, TagReference, RemoteReference] + Iterable parent objects: + + * :class:`Commit ` + * :class:`Submodule ` + * :class:`Reference ` + * :class:`FetchInfo ` + * :class:`PushInfo ` + + Iterable via inheritance: - It requires an id_attribute name to be set which will be queried from its + * :class:`Head ` + * :class:`TagReference ` + * :class:`RemoteReference ` + + This requires an ``id_attribute`` name to be set which will be queried from its contained items to have a means for comparison. - 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. + 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") @@ -1198,7 +1230,14 @@ class IterableObj(Protocol): """Defines an interface for iterable items, so there is a uniform way to retrieve and iterate items within the git repository. - Subclasses = [Submodule, Commit, Reference, PushInfo, FetchInfo, Remote] + Subclasses: + + * :class:`Submodule ` + * :class:`Commit ` + * :class:`Reference ` + * :class:`PushInfo ` + * :class:`FetchInfo ` + * :class:`Remote ` """ __slots__ = () @@ -1211,11 +1250,12 @@ def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Iterator[T_Itera # Return-typed to be compatible with subtypes e.g. Remote. """Find (all) items of this type. - Subclasses can specify ``args`` and ``kwargs`` differently, and may use them for + Subclasses can specify `args` and `kwargs` differently, and may use them for filtering. However, when the method is called with no additional positional or keyword arguments, subclasses are obliged to to yield all items. - :return: Iterator yielding Items + :return: + :class:`~collections.abc.Iterator` yielding Items """ raise NotImplementedError("To be implemented by Subclass") @@ -1225,11 +1265,13 @@ def list_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> IterableList[T_I For more information about the arguments, see :meth:`iter_items`. - :note: Favor the :meth:`iter_items` method as it will avoid eagerly collecting - all items. When there are many items, that can slow performance and increase + :note: + Favor the :meth:`iter_items` method as it will avoid eagerly collecting all + items. When there are many items, that can slow performance and increase memory usage. - :return: list(Item,...) list of item instances + :return: + list(Item,...) list of item instances """ out_list: IterableList = IterableList(cls._id_attribute_) out_list.extend(cls.iter_items(repo, *args, **kwargs)) @@ -1272,7 +1314,8 @@ def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Any: See :meth:`IterableObj.iter_items` for details on usage. - :return: Iterator yielding Items + :return: + Iterator yielding Items """ raise NotImplementedError("To be implemented by Subclass") @@ -1284,7 +1327,8 @@ def list_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Any: See :meth:`IterableObj.list_items` for details on usage. - :return: list(Item,...) list of item instances + :return: + list(Item,...) list of item instances """ out_list: Any = IterableList(cls._id_attribute_) out_list.extend(cls.iter_items(repo, *args, **kwargs)) From 29c63ac8d7c75e4863cd355610da3cabd48a421c Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 21:16:49 -0500 Subject: [PATCH 11/11] Format first Git.execute overload stub like the others This makes it easier to read along with, and compare to, the other overloads. --- git/cmd.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git/cmd.py b/git/cmd.py index eb6c235c7..d3aa5f544 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -922,7 +922,12 @@ def version_info(self) -> Tuple[int, ...]: return self._version_info @overload - def execute(self, command: Union[str, Sequence[Any]], *, as_process: Literal[True]) -> "AutoInterrupt": + def execute( + self, + command: Union[str, Sequence[Any]], + *, + as_process: Literal[True], + ) -> "AutoInterrupt": ... @overload