# Repository mirroring Any repository can be kept in sync with a repository on another forge (GitHub, GitLab, Codeberg, Gitea, Forgejo, Bitbucket, or any smart-HTTP git host). Admins set it up under **Settings → Mirroring**, also on repositories that already exist. ## Setup - **Remote URL**: a public `https://` git URL. IP literals, private networks and this forge's own hosts are refused, and every redirect is re-checked. - **Personal access token** (optional): sent as HTTP Basic auth (username `git` unless set). It needs read access to pull and write (contents) access to push. It is stored encrypted with `SECRETS_KEY` and never shown again. Changing the remote URL requires entering the token again, so a stored token can never be sent to a different host. - **Direction**: bidirectional, pull only, or push only. - **Branches**: globs (`main, release/*, !wip/**`); empty mirrors every branch. Tags can be included. - **Propagate branch deletions** (off by default). ## How a sync works Each repository has a `RepoMirror` Durable Object holding the settings, the ref state of the last sync and a single alarm. A sync: 1. Lists refs on both sides and copies the remote's new objects into hidden `refs/gitfor/mirror/*` refs. Push protection scans that pack exactly as for a push; if it finds secrets nothing is synced. 2. Decides every ref: - Equal: nothing to do. - One side is ahead: fast-forward the other side. - Only on one side: create it on the other side. With deletion propagation on, a ref that is missing on one side and unchanged on the other since the last sync is deleted instead. A delete never drops new commits. - Diverged (bidirectional): three-way merge in the Worker into a merge commit (parents: this repository's tip, then the remote's tip) that both sides fast-forward to. - Diverged (one-way) or unrelated histories: a conflict, unless the strategy prefers the side being written to, which then overwrites it. - Tags are never merged or deleted. 3. Applies updates to this repository through the same gates as a git push, acting as the admin who configured the mirror with write permission. Hidden refs are never touched, branch protection applies (a branch that requires pull requests is never updated), and applied refs fire normal push events, so CI, webhooks and integrations run. 4. Pushes this repository's changes and merge results to the remote. ### Conflict strategies | Strategy | Overlapping edits | | --------------- | --------------------------------------------------------------------- | | `merge` | Not merged. The remote tip is parked on `mirror-conflicts/`. | | `prefer-local` | Conflicting lines keep this repository's side; other edits all merge. | | `prefer-remote` | Conflicting lines keep the remote's side; other edits all merge. | Clean (non-overlapping) changes always merge, whatever the strategy. A parked conflict shows on the settings page with a compare link. Merge `mirror-conflicts/` into the branch (in a pull request or locally) and the next sync pushes the result and deletes the parked branch. Conflict branches are never pushed to the remote. Before each run the mirror re-checks that its configuring admin still administers the repository, that the repository is not archived or taken down, and that a private remote (one that refuses anonymous reads) is only pulled into a private repository. ## Schedule and triggers | Trigger | Free | Pro | | ---------------------------------------- | ---------- | ----------- | | Scheduled sync | daily | hourly | | Push, webhook and "Sync now" (coalesced) | 50 per day | 500 per day | Instances without Stripe billing get the Pro cadence. Triggered syncs are debounced by 10 seconds and run at most once a minute, so a burst of pushes costs a single run. Pushes the mirror itself wrote do not trigger another run. Past the daily budget, triggers wait for the next scheduled run. ### Webhook The settings page shows a payload URL, `/integrations/hooks/mirror/`, and a secret, shown once. Add it as a push webhook on the remote: - GitHub, Bitbucket: `X-Hub-Signature-256` HMAC (content type `application/json`). - Gitea, Forgejo, Gogs: `X-Gitea-Signature` / `X-Forgejo-Signature` HMAC. - GitLab: paste the secret as the secret token (`X-Gitlab-Token`). - Anything else: `Authorization: Bearer ` or `?secret=`. ## Limits Per run: 200 refs staged, 100 branch comparisons, 10 merges, 1 GiB of objects. The rest is deferred to the next run. Merges that are too large for the Worker (more than 1,500 trees, 200 files or 1 MiB files) are treated as conflicts.