New upstream release.
Debian Janitor
2 years ago
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: GitPython |
2 | Version: 3.1.23 | |
2 | Version: 3.1.24 | |
3 | 3 | Summary: GitPython is a python library used to interact with Git repositories |
4 | 4 | Home-page: https://github.com/gitpython-developers/GitPython |
5 | 5 | Author: Sebastian Thiel, Michael Trier |
4 | 4 | MANIFEST.in |
5 | 5 | README.md |
6 | 6 | VERSION |
7 | pyproject.toml | |
7 | 8 | requirements.txt |
8 | 9 | setup.py |
9 | 10 | test-requirements.txt |
0 | 0 | Metadata-Version: 2.1 |
1 | 1 | Name: GitPython |
2 | Version: 3.1.23 | |
2 | Version: 3.1.24 | |
3 | 3 | Summary: GitPython is a python library used to interact with Git repositories |
4 | 4 | Home-page: https://github.com/gitpython-developers/GitPython |
5 | 5 | Author: Sebastian Thiel, Michael Trier |
0 | python-git (3.1.24-1) UNRELEASED; urgency=low | |
1 | ||
2 | * New upstream release. | |
3 | ||
4 | -- Debian Janitor <janitor@jelmer.uk> Sun, 26 Sep 2021 03:30:59 -0000 | |
5 | ||
0 | 6 | python-git (3.1.23-1) unstable; urgency=medium |
1 | 7 | |
2 | 8 | [ Steffen Möller ] |
1 | 1 | Changelog |
2 | 2 | ========= |
3 | 3 | |
4 | 31.23 | |
4 | ||
5 | 3.1.24 | |
5 | 6 | ====== |
6 | 7 | |
8 | * Newly added timeout flag is not be enabled by default, and was renamed to kill_after_timeout | |
9 | ||
10 | See the following for details: | |
11 | https://github.com/gitpython-developers/gitpython/milestone/54?closed=1 | |
12 | https://github.com/gitpython-developers/gitpython/milestone/53?closed=1 | |
13 | ||
14 | 3.1.23 (YANKED) | |
15 | =============== | |
16 | ||
7 | 17 | * This is the second typed release with a lot of improvements under the hood. |
8 | 18 | |
9 | 19 | * General: |
44 | 54 | - Add timeout to handle_process_output(), in case thread.join() hangs. |
45 | 55 | |
46 | 56 | See the following for details: |
47 | https://github.com/gitpython-developers/gitpython/milestone/52?closed=1 | |
57 | https://github.com/gitpython-developers/gitpython/milestone/53?closed=1 | |
48 | 58 | |
49 | 59 | |
50 | 60 | 3.1.20 (YANKED) |
13 | 13 | from typing import Optional |
14 | 14 | from git.types import PathLike |
15 | 15 | |
16 | __version__ = '3.1.23' | |
16 | __version__ = '3.1.24' | |
17 | 17 | |
18 | 18 | |
19 | 19 | #{ Initialization |
20 | 20 | def _init_externals() -> None: |
21 | 21 | """Initialize external projects by putting them into the path""" |
22 | if __version__ == '3.1.23' and 'PYOXIDIZER' not in os.environ: | |
22 | if __version__ == '3.1.24' and 'PYOXIDIZER' not in os.environ: | |
23 | 23 | sys.path.insert(1, osp.join(osp.dirname(__file__), 'ext', 'gitdb')) |
24 | 24 | |
25 | 25 | try: |
78 | 78 | finalizer: Union[None, |
79 | 79 | Callable[[Union[subprocess.Popen, 'Git.AutoInterrupt']], None]] = None, |
80 | 80 | decode_streams: bool = True, |
81 | timeout: float = 10.0) -> None: | |
81 | kill_after_timeout: Union[None, float] = None) -> None: | |
82 | 82 | """Registers for notifications to learn that process output is ready to read, and dispatches lines to |
83 | 83 | the respective line handlers. |
84 | 84 | This function returns once the finalizer returns |
93 | 93 | their contents to handlers. |
94 | 94 | Set it to False if `universal_newline == True` (then streams are in text-mode) |
95 | 95 | or if decoding must happen later (i.e. for Diffs). |
96 | :param timeout: float, timeout to pass to t.join() in case it hangs. Default = 10.0 seconds | |
96 | :param kill_after_timeout: | |
97 | float or None, Default = None | |
98 | To specify a timeout in seconds for the git command, after which the process | |
99 | should be killed. | |
97 | 100 | """ |
98 | 101 | # Use 2 "pump" threads and wait for both to finish. |
99 | 102 | def pump_stream(cmdline: List[str], name: str, stream: Union[BinaryIO, TextIO], is_decode: bool, |
107 | 110 | handler(line_str) |
108 | 111 | else: |
109 | 112 | handler(line) |
113 | ||
110 | 114 | except Exception as ex: |
111 | 115 | log.error(f"Pumping {name!r} of cmd({remove_password_if_present(cmdline)}) failed due to: {ex!r}") |
112 | raise CommandError([f'<{name}-pump>'] + remove_password_if_present(cmdline), ex) from ex | |
116 | if "I/O operation on closed file" not in str(ex): | |
117 | # Only reraise if the error was not due to the stream closing | |
118 | raise CommandError([f'<{name}-pump>'] + remove_password_if_present(cmdline), ex) from ex | |
113 | 119 | finally: |
114 | 120 | stream.close() |
115 | 121 | |
145 | 151 | ## FIXME: Why Join?? Will block if `stdin` needs feeding... |
146 | 152 | # |
147 | 153 | for t in threads: |
148 | t.join(timeout=timeout) | |
154 | t.join(timeout=kill_after_timeout) | |
149 | 155 | if t.is_alive(): |
150 | raise RuntimeError(f"Thread join() timed out in cmd.handle_process_output(). Timeout={timeout} seconds") | |
156 | if isinstance(process, Git.AutoInterrupt): | |
157 | process._terminate() | |
158 | else: # Don't want to deal with the other case | |
159 | raise RuntimeError("Thread join() timed out in cmd.handle_process_output()." | |
160 | f" kill_after_timeout={kill_after_timeout} seconds") | |
161 | if stderr_handler: | |
162 | error_str: Union[str, bytes] = ( | |
163 | "error: process killed because it timed out." | |
164 | f" kill_after_timeout={kill_after_timeout} seconds") | |
165 | if not decode_streams and isinstance(p_stderr, BinaryIO): | |
166 | # Assume stderr_handler needs binary input | |
167 | error_str = cast(str, error_str) | |
168 | error_str = error_str.encode() | |
169 | # We ignore typing on the next line because mypy does not like | |
170 | # the way we inferred that stderr takes str or bytes | |
171 | stderr_handler(error_str) # type: ignore | |
151 | 172 | |
152 | 173 | if finalizer: |
153 | 174 | return finalizer(process) |
385 | 406 | The wait method was overridden to perform automatic status code checking |
386 | 407 | and possibly raise.""" |
387 | 408 | |
388 | __slots__ = ("proc", "args") | |
409 | __slots__ = ("proc", "args", "status") | |
410 | ||
411 | # If this is non-zero it will override any status code during | |
412 | # _terminate, used to prevent race conditions in testing | |
413 | _status_code_if_terminate: int = 0 | |
389 | 414 | |
390 | 415 | def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None: |
391 | 416 | self.proc = proc |
392 | 417 | self.args = args |
393 | ||
394 | def __del__(self) -> None: | |
418 | self.status: Union[int, None] = None | |
419 | ||
420 | def _terminate(self) -> None: | |
421 | """Terminate the underlying process""" | |
395 | 422 | if self.proc is None: |
396 | 423 | return |
397 | 424 | |
403 | 430 | proc.stdout.close() |
404 | 431 | if proc.stderr: |
405 | 432 | proc.stderr.close() |
406 | ||
407 | 433 | # did the process finish already so we have a return code ? |
408 | 434 | try: |
409 | 435 | if proc.poll() is not None: |
436 | self.status = self._status_code_if_terminate or proc.poll() | |
410 | 437 | return None |
411 | 438 | except OSError as ex: |
412 | 439 | log.info("Ignored error after process had died: %r", ex) |
418 | 445 | # try to kill it |
419 | 446 | try: |
420 | 447 | proc.terminate() |
421 | proc.wait() # ensure process goes away | |
448 | status = proc.wait() # ensure process goes away | |
449 | ||
450 | self.status = self._status_code_if_terminate or status | |
422 | 451 | except OSError as ex: |
423 | 452 | log.info("Ignored error after process had died: %r", ex) |
424 | 453 | except AttributeError: |
430 | 459 | call(("TASKKILL /F /T /PID %s 2>nul 1>nul" % str(proc.pid)), shell=True) |
431 | 460 | # END exception handling |
432 | 461 | |
462 | def __del__(self) -> None: | |
463 | self._terminate() | |
464 | ||
433 | 465 | def __getattr__(self, attr: str) -> Any: |
434 | 466 | return getattr(self.proc, attr) |
435 | 467 | |
443 | 475 | if stderr is None: |
444 | 476 | stderr_b = b'' |
445 | 477 | stderr_b = force_bytes(data=stderr, encoding='utf-8') |
446 | ||
478 | status: Union[int, None] | |
447 | 479 | if self.proc is not None: |
448 | 480 | status = self.proc.wait() |
449 | ||
450 | def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes: | |
451 | if stream: | |
452 | try: | |
453 | return stderr_b + force_bytes(stream.read()) | |
454 | except ValueError: | |
455 | return stderr_b or b'' | |
456 | else: | |
481 | p_stderr = self.proc.stderr | |
482 | else: # Assume the underlying proc was killed earlier or never existed | |
483 | status = self.status | |
484 | p_stderr = None | |
485 | ||
486 | def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes: | |
487 | if stream: | |
488 | try: | |
489 | return stderr_b + force_bytes(stream.read()) | |
490 | except ValueError: | |
457 | 491 | return stderr_b or b'' |
458 | ||
459 | if status != 0: | |
460 | errstr = read_all_from_possibly_closed_stream(self.proc.stderr) | |
461 | log.debug('AutoInterrupt wait stderr: %r' % (errstr,)) | |
462 | raise GitCommandError(remove_password_if_present(self.args), status, errstr) | |
492 | else: | |
493 | return stderr_b or b'' | |
494 | ||
463 | 495 | # END status handling |
496 | ||
497 | if status != 0: | |
498 | errstr = read_all_from_possibly_closed_stream(p_stderr) | |
499 | log.debug('AutoInterrupt wait stderr: %r' % (errstr,)) | |
500 | raise GitCommandError(remove_password_if_present(self.args), status, errstr) | |
464 | 501 | return status |
465 | 502 | |
466 | 503 | # END auto interrupt |
693 | 730 | as_process: bool = False, |
694 | 731 | output_stream: Union[None, BinaryIO] = None, |
695 | 732 | stdout_as_string: bool = True, |
696 | kill_after_timeout: Union[None, int] = None, | |
733 | kill_after_timeout: Union[None, float] = None, | |
697 | 734 | with_stdout: bool = True, |
698 | 735 | universal_newlines: bool = False, |
699 | 736 | shell: Union[None, bool] = None, |
816 | 853 | |
817 | 854 | if is_win: |
818 | 855 | cmd_not_found_exception = OSError |
819 | if kill_after_timeout: | |
856 | if kill_after_timeout is not None: | |
820 | 857 | raise GitCommandError(redacted_command, '"kill_after_timeout" feature is not supported on Windows.') |
821 | 858 | else: |
822 | 859 | cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable |
883 | 920 | return |
884 | 921 | # end |
885 | 922 | |
886 | if kill_after_timeout: | |
923 | if kill_after_timeout is not None: | |
887 | 924 | kill_check = threading.Event() |
888 | 925 | watchdog = threading.Timer(kill_after_timeout, _kill_process, args=(proc.pid,)) |
889 | 926 | |
894 | 931 | newline = "\n" if universal_newlines else b"\n" |
895 | 932 | try: |
896 | 933 | if output_stream is None: |
897 | if kill_after_timeout: | |
934 | if kill_after_timeout is not None: | |
898 | 935 | watchdog.start() |
899 | 936 | stdout_value, stderr_value = proc.communicate() |
900 | if kill_after_timeout: | |
937 | if kill_after_timeout is not None: | |
901 | 938 | watchdog.cancel() |
902 | 939 | if kill_check.is_set(): |
903 | 940 | stderr_value = ('Timeout: the command "%s" did not complete in %d ' |
706 | 706 | return self |
707 | 707 | |
708 | 708 | def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt', |
709 | progress: Union[Callable[..., Any], RemoteProgress, None] | |
709 | progress: Union[Callable[..., Any], RemoteProgress, None], | |
710 | kill_after_timeout: Union[None, float] = None, | |
710 | 711 | ) -> IterableList['FetchInfo']: |
711 | 712 | |
712 | 713 | progress = to_progress_instance(progress) |
723 | 724 | cmds = set(FetchInfo._flag_map.keys()) |
724 | 725 | |
725 | 726 | progress_handler = progress.new_message_handler() |
726 | handle_process_output(proc, None, progress_handler, finalizer=None, decode_streams=False) | |
727 | handle_process_output(proc, None, progress_handler, finalizer=None, decode_streams=False, | |
728 | kill_after_timeout=kill_after_timeout) | |
727 | 729 | |
728 | 730 | stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or '' |
729 | 731 | proc.wait(stderr=stderr_text) |
768 | 770 | return output |
769 | 771 | |
770 | 772 | def _get_push_info(self, proc: 'Git.AutoInterrupt', |
771 | progress: Union[Callable[..., Any], RemoteProgress, None]) -> IterableList[PushInfo]: | |
773 | progress: Union[Callable[..., Any], RemoteProgress, None], | |
774 | kill_after_timeout: Union[None, float] = None) -> IterableList[PushInfo]: | |
772 | 775 | progress = to_progress_instance(progress) |
773 | 776 | |
774 | 777 | # read progress information from stderr |
785 | 788 | # If an error happens, additional info is given which we parse below. |
786 | 789 | pass |
787 | 790 | |
788 | handle_process_output(proc, stdout_handler, progress_handler, finalizer=None, decode_streams=False) | |
791 | handle_process_output(proc, stdout_handler, progress_handler, finalizer=None, decode_streams=False, | |
792 | kill_after_timeout=kill_after_timeout) | |
789 | 793 | stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or '' |
790 | 794 | try: |
791 | 795 | proc.wait(stderr=stderr_text) |
792 | 796 | except Exception: |
797 | # This is different than fetch (which fails if there is any std_err | |
798 | # even if there is an output) | |
793 | 799 | if not output: |
794 | 800 | raise |
795 | 801 | elif stderr_text: |
812 | 818 | |
813 | 819 | def fetch(self, refspec: Union[str, List[str], None] = None, |
814 | 820 | progress: Union[RemoteProgress, None, 'UpdateProgress'] = None, |
815 | verbose: bool = True, **kwargs: Any) -> IterableList[FetchInfo]: | |
821 | verbose: bool = True, | |
822 | kill_after_timeout: Union[None, float] = None, | |
823 | **kwargs: Any) -> IterableList[FetchInfo]: | |
816 | 824 | """Fetch the latest changes for this remote |
817 | 825 | |
818 | 826 | :param refspec: |
832 | 840 | for 'refspec' will make use of this facility. |
833 | 841 | :param progress: See 'push' method |
834 | 842 | :param verbose: Boolean for verbose output |
843 | :param kill_after_timeout: | |
844 | To specify a timeout in seconds for the git command, after which the process | |
845 | should be killed. It is set to None by default. | |
835 | 846 | :param kwargs: Additional arguments to be passed to git-fetch |
836 | 847 | :return: |
837 | 848 | IterableList(FetchInfo, ...) list of FetchInfo instances providing detailed |
852 | 863 | |
853 | 864 | proc = self.repo.git.fetch(self, *args, as_process=True, with_stdout=False, |
854 | 865 | universal_newlines=True, v=verbose, **kwargs) |
855 | res = self._get_fetch_info_from_stderr(proc, progress) | |
866 | res = self._get_fetch_info_from_stderr(proc, progress, | |
867 | kill_after_timeout=kill_after_timeout) | |
856 | 868 | if hasattr(self.repo.odb, 'update_cache'): |
857 | 869 | self.repo.odb.update_cache() |
858 | 870 | return res |
859 | 871 | |
860 | 872 | def pull(self, refspec: Union[str, List[str], None] = None, |
861 | 873 | progress: Union[RemoteProgress, 'UpdateProgress', None] = None, |
874 | kill_after_timeout: Union[None, float] = None, | |
862 | 875 | **kwargs: Any) -> IterableList[FetchInfo]: |
863 | 876 | """Pull changes from the given branch, being the same as a fetch followed |
864 | 877 | by a merge of branch with your local branch. |
865 | 878 | |
866 | 879 | :param refspec: see 'fetch' method |
867 | 880 | :param progress: see 'push' method |
881 | :param kill_after_timeout: see 'fetch' method | |
868 | 882 | :param kwargs: Additional arguments to be passed to git-pull |
869 | 883 | :return: Please see 'fetch' method """ |
870 | 884 | if refspec is None: |
873 | 887 | kwargs = add_progress(kwargs, self.repo.git, progress) |
874 | 888 | proc = self.repo.git.pull(self, refspec, with_stdout=False, as_process=True, |
875 | 889 | universal_newlines=True, v=True, **kwargs) |
876 | res = self._get_fetch_info_from_stderr(proc, progress) | |
890 | res = self._get_fetch_info_from_stderr(proc, progress, | |
891 | kill_after_timeout=kill_after_timeout) | |
877 | 892 | if hasattr(self.repo.odb, 'update_cache'): |
878 | 893 | self.repo.odb.update_cache() |
879 | 894 | return res |
880 | 895 | |
881 | 896 | def push(self, refspec: Union[str, List[str], None] = None, |
882 | 897 | progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None] = None, |
898 | kill_after_timeout: Union[None, float] = None, | |
883 | 899 | **kwargs: Any) -> IterableList[PushInfo]: |
884 | 900 | """Push changes from source branch in refspec to target branch in refspec. |
885 | 901 | |
896 | 912 | overrides the ``update()`` function. |
897 | 913 | |
898 | 914 | :note: No further progress information is returned after push returns. |
915 | :param kill_after_timeout: | |
916 | To specify a timeout in seconds for the git command, after which the process | |
917 | should be killed. It is set to None by default. | |
899 | 918 | :param kwargs: Additional arguments to be passed to git-push |
900 | 919 | :return: |
901 | 920 | list(PushInfo, ...) list of PushInfo instances, each |
907 | 926 | be 0.""" |
908 | 927 | kwargs = add_progress(kwargs, self.repo.git, progress) |
909 | 928 | proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True, |
910 | universal_newlines=True, **kwargs) | |
911 | return self._get_push_info(proc, progress) | |
929 | universal_newlines=True, | |
930 | kill_after_timeout=kill_after_timeout, | |
931 | **kwargs) | |
932 | return self._get_push_info(proc, progress, | |
933 | kill_after_timeout=kill_after_timeout) | |
912 | 934 | |
913 | 935 | @ property |
914 | 936 | def config_reader(self) -> SectionConstraint[GitConfigParser]: |
0 | [build-system] | |
1 | requires = ["setuptools", "wheel"] | |
2 | build-backend = "setuptools.build_meta" | |
3 | ||
4 | [tool.pytest.ini_options] | |
5 | python_files = 'test_*.py' | |
6 | testpaths = 'test' # space seperated list of paths from root e.g test tests doc/testing | |
7 | addopts = '--cov=git --cov-report=term --maxfail=10 --force-sugar --disable-warnings' | |
8 | filterwarnings = 'ignore::DeprecationWarning' | |
9 | # --cov coverage | |
10 | # --cov-report term # send report to terminal term-missing -> terminal with line numbers html xml | |
11 | # --cov-report term-missing # to terminal with line numbers | |
12 | # --cov-report html:path # html file at path | |
13 | # --maxfail # number of errors before giving up | |
14 | # -disable-warnings # Disable pytest warnings (not codebase warnings) | |
15 | # -rf # increased reporting of failures | |
16 | # -rE # increased reporting of errors | |
17 | # --ignore-glob=**/gitdb/* # ignore glob paths | |
18 | # filterwarnings ignore::WarningType # ignores those warnings | |
19 | ||
20 | [tool.mypy] | |
21 | disallow_untyped_defs = true | |
22 | no_implicit_optional = true | |
23 | warn_redundant_casts = true | |
24 | # warn_unused_ignores = true | |
25 | warn_unreachable = true | |
26 | show_error_codes = true | |
27 | implicit_reexport = true | |
28 | # strict = true | |
29 | ||
30 | # TODO: remove when 'gitdb' is fully annotated | |
31 | [[tool.mypy.overrides]] | |
32 | module = "gitdb.*" | |
33 | ignore_missing_imports = true | |
34 | ||
35 | [tool.coverage.run] | |
36 | source = ["git"] | |
37 | ||
38 | [tool.coverage.report] | |
39 | include = ["*/git/*"] | |
40 | omit = ["*/git/ext/*"] |