Compare commits

...

9 Commits

Author SHA1 Message Date
Arunavo Ray
0e9d54b517 fix: add backward compatibility for skipStarredIssues field
Since githubConfig is stored as JSON in the database (not individual columns),
no database migration is needed. However, we need to handle reading old configs
that still use the 'skipStarredIssues' field name.

Changes:
- Added skipStarredIssues as optional field in Zod schema (marked deprecated)
- Updated config mapper to check both starredCodeOnly and skipStarredIssues
- Old configs will continue to work seamlessly
- New configs will use starredCodeOnly field name

This ensures zero-downtime upgrades for existing installations.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 09:44:38 +05:30
Arunavo Ray
7a04665b70 v3.8.3 2025-10-03 09:24:28 +05:30
Arunavo Ray
3a3ff314e0 refactor: rename skipStarredIssues to starredCodeOnly
The previous name 'skipStarredIssues' was misleading as it now skips ALL
metadata (not just issues) for starred repositories. The new name
'starredCodeOnly' better reflects the actual behavior - mirroring only
source code for starred repos.

Changes:
- Renamed skipStarredIssues → starredCodeOnly in all files
- Updated UI label from "Don't fetch issues" to "Code-only mode"
- Updated description to clarify it skips ALL metadata types:
  issues, PRs, labels, milestones, wiki, and releases
- Updated database schema, types, config mapper, and all components
- Updated Helm charts, CI configs, and documentation

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 09:22:18 +05:30
Arunavo Ray
fed74ee901 fix: apply skipStarredIssues flag to releases mirroring
Extended the skipStarredIssues flag to also skip releases for starred repos
when "code-only" mode is enabled. Previously, releases were still being
mirrored even when lightweight mode was selected.

Now starred repos with "code-only" mode will skip:
- Issues ✓
- Pull requests ✓
- Labels ✓
- Milestones ✓
- Wiki ✓
- Releases ✓ (this fix)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 09:18:35 +05:30
Arunavo Ray
85ea502276 fix: apply skipStarredIssues flag to all metadata types
Previously, the skipStarredIssues flag (Lightweight mode for starred repos)
only applied to issues. This caused starred repos to mirror all metadata
(pull requests, labels, milestones, wiki) even when "code-only" was selected.

Fixed by applying the skipStarredIssues check to:
- Pull requests mirroring
- Labels mirroring
- Milestones mirroring
- Wiki mirroring (in migration payload)

