From c029ebdcdf611ddb32bfc7e043613c11088dffc5 Mon Sep 17 00:00:00 2001 From: nicogodet Date: Wed, 30 Apr 2025 15:25:53 +0200 Subject: [PATCH 1/4] Erase local folder if branch to use differs from local active branch --- .../profiles/profiles_handler_base.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py index 0e94a3c6..0d2ac2c5 100644 --- a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py +++ b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py @@ -430,6 +430,20 @@ def clone_or_pull(self, to_local_destination_path: Path, attempt: int = 1) -> Re raise_error=False, force_type="git_local", ): + # check if the active branch is the one to use + # if not, remove the local folder and clone again + local_branch = porcelain.active_branch( + repo=Repo(root=f"{to_local_destination_path.resolve()}") + ).decode() + if local_branch != self.DESTINATION_BRANCH_TO_USE: + logger.debug( + f"Local active branch ({local_branch}) " + f"does not match the one to use ({self.DESTINATION_BRANCH_TO_USE})." + ) + rmtree(path=to_local_destination_path, ignore_errors=True) + return self.clone_or_pull( + to_local_destination_path=to_local_destination_path + ) # FETCH logger.debug("Start fetching operations...") try: From d000ec666b240189c3c52c44d440c264ad26a47a Mon Sep 17 00:00:00 2001 From: nicogodet Date: Wed, 30 Apr 2025 15:25:53 +0200 Subject: [PATCH 2/4] Erase local folder if branch to use differs from local active branch --- .../profiles/profiles_handler_base.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py index a8c0a5b0..5e923fdc 100644 --- a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py +++ b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py @@ -430,6 +430,20 @@ def clone_or_pull(self, to_local_destination_path: Path, attempt: int = 1) -> Re raise_error=False, force_type="git_local", ): + # check if the active branch is the one to use + # if not, remove the local folder and clone again + local_branch = porcelain.active_branch( + repo=Repo(root=f"{to_local_destination_path.resolve()}") + ).decode() + if local_branch != self.DESTINATION_BRANCH_TO_USE: + logger.debug( + f"Local active branch ({local_branch}) " + f"does not match the one to use ({self.DESTINATION_BRANCH_TO_USE})." + ) + rmtree(path=to_local_destination_path, ignore_errors=True) + return self.clone_or_pull( + to_local_destination_path=to_local_destination_path + ) # FETCH logger.debug("Start fetching operations...") try: From 09b5430d1590f40db884cb3a13769b14fae7dcca Mon Sep 17 00:00:00 2001 From: nicogodet Date: Tue, 20 May 2025 15:45:01 +0200 Subject: [PATCH 3/4] add tests --- .../profiles/profiles_handler_base.py | 9 ++- tests/test_git_handler_local.py | 34 +++++++++++ tests/test_git_handler_remote.py | 57 +++++++++++++------ 3 files changed, 79 insertions(+), 21 deletions(-) diff --git a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py index 5e923fdc..1737200a 100644 --- a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py +++ b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py @@ -308,19 +308,18 @@ def is_branch_existing_in_repository( def list_remote_branches( self, source_repository_path_or_url: Path | str | None = None ) -> tuple[str]: - """Retrieve git active branch from a local repository. Mainly a checker and a - wrapper around dulwich logic. + """List all branches in a remote or local git repository. Args: source_repository_path_or_url (Path | str, optional): URL or path pointing to a git repository. + If None, uses the object's SOURCE_REPOSITORY_PATH_OR_URL. Raises: - NotGitRepository: if the path is not a valid Git Repository + NotGitRepository: if the path or URL is not a valid Git repository. Returns: - tuple[str]: tuple of branch complete names \ - ('refs/heads/profile-for-qgis-3-34', 'refs/heads/main') + tuple[str]: Tuple of branch reference names (e.g., 'refs/heads/main'). """ # if no local git repository passed, try to use URL defined at object level if source_repository_path_or_url is None and isinstance( diff --git a/tests/test_git_handler_local.py b/tests/test_git_handler_local.py index 37ea68e4..6a17afdb 100644 --- a/tests/test_git_handler_local.py +++ b/tests/test_git_handler_local.py @@ -46,6 +46,10 @@ def setUpClass(cls): cls.source_local_repository_obj = GitPythonRepo.clone_from( url=cls.remote_repo_url, to_path=cls.source_git_path_source ) + cls.source_local_repository_obj.remotes.origin.fetch() + cls.source_local_repository_obj.git.branch( + "another_branch", "origin/another_branch" + ) @classmethod def tearDownClass(cls) -> None: @@ -169,3 +173,33 @@ def test_clone_then_fetch_pull(self): target_repo = local_git_handler.download( destination_local_path=repo_in_temporary_folder ) + + def test_change_branch(self): + """Test clone with specified branch.""" + local_git_handler = LocalGitHandler( + source_repository_path_or_uri=self.source_git_path_source, + branch_to_use="main", + ) + + self.assertEqual(local_git_handler.SOURCE_REPOSITORY_TYPE, "git_local") + + with tempfile.TemporaryDirectory( + prefix="QDT_test_local_git_", + ignore_cleanup_errors=True, + suffix="_change_branch", + ) as tmpdirname: + repo_in_temporary_folder = Path(tmpdirname) + + target_repo = local_git_handler.download( + destination_local_path=repo_in_temporary_folder + ) + self.assertIsInstance(target_repo, Repo) + + local_git_handler = LocalGitHandler( + source_repository_path_or_uri=self.source_git_path_source, + branch_to_use="another_branch", + ) + target_repo = local_git_handler.download( + destination_local_path=repo_in_temporary_folder + ) + self.assertIsInstance(target_repo, Repo) diff --git a/tests/test_git_handler_remote.py b/tests/test_git_handler_remote.py index 5825fd6b..a5ac4cbb 100644 --- a/tests/test_git_handler_remote.py +++ b/tests/test_git_handler_remote.py @@ -39,13 +39,12 @@ class TestGitHandlerRemote(unittest.TestCase): @classmethod def setUpClass(cls): """Executed when module is loaded before any test.""" - cls.good_git_url = "https://gitlab.com/Oslandia/qgis/profils_qgis_fr.git" + cls.good_git_url = "https://github.com/geotribu/profils-qgis.git" # -- TESTS --------------------------------------------------------- def test_initialization(self): """Test remote git repo identifier""" # OK - self.good_git_url = "https://gitlab.com/Oslandia/qgis/profils_qgis_fr.git" remote_git_handler = RemoteGitHandler(source_repository_url=self.good_git_url) self.assertEqual(remote_git_handler.SOURCE_REPOSITORY_TYPE, "git_remote") @@ -62,8 +61,9 @@ def test_is_local_git_repo(self): git_handler = RemoteGitHandler(source_repository_url=self.good_git_url) with tempfile.TemporaryDirectory( - prefix="QDT_test_check_path", + prefix="QDT_test_remote_git_", ignore_cleanup_errors=True, + suffix="_is_local_git_repo", ) as tmpdirname: GitPythonRepo.clone_from(url=good_git_url, to_path=Path(tmpdirname)) # OK @@ -77,7 +77,6 @@ def test_is_local_git_repo(self): def test_git_url_parsed(self): """Test git parsed URL""" - self.good_git_url = "https://gitlab.com/Oslandia/qgis/profils_qgis_fr.git" git_handler = RemoteGitHandler(source_repository_url=self.good_git_url) git_url_parsed = git_handler.url_parsed(self.good_git_url) @@ -100,24 +99,50 @@ def test_git_url_parsed(self): self.assertIn("url", git_url_parsed.data) # values - self.assertIn("gitlab.com", git_url_parsed.domain) - self.assertIn("gitlab.com", git_url_parsed.host) - self.assertEqual("qgis", git_url_parsed.groups_path) - self.assertEqual("Oslandia", git_url_parsed.owner) - self.assertEqual("gitlab", git_url_parsed.platform) - self.assertEqual("profils_qgis_fr", git_url_parsed.repo) + self.assertIn("github.com", git_url_parsed.domain) + self.assertIn("github.com", git_url_parsed.host) + self.assertEqual("", git_url_parsed.groups_path) + self.assertEqual("geotribu", git_url_parsed.owner) + self.assertEqual("github", git_url_parsed.platform) + self.assertEqual("profils-qgis", git_url_parsed.repo) def test_git_clone_remote_url(self): """Test git parsed URL.""" - self.good_git_url = "https://gitlab.com/Oslandia/qgis/profils_qgis_fr.git" git_handler = RemoteGitHandler(source_repository_url=self.good_git_url) - with tempfile.TemporaryDirectory(ignore_cleanup_errors=True) as tmpdirname: - local_dest = Path(tmpdirname) / "test_git_clone" + with tempfile.TemporaryDirectory( + prefix="QDT_test_remote_git_", + ignore_cleanup_errors=True, + suffix="_git_clone", + ) as tmpdirname: # clone - git_handler.download(destination_local_path=local_dest) + git_handler.download(destination_local_path=Path(tmpdirname)) # check if clone worked and new folder is a local git repo - self.assertTrue(git_handler._is_local_path_git_repository(local_dest)) + self.assertTrue(git_handler._is_local_path_git_repository(Path(tmpdirname))) # check pull is working - git_handler.clone_or_pull(to_local_destination_path=local_dest) + git_handler.clone_or_pull(to_local_destination_path=Path(tmpdirname)) + + def test_change_remote_branch(self): + """Test git parsed URL.""" + git_handler = RemoteGitHandler( + source_repository_url=self.good_git_url, + branch_to_use="main", + ) + + with tempfile.TemporaryDirectory( + prefix="QDT_test_remote_git_", + ignore_cleanup_errors=True, + suffix="_change_remote_branch", + ) as tmpdirname: + # clone + git_handler.download(destination_local_path=Path(tmpdirname)) + # check if clone worked and new folder is a local git repo + self.assertTrue(git_handler._is_local_path_git_repository(Path(tmpdirname))) + + git_handler = RemoteGitHandler( + source_repository_url=self.good_git_url, + branch_to_use="another_branch", + ) + git_handler.download(destination_local_path=Path(tmpdirname)) + self.assertTrue(git_handler._is_local_path_git_repository(Path(tmpdirname))) From cef337f097fd8caafaccc002249794d88b839f5d Mon Sep 17 00:00:00 2001 From: nicogodet Date: Tue, 20 May 2025 15:50:53 +0200 Subject: [PATCH 4/4] Avoid file leaking --- qgis_deployment_toolbelt/profiles/profiles_handler_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py index 1737200a..2bd901e3 100644 --- a/qgis_deployment_toolbelt/profiles/profiles_handler_base.py +++ b/qgis_deployment_toolbelt/profiles/profiles_handler_base.py @@ -432,7 +432,7 @@ def clone_or_pull(self, to_local_destination_path: Path, attempt: int = 1) -> Re # check if the active branch is the one to use # if not, remove the local folder and clone again local_branch = porcelain.active_branch( - repo=Repo(root=f"{to_local_destination_path.resolve()}") + repo=f"{to_local_destination_path.resolve()}" ).decode() if local_branch != self.DESTINATION_BRANCH_TO_USE: logger.debug(