From 9b5531bc0e53e03acff70a8c8250115d2d3c9fa4 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 11:34:08 -0500 Subject: [PATCH 01/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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/61] 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 From cd8a3123e322ea6166b9970a87f7cc2e892d2316 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Sat, 24 Feb 2024 21:21:54 -0500 Subject: [PATCH 12/61] Show full-path refresh() in failure message differently This presents it more symbolically, rather than in an example-like style that could confuse. See discussion in #1844. --- git/cmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/cmd.py b/git/cmd.py index d3aa5f544..760a9ff12 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -458,7 +458,7 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: The git executable must be specified in one of the following ways: - be included in your $PATH - be set via $%s - - explicitly set via git.refresh("/full/path/to/git") + - explicitly set via git.refresh() """ ) % cls._git_exec_env_var From 8ec7e32032e0877144517bd15c0e9b6cdd283667 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 14:21:50 -0500 Subject: [PATCH 13/61] Revise docstrings within git.index Along the same lines as e08066c and 1cd73ba. --- git/index/base.py | 531 ++++++++++++++++++++++++++-------------------- git/index/fun.py | 102 +++++---- git/index/typ.py | 13 +- git/index/util.py | 12 +- 4 files changed, 378 insertions(+), 280 deletions(-) diff --git a/git/index/base.py b/git/index/base.py index 9d1db4e35..31186b203 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -101,10 +101,12 @@ def _named_temporary_file_for_subprocess(directory: PathLike) -> Generator[str, None, None]: """Create a named temporary file git subprocesses can open, deleting it afterward. - :param directory: The directory in which the file is created. + :param directory: + The directory in which the file is created. - :return: A context manager object that creates the file and provides its name on - entry, and deletes it on exit. + :return: + A context manager object that creates the file and provides its name on entry, + and deletes it on exit. """ if os.name == "nt": fd, name = tempfile.mkstemp(dir=directory) @@ -123,21 +125,21 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): git command function calls wherever possible. This provides custom merging facilities allowing to merge without actually changing - your index or your working tree. This way you can perform own test-merges based - on the index only without having to deal with the working copy. This is useful - in case of partial working trees. + your index or your working tree. This way you can perform own test-merges based on + the index only without having to deal with the working copy. This is useful in case + of partial working trees. - ``Entries`` + Entries: - The index contains an entries dict whose keys are tuples of type IndexEntry - to facilitate access. + The index contains an entries dict whose keys are tuples of type + :class:`~git.index.typ.IndexEntry` to facilitate access. - You may read the entries dict or manipulate it using IndexEntry instance, i.e.:: + You may read the entries dict or manipulate it using IndexEntry instance, i.e.:: - index.entries[index.entry_key(index_entry_instance)] = index_entry_instance + index.entries[index.entry_key(index_entry_instance)] = index_entry_instance - Make sure you use index.write() once you are done manipulating the index directly - before operating on it using the git command. + Make sure you use :meth:`index.write() ` once you are done manipulating the + index directly before operating on it using the git command. """ __slots__ = ("repo", "version", "entries", "_extension_data", "_file_path") @@ -149,9 +151,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): """Flags for a submodule.""" def __init__(self, repo: "Repo", file_path: Union[PathLike, None] = None) -> None: - """Initialize this Index instance, optionally from the given ``file_path``. + """Initialize this Index instance, optionally from the given `file_path`. - If no file_path is given, we will be created from the current index file. + If no `file_path` is given, we will be created from the current index file. If a stream is not given, the stream will be initialized from the current repository's index on demand. @@ -230,25 +232,22 @@ def write( """Write the current state to our file path or to the given one. :param file_path: - If None, we will write to our stored file path from which we have - been initialized. Otherwise we write to the given file path. - Please note that this will change the file_path of this index to - the one you gave. + If None, we will write to our stored file path from which we have been + initialized. Otherwise we write to the given file path. Please note that + this will change the file_path of this index to the one you gave. :param ignore_extension_data: - If True, the TREE type extension data read in the index will not - be written to disk. NOTE that no extension data is actually written. - Use this if you have altered the index and - would like to use git-write-tree afterwards to create a tree - representing your written changes. - If this data is present in the written index, git-write-tree - will instead write the stored/cached tree. - Alternatively, use IndexFile.write_tree() to handle this case - automatically. + If True, the TREE type extension data read in the index will not be written + to disk. NOTE that no extension data is actually written. + Use this if you have altered the index and would like to use git-write-tree + afterwards to create a tree representing your written changes. + If this data is present in the written index, git-write-tree will instead + write the stored/cached tree. + Alternatively, use :meth:`write_tree` to handle this case automatically. """ # Make sure we have our entries read before getting a write lock. - # Otherwise it would be done when streaming. This can happen if one - # doesn't change the index, but writes it right away. + # Otherwise it would be done when streaming. + # This can happen if one doesn't change the index, but writes it right away. self.entries lfd = LockedFD(file_path or self._file_path) stream = lfd.open(write=True, stream=True) @@ -268,17 +267,17 @@ def write( @post_clear_cache @default_index def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> "IndexFile": - """Merge the given rhs treeish into the current index, possibly taking + """Merge the given `rhs` treeish into the current index, possibly taking a common base treeish into account. - As opposed to the :func:`IndexFile.from_tree` method, this allows you to use an - already existing tree as the left side of the merge. + As opposed to the :func:`from_tree` method, this allows you to use an already + existing tree as the left side of the merge. :param rhs: Treeish reference pointing to the 'other' side of the merge. :param base: - Optional treeish reference pointing to the common base of 'rhs' and this + Optional treeish reference pointing to the common base of `rhs` and this index which equals lhs. :return: @@ -289,7 +288,7 @@ def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> "IndexF If there is a merge conflict. The error will be raised at the first conflicting path. If you want to have proper merge resolution to be done by yourself, you have to commit the changed index (or make a valid tree from - it) and retry with a three-way index.from_tree call. + it) and retry with a three-way :meth:`index.from_tree ` call. """ # -i : ignore working tree status # --aggressive : handle more merge cases @@ -308,15 +307,16 @@ def new(cls, repo: "Repo", *tree_sha: Union[str, Tree]) -> "IndexFile": This method behaves like ``git-read-tree --aggressive`` when doing the merge. - :param repo: The repository treeish are located in. + :param repo: + The repository treeish are located in. :param tree_sha: 20 byte or 40 byte tree sha or tree objects. :return: - New IndexFile instance. Its path will be undefined. - If you intend to write such a merged Index, supply an alternate file_path - to its 'write' method. + New :class:`IndexFile` instance. Its path will be undefined. + If you intend to write such a merged Index, supply an alternate + ``file_path`` to its :meth:`write` method. """ tree_sha_bytes: List[bytes] = [to_bin_sha(str(t)) for t in tree_sha] base_entries = aggressive_tree_merge(repo.odb, tree_sha_bytes) @@ -335,37 +335,40 @@ def new(cls, repo: "Repo", *tree_sha: Union[str, Tree]) -> "IndexFile": @classmethod def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile": - """Merge the given treeish revisions into a new index which is returned. + R"""Merge the given treeish revisions into a new index which is returned. The original index will remain unaltered. :param repo: The repository treeish are located in. :param treeish: - One, two or three Tree Objects, Commits or 40 byte hexshas. The result - changes according to the amount of trees. - If 1 Tree is given, it will just be read into a new index - If 2 Trees are given, they will be merged into a new index using a - two way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' - one. It behaves like a fast-forward. - If 3 Trees are given, a 3-way merge will be performed with the first tree - being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' tree, - tree 3 is the 'other' one. + One, two or three :class:`~git.objects.tree.Tree` objects, + :class:`~git.objects.commit.Commit`\s or 40 byte hexshas. + + The result changes according to the amount of trees: + + 1. If 1 Tree is given, it will just be read into a new index. + 2. If 2 Trees are given, they will be merged into a new index using a two + way merge algorithm. Tree 1 is the 'current' tree, tree 2 is the 'other' + one. It behaves like a fast-forward. + 3. If 3 Trees are given, a 3-way merge will be performed with the first tree + being the common ancestor of tree 2 and tree 3. Tree 2 is the 'current' + tree, tree 3 is the 'other' one. :param kwargs: - Additional arguments passed to git-read-tree. + Additional arguments passed to ``git read-tree``. :return: - New IndexFile instance. It will point to a temporary index location which - does not exist anymore. If you intend to write such a merged Index, supply - an alternate file_path to its 'write' method. + New :class:`IndexFile` instance. It will point to a temporary index location + which does not exist anymore. If you intend to write such a merged Index, + supply an alternate ``file_path`` to its :meth:`write` method. :note: - In the three-way merge case, --aggressive will be specified to automatically - resolve more cases in a commonly correct manner. Specify trivial=True as kwarg - to override that. + In the three-way merge case, ``--aggressive`` will be specified to + automatically resolve more cases in a commonly correct manner. Specify + ``trivial=True`` as a keyword argument to override that. - As the underlying git-read-tree command takes into account the current + As the underlying ``git read-tree`` command takes into account the current index, it will be temporarily moved out of the way to prevent any unexpected interference. """ @@ -387,8 +390,8 @@ def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile arg_list.append("--index-output=%s" % tmp_index) arg_list.extend(treeish) - # Move the current index out of the way - otherwise the merge may fail - # as it considers existing entries. Moving it essentially clears the index. + # Move the current index out of the way - otherwise the merge may fail as it + # considers existing entries. Moving it essentially clears the index. # Unfortunately there is no 'soft' way to do it. # The TemporaryFileSwap ensures the original file gets put back. with TemporaryFileSwap(join_path_native(repo.git_dir, "index")): @@ -402,12 +405,13 @@ def from_tree(cls, repo: "Repo", *treeish: Treeish, **kwargs: Any) -> "IndexFile @unbare_repo def _iter_expand_paths(self: "IndexFile", paths: Sequence[PathLike]) -> Iterator[PathLike]: - """Expand the directories in list of paths to the corresponding paths accordingly. + """Expand the directories in list of paths to the corresponding paths + accordingly. :note: - git will add items multiple times even if a glob overlapped - with manually specified paths or if paths where specified multiple - times - we respect that and do not prune. + git will add items multiple times even if a glob overlapped with manually + specified paths or if paths where specified multiple times - we respect that + and do not prune. """ def raise_exc(e: Exception) -> NoReturn: @@ -436,11 +440,11 @@ def raise_exc(e: Exception) -> NoReturn: if not os.path.exists(abs_path) and ("?" in abs_path or "*" in abs_path or "[" in abs_path): resolved_paths = glob.glob(abs_path) # not abs_path in resolved_paths: - # a glob() resolving to the same path we are feeding it with - # is a glob() that failed to resolve. If we continued calling - # ourselves we'd endlessly recurse. If the condition below - # evaluates to true then we are likely dealing with a file - # whose name contains wildcard characters. + # A glob() resolving to the same path we are feeding it with is a + # glob() that failed to resolve. If we continued calling ourselves + # we'd endlessly recurse. If the condition below evaluates to true + # then we are likely dealing with a file whose name contains wildcard + # characters. if abs_path not in resolved_paths: for f in self._iter_expand_paths(glob.glob(abs_path)): yield str(f).replace(rs, "") @@ -468,22 +472,27 @@ def _write_path_to_stdin( fprogress: Callable[[PathLike, bool, PathLike], None], read_from_stdout: bool = True, ) -> Union[None, str]: - """Write path to proc.stdin and make sure it processes the item, including progress. + """Write path to ``proc.stdin`` and make sure it processes the item, including + progress. - :return: stdout string + :return: + stdout string - :param read_from_stdout: if True, proc.stdout will be read after the item - was sent to stdin. In that case, it will return None. + :param read_from_stdout: + If True, proc.stdout will be read after the item was sent to stdin. In that + case, it will return None. - :note: There is a bug in git-update-index that prevents it from sending - reports just in time. This is why we have a version that tries to - read stdout and one which doesn't. In fact, the stdout is not - important as the piped-in files are processed anyway and just in time. + :note: + There is a bug in git-update-index that prevents it from sending reports + just in time. This is why we have a version that tries to read stdout and + one which doesn't. In fact, the stdout is not important as the piped-in + files are processed anyway and just in time. - :note: Newlines are essential here, gits behaviour is somewhat inconsistent - on this depending on the version, hence we try our best to deal with - newlines carefully. Usually the last newline will not be sent, instead - we will close stdin to break the pipe. + :note: + Newlines are essential here, git's behaviour is somewhat inconsistent on + this depending on the version, hence we try our best to deal with newlines + carefully. Usually the last newline will not be sent, instead we will close + stdin to break the pipe. """ fprogress(filepath, False, item) rval: Union[None, str] = None @@ -506,12 +515,13 @@ def iter_blobs( self, predicate: Callable[[Tuple[StageType, Blob]], bool] = lambda t: True ) -> Iterator[Tuple[StageType, Blob]]: """ - :return: Iterator yielding tuples of Blob objects and stages, tuple(stage, Blob) + :return: + Iterator yielding tuples of Blob objects and stages, tuple(stage, Blob). :param predicate: Function(t) returning True if tuple(stage, Blob) should be yielded by the - iterator. A default filter, the BlobFilter, allows you to yield blobs - only if they match a given list of paths. + iterator. A default filter, the `~git.index.typ.BlobFilter`, allows you to + yield blobs only if they match a given list of paths. """ for entry in self.entries.values(): blob = entry.to_blob(self.repo) @@ -524,14 +534,13 @@ def iter_blobs( def unmerged_blobs(self) -> Dict[PathLike, List[Tuple[StageType, Blob]]]: """ :return: - Dict(path : list( tuple( stage, Blob, ...))), being - a dictionary associating a path in the index with a list containing - sorted stage/blob pairs. + Dict(path : list( tuple( stage, Blob, ...))), being a dictionary associating + a path in the index with a list containing sorted stage/blob pairs. :note: - Blobs that have been removed in one side simply do not exist in the - given stage. I.e. a file removed on the 'other' branch whose entries - are at stage 3 will not have a stage 3 entry. + Blobs that have been removed in one side simply do not exist in the given + stage. That is, a file removed on the 'other' branch whose entries are at + stage 3 will not have a stage 3 entry. """ is_unmerged_blob = lambda t: t[0] != 0 path_map: Dict[PathLike, List[Tuple[StageType, Blob]]] = {} @@ -556,9 +565,11 @@ def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> "IndexFile": For each path there may only be one blob, otherwise a ValueError will be raised claiming the path is already at stage 0. - :raise ValueError: if one of the blobs already existed at stage 0 + :raise ValueError: + If one of the blobs already existed at stage 0. - :return: self + :return: + self :note: You will have to write the index manually once you are done, i.e. @@ -588,8 +599,9 @@ def update(self) -> "IndexFile": """Reread the contents of our index file, discarding all cached information we might have. - :note: This is a possibly dangerous operations as it will discard your changes - to index.entries. + :note: + This is a possibly dangerous operations as it will discard your changes to + :attr:`index.entries `. :return: self """ @@ -598,16 +610,19 @@ def update(self) -> "IndexFile": return self def write_tree(self) -> Tree: - """Write this index to a corresponding Tree object into the repository's - object database and return it. + """Write this index to a corresponding :class:`~git.objects.tree.Tree` object + into the repository's object database and return it. - :return: Tree object representing this index. + :return: + :class:`~git.objects.tree.Tree` object representing this index. - :note: The tree will be written even if one or more objects the tree refers to - does not yet exist in the object database. This could happen if you added - Entries to the index directly. + :note: + The tree will be written even if one or more objects the tree refers to does + not yet exist in the object database. This could happen if you added entries + to the index directly. - :raise ValueError: if there are no entries in the cache + :raise ValueError: + If there are no entries in the cache. :raise UnmergedEntriesError: """ @@ -620,8 +635,8 @@ def write_tree(self) -> Tree: # Copy changed trees only. mdb.stream_copy(mdb.sha_iter(), self.repo.odb) - # Note: Additional deserialization could be saved if write_tree_from_cache - # would return sorted tree entries. + # Note: Additional deserialization could be saved if write_tree_from_cache would + # return sorted tree entries. root_tree = Tree(self.repo, binsha, path="") root_tree._cache = tree_items return root_tree @@ -639,8 +654,11 @@ def _process_diff_args( def _to_relative_path(self, path: PathLike) -> PathLike: """ - :return: Version of path relative to our git directory or raise ValueError - if it is not within our git directory""" + :return: Version of path relative to our git directory or raise + :class:`ValueError` if it is not within our git directory. + + :raise ValueError: + """ if not osp.isabs(path): return path if self.repo.bare: @@ -672,8 +690,12 @@ def _preprocess_add_items( return paths, entries def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry: - """Store file at filepath in the database and return the base index entry - Needs the git_working_dir decorator active ! This must be assured in the calling code""" + """Store file at filepath in the database and return the base index entry. + + :note: + This needs the git_working_dir decorator active! + This must be ensured in the calling code. + """ st = os.lstat(filepath) # Handles non-symlinks as well. if S_ISLNK(st.st_mode): # In PY3, readlink is a string, but we need bytes. @@ -744,8 +766,8 @@ def add( write: bool = True, write_extension_data: bool = False, ) -> List[BaseIndexEntry]: - """Add files from the working tree, specific blobs or BaseIndexEntries - to the index. + R"""Add files from the working tree, specific blobs, or + :class:`~git.index.typ.BaseIndexEntry`\s to the index. :param items: Multiple types of items are supported, types can be mixed within one call. @@ -753,9 +775,10 @@ def add( relative or absolute. - path string + Strings denote a relative or absolute path into the repository pointing - to an existing file, e.g., CHANGES, lib/myfile.ext, - '/home/gitrepo/lib/myfile.ext'. + to an existing file, e.g., ``CHANGES``, `lib/myfile.ext``, + ``/home/gitrepo/lib/myfile.ext``. Absolute paths must start with working tree directory of this index's repository to be considered valid. For example, if it was initialized @@ -769,11 +792,12 @@ def add( directories like ``lib``, which will add all the files within the directory and subdirectories. - This equals a straight git-add. + This equals a straight ``git add``. They are added at stage 0. - - Blob or Submodule object + - :class:~`git.objects.blob.Blob` or :class:`~git.objects.submodule.base.Submodule` object + Blobs are added as they are assuming a valid mode is set. The file they refer to may or may not exist in the file system, but must @@ -787,53 +811,57 @@ def add( except that the mode you have set will be kept. This allows you to create symlinks by settings the mode respectively and writing the target of the symlink directly into the file. This equals a default - Linux-Symlink which is not dereferenced automatically, except that it + Linux symlink which is not dereferenced automatically, except that it can be created on filesystems not supporting it as well. - Please note that globs or directories are not allowed in Blob objects. + Please note that globs or directories are not allowed in + :class:~`git.objects.blob.Blob` objects. They are added at stage 0. - - BaseIndexEntry or type + - :class:`~git.index.typ.BaseIndexEntry` or type + Handling equals the one of Blob objects, but the stage may be explicitly set. Please note that Index Entries require binary sha's. :param force: **CURRENTLY INEFFECTIVE** If True, otherwise ignored or excluded files will be added anyway. - As opposed to the git-add command, we enable this flag by default - as the API user usually wants the item to be added even though - they might be excluded. + As opposed to the ``git add`` command, we enable this flag by default as the + API user usually wants the item to be added even though they might be + excluded. :param fprogress: - Function with signature f(path, done=False, item=item) called for each - path to be added, one time once it is about to be added where done==False - and once after it was added where done=True. - item is set to the actual item we handle, either a Path or a BaseIndexEntry - Please note that the processed path is not guaranteed to be present - in the index already as the index is currently being processed. + Function with signature ``f(path, done=False, item=item)`` called for each + path to be added, one time once it is about to be added where ``done=False`` + and once after it was added where ``done=True``. + ``item`` is set to the actual item we handle, either a Path or a + :class:`~git.index.typ.BaseIndexEntry`. + Please note that the processed path is not guaranteed to be present in the + index already as the index is currently being processed. :param path_rewriter: - Function with signature (string) func(BaseIndexEntry) function returning a - path for each passed entry which is the path to be actually recorded for the - object created from entry.path. This allows you to write an index which is - not identical to the layout of the actual files on your hard-disk. If not - None and ``items`` contain plain paths, these paths will be converted to - Entries beforehand and passed to the path_rewriter. Please note that - entry.path is relative to the git repository. + Function, with signature ``(string) func(BaseIndexEntry)``, returning a path + for each passed entry which is the path to be actually recorded for the + object created from :attr:`entry.path `. + This allows you to write an index which is not identical to the layout of + the actual files on your hard-disk. If not None and `items` contain plain + paths, these paths will be converted to Entries beforehand and passed to the + path_rewriter. Please note that ``entry.path`` is relative to the git + repository. :param write: - If True, the index will be written once it was altered. Otherwise - the changes only exist in memory and are not available to git commands. + If True, the index will be written once it was altered. Otherwise the + changes only exist in memory and are not available to git commands. :param write_extension_data: If True, extension data will be written back to the index. This can lead to issues in case it is containing the 'TREE' extension, which will cause the - `git commit` command to write an old tree, instead of a new one representing - the now changed index. + ``git commit`` command to write an old tree, instead of a new one + representing the now changed index. This doesn't matter if you use :meth:`IndexFile.commit`, which ignores the - `TREE` extension altogether. You should set it to True if you intend to use + 'TREE' extension altogether. You should set it to True if you intend to use :meth:`IndexFile.commit` exclusively while maintaining support for third-party extensions. Besides that, you can usually safely ignore the built-in extensions when using GitPython on repositories that are not @@ -843,18 +871,20 @@ def add( http://opensource.apple.com/source/Git/Git-26/src/git-htmldocs/technical/index-format.txt :return: - List(BaseIndexEntries) representing the entries just actually added. + List of :class:`~git.index.typ.BaseIndexEntry`\s representing the entries + just actually added. :raise OSError: - If a supplied Path did not exist. Please note that BaseIndexEntry - Objects that do not have a null sha will be added even if their paths - do not exist. + If a supplied Path did not exist. Please note that + :class:`~git.index.typ.BaseIndexEntry` objects that do not have a null sha + will be added even if their paths do not exist. """ - # Sort the entries into strings and Entries. Blobs are converted to entries automatically. + # Sort the entries into strings and Entries. + # Blobs are converted to entries automatically. # Paths can be git-added. For everything else we use git-update-index. paths, entries = self._preprocess_add_items(items) entries_added: List[BaseIndexEntry] = [] - # This code needs a working tree, therefore we try not to run it unless required. + # This code needs a working tree, so we try not to run it unless required. # That way, we are OK on a bare repository as well. # If there are no paths, the rewriter has nothing to do either. if paths: @@ -897,7 +927,8 @@ def handle_null_entries(self: "IndexFile") -> None: # END null_entry handling # REWRITE PATHS - # If we have to rewrite the entries, do so now, after we have generated all object sha's. + # If we have to rewrite the entries, do so now, after we have generated all + # object sha's. if path_rewriter: for i, e in enumerate(entries): entries[i] = BaseIndexEntry((e.mode, e.binsha, e.stage, path_rewriter(e))) @@ -955,26 +986,28 @@ def remove( working_tree: bool = False, **kwargs: Any, ) -> List[str]: - """Remove the given items from the index and optionally from - the working tree as well. + R"""Remove the given items from the index and optionally from the working tree + as well. :param items: Multiple types of items are supported which may be be freely mixed. - path string + Remove the given path at all stages. If it is a directory, you must - specify the r=True keyword argument to remove all file entries - below it. If absolute paths are given, they will be converted - to a path relative to the git repository directory containing - the working tree + specify the ``r=True`` keyword argument to remove all file entries below + it. If absolute paths are given, they will be converted to a path + relative to the git repository directory containing the working tree + + The path string may include globs, such as \*.c. - The path string may include globs, such as \\*.c. + - :class:~`git.objects.blob.Blob` object - - Blob Object Only the path portion is used in this case. - - BaseIndexEntry or compatible type - The only relevant information here Yis the path. The stage is ignored. + - :class:`~git.index.typ.BaseIndexEntry` or compatible type + + The only relevant information here is the path. The stage is ignored. :param working_tree: If True, the entry will also be removed from the working tree, physically @@ -982,14 +1015,15 @@ def remove( in it. :param kwargs: - Additional keyword arguments to be passed to git-rm, such - as 'r' to allow recursive removal of + Additional keyword arguments to be passed to ``git rm``, such as ``r`` to + allow recursive removal. :return: - List(path_string, ...) list of repository relative paths that have - been removed effectively. - This is interesting to know in case you have provided a directory or - globs. Paths are relative to the repository. + List(path_string, ...) list of repository relative paths that have been + removed effectively. + + This is interesting to know in case you have provided a directory or globs. + Paths are relative to the repository. """ args = [] if not working_tree: @@ -1013,28 +1047,38 @@ def move( **kwargs: Any, ) -> List[Tuple[str, str]]: """Rename/move the items, whereas the last item is considered the destination of - the move operation. If the destination is a file, the first item (of two) - must be a file as well. If the destination is a directory, it may be preceded - by one or more directories or files. + the move operation. + + If the destination is a file, the first item (of two) must be a file as well. + + If the destination is a directory, it may be preceded by one or more directories + or files. The working tree will be affected in non-bare repositories. - :parma items: + :param items: Multiple types of items are supported, please see the :meth:`remove` method for reference. + :param skip_errors: - If True, errors such as ones resulting from missing source files will - be skipped. + If True, errors such as ones resulting from missing source files will be + skipped. + :param kwargs: - Additional arguments you would like to pass to git-mv, such as dry_run - or force. + Additional arguments you would like to pass to ``git mv``, such as + ``dry_run`` or ``force``. - :return: List(tuple(source_path_string, destination_path_string), ...) - A list of pairs, containing the source file moved as well as its - actual destination. Relative to the repository root. + :return: + List(tuple(source_path_string, destination_path_string), ...) + + A list of pairs, containing the source file moved as well as its actual + destination. Relative to the repository root. + + :raise ValueError: + If only one item was given. - :raise ValueError: If only one item was given - :raise GitCommandError: If git could not handle your request + :raise GitCommandError: + If git could not handle your request. """ args = [] if skip_errors: @@ -1047,13 +1091,13 @@ def move( was_dry_run = kwargs.pop("dry_run", kwargs.pop("n", None)) kwargs["dry_run"] = True - # First execute rename in dryrun so the command tells us what it actually does + # First execute rename in dry run so the command tells us what it actually does # (for later output). out = [] mvlines = self.repo.git.mv(args, paths, **kwargs).splitlines() - # Parse result - first 0:n/2 lines are 'checking ', the remaining ones - # are the 'renaming' ones which we parse. + # Parse result - first 0:n/2 lines are 'checking ', the remaining ones are the + # 'renaming' ones which we parse. for ln in range(int(len(mvlines) / 2), len(mvlines)): tokens = mvlines[ln].split(" to ") assert len(tokens) == 2, "Too many tokens in %s" % mvlines[ln] @@ -1066,7 +1110,7 @@ def move( # Either prepare for the real run, or output the dry-run result. if was_dry_run: return out - # END handle dryrun + # END handle dry run # Now apply the actual operation. kwargs.pop("dry_run") @@ -1085,17 +1129,22 @@ def commit( commit_date: Union[datetime.datetime, str, None] = None, skip_hooks: bool = False, ) -> Commit: - """Commit the current default index file, creating a Commit object. + """Commit the current default index file, creating a + :class:`~git.objects.commit.Commit` object. For more information on the arguments, see :meth:`Commit.create_from_tree `. - :note: If you have manually altered the :attr:`entries` member of this instance, - don't forget to :meth:`write` your changes to disk beforehand. - Passing ``skip_hooks=True`` is the equivalent of using ``-n`` - or ``--no-verify`` on the command line. + :note: + If you have manually altered the :attr:`entries` member of this instance, + don't forget to :meth:`write` your changes to disk beforehand. + + :note: + Passing ``skip_hooks=True`` is the equivalent of using ``-n`` or + ``--no-verify`` on the command line. - :return: :class:`Commit` object representing the new commit + :return: + :class:`~git.objects.commit.Commit` object representing the new commit """ if not skip_hooks: run_commit_hook("pre-commit", self) @@ -1160,44 +1209,49 @@ def checkout( """Check out the given paths or all files from the version known to the index into the working tree. - :note: Be sure you have written pending changes using the :meth:`write` method - in case you have altered the entries dictionary directly. + :note: + Be sure you have written pending changes using the :meth:`write` method in + case you have altered the entries dictionary directly. :param paths: If None, all paths in the index will be checked out. Otherwise an iterable - of relative or absolute paths or a single path pointing to files or directories - in the index is expected. + of relative or absolute paths or a single path pointing to files or + directories in the index is expected. :param force: - If True, existing files will be overwritten even if they contain local modifications. - If False, these will trigger a :class:`CheckoutError`. + If True, existing files will be overwritten even if they contain local + modifications. + If False, these will trigger a :class:`~git.exc.CheckoutError`. :param fprogress: - see :func:`IndexFile.add` for signature and explanation. + See :meth:`IndexFile.add` for signature and explanation. + The provided progress information will contain None as path and item if no - explicit paths are given. Otherwise progress information will be send - prior and after a file has been checked out. + explicit paths are given. Otherwise progress information will be send prior + and after a file has been checked out. :param kwargs: - Additional arguments to be passed to git-checkout-index. + Additional arguments to be passed to ``git checkout-index``. :return: - iterable yielding paths to files which have been checked out and are + Iterable yielding paths to files which have been checked out and are guaranteed to match the version stored in the index. - :raise exc.CheckoutError: - If at least one file failed to be checked out. This is a summary, - hence it will checkout as many files as it can anyway. - If one of files or directories do not exist in the index - ( as opposed to the original git command who ignores them ). - Raise GitCommandError if error lines could not be parsed - this truly is - an exceptional state. - - .. note:: The checkout is limited to checking out the files in the - index. Files which are not in the index anymore and exist in - the working tree will not be deleted. This behaviour is fundamentally - different to *head.checkout*, i.e. if you want git-checkout like behaviour, - use head.checkout instead of index.checkout. + :raise git.exc.CheckoutError: + If at least one file failed to be checked out. This is a summary, hence it + will checkout as many files as it can anyway. + If one of files or directories do not exist in the index (as opposed to the + original git command, which ignores them). + + :raise git.exc.GitCommandError: + If error lines could not be parsed - this truly is an exceptional state. + + :note: + The checkout is limited to checking out the files in the index. Files which + are not in the index anymore and exist in the working tree will not be + deleted. This behaviour is fundamentally different to ``head.checkout``, + i.e. if you want ``git checkout`` like behaviour, use ``head.checkout`` + instead of ``index.checkout``. """ args = ["--index"] if force: @@ -1276,9 +1330,9 @@ def handle_stderr(proc: "Popen[bytes]", iter_checked_out_files: Iterable[PathLik if isinstance(paths, str): paths = [paths] - # Make sure we have our entries loaded before we start checkout_index, - # which will hold a lock on it. We try to get the lock as well during - # our entries initialization. + # Make sure we have our entries loaded before we start checkout_index, which + # will hold a lock on it. We try to get the lock as well during our entries + # initialization. self.entries args.append("--stdin") @@ -1339,40 +1393,50 @@ def reset( head: bool = False, **kwargs: Any, ) -> "IndexFile": - """Reset the index to reflect the tree at the given commit. This will not - adjust our HEAD reference as opposed to HEAD.reset by default. + """Reset the index to reflect the tree at the given commit. This will not adjust + our ``HEAD`` reference by default, as opposed to + :meth:`HEAD.reset `. :param commit: - Revision, Reference or Commit specifying the commit we should represent. - If you want to specify a tree only, use IndexFile.from_tree and overwrite - the default index. + Revision, :class:`~git.refs.reference.Reference` or + :class:`~git.objects.commit.Commit` specifying the commit we should + represent. + + If you want to specify a tree only, use :meth:`IndexFile.from_tree` and + overwrite the default index. :param working_tree: If True, the files in the working tree will reflect the changed index. - If False, the working tree will not be touched + If False, the working tree will not be touched. Please note that changes to the working copy will be discarded without - warning ! + warning! :param head: If True, the head will be set to the given commit. This is False by default, - but if True, this method behaves like HEAD.reset. + but if True, this method behaves like + :meth:`HEAD.reset `. - :param paths: if given as an iterable of absolute or repository-relative paths, - only these will be reset to their state at the given commit-ish. + :param paths: + If given as an iterable of absolute or repository-relative paths, only these + will be reset to their state at the given commit-ish. The paths need to exist at the commit, otherwise an exception will be raised. :param kwargs: - Additional keyword arguments passed to git-reset + Additional keyword arguments passed to ``git reset``. - .. note:: IndexFile.reset, as opposed to HEAD.reset, will not delete any files - in order to maintain a consistent working tree. Instead, it will just - check out the files according to their state in the index. - If you want git-reset like behaviour, use *HEAD.reset* instead. + :note: + :meth:`IndexFile.reset`, as opposed to + :meth:`HEAD.reset `, will not delete any files in + order to maintain a consistent working tree. Instead, it will just check out + the files according to their state in the index. + If you want ``git reset``-like behaviour, use + :meth:`HEAD.reset ` instead. - :return: self""" - # What we actually want to do is to merge the tree into our existing - # index, which is what git-read-tree does. + :return: self + """ + # What we actually want to do is to merge the tree into our existing index, + # which is what git-read-tree does. new_inst = type(self).from_tree(self.repo, commit) if not paths: self.entries = new_inst.entries @@ -1413,14 +1477,15 @@ def diff( create_patch: bool = False, **kwargs: Any, ) -> git_diff.DiffIndex: - """Diff this index against the working copy or a Tree or Commit object. + """Diff this index against the working copy or a :class:`~git.objects.tree.Tree` + or :class:`~git.objects.commit.Commit` object. For documentation of the parameters and return values, see :meth:`Diffable.diff `. :note: - Will only work with indices that represent the default git index as - they have not been initialized with a stream. + Will only work with indices that represent the default git index as they + have not been initialized with a stream. """ # Only run if we are the default repository index. @@ -1430,9 +1495,9 @@ def diff( if other is self.Index: return git_diff.DiffIndex() - # Index against anything but None is a reverse diff with the respective - # item. Handle existing -R flags properly. Transform strings to the object - # so that we can call diff on it. + # Index against anything but None is a reverse diff with the respective item. + # Handle existing -R flags properly. + # Transform strings to the object so that we can call diff on it. if isinstance(other, str): other = self.repo.rev_parse(other) # END object conversion diff --git a/git/index/fun.py b/git/index/fun.py index 580493f6d..785a1c748 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -1,7 +1,8 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ -"""Standalone functions to accompany the index implementation and make it more versatile.""" +"""Standalone functions to accompany the index implementation and make it more +versatile.""" from io import BytesIO import os @@ -78,9 +79,15 @@ def _has_file_extension(path: str) -> str: def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None: """Run the commit hook of the given name. Silently ignore hooks that do not exist. - :param name: name of hook, like 'pre-commit' - :param index: IndexFile instance - :param args: Arguments passed to hook file + :param name: + Name of hook, like ``pre-commit``. + + :param index: + :class:`~git.index.base.IndexFile` instance. + + :param args: + Arguments passed to hook file. + :raises HookExecutionError: """ hp = hook_path(name, index.repo.git_dir) @@ -121,8 +128,8 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None: def stat_mode_to_index_mode(mode: int) -> int: - """Convert the given mode from a stat call to the corresponding index mode - and return it.""" + """Convert the given mode from a stat call to the corresponding index mode and + return it.""" if S_ISLNK(mode): # symlinks return S_IFLNK if S_ISDIR(mode) or S_IFMT(mode) == S_IFGITLINK: # submodules @@ -138,16 +145,19 @@ def write_cache( ) -> None: """Write the cache represented by entries to a stream. - :param entries: **sorted** list of entries + :param entries: + **Sorted** list of entries. - :param stream: stream to wrap into the AdapterStreamCls - it is used for - final output. + :param stream: + Stream to wrap into the AdapterStreamCls - it is used for final output. - :param ShaStreamCls: Type to use when writing to the stream. It produces a sha - while writing to it, before the data is passed on to the wrapped stream + :param ShaStreamCls: + Type to use when writing to the stream. It produces a sha while writing to it, + before the data is passed on to the wrapped stream. - :param extension_data: any kind of data to write as a trailer, it must begin - a 4 byte identifier, followed by its size (4 bytes). + :param extension_data: + Any kind of data to write as a trailer, it must begin a 4 byte identifier, + followed by its size (4 bytes). """ # Wrap the stream into a compatible writer. stream_sha = ShaStreamCls(stream) @@ -197,7 +207,7 @@ def write_cache( def read_header(stream: IO[bytes]) -> Tuple[int, int]: - """Return tuple(version_long, num_entries) from the given stream""" + """Return tuple(version_long, num_entries) from the given stream.""" type_id = stream.read(4) if type_id != b"DIRC": raise AssertionError("Invalid index file header: %r" % type_id) @@ -210,9 +220,13 @@ def read_header(stream: IO[bytes]) -> Tuple[int, int]: def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, int]: - """:return: Key suitable to be used for the index.entries dictionary + """ + :return: + Key suitable to be used for the :attr:`index.entries + ` dictionary. - :param entry: One instance of type BaseIndexEntry or the path and the stage + :param entry: + One instance of type BaseIndexEntry or the path and the stage. """ # def is_entry_key_tup(entry_key: Tuple) -> TypeGuard[Tuple[PathLike, int]]: @@ -234,12 +248,14 @@ def read_cache( ) -> Tuple[int, Dict[Tuple[PathLike, int], "IndexEntry"], bytes, bytes]: """Read a cache file from the given stream. - :return: tuple(version, entries_dict, extension_data, content_sha) + :return: + tuple(version, entries_dict, extension_data, content_sha) - * version is the integer version number. - * entries dict is a dictionary which maps IndexEntry instances to a path at a stage. - * extension_data is '' or 4 bytes of type + 4 bytes of size + size bytes. - * content_sha is a 20 byte sha on all cache file contents. + * *version* is the integer version number. + * *entries_dict* is a dictionary which maps IndexEntry instances to a path at a + stage. + * *extension_data* is ``""`` or 4 bytes of type + 4 bytes of size + size bytes. + * *content_sha* is a 20 byte sha on all cache file contents. """ version, num_entries = read_header(stream) count = 0 @@ -285,15 +301,25 @@ def read_cache( def write_tree_from_cache( entries: List[IndexEntry], odb: "GitCmdObjectDB", sl: slice, si: int = 0 ) -> Tuple[bytes, List["TreeCacheTup"]]: - """Create a tree from the given sorted list of entries and put the respective + R"""Create a tree from the given sorted list of entries and put the respective trees into the given object database. - :param entries: **Sorted** list of IndexEntries - :param odb: Object database to store the trees in - :param si: Start index at which we should start creating subtrees - :param sl: Slice indicating the range we should process on the entries list - :return: tuple(binsha, list(tree_entry, ...)) a tuple of a sha and a list of - tree entries being a tuple of hexsha, mode, name + :param entries: + **Sorted** list of :class:`~git.index.typ.IndexEntry`\s. + + :param odb: + Object database to store the trees in. + + :param si: + Start index at which we should start creating subtrees. + + :param sl: + Slice indicating the range we should process on the entries list. + + :return: + tuple(binsha, list(tree_entry, ...)) + + A tuple of a sha and a list of tree entries being a tuple of hexsha, mode, name. """ tree_items: List["TreeCacheTup"] = [] @@ -346,15 +372,17 @@ def _tree_entry_to_baseindexentry(tree_entry: "TreeCacheTup", stage: int) -> Bas def aggressive_tree_merge(odb: "GitCmdObjectDB", tree_shas: Sequence[bytes]) -> List[BaseIndexEntry]: - """ - :return: List of BaseIndexEntries representing the aggressive merge of the given - trees. All valid entries are on stage 0, whereas the conflicting ones are left - on stage 1, 2 or 3, whereas stage 1 corresponds to the common ancestor tree, - 2 to our tree and 3 to 'their' tree. - - :param tree_shas: 1, 2 or 3 trees as identified by their binary 20 byte shas. - If 1 or two, the entries will effectively correspond to the last given tree. - If 3 are given, a 3 way merge is performed. + R""" + :return: + List of :class:`~git.index.typ.BaseIndexEntry`\s representing the aggressive + merge of the given trees. All valid entries are on stage 0, whereas the + conflicting ones are left on stage 1, 2 or 3, whereas stage 1 corresponds to the + common ancestor tree, 2 to our tree and 3 to 'their' tree. + + :param tree_shas: + 1, 2 or 3 trees as identified by their binary 20 byte shas. If 1 or two, the + entries will effectively correspond to the last given tree. If 3 are given, a 3 + way merge is performed. """ out: List[BaseIndexEntry] = [] diff --git a/git/index/typ.py b/git/index/typ.py index 894e6f16d..dce67626f 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -58,7 +58,8 @@ def __call__(self, stage_blob: Tuple[StageType, Blob]) -> bool: blob_path: Path = blob_pathlike if isinstance(blob_pathlike, Path) else Path(blob_pathlike) for pathlike in self.paths: path: Path = pathlike if isinstance(pathlike, Path) else Path(pathlike) - # TODO: Change to use `PosixPath.is_relative_to` once Python 3.8 is no longer supported. + # TODO: Change to use `PosixPath.is_relative_to` once Python 3.8 is no + # longer supported. filter_parts: List[str] = path.parts blob_parts: List[str] = blob_path.parts if len(filter_parts) > len(blob_parts): @@ -69,8 +70,11 @@ def __call__(self, stage_blob: Tuple[StageType, Blob]) -> bool: class BaseIndexEntryHelper(NamedTuple): - """Typed namedtuple to provide named attribute access for BaseIndexEntry. - Needed to allow overriding __new__ in child class to preserve backwards compat.""" + """Typed named tuple to provide named attribute access for BaseIndexEntry. + + This is needed to allow overriding ``__new__`` in child class to preserve backwards + compatibility. + """ mode: int binsha: bytes @@ -101,7 +105,8 @@ def __new__( Tuple[int, bytes, int, PathLike, bytes, bytes, int, int, int, int, int], ], ) -> "BaseIndexEntry": - """Override __new__ to allow construction from a tuple for backwards compatibility""" + """Override ``__new__`` to allow construction from a tuple for backwards + compatibility.""" return super().__new__(cls, *inp_tuple) def __str__(self) -> str: diff --git a/git/index/util.py b/git/index/util.py index 125925783..8aedb7284 100644 --- a/git/index/util.py +++ b/git/index/util.py @@ -34,8 +34,8 @@ class TemporaryFileSwap: - """Utility class moving a file to a temporary location within the same directory - and moving it back on to where on object deletion.""" + """Utility class moving a file to a temporary location within the same directory and + moving it back on to where on object deletion.""" __slots__ = ("file_path", "tmp_file_path") @@ -66,12 +66,12 @@ def __exit__( def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]: """Decorator for functions that alter the index using the git command. This would - invalidate our possibly existing entries dictionary which is why it must be - deleted to allow it to be lazily reread later. + invalidate our possibly existing entries dictionary which is why it must be deleted + to allow it to be lazily reread later. :note: - This decorator will not be required once all functions are implemented - natively which in fact is possible, but probably not feasible performance wise. + This decorator will not be required once all functions are implemented natively + which in fact is possible, but probably not feasible performance wise. """ @wraps(func) From ca2ab6137b2cae27fb5c68dbd63c83bd8e9b429e Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 14:25:24 -0500 Subject: [PATCH 14/61] Rewrite post_clear_cache note So that it just describes the current situation, rather than suggesting a future direction for IndexFile. This is for #1847. --- git/index/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/index/util.py b/git/index/util.py index 8aedb7284..347e8747f 100644 --- a/git/index/util.py +++ b/git/index/util.py @@ -70,8 +70,8 @@ def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]: to allow it to be lazily reread later. :note: - This decorator will not be required once all functions are implemented natively - which in fact is possible, but probably not feasible performance wise. + This decorator is required because not all functions related to + :class:`~git.index.base.IndexFile` are implemented natively. """ @wraps(func) From 37421e1b8f0618e1d4e5bd322aeb60d23b58e1b9 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 15:06:19 -0500 Subject: [PATCH 15/61] Further revise post_clear_cache docstring For #1847. This removes the note, and splits out some related material from the docstring's top line into a second paragraph for readability (since the first sentence of the top line was complete, and described usage). --- git/index/util.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/git/index/util.py b/git/index/util.py index 347e8747f..0bad11571 100644 --- a/git/index/util.py +++ b/git/index/util.py @@ -65,13 +65,10 @@ def __exit__( def post_clear_cache(func: Callable[..., _T]) -> Callable[..., _T]: - """Decorator for functions that alter the index using the git command. This would - invalidate our possibly existing entries dictionary which is why it must be deleted - to allow it to be lazily reread later. + """Decorator for functions that alter the index using the git command. - :note: - This decorator is required because not all functions related to - :class:`~git.index.base.IndexFile` are implemented natively. + When a git command alters the index, this invalidates our possibly existing entries + dictionary, which is why it must be deleted to allow it to be lazily reread later. """ @wraps(func) From ca32c226e9d52001e7f2feaa516bb2483210db82 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 15:49:04 -0500 Subject: [PATCH 16/61] Condense output_stream description in Git.execute docstring For #1845. --- git/cmd.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 760a9ff12..cab089746 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -1018,12 +1018,8 @@ def execute( :param output_stream: If set to a file-like object, data produced by the git command will be - output to the given stream directly. - This feature only has any effect if `as_process` is False. Processes will - always be created with a pipe due to issues with subprocess. - This merely is a workaround as data will be copied from the - output pipe to the given output stream directly. - Judging from the implementation, you shouldn't use this parameter! + copied to the given stream instead of being returned as a string. + This feature only has any effect if `as_process` is False. :param stdout_as_string: If False, the command's standard output will be bytes. Otherwise, it will be From 3813bfbee34640da121209ecea0388b0cd1d39cf Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 17:52:54 -0500 Subject: [PATCH 17/61] Clarify Submodule.branch_path documentation This changes "Full (relative) path" to "Complete relative path" in the wording used to describe the branch_path initializer parameter and property of the Submodule class. This is to prevent confusion, since "full path" means something like "absolute path" (and is now used as such in error messages introduced since these docstrings were last edited). --- git/objects/submodule/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index d08e967b8..497e5a06a 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -124,7 +124,7 @@ def __init__( the ``url`` parameter. :param parent_commit: See :meth:`set_parent_commit`. :param url: The URL to the remote repository which is the submodule. - :param branch_path: Full (relative) path to ref to checkout when cloning the + :param branch_path: Complete relative path to ref to checkout when cloning the remote repository. """ super().__init__(repo, binsha, mode, path) @@ -1322,7 +1322,7 @@ def branch(self) -> "Head": @property def branch_path(self) -> PathLike: """ - :return: Full (relative) path as string to the branch we would checkout + :return: Complete relative path as string to the branch we would checkout from the remote and track """ return self._branch_path From 63c62ed801380be88ecdf7d686ddcc7cc13cdb29 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 19:43:59 -0500 Subject: [PATCH 18/61] Revise docstrings within git.objects.submodule --- git/objects/submodule/base.py | 82 ++++++++++++------- git/objects/submodule/root.py | 148 +++++++++++++++++++--------------- git/objects/submodule/util.py | 15 ++-- 3 files changed, 147 insertions(+), 98 deletions(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 497e5a06a..58b474fdd 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -60,7 +60,8 @@ class UpdateProgress(RemoteProgress): """Class providing detailed progress information to the caller who should - derive from it and implement the ``update(...)`` message.""" + derive from it and implement the + :meth:`update(...) ` message.""" CLONE, FETCH, UPDWKTREE = [1 << x for x in range(RemoteProgress._num_op_codes, RemoteProgress._num_op_codes + 3)] _num_op_codes: int = RemoteProgress._num_op_codes + 3 @@ -76,8 +77,8 @@ class UpdateProgress(RemoteProgress): # IndexObject comes via the util module. It's a 'hacky' fix thanks to Python's import -# mechanism, which causes plenty of trouble if the only reason for packages and -# modules is refactoring - subpackages shouldn't depend on parent packages. +# mechanism, which causes plenty of trouble if the only reason for packages and modules +# is refactoring - subpackages shouldn't depend on parent packages. class Submodule(IndexObject, TraversableIterableObj): """Implements access to a git submodule. They are special in that their sha represents a commit in the submodule's repository which is to be checked out @@ -119,11 +120,19 @@ def __init__( We only document the parameters that differ from :class:`~git.objects.base.IndexObject`. - :param repo: Our parent repository. - :param binsha: Binary sha referring to a commit in the remote repository. See - the ``url`` parameter. - :param parent_commit: See :meth:`set_parent_commit`. - :param url: The URL to the remote repository which is the submodule. + :param repo: + Our parent repository. + + :param binsha: + Binary sha referring to a commit in the remote repository. + See the `url` parameter. + + :param parent_commit: + See :meth:`set_parent_commit`. + + :param url: + The URL to the remote repository which is the submodule. + :param branch_path: Complete relative path to ref to checkout when cloning the remote repository. """ @@ -1313,9 +1322,11 @@ def exists(self) -> bool: @property def branch(self) -> "Head": """ - :return: The branch instance that we are to checkout + :return: + The branch instance that we are to checkout - :raise InvalidGitRepositoryError: If our module is not yet checked out + :raise InvalidGitRepositoryError: + If our module is not yet checked out. """ return mkhead(self.module(), self._branch_path) @@ -1329,7 +1340,10 @@ def branch_path(self) -> PathLike: @property def branch_name(self) -> str: - """:return: The name of the branch, which is the shortest possible branch name""" + """ + :return: + The name of the branch, which is the shortest possible branch name + """ # Use an instance method, for this we create a temporary Head instance # which uses a repository that is available at least (it makes no difference). return git.Head(self.repo, self._branch_path).name @@ -1342,9 +1356,11 @@ def url(self) -> str: @property def parent_commit(self) -> "Commit_ish": """ - :return: Commit instance with the tree containing the .gitmodules file + :return: + Commit instance with the tree containing the ``.gitmodules`` file - :note: Will always point to the current head's commit if it was not set explicitly. + :note: + Will always point to the current head's commit if it was not set explicitly. """ if self._parent_commit is None: return self.repo.commit() @@ -1353,33 +1369,40 @@ def parent_commit(self) -> "Commit_ish": @property def name(self) -> str: """ - :return: The name of this submodule. It is used to identify it within the - .gitmodules file. + :return: + The name of this submodule. It is used to identify it within the + ``.gitmodules`` file. - :note: By default, this is the name is the path at which to find the submodule, - but in GitPython it should be a unique identifier similar to the identifiers + :note: + By default, this is the name is the path at which to find the submodule, but + in GitPython it should be a unique identifier similar to the identifiers used for remotes, which allows to change the path of the submodule easily. """ return self._name def config_reader(self) -> SectionConstraint[SubmoduleConfigParser]: """ - :return: ConfigReader instance which allows you to query the configuration - values of this submodule, as provided by the .gitmodules file. + :return: + ConfigReader instance which allows you to query the configuration values of + this submodule, as provided by the ``.gitmodules`` file. - :note: The config reader will actually read the data directly from the - repository and thus does not need nor care about your working tree. + :note: + The config reader will actually read the data directly from the repository + and thus does not need nor care about your working tree. - :note: Should be cached by the caller and only kept as long as needed. + :note: + Should be cached by the caller and only kept as long as needed. - :raise IOError: If the .gitmodules file/blob could not be read. + :raise IOError: + If the ``.gitmodules`` file/blob could not be read. """ return self._config_parser_constrained(read_only=True) def children(self) -> IterableList["Submodule"]: """ - :return: IterableList(Submodule, ...) an iterable list of submodules instances - which are children of this submodule or 0 if the submodule is not checked out. + :return: + IterableList(Submodule, ...) An iterable list of submodules instances which + are children of this submodule or 0 if the submodule is not checked out. """ return self._get_intermediate_items(self) @@ -1395,7 +1418,10 @@ def iter_items( *Args: Any, **kwargs: Any, ) -> Iterator["Submodule"]: - """:return: Iterator yielding Submodule instances available in the given repository""" + """ + :return: + Iterator yielding Submodule instances available in the given repository + """ try: pc = repo.commit(parent_commit) # Parent commit instance parser = cls._config_parser(repo, pc, read_only=True) @@ -1423,8 +1449,8 @@ def iter_items( entry = index.entries[index.entry_key(p, 0)] sm = Submodule(repo, entry.binsha, entry.mode, entry.path) except KeyError: - # The submodule doesn't exist, probably it wasn't - # removed from the .gitmodules file. + # The submodule doesn't exist, probably it wasn't removed from the + # .gitmodules file. continue # END handle keyerror # END handle critical error diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py index dde384bbe..8ef874f90 100644 --- a/git/objects/submodule/root.py +++ b/git/objects/submodule/root.py @@ -26,7 +26,8 @@ class RootUpdateProgress(UpdateProgress): - """Utility class which adds more opcodes to the UpdateProgress.""" + """Utility class which adds more opcodes to + :class:`~git.objects.submodule.base.UpdateProgress`.""" REMOVE, PATHCHANGE, BRANCHCHANGE, URLCHANGE = [ 1 << x for x in range(UpdateProgress._num_op_codes, UpdateProgress._num_op_codes + 4) @@ -91,43 +92,59 @@ def update( This method behaves smartly by determining changes of the path of a submodules repository, next to changes to the to-be-checked-out commit or the branch to be checked out. This works if the submodules ID does not change. + Additionally it will detect addition and removal of submodules, which will be handled gracefully. - :param previous_commit: If set to a commit-ish, the commit we should use as the - previous commit the HEAD pointed to before it was set to the commit it - points to now. - If None, it defaults to ``HEAD@{1}`` otherwise - :param recursive: if True, the children of submodules will be updated as well - using the same technique. - :param force_remove: If submodules have been deleted, they will be forcibly - removed. Otherwise the update may fail if a submodule's repository cannot be - deleted as changes have been made to it. + :param previous_commit: + If set to a commit-ish, the commit we should use as the previous commit the + HEAD pointed to before it was set to the commit it points to now. + If None, it defaults to ``HEAD@{1}`` otherwise. + + :param recursive: + If True, the children of submodules will be updated as well using the same + technique. + + :param force_remove: + If submodules have been deleted, they will be forcibly removed. Otherwise + the update may fail if a submodule's repository cannot be deleted as changes + have been made to it. (See :meth:`Submodule.update ` for more information.) - :param init: If we encounter a new module which would need to be initialized, - then do it. - :param to_latest_revision: If True, instead of checking out the revision pointed - to by this submodule's sha, the checked out tracking branch will be merged - with the latest remote branch fetched from the repository's origin. + + :param init: + If we encounter a new module which would need to be initialized, then do it. + + :param to_latest_revision: + If True, instead of checking out the revision pointed to by this submodule's + sha, the checked out tracking branch will be merged with the latest remote + branch fetched from the repository's origin. Unless `force_reset` is specified, a local tracking branch will never be reset into its past, therefore the remote branch must be in the future for this to have an effect. - :param force_reset: If True, submodules may checkout or reset their branch even - if the repository has pending changes that would be overwritten, or if the - local tracking branch is in the future of the remote tracking branch and - would be reset into its past. - :param progress: :class:`RootUpdateProgress` instance or None if no progress - should be sent. - :param dry_run: If True, operations will not actually be performed. Progress - messages will change accordingly to indicate the WOULD DO state of the - operation. - :param keep_going: If True, we will ignore but log all errors, and keep going - recursively. Unless `dry_run` is set as well, `keep_going` could cause + + :param force_reset: + If True, submodules may checkout or reset their branch even if the + repository has pending changes that would be overwritten, or if the local + tracking branch is in the future of the remote tracking branch and would be + reset into its past. + + :param progress: + :class:`RootUpdateProgress` instance or None if no progress should be sent. + + :param dry_run: + If True, operations will not actually be performed. Progress messages will + change accordingly to indicate the WOULD DO state of the operation. + + :param keep_going: + If True, we will ignore but log all errors, and keep going recursively. + Unless `dry_run` is set as well, `keep_going` could cause subsequent/inherited errors you wouldn't see otherwise. In conjunction with `dry_run`, this can be useful to anticipate all errors when updating submodules. - :return: self + + :return: + self """ if self.repo.bare: raise InvalidGitRepositoryError("Cannot update submodules in bare repositories") @@ -234,13 +251,14 @@ def update( ################### if sm.url != psm.url: # Add the new remote, remove the old one. - # This way, if the url just changes, the commits will not - # have to be re-retrieved. + # This way, if the url just changes, the commits will not have + # to be re-retrieved. nn = "__new_origin__" smm = sm.module() rmts = smm.remotes - # Don't do anything if we already have the url we search in place. + # Don't do anything if we already have the url we search in + # place. if len([r for r in rmts if r.url == sm.url]) == 0: progress.update( BEGIN | URLCHANGE, @@ -272,17 +290,17 @@ def update( # END if urls match # END for each remote - # If we didn't find a matching remote, but have exactly one, - # we can safely use this one. + # If we didn't find a matching remote, but have exactly + # one, we can safely use this one. if rmt_for_deletion is None: if len(rmts) == 1: rmt_for_deletion = rmts[0] else: - # If we have not found any remote with the original URL - # we may not have a name. This is a special case, - # and its okay to fail here. - # Alternatively we could just generate a unique name and - # leave all existing ones in place. + # If we have not found any remote with the + # original URL we may not have a name. This is a + # special case, and its okay to fail here. + # Alternatively we could just generate a unique + # name and leave all existing ones in place. raise InvalidGitRepositoryError( "Couldn't find original remote-repo at url %r" % psm.url ) @@ -292,19 +310,19 @@ def update( orig_name = rmt_for_deletion.name smm.delete_remote(rmt_for_deletion) # NOTE: Currently we leave tags from the deleted remotes - # as well as separate tracking branches in the possibly totally - # changed repository (someone could have changed the url to - # another project). At some point, one might want to clean - # it up, but the danger is high to remove stuff the user - # has added explicitly. + # as well as separate tracking branches in the possibly + # totally changed repository (someone could have changed + # the url to another project). At some point, one might + # want to clean it up, but the danger is high to remove + # stuff the user has added explicitly. # Rename the new remote back to what it was. smr.rename(orig_name) - # Early on, we verified that the our current tracking branch - # exists in the remote. Now we have to ensure that the - # sha we point to is still contained in the new remote - # tracking branch. + # Early on, we verified that the our current tracking + # branch exists in the remote. Now we have to ensure + # that the sha we point to is still contained in the new + # remote tracking branch. smsha = sm.binsha found = False rref = smr.refs[self.branch_name] @@ -316,10 +334,11 @@ def update( # END for each commit if not found: - # Adjust our internal binsha to use the one of the remote - # this way, it will be checked out in the next step. - # This will change the submodule relative to us, so - # the user will be able to commit the change easily. + # Adjust our internal binsha to use the one of the + # remote this way, it will be checked out in the + # next step. This will change the submodule relative + # to us, so the user will be able to commit the + # change easily. _logger.warning( "Current sha %s was not contained in the tracking\ branch at the new remote, setting it the the remote's tracking branch", @@ -328,7 +347,8 @@ def update( sm.binsha = rref.commit.binsha # END reset binsha - # NOTE: All checkout is performed by the base implementation of update. + # NOTE: All checkout is performed by the base + # implementation of update. # END handle dry_run progress.update( END | URLCHANGE, @@ -342,7 +362,8 @@ def update( # HANDLE PATH CHANGES ##################### if sm.branch_path != psm.branch_path: - # Finally, create a new tracking branch which tracks the new remote branch. + # Finally, create a new tracking branch which tracks the new + # remote branch. progress.update( BEGIN | BRANCHCHANGE, i, @@ -354,8 +375,8 @@ def update( if not dry_run: smm = sm.module() smmr = smm.remotes - # As the branch might not exist yet, we will have to fetch all remotes - # to be sure... + # As the branch might not exist yet, we will have to fetch + # all remotes to be sure... for remote in smmr: remote.fetch(progress=progress) # END for each remote @@ -372,11 +393,12 @@ def update( # END ensure tracking branch exists tbr.set_tracking_branch(find_first_remote_branch(smmr, sm.branch_name)) - # NOTE: All head-resetting is done in the base implementation of update - # but we will have to checkout the new branch here. As it still points - # to the currently checked out commit, we don't do any harm. - # As we don't want to update working-tree or index, changing the ref is - # all there is to do. + # NOTE: All head-resetting is done in the base + # implementation of update but we will have to checkout the + # new branch here. As it still points to the currently + # checked out commit, we don't do any harm. + # As we don't want to update working-tree or index, changing + # the ref is all there is to do. smm.head.reference = tbr # END handle dry_run @@ -409,10 +431,10 @@ def update( keep_going=keep_going, ) - # Update recursively depth first - question is which inconsistent - # state will be better in case it fails somewhere. Defective branch - # or defective depth. The RootSubmodule type will never process itself, - # which was done in the previous expression. + # Update recursively depth first - question is which inconsistent state will + # be better in case it fails somewhere. Defective branch or defective depth. + # The RootSubmodule type will never process itself, which was done in the + # previous expression. if recursive: # The module would exist by now if we are not in dry_run mode. if sm.module_exists(): diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index f8265798d..91e18a26b 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -35,7 +35,7 @@ def sm_section(name: str) -> str: - """:return: Section title used in .gitmodules configuration file""" + """:return: Section title used in ``.gitmodules`` configuration file""" return f'submodule "{name}"' @@ -51,7 +51,8 @@ def mkhead(repo: "Repo", path: PathLike) -> "Head": def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> "RemoteReference": - """Find the remote branch matching the name of the given branch or raise InvalidGitRepositoryError.""" + """Find the remote branch matching the name of the given branch or raise + :class:`git.exc.InvalidGitRepositoryError`.""" for remote in remotes: try: return remote.refs[branch_name] @@ -69,11 +70,11 @@ def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> " class SubmoduleConfigParser(GitConfigParser): - """Catches calls to _write, and updates the .gitmodules blob in the index + """Catches calls to _write, and updates the ``.gitmodules`` blob in the index with the new data, if we have written into a stream. - Otherwise it would add the local file to the index to make it correspond - with the working tree. Additionally, the cache must be cleared. + Otherwise it would add the local file to the index to make it correspond with the + working tree. Additionally, the cache must be cleared. Please note that no mutating method will work in bare mode. """ @@ -86,8 +87,8 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: # { Interface def set_submodule(self, submodule: "Submodule") -> None: - """Set this instance's submodule. It must be called before - the first write operation begins.""" + """Set this instance's submodule. It must be called before the first write + operation begins.""" self._smref = weakref.ref(submodule) def flush_to_index(self) -> None: From 115451d4605b880e26c59bb415b539cdce984b64 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 20:13:16 -0500 Subject: [PATCH 19/61] Change _write to write in SubmoduleConfigParser docstring Since it appears to refer to the write method, which it overrides, rather that the nonpublic _write method (which is not overridden, and whose direct calls are not affected). --- git/objects/submodule/util.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index 91e18a26b..b02da501e 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -70,8 +70,9 @@ def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> " class SubmoduleConfigParser(GitConfigParser): - """Catches calls to _write, and updates the ``.gitmodules`` blob in the index - with the new data, if we have written into a stream. + """Catches calls to :meth:`~git.config.GitConfigParser.write`, and updates the + ``.gitmodules`` blob in the index with the new data, if we have written into a + stream. Otherwise it would add the local file to the index to make it correspond with the working tree. Additionally, the cache must be cleared. From 521948989134c8e0ab7d4c1063780a0799f4dbc8 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 20:58:21 -0500 Subject: [PATCH 20/61] Fix IndexObject.abspath docstring formatting The original problem where the backslash wasn't included in the docstring at all was fixed in 7dd2095 (#1725), but the backslash still did not appear in rendered Sphinx documentation, because it was also treated as a reStructuredText metacharacter. Although that can be addressed by adding a further backslash to escape it, the effect is ambiguous when the docstring is read in the code. So this just encloses it in a double-backticked code span instead, which is a somewhat clearer way to show it anyway. --- git/objects/base.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/git/objects/base.py b/git/objects/base.py index 934fb40bc..938dd826c 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -226,7 +226,8 @@ def abspath(self) -> PathLike: Absolute path to this index object in the file system (as opposed to the :attr:`path` field which is a path relative to the git repository). - The returned path will be native to the system and contains '\' on Windows. + The returned path will be native to the system and contains ``\`` on + Windows. """ if self.repo.working_tree_dir is not None: return join_path_native(self.repo.working_tree_dir, self.path) From c06dfd9bdc076e44df436836914d07a1f9f695c1 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Mon, 26 Feb 2024 21:49:29 -0500 Subject: [PATCH 21/61] Fix parameter names in TagObject.__init__ --- git/objects/tag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/objects/tag.py b/git/objects/tag.py index f7aaaf477..f874f5bdf 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -59,8 +59,8 @@ def __init__( :param tagged_date: int_seconds_since_epoch The :class:`DateTime` of the tag creation. Use :func:`time.gmtime` to convert it into a different format. - :param tagged_tz_offset: int_seconds_west_of_utc - The timezone that the authored_date is in, in a format similar + :param tagger_tz_offset: int_seconds_west_of_utc + The timezone that the tagged_date is in, in a format similar to :attr:`time.altzone`. """ super().__init__(repo, binsha) From ae37a4a699608c6bc55e023547c8daa473477920 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 13:21:46 -0500 Subject: [PATCH 22/61] Revise docstrings within git.objects But not within git.objects.submodule, which was done in 63c62ed. --- git/objects/__init__.py | 4 +- git/objects/base.py | 71 +++++++---- git/objects/blob.py | 3 +- git/objects/commit.py | 252 ++++++++++++++++++++++++---------------- git/objects/fun.py | 69 ++++++----- git/objects/tag.py | 28 +++-- git/objects/tree.py | 88 ++++++++------ git/objects/util.py | 167 +++++++++++++++----------- 8 files changed, 415 insertions(+), 267 deletions(-) diff --git a/git/objects/__init__.py b/git/objects/__init__.py index fc8d8fbb2..1061ec874 100644 --- a/git/objects/__init__.py +++ b/git/objects/__init__.py @@ -14,8 +14,8 @@ from .tag import * # noqa: F403 from .tree import * # noqa: F403 -# Fix import dependency - add IndexObject to the util module, so that it can be -# imported by the submodule.base. +# Fix import dependency - add IndexObject to the util module, so that it can be imported +# by the submodule.base. smutil.IndexObject = IndexObject # type: ignore[attr-defined] # noqa: F405 smutil.Object = Object # type: ignore[attr-defined] # noqa: F405 del smutil diff --git a/git/objects/base.py b/git/objects/base.py index 938dd826c..df56a4bac 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -37,7 +37,9 @@ class Object(LazyMixin): - """An Object which may be Blobs, Trees, Commits and Tags.""" + """An Object which may be :class:`~git.objects.blob.Blob`, + :class:`~git.objects.tree.Tree`, :class:`~git.objects.commit.Commit` or + `~git.objects.tag.TagObject`.""" NULL_HEX_SHA = "0" * 40 NULL_BIN_SHA = b"\0" * 20 @@ -55,11 +57,14 @@ class Object(LazyMixin): def __init__(self, repo: "Repo", binsha: bytes): """Initialize an object by identifying it by its binary sha. + All keyword arguments will be set on demand if None. - :param repo: repository this object is located in + :param repo: + Repository this object is located in. - :param binsha: 20 byte SHA1 + :param binsha: + 20 byte SHA1 """ super().__init__() self.repo = repo @@ -72,24 +77,28 @@ def __init__(self, repo: "Repo", binsha: bytes): @classmethod def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> Commit_ish: """ - :return: New :class:`Object`` instance of a type appropriate to the object type - behind `id`. The id of the newly created object will be a binsha even though - the input id may have been a Reference or Rev-Spec. + :return: + New :class:`Object` instance of a type appropriate to the object type behind + `id`. The id of the newly created object will be a binsha even though the + input id may have been a `~git.refs.reference.Reference` or rev-spec. - :param id: reference, rev-spec, or hexsha + :param id: + :class:`~git.refs.reference.Reference`, rev-spec, or hexsha - :note: This cannot be a ``__new__`` method as it would always call - :meth:`__init__` with the input id which is not necessarily a binsha. + :note: + This cannot be a ``__new__`` method as it would always call :meth:`__init__` + with the input id which is not necessarily a binsha. """ return repo.rev_parse(str(id)) @classmethod def new_from_sha(cls, repo: "Repo", sha1: bytes) -> Commit_ish: """ - :return: new object instance of a type appropriate to represent the given - binary sha1 + :return: + New object instance of a type appropriate to represent the given binary sha1 - :param sha1: 20 byte binary sha1 + :param sha1: + 20 byte binary sha1 """ if sha1 == cls.NULL_BIN_SHA: # The NULL binsha is always the root commit. @@ -126,11 +135,11 @@ def __hash__(self) -> int: return hash(self.binsha) def __str__(self) -> str: - """:return: string of our SHA1 as understood by all git commands""" + """:return: String of our SHA1 as understood by all git commands""" return self.hexsha def __repr__(self) -> str: - """:return: string with pythonic representation of our object""" + """:return: String with pythonic representation of our object""" return '' % (self.__class__.__name__, self.hexsha) @property @@ -142,17 +151,22 @@ def hexsha(self) -> str: @property def data_stream(self) -> "OStream": """ - :return: File Object compatible stream to the uncompressed raw data of the object + :return: + File-object compatible stream to the uncompressed raw data of the object - :note: Returned streams must be read in order. + :note: + Returned streams must be read in order. """ return self.repo.odb.stream(self.binsha) def stream_data(self, ostream: "OStream") -> "Object": """Write our data directly to the given output stream. - :param ostream: File object compatible stream object. - :return: self + :param ostream: + File-object compatible stream object. + + :return: + self """ istream = self.repo.odb.stream(self.binsha) stream_copy(istream, ostream) @@ -160,8 +174,9 @@ def stream_data(self, ostream: "OStream") -> "Object": class IndexObject(Object): - """Base for all objects that can be part of the index file, namely Tree, Blob and - SubModule objects.""" + """Base for all objects that can be part of the index file, namely + :class:`~git.objects.tree.Tree`, :class:`~git.objects.blob.Blob` and + :class:`~git.objects.submodule.base.Submodule` objects.""" __slots__ = ("path", "mode") @@ -175,16 +190,22 @@ def __init__( mode: Union[None, int] = None, path: Union[None, PathLike] = None, ) -> None: - """Initialize a newly instanced IndexObject. + """Initialize a newly instanced :class:`IndexObject`. + + :param repo: + The :class:`~git.repo.base.Repo` we are located in. + + :param binsha: + 20 byte sha1. - :param repo: The :class:`~git.repo.base.Repo` we are located in. - :param binsha: 20 byte sha1. :param mode: The stat compatible file mode as int, use the :mod:`stat` module to evaluate the information. + :param path: The path to the file in the file system, relative to the git repository root, like ``file.ext`` or ``folder/other.ext``. + :note: Path may not be set if the index object has been created directly, as it cannot be retrieved without knowing the parent tree. @@ -198,8 +219,8 @@ def __init__( def __hash__(self) -> int: """ :return: - Hash of our path as index items are uniquely identifiable by path, not - by their data! + Hash of our path as index items are uniquely identifiable by path, not by + their data! """ return hash(self.path) diff --git a/git/objects/blob.py b/git/objects/blob.py index 6d7e859af..50c12f0d7 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -29,7 +29,8 @@ def mime_type(self) -> str: """ :return: String describing the mime type of this file (based on the filename) - :note: Defaults to 'text/plain' in case the actual file type is unknown. + :note: + Defaults to ``text/plain`` in case the actual file type is unknown. """ guesses = None if self.path: diff --git a/git/objects/commit.py b/git/objects/commit.py index caec1b6c4..e6aa7ac39 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -58,11 +58,11 @@ class Commit(base.Object, TraversableIterableObj, Diffable, Serializable): + """Wraps a git commit object. - """Wraps a git Commit object. - - This class will act lazily on some of its attributes and will query the - value on demand only if it involves calling the git binary.""" + This class will act lazily on some of its attributes and will query the value on + demand only if it involves calling the git binary. + """ # ENVIRONMENT VARIABLES # Read when creating new commits. @@ -108,41 +108,55 @@ def __init__( encoding: Union[str, None] = None, gpgsig: Union[str, None] = None, ) -> None: - """Instantiate a new Commit. All keyword arguments taking None as default will - be implicitly set on first query. + R"""Instantiate a new :class:`Commit`. All keyword arguments taking None as + default will be implicitly set on first query. + + :param binsha: + 20 byte sha1 - :param binsha: 20 byte sha1 :param parents: tuple(Commit, ...) - A tuple of commit ids or actual Commits - :param tree: Tree object - :param author: Actor + A tuple of commit ids or actual :class:`Commit`\s. + + :param tree: + A :class:`~git.objects.tree.Tree` object. + + :param author: :class:`~git.util.Actor` The author Actor object + :param authored_date: int_seconds_since_epoch - The authored DateTime - use time.gmtime() to convert it into a - different format + The authored DateTime - use :func:`time.gmtime` to convert it into a + different format. + :param author_tz_offset: int_seconds_west_of_utc - The timezone that the authored_date is in - :param committer: Actor - The committer string + The timezone that the `authored_date` is in. + + :param committer: :class:`~git.util.Actor` + The committer string. + :param committed_date: int_seconds_since_epoch - The committed DateTime - use time.gmtime() to convert it into a - different format + The committed DateTime - use :func:`time.gmtime` to convert it into a + different format. + :param committer_tz_offset: int_seconds_west_of_utc - The timezone that the committed_date is in + The timezone that the `committed_date` is in. + :param message: string - The commit message + The commit message. + :param encoding: string - Encoding of the message, defaults to UTF-8 + Encoding of the message, defaults to UTF-8. + :param parents: - List or tuple of Commit objects which are our parent(s) in the commit - dependency graph + List or tuple of :class:`Commit` objects which are our parent(s) in the + commit dependency graph. - :return: git.Commit + :return: + :class:`Commit` :note: - Timezone information is in the same format and in the same sign - as what time.altzone returns. The sign is inverted compared to git's - UTC timezone. + Timezone information is in the same format and in the same sign as what + :func:`time.altzone` returns. The sign is inverted compared to git's UTC + timezone. """ super().__init__(repo, binsha) self.binsha = binsha @@ -179,8 +193,11 @@ def _get_intermediate_items(cls, commit: "Commit") -> Tuple["Commit", ...]: def _calculate_sha_(cls, repo: "Repo", commit: "Commit") -> bytes: """Calculate the sha of a commit. - :param repo: Repo object the commit should be part of - :param commit: Commit object for which to generate the sha + :param repo: + :class:`~git.repo.base.Repo` object the commit should be part of. + + :param commit: + :class:`Commit` object for which to generate the sha. """ stream = BytesIO() @@ -194,8 +211,8 @@ def _calculate_sha_(cls, repo: "Repo", commit: "Commit") -> bytes: def replace(self, **kwargs: Any) -> "Commit": """Create new commit object from existing commit object. - Any values provided as keyword arguments will replace the - corresponding attribute in the new object. + Any values provided as keyword arguments will replace the corresponding + attribute in the new object. """ attrs = {k: getattr(self, k) for k in self.__slots__} @@ -239,17 +256,18 @@ def count(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) """Count the number of commits reachable from this commit. :param paths: - An optional path or a list of paths restricting the return value - to commits actually containing the paths. + An optional path or a list of paths restricting the return value to commits + actually containing the paths. :param kwargs: - Additional options to be passed to git-rev-list. They must not alter - the output style of the command, or parsing will yield incorrect results. + Additional options to be passed to ``git rev-list``. They must not alter the + output style of the command, or parsing will yield incorrect results. - :return: An int defining the number of reachable commits + :return: + An int defining the number of reachable commits """ - # Yes, it makes a difference whether empty paths are given or not in our case - # as the empty paths version will ignore merge commits for some reason. + # Yes, it makes a difference whether empty paths are given or not in our case as + # the empty paths version will ignore merge commits for some reason. if paths: return len(self.repo.git.rev_list(self.hexsha, "--", paths, **kwargs).splitlines()) return len(self.repo.git.rev_list(self.hexsha, **kwargs).splitlines()) @@ -258,8 +276,11 @@ def count(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) def name_rev(self) -> str: """ :return: - String describing the commits hex sha based on the closest Reference. - Mostly useful for UI purposes + String describing the commits hex sha based on the closest + `~git.refs.reference.Reference`. + + :note: + Mostly useful for UI purposes. """ return self.repo.git.name_rev(self) @@ -271,24 +292,27 @@ def iter_items( paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any, ) -> Iterator["Commit"]: - """Find all commits matching the given criteria. + R"""Find all commits matching the given criteria. - :param repo: The Repo + :param repo: + The Repo - :param rev: Revision specifier, see git-rev-parse for viable options. + :param rev: + Revision specifier, see git-rev-parse for viable options. :param paths: - An optional path or list of paths, if set only Commits that include the path - or paths will be considered. + An optional path or list of paths. If set only :class:`Commit`\s that + include the path or paths will be considered. :param kwargs: Optional keyword arguments to ``git rev-list`` where: - * ``max_count`` is the maximum number of commits to fetch - * ``skip`` is the number of commits to skip - * ``since`` all commits since e.g. '1970-01-01' + * ``max_count`` is the maximum number of commits to fetch. + * ``skip`` is the number of commits to skip. + * ``since`` all commits since e.g. '1970-01-01'. - :return: Iterator yielding :class:`Commit` items. + :return: + Iterator yielding :class:`Commit` items. """ if "pretty" in kwargs: raise ValueError("--pretty cannot be used as parsing expects single sha's only") @@ -313,13 +337,17 @@ def iter_items( return cls._iter_from_process_or_stream(repo, proc) def iter_parents(self, paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any) -> Iterator["Commit"]: - """Iterate _all_ parents of this commit. + R"""Iterate _all_ parents of this commit. :param paths: - Optional path or list of paths limiting the Commits to those that - contain at least one of the paths - :param kwargs: All arguments allowed by git-rev-list - :return: Iterator yielding Commit objects which are parents of self + Optional path or list of paths limiting the :class:`Commit`\s to those that + contain at least one of the paths. + + :param kwargs: + All arguments allowed by ``git rev-list``. + + :return: + Iterator yielding :class:`Commit` objects which are parents of ``self`` """ # skip ourselves skip = kwargs.get("skip", 1) @@ -334,7 +362,8 @@ def stats(self) -> Stats: """Create a git stat from changes between this commit and its first parent or from all changes done if this is the very first commit. - :return: git.Stats + :return: + :class:`Stats` """ if not self.parents: text = self.repo.git.diff_tree(self.hexsha, "--", numstat=True, no_renames=True, root=True) @@ -364,11 +393,11 @@ def trailers(self) -> Dict[str, str]: def trailers_list(self) -> List[Tuple[str, str]]: """Get the trailers of the message as a list. - Git messages can contain trailer information that are similar to RFC 822 - e-mail headers (see: https://git-scm.com/docs/git-interpret-trailers). + Git messages can contain trailer information that are similar to RFC 822 e-mail + headers (see: https://git-scm.com/docs/git-interpret-trailers). - This functions calls ``git interpret-trailers --parse`` onto the message - to extract the trailer information, returns the raw trailer data as a list. + This functions calls ``git interpret-trailers --parse`` onto the message to + extract the trailer information, returns the raw trailer data as a list. Valid message with trailer:: @@ -382,7 +411,6 @@ def trailers_list(self) -> List[Tuple[str, str]]: key1: value1.2 key2 : value 2 with inner spaces - Returned list will look like this:: [ @@ -391,7 +419,6 @@ def trailers_list(self) -> List[Tuple[str, str]]: ("key2", "value 2 with inner spaces"), ] - :return: List containing key-value tuples of whitespace stripped trailer information. """ @@ -414,12 +441,12 @@ def trailers_list(self) -> List[Tuple[str, str]]: def trailers_dict(self) -> Dict[str, List[str]]: """Get the trailers of the message as a dictionary. - Git messages can contain trailer information that are similar to RFC 822 - e-mail headers (see: https://git-scm.com/docs/git-interpret-trailers). + Git messages can contain trailer information that are similar to RFC 822 e-mail + headers (see: https://git-scm.com/docs/git-interpret-trailers). - This functions calls ``git interpret-trailers --parse`` onto the message - to extract the trailer information. The key value pairs are stripped of - leading and trailing whitespaces before they get saved into a dictionary. + This functions calls ``git interpret-trailers --parse`` onto the message to + extract the trailer information. The key value pairs are stripped of leading and + trailing whitespaces before they get saved into a dictionary. Valid message with trailer:: @@ -433,7 +460,6 @@ def trailers_dict(self) -> Dict[str, List[str]]: key1: value1.2 key2 : value 2 with inner spaces - Returned dictionary will look like this:: { @@ -443,8 +469,8 @@ def trailers_dict(self) -> Dict[str, List[str]]: :return: - Dictionary containing whitespace stripped trailer information. - Mapping trailer keys to a list of their corresponding values. + Dictionary containing whitespace stripped trailer information, mapping + trailer keys to a list of their corresponding values. """ d = defaultdict(list) for key, val in self.trailers_list: @@ -453,13 +479,16 @@ def trailers_dict(self) -> Dict[str, List[str]]: @classmethod def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen, IO]) -> Iterator["Commit"]: - """Parse out commit information into a list of Commit objects. + """Parse out commit information into a list of :class:`Commit` objects. We expect one-line per commit, and parse the actual commit information directly from our lighting fast object database. - :param proc: git-rev-list process instance - one sha per line - :return: iterator supplying :class:`Commit` objects + :param proc: + ``git rev-list`` process instance - one sha per line. + + :return: + Iterator supplying :class:`Commit` objects """ # def is_proc(inp) -> TypeGuard[Popen]: @@ -491,8 +520,8 @@ def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen, yield cls(repo, hex_to_bin(hexsha)) # END for each line in stream - # TODO: Review this - it seems process handling got a bit out of control - # due to many developers trying to fix the open file handles issue. + # TODO: Review this - it seems process handling got a bit out of control due to + # many developers trying to fix the open file handles issue. if hasattr(proc_or_stream, "wait"): proc_or_stream = cast(Popen, proc_or_stream) finalize_process(proc_or_stream) @@ -512,33 +541,49 @@ def create_from_tree( ) -> "Commit": """Commit the given tree, creating a commit object. - :param repo: Repo object the commit should be part of - :param tree: Tree object or hex or bin sha. The tree of the new commit. - :param message: Commit message. It may be an empty string if no message is - provided. It will be converted to a string, in any case. + :param repo: + :class:`~git.repo.base.Repo` object the commit should be part of. + + :param tree: + :class:`~git.objects.tree.Tree` object or hex or bin sha. + The tree of the new commit. + + :param message: + Commit message. It may be an empty string if no message is provided. It will + be converted to a string, in any case. + :param parent_commits: - Optional :class:`Commit` objects to use as parents for the new commit. - If empty list, the commit will have no parents at all and become - a root commit. - If None, the current head commit will be the parent of the - new commit object. + Optional :class:`Commit` objects to use as parents for the new commit. If + empty list, the commit will have no parents at all and become a root commit. + If None, the current head commit will be the parent of the new commit + object. + :param head: If True, the HEAD will be advanced to the new commit automatically. Otherwise the HEAD will remain pointing on the previous commit. This could lead to undesired results when diffing files. - :param author: The name of the author, optional. If unset, the repository - configuration is used to obtain this value. - :param committer: The name of the committer, optional. If unset, the - repository configuration is used to obtain this value. - :param author_date: The timestamp for the author field. - :param commit_date: The timestamp for the committer field. - :return: Commit object representing the new commit. + :param author: + The name of the author, optional. + If unset, the repository configuration is used to obtain this value. + + :param committer: + The name of the committer, optional. + If unset, the repository configuration is used to obtain this value. + + :param author_date: + The timestamp for the author field. + + :param commit_date: + The timestamp for the committer field. + + :return: + :class:`Commit` object representing the new commit. :note: - Additional information about the committer and Author are taken from the - environment or from the git configuration, see git-commit-tree for - more information. + Additional information about the committer and author are taken from the + environment or from the git configuration. See git-commit-tree for more + information. """ if parent_commits is None: try: @@ -595,8 +640,8 @@ def create_from_tree( if not isinstance(conf_encoding, str): raise TypeError("conf_encoding could not be coerced to str") - # If the tree is no object, make sure we create one - otherwise - # the created commit object is invalid. + # If the tree is no object, make sure we create one - otherwise the created + # commit object is invalid. if isinstance(tree, str): tree = repo.tree(tree) # END tree conversion @@ -620,8 +665,8 @@ def create_from_tree( new_commit.binsha = cls._calculate_sha_(repo, new_commit) if head: - # Need late import here, importing git at the very beginning throws - # as well... + # Need late import here, importing git at the very beginning throws as + # well... import git.refs try: @@ -718,7 +763,8 @@ def _deserialize(self, stream: BytesIO) -> "Commit": # END for each parent line self.parents = tuple(self.parents) - # We don't know actual author encoding before we have parsed it, so keep the lines around. + # We don't know actual author encoding before we have parsed it, so keep the + # lines around. author_line = next_line committer_line = readline() @@ -730,7 +776,8 @@ def _deserialize(self, stream: BytesIO) -> "Commit": next_line = readline() # END skip mergetags - # Now we can have the encoding line, or an empty line followed by the optional message. + # Now we can have the encoding line, or an empty line followed by the optional + # message. self.encoding = self.default_encoding self.gpgsig = "" @@ -808,12 +855,13 @@ def _deserialize(self, stream: BytesIO) -> "Commit": @property def co_authors(self) -> List[Actor]: - """ - Search the commit message for any co-authors of this commit. + """Search the commit message for any co-authors of this commit. - Details on co-authors: https://github.blog/2018-01-29-commit-together-with-co-authors/ + Details on co-authors: + https://github.blog/2018-01-29-commit-together-with-co-authors/ - :return: List of co-authors for this commit (as Actor objects). + :return: + List of co-authors for this commit (as :class:`~git.util.Actor` objects). """ co_authors = [] diff --git a/git/objects/fun.py b/git/objects/fun.py index bf6bc21d6..d349737de 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -40,10 +40,13 @@ def tree_to_stream(entries: Sequence[EntryTup], write: Callable[["ReadableBuffer"], Union[int, None]]) -> None: - """Write the given list of entries into a stream using its write method. + """Write the given list of entries into a stream using its ``write`` method. - :param entries: **sorted** list of tuples with (binsha, mode, name) - :param write: write method which takes a data string + :param entries: + **Sorted** list of tuples with (binsha, mode, name). + + :param write: + A ``write`` method which takes a data string. """ ord_zero = ord("0") bit_mask = 7 # 3 bits set. @@ -59,11 +62,11 @@ def tree_to_stream(entries: Sequence[EntryTup], write: Callable[["ReadableBuffer mode_str = mode_str[1:] # END save a byte - # Here it comes: If the name is actually unicode, the replacement below - # will not work as the binsha is not part of the ascii unicode encoding - - # hence we must convert to an UTF-8 string for it to work properly. - # According to my tests, this is exactly what git does, that is it just - # takes the input literally, which appears to be UTF-8 on linux. + # Here it comes: If the name is actually unicode, the replacement below will not + # work as the binsha is not part of the ascii unicode encoding - hence we must + # convert to an UTF-8 string for it to work properly. According to my tests, + # this is exactly what git does, that is it just takes the input literally, + # which appears to be UTF-8 on linux. if isinstance(name, str): name_bytes = name.encode(defenc) else: @@ -73,10 +76,15 @@ def tree_to_stream(entries: Sequence[EntryTup], write: Callable[["ReadableBuffer def tree_entries_from_data(data: bytes) -> List[EntryTup]: - """Reads the binary representation of a tree and returns tuples of Tree items + """Read the binary representation of a tree and returns tuples of + :class:`~git.objects.tree.Tree` items. + + :param data: + Data block with tree data (as bytes). - :param data: data block with tree data (as bytes) - :return: list(tuple(binsha, mode, tree_relative_path), ...)""" + :return: + list(tuple(binsha, mode, tree_relative_path), ...) + """ ord_zero = ord("0") space_ord = ord(" ") len_data = len(data) @@ -89,8 +97,8 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]: # Some git versions truncate the leading 0, some don't. # The type will be extracted from the mode later. while data[i] != space_ord: - # Move existing mode integer up one level being 3 bits - # and add the actual ordinal value of the character. + # Move existing mode integer up one level being 3 bits and add the actual + # ordinal value of the character. mode = (mode << 3) + (data[i] - ord_zero) i += 1 # END while reading mode @@ -122,8 +130,8 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]: def _find_by_name(tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir: bool, start_at: int) -> EntryTupOrNone: """Return data entry matching the given name and tree mode or None. - Before the item is returned, the respective data item is set None in the - tree_data list to mark it done. + Before the item is returned, the respective data item is set None in the `tree_data` + list to mark it done. """ try: @@ -165,6 +173,7 @@ def traverse_trees_recursive( ) -> List[Tuple[EntryTupOrNone, ...]]: """ :return: list of list with entries according to the given binary tree-shas. + The result is encoded in a list of n tuple|None per blob/commit, (n == len(tree_shas)), where: @@ -172,16 +181,19 @@ def traverse_trees_recursive( * [1] == mode as int * [2] == path relative to working tree root - The entry tuple is None if the respective blob/commit did not - exist in the given tree. + The entry tuple is None if the respective blob/commit did not exist in the given + tree. - :param tree_shas: iterable of shas pointing to trees. All trees must - be on the same level. A tree-sha may be None in which case None. + :param tree_shas: + Iterable of shas pointing to trees. All trees must be on the same level. A + tree-sha may be None in which case None. - :param path_prefix: a prefix to be added to the returned paths on this level, - set it '' for the first iteration. + :param path_prefix: + A prefix to be added to the returned paths on this level. + Set it ``""`` for the first iteration. - :note: The ordering of the returned items will be partially lost. + :note: + The ordering of the returned items will be partially lost. """ trees_data: List[List[EntryTupOrNone]] = [] @@ -198,8 +210,8 @@ def traverse_trees_recursive( out: List[Tuple[EntryTupOrNone, ...]] = [] - # Find all matching entries and recursively process them together if the match - # is a tree. If the match is a non-tree item, put it into the result. + # Find all matching entries and recursively process them together if the match is a + # tree. If the match is a non-tree item, put it into the result. # Processed items will be set None. for ti, tree_data in enumerate(trees_data): for ii, item in enumerate(tree_data): @@ -213,8 +225,8 @@ def traverse_trees_recursive( is_dir = S_ISDIR(mode) # Type mode bits # Find this item in all other tree data items. - # Wrap around, but stop one before our current index, hence - # ti+nt, not ti+1+nt. + # Wrap around, but stop one before our current index, hence ti+nt, not + # ti+1+nt. for tio in range(ti + 1, ti + nt): tio = tio % nt entries[tio] = _find_by_name(trees_data[tio], name, is_dir, ii) @@ -245,7 +257,7 @@ def traverse_trees_recursive( def traverse_tree_recursive(odb: "GitCmdObjectDB", tree_sha: bytes, path_prefix: str) -> List[EntryTup]: """ - :return: list of entries of the tree pointed to by the binary tree_sha. + :return: list of entries of the tree pointed to by the binary `tree_sha`. An entry has the following format: @@ -253,7 +265,8 @@ def traverse_tree_recursive(odb: "GitCmdObjectDB", tree_sha: bytes, path_prefix: * [1] mode as int * [2] path relative to the repository - :param path_prefix: Prefix to prepend to the front of all returned paths. + :param path_prefix: + Prefix to prepend to the front of all returned paths. """ entries = [] data = tree_entries_from_data(odb.stream(tree_sha).read()) diff --git a/git/objects/tag.py b/git/objects/tag.py index f874f5bdf..211c84ac7 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -25,7 +25,8 @@ class TagObject(base.Object): - """Non-lightweight tag carrying additional information about an object we are pointing to.""" + """Non-lightweight tag carrying additional information about an object we are + pointing to.""" type: Literal["tag"] = "tag" @@ -51,17 +52,28 @@ def __init__( ) -> None: # @ReservedAssignment """Initialize a tag object with additional data. - :param repo: Repository this object is located in - :param binsha: 20 byte SHA1 - :param object: Object instance of object we are pointing to - :param tag: Name of this tag - :param tagger: Actor identifying the tagger + :param repo: + Repository this object is located in. + + :param binsha: + 20 byte SHA1. + + :param object: + :class:`~git.objects.base.Object` instance of object we are pointing to. + + :param tag: + Name of this tag. + + :param tagger: + :class:`~git.util.Actor` identifying the tagger. + :param tagged_date: int_seconds_since_epoch The :class:`DateTime` of the tag creation. Use :func:`time.gmtime` to convert it into a different format. + :param tagger_tz_offset: int_seconds_west_of_utc - The timezone that the tagged_date is in, in a format similar - to :attr:`time.altzone`. + The timezone that the `tagged_date` is in, in a format similar to + :attr:`time.altzone`. """ super().__init__(repo, binsha) if object is not None: diff --git a/git/objects/tree.py b/git/objects/tree.py index 450973280..da682b8cd 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -54,10 +54,12 @@ class TreeModifier: - """A utility class providing methods to alter the underlying cache in a list-like fashion. + """A utility class providing methods to alter the underlying cache in a list-like + fashion. - Once all adjustments are complete, the _cache, which really is a reference to - the cache of a tree, will be sorted. This ensures it will be in a serializable state. + Once all adjustments are complete, the :attr:`_cache`, which really is a reference + to the cache of a tree, will be sorted. This ensures it will be in a serializable + state. """ __slots__ = ("_cache",) @@ -78,10 +80,11 @@ def _index_by_name(self, name: str) -> int: def set_done(self) -> "TreeModifier": """Call this method once you are done modifying the tree information. - This may be called several times, but be aware that each call will cause - a sort operation. + This may be called several times, but be aware that each call will cause a sort + operation. - :return self: + :return: + self """ self._cache.sort(key=lambda x: (x[2] + "/") if x[1] == Tree.tree_id << 12 else x[2]) return self @@ -93,17 +96,21 @@ def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> "TreeMod """Add the given item to the tree. If an item with the given name already exists, nothing will be done, but a - ValueError will be raised if the sha and mode of the existing item do not match - the one you add, unless force is True + :class:`ValueError` will be raised if the sha and mode of the existing item do + not match the one you add, unless `force` is True. - :param sha: The 20 or 40 byte sha of the item to add + :param sha: + The 20 or 40 byte sha of the item to add. - :param mode: int representing the stat compatible mode of the item + :param mode: + int representing the stat compatible mode of the item. - :param force: If True, an item with your name and information will overwrite any - existing item with the same name, no matter which information it has + :param force: + If True, an item with your name and information will overwrite any existing + item with the same name, no matter which information it has. - :return: self + :return: + self """ if "/" in name: raise ValueError("Name must not contain '/' characters") @@ -136,7 +143,8 @@ def add_unchecked(self, binsha: bytes, mode: int, name: str) -> None: For more information on the parameters, see :meth:`add`. - :param binsha: 20 byte binary sha + :param binsha: + 20 byte binary sha. """ assert isinstance(binsha, bytes) and isinstance(mode, int) and isinstance(name, str) tree_cache = (binsha, mode, name) @@ -153,15 +161,14 @@ def __delitem__(self, name: str) -> None: class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): - """Tree objects represent an ordered list of Blobs and other Trees. + R"""Tree objects represent an ordered list of :class:`~git.objects.blob.Blob`\s and + other :class:`~git.objects.tree.Tree`\s. - ``Tree as a list``:: + Tree as a list:: - Access a specific blob using the - tree['filename'] notation. + Access a specific blob using the ``tree['filename']`` notation. - You may as well access by index - blob = tree[0] + You may likewise access by index, like ``blob = tree[0]``. """ type: Literal["tree"] = "tree" @@ -223,8 +230,12 @@ def _iter_convert_to_object(self, iterable: Iterable[TreeCacheTup]) -> Iterator[ def join(self, file: str) -> IndexObjUnion: """Find the named object in this tree's contents. - :return: ``git.Blob`` or ``git.Tree`` or ``git.Submodule`` - :raise KeyError: if given file or tree does not exist in tree + :return: + :class:`~git.objects.blob.Blob`, :class:`~git.objects.tree.Tree`, + or :class:`~git.objects.submodule.base.Submodule` + + :raise KeyError: + If the given file or tree does not exist in tree. """ msg = "Blob or Tree named %r not found" if "/" in file: @@ -275,9 +286,13 @@ def blobs(self) -> List[Blob]: @property def cache(self) -> TreeModifier: """ - :return: An object allowing to modify the internal cache. This can be used - to change the tree's contents. When done, make sure you call ``set_done`` - on the tree modifier, or serialization behaviour will be incorrect. + :return: + An object allowing modification of the internal cache. This can be used to + change the tree's contents. When done, make sure you call + :meth:`~TreeModifier.set_done` on the tree modifier, or serialization + behaviour will be incorrect. + + :note: See :class:`TreeModifier` for more information on how to alter the cache. """ return TreeModifier(self._cache) @@ -292,12 +307,13 @@ def traverse( ignore_self: int = 1, as_edge: bool = False, ) -> Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]]: - """For documentation, see util.Traversable._traverse(). + """For documentation, see + `Traversable._traverse() `. - Trees are set to ``visit_once = False`` to gain more performance in the traversal. + Trees are set to ``visit_once = False`` to gain more performance in the + traversal. """ - # """ # # To typecheck instead of using cast. # import itertools # def is_tree_traversed(inp: Tuple) -> TypeGuard[Tuple[Iterator[Union['Tree', 'Blob', 'Submodule']]]]: @@ -306,7 +322,8 @@ def traverse( # ret = super().traverse(predicate, prune, depth, branch_first, visit_once, ignore_self) # ret_tup = itertools.tee(ret, 2) # assert is_tree_traversed(ret_tup), f"Type is {[type(x) for x in list(ret_tup[0])]}" - # return ret_tup[0]""" + # return ret_tup[0] + return cast( Union[Iterator[IndexObjUnion], Iterator[TraversedTreeTup]], super()._traverse( @@ -321,8 +338,10 @@ def traverse( def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]: """ - :return: IterableList with the results of the traversal as produced by - traverse() + :return: + :class:`~git.util.IterableList`IterableList with the results of the + traversal as produced by :meth:`traverse` + Tree -> IterableList[Union['Submodule', 'Tree', 'Blob']] """ return super()._list_traverse(*args, **kwargs) @@ -375,9 +394,10 @@ def __reversed__(self) -> Iterator[IndexObjUnion]: def _serialize(self, stream: "BytesIO") -> "Tree": """Serialize this tree into the stream. Assumes sorted tree data. - .. note:: We will assume our tree data to be in a sorted state. If this is not - the case, serialization will not generate a correct tree representation as - these are assumed to be sorted by algorithms. + :note: + We will assume our tree data to be in a sorted state. If this is not the + case, serialization will not generate a correct tree representation as these + are assumed to be sorted by algorithms. """ tree_to_stream(self._cache, stream.write) return self diff --git a/git/objects/util.py b/git/objects/util.py index b25a3f5ff..7ad20d66e 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -88,14 +88,16 @@ class TraverseNT(NamedTuple): def mode_str_to_int(modestr: Union[bytes, str]) -> int: - """ + """Convert mode bits from an octal mode string to an integer mode for git. + :param modestr: - String like 755 or 644 or 100644 - only the last 6 chars will be used. + String like ``755`` or ``644`` or ``100644`` - only the last 6 chars will be + used. :return: - String identifying a mode compatible to the mode methods ids of the - stat module regarding the rwx permissions for user, group and other, - special flags and file system flags, such as whether it is a symlink. + String identifying a mode compatible to the mode methods ids of the stat module + regarding the rwx permissions for user, group and other, special flags and file + system flags, such as whether it is a symlink. """ mode = 0 for iteration, char in enumerate(reversed(modestr[-6:])): @@ -108,13 +110,17 @@ def mode_str_to_int(modestr: Union[bytes, str]) -> int: def get_object_type_by_name( object_type_name: bytes, ) -> Union[Type["Commit"], Type["TagObject"], Type["Tree"], Type["Blob"]]: - """ - :return: A type suitable to handle the given object type name. - Use the type to create new instances. + """Retrieve the Python class GitPython uses to represent a kind of Git object. + + :return: + A type suitable to handle the given as `object_type_name`. + This type can be called create new instances. - :param object_type_name: Member of TYPES + :param object_type_name: + Member of :attr:`Object.TYPES `. - :raise ValueError: If object_type_name is unknown + :raise ValueError: + If `object_type_name` is unknown. """ if object_type_name == b"commit": from . import commit @@ -137,10 +143,11 @@ def get_object_type_by_name( def utctz_to_altz(utctz: str) -> int: - """Convert a git timezone offset into a timezone offset west of - UTC in seconds (compatible with :attr:`time.altzone`). + """Convert a git timezone offset into a timezone offset west of UTC in seconds + (compatible with :attr:`time.altzone`). - :param utctz: git utc timezone string, e.g. +0200 + :param utctz: + git utc timezone string, e.g. +0200 """ int_utctz = int(utctz) seconds = (abs(int_utctz) // 100) * 3600 + (abs(int_utctz) % 100) * 60 @@ -148,9 +155,11 @@ def utctz_to_altz(utctz: str) -> int: def altz_to_utctz_str(altz: float) -> str: - """Convert a timezone offset west of UTC in seconds into a Git timezone offset string. + """Convert a timezone offset west of UTC in seconds into a Git timezone offset + string. - :param altz: Timezone offset in seconds west of UTC + :param altz: + Timezone offset in seconds west of UTC. """ hours = abs(altz) // 3600 minutes = (abs(altz) % 3600) // 60 @@ -160,9 +169,11 @@ def altz_to_utctz_str(altz: float) -> str: def verify_utctz(offset: str) -> str: """ - :raise ValueError: If offset is incorrect + :raise ValueError: + If `offset` is incorrect. - :return: offset + :return: + `offset` """ fmt_exc = ValueError("Invalid timezone offset format: %s" % offset) if len(offset) != 5: @@ -197,7 +208,8 @@ def dst(self, dt: Union[datetime, None]) -> timedelta: def from_timestamp(timestamp: float, tz_offset: float) -> datetime: - """Convert a timestamp + tz_offset into an aware datetime instance.""" + """Convert a `timestamp` + `tz_offset` into an aware :class:`~datetime.datetime` + instance.""" utc_dt = datetime.fromtimestamp(timestamp, utc) try: local_dt = utc_dt.astimezone(tzoffset(tz_offset)) @@ -207,20 +219,22 @@ def from_timestamp(timestamp: float, tz_offset: float) -> datetime: def parse_date(string_date: Union[str, datetime]) -> Tuple[int, int]: - """ - Parse the given date as one of the following: + """Parse the given date as one of the following: * Aware datetime instance * Git internal format: timestamp offset - * RFC 2822: Thu, 07 Apr 2005 22:13:13 +0200. - * ISO 8601 2005-04-07T22:13:13 + * RFC 2822: ``Thu, 07 Apr 2005 22:13:13 +0200`` + * ISO 8601: ``2005-04-07T22:13:13`` The T can be a space as well. - :return: Tuple(int(timestamp_UTC), int(offset)), both in seconds since epoch + :return: + Tuple(int(timestamp_UTC), int(offset)), both in seconds since epoch - :raise ValueError: If the format could not be understood + :raise ValueError: + If the format could not be understood. - :note: Date can also be YYYY.MM.DD, MM/DD/YYYY and DD.MM.YYYY. + :note: + Date can also be ``YYYY.MM.DD``, ``MM/DD/YYYY`` and ``DD.MM.YYYY``. """ if isinstance(string_date, datetime): if string_date.tzinfo: @@ -314,7 +328,8 @@ def parse_actor_and_date(line: str) -> Tuple[Actor, int, int]: author Tom Preston-Werner 1191999972 -0700 - :return: [Actor, int_seconds_since_epoch, int_timezone_offset] + :return: + [Actor, int_seconds_since_epoch, int_timezone_offset] """ actor, epoch, offset = "", "0", "0" m = _re_actor_epoch.search(line) @@ -336,8 +351,8 @@ class ProcessStreamAdapter: """Class wiring all calls to the contained Process instance. Use this type to hide the underlying process to provide access only to a specified - stream. The process is usually wrapped into an AutoInterrupt class to kill - it if the instance goes out of scope. + stream. The process is usually wrapped into an :class:`~git.cmd.Git.AutoInterrupt` + class to kill it if the instance goes out of scope. """ __slots__ = ("_proc", "_stream") @@ -352,14 +367,18 @@ def __getattr__(self, attr: str) -> Any: @runtime_checkable class Traversable(Protocol): - """Simple interface to perform depth-first or breadth-first traversals - in one direction. + """Simple interface to perform depth-first or breadth-first traversals in one + direction. Subclasses only need to implement one function. - Instances of the Subclass must be hashable. + Instances of the subclass must be hashable. + + Defined subclasses: - Defined subclasses = [Commit, Tree, SubModule] + * :class:`Commit ` + * :class:`Tree ` + * :class:`Submodule ` """ __slots__ = () @@ -368,7 +387,7 @@ class Traversable(Protocol): @abstractmethod def _get_intermediate_items(cls, item: Any) -> Sequence["Traversable"]: """ - Returns: + :return: Tuple of items connected to the given item. Must be implemented in subclass. @@ -399,20 +418,23 @@ def _list_traverse( ) -> IterableList[Union["Commit", "Submodule", "Tree", "Blob"]]: """Traverse self and collect all items found. - :return: IterableList with the results of the traversal as produced by - :meth:`traverse`:: + :return: :class:`~git.util.IterableList` with the results of the traversal as + produced by :meth:`traverse`:: - Commit -> IterableList['Commit'] - Submodule -> IterableList['Submodule'] - Tree -> IterableList[Union['Submodule', 'Tree', 'Blob']] + Commit -> IterableList["Commit"] + Submodule -> IterableList["Submodule"] + Tree -> IterableList[Union["Submodule", "Tree", "Blob"]] """ # Commit and Submodule have id.__attribute__ as IterableObj. # Tree has id.__attribute__ inherited from IndexObject. if isinstance(self, Has_id_attribute): id = self._id_attribute_ else: - id = "" # Shouldn't reach here, unless Traversable subclass created with no _id_attribute_. - # Could add _id_attribute_ to Traversable, or make all Traversable also Iterable? + # Shouldn't reach here, unless Traversable subclass created with no + # _id_attribute_. + id = "" + # Could add _id_attribute_ to Traversable, or make all Traversable also + # Iterable? if not as_edge: out: IterableList[Union["Commit", "Submodule", "Tree", "Blob"]] = IterableList(id) @@ -453,46 +475,49 @@ def _traverse( ) -> Union[Iterator[Union["Traversable", "Blob"]], Iterator[TraversedTup]]: """Iterator yielding items found when traversing self. - :param predicate: f(i,d) returns False if item i at depth d should not be - included in the result. + :param predicate: + A function ``f(i,d)`` that returns False if item i at depth ``d`` should not + be included in the result. :param prune: - f(i,d) return True if the search should stop at item i at depth d. Item i - will not be returned. + A function ``f(i,d)`` that returns True if the search should stop at item + ``i`` at depth ``d``. Item ``i`` will not be returned. :param depth: Defines at which level the iteration should not go deeper if -1, there is no limit if 0, you would effectively only get self, the root of the iteration - i.e. if 1, you would only get the first level of predecessors/successors + i.e. if 1, you would only get the first level of predecessors/successors. :param branch_first: - if True, items will be returned branch first, otherwise depth first + If True, items will be returned branch first, otherwise depth first. :param visit_once: - if True, items will only be returned once, although they might be + If True, items will only be returned once, although they might be encountered several times. Loops are prevented that way. :param ignore_self: - if True, self will be ignored and automatically pruned from the result. - Otherwise it will be the first item to be returned. If as_edge is True, the + If True, self will be ignored and automatically pruned from the result. + Otherwise it will be the first item to be returned. If `as_edge` is True, the source of the first edge is None :param as_edge: - if True, return a pair of items, first being the source, second the + If True, return a pair of items, first being the source, second the destination, i.e. tuple(src, dest) with the edge spanning from source to - destination + destination. - :return: Iterator yielding items found when traversing self:: + :return: + Iterator yielding items found when traversing self:: - Commit -> Iterator[Union[Commit, Tuple[Commit, Commit]] - Submodule -> Iterator[Submodule, Tuple[Submodule, Submodule]] - Tree -> Iterator[Union[Blob, Tree, Submodule, - Tuple[Union[Submodule, Tree], Union[Blob, Tree, Submodule]]] + Commit -> Iterator[Union[Commit, Tuple[Commit, Commit]] Submodule -> + Iterator[Submodule, Tuple[Submodule, Submodule]] Tree -> + Iterator[Union[Blob, Tree, Submodule, + Tuple[Union[Submodule, Tree], Union[Blob, Tree, + Submodule]]] - ignore_self=True is_edge=True -> Iterator[item] - ignore_self=True is_edge=False --> Iterator[item] - ignore_self=False is_edge=True -> Iterator[item] | Iterator[Tuple[src, item]] - ignore_self=False is_edge=False -> Iterator[Tuple[src, item]] + ignore_self=True is_edge=True -> Iterator[item] ignore_self=True + is_edge=False --> Iterator[item] ignore_self=False is_edge=True -> + Iterator[item] | Iterator[Tuple[src, item]] ignore_self=False + is_edge=False -> Iterator[Tuple[src, item]] """ visited = set() @@ -526,7 +551,9 @@ def addToStack( visited.add(item) rval: Union[TraversedTup, "Traversable", "Blob"] - if as_edge: # If as_edge return (src, item) unless rrc is None (e.g. for first item). + if as_edge: + # If as_edge return (src, item) unless rrc is None + # (e.g. for first item). rval = (src, item) else: rval = item @@ -549,7 +576,8 @@ def addToStack( @runtime_checkable class Serializable(Protocol): - """Defines methods to serialize and deserialize objects from and into a data stream.""" + """Defines methods to serialize and deserialize objects from and into a data + stream.""" __slots__ = () @@ -557,11 +585,14 @@ class Serializable(Protocol): def _serialize(self, stream: "BytesIO") -> "Serializable": """Serialize the data of this object into the given data stream. - :note: A serialized object would :meth:`_deserialize` into the same object. + :note: + A serialized object would :meth:`_deserialize` into the same object. - :param stream: a file-like object + :param stream: + A file-like object. - :return: self + :return: + self """ raise NotImplementedError("To be implemented in subclass") @@ -569,9 +600,11 @@ def _serialize(self, stream: "BytesIO") -> "Serializable": def _deserialize(self, stream: "BytesIO") -> "Serializable": """Deserialize all information regarding this object from the stream. - :param stream: a file-like object + :param stream: + A file-like object. - :return: self + :return: + self """ raise NotImplementedError("To be implemented in subclass") From 37011bf873b93ad38ab184b3885df66072692a12 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 13:25:05 -0500 Subject: [PATCH 23/61] Fix backslash formatting in git.util docstrings As in 5219489. --- git/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/util.py b/git/util.py index 30b78f7b2..4126c6c73 100644 --- a/git/util.py +++ b/git/util.py @@ -260,7 +260,7 @@ def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * def join_path(a: PathLike, *p: PathLike) -> PathLike: R"""Join path tokens together similar to osp.join, but always use - '/' instead of possibly '\' on Windows.""" + ``/`` instead of possibly ``\`` on Windows.""" path = str(a) for b in p: b = str(b) @@ -300,7 +300,7 @@ def join_path_native(a: PathLike, *p: PathLike) -> PathLike: R"""Like join_path, but makes sure an OS native path is returned. This is only needed to play it safe on Windows and to ensure nice paths that only - use '\'. + use ``\``. """ return to_native_path(join_path(a, *p)) From d9fb2f4c8f59f6074493d780a4ba62e89fa9f5d6 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 13:36:49 -0500 Subject: [PATCH 24/61] Further git.util docstring revisions That I had missed in 1cd73ba. --- git/util.py | 52 +++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/git/util.py b/git/util.py index 4126c6c73..72861ef84 100644 --- a/git/util.py +++ b/git/util.py @@ -201,9 +201,9 @@ def patch_env(name: str, value: str) -> Generator[None, None, None]: def rmtree(path: PathLike) -> None: """Remove the given directory tree recursively. - :note: We use :func:`shutil.rmtree` but adjust its behaviour to see whether files - that couldn't be deleted are read-only. Windows will not remove them in that - case. + :note: + We use :func:`shutil.rmtree` but adjust its behaviour to see whether files that + couldn't be deleted are read-only. Windows will not remove them in that case. """ def handler(function: Callable, path: PathLike, _excinfo: Any) -> None: @@ -241,8 +241,8 @@ def rmfile(path: PathLike) -> None: def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * 1024) -> int: - """Copy all data from the source stream into the destination stream in chunks - of size chunk_size. + """Copy all data from the `source` stream into the `destination` stream in chunks + of size `chunk_size`. :return: Number of bytes written @@ -259,8 +259,8 @@ def stream_copy(source: BinaryIO, destination: BinaryIO, chunk_size: int = 512 * def join_path(a: PathLike, *p: PathLike) -> PathLike: - R"""Join path tokens together similar to osp.join, but always use - ``/`` instead of possibly ``\`` on Windows.""" + R"""Join path tokens together similar to osp.join, but always use ``/`` instead of + possibly ``\`` on Windows.""" path = str(a) for b in p: b = str(b) @@ -297,7 +297,7 @@ def to_native_path_linux(path: PathLike) -> str: def join_path_native(a: PathLike, *p: PathLike) -> PathLike: - R"""Like join_path, but makes sure an OS native path is returned. + R"""Like :func:`join_path`, but makes sure an OS native path is returned. This is only needed to play it safe on Windows and to ensure nice paths that only use ``\``. @@ -308,10 +308,12 @@ def join_path_native(a: PathLike, *p: PathLike) -> PathLike: def assure_directory_exists(path: PathLike, is_file: bool = False) -> bool: """Make sure that the directory pointed to by path exists. - :param is_file: If True, ``path`` is assumed to be a file and handled correctly. + :param is_file: + If True, `path` is assumed to be a file and handled correctly. Otherwise it must be a directory. - :return: True if the directory was created, False if it already existed. + :return: + True if the directory was created, False if it already existed. """ if is_file: path = osp.dirname(path) @@ -339,7 +341,8 @@ def py_where(program: str, path: Optional[PathLike] = None) -> List[str]: :func:`is_cygwin_git`. When a search following all shell rules is needed, :func:`shutil.which` can be used instead. - :note: Neither this function nor :func:`shutil.which` will predict the effect of an + :note: + Neither this function nor :func:`shutil.which` will predict the effect of an executable search on a native Windows system due to a :class:`subprocess.Popen` call without ``shell=True``, because shell and non-shell executable search on Windows differ considerably. @@ -550,8 +553,7 @@ 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. - """ + react to the progress.""" _num_op_codes: int = 9 ( @@ -761,8 +763,8 @@ def update(self, *args: Any, **kwargs: Any) -> None: class Actor: """Actors hold information about a person acting on the repository. They - can be committers and authors or anything with a name and an email as - mentioned in the git log entries.""" + can be committers and authors or anything with a name and an email as mentioned in + the git log entries.""" # PRECOMPILED REGEX name_only_regex = re.compile(r"<(.*)>") @@ -802,7 +804,7 @@ def __repr__(self) -> str: @classmethod def _from_string(cls, string: str) -> "Actor": - """Create an Actor from a string. + """Create an :class:`Actor` from a string. :param string: The string, which is expected to be in regular git format:: @@ -868,10 +870,11 @@ def default_name() -> str: @classmethod def committer(cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint"] = None) -> "Actor": """ - :return: Actor instance corresponding to the configured committer. It behaves - similar to the git implementation, such that the environment will override - configuration values of `config_reader`. If no value is set at all, it will - be generated. + :return: + :class:`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. :param config_reader: ConfigReader to use to retrieve the values from in case they are not set in @@ -887,8 +890,7 @@ def author(cls, config_reader: Union[None, "GitConfigParser", "SectionConstraint class Stats: - """ - Represents stat information as presented by git at the end of a merge. It is + """Represents stat information as presented by git at the end of a merge. It is created from the output of a diff operation. Example:: @@ -949,9 +951,9 @@ 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 asked for explicitly using :meth:`write_sha`. + """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 asked for explicitly using + :meth:`write_sha`. Only useful to the index file. From d1d18c230b8853712cc44e0609b54cf55f09cafa Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 14:34:40 -0500 Subject: [PATCH 25/61] Revise docstrings within git.refs --- git/refs/head.py | 72 +++++++------- git/refs/log.py | 110 ++++++++++++++-------- git/refs/reference.py | 38 +++++--- git/refs/remote.py | 16 ++-- git/refs/symbolic.py | 213 +++++++++++++++++++++++++----------------- git/refs/tag.py | 20 ++-- 6 files changed, 283 insertions(+), 186 deletions(-) diff --git a/git/refs/head.py b/git/refs/head.py index ebc71eb96..a051748ba 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -52,8 +52,9 @@ def __init__(self, repo: "Repo", path: PathLike = _HEAD_NAME): def orig_head(self) -> SymbolicReference: """ - :return: SymbolicReference pointing at the ORIG_HEAD, which is maintained - to contain the previous value of HEAD. + :return: + :class:`~git.refs.symbolic.SymbolicReference` pointing at the ORIG_HEAD, + which is maintained to contain the previous value of HEAD. """ return SymbolicReference(self.repo, self._ORIG_HEAD_NAME) @@ -66,16 +67,16 @@ def reset( **kwargs: Any, ) -> "HEAD": """Reset our HEAD to the given commit optionally synchronizing - the index and working tree. The reference we refer to will be set to - commit as well. + the index and working tree. The reference we refer to will be set to commit as + well. :param commit: - Commit object, Reference Object or string identifying a revision we - should reset HEAD to. + :class:`~git.objects.commit.Commit`, :class:`~git.refs.reference.Reference`, + or string identifying a revision we should reset HEAD to. :param index: - If True, the index will be set to match the given commit. Otherwise - it will not be touched. + If True, the index will be set to match the given commit. + Otherwise it will not be touched. :param working_tree: If True, the working tree will be forcefully adjusted to match the given @@ -87,7 +88,7 @@ def reset( that are to be reset. This allows to partially reset individual files. :param kwargs: - Additional arguments passed to git-reset. + Additional arguments passed to ``git reset``. :return: self """ @@ -123,8 +124,8 @@ def reset( class Head(Reference): - """A Head is a named reference to a Commit. Every Head instance contains a name - and a Commit object. + """A Head is a named reference to a :class:`~git.objects.commit.Commit`. Every Head + instance contains a name and a :class:`~git.objects.commit.Commit` object. Examples:: @@ -150,9 +151,8 @@ def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, * """Delete the given heads. :param force: - If True, the heads will be deleted even if they are not yet merged into - the main development stream. - Default False + If True, the heads will be deleted even if they are not yet merged into the + main development stream. Default False. """ flag = "-d" if force: @@ -163,9 +163,11 @@ def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) """Configure this branch to track the given remote reference. This will alter this branch's configuration accordingly. - :param remote_reference: The remote reference to track or None to untrack - any references. - :return: self + :param remote_reference: + The remote reference to track or None to untrack any references. + + :return: + self """ from .remote import RemoteReference @@ -190,8 +192,10 @@ def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) def tracking_branch(self) -> Union["RemoteReference", None]: """ - :return: The remote_reference we are tracking, or None if we are - not a tracking branch.""" + :return: + The remote reference we are tracking, or None if we are not a tracking + branch. + """ from .remote import RemoteReference reader = self.config_reader() @@ -211,16 +215,18 @@ def rename(self, new_path: PathLike, force: bool = False) -> "Head": """Rename self to a new path. :param new_path: - Either a simple name or a path, i.e. new_name or features/new_name. - The prefix refs/heads is implied. + Either a simple name or a path, e.g. ``new_name`` or ``features/new_name``. + The prefix ``refs/heads`` is implied. :param force: If True, the rename will succeed even if a head with the target name already exists. - :return: self + :return: + self - :note: Respects the ref log as git commands are used. + :note: + Respects the ref log as git commands are used. """ flag = "-m" if force: @@ -247,15 +253,15 @@ def checkout(self, force: bool = False, **kwargs: Any) -> Union["HEAD", "Head"]: ``b="new_branch"`` to create a new branch at the given spot. :return: - The active branch after the checkout operation, usually self unless - a new branch has been created. + The active branch after the checkout operation, usually self unless a new + branch has been created. If there is no active branch, as the HEAD is now detached, the HEAD reference will be returned instead. :note: - By default it is only allowed to checkout heads - everything else - will leave the HEAD detached which is allowed and possible, but remains - a special state that some tools might not be able to handle. + By default it is only allowed to checkout heads - everything else will leave + the HEAD detached which is allowed and possible, but remains a special state + that some tools might not be able to handle. """ kwargs["f"] = force if kwargs["f"] is False: @@ -279,15 +285,17 @@ def _config_parser(self, read_only: bool) -> SectionConstraint[GitConfigParser]: def config_reader(self) -> SectionConstraint[GitConfigParser]: """ - :return: A configuration parser instance constrained to only read - this instance's values. + :return: + A configuration parser instance constrained to only read this instance's + values. """ return self._config_parser(read_only=True) def config_writer(self) -> SectionConstraint[GitConfigParser]: """ - :return: A configuration writer instance with read-and write access - to options of this head. + :return: + A configuration writer instance with read-and write access to options of + this head. """ return self._config_parser(read_only=False) diff --git a/git/refs/log.py b/git/refs/log.py index e45798d8a..260f2fff5 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -44,6 +44,7 @@ class RefLogEntry(Tuple[str, str, Actor, Tuple[int, int], str]): """Named tuple allowing easy access to the revlog data fields.""" _re_hexsha_only = re.compile(r"^[0-9A-Fa-f]{40}$") + __slots__ = () def __repr__(self) -> str: @@ -81,7 +82,7 @@ def actor(self) -> Actor: @property def time(self) -> Tuple[int, int]: - """time as tuple: + """Time as tuple: * [0] = ``int(time)`` * [1] = ``int(timezone_offset)`` in :attr:`time.altzone` format @@ -113,9 +114,11 @@ def new( def from_line(cls, line: bytes) -> "RefLogEntry": """:return: New RefLogEntry instance from the given revlog line. - :param line: Line bytes without trailing newline + :param line: + Line bytes without trailing newline - :raise ValueError: If `line` could not be parsed + :raise ValueError: + If `line` could not be parsed. """ line_str = line.decode(defenc) fields = line_str.split("\t", 1) @@ -147,9 +150,9 @@ def from_line(cls, line: bytes) -> "RefLogEntry": class RefLog(List[RefLogEntry], Serializable): - """A reflog contains RefLogEntrys, each of which defines a certain state - of the head in question. Custom query methods allow to retrieve log entries - by date or by other criteria. + R"""A reflog contains :class:`RefLogEntry`\s, each of which defines a certain state + of the head in question. Custom query methods allow to retrieve log entries by date + or by other criteria. Reflog entries are ordered. The first added entry is first in the list. The last entry, i.e. the last change of the head or reference, is last in the list. @@ -163,8 +166,8 @@ def __new__(cls, filepath: Union[PathLike, None] = None) -> "RefLog": def __init__(self, filepath: Union[PathLike, None] = None): """Initialize this instance with an optional filepath, from which we will - initialize our data. The path is also used to write changes back using - the write() method.""" + initialize our data. The path is also used to write changes back using the + :meth:`write` method.""" self._path = filepath if filepath is not None: self._read_from_file() @@ -189,31 +192,40 @@ def _read_from_file(self) -> None: @classmethod def from_file(cls, filepath: PathLike) -> "RefLog": """ - :return: A new RefLog instance containing all entries from the reflog - at the given filepath - :param filepath: Path to reflog - :raise ValueError: If the file could not be read or was corrupted in some way + :return: + A new :class:`RefLog` instance containing all entries from the reflog at the + given `filepath`. + + :param filepath: + Path to reflog. + + :raise ValueError: + If the file could not be read or was corrupted in some way. """ return cls(filepath) @classmethod def path(cls, ref: "SymbolicReference") -> str: """ - :return: String to absolute path at which the reflog of the given ref - instance would be found. The path is not guaranteed to point to a valid - file though. - :param ref: SymbolicReference instance + :return: + String to absolute path at which the reflog of the given ref instance would + be found. The path is not guaranteed to point to a valid file though. + + :param ref: + :class:`~git.refs.symbolic.SymbolicReference` instance """ return osp.join(ref.repo.git_dir, "logs", to_native_path(ref.path)) @classmethod def iter_entries(cls, stream: Union[str, "BytesIO", mmap]) -> Iterator[RefLogEntry]: """ - :return: Iterator yielding RefLogEntry instances, one for each line read + :return: + Iterator yielding :class:`RefLogEntry` instances, one for each line read from the given stream. - :param stream: File-like object containing the revlog in its native format - or string instance pointing to a file to read. + :param stream: + File-like object containing the revlog in its native format or string + instance pointing to a file to read. """ new_entry = RefLogEntry.from_line if isinstance(stream, str): @@ -233,18 +245,23 @@ def iter_entries(cls, stream: Union[str, "BytesIO", mmap]) -> Iterator[RefLogEnt @classmethod def entry_at(cls, filepath: PathLike, index: int) -> "RefLogEntry": """ - :return: RefLogEntry at the given index. + :return: + :class:`RefLogEntry` at the given index. - :param filepath: Full path to the index file from which to read the entry. + :param filepath: + Full path to the index file from which to read the entry. - :param index: Python list compatible index, i.e. it may be negative to - specify an entry counted from the end of the list. + :param index: + Python list compatible index, i.e. it may be negative to specify an entry + counted from the end of the list. - :raise IndexError: If the entry didn't exist. + :raise IndexError: + If the entry didn't exist. - .. note:: This method is faster as it only parses the entry at index, skipping - all other lines. Nonetheless, the whole file has to be read if - the index is negative. + :note: + This method is faster as it only parses the entry at index, skipping all + other lines. Nonetheless, the whole file has to be read if the index is + negative. """ with open(filepath, "rb") as fp: if index < 0: @@ -264,7 +281,8 @@ def entry_at(cls, filepath: PathLike, index: int) -> "RefLogEntry": def to_file(self, filepath: PathLike) -> None: """Write the contents of the reflog instance to a file at the given filepath. - :param filepath: Path to file, parent directories are assumed to exist. + :param filepath: + Path to file. Parent directories are assumed to exist. """ lfd = LockedFD(filepath) assure_directory_exists(filepath, is_file=True) @@ -290,19 +308,32 @@ def append_entry( ) -> "RefLogEntry": """Append a new log entry to the revlog at filepath. - :param config_reader: Configuration reader of the repository - used to obtain - user information. May also be an Actor instance identifying the committer + :param config_reader: + Configuration reader of the repository - used to obtain user information. + May also be an :class:`~git.util.Actor` instance identifying the committer directly or None. - :param filepath: Full path to the log file. - :param oldbinsha: Binary sha of the previous commit. - :param newbinsha: Binary sha of the current commit. - :param message: Message describing the change to the reference. - :param write: If True, the changes will be written right away. Otherwise the - change will not be written. - :return: RefLogEntry objects which was appended to the log. + :param filepath: + Full path to the log file. + + :param oldbinsha: + Binary sha of the previous commit. + + :param newbinsha: + Binary sha of the current commit. + + :param message: + Message describing the change to the reference. + + :param write: + If True, the changes will be written right away. + Otherwise the change will not be written. + + :return: + :class:`RefLogEntry` objects which was appended to the log. - :note: As we are append-only, concurrent access is not a problem as we do not + :note: + As we are append-only, concurrent access is not a problem as we do not interfere with readers. """ @@ -340,7 +371,8 @@ def append_entry( def write(self) -> "RefLog": """Write this instance's data to the file we are originating from. - :return: self + :return: + self """ if self._path is None: raise ValueError("Instance was not initialized with a path, use to_file(...) instead") diff --git a/git/refs/reference.py b/git/refs/reference.py index 20d42472d..32547278f 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -25,7 +25,8 @@ def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]: - """A decorator raising a TypeError if we are not a valid remote, based on the path.""" + """A decorator raising :class:`TypeError` if we are not a valid remote, based on the + path.""" def wrapper(self: T_References, *args: Any) -> _T: if not self.is_remote(): @@ -56,11 +57,16 @@ class Reference(SymbolicReference, LazyMixin, IterableObj): def __init__(self, repo: "Repo", path: PathLike, check_path: bool = True) -> None: """Initialize this instance. - :param repo: Our parent repository. - :param path: Path relative to the .git/ directory pointing to the ref in - question, e.g. ``refs/heads/master``. - :param check_path: If False, you can provide any path. Otherwise the path must - start with the default path prefix of this type. + :param repo: + Our parent repository. + + :param path: + Path relative to the ``.git/`` directory pointing to the ref in question, + e.g. ``refs/heads/master``. + + :param check_path: + If False, you can provide any path. + Otherwise the path must start with the default path prefix of this type. """ if check_path and not str(path).startswith(self._common_path_default + "/"): raise ValueError(f"Cannot instantiate {self.__class__.__name__!r} from path {path}") @@ -80,7 +86,8 @@ def set_object( ) -> "Reference": """Special version which checks if the head-log needs an update as well. - :return: self + :return: + self """ oldbinsha = None if logmsg is not None: @@ -115,7 +122,10 @@ def set_object( @property def name(self) -> str: - """:return: (shortest) Name of this reference - it may contain path components""" + """ + :return: + (shortest) Name of this reference - it may contain path components + """ # The first two path tokens can be removed as they are # refs/heads or refs/tags or refs/remotes. tokens = self.path.split("/") @@ -144,8 +154,8 @@ def iter_items( def remote_name(self) -> str: """ :return: - Name of the remote we are a reference of, such as 'origin' for a reference - named 'origin/master'. + Name of the remote we are a reference of, such as ``origin`` for a reference + named ``origin/master``. """ tokens = self.path.split("/") # /refs/remotes// @@ -155,10 +165,12 @@ def remote_name(self) -> str: @require_remote_ref_path def remote_head(self) -> str: """ - :return: Name of the remote head itself, e.g. master. + :return: + Name of the remote head itself, e.g. ``master``. - :note: The returned name is usually not qualified enough to uniquely identify - a branch. + :note: + The returned name is usually not qualified enough to uniquely identify a + branch. """ tokens = self.path.split("/") return "/".join(tokens[3:]) diff --git a/git/refs/remote.py b/git/refs/remote.py index 59d02a755..c2c2c1aac 100644 --- a/git/refs/remote.py +++ b/git/refs/remote.py @@ -47,10 +47,10 @@ def iter_items( # super is Reference return super().iter_items(repo, common_path) - # The Head implementation of delete also accepts strs, but this - # implementation does not. mypy doesn't have a way of representing - # tightening the types of arguments in subclasses and recommends Any or - # "type: ignore". (See https://github.com/python/typing/issues/241) + # The Head implementation of delete also accepts strs, but this implementation does + # not. mypy doesn't have a way of representing tightening the types of arguments in + # subclasses and recommends Any or "type: ignore". + # (See: https://github.com/python/typing/issues/241) @classmethod def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None: # type: ignore """Delete the given remote references. @@ -60,9 +60,9 @@ def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None: should not narrow the signature. """ repo.git.branch("-d", "-r", *refs) - # The official deletion method will ignore remote symbolic refs - these - # are generally ignored in the refs/ folder. We don't though - # and delete remainders manually. + # The official deletion method will ignore remote symbolic refs - these are + # generally ignored in the refs/ folder. We don't though and delete remainders + # manually. for ref in refs: try: os.remove(os.path.join(repo.common_dir, ref.path)) @@ -76,5 +76,5 @@ def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None: @classmethod def create(cls, *args: Any, **kwargs: Any) -> NoReturn: - """Raises TypeError. Defined so the create method is disabled.""" + """Raise :class:`TypeError`. Defined so the ``create`` method is disabled.""" raise TypeError("Cannot explicitly create remote references") diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 31f959ac2..6b9fd9ab7 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -100,8 +100,8 @@ def __hash__(self) -> int: def name(self) -> str: """ :return: - In case of symbolic references, the shortest assumable name - is the path itself. + In case of symbolic references, the shortest assumable name is the path + itself. """ return str(self.path) @@ -118,7 +118,8 @@ def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]: """Return an iterator yielding pairs of sha1/path pairs (as strings) for the corresponding refs. - :note: The packed refs file will be kept open as long as we iterate. + :note: + The packed refs file will be kept open as long as we iterate. """ try: with open(cls._get_packed_refs_path(repo), "rt", encoding="UTF-8") as fp: @@ -155,10 +156,12 @@ def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]: @classmethod def dereference_recursive(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> str: """ - :return: hexsha stored in the reference at the given ref_path, recursively dereferencing all - intermediate references as required + :return: + hexsha stored in the reference at the given `ref_path`, recursively + dereferencing all intermediate references as required - :param repo: The repository containing the reference at ref_path + :param repo: + The repository containing the reference at `ref_path`. """ while True: @@ -221,8 +224,9 @@ def _get_ref_info_helper( cls, repo: "Repo", ref_path: Union[PathLike, None] ) -> Union[Tuple[str, None], Tuple[None, str]]: """ - :return: (str(sha), str(target_ref_path)) if available, the sha the file at - rela_path points to, or None. + :return: + (str(sha), str(target_ref_path)) if available, the sha the file at rela_path + points to, or None. target_ref_path is the reference we point to, or None. """ @@ -276,8 +280,8 @@ def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[T def _get_object(self) -> Commit_ish: """ :return: - The object our ref currently refers to. Refs can be cached, they will - always point to the actual object as it gets re-created on each query. + The object our ref currently refers to. Refs can be cached, they will always + point to the actual object as it gets re-created on each query. """ # We have to be dynamic here as we may be a tag which can point to anything. # Our path will be resolved to the hexsha which will be used accordingly. @@ -305,11 +309,15 @@ def set_commit( commit: Union[Commit, "SymbolicReference", str], logmsg: Union[str, None] = None, ) -> "SymbolicReference": - """As set_object, but restricts the type of object to be a Commit. + """Like :meth:`set_object`, but restricts the type of object to be a + :class:`~git.objects.commit.Commit`. + + :raise ValueError: + If `commit` is not a :class:`~git.objects.commit.Commit` object or doesn't + point to a commit. - :raise ValueError: If commit is not a :class:`~git.objects.commit.Commit` object - or doesn't point to a commit - :return: self + :return: + self """ # Check the type - assume the best if it is a base-string. invalid_type = False @@ -339,18 +347,25 @@ def set_object( object: Union[Commit_ish, "SymbolicReference", str], logmsg: Union[str, None] = None, ) -> "SymbolicReference": - """Set the object we point to, possibly dereference our symbolic reference first. - If the reference does not exist, it will be created. - - :param object: A refspec, a :class:`SymbolicReference` or an - :class:`~git.objects.base.Object` instance. - :class:`SymbolicReference` instances will be dereferenced beforehand to - obtain the object they point to. - :param logmsg: If not None, the message will be used in the reflog entry to be - written. Otherwise the reflog is not altered. - :note: Plain :class:`SymbolicReference` instances may not actually point to - objects by convention. - :return: self + """Set the object we point to, possibly dereference our symbolic reference + first. If the reference does not exist, it will be created. + + :param object: + A refspec, a :class:`SymbolicReference` or an + :class:`~git.objects.base.Object` instance. :class:`SymbolicReference` + instances will be dereferenced beforehand to obtain the object they point + to. + + :param logmsg: + If not None, the message will be used in the reflog entry to be written. + Otherwise the reflog is not altered. + + :note: + Plain :class:`SymbolicReference` instances may not actually point to objects + by convention. + + :return: + self """ if isinstance(object, SymbolicReference): object = object.object # @ReservedAssignment @@ -374,10 +389,13 @@ def set_object( def _get_reference(self) -> "SymbolicReference": """ - :return: Reference Object we point to + :return: + :class:`~git.refs.reference.Reference` object we point to - :raise TypeError: If this symbolic reference is detached, hence it doesn't point - to a reference, but to a commit""" + :raise TypeError: + If this symbolic reference is detached, hence it doesn't point to a + reference, but to a commit. + """ sha, target_ref_path = self._get_ref_info(self.repo, self.path) if target_ref_path is None: raise TypeError("%s is a detached symbolic reference as it points to %r" % (self, sha)) @@ -388,10 +406,13 @@ def set_reference( ref: Union[Commit_ish, "SymbolicReference", str], logmsg: Union[str, None] = None, ) -> "SymbolicReference": - """Set ourselves to the given ref. It will stay a symbol if the ref is a Reference. - Otherwise an Object, given as Object instance or refspec, is assumed and if valid, - will be set which effectively detaches the reference if it was a purely - symbolic one. + """Set ourselves to the given `ref`. + + It will stay a symbol if the ref is a :class:`~git.refs.reference.Reference`. + + Otherwise an Object, given as :class:`~git.objects.base.Object` instance or + refspec, is assumed and if valid, will be set which effectively detaches the + reference if it was a purely symbolic one. :param ref: A :class:`SymbolicReference` instance, an :class:`~git.objects.base.Object` @@ -399,15 +420,18 @@ def set_reference( :class:`SymbolicReference` instance, we will point to it. Everything else is dereferenced to obtain the actual object. - :param logmsg: If set to a string, the message will be used in the reflog. + :param logmsg: + If set to a string, the message will be used in the reflog. Otherwise, a reflog entry is not written for the changed reference. The previous commit of the entry will be the commit we point to now. See also: :meth:`log_append` - :return: self + :return: + self - :note: This symbolic reference will not be dereferenced. For that, see + :note: + This symbolic reference will not be dereferenced. For that, see :meth:`set_object`. """ write_value = None @@ -467,8 +491,8 @@ def set_reference( def is_valid(self) -> bool: """ :return: - True if the reference is valid, hence it can be read and points to - a valid object or reference. + True if the reference is valid, hence it can be read and points to a valid + object or reference. """ try: self.object @@ -492,11 +516,13 @@ def is_detached(self) -> bool: def log(self) -> "RefLog": """ - :return: RefLog for this reference. Its last entry reflects the latest change - applied to this reference. + :return: + :class:`~git.refs.log.RefLog` for this reference. + Its last entry reflects the latest change applied to this reference. - .. note:: As the log is parsed every time, its recommended to cache it for use - instead of calling this method repeatedly. It should be considered read-only. + :note: + As the log is parsed every time, its recommended to cache it for use instead + of calling this method repeatedly. It should be considered read-only. """ return RefLog.from_file(RefLog.path(self)) @@ -508,11 +534,17 @@ def log_append( ) -> "RefLogEntry": """Append a logentry to the logfile of this ref. - :param oldbinsha: Binary sha this ref used to point to. - :param message: A message describing the change. - :param newbinsha: The sha the ref points to now. If None, our current commit sha - will be used. - :return: The added :class:`~git.refs.log.RefLogEntry` instance. + :param oldbinsha: + Binary sha this ref used to point to. + + :param message: + A message describing the change. + + :param newbinsha: + The sha the ref points to now. If None, our current commit sha will be used. + + :return: + The added :class:`~git.refs.log.RefLogEntry` instance. """ # NOTE: We use the committer of the currently active commit - this should be # correct to allow overriding the committer on a per-commit level. @@ -532,20 +564,24 @@ def log_append( def log_entry(self, index: int) -> "RefLogEntry": """ - :return: RefLogEntry at the given index + :return: + RefLogEntry at the given index - :param index: Python list compatible positive or negative index + :param index: + Python list compatible positive or negative index. - .. note:: This method must read part of the reflog during execution, hence - it should be used sparingly, or only if you need just one index. - In that case, it will be faster than the :meth:`log` method. + :note: + This method must read part of the reflog during execution, hence it should + be used sparingly, or only if you need just one index. In that case, it will + be faster than the :meth:`log` method. """ return RefLog.entry_at(RefLog.path(self), index) @classmethod def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> PathLike: """ - :return: string with a full repository-relative path which can be used to initialize + :return: + String with a full repository-relative path which can be used to initialize a Reference instance, for instance by using :meth:`Reference.from_path `. """ @@ -566,8 +602,8 @@ def delete(cls, repo: "Repo", path: PathLike) -> None: Repository to delete the reference from. :param path: - Short or full path pointing to the reference, e.g. ``refs/myreference`` - or just ``myreference``, hence ``refs/`` is implied. + Short or full path pointing to the reference, e.g. ``refs/myreference`` or + just ``myreference``, hence ``refs/`` is implied. Alternatively the symbolic reference to be deleted. """ full_ref_path = cls.to_full_path(path) @@ -586,10 +622,10 @@ def delete(cls, repo: "Repo", path: PathLike) -> None: line = line_bytes.decode(defenc) _, _, line_ref = line.partition(" ") line_ref = line_ref.strip() - # Keep line if it is a comment or if the ref to delete is not - # in the line. - # If we deleted the last line and this one is a tag-reference object, - # we drop it as well. + # Keep line if it is a comment or if the ref to delete is not in + # the line. + # If we deleted the last line and this one is a tag-reference + # object, we drop it as well. if (line.startswith("#") or full_ref_path != line_ref) and ( not dropped_last_line or dropped_last_line and not line.startswith("^") ): @@ -604,8 +640,8 @@ def delete(cls, repo: "Repo", path: PathLike) -> None: # Write the new lines. if made_change: - # Binary writing is required, otherwise Windows will - # open the file in text mode and change LF to CRLF! + # Binary writing is required, otherwise Windows will open the file + # in text mode and change LF to CRLF! with open(pack_file_path, "wb") as fd: fd.writelines(line.encode(defenc) for line in new_lines) @@ -630,10 +666,9 @@ def _create( ) -> T_References: """Internal method used to create a new symbolic reference. - If `resolve` is False, the reference will be taken as is, creating - a proper symbolic reference. Otherwise it will be resolved to the - corresponding object and a detached symbolic reference will be created - instead. + If `resolve` is False, the reference will be taken as is, creating a proper + symbolic reference. Otherwise it will be resolved to the corresponding object + and a detached symbolic reference will be created instead. """ git_dir = _git_dir(repo, path) full_ref_path = cls.to_full_path(path) @@ -679,28 +714,30 @@ def create( Repository to create the reference in. :param path: - Full path at which the new symbolic reference is supposed to be - created at, e.g. ``NEW_HEAD`` or ``symrefs/my_new_symref``. + Full path at which the new symbolic reference is supposed to be created at, + e.g. ``NEW_HEAD`` or ``symrefs/my_new_symref``. :param reference: The reference which the new symbolic reference should point to. If it is a commit-ish, the symbolic ref will be detached. :param force: - If True, force creation even if a symbolic reference with that name already exists. - Raise :class:`OSError` otherwise. + If True, force creation even if a symbolic reference with that name already + exists. Raise :class:`OSError` otherwise. :param logmsg: - If not None, the message to append to the reflog. Otherwise no reflog - entry is written. + If not None, the message to append to the reflog. + If None, no reflog entry is written. - :return: Newly created symbolic Reference + :return: + Newly created symbolic reference :raise OSError: - If a (Symbolic)Reference with the same name but different contents - already exists. + If a (Symbolic)Reference with the same name but different contents already + exists. - :note: This does not alter the current HEAD, index or working tree. + :note: + This does not alter the current HEAD, index or working tree. """ return cls._create(repo, path, cls._resolve_ref_on_create, reference, force, logmsg) @@ -708,17 +745,20 @@ def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference" """Rename self to a new path. :param new_path: - Either a simple name or a full path, e.g. ``new_name`` or ``features/new_name``. + Either a simple name or a full path, e.g. ``new_name`` or + ``features/new_name``. The prefix ``refs/`` is implied for references and will be set as needed. In case this is a symbolic ref, there is no implied prefix. :param force: - If True, the rename will succeed even if a head with the target name - already exists. It will be overwritten in that case. + If True, the rename will succeed even if a head with the target name already + exists. It will be overwritten in that case. - :return: self + :return: + self - :raise OSError: If a file at path but with different contents already exists. + :raise OSError: + If a file at path but with different contents already exists. """ new_path = self.to_full_path(new_path) if self.path == new_path: @@ -801,7 +841,8 @@ def iter_items( ) -> Iterator[T_References]: """Find all refs in the repository. - :param repo: is the Repo + :param repo: + The :class:`~git.repo.base.Repo`. :param common_path: Optional keyword argument to the path which is to be shared by all returned @@ -821,13 +862,13 @@ def iter_items( @classmethod def from_path(cls: Type[T_References], repo: "Repo", path: PathLike) -> T_References: - """ - Make a symbolic reference from a path. + """Make a symbolic reference from a path. - :param path: Full ``.git``-directory-relative path name to the Reference to - instantiate. + :param path: + Full ``.git``-directory-relative path name to the Reference to instantiate. - :note: Use :meth:`to_full_path` if you only have a partial path of a known + :note: + Use :meth:`to_full_path` if you only have a partial path of a known Reference type. :return: diff --git a/git/refs/tag.py b/git/refs/tag.py index a59a51337..7edd1d12a 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -43,7 +43,8 @@ class TagReference(Reference): def commit(self) -> "Commit": # type: ignore[override] # LazyMixin has unrelated commit method """:return: Commit object the tag ref points to - :raise ValueError: If the tag points to a tree or blob + :raise ValueError: + If the tag points to a tree or blob. """ obj = self.object while obj.type != "commit": @@ -63,8 +64,9 @@ def commit(self) -> "Commit": # type: ignore[override] # LazyMixin has unrelat @property def tag(self) -> Union["TagObject", None]: """ - :return: Tag object this tag ref points to or None in case - we are a lightweight tag""" + :return: + Tag object this tag ref points to or None in case we are a lightweight tag + """ obj = self.object if obj.type == "tag": return obj @@ -97,21 +99,23 @@ def create( :param logmsg: If not None, the message will be used in your tag object. This will also - create an additional tag object that allows to obtain that information, e.g.:: + create an additional tag object that allows to obtain that information, + e.g.:: tagref.tag.message :param message: - Synonym for the `logmsg` parameter. - Included for backwards compatibility. `logmsg` takes precedence if both are passed. + Synonym for the `logmsg` parameter. Included for backwards compatibility. + `logmsg` takes precedence if both are passed. :param force: If True, force creation of a tag even though that tag already exists. :param kwargs: - Additional keyword arguments to be passed to git-tag. + Additional keyword arguments to be passed to ``git tag``. - :return: A new TagReference. + :return: + A new :class:`TagReference`. """ if "ref" in kwargs and kwargs["ref"]: reference = kwargs["ref"] From 4f6736995cf9816773757b9a6808d6eaabd08fd4 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 18:04:12 -0500 Subject: [PATCH 26/61] Fix backslashes in Repo.__init__ docstring The example code block included a Windows-style path string written with doubled backslashes, but the docstring itself was not a raw string literal, so these collapsed into single backslashes. This makes the whole docstring an R-string, which is sufficient to solve that problem (since the backslashes are in a code block, Sphinx does not itself treat them as metacharacters). In addition, this changes the Windows-style path to be an R-string rather than using doubled backslashes. This is just to improve clarity (and remind readers they can use an R-string for Windows paths), and does not affect correctness. --- git/repo/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index bf7c2cc0d..c840e8a30 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -168,7 +168,7 @@ def __init__( search_parent_directories: bool = False, expand_vars: bool = True, ) -> None: - """Create a new Repo instance. + R"""Create a new Repo instance. :param path: The path to either the root git directory or the bare git repo:: @@ -177,7 +177,7 @@ def __init__( repo = Repo("/Users/mtrier/Development/git-python.git") repo = Repo("~/Development/git-python.git") repo = Repo("$REPOSITORIES/Development/git-python.git") - repo = Repo("C:\\Users\\mtrier\\Development\\git-python\\.git") + repo = Repo(R"C:\Users\mtrier\Development\git-python\.git") - In *Cygwin*, path may be a ``cygdrive/...`` prefixed path. - If it evaluates to false, :envvar:`GIT_DIR` is used, and if this also From 0c8ca1a9bdc7852e0e8b86923765c8cf812154e6 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 18:45:26 -0500 Subject: [PATCH 27/61] Fix Repo.iter_commits docstring about return type It had said it returned a list of Commit objects, but it returns an iterator of Commit objects. --- git/repo/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index c840e8a30..598921ca9 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -692,7 +692,7 @@ def iter_commits( paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any, ) -> Iterator[Commit]: - """A list of Commit objects representing the history of a given ref/commit. + """An iterator of Commit objects representing the history of a given ref/commit. :param rev: Revision specifier, see git-rev-parse for viable options. @@ -708,7 +708,7 @@ def iter_commits( :note: To receive only commits between two named revisions, use the ``"revA...revB"`` revision specifier. - :return: ``git.Commit[]`` + :return: Iterator of ``git.Commit`` """ if rev is None: rev = self.head.commit From b2b6f7c83cafd69ee683dfc2546a98c8699d0a6a Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 19:54:09 -0500 Subject: [PATCH 28/61] Revise docstrings within git.repo --- git/repo/__init__.py | 2 +- git/repo/base.py | 479 ++++++++++++++++++++++++++----------------- git/repo/fun.py | 120 ++++++----- 3 files changed, 366 insertions(+), 235 deletions(-) diff --git a/git/repo/__init__.py b/git/repo/__init__.py index a63d77878..50a8c6f86 100644 --- a/git/repo/__init__.py +++ b/git/repo/__init__.py @@ -1,6 +1,6 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ -"""Initialize the Repo package.""" +"""Initialize the repo package.""" from .base import Repo as Repo # noqa: F401 diff --git a/git/repo/base.py b/git/repo/base.py index 598921ca9..afbc1d75d 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -103,26 +103,34 @@ class BlameEntry(NamedTuple): class Repo: """Represents a git repository and allows you to query references, - gather commit information, generate diffs, create and clone repositories query + gather commit information, generate diffs, create and clone repositories, and query the log. The following attributes are worth using: - 'working_dir' is the working directory of the git command, which is the working tree - directory if available or the .git directory in case of bare repositories + * :attr:`working_dir` is the working directory of the git command, which is the + working tree directory if available or the ``.git`` directory in case of bare + repositories. - 'working_tree_dir' is the working tree directory, but will return None - if we are a bare repository. + * :attr:`working_tree_dir` is the working tree directory, but will return None if we + are a bare repository. - 'git_dir' is the .git repository directory, which is always set. + * :attr:`git_dir` is the ``.git`` repository directory, which is always set. """ DAEMON_EXPORT_FILE = "git-daemon-export-ok" - git = cast("Git", None) # Must exist, or __del__ will fail in case we raise on `__init__()`. + # Must exist, or __del__ will fail in case we raise on `__init__()`. + git = cast("Git", None) + working_dir: PathLike + """The working directory of the git command.""" + _working_tree_dir: Optional[PathLike] = None + git_dir: PathLike + """The ``.git`` repository directory.""" + _common_dir: PathLike = "" # Precompiled regex @@ -168,7 +176,7 @@ def __init__( search_parent_directories: bool = False, expand_vars: bool = True, ) -> None: - R"""Create a new Repo instance. + R"""Create a new :class:`Repo` instance. :param path: The path to either the root git directory or the bare git repo:: @@ -179,14 +187,14 @@ def __init__( repo = Repo("$REPOSITORIES/Development/git-python.git") repo = Repo(R"C:\Users\mtrier\Development\git-python\.git") - - In *Cygwin*, path may be a ``cygdrive/...`` prefixed path. - - If it evaluates to false, :envvar:`GIT_DIR` is used, and if this also - evals to false, the current-directory is used. + - In *Cygwin*, `path` may be a ``cygdrive/...`` prefixed path. + - If `path` is None or an empty string, :envvar:`GIT_DIR` is used. If that + environment variable is absent or empty, the current directory is used. :param odbt: - Object DataBase type - a type which is constructed by providing - the directory containing the database objects, i.e. .git/objects. It will - be used to access all object data + Object DataBase type - a type which is constructed by providing the + directory containing the database objects, i.e. ``.git/objects``. It will be + used to access all object data :param search_parent_directories: If True, all parent directories will be searched for a valid repo as well. @@ -195,18 +203,20 @@ def __init__( GitPython, which is considered a bug though. :raise InvalidGitRepositoryError: + :raise NoSuchPathError: - :return: git.Repo + :return: + git.Repo """ epath = path or os.getenv("GIT_DIR") if not epath: epath = os.getcwd() if Git.is_cygwin(): - # Given how the tests are written, this seems more likely to catch - # Cygwin git used from Windows than Windows git used from Cygwin. - # Therefore changing to Cygwin-style paths is the relevant operation. + # Given how the tests are written, this seems more likely to catch Cygwin + # git used from Windows than Windows git used from Cygwin. Therefore + # changing to Cygwin-style paths is the relevant operation. epath = cygpath(epath) epath = epath or path or os.getcwd() @@ -223,25 +233,26 @@ def __init__( if not os.path.exists(epath): raise NoSuchPathError(epath) - ## Walk up the path to find the `.git` dir. - # + # Walk up the path to find the `.git` dir. curpath = epath git_dir = None while curpath: # ABOUT osp.NORMPATH - # It's important to normalize the paths, as submodules will otherwise initialize their - # repo instances with paths that depend on path-portions that will not exist after being - # removed. It's just cleaner. + # It's important to normalize the paths, as submodules will otherwise + # initialize their repo instances with paths that depend on path-portions + # that will not exist after being removed. It's just cleaner. if is_git_dir(curpath): git_dir = curpath # from man git-config : core.worktree - # Set the path to the root of the working tree. If GIT_COMMON_DIR environment - # variable is set, core.worktree is ignored and not used for determining the - # root of working tree. This can be overridden by the GIT_WORK_TREE environment - # variable. The value can be an absolute path or relative to the path to the .git - # directory, which is either specified by GIT_DIR, or automatically discovered. - # If GIT_DIR is specified but none of GIT_WORK_TREE and core.worktree is specified, - # the current working directory is regarded as the top level of your working tree. + # Set the path to the root of the working tree. If GIT_COMMON_DIR + # environment variable is set, core.worktree is ignored and not used for + # determining the root of working tree. This can be overridden by the + # GIT_WORK_TREE environment variable. The value can be an absolute path + # or relative to the path to the .git directory, which is either + # specified by GIT_DIR, or automatically discovered. If GIT_DIR is + # specified but none of GIT_WORK_TREE and core.worktree is specified, + # the current working directory is regarded as the top level of your + # working tree. self._working_tree_dir = os.path.dirname(git_dir) if os.environ.get("GIT_COMMON_DIR") is None: gitconf = self._config_reader("repository", git_dir) @@ -359,7 +370,8 @@ def _set_description(self, descr: str) -> None: @property def working_tree_dir(self) -> Optional[PathLike]: """ - :return: The working tree directory of our git repository. + :return: + The working tree directory of our git repository. If this is a bare repository, None is returned. """ return self._working_tree_dir @@ -367,8 +379,9 @@ def working_tree_dir(self) -> Optional[PathLike]: @property def common_dir(self) -> PathLike: """ - :return: The git dir that holds everything except possibly HEAD, - FETCH_HEAD, ORIG_HEAD, COMMIT_EDITMSG, index, and logs/. + :return: + The git dir that holds everything except possibly HEAD, FETCH_HEAD, + ORIG_HEAD, COMMIT_EDITMSG, index, and logs/. """ return self._common_dir or self.git_dir @@ -379,17 +392,21 @@ def bare(self) -> bool: @property def heads(self) -> "IterableList[Head]": - """A list of ``Head`` objects representing the branch heads in this repo. + """A list of :class:`~git.refs.head.Head` objects representing the branch heads + in this repo. - :return: ``git.IterableList(Head, ...)`` + :return: + ``git.IterableList(Head, ...)`` """ return Head.list_items(self) @property def references(self) -> "IterableList[Reference]": - """A list of Reference objects representing tags, heads and remote references. + """A list of :class:`~git.refs.reference.Reference` objects representing tags, + heads and remote references. - :return: ``git.IterableList(Reference, ...)`` + :return: + ``git.IterableList(Reference, ...)`` """ return Reference.list_items(self) @@ -402,10 +419,11 @@ def references(self) -> "IterableList[Reference]": @property def index(self) -> "IndexFile": """ - :return: A :class:`~git.index.base.IndexFile` representing this repository's - index. + :return: + A :class:`~git.index.base.IndexFile` representing this repository's index. - :note: This property can be expensive, as the returned + :note: + This property can be expensive, as the returned :class:`~git.index.base.IndexFile` will be reinitialized. It is recommended to reuse the object. """ @@ -413,21 +431,27 @@ def index(self) -> "IndexFile": @property def head(self) -> "HEAD": - """:return: HEAD Object pointing to the current head reference""" + """ + :return: + :class:`~git.refs.head.HEAD` object pointing to the current head reference + """ return HEAD(self, "HEAD") @property def remotes(self) -> "IterableList[Remote]": - """A list of Remote objects allowing to access and manipulate remotes. + """A list of :class:`~git.remote.Remote` objects allowing to access and + manipulate remotes. - :return: ``git.IterableList(Remote, ...)`` + :return: + ``git.IterableList(Remote, ...)`` """ return Remote.list_items(self) def remote(self, name: str = "origin") -> "Remote": - """:return: Remote with the specified name + """:return: The remote with the specified name - :raise ValueError: If no remote with such a name exists + :raise ValueError + If no remote with such a name exists. """ r = Remote(self, name) if not r.exists(): @@ -439,15 +463,17 @@ def remote(self, name: str = "origin") -> "Remote": @property def submodules(self) -> "IterableList[Submodule]": """ - :return: git.IterableList(Submodule, ...) of direct submodules - available from the current head + :return: + git.IterableList(Submodule, ...) of direct submodules available from the + current head """ return Submodule.list_items(self) def submodule(self, name: str) -> "Submodule": - """:return: Submodule with the given name + """:return: The submodule with the given name - :raise ValueError: If no such submodule exists + :raise ValueError: + If no such submodule exists. """ try: return self.submodules[name] @@ -458,10 +484,12 @@ def submodule(self, name: str) -> "Submodule": def create_submodule(self, *args: Any, **kwargs: Any) -> Submodule: """Create a new submodule. - :note: See the documentation of Submodule.add for a description of the - applicable parameters. + :note: + For a description of the applicable parameters, see the documentation of + :meth:`Submodule.add `. - :return: The created submodules. + :return: + The created submodule. """ return Submodule.add(self, *args, **kwargs) @@ -477,7 +505,8 @@ def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]: """Update the submodules, keeping the repository consistent as it will take the previous state into consideration. - :note: For more information, please see the documentation of + :note: + For more information, please see the documentation of :meth:`RootModule.update `. """ return RootModule(self).update(*args, **kwargs) @@ -486,16 +515,22 @@ def submodule_update(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]: @property def tags(self) -> "IterableList[TagReference]": - """A list of ``Tag`` objects that are available in this repo. + """A list of :class:`~git.refs.tag.TagReference` objects that are available in + this repo. - :return: ``git.IterableList(TagReference, ...)`` + :return: + ``git.IterableList(TagReference, ...)`` """ return TagReference.list_items(self) def tag(self, path: PathLike) -> TagReference: - """:return: TagReference Object, reference pointing to a Commit or Tag + """ + :return: + :class:`~git.refs.tag.TagReference` object, reference pointing to a + :class:`~git.objects.commit.Commit` or tag - :param path: path to the tag reference, i.e. 0.1.5 or tags/0.1.5 + :param path: + Path to the tag reference, e.g. ``0.1.5`` or ``tags/0.1.5``. """ full_path = self._to_full_tag_path(path) return TagReference(self, full_path) @@ -522,14 +557,16 @@ def create_head( :note: For more documentation, please see the :meth:`Head.create ` method. - :return: Newly created :class:`~git.refs.head.Head` Reference + :return: + Newly created :class:`~git.refs.head.Head` Reference. """ return Head.create(self, path, commit, logmsg, force) def delete_head(self, *heads: "Union[str, Head]", **kwargs: Any) -> None: """Delete the given heads. - :param kwargs: Additional keyword arguments to be passed to git-branch + :param kwargs: + Additional keyword arguments to be passed to ``git branch``. """ return Head.delete(self, *heads, **kwargs) @@ -543,10 +580,12 @@ def create_tag( ) -> TagReference: """Create a new tag reference. - :note: For more documentation, please see the + :note: + For more documentation, please see the :meth:`TagReference.create ` method. - :return: :class:`~git.refs.tag.TagReference` object + :return: + :class:`~git.refs.tag.TagReference` object """ return TagReference.create(self, path, ref, message, force, **kwargs) @@ -560,7 +599,8 @@ def create_remote(self, name: str, url: str, **kwargs: Any) -> Remote: For more information, please see the documentation of the :meth:`Remote.create ` method. - :return: :class:`~git.remote.Remote` reference + :return: + :class:`~git.remote.Remote` reference """ return Remote.create(self, name, url, **kwargs) @@ -612,7 +652,8 @@ def config_reader( applicable levels will be used. Specify a level in case you know which file you wish to read to prevent reading multiple files. - :note: On Windows, system configuration cannot currently be read as the path is + :note: + On Windows, system configuration cannot currently be read as the path is unknown, instead the global path will be used. """ return self._config_reader(config_level=config_level) @@ -650,37 +691,43 @@ def config_writer(self, config_level: Lit_config_levels = "repository") -> GitCo return GitConfigParser(self._get_config_path(config_level), read_only=False, repo=self, merge_includes=False) def commit(self, rev: Union[str, Commit_ish, None] = None) -> Commit: - """The Commit object for the specified revision. + """The :class:`~git.objects.commit.Commit` object for the specified revision. + + :param rev: + Revision specifier, see ``git rev-parse`` for viable options. - :param rev: revision specifier, see git-rev-parse for viable options. - :return: :class:`git.Commit ` + :return: + :class:`~git.objects.commit.Commit` """ if rev is None: return self.head.commit return self.rev_parse(str(rev) + "^0") def iter_trees(self, *args: Any, **kwargs: Any) -> Iterator["Tree"]: - """:return: Iterator yielding Tree objects + """:return: Iterator yielding :class:`~git.objects.tree.Tree` objects - :note: Accepts all arguments known to the :meth:`iter_commits` method. + :note: + Accepts all arguments known to the :meth:`iter_commits` method. """ return (c.tree for c in self.iter_commits(*args, **kwargs)) def tree(self, rev: Union[Tree_ish, str, None] = None) -> "Tree": - """The Tree object for the given tree-ish revision. + """The :class:`~git.objects.tree.Tree` object for the given tree-ish revision. Examples:: repo.tree(repo.heads[0]) - :param rev: is a revision pointing to a Treeish (being a commit or tree) + :param rev: + A revision pointing to a Treeish (being a commit or tree). - :return: ``git.Tree`` + :return: + :class:`~git.objects.tree.Tree` :note: - If you need a non-root level tree, find it by iterating the root tree. Otherwise - it cannot know about its path relative to the repository root and subsequent - operations might have unexpected results. + If you need a non-root level tree, find it by iterating the root tree. + Otherwise it cannot know about its path relative to the repository root and + subsequent operations might have unexpected results. """ if rev is None: return self.head.commit.tree @@ -692,23 +739,27 @@ def iter_commits( paths: Union[PathLike, Sequence[PathLike]] = "", **kwargs: Any, ) -> Iterator[Commit]: - """An iterator of Commit objects representing the history of a given ref/commit. + """An iterator of :class:`~git.objects.commit.Commit` objects representing the + history of a given ref/commit. :param rev: - Revision specifier, see git-rev-parse for viable options. + Revision specifier, see ``git rev-parse`` for viable options. If None, the active branch will be used. :param paths: - An optional path or a list of paths; if set only commits that include the - path or paths will be returned + An optional path or a list of paths. If set, only commits that include the + path or paths will be returned. :param kwargs: - Arguments to be passed to git-rev-list - common ones are max_count and skip. + Arguments to be passed to ``git rev-list``. + Common ones are ``max_count`` and ``skip``. - :note: To receive only commits between two named revisions, use the + :note: + To receive only commits between two named revisions, use the ``"revA...revB"`` revision specifier. - :return: Iterator of ``git.Commit`` + :return: + Iterator of :class:`~git.objects.commit.Commit` objects """ if rev is None: rev = self.head.commit @@ -716,16 +767,25 @@ def iter_commits( return Commit.iter_items(self, rev, paths, **kwargs) def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]: - """Find the closest common ancestor for the given revision (Commits, Tags, References, etc.). + R"""Find the closest common ancestor for the given revision + (:class:`~git.objects.commit.Commit`\s, :class:`~git.refs.tag.Tag`\s, + :class:`git.refs.reference.Reference`\s, etc.). + + :param rev: + At least two revs to find the common ancestor for. + + :param kwargs: + Additional arguments to be passed to the ``repo.git.merge_base()`` command + which does all the work. - :param rev: At least two revs to find the common ancestor for. - :param kwargs: Additional arguments to be passed to the - ``repo.git.merge_base()`` command which does all the work. - :return: A list of :class:`~git.objects.commit.Commit` objects. If ``--all`` was + :return: + A list of :class:`~git.objects.commit.Commit` objects. If ``--all`` was not passed as a keyword argument, the list will have at max one :class:`~git.objects.commit.Commit`, or is empty if no common merge base exists. - :raises ValueError: If not at least two revs are provided. + + :raises ValueError: + If fewer than two revisions are provided. """ if len(rev) < 2: raise ValueError("Please specify at least two revs, got only %i" % len(rev)) @@ -752,9 +812,14 @@ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]: def is_ancestor(self, ancestor_rev: "Commit", rev: "Commit") -> bool: """Check if a commit is an ancestor of another. - :param ancestor_rev: Rev which should be an ancestor - :param rev: Rev to test against ancestor_rev - :return: ``True``, ancestor_rev is an ancestor to rev. + :param ancestor_rev: + Rev which should be an ancestor. + + :param rev: + Rev to test against `ancestor_rev`. + + :return: + True if `ancestor_rev` is an ancestor to `rev`. """ try: self.git.merge_base(ancestor_rev, rev, is_ancestor=True) @@ -809,7 +874,8 @@ def _set_daemon_export(self, value: object) -> None: def _get_alternates(self) -> List[str]: """The list of alternates for this repo from which objects can be retrieved. - :return: List of strings being pathnames of alternates + :return: + List of strings being pathnames of alternates """ if self.git_dir: alternates_path = osp.join(self.git_dir, "objects", "info", "alternates") @@ -821,17 +887,17 @@ def _get_alternates(self) -> List[str]: return [] def _set_alternates(self, alts: List[str]) -> None: - """Sets the alternates. + """Set the alternates. :param alts: - is the array of string paths representing the alternates at which - git should look for objects, i.e. /home/user/repo/.git/objects + The array of string paths representing the alternates at which git should + look for objects, i.e. ``/home/user/repo/.git/objects``. :raise NoSuchPathError: :note: - The method does not check for the existence of the paths in alts - as the caller is responsible. + The method does not check for the existence of the paths in `alts`, as the + caller is responsible. """ alternates_path = osp.join(self.common_dir, "objects", "info", "alternates") if not alts: @@ -857,9 +923,9 @@ def is_dirty( ) -> bool: """ :return: - ``True`` if the repository is considered dirty. By default it will react - like a git-status without untracked files, hence it is dirty if the - index or the working copy have changes. + True if the repository is considered dirty. By default it will react like a + git-status without untracked files, hence it is dirty if the index or the + working copy have changes. """ if self._bare: # Bare repositories with no associated working directory are @@ -894,11 +960,12 @@ def untracked_files(self) -> List[str]: :return: list(str,...) - Files currently untracked as they have not been staged yet. Paths - are relative to the current working directory of the git command. + Files currently untracked as they have not been staged yet. Paths are + relative to the current working directory of the git command. :note: Ignored files will not appear here, i.e. files mentioned in ``.gitignore``. + :note: This property is expensive, as no cache is involved. To process the result, please consider caching it yourself. @@ -926,23 +993,25 @@ def _get_untracked_files(self, *args: Any, **kwargs: Any) -> List[str]: return untracked_files def ignored(self, *paths: PathLike) -> List[str]: - """Checks if paths are ignored via .gitignore. + """Checks if paths are ignored via ``.gitignore``. This does so using the ``git check-ignore`` method. - :param paths: List of paths to check whether they are ignored or not + :param paths: + List of paths to check whether they are ignored or not. - :return: Subset of those paths which are ignored + :return: + Subset of those paths which are ignored """ try: proc: str = self.git.check_ignore(*paths) except GitCommandError as err: - # If return code is 1, this means none of the items in *paths - # are ignored by Git, so return an empty list. Raise the - # exception on all other return codes. if err.status == 1: + # If return code is 1, this means none of the items in *paths are + # ignored by Git, so return an empty list. return [] else: + # Raise the exception on all other return codes. raise return proc.replace("\\\\", "\\").replace('"', "").split("\n") @@ -951,8 +1020,11 @@ def ignored(self, *paths: PathLike) -> List[str]: def active_branch(self) -> Head: """The name of the currently active branch. - :raises TypeError: If HEAD is detached - :return: Head to the active branch + :raises TypeError: + If HEAD is detached. + + :return: + Head to the active branch """ # reveal_type(self.head.reference) # => Reference return self.head.reference @@ -963,13 +1035,15 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> Unlike :meth:`blame`, this does not return the actual file's contents, only a stream of :class:`BlameEntry` tuples. - :param rev: Revision specifier. If `None`, the blame will include all the latest - uncommitted changes. Otherwise, anything succesfully parsed by git-rev-parse - is a valid option. + :param rev: + Revision specifier. If None, the blame will include all the latest + uncommitted changes. Otherwise, anything successfully parsed by ``git + rev-parse`` is a valid option. - :return: Lazy iterator of :class:`BlameEntry` tuples, where the commit indicates - the commit to blame for the line, and range indicates a span of line numbers - in the resulting file. + :return: + Lazy iterator of :class:`BlameEntry` tuples, where the commit indicates the + commit to blame for the line, and range indicates a span of line numbers in + the resulting file. If you combine all line number ranges outputted by this command, you should get a continuous range spanning all line numbers in the file. @@ -981,7 +1055,8 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> stream = (line for line in data.split(b"\n") if line) while True: try: - line = next(stream) # When exhausted, causes a StopIteration, terminating this function. + # When exhausted, causes a StopIteration, terminating this function. + line = next(stream) except StopIteration: return split_line = line.split() @@ -990,8 +1065,8 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> num_lines = int(num_lines_b) orig_lineno = int(orig_lineno_b) if hexsha not in commits: - # Now read the next few lines and build up a dict of properties - # for this commit. + # Now read the next few lines and build up a dict of properties for this + # commit. props: Dict[bytes, bytes] = {} while True: try: @@ -999,8 +1074,8 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> except StopIteration: return if line == b"boundary": - # "boundary" indicates a root commit and occurs - # instead of the "previous" tag. + # "boundary" indicates a root commit and occurs instead of the + # "previous" tag. continue tag, value = line.split(b" ", 1) @@ -1026,11 +1101,12 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> ) commits[hexsha] = c else: - # Discard all lines until we find "filename" which is - # guaranteed to be the last line. + # Discard all lines until we find "filename" which is guaranteed to be + # the last line. while True: try: - line = next(stream) # Will fail if we reach the EOF unexpectedly. + # Will fail if we reach the EOF unexpectedly. + line = next(stream) except StopIteration: return tag, value = line.split(b" ", 1) @@ -1055,16 +1131,18 @@ def blame( ) -> List[List[Commit | List[str | bytes] | None]] | Iterator[BlameEntry] | None: """The blame information for the given file at the given revision. - :param rev: Revision specifier. If `None`, the blame will include all the latest - uncommitted changes. Otherwise, anything succesfully parsed by git-rev-parse - is a valid option. + :param rev: + Revision specifier. If None, the blame will include all the latest + uncommitted changes. Otherwise, anything successfully parsed by + git-rev-parse is a valid option. :return: list: [git.Commit, list: []] - A list of lists associating a Commit object with a list of lines that - changed within the given commit. The Commit objects will be given in order - of appearance. + A list of lists associating a :class:`~git.objects.commit.Commit` object + with a list of lines that changed within the given commit. The + :class:`~git.objects.commit.Commit` objects will be given in order of + appearance. """ if incremental: return self.blame_incremental(rev, file, **kwargs) @@ -1096,10 +1174,11 @@ class InfoTD(TypedDict, total=False): parts = [] is_binary = True else: - # As we don't have an idea when the binary data ends, as it could contain multiple newlines - # in the process. So we rely on being able to decode to tell us what it is. - # This can absolutely fail even on text files, but even if it does, we should be fine treating it - # as binary instead. + # As we don't have an idea when the binary data ends, as it could + # contain multiple newlines in the process. So we rely on being able to + # decode to tell us what it is. This can absolutely fail even on text + # files, but even if it does, we should be fine treating it as binary + # instead. parts = self.re_whitespace.split(line_str, 1) firstpart = parts[0] is_binary = False @@ -1180,10 +1259,12 @@ class InfoTD(TypedDict, total=False): line = line_str else: line = line_bytes - # NOTE: We are actually parsing lines out of binary data, which can lead to the - # binary being split up along the newline separator. We will append this to the - # blame we are currently looking at, even though it should be concatenated with - # the last line we have seen. + # NOTE: We are actually parsing lines out of binary + # data, which can lead to the binary being split up + # along the newline separator. We will append this + # to the blame we are currently looking at, even + # though it should be concatenated with the last + # line we have seen. blames[-1][1].append(line) info = {"id": sha} @@ -1205,28 +1286,30 @@ def init( """Initialize a git repository at the given path if specified. :param path: - The full path to the repo (traditionally ends with /.git) or None in - which case the repository will be created in the current working directory + The full path to the repo (traditionally ends with ``/.git``). + Or None, in which case the repository will be created in the current working + directory. :param mkdir: - If specified, will create the repository directory if it doesn't - already exist. Creates the directory with a mode=0755. + If specified, will create the repository directory if it doesn't already + exist. Creates the directory with a mode=0755. Only effective if a path is explicitly given. :param odbt: - Object DataBase type - a type which is constructed by providing - the directory containing the database objects, i.e. .git/objects. - It will be used to access all object data. + Object DataBase type - a type which is constructed by providing the + directory containing the database objects, i.e. ``.git/objects``. It will be + used to access all object data. :param expand_vars: - If specified, environment variables will not be escaped. This - can lead to information disclosure, allowing attackers to - access the contents of environment variables. + If specified, environment variables will not be escaped. This can lead to + information disclosure, allowing attackers to access the contents of + environment variables. :param kwargs: - Keyword arguments serving as additional options to the git-init command. + Keyword arguments serving as additional options to the ``git init`` command. - :return: ``git.Repo`` (the newly created repo) + :return: + :class:`Repo` (the newly created repo) """ if path: path = expand_path(path, expand_vars) @@ -1253,7 +1336,7 @@ def _clone( ) -> "Repo": odbt = kwargs.pop("odbt", odb_default_type) - # When pathlib.Path or other classbased path is passed + # When pathlib.Path or other class-based path is passed if not isinstance(path, str): path = str(path) @@ -1315,11 +1398,10 @@ def _clone( # Retain env values that were passed to _clone(). repo.git.update_environment(**git.environment()) - # Adjust remotes - there may be operating systems which use backslashes, - # These might be given as initial paths, but when handling the config file - # that contains the remote from which we were clones, git stops liking it - # as it will escape the backslashes. Hence we undo the escaping just to be - # sure. + # Adjust remotes - there may be operating systems which use backslashes, These + # might be given as initial paths, but when handling the config file that + # contains the remote from which we were clones, git stops liking it as it will + # escape the backslashes. Hence we undo the escaping just to be sure. if repo.remotes: with repo.remotes[0].config_writer as writer: writer.set_value("url", Git.polish_url(repo.remotes[0].url)) @@ -1337,21 +1419,38 @@ def clone( ) -> "Repo": """Create a clone from this repository. - :param path: The full path of the new repo (traditionally ends with - ``./.git``). - :param progress: See :meth:`git.remote.Remote.push`. - :param multi_options: A list of Clone options that can be provided multiple times. + :param path: + The full path of the new repo (traditionally ends with ``./.git``). + + :param progress: + See :meth:`Remote.push `. + + :param multi_options: + A list of ``git clone`` options that can be provided multiple times. + One option per list item which is passed exactly as specified to clone. - For example: ['--config core.filemode=false', '--config core.ignorecase', - '--recurse-submodule=repo1_path', '--recurse-submodule=repo2_path'] - :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. + For example:: + + [ + "--config core.filemode=false", + "--config core.ignorecase", + "--recurse-submodule=repo1_path", + "--recurse-submodule=repo2_path", + ] + + :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: * odbt = ObjectDatabase Type, allowing to determine the object database implementation used by the returned Repo instance. - * All remaining keyword arguments are given to the git-clone command. + * All remaining keyword arguments are given to the ``git clone`` command. - :return: :class:`Repo` (the newly cloned repo) + :return: + :class:`Repo` (the newly cloned repo) """ return self._clone( self.git, @@ -1379,29 +1478,38 @@ def clone_from( ) -> "Repo": """Create a clone from the given URL. - :param url: Valid git url, see http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS + :param url: + Valid git url, see: + http://www.kernel.org/pub/software/scm/git/docs/git-clone.html#URLS - :param to_path: Path to which the repository should be cloned to. + :param to_path: + Path to which the repository should be cloned to. - :param progress: See :meth:`git.remote.Remote.push`. + :param progress: + See :meth:`Remote.push `. - :param env: Optional dictionary containing the desired environment variables. + :param env: + Optional dictionary containing the desired environment variables. - Note: Provided variables will be used to update the execution - environment for `git`. If some variable is not specified in `env` - and is defined in `os.environ`, value from `os.environ` will be used. - If you want to unset some variable, consider providing empty string - as its value. + Note: Provided variables will be used to update the execution environment + for ``git``. If some variable is not specified in `env` and is defined in + :attr:`os.environ`, value from :attr:`os.environ` will be used. If you want + to unset some variable, consider providing empty string as its value. - :param multi_options: See :meth:`clone` method. + :param multi_options: + See the :meth:`clone` method. - :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: See the :meth:`clone` method. + :param kwargs: + See the :meth:`clone` method. - :return: :class:`Repo` instance pointing to the cloned directory. + :return: + :class:`Repo` instance pointing to the cloned directory. """ git = cls.GitCommandWrapperType(os.getcwd()) if env is not None: @@ -1459,10 +1567,14 @@ def archive( def has_separate_working_tree(self) -> bool: """ - :return: True if our git_dir is not at the root of our working_tree_dir, but a .git file with a - platform agnositic symbolic link. Our git_dir will be wherever the .git file points to. + :return: + True if our :attr:`git_dir` is not at the root of our + :attr:`working_tree_dir`, but a ``.git`` file with a platform-agnostic + symbolic link. Our :attr:`git_dir` will be wherever the ``.git`` file points + to. - :note: bare repositories will always return False here + :note: + Bare repositories will always return False here. """ if self.bare: return False @@ -1479,7 +1591,8 @@ def __repr__(self) -> str: def currently_rebasing_on(self) -> Commit | None: """ - :return: The commit which is currently being replayed while rebasing. + :return: + The commit which is currently being replayed while rebasing. None if we are not currently rebasing. """ diff --git a/git/repo/fun.py b/git/repo/fun.py index 63bcfdfc7..1a53a6825 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -57,13 +57,13 @@ def touch(filename: str) -> str: def is_git_dir(d: "PathLike") -> bool: - """This is taken from the git setup.c:is_git_directory - function. + """This is taken from the git setup.c:is_git_directory function. - @throws WorkTreeRepositoryUnsupported if it sees a worktree directory. It's quite hacky to do that here, - but at least clearly indicates that we don't support it. - There is the unlikely danger to throw if we see directories which just look like a worktree dir, - but are none.""" + :raises WorkTreeRepositoryUnsupported: + If it sees a worktree directory. It's quite hacky to do that here, but at least + clearly indicates that we don't support it. There is the unlikely danger to + throw if we see directories which just look like a worktree dir, but are none. + """ if osp.isdir(d): if (osp.isdir(osp.join(d, "objects")) or "GIT_OBJECT_DIRECTORY" in os.environ) and osp.isdir( osp.join(d, "refs") @@ -107,15 +107,15 @@ def find_submodule_git_dir(d: "PathLike") -> Optional["PathLike"]: with open(d) as fp: content = fp.read().rstrip() except IOError: - # it's probably not a file + # It's probably not a file. pass else: if content.startswith("gitdir: "): path = content[8:] if Git.is_cygwin(): - ## Cygwin creates submodules prefixed with `/cygdrive/...` suffixes. - # Cygwin git understands Cygwin paths much better than Windows ones + # Cygwin creates submodules prefixed with `/cygdrive/...` suffixes. + # Cygwin git understands Cygwin paths much better than Windows ones. # Also the Cygwin tests are assuming Cygwin paths. path = cygpath(path) if not osp.isabs(path): @@ -126,9 +126,14 @@ def find_submodule_git_dir(d: "PathLike") -> Optional["PathLike"]: def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]: - """:return: long hexadecimal sha1 from the given less-than-40 byte hexsha - or None if no candidate could be found. - :param hexsha: hexsha with less than 40 byte""" + """ + :return: + Long hexadecimal sha1 from the given less than 40 byte hexsha or None if no + candidate could be found. + + :param hexsha: + hexsha with less than 40 bytes. + """ try: return bin_to_hex(odb.partial_to_complete_sha_hex(hexsha)) except BadObject: @@ -140,25 +145,29 @@ def name_to_object( repo: "Repo", name: str, return_ref: bool = False ) -> Union[SymbolicReference, "Commit", "TagObject", "Blob", "Tree"]: """ - :return: object specified by the given name, hexshas ( short and long ) - as well as references are supported - :param return_ref: if name specifies a reference, we will return the reference - instead of the object. Otherwise it will raise BadObject or BadName + :return: + Object specified by the given name - hexshas (short and long) as well as + references are supported. + + :param return_ref: + If True, and name specifies a reference, we will return the reference + instead of the object. Otherwise it will raise `~gitdb.exc.BadObject` o + `~gitdb.exc.BadName`. """ hexsha: Union[None, str, bytes] = None - # is it a hexsha ? Try the most common ones, which is 7 to 40 + # Is it a hexsha? Try the most common ones, which is 7 to 40. if repo.re_hexsha_shortened.match(name): if len(name) != 40: - # find long sha for short sha + # Find long sha for short sha. hexsha = short_to_long(repo.odb, name) else: hexsha = name # END handle short shas # END find sha if it matches - # if we couldn't find an object for what seemed to be a short hexsha - # try to find it as reference anyway, it could be named 'aaa' for instance + # If we couldn't find an object for what seemed to be a short hexsha, try to find it + # as reference anyway, it could be named 'aaa' for instance. if hexsha is None: for base in ( "%s", @@ -179,12 +188,12 @@ def name_to_object( # END for each base # END handle hexsha - # didn't find any ref, this is an error + # Didn't find any ref, this is an error. if return_ref: raise BadObject("Couldn't find reference named %r" % name) # END handle return ref - # tried everything ? fail + # Tried everything ? fail. if hexsha is None: raise BadName(name) # END assert hexsha was found @@ -216,17 +225,27 @@ def to_commit(obj: Object) -> Union["Commit", "TagObject"]: def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: """ - :return: Object at the given revision, either Commit, Tag, Tree or Blob - :param rev: git-rev-parse compatible revision specification as string, please see - http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html - for details - :raise BadObject: if the given revision could not be found - :raise ValueError: If rev couldn't be parsed - :raise IndexError: If invalid reflog index is specified""" - - # colon search mode ? + :return: + `~git.objects.base.Object` at the given revision, either + `~git.objects.commit.Commit`, `~git.refs.tag.Tag`, `~git.objects.tree.Tree` or + `~git.objects.blob.Blob`. + + :param rev: + ``git rev-parse``-compatible revision specification as string. Please see + http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html for details. + + :raise BadObject: + If the given revision could not be found. + + :raise ValueError: + If `rev` couldn't be parsed. + + :raise IndexError: + If an invalid reflog index is specified. + """ + # Are we in colon search mode? if rev.startswith(":/"): - # colon search mode + # Colon search mode raise NotImplementedError("commit by message search ( regex )") # END handle search @@ -245,7 +264,7 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: token = rev[start] if obj is None: - # token is a rev name + # token is a rev name. if start == 0: ref = repo.head.ref else: @@ -265,29 +284,29 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: start += 1 - # try to parse {type} + # Try to parse {type}. if start < lr and rev[start] == "{": end = rev.find("}", start) if end == -1: raise ValueError("Missing closing brace to define type in %s" % rev) - output_type = rev[start + 1 : end] # exclude brace + output_type = rev[start + 1 : end] # Exclude brace. - # handle type + # Handle type. if output_type == "commit": - pass # default + pass # Default. elif output_type == "tree": try: obj = cast(Commit_ish, obj) obj = to_commit(obj).tree except (AttributeError, ValueError): - pass # error raised later + pass # Error raised later. # END exception handling elif output_type in ("", "blob"): obj = cast("TagObject", obj) if obj and obj.type == "tag": obj = deref_tag(obj) else: - # cannot do anything for non-tags + # Cannot do anything for non-tags. pass # END handle tag elif token == "@": @@ -295,11 +314,10 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: assert ref is not None, "Require Reference to access reflog" revlog_index = None try: - # transform reversed index into the format of our revlog + # Transform reversed index into the format of our revlog. revlog_index = -(int(output_type) + 1) except ValueError as e: - # TODO: Try to parse the other date options, using parse_date - # maybe + # TODO: Try to parse the other date options, using parse_date maybe. raise NotImplementedError("Support for additional @{...} modes not implemented") from e # END handle revlog index @@ -311,23 +329,24 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: obj = Object.new_from_sha(repo, hex_to_bin(entry.newhexsha)) - # make it pass the following checks + # Make it pass the following checks. output_type = "" else: raise ValueError("Invalid output type: %s ( in %s )" % (output_type, rev)) # END handle output type - # empty output types don't require any specific type, its just about dereferencing tags + # Empty output types don't require any specific type, its just about + # dereferencing tags. if output_type and obj and obj.type != output_type: raise ValueError("Could not accommodate requested object type %r, got %s" % (output_type, obj.type)) # END verify output type - start = end + 1 # skip brace + start = end + 1 # Skip brace. parsed_to = start continue # END parse type - # try to parse a number + # Try to parse a number. num = 0 if token != ":": found_digit = False @@ -341,15 +360,14 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: # END handle number # END number parse loop - # no explicit number given, 1 is the default - # It could be 0 though + # No explicit number given, 1 is the default. It could be 0 though. if not found_digit: num = 1 # END set default num # END number parsing only if non-blob mode parsed_to = start - # handle hierarchy walk + # Handle hierarchy walk. try: obj = cast(Commit_ish, obj) if token == "~": @@ -359,7 +377,7 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: # END for each history item to walk elif token == "^": obj = to_commit(obj) - # must be n'th parent + # Must be n'th parent. if num: obj = obj.parents[num - 1] elif token == ":": @@ -378,7 +396,7 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: # END exception handling # END parse loop - # still no obj ? Its probably a simple name + # Still no obj? It's probably a simple name. if obj is None: obj = cast(Commit_ish, name_to_object(repo, rev)) parsed_to = lr From c67b2e2a05be13c70a72147b9553348cdc5217a9 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 19:55:36 -0500 Subject: [PATCH 29/61] Adjust spacing in colon seach mode NotImplementedError This is a (very) minor improvement that uses the more common convention of not padding parentheses on the inside with spaces in prose, in an exception message. --- git/repo/fun.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/repo/fun.py b/git/repo/fun.py index 1a53a6825..b080cac5b 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -246,7 +246,7 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: # Are we in colon search mode? if rev.startswith(":/"): # Colon search mode - raise NotImplementedError("commit by message search ( regex )") + raise NotImplementedError("commit by message search (regex)") # END handle search obj: Union[Commit_ish, "Reference", None] = None From 5ee87441a5c936a55c69e310e4c8812b635cbdf0 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 20:10:33 -0500 Subject: [PATCH 30/61] Update git source link in Repo.merge_base comment And link to a specific tag (the most recent stable version tag) so the the hyperlink won't break in the future (as long as GitHub URLs keep working). --- git/repo/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index afbc1d75d..a0266f090 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -798,8 +798,8 @@ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]: if err.status == 128: raise # END handle invalid rev - # Status code 1 is returned if there is no merge-base - # (see https://github.com/git/git/blob/master/builtin/merge-base.c#L16) + # Status code 1 is returned if there is no merge-base. + # (See: https://github.com/git/git/blob/v2.44.0/builtin/merge-base.c#L19) return res # END exception handling From c8b6cf0cd96e6c0129aea563cb5092d6658f4424 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 20:32:14 -0500 Subject: [PATCH 31/61] Update comment about improving expand_path overloads --- git/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/util.py b/git/util.py index 72861ef84..4929affeb 100644 --- a/git/util.py +++ b/git/util.py @@ -500,7 +500,7 @@ def expand_path(p: None, expand_vars: bool = ...) -> None: @overload def expand_path(p: PathLike, expand_vars: bool = ...) -> str: - # improve these overloads when 3.5 dropped + # TODO: Support for Python 3.5 has been dropped, so these overloads can be improved. ... From bcc0c27482a46ff030cbddf75feac070f169263e Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 20:38:15 -0500 Subject: [PATCH 32/61] Fix recent inconsistency, using :raise:, not :raises: GitPython used :raise: consistently before, but I recently introduced some occurrences of :raises: by accident. This changes those to :raise:. --- git/index/fun.py | 2 +- git/repo/base.py | 2 +- git/repo/fun.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git/index/fun.py b/git/index/fun.py index 785a1c748..a60a3b809 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -88,7 +88,7 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None: :param args: Arguments passed to hook file. - :raises HookExecutionError: + :raise HookExecutionError: """ hp = hook_path(name, index.repo.git_dir) if not os.access(hp, os.X_OK): diff --git a/git/repo/base.py b/git/repo/base.py index a0266f090..1e58a24d0 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -784,7 +784,7 @@ def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]: :class:`~git.objects.commit.Commit`, or is empty if no common merge base exists. - :raises ValueError: + :raise ValueError: If fewer than two revisions are provided. """ if len(rev) < 2: diff --git a/git/repo/fun.py b/git/repo/fun.py index b080cac5b..fa2cebb67 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -59,7 +59,7 @@ def touch(filename: str) -> str: def is_git_dir(d: "PathLike") -> bool: """This is taken from the git setup.c:is_git_directory function. - :raises WorkTreeRepositoryUnsupported: + :raise WorkTreeRepositoryUnsupported: If it sees a worktree directory. It's quite hacky to do that here, but at least clearly indicates that we don't support it. There is the unlikely danger to throw if we see directories which just look like a worktree dir, but are none. From 0231b745b0005cb795a1a08493de018590daadef Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 21:09:57 -0500 Subject: [PATCH 33/61] Further revise docstrings in git.objects.submodule.base I had missed a lot of stuff there in 63c62ed. --- git/objects/submodule/base.py | 447 ++++++++++++++++++++++------------ 1 file changed, 289 insertions(+), 158 deletions(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 58b474fdd..70a021c54 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -133,8 +133,9 @@ def __init__( :param url: The URL to the remote repository which is the submodule. - :param branch_path: Complete relative path to ref to checkout when cloning the - remote repository. + :param branch_path: + Complete relative path to ref to checkout when cloning the remote + repository. """ super().__init__(repo, binsha, mode, path) self.size = 0 @@ -214,10 +215,12 @@ def _config_parser( cls, repo: "Repo", parent_commit: Union[Commit_ish, None], read_only: bool ) -> SubmoduleConfigParser: """ - :return: Config Parser constrained to our submodule in read or write mode + :return: + Config parser constrained to our submodule in read or write mode - :raise IOError: If the .gitmodules file cannot be found, either locally or in - the repository at the given parent commit. Otherwise the exception would be + :raise IOError: + If the ``.gitmodules`` file cannot be found, either locally or in the + repository at the given parent commit. Otherwise the exception would be delayed until the first access of the config parser. """ parent_matches_head = True @@ -225,7 +228,8 @@ def _config_parser( try: parent_matches_head = repo.head.commit == parent_commit except ValueError: - # We are most likely in an empty repository, so the HEAD doesn't point to a valid ref. + # We are most likely in an empty repository, so the HEAD doesn't point + # to a valid ref. pass # END handle parent_commit fp_module: Union[str, BytesIO] @@ -260,13 +264,17 @@ def _clear_cache(self) -> None: @classmethod def _sio_modules(cls, parent_commit: Commit_ish) -> BytesIO: - """:return: Configuration file as BytesIO - we only access it through the respective blob's data""" + """ + :return: + Configuration file as BytesIO - we only access it through the respective + blob's data + """ sio = BytesIO(parent_commit.tree[cls.k_modules_file].data_stream.read()) sio.name = cls.k_modules_file return sio def _config_parser_constrained(self, read_only: bool) -> SectionConstraint: - """:return: Config Parser constrained to our submodule in read or write mode""" + """:return: Config parser constrained to our submodule in read or write mode""" try: pc: Union["Commit_ish", None] = self.parent_commit except ValueError: @@ -296,14 +304,29 @@ def _clone_repo( **kwargs: Any, ) -> "Repo": """ - :return: Repo instance of newly cloned repository - :param repo: Our parent repository - :param url: URL to clone from - :param path: Repository - relative path to the submodule checkout location - :param name: Canonical name of the submodule - :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 given to git.clone + :return: + :class:`~git.repo.base.Repo` instance of newly cloned repository. + + :param repo: + Our parent repository. + + :param url: + URL to clone from. + + :param path: + Repository-relative path to the submodule checkout location. + + :param name: + Canonical name of the submodule. + + :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 given to ``git clone`` """ module_abspath = cls._module_abspath(repo, path, name) module_checkout_path = module_abspath @@ -328,8 +351,11 @@ def _clone_repo( @classmethod def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike: - """:return: a path guaranteed to be relative to the given parent - repository - :raise ValueError: if path is not contained in the parent repository's working tree""" + """:return: a path guaranteed to be relative to the given parent repository + + :raise ValueError: + If path is not contained in the parent repository's working tree + """ path = to_native_path_linux(path) if path.endswith("/"): path = path[:-1] @@ -352,16 +378,26 @@ def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike: @classmethod def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None: - """Write a .git file containing a(preferably) relative path to the actual git module repository. + """Write a .git file containing a (preferably) relative path to the actual git + module repository. + + It is an error if the `module_abspath` cannot be made into a relative path, + relative to the `working_tree_dir`. + + :note: + This will overwrite existing files! + + :note: + As we rewrite both the git file as well as the module configuration, we + might fail on the configuration and will not roll back changes done to the + git file. This should be a non-issue, but may easily be fixed if it becomes + one. - It is an error if the module_abspath cannot be made into a relative path, relative to the working_tree_dir + :param working_tree_dir: + Directory to write the ``.git`` file into. - :note: This will overwrite existing files! - :note: as we rewrite both the git file as well as the module configuration, we might fail on the configuration - and will not roll back changes done to the git file. This should be a non - issue, but may easily be fixed - if it becomes one. - :param working_tree_dir: Directory to write the .git file into - :param module_abspath: Absolute path to the bare repository + :param module_abspath: + Absolute path to the bare repository. """ git_file = osp.join(working_tree_dir, ".git") rela_path = osp.relpath(module_abspath, start=working_tree_dir) @@ -395,54 +431,77 @@ def add( allow_unsafe_protocols: bool = False, ) -> "Submodule": """Add a new submodule to the given repository. This will alter the index - as well as the .gitmodules file, but will not create a new commit. + as well as the ``.gitmodules`` file, but will not create a new commit. If the submodule already exists, no matter if the configuration differs from the one provided, the existing submodule will be returned. - :param repo: Repository instance which should receive the submodule. - :param name: The name/identifier for the submodule. - :param path: Repository-relative or absolute path at which the submodule - should be located. + :param repo: + Repository instance which should receive the submodule. + + :param name: + The name/identifier for the submodule. + + :param path: + Repository-relative or absolute path at which the submodule should be + located. It will be created as required during the repository initialization. - :param url: git-clone compatible URL, see git-clone reference for more information. - If None, the repository is assumed to exist, and the url of the first - remote is taken instead. This is useful if you want to make an existing - repository a submodule of another one. - :param branch: name of branch at which the submodule should (later) be checked out. - The given branch must exist in the remote repository, and will be checked - out locally as a tracking branch. - It will only be written into the configuration if it not None, which is - when the checked out branch will be the one the remote HEAD pointed to. - The result you get in these situation is somewhat fuzzy, and it is recommended - to specify at least 'master' here. - Examples are 'master' or 'feature/new'. - :param no_checkout: If True, and if the repository has to be cloned manually, - no checkout will be performed. - :param depth: Create a shallow clone with a history truncated to the - specified number of commits. - :param env: Optional dictionary containing the desired environment variables. + + :param url: + git-clone compatible URL, see git-clone reference for more information. + If None, the repository is assumed to exist, and the url of the first remote + is taken instead. This is useful if you want to make an existing repository + a submodule of another one. + + :param branch: + Name of branch at which the submodule should (later) be checked out. The + given branch must exist in the remote repository, and will be checked out + locally as a tracking branch. + It will only be written into the configuration if it not None, which is when + the checked out branch will be the one the remote HEAD pointed to. + The result you get in these situation is somewhat fuzzy, and it is + recommended to specify at least ``master`` here. + Examples are ``master`` or ``feature/new``. + + :param no_checkout: + If True, and if the repository has to be cloned manually, no checkout will + be performed. + + :param depth: + Create a shallow clone with a history truncated to the specified number of + commits. + + :param env: + Optional dictionary containing the desired environment variables. + Note: Provided variables will be used to update the execution environment for ``git``. If some variable is not specified in `env` and is defined in attr:`os.environ`, the value from attr:`os.environ` will be used. If you want to unset some variable, consider providing an empty string as its value. + :param clone_multi_options: A list of Clone options. Please see :meth:`Repo.clone ` for details. - :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 - :return: The newly created submodule instance. - :note: Works atomically, such that no change will be done if the repository - update fails for instance. - """ + :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``. + + :return: + The newly created :class:`Submodule` instance. + + :note: + Works atomically, such that no change will be done if, for example, the + repository update fails. + """ if repo.bare: raise InvalidGitRepositoryError("Cannot add submodules to bare repositories") # END handle bare repos path = cls._to_relative_path(repo, path) - # Ensure we never put backslashes into the URL, as some operating systems - # like it... + # Ensure we never put backslashes into the URL, as might happen on Windows. if url is not None: url = to_native_path_linux(url) # END ensure URL correctness @@ -569,24 +628,29 @@ def update( allow_unsafe_options: bool = False, allow_unsafe_protocols: bool = False, ) -> "Submodule": - """Update the repository of this submodule to point to the checkout - we point at with the binsha of this instance. + """Update the repository of this submodule to point to the checkout we point at + with the binsha of this instance. :param recursive: If True, we will operate recursively and update child modules as well. + :param init: If True, the module repository will be cloned into place if necessary. + :param to_latest_revision: If True, the submodule's sha will be ignored during checkout. Instead, the remote will be fetched, and the local tracking branch updated. This only works if we have a local tracking branch, which is the case if the remote repository had a master branch, or of the 'branch' option was specified for this submodule and the branch existed remotely. + :param progress: - UpdateProgress instance or None if no progress should be shown. + :class:`UpdateProgress` instance, or None if no progress should be shown. + :param dry_run: If True, the operation will only be simulated, but not performed. All performed operations are read-only. + :param force: If True, we may reset heads even if the repository in question is dirty. Additionally we will be allowed to set a tracking branch which is ahead of @@ -594,31 +658,40 @@ def update( This will essentially 'forget' commits. If False, local tracking branches that are in the future of their respective remote branches will simply not be moved. + :param keep_going: If True, we will ignore but log all errors, and keep going recursively. - Unless dry_run is set as well, keep_going could cause subsequent / inherited - errors you wouldn't see otherwise. - In conjunction with dry_run, it can be useful to anticipate all errors when - updating submodules. + Unless `dry_run` is set as well, `keep_going` could cause + subsequent/inherited errors you wouldn't see otherwise. + In conjunction with `dry_run`, it can be useful to anticipate all errors + when updating submodules. + :param env: Optional dictionary containing the desired environment variables. Note: Provided variables will be used to update the execution environment for ``git``. If some variable is not specified in `env` and is defined in attr:`os.environ`, value from attr:`os.environ` will be used. If you want to unset some variable, consider providing the empty string as its value. + :param clone_multi_options: - List of Clone options. + List of ``git clone`` options. Please see :meth:`Repo.clone ` for details. They only take effect with the `init` option. + :param allow_unsafe_protocols: - Allow unsafe protocols to be used, like ext. + Allow unsafe protocols to be used, like ``ext``. + :param allow_unsafe_options: - Allow unsafe options to be used, like --upload-pack. + Allow unsafe options to be used, like ``--upload-pack``. - :note: Does nothing in bare repositories. - :note: This method is definitely not atomic if `recursive` is True. + :note: + Does nothing in bare repositories. - :return: self + :note: + This method is definitely not atomic if `recursive` is True. + + :return: + self """ if self.repo.bare: return self @@ -742,9 +815,10 @@ def update( # END handle tracking branch # NOTE: Have to write the repo config file as well, otherwise the - # default implementation will be offended and not update the repository. - # Maybe this is a good way to ensure it doesn't get into our way, but - # we want to stay backwards compatible too... It's so redundant! + # default implementation will be offended and not update the + # repository. Maybe this is a good way to ensure it doesn't get into + # our way, but we want to stay backwards compatible too... It's so + # redundant! with self.repo.config_writer() as writer: writer.set_value(sm_section(self.name), "url", self.url) # END handle dry_run @@ -755,7 +829,8 @@ def update( binsha = self.binsha hexsha = self.hexsha if mrepo is not None: - # mrepo is only set if we are not in dry-run mode or if the module existed. + # mrepo is only set if we are not in dry-run mode or if the module + # existed. is_detached = mrepo.head.is_detached # END handle dry_run @@ -782,10 +857,12 @@ def update( # Update the working tree. # Handles dry_run. if mrepo is not None and mrepo.head.commit.binsha != binsha: - # We must ensure that our destination sha (the one to point to) is in the future of our current head. - # Otherwise, we will reset changes that might have been done on the submodule, but were not yet pushed. - # We also handle the case that history has been rewritten, leaving no merge-base. In that case - # we behave conservatively, protecting possible changes the user had done. + # We must ensure that our destination sha (the one to point to) is in + # the future of our current head. Otherwise, we will reset changes that + # might have been done on the submodule, but were not yet pushed. We + # also handle the case that history has been rewritten, leaving no + # merge-base. In that case we behave conservatively, protecting possible + # changes the user had done. may_reset = True if mrepo.head.commit.binsha != self.NULL_BIN_SHA: base_commit = mrepo.merge_base(mrepo.head.commit, hexsha) @@ -822,10 +899,10 @@ def update( if not dry_run and may_reset: if is_detached: - # NOTE: For now we force. The user is not supposed to change detached - # submodules anyway. Maybe at some point this becomes an option, to - # properly handle user modifications - see below for future options - # regarding rebase and merge. + # NOTE: For now we force. The user is not supposed to change + # detached submodules anyway. Maybe at some point this becomes + # an option, to properly handle user modifications - see below + # for future options regarding rebase and merge. mrepo.git.checkout(hexsha, force=force) else: mrepo.head.reset(hexsha, index=True, working_tree=True) @@ -871,19 +948,30 @@ def move(self, module_path: PathLike, configuration: bool = True, module: bool = the repository at our current path, changing the configuration, as well as adjusting our index entry accordingly. - :param module_path: The path to which to move our module in the parent - repository's working tree, given as repository - relative or absolute path. - Intermediate directories will be created accordingly. If the path already - exists, it must be empty. Trailing (back)slashes are removed automatically. - :param configuration: If True, the configuration will be adjusted to let - the submodule point to the given path. - :param module: If True, the repository managed by this submodule - will be moved as well. If False, we don't move the submodule's checkout, - which may leave the parent repository in an inconsistent state. - :return: self - :raise ValueError: If the module path existed and was not empty, or was a file. - :note: Currently the method is not atomic, and it could leave the repository - in an inconsistent state if a sub-step fails for some reason. + :param module_path: + The path to which to move our module in the parent repository's working + tree, given as repository - relative or absolute path. Intermediate + directories will be created accordingly. If the path already exists, it must + be empty. Trailing (back)slashes are removed automatically. + + :param configuration: + If True, the configuration will be adjusted to let the submodule point to + the given path. + + :param module: + If True, the repository managed by this submodule will be moved as well. If + False, we don't move the submodule's checkout, which may leave the parent + repository in an inconsistent state. + + :return: + self + + :raise ValueError: + If the module path existed and was not empty, or was a file. + + :note: + Currently the method is not atomic, and it could leave the repository in an + inconsistent state if a sub-step fails for some reason. """ if module + configuration < 1: raise ValueError("You must specify to move at least the module or the configuration of the submodule") @@ -940,8 +1028,8 @@ def move(self, module_path: PathLike, configuration: bool = True, module: bool = # END handle git file rewrite # END move physical module - # Rename the index entry - we have to manipulate the index directly as - # git-mv cannot be used on submodules... yeah. + # Rename the index entry - we have to manipulate the index directly as git-mv + # cannot be used on submodules... yeah. previous_sm_path = self.path try: if configuration: @@ -967,7 +1055,8 @@ def move(self, module_path: PathLike, configuration: bool = True, module: bool = raise # END handle undo rename - # Auto-rename submodule if its name was 'default', that is, the checkout directory. + # Auto-rename submodule if its name was 'default', that is, the checkout + # directory. if previous_sm_path == self.name: self.rename(module_checkout_path) @@ -982,30 +1071,46 @@ def remove( dry_run: bool = False, ) -> "Submodule": """Remove this submodule from the repository. This will remove our entry - from the .gitmodules file and the entry in the .git/config file. - - :param module: If True, the checked out module we point to will be deleted as - well. If that module is currently on a commit outside any branch in the - remote, or if it is ahead of its tracking branch, or if there are modified - or untracked files in its working tree, then the removal will fail. In case - the removal of the repository fails for these reasons, the submodule status - will not have been altered. + from the ``.gitmodules`` file and the entry in the ``.git/config`` file. + + :param module: + If True, the checked out module we point to will be deleted as well. If that + module is currently on a commit outside any branch in the remote, or if it + is ahead of its tracking branch, or if there are modified or untracked files + in its working tree, then the removal will fail. In case the removal of the + repository fails for these reasons, the submodule status will not have been + altered. If this submodule has child modules of its own, these will be deleted prior to touching the direct submodule. - :param force: Enforces the deletion of the module even though it contains - modifications. This basically enforces a brute-force file system based - deletion. - :param configuration: If True, the submodule is deleted from the configuration, - otherwise it isn't. Although this should be enabled most of the time, this - flag enables you to safely delete the repository of your submodule. - :param dry_run: If True, we will not actually do anything, but throw the errors - we would usually throw. - :return: self - :note: Doesn't work in bare repositories. - :note: Doesn't work atomically, as failure to remove any part of the submodule - will leave an inconsistent state. - :raise InvalidGitRepositoryError: Thrown if the repository cannot be deleted. - :raise OSError: If directories or files could not be removed. + + :param force: + Enforces the deletion of the module even though it contains modifications. + This basically enforces a brute-force file system based deletion. + + :param configuration: + If True, the submodule is deleted from the configuration, otherwise it + isn't. Although this should be enabled most of the time, this flag enables + you to safely delete the repository of your submodule. + + :param dry_run: + If True, we will not actually do anything, but throw the errors we would + usually throw. + + :return: + self + + :note: + Doesn't work in bare repositories. + + :note: + Doesn't work atomically, as failure to remove any part of the submodule will + leave an inconsistent state. + + :raise InvalidGitRepositoryError: + Thrown if the repository cannot be deleted. + + :raise OSError: + If directories or files could not be removed. """ if not (module or configuration): raise ValueError("Need to specify to delete at least the module, or the configuration") @@ -1019,8 +1124,9 @@ def remove( del csm if configuration and not dry_run and nc > 0: - # Ensure we don't leave the parent repository in a dirty state, and commit our changes. - # It's important for recursive, unforced, deletions to work as expected. + # Ensure we don't leave the parent repository in a dirty state, and commit + # our changes. It's important for recursive, unforced, deletions to work as + # expected. self.module().index.commit("Removed at least one of child-modules of '%s'" % self.name) # END handle recursion @@ -1031,8 +1137,9 @@ def remove( git_dir = mod.git_dir if force: # Take the fast lane and just delete everything in our module path. - # TODO: If we run into permission problems, we have a highly inconsistent - # state. Delete the .git folders last, start with the submodules first. + # TODO: If we run into permission problems, we have a highly + # inconsistent state. Delete the .git folders last, start with the + # submodules first. mp = self.abspath method: Union[None, Callable[[PathLike], None]] = None if osp.islink(mp): @@ -1129,18 +1236,24 @@ def remove( def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) -> "Submodule": """Set this instance to use the given commit whose tree is supposed to - contain the .gitmodules blob. + contain the ``.gitmodules`` blob. :param commit: Commit-ish reference pointing at the root_tree, or None to always point to the most recent commit + :param check: - If True, relatively expensive checks will be performed to verify - validity of the submodule. - :raise ValueError: If the commit's tree didn't contain the .gitmodules blob. + If True, relatively expensive checks will be performed to verify validity of + the submodule. + + :raise ValueError: + If the commit's tree didn't contain the ``.gitmodules`` blob. + :raise ValueError: If the parent commit didn't store this submodule under the current path. - :return: self + + :return: + self """ if commit is None: self._parent_commit = None @@ -1179,21 +1292,28 @@ def config_writer( self, index: Union["IndexFile", None] = None, write: bool = True ) -> SectionConstraint["SubmoduleConfigParser"]: """ - :return: A config writer instance allowing you to read and write the data - belonging to this submodule into the .gitmodules file. + :return: + A config writer instance allowing you to read and write the data belonging + to this submodule into the ``.gitmodules`` file. - :param index: If not None, an IndexFile instance which should be written. + :param index: + If not None, an IndexFile instance which should be written. Defaults to the index of the Submodule's parent repository. - :param write: If True, the index will be written each time a configuration - value changes. - :note: The parameters allow for a more efficient writing of the index, - as you can pass in a modified index on your own, prevent automatic writing, - and write yourself once the whole operation is complete. + :param write: + If True, the index will be written each time a configuration value changes. + + :note: + The parameters allow for a more efficient writing of the index, as you can + pass in a modified index on your own, prevent automatic writing, and write + yourself once the whole operation is complete. + + :raise ValueError: + If trying to get a writer on a parent_commit which does not match the + current head commit. - :raise ValueError: If trying to get a writer on a parent_commit which does not - match the current head commit. - :raise IOError: If the .gitmodules file/blob could not be read + :raise IOError: + If the ``.gitmodules`` file/blob could not be read. """ writer = self._config_parser_constrained(read_only=False) if index is not None: @@ -1208,22 +1328,24 @@ def rename(self, new_name: str) -> "Submodule": :note: This method takes care of renaming the submodule in various places, such as: - * $parent_git_dir / config - * $working_tree_dir / .gitmodules + * ``$parent_git_dir / config`` + * ``$working_tree_dir / .gitmodules`` * (git >= v1.8.0: move submodule repository to new name) - As .gitmodules will be changed, you would need to make a commit afterwards. The - changed .gitmodules file will already be added to the index. + As ``.gitmodules`` will be changed, you would need to make a commit afterwards. + The changed ``.gitmodules`` file will already be added to the index. - :return: This submodule instance + :return: + This :class:`Submodule` instance """ if self.name == new_name: return self # .git/config with self.repo.config_writer() as pw: - # As we ourselves didn't write anything about submodules into the parent .git/config, - # we will not require it to exist, and just ignore missing entries. + # As we ourselves didn't write anything about submodules into the parent + # .git/config, we will not require it to exist, and just ignore missing + # entries. if pw.has_section(sm_section(self.name)): pw.rename_section(sm_section(self.name), sm_section(new_name)) @@ -1260,8 +1382,9 @@ def module(self) -> "Repo": """ :return: Repo instance initialized from the repository at our submodule path - :raise InvalidGitRepositoryError: If a repository was not available. This could - also mean that it was not yet initialized. + :raise InvalidGitRepositoryError: + If a repository was not available. + This could also mean that it was not yet initialized. """ module_checkout_abspath = self.abspath try: @@ -1276,7 +1399,11 @@ def module(self) -> "Repo": # END handle exceptions def module_exists(self) -> bool: - """:return: True if our module exists and is a valid git repository. See module() method.""" + """ + :return: + True if our module exists and is a valid git repository. + See the :meth:`module` method. + """ try: self.module() return True @@ -1286,9 +1413,10 @@ def module_exists(self) -> bool: def exists(self) -> bool: """ - :return: True if the submodule exists, False otherwise. Please note that - a submodule may exist (in the .gitmodules file) even though its module - doesn't exist on disk. + :return: + True if the submodule exists, False otherwise. + Please note that a submodule may exist (in the ``.gitmodules`` file) even + though its module doesn't exist on disk. """ # Keep attributes for later, and restore them if we have no valid data. # This way we do not actually alter the state of the object. @@ -1299,7 +1427,8 @@ def exists(self) -> bool: loc[attr] = getattr(self, attr) # END if we have the attribute cache except (cp.NoSectionError, ValueError): - # On PY3, this can happen apparently... don't know why this doesn't happen on PY2. + # On PY3, this can happen apparently... don't know why this doesn't + # happen on PY2. pass # END for each attr self._clear_cache() @@ -1333,8 +1462,9 @@ def branch(self) -> "Head": @property def branch_path(self) -> PathLike: """ - :return: Complete relative path as string to the branch we would checkout - from the remote and track + :return: + Complete relative path as string to the branch we would checkout from the + remote and track """ return self._branch_path @@ -1344,8 +1474,8 @@ def branch_name(self) -> str: :return: The name of the branch, which is the shortest possible branch name """ - # Use an instance method, for this we create a temporary Head instance - # which uses a repository that is available at least (it makes no difference). + # Use an instance method, for this we create a temporary Head instance which + # uses a repository that is available at least (it makes no difference). return git.Head(self.repo, self._branch_path).name @property @@ -1420,7 +1550,8 @@ def iter_items( ) -> Iterator["Submodule"]: """ :return: - Iterator yielding Submodule instances available in the given repository + Iterator yielding :class:`Submodule` instances available in the given + repository """ try: pc = repo.commit(parent_commit) # Parent commit instance From 8344f442b4c002ec7f0a351c6a61d6d9ca084bce Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 21:19:11 -0500 Subject: [PATCH 34/61] Revise Repo.archive docstring I had missed this in b2b6f7c. --- git/repo/base.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index 1e58a24d0..71468bebc 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -1535,22 +1535,29 @@ def archive( ) -> Repo: """Archive the tree at the given revision. - :param ostream: file compatible stream object to which the archive will be written as bytes. + :param ostream: + File-compatible stream object to which the archive will be written as bytes. - :param treeish: is the treeish name/id, defaults to active branch. + :param treeish: + The treeish name/id, defaults to active branch. - :param prefix: is the optional prefix to prepend to each filename in the archive. + :param prefix: + The optional prefix to prepend to each filename in the archive. - :param kwargs: Additional arguments passed to git-archive: + :param kwargs: + Additional arguments passed to ``git archive``: - * Use the 'format' argument to define the kind of format. Use - specialized ostreams to write any format supported by python. - * You may specify the special **path** keyword, which may either be a repository-relative - path to a directory or file to place into the archive, or a list or tuple of multiple paths. + * Use the 'format' argument to define the kind of format. Use specialized + ostreams to write any format supported by Python. + * You may specify the special **path** keyword, which may either be a + repository-relative path to a directory or file to place into the archive, + or a list or tuple of multiple paths. - :raise GitCommandError: If something went wrong. + :raise GitCommandError: + If something went wrong. - :return: self + :return: + self """ if treeish is None: treeish = self.head.commit From 432ec72b759289cd8b8967847dd8bafcd6b78915 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 21:22:50 -0500 Subject: [PATCH 35/61] Fix another :raises: to :raise: Missed in bcc0c27. --- git/repo/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/repo/base.py b/git/repo/base.py index 71468bebc..19e951567 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -1020,7 +1020,7 @@ def ignored(self, *paths: PathLike) -> List[str]: def active_branch(self) -> Head: """The name of the currently active branch. - :raises TypeError: + :raise TypeError: If HEAD is detached. :return: From 5ca584444b1fe00d5c52341cd267588e64174d7f Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 21:26:45 -0500 Subject: [PATCH 36/61] Fully qualify non-builtin exceptions in :raise: This expands exceptions from git.exc and gitdb.exc in :raise: sections of docstrings, to include those qualifiers. This serves a few purposes. The main benefits are: - Immediately clarify which exceptions are not built-in ones, as not all readers are necessarily familiar with all built-in exceptions. - Distinguish exceptions defined in GitPython (in git.exc) from those defined in gitdb (in gitdb.exc). Although the gitdb exceptions GitPython uses are imported into git.exc and listed in __all__, they are still not introduced in GitPython, which is relevant for (a) preventing confusion, so developers don't think they accidentally imported a different same-named exception, (b) understanding why they do not have documentation in the generated GitPython docs. It also has these minor benefits: - May help sphinx-autodoc find them. In practice I think this is unlikely to be necessary. - Most other references, including references to classes, within docstrings in the git module, are now fully qualified, when not defined/introduced in the same module. So this is consistent with that. This consistency might be more than a minor benefit if there were a committment to keep most such refernces fully qualified, but this really should not be a goal: especially where Sphinx autodoc is guaranteed to be able to resolve them, shortening (in some cases, re-shortening) the references in the future may lead to better docstring readability. --- git/cmd.py | 4 ++-- git/db.py | 8 ++++---- git/index/base.py | 6 +++--- git/index/fun.py | 2 +- git/objects/submodule/base.py | 6 +++--- git/remote.py | 2 +- git/repo/base.py | 10 +++++----- git/repo/fun.py | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index cab089746..997f9d41e 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -699,7 +699,7 @@ def wait(self, stderr: Union[None, str, bytes] = b"") -> int: May deadlock if output or error pipes are used and not handled separately. - :raise GitCommandError: + :raise git.exc.GitCommandError: If the return status is not 0. """ if stderr is None: @@ -1091,7 +1091,7 @@ def execute( Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent output regardless of system language. - :raise GitCommandError: + :raise git.exc.GitCommandError: :note: If you add additional keyword arguments to the signature of this method, diff --git a/git/db.py b/git/db.py index dff59f47a..eb7f758da 100644 --- a/git/db.py +++ b/git/db.py @@ -56,13 +56,13 @@ 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 gitdb.exc.AmbiguousObjectName: - :raise BadObject: + :raise gitdb.exc.BadObject: :note: - Currently we only raise :class:`BadObject` as git does not communicate - ambiguous objects separately. + Currently we only raise :class:`~gitdb.exc.BadObject` as git does not + communicate ambiguous objects separately. """ try: hexsha, _typename, _size = self._git.get_object_header(partial_hexsha) diff --git a/git/index/base.py b/git/index/base.py index 31186b203..5c738cdf2 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -284,7 +284,7 @@ def merge_tree(self, rhs: Treeish, base: Union[None, Treeish] = None) -> "IndexF self (containing the merge and possibly unmerged entries in case of conflicts) - :raise GitCommandError: + :raise git.exc.GitCommandError: If there is a merge conflict. The error will be raised at the first conflicting path. If you want to have proper merge resolution to be done by yourself, you have to commit the changed index (or make a valid tree from @@ -624,7 +624,7 @@ def write_tree(self) -> Tree: :raise ValueError: If there are no entries in the cache. - :raise UnmergedEntriesError: + :raise git.exc.UnmergedEntriesError: """ # We obtain no lock as we just flush our contents to disk as tree. # If we are a new index, the entries access will load our data accordingly. @@ -1077,7 +1077,7 @@ def move( :raise ValueError: If only one item was given. - :raise GitCommandError: + :raise git.exc.GitCommandError: If git could not handle your request. """ args = [] diff --git a/git/index/fun.py b/git/index/fun.py index a60a3b809..1d4ca9b93 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -88,7 +88,7 @@ def run_commit_hook(name: str, index: "IndexFile", *args: str) -> None: :param args: Arguments passed to hook file. - :raise HookExecutionError: + :raise git.exc.HookExecutionError: """ hp = hook_path(name, index.repo.git_dir) if not os.access(hp, os.X_OK): diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 70a021c54..159403f24 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -1106,7 +1106,7 @@ def remove( Doesn't work atomically, as failure to remove any part of the submodule will leave an inconsistent state. - :raise InvalidGitRepositoryError: + :raise git.exc.InvalidGitRepositoryError: Thrown if the repository cannot be deleted. :raise OSError: @@ -1382,7 +1382,7 @@ def module(self) -> "Repo": """ :return: Repo instance initialized from the repository at our submodule path - :raise InvalidGitRepositoryError: + :raise git.exc.InvalidGitRepositoryError: If a repository was not available. This could also mean that it was not yet initialized. """ @@ -1454,7 +1454,7 @@ def branch(self) -> "Head": :return: The branch instance that we are to checkout - :raise InvalidGitRepositoryError: + :raise git.exc.InvalidGitRepositoryError: If our module is not yet checked out. """ return mkhead(self.module(), self._branch_path) diff --git a/git/remote.py b/git/remote.py index 5bee9edf5..249a6b7bf 100644 --- a/git/remote.py +++ b/git/remote.py @@ -801,7 +801,7 @@ def create(cls, repo: "Repo", name: str, url: str, allow_unsafe_protocols: bool :return: New :class:`Remote` instance - :raise GitCommandError: + :raise git.exc.GitCommandError: In case an origin with that name already exists. """ scmd = "add" diff --git a/git/repo/base.py b/git/repo/base.py index 19e951567..7b3e514c1 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -202,12 +202,12 @@ def __init__( Please note that this was the default behaviour in older versions of GitPython, which is considered a bug though. - :raise InvalidGitRepositoryError: + :raise git.exc.InvalidGitRepositoryError: - :raise NoSuchPathError: + :raise git.exc.NoSuchPathError: :return: - git.Repo + :class:`Repo` """ epath = path or os.getenv("GIT_DIR") @@ -893,7 +893,7 @@ def _set_alternates(self, alts: List[str]) -> None: The array of string paths representing the alternates at which git should look for objects, i.e. ``/home/user/repo/.git/objects``. - :raise NoSuchPathError: + :raise git.exc.NoSuchPathError: :note: The method does not check for the existence of the paths in `alts`, as the @@ -1553,7 +1553,7 @@ def archive( repository-relative path to a directory or file to place into the archive, or a list or tuple of multiple paths. - :raise GitCommandError: + :raise git.exc.GitCommandError: If something went wrong. :return: diff --git a/git/repo/fun.py b/git/repo/fun.py index fa2cebb67..4936f1773 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -59,7 +59,7 @@ def touch(filename: str) -> str: def is_git_dir(d: "PathLike") -> bool: """This is taken from the git setup.c:is_git_directory function. - :raise WorkTreeRepositoryUnsupported: + :raise git.exc.WorkTreeRepositoryUnsupported: If it sees a worktree directory. It's quite hacky to do that here, but at least clearly indicates that we don't support it. There is the unlikely danger to throw if we see directories which just look like a worktree dir, but are none. @@ -234,7 +234,7 @@ def rev_parse(repo: "Repo", rev: str) -> Union["Commit", "Tag", "Tree", "Blob"]: ``git rev-parse``-compatible revision specification as string. Please see http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html for details. - :raise BadObject: + :raise gitdb.exc.BadObject: If the given revision could not be found. :raise ValueError: From e6768ec9a06c295808ea1b4b7ed91e57fb4a2bf3 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Tue, 27 Feb 2024 21:50:38 -0500 Subject: [PATCH 37/61] Improve Git.execute docstring formatting re: max_chunk_size --- git/cmd.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 997f9d41e..4bd53ea0f 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -1067,9 +1067,9 @@ def execute( A dictionary of environment variables to be passed to :class:`subprocess.Popen`. :param max_chunk_size: - Maximum number of bytes in one chunk of data passed to the output_stream in - one invocation of write() method. If the given number is not positive then - the default value is used. + Maximum number of bytes in one chunk of data passed to the `output_stream` + in one invocation of its ``write()`` method. If the given number is not + positive then the default value is used. :param strip_newline_in_stdout: Whether to strip the trailing ``\n`` of the command stdout. From cd61eb4f7a5c9cd1c09b362335577d87a441e2c1 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 17:23:04 -0500 Subject: [PATCH 38/61] Further revise docstrings in second-level modules Some stuff unintentionally missed in 1cd73ba and some subsequent commits, plus a few further things intentionally omitted there, such as converting True, False, and None literals, to double backticked form (rather than bare or single-backticked). --- git/__init__.py | 9 +++-- git/cmd.py | 103 +++++++++++++++++++++++++----------------------- git/config.py | 46 +++++++++++---------- git/diff.py | 17 ++++---- git/exc.py | 4 +- git/remote.py | 37 ++++++++--------- git/util.py | 8 ++-- 7 files changed, 119 insertions(+), 105 deletions(-) diff --git a/git/__init__.py b/git/__init__.py index bc97a6eff..6fc6110f4 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -129,12 +129,13 @@ def refresh(path: Optional[PathLike] = None) -> None: :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. + :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. + 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. diff --git a/git/cmd.py b/git/cmd.py index 4bd53ea0f..d0c76eafe 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -112,25 +112,25 @@ def handle_process_output( This function returns once the finalizer returns. :param process: - :class:`subprocess.Popen` instance + :class:`subprocess.Popen` instance. :param stdout_handler: - f(stdout_line_string), or None + f(stdout_line_string), or ``None``. :param stderr_handler: - f(stderr_line_string), or None + f(stderr_line_string), or ``None``. :param finalizer: - f(proc) - wait for proc to finish + 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 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). + 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 + :class:`float` or ``None``, Default = ``None`` To specify a timeout in seconds for the git command, after which the process should be killed. @@ -236,7 +236,7 @@ def _safer_popen_windows( itself be searched automatically by the shell. This wrapper covers all those cases. :note: - This currently works by setting the ``NoDefaultCurrentDirectoryInExePath`` + This currently works by setting the :envvar:`NoDefaultCurrentDirectoryInExePath` environment variable during subprocess creation. It also takes care of passing Windows-specific process creation flags, but that is unrelated to path search. @@ -311,8 +311,8 @@ class Git: Debugging: - * Set the ``GIT_PYTHON_TRACE`` environment variable print each invocation of the - command to stdout. + * Set the :envvar:`GIT_PYTHON_TRACE` environment variable to print each invocation + of the command to stdout. * Set its value to ``full`` to see details about the returned values. """ @@ -351,7 +351,7 @@ def __setstate__(self, d: Dict[str, Any]) -> None: """Enables debugging of GitPython's git commands.""" USE_SHELL = False - """Deprecated. If set to True, a shell will be used when executing git commands. + """Deprecated. If set to ``True``, a shell will be used when executing git commands. Prior to GitPython 2.0.8, this had a narrow purpose in suppressing console windows in graphical Windows applications. In 2.0.8 and higher, it provides no benefit, as @@ -401,30 +401,32 @@ def refresh(cls, path: Union[None, PathLike] = None) -> bool: 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 - 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. + 1. Pass no `path` argument and do not set the + :envvar:`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 :envvar:`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. + in each command run. Setting :envvar:`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 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 - ``GIT_PYTHON_GIT_EXECUTABLE`` environment variable is not consulted. + :envvar:`GIT_PYTHON_GIT_EXECUTABLE` environment variable is not + consulted. :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 - variable. The class attribute is set no matter how refreshing is performed. + not be confused with the related :envvar:`GIT_PYTHON_GIT_EXECUTABLE` + environment variable. The class attribute is set no matter how refreshing is + performed. """ # Discern which path to refresh with. if path is not None: @@ -571,9 +573,9 @@ def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> str: def polish_url(cls, url: str, is_cygwin: Union[None, bool] = None) -> PathLike: """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 - undo the escaping just to be sure. + Windows might create config files containing paths with backslashes, but git + stops liking them as it will escape the backslashes. Hence we undo the escaping + just to be sure. """ if is_cygwin is None: is_cygwin = cls.is_cygwin() @@ -638,8 +640,8 @@ class AutoInterrupt: __slots__ = ("proc", "args", "status") - # If this is non-zero it will override any status code during - # _terminate, used to prevent race conditions in testing. + # If this is non-zero it will override any status code during _terminate, used + # to prevent race conditions in testing. _status_code_if_terminate: int = 0 def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None: @@ -844,7 +846,7 @@ def __init__(self, working_dir: Union[None, PathLike] = None): """Initialize this instance with: :param working_dir: - Git directory we should work in. If None, we always work in the current + Git directory we should work in. If ``None``, we always work in the current directory as returned by :func:`os.getcwd`. This is meant to be the working tree directory if available, or the ``.git`` directory in case of bare repositories. @@ -1022,8 +1024,8 @@ def execute( This feature only has any effect if `as_process` is False. :param stdout_as_string: - If False, the command's standard output will be bytes. Otherwise, it will be - decoded into a string using the default encoding (usually UTF-8). + If ``False``, the command's standard output will be bytes. Otherwise, it + will be decoded into a string using the default encoding (usually UTF-8). The latter can fail, if the output contains binary data. :param kill_after_timeout: @@ -1045,15 +1047,16 @@ def execute( is manually removed. :param with_stdout: - If True, default True, we open stdout on the created process. + 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)`). - If this is not `None`, it overrides :attr:`USE_SHELL`. + Whether to invoke commands through a shell + (see :class:`Popen(..., shell=True) `). + If this is not ``None``, it overrides :attr:`USE_SHELL`. Passing ``shell=True`` to this or any other GitPython function should be avoided, as it is unsafe under most circumstances. This is because it is @@ -1064,7 +1067,8 @@ def execute( issues. :param env: - A dictionary of environment variables to be passed to :class:`subprocess.Popen`. + A dictionary of environment variables to be passed to + :class:`subprocess.Popen`. :param max_chunk_size: Maximum number of bytes in one chunk of data passed to the `output_stream` @@ -1083,7 +1087,7 @@ def execute( * str(output) if extended_output = False (Default) * tuple(int(status), str(stdout), str(stderr)) if extended_output = True - If output_stream is True, the stdout value will be your output stream: + 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 @@ -1288,7 +1292,7 @@ def update_environment(self, **kwargs: Any) -> Dict[str, Union[str, None]]: self.update_environment(**old_env) :param kwargs: - Environment variables to use for git processes + Environment variables to use for git processes. :return: Dict that maps environment variables to their old values @@ -1367,8 +1371,8 @@ def __call__(self, **kwargs: Any) -> "Git": :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. Examples:: @@ -1409,16 +1413,16 @@ def _call_process( 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. + 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. :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``. + - 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. @@ -1431,9 +1435,9 @@ def _call_process( git rev-list max-count 10 --header master :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. + Same as :meth:`execute`. If no args are given, used :meth:`execute`'s + default (especially ``as_process = False``, ``stdout_as_string = True``) and + return :class:`str`. """ # Handle optional arguments prior to calling transform_kwargs. # Otherwise these'll end up in args, which is bad. @@ -1480,10 +1484,11 @@ def _parse_object_header(self, header_line: str) -> Tuple[str, str, int]: :param header_line: type_string size_as_int - :return: (hex_sha, type_string, size_as_int) + :return: + (hex_sha, type_string, size_as_int) :raise ValueError: - If the header contains indication for an error due to incorrect input sha + If the header contains indication for an error due to incorrect input sha. """ tokens = header_line.split() if len(tokens) != 3: diff --git a/git/config.py b/git/config.py index b5cff01cd..ff5c9d564 100644 --- a/git/config.py +++ b/git/config.py @@ -186,8 +186,8 @@ def config(self) -> T_ConfigParser: return self._config def release(self) -> None: - """Equivalent to GitConfigParser.release(), which is called on our underlying - parser instance.""" + """Equivalent to :meth:`GitConfigParser.release`, which is called on our + underlying parser instance.""" return self._config.release() def __enter__(self) -> "SectionConstraint[T_ConfigParser]": @@ -292,7 +292,7 @@ class GitConfigParser(cp.RawConfigParser, metaclass=MetaParserBuilder): t_lock = LockFile """The lock type determines the type of lock to use in new configuration readers. - They must be compatible to the LockFile interface. + They must be compatible to the :class:`~git.util.LockFile` interface. A suitable alternative would be the :class:`~git.util.BlockingLockFile`. """ @@ -326,14 +326,15 @@ def __init__( A file path or file object, or a sequence of possibly more than one of them. :param read_only: - If True, the ConfigParser may only read the data, but not change it. - If False, only a single file path or file object may be given. We will write - back the changes when they happen, or when the ConfigParser is released. - This will not happen if other configuration files have been included. + If ``True``, the ConfigParser may only read the data, but not change it. + If ``False``, only a single file path or file object may be given. We will + write back the changes when they happen, or when the ConfigParser is + released. This will not happen if other configuration files have been + included. :param merge_includes: - If True, we will read files mentioned in ``[include]`` sections and merge - their contents into ours. This makes it impossible to write back an + If ``True``, we will read files mentioned in ``[include]`` sections and + merge their contents into ours. This makes it impossible to write back an individual configuration file. Thus, if you want to modify a single configuration file, turn this off to leave the original dataset unaltered when reading it. @@ -347,7 +348,8 @@ def __init__( self._defaults: _OMD self._sections: _OMD # type: ignore # mypy/typeshed bug? - # Used in Python 3. Needs to stay in sync with sections for underlying implementation to work. + # Used in Python 3. Needs to stay in sync with sections for underlying + # implementation to work. if not hasattr(self, "_proxies"): self._proxies = self._dict() @@ -593,7 +595,7 @@ def read(self) -> None: # type: ignore[override] Nothing :raise IOError: - If a file cannot be handled + If a file cannot be handled. """ if self._is_initialized: return @@ -712,7 +714,8 @@ 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 + 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: @@ -778,8 +781,8 @@ def get_value( 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 @@ -809,8 +812,8 @@ 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 @@ -868,7 +871,7 @@ def set_value(self, section: str, option: str, value: Union[str, bytes, int, flo """Set the given option in section to the given value. This will create the section if required, and will not throw as opposed to the - default ConfigParser 'set' method. + default ConfigParser ``set`` method. :param section: Name of the section in which the option resides or should reside. @@ -893,9 +896,9 @@ def add_value(self, section: str, option: str, value: Union[str, bytes, int, flo """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'. + default ConfigParser ``set`` method. The value becomes the new value of the + option as returned by :meth:`get_value`, and appends to the list of values + returned by :meth:`get_values`. :param section: Name of the section in which the option resides or should reside. @@ -923,7 +926,8 @@ def rename_section(self, section: str, new_name: str) -> "GitConfigParser": * ``section`` doesn't exist. * A section with ``new_name`` does already exist. - :return: This instance + :return: + This instance """ if not self.has_section(section): raise ValueError("Source section '%s' doesn't exist" % section) diff --git a/git/diff.py b/git/diff.py index 7205136d0..3c760651b 100644 --- a/git/diff.py +++ b/git/diff.py @@ -118,7 +118,7 @@ def diff( :param other: This the item to compare us with. - * If None, we will be compared to the working tree. + * If ``None``, we will be compared to the working tree. * If :class:`~git.index.base.Treeish`, it will be compared against the respective tree. * If :class:`~Diffable.Index`, it will be compared against the index. @@ -131,7 +131,7 @@ def diff( include at least one of the given path or paths. :param create_patch: - If True, the returned :class:`Diff` contains a detailed patch that if + If ``True``, the returned :class:`Diff` contains a detailed patch that if applied makes the self to other. Patches are somewhat costly as blobs have to be read and diffed. @@ -139,7 +139,8 @@ def diff( Additional arguments passed to ``git diff``, such as ``R=True`` to swap both sides of the diff. - :return: git.DiffIndex + :return: + :class:`DiffIndex` :note: On a bare repository, `other` needs to be provided as @@ -262,7 +263,7 @@ class Diff: 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: + There are a few cases where ``None`` has to be expected as member variable value: New File:: @@ -468,7 +469,8 @@ def rename_to(self) -> Optional[str]: @property def renamed(self) -> bool: """ - :return: True if the blob of our diff has been renamed + :return: + ``True`` if the blob of our diff has been renamed :note: This property is deprecated. @@ -499,11 +501,12 @@ def _index_from_patch_format(cls, repo: "Repo", proc: Union["Popen", "Git.AutoIn """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 repo: + The repository we are operating on. :param proc: ``git diff`` process to read from - (supports :class:`Git.AutoInterrupt` wrapper). + (supports :class:`Git.AutoInterrupt ` wrapper). :return: :class:`DiffIndex` diff --git a/git/exc.py b/git/exc.py index 276d79e42..9f6462b39 100644 --- a/git/exc.py +++ b/git/exc.py @@ -136,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 :envvar:`PATH` or at the + path given by the :envvar:`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) diff --git a/git/remote.py b/git/remote.py index 249a6b7bf..265f1901f 100644 --- a/git/remote.py +++ b/git/remote.py @@ -125,8 +125,7 @@ def to_progress_instance( class PushInfo(IterableObj): - """ - Carries information about the result of a push operation of a single head:: + """Carries information about the result of a push operation of a single head:: info = remote.push()[0] info.flags # bitflags providing more information about the result @@ -222,8 +221,8 @@ def remote_ref(self) -> Union[RemoteReference, TagReference]: @classmethod def _from_line(cls, remote: "Remote", line: str) -> "PushInfo": - """Create a new PushInfo instance as parsed from line which is expected to be like - refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes.""" + """Create a new :class:`PushInfo` instance as parsed from line which is expected + to be like refs/heads/master:refs/heads/master 05d2687..1d0568e as bytes.""" control_character, from_to, summary = line.split("\t", 3) flags = 0 @@ -279,7 +278,7 @@ def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> NoReturn: # -> class PushInfoList(IterableList[PushInfo]): - """IterableList of :class:`PushInfo` objects.""" + """:class:`~git.util.IterableList` of :class:`PushInfo` objects.""" def __new__(cls) -> "PushInfoList": return cast(PushInfoList, IterableList.__new__(cls, "push_infos")) @@ -295,8 +294,7 @@ def raise_if_error(self) -> None: class FetchInfo(IterableObj): - """ - Carries information about the results of a fetch operation of a single head:: + """Carries information about the results of a fetch operation of a single head:: info = remote.fetch()[0] info.ref # Symbolic Reference or RemoteReference to the changed @@ -559,7 +557,7 @@ def __init__(self, repo: "Repo", name: str) -> None: The repository we are a remote of. :param name: - The name of the remote, e.g. 'origin'. + The name of the remote, e.g. ``origin``. """ self.repo = repo self.name = name @@ -611,7 +609,7 @@ def __hash__(self) -> int: def exists(self) -> bool: """ :return: - True if this is a valid, existing remote. + ``True`` if this is a valid, existing remote. Valid remotes have an entry in the repository's configuration. """ try: @@ -683,7 +681,7 @@ def add_url(self, url: str, allow_unsafe_protocols: bool = False, **kwargs: Any) 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. @@ -733,7 +731,7 @@ def urls(self) -> Iterator[str]: def refs(self) -> IterableList[RemoteReference]: """ :return: - :class:`~git.util.IterableList` of :class:`git.refs.remote.RemoteReference` + :class:`~git.util.IterableList` of :class:`~git.refs.remote.RemoteReference` objects. It is prefixed, allowing you to omit the remote path portion, e.g.:: @@ -748,7 +746,7 @@ def refs(self) -> IterableList[RemoteReference]: def stale_refs(self) -> IterableList[Reference]: """ :return: - :class:`~git.util.IterableList` of :class:`git.refs.remote.RemoteReference` + :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. @@ -1019,13 +1017,15 @@ def fetch( supplying a list rather than a string for 'refspec' will make use of this facility. - :param progress: See :meth:`push` method. + :param progress: + See :meth:`push` method. - :param verbose: Boolean for verbose output. + :param verbose: + Boolean for verbose output. :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. + should be killed. It is set to ``None`` by default. :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ``ext``. @@ -1101,7 +1101,7 @@ def pull( Additional arguments to be passed to ``git pull``. :return: - Please see :meth:`fetch` method + Please see :meth:`fetch` method. """ if refspec is None: # No argument refspec, then ensure the repo's config has a fetch refspec. @@ -1141,7 +1141,7 @@ 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='')``. See :meth:`RemoteProgress.update ` for a @@ -1163,7 +1163,8 @@ def push( :param allow_unsafe_options: 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 diff --git a/git/util.py b/git/util.py index 4929affeb..e7dd94a08 100644 --- a/git/util.py +++ b/git/util.py @@ -112,7 +112,7 @@ def _read_win_env_flag(name: str, default: bool) -> bool: :return: On Windows, the flag, or the `default` value if absent or ambiguous. - On all other operating systems, False. + On all other operating systems, ``False``. :note: This only accesses the environment on Windows. @@ -309,11 +309,11 @@ def assure_directory_exists(path: PathLike, is_file: bool = False) -> bool: """Make sure that the directory pointed to by path exists. :param is_file: - If True, `path` is assumed to be a file and handled correctly. + If ``True``, `path` is assumed to be a file and handled correctly. Otherwise it must be a directory. :return: - True if the directory was created, False if it already existed. + ``True`` if the directory was created, ``False`` if it already existed. """ if is_file: path = osp.dirname(path) @@ -734,7 +734,7 @@ def update( Current absolute count of items. :param max_count: - The maximum count of items we expect. It may be None in case there is no + 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: From fc1762b94ec9c8e22e0eb3b817604d15153f01b2 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 17:26:38 -0500 Subject: [PATCH 39/61] Undo a couple minor black-incompatible changes Two of the docstrings in git.remote need to have a newline before the conceptually "first" line in order for black to accept the indentation of the subsequent text, which appears intentional and works fine as reStructuredText. Otherwise black dedents the code block, which affects rendering and is also less readable. --- git/remote.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/git/remote.py b/git/remote.py index 265f1901f..3d5b8fdc4 100644 --- a/git/remote.py +++ b/git/remote.py @@ -125,7 +125,8 @@ def to_progress_instance( class PushInfo(IterableObj): - """Carries information about the result of a push operation of a single head:: + """ + Carries information about the result of a push operation of a single head:: info = remote.push()[0] info.flags # bitflags providing more information about the result @@ -294,7 +295,8 @@ def raise_if_error(self) -> None: class FetchInfo(IterableObj): - """Carries information about the results of a fetch operation of a single head:: + """ + Carries information about the results of a fetch operation of a single head:: info = remote.fetch()[0] info.ref # Symbolic Reference or RemoteReference to the changed From 1b25a13a36ec641ffd63a6f1a97c3bdf8f6d28de Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 17:30:46 -0500 Subject: [PATCH 40/61] Further revise docstrings within git.index --- git/index/base.py | 147 ++++++++++++++++++++++++---------------------- git/index/fun.py | 4 +- git/index/typ.py | 36 +++++++----- 3 files changed, 99 insertions(+), 88 deletions(-) diff --git a/git/index/base.py b/git/index/base.py index 5c738cdf2..249144d54 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -3,8 +3,8 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ -"""Module containing IndexFile, an Index implementation facilitating all kinds of index -manipulations such as querying and merging.""" +"""Module containing :class:`IndexFile`, an Index implementation facilitating all kinds +of index manipulations such as querying and merging.""" import contextlib import datetime @@ -125,9 +125,9 @@ class IndexFile(LazyMixin, git_diff.Diffable, Serializable): git command function calls wherever possible. This provides custom merging facilities allowing to merge without actually changing - your index or your working tree. This way you can perform own test-merges based on - the index only without having to deal with the working copy. This is useful in case - of partial working trees. + your index or your working tree. This way you can perform your own test merges based + on the index only without having to deal with the working copy. This is useful in + case of partial working trees. Entries: @@ -211,7 +211,7 @@ def _deserialize(self, stream: IO) -> "IndexFile": return self def _entries_sorted(self) -> List[IndexEntry]: - """:return: list of entries, in a sorted fashion, first by path, then by stage""" + """:return: List of entries, in a sorted fashion, first by path, then by stage""" return sorted(self.entries.values(), key=lambda e: (e.path, e.stage)) def _serialize(self, stream: IO, ignore_extension_data: bool = False) -> "IndexFile": @@ -232,17 +232,17 @@ def write( """Write the current state to our file path or to the given one. :param file_path: - If None, we will write to our stored file path from which we have been + If ``None``, we will write to our stored file path from which we have been initialized. Otherwise we write to the given file path. Please note that - this will change the file_path of this index to the one you gave. + this will change the `file_path` of this index to the one you gave. :param ignore_extension_data: - If True, the TREE type extension data read in the index will not be written - to disk. NOTE that no extension data is actually written. - Use this if you have altered the index and would like to use git-write-tree - afterwards to create a tree representing your written changes. - If this data is present in the written index, git-write-tree will instead - write the stored/cached tree. + If ``True``, the TREE type extension data read in the index will not be + written to disk. NOTE that no extension data is actually written. + Use this if you have altered the index and would like to use + ``git write-tree`` afterwards to create a tree representing your written + changes. If this data is present in the written index, ``git write-tree`` + will instead write the stored/cached tree. Alternatively, use :meth:`write_tree` to handle this case automatically. """ # Make sure we have our entries read before getting a write lock. @@ -479,8 +479,8 @@ def _write_path_to_stdin( stdout string :param read_from_stdout: - If True, proc.stdout will be read after the item was sent to stdin. In that - case, it will return None. + If ``True``, ``proc.stdout`` will be read after the item was sent to stdin. + In that case, it will return ``None``. :note: There is a bug in git-update-index that prevents it from sending reports @@ -516,12 +516,13 @@ def iter_blobs( ) -> Iterator[Tuple[StageType, Blob]]: """ :return: - Iterator yielding tuples of Blob objects and stages, tuple(stage, Blob). + Iterator yielding tuples of :class:`~git.objects.blob.Blob` objects and + stages, tuple(stage, Blob). :param predicate: - Function(t) returning True if tuple(stage, Blob) should be yielded by the - iterator. A default filter, the `~git.index.typ.BlobFilter`, allows you to - yield blobs only if they match a given list of paths. + Function(t) returning ``True`` if tuple(stage, Blob) should be yielded by + the iterator. A default filter, the `~git.index.typ.BlobFilter`, allows you + to yield blobs only if they match a given list of paths. """ for entry in self.entries.values(): blob = entry.to_blob(self.repo) @@ -534,8 +535,8 @@ def iter_blobs( def unmerged_blobs(self) -> Dict[PathLike, List[Tuple[StageType, Blob]]]: """ :return: - Dict(path : list( tuple( stage, Blob, ...))), being a dictionary associating - a path in the index with a list containing sorted stage/blob pairs. + Dict(path : list(tuple(stage, Blob, ...))), being a dictionary associating a + path in the index with a list containing sorted stage/blob pairs. :note: Blobs that have been removed in one side simply do not exist in the given @@ -562,8 +563,8 @@ def resolve_blobs(self, iter_blobs: Iterator[Blob]) -> "IndexFile": This will effectively remove the index entries of the respective path at all non-null stages and add the given blob as new stage null blob. - For each path there may only be one blob, otherwise a ValueError will be raised - claiming the path is already at stage 0. + For each path there may only be one blob, otherwise a :class:`ValueError` will + be raised claiming the path is already at stage 0. :raise ValueError: If one of the blobs already existed at stage 0. @@ -603,7 +604,8 @@ def update(self) -> "IndexFile": This is a possibly dangerous operations as it will discard your changes to :attr:`index.entries `. - :return: self + :return: + self """ self._delete_entries_cache() # Allows to lazily reread on demand. @@ -654,8 +656,9 @@ def _process_diff_args( def _to_relative_path(self, path: PathLike) -> PathLike: """ - :return: Version of path relative to our git directory or raise - :class:`ValueError` if it is not within our git directory. + :return: + Version of path relative to our git directory or raise :class:`ValueError` + if it is not within our git directory. :raise ValueError: """ @@ -693,7 +696,7 @@ def _store_path(self, filepath: PathLike, fprogress: Callable) -> BaseIndexEntry """Store file at filepath in the database and return the base index entry. :note: - This needs the git_working_dir decorator active! + This needs the :func:`~git.index.util.git_working_dir` decorator active! This must be ensured in the calling code. """ st = os.lstat(filepath) # Handles non-symlinks as well. @@ -810,24 +813,25 @@ def add( The handling now very much equals the way string paths are processed, except that the mode you have set will be kept. This allows you to create symlinks by settings the mode respectively and writing the target - of the symlink directly into the file. This equals a default - Linux symlink which is not dereferenced automatically, except that it - can be created on filesystems not supporting it as well. + of the symlink directly into the file. This equals a default Linux + symlink which is not dereferenced automatically, except that it can be + created on filesystems not supporting it as well. Please note that globs or directories are not allowed in - :class:~`git.objects.blob.Blob` objects. + :class:`~git.objects.blob.Blob` objects. They are added at stage 0. - :class:`~git.index.typ.BaseIndexEntry` or type - Handling equals the one of Blob objects, but the stage may be explicitly - set. Please note that Index Entries require binary sha's. + Handling equals the one of :class:~`git.objects.blob.Blob` objects, but + the stage may be explicitly set. Please note that Index Entries require + binary sha's. :param force: **CURRENTLY INEFFECTIVE** - If True, otherwise ignored or excluded files will be added anyway. - As opposed to the ``git add`` command, we enable this flag by default as the + If ``True``, otherwise ignored or excluded files will be added anyway. As + opposed to the ``git add`` command, we enable this flag by default as the API user usually wants the item to be added even though they might be excluded. @@ -835,8 +839,10 @@ def add( Function with signature ``f(path, done=False, item=item)`` called for each path to be added, one time once it is about to be added where ``done=False`` and once after it was added where ``done=True``. - ``item`` is set to the actual item we handle, either a Path or a + + ``item`` is set to the actual item we handle, either a path or a :class:`~git.index.typ.BaseIndexEntry`. + Please note that the processed path is not guaranteed to be present in the index already as the index is currently being processed. @@ -845,24 +851,24 @@ def add( for each passed entry which is the path to be actually recorded for the object created from :attr:`entry.path `. This allows you to write an index which is not identical to the layout of - the actual files on your hard-disk. If not None and `items` contain plain - paths, these paths will be converted to Entries beforehand and passed to the - path_rewriter. Please note that ``entry.path`` is relative to the git + the actual files on your hard-disk. If not ``None`` and `items` contain + plain paths, these paths will be converted to Entries beforehand and passed + to the path_rewriter. Please note that ``entry.path`` is relative to the git repository. :param write: - If True, the index will be written once it was altered. Otherwise the + If ``True``, the index will be written once it was altered. Otherwise the changes only exist in memory and are not available to git commands. :param write_extension_data: - If True, extension data will be written back to the index. This can lead to - issues in case it is containing the 'TREE' extension, which will cause the - ``git commit`` command to write an old tree, instead of a new one + If ``True``, extension data will be written back to the index. This can lead + to issues in case it is containing the 'TREE' extension, which will cause + the ``git commit`` command to write an old tree, instead of a new one representing the now changed index. This doesn't matter if you use :meth:`IndexFile.commit`, which ignores the - 'TREE' extension altogether. You should set it to True if you intend to use - :meth:`IndexFile.commit` exclusively while maintaining support for + 'TREE' extension altogether. You should set it to ``True`` if you intend to + use :meth:`IndexFile.commit` exclusively while maintaining support for third-party extensions. Besides that, you can usually safely ignore the built-in extensions when using GitPython on repositories that are not handled manually at all. @@ -875,7 +881,7 @@ def add( just actually added. :raise OSError: - If a supplied Path did not exist. Please note that + If a supplied path did not exist. Please note that :class:`~git.index.typ.BaseIndexEntry` objects that do not have a null sha will be added even if their paths do not exist. """ @@ -999,7 +1005,7 @@ def remove( it. If absolute paths are given, they will be converted to a path relative to the git repository directory containing the working tree - The path string may include globs, such as \*.c. + The path string may include globs, such as ``*.c``. - :class:~`git.objects.blob.Blob` object @@ -1010,9 +1016,9 @@ def remove( The only relevant information here is the path. The stage is ignored. :param working_tree: - If True, the entry will also be removed from the working tree, physically - removing the respective file. This may fail if there are uncommitted changes - in it. + If ``True``, the entry will also be removed from the working tree, + physically removing the respective file. This may fail if there are + uncommitted changes in it. :param kwargs: Additional keyword arguments to be passed to ``git rm``, such as ``r`` to @@ -1061,7 +1067,7 @@ def move( for reference. :param skip_errors: - If True, errors such as ones resulting from missing source files will be + If ``True``, errors such as ones resulting from missing source files will be skipped. :param kwargs: @@ -1214,21 +1220,21 @@ def checkout( case you have altered the entries dictionary directly. :param paths: - If None, all paths in the index will be checked out. Otherwise an iterable - of relative or absolute paths or a single path pointing to files or - directories in the index is expected. + If ``None``, all paths in the index will be checked out. + Otherwise an iterable of relative or absolute paths or a single path + pointing to files or directories in the index is expected. :param force: - If True, existing files will be overwritten even if they contain local + If ``True``, existing files will be overwritten even if they contain local modifications. - If False, these will trigger a :class:`~git.exc.CheckoutError`. + If ``False``, these will trigger a :class:`~git.exc.CheckoutError`. :param fprogress: See :meth:`IndexFile.add` for signature and explanation. - The provided progress information will contain None as path and item if no - explicit paths are given. Otherwise progress information will be send prior - and after a file has been checked out. + The provided progress information will contain ``None`` as path and item if + no explicit paths are given. Otherwise progress information will be send + prior and after a file has been checked out. :param kwargs: Additional arguments to be passed to ``git checkout-index``. @@ -1238,10 +1244,10 @@ def checkout( guaranteed to match the version stored in the index. :raise git.exc.CheckoutError: - If at least one file failed to be checked out. This is a summary, hence it - will checkout as many files as it can anyway. - If one of files or directories do not exist in the index (as opposed to the - original git command, which ignores them). + * If at least one file failed to be checked out. This is a summary, hence it + will checkout as many files as it can anyway. + * If one of files or directories do not exist in the index (as opposed to + the original git command, which ignores them). :raise git.exc.GitCommandError: If error lines could not be parsed - this truly is an exceptional state. @@ -1394,7 +1400,7 @@ def reset( **kwargs: Any, ) -> "IndexFile": """Reset the index to reflect the tree at the given commit. This will not adjust - our ``HEAD`` reference by default, as opposed to + our HEAD reference by default, as opposed to :meth:`HEAD.reset `. :param commit: @@ -1406,14 +1412,14 @@ def reset( overwrite the default index. :param working_tree: - If True, the files in the working tree will reflect the changed index. - If False, the working tree will not be touched. + If ``True``, the files in the working tree will reflect the changed index. + If ``False``, the working tree will not be touched. Please note that changes to the working copy will be discarded without warning! :param head: - If True, the head will be set to the given commit. This is False by default, - but if True, this method behaves like + If ``True``, the head will be set to the given commit. This is ``False`` by + default, but if ``True``, this method behaves like :meth:`HEAD.reset `. :param paths: @@ -1433,7 +1439,8 @@ def reset( If you want ``git reset``-like behaviour, use :meth:`HEAD.reset ` instead. - :return: self + :return: + self """ # What we actually want to do is to merge the tree into our existing index, # which is what git-read-tree does. diff --git a/git/index/fun.py b/git/index/fun.py index 1d4ca9b93..58335739e 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -222,8 +222,8 @@ def read_header(stream: IO[bytes]) -> Tuple[int, int]: def entry_key(*entry: Union[BaseIndexEntry, PathLike, int]) -> Tuple[PathLike, int]: """ :return: - Key suitable to be used for the :attr:`index.entries - ` dictionary. + Key suitable to be used for the + :attr:`index.entries ` dictionary. :param entry: One instance of type BaseIndexEntry or the path and the stage. diff --git a/git/index/typ.py b/git/index/typ.py index dce67626f..a7d2ad47a 100644 --- a/git/index/typ.py +++ b/git/index/typ.py @@ -36,9 +36,9 @@ class BlobFilter: - """ - Predicate to be used by iter_blobs allowing to filter only return blobs which - match the given list of directories or files. + """Predicate to be used by + :meth:`IndexFile.iter_blobs ` allowing to + filter only return blobs which match the given list of directories or files. The given paths are given relative to the repository. """ @@ -48,8 +48,8 @@ class BlobFilter: def __init__(self, paths: Sequence[PathLike]) -> None: """ :param paths: - Tuple or list of paths which are either pointing to directories or - to files relative to the current repository + Tuple or list of paths which are either pointing to directories or to files + relative to the current repository. """ self.paths = paths @@ -70,7 +70,7 @@ def __call__(self, stage_blob: Tuple[StageType, Blob]) -> bool: class BaseIndexEntryHelper(NamedTuple): - """Typed named tuple to provide named attribute access for BaseIndexEntry. + """Typed named tuple to provide named attribute access for :class:`BaseIndexEntry`. This is needed to allow overriding ``__new__`` in child class to preserve backwards compatibility. @@ -90,12 +90,12 @@ class BaseIndexEntryHelper(NamedTuple): class BaseIndexEntry(BaseIndexEntryHelper): - """Small brother of an index entry which can be created to describe changes + R"""Small brother of an index entry which can be created to describe changes done to the index in which case plenty of additional information is not required. - As the first 4 data members match exactly to the IndexEntry type, methods - expecting a BaseIndexEntry can also handle full IndexEntries even if they - use numeric indices for performance reasons. + As the first 4 data members match exactly to the :class:`IndexEntry` type, methods + expecting a :class:`BaseIndexEntry` can also handle full :class:`IndexEntry`\s even + if they use numeric indices for performance reasons. """ def __new__( @@ -127,9 +127,11 @@ def stage(self) -> int: * 0 = default stage * 1 = stage before a merge or common ancestor entry in case of a 3 way merge * 2 = stage of entries from the 'left' side of the merge - * 3 = stage of entries from the right side of the merge + * 3 = stage of entries from the 'right' side of the merge - :note: For more information, see http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html + :note: + For more information, see: + http://www.kernel.org/pub/software/scm/git/docs/git-read-tree.html """ return (self.flags & CE_STAGEMASK) >> CE_STAGESHIFT @@ -144,7 +146,8 @@ def to_blob(self, repo: "Repo") -> Blob: class IndexEntry(BaseIndexEntry): - """Allows convenient access to IndexEntry data without completely unpacking it. + """Allows convenient access to index entry data as defined in + :class:`BaseIndexEntry` without completely unpacking it. Attributes usually accessed often are cached in the tuple whereas others are unpacked on demand. @@ -163,17 +166,18 @@ def ctime(self) -> Tuple[int, int]: @property def mtime(self) -> Tuple[int, int]: - """See ctime property, but returns modification time.""" + """See :attr:`ctime` property, but returns modification time.""" return cast(Tuple[int, int], unpack(">LL", self.mtime_bytes)) @classmethod def from_base(cls, base: "BaseIndexEntry") -> "IndexEntry": """ :return: - Minimal entry as created from the given BaseIndexEntry instance. + Minimal entry as created from the given :class:`BaseIndexEntry` instance. Missing values will be set to null-like values. - :param base: Instance of type :class:`BaseIndexEntry` + :param base: + Instance of type :class:`BaseIndexEntry`. """ time = pack(">LL", 0, 0) return IndexEntry((base.mode, base.binsha, base.flags, base.path, time, time, 0, 0, 0, 0, 0)) From 08a80aa1d91af4745dd369db7ad48a23df7145a9 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 17:49:11 -0500 Subject: [PATCH 41/61] Further revise docstrings within git.objects.submodule --- git/objects/submodule/base.py | 128 +++++++++++++++++++--------------- git/objects/submodule/root.py | 28 ++++---- git/objects/submodule/util.py | 2 +- 3 files changed, 86 insertions(+), 72 deletions(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 159403f24..dc1deee36 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -266,8 +266,8 @@ def _clear_cache(self) -> None: def _sio_modules(cls, parent_commit: Commit_ish) -> BytesIO: """ :return: - Configuration file as BytesIO - we only access it through the respective - blob's data + Configuration file as :class:`~io.BytesIO` - we only access it through the + respective blob's data """ sio = BytesIO(parent_commit.tree[cls.k_modules_file].data_stream.read()) sio.name = cls.k_modules_file @@ -354,7 +354,7 @@ def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike: """:return: a path guaranteed to be relative to the given parent repository :raise ValueError: - If path is not contained in the parent repository's working tree + If path is not contained in the parent repository's working tree. """ path = to_native_path_linux(path) if path.endswith("/"): @@ -378,8 +378,8 @@ def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike: @classmethod def _write_git_file_and_module_config(cls, working_tree_dir: PathLike, module_abspath: PathLike) -> None: - """Write a .git file containing a (preferably) relative path to the actual git - module repository. + """Write a ``.git`` file containing a (preferably) relative path to the actual + git module repository. It is an error if the `module_abspath` cannot be made into a relative path, relative to the `working_tree_dir`. @@ -430,10 +430,10 @@ def add( allow_unsafe_options: bool = False, allow_unsafe_protocols: bool = False, ) -> "Submodule": - """Add a new submodule to the given repository. This will alter the index - as well as the ``.gitmodules`` file, but will not create a new commit. - If the submodule already exists, no matter if the configuration differs - from the one provided, the existing submodule will be returned. + """Add a new submodule to the given repository. This will alter the index as + well as the ``.gitmodules`` file, but will not create a new commit. If the + submodule already exists, no matter if the configuration differs from the one + provided, the existing submodule will be returned. :param repo: Repository instance which should receive the submodule. @@ -448,23 +448,23 @@ def add( :param url: git-clone compatible URL, see git-clone reference for more information. - If None, the repository is assumed to exist, and the url of the first remote - is taken instead. This is useful if you want to make an existing repository - a submodule of another one. + If ``None```, the repository is assumed to exist, and the url of the first + remote is taken instead. This is useful if you want to make an existing + repository a submodule of another one. :param branch: Name of branch at which the submodule should (later) be checked out. The given branch must exist in the remote repository, and will be checked out locally as a tracking branch. - It will only be written into the configuration if it not None, which is when - the checked out branch will be the one the remote HEAD pointed to. + It will only be written into the configuration if it not ``None``, which is + when the checked out branch will be the one the remote HEAD pointed to. The result you get in these situation is somewhat fuzzy, and it is recommended to specify at least ``master`` here. Examples are ``master`` or ``feature/new``. :param no_checkout: - If True, and if the repository has to be cloned manually, no checkout will - be performed. + If ``True``, and if the repository has to be cloned manually, no checkout + will be performed. :param depth: Create a shallow clone with a history truncated to the specified number of @@ -479,7 +479,8 @@ def add( want to unset some variable, consider providing an empty string as its value. - :param clone_multi_options: A list of Clone options. Please see + :param clone_multi_options: + A list of clone options. Please see :meth:`Repo.clone ` for details. :param allow_unsafe_protocols: @@ -632,44 +633,49 @@ def update( with the binsha of this instance. :param recursive: - If True, we will operate recursively and update child modules as well. + If ``True``, we will operate recursively and update child modules as well. :param init: - If True, the module repository will be cloned into place if necessary. + If ``True``, the module repository will be cloned into place if necessary. :param to_latest_revision: - If True, the submodule's sha will be ignored during checkout. Instead, the - remote will be fetched, and the local tracking branch updated. This only + If ``True``, the submodule's sha will be ignored during checkout. Instead, + the remote will be fetched, and the local tracking branch updated. This only works if we have a local tracking branch, which is the case if the remote - repository had a master branch, or of the 'branch' option was specified for - this submodule and the branch existed remotely. + repository had a master branch, or of the ``branch`` option was specified + for this submodule and the branch existed remotely. :param progress: - :class:`UpdateProgress` instance, or None if no progress should be shown. + :class:`UpdateProgress` instance, or ``None`` if no progress should be + shown. :param dry_run: - If True, the operation will only be simulated, but not performed. + If ``True``, the operation will only be simulated, but not performed. All performed operations are read-only. :param force: - If True, we may reset heads even if the repository in question is dirty. + If ``True``, we may reset heads even if the repository in question is dirty. Additionally we will be allowed to set a tracking branch which is ahead of its remote branch back into the past or the location of the remote branch. This will essentially 'forget' commits. - If False, local tracking branches that are in the future of their respective - remote branches will simply not be moved. + + If ``False``, local tracking branches that are in the future of their + respective remote branches will simply not be moved. :param keep_going: - If True, we will ignore but log all errors, and keep going recursively. + If ``True``, we will ignore but log all errors, and keep going recursively. Unless `dry_run` is set as well, `keep_going` could cause subsequent/inherited errors you wouldn't see otherwise. In conjunction with `dry_run`, it can be useful to anticipate all errors when updating submodules. - :param env: Optional dictionary containing the desired environment variables. + :param env: + Optional dictionary containing the desired environment variables. + Note: Provided variables will be used to update the execution environment for ``git``. If some variable is not specified in `env` and is defined in attr:`os.environ`, value from attr:`os.environ` will be used. + If you want to unset some variable, consider providing the empty string as its value. @@ -688,7 +694,7 @@ def update( Does nothing in bare repositories. :note: - This method is definitely not atomic if `recursive` is True. + This method is definitely not atomic if `recursive` is ``True``. :return: self @@ -950,18 +956,18 @@ def move(self, module_path: PathLike, configuration: bool = True, module: bool = :param module_path: The path to which to move our module in the parent repository's working - tree, given as repository - relative or absolute path. Intermediate + tree, given as repository-relative or absolute path. Intermediate directories will be created accordingly. If the path already exists, it must be empty. Trailing (back)slashes are removed automatically. :param configuration: - If True, the configuration will be adjusted to let the submodule point to - the given path. + If ``True``, the configuration will be adjusted to let the submodule point + to the given path. :param module: - If True, the repository managed by this submodule will be moved as well. If - False, we don't move the submodule's checkout, which may leave the parent - repository in an inconsistent state. + If ``True``, the repository managed by this submodule will be moved as well. + If ``False``, we don't move the submodule's checkout, which may leave the + parent repository in an inconsistent state. :return: self @@ -1074,12 +1080,13 @@ def remove( from the ``.gitmodules`` file and the entry in the ``.git/config`` file. :param module: - If True, the checked out module we point to will be deleted as well. If that - module is currently on a commit outside any branch in the remote, or if it - is ahead of its tracking branch, or if there are modified or untracked files - in its working tree, then the removal will fail. In case the removal of the - repository fails for these reasons, the submodule status will not have been - altered. + If ``True``, the checked out module we point to will be deleted as well. If + that module is currently on a commit outside any branch in the remote, or if + it is ahead of its tracking branch, or if there are modified or untracked + files in its working tree, then the removal will fail. In case the removal + of the repository fails for these reasons, the submodule status will not + have been altered. + If this submodule has child modules of its own, these will be deleted prior to touching the direct submodule. @@ -1088,12 +1095,12 @@ def remove( This basically enforces a brute-force file system based deletion. :param configuration: - If True, the submodule is deleted from the configuration, otherwise it + If ``True``, the submodule is deleted from the configuration, otherwise it isn't. Although this should be enabled most of the time, this flag enables you to safely delete the repository of your submodule. :param dry_run: - If True, we will not actually do anything, but throw the errors we would + If ``True``, we will not actually do anything, but throw the errors we would usually throw. :return: @@ -1239,12 +1246,12 @@ def set_parent_commit(self, commit: Union[Commit_ish, None], check: bool = True) contain the ``.gitmodules`` blob. :param commit: - Commit-ish reference pointing at the root_tree, or None to always point to - the most recent commit + Commit-ish reference pointing at the root_tree, or ``None`` to always point + to the most recent commit. :param check: - If True, relatively expensive checks will be performed to verify validity of - the submodule. + If ``True``, relatively expensive checks will be performed to verify + validity of the submodule. :raise ValueError: If the commit's tree didn't contain the ``.gitmodules`` blob. @@ -1297,8 +1304,9 @@ def config_writer( to this submodule into the ``.gitmodules`` file. :param index: - If not None, an IndexFile instance which should be written. - Defaults to the index of the Submodule's parent repository. + If not ``None``, an :class:`~git.index.base.IndexFile` instance which should + be written. Defaults to the index of the :class:`Submodule`'s parent + repository. :param write: If True, the index will be written each time a configuration value changes. @@ -1380,7 +1388,9 @@ def rename(self, new_name: str) -> "Submodule": @unbare_repo def module(self) -> "Repo": """ - :return: Repo instance initialized from the repository at our submodule path + :return: + :class:`~git.repo.base.Repo` instance initialized from the repository at our + submodule path :raise git.exc.InvalidGitRepositoryError: If a repository was not available. @@ -1401,7 +1411,7 @@ def module(self) -> "Repo": def module_exists(self) -> bool: """ :return: - True if our module exists and is a valid git repository. + ``True`` if our module exists and is a valid git repository. See the :meth:`module` method. """ try: @@ -1414,7 +1424,7 @@ def module_exists(self) -> bool: def exists(self) -> bool: """ :return: - True if the submodule exists, False otherwise. + ``True`` if the submodule exists, ``False`` otherwise. Please note that a submodule may exist (in the ``.gitmodules`` file) even though its module doesn't exist on disk. """ @@ -1480,14 +1490,15 @@ def branch_name(self) -> str: @property def url(self) -> str: - """:return: The url to the repository which our module - repository refers to""" + """:return: The url to the repository our submodule's repository refers to""" return self._url @property def parent_commit(self) -> "Commit_ish": """ :return: - Commit instance with the tree containing the ``.gitmodules`` file + :class:`~git.objects.commit.Commit` instance with the tree containing the + ``.gitmodules`` file :note: Will always point to the current head's commit if it was not set explicitly. @@ -1531,8 +1542,9 @@ def config_reader(self) -> SectionConstraint[SubmoduleConfigParser]: def children(self) -> IterableList["Submodule"]: """ :return: - IterableList(Submodule, ...) An iterable list of submodules instances which - are children of this submodule or 0 if the submodule is not checked out. + IterableList(Submodule, ...) An iterable list of :class:`Submodule` + instances which are children of this submodule or 0 if the submodule is not + checked out. """ return self._get_intermediate_items(self) diff --git a/git/objects/submodule/root.py b/git/objects/submodule/root.py index 8ef874f90..ac0f7ad94 100644 --- a/git/objects/submodule/root.py +++ b/git/objects/submodule/root.py @@ -89,9 +89,9 @@ def update( ) -> "RootModule": """Update the submodules of this repository to the current HEAD commit. - This method behaves smartly by determining changes of the path of a submodules + This method behaves smartly by determining changes of the path of a submodule's repository, next to changes to the to-be-checked-out commit or the branch to be - checked out. This works if the submodules ID does not change. + checked out. This works if the submodule's ID does not change. Additionally it will detect addition and removal of submodules, which will be handled gracefully. @@ -99,11 +99,11 @@ def update( :param previous_commit: If set to a commit-ish, the commit we should use as the previous commit the HEAD pointed to before it was set to the commit it points to now. - If None, it defaults to ``HEAD@{1}`` otherwise. + If ``None``, it defaults to ``HEAD@{1}`` otherwise. :param recursive: - If True, the children of submodules will be updated as well using the same - technique. + If ``True``, the children of submodules will be updated as well using the + same technique. :param force_remove: If submodules have been deleted, they will be forcibly removed. Otherwise @@ -116,28 +116,30 @@ def update( If we encounter a new module which would need to be initialized, then do it. :param to_latest_revision: - If True, instead of checking out the revision pointed to by this submodule's - sha, the checked out tracking branch will be merged with the latest remote - branch fetched from the repository's origin. + If ``True``, instead of checking out the revision pointed to by this + submodule's sha, the checked out tracking branch will be merged with the + latest remote branch fetched from the repository's origin. + Unless `force_reset` is specified, a local tracking branch will never be reset into its past, therefore the remote branch must be in the future for this to have an effect. :param force_reset: - If True, submodules may checkout or reset their branch even if the + If ``True``, submodules may checkout or reset their branch even if the repository has pending changes that would be overwritten, or if the local tracking branch is in the future of the remote tracking branch and would be reset into its past. :param progress: - :class:`RootUpdateProgress` instance or None if no progress should be sent. + :class:`RootUpdateProgress` instance, or ``None`` if no progress should be + sent. :param dry_run: - If True, operations will not actually be performed. Progress messages will - change accordingly to indicate the WOULD DO state of the operation. + If ``True``, operations will not actually be performed. Progress messages + will change accordingly to indicate the WOULD DO state of the operation. :param keep_going: - If True, we will ignore but log all errors, and keep going recursively. + If ``True``, we will ignore but log all errors, and keep going recursively. Unless `dry_run` is set as well, `keep_going` could cause subsequent/inherited errors you wouldn't see otherwise. In conjunction with `dry_run`, this can be useful to anticipate all errors diff --git a/git/objects/submodule/util.py b/git/objects/submodule/util.py index b02da501e..e5af5eb31 100644 --- a/git/objects/submodule/util.py +++ b/git/objects/submodule/util.py @@ -52,7 +52,7 @@ def mkhead(repo: "Repo", path: PathLike) -> "Head": def find_first_remote_branch(remotes: Sequence["Remote"], branch_name: str) -> "RemoteReference": """Find the remote branch matching the name of the given branch or raise - :class:`git.exc.InvalidGitRepositoryError`.""" + :class:`~git.exc.InvalidGitRepositoryError`.""" for remote in remotes: try: return remote.refs[branch_name] From bc48d264df657f3d5593760365427467980b1ef9 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 18:46:22 -0500 Subject: [PATCH 42/61] Further revise other docstrings within git.objects --- git/objects/base.py | 12 ++++++------ git/objects/blob.py | 3 ++- git/objects/commit.py | 28 ++++++++++++++-------------- git/objects/fun.py | 14 ++++++++------ git/objects/tree.py | 23 +++++++++++------------ git/objects/util.py | 37 +++++++++++++++++++------------------ 6 files changed, 60 insertions(+), 57 deletions(-) diff --git a/git/objects/base.py b/git/objects/base.py index df56a4bac..5cee8e405 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -58,7 +58,7 @@ class Object(LazyMixin): def __init__(self, repo: "Repo", binsha: bytes): """Initialize an object by identifying it by its binary sha. - All keyword arguments will be set on demand if None. + All keyword arguments will be set on demand if ``None``. :param repo: Repository this object is located in. @@ -83,7 +83,7 @@ def new(cls, repo: "Repo", id: Union[str, "Reference"]) -> Commit_ish: input id may have been a `~git.refs.reference.Reference` or rev-spec. :param id: - :class:`~git.refs.reference.Reference`, rev-spec, or hexsha + :class:`~git.refs.reference.Reference`, rev-spec, or hexsha. :note: This cannot be a ``__new__`` method as it would always call :meth:`__init__` @@ -119,13 +119,13 @@ def _set_cache_(self, attr: str) -> None: super()._set_cache_(attr) def __eq__(self, other: Any) -> bool: - """:return: True if the objects have the same SHA1""" + """:return: ``True`` if the objects have the same SHA1""" if not hasattr(other, "binsha"): return False return self.binsha == other.binsha def __ne__(self, other: Any) -> bool: - """:return: True if the objects do not have the same SHA1""" + """:return: ``True`` if the objects do not have the same SHA1""" if not hasattr(other, "binsha"): return True return self.binsha != other.binsha @@ -199,8 +199,8 @@ def __init__( 20 byte sha1. :param mode: - The stat compatible file mode as int, use the :mod:`stat` module to evaluate - the information. + The stat-compatible file mode as :class:`int`. + Use the :mod:`stat` module to evaluate the information. :param path: The path to the file in the file system, relative to the git repository diff --git a/git/objects/blob.py b/git/objects/blob.py index 50c12f0d7..253ceccb5 100644 --- a/git/objects/blob.py +++ b/git/objects/blob.py @@ -27,7 +27,8 @@ class Blob(base.IndexObject): @property def mime_type(self) -> str: """ - :return: String describing the mime type of this file (based on the filename) + :return: + String describing the mime type of this file (based on the filename) :note: Defaults to ``text/plain`` in case the actual file type is unknown. diff --git a/git/objects/commit.py b/git/objects/commit.py index e6aa7ac39..b5b1c540d 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -108,11 +108,11 @@ def __init__( encoding: Union[str, None] = None, gpgsig: Union[str, None] = None, ) -> None: - R"""Instantiate a new :class:`Commit`. All keyword arguments taking None as + R"""Instantiate a new :class:`Commit`. All keyword arguments taking ``None`` as default will be implicitly set on first query. :param binsha: - 20 byte sha1 + 20 byte sha1. :param parents: tuple(Commit, ...) A tuple of commit ids or actual :class:`Commit`\s. @@ -120,8 +120,8 @@ def __init__( :param tree: A :class:`~git.objects.tree.Tree` object. - :param author: :class:`~git.util.Actor` - The author Actor object + :param author: + The author :class:`~git.util.Actor` object. :param authored_date: int_seconds_since_epoch The authored DateTime - use :func:`time.gmtime` to convert it into a @@ -130,8 +130,8 @@ def __init__( :param author_tz_offset: int_seconds_west_of_utc The timezone that the `authored_date` is in. - :param committer: :class:`~git.util.Actor` - The committer string. + :param committer: + The committer string, as an :class:`~git.util.Actor` object. :param committed_date: int_seconds_since_epoch The committed DateTime - use :func:`time.gmtime` to convert it into a @@ -209,7 +209,7 @@ def _calculate_sha_(cls, repo: "Repo", commit: "Commit") -> bytes: return istream.binsha def replace(self, **kwargs: Any) -> "Commit": - """Create new commit object from existing commit object. + """Create new commit object from an existing commit object. Any values provided as keyword arguments will replace the corresponding attribute in the new object. @@ -295,7 +295,7 @@ def iter_items( R"""Find all commits matching the given criteria. :param repo: - The Repo + The :class:`~git.repo.base.Repo`. :param rev: Revision specifier, see git-rev-parse for viable options. @@ -378,7 +378,7 @@ def stats(self) -> Stats: @property def trailers(self) -> Dict[str, str]: - """Get the trailers of the message as a dictionary + """Deprecated. Get the trailers of the message as a dictionary. :note: This property is deprecated, please use either :attr:`trailers_list` or :attr:`trailers_dict``. @@ -396,7 +396,7 @@ def trailers_list(self) -> List[Tuple[str, str]]: Git messages can contain trailer information that are similar to RFC 822 e-mail headers (see: https://git-scm.com/docs/git-interpret-trailers). - This functions calls ``git interpret-trailers --parse`` onto the message to + This function calls ``git interpret-trailers --parse`` onto the message to extract the trailer information, returns the raw trailer data as a list. Valid message with trailer:: @@ -444,7 +444,7 @@ def trailers_dict(self) -> Dict[str, List[str]]: Git messages can contain trailer information that are similar to RFC 822 e-mail headers (see: https://git-scm.com/docs/git-interpret-trailers). - This functions calls ``git interpret-trailers --parse`` onto the message to + This function calls ``git interpret-trailers --parse`` onto the message to extract the trailer information. The key value pairs are stripped of leading and trailing whitespaces before they get saved into a dictionary. @@ -481,7 +481,7 @@ def trailers_dict(self) -> Dict[str, List[str]]: def _iter_from_process_or_stream(cls, repo: "Repo", proc_or_stream: Union[Popen, IO]) -> Iterator["Commit"]: """Parse out commit information into a list of :class:`Commit` objects. - We expect one-line per commit, and parse the actual commit information directly + We expect one line per commit, and parse the actual commit information directly from our lighting fast object database. :param proc: @@ -555,11 +555,11 @@ def create_from_tree( :param parent_commits: Optional :class:`Commit` objects to use as parents for the new commit. If empty list, the commit will have no parents at all and become a root commit. - If None, the current head commit will be the parent of the new commit + If ``None``, the current head commit will be the parent of the new commit object. :param head: - If True, the HEAD will be advanced to the new commit automatically. + If ``True``, the HEAD will be advanced to the new commit automatically. Otherwise the HEAD will remain pointing on the previous commit. This could lead to undesired results when diffing files. diff --git a/git/objects/fun.py b/git/objects/fun.py index d349737de..ad5bc9a88 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -128,7 +128,7 @@ def tree_entries_from_data(data: bytes) -> List[EntryTup]: def _find_by_name(tree_data: MutableSequence[EntryTupOrNone], name: str, is_dir: bool, start_at: int) -> EntryTupOrNone: - """Return data entry matching the given name and tree mode or None. + """Return data entry matching the given name and tree mode or ``None``. Before the item is returned, the respective data item is set None in the `tree_data` list to mark it done. @@ -172,7 +172,8 @@ def traverse_trees_recursive( odb: "GitCmdObjectDB", tree_shas: Sequence[Union[bytes, None]], path_prefix: str ) -> List[Tuple[EntryTupOrNone, ...]]: """ - :return: list of list with entries according to the given binary tree-shas. + :return: + List of list with entries according to the given binary tree-shas. The result is encoded in a list of n tuple|None per blob/commit, (n == len(tree_shas)), where: @@ -181,12 +182,12 @@ def traverse_trees_recursive( * [1] == mode as int * [2] == path relative to working tree root - The entry tuple is None if the respective blob/commit did not exist in the given - tree. + The entry tuple is ``None`` if the respective blob/commit did not exist in the + given tree. :param tree_shas: Iterable of shas pointing to trees. All trees must be on the same level. A - tree-sha may be None in which case None. + tree-sha may be ``None`` in which case ``None``. :param path_prefix: A prefix to be added to the returned paths on this level. @@ -257,7 +258,8 @@ def traverse_trees_recursive( def traverse_tree_recursive(odb: "GitCmdObjectDB", tree_sha: bytes, path_prefix: str) -> List[EntryTup]: """ - :return: list of entries of the tree pointed to by the binary `tree_sha`. + :return: + List of entries of the tree pointed to by the binary `tree_sha`. An entry has the following format: diff --git a/git/objects/tree.py b/git/objects/tree.py index da682b8cd..69cd6ef3f 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -97,17 +97,17 @@ def add(self, sha: bytes, mode: int, name: str, force: bool = False) -> "TreeMod If an item with the given name already exists, nothing will be done, but a :class:`ValueError` will be raised if the sha and mode of the existing item do - not match the one you add, unless `force` is True. + not match the one you add, unless `force` is ``True``. :param sha: The 20 or 40 byte sha of the item to add. :param mode: - int representing the stat compatible mode of the item. + :class:`int` representing the stat-compatible mode of the item. :param force: - If True, an item with your name and information will overwrite any existing - item with the same name, no matter which information it has. + If ``True``, an item with your name and information will overwrite any + existing item with the same name, no matter which information it has. :return: self @@ -164,11 +164,10 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): R"""Tree objects represent an ordered list of :class:`~git.objects.blob.Blob`\s and other :class:`~git.objects.tree.Tree`\s. - Tree as a list:: + Tree as a list: - Access a specific blob using the ``tree['filename']`` notation. - - You may likewise access by index, like ``blob = tree[0]``. + * Access a specific blob using the ``tree['filename']`` notation. + * You may likewise access by index, like ``blob = tree[0]``. """ type: Literal["tree"] = "tree" @@ -235,7 +234,7 @@ def join(self, file: str) -> IndexObjUnion: or :class:`~git.objects.submodule.base.Submodule` :raise KeyError: - If the given file or tree does not exist in tree. + If the given file or tree does not exist in this tree. """ msg = "Blob or Tree named %r not found" if "/" in file: @@ -275,12 +274,12 @@ def __truediv__(self, file: str) -> IndexObjUnion: @property def trees(self) -> List["Tree"]: - """:return: list(Tree, ...) list of trees directly below this tree""" + """:return: list(Tree, ...) List of trees directly below this tree""" return [i for i in self if i.type == "tree"] @property def blobs(self) -> List[Blob]: - """:return: list(Blob, ...) list of blobs directly below this tree""" + """:return: list(Blob, ...) List of blobs directly below this tree""" return [i for i in self if i.type == "blob"] @property @@ -342,7 +341,7 @@ def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion :class:`~git.util.IterableList`IterableList with the results of the traversal as produced by :meth:`traverse` - Tree -> IterableList[Union['Submodule', 'Tree', 'Blob']] + Tree -> IterableList[Union[Submodule, Tree, Blob]] """ return super()._list_traverse(*args, **kwargs) diff --git a/git/objects/util.py b/git/objects/util.py index 7ad20d66e..6f46bab18 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -95,9 +95,9 @@ def mode_str_to_int(modestr: Union[bytes, str]) -> int: used. :return: - String identifying a mode compatible to the mode methods ids of the stat module - regarding the rwx permissions for user, group and other, special flags and file - system flags, such as whether it is a symlink. + String identifying a mode compatible to the mode methods ids of the :mod:`stat` + module regarding the rwx permissions for user, group and other, special flags + and file system flags, such as whether it is a symlink. """ mode = 0 for iteration, char in enumerate(reversed(modestr[-6:])): @@ -401,7 +401,7 @@ class Tree:: (cls, Tree) -> Tuple[Tree, ...] def list_traverse(self, *args: Any, **kwargs: Any) -> Any: """Traverse self and collect all items found. - Calling this directly only the abstract base class, including via a ``super()`` + Calling this directly on the abstract base class, including via a ``super()`` proxy, is deprecated. Only overridden implementations should be called. """ warnings.warn( @@ -418,12 +418,13 @@ def _list_traverse( ) -> IterableList[Union["Commit", "Submodule", "Tree", "Blob"]]: """Traverse self and collect all items found. - :return: :class:`~git.util.IterableList` with the results of the traversal as + :return: + :class:`~git.util.IterableList` with the results of the traversal as produced by :meth:`traverse`:: - Commit -> IterableList["Commit"] - Submodule -> IterableList["Submodule"] - Tree -> IterableList[Union["Submodule", "Tree", "Blob"]] + Commit -> IterableList[Commit] + Submodule -> IterableList[Submodule] + Tree -> IterableList[Union[Submodule, Tree, Blob]] """ # Commit and Submodule have id.__attribute__ as IterableObj. # Tree has id.__attribute__ inherited from IndexObject. @@ -476,12 +477,12 @@ def _traverse( """Iterator yielding items found when traversing self. :param predicate: - A function ``f(i,d)`` that returns False if item i at depth ``d`` should not - be included in the result. + A function ``f(i,d)`` that returns ``False`` if item i at depth ``d`` should + not be included in the result. :param prune: - A function ``f(i,d)`` that returns True if the search should stop at item - ``i`` at depth ``d``. Item ``i`` will not be returned. + A function ``f(i,d)`` that returns ``True`` if the search should stop at + item ``i`` at depth ``d``. Item ``i`` will not be returned. :param depth: Defines at which level the iteration should not go deeper if -1, there is no @@ -489,19 +490,19 @@ def _traverse( i.e. if 1, you would only get the first level of predecessors/successors. :param branch_first: - If True, items will be returned branch first, otherwise depth first. + If ``True``, items will be returned branch first, otherwise depth first. :param visit_once: - If True, items will only be returned once, although they might be + If ``True``, items will only be returned once, although they might be encountered several times. Loops are prevented that way. :param ignore_self: - If True, self will be ignored and automatically pruned from the result. - Otherwise it will be the first item to be returned. If `as_edge` is True, the - source of the first edge is None + If ``True``, self will be ignored and automatically pruned from the result. + Otherwise it will be the first item to be returned. If `as_edge` is + ``True``, the source of the first edge is ``None``. :param as_edge: - If True, return a pair of items, first being the source, second the + If ``True``, return a pair of items, first being the source, second the destination, i.e. tuple(src, dest) with the edge spanning from source to destination. From 30f7da508154df8b968f171b789c366c52276cee Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 18:50:46 -0500 Subject: [PATCH 43/61] Fix erroneous reference to DateTime "class" DateTime is not a class here, and the parameter is expected as int. This fixes a documentation bug I introduced in cd16a35 (#1725). --- git/objects/tag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/objects/tag.py b/git/objects/tag.py index 211c84ac7..357c8ff7e 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -68,7 +68,7 @@ def __init__( :class:`~git.util.Actor` identifying the tagger. :param tagged_date: int_seconds_since_epoch - The :class:`DateTime` of the tag creation. + The DateTime of the tag creation. Use :func:`time.gmtime` to convert it into a different format. :param tagger_tz_offset: int_seconds_west_of_utc From 61269970f2b88ba008e5b8c5e4d5e4d2975d82d7 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 19:06:07 -0500 Subject: [PATCH 44/61] Improve docstrings about tags - Fix long-outdated information in the git.objects.tag docstring that seems to have been left over from when there was no such separate module. This module has only a tag-related class, not all classes that derive from git.object.base.Object. - Expand the git.objects.tag docstring to clarify what the module provides, add a git.refs.tag module docstring explaining what that provides, and cross-reference them with an explanation of how they differ (mentioning how this relates to git concepts). - Expand thie git.object.tag.TagObject class docstring to include the term "annotated" (retaining "non-lightweight" in parentheses). --- git/objects/tag.py | 10 +++++++--- git/refs/tag.py | 7 +++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/git/objects/tag.py b/git/objects/tag.py index 357c8ff7e..7589a5be1 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -3,7 +3,11 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ -"""Object-based types.""" +"""Provides an :class:`git.objects.base.Object`-based type for annotated tags. + +This defines the :class:`TagReference` class, which represents annotated tags. +For lightweight tags, see the :mod:`git.refs.tag` module. +""" from . import base from .util import get_object_type_by_name, parse_actor_and_date @@ -25,8 +29,8 @@ class TagObject(base.Object): - """Non-lightweight tag carrying additional information about an object we are - pointing to.""" + """Annotated (i.e. non-lightweight) tag carrying additional information about an + object we are pointing to.""" type: Literal["tag"] = "tag" diff --git a/git/refs/tag.py b/git/refs/tag.py index 7edd1d12a..99c4bf443 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -1,6 +1,13 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ +"""Provides a :class:`~git.refs.reference.Reference`-based type for lightweight tags. + +This defines the :class:`TagReference` class (and its alias :class:`Tag`), which +represents lightweight tags. For annotated tags (which are git objects), see the +:mod:`git.objects.tag` module. +""" + from .reference import Reference __all__ = ["TagReference", "Tag"] From 110706e2f49eb90b29ceb3057340437d3d93245f Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 19:25:50 -0500 Subject: [PATCH 45/61] Fix param name in TagRefernece docstring and add info The "reference" parameter was listed as "ref" in the docstring. This fixes that, slightly expands its description, and also adds documentation for the "repo" parameter. --- git/refs/tag.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/git/refs/tag.py b/git/refs/tag.py index 99c4bf443..89a82d1be 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -72,7 +72,8 @@ def commit(self) -> "Commit": # type: ignore[override] # LazyMixin has unrelat def tag(self) -> Union["TagObject", None]: """ :return: - Tag object this tag ref points to or None in case we are a lightweight tag + Tag object this tag ref points to, or ``None`` in case we are a lightweight + tag """ obj = self.object if obj.type == "tag": @@ -96,13 +97,17 @@ def create( ) -> "TagReference": """Create a new tag reference. + :param repo: + The :class:`~git.repo.base.Repo` to create the tag in. + :param path: The name of the tag, e.g. ``1.0`` or ``releases/1.0``. The prefix ``refs/tags`` is implied. - :param ref: + :param reference: A reference to the :class:`~git.objects.base.Object` you want to tag. - The Object can be a commit, tree or blob. + The referenced object can be a :class:`~git.objects.commit.Commit`, + :class:`~git.objects.tree.Tree`, or :class:`~git.objects.blob.Blob`. :param logmsg: If not None, the message will be used in your tag object. This will also From b0e5bffef39ae6064bb2a1909bc0b536244b4b4c Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 19:29:29 -0500 Subject: [PATCH 46/61] Undo some expansion of "reference" parameter This keeps most of the changes from the previous commit, but it undoes the expansion of the git object types a lightweight tag can refer to into GitPython git.objects.base.Object-based types, which seems more misleading than helpful because an uncareful reading could lead to an incorrect belief that the corresponding argument should or could be an object of such types. --- git/refs/tag.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/git/refs/tag.py b/git/refs/tag.py index 89a82d1be..fd2dee131 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -106,8 +106,7 @@ def create( :param reference: A reference to the :class:`~git.objects.base.Object` you want to tag. - The referenced object can be a :class:`~git.objects.commit.Commit`, - :class:`~git.objects.tree.Tree`, or :class:`~git.objects.blob.Blob`. + The referenced object can be a commit, tree, or blob. :param logmsg: If not None, the message will be used in your tag object. This will also From a5a1b2c541072ee73b5c295581f678acb7c91c2c Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 19:34:11 -0500 Subject: [PATCH 47/61] Add a bit of missing docstring formatting --- git/refs/tag.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/refs/tag.py b/git/refs/tag.py index fd2dee131..6a6dad09a 100644 --- a/git/refs/tag.py +++ b/git/refs/tag.py @@ -109,7 +109,7 @@ def create( The referenced object can be a commit, tree, or blob. :param logmsg: - If not None, the message will be used in your tag object. This will also + If not ``None``, the message will be used in your tag object. This will also create an additional tag object that allows to obtain that information, e.g.:: @@ -120,7 +120,7 @@ def create( `logmsg` takes precedence if both are passed. :param force: - If True, force creation of a tag even though that tag already exists. + If ``True``, force creation of a tag even though that tag already exists. :param kwargs: Additional keyword arguments to be passed to ``git tag``. From 018ebaf72d2d96b2ca98a46748e857abb829a9d4 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 19:53:46 -0500 Subject: [PATCH 48/61] Further revise docstrings within git.repo --- git/repo/base.py | 74 ++++++++++++++++++++++++++---------------------- git/repo/fun.py | 8 +++--- 2 files changed, 44 insertions(+), 38 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index 7b3e514c1..4745a2411 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -102,9 +102,9 @@ class BlameEntry(NamedTuple): class Repo: - """Represents a git repository and allows you to query references, - gather commit information, generate diffs, create and clone repositories, and query - the log. + """Represents a git repository and allows you to query references, father + commit information, generate diffs, create and clone repositories, and query the + log. The following attributes are worth using: @@ -188,16 +188,18 @@ def __init__( repo = Repo(R"C:\Users\mtrier\Development\git-python\.git") - In *Cygwin*, `path` may be a ``cygdrive/...`` prefixed path. - - If `path` is None or an empty string, :envvar:`GIT_DIR` is used. If that - environment variable is absent or empty, the current directory is used. + - If `path` is ``None`` or an empty string, :envvar:`GIT_DIR` is used. If + that environment variable is absent or empty, the current directory is + used. :param odbt: Object DataBase type - a type which is constructed by providing the directory containing the database objects, i.e. ``.git/objects``. It will be - used to access all object data + used to access all object data. :param search_parent_directories: - If True, all parent directories will be searched for a valid repo as well. + If ``True``, all parent directories will be searched for a valid repo as + well. Please note that this was the default behaviour in older versions of GitPython, which is considered a bug though. @@ -372,7 +374,7 @@ def working_tree_dir(self) -> Optional[PathLike]: """ :return: The working tree directory of our git repository. - If this is a bare repository, None is returned. + If this is a bare repository, ``None`` is returned. """ return self._working_tree_dir @@ -387,7 +389,7 @@ def common_dir(self) -> PathLike: @property def bare(self) -> bool: - """:return: True if the repository is bare""" + """:return: ``True`` if the repository is bare""" return self._bare @property @@ -450,7 +452,7 @@ def remotes(self) -> "IterableList[Remote]": def remote(self, name: str = "origin") -> "Remote": """:return: The remote with the specified name - :raise ValueError + :raise ValueError: If no remote with such a name exists. """ r = Remote(self, name) @@ -494,10 +496,13 @@ def create_submodule(self, *args: Any, **kwargs: Any) -> Submodule: return Submodule.add(self, *args, **kwargs) def iter_submodules(self, *args: Any, **kwargs: Any) -> Iterator[Submodule]: - """An iterator yielding Submodule instances, see Traversable interface - for a description of args and kwargs. + """An iterator yielding Submodule instances. - :return: Iterator + See the `~git.objects.util.Traversable` interface for a description of `args` + and `kwargs`. + + :return: + Iterator """ return RootModule(self).traverse(*args, **kwargs) @@ -554,7 +559,8 @@ def create_head( ) -> "Head": """Create a new head within the repository. - :note: For more documentation, please see the + :note: + For more documentation, please see the :meth:`Head.create ` method. :return: @@ -648,7 +654,7 @@ def config_reader( configuration files. :param config_level: - For possible values, see the :meth:`config_writer` method. If None, all + For possible values, see the :meth:`config_writer` method. If ``None``, all applicable levels will be used. Specify a level in case you know which file you wish to read to prevent reading multiple files. @@ -744,7 +750,7 @@ def iter_commits( :param rev: Revision specifier, see ``git rev-parse`` for viable options. - If None, the active branch will be used. + If ``None``, the active branch will be used. :param paths: An optional path or a list of paths. If set, only commits that include the @@ -759,7 +765,7 @@ def iter_commits( ``"revA...revB"`` revision specifier. :return: - Iterator of :class:`~git.objects.commit.Commit` objects + Iterator of :class:`~git.objects.commit.Commit` objects """ if rev is None: rev = self.head.commit @@ -819,7 +825,7 @@ def is_ancestor(self, ancestor_rev: "Commit", rev: "Commit") -> bool: Rev to test against `ancestor_rev`. :return: - True if `ancestor_rev` is an ancestor to `rev`. + ``True`` if `ancestor_rev` is an ancestor to `rev`. """ try: self.git.merge_base(ancestor_rev, rev, is_ancestor=True) @@ -923,9 +929,9 @@ def is_dirty( ) -> bool: """ :return: - True if the repository is considered dirty. By default it will react like a - git-status without untracked files, hence it is dirty if the index or the - working copy have changes. + ``True`` if the repository is considered dirty. By default it will react + like a git-status without untracked files, hence it is dirty if the index or + the working copy have changes. """ if self._bare: # Bare repositories with no associated working directory are @@ -1036,9 +1042,9 @@ def blame_incremental(self, rev: str | HEAD | None, file: str, **kwargs: Any) -> stream of :class:`BlameEntry` tuples. :param rev: - Revision specifier. If None, the blame will include all the latest - uncommitted changes. Otherwise, anything successfully parsed by ``git - rev-parse`` is a valid option. + Revision specifier. If ``None``, the blame will include all the latest + uncommitted changes. Otherwise, anything successfully parsed by + ``git rev-parse`` is a valid option. :return: Lazy iterator of :class:`BlameEntry` tuples, where the commit indicates the @@ -1132,9 +1138,9 @@ def blame( """The blame information for the given file at the given revision. :param rev: - Revision specifier. If None, the blame will include all the latest + Revision specifier. If ``None``, the blame will include all the latest uncommitted changes. Otherwise, anything successfully parsed by - git-rev-parse is a valid option. + ``git rev-parse`` is a valid option. :return: list: [git.Commit, list: []] @@ -1286,9 +1292,9 @@ def init( """Initialize a git repository at the given path if specified. :param path: - The full path to the repo (traditionally ends with ``/.git``). - Or None, in which case the repository will be created in the current working - directory. + The full path to the repo (traditionally ends with ``/.git``). Or + ``None``, in which case the repository will be created in the current + working directory. :param mkdir: If specified, will create the repository directory if it doesn't already @@ -1445,7 +1451,7 @@ def clone( Allow unsafe options to be used, like ``--upload-pack``. :param kwargs: - * odbt = ObjectDatabase Type, allowing to determine the object database + * ``odbt`` = ObjectDatabase Type, allowing to determine the object database implementation used by the returned Repo instance. * All remaining keyword arguments are given to the ``git clone`` command. @@ -1547,9 +1553,9 @@ def archive( :param kwargs: Additional arguments passed to ``git archive``: - * Use the 'format' argument to define the kind of format. Use specialized + * Use the ``format`` argument to define the kind of format. Use specialized ostreams to write any format supported by Python. - * You may specify the special **path** keyword, which may either be a + * You may specify the special ``path`` keyword, which may either be a repository-relative path to a directory or file to place into the archive, or a list or tuple of multiple paths. @@ -1581,7 +1587,7 @@ def has_separate_working_tree(self) -> bool: to. :note: - Bare repositories will always return False here. + Bare repositories will always return ``False`` here. """ if self.bare: return False @@ -1601,7 +1607,7 @@ def currently_rebasing_on(self) -> Commit | None: :return: The commit which is currently being replayed while rebasing. - None if we are not currently rebasing. + ``None`` if we are not currently rebasing. """ if self.git_dir: rebase_head_file = osp.join(self.git_dir, "REBASE_HEAD") diff --git a/git/repo/fun.py b/git/repo/fun.py index 4936f1773..e9fad2c46 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -128,7 +128,7 @@ def find_submodule_git_dir(d: "PathLike") -> Optional["PathLike"]: def short_to_long(odb: "GitCmdObjectDB", hexsha: str) -> Optional[bytes]: """ :return: - Long hexadecimal sha1 from the given less than 40 byte hexsha or None if no + Long hexadecimal sha1 from the given less than 40 byte hexsha, or ``None`` if no candidate could be found. :param hexsha: @@ -150,7 +150,7 @@ def name_to_object( references are supported. :param return_ref: - If True, and name specifies a reference, we will return the reference + If ``True``, and name specifies a reference, we will return the reference instead of the object. Otherwise it will raise `~gitdb.exc.BadObject` o `~gitdb.exc.BadName`. """ @@ -202,7 +202,7 @@ def name_to_object( def deref_tag(tag: "Tag") -> "TagObject": - """Recursively dereference a tag and return the resulting object""" + """Recursively dereference a tag and return the resulting object.""" while True: try: tag = tag.object @@ -213,7 +213,7 @@ def deref_tag(tag: "Tag") -> "TagObject": def to_commit(obj: Object) -> Union["Commit", "TagObject"]: - """Convert the given object to a commit if possible and return it""" + """Convert the given object to a commit if possible and return it.""" if obj.type == "tag": obj = deref_tag(obj) From 608147ec7fa240686cc88612e1962c18fc6e8549 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 20:07:13 -0500 Subject: [PATCH 49/61] Better explain conditional cleanup in test_base_object This expands the comment added in 41fac85 (#1770) to make more clear that this particular cleanup is deliberately done only when the operation was successful (and why). --- test/test_base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/test_base.py b/test/test_base.py index 74f342071..693c97f20 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -72,7 +72,10 @@ def test_base_object(self): self.assertEqual(item, item.stream_data(tmpfile)) tmpfile.seek(0) self.assertEqual(tmpfile.read(), data) - os.remove(tmpfile.name) # Do it this way so we can inspect the file on failure. + + # Remove the file this way, instead of with a context manager or "finally", + # so it is only removed on success, and we can inspect the file on failure. + os.remove(tmpfile.name) # END for each object type to create # Each has a unique sha. From 5cf5b6049f4e4cc83b08c1b2f36aaf0ad48b67f1 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 20:48:52 -0500 Subject: [PATCH 50/61] Revise test suite docstrings and comments In a more limited way than in the git/ code. Docstring revisions are, in spirit, along the same lines as those in the git package, but much more conservative, because the tests' docstrings are not rendered into Sphinx documentation and there is no current plan to do so (so the tradeoffs are different, with even a tiny decrease in clarity when read in the code being a reason to avoid changes that use Sphinx roles more robustly or that would improve hypothetical rendered documentation), and because the test suite uses docstrings more sparingly and the existing docstrings were mostly already clear and easy to read. This wraps commnts to 88 columns as most comments now are in the git package, except it avoids doing so when doing so would make anything even slightly less clear, and where it would require significant further style or spacing changes for it to remain obvious (even before reading a comment) what the comment applies to, and in most portions of tutorial-generating test case code (where, although clarity would improve when reading the tests, it might sometimes decrease in the generated documentation). --- test/lib/helper.py | 13 ++-- test/performance/test_streams.py | 4 +- test/test_base.py | 4 +- test/test_clone.py | 4 +- test/test_commit.py | 16 +++-- test/test_config.py | 12 ++-- test/test_diff.py | 23 ++++--- test/test_docs.py | 26 ++++---- test/test_fun.py | 3 +- test/test_git.py | 22 +++--- test/test_index.py | 49 +++++++------- test/test_installation.py | 6 +- test/test_refs.py | 50 +++++++++----- test/test_remote.py | 39 +++++------ test/test_repo.py | 34 +++++----- test/test_submodule.py | 111 ++++++++++++++++++------------- test/test_util.py | 13 ++-- 17 files changed, 243 insertions(+), 186 deletions(-) diff --git a/test/lib/helper.py b/test/lib/helper.py index f951a6a12..26469ed5d 100644 --- a/test/lib/helper.py +++ b/test/lib/helper.py @@ -346,17 +346,20 @@ class TestBase(TestCase): """Base class providing default functionality to all tests such as: - Utility functions provided by the TestCase base of the unittest method such as:: + self.fail("todo") self.assertRaises(...) - Class level repository which is considered read-only as it is shared among all test cases in your type. + Access it using:: - self.rorepo # 'ro' stands for read-only + + self.rorepo # 'ro' stands for read-only The rorepo is in fact your current project's git repo. If you refer to specific - shas for your objects, be sure you choose some that are part of the immutable portion - of the project history (so that tests don't fail for others). + shas for your objects, be sure you choose some that are part of the immutable + portion of the project history (so that tests don't fail for others). """ def _small_repo_url(self): @@ -383,8 +386,8 @@ def tearDownClass(cls): def _make_file(self, rela_path, data, repo=None): """ - Create a file at the given path relative to our repository, filled - with the given data. + Create a file at the given path relative to our repository, filled with the + given data. :return: An absolute path to the created file. """ diff --git a/test/performance/test_streams.py b/test/performance/test_streams.py index ba5cbe415..56b5274ec 100644 --- a/test/performance/test_streams.py +++ b/test/performance/test_streams.py @@ -25,8 +25,8 @@ class TestObjDBPerformance(TestBigRepoR): @with_rw_repo("HEAD", bare=True) def test_large_data_streaming(self, rwrepo): - # TODO: This part overlaps with the same file in gitdb.test.performance.test_stream. - # It should be shared if possible. + # TODO: This part overlaps with the same file in + # gitdb.test.performance.test_stream. It should be shared if possible. ldb = LooseObjectDB(osp.join(rwrepo.git_dir, "objects")) for randomize in range(2): diff --git a/test/test_base.py b/test/test_base.py index 693c97f20..ef7486e86 100644 --- a/test/test_base.py +++ b/test/test_base.py @@ -135,8 +135,8 @@ def test_add_unicode(self, rw_repo): # https://github.com/gitpython-developers/GitPython/issues/147#issuecomment-68881897 # Therefore, it must be added using the Python implementation. rw_repo.index.add([file_path]) - # However, when the test winds down, rmtree fails to delete this file, which is recognized - # as ??? only. + # However, when the test winds down, rmtree fails to delete this file, which + # is recognized as ??? only. else: # On POSIX, we can just add Unicode files without problems. rw_repo.git.add(rw_repo.working_dir) diff --git a/test/test_clone.py b/test/test_clone.py index dcab7ad6f..be2e6b19b 100644 --- a/test/test_clone.py +++ b/test/test_clone.py @@ -19,8 +19,8 @@ def test_checkout_in_non_empty_dir(self, rw_dir): garbage_file = non_empty_dir / "not-empty" garbage_file.write_text("Garbage!") - # Verify that cloning into the non-empty dir fails while complaining about - # the target directory not being empty/non-existent. + # Verify that cloning into the non-empty dir fails while complaining about the + # target directory not being empty/non-existent. try: self.rorepo.clone(non_empty_dir) except git.GitCommandError as exc: diff --git a/test/test_commit.py b/test/test_commit.py index fddb91c17..5571b9ecb 100644 --- a/test/test_commit.py +++ b/test/test_commit.py @@ -27,8 +27,8 @@ class TestCommitSerialization(TestBase): def assert_commit_serialization(self, rwrepo, commit_id, print_performance_info=False): - """Traverse all commits in the history of commit identified by commit_id and check - if the serialization works. + """Traverse all commits in the history of commit identified by commit_id and + check if the serialization works. :param print_performance_info: If True, we will show how fast we are. """ @@ -317,8 +317,9 @@ def test_count(self): self.assertEqual(self.rorepo.tag("refs/tags/0.1.5").commit.count(), 143) def test_list(self): - # This doesn't work anymore, as we will either attempt getattr with bytes, or compare 20 byte string - # with actual 20 byte bytes. This usage makes no sense anyway. + # This doesn't work anymore, as we will either attempt getattr with bytes, or + # compare 20 byte string with actual 20 byte bytes. This usage makes no sense + # anyway. assert isinstance( Commit.list_items(self.rorepo, "0.1.5", max_count=5)["5117c9c8a4d3af19a9958677e45cda9269de1541"], Commit, @@ -383,8 +384,8 @@ def test_serialization_unicode_support(self): self.assertEqual(cmt.author.name, ncmt.author.name) self.assertEqual(cmt.message, ncmt.message) - # Actually, it can't be printed in a shell as repr wants to have ascii only - # it appears. + # Actually, it can't be printed in a shell as repr wants to have ascii only it + # appears. cmt.author.__repr__() def test_invalid_commit(self): @@ -498,7 +499,8 @@ def test_trailers(self): KEY_2 = "Key" VALUE_2 = "Value with inner spaces" - # Check that the following trailer example is extracted from multiple msg variations. + # Check that the following trailer example is extracted from multiple msg + # variations. TRAILER = f"{KEY_1}: {VALUE_1_1}\n{KEY_2}: {VALUE_2}\n{KEY_1}: {VALUE_1_2}" msgs = [ f"Subject\n\n{TRAILER}\n", diff --git a/test/test_config.py b/test/test_config.py index f493c5672..4843d91eb 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -41,7 +41,7 @@ def _to_memcache(self, file_path): return sio def test_read_write(self): - # writer must create the exact same file as the one read before + # The writer must create the exact same file as the one read before. for filename in ("git_config", "git_config_global"): file_obj = self._to_memcache(fixture_path(filename)) with GitConfigParser(file_obj, read_only=False) as w_config: @@ -56,7 +56,8 @@ def test_read_write(self): self._to_memcache(fixture_path(filename)).getvalue(), ) - # Creating an additional config writer must fail due to exclusive access. + # Creating an additional config writer must fail due to exclusive + # access. with self.assertRaises(IOError): GitConfigParser(file_obj, read_only=False) @@ -91,8 +92,8 @@ def test_includes_order(self): r_config.read() # Enforce reading. # Simple inclusions, again checking them taking precedence. assert r_config.get_value("sec", "var0") == "value0_included" - # This one should take the git_config_global value since included - # values must be considered as soon as they get them. + # This one should take the git_config_global value since included values + # must be considered as soon as they get them. assert r_config.get_value("diff", "tool") == "meld" try: # FIXME: Split this assertion out somehow and mark it xfail (or fix it). @@ -109,7 +110,8 @@ def test_lock_reentry(self, rw_dir): # Entering again locks the file again... with gcp as cw: cw.set_value("include", "some_other_value", "b") - # ...so creating an additional config writer must fail due to exclusive access. + # ...so creating an additional config writer must fail due to exclusive + # access. with self.assertRaises(IOError): GitConfigParser(fpl, read_only=False) # but work when the lock is removed diff --git a/test/test_diff.py b/test/test_diff.py index 87f92f5d1..cdd473f7f 100644 --- a/test/test_diff.py +++ b/test/test_diff.py @@ -271,18 +271,18 @@ def test_diff_unsafe_paths(self): self.assertEqual(res[10].b_rawpath, b"path/\x80-invalid-unicode-path.txt") # The "Moves" - # NOTE: The path prefixes a/ and b/ here are legit! We're actually - # verifying that it's not "a/a/" that shows up, see the fixture data. - self.assertEqual(res[11].a_path, "a/with spaces") # NOTE: path a/ here legit! - self.assertEqual(res[11].b_path, "b/with some spaces") # NOTE: path b/ here legit! + # NOTE: The path prefixes "a/" and "b/" here are legit! We're actually verifying + # that it's not "a/a/" that shows up; see the fixture data. + self.assertEqual(res[11].a_path, "a/with spaces") # NOTE: path "a/"" legit! + self.assertEqual(res[11].b_path, "b/with some spaces") # NOTE: path "b/"" legit! self.assertEqual(res[12].a_path, "a/ending in a space ") self.assertEqual(res[12].b_path, "b/ending with space ") self.assertEqual(res[13].a_path, 'a/"with-quotes"') self.assertEqual(res[13].b_path, 'b/"with even more quotes"') def test_diff_patch_format(self): - # Test all of the 'old' format diffs for completeness - it should at least - # be able to deal with it. + # Test all of the 'old' format diffs for completeness - it should at least be + # able to deal with it. fixtures = ( "diff_2", "diff_2f", @@ -345,8 +345,9 @@ def test_diff_submodule(self): repo.create_tag("2") diff = repo.commit("1").diff(repo.commit("2"))[0] - # If diff is unable to find the commit hashes (looks in wrong repo) the *_blob.size - # property will be a string containing exception text, an int indicates success. + # If diff is unable to find the commit hashes (looks in wrong repo) the + # *_blob.size property will be a string containing exception text, an int + # indicates success. self.assertIsInstance(diff.a_blob.size, int) self.assertIsInstance(diff.b_blob.size, int) @@ -392,9 +393,9 @@ def test_diff_interface(self): # END for each other side # END for each commit - # Assert that we could always find at least one instance of the members we - # can iterate in the diff index - if not this indicates its not working correctly - # or our test does not span the whole range of possibilities. + # Assert that we could always find at least one instance of the members we can + # iterate in the diff index - if not this indicates its not working correctly or + # our test does not span the whole range of possibilities. for key, value in assertion_map.items(): self.assertIsNotNone(value, "Did not find diff for %s" % key) # END for each iteration type diff --git a/test/test_docs.py b/test/test_docs.py index 48922eba8..409f66bb3 100644 --- a/test/test_docs.py +++ b/test/test_docs.py @@ -19,8 +19,9 @@ class Tutorials(TestBase): def tearDown(self): gc.collect() - # ACTUALLY skipped by git.util.rmtree (in local onerror function), from the last call to it via - # git.objects.submodule.base.Submodule.remove (at "handle separate bare repository"), line 1062. + # ACTUALLY skipped by git.util.rmtree (in local onerror function), from the last + # call to it via git.objects.submodule.base.Submodule.remove + # (at "handle separate bare repository"), line 1062. # # @skipIf(HIDE_WINDOWS_KNOWN_ERRORS, # "FIXME: helper.wrapper fails with: PermissionError: [WinError 5] Access is denied: " @@ -31,8 +32,8 @@ def test_init_repo_object(self, rw_dir): from git import Repo # rorepo is a Repo instance pointing to the git-python repository. - # For all you know, the first argument to Repo is a path to the repository - # you want to work with. + # For all you know, the first argument to Repo is a path to the repository you + # want to work with. repo = Repo(self.rorepo.working_tree_dir) assert not repo.bare # ![1-test_init_repo_object] @@ -149,8 +150,8 @@ def update(self, op_code, cur_count, max_count=None, message=""): assert origin.exists() for fetch_info in origin.fetch(progress=MyProgressPrinter()): print("Updated %s to %s" % (fetch_info.ref, fetch_info.commit)) - # Create a local branch at the latest fetched master. We specify the name statically, but you have all - # information to do it programmatically as well. + # Create a local branch at the latest fetched master. We specify the name + # statically, but you have all information to do it programmatically as well. bare_master = bare_repo.create_head("master", origin.refs.master) bare_repo.head.set_reference(bare_master) assert not bare_repo.delete_remote(origin).exists() @@ -188,9 +189,9 @@ def update(self, op_code, cur_count, max_count=None, message=""): # submodules # [14-test_init_repo_object] - # Create a new submodule and check it out on the spot, setup to track master branch of `bare_repo`. - # As our GitPython repository has submodules already that point to GitHub, make sure we don't - # interact with them. + # Create a new submodule and check it out on the spot, setup to track master + # branch of `bare_repo`. As our GitPython repository has submodules already that + # point to GitHub, make sure we don't interact with them. for sm in cloned_repo.submodules: assert not sm.remove().exists() # after removal, the sm doesn't exist anymore sm = cloned_repo.create_submodule("mysubrepo", "path/to/subrepo", url=bare_repo.git_dir, branch="master") @@ -424,8 +425,8 @@ def test_references_and_objects(self, rw_dir): with origin.config_writer as cw: cw.set("pushurl", "other_url") - # Please note that in Python 2, writing origin.config_writer.set(...) is totally safe. - # In py3 __del__ calls can be delayed, thus not writing changes in time. + # Please note that in Python 2, writing origin.config_writer.set(...) is totally + # safe. In py3 __del__ calls can be delayed, thus not writing changes in time. # ![26-test_references_and_objects] # [27-test_references_and_objects] @@ -462,7 +463,8 @@ def test_references_and_objects(self, rw_dir): # ![29-test_references_and_objects] # [30-test_references_and_objects] - # Check out the branch using git-checkout. It will fail as the working tree appears dirty. + # Check out the branch using git-checkout. + # It will fail as the working tree appears dirty. self.assertRaises(git.GitCommandError, repo.heads.master.checkout) repo.heads.past_branch.checkout() # ![30-test_references_and_objects] diff --git a/test/test_fun.py b/test/test_fun.py index 566bc9aae..2d30d355a 100644 --- a/test/test_fun.py +++ b/test/test_fun.py @@ -35,7 +35,8 @@ def _assert_index_entries(self, entries, trees): # END assert entry matches fully def test_aggressive_tree_merge(self): - # Head tree with additions, removals and modification compared to its predecessor. + # Head tree with additions, removals and modification compared to its + # predecessor. odb = self.rorepo.odb HC = self.rorepo.commit("6c1faef799095f3990e9970bc2cb10aa0221cf9c") H = HC.tree diff --git a/test/test_git.py b/test/test_git.py index 97e21cad4..e1a8bda5e 100644 --- a/test/test_git.py +++ b/test/test_git.py @@ -253,21 +253,27 @@ def test_it_avoids_upcasing_unrelated_environment_variable_names(self): if old_name == old_name.upper(): raise RuntimeError("test bug or strange locale: old_name invariant under upcasing") - # Step 1: Set the environment variable in this parent process. Because os.putenv is a thin - # wrapper around a system API, os.environ never sees the variable in this parent - # process, so the name is not upcased even on Windows. + # Step 1 + # + # Set the environment variable in this parent process. Because os.putenv is a + # thin wrapper around a system API, os.environ never sees the variable in this + # parent process, so the name is not upcased even on Windows. os.putenv(old_name, "1") - # Step 2: Create the child process that inherits the environment variable. The child uses - # GitPython, and we are testing that it passes the variable with the exact original - # name to its own child process (the grandchild). + # Step 2 + # + # Create the child process that inherits the environment variable. The child + # uses GitPython, and we are testing that it passes the variable with the exact + # original name to its own child process (the grandchild). cmdline = [ sys.executable, fixture_path("env_case.py"), # Contains steps 3 and 4. self.rorepo.working_dir, old_name, ] - pair_text = subprocess.check_output(cmdline, shell=False, text=True) # Run steps 3 and 4. + + # Run steps 3 and 4. + pair_text = subprocess.check_output(cmdline, shell=False, text=True) new_name = pair_text.split("=")[0] self.assertEqual(new_name, old_name) @@ -668,7 +674,7 @@ def test_successful_default_refresh_invalidates_cached_version_info(self): # as unintended shell expansions can occur, and is deprecated. Instead, # use a custom command, by setting the GIT_PYTHON_GIT_EXECUTABLE # environment variable to git.cmd or by passing git.cmd's full path to - # git.refresh. Or wrap the script with a .exe shim. + # git.refresh. Or wrap the script with a .exe shim.) stack.enter_context(mock.patch.object(Git, "USE_SHELL", True)) new_git = Git() diff --git a/test/test_index.py b/test/test_index.py index fd62bb893..fa64b82a2 100644 --- a/test/test_index.py +++ b/test/test_index.py @@ -219,8 +219,7 @@ def _fprogress(self, path, done, item): self._fprogress_map[path] = curval + 1 def _fprogress_add(self, path, done, item): - """Called as progress func - we keep track of the proper - call order""" + """Called as progress func - we keep track of the proper call order.""" assert item is not None self._fprogress(path, done, item) @@ -385,17 +384,17 @@ def test_index_merge_tree(self, rw_repo): # FAKE MERGE ############# - # Add a change with a NULL sha that should conflict with next_commit. We - # pretend there was a change, but we do not even bother adding a proper - # sha for it (which makes things faster of course). + # Add a change with a NULL sha that should conflict with next_commit. We pretend + # there was a change, but we do not even bother adding a proper sha for it + # (which makes things faster of course). manifest_fake_entry = BaseIndexEntry((manifest_entry[0], b"\0" * 20, 0, manifest_entry[3])) # Try write flag. self._assert_entries(rw_repo.index.add([manifest_fake_entry], write=False)) - # Add actually resolves the null-hex-sha for us as a feature, but we can - # edit the index manually. + # Add actually resolves the null-hex-sha for us as a feature, but we can edit + # the index manually. assert rw_repo.index.entries[manifest_key].binsha != Object.NULL_BIN_SHA - # We must operate on the same index for this! It's a bit problematic as - # it might confuse people. + # We must operate on the same index for this! It's a bit problematic as it might + # confuse people. index = rw_repo.index index.entries[manifest_key] = IndexEntry.from_base(manifest_fake_entry) index.write() @@ -404,19 +403,20 @@ def test_index_merge_tree(self, rw_repo): # Write an unchanged index (just for the fun of it). rw_repo.index.write() - # A three way merge would result in a conflict and fails as the command will - # not overwrite any entries in our index and hence leave them unmerged. This is + # A three way merge would result in a conflict and fails as the command will not + # overwrite any entries in our index and hence leave them unmerged. This is # mainly a protection feature as the current index is not yet in a tree. self.assertRaises(GitCommandError, index.merge_tree, next_commit, base=parent_commit) - # The only way to get the merged entries is to safe the current index away into a tree, - # which is like a temporary commit for us. This fails as well as the NULL sha does not - # have a corresponding object. + # The only way to get the merged entries is to safe the current index away into + # a tree, which is like a temporary commit for us. This fails as well as the + # NULL sha does not have a corresponding object. # NOTE: missing_ok is not a kwarg anymore, missing_ok is always true. # self.assertRaises(GitCommandError, index.write_tree) - # If missing objects are okay, this would work though (they are always okay now). - # As we can't read back the tree with NULL_SHA, we rather set it to something else. + # If missing objects are okay, this would work though (they are always okay + # now). As we can't read back the tree with NULL_SHA, we rather set it to + # something else. index.entries[manifest_key] = IndexEntry(manifest_entry[:1] + (hex_to_bin("f" * 40),) + manifest_entry[2:]) tree = index.write_tree() @@ -428,7 +428,7 @@ def test_index_merge_tree(self, rw_repo): @with_rw_repo("0.1.6") def test_index_file_diffing(self, rw_repo): - # Default Index instance points to our index. + # Default IndexFile instance points to our index. index = IndexFile(rw_repo) assert index.path is not None assert len(index.entries) @@ -439,8 +439,8 @@ def test_index_file_diffing(self, rw_repo): # Could sha it, or check stats. # Test diff. - # Resetting the head will leave the index in a different state, and the - # diff will yield a few changes. + # Resetting the head will leave the index in a different state, and the diff + # will yield a few changes. cur_head_commit = rw_repo.head.reference.commit rw_repo.head.reset("HEAD~6", index=True, working_tree=False) @@ -956,10 +956,10 @@ def test_index_new(self): @with_rw_repo("HEAD", bare=True) def test_index_bare_add(self, rw_bare_repo): - # Something is wrong after cloning to a bare repo, reading the - # property rw_bare_repo.working_tree_dir will return '/tmp' - # instead of throwing the Exception we are expecting. This is - # a quick hack to make this test fail when expected. + # Something is wrong after cloning to a bare repo, reading the property + # rw_bare_repo.working_tree_dir will return '/tmp' instead of throwing the + # Exception we are expecting. This is a quick hack to make this test fail when + # expected. assert rw_bare_repo.working_tree_dir is None assert rw_bare_repo.bare contents = b"This is a BytesIO file" @@ -984,7 +984,8 @@ def test_index_bare_add(self, rw_bare_repo): @with_rw_directory def test_add_utf8P_path(self, rw_dir): - # NOTE: fp is not a Unicode object in Python 2 (which is the source of the problem). + # NOTE: fp is not a Unicode object in Python 2 + # (which is the source of the problem). fp = osp.join(rw_dir, "ø.txt") with open(fp, "wb") as fs: fs.write("content of ø".encode("utf-8")) diff --git a/test/test_installation.py b/test/test_installation.py index 15ed5b13b..ae6472e98 100644 --- a/test/test_installation.py +++ b/test/test_installation.py @@ -46,9 +46,9 @@ def test_installation(self, rw_dir): msg=result.stderr or result.stdout or "Dependencies not installed", ) - # Even IF gitdb or any other dependency is supplied during development - # by inserting its location into PYTHONPATH or otherwise patched into - # sys.path, make sure it is not wrongly inserted as the *first* entry. + # Even IF gitdb or any other dependency is supplied during development by + # inserting its location into PYTHONPATH or otherwise patched into sys.path, + # make sure it is not wrongly inserted as the *first* entry. result = subprocess.run( [venv.python, "-c", "import sys; import git; print(sys.path)"], stdout=subprocess.PIPE, diff --git a/test/test_refs.py b/test/test_refs.py index 2656ceab1..28db70c6e 100644 --- a/test/test_refs.py +++ b/test/test_refs.py @@ -245,8 +245,8 @@ def test_head_reset(self, rw_repo): cur_head.reset(new_head_commit) rw_repo.index.checkout(["lib"], force=True) - # Now that we have a write write repo, change the HEAD reference - it's - # like "git-reset --soft". + # Now that we have a write write repo, change the HEAD reference - it's like + # "git-reset --soft". heads = rw_repo.heads assert heads for head in heads: @@ -349,8 +349,8 @@ def test_head_reset(self, rw_repo): for remote in remotes: refs = remote.refs - # If a HEAD exists, it must be deleted first. Otherwise it might - # end up pointing to an invalid ref it the ref was deleted before. + # If a HEAD exists, it must be deleted first. Otherwise it might end up + # pointing to an invalid ref it the ref was deleted before. remote_head_name = "HEAD" if remote_head_name in refs: RemoteReference.delete(rw_repo, refs[remote_head_name]) @@ -383,7 +383,7 @@ def test_head_reset(self, rw_repo): # Setting a non-commit as commit fails, but succeeds as object. head_tree = head.commit.tree self.assertRaises(ValueError, setattr, head, "commit", head_tree) - assert head.commit == old_commit # and the ref did not change + assert head.commit == old_commit # And the ref did not change. # We allow heads to point to any object. head.object = head_tree assert head.object == head_tree @@ -492,8 +492,8 @@ def test_head_reset(self, rw_repo): # Would raise if the symref wouldn't have been deleted (probably). symref = SymbolicReference.create(rw_repo, symref_path, cur_head.reference) - # Test symbolic references which are not at default locations like HEAD - # or FETCH_HEAD - they may also be at spots in refs of course. + # Test symbolic references which are not at default locations like HEAD or + # FETCH_HEAD - they may also be at spots in refs of course. symbol_ref_path = "refs/symbol_ref" symref = SymbolicReference(rw_repo, symbol_ref_path) assert symref.path == symbol_ref_path @@ -525,14 +525,13 @@ def test_head_reset(self, rw_repo): assert active_branch in heads assert rw_repo.tags - # We should be able to iterate all symbolic refs as well - in that case - # we should expect only symbolic references to be returned. + # We should be able to iterate all symbolic refs as well - in that case we + # should expect only symbolic references to be returned. for symref in SymbolicReference.iter_items(rw_repo): assert not symref.is_detached - # When iterating references, we can get references and symrefs - # when deleting all refs, I'd expect them to be gone! Even from - # the packed ones. + # When iterating references, we can get references and symrefs when deleting all + # refs, I'd expect them to be gone! Even from the packed ones. # For this to work, we must not be on any branch. rw_repo.head.reference = rw_repo.head.commit deleted_refs = set() @@ -577,9 +576,9 @@ def test_head_reset(self, rw_repo): self.assertRaises(ValueError, setattr, ref, "commit", "nonsense") assert not ref.is_valid() - # I am sure I had my reason to make it a class method at first, but - # now it doesn't make so much sense anymore, want an instance method as well - # See http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27 + # I am sure I had my reason to make it a class method at first, but now it + # doesn't make so much sense anymore, want an instance method as well. See: + # http://byronimo.lighthouseapp.com/projects/51787-gitpython/tickets/27 Reference.delete(ref.repo, ref.path) assert not ref.is_valid() @@ -619,8 +618,8 @@ def test_reflog(self): def test_refs_outside_repo(self): # Create a file containing a valid reference outside the repository. Attempting - # to access it should raise an exception, due to it containing a parent directory - # reference ('..'). This tests for CVE-2023-41040. + # to access it should raise an exception, due to it containing a parent + # directory reference ('..'). This tests for CVE-2023-41040. git_dir = Path(self.rorepo.git_dir) repo_parent_dir = git_dir.parent.parent with tempfile.NamedTemporaryFile(dir=repo_parent_dir) as ref_file: @@ -630,37 +629,52 @@ def test_refs_outside_repo(self): self.assertRaises(BadName, self.rorepo.commit, f"../../{ref_file_name}") def test_validity_ref_names(self): + """Ensure ref names are checked for validity. + + This is based on the rules specified in: + https://git-scm.com/docs/git-check-ref-format/#_description + """ check_ref = SymbolicReference._check_ref_name_valid - # Based on the rules specified in https://git-scm.com/docs/git-check-ref-format/#_description. + # Rule 1 self.assertRaises(ValueError, check_ref, ".ref/begins/with/dot") self.assertRaises(ValueError, check_ref, "ref/component/.begins/with/dot") self.assertRaises(ValueError, check_ref, "ref/ends/with/a.lock") self.assertRaises(ValueError, check_ref, "ref/component/ends.lock/with/period_lock") + # Rule 2 check_ref("valid_one_level_refname") + # Rule 3 self.assertRaises(ValueError, check_ref, "ref/contains/../double/period") + # Rule 4 for c in " ~^:": self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{c}/character") for code in range(0, 32): self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{chr(code)}/ASCII/control_character") self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{chr(127)}/ASCII/control_character") + # Rule 5 for c in "*?[": self.assertRaises(ValueError, check_ref, f"ref/contains/invalid{c}/character") + # Rule 6 self.assertRaises(ValueError, check_ref, "/ref/begins/with/slash") self.assertRaises(ValueError, check_ref, "ref/ends/with/slash/") self.assertRaises(ValueError, check_ref, "ref/contains//double/slash/") + # Rule 7 self.assertRaises(ValueError, check_ref, "ref/ends/with/dot.") + # Rule 8 self.assertRaises(ValueError, check_ref, "ref/contains@{/at_brace") + # Rule 9 self.assertRaises(ValueError, check_ref, "@") + # Rule 10 self.assertRaises(ValueError, check_ref, "ref/contain\\s/backslash") + # Valid reference name should not raise. check_ref("valid/ref/name") diff --git a/test/test_remote.py b/test/test_remote.py index df6034326..c0bd11f80 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -294,11 +294,11 @@ def get_info(res, remote, name): # Provoke to receive actual objects to see what kind of output we have to # expect. For that we need a remote transport protocol. - # Create a new UN-shared repo and fetch into it after we pushed a change - # to the shared repo. + # Create a new UN-shared repo and fetch into it after we pushed a change to the + # shared repo. other_repo_dir = tempfile.mktemp("other_repo") - # Must clone with a local path for the repo implementation not to freak out - # as it wants local paths only (which I can understand). + # Must clone with a local path for the repo implementation not to freak out as + # it wants local paths only (which I can understand). other_repo = remote_repo.clone(other_repo_dir, shared=False) remote_repo_url = osp.basename(remote_repo.git_dir) # git-daemon runs with appropriate `--base-path`. remote_repo_url = Git.polish_url("git://localhost:%s/%s" % (GIT_DAEMON_PORT, remote_repo_url)) @@ -317,10 +317,10 @@ def get_info(res, remote, name): self._commit_random_file(rw_repo) remote.push(rw_repo.head.reference) - # Here I would expect to see remote-information about packing - # objects and so on. Unfortunately, this does not happen - # if we are redirecting the output - git explicitly checks for this - # and only provides progress information to ttys. + # Here I would expect to see remote-information about packing objects and so + # on. Unfortunately, this does not happen if we are redirecting the output - + # git explicitly checks for this and only provides progress information to + # ttys. res = fetch_and_test(other_origin) finally: rmtree(other_repo_dir) @@ -333,8 +333,8 @@ def _assert_push_and_pull(self, remote, rw_repo, remote_repo): try: lhead.reference = rw_repo.heads.master except AttributeError: - # If the author is on a non-master branch, the clones might not have - # a local master yet. We simply create it. + # If the author is on a non-master branch, the clones might not have a local + # master yet. We simply create it. lhead.reference = rw_repo.create_head("master") # END master handling lhead.reset(remote.refs.master, working_tree=True) @@ -488,8 +488,8 @@ def test_base(self, rw_repo, remote_repo): self._assert_push_and_pull(remote, rw_repo, remote_repo) # FETCH TESTING - # Only for remotes - local cases are the same or less complicated - # as additional progress information will never be emitted. + # Only for remotes - local cases are the same or less complicated as + # additional progress information will never be emitted. if remote.name == "daemon_origin": self._do_test_fetch(remote, rw_repo, remote_repo, kill_after_timeout=10.0) ran_fetch_test = True @@ -508,7 +508,8 @@ def test_base(self, rw_repo, remote_repo): # Verify we can handle prunes when fetching. # stderr lines look like this: x [deleted] (none) -> origin/experiment-2012 # These should just be skipped. - # If we don't have a manual checkout, we can't actually assume there are any non-master branches. + # If we don't have a manual checkout, we can't actually assume there are any + # non-master branches. remote_repo.create_head("myone_for_deletion") # Get the branch - to be pruned later origin.fetch() @@ -812,8 +813,8 @@ def test_fetch_unsafe_url_allowed(self, rw_repo): "fd::17/foo", ] for url in urls: - # The URL will be allowed into the command, but the command will - # fail since we don't have that protocol enabled in the Git config file. + # The URL will be allowed into the command, but the command will fail + # since we don't have that protocol enabled in the Git config file. with self.assertRaises(GitCommandError): remote.fetch(url, allow_unsafe_protocols=True) assert not tmp_file.exists() @@ -880,8 +881,8 @@ def test_pull_unsafe_url_allowed(self, rw_repo): "fd::17/foo", ] for url in urls: - # The URL will be allowed into the command, but the command will - # fail since we don't have that protocol enabled in the Git config file. + # The URL will be allowed into the command, but the command will fail + # since we don't have that protocol enabled in the Git config file. with self.assertRaises(GitCommandError): remote.pull(url, allow_unsafe_protocols=True) assert not tmp_file.exists() @@ -948,8 +949,8 @@ def test_push_unsafe_url_allowed(self, rw_repo): "fd::17/foo", ] for url in urls: - # The URL will be allowed into the command, but the command will - # fail since we don't have that protocol enabled in the Git config file. + # The URL will be allowed into the command, but the command will fail + # since we don't have that protocol enabled in the Git config file. with self.assertRaises(GitCommandError): remote.push(url, allow_unsafe_protocols=True) assert not tmp_file.exists() diff --git a/test/test_repo.py b/test/test_repo.py index 465bb2574..30a44b6c1 100644 --- a/test/test_repo.py +++ b/test/test_repo.py @@ -543,8 +543,8 @@ def test_init(self): try: rmtree(clone_path) except OSError: - # When relative paths are used, the clone may actually be inside - # of the parent directory. + # When relative paths are used, the clone may actually be inside of + # the parent directory. pass # END exception handling @@ -556,8 +556,8 @@ def test_init(self): try: rmtree(clone_path) except OSError: - # When relative paths are used, the clone may actually be inside - # of the parent directory. + # When relative paths are used, the clone may actually be inside of + # the parent directory. pass # END exception handling @@ -832,8 +832,8 @@ def test_config_level_paths(self): assert self.rorepo._get_config_path(config_level) def test_creation_deletion(self): - # Just a very quick test to assure it generally works. There are - # specialized cases in the test_refs module. + # Just a very quick test to assure it generally works. There are specialized + # cases in the test_refs module. head = self.rorepo.create_head("new_head", "HEAD~1") self.rorepo.delete_head(head) @@ -1027,7 +1027,8 @@ def test_rev_parse(self): num_resolved += 1 except (BadName, BadObject): print("failed on %s" % path_section) - # This is fine if we have something like 112, which belongs to remotes/rname/merge-requests/112. + # This is fine if we have something like 112, which belongs to + # remotes/rname/merge-requests/112. # END exception handling # END for each token if ref_no == 3 - 1: @@ -1149,7 +1150,7 @@ def test_submodule_update(self, rwrepo): ) self.assertIsInstance(sm, Submodule) - # NOTE: the rest of this functionality is tested in test_submodule. + # NOTE: The rest of this functionality is tested in test_submodule. @with_rw_repo("HEAD") def test_git_file(self, rwrepo): @@ -1178,8 +1179,9 @@ def last_commit(repo, rev, path): # This is based on this comment: # https://github.com/gitpython-developers/GitPython/issues/60#issuecomment-23558741 # And we expect to set max handles to a low value, like 64. - # You should set ulimit -n X, see .travis.yml - # The loops below would easily create 500 handles if these would leak (4 pipes + multiple mapped files). + # You should set ulimit -n X. See .travis.yml. + # The loops below would easily create 500 handles if these would leak + # (4 pipes + multiple mapped files). for _ in range(64): for repo_type in (GitCmdObjectDB, GitDB): repo = Repo(self.rorepo.working_tree_dir, odbt=repo_type) @@ -1200,8 +1202,8 @@ def test_empty_repo(self, rw_dir): self.assertEqual(r.active_branch.name, "master") assert not r.active_branch.is_valid(), "Branch is yet to be born" - # Actually, when trying to create a new branch without a commit, git itself fails. - # We should, however, not fail ungracefully. + # Actually, when trying to create a new branch without a commit, git itself + # fails. We should, however, not fail ungracefully. self.assertRaises(BadName, r.create_head, "foo") self.assertRaises(BadName, r.create_head, "master") # It's expected to not be able to access a tree @@ -1315,13 +1317,13 @@ def test_git_work_tree_dotgit(self, rw_dir): repo = Repo(worktree_path) self.assertIsInstance(repo, Repo) - # This ensures we're able to actually read the refs in the tree, which - # means we can read commondir correctly. + # This ensures we're able to actually read the refs in the tree, which means we + # can read commondir correctly. commit = repo.head.commit self.assertIsInstance(commit, Object) - # This ensures we can read the remotes, which confirms we're reading - # the config correctly. + # This ensures we can read the remotes, which confirms we're reading the config + # correctly. origin = repo.remotes.origin self.assertIsInstance(origin, Remote) diff --git a/test/test_submodule.py b/test/test_submodule.py index 993f6b57e..68164729b 100644 --- a/test/test_submodule.py +++ b/test/test_submodule.py @@ -94,13 +94,15 @@ def _do_base_tests(self, rwrepo): # The module is not checked-out yet. self.assertRaises(InvalidGitRepositoryError, sm.module) - # ...which is why we can't get the branch either - it points into the module() repository. + # ...which is why we can't get the branch either - it points into the module() + # repository. self.assertRaises(InvalidGitRepositoryError, getattr, sm, "branch") # branch_path works, as it's just a string. assert isinstance(sm.branch_path, str) - # Some commits earlier we still have a submodule, but it's at a different commit. + # Some commits earlier we still have a submodule, but it's at a different + # commit. smold = next(Submodule.iter_items(rwrepo, self.k_subm_changed)) assert smold.binsha != sm.binsha assert smold != sm # the name changed @@ -141,11 +143,12 @@ def _do_base_tests(self, rwrepo): smold.set_parent_commit(self.k_subm_changed + "~1") assert smold.binsha != sm.binsha - # Raises if the sm didn't exist in new parent - it keeps its - # parent_commit unchanged. + # Raises if the sm didn't exist in new parent - it keeps its parent_commit + # unchanged. self.assertRaises(ValueError, smold.set_parent_commit, self.k_no_subm_tag) - # TEST TODO: If a path is in the .gitmodules file, but not in the index, it raises. + # TODO: Test that, if a path is in the .gitmodules file, but not in the index, + # then it raises. # TEST UPDATE ############## @@ -196,8 +199,8 @@ def _do_base_tests(self, rwrepo): # INTERLEAVE ADD TEST ##################### - # url must match the one in the existing repository (if submodule name suggests a new one) - # or we raise. + # url must match the one in the existing repository (if submodule name + # suggests a new one) or we raise. self.assertRaises( ValueError, Submodule.add, @@ -228,7 +231,8 @@ def _do_base_tests(self, rwrepo): assert not csm.module_exists() csm_repopath = csm.path - # Adjust the path of the submodules module to point to the local destination. + # Adjust the path of the submodules module to point to the local + # destination. new_csmclone_path = Git.polish_url(osp.join(self.rorepo.working_tree_dir, sm.path, csm.path)) with csm.config_writer() as writer: writer.set_value("url", new_csmclone_path) @@ -249,7 +253,8 @@ def _do_base_tests(self, rwrepo): # This flushed in a sub-submodule. assert len(list(rwrepo.iter_submodules())) == 2 - # Reset both heads to the previous version, verify that to_latest_revision works. + # Reset both heads to the previous version, verify that to_latest_revision + # works. smods = (sm.module(), csm.module()) for repo in smods: repo.head.reset("HEAD~2", working_tree=1) @@ -296,8 +301,8 @@ def _do_base_tests(self, rwrepo): # Must delete something. self.assertRaises(ValueError, csm.remove, module=False, configuration=False) - # module() is supposed to point to gitdb, which has a child-submodule whose URL is still pointing - # to GitHub. To save time, we will change it to: + # module() is supposed to point to gitdb, which has a child-submodule whose + # URL is still pointing to GitHub. To save time, we will change it to: csm.set_parent_commit(csm.repo.head.commit) with csm.config_writer() as cw: cw.set_value("url", self._small_repo_url()) @@ -399,8 +404,8 @@ def _do_base_tests(self, rwrepo): rwrepo.index.commit("my submod commit") assert len(rwrepo.submodules) == 2 - # Needs update, as the head changed. It thinks it's in the history - # of the repo otherwise. + # Needs update, as the head changed. + # It thinks it's in the history of the repo otherwise. nsm.set_parent_commit(rwrepo.head.commit) osm.set_parent_commit(rwrepo.head.commit) @@ -434,7 +439,8 @@ def _do_base_tests(self, rwrepo): # REMOVE 'EM ALL ################ - # If a submodule's repo has no remotes, it can't be added without an explicit url. + # If a submodule's repo has no remotes, it can't be added without an + # explicit url. osmod = osm.module() osm.remove(module=False) @@ -510,7 +516,8 @@ def test_root_module(self, rwrepo): # TEST UPDATE ############# - # Set up a commit that removes existing, adds new and modifies existing submodules. + # Set up a commit that removes existing, adds new and modifies existing + # submodules. rm = RootModule(rwrepo) assert len(rm.children()) == 1 @@ -534,13 +541,15 @@ def test_root_module(self, rwrepo): sm.update(recursive=False) assert sm.module_exists() with sm.config_writer() as writer: - writer.set_value("path", fp) # Change path to something with prefix AFTER url change. + # Change path to something with prefix AFTER url change. + writer.set_value("path", fp) - # Update doesn't fail, because list_items ignores the wrong path in such situations. + # Update doesn't fail, because list_items ignores the wrong path in such + # situations. rm.update(recursive=False) - # Move it properly - doesn't work as it its path currently points to an indexentry - # which doesn't exist (move it to some path, it doesn't matter here). + # Move it properly - doesn't work as it its path currently points to an + # indexentry which doesn't exist (move it to some path, it doesn't matter here). self.assertRaises(InvalidGitRepositoryError, sm.move, pp) # Reset the path(cache) to where it was, now it works. sm.path = prep @@ -588,23 +597,27 @@ def test_root_module(self, rwrepo): rm.update(recursive=False, dry_run=True, force_remove=True) assert osp.isdir(smp) - # When removing submodules, we may get new commits as nested submodules are auto-committing changes - # to allow deletions without force, as the index would be dirty otherwise. + # When removing submodules, we may get new commits as nested submodules are + # auto-committing changes to allow deletions without force, as the index would + # be dirty otherwise. # QUESTION: Why does this seem to work in test_git_submodule_compatibility() ? self.assertRaises(InvalidGitRepositoryError, rm.update, recursive=False, force_remove=False) rm.update(recursive=False, force_remove=True) assert not osp.isdir(smp) - # 'Apply work' to the nested submodule and ensure this is not removed/altered during updates - # Need to commit first, otherwise submodule.update wouldn't have a reason to change the head. + # 'Apply work' to the nested submodule and ensure this is not removed/altered + # during updates. We need to commit first, otherwise submodule.update wouldn't + # have a reason to change the head. touch(osp.join(nsm.module().working_tree_dir, "new-file")) - # We cannot expect is_dirty to even run as we wouldn't reset a head to the same location. + # We cannot expect is_dirty to even run as we wouldn't reset a head to the same + # location. assert nsm.module().head.commit.hexsha == nsm.hexsha nsm.module().index.add([nsm]) nsm.module().index.commit("added new file") rm.update(recursive=False, dry_run=True, progress=prog) # Would not change head, and thus doesn't fail. - # Everything we can do from now on will trigger the 'future' check, so no is_dirty() check will even run. - # This would only run if our local branch is in the past and we have uncommitted changes. + # Everything we can do from now on will trigger the 'future' check, so no + # is_dirty() check will even run. This would only run if our local branch is in + # the past and we have uncommitted changes. prev_commit = nsm.module().head.commit rm.update(recursive=False, dry_run=False, progress=prog) @@ -616,8 +629,8 @@ def test_root_module(self, rwrepo): # Change url... # ============= - # ...to the first repository. This way we have a fast checkout, and a completely different - # repository at the different url. + # ...to the first repository. This way we have a fast checkout, and a completely + # different repository at the different url. nsm.set_parent_commit(csmremoved) nsmurl = Git.polish_url(osp.join(self.rorepo.working_tree_dir, rsmsp[0])) with nsm.config_writer() as writer: @@ -637,16 +650,15 @@ def test_root_module(self, rwrepo): assert len(rwrepo.submodules) == 1 assert not rwrepo.submodules[0].children()[0].module_exists(), "nested submodule should not be checked out" - # Add the submodule's changed commit to the index, which is what the - # user would do. - # Beforehand, update our instance's binsha with the new one. + # Add the submodule's changed commit to the index, which is what the user would + # do. Beforehand, update our instance's binsha with the new one. nsm.binsha = nsm.module().head.commit.binsha rwrepo.index.add([nsm]) # Change branch. # ============== - # We only have one branch, so we switch to a virtual one, and back - # to the current one to trigger the difference. + # We only have one branch, so we switch to a virtual one, and back to the + # current one to trigger the difference. cur_branch = nsm.branch nsmm = nsm.module() prev_commit = nsmm.head.commit @@ -808,8 +820,8 @@ def test_git_submodules_and_add_sm_with_new_commit(self, rwdir): smm.git.add(Git.polish_url(fp)) smm.git.commit(m="new file added") - # Submodules are retrieved from the current commit's tree, therefore we can't really get a new submodule - # object pointing to the new submodule commit. + # Submodules are retrieved from the current commit's tree, therefore we can't + # really get a new submodule object pointing to the new submodule commit. sm_too = parent.submodules["module_moved"] assert parent.head.commit.tree[sm.path].binsha == sm.binsha assert sm_too.binsha == sm.binsha, "cached submodule should point to the same commit as updated one" @@ -848,8 +860,9 @@ def assert_exists(sm, value=True): # END assert_exists - # As git is backwards compatible itself, it would still recognize what we do here... unless we really - # muss it up. That's the only reason why the test is still here... + # As git is backwards compatible itself, it would still recognize what we do + # here... unless we really muss it up. That's the only reason why the test is + # still here... assert len(parent.git.submodule().splitlines()) == 1 module_repo_path = osp.join(sm.module().working_tree_dir, ".git") @@ -885,7 +898,8 @@ def assert_exists(sm, value=True): assert_exists(csm) # Rename nested submodule. - # This name would move itself one level deeper - needs special handling internally. + # This name would move itself one level deeper - needs special handling + # internally. new_name = csm.name + "/mine" assert csm.rename(new_name).name == new_name assert_exists(csm) @@ -1011,13 +1025,15 @@ def test_branch_renames(self, rw_dir): sm_source_repo.index.add([new_file]) sm.repo.index.commit("added new file") - # Change designated submodule checkout branch to the new upstream feature branch. + # Change designated submodule checkout branch to the new upstream feature + # branch. with sm.config_writer() as smcw: smcw.set_value("branch", sm_fb.name) assert sm.repo.is_dirty(index=True, working_tree=False) sm.repo.index.commit("changed submodule branch to '%s'" % sm_fb) - # Verify submodule update with feature branch that leaves currently checked out branch in it's past. + # Verify submodule update with feature branch that leaves currently checked out + # branch in it's past. sm_mod = sm.module() prev_commit = sm_mod.commit() assert sm_mod.head.ref.name == "master" @@ -1029,7 +1045,8 @@ def test_branch_renames(self, rw_dir): assert sm_mod.head.ref.name == sm_fb.name assert sm_mod.commit() == sm_fb.commit - # Create new branch which is in our past, and thus seemingly unrelated to the currently checked out one. + # Create new branch which is in our past, and thus seemingly unrelated to the + # currently checked out one. # To make it even 'harder', we shall fork and create a new commit. sm_pfb = sm_source_repo.create_head("past-feature", commit="HEAD~20") sm_pfb.checkout() @@ -1043,8 +1060,8 @@ def test_branch_renames(self, rw_dir): # Test submodule updates - must fail if submodule is dirty. touch(osp.join(sm_mod.working_tree_dir, "unstaged file")) - # This doesn't fail as our own submodule binsha didn't change, and the reset is only triggered if - # to_latest_revision is True. + # This doesn't fail as our own submodule binsha didn't change, and the reset is + # only triggered if to_latest_revision is True. parent_repo.submodule_update(to_latest_revision=False) assert sm_mod.head.ref.name == sm_pfb.name, "should have been switched to past head" assert sm_mod.commit() == sm_fb.commit, "Head wasn't reset" @@ -1184,8 +1201,8 @@ def test_submodule_add_unsafe_url_allowed(self, rw_repo): "fd::/foo", ] for url in urls: - # The URL will be allowed into the command, but the command will - # fail since we don't have that protocol enabled in the Git config file. + # The URL will be allowed into the command, but the command will fail + # since we don't have that protocol enabled in the Git config file. with self.assertRaises(GitCommandError): Submodule.add(rw_repo, "new", "new", url, allow_unsafe_protocols=True) assert not tmp_file.exists() @@ -1269,8 +1286,8 @@ def test_submodule_update_unsafe_url_allowed(self, rw_repo): ] for url in urls: submodule = Submodule(rw_repo, b"\0" * 20, name="new", path="new", url=url) - # The URL will be allowed into the command, but the command will - # fail since we don't have that protocol enabled in the Git config file. + # The URL will be allowed into the command, but the command will fail + # since we don't have that protocol enabled in the Git config file. with self.assertRaises(GitCommandError): submodule.update(allow_unsafe_protocols=True) assert not tmp_file.exists() diff --git a/test/test_util.py b/test/test_util.py index 65f77082d..824b3ab3d 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -153,7 +153,8 @@ def _patch_for_wrapping_test(self, mocker, hide_windows_known_errors): reason="PermissionError is only ever wrapped on Windows", ) def test_wraps_perm_error_if_enabled(self, mocker, permission_error_tmpdir): - """rmtree wraps PermissionError on Windows when HIDE_WINDOWS_KNOWN_ERRORS is true.""" + """rmtree wraps PermissionError on Windows when HIDE_WINDOWS_KNOWN_ERRORS is + true.""" self._patch_for_wrapping_test(mocker, True) with pytest.raises(SkipTest): @@ -171,7 +172,8 @@ def test_wraps_perm_error_if_enabled(self, mocker, permission_error_tmpdir): ], ) def test_does_not_wrap_perm_error_unless_enabled(self, mocker, permission_error_tmpdir, hide_windows_known_errors): - """rmtree does not wrap PermissionError on non-Windows systems or when HIDE_WINDOWS_KNOWN_ERRORS is false.""" + """rmtree does not wrap PermissionError on non-Windows systems or when + HIDE_WINDOWS_KNOWN_ERRORS is false.""" self._patch_for_wrapping_test(mocker, hide_windows_known_errors) with pytest.raises(PermissionError): @@ -182,7 +184,9 @@ def test_does_not_wrap_perm_error_unless_enabled(self, mocker, permission_error_ @pytest.mark.parametrize("hide_windows_known_errors", [False, True]) def test_does_not_wrap_other_errors(self, tmp_path, mocker, hide_windows_known_errors): - file_not_found_tmpdir = tmp_path / "testdir" # It is deliberately never created. + # The file is deliberately never created. + file_not_found_tmpdir = tmp_path / "testdir" + self._patch_for_wrapping_test(mocker, hide_windows_known_errors) with pytest.raises(FileNotFoundError): @@ -502,7 +506,8 @@ def test_actor_get_uid_laziness_called(self, mock_get_uid): committer = Actor.committer(None) author = Actor.author(None) # We can't test with `self.rorepo.config_reader()` here, as the UUID laziness - # depends on whether the user running the test has their global user.name config set. + # depends on whether the user running the test has their global user.name config + # set. self.assertEqual(committer.name, "user") self.assertTrue(committer.email.startswith("user@")) self.assertEqual(author.name, "user") From 4b04d8a33371d655a3cb5416ba18215fa08e0edd Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 21:34:30 -0500 Subject: [PATCH 51/61] Better clarify Submodule.branch_path documentation This revisits the changes in 3813bfb and takes a different approach, using the phrase "full repository-relative path" that is used elsewhere in the documentation and seems clear in context. Although this brings back the potential confusion around having the terms "full" and "relative" used together to describe a path, this may no longer be an issue now that the phrase "repository-relative" is used here as it is elsewhere. (In particular, see the SymbolicReference.to_full_path docstring.) --- git/objects/submodule/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index dc1deee36..6379d1500 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -134,7 +134,7 @@ def __init__( The URL to the remote repository which is the submodule. :param branch_path: - Complete relative path to ref to checkout when cloning the remote + Full repository-relative path to ref to checkout when cloning the remote repository. """ super().__init__(repo, binsha, mode, path) @@ -1473,8 +1473,8 @@ def branch(self) -> "Head": def branch_path(self) -> PathLike: """ :return: - Complete relative path as string to the branch we would checkout from the - remote and track + Full repository-relative path as string to the branch we would checkout from + the remote and track """ return self._branch_path From 254c82a00a3a6d141cfb6df7fdb8f33a23d4ebcb Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 21:58:22 -0500 Subject: [PATCH 52/61] More docstring revisions within git.refs --- git/refs/head.py | 32 +++++++++++++++---------------- git/refs/log.py | 8 ++++---- git/refs/reference.py | 7 ++++--- git/refs/remote.py | 2 +- git/refs/symbolic.py | 44 +++++++++++++++++++++---------------------- 5 files changed, 47 insertions(+), 46 deletions(-) diff --git a/git/refs/head.py b/git/refs/head.py index a051748ba..d546cb4ef 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -36,8 +36,8 @@ def strip_quotes(string: str) -> str: class HEAD(SymbolicReference): - """Special case of a SymbolicReference representing the repository's - HEAD reference.""" + """Special case of a SymbolicReference representing the repository's HEAD + reference.""" _HEAD_NAME = "HEAD" _ORIG_HEAD_NAME = "ORIG_HEAD" @@ -66,22 +66,21 @@ def reset( paths: Union[PathLike, Sequence[PathLike], None] = None, **kwargs: Any, ) -> "HEAD": - """Reset our HEAD to the given commit optionally synchronizing - the index and working tree. The reference we refer to will be set to commit as - well. + """Reset our HEAD to the given commit optionally synchronizing the index and + working tree. The reference we refer to will be set to commit as well. :param commit: :class:`~git.objects.commit.Commit`, :class:`~git.refs.reference.Reference`, or string identifying a revision we should reset HEAD to. :param index: - If True, the index will be set to match the given commit. + If ``True``, the index will be set to match the given commit. Otherwise it will not be touched. :param working_tree: - If True, the working tree will be forcefully adjusted to match the given + If ``True``, the working tree will be forcefully adjusted to match the given commit, possibly overwriting uncommitted changes without warning. - If `working_tree` is True, `index` must be True as well. + If `working_tree` is ``True``, `index` must be ``True`` as well. :param paths: Single path or list of paths relative to the git root directory @@ -90,7 +89,8 @@ def reset( :param kwargs: Additional arguments passed to ``git reset``. - :return: self + :return: + self """ mode: Union[str, None] mode = "--soft" @@ -151,8 +151,8 @@ def delete(cls, repo: "Repo", *heads: "Union[Head, str]", force: bool = False, * """Delete the given heads. :param force: - If True, the heads will be deleted even if they are not yet merged into the - main development stream. Default False. + If ``True``, the heads will be deleted even if they are not yet merged into + the main development stream. Default ``False``. """ flag = "-d" if force: @@ -193,7 +193,7 @@ def set_tracking_branch(self, remote_reference: Union["RemoteReference", None]) def tracking_branch(self) -> Union["RemoteReference", None]: """ :return: - The remote reference we are tracking, or None if we are not a tracking + The remote reference we are tracking, or ``None`` if we are not a tracking branch. """ from .remote import RemoteReference @@ -219,14 +219,14 @@ def rename(self, new_path: PathLike, force: bool = False) -> "Head": The prefix ``refs/heads`` is implied. :param force: - If True, the rename will succeed even if a head with the target name + If ``True``, the rename will succeed even if a head with the target name already exists. :return: self :note: - Respects the ref log as git commands are used. + Respects the ref log, as git commands are used. """ flag = "-m" if force: @@ -244,8 +244,8 @@ def checkout(self, force: bool = False, **kwargs: Any) -> Union["HEAD", "Head"]: The command will fail if changed working tree files would be overwritten. :param force: - If True, changes to the index and the working tree will be discarded. - If False, :class:`~git.exc.GitCommandError` will be raised in that + If ``True``, changes to the index and the working tree will be discarded. + If ``False``, :class:`~git.exc.GitCommandError` will be raised in that situation. :param kwargs: diff --git a/git/refs/log.py b/git/refs/log.py index 260f2fff5..7aefeb4e6 100644 --- a/git/refs/log.py +++ b/git/refs/log.py @@ -104,7 +104,7 @@ def new( tz_offset: int, message: str, ) -> "RefLogEntry": # skipcq: PYL-W0621 - """:return: New instance of a RefLogEntry""" + """:return: New instance of a :class:`RefLogEntry`""" if not isinstance(actor, Actor): raise ValueError("Need actor instance, got %s" % actor) # END check types @@ -112,7 +112,7 @@ def new( @classmethod def from_line(cls, line: bytes) -> "RefLogEntry": - """:return: New RefLogEntry instance from the given revlog line. + """:return: New :class:`RefLogEntry` instance from the given revlog line. :param line: Line bytes without trailing newline @@ -311,7 +311,7 @@ def append_entry( :param config_reader: Configuration reader of the repository - used to obtain user information. May also be an :class:`~git.util.Actor` instance identifying the committer - directly or None. + directly or ``None``. :param filepath: Full path to the log file. @@ -326,7 +326,7 @@ def append_entry( Message describing the change to the reference. :param write: - If True, the changes will be written right away. + If ``True``, the changes will be written right away. Otherwise the change will not be written. :return: diff --git a/git/refs/reference.py b/git/refs/reference.py index 32547278f..d1a9f3c54 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -65,7 +65,7 @@ def __init__(self, repo: "Repo", path: PathLike, check_path: bool = True) -> Non e.g. ``refs/heads/master``. :param check_path: - If False, you can provide any path. + If ``False``, you can provide any path. Otherwise the path must start with the default path prefix of this type. """ if check_path and not str(path).startswith(self._common_path_default + "/"): @@ -141,8 +141,9 @@ def iter_items( *args: Any, **kwargs: Any, ) -> Iterator[T_References]: - """Equivalent to SymbolicReference.iter_items, but will return non-detached - references as well.""" + """Equivalent to + :meth:`SymbolicReference.iter_items `, + but will return non-detached references as well.""" return cls._iter_items(repo, common_path) # }END interface diff --git a/git/refs/remote.py b/git/refs/remote.py index c2c2c1aac..bb2a4e438 100644 --- a/git/refs/remote.py +++ b/git/refs/remote.py @@ -56,7 +56,7 @@ def delete(cls, repo: "Repo", *refs: "RemoteReference", **kwargs: Any) -> None: """Delete the given remote references. :note: - kwargs are given for comparability with the base class method as we + `kwargs` are given for comparability with the base class method as we should not narrow the signature. """ repo.git.branch("-d", "-r", *refs) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 6b9fd9ab7..70af4615d 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -63,7 +63,7 @@ class SymbolicReference: This does not point to a specific commit, but to another :class:`~git.refs.head.Head`, which itself specifies a commit. - A typical example for a symbolic reference is ``HEAD``. + A typical example for a symbolic reference is :class:`~git.refs.head.HEAD`. """ __slots__ = ("repo", "path") @@ -115,8 +115,8 @@ def _get_packed_refs_path(cls, repo: "Repo") -> str: @classmethod def _iter_packed_refs(cls, repo: "Repo") -> Iterator[Tuple[str, str]]: - """Return an iterator yielding pairs of sha1/path pairs (as strings) - for the corresponding refs. + """Return an iterator yielding pairs of sha1/path pairs (as strings) for the + corresponding refs. :note: The packed refs file will be kept open as long as we iterate. @@ -226,9 +226,9 @@ def _get_ref_info_helper( """ :return: (str(sha), str(target_ref_path)) if available, the sha the file at rela_path - points to, or None. + points to, or ``None``. - target_ref_path is the reference we point to, or None. + target_ref_path is the reference we point to, or ``None``. """ if ref_path: cls._check_ref_name_valid(ref_path) @@ -273,7 +273,7 @@ def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[T :return: (str(sha), str(target_ref_path)) if available, the sha the file at rela_path points to, or None. - target_ref_path is the reference we point to, or None. + target_ref_path is the reference we point to, or ``None``. """ return cls._get_ref_info_helper(repo, ref_path) @@ -313,7 +313,7 @@ def set_commit( :class:`~git.objects.commit.Commit`. :raise ValueError: - If `commit` is not a :class:`~git.objects.commit.Commit` object or doesn't + If `commit` is not a :class:`~git.objects.commit.Commit` object, nor does it point to a commit. :return: @@ -357,7 +357,7 @@ def set_object( to. :param logmsg: - If not None, the message will be used in the reflog entry to be written. + If not ``None``, the message will be used in the reflog entry to be written. Otherwise the reflog is not altered. :note: @@ -491,8 +491,8 @@ def set_reference( def is_valid(self) -> bool: """ :return: - True if the reference is valid, hence it can be read and points to a valid - object or reference. + ``True`` if the reference is valid, hence it can be read and points to a + valid object or reference. """ try: self.object @@ -505,7 +505,7 @@ def is_valid(self) -> bool: def is_detached(self) -> bool: """ :return: - True if we are a detached reference, hence we point to a specific commit + ``True`` if we are a detached reference, hence we point to a specific commit instead to another reference. """ try: @@ -565,7 +565,7 @@ def log_append( def log_entry(self, index: int) -> "RefLogEntry": """ :return: - RefLogEntry at the given index + :class:`~git.refs.log.RefLogEntry` at the given index :param index: Python list compatible positive or negative index. @@ -582,7 +582,7 @@ def to_full_path(cls, path: Union[PathLike, "SymbolicReference"]) -> PathLike: """ :return: String with a full repository-relative path which can be used to initialize - a Reference instance, for instance by using + a :class:`~git.refs.reference.Reference` instance, for instance by using :meth:`Reference.from_path `. """ if isinstance(path, SymbolicReference): @@ -666,7 +666,7 @@ def _create( ) -> T_References: """Internal method used to create a new symbolic reference. - If `resolve` is False, the reference will be taken as is, creating a proper + If `resolve` is ``False``, the reference will be taken as is, creating a proper symbolic reference. Otherwise it will be resolved to the corresponding object and a detached symbolic reference will be created instead. """ @@ -722,12 +722,12 @@ def create( If it is a commit-ish, the symbolic ref will be detached. :param force: - If True, force creation even if a symbolic reference with that name already - exists. Raise :class:`OSError` otherwise. + If ``True``, force creation even if a symbolic reference with that name + already exists. Raise :class:`OSError` otherwise. :param logmsg: - If not None, the message to append to the reflog. - If None, no reflog entry is written. + If not ``None``, the message to append to the reflog. + If ``None``, no reflog entry is written. :return: Newly created symbolic reference @@ -751,8 +751,8 @@ def rename(self, new_path: PathLike, force: bool = False) -> "SymbolicReference" In case this is a symbolic ref, there is no implied prefix. :param force: - If True, the rename will succeed even if a head with the target name already - exists. It will be overwritten in that case. + If ``True``, the rename will succeed even if a head with the target name + already exists. It will be overwritten in that case. :return: self @@ -847,8 +847,8 @@ def iter_items( :param common_path: Optional keyword argument to the path which is to be shared by all returned Ref objects. - Defaults to class specific portion if None, ensuring that only refs suitable - for the actual class are returned. + Defaults to class specific portion if ``None``, ensuring that only refs + suitable for the actual class are returned. :return: A list of :class:`SymbolicReference`, each guaranteed to be a symbolic ref From 679d2e87b4a4c758a1059854f03fb6b4b2fd28d3 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 22:01:26 -0500 Subject: [PATCH 53/61] Fix exception type in require_remote_ref_path docstring The require_remote_ref_path decorator has always used ValueError (ever since its introduction in a92ab80) but was documented as using TypeError. ValueError is the correct exception to raise here, since this is not any kind of type error or related condition. So the bug wasn't in the function's behavior, but instead in the way that behavior was documented. (The bug was fairly minor, since the function is not listed in __all__.) --- git/refs/reference.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/refs/reference.py b/git/refs/reference.py index d1a9f3c54..a7b545fed 100644 --- a/git/refs/reference.py +++ b/git/refs/reference.py @@ -25,7 +25,7 @@ def require_remote_ref_path(func: Callable[..., _T]) -> Callable[..., _T]: - """A decorator raising :class:`TypeError` if we are not a valid remote, based on the + """A decorator raising :class:`ValueError` if we are not a valid remote, based on the path.""" def wrapper(self: T_References, *args: Any) -> _T: From ee0301ab69e4f2af1248596a9efb897893c54da1 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 23:13:54 -0500 Subject: [PATCH 54/61] More docstring revisions in second-level modules and git.__init__ --- git/__init__.py | 2 +- git/cmd.py | 36 +++++++++++++++++++----------------- git/config.py | 11 ++++------- git/db.py | 3 ++- git/diff.py | 17 +++++++++-------- git/remote.py | 39 +++++++++++++++++++++------------------ git/util.py | 6 +++--- 7 files changed, 59 insertions(+), 55 deletions(-) diff --git a/git/__init__.py b/git/__init__.py index 6fc6110f4..ed8a88d4b 100644 --- a/git/__init__.py +++ b/git/__init__.py @@ -127,7 +127,7 @@ def refresh(path: Optional[PathLike] = None) -> None: immediately, relative to the current directory. :note: - The *path* parameter is usually omitted and cannot be used to specify a custom + 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. diff --git a/git/cmd.py b/git/cmd.py index d0c76eafe..949814765 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -106,7 +106,7 @@ def handle_process_output( decode_streams: bool = True, kill_after_timeout: Union[None, float] = None, ) -> None: - """Register for notifications to learn that process output is ready to read, and + R"""Register for notifications to learn that process output is ready to read, and dispatch lines to the respective line handlers. This function returns once the finalizer returns. @@ -126,8 +126,11 @@ def handle_process_output( :param decode_streams: 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). + + This defaults to ``True``. Set it to ``False``: + + - if ``universal_newlines == True``, as then streams are in text mode, or + - if decoding must happen later, such as for :class:`~git.diff.Diff`\s. :param kill_after_timeout: :class:`float` or ``None``, Default = ``None`` @@ -379,15 +382,14 @@ def __setstate__(self, d: Dict[str, Any]) -> None: :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`. + ``__init__``. It can also be changed by explicitly calling :func:`git.refresh`. """ _refresh_token = object() # Since None would match an initial _version_info_token. @classmethod def refresh(cls, path: Union[None, PathLike] = None) -> bool: - """This gets called by the refresh function (see the top level __init__). + """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 @@ -868,8 +870,7 @@ def __init__(self, working_dir: Union[None, PathLike] = None): self.cat_file_all: Union[None, TBD] = None def __getattr__(self, name: str) -> Any: - """A convenience method as it allows to call the command as if it was - an object. + """A convenience method as it allows to call the command as if it was an object. :return: Callable object that will execute call :meth:`_call_process` with your @@ -899,7 +900,7 @@ def working_dir(self) -> Union[None, PathLike]: @property def version_info(self) -> Tuple[int, ...]: """ - :return: tuple with integers representing the major, minor and additional + :return: Tuple with integers representing the major, minor and additional version numbers as parsed from ``git version``. Up to four fields are used. This value is generated on demand and is cached. @@ -1021,7 +1022,7 @@ def execute( :param output_stream: If set to a file-like object, data produced by the git command will be copied to the given stream instead of being returned as a string. - This feature only has any effect if `as_process` is False. + This feature only has any effect if `as_process` is ``False``. :param stdout_as_string: If ``False``, the command's standard output will be bytes. Otherwise, it @@ -1030,10 +1031,10 @@ def execute( :param kill_after_timeout: Specifies a timeout in seconds for the git command, after which the process - should be killed. This will have no effect if `as_process` is set to True. - It is set to None by default and will let the process run until the timeout - is explicitly specified. Uses of this feature should be carefully - considered, due to the following limitations: + should be killed. This will have no effect if `as_process` is set to + ``True``. It is set to ``None`` by default and will let the process run + until the timeout is explicitly specified. Uses of this feature should be + carefully considered, due to the following limitations: 1. This feature is not supported at all on Windows. 2. Effectiveness may vary by operating system. ``ps --ppid`` is used to @@ -1099,7 +1100,7 @@ def execute( :note: If you add additional keyword arguments to the signature of this method, - you must update the execute_kwargs tuple housed in this module. + you must update the ``execute_kwargs`` variable housed in this module. """ # Remove password for the command if present. redacted_command = remove_password_if_present(command) @@ -1420,9 +1421,10 @@ def _call_process( :param kwargs: Contains key-values for the following: - - The :meth:`execute()` kwds, as listed in :var:`execute_kwargs`. + - The :meth:`execute()` kwds, as listed in ``execute_kwargs``. - "Command options" to be converted by :meth:`transform_kwargs`. - - The ``insert_kwargs_after`` key which its value must match one of ``*args``. + - 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. diff --git a/git/config.py b/git/config.py index ff5c9d564..b358ee417 100644 --- a/git/config.py +++ b/git/config.py @@ -411,7 +411,7 @@ def release(self) -> None: not be used anymore afterwards. In Python 3, it's required to explicitly release locks and flush changes, as - :meth:`__del__` is not called deterministically anymore. + ``__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. @@ -539,7 +539,7 @@ 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)``. + The list of paths, where each path is a tuple of (option, value). """ paths = [] @@ -591,9 +591,6 @@ 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. """ @@ -765,7 +762,7 @@ def add_section(self, section: str) -> None: @property def read_only(self) -> bool: - """:return: True if this instance may change the configuration file""" + """:return: ``True`` if this instance may change the configuration file""" return self._read_only # FIXME: Figure out if default or return type can really include bool. @@ -918,7 +915,7 @@ def add_value(self, section: str, option: str, value: Union[str, bytes, int, flo return self def rename_section(self, section: str, new_name: str) -> "GitConfigParser": - """Rename the given section to new_name. + """Rename the given section to `new_name`. :raise ValueError: If: diff --git a/git/db.py b/git/db.py index eb7f758da..3f496a979 100644 --- a/git/db.py +++ b/git/db.py @@ -54,7 +54,8 @@ def stream(self, binsha: bytes) -> OStream: def partial_to_complete_sha_hex(self, partial_hexsha: str) -> bytes: """ - :return: Full binary 20 byte sha from the given partial hexsha + :return: + Full binary 20 byte sha from the given partial hexsha :raise gitdb.exc.AmbiguousObjectName: diff --git a/git/diff.py b/git/diff.py index 3c760651b..adddaa7f6 100644 --- a/git/diff.py +++ b/git/diff.py @@ -121,9 +121,9 @@ def diff( * If ``None``, we will be compared to the working tree. * If :class:`~git.index.base.Treeish`, it will be compared against the respective tree. - * If :class:`~Diffable.Index`, it will be compared against the index. + * If :class:`Diffable.Index`, it will be compared against the index. * If :attr:`git.NULL_TREE`, it will compare against the empty tree. - * It defaults to :class:`~Diffable.Index` so that the method will not by + * It defaults to :class:`Diffable.Index` so that the method will not by default fail on bare repositories. :param paths: @@ -280,11 +280,11 @@ class Diff: 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. - The path will be available, though. + as a corresponding object does not yet exist. The mode will be null as well. The + path will be available, though. - If it is listed in a diff, the working tree version of the file must - differ from the version in the index or tree, and hence has been modified. + If it is listed in a diff, the working tree version of the file must differ from + the version in the index or tree, and hence has been modified. """ # Precompiled regex. @@ -468,7 +468,8 @@ def rename_to(self) -> Optional[str]: @property def renamed(self) -> bool: - """ + """Deprecated, use :attr:`renamed_file` instead. + :return: ``True`` if the blob of our diff has been renamed @@ -480,7 +481,7 @@ def renamed(self) -> bool: @property def renamed_file(self) -> bool: - """:return: True if the blob of our diff has been renamed""" + """:return: ``True`` if the blob of our diff has been renamed""" return self.rename_from != self.rename_to @classmethod diff --git a/git/remote.py b/git/remote.py index 3d5b8fdc4..6ce720ee3 100644 --- a/git/remote.py +++ b/git/remote.py @@ -338,7 +338,7 @@ class FetchInfo(IterableObj): @classmethod def refresh(cls) -> Literal[True]: - """This gets called by the refresh function (see the top level __init__).""" + """This gets called by the refresh function (see the top level ``__init__``).""" # Clear the old values in _flag_map. with contextlib.suppress(KeyError): del cls._flag_map["t"] @@ -386,19 +386,22 @@ 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 :class:`FetchInfo` object representing this information. - We can handle a line as follows: - "%c %-\\*s %-\\*s -> %s%s" + We can handle a line as follows:: - Where c is either ' ', !, +, -, \\*, or = - ! means error - + 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 a fast-forward + %c %-*s %-*s -> %s%s - fetch line is the corresponding line from FETCH_HEAD, like - acb0fa8b94ef421ad60c8507b634759a472cd56c not-for-merge branch '0.1.7RC' of /tmp/tmpya0vairemote_repo + Where ``c`` is either a space, ``!``, ``+``, ``-``, ``*``, or ``=``: + + - '!' means error + - '+' 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 a fast-forward + + `fetch_line` is the corresponding line from FETCH_HEAD, like:: + + acb0fa8b94ef421ad60c8507b634759a472cd56c not-for-merge branch '0.1.7RC' of /tmp/tmpya0vairemote_repo """ match = cls._re_fetch_result.match(line) if match is None: @@ -625,7 +628,7 @@ def exists(self) -> bool: @classmethod def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Iterator["Remote"]: - """:return: Iterator yielding Remote objects of the given repository""" + """:return: Iterator yielding :class:`Remote` objects of the given repository""" for section in repo.config_reader("repository").sections(): if not section.startswith("remote "): continue @@ -639,7 +642,7 @@ 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. @@ -1020,7 +1023,7 @@ def fetch( facility. :param progress: - See :meth:`push` method. + See the :meth:`push` method. :param verbose: Boolean for verbose output. @@ -1081,8 +1084,8 @@ def pull( allow_unsafe_options: bool = False, **kwargs: Any, ) -> IterableList[FetchInfo]: - """Pull changes from the given branch, being the same as a fetch followed - by a merge of branch with your local branch. + """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. @@ -1157,7 +1160,7 @@ def push( :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. + should be killed. It is set to ``None`` by default. :param allow_unsafe_protocols: Allow unsafe protocols to be used, like ``ext``. diff --git a/git/util.py b/git/util.py index e7dd94a08..bab765a09 100644 --- a/git/util.py +++ b/git/util.py @@ -762,9 +762,9 @@ def update(self, *args: Any, **kwargs: Any) -> None: class Actor: - """Actors hold information about a person acting on the repository. They - can be committers and authors or anything with a name and an email as mentioned in - the git log entries.""" + """Actors hold information about a person acting on the repository. They can be + committers and authors or anything with a name and an email as mentioned in the git + log entries.""" # PRECOMPILED REGEX name_only_regex = re.compile(r"<(.*)>") From 231c3ef758a004bb51fae2868cb59742069c7dde Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 23:23:45 -0500 Subject: [PATCH 55/61] More docstring revisions within git.repo --- git/repo/base.py | 15 +++++++-------- git/repo/fun.py | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index 4745a2411..a54591746 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -102,9 +102,8 @@ class BlameEntry(NamedTuple): class Repo: - """Represents a git repository and allows you to query references, father - commit information, generate diffs, create and clone repositories, and query the - log. + """Represents a git repository and allows you to query references, create commit + information, generate diffs, create and clone repositories, and query the log. The following attributes are worth using: @@ -112,8 +111,8 @@ class Repo: working tree directory if available or the ``.git`` directory in case of bare repositories. - * :attr:`working_tree_dir` is the working tree directory, but will return None if we - are a bare repository. + * :attr:`working_tree_dir` is the working tree directory, but will return ``None`` + if we are a bare repository. * :attr:`git_dir` is the ``.git`` repository directory, which is always set. """ @@ -596,7 +595,7 @@ def create_tag( return TagReference.create(self, path, ref, message, force, **kwargs) def delete_tag(self, *tags: TagReference) -> None: - """Delete the given tag references""" + """Delete the given tag references.""" return TagReference.delete(self, *tags) def create_remote(self, name: str, url: str, **kwargs: Any) -> Remote: @@ -775,7 +774,7 @@ def iter_commits( def merge_base(self, *rev: TBD, **kwargs: Any) -> List[Union[Commit_ish, None]]: R"""Find the closest common ancestor for the given revision (:class:`~git.objects.commit.Commit`\s, :class:`~git.refs.tag.Tag`\s, - :class:`git.refs.reference.Reference`\s, etc.). + :class:`~git.refs.reference.Reference`\s, etc.). :param rev: At least two revs to find the common ancestor for. @@ -1030,7 +1029,7 @@ def active_branch(self) -> Head: If HEAD is detached. :return: - Head to the active branch + :class:`~git.refs.head.Head` to the active branch """ # reveal_type(self.head.reference) # => Reference return self.head.reference diff --git a/git/repo/fun.py b/git/repo/fun.py index e9fad2c46..e3c69c68c 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -151,7 +151,7 @@ def name_to_object( :param return_ref: If ``True``, and name specifies a reference, we will return the reference - instead of the object. Otherwise it will raise `~gitdb.exc.BadObject` o + instead of the object. Otherwise it will raise `~gitdb.exc.BadObject` or `~gitdb.exc.BadName`. """ hexsha: Union[None, str, bytes] = None From e166a0a7b9dc2643c686e0a5b365e22d0fa88ae7 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 23:45:33 -0500 Subject: [PATCH 56/61] More docstring revisions within git.objects --- git/objects/base.py | 2 +- git/objects/commit.py | 15 ++++++++------- git/objects/fun.py | 4 ++-- git/objects/tag.py | 2 +- git/objects/tree.py | 10 +++++----- git/objects/util.py | 15 ++++++++------- 6 files changed, 25 insertions(+), 23 deletions(-) diff --git a/git/objects/base.py b/git/objects/base.py index 5cee8e405..e78420e8a 100644 --- a/git/objects/base.py +++ b/git/objects/base.py @@ -98,7 +98,7 @@ def new_from_sha(cls, repo: "Repo", sha1: bytes) -> Commit_ish: New object instance of a type appropriate to represent the given binary sha1 :param sha1: - 20 byte binary sha1 + 20 byte binary sha1. """ if sha1 == cls.NULL_BIN_SHA: # The NULL binsha is always the root commit. diff --git a/git/objects/commit.py b/git/objects/commit.py index b5b1c540d..06ab0898b 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -298,7 +298,7 @@ def iter_items( The :class:`~git.repo.base.Repo`. :param rev: - Revision specifier, see git-rev-parse for viable options. + Revision specifier. See git-rev-parse for viable options. :param paths: An optional path or list of paths. If set only :class:`Commit`\s that @@ -309,7 +309,7 @@ def iter_items( * ``max_count`` is the maximum number of commits to fetch. * ``skip`` is the number of commits to skip. - * ``since`` all commits since e.g. '1970-01-01'. + * ``since`` selects all commits since some date, e.g. ``"1970-01-01"``. :return: Iterator yielding :class:`Commit` items. @@ -380,12 +380,13 @@ def stats(self) -> Stats: def trailers(self) -> Dict[str, str]: """Deprecated. Get the trailers of the message as a dictionary. - :note: This property is deprecated, please use either :attr:`trailers_list` or - :attr:`trailers_dict``. + :note: + This property is deprecated, please use either :attr:`trailers_list` or + :attr:`trailers_dict`. :return: - Dictionary containing whitespace stripped trailer information. Only contains - the latest instance of each trailer key. + Dictionary containing whitespace stripped trailer information. + Only contains the latest instance of each trailer key. """ return {k: v[0] for k, v in self.trailers_dict.items()} @@ -539,7 +540,7 @@ def create_from_tree( author_date: Union[None, str, datetime.datetime] = None, commit_date: Union[None, str, datetime.datetime] = None, ) -> "Commit": - """Commit the given tree, creating a commit object. + """Commit the given tree, creating a :class:`Commit` object. :param repo: :class:`~git.repo.base.Repo` object the commit should be part of. diff --git a/git/objects/fun.py b/git/objects/fun.py index ad5bc9a88..22b99cb6b 100644 --- a/git/objects/fun.py +++ b/git/objects/fun.py @@ -186,8 +186,8 @@ def traverse_trees_recursive( given tree. :param tree_shas: - Iterable of shas pointing to trees. All trees must be on the same level. A - tree-sha may be ``None`` in which case ``None``. + Iterable of shas pointing to trees. All trees must be on the same level. + A tree-sha may be ``None``, in which case ``None``. :param path_prefix: A prefix to be added to the returned paths on this level. diff --git a/git/objects/tag.py b/git/objects/tag.py index 7589a5be1..f455c55fc 100644 --- a/git/objects/tag.py +++ b/git/objects/tag.py @@ -3,7 +3,7 @@ # This module is part of GitPython and is released under the # 3-Clause BSD License: https://opensource.org/license/bsd-3-clause/ -"""Provides an :class:`git.objects.base.Object`-based type for annotated tags. +"""Provides an :class:`~git.objects.base.Object`-based type for annotated tags. This defines the :class:`TagReference` class, which represents annotated tags. For lightweight tags, see the :mod:`git.refs.tag` module. diff --git a/git/objects/tree.py b/git/objects/tree.py index 69cd6ef3f..a506bba7d 100644 --- a/git/objects/tree.py +++ b/git/objects/tree.py @@ -166,7 +166,7 @@ class Tree(IndexObject, git_diff.Diffable, util.Traversable, util.Serializable): Tree as a list: - * Access a specific blob using the ``tree['filename']`` notation. + * Access a specific blob using the ``tree["filename"]`` notation. * You may likewise access by index, like ``blob = tree[0]``. """ @@ -215,8 +215,8 @@ def _set_cache_(self, attr: str) -> None: # END handle attribute def _iter_convert_to_object(self, iterable: Iterable[TreeCacheTup]) -> Iterator[IndexObjUnion]: - """Iterable yields tuples of (binsha, mode, name), which will be converted - to the respective object representation. + """Iterable yields tuples of (binsha, mode, name), which will be converted to + the respective object representation. """ for binsha, mode, name in iterable: path = join_path(self.path, name) @@ -338,8 +338,8 @@ def traverse( def list_traverse(self, *args: Any, **kwargs: Any) -> IterableList[IndexObjUnion]: """ :return: - :class:`~git.util.IterableList`IterableList with the results of the - traversal as produced by :meth:`traverse` + :class:`~git.util.IterableList` with the results of the traversal as + produced by :meth:`traverse` Tree -> IterableList[Union[Submodule, Tree, Blob]] """ diff --git a/git/objects/util.py b/git/objects/util.py index 6f46bab18..6f4e7d087 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -474,7 +474,7 @@ def _traverse( ignore_self: int = 1, as_edge: bool = False, ) -> Union[Iterator[Union["Traversable", "Blob"]], Iterator[TraversedTup]]: - """Iterator yielding items found when traversing self. + """Iterator yielding items found when traversing `self`. :param predicate: A function ``f(i,d)`` that returns ``False`` if item i at depth ``d`` should @@ -485,9 +485,10 @@ def _traverse( item ``i`` at depth ``d``. Item ``i`` will not be returned. :param depth: - Defines at which level the iteration should not go deeper if -1, there is no - limit if 0, you would effectively only get self, the root of the iteration - i.e. if 1, you would only get the first level of predecessors/successors. + Defines at which level the iteration should not go deeper if -1. There is no + limit if 0, you would effectively only get `self`, the root of the + iteration. If 1, you would only get the first level of + predecessors/successors. :param branch_first: If ``True``, items will be returned branch first, otherwise depth first. @@ -497,8 +498,8 @@ def _traverse( encountered several times. Loops are prevented that way. :param ignore_self: - If ``True``, self will be ignored and automatically pruned from the result. - Otherwise it will be the first item to be returned. If `as_edge` is + If ``True``, `self` will be ignored and automatically pruned from the + result. Otherwise it will be the first item to be returned. If `as_edge` is ``True``, the source of the first edge is ``None``. :param as_edge: @@ -507,7 +508,7 @@ def _traverse( destination. :return: - Iterator yielding items found when traversing self:: + Iterator yielding items found when traversing `self`:: Commit -> Iterator[Union[Commit, Tuple[Commit, Commit]] Submodule -> Iterator[Submodule, Tuple[Submodule, Submodule]] Tree -> From ffeb7e76c54c3e8af1e94e116f3a1ef714b36e19 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Wed, 28 Feb 2024 23:57:35 -0500 Subject: [PATCH 57/61] More docstring revisions in git.objects.submodule.base --- git/objects/submodule/base.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 6379d1500..cdd7c8e1b 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -172,7 +172,7 @@ def _set_cache_(self, attr: str) -> None: @classmethod def _get_intermediate_items(cls, item: "Submodule") -> IterableList["Submodule"]: - """:return: all the submodules of our module repository""" + """:return: All the submodules of our module repository""" try: return cls.list_items(item.module()) except InvalidGitRepositoryError: @@ -326,7 +326,7 @@ def _clone_repo( Allow unsafe options to be used, like ``--upload-pack``. :param kwargs: - Additional arguments given to ``git clone`` + Additional arguments given to ``git clone``. """ module_abspath = cls._module_abspath(repo, path, name) module_checkout_path = module_abspath @@ -351,7 +351,7 @@ def _clone_repo( @classmethod def _to_relative_path(cls, parent_repo: "Repo", path: PathLike) -> PathLike: - """:return: a path guaranteed to be relative to the given parent repository + """:return: A path guaranteed to be relative to the given parent repository :raise ValueError: If path is not contained in the parent repository's working tree. @@ -642,7 +642,7 @@ def update( If ``True``, the submodule's sha will be ignored during checkout. Instead, the remote will be fetched, and the local tracking branch updated. This only works if we have a local tracking branch, which is the case if the remote - repository had a master branch, or of the ``branch`` option was specified + repository had a master branch, or if the ``branch`` option was specified for this submodule and the branch existed remotely. :param progress: @@ -1309,7 +1309,7 @@ def config_writer( repository. :param write: - If True, the index will be written each time a configuration value changes. + If ``True``, the index will be written each time a configuration value changes. :note: The parameters allow for a more efficient writing of the index, as you can From ec9395526719a87127efa9e8b942a05a4238aa25 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Thu, 29 Feb 2024 00:18:48 -0500 Subject: [PATCH 58/61] Further refine some docstring revisions --- git/cmd.py | 16 +++++++++------- git/diff.py | 4 ++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/git/cmd.py b/git/cmd.py index 949814765..6574cbb34 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -127,10 +127,10 @@ def handle_process_output( Assume stdout/stderr streams are binary and decode them before pushing their contents to handlers. - This defaults to ``True``. Set it to ``False``: + This defaults to ``True``. Set it to ``False`` if: - - if ``universal_newlines == True``, as then streams are in text mode, or - - if decoding must happen later, such as for :class:`~git.diff.Diff`\s. + - ``universal_newlines == True``, as then streams are in text mode, or + - decoding must happen later, such as for :class:`~git.diff.Diff`\s. :param kill_after_timeout: :class:`float` or ``None``, Default = ``None`` @@ -1085,13 +1085,15 @@ def execute( specify may not be the same ones. :return: - * str(output) if extended_output = False (Default) - * tuple(int(status), str(stdout), str(stderr)) if extended_output = True + * str(output), if `extended_output` is ``False`` (Default) + * tuple(int(status), str(stdout), str(stderr)), + if `extended_output` is ``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 + * output_stream, if `extended_output` is ``False`` + * tuple(int(status), output_stream, str(stderr)), + if `extended_output` is ``True`` Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent output regardless of system language. diff --git a/git/diff.py b/git/diff.py index adddaa7f6..a89ca9880 100644 --- a/git/diff.py +++ b/git/diff.py @@ -112,8 +112,8 @@ def diff( create_patch: bool = False, **kwargs: Any, ) -> "DiffIndex": - """Create diffs between two items being trees, trees and index or an - index and the working tree. Detects renames automatically. + """Create diffs between two items being trees, trees and index or an index and + the working tree. Detects renames automatically. :param other: This the item to compare us with. From 63983c2b9206a4a599112aa6c302a143f4661799 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Thu, 29 Feb 2024 00:41:10 -0500 Subject: [PATCH 59/61] Remove note in GitCmdObjectDB docstring For #1849. --- git/db.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/git/db.py b/git/db.py index 3f496a979..bf0de40de 100644 --- a/git/db.py +++ b/git/db.py @@ -30,10 +30,6 @@ class GitCmdObjectDB(LooseObjectDB): objects, pack files and an alternates file. 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. """ def __init__(self, root_path: PathLike, git: "Git") -> None: From f43292e4e4860c856de0ac52ef4a326aacff6579 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Thu, 29 Feb 2024 00:51:12 -0500 Subject: [PATCH 60/61] Somewhat improve _get_ref_info{,_helper} docstrings + Add a missing :class: reference in _get_commit docstring. --- git/refs/symbolic.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index 70af4615d..142952e06 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -225,10 +225,10 @@ def _get_ref_info_helper( ) -> Union[Tuple[str, None], Tuple[None, str]]: """ :return: - (str(sha), str(target_ref_path)) if available, the sha the file at rela_path - points to, or ``None``. + *(str(sha), str(target_ref_path))*, where: - target_ref_path is the reference we point to, or ``None``. + * *sha* is of the file at rela_path points to if available, or ``None``. + * *target_ref_path* is the reference we point to, or ``None``. """ if ref_path: cls._check_ref_name_valid(ref_path) @@ -270,10 +270,11 @@ def _get_ref_info_helper( @classmethod def _get_ref_info(cls, repo: "Repo", ref_path: Union[PathLike, None]) -> Union[Tuple[str, None], Tuple[None, str]]: """ - :return: (str(sha), str(target_ref_path)) if available, the sha the file at - rela_path points to, or None. + :return: + *(str(sha), str(target_ref_path))*, where: - target_ref_path is the reference we point to, or ``None``. + * *sha* is of the file at rela_path points to if available, or ``None``. + * *target_ref_path* is the reference we point to, or ``None``. """ return cls._get_ref_info_helper(repo, ref_path) @@ -290,9 +291,9 @@ def _get_object(self) -> Commit_ish: def _get_commit(self) -> "Commit": """ :return: - Commit object we point to. This works for detached and non-detached - :class:`SymbolicReference` instances. The symbolic reference will be - dereferenced recursively. + :class:`~git.objects.commit.Commit` object we point to. This works for + detached and non-detached :class:`SymbolicReference` instances. The symbolic + reference will be dereferenced recursively. """ obj = self._get_object() if obj.type == "tag": From 37c93de3d31aabf32f0032a2a49a9fee285fa6e8 Mon Sep 17 00:00:00 2001 From: Eliah Kagan Date: Thu, 29 Feb 2024 03:44:44 -0500 Subject: [PATCH 61/61] A couple more small docstring refinements - Single vs. double backtick error/inconsistency in one place (parameter names were in in doubled backticks, now single) - Undo making "Iterator" in the phrase "Iterator yielding" a reference to collections.abc.Iterator, which is unnecessary and not done anywhere else. --- git/config.py | 4 ++-- git/util.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/git/config.py b/git/config.py index b358ee417..2164f67dc 100644 --- a/git/config.py +++ b/git/config.py @@ -920,8 +920,8 @@ def rename_section(self, section: str, new_name: str) -> "GitConfigParser": :raise ValueError: If: - * ``section`` doesn't exist. - * A section with ``new_name`` does already exist. + * `section` doesn't exist. + * A section with `new_name` does already exist. :return: This instance diff --git a/git/util.py b/git/util.py index bab765a09..52f9dbdad 100644 --- a/git/util.py +++ b/git/util.py @@ -1257,7 +1257,7 @@ def iter_items(cls, repo: "Repo", *args: Any, **kwargs: Any) -> Iterator[T_Itera keyword arguments, subclasses are obliged to to yield all items. :return: - :class:`~collections.abc.Iterator` yielding Items + Iterator yielding Items """ raise NotImplementedError("To be implemented by Subclass")