From 3a24f994a17d755cb685d5b00edd7ed525f35940 Mon Sep 17 00:00:00 2001 From: evallesp Date: Fri, 14 Mar 2025 11:02:38 +0100 Subject: [PATCH] Remove remote for re-creating it if url has changed We're adding the possibility to remove the remote. This, with the addition of names_url_dict, makes the posibility of checking the remote url, and if it's updated, then remove it for re-creating. --- git_wrapper/remote.py | 33 +++++++++++++++++++ tests/test_remote.py | 77 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 107 insertions(+), 3 deletions(-) diff --git a/git_wrapper/remote.py b/git_wrapper/remote.py index 9bd1ac5..d75e30c 100644 --- a/git_wrapper/remote.py +++ b/git_wrapper/remote.py @@ -24,6 +24,14 @@ def names(self): """ return [x.name for x in self.git_repo.repo.remotes] + def names_url_dict(self): + """Returns a dict of remotes for a given repo with its url + + :return dict: A dict of utf-8 encoded remote names with + url as its value + """ + return {x.name: x.url for x in self.git_repo.repo.remotes} + def add(self, name, url): """Adds a remote to the given repo @@ -111,3 +119,28 @@ def fetch_all(self, prune=False, prune_tags=False): if errors: msg = f"Error fetching these remotes: {', '.join(errors)}" raise exceptions.RemoteException(msg) + + def remove(self, name): + """Remove the specified remote from the given repo + + :param str name: Remote name to remove + :return bool: True if the remote was removed, False otherwise + """ + working_dir = self.git_repo.repo.working_dir + self.logger.debug(f"Removing remote {name} from repo {working_dir}") + ret_status = False + + try: + remote = self.git_repo.repo.remote(name) + except ValueError: + repo = self.git_repo.repo.working_dir + msg = f"Remote {name} does not exist on repo {repo}" + raise exceptions.ReferenceNotFoundException(msg) + + try: + self.git_repo.repo.delete_remote(remote) + ret_status = True + except git.CommandError: + return ret_status + + return ret_status diff --git a/tests/test_remote.py b/tests/test_remote.py index 685f7c6..2c89401 100644 --- a/tests/test_remote.py +++ b/tests/test_remote.py @@ -1,6 +1,7 @@ #! /usr/bin/env python """Tests for GitRemote""" +from dataclasses import dataclass from mock import Mock import git @@ -10,14 +11,29 @@ from git_wrapper.repo import GitRepo +@dataclass +class RemoteNames: + name: str + + +@dataclass +class RemoteNamesUrl(RemoteNames): + url: str + + def remote_generator(names): """Generates objects to be used with git.Repo.remotes call""" ret_data = [] for name in names: - obj = type('', (), {})() - obj.name = name - ret_data.append(obj) + ret_data.append(RemoteNames(name)) + return ret_data + +def remote_generator_url(remotes): + """Generates objects to be used with git.Repo.remotes call""" + ret_data = [] + for name, url in remotes.items(): + ret_data.append(RemoteNamesUrl(name, url)) return ret_data @@ -36,6 +52,21 @@ def test_get_remotes_returns_list(mock_repo): assert expected == git_util.remote.names() +def test_get_remotes_returns_dict(mock_repo): + """ + GIVEN GitRepo is initialized with a path and repo + WHEN remote.names_url_dict is called + THEN a dict of remote names with its url is returned + """ + expected = {'a': 1, 'b': 2, 'c': 3} + attrs = {'remotes': remote_generator_url(expected)} + mock_repo.configure_mock(**attrs) + + git_util = GitRepo('./', mock_repo) + + assert expected == git_util.remote.names_url_dict() + + def test_add_remote_adds(mock_repo): """ GIVEN GitRepo initialized with a path and repo @@ -89,6 +120,46 @@ def test_add_remote_update_fails(mock_repo): delete_mock.assert_called_once_with(remote_mock) +def test_remove_remote_removes(mock_repo): + """ + GIVEN GitRepo initialized with a path and repo + WHEN remote.remove is called with a name and url + THEN a TRUE status is returned + """ + git_util = GitRepo('./', mock_repo) + + assert git_util.remote.remove('origin') is True + + +def test_remove_remote_remote_fails(mock_repo): + """ + GIVEN GitRepo initialized with a path and repo + WHEN remote.remove is called with a name and url + AND the repo.remote fails with an exception + THEN a ReferenceNotFoundException is raised + """ + mock_repo.remote.side_effect = ValueError + + repo = GitRepo(repo=mock_repo) + with pytest.raises(exceptions.ReferenceNotFoundException): + repo.remote.remove("doesntExist") + + mock_repo.remote.assert_called_with("doesntExist") + + +def test_remove_remote_remove_fails(mock_repo): + """ + GIVEN GitRepo initialized with a path and repo + WHEN remote.remove is called with a name and url + AND the repo.delete_remote fails with an exception + THEN a False status is returned + """ + mock_repo.delete_remote.side_effect = git.CommandError('remove') + git_util = GitRepo('./', mock_repo) + + assert git_util.remote.remove('rdo') is False + + def test_fetch(mock_repo): """ GIVEN GitRepo is initialized with a path and repo