Skip to content

Commit 3857f2f

Browse files
authored
Merge pull request #150 from RayLabsHQ/148-release-order-again
fix: Detect and recreate incorrectly ordered releases (#148)
2 parents d0cade6 + e951e97 commit 3857f2f

File tree

1 file changed

+109
-16
lines changed

1 file changed

+109
-16
lines changed

src/lib/gitea.ts

Lines changed: 109 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,40 @@
11
import {
2-
repoStatusEnum,
2+
and,
3+
eq,
4+
} from 'drizzle-orm';
5+
6+
import type { Config } from '@/types/config';
7+
import { membershipRoleEnum } from '@/types/organizations';
8+
import {
39
type RepositoryVisibility,
410
type RepoStatus,
5-
} from "@/types/Repository";
6-
import { membershipRoleEnum } from "@/types/organizations";
7-
import { Octokit } from "@octokit/rest";
8-
import type { Config } from "@/types/config";
9-
import type { Organization, Repository } from "./db/schema";
10-
import { httpPost, httpGet, httpDelete, httpPut, httpPatch } from "./http-client";
11-
import { createMirrorJob } from "./helpers";
12-
import { db, organizations, repositories } from "./db";
13-
import { eq, and } from "drizzle-orm";
14-
import { decryptConfigTokens } from "./utils/config-encryption";
15-
import { formatDateShort } from "./utils";
11+
repoStatusEnum,
12+
} from '@/types/Repository';
13+
import { Octokit } from '@octokit/rest';
14+
15+
import {
16+
db,
17+
organizations,
18+
repositories,
19+
} from './db';
20+
import type {
21+
Organization,
22+
Repository,
23+
} from './db/schema';
24+
import { createMirrorJob } from './helpers';
25+
import {
26+
httpDelete,
27+
httpGet,
28+
httpPatch,
29+
httpPost,
30+
httpPut,
31+
} from './http-client';
1632
import {
1733
parseRepositoryMetadataState,
1834
serializeRepositoryMetadataState,
19-
} from "./metadata-state";
35+
} from './metadata-state';
36+
import { formatDateShort } from './utils';
37+
import { decryptConfigTokens } from './utils/config-encryption';
2038

2139
/**
2240
* Helper function to get organization configuration including destination override
@@ -2011,10 +2029,85 @@ export async function mirrorGitHubReleasesToGitea({
20112029
console.log(`[Releases] ${idx + 1}. ${rel.tag_name} - Originally published: ${date.toISOString()}`);
20122030
});
20132031

2032+
// Check if existing releases in Gitea are in the wrong order
2033+
// If so, we need to delete and recreate them to fix the ordering
2034+
let needsRecreation = false;
2035+
try {
2036+
const existingReleasesResponse = await httpGet(
2037+
`${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repoName}/releases?per_page=100`,
2038+
{
2039+
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
2040+
}
2041+
).catch(() => null);
2042+
2043+
if (existingReleasesResponse && existingReleasesResponse.data && Array.isArray(existingReleasesResponse.data)) {
2044+
const existingReleases = existingReleasesResponse.data;
2045+
2046+
if (existingReleases.length > 0) {
2047+
console.log(`[Releases] Found ${existingReleases.length} existing releases in Gitea, checking chronological order...`);
2048+
2049+
// Create a map of tag_name to expected chronological index (0 = oldest, n = newest)
2050+
const expectedOrder = new Map<string, number>();
2051+
releasesToProcess.forEach((rel, idx) => {
2052+
expectedOrder.set(rel.tag_name, idx);
2053+
});
2054+
2055+
// Check if existing releases are in the correct order based on created_unix
2056+
// Gitea sorts by created_unix DESC, so newer releases should have higher created_unix values
2057+
const releasesThatShouldExist = existingReleases.filter(r => expectedOrder.has(r.tag_name));
2058+
2059+
if (releasesThatShouldExist.length > 1) {
2060+
for (let i = 0; i < releasesThatShouldExist.length - 1; i++) {
2061+
const current = releasesThatShouldExist[i];
2062+
const next = releasesThatShouldExist[i + 1];
2063+
2064+
const currentExpectedIdx = expectedOrder.get(current.tag_name)!;
2065+
const nextExpectedIdx = expectedOrder.get(next.tag_name)!;
2066+
2067+
// Since Gitea returns releases sorted by created_unix DESC:
2068+
// - Earlier releases in the list should have HIGHER expected indices (newer)
2069+
// - Later releases in the list should have LOWER expected indices (older)
2070+
if (currentExpectedIdx < nextExpectedIdx) {
2071+
console.log(`[Releases] ⚠️ Incorrect ordering detected: ${current.tag_name} (index ${currentExpectedIdx}) appears before ${next.tag_name} (index ${nextExpectedIdx})`);
2072+
needsRecreation = true;
2073+
break;
2074+
}
2075+
}
2076+
}
2077+
2078+
if (needsRecreation) {
2079+
console.log(`[Releases] ⚠️ Releases are in incorrect chronological order. Will delete and recreate all releases.`);
2080+
2081+
// Delete all existing releases that we're about to recreate
2082+
for (const existingRelease of releasesThatShouldExist) {
2083+
try {
2084+
console.log(`[Releases] Deleting incorrectly ordered release: ${existingRelease.tag_name}`);
2085+
await httpDelete(
2086+
`${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repoName}/releases/${existingRelease.id}`,
2087+
{
2088+
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
2089+
}
2090+
);
2091+
} catch (deleteError) {
2092+
console.error(`[Releases] Failed to delete release ${existingRelease.tag_name}: ${deleteError instanceof Error ? deleteError.message : String(deleteError)}`);
2093+
}
2094+
}
2095+
2096+
console.log(`[Releases] ✅ Deleted ${releasesThatShouldExist.length} releases. Will recreate in correct chronological order.`);
2097+
} else {
2098+
console.log(`[Releases] ✅ Existing releases are in correct chronological order.`);
2099+
}
2100+
}
2101+
}
2102+
} catch (orderCheckError) {
2103+
console.warn(`[Releases] Could not verify release order: ${orderCheckError instanceof Error ? orderCheckError.message : String(orderCheckError)}`);
2104+
// Continue with normal processing
2105+
}
2106+
20142107
for (const release of releasesToProcess) {
20152108
try {
2016-
// Check if release already exists
2017-
const existingReleasesResponse = await httpGet(
2109+
// Check if release already exists (skip check if we just deleted all releases)
2110+
const existingReleasesResponse = needsRecreation ? null : await httpGet(
20182111
`${config.giteaConfig.url}/api/v1/repos/${repoOwner}/${repoName}/releases/tags/${release.tag_name}`,
20192112
{
20202113
Authorization: `token ${decryptedConfig.giteaConfig.token}`,
@@ -2801,4 +2894,4 @@ export async function archiveGiteaRepo(
28012894
console.log(`[Archive] Repository ${owner}/${repo} data is preserved but not marked as archived`);
28022895
// Don't throw - we want cleanup to continue for other repos
28032896
}
2804-
}
2897+
}

0 commit comments

Comments
 (0)