Anhang A. Git’s Mängel

Ein paar Git-Probleme habe ich bisher unter den Teppich gekehrt. Einige lassen sich einfach mit Skripten und Hooks lösen, andere erfordern eine Reorganisation oder Neudefinition des gesamten Projekt und für die wenigen verbleibenden Beeinträchtigungen kannst Du nur auf eine Lösung warten. Oder noch besser, anpacken und mithelfen.

SHA1 Schwäche

Mit der Zeit entdecken Kryptographen immer mehr Schwächen an SHA1. Schon heute wäre für finanzkräftige Unternehmen es technisch machbar, Hash-Kollisionen zu finden. In ein paar Jahren hat vielleicht schon ein ganz normaler Heim-PC ausreichend Rechenleistung, um ein Git Reopsitory unbemerkt zu korrumpieren.

Hoffentlich stellt Git auf eine bessere Hash Funktion um, bevor die Forschung SHA1 komplett unnütz macht.

Microsoft Windows

Git unter Microsoft Windows kann frustrierend sein:

Dateien ohne Bezug

Wenn Dein Projekt sehr groß ist und viele Dateien enthält, die in keinem direkten Bezug stehen, trotzdem aber häufig geändert werden, kann Git nachteiliger sein als andere Systeme, weil es keine einzelnen Dateien überwacht. Git überwacht immer das ganze Projekt, was normalerweise schon von Vorteil ist.

Eine Lösung ist es, Dein Projekt in kleinere Stücke aufzuteilen, von denen jedes nur die in Beziehung stehenden Dateien enthält. Benutze git submodule wenn Du trotzdem alles in einem einzigen Repository halten willst.

Wer macht was?

Einige Versionsverwaltungssysteme zwingen Dich explizit, eine Datei auf irgendeine Weise für die Bearbeitung zu kennzeichnen. Obwohl es extrem lästig ist, wenn es die Kommunikation mit einem zentralen Server erfordert, so hat es doch zwei Vorteile:

  1. Unterschiede sind schnell gefunden, weil nur die markierten Dateien untersucht werden müssen.
  2. Jeder kann herausfinden wer sonst gerade an einer Datei arbeitet, indem er beim zentralen Server anfragt, wer die Datei zum Bearbeiten markiert hat.

Mit geeigneten Skripten kannst Du das auch mit Git hinkriegen. Das erfordert aber die Mitarbeit der Programmierer, denn sie müssen die Skripte auch aufrufen, wenn sie eine Datei bearbeiten.

Dateihistorie

Da Git die Änderungen über das gesamte Projekt aufzeichnet, erfordert die Rekonstruktion des Verlaufs einer einzelnen Datei mehr Aufwand als in Versionsverwaltungssystemen, die einzelne Dateien überwachen.

Die Nachteile sind üblicherweise gering und werden gern in Kauf genommen, da andere Operationen dafür unglaublich effizient sind. Zum Beispiel ist git checkout schneller als cp -a, und projektweite Unterschiede sind besser zu komprimieren als eine Sammlung von Änderungen auf Dateibasis.

Der erster Klon

Einen Klon zu erstellen ist aufwendiger als in anderen Versionsverwaltungssystemen, wenn ein längerer Verlauf existiert.

Der initiale Aufwand lohnt sich aber auf längere Sicht, da die meisten zukünftigen Operationen dann schnell und offline erfolgen. Trotzdem gibt es Situationen, in denen es besser ist, einen oberflächlichen Klon mit der --depth Option zu erstellen. Das geht wesentlich schneller, aber der resultierende Klon hat nur eingeschränkte Funktionalität.

Unbeständige Projekte

Git wurde geschrieben um schnell zu sein, im Hinblick auf die Größe der Änderungen. Leute machen kleine Änderungen von Version zu Version. Ein einzeiliger Bugfix hier, eine neue Funktion da, verbesserte Kommentare und so weiter. Aber wenn sich Deine Dateien zwischen aufeinanderfolgenden Versionen gravierend ändern, dann wird zwangsläufig mit jedem Commit Dein Verlauf um die Größe des gesamten Projekts wachsen.

Es gibt nichts, was irgendein Versionsverwaltungssystem dagegen machen kann, aber der Standard Git Anwender leidet mehr darunter, weil normalerweise der ganze Verlauf geklont wird.

Die Ursachen für die großen Unterschiede sollten ermittelt werden. Vielleicht können Dateiformate geändert werden. Kleinere Bearbeitungen sollten auch nur minimale Änderungen an so wenig Dateien wie möglich bewirken.

