ARUNAVO RAY 0f700c7197 feat: per-repository and per-organization mirror option overrides (#362)
* feat: per-repository and per-organization mirror option overrides

Closes the gap reported in #361: a repository whose LFS fetch fails could
not be mirrored at all, because LFS was a single global switch. Gitea runs
the LFS fetch inside its own migration, so a failure aborts the whole
migration with nothing to salvage. The only fix available to us is to stop
asking for LFS on that repository.

Mirror options now resolve across three tiers, per flag, most specific
first: repository override, then organization override, then the global
config. NULL at a tier means inherit, so overriding LFS on one repo leaves
its other options alone.

Adds resolveMirrorOptions(), which replaces 24 scattered reads of
config.giteaConfig.* across three near-identical blocks: the two mirror
paths in gitea.ts and the sync path in gitea-enhanced.ts. The third was
easy to miss and mattered: without it, overrides would have been honored
on first mirror and silently ignored on every scheduled sync afterwards.

The starredCodeOnly clamp is folded into the resolver and deliberately
outranks explicit overrides, preserving the existing behavior that starred
repos mirror code only.

Editing is on the objects themselves, via the existing three-dot menus on
the Repositories and Organizations pages, not in Configuration, which keeps
holding the global defaults. A repositories filter and a row badge make it
possible to find which repos deviate.

Malformed override JSON degrades to "inherit" rather than throwing, so a
bad value can never break a mirror run.

* fix(ui): disable mirror toggles that cannot take effect, and explain why

A toggle the runtime will ignore should not look editable. Three cases
were doing exactly that, so they now share one mechanism:
getMirrorOverrideGating() returns a reason string per flag, and the dialog
disables the control and prints that reason underneath.

Starred clamp. When a repo is starred and starredCodeOnly is set, the
resolver forces every metadata flag off regardless of the override. The
dialog previously let the user set them anyway. The clamped set now lives
in STARRED_CLAMPED_KEYS, shared by the resolver and the gating helper so
the flags the UI disables cannot drift from the ones the runtime clamps.
LFS is deliberately excluded from that set, since turning LFS off per
repository is the point of #361, and a test pins that.

Labels. shouldMirrorLabels is `mirrorLabels && !mirrorIssues` in both
mirror paths, so labels cannot take effect while issues are mirrored. That
gate reacts live to the in-progress edit rather than only the saved state.

Inherit hint. For a repository the hint now reflects global -> org instead
of global alone, via a new name-keyed GET
/api/organizations/mirror-overrides that reuses
loadOrganizationMirrorOverrides. Personal repos skip the fetch, a failed
fetch degrades to the global values rather than blocking the dialog, and
the hint is suppressed while in flight so it is never briefly wrong.

Widens the UI to mirrorLabels and mirrorMilestones. mirrorMetadata stays
out: no mirror path reads it, so a per-object override would resolve
correctly and then do nothing. See the report for that finding.

useGiteaConfig additionally returns advancedOptions so callers can read
starredCodeOnly without a second request.

* fix(ui): read inherited mirror flags from mirrorOptions, not giteaConfig

/api/config does not return the mirror flags on giteaConfig. On the way
out, mapDbToUiConfig reshapes them into a separate mirrorOptions object
using different names (mirrorLFS, not lfs) and nesting the metadata flags
under metadataComponents with short names (issues, not mirrorIssues).

The dialog was written against the DB shape, so every lookup returned
undefined. Coerced to false, that is indistinguishable from a real "off",
which produced two symptoms: every inherit hint read "currently off"
regardless of the actual global config, and the labels gate never fired
because the effective issues value it keys on came from the same dead
source.

Adds mirrorOptionsToFlags() as the single conversion point, matching the
derivation in mapUiToDbConfig exactly, including that mirrorMetadata is a
master switch over the metadata components while lfs and mirrorReleases
sit outside it. Both dialogs now go through it, and useGiteaConfig returns
mirrorOptions alongside advancedOptions.

Server-side mirroring was never affected: the resolver reads config
straight from the DB, where the flags really do live on giteaConfig.

The existing tests could not catch this because they build a synthetic
config already in DB shape, so a client/server mismatch is invisible to
them. The new tests push flags through the real config-mapper in both
directions and assert the derived flags equal what mapUiToDbConfig would
persist, so they fail if that mapping changes again. One test pins the
root cause directly: that giteaConfig in the API payload carries no flags.

* fix(ui): surface the mirror-options filter on desktop, and on organizations

The overrides filter only existed inside the mobile filter drawer, so on
desktop the Repositories page showed a "Custom" badge on overridden rows
with no way to filter to them. Being able to answer "which repos deviate
from my defaults" on this page is the reason the Configuration-page
listing was dropped, so desktop could not do the one job it was given.

Adds the control to the desktop filter row next to status and sort, using
the bare Select-with-placeholder style those use rather than the drawer's
labelled markup. The mobile control is unchanged.

The Organizations page had a wider version of the same gap: it renders the
"Custom options" badge but never had this filter on either layout, and
OrganizationsList did not filter on it at all. Added the predicate plus
the control in both its layouts, so the two pages behave the same.

Also folds hasOverrides into activeFilterCount on both pages and into the
Organizations clear-all reset. Without that the mobile filter badge
undercounted an active overrides filter, and clearing filters left it set.

* fix(ui): stop double-applying the mirrorMetadata switch when reading config

mirrorOptionsToFlags ANDed each metadata component with mirrorMetadata on
the way in. That is a write-path rule: mapUiToDbConfig already applies it
when persisting, so a config saved through the settings UI has it baked
into the stored flag. Applying it again on read double-applies it.

The read path does not need it. mapDbToUiConfig puts the raw stored values
into metadataComponents, so metadataComponents.issues is exactly
giteaConfig.mirrorIssues, which is the field gitea.ts and
gitea-enhanced.ts read. Mapping the components straight through is 1:1
with runtime behavior.

Double-applying was invisible while the stored state was self-consistent,
since false && false is still false. It diverged when the state did not
come from the UI write path, which env vars allow: MIRROR_METADATA=false
with MIRROR_ISSUES=true stores mirrorMetadata:false, mirrorIssues:true.
The runtime mirrors issues; the dialog reported off. Measured against a
config in that state, five flags misreported.

I previously described this as an unrecoverable loss in the API shape.
That was wrong. Both values reach the client separately and unmerged, so
the payload carries full information and the loss was one we introduced.

Rewrites the test that asserted the derived flags match what
mapUiToDbConfig would persist. Comparing against the write derivation is
what pinned the bug in place. It now asserts the property that matters,
that flags derived from the API payload equal the stored flags the runtime
reads, plus a case checking agreement with resolveMirrorOptions on an
inconsistent config. Both fail if the AND returns.
2026-08-20 06:03:19 +05:30
2025-07-11 01:04:50 +05:30
2025-05-18 09:31:23 +05:30
2025-11-06 07:43:14 +05:30
2025-10-31 09:00:18 +05:30
2025-10-24 07:57:30 +05:30
2025-10-22 13:46:30 +05:30
2026-03-01 07:35:13 +05:30
2025-10-24 07:57:30 +05:30
2025-05-18 09:31:23 +05:30
2025-07-16 22:51:47 +05:30
2025-07-10 21:44:35 +05:30
2025-07-10 23:15:37 +05:30
2025-07-07 08:50:54 +05:30
2026-08-07 17:03:41 +05:30
2025-07-11 01:17:54 +05:30

Gitea Mirror Logo

Gitea Mirror

Automatically mirror repositories from GitHub to your self-hosted Gitea/Forgejo instance.

release build container license

🚀 Quick Start

# Fastest way - using the simplified Docker setup
docker compose -f docker-compose.alt.yml up -d

# Access at http://localhost:4321

First user signup becomes admin. Configure GitHub and Gitea/Forgejo through the web interface!

📖 Full documentation - quickstart, deployment, configuration, notifications, SSO and more. Offline copies live in docs/.

Dashboard Dashboard Mobile

✨ Features

  • 🔁 Mirror public, private, and starred GitHub repos to Gitea/Forgejo
  • 🏛️ GitHub Enterprise support - Works with GHES and GHEC with data residency via GH_API_URL
  • 🏢 Mirror entire organizations with flexible strategies
  • 🎯 Custom destination control for repos and organizations
  • 📦 Git LFS support - Mirror large files with Git LFS
  • 📝 Metadata mirroring - Issues, pull requests (as issues), labels, milestones, releases, wiki
  • 🚫 Repository ignore - Mark specific repos to skip
  • 🔐 Secure authentication with Better Auth (email/password, SSO, OIDC, header auth)
  • 🔔 Notifications - ntfy, Apprise, Gotify, or any webhook with signed payloads
  • 📊 Real-time dashboard with activity logs
  • ⏱️ Scheduled automatic mirroring with configurable intervals
  • 🔄 Auto-discovery - Automatically import new GitHub repositories
  • 🧹 Repository cleanup - Auto-remove repos deleted from GitHub
  • 🎯 Proper mirror intervals - Respects configured sync intervals
  • 🛡️ Force-push protection - Smart detection with backup-on-demand or block-and-approve modes
  • 🗑️ Automatic database cleanup with configurable retention
  • 🐳 Dockerized with multi-arch support (AMD64/ARM64)

📸 Screenshots

Repositories Repositories Mobile
Organisations Organisations Mobile

Installation

We provide two Docker Compose options:

Option 1: Quick Start (docker-compose.alt.yml)

Perfect for trying out Gitea Mirror or simple deployments:

# Clone repository
git clone https://github.com/RayLabsHQ/gitea-mirror.git
cd gitea-mirror

# Start with simplified setup
docker compose -f docker-compose.alt.yml up -d

# Access at http://localhost:4321

Features:

  • ✅ Pre-built image - no building required
  • ✅ Minimal configuration needed
  • ✅ Data stored in ./data directory
  • ✅ Configure everything through web UI
  • ✅ Automatic user/group ID mapping

Best for:

  • First-time users
  • Testing and evaluation
  • Simple deployments
  • When you prefer web-based configuration

Option 2: Full Setup (docker-compose.yml)

For production deployments with environment-based configuration:

# Start with full configuration options
docker compose up -d

Features:

  • ✅ Build from source or use pre-built image
  • ✅ Complete environment variable configuration
  • ✅ Support for custom CA certificates
  • ✅ Advanced mirror settings (forks, wiki, issues)
  • ✅ Multi-registry support

Best for:

  • Production deployments
  • Automated/scripted setups
  • Advanced mirror configurations
  • When using self-signed certificates

Using Pre-built Image Directly

docker pull ghcr.io/raylabshq/gitea-mirror:latest

Configuration Options

Quick Start Configuration (docker-compose.alt.yml)

Minimal .env file (optional - has sensible defaults):

# Custom port (default: 4321)
PORT=4321

# User/Group IDs for file permissions (default: 1000)
PUID=1000
PGID=1000

# Session secret (auto-generated if not set)
BETTER_AUTH_SECRET=your-secret-key-change-this-in-production

All other settings are configured through the web interface after starting.

Full Setup Configuration (docker-compose.yml)

Supports extensive environment variables for automated deployment. See the full docker-compose.yml for all available options including GitHub tokens, Gitea URLs, mirror settings, and more.

📚 For a complete list of all supported environment variables, see the Environment Variables Documentation.

LXC Container (Proxmox)

# One-line install on Proxmox VE
bash -c "$(curl -fsSL https://raw.githubusercontent.com/community-scripts/ProxmoxVE/main/ct/gitea-mirror.sh)"

See the Proxmox VE Community Scripts for more details.

Nix/NixOS

Zero-configuration deployment with Nix:

# Run immediately - no setup needed!
nix run --extra-experimental-features 'nix-command flakes' github:RayLabsHQ/gitea-mirror

# Or build and run locally
nix build --extra-experimental-features 'nix-command flakes'
./result/bin/gitea-mirror

# Or install to profile
nix profile install --extra-experimental-features 'nix-command flakes' github:RayLabsHQ/gitea-mirror
gitea-mirror

NixOS users - add to your configuration:

{
  inputs.gitea-mirror.url = "github:RayLabsHQ/gitea-mirror";

  services.gitea-mirror = {
    enable = true;
    betterAuthUrl = "https://mirror.example.com";
    openFirewall = true;
  };
}

Secrets auto-generate, database auto-initializes. See NIX.md for quick reference or docs/NIX_DEPLOYMENT.md for full documentation.

Manual Installation

# Install Bun
curl -fsSL https://bun.sh/install | bash

# Setup and run
bun run setup
bun run dev

Usage

  1. First Time Setup

    • Navigate to http://localhost:4321
    • Create admin account (first user signup)
    • Configure GitHub and Gitea/Forgejo connections
  2. Mirror Strategies

    • Preserve Structure: Maintains GitHub organization structure
    • Single Organization: All repos go to one Gitea/Forgejo organization
    • Flat User: All repos under your Gitea/Forgejo user account
    • Mixed Mode: Personal repos in one org, organization repos preserve structure
  3. Customization

    • Click edit buttons on organization cards to set custom destinations
    • Override individual repository destinations in the table view
    • Starred repositories can go to a dedicated org or preserve source owner/org paths

Advanced Features

Git LFS (Large File Storage)

Mirror Git LFS objects along with your repositories:

  • Enable "Mirror LFS" option in Settings → Mirror Options
  • Requires Gitea/Forgejo server with LFS enabled (LFS_START_SERVER = true)
  • Requires Git v2.1.2+ on the server

Metadata Mirroring

Transfer complete repository metadata from GitHub to Gitea/Forgejo:

  • Issues - Mirror all issues with comments and labels
  • Pull Requests - Transfer PR discussions to Gitea/Forgejo
  • Labels - Preserve repository labels
  • Milestones - Keep project milestones
  • Wiki - Mirror wiki content
  • Releases - Transfer GitHub releases with assets

Enable in Settings → Mirror Options → Mirror metadata

Repository Management

  • Ignore Status - Mark repositories to skip from mirroring
  • Automatic Cleanup - Configure retention period for activity logs
  • Scheduled Sync - Set custom intervals for automatic mirroring

Automatic Syncing & Synchronization

Gitea Mirror provides powerful automatic synchronization features:

Features (v3.4.0+)

  • Auto-discovery: Automatically discovers and imports new GitHub repositories
  • Repository cleanup: Removes repositories that no longer exist in GitHub
  • Proper intervals: Mirrors respect your configured sync intervals (not Gitea/Forgejo's default 24h)
  • Smart scheduling: Only syncs repositories that need updating
  • Auto-start on boot (v3.5.3+): Automatically imports and mirrors all repositories when SCHEDULE_ENABLED=true or GITEA_MIRROR_INTERVAL is set - no manual clicks required!

Navigate to the Configuration page and enable "Automatic Syncing" with your preferred interval.

Configuration via Environment Variables

🚀 Set it and forget it! With these environment variables, Gitea Mirror will automatically:

  1. Import all your GitHub repositories on startup (no manual import needed!)
  2. Mirror them to Gitea/Forgejo immediately
  3. Keep them synchronized based on your interval
  4. Auto-discover new repos you create/star on GitHub
  5. Clean up repos you delete from GitHub
# Option 1: Enable automatic scheduling (triggers auto-start)
SCHEDULE_ENABLED=true
SCHEDULE_INTERVAL=3600        # Check every hour (or use cron: "0 * * * *")

# Option 2: Set mirror interval (also triggers auto-start)
GITEA_MIRROR_INTERVAL=8h     # Every 8 hours
# Other examples: 5m, 30m, 1h, 24h, 1d, 7d

# Advanced: Use cron expressions for specific times
SCHEDULE_INTERVAL="0 2 * * *"  # Daily at 2 AM (optimize bandwidth usage)

# Auto-import new repositories (default: true)
AUTO_IMPORT_REPOS=true

# Auto-cleanup orphaned repositories
CLEANUP_DELETE_IF_NOT_IN_GITHUB=true
CLEANUP_ORPHANED_REPO_ACTION=archive  # 'archive' (recommended) or 'delete'
CLEANUP_DRY_RUN=false                 # Set to true to test without changes

Important Notes:

  • Auto-Start: When SCHEDULE_ENABLED=true or GITEA_MIRROR_INTERVAL is set, the service automatically imports all GitHub repositories and mirrors them on startup. No manual "Import" or "Mirror" button clicks required!
  • The scheduler checks every minute for tasks to run. The GITEA_MIRROR_INTERVAL determines how often each repository is actually synced. For example, with 8h, each repo syncs every 8 hours from its last successful sync.
  • Large repo bootstrap: For first-time mirroring of large repositories (especially with metadata/LFS), avoid very short intervals (for example 5m). Start with a longer interval (1h to 8h) or temporarily disable scheduling during the initial import/mirror run, then enable your regular interval after the first pass completes.
  • Why this matters: If your Gitea/Forgejo instance takes a long time to complete migrations/imports, aggressive schedules can cause repeated retries and duplicate-looking mirror attempts.

🛡️ Backup Protection Features:

  • No Accidental Deletions: Repository cleanup is automatically skipped if GitHub is inaccessible (account deleted, banned, or API errors)
  • Archive Never Deletes Data: The archive action preserves all repository data:
    • Regular repositories: Made read-only using Gitea/Forgejo's archive feature
    • Mirror repositories: Renamed with archived- prefix (Gitea/Forgejo API limitation prevents archiving mirrors)
    • Failed operations: Repository remains fully accessible even if marking as archived fails
  • Manual Sync on Demand: Archived mirrors stay in Gitea/Forgejo with automatic syncs disabled; trigger Manual Sync from the Repositories page whenever you need fresh data.
  • The Whole Point of Backups: Your Gitea/Forgejo mirrors are preserved even when GitHub sources disappear - that's why you have backups!
  • Strongly Recommended: Always use CLEANUP_ORPHANED_REPO_ACTION=archive (default) instead of delete

GitHub Enterprise (GHES / GHEC with Data Residency)

Gitea Mirror works with non-github.com GitHub deployments. Point the client at your Enterprise API via the GH_API_URL environment variable:

# GitHub Enterprise Server (self-hosted)
GH_API_URL=https://ghe.example.com/api/v3

# GitHub Enterprise Cloud with data residency
GH_API_URL=https://api.TENANT.ghe.com

Standard GitHub Enterprise Cloud on github.com needs no override. Use a token issued by the target Enterprise instance for GITHUB_TOKEN.

Troubleshooting

Reverse Proxy Configuration

If you run behind a reverse proxy on a subpath (for example https://git.example.com/mirror), configure:

# BASE_URL handles the path prefix — auth URLs stay as origin only
BASE_URL=/mirror
BETTER_AUTH_URL=https://git.example.com
PUBLIC_BETTER_AUTH_URL=https://git.example.com
BETTER_AUTH_TRUSTED_ORIGINS=https://git.example.com
# → Auth endpoints resolve to: https://git.example.com/mirror/api/auth/*

Notes:

  • BASE_URL sets the application path prefix.
  • BETTER_AUTH_URL and PUBLIC_BETTER_AUTH_URL should be origin only (e.g. https://git.example.com). Do not include the base path — it is applied automatically from BASE_URL. Any path accidentally included is stripped.
  • BETTER_AUTH_TRUSTED_ORIGINS should also contain origins only (no path).
  • BASE_URL is runtime configuration, so prebuilt registry images can be reused across different subpaths.

Mirror Token Rotation (GitHub Token Changed)

For existing pull-mirror repositories, changing the GitHub token in Gitea Mirror does not always update stored mirror credentials in Gitea/Forgejo for already-created repositories.

If sync logs show authentication failures (for example terminal prompts disabled), do one of the following:

  1. In Gitea/Forgejo, open repository Settings → Mirror Settings and update the mirror authorization password/token.
  2. Or delete and re-mirror the repository so it is recreated with current credentials.

Re-sync Metadata After Changing Mirror Options

If you enable metadata options (issues/PRs/labels/milestones/releases) after repositories were already mirrored:

  1. Go to Repositories, select the repositories, and click Sync to run a fresh sync pass.
  2. For a full metadata refresh, use Re-run Metadata on selected repositories. This clears metadata sync state for those repos and immediately starts Sync.
  3. If some repositories still miss metadata, reset metadata sync state in SQLite and sync again:
sqlite3 data/gitea-mirror.db "UPDATE repositories SET metadata = NULL;"

This clears per-repository metadata completion flags so the next sync can re-run metadata import steps.

Mirror Interval vs Gitea/Forgejo MIN_INTERVAL

Gitea Mirror treats the interval configured in Configuration (or GITEA_MIRROR_INTERVAL) as the source of truth and applies it to mirrored repositories during sync.

If your Gitea/Forgejo server has mirror.MIN_INTERVAL set to a higher value (for example 24h) and Gitea Mirror is set lower (for example 8h), sync/mirror operations can fail when updating mirror settings.

To avoid this:

  1. Set Gitea Mirror interval to a value greater than or equal to your server MIN_INTERVAL.
  2. Do not rely on manual per-repository mirror interval edits in Gitea/Forgejo, as they will be overwritten on sync.

Development

# Install dependencies
bun install

# Run development server
bun run dev

# Run tests
bun test

# Build for production
bun run build

Technologies

  • Frontend: Astro, React, Shadcn UI, Tailwind CSS v4
  • Backend: Bun runtime, SQLite, Drizzle ORM
  • APIs: GitHub (Octokit), Gitea/Forgejo REST API
  • Auth: Better Auth with session-based authentication

Security

Token Encryption

  • All GitHub and Gitea/Forgejo API tokens are encrypted at rest using AES-256-GCM
  • Encryption is automatic and transparent to users
  • Set ENCRYPTION_SECRET environment variable for production deployments
  • Falls back to BETTER_AUTH_SECRET if not set

Password Security

  • User passwords are securely hashed by Better Auth
  • Never stored in plaintext
  • Secure cookie-based session management

Admin Password Recovery (CLI)

If email delivery is not configured, an admin with server access can reset a user password from the command line:

bun run reset-password -- --email=user@example.com --new-password='new-secure-password'

What this does:

  • Updates the credential password hash for the matching user
  • Creates a credential account if one does not already exist
  • Invalidates all active sessions for that user (forces re-login)

Use this only from trusted server/admin environments.

Authentication

Gitea Mirror supports multiple authentication methods. Email/password authentication is the default and always enabled.

1. Email & Password (Default)

The standard authentication method. First user to sign up becomes the admin.

2. Single Sign-On (SSO) with OIDC

Enable users to sign in with external identity providers like Google, Azure AD, Okta, Authentik, or any OIDC-compliant service.

Configuration:

  1. Navigate to Settings → Authentication & SSO
  2. Click "Add Provider"
  3. Enter your OIDC provider details:
    • Issuer URL (e.g., https://accounts.google.com)
    • Client ID and Secret from your provider
    • Use the "Discover" button to auto-fill endpoints

Redirect URL for your provider:

https://your-domain.com/api/auth/sso/callback/{provider-id}

Need help? The SSO & OIDC guide now includes a working Authentik walkthrough plus troubleshooting tips. If you upgraded from a version earlier than v3.8.10 and see TypeError … url.startsWith after the callback, delete the old provider and add it again using the Discover button (see #73 and #122).

3. Header Authentication (Reverse Proxy)

Perfect for automatic authentication when using reverse proxies like Authentik, Authelia, or Traefik Forward Auth.

Environment Variables:

# Enable header authentication
HEADER_AUTH_ENABLED=true

# Header names (customize based on your proxy)
HEADER_AUTH_USER_HEADER=X-Authentik-Username
HEADER_AUTH_EMAIL_HEADER=X-Authentik-Email
HEADER_AUTH_NAME_HEADER=X-Authentik-Name

# Auto-provision new users
HEADER_AUTH_AUTO_PROVISION=true

# Restrict to specific email domains (optional)
HEADER_AUTH_ALLOWED_DOMAINS=example.com,company.org

How it works:

  • Users authenticated by your reverse proxy are automatically logged in
  • No additional login step required
  • New users can be auto-provisioned if enabled
  • Falls back to regular authentication if headers are missing

Example Authentik Configuration:

# In your reverse proxy configuration
proxy_set_header X-Authentik-Username $authentik_username;
proxy_set_header X-Authentik-Email $authentik_email;
proxy_set_header X-Authentik-Name $authentik_name;

4. OAuth Applications (Act as Identity Provider)

Gitea Mirror can also act as an OIDC provider for other applications. Register OAuth applications in Settings → Authentication & SSO → OAuth Applications tab.

Use cases:

  • Allow other services to authenticate using Gitea Mirror accounts
  • Create service-to-service authentication
  • Build integrations with your Gitea Mirror instance

Known Limitations

Pull Request Mirroring Implementation

Pull requests cannot be created as actual PRs in Gitea/Forgejo due to API limitations. Instead, they are mirrored as enriched issues with comprehensive metadata.

Why real PR mirroring isn't possible:

  • Gitea/Forgejo's API doesn't support creating pull requests from external sources
  • Real PRs require actual Git branches with commits to exist in the repository
  • Would require complex branch synchronization and commit replication
  • The mirror relationship is one-way (GitHub → Gitea/Forgejo) for repository content

How we handle Pull Requests: PRs are mirrored as issues with rich metadata including:

  • 🏷️ Special "pull-request" label for identification
  • 📌 [PR #number] prefix in title with status indicators ([MERGED], [CLOSED])
  • 👤 Original author and creation date
  • 📝 Complete commit history (up to 10 commits with links)
  • 📊 File changes summary with additions/deletions
  • 📁 List of modified files (up to 20 files)
  • 💬 Original PR description and comments
  • 🔀 Base and head branch information
  • ✅ Merge status tracking

This approach preserves all important PR information while working within Gitea/Forgejo's API constraints. The PRs appear in the issue tracker with clear visual distinction and comprehensive details.

Contributing

Contributions are welcome! Please read our Contributing Guidelines for details on our code of conduct and the process for submitting pull requests.

License

GNU Affero General Public License v3.0 (AGPL-3.0) - see LICENSE file for details.

Star History

Star History Chart

Support

S
Description
Gitea Mirror auto-syncs GitHub repos to your self-hosted Gitea/Forgejo, with a sleek Web UI and easy Docker deployment.
https://gitea-mirror.raylabs.io Readme
38 MiB
0 Stars 1 Watchers 0 Forks
Languages
TypeScript 84.7%
MDX 6.9%
Nix 5.1%
Astro 1.7%
Shell 1.2%
Other 0.4%