NuGet-Pakete in Git ausschließen (packages-Ordner)

NuGet ist eine praktische Paketverwaltung für die .NET Welt: Andere Entwickler können dort Bibliotheken zur Verfügung stellen, damit sie jeder Entwickler verwenden kann. Im besten Falle sind diese zudem quelloffen, sodass jeder mitwirken kann – statt das Rad ständig neu zu erfinden. Verwendet man für den eigenen Quellcode, in dem man die NuGet-Pakete verwendet, eine Versionsverwaltung, sollte man jedoch darauf achten, die NuGet-Ordner dort korrekt auszuschließen.

Warum muss NuGet ausgeschlossen werden?

Der Paketmanager erstellt pro Projektmappe einen Zwischenspeicher der heruntergeladenen Pakete im Ordner packages. Damit entfällt das mehrfache herunterladen eines Paketes, weil man dies in mehreren Projekten einer Projektmappe verwendet. In diesem Ordner befinden sich die Bibliotheken (meist .dll Dateien) mit ggf. notwendigen Abhängigkeiten.

Doch Zusammen mit der Quellcode-Verwaltung Git ergibt sich damit ein Problem: Der packages-Ordner wird standardmäßig ebenfalls versioniert. Das kann nicht nur die Übersicht stören, sondern bläht das Repository auf: Das Klonen wird langsamer und es verschwendet zudem Speicher. Mit ein paar Paketen und ihren Abhängigkeiten wächst er nämlich schnell auf dutzende MB an.

Generell ist es keine gute Idee, Binärdateien zu Versionieren. Git kann dort keine Unterschiede erkennen und sie inkrementell speichern, was bei textbasierten Dateien sehr effizient funktioniert. Anders ausgedrückt: Ihr habt dort eine 500 KB große DLL-Datei. Wird diese aktualisiert, belegt sie im Repository noch mal 500 MB (also insgesamt 1 MB) – selbst wenn nur Kleinigkeiten geändert wurden. Das mag bei 500 KB noch wenig erscheinen. Es summiert sich aber, zumal der Speicherbedarf durch Updates mit der Zeit wächst.

Mit Paketmanagern wie NuGet besteht zudem keine Notwendigkeit dafür: Sie dokumentieren in der csproj-Datei des Projektes, welches Paket in welcher Version benötigt wird. Dadurch kann man sich diese bei Bedarf aus dem Netz laden.

So löst ihr das Problem

Es empfiehlt sich daher, mithilfe der versteckten Datei .gitignore den Ordner auszuschließen. Dazu öffnet man besagte Datei mit einem Texteditor und fügt die beiden folgenden Zeilen ein:

/packages/
!/packages/repositories.config

Git wird den Inhalt des Ordners nun ignorieren, lediglich die Konfigurationsdatei der Repositories ist davon ausgenommen. Dort sind ggf. manuell hinzugefügte eigene Paketquellen aufgelistet. Ähnlich wie bei der csproj handelt es sich um eine reine Konfigurationsdatei, die man problemlos versionieren kann und sollte.

Wichtig ist zu verstehen, dass die .gitignore nur für neue/veränderte Dateien gilt. Sie hat keine Auswirkungen auf bereits eingecheckte Inhalte bestehender Commits – diese bleiben bestehen, selbst wenn man nachträglich eine Datei ausschließt. Am wenigsten Arbeit und Probleme macht man sich, in dem die .gitignore zu Beginn eines Projektes als Erstes anlegt. Vor dem ersten Commit sorgsam prüfen, was für Dateien eingecheckt werden und ggf. entsprechende Ausnahmen festlegen. Die gibt es in nahezu jedem Projekt. In .NET werden z.B. Binärdateien in bin angelegt. Dort macht es ebenfalls wenig Sinn, nach jedem Build neue Binärdateien zu haben, die Git als neue Änderungen erfasst.

Bereits eingecheckten packages-Ordner entfernen?

Wenn ihr den packages Ordner bereits zuvor eingecheckt habt, könnt ihr diesen mit git rm wieder entfernen:

git rm -r --cached packages
git commit -m 'Removed NuGet packages directoy"'
git push origin master

Dies bezieht sich nur auf Git und entfernt nichts in eurem Arbeitsverzeichnis. Die Pakete bleiben weiterhin dort, wo sie für die Entwicklung ja auch gebraucht werden. Allerdings hat auch das keine Auswirkungen auf den Verlauf (History) eures Repositorys. Mit anderen Worten: In früheren Commits ist der Ordner weiterhin vorhanden und belegt damit beim Klonen weiterhin Speicher.

Das kann theoretisch gelöst werden, in dem man den Verlauf neu schreibt: Vereinfacht gesagt entfernt das den Ordner aus dem Commit, in dem er erstmalig hinzugefügt wurde. Außerdem in allen weiteren Commits, die z.B. durch Updates oder die Installation neuer Pakete in diesem Ordner entstanden sind. Dies ist – wenn überhaupt – mit äußerster Vorsicht zu genießen: Git ist nicht dafür gedacht, Commits bauen aufeinander auf. Zwar bietet Git Möglichkeiten um dies umzusetzen. Allerdings werden alle Kopien dadurch unbrauchbar bzw. es kann zu Problemen führen, da die ja dann einen anderen Versionsstand besitzen.

Praktikabel ist das aus meiner Sicht nur, wenn ihr alleine an dem Repository arbeitet. Im Falle von mehreren Personen müsst ihr sicherstellen, dass dies zu einem festgelegten Zeitpunkt stattfindet, alle informiert sind, ihre lokalen Kopien löschen und neu klonen. Zu empfehlen ist es nicht. Gerade bei kleineren Datenmengen ist der bessere Weg, diesen Fehler mit der .gitignore nachträglich zu korrigieren und den Datenmüll im Verlauf stehen zu lassen. Beim nächsten Repository weiß man es dann besser, sodass dieses Problem von Anfang an vermieden werden kann.

Leave a Reply