Vielleicht ist eher eine Datenbank oder Sicherungs-/Archivierungslösung gesucht, nicht ein Versionsverwaltungssystem. Ein Versionsverwaltungssystem zum Beispiel ist eine ungeeignete Lösung um Fotos zu verwalten, die periodisch von einer Webcam gemacht werden.

Wenn die Dateien sich tatsächlich konstant verändern, und sie wirklich versioniert werden müssen, ist es eine Möglichkeit, Git in zentralisierter Form zu verwenden. Jeder kann oberflächliche Klone erstellen, die nur wenig oder gar nichts vom Verlauf des Projekts enthalten. Natürlich sind dann viele Git Funktionen nicht verfügbar, und Änderungen müssen als Patches übermittelt werden. Das funktioniert wahrscheinlich ganz gut, wenn auch unklar ist, warum jemand die Versionsgeschichte von wahnsinnig instabilen Dateien braucht.

Ein anderes Beispiel ist ein Projekt, das von Firmware abhängig ist, welche die Form einer großen Binärdatei annimmt. Der Verlauf der Firmware interessiert den Anwender nicht und Änderungen lassen sich schlecht komprimieren, so blähen Firmwarerevisionen die Größe des Repository unnötig auf.

In diesem Fall sollte der Quellcode der Firmware in einem Git Repository gehalten werden und die Binärdatei außerhalb des Projekts. Um das Leben zu vereinfachen, könnte jemand ein Skript erstellen, das Git benutzt, um den Quellcode zu klonen und rsync oder einen oberflächlichen Klon für die Firmware.

Globaler Zähler

Verschiedene Versionsverwaltungssysteme unterhalten einen Zähler, der mit jedem Commit erhöht wird. Git referenziert Änderungen anhand ihres SHA1-Hash, was in vielen Fällen besser ist.

Aber einige Leute sind diesen Zähler gewöhnt. Zum Glück ist es einfach, Skripte zu schreiben, sodass mit jedem Update das zentrale Git Repository einen Zähler erhöht. Vielleicht in Form eines Tags, der mit dem SHA1-Hash des letzten Commit verknüpft ist.

Jeder Klon könnte einen solchen Zähler bereitstellen, aber der wäre vermutlich nutzlos, denn nur der Zähler des zentralen Repository ist für alle relevant.

Leere Unterverzeichnisse

Leere Unterverzeichnisse können nicht überwacht werden. Erstelle eine Dummy-Datei, um dieses Problem zu umgehen.

Die aktuelle Implementierung von Git, weniger sein Design, ist verantwortlich für diesen Pferdefuß. Mit etwas Glück, wenn Git’s Verbreitung zunimmt und mehr Anwender nach dieser Funktion verlangen, wird sie vielleicht implementiert.

Initialer Commit

Ein klischeehafter Computerwissenschaftler zählt von 0 statt von 1. Leider, bezogen auf Commits, hält sich Git nicht an diese Konvention. Viele Kommandos sind mürrisch vor dem intialen Commit. Zusätzlich müssen verschiedene Grenzfälle speziell behandelt werden, wie der Rebase eines Branch mit einem abweichenden initialen Commit.

Git würde davon provitieren, einen Null-Commit zu definieren: sofort nach dem Erstellen eines Repository wird der HEAD auf eine Zeichenfolge von 20 Null-Bytes gesetzt. Dieser spezielle Commit repräsentiert einen leeren Tree, ohne Eltern, irgendwann vielleicht der Vorfahr aller Git Repositories.

Würde dann zum Beispiel git log ausgeführt, würde der Anwender darüber informiert, dass noch keine Commits gemacht wurden, anstelle mit einem fatalen Fehler zu beenden. Das gilt stellvertretend für andere Anweisungen.

Jeder initiale Commit ist dann stillschweigend ein Abkömmling dieses Null-Commits.

Leider gibt es noch ein paar Problemfälle. Wenn mehrere Branches mit unterschiedlichen initialen Commits zusammengeführt und dann ein Rebase gemacht wird, ist ein manuelles Eingreifen erforderlich.

Eigenarten der Anwendung

Für die Commits A und B, hängt die Bedeutung der Ausdrücke "A..B" und "A…B" davon ab, ob eine Anweisung zwei Endpunkte erwartet oder einen Bereich. Siehe git help diff und git help rev-parse.