Now starred repos with "code-only" mode truly mirror only source code,
skipping all metadata as intended.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-03 09:13:58 +05:30
Arunavo Ray
ffb7bd3cb0 v3.8.2 2025-10-02 21:19:32 +05:30
ARUNAVO RAY
b39d7a2179 Merge pull request #107 from RayLabsHQ/fix/issue-97-migration-duplicates
fix: resolve migration 0005 duplicate constraint failure (#97)
2025-10-02 07:45:42 +05:30
Arunavo Ray
bf99a95dc6 ci: add more paths to trigger Docker builds
- Added docker-entrypoint.sh to trigger paths
- Added drizzle/** for database migrations
- Added scripts/** for database management scripts
- Added src/** for source code changes

This ensures Docker images are rebuilt when critical runtime
files change, not just package dependencies.
2025-10-01 08:02:56 +05:30
Arunavo Ray
2ea917fdaa fix: resolve migration 0005 duplicate constraint failure (#97)
**Problem:**
- Users upgrading to v3.7.2 encountered database migration failures
- Migration 0005 tried to add unique index without handling existing duplicates
- Hybrid initialization (manual SQL + Drizzle) caused schema inconsistencies
- Error: "UNIQUE constraint failed: repositories.user_id, repositories.full_name"

**Solution:**

1. **Fixed Migration 0005:**
   - Added deduplication step before creating unique index
   - Removes duplicate (user_id, full_name) entries, keeping most recent
   - Safely creates unique constraint after cleanup

2. **Removed Hybrid Database Initialization:**
   - Eliminated 154 lines of manual SQL from docker-entrypoint.sh
   - Now uses Drizzle exclusively for schema management
   - Single source of truth prevents schema drift
   - Migrations run automatically via src/lib/db/index.ts

**Testing:**
-  Fresh database initialization works
-  Duplicate deduplication verified
-  Unique constraint properly enforced
-  All 6 migrations apply cleanly

**Changes:**
- docker-entrypoint.sh: Removed manual table creation SQL
- drizzle/0005_polite_preak.sql: Added deduplication before index creation

Fixes #97
2025-10-01 07:57:16 +05:30
18 changed files with 127 additions and 207 deletions

View File

@@ -37,7 +37,7 @@ gitea-mirror:
type: "personal"
privateRepositories: true
skipForks: false
skipStarredIssues: false
starredCodeOnly: false
gitea:
url: "https://gitea.example.com"
token: "not-used-in-template"

View File

@@ -10,6 +10,10 @@ on:
- 'package.json'
- 'bun.lock*'
- '.github/workflows/docker-build.yml'
- 'docker-entrypoint.sh'
- 'drizzle/**'
- 'scripts/**'
- 'src/**'
pull_request:
paths:
- 'Dockerfile'
@@ -17,6 +21,10 @@ on:
- 'package.json'
- 'bun.lock*'
- '.github/workflows/docker-build.yml'
- 'docker-entrypoint.sh'
- 'drizzle/**'
- 'scripts/**'
- 'src/**'
schedule:
- cron: '0 0 * * 0' # Weekly security scan on Sunday at midnight

View File

@@ -193,7 +193,7 @@ export async function POST({ request }: APIContext) {
### Advanced Options (UI Fields)
- **skipForks**: Skip forked repositories (default: false)
- **skipStarredIssues**: Skip issues for starred repositories (default: false) - enables "Lightweight mode" for starred repos
- **starredCodeOnly**: Skip issues for starred repositories (default: false) - enables "Lightweight mode" for starred repos
### Repository Statuses
Repositories can have the following statuses:

View File

@@ -120,161 +120,13 @@ fi
# Dependencies are already installed during the Docker build process
# Initialize the database if it doesn't exist
# Note: Drizzle migrations will be run automatically when the app starts (see src/lib/db/index.ts)
if [ ! -f "/app/data/gitea-mirror.db" ]; then
echo "Initializing database..."
if [ -f "dist/scripts/init-db.js" ]; then
bun dist/scripts/init-db.js
elif [ -f "dist/scripts/manage-db.js" ]; then
bun dist/scripts/manage-db.js init
elif [ -f "scripts/manage-db.ts" ]; then
bun scripts/manage-db.ts init
else
echo "Warning: Could not find database initialization scripts in dist/scripts."
echo "Creating and initializing database manually..."
# Create the database file
echo "Database not found. It will be created and initialized via Drizzle migrations on first app startup..."
# Create empty database file so migrations can run
touch /app/data/gitea-mirror.db
# Initialize the database with required tables
sqlite3 /app/data/gitea-mirror.db <<EOF
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
username TEXT NOT NULL,
password TEXT NOT NULL,
email TEXT NOT NULL,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS configs (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1,
github_config TEXT NOT NULL,
gitea_config TEXT NOT NULL,
include TEXT NOT NULL DEFAULT '["*"]',
exclude TEXT NOT NULL DEFAULT '[]',
schedule_config TEXT NOT NULL,
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE IF NOT EXISTS repositories (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
config_id TEXT NOT NULL,
name TEXT NOT NULL,
full_name TEXT NOT NULL,
url TEXT NOT NULL,
clone_url TEXT NOT NULL,
owner TEXT NOT NULL,
organization TEXT,
mirrored_location TEXT DEFAULT '',
destination_org TEXT,
is_private INTEGER NOT NULL DEFAULT 0,
is_fork INTEGER NOT NULL DEFAULT 0,
forked_from TEXT,
has_issues INTEGER NOT NULL DEFAULT 0,
is_starred INTEGER NOT NULL DEFAULT 0,
is_archived INTEGER NOT NULL DEFAULT 0,
size INTEGER NOT NULL DEFAULT 0,
has_lfs INTEGER NOT NULL DEFAULT 0,
has_submodules INTEGER NOT NULL DEFAULT 0,
language TEXT,
description TEXT,
default_branch TEXT NOT NULL,
visibility TEXT NOT NULL DEFAULT 'public',
status TEXT NOT NULL DEFAULT 'imported',
last_mirrored INTEGER,
error_message TEXT,
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (config_id) REFERENCES configs(id)
);
-- Uniqueness of (user_id, full_name) for repositories is enforced via drizzle migrations
CREATE TABLE IF NOT EXISTS organizations (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
config_id TEXT NOT NULL,
name TEXT NOT NULL,
avatar_url TEXT NOT NULL,
membership_role TEXT NOT NULL DEFAULT 'member',
is_included INTEGER NOT NULL DEFAULT 1,
status TEXT NOT NULL DEFAULT 'imported',
last_mirrored INTEGER,
error_message TEXT,
repository_count INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
FOREIGN KEY (user_id) REFERENCES users(id),
FOREIGN KEY (config_id) REFERENCES configs(id)
);
CREATE TABLE IF NOT EXISTS mirror_jobs (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
repository_id TEXT,
repository_name TEXT,
organization_id TEXT,
organization_name TEXT,
details TEXT,
status TEXT NOT NULL DEFAULT 'imported',
message TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
-- New fields for job resilience
job_type TEXT NOT NULL DEFAULT 'mirror',
batch_id TEXT,
total_items INTEGER,
completed_items INTEGER DEFAULT 0,
item_ids TEXT, -- JSON array as text
completed_item_ids TEXT DEFAULT '[]', -- JSON array as text
in_progress INTEGER NOT NULL DEFAULT 0, -- Boolean as integer
started_at TIMESTAMP,
completed_at TIMESTAMP,
last_checkpoint TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_user_id ON mirror_jobs(user_id);
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_batch_id ON mirror_jobs(batch_id);
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_in_progress ON mirror_jobs(in_progress);
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_job_type ON mirror_jobs(job_type);
CREATE INDEX IF NOT EXISTS idx_mirror_jobs_timestamp ON mirror_jobs(timestamp);
CREATE TABLE IF NOT EXISTS events (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
channel TEXT NOT NULL,
payload TEXT NOT NULL,
read INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX IF NOT EXISTS idx_events_user_channel ON events(user_id, channel);
CREATE INDEX IF NOT EXISTS idx_events_created_at ON events(created_at);
CREATE INDEX IF NOT EXISTS idx_events_read ON events(read);
EOF
echo "Database initialized with required tables."
fi
else
echo "Database already exists, checking for issues..."
if [ -f "dist/scripts/fix-db-issues.js" ]; then
bun dist/scripts/fix-db-issues.js
elif [ -f "dist/scripts/manage-db.js" ]; then
bun dist/scripts/manage-db.js fix
elif [ -f "scripts/manage-db.ts" ]; then
bun scripts/manage-db.ts fix
fi
echo "Database exists, checking integrity..."
echo "Database already exists, Drizzle will check for pending migrations on startup..."
fi
# Extract version from package.json and set as environment variable

View File

@@ -1 +1,11 @@
CREATE UNIQUE INDEX `uniq_repositories_user_full_name` ON `repositories` (`user_id`,`full_name`);
-- Step 1: Remove duplicate repositories, keeping the most recently updated one
-- This handles cases where users have duplicate entries from before the unique constraint
DELETE FROM repositories
WHERE rowid NOT IN (
SELECT MAX(rowid)
FROM repositories
GROUP BY user_id, full_name
);
--> statement-breakpoint
-- Step 2: Now create the unique index safely
CREATE UNIQUE INDEX uniq_repositories_user_full_name ON repositories (user_id, full_name);

View File

@@ -175,7 +175,7 @@ These values populate a **ConfigMap** (non-secret) and a **Secret** (for tokens
| `gitea-mirror.github.type` | `personal` | `GITHUB_TYPE` |
| `gitea-mirror.github.privateRepositories` | `true` | `PRIVATE_REPOSITORIES` |
| `gitea-mirror.github.skipForks` | `false` | `SKIP_FORKS` |
| `gitea-mirror.github.skipStarredIssues` | `false` | `SKIP_STARRED_ISSUES` |
| `gitea-mirror.github.starredCodeOnly` | `false` | `SKIP_STARRED_ISSUES` |
| `gitea-mirror.github.mirrorStarred` | `false` | `MIRROR_STARRED` |
### Gitea

View File

@@ -18,7 +18,7 @@ data:
PRIVATE_REPOSITORIES: {{ $gm.github.privateRepositories | quote }}
MIRROR_STARRED: {{ $gm.github.mirrorStarred | quote }}
SKIP_FORKS: {{ $gm.github.skipForks | quote }}
SKIP_STARRED_ISSUES: {{ $gm.github.skipStarredIssues | quote }}
SKIP_STARRED_ISSUES: {{ $gm.github.starredCodeOnly | quote }}
# Gitea Config
GITEA_URL: {{ $gm.gitea.url | quote }}
GITEA_USERNAME: {{ $gm.gitea.username | quote }}

View File

@@ -126,7 +126,7 @@ gitea-mirror:
privateRepositories: true
mirrorStarred: false
skipForks: false
skipStarredIssues: false
starredCodeOnly: false
gitea:
url: ""

View File

@@ -1,7 +1,7 @@
{
"name": "gitea-mirror",
"type": "module",
"version": "3.7.1",
"version": "3.8.3",
"engines": {
"bun": ">=1.2.9"
},

View File

@@ -67,21 +67,21 @@ export function AdvancedOptionsForm({
<div className="flex items-center">
<Checkbox
id="skip-starred-issues"
checked={config.skipStarredIssues}
id="starred-code-only"
checked={config.starredCodeOnly}
onCheckedChange={(checked) =>
handleChange("skipStarredIssues", Boolean(checked))
handleChange("starredCodeOnly", Boolean(checked))
}
/>
<label
htmlFor="skip-starred-issues"
htmlFor="starred-code-only"
className="ml-2 text-sm select-none"
>
Don't fetch issues for starred repos
Code-only mode for starred repos
</label>
</div>
<p className="text-xs text-muted-foreground ml-6">
Skip mirroring issues and pull requests for starred repositories
Mirror only source code for starred repositories, skipping all metadata (issues, PRs, labels, milestones, wiki, releases)
</p>
</div>
</CardContent>

View File

@@ -71,7 +71,7 @@ export function ConfigTabs() {
},
advancedOptions: {
skipForks: false,
skipStarredIssues: false,
starredCodeOnly: false,
},
});
const { user } = useAuth();

View File

@@ -89,10 +89,10 @@ export function GitHubMirrorSettings({
// 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,
releases: !advancedOptions.starredCodeOnly && mirrorOptions.mirrorReleases,
issues: !advancedOptions.starredCodeOnly && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.issues,
pullRequests: !advancedOptions.starredCodeOnly && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.pullRequests,
wiki: !advancedOptions.starredCodeOnly && mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.wiki,
};
const starredContentCount = Object.entries(starredRepoContent).filter(([key, value]) => key !== 'code' && value).length;
@@ -168,7 +168,7 @@ export function GitHubMirrorSettings({
className="h-8 text-xs font-normal min-w-[140px] md:min-w-[140px] justify-between"
>
<span>
{advancedOptions.skipStarredIssues ? (
{advancedOptions.starredCodeOnly ? (
"Code only"
) : starredContentCount === 0 ? (
"Code only"
@@ -206,8 +206,8 @@ export function GitHubMirrorSettings({
<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)}
checked={advancedOptions.starredCodeOnly}
onCheckedChange={(checked) => handleAdvancedChange('starredCodeOnly', !!checked)}
/>
<Label
htmlFor="starred-lightweight"
@@ -222,7 +222,7 @@ export function GitHubMirrorSettings({
</Label>
</div>
{!advancedOptions.skipStarredIssues && (
{!advancedOptions.starredCodeOnly && (
<>
<Separator className="my-2" />
<div className="space-y-2">

View File

@@ -27,7 +27,8 @@ export const githubConfigSchema = z.object({
starredReposOrg: z.string().optional(),
mirrorStrategy: z.enum(["preserve", "single-org", "flat-user", "mixed"]).default("preserve"),
defaultOrg: z.string().optional(),
skipStarredIssues: z.boolean().default(false),
starredCodeOnly: z.boolean().default(false),
skipStarredIssues: z.boolean().optional(), // Deprecated: kept for backward compatibility, use starredCodeOnly instead
starredDuplicateStrategy: z.enum(["suffix", "prefix", "owner-org"]).default("suffix").optional(),
});

View File

@@ -21,7 +21,7 @@ interface EnvConfig {
mirrorOrganizations?: boolean;
preserveOrgStructure?: boolean;
onlyMirrorOrgs?: boolean;
skipStarredIssues?: boolean;
starredCodeOnly?: boolean;
starredReposOrg?: string;
mirrorStrategy?: 'preserve' | 'single-org' | 'flat-user' | 'mixed';
};
@@ -107,7 +107,7 @@ function parseEnvConfig(): EnvConfig {
mirrorOrganizations: process.env.MIRROR_ORGANIZATIONS === 'true',
preserveOrgStructure: process.env.PRESERVE_ORG_STRUCTURE === 'true',
onlyMirrorOrgs: process.env.ONLY_MIRROR_ORGS === 'true',
skipStarredIssues: process.env.SKIP_STARRED_ISSUES === 'true',
starredCodeOnly: process.env.SKIP_STARRED_ISSUES === 'true',
starredReposOrg: process.env.STARRED_REPOS_ORG,
mirrorStrategy: process.env.MIRROR_STRATEGY as 'preserve' | 'single-org' | 'flat-user' | 'mixed',
},
@@ -253,7 +253,7 @@ export async function initializeConfigFromEnv(): Promise<void> {
starredReposOrg: envConfig.github.starredReposOrg || existingConfig?.[0]?.githubConfig?.starredReposOrg || 'starred',
mirrorStrategy,
defaultOrg: envConfig.gitea.organization || existingConfig?.[0]?.githubConfig?.defaultOrg || 'github-mirrors',
skipStarredIssues: envConfig.github.skipStarredIssues ?? existingConfig?.[0]?.githubConfig?.skipStarredIssues ?? false,
starredCodeOnly: envConfig.github.starredCodeOnly ?? existingConfig?.[0]?.githubConfig?.starredCodeOnly ?? false,
};
// Build Gitea config

View File

@@ -331,7 +331,7 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => {
excludeOrgs: [],
mirrorPublicOrgs: false,
publicOrgs: [],
skipStarredIssues: false,
starredCodeOnly: false,
mirrorStrategy: "preserve"
},
giteaConfig: {

View File

@@ -423,12 +423,16 @@ export const mirrorGithubRepoToGitea = async ({
// Prepare migration payload
// For private repos, use separate auth fields instead of embedding credentials in URL
// This is required for Forgejo 12+ which rejects URLs with embedded credentials
// Skip wiki for starred repos if starredCodeOnly is enabled
const shouldMirrorWiki = config.giteaConfig?.wiki &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
const migratePayload: any = {
clone_addr: cloneAddress,
repo_name: targetRepoName,
mirror: true,
mirror_interval: config.giteaConfig?.mirrorInterval || "8h",
wiki: config.giteaConfig?.wiki || false,
wiki: shouldMirrorWiki || false,
lfs: config.giteaConfig?.lfs || false,
private: repository.isPrivate,
repo_owner: repoOwner,
@@ -457,8 +461,13 @@ export const mirrorGithubRepoToGitea = async ({
);
//mirror releases
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}`);
if (config.giteaConfig?.mirrorReleases) {
// Skip releases for starred repos if starredCodeOnly is enabled
const shouldMirrorReleases = config.giteaConfig?.mirrorReleases &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorReleases=${shouldMirrorReleases}`);
if (shouldMirrorReleases) {
try {
await mirrorGitHubReleasesToGitea({
config,
@@ -475,11 +484,11 @@ export const mirrorGithubRepoToGitea = async ({
}
// clone issues
// Skip issues for starred repos if skipStarredIssues is enabled
// Skip issues for starred repos if starredCodeOnly is enabled
const shouldMirrorIssues = config.giteaConfig?.mirrorIssues &&
!(repository.isStarred && config.githubConfig?.skipStarredIssues);
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, isStarred=${repository.isStarred}, skipStarredIssues=${config.githubConfig?.skipStarredIssues}, shouldMirrorIssues=${shouldMirrorIssues}`);
console.log(`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorIssues=${shouldMirrorIssues}`);
if (shouldMirrorIssues) {
try {
@@ -498,8 +507,13 @@ export const mirrorGithubRepoToGitea = async ({
}
// Mirror pull requests if enabled
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}`);
if (config.giteaConfig?.mirrorPullRequests) {
// Skip pull requests for starred repos if starredCodeOnly is enabled
const shouldMirrorPullRequests = config.giteaConfig?.mirrorPullRequests &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorPullRequests=${shouldMirrorPullRequests}`);
if (shouldMirrorPullRequests) {
try {
await mirrorGitRepoPullRequestsToGitea({
config,
@@ -516,8 +530,13 @@ export const mirrorGithubRepoToGitea = async ({
}
// Mirror labels if enabled (and not already done via issues)
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}`);
if (config.giteaConfig?.mirrorLabels && !shouldMirrorIssues) {
// Skip labels for starred repos if starredCodeOnly is enabled
const shouldMirrorLabels = config.giteaConfig?.mirrorLabels && !shouldMirrorIssues &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorLabels=${shouldMirrorLabels}`);
if (shouldMirrorLabels) {
try {
await mirrorGitRepoLabelsToGitea({
config,
@@ -534,8 +553,13 @@ export const mirrorGithubRepoToGitea = async ({
}
// Mirror milestones if enabled
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}`);
if (config.giteaConfig?.mirrorMilestones) {
// Skip milestones for starred repos if starredCodeOnly is enabled
const shouldMirrorMilestones = config.giteaConfig?.mirrorMilestones &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorMilestones=${shouldMirrorMilestones}`);
if (shouldMirrorMilestones) {
try {
await mirrorGitRepoMilestonesToGitea({
config,
@@ -824,13 +848,17 @@ export async function mirrorGitHubRepoToGiteaOrg({
// Prepare migration payload
// For private repos, use separate auth fields instead of embedding credentials in URL
// This is required for Forgejo 12+ which rejects URLs with embedded credentials
// Skip wiki for starred repos if starredCodeOnly is enabled
const shouldMirrorWiki = config.giteaConfig?.wiki &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
const migratePayload: any = {
clone_addr: cloneAddress,
uid: giteaOrgId,
repo_name: targetRepoName,
mirror: true,
mirror_interval: config.giteaConfig?.mirrorInterval || "8h",
wiki: config.giteaConfig?.wiki || false,
wiki: shouldMirrorWiki || false,
lfs: config.giteaConfig?.lfs || false,
private: repository.isPrivate,
};
@@ -856,8 +884,13 @@ export async function mirrorGitHubRepoToGiteaOrg({
);
//mirror releases
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}`);
if (config.giteaConfig?.mirrorReleases) {
// Skip releases for starred repos if starredCodeOnly is enabled
const shouldMirrorReleases = config.giteaConfig?.mirrorReleases &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Release mirroring check: mirrorReleases=${config.giteaConfig?.mirrorReleases}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorReleases=${shouldMirrorReleases}`);
if (shouldMirrorReleases) {
try {
await mirrorGitHubReleasesToGitea({
config,
@@ -874,11 +907,11 @@ export async function mirrorGitHubRepoToGiteaOrg({
}
// Clone issues
// Skip issues for starred repos if skipStarredIssues is enabled
// Skip issues for starred repos if starredCodeOnly is enabled
const shouldMirrorIssues = config.giteaConfig?.mirrorIssues &&
!(repository.isStarred && config.githubConfig?.skipStarredIssues);
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, isStarred=${repository.isStarred}, skipStarredIssues=${config.githubConfig?.skipStarredIssues}, shouldMirrorIssues=${shouldMirrorIssues}`);
console.log(`[Metadata] Issue mirroring check: mirrorIssues=${config.giteaConfig?.mirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorIssues=${shouldMirrorIssues}`);
if (shouldMirrorIssues) {
try {
@@ -897,8 +930,13 @@ export async function mirrorGitHubRepoToGiteaOrg({
}
// Mirror pull requests if enabled
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}`);
if (config.giteaConfig?.mirrorPullRequests) {
// Skip pull requests for starred repos if starredCodeOnly is enabled
const shouldMirrorPullRequests = config.giteaConfig?.mirrorPullRequests &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Pull request mirroring check: mirrorPullRequests=${config.giteaConfig?.mirrorPullRequests}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorPullRequests=${shouldMirrorPullRequests}`);
if (shouldMirrorPullRequests) {
try {
await mirrorGitRepoPullRequestsToGitea({
config,
@@ -915,8 +953,13 @@ export async function mirrorGitHubRepoToGiteaOrg({
}
// Mirror labels if enabled (and not already done via issues)
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}`);
if (config.giteaConfig?.mirrorLabels && !shouldMirrorIssues) {
// Skip labels for starred repos if starredCodeOnly is enabled
const shouldMirrorLabels = config.giteaConfig?.mirrorLabels && !shouldMirrorIssues &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Label mirroring check: mirrorLabels=${config.giteaConfig?.mirrorLabels}, shouldMirrorIssues=${shouldMirrorIssues}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorLabels=${shouldMirrorLabels}`);
if (shouldMirrorLabels) {
try {
await mirrorGitRepoLabelsToGitea({
config,
@@ -933,8 +976,13 @@ export async function mirrorGitHubRepoToGiteaOrg({
}
// Mirror milestones if enabled
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}`);
if (config.giteaConfig?.mirrorMilestones) {
// Skip milestones for starred repos if starredCodeOnly is enabled
const shouldMirrorMilestones = config.giteaConfig?.mirrorMilestones &&
!(repository.isStarred && config.githubConfig?.starredCodeOnly);
console.log(`[Metadata] Milestone mirroring check: mirrorMilestones=${config.giteaConfig?.mirrorMilestones}, isStarred=${repository.isStarred}, starredCodeOnly=${config.githubConfig?.starredCodeOnly}, shouldMirrorMilestones=${shouldMirrorMilestones}`);
if (shouldMirrorMilestones) {
try {
await mirrorGitRepoMilestonesToGitea({
config,

View File

@@ -54,7 +54,7 @@ export function mapUiToDbConfig(
defaultOrg: giteaConfig.organization,
// Advanced options
skipStarredIssues: advancedOptions.skipStarredIssues,
starredCodeOnly: advancedOptions.starredCodeOnly,
};
// Map Gitea config to match database schema
@@ -152,7 +152,8 @@ export function mapDbToUiConfig(dbConfig: any): {
// Map advanced options
const advancedOptions: AdvancedOptions = {
skipForks: !(dbConfig.githubConfig?.includeForks ?? true), // Invert includeForks to get skipForks
skipStarredIssues: dbConfig.githubConfig?.skipStarredIssues || false,
// Support both old (skipStarredIssues) and new (starredCodeOnly) field names for backward compatibility
starredCodeOnly: dbConfig.githubConfig?.starredCodeOnly ?? (dbConfig.githubConfig as any)?.skipStarredIssues ?? false,
};
return {

View File

@@ -55,7 +55,7 @@ export interface MirrorOptions {
export interface AdvancedOptions {
skipForks: boolean;
skipStarredIssues: boolean;
starredCodeOnly: boolean;
}
export interface SaveConfigApiRequest {