This commit is contained in:
Arunavo Ray
2025-08-28 19:01:39 +05:30
parent 36f8d41d38
commit 10a14d88ef
6 changed files with 76 additions and 295 deletions

View File

@@ -57,7 +57,7 @@ export function GitHubMirrorSettings({
onGitHubConfigChange({ ...githubConfig, [field]: value });
};
const handleMirrorChange = (field: keyof MirrorOptions, value: boolean) => {
const handleMirrorChange = (field: keyof MirrorOptions, value: boolean | number) => {
onMirrorOptionsChange({ ...mirrorOptions, [field]: value });
};
@@ -313,16 +313,41 @@ export function GitHubMirrorSettings({
onCheckedChange={(checked) => handleMirrorChange('mirrorReleases', !!checked)}
/>
<div className="space-y-0.5 flex-1">
<Label
htmlFor="mirror-releases"
className="text-sm font-normal cursor-pointer flex items-center gap-2"
>
<Tag className="h-3.5 w-3.5" />
Releases & Tags
</Label>
<p className="text-xs text-muted-foreground">
Include GitHub releases, tags, and associated assets
</p>
<div className="flex items-center justify-between">
<div className="flex-1">
<Label
htmlFor="mirror-releases"
className="text-sm font-normal cursor-pointer flex items-center gap-2"
>
<Tag className="h-3.5 w-3.5" />
Releases & Tags
</Label>
<p className="text-xs text-muted-foreground">
Include GitHub releases, tags, and associated assets
</p>
</div>
{mirrorOptions.mirrorReleases && (
<div className="flex items-center gap-2 ml-4">
<label htmlFor="release-limit" className="text-xs text-muted-foreground">
Latest
</label>
<input
id="release-limit"
type="number"
min="1"
max="100"
value={mirrorOptions.releaseLimit || 10}
onChange={(e) => {
const value = parseInt(e.target.value) || 10;
const clampedValue = Math.min(100, Math.max(1, value));
handleMirrorChange('releaseLimit', clampedValue);
}}
className="w-16 px-2 py-1 text-xs border border-input rounded bg-background text-foreground"
/>
<span className="text-xs text-muted-foreground">releases</span>
</div>
)}
</div>
</div>
</div>
@@ -452,6 +477,31 @@ export function GitHubMirrorSettings({
>
<GitPullRequest className="h-3.5 w-3.5 text-muted-foreground" />
Pull Requests
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-3 w-3 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent side="right" className="max-w-sm">
<div className="space-y-2">
<p className="font-semibold">Pull Requests are mirrored as issues</p>
<p className="text-xs">
Due to Gitea API limitations, PRs cannot be created as actual pull requests.
Instead, they are mirrored as issues with:
</p>
<ul className="text-xs space-y-1 ml-3">
<li>• [PR #number] prefix in title</li>
<li>• Full PR description and metadata</li>
<li>• Commit history (up to 10 commits)</li>
<li>• File changes summary</li>
<li>• Diff preview (first 5 files)</li>
<li>• Review comments preserved</li>
<li>• Merge/close status tracking</li>
</ul>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</Label>
</div>

View File

@@ -1,282 +0,0 @@
import React from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Checkbox } from "../ui/checkbox";
import type { MirrorOptions } from "@/types/config";
import { RefreshCw, Info } from "lucide-react";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "../ui/tooltip";
interface MirrorOptionsFormProps {
config: MirrorOptions;
setConfig: React.Dispatch<React.SetStateAction<MirrorOptions>>;
onAutoSave?: (config: MirrorOptions) => Promise<void>;
isAutoSaving?: boolean;
}
export function MirrorOptionsForm({
config,
setConfig,
onAutoSave,
isAutoSaving = false,
}: MirrorOptionsFormProps) {
const handleChange = (name: string, checked: boolean) => {
let newConfig = { ...config };
if (name === "mirrorMetadata") {
newConfig.mirrorMetadata = checked;
// If disabling metadata, also disable all components
if (!checked) {
newConfig.metadataComponents = {
issues: false,
pullRequests: false, // Keep for backwards compatibility but not shown in UI
labels: false,
milestones: false,
wiki: false,
};
}
} else if (name.startsWith("metadataComponents.")) {
const componentName = name.split(".")[1] as keyof typeof config.metadataComponents;
newConfig.metadataComponents = {
...config.metadataComponents,
[componentName]: checked,
};
} else {
newConfig = {
...config,
[name]: checked,
};
}
setConfig(newConfig);
// Auto-save
if (onAutoSave) {
onAutoSave(newConfig);
}
};
return (
<Card className="self-start">
<CardHeader>
<CardTitle className="text-lg font-semibold flex items-center justify-between">
Mirror Options
{isAutoSaving && (
<div className="flex items-center text-sm text-muted-foreground">
<RefreshCw className="h-3 w-3 animate-spin mr-1" />
<span className="text-xs">Auto-saving...</span>
</div>
)}
</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Repository Content */}
<div className="space-y-4">
<h4 className="text-sm font-medium text-foreground">Repository Content</h4>
<div className="flex items-center">
<Checkbox
id="mirror-releases"
checked={config.mirrorReleases}
onCheckedChange={(checked) =>
handleChange("mirrorReleases", Boolean(checked))
}
/>
<label
htmlFor="mirror-releases"
className="ml-2 text-sm select-none flex items-center"
>
Mirror releases
<Tooltip>
<TooltipTrigger asChild>
<span className="ml-1 cursor-pointer text-muted-foreground">
<Info size={14} />
</span>
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs text-xs">
Include GitHub releases and tags in the mirror
</TooltipContent>
</Tooltip>
</label>
</div>
<div className="flex items-center">
<Checkbox
id="mirror-lfs"
checked={config.mirrorLFS}
onCheckedChange={(checked) =>
handleChange("mirrorLFS", Boolean(checked))
}
/>
<label
htmlFor="mirror-lfs"
className="ml-2 text-sm select-none flex items-center"
>
Mirror LFS (Large File Storage)
<Tooltip>
<TooltipTrigger asChild>
<span className="ml-1 cursor-pointer text-muted-foreground">
<Info size={14} />
</span>
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs text-xs">
Mirror Git LFS objects. Requires LFS to be enabled on your Gitea server and Git v2.1.2+
</TooltipContent>
</Tooltip>
</label>
</div>
<div className="flex items-center">
<Checkbox
id="mirror-metadata"
checked={config.mirrorMetadata}
onCheckedChange={(checked) =>
handleChange("mirrorMetadata", Boolean(checked))
}
/>
<label
htmlFor="mirror-metadata"
className="ml-2 text-sm select-none flex items-center"
>
Mirror metadata
<Tooltip>
<TooltipTrigger asChild>
<span className="ml-1 cursor-pointer text-muted-foreground">
<Info size={14} />
</span>
</TooltipTrigger>
<TooltipContent side="right" className="max-w-xs text-xs">
Include issues, pull requests, labels, milestones, and wiki
</TooltipContent>
</Tooltip>
</label>
</div>
{/* Metadata Components */}
{config.mirrorMetadata && (
<div className="ml-6 space-y-3 border-l-2 border-muted pl-4">
<h5 className="text-xs font-medium text-muted-foreground uppercase tracking-wide">
Metadata Components
</h5>
<div className="grid grid-cols-1 gap-2">
<div className="flex items-center">
<Checkbox
id="metadata-issues"
checked={config.metadataComponents.issues}
onCheckedChange={(checked) =>
handleChange("metadataComponents.issues", Boolean(checked))
}
disabled={!config.mirrorMetadata}
/>
<label
htmlFor="metadata-issues"
className="ml-2 text-sm select-none"
>
Issues
</label>
</div>
<div className="flex items-center">
<Checkbox
id="metadata-pullRequests"
checked={config.metadataComponents.pullRequests}
onCheckedChange={(checked) =>
handleChange("metadataComponents.pullRequests", Boolean(checked))
}
disabled={!config.mirrorMetadata}
/>
<label
htmlFor="metadata-pullRequests"
className="ml-2 text-sm select-none"
>
Pull Requests (as issues)
</label>
<TooltipProvider>
<Tooltip>
<TooltipTrigger>
<Info className="h-3 w-3 ml-1 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent side="right" className="max-w-sm">
<div className="space-y-2">
<p className="font-semibold">Pull Requests are mirrored as issues</p>
<p className="text-xs">
Due to Gitea API limitations, PRs cannot be created as actual pull requests.
Instead, they are mirrored as issues with:
</p>
<ul className="text-xs space-y-1 ml-3">
<li> [PR #number] prefix in title</li>
<li> Full PR description and metadata</li>
<li> Commit history (up to 10 commits)</li>
<li> File changes summary</li>
<li> Diff preview (first 5 files)</li>
<li> Review comments preserved</li>
<li> Merge/close status tracking</li>
</ul>
</div>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
<div className="flex items-center">
<Checkbox
id="metadata-labels"
checked={config.metadataComponents.labels}
onCheckedChange={(checked) =>
handleChange("metadataComponents.labels", Boolean(checked))
}
disabled={!config.mirrorMetadata}
/>
<label
htmlFor="metadata-labels"
className="ml-2 text-sm select-none"
>
Labels
</label>
</div>
<div className="flex items-center">
<Checkbox
id="metadata-milestones"
checked={config.metadataComponents.milestones}
onCheckedChange={(checked) =>
handleChange("metadataComponents.milestones", Boolean(checked))
}
disabled={!config.mirrorMetadata}
/>
<label
htmlFor="metadata-milestones"
className="ml-2 text-sm select-none"
>
Milestones
</label>
</div>
<div className="flex items-center">
<Checkbox
id="metadata-wiki"
checked={config.metadataComponents.wiki}
onCheckedChange={(checked) =>
handleChange("metadataComponents.wiki", Boolean(checked))
}
disabled={!config.mirrorMetadata}
/>
<label
htmlFor="metadata-wiki"
className="ml-2 text-sm select-none"
>
Wiki
</label>
</div>
</div>
</div>
)}
</div>
</CardContent>
</Card>
);
}

View File

@@ -53,6 +53,7 @@ export const giteaConfigSchema = z.object({
.default("reference"),
// Mirror options
mirrorReleases: z.boolean().default(false),
releaseLimit: z.number().default(10),
mirrorMetadata: z.boolean().default(false),
mirrorIssues: z.boolean().default(false),
mirrorPullRequests: z.boolean().default(false),

View File

@@ -1399,12 +1399,16 @@ export async function mirrorGitHubReleasesToGitea({
throw new Error(`Repository ${repository.name} does not exist in Gitea at ${repoOwner}. Please ensure the repository is mirrored first.`);
}
// Get release limit from config (default to 10)
const releaseLimit = config.giteaConfig?.releaseLimit || 10;
const releases = await octokit.rest.repos.listReleases({
owner: repository.owner,
repo: repository.name,
per_page: releaseLimit, // Only fetch the latest N releases
});
console.log(`[Releases] Found ${releases.data.length} releases to mirror for ${repository.fullName}`);
console.log(`[Releases] Found ${releases.data.length} releases (limited to latest ${releaseLimit}) to mirror for ${repository.fullName}`);
if (releases.data.length === 0) {
console.log(`[Releases] No releases to mirror for ${repository.fullName}`);
@@ -1414,7 +1418,12 @@ export async function mirrorGitHubReleasesToGitea({
let mirroredCount = 0;
let skippedCount = 0;
for (const release of releases.data) {
// Sort releases by created_at to ensure we get the most recent ones
const sortedReleases = releases.data.sort((a, b) =>
new Date(b.created_at).getTime() - new Date(a.created_at).getTime()
).slice(0, releaseLimit);
for (const release of sortedReleases) {
try {
// Check if release already exists
const existingReleasesResponse = await httpGet(

View File

@@ -89,6 +89,7 @@ export function mapUiToDbConfig(
// Mirror options from UI
mirrorReleases: mirrorOptions.mirrorReleases,
releaseLimit: mirrorOptions.releaseLimit || 10,
mirrorMetadata: mirrorOptions.mirrorMetadata,
mirrorIssues: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.issues,
mirrorPullRequests: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.pullRequests,
@@ -135,6 +136,7 @@ export function mapDbToUiConfig(dbConfig: any): {
// Map mirror options from various database fields
const mirrorOptions: MirrorOptions = {
mirrorReleases: dbConfig.giteaConfig?.mirrorReleases || false,
releaseLimit: dbConfig.giteaConfig?.releaseLimit || 10,
mirrorLFS: dbConfig.giteaConfig?.lfs || false,
mirrorMetadata: dbConfig.giteaConfig?.mirrorMetadata || false,
metadataComponents: {

View File

@@ -38,6 +38,7 @@ export interface GitHubConfig {
export interface MirrorOptions {
mirrorReleases: boolean;
releaseLimit?: number; // Limit number of releases to mirror (default: 10)
mirrorLFS: boolean; // Mirror Git LFS objects
mirrorMetadata: boolean;
metadataComponents: {