mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-06 11:36:44 +03:00
Merge pull request #136 from RayLabsHQ/fix/metadata-sync-config-change
fix: sync metadata after config toggles
This commit is contained in:
@@ -8,6 +8,10 @@ mock.module("@/lib/helpers", () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const mockMirrorGitHubReleasesToGitea = mock(() => Promise.resolve());
|
const mockMirrorGitHubReleasesToGitea = mock(() => Promise.resolve());
|
||||||
|
const mockMirrorGitRepoIssuesToGitea = mock(() => Promise.resolve());
|
||||||
|
const mockMirrorGitRepoPullRequestsToGitea = mock(() => Promise.resolve());
|
||||||
|
const mockMirrorGitRepoLabelsToGitea = mock(() => Promise.resolve());
|
||||||
|
const mockMirrorGitRepoMilestonesToGitea = mock(() => Promise.resolve());
|
||||||
const mockGetGiteaRepoOwnerAsync = mock(() => Promise.resolve("starred"));
|
const mockGetGiteaRepoOwnerAsync = mock(() => Promise.resolve("starred"));
|
||||||
|
|
||||||
// Mock the database module
|
// Mock the database module
|
||||||
@@ -128,6 +132,36 @@ const mockHttpGet = mock(async (url: string, headers?: any) => {
|
|||||||
headers: new Headers()
|
headers: new Headers()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (url.includes("/api/v1/repos/starred/metadata-repo")) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
id: 790,
|
||||||
|
name: "metadata-repo",
|
||||||
|
mirror: true,
|
||||||
|
owner: { login: "starred" },
|
||||||
|
mirror_interval: "8h",
|
||||||
|
private: false,
|
||||||
|
},
|
||||||
|
status: 200,
|
||||||
|
statusText: "OK",
|
||||||
|
headers: new Headers(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (url.includes("/api/v1/repos/starred/already-synced-repo")) {
|
||||||
|
return {
|
||||||
|
data: {
|
||||||
|
id: 791,
|
||||||
|
name: "already-synced-repo",
|
||||||
|
mirror: true,
|
||||||
|
owner: { login: "starred" },
|
||||||
|
mirror_interval: "8h",
|
||||||
|
private: false,
|
||||||
|
},
|
||||||
|
status: 200,
|
||||||
|
statusText: "OK",
|
||||||
|
headers: new Headers(),
|
||||||
|
};
|
||||||
|
}
|
||||||
if (url.includes("/api/v1/repos/")) {
|
if (url.includes("/api/v1/repos/")) {
|
||||||
throw new MockHttpError("Not Found", 404, "Not Found");
|
throw new MockHttpError("Not Found", 404, "Not Found");
|
||||||
}
|
}
|
||||||
@@ -224,6 +258,10 @@ describe("Enhanced Gitea Operations", () => {
|
|||||||
mockDb.insert.mockClear();
|
mockDb.insert.mockClear();
|
||||||
mockDb.update.mockClear();
|
mockDb.update.mockClear();
|
||||||
mockMirrorGitHubReleasesToGitea.mockClear();
|
mockMirrorGitHubReleasesToGitea.mockClear();
|
||||||
|
mockMirrorGitRepoIssuesToGitea.mockClear();
|
||||||
|
mockMirrorGitRepoPullRequestsToGitea.mockClear();
|
||||||
|
mockMirrorGitRepoLabelsToGitea.mockClear();
|
||||||
|
mockMirrorGitRepoMilestonesToGitea.mockClear();
|
||||||
mockGetGiteaRepoOwnerAsync.mockClear();
|
mockGetGiteaRepoOwnerAsync.mockClear();
|
||||||
mockGetGiteaRepoOwnerAsync.mockImplementation(() => Promise.resolve("starred"));
|
mockGetGiteaRepoOwnerAsync.mockImplementation(() => Promise.resolve("starred"));
|
||||||
// Reset tracking variables
|
// Reset tracking variables
|
||||||
@@ -426,6 +464,10 @@ describe("Enhanced Gitea Operations", () => {
|
|||||||
{
|
{
|
||||||
getGiteaRepoOwnerAsync: mockGetGiteaRepoOwnerAsync,
|
getGiteaRepoOwnerAsync: mockGetGiteaRepoOwnerAsync,
|
||||||
mirrorGitHubReleasesToGitea: mockMirrorGitHubReleasesToGitea,
|
mirrorGitHubReleasesToGitea: mockMirrorGitHubReleasesToGitea,
|
||||||
|
mirrorGitRepoIssuesToGitea: mockMirrorGitRepoIssuesToGitea,
|
||||||
|
mirrorGitRepoPullRequestsToGitea: mockMirrorGitRepoPullRequestsToGitea,
|
||||||
|
mirrorGitRepoLabelsToGitea: mockMirrorGitRepoLabelsToGitea,
|
||||||
|
mirrorGitRepoMilestonesToGitea: mockMirrorGitRepoMilestonesToGitea,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
).rejects.toThrow("Repository non-mirror-repo is not a mirror. Cannot sync.");
|
).rejects.toThrow("Repository non-mirror-repo is not a mirror. Cannot sync.");
|
||||||
@@ -470,6 +512,10 @@ describe("Enhanced Gitea Operations", () => {
|
|||||||
{
|
{
|
||||||
getGiteaRepoOwnerAsync: mockGetGiteaRepoOwnerAsync,
|
getGiteaRepoOwnerAsync: mockGetGiteaRepoOwnerAsync,
|
||||||
mirrorGitHubReleasesToGitea: mockMirrorGitHubReleasesToGitea,
|
mirrorGitHubReleasesToGitea: mockMirrorGitHubReleasesToGitea,
|
||||||
|
mirrorGitRepoIssuesToGitea: mockMirrorGitRepoIssuesToGitea,
|
||||||
|
mirrorGitRepoPullRequestsToGitea: mockMirrorGitRepoPullRequestsToGitea,
|
||||||
|
mirrorGitRepoLabelsToGitea: mockMirrorGitRepoLabelsToGitea,
|
||||||
|
mirrorGitRepoMilestonesToGitea: mockMirrorGitRepoMilestonesToGitea,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -482,6 +528,130 @@ describe("Enhanced Gitea Operations", () => {
|
|||||||
expect(releaseCall.config.githubConfig?.token).toBe("github-token");
|
expect(releaseCall.config.githubConfig?.token).toBe("github-token");
|
||||||
expect(releaseCall.octokit).toBeDefined();
|
expect(releaseCall.octokit).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("mirrors metadata components when enabled and not previously synced", async () => {
|
||||||
|
const config: Partial<Config> = {
|
||||||
|
userId: "user123",
|
||||||
|
githubConfig: {
|
||||||
|
username: "testuser",
|
||||||
|
token: "github-token",
|
||||||
|
privateRepositories: true,
|
||||||
|
mirrorStarred: false,
|
||||||
|
},
|
||||||
|
giteaConfig: {
|
||||||
|
url: "https://gitea.example.com",
|
||||||
|
token: "encrypted-token",
|
||||||
|
defaultOwner: "testuser",
|
||||||
|
mirrorReleases: true,
|
||||||
|
mirrorMetadata: true,
|
||||||
|
mirrorIssues: true,
|
||||||
|
mirrorPullRequests: true,
|
||||||
|
mirrorLabels: true,
|
||||||
|
mirrorMilestones: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const repository: Repository = {
|
||||||
|
id: "repo789",
|
||||||
|
name: "metadata-repo",
|
||||||
|
fullName: "user/metadata-repo",
|
||||||
|
owner: "user",
|
||||||
|
cloneUrl: "https://github.com/user/metadata-repo.git",
|
||||||
|
isPrivate: false,
|
||||||
|
isStarred: false,
|
||||||
|
status: repoStatusEnum.parse("mirrored"),
|
||||||
|
visibility: "public",
|
||||||
|
userId: "user123",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
metadata: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
await syncGiteaRepoEnhanced(
|
||||||
|
{ config, repository },
|
||||||
|
{
|
||||||
|
getGiteaRepoOwnerAsync: mockGetGiteaRepoOwnerAsync,
|
||||||
|
mirrorGitHubReleasesToGitea: mockMirrorGitHubReleasesToGitea,
|
||||||
|
mirrorGitRepoIssuesToGitea: mockMirrorGitRepoIssuesToGitea,
|
||||||
|
mirrorGitRepoPullRequestsToGitea: mockMirrorGitRepoPullRequestsToGitea,
|
||||||
|
mirrorGitRepoLabelsToGitea: mockMirrorGitRepoLabelsToGitea,
|
||||||
|
mirrorGitRepoMilestonesToGitea: mockMirrorGitRepoMilestonesToGitea,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockMirrorGitHubReleasesToGitea).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockMirrorGitRepoIssuesToGitea).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockMirrorGitRepoPullRequestsToGitea).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockMirrorGitRepoMilestonesToGitea).toHaveBeenCalledTimes(1);
|
||||||
|
// Labels should be skipped because issues already import them
|
||||||
|
expect(mockMirrorGitRepoLabelsToGitea).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("skips metadata mirroring when components already synced", async () => {
|
||||||
|
const config: Partial<Config> = {
|
||||||
|
userId: "user123",
|
||||||
|
githubConfig: {
|
||||||
|
username: "testuser",
|
||||||
|
token: "github-token",
|
||||||
|
privateRepositories: true,
|
||||||
|
mirrorStarred: false,
|
||||||
|
},
|
||||||
|
giteaConfig: {
|
||||||
|
url: "https://gitea.example.com",
|
||||||
|
token: "encrypted-token",
|
||||||
|
defaultOwner: "testuser",
|
||||||
|
mirrorReleases: false,
|
||||||
|
mirrorMetadata: true,
|
||||||
|
mirrorIssues: true,
|
||||||
|
mirrorPullRequests: true,
|
||||||
|
mirrorLabels: true,
|
||||||
|
mirrorMilestones: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const repository: Repository = {
|
||||||
|
id: "repo790",
|
||||||
|
name: "already-synced-repo",
|
||||||
|
fullName: "user/already-synced-repo",
|
||||||
|
owner: "user",
|
||||||
|
cloneUrl: "https://github.com/user/already-synced-repo.git",
|
||||||
|
isPrivate: false,
|
||||||
|
isStarred: false,
|
||||||
|
status: repoStatusEnum.parse("mirrored"),
|
||||||
|
visibility: "public",
|
||||||
|
userId: "user123",
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
metadata: JSON.stringify({
|
||||||
|
components: {
|
||||||
|
releases: true,
|
||||||
|
issues: true,
|
||||||
|
pullRequests: true,
|
||||||
|
labels: true,
|
||||||
|
milestones: true,
|
||||||
|
},
|
||||||
|
lastSyncedAt: new Date().toISOString(),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
await syncGiteaRepoEnhanced(
|
||||||
|
{ config, repository },
|
||||||
|
{
|
||||||
|
getGiteaRepoOwnerAsync: mockGetGiteaRepoOwnerAsync,
|
||||||
|
mirrorGitHubReleasesToGitea: mockMirrorGitHubReleasesToGitea,
|
||||||
|
mirrorGitRepoIssuesToGitea: mockMirrorGitRepoIssuesToGitea,
|
||||||
|
mirrorGitRepoPullRequestsToGitea: mockMirrorGitRepoPullRequestsToGitea,
|
||||||
|
mirrorGitRepoLabelsToGitea: mockMirrorGitRepoLabelsToGitea,
|
||||||
|
mirrorGitRepoMilestonesToGitea: mockMirrorGitRepoMilestonesToGitea,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(mockMirrorGitHubReleasesToGitea).not.toHaveBeenCalled();
|
||||||
|
expect(mockMirrorGitRepoIssuesToGitea).not.toHaveBeenCalled();
|
||||||
|
expect(mockMirrorGitRepoPullRequestsToGitea).not.toHaveBeenCalled();
|
||||||
|
expect(mockMirrorGitRepoLabelsToGitea).not.toHaveBeenCalled();
|
||||||
|
expect(mockMirrorGitRepoMilestonesToGitea).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("handleExistingNonMirrorRepo", () => {
|
describe("handleExistingNonMirrorRepo", () => {
|
||||||
|
|||||||
@@ -15,10 +15,18 @@ import { httpPost, httpGet, httpPatch, HttpError } from "./http-client";
|
|||||||
import { db, repositories } from "./db";
|
import { db, repositories } from "./db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { repoStatusEnum } from "@/types/Repository";
|
import { repoStatusEnum } from "@/types/Repository";
|
||||||
|
import {
|
||||||
|
parseRepositoryMetadataState,
|
||||||
|
serializeRepositoryMetadataState,
|
||||||
|
} from "./metadata-state";
|
||||||
|
|
||||||
type SyncDependencies = {
|
type SyncDependencies = {
|
||||||
getGiteaRepoOwnerAsync: typeof import("./gitea")["getGiteaRepoOwnerAsync"];
|
getGiteaRepoOwnerAsync: typeof import("./gitea")["getGiteaRepoOwnerAsync"];
|
||||||
mirrorGitHubReleasesToGitea: typeof import("./gitea")["mirrorGitHubReleasesToGitea"];
|
mirrorGitHubReleasesToGitea: typeof import("./gitea")["mirrorGitHubReleasesToGitea"];
|
||||||
|
mirrorGitRepoIssuesToGitea: typeof import("./gitea")["mirrorGitRepoIssuesToGitea"];
|
||||||
|
mirrorGitRepoPullRequestsToGitea: typeof import("./gitea")["mirrorGitRepoPullRequestsToGitea"];
|
||||||
|
mirrorGitRepoLabelsToGitea: typeof import("./gitea")["mirrorGitRepoLabelsToGitea"];
|
||||||
|
mirrorGitRepoMilestonesToGitea: typeof import("./gitea")["mirrorGitRepoMilestonesToGitea"];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -330,36 +338,236 @@ export async function syncGiteaRepoEnhanced({
|
|||||||
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const metadataState = parseRepositoryMetadataState(repository.metadata);
|
||||||
|
let metadataUpdated = false;
|
||||||
|
const skipMetadataForStarred =
|
||||||
|
repository.isStarred && config.githubConfig?.starredCodeOnly;
|
||||||
|
let metadataOctokit: Octokit | null = null;
|
||||||
|
|
||||||
|
const ensureOctokit = (): Octokit | null => {
|
||||||
|
if (metadataOctokit) {
|
||||||
|
return metadataOctokit;
|
||||||
|
}
|
||||||
|
if (!decryptedConfig.githubConfig?.token) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
metadataOctokit = new Octokit({
|
||||||
|
auth: decryptedConfig.githubConfig.token,
|
||||||
|
});
|
||||||
|
return metadataOctokit;
|
||||||
|
};
|
||||||
|
|
||||||
const shouldMirrorReleases =
|
const shouldMirrorReleases =
|
||||||
decryptedConfig.giteaConfig?.mirrorReleases &&
|
!!config.giteaConfig?.mirrorReleases && !skipMetadataForStarred;
|
||||||
!(repository.isStarred && decryptedConfig.githubConfig?.starredCodeOnly);
|
const shouldMirrorIssuesThisRun =
|
||||||
|
!!config.giteaConfig?.mirrorIssues &&
|
||||||
|
!skipMetadataForStarred &&
|
||||||
|
!metadataState.components.issues;
|
||||||
|
const shouldMirrorPullRequests =
|
||||||
|
!!config.giteaConfig?.mirrorPullRequests &&
|
||||||
|
!skipMetadataForStarred &&
|
||||||
|
!metadataState.components.pullRequests;
|
||||||
|
const shouldMirrorLabels =
|
||||||
|
!!config.giteaConfig?.mirrorLabels &&
|
||||||
|
!skipMetadataForStarred &&
|
||||||
|
!shouldMirrorIssuesThisRun &&
|
||||||
|
!metadataState.components.labels;
|
||||||
|
const shouldMirrorMilestones =
|
||||||
|
!!config.giteaConfig?.mirrorMilestones &&
|
||||||
|
!skipMetadataForStarred &&
|
||||||
|
!metadataState.components.milestones;
|
||||||
|
|
||||||
if (shouldMirrorReleases) {
|
if (shouldMirrorReleases) {
|
||||||
if (!decryptedConfig.githubConfig?.token) {
|
const octokit = ensureOctokit();
|
||||||
|
if (!octokit) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[Sync] Skipping release mirroring for ${repository.name}: Missing GitHub token`
|
`[Sync] Skipping release mirroring for ${repository.name}: Missing GitHub token`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const octokit = new Octokit({ auth: decryptedConfig.githubConfig.token });
|
|
||||||
await dependencies.mirrorGitHubReleasesToGitea({
|
await dependencies.mirrorGitHubReleasesToGitea({
|
||||||
config: decryptedConfig,
|
config,
|
||||||
octokit,
|
octokit,
|
||||||
repository,
|
repository,
|
||||||
giteaOwner: repoOwner,
|
giteaOwner: repoOwner,
|
||||||
giteaRepoName: repository.name,
|
giteaRepoName: repository.name,
|
||||||
});
|
});
|
||||||
console.log(`[Sync] Mirrored releases for ${repository.name} after sync`);
|
metadataState.components.releases = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Sync] Mirrored releases for ${repository.name} after sync`
|
||||||
|
);
|
||||||
} catch (releaseError) {
|
} catch (releaseError) {
|
||||||
console.error(
|
console.error(
|
||||||
`[Sync] Failed to mirror releases for ${repository.name}: ${
|
`[Sync] Failed to mirror releases for ${repository.name}: ${
|
||||||
releaseError instanceof Error ? releaseError.message : String(releaseError)
|
releaseError instanceof Error
|
||||||
|
? releaseError.message
|
||||||
|
: String(releaseError)
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (shouldMirrorIssuesThisRun) {
|
||||||
|
const octokit = ensureOctokit();
|
||||||
|
if (!octokit) {
|
||||||
|
console.warn(
|
||||||
|
`[Sync] Skipping issue mirroring for ${repository.name}: Missing GitHub token`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await dependencies.mirrorGitRepoIssuesToGitea({
|
||||||
|
config,
|
||||||
|
octokit,
|
||||||
|
repository,
|
||||||
|
giteaOwner: repoOwner,
|
||||||
|
giteaRepoName: repository.name,
|
||||||
|
});
|
||||||
|
metadataState.components.issues = true;
|
||||||
|
metadataState.components.labels = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Sync] Mirrored issues for ${repository.name} after sync`
|
||||||
|
);
|
||||||
|
} catch (issueError) {
|
||||||
|
console.error(
|
||||||
|
`[Sync] Failed to mirror issues for ${repository.name}: ${
|
||||||
|
issueError instanceof Error
|
||||||
|
? issueError.message
|
||||||
|
: String(issueError)
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorIssues &&
|
||||||
|
metadataState.components.issues
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Sync] Issues already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldMirrorPullRequests) {
|
||||||
|
const octokit = ensureOctokit();
|
||||||
|
if (!octokit) {
|
||||||
|
console.warn(
|
||||||
|
`[Sync] Skipping pull request mirroring for ${repository.name}: Missing GitHub token`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await dependencies.mirrorGitRepoPullRequestsToGitea({
|
||||||
|
config,
|
||||||
|
octokit,
|
||||||
|
repository,
|
||||||
|
giteaOwner: repoOwner,
|
||||||
|
giteaRepoName: repository.name,
|
||||||
|
});
|
||||||
|
metadataState.components.pullRequests = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Sync] Mirrored pull requests for ${repository.name} after sync`
|
||||||
|
);
|
||||||
|
} catch (prError) {
|
||||||
|
console.error(
|
||||||
|
`[Sync] Failed to mirror pull requests for ${repository.name}: ${
|
||||||
|
prError instanceof Error ? prError.message : String(prError)
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorPullRequests &&
|
||||||
|
metadataState.components.pullRequests
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Sync] Pull requests already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldMirrorLabels) {
|
||||||
|
const octokit = ensureOctokit();
|
||||||
|
if (!octokit) {
|
||||||
|
console.warn(
|
||||||
|
`[Sync] Skipping label mirroring for ${repository.name}: Missing GitHub token`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await dependencies.mirrorGitRepoLabelsToGitea({
|
||||||
|
config,
|
||||||
|
octokit,
|
||||||
|
repository,
|
||||||
|
giteaOwner: repoOwner,
|
||||||
|
giteaRepoName: repository.name,
|
||||||
|
});
|
||||||
|
metadataState.components.labels = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Sync] Mirrored labels for ${repository.name} after sync`
|
||||||
|
);
|
||||||
|
} catch (labelError) {
|
||||||
|
console.error(
|
||||||
|
`[Sync] Failed to mirror labels for ${repository.name}: ${
|
||||||
|
labelError instanceof Error
|
||||||
|
? labelError.message
|
||||||
|
: String(labelError)
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorLabels &&
|
||||||
|
metadataState.components.labels
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Sync] Labels already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldMirrorMilestones) {
|
||||||
|
const octokit = ensureOctokit();
|
||||||
|
if (!octokit) {
|
||||||
|
console.warn(
|
||||||
|
`[Sync] Skipping milestone mirroring for ${repository.name}: Missing GitHub token`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await dependencies.mirrorGitRepoMilestonesToGitea({
|
||||||
|
config,
|
||||||
|
octokit,
|
||||||
|
repository,
|
||||||
|
giteaOwner: repoOwner,
|
||||||
|
giteaRepoName: repository.name,
|
||||||
|
});
|
||||||
|
metadataState.components.milestones = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Sync] Mirrored milestones for ${repository.name} after sync`
|
||||||
|
);
|
||||||
|
} catch (milestoneError) {
|
||||||
|
console.error(
|
||||||
|
`[Sync] Failed to mirror milestones for ${repository.name}: ${
|
||||||
|
milestoneError instanceof Error
|
||||||
|
? milestoneError.message
|
||||||
|
: String(milestoneError)
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorMilestones &&
|
||||||
|
metadataState.components.milestones
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Sync] Milestones already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadataUpdated) {
|
||||||
|
metadataState.lastSyncedAt = new Date().toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
// Mark repo as "synced" in DB
|
// Mark repo as "synced" in DB
|
||||||
await db
|
await db
|
||||||
.update(repositories)
|
.update(repositories)
|
||||||
@@ -369,6 +577,9 @@ export async function syncGiteaRepoEnhanced({
|
|||||||
lastMirrored: new Date(),
|
lastMirrored: new Date(),
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
mirroredLocation: `${repoOwner}/${repository.name}`,
|
mirroredLocation: `${repoOwner}/${repository.name}`,
|
||||||
|
metadata: metadataUpdated
|
||||||
|
? serializeRepositoryMetadataState(metadataState)
|
||||||
|
: repository.metadata ?? null,
|
||||||
})
|
})
|
||||||
.where(eq(repositories.id, repository.id!));
|
.where(eq(repositories.id, repository.id!));
|
||||||
|
|
||||||
|
|||||||
332
src/lib/gitea.ts
332
src/lib/gitea.ts
@@ -13,6 +13,10 @@ import { db, organizations, repositories } from "./db";
|
|||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { decryptConfigTokens } from "./utils/config-encryption";
|
import { decryptConfigTokens } from "./utils/config-encryption";
|
||||||
import { formatDateShort } from "./utils";
|
import { formatDateShort } from "./utils";
|
||||||
|
import {
|
||||||
|
parseRepositoryMetadataState,
|
||||||
|
serializeRepositoryMetadataState,
|
||||||
|
} from "./metadata-state";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to get organization configuration including destination override
|
* Helper function to get organization configuration including destination override
|
||||||
@@ -587,12 +591,18 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
//mirror releases
|
const metadataState = parseRepositoryMetadataState(repository.metadata);
|
||||||
// Skip releases for starred repos if starredCodeOnly is enabled
|
let metadataUpdated = false;
|
||||||
const shouldMirrorReleases = config.giteaConfig?.mirrorReleases &&
|
const skipMetadataForStarred =
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
repository.isStarred && config.githubConfig?.starredCodeOnly;
|
||||||
|
|
||||||
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorReleases=${shouldMirrorReleases}`);
|
// Mirror releases if enabled (always allowed to rerun for updates)
|
||||||
|
const shouldMirrorReleases =
|
||||||
|
!!config.giteaConfig?.mirrorReleases && !skipMetadataForStarred;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorReleases=${shouldMirrorReleases}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorReleases) {
|
if (shouldMirrorReleases) {
|
||||||
try {
|
try {
|
||||||
@@ -603,21 +613,32 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
giteaOwner: repoOwner,
|
giteaOwner: repoOwner,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored releases for ${repository.name}`);
|
metadataState.components.releases = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored releases for ${repository.name}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror releases for ${repository.name}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror releases for ${repository.name}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other operations even if releases fail
|
// Continue with other operations even if releases fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// clone issues
|
// Determine metadata operations to avoid duplicates
|
||||||
// Skip issues for starred repos if starredCodeOnly is enabled
|
const shouldMirrorIssuesThisRun =
|
||||||
const shouldMirrorIssues = config.giteaConfig?.mirrorIssues &&
|
!!config.giteaConfig?.mirrorIssues &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!skipMetadataForStarred &&
|
||||||
|
!metadataState.components.issues;
|
||||||
console.log(`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorIssues=${shouldMirrorIssues}`);
|
|
||||||
|
console.log(
|
||||||
if (shouldMirrorIssues) {
|
`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, alreadyMirrored=${metadataState.components.issues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorIssues=${shouldMirrorIssuesThisRun}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (shouldMirrorIssuesThisRun) {
|
||||||
try {
|
try {
|
||||||
await mirrorGitRepoIssuesToGitea({
|
await mirrorGitRepoIssuesToGitea({
|
||||||
config,
|
config,
|
||||||
@@ -626,19 +647,34 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
giteaOwner: repoOwner,
|
giteaOwner: repoOwner,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored issues for ${repository.name}`);
|
metadataState.components.issues = true;
|
||||||
|
metadataState.components.labels = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored issues for ${repository.name}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror issues for ${repository.name}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror issues for ${repository.name}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if issues fail
|
// Continue with other metadata operations even if issues fail
|
||||||
}
|
}
|
||||||
|
} else if (config.giteaConfig?.mirrorIssues && metadataState.components.issues) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Issues already mirrored for ${repository.name}; skipping to avoid duplicates`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror pull requests if enabled
|
const shouldMirrorPullRequests =
|
||||||
// Skip pull requests for starred repos if starredCodeOnly is enabled
|
!!config.giteaConfig?.mirrorPullRequests &&
|
||||||
const shouldMirrorPullRequests = config.giteaConfig?.mirrorPullRequests &&
|
!skipMetadataForStarred &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!metadataState.components.pullRequests;
|
||||||
|
|
||||||
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorPullRequests=${shouldMirrorPullRequests}`);
|
console.log(
|
||||||
|
`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}, alreadyMirrored=${metadataState.components.pullRequests}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorPullRequests=${shouldMirrorPullRequests}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorPullRequests) {
|
if (shouldMirrorPullRequests) {
|
||||||
try {
|
try {
|
||||||
@@ -649,19 +685,37 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
giteaOwner: repoOwner,
|
giteaOwner: repoOwner,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored pull requests for ${repository.name}`);
|
metadataState.components.pullRequests = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored pull requests for ${repository.name}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror pull requests for ${repository.name}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror pull requests for ${repository.name}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if PRs fail
|
// Continue with other metadata operations even if PRs fail
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorPullRequests &&
|
||||||
|
metadataState.components.pullRequests
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Pull requests already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror labels if enabled (and not already done via issues)
|
const shouldMirrorLabels =
|
||||||
// Skip labels for starred repos if starredCodeOnly is enabled
|
!!config.giteaConfig?.mirrorLabels &&
|
||||||
const shouldMirrorLabels = config.giteaConfig?.mirrorLabels && !shouldMirrorIssues &&
|
!skipMetadataForStarred &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!shouldMirrorIssuesThisRun &&
|
||||||
|
!metadataState.components.labels;
|
||||||
|
|
||||||
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorLabels=${shouldMirrorLabels}`);
|
console.log(
|
||||||
|
`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, alreadyMirrored=${metadataState.components.labels}, issuesRunning=${shouldMirrorIssuesThisRun}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorLabels=${shouldMirrorLabels}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorLabels) {
|
if (shouldMirrorLabels) {
|
||||||
try {
|
try {
|
||||||
@@ -672,19 +726,33 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
giteaOwner: repoOwner,
|
giteaOwner: repoOwner,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored labels for ${repository.name}`);
|
metadataState.components.labels = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored labels for ${repository.name}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror labels for ${repository.name}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror labels for ${repository.name}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if labels fail
|
// Continue with other metadata operations even if labels fail
|
||||||
}
|
}
|
||||||
|
} else if (config.giteaConfig?.mirrorLabels && metadataState.components.labels) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Labels already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror milestones if enabled
|
const shouldMirrorMilestones =
|
||||||
// Skip milestones for starred repos if starredCodeOnly is enabled
|
!!config.giteaConfig?.mirrorMilestones &&
|
||||||
const shouldMirrorMilestones = config.giteaConfig?.mirrorMilestones &&
|
!skipMetadataForStarred &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!metadataState.components.milestones;
|
||||||
|
|
||||||
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorMilestones=${shouldMirrorMilestones}`);
|
console.log(
|
||||||
|
`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}, alreadyMirrored=${metadataState.components.milestones}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorMilestones=${shouldMirrorMilestones}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorMilestones) {
|
if (shouldMirrorMilestones) {
|
||||||
try {
|
try {
|
||||||
@@ -695,11 +763,30 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
giteaOwner: repoOwner,
|
giteaOwner: repoOwner,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored milestones for ${repository.name}`);
|
metadataState.components.milestones = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored milestones for ${repository.name}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror milestones for ${repository.name}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror milestones for ${repository.name}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if milestones fail
|
// Continue with other metadata operations even if milestones fail
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorMilestones &&
|
||||||
|
metadataState.components.milestones
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Milestones already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadataUpdated) {
|
||||||
|
metadataState.lastSyncedAt = new Date().toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Repository ${repository.name} mirrored successfully as ${targetRepoName}`);
|
console.log(`Repository ${repository.name} mirrored successfully as ${targetRepoName}`);
|
||||||
@@ -713,6 +800,9 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
lastMirrored: new Date(),
|
lastMirrored: new Date(),
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
mirroredLocation: `${repoOwner}/${targetRepoName}`,
|
mirroredLocation: `${repoOwner}/${targetRepoName}`,
|
||||||
|
metadata: metadataUpdated
|
||||||
|
? serializeRepositoryMetadataState(metadataState)
|
||||||
|
: repository.metadata ?? null,
|
||||||
})
|
})
|
||||||
.where(eq(repositories.id, repository.id!));
|
.where(eq(repositories.id, repository.id!));
|
||||||
|
|
||||||
@@ -1053,12 +1143,17 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
//mirror releases
|
const metadataState = parseRepositoryMetadataState(repository.metadata);
|
||||||
// Skip releases for starred repos if starredCodeOnly is enabled
|
let metadataUpdated = false;
|
||||||
const shouldMirrorReleases = config.giteaConfig?.mirrorReleases &&
|
const skipMetadataForStarred =
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
repository.isStarred && config.githubConfig?.starredCodeOnly;
|
||||||
|
|
||||||
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorReleases=${shouldMirrorReleases}`);
|
const shouldMirrorReleases =
|
||||||
|
!!config.giteaConfig?.mirrorReleases && !skipMetadataForStarred;
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorReleases=${shouldMirrorReleases}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorReleases) {
|
if (shouldMirrorReleases) {
|
||||||
try {
|
try {
|
||||||
@@ -1069,21 +1164,31 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
giteaOwner: orgName,
|
giteaOwner: orgName,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored releases for ${repository.name}`);
|
metadataState.components.releases = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored releases for ${repository.name}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror releases for ${repository.name}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror releases for ${repository.name}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other operations even if releases fail
|
// Continue with other operations even if releases fail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone issues
|
const shouldMirrorIssuesThisRun =
|
||||||
// Skip issues for starred repos if starredCodeOnly is enabled
|
!!config.giteaConfig?.mirrorIssues &&
|
||||||
const shouldMirrorIssues = config.giteaConfig?.mirrorIssues &&
|
!skipMetadataForStarred &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!metadataState.components.issues;
|
||||||
|
|
||||||
console.log(`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorIssues=${shouldMirrorIssues}`);
|
console.log(
|
||||||
|
`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, alreadyMirrored=${metadataState.components.issues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorIssues=${shouldMirrorIssuesThisRun}`
|
||||||
if (shouldMirrorIssues) {
|
);
|
||||||
|
|
||||||
|
if (shouldMirrorIssuesThisRun) {
|
||||||
try {
|
try {
|
||||||
await mirrorGitRepoIssuesToGitea({
|
await mirrorGitRepoIssuesToGitea({
|
||||||
config,
|
config,
|
||||||
@@ -1092,19 +1197,37 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
giteaOwner: orgName,
|
giteaOwner: orgName,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored issues for ${repository.name} to org ${orgName}/${targetRepoName}`);
|
metadataState.components.issues = true;
|
||||||
|
metadataState.components.labels = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored issues for ${repository.name} to org ${orgName}/${targetRepoName}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror issues for ${repository.name} to org ${orgName}/${targetRepoName}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror issues for ${repository.name} to org ${orgName}/${targetRepoName}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if issues fail
|
// Continue with other metadata operations even if issues fail
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorIssues &&
|
||||||
|
metadataState.components.issues
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Issues already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror pull requests if enabled
|
const shouldMirrorPullRequests =
|
||||||
// Skip pull requests for starred repos if starredCodeOnly is enabled
|
!!config.giteaConfig?.mirrorPullRequests &&
|
||||||
const shouldMirrorPullRequests = config.giteaConfig?.mirrorPullRequests &&
|
!skipMetadataForStarred &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!metadataState.components.pullRequests;
|
||||||
|
|
||||||
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorPullRequests=${shouldMirrorPullRequests}`);
|
console.log(
|
||||||
|
`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}, alreadyMirrored=${metadataState.components.pullRequests}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorPullRequests=${shouldMirrorPullRequests}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorPullRequests) {
|
if (shouldMirrorPullRequests) {
|
||||||
try {
|
try {
|
||||||
@@ -1115,19 +1238,37 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
giteaOwner: orgName,
|
giteaOwner: orgName,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored pull requests for ${repository.name} to org ${orgName}/${targetRepoName}`);
|
metadataState.components.pullRequests = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored pull requests for ${repository.name} to org ${orgName}/${targetRepoName}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror pull requests for ${repository.name} to org ${orgName}/${targetRepoName}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror pull requests for ${repository.name} to org ${orgName}/${targetRepoName}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if PRs fail
|
// Continue with other metadata operations even if PRs fail
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorPullRequests &&
|
||||||
|
metadataState.components.pullRequests
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Pull requests already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror labels if enabled (and not already done via issues)
|
const shouldMirrorLabels =
|
||||||
// Skip labels for starred repos if starredCodeOnly is enabled
|
!!config.giteaConfig?.mirrorLabels &&
|
||||||
const shouldMirrorLabels = config.giteaConfig?.mirrorLabels && !shouldMirrorIssues &&
|
!skipMetadataForStarred &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!shouldMirrorIssuesThisRun &&
|
||||||
|
!metadataState.components.labels;
|
||||||
|
|
||||||
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorLabels=${shouldMirrorLabels}`);
|
console.log(
|
||||||
|
`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, alreadyMirrored=${metadataState.components.labels}, issuesRunning=${shouldMirrorIssuesThisRun}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorLabels=${shouldMirrorLabels}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorLabels) {
|
if (shouldMirrorLabels) {
|
||||||
try {
|
try {
|
||||||
@@ -1138,19 +1279,36 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
giteaOwner: orgName,
|
giteaOwner: orgName,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored labels for ${repository.name} to org ${orgName}/${targetRepoName}`);
|
metadataState.components.labels = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored labels for ${repository.name} to org ${orgName}/${targetRepoName}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror labels for ${repository.name} to org ${orgName}/${targetRepoName}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror labels for ${repository.name} to org ${orgName}/${targetRepoName}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if labels fail
|
// Continue with other metadata operations even if labels fail
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorLabels &&
|
||||||
|
metadataState.components.labels
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Labels already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mirror milestones if enabled
|
const shouldMirrorMilestones =
|
||||||
// Skip milestones for starred repos if starredCodeOnly is enabled
|
!!config.giteaConfig?.mirrorMilestones &&
|
||||||
const shouldMirrorMilestones = config.giteaConfig?.mirrorMilestones &&
|
!skipMetadataForStarred &&
|
||||||
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
|
!metadataState.components.milestones;
|
||||||
|
|
||||||
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorMilestones=${shouldMirrorMilestones}`);
|
console.log(
|
||||||
|
`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}, alreadyMirrored=${metadataState.components.milestones}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorMilestones=${shouldMirrorMilestones}`
|
||||||
|
);
|
||||||
|
|
||||||
if (shouldMirrorMilestones) {
|
if (shouldMirrorMilestones) {
|
||||||
try {
|
try {
|
||||||
@@ -1161,11 +1319,30 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
giteaOwner: orgName,
|
giteaOwner: orgName,
|
||||||
giteaRepoName: targetRepoName,
|
giteaRepoName: targetRepoName,
|
||||||
});
|
});
|
||||||
console.log(`[Metadata] Successfully mirrored milestones for ${repository.name} to org ${orgName}/${targetRepoName}`);
|
metadataState.components.milestones = true;
|
||||||
|
metadataUpdated = true;
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Successfully mirrored milestones for ${repository.name} to org ${orgName}/${targetRepoName}`
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[Metadata] Failed to mirror milestones for ${repository.name} to org ${orgName}/${targetRepoName}: ${error instanceof Error ? error.message : String(error)}`);
|
console.error(
|
||||||
|
`[Metadata] Failed to mirror milestones for ${repository.name} to org ${orgName}/${targetRepoName}: ${
|
||||||
|
error instanceof Error ? error.message : String(error)
|
||||||
|
}`
|
||||||
|
);
|
||||||
// Continue with other metadata operations even if milestones fail
|
// Continue with other metadata operations even if milestones fail
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
config.giteaConfig?.mirrorMilestones &&
|
||||||
|
metadataState.components.milestones
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`[Metadata] Milestones already mirrored for ${repository.name}; skipping`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metadataUpdated) {
|
||||||
|
metadataState.lastSyncedAt = new Date().toISOString();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
@@ -1181,6 +1358,9 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
lastMirrored: new Date(),
|
lastMirrored: new Date(),
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
mirroredLocation: `${orgName}/${targetRepoName}`,
|
mirroredLocation: `${orgName}/${targetRepoName}`,
|
||||||
|
metadata: metadataUpdated
|
||||||
|
? serializeRepositoryMetadataState(metadataState)
|
||||||
|
: repository.metadata ?? null,
|
||||||
})
|
})
|
||||||
.where(eq(repositories.id, repository.id!));
|
.where(eq(repositories.id, repository.id!));
|
||||||
|
|
||||||
|
|||||||
75
src/lib/metadata-state.ts
Normal file
75
src/lib/metadata-state.ts
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
interface MetadataComponentsState {
|
||||||
|
releases: boolean;
|
||||||
|
issues: boolean;
|
||||||
|
pullRequests: boolean;
|
||||||
|
labels: boolean;
|
||||||
|
milestones: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RepositoryMetadataState {
|
||||||
|
components: MetadataComponentsState;
|
||||||
|
lastSyncedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultComponents: MetadataComponentsState = {
|
||||||
|
releases: false,
|
||||||
|
issues: false,
|
||||||
|
pullRequests: false,
|
||||||
|
labels: false,
|
||||||
|
milestones: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function createDefaultMetadataState(): RepositoryMetadataState {
|
||||||
|
return {
|
||||||
|
components: { ...defaultComponents },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseRepositoryMetadataState(
|
||||||
|
raw: unknown
|
||||||
|
): RepositoryMetadataState {
|
||||||
|
const base = createDefaultMetadataState();
|
||||||
|
|
||||||
|
if (!raw) {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: any = raw;
|
||||||
|
|
||||||
|
if (typeof raw === "string") {
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parsed || typeof parsed !== "object") {
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.components && typeof parsed.components === "object") {
|
||||||
|
base.components = {
|
||||||
|
...base.components,
|
||||||
|
releases: Boolean(parsed.components.releases),
|
||||||
|
issues: Boolean(parsed.components.issues),
|
||||||
|
pullRequests: Boolean(parsed.components.pullRequests),
|
||||||
|
labels: Boolean(parsed.components.labels),
|
||||||
|
milestones: Boolean(parsed.components.milestones),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof parsed.lastSyncedAt === "string") {
|
||||||
|
base.lastSyncedAt = parsed.lastSyncedAt;
|
||||||
|
} else if (typeof parsed.lastMetadataSync === "string") {
|
||||||
|
base.lastSyncedAt = parsed.lastMetadataSync;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function serializeRepositoryMetadataState(
|
||||||
|
state: RepositoryMetadataState
|
||||||
|
): string {
|
||||||
|
return JSON.stringify(state);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user