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
This commit is contained in:
Yuzu
2026-07-16 22:24:58 +07:00
committed by GitHub
parent 40efb9b83a
commit 537aae952d
2 changed files with 35 additions and 24 deletions
+29 -8
View File
@@ -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;
+6 -16
View File
@@ -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;
},