diff --git a/git/remote.py b/git/remote.py index 2cf5678b6..7d5918a5a 100644 --- a/git/remote.py +++ b/git/remote.py @@ -235,6 +235,22 @@ def iter_items(cls, repo: 'Repo', *args: Any, **kwargs: Any raise NotImplementedError +class PushInfoList(IterableList[PushInfo]): + def __new__(cls) -> 'PushInfoList': + return cast(PushInfoList, IterableList.__new__(cls, 'push_infos')) + + def __init__(self) -> None: + super().__init__('push_infos') + self.error: Optional[Exception] = None + + def raise_if_error(self) -> None: + """ + Raise an exception if any ref failed to push. + """ + if self.error: + raise self.error + + class FetchInfo(IterableObj, object): """ @@ -774,7 +790,7 @@ def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt', def _get_push_info(self, proc: 'Git.AutoInterrupt', progress: Union[Callable[..., Any], RemoteProgress, None], - kill_after_timeout: Union[None, float] = None) -> IterableList[PushInfo]: + kill_after_timeout: Union[None, float] = None) -> PushInfoList: progress = to_progress_instance(progress) # read progress information from stderr @@ -782,7 +798,7 @@ def _get_push_info(self, proc: 'Git.AutoInterrupt', # read the lines manually as it will use carriage returns between the messages # to override the previous one. This is why we read the bytes manually progress_handler = progress.new_message_handler() - output: IterableList[PushInfo] = IterableList('push_infos') + output: PushInfoList = PushInfoList() def stdout_handler(line: str) -> None: try: @@ -796,13 +812,14 @@ def stdout_handler(line: str) -> None: stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or '' try: proc.wait(stderr=stderr_text) - except Exception: + except Exception as e: # This is different than fetch (which fails if there is any std_err # even if there is an output) if not output: raise elif stderr_text: log.warning("Error lines received while fetching: %s", stderr_text) + output.error = e return output diff --git a/test/test_docs.py b/test/test_docs.py index 220156bce..8897bbb75 100644 --- a/test/test_docs.py +++ b/test/test_docs.py @@ -393,7 +393,8 @@ def test_references_and_objects(self, rw_dir): origin.rename('new_origin') # push and pull behaves similarly to `git push|pull` origin.pull() - origin.push() + origin.push() # attempt push, ignore errors + origin.push().raise_if_error() # push and raise error if it fails # assert not empty_repo.delete_remote(origin).exists() # create and delete remotes # ![25-test_references_and_objects] diff --git a/test/test_remote.py b/test/test_remote.py index 088fdad55..761a7a3e7 100644 --- a/test/test_remote.py +++ b/test/test_remote.py @@ -30,7 +30,7 @@ fixture, GIT_DAEMON_PORT ) -from git.util import rmtree, HIDE_WINDOWS_FREEZE_ERRORS +from git.util import rmtree, HIDE_WINDOWS_FREEZE_ERRORS, IterableList import os.path as osp @@ -128,6 +128,9 @@ def _do_test_fetch_result(self, results, remote): # END for each info def _do_test_push_result(self, results, remote): + self.assertIsInstance(results, list) + self.assertIsInstance(results, IterableList) + self.assertGreater(len(results), 0) self.assertIsInstance(results[0], PushInfo) for info in results: @@ -151,6 +154,12 @@ def _do_test_push_result(self, results, remote): # END error checking # END for each info + if any([info.flags & info.ERROR for info in results]): + self.assertRaises(GitCommandError, results.raise_if_error) + else: + # No errors, so this should do nothing + results.raise_if_error() + def _do_test_fetch_info(self, repo): self.assertRaises(ValueError, FetchInfo._from_line, repo, "nonsense", '') self.assertRaises(