docs: reframe as generic SVN-Git mirror, Gitea as example integration
This commit is contained in:
@@ -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]
|
||||||
|
|||||||
@@ -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
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user