mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-03-13 22:12:54 +03:00
Implement incremental issue and PR metadata sync
This commit is contained in:
@@ -587,7 +587,7 @@ describe("Enhanced Gitea Operations", () => {
|
|||||||
expect(mockMirrorGitRepoLabelsToGitea).not.toHaveBeenCalled();
|
expect(mockMirrorGitRepoLabelsToGitea).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("skips metadata mirroring when components already synced", async () => {
|
test("continues incremental issue and PR syncing when metadata was previously synced", async () => {
|
||||||
const config: Partial<Config> = {
|
const config: Partial<Config> = {
|
||||||
userId: "user123",
|
userId: "user123",
|
||||||
githubConfig: {
|
githubConfig: {
|
||||||
@@ -647,8 +647,8 @@ describe("Enhanced Gitea Operations", () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
expect(mockMirrorGitHubReleasesToGitea).not.toHaveBeenCalled();
|
expect(mockMirrorGitHubReleasesToGitea).not.toHaveBeenCalled();
|
||||||
expect(mockMirrorGitRepoIssuesToGitea).not.toHaveBeenCalled();
|
expect(mockMirrorGitRepoIssuesToGitea).toHaveBeenCalledTimes(1);
|
||||||
expect(mockMirrorGitRepoPullRequestsToGitea).not.toHaveBeenCalled();
|
expect(mockMirrorGitRepoPullRequestsToGitea).toHaveBeenCalledTimes(1);
|
||||||
expect(mockMirrorGitRepoLabelsToGitea).not.toHaveBeenCalled();
|
expect(mockMirrorGitRepoLabelsToGitea).not.toHaveBeenCalled();
|
||||||
expect(mockMirrorGitRepoMilestonesToGitea).not.toHaveBeenCalled();
|
expect(mockMirrorGitRepoMilestonesToGitea).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -361,12 +361,10 @@ export async function syncGiteaRepoEnhanced({
|
|||||||
!!config.giteaConfig?.mirrorReleases && !skipMetadataForStarred;
|
!!config.giteaConfig?.mirrorReleases && !skipMetadataForStarred;
|
||||||
const shouldMirrorIssuesThisRun =
|
const shouldMirrorIssuesThisRun =
|
||||||
!!config.giteaConfig?.mirrorIssues &&
|
!!config.giteaConfig?.mirrorIssues &&
|
||||||
!skipMetadataForStarred &&
|
!skipMetadataForStarred;
|
||||||
!metadataState.components.issues;
|
|
||||||
const shouldMirrorPullRequests =
|
const shouldMirrorPullRequests =
|
||||||
!!config.giteaConfig?.mirrorPullRequests &&
|
!!config.giteaConfig?.mirrorPullRequests &&
|
||||||
!skipMetadataForStarred &&
|
!skipMetadataForStarred;
|
||||||
!metadataState.components.pullRequests;
|
|
||||||
const shouldMirrorLabels =
|
const shouldMirrorLabels =
|
||||||
!!config.giteaConfig?.mirrorLabels &&
|
!!config.giteaConfig?.mirrorLabels &&
|
||||||
!skipMetadataForStarred &&
|
!skipMetadataForStarred &&
|
||||||
@@ -440,13 +438,6 @@ export async function syncGiteaRepoEnhanced({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
|
||||||
config.giteaConfig?.mirrorIssues &&
|
|
||||||
metadataState.components.issues
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`[Sync] Issues already mirrored for ${repository.name}; skipping`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldMirrorPullRequests) {
|
if (shouldMirrorPullRequests) {
|
||||||
@@ -477,13 +468,6 @@ export async function syncGiteaRepoEnhanced({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (
|
|
||||||
config.giteaConfig?.mirrorPullRequests &&
|
|
||||||
metadataState.components.pullRequests
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`[Sync] Pull requests already mirrored for ${repository.name}; skipping`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldMirrorLabels) {
|
if (shouldMirrorLabels) {
|
||||||
|
|||||||
347
src/lib/gitea.ts
347
src/lib/gitea.ts
@@ -1773,6 +1773,53 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ghIssueMarkerRegex = /\[GH-ISSUE #(\d+)\]/i;
|
||||||
|
const extractGitHubIssueNumber = (value: string | null | undefined): number | null => {
|
||||||
|
if (!value) return null;
|
||||||
|
const match = value.match(ghIssueMarkerRegex);
|
||||||
|
if (!match?.[1]) return null;
|
||||||
|
const parsed = Number.parseInt(match[1], 10);
|
||||||
|
return Number.isFinite(parsed) ? parsed : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const existingGiteaIssues: any[] = [];
|
||||||
|
const titleFallbackMap = new Map<string, any[]>();
|
||||||
|
const giteaIssueByGitHubNumber = new Map<number, any>();
|
||||||
|
let issuesPage = 1;
|
||||||
|
const issuesPerPage = 100;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const existingIssuesRes = await httpGet(
|
||||||
|
`${config.giteaConfig.url}/api/v1/repos/${giteaOwner}/${repoName}/issues?state=all&page=${issuesPage}&limit=${issuesPerPage}`,
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const pageIssues = Array.isArray(existingIssuesRes.data)
|
||||||
|
? existingIssuesRes.data
|
||||||
|
: [];
|
||||||
|
if (!pageIssues.length) break;
|
||||||
|
|
||||||
|
existingGiteaIssues.push(...pageIssues);
|
||||||
|
if (pageIssues.length < issuesPerPage) break;
|
||||||
|
issuesPage += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const giteaIssue of existingGiteaIssues) {
|
||||||
|
const mappedNumber = extractGitHubIssueNumber(giteaIssue.title);
|
||||||
|
if (mappedNumber !== null) {
|
||||||
|
giteaIssueByGitHubNumber.set(mappedNumber, giteaIssue);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = (giteaIssue.title || "").trim();
|
||||||
|
if (!title) continue;
|
||||||
|
const existing = titleFallbackMap.get(title) || [];
|
||||||
|
existing.push(giteaIssue);
|
||||||
|
titleFallbackMap.set(title, existing);
|
||||||
|
}
|
||||||
|
|
||||||
// Get existing labels from Gitea
|
// Get existing labels from Gitea
|
||||||
const giteaLabelsRes = await httpGet(
|
const giteaLabelsRes = await httpGet(
|
||||||
`${config.giteaConfig.url}/api/v1/repos/${giteaOwner}/${repoName}/labels`,
|
`${config.giteaConfig.url}/api/v1/repos/${giteaOwner}/${repoName}/labels`,
|
||||||
@@ -1848,57 +1895,71 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
|||||||
const issueOriginHeader = `Originally created by @${issueAuthor} on GitHub${
|
const issueOriginHeader = `Originally created by @${issueAuthor} on GitHub${
|
||||||
issueCreatedOn ? ` (${issueCreatedOn})` : ""
|
issueCreatedOn ? ` (${issueCreatedOn})` : ""
|
||||||
}.`;
|
}.`;
|
||||||
|
const issueMarker = `[GH-ISSUE #${issue.number}]`;
|
||||||
|
const mirroredTitle = `${issueMarker} ${issue.title}`;
|
||||||
|
const issueBody = `${issueOriginHeader}\nOriginal GitHub issue: ${issue.html_url}${originalAssignees}\n\n${issue.body ?? ""}`;
|
||||||
|
|
||||||
const issuePayload: any = {
|
const issuePayload: any = {
|
||||||
title: issue.title,
|
title: mirroredTitle,
|
||||||
body: `${issueOriginHeader}${originalAssignees}\n\n${issue.body ?? ""}`,
|
body: issueBody,
|
||||||
closed: issue.state === "closed",
|
closed: issue.state === "closed",
|
||||||
labels: giteaLabelIds,
|
labels: giteaLabelIds,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the issue in Gitea
|
let existingIssue = giteaIssueByGitHubNumber.get(issue.number);
|
||||||
const createdIssue = await httpPost(
|
if (!existingIssue) {
|
||||||
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues`,
|
const titleFallbackCandidates = titleFallbackMap.get(issue.title.trim()) || [];
|
||||||
issuePayload,
|
if (titleFallbackCandidates.length === 1) {
|
||||||
{
|
existingIssue = titleFallbackCandidates[0];
|
||||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
giteaIssueByGitHubNumber.set(issue.number, existingIssue);
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
// Verify and explicitly close if the issue should be closed but wasn't
|
|
||||||
// Gitea's API creates issues as open first, then closes them - this can fail silently
|
|
||||||
const shouldBeClosed = issue.state === "closed";
|
|
||||||
const isActuallyClosed = createdIssue.data.state === "closed";
|
|
||||||
|
|
||||||
if (shouldBeClosed && !isActuallyClosed) {
|
|
||||||
console.log(
|
|
||||||
`[Issues] Issue #${createdIssue.data.number} was not closed during creation, attempting explicit close`
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
await httpPatch(
|
|
||||||
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${createdIssue.data.number}`,
|
|
||||||
{ state: "closed" },
|
|
||||||
{
|
|
||||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
console.log(
|
console.log(
|
||||||
`[Issues] Successfully closed issue #${createdIssue.data.number}`
|
`[Issues] Matched legacy issue by title for #${issue.number}; converting to marker-based title`
|
||||||
);
|
|
||||||
} catch (closeError) {
|
|
||||||
console.error(
|
|
||||||
`[Issues] Failed to close issue #${createdIssue.data.number}: ${
|
|
||||||
closeError instanceof Error ? closeError.message : String(closeError)
|
|
||||||
}`
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify body content was synced correctly
|
let targetIssueNumber: number;
|
||||||
if (issue.body && (!createdIssue.data.body || createdIssue.data.body.length === 0)) {
|
if (existingIssue) {
|
||||||
console.warn(
|
targetIssueNumber = existingIssue.number;
|
||||||
`[Issues] Issue #${createdIssue.data.number} may have missing body content - original had ${issue.body.length} chars`
|
await httpPatch(
|
||||||
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${targetIssueNumber}`,
|
||||||
|
{
|
||||||
|
title: issuePayload.title,
|
||||||
|
body: issuePayload.body,
|
||||||
|
state: issue.state === "closed" ? "closed" : "open",
|
||||||
|
labels: issuePayload.labels,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
const createdIssue = await httpPost(
|
||||||
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues`,
|
||||||
|
issuePayload,
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
targetIssueNumber = createdIssue.data.number;
|
||||||
|
|
||||||
|
if (issue.state === "closed" && createdIssue.data.state !== "closed") {
|
||||||
|
try {
|
||||||
|
await httpPatch(
|
||||||
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${targetIssueNumber}`,
|
||||||
|
{ state: "closed" },
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (closeError) {
|
||||||
|
console.error(
|
||||||
|
`[Issues] Failed to close issue #${targetIssueNumber}: ${
|
||||||
|
closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone comments
|
// Clone comments
|
||||||
@@ -1924,9 +1985,41 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
|||||||
|
|
||||||
// Process comments sequentially to preserve historical ordering
|
// Process comments sequentially to preserve historical ordering
|
||||||
if (sortedComments.length > 0) {
|
if (sortedComments.length > 0) {
|
||||||
|
const existingComments: any[] = [];
|
||||||
|
let commentsPage = 1;
|
||||||
|
const commentsPerPage = 100;
|
||||||
|
while (true) {
|
||||||
|
const existingCommentsRes = await httpGet(
|
||||||
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${targetIssueNumber}/comments?page=${commentsPage}&limit=${commentsPerPage}`,
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const pageComments = Array.isArray(existingCommentsRes.data)
|
||||||
|
? existingCommentsRes.data
|
||||||
|
: [];
|
||||||
|
if (!pageComments.length) break;
|
||||||
|
existingComments.push(...pageComments);
|
||||||
|
if (pageComments.length < commentsPerPage) break;
|
||||||
|
commentsPage += 1;
|
||||||
|
}
|
||||||
|
const mirroredCommentIds = new Set<number>();
|
||||||
|
for (const existingComment of existingComments) {
|
||||||
|
const marker = String(existingComment.body || "").match(
|
||||||
|
/<!--\s*gh-comment-id:(\d+)\s*-->/i
|
||||||
|
);
|
||||||
|
if (marker?.[1]) {
|
||||||
|
const parsed = Number.parseInt(marker[1], 10);
|
||||||
|
if (Number.isFinite(parsed)) mirroredCommentIds.add(parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await processWithRetry(
|
await processWithRetry(
|
||||||
sortedComments,
|
sortedComments,
|
||||||
async (comment) => {
|
async (comment) => {
|
||||||
|
if (mirroredCommentIds.has(comment.id)) {
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
const commenter = comment.user?.login ?? "unknown";
|
const commenter = comment.user?.login ?? "unknown";
|
||||||
const commentDate = formatDateShort(comment.created_at);
|
const commentDate = formatDateShort(comment.created_at);
|
||||||
const commentHeader = `@${commenter} commented on GitHub${
|
const commentHeader = `@${commenter} commented on GitHub${
|
||||||
@@ -1934,9 +2027,9 @@ export const mirrorGitRepoIssuesToGitea = async ({
|
|||||||
}:`;
|
}:`;
|
||||||
|
|
||||||
await httpPost(
|
await httpPost(
|
||||||
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${createdIssue.data.number}/comments`,
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${targetIssueNumber}/comments`,
|
||||||
{
|
{
|
||||||
body: `${commentHeader}\n\n${comment.body ?? ""}`,
|
body: `<!-- gh-comment-id:${comment.id} -->\n${commentHeader}\n\n${comment.body ?? ""}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
@@ -2438,6 +2531,34 @@ export async function mirrorGitRepoPullRequestsToGitea({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const existingPrIssuesByNumber = new Map<number, any>();
|
||||||
|
let prIssuesPage = 1;
|
||||||
|
const prIssuesPerPage = 100;
|
||||||
|
while (true) {
|
||||||
|
const existingIssuesRes = await httpGet(
|
||||||
|
`${config.giteaConfig.url}/api/v1/repos/${giteaOwner}/${repoName}/issues?state=all&page=${prIssuesPage}&limit=${prIssuesPerPage}`,
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const pageIssues = Array.isArray(existingIssuesRes.data)
|
||||||
|
? existingIssuesRes.data
|
||||||
|
: [];
|
||||||
|
if (!pageIssues.length) break;
|
||||||
|
|
||||||
|
for (const issue of pageIssues) {
|
||||||
|
const match = String(issue.title || "").match(/\[PR #(\d+)\]/i);
|
||||||
|
if (!match?.[1]) continue;
|
||||||
|
const prNumber = Number.parseInt(match[1], 10);
|
||||||
|
if (Number.isFinite(prNumber)) {
|
||||||
|
existingPrIssuesByNumber.set(prNumber, issue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pageIssues.length < prIssuesPerPage) break;
|
||||||
|
prIssuesPage += 1;
|
||||||
|
}
|
||||||
|
|
||||||
const { processWithRetry } = await import("@/lib/utils/concurrency");
|
const { processWithRetry } = await import("@/lib/utils/concurrency");
|
||||||
|
|
||||||
const rawPullConcurrency = config.giteaConfig?.pullRequestConcurrency ?? 5;
|
const rawPullConcurrency = config.giteaConfig?.pullRequestConcurrency ?? 5;
|
||||||
@@ -2535,40 +2656,51 @@ export async function mirrorGitRepoPullRequestsToGitea({
|
|||||||
closed: pr.state === "closed" || pr.merged_at !== null,
|
closed: pr.state === "closed" || pr.merged_at !== null,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log(`[Pull Requests] Creating enriched issue for PR #${pr.number}: ${pr.title}`);
|
const existingPrIssue = existingPrIssuesByNumber.get(pr.number);
|
||||||
const createdPrIssue = await httpPost(
|
if (existingPrIssue) {
|
||||||
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues`,
|
await httpPatch(
|
||||||
issueData,
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${existingPrIssue.number}`,
|
||||||
{
|
{
|
||||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
title: issueData.title,
|
||||||
}
|
body: issueData.body,
|
||||||
);
|
state: issueData.closed ? "closed" : "open",
|
||||||
|
labels: issueData.labels,
|
||||||
// Verify and explicitly close if the PR issue should be closed but wasn't
|
},
|
||||||
const prShouldBeClosed = pr.state === "closed" || pr.merged_at !== null;
|
{
|
||||||
const prIsActuallyClosed = createdPrIssue.data.state === "closed";
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
if (prShouldBeClosed && !prIsActuallyClosed) {
|
|
||||||
console.log(
|
|
||||||
`[Pull Requests] Issue for PR #${pr.number} was not closed during creation, attempting explicit close`
|
|
||||||
);
|
);
|
||||||
try {
|
} else {
|
||||||
await httpPatch(
|
console.log(`[Pull Requests] Creating enriched issue for PR #${pr.number}: ${pr.title}`);
|
||||||
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${createdPrIssue.data.number}`,
|
const createdPrIssue = await httpPost(
|
||||||
{ state: "closed" },
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues`,
|
||||||
{
|
issueData,
|
||||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
{
|
||||||
}
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
);
|
}
|
||||||
console.log(
|
);
|
||||||
`[Pull Requests] Successfully closed issue for PR #${pr.number}`
|
existingPrIssuesByNumber.set(pr.number, createdPrIssue.data);
|
||||||
);
|
|
||||||
} catch (closeError) {
|
// Verify and explicitly close if the PR issue should be closed but wasn't
|
||||||
console.error(
|
const prShouldBeClosed = pr.state === "closed" || pr.merged_at !== null;
|
||||||
`[Pull Requests] Failed to close issue for PR #${pr.number}: ${
|
const prIsActuallyClosed = createdPrIssue.data.state === "closed";
|
||||||
closeError instanceof Error ? closeError.message : String(closeError)
|
|
||||||
}`
|
if (prShouldBeClosed && !prIsActuallyClosed) {
|
||||||
);
|
try {
|
||||||
|
await httpPatch(
|
||||||
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${createdPrIssue.data.number}`,
|
||||||
|
{ state: "closed" },
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (closeError) {
|
||||||
|
console.error(
|
||||||
|
`[Pull Requests] Failed to close issue for PR #${pr.number}: ${
|
||||||
|
closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
|
}`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2585,33 +2717,50 @@ export async function mirrorGitRepoPullRequestsToGitea({
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const createdBasicPrIssue = await httpPost(
|
const existingPrIssue = existingPrIssuesByNumber.get(pr.number);
|
||||||
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues`,
|
if (existingPrIssue) {
|
||||||
basicIssueData,
|
await httpPatch(
|
||||||
{
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${existingPrIssue.number}`,
|
||||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
{
|
||||||
}
|
title: basicIssueData.title,
|
||||||
);
|
body: basicIssueData.body,
|
||||||
|
state: basicIssueData.closed ? "closed" : "open",
|
||||||
|
labels: basicIssueData.labels,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const createdBasicPrIssue = await httpPost(
|
||||||
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues`,
|
||||||
|
basicIssueData,
|
||||||
|
{
|
||||||
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
existingPrIssuesByNumber.set(pr.number, createdBasicPrIssue.data);
|
||||||
|
|
||||||
// Verify and explicitly close if needed
|
// Verify and explicitly close if needed
|
||||||
const basicPrShouldBeClosed = pr.state === "closed" || pr.merged_at !== null;
|
const basicPrShouldBeClosed = pr.state === "closed" || pr.merged_at !== null;
|
||||||
const basicPrIsActuallyClosed = createdBasicPrIssue.data.state === "closed";
|
const basicPrIsActuallyClosed = createdBasicPrIssue.data.state === "closed";
|
||||||
|
|
||||||
if (basicPrShouldBeClosed && !basicPrIsActuallyClosed) {
|
if (basicPrShouldBeClosed && !basicPrIsActuallyClosed) {
|
||||||
try {
|
try {
|
||||||
await httpPatch(
|
await httpPatch(
|
||||||
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${createdBasicPrIssue.data.number}`,
|
`${config.giteaConfig!.url}/api/v1/repos/${giteaOwner}/${repoName}/issues/${createdBasicPrIssue.data.number}`,
|
||||||
{ state: "closed" },
|
{ state: "closed" },
|
||||||
{
|
{
|
||||||
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
Authorization: `token ${decryptedConfig.giteaConfig!.token}`,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (closeError) {
|
} catch (closeError) {
|
||||||
console.error(
|
console.error(
|
||||||
`[Pull Requests] Failed to close basic issue for PR #${pr.number}: ${
|
`[Pull Requests] Failed to close basic issue for PR #${pr.number}: ${
|
||||||
closeError instanceof Error ? closeError.message : String(closeError)
|
closeError instanceof Error ? closeError.message : String(closeError)
|
||||||
}`
|
}`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user