<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Git Archive - Das ist die Welt von Thomas</title>
	<atom:link href="https://www.schiffler.eu/thema/git/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.schiffler.eu/thema/git/</link>
	<description>meine Gedanken, mal strukturiert, mal nicht ...</description>
	<lastBuildDate>Tue, 08 Sep 2026 11:17:21 +0000</lastBuildDate>
	<language>de</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.1</generator>

<image>
	<url>https://www.schiffler.eu/wp-content/uploads/2025/07/cropped-Profilfoto_2024-32x32.png</url>
	<title>Git Archive - Das ist die Welt von Thomas</title>
	<link>https://www.schiffler.eu/thema/git/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>WordPress-Plugin mit GitLab CI nach SVN deployen: mein Release läuft auf einen Klick</title>
		<link>https://www.schiffler.eu/wordpress-plugin-gitlab-ci-nach-svn-deployen/</link>
					<comments>https://www.schiffler.eu/wordpress-plugin-gitlab-ci-nach-svn-deployen/#respond</comments>
		
		<dc:creator><![CDATA[Thomas Schiffler]]></dc:creator>
		<pubDate>Tue, 08 Sep 2026 05:40:54 +0000</pubDate>
				<category><![CDATA[IT-Know How]]></category>
		<category><![CDATA[Automatisierung]]></category>
		<category><![CDATA[Codeschnipsel]]></category>
		<category><![CDATA[Git]]></category>
		<category><![CDATA[gitlab]]></category>
		<category><![CDATA[KI-generierte Bilder]]></category>
		<category><![CDATA[Plugin]]></category>
		<category><![CDATA[Portrait-Archiv]]></category>
		<category><![CDATA[Squadeno]]></category>
		<category><![CDATA[Wordpress]]></category>
		<guid isPermaLink="false">https://www.schiffler.eu/?p=3243</guid>

					<description><![CDATA[<p>Ich habe seit Jahren ein Plugin im offiziellen WordPress-Verzeichnis: portrait-archiv-shop, den Shop-Teil meiner Bildagentur portrait-archiv.com. Aktuell Version 4.4.0. Und ich &#8230; <a href="https://www.schiffler.eu/wordpress-plugin-gitlab-ci-nach-svn-deployen/" class="more-link">More <span class="screen-reader-text">WordPress-Plugin mit GitLab CI nach SVN deployen: mein Release läuft auf einen Klick</span> <span class="meta-nav">&#8594;</span></a></p>
<p>Der Beitrag <a href="https://www.schiffler.eu/wordpress-plugin-gitlab-ci-nach-svn-deployen/">WordPress-Plugin mit GitLab CI nach SVN deployen: mein Release läuft auf einen Klick</a> erschien zuerst auf <a href="https://www.schiffler.eu">Das ist die Welt von Thomas</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p class="wp-block-paragraph">Ich habe seit Jahren ein Plugin im offiziellen WordPress-Verzeichnis: <a href="https://de.wordpress.org/plugins/portrait-archiv-shop/" target="_blank" rel="noopener">portrait-archiv-shop</a>, den Shop-Teil <a href="https://www.portrait-service.com">meiner Bildagentur portrait-archiv.com</a>. Aktuell Version 4.4.0. Und ich habe ziemlich lange gebraucht, um mein WordPress-Plugin mit GitLab CI nach SVN zu deployen, statt es jedes Mal von Hand hochzuschieben. Schwierig war das nie. Ich habe es geschoben, so wie man alles schiebt, was man ja irgendwie hinkriegt, wenn man sich eine Stunde hinsetzt und konzentriert.</p>



<p class="wp-block-paragraph">Nur: Ein Release, für das man sich konzentrieren muss, geht irgendwann schief.</p>



<h2 class="wp-block-heading">Warum mich das SVN von wordpress.org so genervt hat</h2>



<p class="wp-block-paragraph">Mein Entwicklungsprozess sieht bei jedem Projekt gleich aus. Alles liegt in meinem GitLab, <code>feature/*</code> zweigt von <code>integration</code> ab, <code>integration</code> von <code>master</code>. Die CI lintet, testet, baut ein Paket. Wenn das Paket grün ist, ist es das Ergebnis, mit dem ich weiterarbeite. Das ist mein Takt, seit Jahren, über alle Projekte hinweg.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph">wordpress.org spielt bei dem Spiel nicht mit. Das Plugin-Verzeichnis läuft auf Subversion, mit der klassischen Struktur aus <code>trunk</code>, <code>tags</code> und <code>assets</code>. Kein Git, keine Pull Requests, kein Tag-Objekt. Ein Release ist dort ein Verzeichnis, das du kopierst. Und <code>assets</code> ist nicht mal Teil des Plugins, da liegen Banner, Icon und Screenshots für die Store-Seite.</p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large has-lightbox"><img fetchpriority="high" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_09-1024x559.jpeg" alt="Alte Werkzeuge für neue Dinge" class="wp-image-3237" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_09-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_09-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_09-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_09.jpeg 1408w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



<p class="wp-block-paragraph">Von Hand heißt das jedes Mal: SVN auschecken, Dateien nach <code>trunk</code> kopieren, neue Dateien mit <code>svn add</code> anmelden, gelöschte mit <code>svn delete</code> abmelden, <code>svn copy trunk tags/4.4.0</code>, committen. Sechs Handgriffe, in der richtigen Reihenfolge, mit einem Passwort, das ich mir nie merke. Alle zwei Monate, also selten genug, dass ich jedes Mal in meinen alten Notizen nachgesehen habe, wie das nochmal ging.</p>



<p class="wp-block-paragraph">Der Beweis, dass das nicht funktioniert, lag jahrelang im veröffentlichten <code>trunk</code>. Aus den Handbetrieb-Zeiten schleppte mein Plugin <code>.project</code>, <code>.buildpath</code> und <code>.settings</code> mit sich herum, Eclipse-Altlasten von einem Rechner, den ich längst nicht mehr habe. Aufgefallen ist es erst, als der offizielle WordPress Plugin Check bei mir in der Pipeline lief und mir sagte: „Hidden files are not permitted.&#8220; Die Dateien lagen also im Store-Download, jahrelang, bei jedem, der das Plugin installiert hat. Beschwert hat sich nie jemand.</p>



<p class="wp-block-paragraph">Ein zweiter Punkt, über den ich beim ersten Versuch gestolpert bin und der in keiner Doku fett genug steht: Das SVN-Passwort ist nicht dein wordpress.org-Passwort. Du legst es separat im Profil unter „Account &amp; Security&#8220; an. Ich war eine ganze Weile überzeugt, mein Zugang sei kaputt.</p>



<h2 class="wp-block-heading">Die Grundidee: SVN ist kein Arbeitsplatz, sondern ein Ausgabegerät</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large has-lightbox"><img decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_03-1024x559.jpeg" alt="ungeordneter Schreibtisch am Abend" class="wp-image-3231" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_03-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_03-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_03-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_03.jpeg 1408w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph">Der Denkfehler war, das SVN als zweites Repository zu betrachten, in dem ich irgendwie auch arbeite. Sobald man es als Drucker begreift, löst sich die Sache auf. Git ist die Wahrheit. Das SVN bekommt fertige Ware geliefert und interessiert mich sonst nicht.</p>



<p class="wp-block-paragraph">Praktisch bedeutet das: Kein Mensch checkt das Plugin-SVN mehr aus. Ein Skript kennt die sechs Handgriffe, die CI ruft es auf, und die Zugangsdaten liegen als maskierte CI/CD-Variablen in GitLab:</p>
</div>
</div>



<pre class="wp-block-code advgb-dyn-c4886699"><code>SVN_USERNAME = mein wordpress.org-Benutzername (case-sensitiv)
SVN_PASSWORD = das separate SVN-Passwort aus dem Profil
</code></pre>



<p class="wp-block-paragraph">Das ist die ganze Konfiguration. Alles andere steckt in Skripten, die im Repo liegen und die ich lokal genauso aufrufen kann.</p>



<h2 class="wp-block-heading">Was passiert, bevor überhaupt jemand an SVN denkt</h2>



<p class="wp-block-paragraph">Die Pipeline hat sechs Stufen: <code>image</code>, <code>lint</code>, <code>test</code>, <code>quality</code>, <code>build</code>, <code>deploy</code>. Bis es zum Deploy kommt, kann die Pipeline an vier Stellen vorher Nein sagen.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large has-lightbox"><img decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_05-1024x559.jpeg" alt="die moderne Werkbank" class="wp-image-3233" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_05-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_05-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_05-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_05.jpeg 1408w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph"><strong>Stufe 0, das eigene CI-Image.</strong> Alle Jobs laufen in einem Base-Image, das die Pipeline selbst baut: PHP 8.1 mit den nötigen Extensions, Composer, wp-cli, MySQL-Client und, wichtig für später, <code>subversion</code> und <code>rsync</code>. Gebaut wird es mit Kaniko, also ohne Docker-Daemon und ohne Docker-in-Docker. Das Ergebnis landet in meiner eigenen Container-Registry, der Layer-Cache gleich daneben. Vorher hat jeder Job sein <code>apt-get install</code> selbst gemacht, was pro Lauf Minuten gekostet hat und bei jeder Netzstörung rot war.</p>
</div>
</div>



<p class="wp-block-paragraph"><strong>Stufe 1, Lint.</strong> PHPCS mit dem WordPress-Coding-Standard, strikt auf den modernen Kern in <code>plugin/src</code> (null Errors), dazu die PHP-8.1-Kompatibilitätsprüfung auf dem gesamten Plugin. Das Plugin ist alt, es gibt noch prozedurale Dateien aus der Frühzeit. Die wandern nach und nach in <code>plugin/src</code> und werden dort dann automatisch mitgeprüft. Ich finde das ehrlicher, als den Standard auf alles zu werfen, tausend Findings zu sehen und den Job dann dauerhaft auf <code>allow_failure</code> zu stellen. Dazu PHPStan auf Level 5 mit <code>phpstan-wordpress</code> und <code>composer audit</code> für bekannte Schwachstellen in den Abhängigkeiten.</p>



<p class="wp-block-paragraph"><strong>Stufe 2, Test.</strong> Die komplette PHPUnit-Suite gegen ein echtes WordPress. Als Service läuft <code>mariadb:10.11</code>, <code>bin/install-wp-tests.sh</code> zieht sich WordPress 6.5 samt Test-Library, dann läuft PHPUnit. Auf <code>integration</code> ist dieser Job die eigentliche Freigabe: Nur wenn er grün ist, darf nach <code>master</code> gemergt werden. Das erzwinge ich zusätzlich in GitLab über geschützte Branches und „Pipelines must succeed&#8220;, weil eine Regel, die nur in einem YAML-Kommentar steht, keine Regel ist.</p>



<p class="wp-block-paragraph"><strong>Stufe 3, Quality.</strong> Hier läuft der offizielle <a href="https://wordpress.org/plugins/plugin-check/" target="_blank" rel="noopener">Plugin Check</a> von wordpress.org, also genau das Werkzeug, mit dem auch das Plugin-Team schaut. Der Job baut sich ein Wegwerf-WordPress in <code>/tmp</code>, hängt das Plugin per Symlink ein und prüft es. Die Schwelle steht bei <code>warning</code>, nicht bei <code>error</code>: null Fehler und null Warnungen, sonst bricht die Pipeline.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph">Das war unbequem. Die alten Warnungen, vor allem <code>DirectDatabaseQuery</code> an meinem lokalen Bild-Cache, musste ich einzeln durchgehen: echte Findings gefixt, die unvermeidbaren Fehlalarme mit einem begründeten <code>phpcs:ignore</code> versehen. Danach war die Liste leer, und seitdem ist jede neue Warnung ein echtes Signal. Gefunden hat der Job unter anderem die Eclipse-Altlasten von oben, die mir in fünf Jahren Handbetrieb nie aufgefallen sind.</p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_01-1024x559.jpeg" alt="Alte Bilder, alte Tags" class="wp-image-3229" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_01-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_01-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_01-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_01.jpeg 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



<p class="wp-block-paragraph">Kleiner Nebeneffekt, der mich lange geärgert hat: Sobald ein Branch einen offenen Merge Request hat, lief bei mir alles doppelt, einmal als Branch-Pipeline und einmal als MR-Pipeline, auf demselben Commit. Drei Regeln im <code>workflow</code>-Block räumen das auf, und <code>auto_cancel: on_new_commit: interruptible</code> beendet alte Läufe, wenn ich nachschiebe. Bei einer Pipeline, die für jeden Lauf ein komplettes WordPress installiert, merkt man das direkt an der Wartezeit.</p>



<h2 class="wp-block-heading">Der Build: was ins Paket gehört und was nicht</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large has-lightbox"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_02-1024x559.jpeg" alt="unterschiedliche Tags für unterschiedliche Dinge" class="wp-image-3230" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_02-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_02-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_02-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_02.jpeg 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph">Auf <code>master</code> entsteht das eigentliche Auslieferungspaket, per <code>bin/build-plugin.sh</code>. Das Skript macht zwei Dinge, die wichtiger sind als das Zippen selbst.</p>



<p class="wp-block-paragraph">Erstens liest es die Version aus <code>plugin/readme.txt</code>, aus dem Feld <code>Stable tag</code>. Das ist bei wordpress.org die Angabe, die entscheidet, was ausgeliefert wird. Zweitens vergleicht es sie mit der <code>Version:</code> im Plugin-Header. Weichen die voneinander ab, bricht es ab:</p>
</div>
</div>



<pre class="wp-block-code"><code>VERSION=$(grep -i "^Stable tag:" "${PLUGIN_DIR}/readme.txt" | head -1 | sed 's/.*: *//' | tr -d '&#91;:space:]')
HEADER_VERSION=$(grep -iE "^\s*Version:" "${PLUGIN_DIR}/portraitarchiv-store.php" | head -1 | sed 's/.*: *//' | tr -d '&#91;:space:]')
if &#91; "$HEADER_VERSION" != "$VERSION" ]; then
    echo "FEHLER: Header-Version ($HEADER_VERSION) != readme Stable tag ($VERSION)"
    exit 1
fi
</code></pre>



<p class="wp-block-paragraph">Der Grund für die Härte: Eine falsch getaggte Version bekommst du bei wordpress.org nicht mehr zurück. Es gibt kein Zurückziehen, nur ein weiteres Release hinterher, und der Store zeigt in der Zwischenzeit fröhlich die kaputte Version an.</p>



<p class="wp-block-paragraph">Das Zip enthält einen Top-Level-Ordner mit dem Plugin-Slug, so wie WordPress es beim Installieren erwartet, und IDE- sowie VCS-Reste sind per <code>rsync</code>-Exclude ausgeschlossen. Es geht zusätzlich als Artefakt in die Pipeline, versioniert, 5 Tage haltbar. Damit habe ich für jede ausgelieferte Version das exakte Paket zum Download, ohne im SVN zu wühlen.</p>



<h2 class="wp-block-heading">Der Deploy: vierzig Zeilen, die das SVN erledigen</h2>



<p class="wp-block-paragraph"><code>bin/deploy-svn.sh</code> ist der Kern. Der Ablauf ist genau der Handbetrieb von früher, nur eben aufgeschrieben.</p>



<p class="wp-block-paragraph">Version lesen, SVN auschecken, <code>trunk</code> synchronisieren:</p>



<pre class="wp-block-code"><code>svn checkout --quiet "$SVN_URL" "$BUILD_DIR"

rsync -a --delete \
    --exclude='.svn' --exclude='.git' \
    --exclude='.settings' --exclude='.project' --exclude='.buildpath' \
    "${PLUGIN_DIR}/" "${BUILD_DIR}/trunk/"
</code></pre>



<p class="wp-block-paragraph"><code>--delete</code> ist hier das entscheidende Flag. Ohne das wächst der <code>trunk</code> bei jedem Release, weil gelöschte Dateien einfach liegen bleiben. Genau so entstehen die Altlasten, die ich oben beschrieben habe.</p>



<p class="wp-block-paragraph">Danach die Store-Assets aus <code>.wordpress-org</code> nach <code>assets</code>, ebenfalls mit <code>--delete</code>. Dann der Schritt, den man von Hand am liebsten vergisst: SVN muss man mitteilen, welche Dateien neu sind und welche verschwunden sind.</p>



<pre class="wp-block-code"><code>svn add --force . --quiet
svn status | awk '/^!/ {print $2}' | xargs -r svn delete --quiet
</code></pre>



<p class="wp-block-paragraph">Das <code>!</code> in der Statusausgabe steht für „laut SVN vorhanden, im Dateisystem weg&#8220;. Ohne diese Zeile scheitert der Commit oder, schlimmer, er läuft durch und lässt Dateien im Verzeichnis, die du längst gelöscht hast.</p>



<p class="wp-block-paragraph">Dann der Tag. Und zwar bedingt:</p>



<pre class="wp-block-code"><code>if svn ls "${SVN_URL}/tags/${VERSION}" &gt;/dev/null 2&gt;&amp;1; then
    echo "==&gt; Tag ${VERSION} existiert bereits im SVN – überspringe Tag-Erstellung"
else
    svn copy --quiet "trunk" "tags/${VERSION}"
fi
</code></pre>



<p class="wp-block-paragraph">Das ist mir wichtiger geworden, als es aussieht. Der Deploy läuft bei mir auf jedem <code>master</code>-Commit, also auch bei einem Nachtrag am Changelog ohne Versionsbump. Wenn der Job dann am schon existierenden Tag stirbt, ist meine Pipeline rot, obwohl alles in Ordnung ist. Seitdem kann ich den Job zweimal laufen lassen, ohne vorher nachzudenken, und traue mich deshalb auch, ihn mal von Hand anzustoßen.</p>



<p class="wp-block-paragraph">Am Ende ein einziger Commit, der <code>trunk</code>, <code>assets</code> und den neuen Tag zusammen veröffentlicht:</p>



<pre class="wp-block-code"><code>svn commit --username "$SVN_USERNAME" --password "$SVN_PASSWORD" \
    --non-interactive --no-auth-cache -m "Release ${VERSION}"
</code></pre>



<p class="wp-block-paragraph">Zwei Details noch. Das Skript kennt <code>DRY_RUN=1</code>, dann bereitet es alles vor und zeigt nur den <code>svn status</code>. So habe ich es entwickelt, ohne das öffentliche Verzeichnis als Testumgebung zu benutzen. Und in der Pipeline steht der Job auf <code>interruptible: false</code>. Alle anderen Jobs dürfen abgebrochen werden, wenn ich einen neuen Commit nachschiebe. Ein halb übertragener SVN-Commit dagegen ist öffentlich sichtbarer Murks.</p>



<h2 class="wp-block-heading">Der Git-Tag: dieselbe Version in beiden Welten</h2>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph">Nach erfolgreichem SVN-Push setzt ein zweiter Job denselben Tag im Git, also <code>4.4.0</code> hier und <code>tags/4.4.0</code> dort. Damit finde ich Monate später zu jeder ausgelieferten Version den Commit, ohne zu raten. Bei einer Support-Anfrage ist das immer die erste Frage, die ich mir selbst stellen muss: Welchen Stand hat der Kunde da eigentlich installiert?</p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large has-lightbox"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_12-1024x559.jpeg" alt="sauber sortierte Pakete mit Tags" class="wp-image-3240" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_12-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_12-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_12-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_12.jpeg 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



<p class="wp-block-paragraph">Und hier bin ich in eine Falle gelaufen, die nichts mit WordPress zu tun hat. Der eingebaute Weg über das <code>release:</code>-Keyword in GitLab CI hat bei mir nicht funktioniert: Der Runner-Step delegiert intern an <code>glab</code>, und <code>glab</code> kennt keine Option, die TLS-Prüfung zu überspringen. Meine GitLab-Instanz läuft mit selbstsigniertem Zertifikat, also endete jeder Versuch bei <code>x509: certificate signed by unknown authority</code>. Die Lösung war, <code>release-cli</code> direkt im Script aufzurufen. Das spricht die Releases-API an, akzeptiert <code>--insecure-https</code> und liest Server-URL, Projekt-ID und Job-Token selbst aus den vordefinierten CI-Variablen.</p>



<pre class="wp-block-code"><code>tag:release:
  stage: deploy
  needs: &#91;"deploy:svn"]
  interruptible: false
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  script:
    - 'VERSION=$(grep -i "^Stable tag:" plugin/readme.txt | head -1 | sed ''s/.*: *//'' | tr -d ''&#91;:space:]'')'
    - &gt;
      release-cli --insecure-https create
      --tag-name "$VERSION" --ref "$CI_COMMIT_SHA" --name "$VERSION"
      --description "Release $VERSION - Changelog siehe plugin/readme.txt"
</code></pre>



<p class="wp-block-paragraph">Wer eine GitLab-Instanz mit ordentlichem Zertifikat hat, kann bei <code>release:</code> bleiben. Ich schreibe es trotzdem hin, weil die Fehlermeldung nach einem Zertifikatsproblem im Repository aussieht und man erstmal an der völlig falschen Stelle sucht.</p>



<h2 class="wp-block-heading">Was ich beim zweiten Plugin anders gemacht habe</h2>



<p class="wp-block-paragraph">Vor ein paar Wochen habe ich für einen <a href="https://www.ssvon.de">Sportverein hier im Ort</a> <a href="https://squadeno.com">ein Plugin gebaut</a>. Die sind mit ihrer Verwaltung ziemlich am Anschlag, Termine, Mannschaften, Ansprechpartner, das alles per Mail und Excel. Weil ich das Ergebnis sowieso brauchbar finde, wollte ich es auch veröffentlichen, und damit stand ich wieder vor derselben SVN-Strecke.</p>



<p class="wp-block-paragraph">Diesmal war es eine Stunde Arbeit statt eines Abends, weil das Muster stand. Vier Dinge habe ich dabei bewusst anders gelöst, und die zeigen ziemlich gut, wo die Shop-Pipeline noch die erste Ausbaustufe ist.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_10-1024x559.jpeg" alt="sortierte Artefakte" class="wp-image-3238" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_10-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_10-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_10-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_10.jpeg 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph"><strong>Online geht das Artefakt, nicht das Repository.</strong> Im Shop synchronisiert das Deploy-Skript den <code>plugin/</code>-Ordner aus dem Arbeitsverzeichnis. Im neuen Plugin ist die Quelle ausschließlich das gebaute Zip aus dem Build-Job. Das Skript entpackt es und schiebt genau diesen Inhalt ins SVN. Der Unterschied ist keine Kosmetik: So kann prinzipiell nichts online gehen, was nicht vorher gebaut, getestet und geprüft wurde, und Entwicklungsdateien können gar nicht erst mitrutschen.</p>
</div>
</div>



<p class="wp-block-paragraph"><strong>Der Checkout bleibt schlank.</strong> Ein vollständiger Checkout des Plugin-SVN holt jedes alte Release mit. Beim ersten Plugin fällt das nicht auf, bei Version 40 schon. Also sparse:</p>



<pre class="wp-block-code"><code>svn checkout --quiet --depth immediates "$SVN_URL" "$CHECKOUT"
# trunk und assets vollständig, tags nur als Verzeichnisliste
svn update --quiet --set-depth infinity trunk
svn update --quiet --set-depth immediates tags
</code></pre>



<p class="wp-block-paragraph">Der Inhalt alter Tags interessiert niemanden, ich muss nur wissen, welche es schon gibt.</p>



<p class="wp-block-paragraph"><strong>Der Deploy ist sichtbar, wartet aber auf mich.</strong> Im Shop läuft er auf jedem <code>master</code>-Commit automatisch. Beim neuen Plugin steht er als manueller Job in der Pipeline und wartet auf den Play-Knopf, und mit einer gesetzten Variable <code>WPORG_DEPLOY=1</code> läuft er automatisch durch. Zwei Regelzweige, weil GitLab einen Job, dessen Regel nicht greift, gar nicht anlegt, und eine leere Stage zeigt die Oberfläche nicht an. Mir war wichtig, dass ich die Stage sehe, auch wenn gerade nichts rausgehen soll.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph"><strong>Der Tag-Job stellt die richtige Frage.</strong> Das ist der Fehler, auf den ich am wenigsten stolz bin. Im Shop hängt der Tag-Job an einer <code>changes</code>-Regel auf <code>plugin/readme.txt</code>, damit er nur bei einem Versionsbump feuert. Klingt vernünftig, ist aber die falsche Frage. Die Regel fragt „hat sich diese Datei in diesem Commit geändert?&#8220;, entscheidend wäre „fehlt der Tag?&#8220;. Am 29. August ist mir das aufgefallen: Ein Deploy, den ich von Hand angestoßen hatte, blieb ohne Git-Tag zurück, weil der Commit die readme nicht angefasst hatte. Der neue Job läuft nach jedem erfolgreichen SVN-Deploy und entscheidet selbst:</p>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large has-lightbox"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-1024x559.jpeg" alt="Paketierungspipeline" class="wp-image-3242" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14.jpeg 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>
</div>



<pre class="wp-block-code"><code>if release-cli --insecure-https get --tag-name "$VERSION" &gt;/dev/null 2&gt;&amp;1; then
  echo "==&gt; Release ${VERSION} existiert bereits – kein neuer Tag."
  exit 0
fi
</code></pre>



<p class="wp-block-paragraph">Dieselbe Idempotenz-Idee wie beim SVN-Tag. Prüfe den Zustand, nicht das Ereignis.</p>



<p class="wp-block-paragraph">Dazu noch eine Kleinigkeit, die ich beim zweiten Mal sauberer gelöst habe: Die Version steht dort an drei Stellen, in der <code>readme.txt</code>, im Plugin-Header und in einer PHP-Konstante fürs Cache-Busting. Es gibt genau ein Skript, das sie ermittelt und dabei alle drei auf Gleichheit prüft. Build, Deploy und Tag greifen darauf zu. Im Shop steckt derselbe Vergleich noch im Build-Skript selbst.</p>



<h2 class="wp-block-heading">Was es gebracht hat</h2>



<p class="wp-block-paragraph">Ein Release ist bei mir jetzt ein Merge nach <code>master</code> und, beim neuen Plugin, ein Klick. Ich muss nichts über Subversion wissen, während ich das mache. Der Teil, der früher Konzentration gebraucht hat, ist der Teil, über den ich am wenigsten nachdenke.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:33.33%">
<figure class="wp-block-image size-large has-lightbox"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-1024x559.jpeg" alt="Paketierungspipeline" class="wp-image-3242" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_14.jpeg 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:66.66%">
<p class="wp-block-paragraph">Der zweite Gewinn ist der, mit dem ich nicht gerechnet habe: Weil das Deployen billig geworden ist, veröffentliche ich häufiger. Vorher habe ich Fixes gesammelt, bis sich der Aufwand „lohnt&#8220;. Version 4.3.0 mit den signierten Bild-URLs wäre früher ein Ereignis gewesen, für das ich mir einen ruhigen Abend freihalte.</p>
</div>
</div>



<p class="wp-block-paragraph">Wenn ich nochmal bei null anfinge, würde ich zwei Dinge sofort anders machen: das gebaute Artefakt deployen und nicht das Arbeitsverzeichnis, und den Plugin Check vom ersten Tag an als hartes Gate laufen lassen, bevor sich Altlasten ansammeln, die dann jahrelang im Store liegen. Die vier Punkte aus dem zweiten Plugin ziehe ich beim Shop nach, das steht auf der Liste.</p>



<p class="wp-block-paragraph">Und das SVN? Interessiert mich inzwischen ungefähr so sehr wie der Druckertreiber.</p>



<div class="wp-block-columns is-layout-flex wp-container-core-columns-is-layout-8f761849 wp-block-columns-is-layout-flex">
<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:25%"></div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:50%">
<figure class="wp-block-image size-large has-lightbox"><img loading="lazy" decoding="async" width="1024" height="559" src="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_08-1024x559.jpeg" alt="geordneter Schreibtisch am Morgen" class="wp-image-3236" srcset="https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_08-1024x559.jpeg 1024w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_08-300x164.jpeg 300w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_08-768x419.jpeg 768w, https://www.schiffler.eu/wp-content/uploads/2026/09/gitSvnPipeline_08.jpeg 1408w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>
</div>



<div class="wp-block-column is-layout-flow wp-block-column-is-layout-flow" style="flex-basis:25%"></div>
</div>
<p>Der Beitrag <a href="https://www.schiffler.eu/wordpress-plugin-gitlab-ci-nach-svn-deployen/">WordPress-Plugin mit GitLab CI nach SVN deployen: mein Release läuft auf einen Klick</a> erschien zuerst auf <a href="https://www.schiffler.eu">Das ist die Welt von Thomas</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.schiffler.eu/wordpress-plugin-gitlab-ci-nach-svn-deployen/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Object Caching 43/50 objects using APC
Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Minified using APC
Database Caching 5/21 queries in 0.002 seconds using APC

Served from: www.schiffler.eu @ 2026-09-08 14:06:25 by W3 Total Cache
-->