11import {
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' ;
1632import {
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