Git LFS - Große Dateien in git Repositories sichern

Ich möchte gar nicht lange um den heißen Brei herumreden: Git ist eine Versionsverwaltung, die hervorragend mit Textdateien bzw. Source Code zurechtkommt, aber nicht mit Binärdateien (Bilder, Musik, Videos, etc.).

Jetzt ist es aber so, dass man gelegentlich doch gleichzeitig Code und Binärdateien in einem Repository braucht.

Bis ca. 1 GB Daten klappt das auch noch recht gut, aber darüber wird's wirklich unangenehm. Leider sind meine Repos, bei denen ich Binärdateien mit drin habe, mittlerweile 2-50 GB groß, und um's kurz zu sagen: Das macht einfach keinen Spaß mehr.

Abhilfe schafft Git LFS - der Git Large File Storage.

Da Git LFS in der Standard-Git-Installation schon enthalten ist (außer ihr habt irgendein total kurioses Setup), gibt's eigentlich keinen Grund das nicht zu verwenden. Die größeren Code-Hosting-Plattformen wie GitHub und GitLab unterstützen Git LFS, und auch der mittels Gitea selbst gehostete Server hat bereits ein entsprechendes Backend integriert, man muss es maximal noch aktivieren.

Für die, die selbst einen Git-Server betreiben übrigens auch ein Vorteil: Das LFS-Backend kann man i.d.R. auslagern, d.h. das muss nicht zwangsweise auf dem gleichen Server liegen. Gitea kann bspw. einen S3-Storage dafür verwenden. Und wer auch den S3-kompatiblen Storage gerne selbst hostet kann dafür bspw. Min.io verwenden. Ich liebe sowas ja :D

Git LFS einrichten

Bevor man Git LFS nutzen kann muss die Erweiterung einmalig installiert werden ("einmalig" bedeutet hier pro Nutzeraccount/pro Gerät gemacht werden):

$ git lfs install

Ein neues Repo mit Git LFS anlegen

Zuerst legt man fest, welche Art von Dateien (im aktuellen Repo) über LFS getrackt werden sollen:

$ git lfs track "*.jpg"
$ git lfs track "*.png"

Daraufhin wird eine Datei namens .gitattributes angelegt, welche man zum Repo hinzufügen muss:

$ git add .gitattributes

Anschließend den Spaß ins Repo committen und fertig:

$ git commit -m "Added .gitattributes file/LFS"
$ git push origin main

Vorhandenes Repo zu Git LFS migrieren, inklusive Rewrite der History

Wenn man ein bestehendes Repo konvertieren möchte wird's etwas komplexer. Hier gibt's zwei Möglichkeiten:

  • Nur neue Dateien via Git LFS tracken lassen
  • Das komplette Repo neu schreiben um auch bestehende Daten via Git LFS zu tracken

Die erste Option funktioniert im Prinzip so, wie oben beim Anlegen eines neuen Repos bereits beschrieben: Mittels git lfs track tracken lassen, und .gitattributes mit ins Repo aufnehmen.

Hat nur einen gravierenden Nachteil: Wenn man schon ein gutes Gigabyte Daten in einem Repo hat, wird es dadurch nicht schneller. Es vermeidet nur, dass Checkout, Commit, etc. noch langsamer werden.

Migration eines bestehenden Repos

Die wesentlich bessere Option (sofern möglich, schließlich muss jede Person, die das Repo nutzt, mitziehen), ist eine komplette Migration.

Dazu kann man zuerst einen Dry-Run machen:

$ git lfs migrate info --everything --include="*.jpg,*.png"

Und wenn man sich sicher ist, dass das passt, führt man die Migration aus:

$ git lfs migrate import --everything --include="*.jpg,*.png" --verbose

Die Migration erstellt im Prinzip ein komplett neues Repo, welches dann gepusht werden muss. Ein Upload kann entsprechend lange dauern.

Checkout im lokalen Repository

Wer ein bestehendes Repository migriert muss gegebenenfalls noch einen LFS checkout durchführen:

$ git lfs checkout

Führt man den Befehl nicht aus, kann es vorkommen, dass lokalen Dateien, die über LFS verwaltet werden, 0 Byte groß sind.

File-Locking

Git LFS unterstützt auch Lockfiles, wobei vom verwendeten Server abhängt ob man diese benutzen kann oder nicht. In der Regel wird man von Git beim Pushen darauf hingewiesen, wenn ein Server Lockfiles unterstützt, man diese aber noch nicht nutzt.

Um Lockfiles global für alle Repositories zu aktivieren reicht folgender Befehl:

$ git config --global lfs.locksverify true

Alternativen zu Git LFS

Ich habe mir auch Alternativen zu Git LFS angesehen und mal zusammengefasst. Ist nicht hübsch, und einzig relevant ist im Prinzip nur git-annex. Da Git LFS aber mehr oder weniger schon Teil der Standard-Git-Installation ist, ergibt git-annex für mich nur dort Sinn, wo es bereits verwendet wird. Für neue Repos würde ich immer zu Git LFS greifen.

Sortierung ist nach absteigender Relevanz bzw. Datum der letzten Aktivität

  • git-annex - Dateien werden hierbei in einem seperaten Repository über git-annex verwaltet und auch nur bei Bedarf bzw. auf Befehl heruntergeladen, unterstützt prinzipiell viele und gleichzeitig mehrere Storage-Backends (verteiltes System), Projekt scheint etwas altbacken, öffentliches Repo schwer zu finden, wäre gut möglich, dass das Projekt gerade einen langsamen Tod stirbt, letzter Release August 2020

  • git-bigstore - begrenzte Möglichkeit bzgl. Storage-Backend, arbeitet wie git-lfs via .gitattributes`, letzte Änderung Februar 2020

  • git-fat - Integration in git ähnlich git-lfs, arbeitet via .gitattributes, Storage muss pro Repo wenn man's richtig macht nur einmalig konfiguriert werden, ein separater Storage ist dennoch nötig, letzte Änderung August 2018

  • git-sym - arbeitet mit Symlinks, benötigt einen separaten Befehl (git-sym), keine Automatisierung bzw. viel Handarbeit nötig, letzte Änderung März 2018

  • git-bigfile - Fork von git-media mit gleichen Vor-/Nachteilen, Projekt offiziell deprecated (letzte Änderung Juli 2016), empfiehlt die Migration nach git-lfs

  • git-media - arbeitet wie git-lfs via .gitattributes, externer Storage nötig welcher separat konfiguriert werden muss, ansonsten scheinbar gut in git integriert, letzte Änderung September 2015


Die Inspiration für diesen Artikel stammt übrigens von hier.