From 537aae952d00f1124a8a5972fc15eeb46f10234c Mon Sep 17 00:00:00 2001 From: Yuzu Date: Thu, 16 Jul 2026 22:24:58 +0700 Subject: [PATCH] fix: honor destination overrides in bulk org mirroring and crash recovery (#343) (#344) Routes the bulk Mirror Organization path and crash recovery through the canonical destination resolver (getGiteaRepoOwnerAsync), so org-level and per-repo destination overrides are honored, the mixed strategy no longer sends org repos to the user's personal account (previously uid was dropped from the migrate payload and Gitea defaulted to the authenticated user), and starred repos follow starred-repo mode even when swept up in a bulk org mirror. Fixes #343 --- src/lib/gitea.ts | 37 +++++++++++++++++++++++++++++-------- src/lib/recovery.ts | 22 ++++++---------------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/lib/gitea.ts b/src/lib/gitea.ts index e950af5..7517a89 100644 --- a/src/lib/gitea.ts +++ b/src/lib/gitea.ts @@ -1995,11 +1995,20 @@ export async function mirrorGitHubOrgToGitea({ const mirrorStrategy = config.githubConfig?.mirrorStrategy || (config.giteaConfig?.preserveOrgStructure ? "preserve" : "flat-user"); - let giteaOrgId: number; + let giteaOrgId: number | undefined; let targetOrgName: string; // Determine the target organization based on strategy - if (mirrorStrategy === "single-org" && config.giteaConfig?.organization) { + if (organization.destinationOrg) { + // Organization-level override takes precedence over the strategy + targetOrgName = organization.destinationOrg; + giteaOrgId = await getOrCreateGiteaOrg({ + orgId: organization.id, + orgName: targetOrgName, + config, + }); + console.log(`Using organization override: ${organization.name} -> ${targetOrgName}`); + } else if (mirrorStrategy === "single-org" && config.giteaConfig?.organization) { // For single-org strategy, use the configured destination organization targetOrgName = config.giteaConfig.organization || config.giteaConfig.defaultOwner; giteaOrgId = await getOrCreateGiteaOrg({ @@ -2062,23 +2071,35 @@ export async function mirrorGitHubOrgToGitea({ `Starting mirror for repository: ${repo.name} from GitHub org ${organization.name}` ); - // Mirror the repository based on strategy - if (mirrorStrategy === "flat-user") { - // For flat-user strategy, mirror directly to user account + // Resolve per repo with the canonical precedence + const owner = await getGiteaRepoOwnerAsync({ config, repository: repoData }); + + if (owner === config.giteaConfig?.defaultOwner) { await mirrorGithubRepoToGitea({ octokit, repository: repoData, config, }); - } else { - // For preserve and single-org strategies, use organization + } else if (owner === targetOrgName && giteaOrgId !== undefined) { await mirrorGitHubRepoToGiteaOrg({ octokit, config, repository: repoData, - giteaOrgId: giteaOrgId!, + giteaOrgId, orgName: targetOrgName, }); + } else { + const ownerOrgId = await getOrCreateGiteaOrg({ + orgName: owner, + config, + }); + await mirrorGitHubRepoToGiteaOrg({ + octokit, + config, + repository: repoData, + giteaOrgId: ownerOrgId, + orgName: owner, + }); } return repo; diff --git a/src/lib/recovery.ts b/src/lib/recovery.ts index 0d9d631..04a2f61 100644 --- a/src/lib/recovery.ts +++ b/src/lib/recovery.ts @@ -6,7 +6,7 @@ import { findInterruptedJobs, resumeInterruptedJob } from './helpers'; import { db, repositories, organizations, mirrorJobs, configs } from './db'; import { eq, and, lt, inArray, sql } from 'drizzle-orm'; -import { mirrorGithubRepoToGitea, mirrorGitHubOrgRepoToGiteaOrg, syncGiteaRepo } from './gitea'; +import { mirrorGithubRepoToGitea, syncGiteaRepo } from './gitea'; import { createGitHubClient } from './github'; import { processWithResilience } from './utils/concurrency'; import { repositoryVisibilityEnum, repoStatusEnum } from '@/types/Repository'; @@ -290,21 +290,11 @@ async function recoverMirrorJob(job: any, remainingItemIds: string[]) { mirroredLocation: repo.mirroredLocation || "", }; - // Mirror the repository based on whether it's in an organization - if (repo.organization && config.giteaConfig.preserveOrgStructure) { - await mirrorGitHubOrgRepoToGiteaOrg({ - config, - octokit, - orgName: repo.organization, - repository: repoData, - }); - } else { - await mirrorGithubRepoToGitea({ - octokit, - repository: repoData, - config, - }); - } + await mirrorGithubRepoToGitea({ + octokit, + repository: repoData, + config, + }); return repo; },