SetFork Docs

Mirror to GitHub/GitLab

A push copy of your list on an external forge: setup, tokens, what travels, and how to read the errors.

A list can be mirrored to a repository on GitHub, GitLab or any other forge that speaks git over HTTPS. SetFork stays the source of truth and the mirror is a shop window and a backup: after every new version SetFork pushes the changes out on its own.

The mirror is one-way. Edits made on the forge never travel back and will be overwritten by the next push. If you want to edit from a terminal, push to SetFork itself.

What travels

TravelsStays behind
The main branch with the full version historySuggestion branches (drafts)
Version tags v1, v2, … and release tagsSuggestions, discussions, reviews
README.md — the shop windowStars, watchers, runs
list.json — the canonical listList settings and access rights
.gitattributes

Exactly as when GitHub and GitLab mirror each other, only git objects travel: commits, branches and tags. Everything that lives around the repository stays in SetFork.

Step 1. Create an empty repository on the forge

GitHubNew repository, with no README, .gitignore or licence.

GitLabNew project → Create blank project, and clear the Initialize repository with a README checkbox.

⚠️ Do not create the repository with a README. The first mirror push force-overwrites any foreign history; GitHub allows that, but on GitLab the main branch is protected by default and force pushes to it are refused — the first push would fail.

Mirror a private list into a private repository: SetFork does not check visibility on the forge side.

Step 2. Get a token

You never need to enter a username: SetFork fills it in by hostname (x-access-token for GitHub, oauth2 for GitLab, git for everything else).

GitHub

A fine-grained token — Settings → Developer settings → Personal access tokens → Fine-grained tokens → Generate new token:

  • Repository accessOnly select repositories → pick the mirror repository;
  • Permissions → Repository permissions → ContentsRead and write;
  • expiry is up to you; once it lapses the mirror stops pushing and records the error in the settings.

A classic token works too — it needs the repo scope.

GitLab

Project → Settings → Access tokens → Add new token:

  • RoleMaintainer: the main branch is protected by default, and only Maintainers and Owners may push to a protected branch;
  • Scopeswrite_repository (read-write git access over HTTPS).

A deploy token with the write_repository scope works as well.

Other forges

Gitea, Codeberg, Forgejo, Bitbucket: any token with write access to the repository. Anything that accepts https://user:token@host/path.git will work.

Step 3. Turn the mirror on in SetFork

Open the list → Settings → Mirror and fill in:

  • Repository URLhttps://github.com/you/your-repo (a trailing .git is fine). Only https://, and no credentials in the address itself;
  • Access token — from step 2. It is encrypted and never shown again. On later saves an empty field means "keep the current one".

Save sends the first push immediately, and the status appears next to it: the time of the last sync, or the full text of the error.

From then on SetFork pushes by itself — after every event that moves main:

  • publishing a new version in the interface;
  • a git push into the list;
  • merging a suggestion, including a manual conflict resolution;
  • creating a release tag.

The Sync now button pushes on demand — use it to retry a failed push as well. There are no automatic retries: after an error the next attempt happens on the next version or on that button.

Disable mirror clears the URL and the token. The repository on the forge is left untouched.

How the mirror catches up

The push uses an explicit set of refs:

git push --prune <remote> \
  +refs/heads/main:refs/heads/main \
  +refs/tags/*:refs/tags/*

Three consequences follow:

  • + (force) — the mirror must match SetFork. Hand-made commits on the forge's main are lost at the next push.
  • --prune — a tag removed in SetFork disappears from the mirror too. Unrelated branches on the forge are left alone: only main falls under the rule.
  • No --mirror: it pushes everything under refs/, and GitHub's refs/pull/* are read-only — such a push would fail halfway.

Errors and what they mean

Git's error text is shown in the settings in full, with credentials stripped out.

ErrorCause
src refspec refs/heads/main does not match anyThe list has no versions yet — there is nothing to publish
Write access to repository not granted (403)The token lacks Contents: Read and write, or it does not cover this repository
Invalid username or token / HTTP Basic: Access deniedThe token expired, was revoked, or was copied incompletely
Repository not foundA typo in the URL, or a private repository the token cannot reach
pre-receive hook declined (GitLab)The token's role is below Maintainer, or the repository already had history and a force push into a protected branch is required
timeout after 60sThe forge is unreachable from the server; press "Sync now" later
SETFORK_MIRROR_SECRET is not setSelf-hosted installs only — see below

Self-hosting

The mirror needs a shared token-encryption secret — the SETFORK_MIRROR_SECRET variable, identical for the application and for the git core: the application encrypts the token, the core decrypts it when pushing. Without the variable the section still works, but every push honestly records the error in the status. The secret is a random string; changing it makes every stored token unreadable (they have to be entered again).

The server also needs outbound HTTPS access to the forge.

On this page