docs: reframe as generic SVN-Git mirror, Gitea as example integration

This commit is contained in:
2026-08-23 19:21:07 +02:00
parent 2b9ef5f2af
commit 86d34cf3b7
3 changed files with 91 additions and 37 deletions
+1 -1
View File
@@ -91,6 +91,6 @@ Licensee: _______________________ Date: __________
Schedule A -- Software Schedule A -- Software
- Project name: gitea-svn-mirror - Project name: svn-git-mirror
- Repository: [URL or path] - Repository: [URL or path]
- Commit / version: [hash or tag] - Commit / version: [hash or tag]
+78 -29
View File
@@ -1,6 +1,12 @@
# gitea-svn-mirror # svn-git-mirror
Py sidecar SVN↔Git bidirectional mirror for Gitea. Uses own mapping DB (not `git svn`) so Git commit hashes never change. Py sidecar SVN↔Git bidirectional mirror. Uses own mapping DB (not `git svn`) so Git commit hashes never change.
Works with any Git server that exposes bare repos on disk (Gitea, Forgejo,
GitLab, bare `git init --bare`, etc.). The Gitea-specific integration code
(`svn_mirror/gitea.py`, `svn_mirror/webhook.py`) serves as a **working
example** and can be adapted to other Git servers — see
[Adapting to other Git servers](#adapting-to-other-git-servers).
## Architecture ## Architecture
@@ -11,7 +17,7 @@ Py sidecar SVN↔Git bidirectional mirror for Gitea. Uses own mapping DB (not `g
└──────────────┘ │ │ └──────────────┘ │ │
│ ┌──────────────┐ │ │ ┌──────────────┐ │
┌──────────────┐ git push │ │ canonical │ │ ┌──────────────┐ git push │ │ canonical │ │
│ Gitea │ ◄─────────── │ │ bare repo │ │ │ Git server │ ◄─────────── │ │ bare repo │ │
│ (webhook) │ ────────────► │ └──────────────┘ │ │ (webhook) │ ────────────► │ └──────────────┘ │
└──────────────┘ git fetch │ │ └──────────────┘ git fetch │ │
│ SQLite mapping │ │ SQLite mapping │
@@ -28,7 +34,7 @@ Py sidecar SVN↔Git bidirectional mirror for Gitea. Uses own mapping DB (not `g
- Python 3.11+ - Python 3.11+
- `git` + `git-svn` (for one-time `git svn clone --no-metadata` during init) - `git` + `git-svn` (for one-time `git svn clone --no-metadata` during init)
- `svn` CLI (Subversion client) - `svn` CLI (Subversion client)
- Gitea instance (bare repos accessible on disk, not just via HTTP API) - A Git server with bare repos accessible on disk (Gitea, Forgejo, GitLab, or plain `git init --bare`)
- UTF-8 system locale (SVN needs it for non-ASCII filenames) - UTF-8 system locale (SVN needs it for non-ASCII filenames)
### Install system deps ### Install system deps
@@ -90,10 +96,14 @@ mirrors:
username: # SVN login (omit if anonymous access) username: # SVN login (omit if anonymous access)
password: # SVN password password: # SVN password
# ── Git server integration ──────────────────────────────────
# The config below uses "gitea" as the section name for historical
# reasons. In practice this works with any Git server that exposes
# bare repos on disk. See "Adapting to other Git servers" below.
gitea: gitea:
owner: myorg owner: myorg
repo: myproject repo: myproject
repos_path: /var/lib/gitea/data/repositories # path to Gitea's bare repos repos_path: /var/lib/gitea/data/repositories # path to bare repos on disk
webhook_secret: "choose-a-random-secret" # shared HMAC secret webhook_secret: "choose-a-random-secret" # shared HMAC secret
webhook_host: "0.0.0.0" webhook_host: "0.0.0.0"
webhook_port: 8080 webhook_port: 8080
@@ -105,16 +115,19 @@ mirrors:
sync_interval: 120 # SVN→Git poll interval (seconds) sync_interval: 120 # SVN→Git poll interval (seconds)
``` ```
### `repos_path` — finding Gitea's repos on disk ### `repos_path` — finding your Git server's repos on disk
The sidecar accesses Gitea's repos **on disk**, not via HTTP API. Find the path: The sidecar accesses your Git server's repos **on disk**, not via HTTP API.
Find the path:
| Setup | Typical path | | Setup | Typical path |
|-------|-------------| |-------|-------------|
| Gitea binary | `/var/lib/gitea/data/repositories` | | Gitea binary | `/var/lib/gitea/data/repositories` |
| Gitea Docker | `/data/git/repositories` | | Gitea Docker | `/data/git/repositories` |
| Forgejo | `/var/lib/forgejo/data/repositories` |
| GitLab | `/var/opt/gitlab/git-data/repositories` |
| `git` user home | `/home/git/repositories` | | `git` user home | `/home/git/repositories` |
| Gitea LXC | `/home/gitea/repo/` | | Plain bare repos | wherever you ran `git init --bare` |
If unset the tool tries common defaults. Set it explicitly to be safe. If unset the tool tries common defaults. Set it explicitly to be safe.
@@ -202,7 +215,7 @@ on the same interval.
```ini ```ini
# /etc/systemd/system/svn-mirror.service # /etc/systemd/system/svn-mirror.service
[Unit] [Unit]
Description=gitea-svn-mirror daemon Description=svn-git-mirror daemon
After=network-online.target After=network-online.target
[Service] [Service]
@@ -227,14 +240,25 @@ systemctl enable --now svn-mirror
python -m svn_mirror webhook --config /etc/svn-mirror/config.yml python -m svn_mirror webhook --config /etc/svn-mirror/config.yml
``` ```
Listens for Gitea push webhooks on `POST /webhook`. When a push arrives Listens for push webhooks on `POST /webhook`. When a push arrives
it fetches the new commits from Gitea and runs `sync_git_to_svn`. it fetches the new commits from the Git server and runs `sync_git_to_svn`.
**Gitea webhook config:** Go to repo → Settings → Webhooks → Add: **Example — Gitea webhook config:** Go to repo → Settings → Webhooks → Add:
- Target URL: `http://your-server:8080/webhook` - Target URL: `http://your-server:8080/webhook`
- Secret: same as `webhook_secret` in config - Secret: same as `webhook_secret` in config
- Events: "Push" - Events: "Push"
**Example — GitLab webhook config:** Go to repo → Settings → Webhooks:
- URL: `http://your-server:8080/webhook`
- Secret token: same as `webhook_secret` in config
- Trigger: "Push events"
> **Note:** The webhook receiver currently parses the Gitea payload format
> (`X-Gitea-Signature` header, `repository.owner.login` field). To use
> another Git server, adapt the header name and payload parsing in
> `svn_mirror/webhook.py`. See
> [Adapting to other Git servers](#adapting-to-other-git-servers).
### Mode C: systemd service (recommended) ### Mode C: systemd service (recommended)
The daemon handles **bidirectional** sync: SVN→Git polling AND Git→SVN push The daemon handles **bidirectional** sync: SVN→Git polling AND Git→SVN push
@@ -243,10 +267,10 @@ on each cycle. One service is all you need.
#### Quick install via script #### Quick install via script
Use the provided install script to generate and enable the systemd service Use the provided install script to generate and enable the systemd service
running as a given user (e.g. `gitea`): running as a given user (e.g. `git`):
```bash ```bash
sudo ./deploy/install.sh --install-dir /opt/svn-git-server --user gitea sudo ./deploy/install.sh --install-dir /opt/svn-git-server --user git
``` ```
This will: This will:
@@ -264,6 +288,10 @@ Options:
| `--venv DIR` | `<install-dir>/.venv` | Virtualenv path | | `--venv DIR` | `<install-dir>/.venv` | Virtualenv path |
| `--uninstall` | — | Remove service instead of installing | | `--uninstall` | — | Remove service instead of installing |
> **Note:** The default `--user` is `gitea` for historical reasons. Use
> whatever user owns your Git server's repos (e.g. `git`, `forgejo`,
> `gitlab`).
After install, start the service: After install, start the service:
```bash ```bash
@@ -288,27 +316,30 @@ sudo ./deploy/install.sh --uninstall
screen -dmS svn-mirror python3.11 -m svn_mirror daemon --config /etc/svn-mirror/config.yml screen -dmS svn-mirror python3.11 -m svn_mirror daemon --config /etc/svn-mirror/config.yml
``` ```
### Mode D: Gitea post-receive hook (filesystem) ### Mode D: Post-receive hook (filesystem)
For setups without network access to a webhook receiver, install a For setups without network access to a webhook receiver, install a
post-receive hook in Gitea's repo that calls sync directly: post-receive hook in your Git server's repo that calls sync directly.
**Example — Gitea/Forgejo:**
```bash ```bash
# Create hook (one-time setup) # Create hook (one-time setup)
tee /home/gitea/repo/inpixal/pixkit.git/hooks/post-receive.d/sync-to-svn << 'HOOK' tee /home/gitea/repo/myorg/myproject.git/hooks/post-receive.d/sync-to-svn << 'HOOK'
#!/bin/bash #!/bin/bash
cd /home/git/apps/svn-git-server cd /opt/svn-git-server
. .venv/bin/activate . .venv/bin/activate
python3.11 -m svn_mirror sync --mirror pixkit --config /etc/svn-mirror/config.yml python3.11 -m svn_mirror sync --mirror my-project --config /etc/svn-mirror/config.yml
HOOK HOOK
chmod +x /home/gitea/repo/inpixal/pixkit.git/hooks/post-receive.d/sync-to-svn chmod +x /home/gitea/repo/myorg/myproject.git/hooks/post-receive.d/sync-to-svn
``` ```
Each `git push` to Gitea then triggers an immediate SVN sync. Each `git push` to the Git server then triggers an immediate SVN sync.
**Note:** Direct filesystem `git push` to Gitea's bare repo is blocked by **Note:** Direct filesystem `git push` to a Git server's on-disk bare repo
Gitea's `pre-receive` hook. To force-push (e.g. initial import to Gitea), may be blocked by the server's `pre-receive` hook (Gitea and GitLab both
temporarily disable the hook: do this). The daemon/sync uses its own `push_to_gitea()` to work around
this by using `git fetch` instead of `git push`. To force-push (e.g.
initial import), temporarily disable the hook:
```bash ```bash
mv /path/to/repo.git/hooks/pre-receive{,.disabled} mv /path/to/repo.git/hooks/pre-receive{,.disabled}
@@ -372,7 +403,7 @@ Shows the assessment report and what would be done, without making any changes.
| `sync` | Bidirectional sync (SVN→Git + Git→SVN) | | `sync` | Bidirectional sync (SVN→Git + Git→SVN) |
| `push` | Git→SVN direction only | | `push` | Git→SVN direction only |
| `daemon` | Continuous polling loop | | `daemon` | Continuous polling loop |
| `webhook` | Gitea webhook receiver | | `webhook` | Webhook receiver (Gitea format; adaptable) |
| `reconcile` | Fix divergence (see above) | | `reconcile` | Fix divergence (see above) |
## File layout ## File layout
@@ -401,7 +432,25 @@ Shows the assessment report and what would be done, without making any changes.
(the tool pipes password to stdin instead). (the tool pipes password to stdin instead).
- **All SVN branches become Git branches**: If the SVN repo uses branches for - **All SVN branches become Git branches**: If the SVN repo uses branches for
release tags, the Git canonical repo will contain hundreds of branches. release tags, the Git canonical repo will contain hundreds of branches.
Consider filtering via `refs/tags/` in the Gitea push refspec. Consider filtering via `refs/tags/` in the push refspec.
- **Gitea filesystem push**: Direct `git push` to Gitea's on-disk bare repo - **Git server filesystem push**: Direct `git push` to a Git server's
is blocked by Gitea's `pre-receive` hook. The daemon/sync uses its own on-disk bare repo may be blocked by the server's `pre-receive` hook
`push_to_gitea()` to work around this. (Gitea, GitLab). The daemon/sync uses its own `push_to_gitea()` to
work around this by using `git fetch` instead.
## Adapting to other Git servers
The core SVN↔Git sync engine is server-agnostic. The Gitea-specific
code is limited to two files and can be adapted to other Git servers
(Forgejo, GitLab, Gitea, plain bare repos, etc.):
| What | Where | What to change |
|-------|-------|----------------|
| Push to Git server | `svn_mirror/gitea.py``push_to_gitea()` | Uses `git fetch` into the bare repo (generic). The `_trigger_gitea_post_receive()` function calls the `gitea` binary — replace with your server's hook trigger or remove it. |
| Webhook receiver | `svn_mirror/webhook.py` | Parses `X-Gitea-Signature` header and Gitea JSON payload. Adapt the header name (`X-Gitlab-Token`, etc.) and payload fields (`repository.owner.login` → your server's equivalent). |
| Config section | `svn_mirror/config.py``GiteaConfig` | The `gitea:` config block. The fields (`owner`, `repo`, `repos_path`, `webhook_*`) are generic; only the section name is Gitea-specific. |
| Default repo paths | `svn_mirror/config.py``repo_dir` | Hardcoded Gitea defaults. Add your server's path or set `repos_path` explicitly. |
For **plain bare repos** (no Git server), no adaptation is needed — just
set `repos_path` to the directory containing your bare repos and skip
the webhook/post-receive integration (use the daemon or manual `sync`).
+12 -7
View File
@@ -1,4 +1,4 @@
# gitea-svn-mirror configuration # svn-git-mirror configuration
# Full reference: see README.md # Full reference: see README.md
data_dir: /var/svn-mirror data_dir: /var/svn-mirror
@@ -19,17 +19,22 @@ mirrors:
# branches: branches # branches: branches
# tags: tags # tags: tags
# ── Gitea integration ─────────────────────────────────── # ── Git server integration ─────────────────────────────
# Works with any Git server exposing bare repos on disk
# (Gitea, Forgejo, GitLab, plain bare repos, etc.).
# The section is named "gitea" for historical reasons.
gitea: gitea:
owner: myorg owner: myorg
repo: myproject repo: myproject
# Path to Gitea's on-disk bare repos. Required for # Path to bare repos on disk. Required for push/fetch
# push_to_gitea, fetch_from_gitea, and the webhook flow. # and the webhook flow.
# Typical locations: # Typical locations:
# Gitea binary → /var/lib/gitea/data/repositories # Gitea binary → /var/lib/gitea/data/repositories
# Gitea Docker → /data/git/repositories # Gitea Docker → /data/git/repositories
# git user → /home/git/repositories # Forgejo → /var/lib/forgejo/data/repositories
# GitLab → /var/opt/gitlab/git-data/repositories
# git user → /home/git/repositories
repos_path: /var/lib/gitea/data/repositories repos_path: /var/lib/gitea/data/repositories
# Webhook receiver (used by `webhook` command) # Webhook receiver (used by `webhook` command)