chore(repo): Umzug nach Gitea — GitHub bleibt Klon, main von Hand gespiegelt (D95)
Deploy to GitHub Pages / build (push) Canceled after 0s
Deploy to GitHub Pages / deploy (push) Canceled after 0s

Zuhause ist künftig git.javagil.de/mi/werkbaum (origin); GitHub bleibt unter
dem Remote `github` ein Klon, den scripts/push-github.sh mit `main` versorgt —
nur vorwärts, nie erzwungen.

Der Klon bleibt aus zwei gemessenen Gründen: Gitea sendet auf raw-Dateien kein
Access-Control-Allow-Origin (nachgemessen an mi/werkdock), die
?sourceUrl=-Beispiellinks der READMEs müssen also auf raw.githubusercontent.com
zeigen; und der Pages-Workflow lässt sich nicht mitnehmen.

Footer, llms.txt und llms.md zeigen jetzt auf Gitea (Repo-Seite ohne Anmeldung
lesbar, HTTP 200 geprüft); beide COMMIT_URL-Stellen (deploy-prod.sh und der
Pages-Workflow) ziehen mit. deploy-prod.sh prüft origin und github getrennt und
erinnert an das Spiegeln, statt selbst zu pushen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mhoennig
2026-09-03 17:26:15 +02:00
co-authored by Claude Opus 5
parent 736e9c1d88
commit 29a217f892
12 changed files with 236 additions and 23 deletions
+1 -1
View File
@@ -76,7 +76,7 @@ jobs:
[ -z "$BASE" ] && BASE="$(git rev-list --max-parents=0 HEAD | tail -1)" [ -z "$BASE" ] && BASE="$(git rev-list --max-parents=0 HEAD | tail -1)"
MICRO="$(git rev-list --count "${BASE}..HEAD")" # Commits seit dem Bump MICRO="$(git rev-list --count "${BASE}..HEAD")" # Commits seit dem Bump
BUILD_VERSION="${MAJORMINOR}.${MICRO}" BUILD_VERSION="${MAJORMINOR}.${MICRO}"
COMMIT_URL="https://github.com/mhoennig/werkbaum/commit/$(git rev-parse HEAD)" COMMIT_URL="https://git.javagil.de/mi/werkbaum/commit/$(git rev-parse HEAD)"
echo "Footer-Version: ${BUILD_VERSION} -> ${COMMIT_URL}" echo "Footer-Version: ${BUILD_VERSION} -> ${COMMIT_URL}"
mkdir -p site mkdir -p site
sed -e 's#\.\./LICENSE#LICENSE#g' \ sed -e 's#\.\./LICENSE#LICENSE#g' \
+5 -5
View File
@@ -4,12 +4,12 @@ werkator:
version: version:
since: "1.0.1" since: "1.0.1"
# Commit status target. Points at GitHub for now; the repository will move to # Commit status target. The repository lives on Gitea since D95; GitHub stays a
# https://git.javagil.de (Gitea) later, GitHub stays a synchronised clone. Status # clone that `scripts/push-github.sh` feeds by hand. Status checks are posted
# checks are posted only once the origin is a Gitea; until then this is a label. # only once the instance holds a Gitea token; until then this is a label.
gitea: gitea:
baseUrl: https://github.com baseUrl: https://git.javagil.de
owner: mhoennig owner: mi
repo: werkbaum repo: werkbaum
statusContext: werkator statusContext: werkator
+20 -3
View File
@@ -142,6 +142,22 @@ Das Backend einrichten: siehe [backend/README.md](backend/README.md) und
> `?etherpad=`-Link zeigt heute einen Hinweis hierher, statt still nichts zu > `?etherpad=`-Link zeigt heute einen Hinweis hierher, statt still nichts zu
> tun; siehe `docs/DECISIONS.md` D78. > tun; siehe `docs/DECISIONS.md` D78.
### Wo der Code liegt
Das Repository liegt auf **<https://git.javagil.de/mi/werkbaum>** (Gitea) — das
ist `origin`, und dort gehören die Branches hin:
```bash
git clone mih09-git@git.javagil.de:mi/werkbaum.git # oder https://git.javagil.de/mi/werkbaum.git
```
**GitHub bleibt ein Klon.** `main` wird dorthin von Hand gespiegelt
(`scripts/push-github.sh`), denn zwei Dinge hängen daran: die
GitHub-Pages-Instanz (der 🚧 *latest build* ganz oben) und die Beispiel-Links
weiter oben, die ihre Pläne per `?sourceUrl=` von `raw.githubusercontent.com`
laden. Gitea liefert `raw`-Dateien **ohne** `Access-Control-Allow-Origin` aus —
dort blockte der Browser sie (`docs/DECISIONS.md` D23, D95).
### Lokal ausführen ### Lokal ausführen
Die Editor-Quelle liegt jetzt als ES-Module unter `frontend/src/`, gebündelt mit Die Editor-Quelle liegt jetzt als ES-Module unter `frontend/src/`, gebündelt mit
@@ -228,7 +244,8 @@ anderen Domain läge das Web-Verzeichnis in `…/subs-ssl/<name>/`.)
Der Editor wird per GitHub Actions als statische Seite auf **GitHub Pages** Der Editor wird per GitHub Actions als statische Seite auf **GitHub Pages**
veröffentlicht (Workflow: `.github/workflows/pages.yml`). Ausgelöst bei jedem veröffentlicht (Workflow: `.github/workflows/pages.yml`). Ausgelöst bei jedem
Push auf `main` sowie manuell (`workflow_dispatch`). Push auf `main` **des Klons** — also nachdem `scripts/push-github.sh` gespiegelt
hat (D95) — sowie manuell (`workflow_dispatch`).
Der Workflow richtet Node ein, führt `npm ci`, `npm test` (Vitest) und Der Workflow richtet Node ein, führt `npm ci`, `npm test` (Vitest) und
`npm run build` (Vite) aus und veröffentlicht die gebündelte `npm run build` (Vite) aus und veröffentlicht die gebündelte
@@ -244,8 +261,8 @@ gepflegt), die **Micro-Stelle** aus der Zahl der Commits seit diesem letzten
Bump — sie steigt also mit jedem Commit und beginnt nach einem Bump wieder bei Bump — sie steigt also mit jedem Commit und beginnt nach einem Bump wieder bei
`0` (`Werkbaum 1.0.0`, `1.0.1`, … dann `VERSION` auf `1.1` bumpen → `1.1.0`). Es `0` (`Werkbaum 1.0.0`, `1.0.1`, … dann `VERSION` auf `1.1` bumpen → `1.1.0`). Es
wird nichts ins Repo zurückgeschrieben. Im Footer verlinkt der Name **Werkbaum** wird nichts ins Repo zurückgeschrieben. Im Footer verlinkt der Name **Werkbaum**
die Repo-Startseite, die **Versionsnummer** genau den zugehörigen Commit die Repo-Startseite auf Gitea, die **Versionsnummer** genau den zugehörigen
(`…/commit/<sha>`). Lokal geöffnet zeigt der Editor den Platzhalter aus dem Commit dort (`…/commit/<sha>`) — der Klon trägt dieselben SHAs. Lokal geöffnet zeigt der Editor den Platzhalter aus dem
Quelltext (`Werkbaum 1.0`). Quelltext (`Werkbaum 1.0`).
**Einmalige Einrichtung:** In den Repo-Settings unter **Pages** als **Source** **Einmalige Einrichtung:** In den Repo-Settings unter **Pages** als **Source**
+20 -3
View File
@@ -136,6 +136,22 @@ Setting up the backend: see [backend/README.md](backend/README.md) and
> old `?etherpad=` link now shows a note pointing here rather than silently > old `?etherpad=` link now shows a note pointing here rather than silently
> doing nothing; see `docs/DECISIONS.md` D78. > doing nothing; see `docs/DECISIONS.md` D78.
### Where the code lives
The repository is at **<https://git.javagil.de/mi/werkbaum>** (Gitea) — that is
`origin`, and that is where branches belong:
```bash
git clone mih09-git@git.javagil.de:mi/werkbaum.git # or https://git.javagil.de/mi/werkbaum.git
```
**GitHub stays a clone.** `main` is mirrored there by hand
(`scripts/push-github.sh`), because two things hang off it: the GitHub Pages
instance (the 🚧 *latest build* linked at the top) and the example links above,
which load plan files from `raw.githubusercontent.com` via `?sourceUrl=`. Gitea
serves raw files **without** an `Access-Control-Allow-Origin` header, so the
browser would block them there (`docs/DECISIONS.md` D23, D95).
### Running it locally ### Running it locally
The editor source now lives as ES modules under `frontend/src/`, bundled by The editor source now lives as ES modules under `frontend/src/`, bundled by
@@ -225,7 +241,8 @@ directory would be `…/subs-ssl/<name>/`.)
The editor is published as a static page on **GitHub Pages** via GitHub The editor is published as a static page on **GitHub Pages** via GitHub
Actions (workflow: `.github/workflows/pages.yml`). Triggered on every push to Actions (workflow: `.github/workflows/pages.yml`). Triggered on every push to
`main` and manually (`workflow_dispatch`). `main` **of the clone** — that is, after `scripts/push-github.sh` has mirrored it
(D95) — and manually (`workflow_dispatch`).
The workflow sets up Node, runs `npm ci`, `npm test` (Vitest) and `npm run build` The workflow sets up Node, runs `npm ci`, `npm test` (Vitest) and `npm run build`
(Vite), then publishes the bundled `frontend/dist/index.html` as `index.html` at (Vite), then publishes the bundled `frontend/dist/index.html` as `index.html` at
@@ -240,8 +257,8 @@ commit"), and the **micro** part is the number of commits since that last bump
so it grows with every commit and resets to `0` right after a bump so it grows with every commit and resets to `0` right after a bump
(`Werkbaum 1.0.0`, `1.0.1`, … then bump `VERSION` to `1.1``1.1.0`). Nothing is (`Werkbaum 1.0.0`, `1.0.1`, … then bump `VERSION` to `1.1``1.1.0`). Nothing is
written back to the repo. In the footer the name **Werkbaum** links to the written back to the repo. In the footer the name **Werkbaum** links to the
repository, while the **version number** links to that exact commit repository on Gitea, while the **version number** links to that exact commit
(`…/commit/<sha>`). Opened locally, the editor shows the source placeholder there (`…/commit/<sha>`) — the clone carries the same SHAs. Opened locally, the editor shows the source placeholder
(`Werkbaum 1.0`). (`Werkbaum 1.0`).
**One-time setup:** In the repo settings under **Pages**, select **Source** = **One-time setup:** In the repo settings under **Pages**, select **Source** =
+5
View File
@@ -17,6 +17,11 @@ the git history of `docs/examples/werkbaum.werkbaum`. A day can therefore carry
a link without having a note (someone forgot to write one) — but never the a link without having a note (someone forgot to write one) — but never the
reverse. reverse.
## 2026-09-03
- The project moved to its own Gitea at git.javagil.de — the footer's repository and version links point there now, and GitHub stays a clone carrying the very same commits
- Every push is built by Werkator, the project's own CI, alongside the GitHub Pages build: backend and frontend as two named builds
## 2026-09-02 ## 2026-09-02
- Running the installed app and a browser tab side by side works now: text, names and earlier states live under their own storage key per document and deleting leaves a tombstone, so windows stop deleting each other's work - Running the installed app and a browser tab side by side works now: text, names and earlier states live under their own storage key per document and deleting leaves a tombstone, so windows stop deleting each other's work
+55
View File
@@ -9089,3 +9089,58 @@ die Browser-Fläche war verborgen, getippt wurde per `value` + `input`
D91-Nachtrag 9), und der Live-Feed ruht im dauerhaft verborgenen D91-Nachtrag 9), und der Live-Feed ruht im dauerhaft verborgenen
Automatisierungs-Tab (D76-Nachtrag 1) — dass B A's Zeile sieht, ist deshalb Automatisierungs-Tab (D76-Nachtrag 1) — dass B A's Zeile sieht, ist deshalb
über die Antwort auf den eigenen PATCH gemessen, nicht über den Feed. über die Antwort auf den eigenen PATCH gemessen, nicht über den Feed.
## D95 — Das Repo zieht nach Gitea; GitHub bleibt ein Klon, von Hand gespiegelt (2026-09-03)
Werkbaum lag auf GitHub. Seit Werkator (die eigene CI, https://werkator.javagil.de)
die Builds fährt und `werkdock` bereits auf `git.javagil.de` liegt, gehört auch
Werkbaum dorthin: **`origin` ist jetzt `mi/werkbaum` auf Gitea**, GitHub bleibt
als **Klon** unter dem Remote `github` stehen.
**Der Klon ist kein Andenken, sondern zwei gemessene Zwänge:**
- **CORS.** Die Beispiel-Links beider READMEs laden ihre Pläne per
`?sourceUrl=` (D23) — und das geht nur, wenn die Quelle
`Access-Control-Allow-Origin` sendet. Nachgemessen an
`https://git.javagil.de/mi/werkdock/raw/branch/main/README.md` (mit
`Origin:`-Header): HTTP 200, `Access-Control-Expose-Headers`, aber **kein
`Access-Control-Allow-Origin`**. Ein Beispiel-Link auf Gitea liefe also
genau in den Fehler, den D23 als häufigsten benennt. `raw.githubusercontent.com`
sendet ihn (D24) — die Beispiel-Links bleiben deshalb dort.
- **GitHub Pages.** Die „latest build“-Instanz (D16) ist ein
Actions-Workflow und lässt sich nicht mitnehmen. Sie bleibt, und sie baut,
sobald `main` auf dem Klon ankommt.
**Gespiegelt wird von Hand, und nur `main`** (`scripts/push-github.sh`).
Feature-Branches und Tags bleiben in Gitea; was auf GitHub steht, ist der
veröffentlichte Stand. Erwogen und verworfen: **ein `origin` mit zwei
Push-URLs** (spiegelt bei jedem Push, auch bei Zwischenständen, und ein
Fehler an einer der beiden Seiten bricht den Push) und der **Voll-Mirror**
(`--mirror` löscht auf GitHub Branches, die in Gitea verschwunden sind — bei
einem öffentlichen Klon die unhöflichste Variante).
**Der Deploy spiegelt nicht, er erinnert.** `scripts/deploy-prod.sh` prüft
jetzt zweierlei getrennt: ob HEAD auf **origin (Gitea)** liegt — daran hängt
der Footer-Versionslink — und ob er auf **github** liegt; fehlt das zweite,
nennt es den Skript-Namen. Ausdrücklich so entschieden (Multiple-Choice): Ein
Deploy, der nebenbei woanders hin pusht, tut mehr, als sein Name sagt.
**Nie erzwungen.** Liegt `github/main` nicht in der Historie von `main`,
bricht das Spiegel-Script ab und zeigt `git log main..github/main`. Ein
Force-Push auf einen Klon, den andere geklont haben, ist eine bewusste
Handlung und keine Zeile in einem Hilfsskript.
**Die Links im Produkt zeigen auf Gitea** — Footer-Repo-Link und
Footer-Versionslink (`…/commit/<sha>`, D16), `llms.txt` und `llms.md` (D43).
Voraussetzung geprüft: Die Gitea-Instanz liefert Repo-Seite und API **ohne
Anmeldung** (HTTP 200). Die SHAs sind auf beiden Seiten dieselben, der Link
bleibt also auch für jemanden auflösbar, der vom Klon kommt. Beide
`COMMIT_URL`-Stellen ziehen mit — `deploy-prod.sh` **und** der
Pages-Workflow; das ist dieselbe Doppelpflege, die D16 schon für die
`sed`-Regeln benannt hat.
**Werkator:** Die `gitea:`-Sektion der `.werkator.yml` nennt jetzt
`git.javagil.de` / `mi` / `werkbaum` statt der GitHub-Platzhalter, und der
beobachtete Klon `~/werkbaum` auf mih09 bekommt Gitea als `origin` — sonst
sähe der Watcher neue Commits erst nach dem Spiegeln. **Status-Checks werden
weiterhin nicht gepostet**, solange auf der Instanz kein Gitea-Token liegt;
die Sektion ist bis dahin eine Beschriftung.
+13
View File
@@ -124,6 +124,8 @@
- [^] #bld.backend.proxy: Apache passes /api/ on, in .htaccess (XS) %% measured: the P flag is allowed - [^] #bld.backend.proxy: Apache passes /api/ on, in .htaccess (XS) %% measured: the P flag is allowed
- [^] #bld.backend.remote: One command for everything on the server (S) %% remote <target> <action> - [^] #bld.backend.remote: One command for everything on the server (S) %% remote <target> <action>
- [^] #bld.backend.backup: Save the database, and prove it can be read back (XS) - [^] #bld.backend.backup: Save the database, and prove it can be read back (XS)
- [x] #bld.werkator: Also built by Werkator, the project's own CI (S) %% .werkator.yml, backend + frontend
- [x] #bld.gitea: The repository lives on Gitea, GitHub stays a clone (S)
- [-] #bld.ghpages: Deploy through a gh-pages branch (S) %% GitHub Actions instead - [-] #bld.ghpages: Deploy through a gh-pages branch (S) %% GitHub Actions instead
- [-] #bld.gfonts: Fonts from Google (XS) %% self-hosted, no third-party request - [-] #bld.gfonts: Fonts from Google (XS) %% self-hosted, no third-party request
- [ ] #be: Backend (XL) - [ ] #be: Backend (XL)
@@ -853,6 +855,17 @@
taken while the service runs can be torn. Stop, fetch, start again — and read taken while the service runs can be torn. Stop, fetch, start again — and read
the archive back before trusting it. the archive back before trusting it.
#bld.werkator
Werkator, the project's own declarative CI, builds every push from a
committed .werkator.yml — backend and frontend as two named builds. It builds
and tests only; deploying stays with the scripts.
#bld.gitea
The repository moved to Gitea; GitHub stays a clone that carries the
published main. The clone is not sentiment: the Pages instance hangs off it,
and so do the example links, which need a raw endpoint that sends CORS
headers.
#bld.ghpages #bld.ghpages
Rejected: the official Actions route needs no extra branch and no deploy Rejected: the official Actions route needs no extra branch and no deploy
key. key.
+1 -1
View File
@@ -297,7 +297,7 @@
</div> </div>
</div> </div>
<footer class="site-footer"><a href="https://github.com/mhoennig/werkbaum" target="_blank" rel="noopener">Werkbaum</a>&nbsp;<a class="ver" href="https://github.com/mhoennig/werkbaum/commit/main" target="_blank" rel="noopener">1.0</a> · <a href="llms.md" target="_blank" rel="noopener" title="Werkbaum-Notation für KI-Agenten · Werkbaum notation for AI agents (Markdown)">llms.md</a> © 2026 <a href="https://michael.hoennig.de" target="_blank" rel="noopener">Michael Hönnig</a> · <span class="vibe" title="Fable 5 and Opus 4.8">Built with Claude Code</span> <a href="../LICENSE" target="_blank" rel="noopener">License</a> · <a href="https://michael.hoennig.de/imprint.html" target="_blank" rel="noopener" data-i18n="imprint">Impressum</a> · <a id="privacyLink" href="https://michael.hoennig.de/datenschutzerklaerung.html" target="_blank" rel="noopener" data-i18n="privacy">Datenschutz</a></footer> <footer class="site-footer"><a href="https://git.javagil.de/mi/werkbaum" target="_blank" rel="noopener">Werkbaum</a>&nbsp;<a class="ver" href="https://git.javagil.de/mi/werkbaum/commit/main" target="_blank" rel="noopener">1.0</a> · <a href="llms.md" target="_blank" rel="noopener" title="Werkbaum-Notation für KI-Agenten · Werkbaum notation for AI agents (Markdown)">llms.md</a> © 2026 <a href="https://michael.hoennig.de" target="_blank" rel="noopener">Michael Hönnig</a> · <span class="vibe" title="Fable 5 and Opus 4.8">Built with Claude Code</span> <a href="../LICENSE" target="_blank" rel="noopener">License</a> · <a href="https://michael.hoennig.de/imprint.html" target="_blank" rel="noopener" data-i18n="imprint">Impressum</a> · <a id="privacyLink" href="https://michael.hoennig.de/datenschutzerklaerung.html" target="_blank" rel="noopener" data-i18n="privacy">Datenschutz</a></footer>
<!-- Knoten-Fenster für Touch-Geräte (D52): Ohne Zeiger gibt es keinen Tooltip. <!-- Knoten-Fenster für Touch-Geräte (D52): Ohne Zeiger gibt es keinen Tooltip.
Es liegt auf <body> und ist `position:fixed` — in `#out` hinein würde es Es liegt auf <body> und ist `position:fixed` — in `#out` hinein würde es
+1 -1
View File
@@ -3,7 +3,7 @@
Werkbaum is a plain-text notation for work-breakdown structures (WBS) with Werkbaum is a plain-text notation for work-breakdown structures (WBS) with
and/or decomposition. This guide teaches you to **read and write** it. The and/or decomposition. This guide teaches you to **read and write** it. The
normative specification (German) is normative specification (German) is
https://github.com/mhoennig/werkbaum/blob/main/docs/SPEC.md — if this guide https://git.javagil.de/mi/werkbaum/src/branch/main/docs/SPEC.md — if this guide
and the SPEC disagree, the SPEC wins. and the SPEC disagree, the SPEC wins.
Files are UTF-8 with LF line endings; the extension `.werkbaum` is a Files are UTF-8 with LF line endings; the extension `.werkbaum` is a
+3 -3
View File
@@ -17,7 +17,7 @@ is needed to store, share or version a plan.
gates, status codes and progress ranks, sizes, node ids, dependencies, gates, status codes and progress ranks, sizes, node ids, dependencies,
folding marks, descriptions, plus a complete example. Self-contained; start folding marks, descriptions, plus a complete example. Self-contained; start
here. here.
- [Specification](https://github.com/mhoennig/werkbaum/blob/main/docs/SPEC.md): - [Specification](https://git.javagil.de/mi/werkbaum/src/branch/main/docs/SPEC.md):
The normative language definition (German). Where it and the guide disagree, The normative language definition (German). Where it and the guide disagree,
it wins. it wins.
- [Editor](https://werkbaum.javagil.de/): Renders a plan as a diagram. - [Editor](https://werkbaum.javagil.de/): Renders a plan as a diagram.
@@ -26,8 +26,8 @@ is needed to store, share or version a plan.
## Optional ## Optional
- [Design decisions](https://github.com/mhoennig/werkbaum/blob/main/docs/DECISIONS.md): - [Design decisions](https://git.javagil.de/mi/werkbaum/src/branch/main/docs/DECISIONS.md):
Why the notation looks the way it does, at length (German). Background only, Why the notation looks the way it does, at length (German). Background only,
not needed to read or write plans. not needed to read or write plans.
- [Repository](https://github.com/mhoennig/werkbaum): Source and example plans - [Repository](https://git.javagil.de/mi/werkbaum): Source and example plans
under `docs/examples/*.werkbaum`. under `docs/examples/*.werkbaum`.
+15 -6
View File
@@ -117,13 +117,22 @@ if git -C "$ROOT" rev-parse HEAD >/dev/null 2>&1; then
[ -z "$BASE" ] && BASE="$(git -C "$ROOT" rev-list --max-parents=0 HEAD | tail -1)" [ -z "$BASE" ] && BASE="$(git -C "$ROOT" rev-list --max-parents=0 HEAD | tail -1)"
MICRO="$(git -C "$ROOT" rev-list --count "${BASE}..HEAD")" MICRO="$(git -C "$ROOT" rev-list --count "${BASE}..HEAD")"
BUILD_VERSION="${MAJORMINOR}.${MICRO}" BUILD_VERSION="${MAJORMINOR}.${MICRO}"
COMMIT_URL="https://github.com/mhoennig/werkbaum/commit/$(git -C "$ROOT" rev-parse HEAD)" COMMIT_URL="https://git.javagil.de/mi/werkbaum/commit/$(git -C "$ROOT" rev-parse HEAD)"
echo "==> Footer-Version ${BUILD_VERSION} -> ${COMMIT_URL}" echo "==> Footer-Version ${BUILD_VERSION} -> ${COMMIT_URL}"
# Der Link zeigt ins Leere, solange der Commit nicht auf GitHub liegt — nach # Der Link zeigt auf Gitea (origin, D95) und läuft ins Leere, solange der
# einer Beförderung (Schritt 0) ist das der Normalfall. # Commit dort nicht liegt — nach einer Beförderung (Schritt 0) ist das der
if [ -z "$(git -C "$ROOT" branch -r --contains HEAD 2>/dev/null)" ]; then # Normalfall.
echo " ! HEAD liegt noch nicht auf origin — der Footer-Versionslink läuft" >&2 if ! git -C "$ROOT" branch -r --contains HEAD 2>/dev/null | grep -q "^ *origin/"; then
echo " ins Leere, bis 'git push' nachgeholt ist." >&2 echo " ! HEAD liegt noch nicht auf origin (Gitea) — der Footer-Versionslink" >&2
echo " läuft ins Leere, bis 'git push' nachgeholt ist." >&2
fi
# GitHub ist seit D95 nur noch ein Klon und wird VON HAND gespiegelt. Der
# Deploy spiegelt nicht selbst — er erinnert nur, denn die Pages-Instanz und
# die ?sourceUrl=-Beispiellinks (raw.githubusercontent.com) hängen daran.
if git -C "$ROOT" remote get-url github >/dev/null 2>&1 &&
! git -C "$ROOT" branch -r --contains HEAD 2>/dev/null | grep -q "^ *github/"; then
echo " ! HEAD liegt noch nicht auf github — der Klon hinkt hinterher;" >&2
echo " 'scripts/push-github.sh' holt es nach." >&2
fi fi
SED_ARGS+=(-e "s#\(<a class=\"ver\" href=\"\)[^\"]*#\1${COMMIT_URL}#") SED_ARGS+=(-e "s#\(<a class=\"ver\" href=\"\)[^\"]*#\1${COMMIT_URL}#")
SED_ARGS+=(-e "s#\(<a class=\"ver\"[^>]*>\)[0-9.]\+</a>#\1${BUILD_VERSION}</a>#") SED_ARGS+=(-e "s#\(<a class=\"ver\"[^>]*>\)[0-9.]\+</a>#\1${BUILD_VERSION}</a>#")
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
#
# Werkbaum — main nach GitHub spiegeln.
#
# Zuhause des Repos ist seit D95 Gitea (`origin`, https://git.javagil.de/mi/werkbaum);
# GitHub bleibt ein Klon unter `github`. Dieser Lauf schiebt genau EINEN Branch
# dorthin: `main`. Feature-Branches und Tags bleiben in Gitea — was auf GitHub
# steht, ist der veröffentlichte Stand.
#
# Der Klon ist kein Selbstzweck. An ihm hängen zwei Dinge, die nicht umziehen
# können:
# * der GitHub-Pages-Workflow (`.github/workflows/pages.yml`), also die
# „latest build"-Instanz mhoennig.github.io/werkbaum;
# * die Beispiel-Links der READMEs, die per `?sourceUrl=` von
# raw.githubusercontent.com laden — Gitea sendet auf `raw`-Dateien KEIN
# `Access-Control-Allow-Origin`, der Browser blockte sie also (D23, D95).
#
# Verwendung:
# scripts/push-github.sh [-n] [-y]
#
# -n, --dry-run nur zeigen, was gepusht würde
# -y, --yes ohne Rückfrage pushen
#
# Aufgerufen wird von Hand. `scripts/deploy-prod.sh` spiegelt NICHT selbst — es
# erinnert nur, wenn der deployte Commit noch nicht auf GitHub liegt (D95).
#
# Es wird nie erzwungen: Liegt github/main nicht in der Historie von main,
# bricht der Lauf ab und sagt, was er gefunden hat. Ein Force-Push auf einen
# Klon, den andere geklont haben, ist eine bewusste Handlung und keine Zeile in
# einem Hilfsskript.
set -euo pipefail
DRY=0
YES=0
for arg in "$@"; do
case "$arg" in
-n|--dry-run) DRY=1 ;;
-y|--yes) YES=1 ;;
-h|--help)
awk 'NR>2 { if ($0 ~ /^#/) { sub(/^# ?/, ""); print } else exit }' "$0"
exit 0 ;;
*) echo "Unbekannte Option: $arg" >&2; exit 2 ;;
esac
done
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT"
REMOTE="${GITHUB_REMOTE:-github}"
git remote get-url "$REMOTE" >/dev/null 2>&1 || {
echo "Kein Remote '$REMOTE'. Einmalig anlegen:" >&2
echo " git remote add $REMOTE git@github.com:mhoennig/werkbaum.git" >&2
exit 2
}
echo "==> Hole $REMOTE"
git fetch --quiet "$REMOTE" || {
echo "Konnte $REMOTE nicht holen." >&2; exit 1
}
LOCAL="$(git rev-parse main)"
REMOTE_SHA="$(git rev-parse "$REMOTE/main" 2>/dev/null || echo "")"
if [ "$LOCAL" = "$REMOTE_SHA" ]; then
echo "==> $REMOTE/main ist bereits auf $(git rev-parse --short main) — nichts zu tun."
exit 0
fi
# Nur vorwärts. Ist der Fernstand kein Vorfahr, hat dort jemand etwas liegen,
# das ein Push wegwürfe — dann lieber abbrechen als überschreiben.
if [ -n "$REMOTE_SHA" ] && ! git merge-base --is-ancestor "$REMOTE_SHA" "$LOCAL"; then
echo "ABBRUCH: $REMOTE/main ($(git rev-parse --short "$REMOTE/main")) liegt nicht in der" >&2
echo "Historie von main ($(git rev-parse --short main)) — ein Push würde dort Commits" >&2
echo "verlieren. Erst ansehen:" >&2
echo " git log --oneline main..$REMOTE/main" >&2
exit 1
fi
if [ -n "$REMOTE_SHA" ]; then RANGE="$REMOTE_SHA..main"; else RANGE="main"; fi
echo "==> $(git rev-list --count "$RANGE") Commit(s) nach $REMOTE/main zu spiegeln:"
git --no-pager log --oneline "$RANGE" | head -20
if [ "$DRY" = "1" ]; then
echo "==> --dry-run: nichts gepusht."
exit 0
fi
if [ "$YES" != "1" ]; then
read -r -p "main nach $REMOTE pushen? [j/N] " a
case "$a" in j|J|y|Y) ;; *) echo "Abgebrochen."; exit 0 ;; esac
fi
git push "$REMOTE" main:main
echo "==> main auf $REMOTE ist jetzt $(git rev-parse --short main)."
echo " Der Pages-Workflow baut daraufhin von selbst."