gerrit-clone 2.2.4__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/.pre-commit-config.yaml +4 -4
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/PKG-INFO +37 -18
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/README.md +36 -17
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_clone_run.py +31 -98
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_clone_setup.py +11 -3
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_mirror_run.py +27 -4
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_refresh_run.py +51 -121
- gerrit_clone-2.3.0/src/gerrit_clone/content_intent.py +230 -0
- gerrit_clone-2.3.0/src/gerrit_clone/content_intent_resolve.py +154 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_policy.py +38 -71
- gerrit_clone-2.3.0/src/gerrit_clone/content_spec.py +130 -0
- gerrit_clone-2.3.0/src/gerrit_clone/content_stage.py +99 -0
- gerrit_clone-2.3.0/src/gerrit_clone/mirror_cleanup.py +166 -0
- gerrit_clone-2.3.0/src/gerrit_clone/mirror_content_filters.py +150 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_manager.py +27 -33
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/models.py +1 -1
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_filtered.py +45 -7
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_manager.py +2 -2
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_parallel.py +1 -1
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_worker.py +18 -4
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/conftest.py +164 -1
- gerrit_clone-2.3.0/tests/test_content_intent.py +435 -0
- gerrit_clone-2.3.0/tests/test_content_intent_commands.py +458 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_content_legacy.py +149 -4
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_content_policy.py +2 -2
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_mirror_manager.py +21 -7
- gerrit_clone-2.3.0/tests/test_network_guard.py +252 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_refresh_bare_mirrors.py +47 -11
- gerrit_clone-2.2.4/src/gerrit_clone/mirror_cleanup.py +0 -68
- gerrit_clone-2.2.4/src/gerrit_clone/mirror_content_filters.py +0 -218
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/.gitignore +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/IMPLEMENTATION.md +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/LICENSE +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/action.yaml +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/pyproject.toml +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/__init__.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_app.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_clone.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_clone_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_config.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_hooks.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_mirror.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_mirror_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_mirror_setup.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_options_clone.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_options_mirror.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_options_refresh.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_options_reset.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_refresh.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_refresh_report.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_reset.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_reset_run.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/cli_session.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_batching.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_cleanup.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_collection.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_conflicts.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_diagnostics.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_git_env.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_locking.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_manager.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_nesting.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_orchestrator.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_ordering.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_refresh.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_reporting.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_reservations.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_results.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_retry_policy.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_stages.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_timeout.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/clone_utils.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/concurrent_utils.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/config.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/config_env.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/config_errors.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_filter.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_git.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_legacy.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_origin.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_patterns.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_redaction.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_removal.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_scan.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_scan_process.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/content_worktree.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/error_codes.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/error_collection.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/file_logging.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/gerrit_api.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/git_comparison.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/git_credential_env.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/git_utils.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_api.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_async_pacing.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_async_repo.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_batch_support.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_clone_env.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_clone_results.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_clone_url.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_gh_cli.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_graphql.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_repo_ops.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_token_hygiene.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_transport.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_url_safety.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/github_worker.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/logging.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_branch_repair.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_default_branch.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_filtering.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_git_refs.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_planning.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_push.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/mirror_result_builder.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/model_clone_results.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/model_config_validation.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/model_enums.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/model_project.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/model_project_filters.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/model_refresh_results.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/netrc.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/netrc_discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/netrc_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/netrc_parser.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/output_utils.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/path_errors.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/path_naming.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/pathing.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/process_groups.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/progress.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/progress_display.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/progress_live.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/progress_modes.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/progress_results.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/progress_summary.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/py.typed +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/rate_limit.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/rate_limit_budget.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/rate_limit_helpers.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/rate_limit_progress.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_branch_repair.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_execution.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_force.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_git_env.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_output.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/refresh_repo_state.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/reset_confirmation.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/reset_deletion.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/reset_display.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/reset_manager.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/reset_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/retry.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/rich_optional.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/rich_status.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/ssh_discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/ssh_identity.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/subprocess_streaming.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/subprocess_tracking.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/unified_discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/worker.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/src/gerrit_clone/worker_preflight.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/__init__.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/integration/__init__.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/integration/test_discovery_integration.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/integration/test_github_clone.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_action_cli_alignment.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_cli_netrc_options.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_clone_git_env.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_clone_manager_github_refresh.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_clone_nesting.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_clone_orchestrator_timeout.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_clone_results.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_clone_utils.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_config.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_content_filter.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_content_origin.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_content_redaction.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_content_tracking.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_content_worktree_failures.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_file_logging_changes.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_gerrit_api.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_git_comparison.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_git_credential_env.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_git_utils.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_github_api.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_logging.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_netrc.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_output_utils.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_pathing.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_project_filtering.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_rate_limit.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_refresh_manager.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_refresh_remotes.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_refresh_tracking.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_refresh_worker.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_reset_manager.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_reset_models.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_retry.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_rich_optional.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_ssh_identity.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_subprocess_tracking.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/test_worker.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/unit/__init__.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/unit/test_discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/unit/test_github_discovery.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/unit/test_github_worker.py +0 -0
- {gerrit_clone-2.2.4 → gerrit_clone-2.3.0}/tests/unit/test_unified_discovery.py +0 -0
|
@@ -74,14 +74,14 @@ repos:
|
|
|
74
74
|
types: [yaml]
|
|
75
75
|
|
|
76
76
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
77
|
-
rev:
|
|
77
|
+
rev: f12be1ebaa5351c1fc76472de98db2c3446c8253 # frozen: v0.16.10
|
|
78
78
|
hooks:
|
|
79
79
|
- id: ruff
|
|
80
80
|
args: [--fix, --exit-non-zero-on-fix]
|
|
81
81
|
- id: ruff-format
|
|
82
82
|
|
|
83
83
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
|
84
|
-
rev:
|
|
84
|
+
rev: 2834ec6639549dd6796205c8f011dedcd587288b # frozen: v2.4.0
|
|
85
85
|
hooks:
|
|
86
86
|
- id: mypy
|
|
87
87
|
additional_dependencies:
|
|
@@ -126,12 +126,12 @@ repos:
|
|
|
126
126
|
- id: codespell
|
|
127
127
|
|
|
128
128
|
- repo: https://github.com/DetachHead/basedpyright-prek-mirror
|
|
129
|
-
rev:
|
|
129
|
+
rev: a11caebebdbf797e1e96611123db279b334a7675 # frozen: 1.40.2
|
|
130
130
|
hooks:
|
|
131
131
|
- id: basedpyright
|
|
132
132
|
|
|
133
133
|
- repo: https://github.com/lfreleng-actions/gha-workflow-linter
|
|
134
|
-
rev:
|
|
134
|
+
rev: b23b60e5884e985320a030e6114032152e2373cd # frozen: v1.7.1
|
|
135
135
|
hooks:
|
|
136
136
|
- id: gha-workflow-linter
|
|
137
137
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: gerrit-clone
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: A multi-threaded CLI tool for bulk cloning repositories from Gerrit servers
|
|
5
5
|
Project-URL: Homepage, https://github.com/lfreleng-actions/gerrit-clone-action
|
|
6
6
|
Project-URL: Documentation, https://github.com/lfreleng-actions/gerrit-clone-action#readme
|
|
@@ -237,24 +237,35 @@ nothing. If the
|
|
|
237
237
|
command finds no repositories at all, it says so and exits with status 1
|
|
238
238
|
rather than reporting success.
|
|
239
239
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
240
|
+
Content filtering (`--remove-files`, `--git-filter`, `--redact-secrets`) is a
|
|
241
|
+
decision about a whole clone tree, and later runs keep to it. The first run
|
|
242
|
+
that filters writes the decision to `.gerrit-clone/filter-policy.json` at the
|
|
243
|
+
root of the tree. Every later `clone`, `refresh` or `mirror` in that tree
|
|
244
|
+
applies it, even without the options: to repositories cloned later, to files
|
|
245
|
+
that appear upstream later, and to a repository you delete and clone again.
|
|
246
|
+
Options add to the decision; leaving one out never removes a filter. The file
|
|
247
|
+
keeps each `--git-filter` token as a SHA-256 digest only, so a run must pass
|
|
248
|
+
those tokens again. If a run lacks a project's tokens, `refresh` skips that
|
|
249
|
+
project, `clone` exits with a filtering failure, and `mirror` aborts the
|
|
250
|
+
whole batch before it pushes anything; each gives the number of tokens the
|
|
251
|
+
project needs. A file the tool cannot read stops the run.
|
|
252
|
+
|
|
253
|
+
Each repository also records which filters rewrote it. Fetching would force
|
|
254
|
+
the original history back, filtered content included, so refresh copies each
|
|
255
|
+
mirror it filters, fetches and filters the copy, and publishes its refs only
|
|
256
|
+
when every step succeeds; a failure leaves the mirror unchanged. A filtered
|
|
257
|
+
working copy (`--no-mirror`) always gets skipped: re-clone it to update it.
|
|
258
|
+
Every remote of a filtered repository refuses pushes. A tree filtered before
|
|
259
|
+
the tree-level file existed gains one from these per-repository records on
|
|
260
|
+
its next run. Releases up to v2.2.4 recorded no filters at all, so refresh
|
|
261
|
+
skips any repository they filtered, whatever filters you pass, and asks you
|
|
262
|
+
to re-clone it, and `mirror --overwrite` leaves it out of the run rather
|
|
263
|
+
than cloning or pushing it: delete it yourself, then clone it again with the
|
|
264
|
+
filters it needs. The tool recognises one by the traces those releases left: the
|
|
254
265
|
commit map a `git filter-repo` rewrite leaves in the git directory, or a
|
|
255
266
|
branch tip committed by the file-removal fallback (`Remove filtered files
|
|
256
267
|
for platform sync`).
|
|
257
|
-
A `--dry-run` applies no content filters.
|
|
268
|
+
A `--dry-run` applies no content filters and records nothing.
|
|
258
269
|
|
|
259
270
|
Refresh all repositories in the current directory:
|
|
260
271
|
|
|
@@ -915,8 +926,16 @@ export GITHUB_TOKEN=github_pat_your_token_here
|
|
|
915
926
|
|
|
916
927
|
**With `--overwrite` flag**:
|
|
917
928
|
|
|
918
|
-
- Removes and re-clones local repositories
|
|
919
|
-
|
|
929
|
+
- Removes and re-clones local repositories, then pushes each one
|
|
930
|
+
(`git push --mirror`) to its GitHub repository, existing ones included;
|
|
931
|
+
the tree's recorded content filters still apply first
|
|
932
|
+
- Leaves out of the run any repository a release up to v2.2.4
|
|
933
|
+
content-filtered, and any directory holding one: those releases recorded
|
|
934
|
+
no filters to apply again, so the run neither re-clones nor pushes it,
|
|
935
|
+
`--recreate` leaves its GitHub repository alone, and the manifest lists
|
|
936
|
+
it as skipped
|
|
937
|
+
- Without `--recreate`, reuses existing GitHub repositories rather than
|
|
938
|
+
deleting them
|
|
920
939
|
|
|
921
940
|
**With both `--recreate` and `--overwrite`**:
|
|
922
941
|
|
|
@@ -195,24 +195,35 @@ nothing. If the
|
|
|
195
195
|
command finds no repositories at all, it says so and exits with status 1
|
|
196
196
|
rather than reporting success.
|
|
197
197
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
198
|
+
Content filtering (`--remove-files`, `--git-filter`, `--redact-secrets`) is a
|
|
199
|
+
decision about a whole clone tree, and later runs keep to it. The first run
|
|
200
|
+
that filters writes the decision to `.gerrit-clone/filter-policy.json` at the
|
|
201
|
+
root of the tree. Every later `clone`, `refresh` or `mirror` in that tree
|
|
202
|
+
applies it, even without the options: to repositories cloned later, to files
|
|
203
|
+
that appear upstream later, and to a repository you delete and clone again.
|
|
204
|
+
Options add to the decision; leaving one out never removes a filter. The file
|
|
205
|
+
keeps each `--git-filter` token as a SHA-256 digest only, so a run must pass
|
|
206
|
+
those tokens again. If a run lacks a project's tokens, `refresh` skips that
|
|
207
|
+
project, `clone` exits with a filtering failure, and `mirror` aborts the
|
|
208
|
+
whole batch before it pushes anything; each gives the number of tokens the
|
|
209
|
+
project needs. A file the tool cannot read stops the run.
|
|
210
|
+
|
|
211
|
+
Each repository also records which filters rewrote it. Fetching would force
|
|
212
|
+
the original history back, filtered content included, so refresh copies each
|
|
213
|
+
mirror it filters, fetches and filters the copy, and publishes its refs only
|
|
214
|
+
when every step succeeds; a failure leaves the mirror unchanged. A filtered
|
|
215
|
+
working copy (`--no-mirror`) always gets skipped: re-clone it to update it.
|
|
216
|
+
Every remote of a filtered repository refuses pushes. A tree filtered before
|
|
217
|
+
the tree-level file existed gains one from these per-repository records on
|
|
218
|
+
its next run. Releases up to v2.2.4 recorded no filters at all, so refresh
|
|
219
|
+
skips any repository they filtered, whatever filters you pass, and asks you
|
|
220
|
+
to re-clone it, and `mirror --overwrite` leaves it out of the run rather
|
|
221
|
+
than cloning or pushing it: delete it yourself, then clone it again with the
|
|
222
|
+
filters it needs. The tool recognises one by the traces those releases left: the
|
|
212
223
|
commit map a `git filter-repo` rewrite leaves in the git directory, or a
|
|
213
224
|
branch tip committed by the file-removal fallback (`Remove filtered files
|
|
214
225
|
for platform sync`).
|
|
215
|
-
A `--dry-run` applies no content filters.
|
|
226
|
+
A `--dry-run` applies no content filters and records nothing.
|
|
216
227
|
|
|
217
228
|
Refresh all repositories in the current directory:
|
|
218
229
|
|
|
@@ -873,8 +884,16 @@ export GITHUB_TOKEN=github_pat_your_token_here
|
|
|
873
884
|
|
|
874
885
|
**With `--overwrite` flag**:
|
|
875
886
|
|
|
876
|
-
- Removes and re-clones local repositories
|
|
877
|
-
|
|
887
|
+
- Removes and re-clones local repositories, then pushes each one
|
|
888
|
+
(`git push --mirror`) to its GitHub repository, existing ones included;
|
|
889
|
+
the tree's recorded content filters still apply first
|
|
890
|
+
- Leaves out of the run any repository a release up to v2.2.4
|
|
891
|
+
content-filtered, and any directory holding one: those releases recorded
|
|
892
|
+
no filters to apply again, so the run neither re-clones nor pushes it,
|
|
893
|
+
`--recreate` leaves its GitHub repository alone, and the manifest lists
|
|
894
|
+
it as skipped
|
|
895
|
+
- Without `--recreate`, reuses existing GitHub repositories rather than
|
|
896
|
+
deleting them
|
|
878
897
|
|
|
879
898
|
**With both `--recreate` and `--overwrite`**:
|
|
880
899
|
|
|
@@ -21,12 +21,7 @@ from rich.text import Text
|
|
|
21
21
|
from gerrit_clone import __version__, cli_hooks
|
|
22
22
|
from gerrit_clone import cli_clone_setup as setup
|
|
23
23
|
from gerrit_clone.cli_app import is_github_actions_context
|
|
24
|
-
from gerrit_clone.
|
|
25
|
-
apply_content_filters,
|
|
26
|
-
is_shallow_repository,
|
|
27
|
-
normalize_file_patterns,
|
|
28
|
-
parse_git_filter_spec,
|
|
29
|
-
)
|
|
24
|
+
from gerrit_clone.content_stage import filter_repository
|
|
30
25
|
from gerrit_clone.error_codes import DiscoveryError, ExitCode
|
|
31
26
|
from gerrit_clone.rich_status import (
|
|
32
27
|
handle_crash_display,
|
|
@@ -37,7 +32,8 @@ from gerrit_clone.rich_status import (
|
|
|
37
32
|
if TYPE_CHECKING:
|
|
38
33
|
from gerrit_clone.cli_clone_models import CloneRequest
|
|
39
34
|
from gerrit_clone.cli_session import CliSession
|
|
40
|
-
from gerrit_clone.
|
|
35
|
+
from gerrit_clone.content_spec import ContentFilterSpec
|
|
36
|
+
from gerrit_clone.models import BatchResult, Config
|
|
41
37
|
|
|
42
38
|
|
|
43
39
|
def run_clone(request: CloneRequest, session: CliSession) -> None:
|
|
@@ -70,7 +66,7 @@ def run_clone(request: CloneRequest, session: CliSession) -> None:
|
|
|
70
66
|
setup.show_startup_banner(console, config)
|
|
71
67
|
|
|
72
68
|
batch_result = _clone_repositories(config)
|
|
73
|
-
_apply_content_filters(request, console, batch_result)
|
|
69
|
+
_apply_content_filters(request, console, batch_result, config.content_filters)
|
|
74
70
|
_report_results(request, session, batch_result)
|
|
75
71
|
exit_code = _determine_exit_code(session, batch_result)
|
|
76
72
|
|
|
@@ -105,116 +101,53 @@ def _clone_repositories(config: Config) -> BatchResult:
|
|
|
105
101
|
|
|
106
102
|
|
|
107
103
|
def _apply_content_filters(
|
|
108
|
-
request: CloneRequest,
|
|
104
|
+
request: CloneRequest,
|
|
105
|
+
console: Console,
|
|
106
|
+
batch_result: BatchResult,
|
|
107
|
+
spec: ContentFilterSpec | None,
|
|
109
108
|
) -> None:
|
|
110
|
-
"""
|
|
111
|
-
|
|
112
|
-
normalize_file_patterns([request.remove_files])
|
|
113
|
-
if request.remove_files
|
|
114
|
-
else None
|
|
115
|
-
)
|
|
116
|
-
git_filter_projects = (
|
|
117
|
-
parse_git_filter_spec(request.git_filter) if request.git_filter else None
|
|
118
|
-
)
|
|
119
|
-
if not (remove_file_patterns or git_filter_projects or request.redact_secrets):
|
|
109
|
+
"""Filter every successful clone, as each project's filters decide."""
|
|
110
|
+
if spec is None:
|
|
120
111
|
return
|
|
121
112
|
|
|
122
113
|
if not request.quiet:
|
|
123
114
|
console.print("[cyan]🔧 Applying content filters...[/cyan]")
|
|
124
115
|
filter_success = filter_fail = 0
|
|
125
116
|
for cr in batch_result.results:
|
|
117
|
+
if cr.skipped and cr.path and spec.missing_tokens(cr.path):
|
|
118
|
+
# The refresh pass refused it for the tokens, before any
|
|
119
|
+
# fetch. Clone reports that as the filtering failure it is,
|
|
120
|
+
# whether or not upstream had moved on.
|
|
121
|
+
filter_fail += 1
|
|
122
|
+
if not request.quiet:
|
|
123
|
+
console.print(
|
|
124
|
+
f"[yellow]⚠️ Filter failed for {cr.project.name}: "
|
|
125
|
+
f"{cr.error_message}[/yellow]"
|
|
126
|
+
)
|
|
127
|
+
continue
|
|
126
128
|
if not cr.success or not cr.path:
|
|
127
129
|
continue
|
|
128
130
|
if cr.content_filtered:
|
|
129
131
|
# Re-filtered as part of its staged refresh already.
|
|
130
132
|
filter_success += 1
|
|
131
133
|
continue
|
|
132
|
-
|
|
133
|
-
|
|
134
|
+
reason = filter_repository(
|
|
135
|
+
spec, cr.path, spec.project_name(cr.path), request.clone_timeout
|
|
134
136
|
)
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
f"[cyan]Content filtering: {filter_success} succeeded, {filter_fail} failed[/cyan]"
|
|
140
|
-
)
|
|
141
|
-
if filter_fail > 0:
|
|
142
|
-
raise typer.Exit(ExitCode.GENERAL_ERROR)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def _filter_repository(
|
|
146
|
-
request: CloneRequest,
|
|
147
|
-
console: Console,
|
|
148
|
-
result: CloneResult,
|
|
149
|
-
remove_file_patterns: list[str] | None,
|
|
150
|
-
git_filter_projects: dict[str, list[str]] | None,
|
|
151
|
-
) -> tuple[int, int]:
|
|
152
|
-
"""Filter one cloned repository.
|
|
153
|
-
|
|
154
|
-
Returns:
|
|
155
|
-
Tuple of (successes, failures) to add to the run totals
|
|
156
|
-
"""
|
|
157
|
-
# Fail closed on shallow repositories when history-
|
|
158
|
-
# dependent filters are requested: --git-filter /
|
|
159
|
-
# --redact-secrets rely on the full commit history, so
|
|
160
|
-
# a shallow repo can hide older secrets (and a later
|
|
161
|
-
# unshallow fetch could reintroduce blocked content),
|
|
162
|
-
# giving a false sense of safety. Probe each repo
|
|
163
|
-
# individually rather than only checking config.depth:
|
|
164
|
-
# a repo that already existed locally (e.g. cloned
|
|
165
|
-
# earlier with --depth) is shallow even when this run
|
|
166
|
-
# sets no --depth. --remove-files targets file paths
|
|
167
|
-
# present at the branch tips and does not depend on
|
|
168
|
-
# full history being available (though it may still
|
|
169
|
-
# rewrite history via git filter-repo when that tool
|
|
170
|
-
# is present), so it is still applied; only the unsafe
|
|
171
|
-
# history-scanning filters are dropped for the shallow
|
|
172
|
-
# repo.
|
|
173
|
-
repo_git_filter = git_filter_projects
|
|
174
|
-
repo_redact = request.redact_secrets
|
|
175
|
-
history_filters_skipped = False
|
|
176
|
-
if (git_filter_projects or request.redact_secrets) and (
|
|
177
|
-
is_shallow_repository(result.path)
|
|
178
|
-
):
|
|
137
|
+
if reason is None:
|
|
138
|
+
filter_success += 1
|
|
139
|
+
continue
|
|
140
|
+
filter_fail += 1
|
|
179
141
|
if not request.quiet:
|
|
180
142
|
console.print(
|
|
181
|
-
"[
|
|
182
|
-
"--redact-secrets on shallow repo "
|
|
183
|
-
f"{result.project.name}: truncated history can hide "
|
|
184
|
-
"older secrets. Re-clone without --depth.[/red]"
|
|
143
|
+
f"[yellow]⚠️ Filter failed for {cr.project.name}: {reason}[/yellow]"
|
|
185
144
|
)
|
|
186
|
-
# The requested redaction/rewrite did not run, so
|
|
187
|
-
# this repo counts as a filtering failure even if
|
|
188
|
-
# the safe --remove-files step below succeeds.
|
|
189
|
-
history_filters_skipped = True
|
|
190
|
-
repo_git_filter = None
|
|
191
|
-
repo_redact = False
|
|
192
|
-
if not remove_file_patterns:
|
|
193
|
-
# Nothing history-independent left to do.
|
|
194
|
-
return 0, 1
|
|
195
|
-
success, error = apply_content_filters(
|
|
196
|
-
result.path,
|
|
197
|
-
result.project.name,
|
|
198
|
-
remove_patterns=remove_file_patterns,
|
|
199
|
-
git_filter_projects=repo_git_filter,
|
|
200
|
-
redact_secrets=repo_redact,
|
|
201
|
-
timeout=request.clone_timeout,
|
|
202
|
-
)
|
|
203
|
-
if history_filters_skipped:
|
|
204
|
-
# Already counted as a failure above; don't also count
|
|
205
|
-
# the safe --remove-files step as a success.
|
|
206
|
-
if not success and not request.quiet:
|
|
207
|
-
console.print(
|
|
208
|
-
f"[yellow]⚠️ Filter failed for {result.project.name}: {error}[/yellow]"
|
|
209
|
-
)
|
|
210
|
-
return 0, 1
|
|
211
|
-
if success:
|
|
212
|
-
return 1, 0
|
|
213
145
|
if not request.quiet:
|
|
214
146
|
console.print(
|
|
215
|
-
f"[
|
|
147
|
+
f"[cyan]Content filtering: {filter_success} succeeded, {filter_fail} failed[/cyan]"
|
|
216
148
|
)
|
|
217
|
-
|
|
149
|
+
if filter_fail > 0:
|
|
150
|
+
raise typer.Exit(ExitCode.GENERAL_ERROR)
|
|
218
151
|
|
|
219
152
|
|
|
220
153
|
def _report_results(
|
|
@@ -21,7 +21,9 @@ from rich.text import Text
|
|
|
21
21
|
from gerrit_clone import cli_hooks
|
|
22
22
|
from gerrit_clone.cli_app import format_version_string
|
|
23
23
|
from gerrit_clone.config import ConfigurationError, load_config
|
|
24
|
-
from gerrit_clone.
|
|
24
|
+
from gerrit_clone.content_intent import IntentError
|
|
25
|
+
from gerrit_clone.content_intent_resolve import resolve_filters
|
|
26
|
+
from gerrit_clone.content_spec import ContentFilterSpec
|
|
25
27
|
from gerrit_clone.error_codes import ExitCode
|
|
26
28
|
from gerrit_clone.file_logging import (
|
|
27
29
|
cli_args_to_dict,
|
|
@@ -318,13 +320,19 @@ def build_config(
|
|
|
318
320
|
except ConfigurationError as e:
|
|
319
321
|
_report_configuration_error(session, e)
|
|
320
322
|
raise typer.Exit(ExitCode.CONFIGURATION_ERROR) from e
|
|
321
|
-
|
|
323
|
+
options = ContentFilterSpec.from_options(
|
|
322
324
|
request.remove_files, request.git_filter, request.redact_secrets, config.path
|
|
323
325
|
)
|
|
326
|
+
try:
|
|
327
|
+
# Written down before anything is cloned or refreshed.
|
|
328
|
+
config.content_filters = resolve_filters(config.path, options, persist=True)
|
|
329
|
+
except IntentError as e:
|
|
330
|
+
_report_configuration_error(session, e)
|
|
331
|
+
raise typer.Exit(ExitCode.CONFIGURATION_ERROR) from e
|
|
324
332
|
return config
|
|
325
333
|
|
|
326
334
|
|
|
327
|
-
def _report_configuration_error(session: CliSession, error:
|
|
335
|
+
def _report_configuration_error(session: CliSession, error: Exception) -> None:
|
|
328
336
|
"""Log and display a configuration error before aborting."""
|
|
329
337
|
if session.file_logger:
|
|
330
338
|
session.file_logger.error("Configuration error: %s", str(error))
|
|
@@ -28,6 +28,7 @@ from gerrit_clone.cli_mirror_setup import (
|
|
|
28
28
|
validate_request,
|
|
29
29
|
)
|
|
30
30
|
from gerrit_clone.content_filter import normalize_file_patterns, parse_git_filter_spec
|
|
31
|
+
from gerrit_clone.content_intent import IntentError
|
|
31
32
|
from gerrit_clone.error_codes import ExitCode
|
|
32
33
|
from gerrit_clone.mirror_manager import (
|
|
33
34
|
MirrorBatchResult,
|
|
@@ -43,6 +44,7 @@ if TYPE_CHECKING:
|
|
|
43
44
|
|
|
44
45
|
from gerrit_clone.cli_mirror_models import MirrorRequest
|
|
45
46
|
from gerrit_clone.cli_session import CliSession
|
|
47
|
+
from gerrit_clone.mirror_models import MirrorResult
|
|
46
48
|
from gerrit_clone.models import Config, Project
|
|
47
49
|
|
|
48
50
|
|
|
@@ -102,11 +104,17 @@ def run_mirror(request: MirrorRequest, session: CliSession) -> None:
|
|
|
102
104
|
redact_secrets=request.redact_secrets,
|
|
103
105
|
)
|
|
104
106
|
|
|
107
|
+
if not projects_to_mirror:
|
|
108
|
+
# Nothing to mirror, but the run's filters still decide for the
|
|
109
|
+
# projects a later run selects: the manager records them.
|
|
110
|
+
_mirror(mirror_manager, [], console)
|
|
111
|
+
raise typer.Exit(0)
|
|
112
|
+
|
|
105
113
|
started_at = datetime.now(UTC)
|
|
106
114
|
if not request.quiet:
|
|
107
115
|
console.print("🚀 Starting mirror operation...")
|
|
108
116
|
|
|
109
|
-
results = mirror_manager
|
|
117
|
+
results = _mirror(mirror_manager, projects_to_mirror, console)
|
|
110
118
|
|
|
111
119
|
completed_at = datetime.now(UTC)
|
|
112
120
|
|
|
@@ -130,6 +138,17 @@ def run_mirror(request: MirrorRequest, session: CliSession) -> None:
|
|
|
130
138
|
_finish(request, session, batch_result)
|
|
131
139
|
|
|
132
140
|
|
|
141
|
+
def _mirror(
|
|
142
|
+
mirror_manager: MirrorManager, projects: list[Project], console: Console
|
|
143
|
+
) -> list[MirrorResult]:
|
|
144
|
+
"""Mirror *projects*; a tree whose filter intent is unreadable stops the run."""
|
|
145
|
+
try:
|
|
146
|
+
return mirror_manager.mirror_projects(projects)
|
|
147
|
+
except IntentError as exc:
|
|
148
|
+
console.print(f"[red]❌ {exc}[/red]")
|
|
149
|
+
raise typer.Exit(ExitCode.CONFIGURATION_ERROR) from exc
|
|
150
|
+
|
|
151
|
+
|
|
133
152
|
def _select_projects(
|
|
134
153
|
request: MirrorRequest,
|
|
135
154
|
console: Console,
|
|
@@ -137,12 +156,16 @@ def _select_projects(
|
|
|
137
156
|
project_filters: list[str],
|
|
138
157
|
exclude_filters: list[str],
|
|
139
158
|
) -> list[Project]:
|
|
140
|
-
"""Discover projects and apply the include/exclude filters.
|
|
159
|
+
"""Discover projects and apply the include/exclude filters.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
The projects to mirror; empty, after saying so, if none are.
|
|
163
|
+
"""
|
|
141
164
|
all_projects, _discovery_stats = cli_hooks.discover_projects(config)
|
|
142
165
|
|
|
143
166
|
if not all_projects:
|
|
144
167
|
console.print("[yellow]No projects found on Gerrit server[/yellow]")
|
|
145
|
-
|
|
168
|
+
return []
|
|
146
169
|
|
|
147
170
|
# Filter projects by include/exclude patterns
|
|
148
171
|
if project_filters or exclude_filters:
|
|
@@ -156,7 +179,7 @@ def _select_projects(
|
|
|
156
179
|
|
|
157
180
|
if not projects_to_mirror:
|
|
158
181
|
console.print("[yellow]No projects matched the specified filters[/yellow]")
|
|
159
|
-
|
|
182
|
+
return []
|
|
160
183
|
|
|
161
184
|
if not request.quiet:
|
|
162
185
|
console.print(
|