mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-03-14 06:23:01 +03:00
fix preserve strategy fork owner routing (#215)
This commit is contained in:
@@ -72,10 +72,21 @@ mock.module("./gitea", () => {
|
|||||||
const mirrorStrategy =
|
const mirrorStrategy =
|
||||||
config?.githubConfig?.mirrorStrategy ||
|
config?.githubConfig?.mirrorStrategy ||
|
||||||
(config?.giteaConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
(config?.giteaConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
|
const configuredGitHubOwner =
|
||||||
|
(config?.githubConfig?.owner || config?.githubConfig?.username || "")
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const repoOwner = repository?.owner?.trim().toLowerCase();
|
||||||
|
|
||||||
switch (mirrorStrategy) {
|
switch (mirrorStrategy) {
|
||||||
case "preserve":
|
case "preserve":
|
||||||
return repository?.organization || config?.giteaConfig?.defaultOwner || "giteauser";
|
if (repository?.organization) {
|
||||||
|
return repository.organization;
|
||||||
|
}
|
||||||
|
if (configuredGitHubOwner && repoOwner && repoOwner !== configuredGitHubOwner) {
|
||||||
|
return repository.owner;
|
||||||
|
}
|
||||||
|
return config?.giteaConfig?.defaultOwner || "giteauser";
|
||||||
case "single-org":
|
case "single-org":
|
||||||
return config?.giteaConfig?.organization || config?.giteaConfig?.defaultOwner || "giteauser";
|
return config?.giteaConfig?.organization || config?.giteaConfig?.defaultOwner || "giteauser";
|
||||||
case "mixed":
|
case "mixed":
|
||||||
@@ -99,7 +110,7 @@ mock.module("./gitea", () => {
|
|||||||
return mockDbSelectResult[0].destinationOrg;
|
return mockDbSelectResult[0].destinationOrg;
|
||||||
}
|
}
|
||||||
|
|
||||||
return config?.giteaConfig?.defaultOwner || "giteauser";
|
return mockGetGiteaRepoOwner({ config, repository });
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
isRepoPresentInGitea: mockIsRepoPresentInGitea,
|
isRepoPresentInGitea: mockIsRepoPresentInGitea,
|
||||||
@@ -376,6 +387,7 @@ describe("Gitea Repository Mirroring", () => {
|
|||||||
describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
||||||
const baseConfig: Partial<Config> = {
|
const baseConfig: Partial<Config> = {
|
||||||
githubConfig: {
|
githubConfig: {
|
||||||
|
owner: "testuser",
|
||||||
username: "testuser",
|
username: "testuser",
|
||||||
token: "token",
|
token: "token",
|
||||||
preserveOrgStructure: false,
|
preserveOrgStructure: false,
|
||||||
@@ -484,6 +496,18 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
expect(result).toBe("giteauser");
|
expect(result).toBe("giteauser");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("preserve strategy: personal repos owned by another user keep source owner namespace", () => {
|
||||||
|
const repo = {
|
||||||
|
...baseRepo,
|
||||||
|
owner: "nice-user",
|
||||||
|
fullName: "nice-user/test-repo",
|
||||||
|
organization: undefined,
|
||||||
|
isForked: true,
|
||||||
|
};
|
||||||
|
const result = getGiteaRepoOwner({ config: baseConfig, repository: repo });
|
||||||
|
expect(result).toBe("nice-user");
|
||||||
|
});
|
||||||
|
|
||||||
test("preserve strategy: org repos go to same org name", () => {
|
test("preserve strategy: org repos go to same org name", () => {
|
||||||
const repo = { ...baseRepo, organization: "myorg" };
|
const repo = { ...baseRepo, organization: "myorg" };
|
||||||
const result = getGiteaRepoOwner({ config: baseConfig, repository: repo });
|
const result = getGiteaRepoOwner({ config: baseConfig, repository: repo });
|
||||||
@@ -589,4 +613,26 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
|
|
||||||
expect(result).toBe("FOO");
|
expect(result).toBe("FOO");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getGiteaRepoOwnerAsync preserves external personal owner for preserve strategy", async () => {
|
||||||
|
const configWithUser: Partial<Config> = {
|
||||||
|
...baseConfig,
|
||||||
|
userId: "user-id",
|
||||||
|
};
|
||||||
|
|
||||||
|
const repo = {
|
||||||
|
...baseRepo,
|
||||||
|
owner: "nice-user",
|
||||||
|
fullName: "nice-user/test-repo",
|
||||||
|
organization: undefined,
|
||||||
|
isForked: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await getGiteaRepoOwnerAsync({
|
||||||
|
config: configWithUser,
|
||||||
|
repository: repo,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("nice-user");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -138,14 +138,35 @@ export const getGiteaRepoOwner = ({
|
|||||||
// Get the mirror strategy - use preserveOrgStructure for backward compatibility
|
// Get the mirror strategy - use preserveOrgStructure for backward compatibility
|
||||||
const mirrorStrategy = config.githubConfig.mirrorStrategy ||
|
const mirrorStrategy = config.githubConfig.mirrorStrategy ||
|
||||||
(config.giteaConfig.preserveOrgStructure ? "preserve" : "flat-user");
|
(config.giteaConfig.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
|
const configuredGitHubOwner =
|
||||||
|
(
|
||||||
|
config.githubConfig.owner ||
|
||||||
|
(config.githubConfig as typeof config.githubConfig & { username?: string }).username ||
|
||||||
|
""
|
||||||
|
)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
|
||||||
switch (mirrorStrategy) {
|
switch (mirrorStrategy) {
|
||||||
case "preserve":
|
case "preserve":
|
||||||
// Keep GitHub structure - org repos go to same org, personal repos to user (or override)
|
// Keep GitHub structure:
|
||||||
|
// - org repos stay in the same org
|
||||||
|
// - personal repos owned by other users keep their source owner namespace
|
||||||
|
// - personal repos owned by the configured account go to defaultOwner
|
||||||
if (repository.organization) {
|
if (repository.organization) {
|
||||||
return repository.organization;
|
return repository.organization;
|
||||||
}
|
}
|
||||||
// Use personal repos override if configured, otherwise use username
|
|
||||||
|
const normalizedRepoOwner = repository.owner.trim().toLowerCase();
|
||||||
|
if (
|
||||||
|
normalizedRepoOwner &&
|
||||||
|
configuredGitHubOwner &&
|
||||||
|
normalizedRepoOwner !== configuredGitHubOwner
|
||||||
|
) {
|
||||||
|
return repository.owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Personal repos from the configured GitHub account go to the configured default owner
|
||||||
return config.giteaConfig.defaultOwner;
|
return config.giteaConfig.defaultOwner;
|
||||||
|
|
||||||
case "single-org":
|
case "single-org":
|
||||||
@@ -376,6 +397,23 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
|
|
||||||
// Get the correct owner based on the strategy (with organization overrides)
|
// Get the correct owner based on the strategy (with organization overrides)
|
||||||
let repoOwner = await getGiteaRepoOwnerAsync({ config, repository });
|
let repoOwner = await getGiteaRepoOwnerAsync({ config, repository });
|
||||||
|
const mirrorStrategy = config.githubConfig.mirrorStrategy ||
|
||||||
|
(config.giteaConfig.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
|
const configuredGitHubOwner = (
|
||||||
|
config.githubConfig.owner ||
|
||||||
|
(config.githubConfig as typeof config.githubConfig & { username?: string }).username ||
|
||||||
|
""
|
||||||
|
)
|
||||||
|
.trim()
|
||||||
|
.toLowerCase();
|
||||||
|
const normalizedRepoOwner = repository.owner.trim().toLowerCase();
|
||||||
|
const isExternalPersonalRepoInPreserveMode =
|
||||||
|
mirrorStrategy === "preserve" &&
|
||||||
|
!repository.organization &&
|
||||||
|
!repository.isStarred &&
|
||||||
|
normalizedRepoOwner !== "" &&
|
||||||
|
configuredGitHubOwner !== "" &&
|
||||||
|
normalizedRepoOwner !== configuredGitHubOwner;
|
||||||
|
|
||||||
// Determine the actual repository name to use (handle duplicates for starred repos)
|
// Determine the actual repository name to use (handle duplicates for starred repos)
|
||||||
let targetRepoName = repository.name;
|
let targetRepoName = repository.name;
|
||||||
@@ -520,6 +558,13 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
(orgError.message.includes('Permission denied') ||
|
(orgError.message.includes('Permission denied') ||
|
||||||
orgError.message.includes('Authentication failed') ||
|
orgError.message.includes('Authentication failed') ||
|
||||||
orgError.message.includes('does not have permission'))) {
|
orgError.message.includes('does not have permission'))) {
|
||||||
|
if (isExternalPersonalRepoInPreserveMode) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot create/access namespace "${repoOwner}" for ${repository.fullName}. ` +
|
||||||
|
`Refusing fallback to "${config.giteaConfig.defaultOwner}" in preserve mode to avoid cross-owner overwrite.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
console.warn(`[Fallback] Organization creation/access failed. Attempting to mirror to user account instead.`);
|
console.warn(`[Fallback] Organization creation/access failed. Attempting to mirror to user account instead.`);
|
||||||
|
|
||||||
// Update the repository owner to use the user account
|
// Update the repository owner to use the user account
|
||||||
|
|||||||
Reference in New Issue
Block a user