mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-03-14 06:23:01 +03:00
Merge pull request #174 from tasarren/feat/starred-by-org
Allow starred repos to be mirrored preserving structure
This commit is contained in:
@@ -47,6 +47,7 @@ DOCKER_TAG=latest
|
|||||||
# SKIP_FORKS=false
|
# SKIP_FORKS=false
|
||||||
# MIRROR_STARRED=false
|
# MIRROR_STARRED=false
|
||||||
# STARRED_REPOS_ORG=starred # Organization name for starred repos
|
# STARRED_REPOS_ORG=starred # Organization name for starred repos
|
||||||
|
# STARRED_REPOS_MODE=dedicated-org # dedicated-org | preserve-owner
|
||||||
|
|
||||||
# Organization Settings
|
# Organization Settings
|
||||||
# MIRROR_ORGANIZATIONS=false
|
# MIRROR_ORGANIZATIONS=false
|
||||||
@@ -183,4 +184,4 @@ DOCKER_TAG=latest
|
|||||||
# ===========================================
|
# ===========================================
|
||||||
|
|
||||||
# TLS/SSL Configuration
|
# TLS/SSL Configuration
|
||||||
# GITEA_SKIP_TLS_VERIFY=false # WARNING: Only use for testing
|
# GITEA_SKIP_TLS_VERIFY=false # WARNING: Only use for testing
|
||||||
|
|||||||
6
.github/workflows/README.md
vendored
6
.github/workflows/README.md
vendored
@@ -30,15 +30,17 @@ This workflow runs on all branches and pull requests. It:
|
|||||||
|
|
||||||
### Docker Build and Push (`docker-build.yml`)
|
### Docker Build and Push (`docker-build.yml`)
|
||||||
|
|
||||||
This workflow builds and pushes Docker images to GitHub Container Registry (ghcr.io), but only when changes are merged to the main branch.
|
This workflow builds Docker images on pushes and pull requests, and pushes to GitHub Container Registry (ghcr.io) when permissions allow (main/tags and same-repo PRs).
|
||||||
|
|
||||||
**When it runs:**
|
**When it runs:**
|
||||||
- On push to the main branch
|
- On push to the main branch
|
||||||
- On tag creation (v*)
|
- On tag creation (v*)
|
||||||
|
- On pull requests (build + scan; push only for same-repo PRs)
|
||||||
|
|
||||||
**Key features:**
|
**Key features:**
|
||||||
- Builds multi-architecture images (amd64 and arm64)
|
- Builds multi-architecture images (amd64 and arm64)
|
||||||
- Pushes images only on main branch, not for PRs
|
- Pushes images for main/tags and same-repo PRs
|
||||||
|
- Skips registry push for fork PRs (avoids package write permission failures)
|
||||||
- Uses build caching to speed up builds
|
- Uses build caching to speed up builds
|
||||||
- Creates multiple tags for each image (latest, semver, sha)
|
- Creates multiple tags for each image (latest, semver, sha)
|
||||||
|
|
||||||
|
|||||||
6
.github/workflows/docker-build.yml
vendored
6
.github/workflows/docker-build.yml
vendored
@@ -55,6 +55,7 @@ jobs:
|
|||||||
driver-opts: network=host
|
driver-opts: network=host
|
||||||
|
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: ${{ env.REGISTRY }}
|
registry: ${{ env.REGISTRY }}
|
||||||
@@ -105,7 +106,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
platforms: linux/amd64,linux/arm64
|
platforms: linux/amd64,linux/arm64
|
||||||
push: true
|
push: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
cache-from: type=gha
|
cache-from: type=gha
|
||||||
@@ -128,13 +129,14 @@ jobs:
|
|||||||
|
|
||||||
# Wait for image to be available in registry
|
# Wait for image to be available in registry
|
||||||
- name: Wait for image availability
|
- name: Wait for image availability
|
||||||
|
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
|
||||||
run: |
|
run: |
|
||||||
echo "Waiting for image to be available in registry..."
|
echo "Waiting for image to be available in registry..."
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
||||||
# Add comment to PR with image details
|
# Add comment to PR with image details
|
||||||
- name: Comment PR with image tag
|
- name: Comment PR with image tag
|
||||||
if: github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ bun run dev
|
|||||||
3. **Customization**
|
3. **Customization**
|
||||||
- Click edit buttons on organization cards to set custom destinations
|
- Click edit buttons on organization cards to set custom destinations
|
||||||
- Override individual repository destinations in the table view
|
- Override individual repository destinations in the table view
|
||||||
- Starred repositories automatically go to a dedicated organization
|
- Starred repositories can go to a dedicated org or preserve source owner/org paths
|
||||||
|
|
||||||
## Advanced Features
|
## Advanced Features
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ Settings for connecting to and configuring GitHub repository sources.
|
|||||||
| `SKIP_FORKS` | Skip forked repositories | `false` | `true`, `false` |
|
| `SKIP_FORKS` | Skip forked repositories | `false` | `true`, `false` |
|
||||||
| `MIRROR_STARRED` | Mirror starred repositories | `false` | `true`, `false` |
|
| `MIRROR_STARRED` | Mirror starred repositories | `false` | `true`, `false` |
|
||||||
| `STARRED_REPOS_ORG` | Organization name for starred repos | `starred` | Any string |
|
| `STARRED_REPOS_ORG` | Organization name for starred repos | `starred` | Any string |
|
||||||
|
| `STARRED_REPOS_MODE` | How starred repos are mirrored | `dedicated-org` | `dedicated-org`, `preserve-owner` |
|
||||||
|
|
||||||
### Organization Settings
|
### Organization Settings
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ export function ConfigTabs() {
|
|||||||
organization: 'github-mirrors',
|
organization: 'github-mirrors',
|
||||||
visibility: 'public',
|
visibility: 'public',
|
||||||
starredReposOrg: 'starred',
|
starredReposOrg: 'starred',
|
||||||
|
starredReposMode: 'dedicated-org',
|
||||||
preserveOrgStructure: false,
|
preserveOrgStructure: false,
|
||||||
},
|
},
|
||||||
scheduleConfig: {
|
scheduleConfig: {
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
|
|||||||
strategy={mirrorStrategy}
|
strategy={mirrorStrategy}
|
||||||
destinationOrg={config.organization}
|
destinationOrg={config.organization}
|
||||||
starredReposOrg={config.starredReposOrg}
|
starredReposOrg={config.starredReposOrg}
|
||||||
|
starredReposMode={config.starredReposMode}
|
||||||
onStrategyChange={setMirrorStrategy}
|
onStrategyChange={setMirrorStrategy}
|
||||||
githubUsername={githubUsername}
|
githubUsername={githubUsername}
|
||||||
giteaUsername={config.username}
|
giteaUsername={config.username}
|
||||||
@@ -235,6 +236,7 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
|
|||||||
strategy={mirrorStrategy}
|
strategy={mirrorStrategy}
|
||||||
destinationOrg={config.organization}
|
destinationOrg={config.organization}
|
||||||
starredReposOrg={config.starredReposOrg}
|
starredReposOrg={config.starredReposOrg}
|
||||||
|
starredReposMode={config.starredReposMode}
|
||||||
personalReposOrg={config.personalReposOrg}
|
personalReposOrg={config.personalReposOrg}
|
||||||
visibility={config.visibility}
|
visibility={config.visibility}
|
||||||
onDestinationOrgChange={(org) => {
|
onDestinationOrgChange={(org) => {
|
||||||
@@ -247,6 +249,11 @@ export function GiteaConfigForm({ config, setConfig, onAutoSave, isAutoSaving, g
|
|||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
if (onAutoSave) onAutoSave(newConfig);
|
if (onAutoSave) onAutoSave(newConfig);
|
||||||
}}
|
}}
|
||||||
|
onStarredReposModeChange={(mode) => {
|
||||||
|
const newConfig = { ...config, starredReposMode: mode };
|
||||||
|
setConfig(newConfig);
|
||||||
|
if (onAutoSave) onAutoSave(newConfig);
|
||||||
|
}}
|
||||||
onPersonalReposOrgChange={(org) => {
|
onPersonalReposOrgChange={(org) => {
|
||||||
const newConfig = { ...config, personalReposOrg: org };
|
const newConfig = { ...config, personalReposOrg: org };
|
||||||
setConfig(newConfig);
|
setConfig(newConfig);
|
||||||
|
|||||||
@@ -9,16 +9,18 @@ import {
|
|||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import type { MirrorStrategy, GiteaOrgVisibility } from "@/types/config";
|
import type { MirrorStrategy, GiteaOrgVisibility, StarredReposMode } from "@/types/config";
|
||||||
|
|
||||||
interface OrganizationConfigurationProps {
|
interface OrganizationConfigurationProps {
|
||||||
strategy: MirrorStrategy;
|
strategy: MirrorStrategy;
|
||||||
destinationOrg?: string;
|
destinationOrg?: string;
|
||||||
starredReposOrg?: string;
|
starredReposOrg?: string;
|
||||||
|
starredReposMode?: StarredReposMode;
|
||||||
personalReposOrg?: string;
|
personalReposOrg?: string;
|
||||||
visibility: GiteaOrgVisibility;
|
visibility: GiteaOrgVisibility;
|
||||||
onDestinationOrgChange: (org: string) => void;
|
onDestinationOrgChange: (org: string) => void;
|
||||||
onStarredReposOrgChange: (org: string) => void;
|
onStarredReposOrgChange: (org: string) => void;
|
||||||
|
onStarredReposModeChange: (mode: StarredReposMode) => void;
|
||||||
onPersonalReposOrgChange: (org: string) => void;
|
onPersonalReposOrgChange: (org: string) => void;
|
||||||
onVisibilityChange: (visibility: GiteaOrgVisibility) => void;
|
onVisibilityChange: (visibility: GiteaOrgVisibility) => void;
|
||||||
}
|
}
|
||||||
@@ -33,13 +35,19 @@ export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps>
|
|||||||
strategy,
|
strategy,
|
||||||
destinationOrg,
|
destinationOrg,
|
||||||
starredReposOrg,
|
starredReposOrg,
|
||||||
|
starredReposMode,
|
||||||
personalReposOrg,
|
personalReposOrg,
|
||||||
visibility,
|
visibility,
|
||||||
onDestinationOrgChange,
|
onDestinationOrgChange,
|
||||||
onStarredReposOrgChange,
|
onStarredReposOrgChange,
|
||||||
|
onStarredReposModeChange,
|
||||||
onPersonalReposOrgChange,
|
onPersonalReposOrgChange,
|
||||||
onVisibilityChange,
|
onVisibilityChange,
|
||||||
}) => {
|
}) => {
|
||||||
|
const activeStarredMode = starredReposMode || "dedicated-org";
|
||||||
|
const showStarredReposOrgInput = activeStarredMode === "dedicated-org";
|
||||||
|
const showDestinationOrgInput = strategy === "single-org" || strategy === "mixed";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
@@ -49,38 +57,94 @@ export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps>
|
|||||||
</h4>
|
</h4>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* First row - Organization inputs with consistent layout */}
|
<div className="space-y-2">
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
<Label className="text-sm font-normal flex items-center gap-2">
|
||||||
{/* Left column - always shows starred repos org */}
|
Starred Repository Destination
|
||||||
<div className="space-y-1">
|
<TooltipProvider>
|
||||||
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
|
<Tooltip>
|
||||||
<Star className="h-3.5 w-3.5" />
|
<TooltipTrigger>
|
||||||
Starred Repos Organization
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
<TooltipProvider>
|
</TooltipTrigger>
|
||||||
<Tooltip>
|
<TooltipContent>
|
||||||
<TooltipTrigger>
|
<p>Choose whether starred repos use one org or keep their source Owner/Org paths</p>
|
||||||
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
</TooltipContent>
|
||||||
</TooltipTrigger>
|
</Tooltip>
|
||||||
<TooltipContent>
|
</TooltipProvider>
|
||||||
<p>Starred repositories will be organized separately in this organization</p>
|
</Label>
|
||||||
</TooltipContent>
|
<div className="rounded-lg border bg-muted/20 p-2">
|
||||||
</Tooltip>
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-2">
|
||||||
</TooltipProvider>
|
<button
|
||||||
</Label>
|
type="button"
|
||||||
<Input
|
onClick={() => onStarredReposModeChange("dedicated-org")}
|
||||||
id="starredReposOrg"
|
aria-pressed={activeStarredMode === "dedicated-org"}
|
||||||
value={starredReposOrg || ""}
|
className={cn(
|
||||||
onChange={(e) => onStarredReposOrgChange(e.target.value)}
|
"text-left px-3 py-2 rounded-md border text-sm transition-all",
|
||||||
placeholder="starred"
|
activeStarredMode === "dedicated-org"
|
||||||
className=""
|
? "bg-accent border-accent-foreground/30 ring-1 ring-accent-foreground/20 font-medium shadow-sm"
|
||||||
/>
|
: "bg-background hover:bg-accent/50 border-input"
|
||||||
<p className="text-xs text-muted-foreground mt-1">
|
)}
|
||||||
Keep starred repos organized separately
|
>
|
||||||
|
Dedicated Organization
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => onStarredReposModeChange("preserve-owner")}
|
||||||
|
aria-pressed={activeStarredMode === "preserve-owner"}
|
||||||
|
className={cn(
|
||||||
|
"text-left px-3 py-2 rounded-md border text-sm transition-all",
|
||||||
|
activeStarredMode === "preserve-owner"
|
||||||
|
? "bg-accent border-accent-foreground/30 ring-1 ring-accent-foreground/20 font-medium shadow-sm"
|
||||||
|
: "bg-background hover:bg-accent/50 border-input"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Preserve Source Owner/Org
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<p className="mt-2 px-1 text-xs text-muted-foreground">
|
||||||
|
{
|
||||||
|
activeStarredMode === "dedicated-org"
|
||||||
|
? "All starred repositories go to a single destination organization."
|
||||||
|
: "Starred repositories keep their original GitHub Owner/Org destination."
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Right column - shows destination org for single-org/mixed, personal repos org for preserve, empty div for others */}
|
{/* First row - Organization inputs */}
|
||||||
{strategy === "single-org" || strategy === "mixed" ? (
|
{(showStarredReposOrgInput || showDestinationOrgInput) && (
|
||||||
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
{showStarredReposOrgInput ? (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<Label htmlFor="starredReposOrg" className="text-sm font-normal flex items-center gap-2">
|
||||||
|
<Star className="h-3.5 w-3.5" />
|
||||||
|
Starred Repos Organization
|
||||||
|
<TooltipProvider>
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger>
|
||||||
|
<Info className="h-3.5 w-3.5 text-muted-foreground" />
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>
|
||||||
|
<p>Starred repositories will be organized separately in this organization</p>
|
||||||
|
</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
</TooltipProvider>
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
id="starredReposOrg"
|
||||||
|
value={starredReposOrg || ""}
|
||||||
|
onChange={(e) => onStarredReposOrgChange(e.target.value)}
|
||||||
|
placeholder="starred"
|
||||||
|
className=""
|
||||||
|
/>
|
||||||
|
<p className="text-xs text-muted-foreground mt-1">
|
||||||
|
Keep starred repos organized separately
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="hidden md:block" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{showDestinationOrgInput ? (
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<Label htmlFor="destinationOrg" className="text-sm font-normal flex items-center gap-2">
|
<Label htmlFor="destinationOrg" className="text-sm font-normal flex items-center gap-2">
|
||||||
{strategy === "mixed" ? "Personal Repos Organization" : "Destination Organization"}
|
{strategy === "mixed" ? "Personal Repos Organization" : "Destination Organization"}
|
||||||
@@ -114,10 +178,11 @@ export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps>
|
|||||||
}
|
}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="hidden md:block" />
|
<div className="hidden md:block" />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Second row - Organization Visibility (always shown) */}
|
{/* Second row - Organization Visibility (always shown) */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -172,4 +237,3 @@ export const OrganizationConfiguration: React.FC<OrganizationConfigurationProps>
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
HoverCardTrigger,
|
HoverCardTrigger,
|
||||||
} from "@/components/ui/hover-card";
|
} from "@/components/ui/hover-card";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
import type { StarredReposMode } from "@/types/config";
|
||||||
|
|
||||||
export type MirrorStrategy = "preserve" | "single-org" | "flat-user" | "mixed";
|
export type MirrorStrategy = "preserve" | "single-org" | "flat-user" | "mixed";
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ interface OrganizationStrategyProps {
|
|||||||
strategy: MirrorStrategy;
|
strategy: MirrorStrategy;
|
||||||
destinationOrg?: string;
|
destinationOrg?: string;
|
||||||
starredReposOrg?: string;
|
starredReposOrg?: string;
|
||||||
|
starredReposMode?: StarredReposMode;
|
||||||
onStrategyChange: (strategy: MirrorStrategy) => void;
|
onStrategyChange: (strategy: MirrorStrategy) => void;
|
||||||
githubUsername?: string;
|
githubUsername?: string;
|
||||||
giteaUsername?: string;
|
giteaUsername?: string;
|
||||||
@@ -76,13 +78,18 @@ const MappingPreview: React.FC<{
|
|||||||
config: typeof strategyConfig.preserve;
|
config: typeof strategyConfig.preserve;
|
||||||
destinationOrg?: string;
|
destinationOrg?: string;
|
||||||
starredReposOrg?: string;
|
starredReposOrg?: string;
|
||||||
|
starredReposMode?: StarredReposMode;
|
||||||
githubUsername?: string;
|
githubUsername?: string;
|
||||||
giteaUsername?: string;
|
giteaUsername?: string;
|
||||||
}> = ({ strategy, config, destinationOrg, starredReposOrg, githubUsername, giteaUsername }) => {
|
}> = ({ strategy, config, destinationOrg, starredReposOrg, starredReposMode, githubUsername, giteaUsername }) => {
|
||||||
const displayGithubUsername = githubUsername || "<username>";
|
const displayGithubUsername = githubUsername || "<username>";
|
||||||
const displayGiteaUsername = giteaUsername || "<username>";
|
const displayGiteaUsername = giteaUsername || "<username>";
|
||||||
const isGithubPlaceholder = !githubUsername;
|
const isGithubPlaceholder = !githubUsername;
|
||||||
const isGiteaPlaceholder = !giteaUsername;
|
const isGiteaPlaceholder = !giteaUsername;
|
||||||
|
const starredDestination =
|
||||||
|
(starredReposMode || "dedicated-org") === "preserve-owner"
|
||||||
|
? "awesome/starred-repo"
|
||||||
|
: `${starredReposOrg || "starred"}/starred-repo`;
|
||||||
|
|
||||||
if (strategy === "preserve") {
|
if (strategy === "preserve") {
|
||||||
return (
|
return (
|
||||||
@@ -122,7 +129,7 @@ const MappingPreview: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
<span>{starredReposOrg || "starred"}/starred-repo</span>
|
<span>{starredDestination}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -168,7 +175,7 @@ const MappingPreview: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
<span>{starredReposOrg || "starred"}/starred-repo</span>
|
<span>{starredDestination}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -214,7 +221,7 @@ const MappingPreview: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
<span>{starredReposOrg || "starred"}/starred-repo</span>
|
<span>{starredDestination}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +267,7 @@ const MappingPreview: React.FC<{
|
|||||||
</div>
|
</div>
|
||||||
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
<div className={cn("flex items-center gap-2 p-1.5 rounded text-xs", config.repoColors.bg)}>
|
||||||
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
<Building2 className={cn("h-3 w-3", config.repoColors.icon)} />
|
||||||
<span>{starredReposOrg || "starred"}/starred-repo</span>
|
<span>{starredDestination}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -275,6 +282,7 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
strategy,
|
strategy,
|
||||||
destinationOrg,
|
destinationOrg,
|
||||||
starredReposOrg,
|
starredReposOrg,
|
||||||
|
starredReposMode,
|
||||||
onStrategyChange,
|
onStrategyChange,
|
||||||
githubUsername,
|
githubUsername,
|
||||||
giteaUsername,
|
giteaUsername,
|
||||||
@@ -339,7 +347,7 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
<span className="text-xs font-medium">Starred Repositories</span>
|
<span className="text-xs font-medium">Starred Repositories</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-muted-foreground pl-5">
|
<p className="text-xs text-muted-foreground pl-5">
|
||||||
Always go to the configured starred repos organization and cannot be overridden.
|
Follow your starred-repo mode and cannot be overridden per repository.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -415,6 +423,7 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
config={config}
|
config={config}
|
||||||
destinationOrg={destinationOrg}
|
destinationOrg={destinationOrg}
|
||||||
starredReposOrg={starredReposOrg}
|
starredReposOrg={starredReposOrg}
|
||||||
|
starredReposMode={starredReposMode}
|
||||||
githubUsername={githubUsername}
|
githubUsername={githubUsername}
|
||||||
giteaUsername={giteaUsername}
|
giteaUsername={giteaUsername}
|
||||||
/>
|
/>
|
||||||
@@ -434,4 +443,4 @@ export const OrganizationStrategy: React.FC<OrganizationStrategyProps> = ({
|
|||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,9 +28,16 @@ export function InlineDestinationEditor({
|
|||||||
|
|
||||||
// Determine the default destination based on repository properties and config
|
// Determine the default destination based on repository properties and config
|
||||||
const getDefaultDestination = () => {
|
const getDefaultDestination = () => {
|
||||||
// Starred repos always go to the configured starredReposOrg
|
// Starred repos can use either dedicated org or preserved source owner
|
||||||
if (repository.isStarred && giteaConfig?.starredReposOrg) {
|
if (repository.isStarred) {
|
||||||
return giteaConfig.starredReposOrg;
|
const starredReposMode = giteaConfig?.starredReposMode || "dedicated-org";
|
||||||
|
if (starredReposMode === "preserve-owner") {
|
||||||
|
return repository.organization || repository.owner;
|
||||||
|
}
|
||||||
|
if (giteaConfig?.starredReposOrg) {
|
||||||
|
return giteaConfig.starredReposOrg;
|
||||||
|
}
|
||||||
|
return "starred";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check mirror strategy
|
// Check mirror strategy
|
||||||
@@ -60,7 +67,7 @@ export function InlineDestinationEditor({
|
|||||||
const defaultDestination = getDefaultDestination();
|
const defaultDestination = getDefaultDestination();
|
||||||
const currentDestination = repository.destinationOrg || defaultDestination;
|
const currentDestination = repository.destinationOrg || defaultDestination;
|
||||||
const hasOverride = repository.destinationOrg && repository.destinationOrg !== defaultDestination;
|
const hasOverride = repository.destinationOrg && repository.destinationOrg !== defaultDestination;
|
||||||
const isStarredRepo = repository.isStarred && giteaConfig?.starredReposOrg;
|
const isStarredRepo = repository.isStarred;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isEditing && inputRef.current) {
|
if (isEditing && inputRef.current) {
|
||||||
@@ -184,4 +191,4 @@ export function InlineDestinationEditor({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const githubConfigSchema = z.object({
|
|||||||
includePublic: z.boolean().default(true),
|
includePublic: z.boolean().default(true),
|
||||||
includeOrganizations: z.array(z.string()).default([]),
|
includeOrganizations: z.array(z.string()).default([]),
|
||||||
starredReposOrg: z.string().optional(),
|
starredReposOrg: z.string().optional(),
|
||||||
|
starredReposMode: z.enum(["dedicated-org", "preserve-owner"]).default("dedicated-org"),
|
||||||
mirrorStrategy: z.enum(["preserve", "single-org", "flat-user", "mixed"]).default("preserve"),
|
mirrorStrategy: z.enum(["preserve", "single-org", "flat-user", "mixed"]).default("preserve"),
|
||||||
defaultOrg: z.string().optional(),
|
defaultOrg: z.string().optional(),
|
||||||
starredCodeOnly: z.boolean().default(false),
|
starredCodeOnly: z.boolean().default(false),
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ interface EnvConfig {
|
|||||||
onlyMirrorOrgs?: boolean;
|
onlyMirrorOrgs?: boolean;
|
||||||
starredCodeOnly?: boolean;
|
starredCodeOnly?: boolean;
|
||||||
starredReposOrg?: string;
|
starredReposOrg?: string;
|
||||||
|
starredReposMode?: 'dedicated-org' | 'preserve-owner';
|
||||||
mirrorStrategy?: 'preserve' | 'single-org' | 'flat-user' | 'mixed';
|
mirrorStrategy?: 'preserve' | 'single-org' | 'flat-user' | 'mixed';
|
||||||
};
|
};
|
||||||
gitea: {
|
gitea: {
|
||||||
@@ -112,6 +113,7 @@ function parseEnvConfig(): EnvConfig {
|
|||||||
onlyMirrorOrgs: process.env.ONLY_MIRROR_ORGS === 'true',
|
onlyMirrorOrgs: process.env.ONLY_MIRROR_ORGS === 'true',
|
||||||
starredCodeOnly: process.env.SKIP_STARRED_ISSUES === 'true',
|
starredCodeOnly: process.env.SKIP_STARRED_ISSUES === 'true',
|
||||||
starredReposOrg: process.env.STARRED_REPOS_ORG,
|
starredReposOrg: process.env.STARRED_REPOS_ORG,
|
||||||
|
starredReposMode: process.env.STARRED_REPOS_MODE as 'dedicated-org' | 'preserve-owner',
|
||||||
mirrorStrategy: process.env.MIRROR_STRATEGY as 'preserve' | 'single-org' | 'flat-user' | 'mixed',
|
mirrorStrategy: process.env.MIRROR_STRATEGY as 'preserve' | 'single-org' | 'flat-user' | 'mixed',
|
||||||
},
|
},
|
||||||
gitea: {
|
gitea: {
|
||||||
@@ -256,6 +258,7 @@ export async function initializeConfigFromEnv(): Promise<void> {
|
|||||||
includePublic: envConfig.github.publicRepositories ?? existingConfig?.[0]?.githubConfig?.includePublic ?? true,
|
includePublic: envConfig.github.publicRepositories ?? existingConfig?.[0]?.githubConfig?.includePublic ?? true,
|
||||||
includeOrganizations: envConfig.github.mirrorOrganizations ? [] : (existingConfig?.[0]?.githubConfig?.includeOrganizations ?? []),
|
includeOrganizations: envConfig.github.mirrorOrganizations ? [] : (existingConfig?.[0]?.githubConfig?.includeOrganizations ?? []),
|
||||||
starredReposOrg: envConfig.github.starredReposOrg || existingConfig?.[0]?.githubConfig?.starredReposOrg || 'starred',
|
starredReposOrg: envConfig.github.starredReposOrg || existingConfig?.[0]?.githubConfig?.starredReposOrg || 'starred',
|
||||||
|
starredReposMode: envConfig.github.starredReposMode || existingConfig?.[0]?.githubConfig?.starredReposMode || 'dedicated-org',
|
||||||
mirrorStrategy,
|
mirrorStrategy,
|
||||||
defaultOrg: envConfig.gitea.organization || existingConfig?.[0]?.githubConfig?.defaultOrg || 'github-mirrors',
|
defaultOrg: envConfig.gitea.organization || existingConfig?.[0]?.githubConfig?.defaultOrg || 'github-mirrors',
|
||||||
starredCodeOnly: envConfig.github.starredCodeOnly ?? existingConfig?.[0]?.githubConfig?.starredCodeOnly ?? false,
|
starredCodeOnly: envConfig.github.starredCodeOnly ?? existingConfig?.[0]?.githubConfig?.starredCodeOnly ?? false,
|
||||||
|
|||||||
@@ -342,6 +342,8 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
mirrorPublicOrgs: false,
|
mirrorPublicOrgs: false,
|
||||||
publicOrgs: [],
|
publicOrgs: [],
|
||||||
starredCodeOnly: false,
|
starredCodeOnly: false,
|
||||||
|
starredReposOrg: "starred",
|
||||||
|
starredReposMode: "dedicated-org",
|
||||||
mirrorStrategy: "preserve"
|
mirrorStrategy: "preserve"
|
||||||
},
|
},
|
||||||
giteaConfig: {
|
giteaConfig: {
|
||||||
@@ -350,7 +352,6 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
token: "gitea-token",
|
token: "gitea-token",
|
||||||
organization: "github-mirrors",
|
organization: "github-mirrors",
|
||||||
visibility: "public",
|
visibility: "public",
|
||||||
starredReposOrg: "starred",
|
|
||||||
preserveVisibility: false
|
preserveVisibility: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -390,8 +391,8 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
const repo = { ...baseRepo, isStarred: true };
|
const repo = { ...baseRepo, isStarred: true };
|
||||||
const configWithoutStarredOrg = {
|
const configWithoutStarredOrg = {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
giteaConfig: {
|
githubConfig: {
|
||||||
...baseConfig.giteaConfig,
|
...baseConfig.githubConfig,
|
||||||
starredReposOrg: undefined
|
starredReposOrg: undefined
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -399,6 +400,34 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
expect(result).toBe("starred");
|
expect(result).toBe("starred");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("starred repos preserve owner/org when starredReposMode is preserve-owner", () => {
|
||||||
|
const repo = { ...baseRepo, isStarred: true, owner: "FOO", organization: "FOO", fullName: "FOO/BAR" };
|
||||||
|
const configWithPreserveStarred = {
|
||||||
|
...baseConfig,
|
||||||
|
githubConfig: {
|
||||||
|
...baseConfig.githubConfig!,
|
||||||
|
starredReposMode: "preserve-owner" as const,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getGiteaRepoOwner({ config: configWithPreserveStarred, repository: repo });
|
||||||
|
expect(result).toBe("FOO");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("starred personal repos preserve owner when starredReposMode is preserve-owner", () => {
|
||||||
|
const repo = { ...baseRepo, isStarred: true, owner: "alice", organization: undefined, fullName: "alice/demo" };
|
||||||
|
const configWithPreserveStarred = {
|
||||||
|
...baseConfig,
|
||||||
|
githubConfig: {
|
||||||
|
...baseConfig.githubConfig!,
|
||||||
|
starredReposMode: "preserve-owner" as const,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getGiteaRepoOwner({ config: configWithPreserveStarred, repository: repo });
|
||||||
|
expect(result).toBe("alice");
|
||||||
|
});
|
||||||
|
|
||||||
// Removed test for personalReposOrg as this field no longer exists
|
// Removed test for personalReposOrg as this field no longer exists
|
||||||
|
|
||||||
test("preserve strategy: personal repos fallback to username when no override", () => {
|
test("preserve strategy: personal repos fallback to username when no override", () => {
|
||||||
@@ -492,4 +521,24 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
|
|||||||
|
|
||||||
expect(result).toBe("custom-org");
|
expect(result).toBe("custom-org");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("getGiteaRepoOwnerAsync preserves starred owner when preserve-owner mode is enabled", async () => {
|
||||||
|
const configWithUser: Partial<Config> = {
|
||||||
|
...baseConfig,
|
||||||
|
userId: "user-id",
|
||||||
|
githubConfig: {
|
||||||
|
...baseConfig.githubConfig!,
|
||||||
|
starredReposMode: "preserve-owner",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const repo = { ...baseRepo, isStarred: true, owner: "FOO", organization: "FOO", fullName: "FOO/BAR" };
|
||||||
|
|
||||||
|
const result = await getGiteaRepoOwnerAsync({
|
||||||
|
config: configWithUser,
|
||||||
|
repository: repo,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toBe("FOO");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,8 +77,12 @@ export const getGiteaRepoOwnerAsync = async ({
|
|||||||
throw new Error("User ID is required for organization overrides.");
|
throw new Error("User ID is required for organization overrides.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if repository is starred - starred repos always go to starredReposOrg (highest priority)
|
// Check if repository is starred
|
||||||
if (repository.isStarred) {
|
if (repository.isStarred) {
|
||||||
|
const starredReposMode = config.githubConfig.starredReposMode || "dedicated-org";
|
||||||
|
if (starredReposMode === "preserve-owner") {
|
||||||
|
return repository.organization || repository.owner;
|
||||||
|
}
|
||||||
return config.githubConfig.starredReposOrg || "starred";
|
return config.githubConfig.starredReposOrg || "starred";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,8 +126,12 @@ export const getGiteaRepoOwner = ({
|
|||||||
throw new Error("Gitea username is required.");
|
throw new Error("Gitea username is required.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if repository is starred - starred repos always go to starredReposOrg
|
// Check if repository is starred
|
||||||
if (repository.isStarred) {
|
if (repository.isStarred) {
|
||||||
|
const starredReposMode = config.githubConfig.starredReposMode || "dedicated-org";
|
||||||
|
if (starredReposMode === "preserve-owner") {
|
||||||
|
return repository.organization || repository.owner;
|
||||||
|
}
|
||||||
return config.githubConfig.starredReposOrg || "starred";
|
return config.githubConfig.starredReposOrg || "starred";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,7 +380,11 @@ export const mirrorGithubRepoToGitea = async ({
|
|||||||
// Determine the actual repository name to use (handle duplicates for starred repos)
|
// Determine the actual repository name to use (handle duplicates for starred repos)
|
||||||
let targetRepoName = repository.name;
|
let targetRepoName = repository.name;
|
||||||
|
|
||||||
if (repository.isStarred && config.githubConfig) {
|
if (
|
||||||
|
repository.isStarred &&
|
||||||
|
config.githubConfig &&
|
||||||
|
(config.githubConfig.starredReposMode || "dedicated-org") === "dedicated-org"
|
||||||
|
) {
|
||||||
// Extract GitHub owner from full_name (format: owner/repo)
|
// Extract GitHub owner from full_name (format: owner/repo)
|
||||||
const githubOwner = repository.fullName.split('/')[0];
|
const githubOwner = repository.fullName.split('/')[0];
|
||||||
|
|
||||||
@@ -990,7 +1002,11 @@ export async function mirrorGitHubRepoToGiteaOrg({
|
|||||||
// Determine the actual repository name to use (handle duplicates for starred repos)
|
// Determine the actual repository name to use (handle duplicates for starred repos)
|
||||||
let targetRepoName = repository.name;
|
let targetRepoName = repository.name;
|
||||||
|
|
||||||
if (repository.isStarred && config.githubConfig) {
|
if (
|
||||||
|
repository.isStarred &&
|
||||||
|
config.githubConfig &&
|
||||||
|
(config.githubConfig.starredReposMode || "dedicated-org") === "dedicated-org"
|
||||||
|
) {
|
||||||
// Extract GitHub owner from full_name (format: owner/repo)
|
// Extract GitHub owner from full_name (format: owner/repo)
|
||||||
const githubOwner = repository.fullName.split('/')[0];
|
const githubOwner = repository.fullName.split('/')[0];
|
||||||
|
|
||||||
|
|||||||
@@ -92,8 +92,13 @@ async function preCreateOrganizations({
|
|||||||
// Get unique organization names
|
// Get unique organization names
|
||||||
const orgNames = new Set<string>();
|
const orgNames = new Set<string>();
|
||||||
|
|
||||||
// Add starred repos org
|
const starredReposMode = config.githubConfig?.starredReposMode || "dedicated-org";
|
||||||
if (config.githubConfig?.starredReposOrg) {
|
|
||||||
|
if (starredReposMode === "preserve-owner") {
|
||||||
|
for (const repo of repositories) {
|
||||||
|
orgNames.add(repo.organization || repo.owner);
|
||||||
|
}
|
||||||
|
} else if (config.githubConfig?.starredReposOrg) {
|
||||||
orgNames.add(config.githubConfig.starredReposOrg);
|
orgNames.add(config.githubConfig.starredReposOrg);
|
||||||
} else {
|
} else {
|
||||||
orgNames.add("starred");
|
orgNames.add("starred");
|
||||||
@@ -129,7 +134,11 @@ async function processStarredRepository({
|
|||||||
octokit: Octokit;
|
octokit: Octokit;
|
||||||
strategyConfig: ReturnType<typeof getMirrorStrategyConfig>;
|
strategyConfig: ReturnType<typeof getMirrorStrategyConfig>;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const starredOrg = config.githubConfig?.starredReposOrg || "starred";
|
const starredReposMode = config.githubConfig?.starredReposMode || "dedicated-org";
|
||||||
|
const starredOrg =
|
||||||
|
starredReposMode === "preserve-owner"
|
||||||
|
? repository.organization || repository.owner
|
||||||
|
: config.githubConfig?.starredReposOrg || "starred";
|
||||||
|
|
||||||
// Check if repository exists in Gitea
|
// Check if repository exists in Gitea
|
||||||
const existingRepo = await getGiteaRepoInfo({
|
const existingRepo = await getGiteaRepoInfo({
|
||||||
@@ -257,7 +266,11 @@ export async function syncStarredRepositories({
|
|||||||
if (error instanceof Error && error.message.includes("not a mirror")) {
|
if (error instanceof Error && error.message.includes("not a mirror")) {
|
||||||
console.warn(`Repository ${repository.name} is not a mirror, handling...`);
|
console.warn(`Repository ${repository.name} is not a mirror, handling...`);
|
||||||
|
|
||||||
const starredOrg = config.githubConfig?.starredReposOrg || "starred";
|
const starredReposMode = config.githubConfig?.starredReposMode || "dedicated-org";
|
||||||
|
const starredOrg =
|
||||||
|
starredReposMode === "preserve-owner"
|
||||||
|
? repository.organization || repository.owner
|
||||||
|
: config.githubConfig?.starredReposOrg || "starred";
|
||||||
const repoInfo = await getGiteaRepoInfo({
|
const repoInfo = await getGiteaRepoInfo({
|
||||||
config,
|
config,
|
||||||
owner: starredOrg,
|
owner: starredOrg,
|
||||||
@@ -287,4 +300,4 @@ export async function syncStarredRepositories({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export async function createDefaultConfig({ userId, envOverrides = {} }: Default
|
|||||||
includePublic: true,
|
includePublic: true,
|
||||||
includeOrganizations: [],
|
includeOrganizations: [],
|
||||||
starredReposOrg: "starred",
|
starredReposOrg: "starred",
|
||||||
|
starredReposMode: "dedicated-org",
|
||||||
mirrorStrategy: "preserve",
|
mirrorStrategy: "preserve",
|
||||||
defaultOrg: "github-mirrors",
|
defaultOrg: "github-mirrors",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export function mapUiToDbConfig(
|
|||||||
|
|
||||||
// Starred repos organization
|
// Starred repos organization
|
||||||
starredReposOrg: giteaConfig.starredReposOrg,
|
starredReposOrg: giteaConfig.starredReposOrg,
|
||||||
|
starredReposMode: giteaConfig.starredReposMode || "dedicated-org",
|
||||||
|
|
||||||
// Mirror strategy
|
// Mirror strategy
|
||||||
mirrorStrategy: giteaConfig.mirrorStrategy || "preserve",
|
mirrorStrategy: giteaConfig.mirrorStrategy || "preserve",
|
||||||
@@ -131,6 +132,7 @@ export function mapDbToUiConfig(dbConfig: any): {
|
|||||||
organization: dbConfig.githubConfig?.defaultOrg || "github-mirrors", // Get from GitHub config
|
organization: dbConfig.githubConfig?.defaultOrg || "github-mirrors", // Get from GitHub config
|
||||||
visibility: dbConfig.giteaConfig?.visibility === "default" ? "public" : dbConfig.giteaConfig?.visibility || "public",
|
visibility: dbConfig.giteaConfig?.visibility === "default" ? "public" : dbConfig.giteaConfig?.visibility || "public",
|
||||||
starredReposOrg: dbConfig.githubConfig?.starredReposOrg || "starred", // Get from GitHub config
|
starredReposOrg: dbConfig.githubConfig?.starredReposOrg || "starred", // Get from GitHub config
|
||||||
|
starredReposMode: dbConfig.githubConfig?.starredReposMode || "dedicated-org", // Get from GitHub config
|
||||||
preserveOrgStructure: dbConfig.giteaConfig?.preserveVisibility || false, // Map preserveVisibility
|
preserveOrgStructure: dbConfig.giteaConfig?.preserveVisibility || false, // Map preserveVisibility
|
||||||
mirrorStrategy: dbConfig.githubConfig?.mirrorStrategy || "preserve", // Get from GitHub config
|
mirrorStrategy: dbConfig.githubConfig?.mirrorStrategy || "preserve", // Get from GitHub config
|
||||||
personalReposOrg: undefined, // Not stored in current schema
|
personalReposOrg: undefined, // Not stored in current schema
|
||||||
|
|||||||
@@ -108,15 +108,14 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
console.log(`Repository ${repo.name} will be mirrored to owner: ${owner}`);
|
console.log(`Repository ${repo.name} will be mirrored to owner: ${owner}`);
|
||||||
|
|
||||||
// For single-org and starred repos strategies, or when mirroring to an org,
|
// For single-org strategy, or when mirroring to an org,
|
||||||
// always use the org mirroring function to ensure proper organization handling
|
// use the org mirroring function to ensure proper organization handling
|
||||||
const mirrorStrategy = config.githubConfig?.mirrorStrategy ||
|
const mirrorStrategy = config.githubConfig?.mirrorStrategy ||
|
||||||
(config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
(config.giteaConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
|
|
||||||
const shouldUseOrgMirror =
|
const shouldUseOrgMirror =
|
||||||
owner !== config.giteaConfig?.defaultOwner || // Different owner means org
|
owner !== config.giteaConfig?.defaultOwner || // Different owner means org
|
||||||
mirrorStrategy === "single-org" || // Single-org strategy always uses org
|
mirrorStrategy === "single-org"; // Single-org strategy always uses org
|
||||||
repoData.isStarred; // Starred repos always go to org
|
|
||||||
|
|
||||||
if (shouldUseOrgMirror) {
|
if (shouldUseOrgMirror) {
|
||||||
await mirrorGitHubOrgRepoToGiteaOrg({
|
await mirrorGitHubOrgRepoToGiteaOrg({
|
||||||
@@ -222,4 +221,4 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
return createSecureErrorResponse(error, "mirror-repo API", 500);
|
return createSecureErrorResponse(error, "mirror-repo API", 500);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -142,15 +142,14 @@ export const POST: APIRoute = async ({ request }) => {
|
|||||||
|
|
||||||
console.log(`Importing repo: ${repo.name} to owner: ${owner}`);
|
console.log(`Importing repo: ${repo.name} to owner: ${owner}`);
|
||||||
|
|
||||||
// For single-org and starred repos strategies, or when mirroring to an org,
|
// For single-org strategy, or when mirroring to an org,
|
||||||
// always use the org mirroring function to ensure proper organization handling
|
// use the org mirroring function to ensure proper organization handling
|
||||||
const mirrorStrategy = config.githubConfig?.mirrorStrategy ||
|
const mirrorStrategy = config.githubConfig?.mirrorStrategy ||
|
||||||
(config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
(config.giteaConfig?.preserveOrgStructure ? "preserve" : "flat-user");
|
||||||
|
|
||||||
const shouldUseOrgMirror =
|
const shouldUseOrgMirror =
|
||||||
owner !== config.giteaConfig?.defaultOwner || // Different owner means org
|
owner !== config.giteaConfig?.defaultOwner || // Different owner means org
|
||||||
mirrorStrategy === "single-org" || // Single-org strategy always uses org
|
mirrorStrategy === "single-org"; // Single-org strategy always uses org
|
||||||
repoData.isStarred; // Starred repos always go to org
|
|
||||||
|
|
||||||
if (shouldUseOrgMirror) {
|
if (shouldUseOrgMirror) {
|
||||||
await mirrorGitHubOrgRepoToGiteaOrg({
|
await mirrorGitHubOrgRepoToGiteaOrg({
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { type Config as ConfigType } from "@/lib/db/schema";
|
|||||||
|
|
||||||
export type GiteaOrgVisibility = "public" | "private" | "limited";
|
export type GiteaOrgVisibility = "public" | "private" | "limited";
|
||||||
export type MirrorStrategy = "preserve" | "single-org" | "flat-user" | "mixed";
|
export type MirrorStrategy = "preserve" | "single-org" | "flat-user" | "mixed";
|
||||||
|
export type StarredReposMode = "dedicated-org" | "preserve-owner";
|
||||||
|
|
||||||
export interface GiteaConfig {
|
export interface GiteaConfig {
|
||||||
url: string;
|
url: string;
|
||||||
@@ -10,6 +11,7 @@ export interface GiteaConfig {
|
|||||||
organization: string;
|
organization: string;
|
||||||
visibility: GiteaOrgVisibility;
|
visibility: GiteaOrgVisibility;
|
||||||
starredReposOrg: string;
|
starredReposOrg: string;
|
||||||
|
starredReposMode?: StarredReposMode;
|
||||||
preserveOrgStructure: boolean;
|
preserveOrgStructure: boolean;
|
||||||
mirrorStrategy?: MirrorStrategy; // New field for the strategy
|
mirrorStrategy?: MirrorStrategy; // New field for the strategy
|
||||||
personalReposOrg?: string; // Override destination for personal repos
|
personalReposOrg?: string; // Override destination for personal repos
|
||||||
@@ -46,6 +48,7 @@ export interface GitHubConfig {
|
|||||||
privateRepositories: boolean;
|
privateRepositories: boolean;
|
||||||
mirrorStarred: boolean;
|
mirrorStarred: boolean;
|
||||||
starredDuplicateStrategy?: DuplicateNameStrategy;
|
starredDuplicateStrategy?: DuplicateNameStrategy;
|
||||||
|
starredReposMode?: StarredReposMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MirrorOptions {
|
export interface MirrorOptions {
|
||||||
|
|||||||
Reference in New Issue
Block a user