From 6e2a7e521ca1e9b8aae58bbe4eaebbb107d828bb Mon Sep 17 00:00:00 2001 From: Rodos Date: Sun, 7 Dec 2025 21:21:14 +1100 Subject: [PATCH] fix: --all-starred now clones repos without --repositories --- github_backup/github_backup.py | 14 ++- tests/test_all_starred.py | 161 +++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 8 deletions(-) create mode 100644 tests/test_all_starred.py diff --git a/github_backup/github_backup.py b/github_backup/github_backup.py index cdb536d..bbacdae 100644 --- a/github_backup/github_backup.py +++ b/github_backup/github_backup.py @@ -561,7 +561,7 @@ def get_github_host(args): def read_file_contents(file_uri): - return open(file_uri[len(FILE_URI_PREFIX) :], "rt").readline().strip() + return open(file_uri[len(FILE_URI_PREFIX):], "rt").readline().strip() def get_github_repo_url(args, repository): @@ -1672,9 +1672,10 @@ def backup_repositories(args, output_directory, repositories): repo_url = get_github_repo_url(args, repository) include_gists = args.include_gists or args.include_starred_gists + include_starred = args.all_starred and repository.get("is_starred") if (args.include_repository or args.include_everything) or ( include_gists and repository.get("is_gist") - ): + ) or include_starred: repo_name = ( repository.get("name") if not repository.get("is_gist") @@ -2023,12 +2024,9 @@ def fetch_repository( ): if bare_clone: if os.path.exists(local_dir): - clone_exists = ( - subprocess.check_output( - ["git", "rev-parse", "--is-bare-repository"], cwd=local_dir - ) - == b"true\n" - ) + clone_exists = subprocess.check_output( + ["git", "rev-parse", "--is-bare-repository"], cwd=local_dir + ) == b"true\n" else: clone_exists = False else: diff --git a/tests/test_all_starred.py b/tests/test_all_starred.py new file mode 100644 index 0000000..f59a67e --- /dev/null +++ b/tests/test_all_starred.py @@ -0,0 +1,161 @@ +"""Tests for --all-starred flag behavior (issue #225).""" + +import pytest +from unittest.mock import Mock, patch + +from github_backup import github_backup + + +class TestAllStarredCloning: + """Test suite for --all-starred repository cloning behavior. + + Issue #225: --all-starred should clone starred repos without requiring --repositories. + """ + + def _create_mock_args(self, **overrides): + """Create a mock args object with sensible defaults.""" + args = Mock() + args.user = "testuser" + args.output_directory = "/tmp/backup" + args.include_repository = False + args.include_everything = False + args.include_gists = False + args.include_starred_gists = False + args.all_starred = False + args.skip_existing = False + args.bare_clone = False + args.lfs_clone = False + args.no_prune = False + args.include_wiki = False + args.include_issues = False + args.include_issue_comments = False + args.include_issue_events = False + args.include_pulls = False + args.include_pull_comments = False + args.include_pull_commits = False + args.include_pull_details = False + args.include_labels = False + args.include_hooks = False + args.include_milestones = False + args.include_releases = False + args.include_assets = False + args.include_attachments = False + args.incremental = False + args.incremental_by_files = False + args.github_host = None + args.prefer_ssh = False + args.token_classic = None + args.token_fine = None + args.username = None + args.password = None + args.as_app = False + args.osx_keychain_item_name = None + args.osx_keychain_item_account = None + + for key, value in overrides.items(): + setattr(args, key, value) + + return args + + @patch('github_backup.github_backup.fetch_repository') + @patch('github_backup.github_backup.get_github_repo_url') + def test_all_starred_clones_without_repositories_flag(self, mock_get_url, mock_fetch): + """--all-starred should clone starred repos without --repositories flag. + + This is the core fix for issue #225. + """ + args = self._create_mock_args(all_starred=True) + mock_get_url.return_value = "https://github.com/otheruser/awesome-project.git" + + # A starred repository (is_starred flag set by retrieve_repositories) + starred_repo = { + "name": "awesome-project", + "full_name": "otheruser/awesome-project", + "owner": {"login": "otheruser"}, + "private": False, + "fork": False, + "has_wiki": False, + "is_starred": True, # This flag is set for starred repos + } + + with patch('github_backup.github_backup.mkdir_p'): + github_backup.backup_repositories(args, "/tmp/backup", [starred_repo]) + + # fetch_repository should be called for the starred repo + assert mock_fetch.called, "--all-starred should trigger repository cloning" + mock_fetch.assert_called_once() + call_args = mock_fetch.call_args + assert call_args[0][0] == "awesome-project" # repo name + + @patch('github_backup.github_backup.fetch_repository') + @patch('github_backup.github_backup.get_github_repo_url') + def test_starred_repo_not_cloned_without_all_starred_flag(self, mock_get_url, mock_fetch): + """Starred repos should NOT be cloned if --all-starred is not set.""" + args = self._create_mock_args(all_starred=False) + mock_get_url.return_value = "https://github.com/otheruser/awesome-project.git" + + starred_repo = { + "name": "awesome-project", + "full_name": "otheruser/awesome-project", + "owner": {"login": "otheruser"}, + "private": False, + "fork": False, + "has_wiki": False, + "is_starred": True, + } + + with patch('github_backup.github_backup.mkdir_p'): + github_backup.backup_repositories(args, "/tmp/backup", [starred_repo]) + + # fetch_repository should NOT be called + assert not mock_fetch.called, "Starred repos should not be cloned without --all-starred" + + @patch('github_backup.github_backup.fetch_repository') + @patch('github_backup.github_backup.get_github_repo_url') + def test_non_starred_repo_not_cloned_with_only_all_starred(self, mock_get_url, mock_fetch): + """Non-starred repos should NOT be cloned when only --all-starred is set.""" + args = self._create_mock_args(all_starred=True) + mock_get_url.return_value = "https://github.com/testuser/my-project.git" + + # A regular (non-starred) repository + regular_repo = { + "name": "my-project", + "full_name": "testuser/my-project", + "owner": {"login": "testuser"}, + "private": False, + "fork": False, + "has_wiki": False, + # No is_starred flag + } + + with patch('github_backup.github_backup.mkdir_p'): + github_backup.backup_repositories(args, "/tmp/backup", [regular_repo]) + + # fetch_repository should NOT be called for non-starred repos + assert not mock_fetch.called, "Non-starred repos should not be cloned with only --all-starred" + + @patch('github_backup.github_backup.fetch_repository') + @patch('github_backup.github_backup.get_github_repo_url') + def test_repositories_flag_still_works(self, mock_get_url, mock_fetch): + """--repositories flag should still clone repos as before.""" + args = self._create_mock_args(include_repository=True) + mock_get_url.return_value = "https://github.com/testuser/my-project.git" + + regular_repo = { + "name": "my-project", + "full_name": "testuser/my-project", + "owner": {"login": "testuser"}, + "private": False, + "fork": False, + "has_wiki": False, + } + + with patch('github_backup.github_backup.mkdir_p'): + github_backup.backup_repositories(args, "/tmp/backup", [regular_repo]) + + # fetch_repository should be called + assert mock_fetch.called, "--repositories should trigger repository cloning" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"])