From 2d51e64ae5038003e6630c57da1ae2fa5c90eafb Mon Sep 17 00:00:00 2001 From: Luis Chamberlain Date: Wed, 9 Jul 2025 16:46:46 -0700 Subject: [PATCH] Add mirror directory support --- kernel_patches_daemon/branch_worker.py | 17 ++++++++++++++++- kernel_patches_daemon/config.py | 2 ++ kernel_patches_daemon/github_sync.py | 1 + tests/test_branch_worker.py | 20 ++++++++++++++++++++ tests/test_config.py | 1 + tests/test_github_sync.py | 7 +++++++ 6 files changed, 47 insertions(+), 1 deletion(-) diff --git a/kernel_patches_daemon/branch_worker.py b/kernel_patches_daemon/branch_worker.py index 80d801e..9ed92e8 100644 --- a/kernel_patches_daemon/branch_worker.py +++ b/kernel_patches_daemon/branch_worker.py @@ -547,6 +547,7 @@ def __init__( app_auth: Optional[Auth.AppInstallationAuth] = None, email: Optional[EmailConfig] = None, http_retries: Optional[int] = None, + mirror_dir: Optional[str] = None, ) -> None: super().__init__( repo_url=repo_url, @@ -559,6 +560,7 @@ def __init__( self.email = email self.log_extractor = log_extractor + self.mirror_dir = mirror_dir self.ci_repo_url = ci_repo_url self.ci_repo_dir = _uniq_tmp_folder(ci_repo_url, ci_branch, base_directory) self.ci_branch = ci_branch @@ -682,9 +684,22 @@ def do_sync(self) -> None: def full_sync(self, path: str, url: str, branch: str) -> git.Repo: logging.info(f"Doing full clone from {redact_url(url)}, branch: {branch}") + multi_opts: Optional[List[str]] = None + if self.mirror_dir: + upstream_name = os.path.basename(self.upstream_url) + reference_path = os.path.join(self.mirror_dir, upstream_name) + fallback = os.path.join(self.mirror_dir, "linux.git") + if not os.path.exists(reference_path) and os.path.exists(fallback): + reference_path = fallback + if os.path.exists(reference_path): + multi_opts = ["--reference", reference_path] + with HistogramMetricTimer(git_clone_duration, {"branch": branch}): shutil.rmtree(path, ignore_errors=True) - repo = git.Repo.clone_from(url, path) + if multi_opts: + repo = git.Repo.clone_from(url, path, multi_options=multi_opts) + else: + repo = git.Repo.clone_from(url, path) _reset_repo(repo, f"origin/{branch}") git_clone_counter.add(1, {"branch": branch}) diff --git a/kernel_patches_daemon/config.py b/kernel_patches_daemon/config.py index b1f6bd6..6395a10 100644 --- a/kernel_patches_daemon/config.py +++ b/kernel_patches_daemon/config.py @@ -171,6 +171,7 @@ class KPDConfig: branches: Dict[str, BranchConfig] tag_to_branch_mapping: Dict[str, List[str]] base_directory: str + mirror_dir: Optional[str] = None @classmethod def from_json(cls, json: Dict) -> "KPDConfig": @@ -203,6 +204,7 @@ def from_json(cls, json: Dict) -> "KPDConfig": for name, json_config in json["branches"].items() }, base_directory=json["base_directory"], + mirror_dir=json.get("mirror_dir"), ) @classmethod diff --git a/kernel_patches_daemon/github_sync.py b/kernel_patches_daemon/github_sync.py index 3dd6e09..5880267 100644 --- a/kernel_patches_daemon/github_sync.py +++ b/kernel_patches_daemon/github_sync.py @@ -114,6 +114,7 @@ def __init__( ci_branch=branch_config.ci_branch, log_extractor=_log_extractor_from_project(kpd_config.patchwork.project), base_directory=kpd_config.base_directory, + mirror_dir=kpd_config.mirror_dir, http_retries=http_retries, github_oauth_token=branch_config.github_oauth_token, app_auth=github_app_auth_from_branch_config(branch_config), diff --git a/tests/test_branch_worker.py b/tests/test_branch_worker.py index f4c7396..e5cd071 100644 --- a/tests/test_branch_worker.py +++ b/tests/test_branch_worker.py @@ -68,6 +68,7 @@ TEST_CI_REPO_URL = f"https://user:pass@127.0.0.1/ci-org/{TEST_CI_REPO}" TEST_CI_BRANCH = "test_ci_branch" TEST_BASE_DIRECTORY = "/repos" +TEST_MIRROR_DIRECTORY = "/mirror" TEST_BRANCH = "test-branch" TEST_CONFIG: Dict[str, Any] = { "version": 2, @@ -124,6 +125,7 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: "ci_branch": TEST_CI_BRANCH, "log_extractor": DefaultGithubLogExtractor(), "base_directory": TEST_BASE_DIRECTORY, + "mirror_dir": None, } presets.update(kwargs) @@ -464,6 +466,24 @@ def test_fetch_repo_path_exists_git_exception(self) -> None: self._bw.fetch_repo(*fetch_params) fr.assert_called_once_with(*fetch_params) + def test_full_sync_with_mirror_dir(self) -> None: + bw = BranchWorkerMock(mirror_dir=TEST_MIRROR_DIRECTORY) + reference = os.path.join( + TEST_MIRROR_DIRECTORY, os.path.basename(TEST_UPSTREAM_REPO_URL) + ) + with ( + patch("kernel_patches_daemon.branch_worker.os.path.exists") as exists, + patch("kernel_patches_daemon.branch_worker.shutil.rmtree") as rm, + ): + exists.side_effect = lambda p: p == reference + bw.upstream_url = TEST_UPSTREAM_REPO_URL + bw.full_sync("somepath", "giturl", "branch") + self._git_repo_mock.clone_from.assert_called_once_with( + "giturl", + "somepath", + multi_options=["--reference", reference], + ) + def test_expire_branches(self) -> None: """Only the branch that matches pattern and is expired should be deleted""" not_expired_time = datetime.fromtimestamp(3 * BRANCH_TTL) diff --git a/tests/test_config.py b/tests/test_config.py index 8d79fa3..b799c23 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -208,5 +208,6 @@ def test_valid(self) -> None: ), }, base_directory="/repos", + mirror_dir=None, ) self.assertEqual(config, expected_config) diff --git a/tests/test_github_sync.py b/tests/test_github_sync.py index 789dbf4..668a1d1 100644 --- a/tests/test_github_sync.py +++ b/tests/test_github_sync.py @@ -126,6 +126,13 @@ class TestCase: gh.workers[TEST_BRANCH].ci_repo_dir.startswith(case.prefix), ) + def test_init_with_mirror_dir(self) -> None: + config = copy.copy(TEST_CONFIG) + config["mirror_dir"] = "/mirror" + kpd_config = KPDConfig.from_json(config) + gh = GithubSyncMock(kpd_config=kpd_config) + self.assertEqual("/mirror", gh.workers[TEST_BRANCH].mirror_dir) + def test_close_existing_prs_for_series(self) -> None: matching_pr_mock = MagicMock() matching_pr_mock.title = "matching"