mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-08 12:36:44 +03:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
818ba77693 | ||
|
|
056970e577 | ||
|
|
65ea73e238 | ||
|
|
398f00aceb | ||
|
|
50972713a3 | ||
|
|
fbf3033455 | ||
|
|
cc4d8dabbc |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -7,11 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
## [2.15.0] - 2025-06-17
|
## [2.16.1] - 2025-06-17
|
||||||
|
|
||||||
|
### Improved
|
||||||
|
- Improved repository owner handling and mirror strategy in Gitea integration
|
||||||
|
- Updated label for starred repositories organization for consistency
|
||||||
|
|
||||||
|
## [2.16.0] - 2025-06-17
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
- Enhanced OrganizationConfiguration component with improved layout and metadata options
|
- Enhanced OrganizationConfiguration component with improved layout and metadata options
|
||||||
- New GitHubMirrorSettings component with better organization and flexibility
|
- New GitHubMirrorSettings component with better organization and flexibility
|
||||||
|
- Enhanced starred repositories content selection and improved layout
|
||||||
|
|
||||||
### Improved
|
### Improved
|
||||||
- Enhanced configuration interface layout and spacing across multiple components
|
- Enhanced configuration interface layout and spacing across multiple components
|
||||||
@@ -19,6 +26,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Improved responsive layout for larger screens in configuration forms
|
- Improved responsive layout for larger screens in configuration forms
|
||||||
- Better icon usage and clarity in configuration components
|
- Better icon usage and clarity in configuration components
|
||||||
- Enhanced tooltip descriptions and component organization
|
- Enhanced tooltip descriptions and component organization
|
||||||
|
- Improved version comparison logic in health API
|
||||||
|
- Enhanced issue mirroring logic for starred repositories
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Fixed mirror to single organization functionality
|
- Fixed mirror to single organization functionality
|
||||||
@@ -29,6 +38,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Simplified component structures by removing unused imports and dependencies
|
- Simplified component structures by removing unused imports and dependencies
|
||||||
- Enhanced layout flexibility in GitHubConfigForm and GiteaConfigForm components
|
- Enhanced layout flexibility in GitHubConfigForm and GiteaConfigForm components
|
||||||
- Improved component organization and code clarity
|
- Improved component organization and code clarity
|
||||||
|
- Removed ConnectionsForm and useMirror hook for better code organization
|
||||||
|
|
||||||
## [2.14.0] - 2025-06-17
|
## [2.14.0] - 2025-06-17
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "gitea-mirror",
|
"name": "gitea-mirror",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.15.0",
|
"version": "2.16.1",
|
||||||
"engines": {
|
"engines": {
|
||||||
"bun": ">=1.2.9"
|
"bun": ">=1.2.9"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { GitHubConfigForm } from './GitHubConfigForm';
|
|
||||||
import { GiteaConfigForm } from './GiteaConfigForm';
|
|
||||||
import { Separator } from '../ui/separator';
|
|
||||||
import type { GitHubConfig, GiteaConfig } from '@/types/config';
|
|
||||||
|
|
||||||
interface ConnectionsFormProps {
|
|
||||||
githubConfig: GitHubConfig;
|
|
||||||
giteaConfig: GiteaConfig;
|
|
||||||
setGithubConfig: (update: GitHubConfig | ((prev: GitHubConfig) => GitHubConfig)) => void;
|
|
||||||
setGiteaConfig: (update: GiteaConfig | ((prev: GiteaConfig) => GiteaConfig)) => void;
|
|
||||||
onAutoSaveGitHub?: (config: GitHubConfig) => Promise<void>;
|
|
||||||
onAutoSaveGitea?: (config: GiteaConfig) => Promise<void>;
|
|
||||||
isAutoSavingGitHub?: boolean;
|
|
||||||
isAutoSavingGitea?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function ConnectionsForm({
|
|
||||||
githubConfig,
|
|
||||||
giteaConfig,
|
|
||||||
setGithubConfig,
|
|
||||||
setGiteaConfig,
|
|
||||||
onAutoSaveGitHub,
|
|
||||||
onAutoSaveGitea,
|
|
||||||
isAutoSavingGitHub,
|
|
||||||
isAutoSavingGitea,
|
|
||||||
}: ConnectionsFormProps) {
|
|
||||||
return (
|
|
||||||
<div className="space-y-6">
|
|
||||||
<GitHubConfigForm
|
|
||||||
config={githubConfig}
|
|
||||||
setConfig={setGithubConfig}
|
|
||||||
onAutoSave={onAutoSaveGitHub}
|
|
||||||
isAutoSaving={isAutoSavingGitHub}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<GiteaConfigForm
|
|
||||||
config={giteaConfig}
|
|
||||||
setConfig={setGiteaConfig}
|
|
||||||
onAutoSave={onAutoSaveGitea}
|
|
||||||
isAutoSaving={isAutoSavingGitea}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -76,6 +76,18 @@ export function GitHubMirrorSettings({
|
|||||||
|
|
||||||
// When metadata is disabled, all components should be disabled
|
// When metadata is disabled, all components should be disabled
|
||||||
const isMetadataEnabled = mirrorOptions.mirrorMetadata;
|
const isMetadataEnabled = mirrorOptions.mirrorMetadata;
|
||||||
|
|
||||||
|
// Calculate what content is included for starred repos
|
||||||
|
const starredRepoContent = {
|
||||||
|
code: true, // Always included
|
||||||
|
releases: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorReleases,
|
||||||
|
issues: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.issues,
|
||||||
|
pullRequests: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.pullRequests,
|
||||||
|
wiki: !advancedOptions.skipStarredIssues && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.wiki,
|
||||||
|
};
|
||||||
|
|
||||||
|
const starredContentCount = Object.entries(starredRepoContent).filter(([key, value]) => key !== 'code' && value).length;
|
||||||
|
const totalStarredOptions = 4; // releases, issues, PRs, wiki
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -112,7 +124,7 @@ export function GitHubMirrorSettings({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex items-start space-x-3">
|
<div className="flex items-start space-x-3">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
id="starred-repos"
|
id="starred-repos"
|
||||||
@@ -133,44 +145,136 @@ export function GitHubMirrorSettings({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Lightweight starred repos option - inline to prevent layout shift */}
|
{/* Starred repos content selection - inline to prevent layout shift */}
|
||||||
<div className={cn(
|
<div className={cn(
|
||||||
"flex items-start space-x-3 transition-all duration-200",
|
"flex items-center justify-end transition-opacity duration-200",
|
||||||
githubConfig.mirrorStarred
|
githubConfig.mirrorStarred ? "opacity-100" : "opacity-0 pointer-events-none"
|
||||||
? "opacity-100 lg:pl-6 lg:border-l lg:border-border"
|
|
||||||
: "opacity-0 pointer-events-none"
|
|
||||||
)}>
|
)}>
|
||||||
<Checkbox
|
<Popover>
|
||||||
id="skip-starred-metadata"
|
<PopoverTrigger asChild>
|
||||||
checked={advancedOptions.skipStarredIssues}
|
<Button
|
||||||
onCheckedChange={(checked) => handleAdvancedChange('skipStarredIssues', !!checked)}
|
variant="outline"
|
||||||
disabled={!githubConfig.mirrorStarred}
|
size="sm"
|
||||||
/>
|
disabled={!githubConfig.mirrorStarred}
|
||||||
<div className="space-y-0.5 flex-1">
|
className="h-8 text-xs font-normal min-w-[140px] justify-between"
|
||||||
<Label
|
>
|
||||||
htmlFor="skip-starred-metadata"
|
<span>
|
||||||
className="text-sm font-normal cursor-pointer flex items-center gap-2"
|
{advancedOptions.skipStarredIssues ? (
|
||||||
>
|
"Code only"
|
||||||
Lightweight mirroring
|
) : starredContentCount === 0 ? (
|
||||||
<TooltipProvider>
|
"Code only"
|
||||||
<Tooltip>
|
) : starredContentCount === totalStarredOptions ? (
|
||||||
<TooltipTrigger>
|
"Full content"
|
||||||
<Info className="h-3 w-3 text-muted-foreground" />
|
) : (
|
||||||
</TooltipTrigger>
|
`${starredContentCount + 1} of ${totalStarredOptions + 1} selected`
|
||||||
<TooltipContent side="right" className="max-w-xs">
|
)}
|
||||||
<p className="text-xs">
|
</span>
|
||||||
When enabled, starred repositories will only mirror code,
|
<ChevronDown className="ml-2 h-3 w-3 opacity-50" />
|
||||||
skipping issues, PRs, and other metadata to reduce storage
|
</Button>
|
||||||
and improve performance.
|
</PopoverTrigger>
|
||||||
</p>
|
<PopoverContent align="end" className="w-72">
|
||||||
</TooltipContent>
|
<div className="space-y-3">
|
||||||
</Tooltip>
|
<div className="flex items-center justify-between">
|
||||||
</TooltipProvider>
|
<div className="text-sm font-medium">Starred repos content</div>
|
||||||
</Label>
|
<TooltipProvider>
|
||||||
<p className="text-xs text-muted-foreground">
|
<Tooltip>
|
||||||
Only code, skip issues and metadata
|
<TooltipTrigger>
|
||||||
</p>
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
</div>
|
</TooltipTrigger>
|
||||||
|
<TooltipContent side="left" className="max-w-xs">
|
||||||
|
<p className="text-xs">
|
||||||
|
Choose what content to mirror from starred repositories.
|
||||||
|
Selecting "Lightweight mode" will only mirror code for better performance.
|
||||||
|
</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator className="my-2" />
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center space-x-3 py-1 px-1 rounded hover:bg-accent">
|
||||||
|
<Checkbox
|
||||||
|
id="starred-lightweight"
|
||||||
|
checked={advancedOptions.skipStarredIssues}
|
||||||
|
onCheckedChange={(checked) => handleAdvancedChange('skipStarredIssues', !!checked)}
|
||||||
|
/>
|
||||||
|
<Label
|
||||||
|
htmlFor="starred-lightweight"
|
||||||
|
className="text-sm font-normal cursor-pointer flex-1"
|
||||||
|
>
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<div className="font-medium">Lightweight mode</div>
|
||||||
|
<div className="text-xs text-muted-foreground">
|
||||||
|
Only mirror code, skip all metadata
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!advancedOptions.skipStarredIssues && (
|
||||||
|
<>
|
||||||
|
<Separator className="my-2" />
|
||||||
|
<div className="space-y-2">
|
||||||
|
<p className="text-xs font-medium text-muted-foreground">
|
||||||
|
Content included for starred repos:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="space-y-1.5">
|
||||||
|
<div className="flex items-center gap-2 text-xs pl-2">
|
||||||
|
<GitBranch className="h-3 w-3 text-muted-foreground" />
|
||||||
|
<span>Source code</span>
|
||||||
|
<Badge variant="secondary" className="ml-auto text-[10px] px-2 h-4">Always</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-center gap-2 text-xs pl-2",
|
||||||
|
starredRepoContent.releases ? "" : "opacity-50"
|
||||||
|
)}>
|
||||||
|
<Tag className="h-3 w-3 text-muted-foreground" />
|
||||||
|
<span>Releases & Tags</span>
|
||||||
|
{starredRepoContent.releases && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-center gap-2 text-xs pl-2",
|
||||||
|
starredRepoContent.issues ? "" : "opacity-50"
|
||||||
|
)}>
|
||||||
|
<MessageSquare className="h-3 w-3 text-muted-foreground" />
|
||||||
|
<span>Issues</span>
|
||||||
|
{starredRepoContent.issues && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-center gap-2 text-xs pl-2",
|
||||||
|
starredRepoContent.pullRequests ? "" : "opacity-50"
|
||||||
|
)}>
|
||||||
|
<GitPullRequest className="h-3 w-3 text-muted-foreground" />
|
||||||
|
<span>Pull Requests</span>
|
||||||
|
{starredRepoContent.pullRequests && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={cn(
|
||||||
|
"flex items-center gap-2 text-xs pl-2",
|
||||||
|
starredRepoContent.wiki ? "" : "opacity-50"
|
||||||
|
)}>
|
||||||
|
<BookOpen className="h-3 w-3 text-muted-foreground" />
|
||||||
|
<span>Wiki</span>
|
||||||
|
{starredRepoContent.wiki && <Badge variant="outline" className="ml-auto text-[10px] px-2 h-4">Included</Badge>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="text-[10px] text-muted-foreground mt-2">
|
||||||
|
To include more content, enable them in the Content & Data section below
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps>
|
|||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
|
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
|
||||||
<Star className="h-3.5 w-3.5" />
|
<Star className="h-3.5 w-3.5" />
|
||||||
Starred Repositories Organization
|
Starred Repos Organization
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { mirrorApi } from '@/lib/api';
|
|
||||||
import type { MirrorJob } from '@/lib/db/schema';
|
|
||||||
|
|
||||||
export function useMirror() {
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
const [currentJob, setCurrentJob] = useState<MirrorJob | null>(null);
|
|
||||||
const [jobs, setJobs] = useState<MirrorJob[]>([]);
|
|
||||||
|
|
||||||
const startMirror = async (configId: string, repositoryIds?: string[]) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const job = await mirrorApi.startMirror(configId, repositoryIds);
|
|
||||||
setCurrentJob(job);
|
|
||||||
return job;
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to start mirroring');
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMirrorJobs = async (configId: string) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const fetchedJobs = await mirrorApi.getMirrorJobs(configId);
|
|
||||||
setJobs(fetchedJobs);
|
|
||||||
return fetchedJobs;
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to fetch mirror jobs');
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getMirrorJob = async (jobId: string) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const job = await mirrorApi.getMirrorJob(jobId);
|
|
||||||
setCurrentJob(job);
|
|
||||||
return job;
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to fetch mirror job');
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const cancelMirrorJob = async (jobId: string) => {
|
|
||||||
setIsLoading(true);
|
|
||||||
setError(null);
|
|
||||||
try {
|
|
||||||
const result = await mirrorApi.cancelMirrorJob(jobId);
|
|
||||||
if (result.success && currentJob?.id === jobId) {
|
|
||||||
setCurrentJob({ ...currentJob, status: 'failed' });
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (err) {
|
|
||||||
setError(err instanceof Error ? err.message : 'Failed to cancel mirror job');
|
|
||||||
throw err;
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoading,
|
|
||||||
error,
|
|
||||||
currentJob,
|
|
||||||
jobs,
|
|
||||||
startMirror,
|
|
||||||
getMirrorJobs,
|
|
||||||
getMirrorJob,
|
|
||||||
cancelMirrorJob,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
100
src/lib/gitea.ts
100
src/lib/gitea.ts
@@ -160,15 +160,18 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
throw new Error("Gitea username is required.");
|
throw new Error("Gitea username is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the correct owner based on the strategy
|
||||||
|
const repoOwner = getGiteaRepoOwner({ config, repository });
|
||||||
|
|
||||||
const isExisting = await isRepoPresentInGitea({
|
const isExisting = await isRepoPresentInGitea({
|
||||||
config,
|
config,
|
||||||
owner: config.giteaConfig.username,
|
owner: repoOwner,
|
||||||
repoName: repository.name,
|
repoName: repository.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isExisting) {
|
if (isExisting) {
|
||||||
console.log(
|
console.log(
|
||||||
`Repository ${repository.name} already exists in Gitea. Updating database status.`
|
`Repository ${repository.name} already exists in Gitea under ${repoOwner}. Updating database status.`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update database to reflect that the repository is already mirrored
|
// Update database to reflect that the repository is already mirrored
|
||||||
@@ -179,7 +182,7 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
lastMirrored: new Date(),
|
lastMirrored: new Date(),
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
mirroredLocation: `${config.giteaConfig.username}/${repository.name}`,
|
mirroredLocation: `${repoOwner}/${repository.name}`,
|
||||||
})
|
})
|
||||||
.where(eq(repositories.id, repository.id!));
|
.where(eq(repositories.id, repository.id!));
|
||||||
|
|
||||||
@@ -189,7 +192,7 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
repositoryId: repository.id,
|
repositoryId: repository.id,
|
||||||
repositoryName: repository.name,
|
repositoryName: repository.name,
|
||||||
message: `Repository ${repository.name} already exists in Gitea`,
|
message: `Repository ${repository.name} already exists in Gitea`,
|
||||||
details: `Repository ${repository.name} was found to already exist in Gitea and database status was updated.`,
|
details: `Repository ${repository.name} was found to already exist in Gitea under ${repoOwner} and database status was updated.`,
|
||||||
status: "mirrored",
|
status: "mirrored",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -238,6 +241,15 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
|
|
||||||
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/migrate`;
|
const apiUrl = `${config.giteaConfig.url}/api/v1/repos/migrate`;
|
||||||
|
|
||||||
|
// Handle organization creation if needed for single-org or preserve strategies
|
||||||
|
if (repoOwner !== config.giteaConfig.username && !repository.isStarred) {
|
||||||
|
// Need to create the organization if it doesn't exist
|
||||||
|
await getOrCreateGiteaOrg({
|
||||||
|
orgName: repoOwner,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const response = await httpPost(
|
const response = await httpPost(
|
||||||
apiUrl,
|
apiUrl,
|
||||||
{
|
{
|
||||||
@@ -246,7 +258,7 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
mirror: true,
|
mirror: true,
|
||||||
wiki: config.githubConfig.mirrorWiki || false, // will mirror wiki if it exists
|
wiki: config.githubConfig.mirrorWiki || false, // will mirror wiki if it exists
|
||||||
private: repository.isPrivate,
|
private: repository.isPrivate,
|
||||||
repo_owner: config.giteaConfig.username,
|
repo_owner: repoOwner,
|
||||||
description: "",
|
description: "",
|
||||||
service: "git",
|
service: "git",
|
||||||
},
|
},
|
||||||
@@ -263,7 +275,11 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// clone issues
|
// clone issues
|
||||||
if (config.githubConfig.mirrorIssues) {
|
// Skip issues for starred repos if skipStarredIssues is enabled
|
||||||
|
const shouldMirrorIssues = config.githubConfig.mirrorIssues &&
|
||||||
|
!(repository.isStarred && config.githubConfig.skipStarredIssues);
|
||||||
|
|
||||||
|
if (shouldMirrorIssues) {
|
||||||
await mirrorGitRepoIssuesToGitea({
|
await mirrorGitRepoIssuesToGitea({
|
||||||
config,
|
config,
|
||||||
octokit,
|
octokit,
|
||||||
@@ -282,7 +298,7 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
lastMirrored: new Date(),
|
lastMirrored: new Date(),
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
mirroredLocation: `${config.giteaConfig.username}/${repository.name}`,
|
mirroredLocation: `${repoOwner}/${repository.name}`,
|
||||||
})
|
})
|
||||||
.where(eq(repositories.id, repository.id!));
|
.where(eq(repositories.id, repository.id!));
|
||||||
|
|
||||||
@@ -608,7 +624,11 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Clone issues
|
// Clone issues
|
||||||
if (config.githubConfig?.mirrorIssues) {
|
// Skip issues for starred repos if skipStarredIssues is enabled
|
||||||
|
const shouldMirrorIssues = config.githubConfig?.mirrorIssues &&
|
||||||
|
!(repository.isStarred && config.githubConfig?.skipStarredIssues);
|
||||||
|
|
||||||
|
if (shouldMirrorIssues) {
|
||||||
await mirrorGitRepoIssuesToGitea({
|
await mirrorGitRepoIssuesToGitea({
|
||||||
config,
|
config,
|
||||||
octokit,
|
octokit,
|
||||||
@@ -755,11 +775,37 @@ export async function mirrorGitHubOrgToGitea({
|
|||||||
status: repoStatusEnum.parse("mirroring"),
|
status: repoStatusEnum.parse("mirroring"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const giteaOrgId = await getOrCreateGiteaOrg({
|
// Get the mirror strategy - use preserveOrgStructure for backward compatibility
|
||||||
orgId: organization.id,
|
const mirrorStrategy = config.giteaConfig?.mirrorStrategy ||
|
||||||
orgName: organization.name,
|
(config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
config,
|
|
||||||
});
|
let giteaOrgId: number;
|
||||||
|
let targetOrgName: string;
|
||||||
|
|
||||||
|
// Determine the target organization based on strategy
|
||||||
|
if (mirrorStrategy === "single-org" && config.giteaConfig?.organization) {
|
||||||
|
// For single-org strategy, use the configured destination organization
|
||||||
|
targetOrgName = config.giteaConfig.organization;
|
||||||
|
giteaOrgId = await getOrCreateGiteaOrg({
|
||||||
|
orgId: organization.id,
|
||||||
|
orgName: targetOrgName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
console.log(`Using single organization strategy: all repos will go to ${targetOrgName}`);
|
||||||
|
} else if (mirrorStrategy === "preserve") {
|
||||||
|
// For preserve strategy, create/use an org with the same name as GitHub
|
||||||
|
targetOrgName = organization.name;
|
||||||
|
giteaOrgId = await getOrCreateGiteaOrg({
|
||||||
|
orgId: organization.id,
|
||||||
|
orgName: targetOrgName,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// For flat-user strategy, we shouldn't create organizations at all
|
||||||
|
// Skip organization creation and let individual repos be handled by getGiteaRepoOwner
|
||||||
|
console.log(`Using flat-user strategy: repos will be placed under user account`);
|
||||||
|
targetOrgName = config.giteaConfig?.username || "";
|
||||||
|
}
|
||||||
|
|
||||||
//query the db with the org name and get the repos
|
//query the db with the org name and get the repos
|
||||||
const orgRepos = await db
|
const orgRepos = await db
|
||||||
@@ -797,17 +843,27 @@ export async function mirrorGitHubOrgToGitea({
|
|||||||
|
|
||||||
// Log the start of mirroring
|
// Log the start of mirroring
|
||||||
console.log(
|
console.log(
|
||||||
`Starting mirror for repository: ${repo.name} in organization ${organization.name}`
|
`Starting mirror for repository: ${repo.name} from GitHub org ${organization.name}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Mirror the repository
|
// Mirror the repository based on strategy
|
||||||
await mirrorGitHubRepoToGiteaOrg({
|
if (mirrorStrategy === "flat-user") {
|
||||||
octokit,
|
// For flat-user strategy, mirror directly to user account
|
||||||
config,
|
await mirrorGithubRepoToGitea({
|
||||||
repository: repoData,
|
octokit,
|
||||||
giteaOrgId,
|
repository: repoData,
|
||||||
orgName: organization.name,
|
config,
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// For preserve and single-org strategies, use organization
|
||||||
|
await mirrorGitHubRepoToGiteaOrg({
|
||||||
|
octokit,
|
||||||
|
config,
|
||||||
|
repository: repoData,
|
||||||
|
giteaOrgId: giteaOrgId!,
|
||||||
|
orgName: targetOrgName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return repo;
|
return repo;
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export const GET: APIRoute = async () => {
|
|||||||
latestVersion: latestVersion,
|
latestVersion: latestVersion,
|
||||||
updateAvailable: latestVersion !== "unknown" &&
|
updateAvailable: latestVersion !== "unknown" &&
|
||||||
currentVersion !== "unknown" &&
|
currentVersion !== "unknown" &&
|
||||||
latestVersion !== currentVersion,
|
compareVersions(currentVersion, latestVersion) < 0,
|
||||||
database: dbStatus,
|
database: dbStatus,
|
||||||
recovery: recoveryStatus,
|
recovery: recoveryStatus,
|
||||||
system: systemInfo,
|
system: systemInfo,
|
||||||
@@ -174,6 +174,28 @@ function formatBytes(bytes: number): string {
|
|||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare semantic versions
|
||||||
|
* Returns:
|
||||||
|
* -1 if v1 < v2
|
||||||
|
* 0 if v1 = v2
|
||||||
|
* 1 if v1 > v2
|
||||||
|
*/
|
||||||
|
function compareVersions(v1: string, v2: string): number {
|
||||||
|
const parts1 = v1.split('.').map(Number);
|
||||||
|
const parts2 = v2.split('.').map(Number);
|
||||||
|
|
||||||
|
for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
|
||||||
|
const part1 = parts1[i] || 0;
|
||||||
|
const part2 = parts2[i] || 0;
|
||||||
|
|
||||||
|
if (part1 < part2) return -1;
|
||||||
|
if (part1 > part2) return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check for the latest version from GitHub releases
|
* Check for the latest version from GitHub releases
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user