Gefiltert, nur nicht die Nachrichten
Zwei meiner Werkzeuge entstehen auf der eigenen GitLab-Instanz und werden auf GitHub veröffentlicht. Ein CI-Job schreibt dabei die Historie um. Er entfernt interne Dateien und ersetzt interne Links durch öffentliche. Ich hielt das für vollständig, und es war es nicht. Teil 8 von „The Ops Log“.
TL;DR
TL;DR:git filter-repo mit --path und --replace-text bereinigt Dateien, aber keine Commit-Nachrichten und keine Tag-Texte. In einem Repository standen deshalb interne Links öffentlich in der Historie, im zweiten wären Namen interner Projekte hinausgegangen. Der Mirror schreibt jetzt auch die Nachrichten um. Danach prüft er jede Nachricht und jeden Tag gegen dieselben Muster und pusht nicht, solange etwas übrig ist.
Was passiert ist
git filter-repo entfernt mit --path Dateien aus jeder Revision und ersetzt mit --replace-text Text in Dateiinhalten. Beides betrifft Blobs, also Dateien. Commit-Nachrichten und die Texte annotierter Tags sind keine Blobs. Sie gehen unverändert durch.
Zwei Funde
Im ersten Repository standen in den Release-Commits Links auf die interne GitLab-Instanz. Das Release-Werkzeug schreibt den Changelog auch in die Commit-Nachricht, mit einem Link je Commit. Die Datei CHANGELOG.md war bereinigt, ihre Kopie in der Nachricht nicht, und die war veröffentlicht.
Im zweiten Repository nannten Commit-Nachrichten die Namen interner Projekte, weil eine Ausnahme in der Konfiguration genau sie betraf. Die Konfiguration selbst wird nicht gespiegelt. Die Nachricht, die sie erklärte, wäre es geworden. Hinaus ging nichts, weil der Mirror noch kein Token hatte.
Aufgefallen ist beides erst, als ich gezielt nachgesehen habe, ob wirklich nichts Internes hinausgeht. Die Dateien waren sauber, die Nachrichten nicht.
Auch die Nachrichten umschreiben, dann prüfen
Die Mirror-Komponente hat einen dritten Eingang bekommen, replace-message. Er ruft git filter-repo --replace-message auf und schreibt damit Commit-Nachrichten und Tag-Texte um. Danach liest der Job jede Nachricht der umgeschriebenen Historie und jeden Tag-Text. Jedes Muster prüft er erneut, wörtlich, als regulären Ausdruck oder als Glob. Bleibt ein Treffer, bricht der Job ab und pusht nicht.
Die Musterliste hat zwei Schichten. Oben stehen gezielte Umformulierungen, damit die Sätze lesbar bleiben. Darunter stehen einzelne Namen als Netz. Was die gezielten Regeln verfehlen, ersetzt das Netz. Was auch das Netz nicht erwischt, hält die Prüfung auf.
Die öffentliche Historie neu geschrieben
Für das erste Repository hieß das: die GitHub-Historie per Force-Push ersetzen, alle Tags neu schreiben und danach von außen prüfen. Ein frischer Klon von GitHub, alle Nachrichten und Tag-Texte durchsucht, ergab null Treffer für die interne Domain. Stand 13. Oktober: 78 Commits und 10 Tags, alle Nachrichten sauber.
Wer den alten Stand geklont hat, behält ihn allerdings. Ein Force-Push löscht nichts aus fremden Kopien.
Zwei Stolperfallen
Die Musterliste kommt als JSON in einen Python-String, und ein \w darin zerstörte das JSON. Statt Escapes stehen deshalb Zeichenklassen in der Liste, zum Beispiel [.] für einen Punkt.
Außerdem verschwinden leere Commits. Ein Commit, der nur entfernte Dateien geändert hatte, ist nach dem Filter leer. Er fällt mitsamt seiner Nachricht weg, und das ist hier erwünscht.
Die Lehre
Ein öffentlicher Mirror veröffentlicht die ganze Historie, also Dateien, Nachrichten, Tags und Release-Texte. Ein Filter ohne Prüfung danach ist eine Hoffnung, und die Prüfung muss den Push verhindern können. Die beste Regel steht aber vor jedem Filter: In Repositories mit öffentlichem Mirror kommen keine internen Namen in Commit-Nachrichten.
Häufige Fragen
Reicht --replace-text nicht?+
Nein. Es wirkt nur auf Dateiinhalte. Für Nachrichten und Tags braucht es --replace-message.
Hilft ein Force-Push?+
Für alle, die danach klonen, ja. Wer den alten Stand schon hat, behält ihn.
Was, wenn ein Geheimnis in einer Nachricht stand?+
Dann reicht Umschreiben nicht. Das Geheimnis gilt als veröffentlicht und wird ausgetauscht.
Fazit
Gefiltert habe ich, was ich als Datei vor Augen hatte. Die Nachrichten standen nicht auf meiner Liste, weil ich sie nie als Inhalt gesehen habe. Seitdem prüft der Mirror das Ergebnis und nicht meine Annahme, und ein Treffer hält den Push auf.
Wer den Anfang verpasst hat, findet ihn in Teil 7: Erst die Freigabe, dann der Schalter.
Ich prüfe deine öffentlichen Mirrors auf das, was der Filter nicht sieht.
Dateien, Commit-Nachrichten, Tags und Release-Texte gegen eine Musterliste, dazu ein Mirror-Job, der nicht pusht, solange etwas übrig ist.
Statt Beratung auf Papier betreibe ich den Mirror anschließend mit.
Über den Autor

Kai Ole Hartwig
Programmiert seit 2002 – autodidaktisch gelernt, 2012 mit KO-Web selbständig gemacht. Über 100 Projekte, Fokus auf Security, Performance, Automatisierung und Qualität. Heute freiberuflich: DevSecOps-Beratung, Schulungen und Softwareentwicklung.