diff --git a/LICENSE b/LICENSE index b615e2e..41f8b36 100644 --- a/LICENSE +++ b/LICENSE @@ -91,6 +91,6 @@ Licensee: _______________________ Date: __________ Schedule A -- Software -- Project name: gitea-svn-mirror +- Project name: svn-git-mirror - Repository: [URL or path] - Commit / version: [hash or tag] diff --git a/README.md b/README.md index dda9398..f06bea9 100644 --- a/README.md +++ b/README.md @@ -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 @@ -11,7 +17,7 @@ Py sidecar SVN↔Git bidirectional mirror for Gitea. Uses own mapping DB (not `g └──────────────┘ │ │ │ ┌──────────────┐ │ ┌──────────────┐ git push │ │ canonical │ │ -│ Gitea │ ◄─────────── │ │ bare repo │ │ +│ Git server │ ◄─────────── │ │ bare repo │ │ │ (webhook) │ ────────────► │ └──────────────┘ │ └──────────────┘ git fetch │ │ │ SQLite mapping │ @@ -28,7 +34,7 @@ Py sidecar SVN↔Git bidirectional mirror for Gitea. Uses own mapping DB (not `g - Python 3.11+ - `git` + `git-svn` (for one-time `git svn clone --no-metadata` during init) - `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) ### Install system deps @@ -90,10 +96,14 @@ mirrors: username: # SVN login (omit if anonymous access) 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: owner: myorg 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_host: "0.0.0.0" webhook_port: 8080 @@ -105,16 +115,19 @@ mirrors: 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 | |-------|-------------| | Gitea binary | `/var/lib/gitea/data/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` | -| 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. @@ -202,7 +215,7 @@ on the same interval. ```ini # /etc/systemd/system/svn-mirror.service [Unit] -Description=gitea-svn-mirror daemon +Description=svn-git-mirror daemon After=network-online.target [Service] @@ -227,14 +240,25 @@ systemctl enable --now svn-mirror python -m svn_mirror webhook --config /etc/svn-mirror/config.yml ``` -Listens for Gitea push webhooks on `POST /webhook`. When a push arrives -it fetches the new commits from Gitea and runs `sync_git_to_svn`. +Listens for push webhooks on `POST /webhook`. When a push arrives +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` - Secret: same as `webhook_secret` in config - 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) 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 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 -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: @@ -264,6 +288,10 @@ Options: | `--venv DIR` | `/.venv` | Virtualenv path | | `--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: ```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 ``` -### Mode D: Gitea post-receive hook (filesystem) +### Mode D: Post-receive hook (filesystem) 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 # 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 -cd /home/git/apps/svn-git-server +cd /opt/svn-git-server . .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 -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 -Gitea's `pre-receive` hook. To force-push (e.g. initial import to Gitea), -temporarily disable the hook: +**Note:** Direct filesystem `git push` to a Git server's on-disk bare repo +may be blocked by the server's `pre-receive` hook (Gitea and GitLab both +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 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) | | `push` | Git→SVN direction only | | `daemon` | Continuous polling loop | -| `webhook` | Gitea webhook receiver | +| `webhook` | Webhook receiver (Gitea format; adaptable) | | `reconcile` | Fix divergence (see above) | ## 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). - **All SVN branches become Git branches**: If the SVN repo uses branches for release tags, the Git canonical repo will contain hundreds of branches. - Consider filtering via `refs/tags/` in the Gitea push refspec. -- **Gitea filesystem push**: Direct `git push` to Gitea's on-disk bare repo - is blocked by Gitea's `pre-receive` hook. The daemon/sync uses its own - `push_to_gitea()` to work around this. + Consider filtering via `refs/tags/` in the push refspec. +- **Git server filesystem push**: Direct `git push` to a Git server's + on-disk bare repo may be blocked by the server's `pre-receive` hook + (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`). diff --git a/config.yml.example b/config.yml.example index 906d062..68b99b4 100644 --- a/config.yml.example +++ b/config.yml.example @@ -1,4 +1,4 @@ -# gitea-svn-mirror configuration +# svn-git-mirror configuration # Full reference: see README.md data_dir: /var/svn-mirror @@ -19,17 +19,22 @@ mirrors: # branches: branches # 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: owner: myorg repo: myproject - # Path to Gitea's on-disk bare repos. Required for - # push_to_gitea, fetch_from_gitea, and the webhook flow. + # Path to bare repos on disk. Required for push/fetch + # and the webhook flow. # Typical locations: - # Gitea binary → /var/lib/gitea/data/repositories - # Gitea Docker → /data/git/repositories - # git user → /home/git/repositories + # Gitea binary → /var/lib/gitea/data/repositories + # Gitea Docker → /data/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 # Webhook receiver (used by `webhook` command)