Datenverlust auf Production mit git clean – Eine Story aus dem Support

Weshalb “git clean” zu Datenverlust geführt hat und weshalb dem nicht immer so ist. Eine kleine technische Analyse aus dem Support.

Datenverlust ist unschön und doch werden wir im Support hin und wieder mit dem Thema konfrontiert. In den meisten Fällen geht es nur darum, beim Restore zu unterstützen. In Fällen, bei denen nicht klar ist, wie es dazu kam, analysieren wir das Problem aber auch gerne gemeinsam mit dem Kunden.

Das Git-Clean-Problem

Kürzlich wandte sich ein Kunde an unseren Support. Sein Git-Workflow, welcher in anderen Fällen zu funktionieren schien, hatte zu Datenverlust geführt. Nach einem Restore der Daten boten wir dem Kunden an, uns das Problem genauer anzuschauen und sein Vorgehen zu analysieren.

Das Support-Ticket lässt sich wie folgt zusammenfassen:

  • Das Ausführen des Befehls git clean hat den Medien-Ordner der Website gelöscht.
  • Somit sind Assets wie Bilder und Videos nicht mehr vorhanden.
  • Der Medien-Ordner steht im gitignore-File, ist somit untracked und wird nicht von git verwaltet.
  • Das Problem ist lokal nicht reproduzierbar.
  • Der Kunde hat nicht erwartet, dass das passiert.

In solchen Fällen versuchen wir, das Problem lokal oder auf einem Test-System mit denselben Software-Versionen nachzuvollziehen.

$ history | grep "git clean"
1757  2020-10-08 11:03  git clean -fd

Gemäss zsh history hat der Kunde ein git clean -fd ausgeführt. Nun stellt sich also die Frage: Löscht ein git clean -fd auch Files aus Verzeichnissen, die von Git ignoriert werden? Damit sind Verzeichnisse gemeint, die im gitignore-File stehen und deshalb mit git status ebenfalls nicht angezeigt werden. Aus Sicht von Git denkt man daher auch nicht unbedingt daran, dass da noch Daten liegen.

Die Antwort: Es kommt drauf an.

Der Ablauf lässt sich wie folgt nachstellen:

git init
echo "/www/media/*" > .gitignore
mkdir -p www/media/
echo "test" > www/media/testfile
git add .
git commit -m "initial commit"
git clean -fd

Wird das Testfile gelöscht oder nicht?

Hier wird es interessant: In unseren Tests können wir das Verhalten mit Debian 9 nachvollziehen, nicht aber mit Debian 10. Die Manpage git-clean(1) schreibt für die Optionen -f und -d unter Debian 9 und 10 eins zu eins dasselbe, gibt also auch nicht weiter Auskunft, weshalb wir diese Differenzen feststellen.

  • Git 2.11.0 (Debian 9): Testfile wird gelöscht.
  • Git 2.20.1 (Debian 10): Testfile wird nicht gelöscht.

Wir stellen also ein anderes Verhalten fest, je nach Git-Version. Das ist natürlich ungünstig. Je nachdem, welches Verhalten man sich gewöhnt ist und erwartet, kann das zu bösen Überraschungen führen.

Und tatsächlich wurde das Verhalten von git clean -fd angepasst. In einer git commit description (siehe 6b1db43 auf GitHub) schreibt das Git-Projekt folgendes.

«There is an implicit assumption that a directory containing only untracked and ignored paths should itself be considered untracked. This makes sense in use cases where we’re asking if a directory should be added to the git database, but not when we’re asking if a directory can be safely removed from the working tree; as a result, clean -d would assume that an “untracked” directory containing ignored paths could be deleted, even though doing so would also remove the ignored paths.»

«To get around this, we teach clean -d to collect ignored paths and skip an untracked directory if it contained an ignored path, instead just removing the untracked contents thereof.»

Hier wurde also das Verhalten von git clean -df geändert, was zu diesem Missverständnis geführt hat. Im Grunde geschah die Änderung aber mit guter Absicht.

Fazit

Software verändert sich. Deshalb ist immer davon auszugehen, dass etwas nicht wie geplant funktioniert. Automatische Pipelines wie z. B. GitLab CI/CD helfen dabei, einen automatischen und erprobten Workflow zu finden, welcher zuerst innerhalb derselben Umgebung getestet werden kann. Im beschriebenen Fall war dann aber doch auch ein bisschen Pech dabei.

Übrigens: Ab Managed Server Generation 7 sind alle Backups Read-only unter ~/backup gemountet und zugänglich. Somit ermöglichen wir einen selbstständigen und schnellstmöglichen Restore der Daten. Technisch gelöst haben wir das mit dem Filesystem Btrfs. Aber das ist ein Thema für einen anderen Blogpost…