Aus Linux-Magazin 03/2006

Versionskontrollsystem des Linux-Kernels

© photocase.com

Ohne ordentliche Verwaltung des Quellcode wäre die Kernelentwicklung für Linux längst im Chaos versunken. Seit Mitte 2005 ist dafür das Programm Git zuständig, das vom Kernel-Hüter Linus Torvalds selbst stammt. Dieser Artikel gibt einen Überblick über Geschichte und Anwendung des Tools.

Große freie Softwareprojekte verwalten erfordert viel Disziplin und die passenden Werkzeuge, damit viele Entwickler über weite Entfernungen und unterschiedliche Zeitzonen hinweg gemeinsam am Quellcode arbeiten können. Der Linux-Kernel hat in dieser Beziehung schon mehrere Abläufe erlebt.

Nach und nach lernten die Entwickler aus den Fehlern der Vergangenheit und entwickelten angemessene Regeln und Werkzeuge. Sie definierten die Kriterien der Versionsbenennung neu, legten mehrere Protokolle fest, die anzeigen wie und wann die Programmierer ihre Patches abgeben müssen, wie man die Verteilerlisten benutzt und den Quellcode des Betriebssystemkerns verwaltet und verteilt.

Eines der ersten Systeme zur Quellcode-Verwaltung war das Concurrent Version System (CVS, [1]), das bis Ende der 90er Jahre als das beste Werkzeug galt. Heute hat es seine herausragende Stellung gegenüber Konkurrenten wie Subversion [2] eingebüßt. CVS-Benutzer kennen die Probleme mit der Umbenennung von Dateien, mit nicht atomaren Transaktionen und mit dem Schneckentempo mancher wichtiger Operationen. Um Missverständnissen vorzubeugen: CVS ist ein hervorragendes Werkzeug für kleine Projekte, aber für große und für solche mit vielen Entwicklungszweigen ist es nicht unbedingt empfehlenswert.

Das beste Werkzeug für die Aufgabe

Viele Kernelprogrammierer, unter ihnen Linus Torvalds, sahen spätestens seit Kernel 2.4 in CVS den ausschlaggebenden Grund dafür, warum sich die Kernelentwicklung verzögerte. Verlorene oder nie aufgenommene Patches und die wegen der zentralen Aufbewahrung geringere Produktivität galten als Gründe für die schlechtere Qualität des fertigen Programmcode.

Schließlich übertrug Linus Torvalds 2002 die gesamte Verwaltung des Quellcode dem nicht freien Werkzeug Bitkeeper [3] der Firma Bitmover. Larry McVoy, der Präsident des Unternehmens, stellte das Programm und die Bitkeeper-Repositories den Kernelentwicklern kostenlos zur Verfügung. Viele Entwickler verstanden das als Eindringen des Teufels in die Küche der Gemeinschaft der freien Software. Richard Stallman und die halbe Community wiesen auf die Gefahren hin, die damit verbunden seien, einem proprietären Projekt die Entwicklung eines freien anzuvertrauen. Aber Linus’ Argument ließ wie häufig keinen Widerspruch zu: Er habe das beste Werkzeug für die Aufgabe gewählt.

Böser Bitkeeper

Bitkeeper unterstützt besonders die dezentrale Entwicklung: Jeder Programmierer arbeitet an einem eigenen Entwicklungszweig und kann diesen irgendwann mit dem Code der anderen Kollegen vereinen. Tatsächlich gewöhnten sich die Beteiligten schnell an diese Arbeitsweise.

Jene Teile der Community, die es für unpassend hielten, ein unfreies Protokoll für eine derart delikate Angelegenheit zu verwenden, beschäftigten sich derweil mit Reverse Enginering: Sie analysierten das Protokoll und fingen an einige Algorithmen und internen Mechanismen, die Bitmover geheim hielt, zu durchleuchten. Daraus entstand ein Open-Source-Werkzeug, das Bitkeeper-Repositories zumindest exportieren konnte.

Nach verschiedenen Streitereien zwischen der Firma und den Entwicklern zog Bitmover zu Beginn des Jahres 2005 die Erlaubnis zurück, Bitkeeper gratis für freie Softwareprojekte zu verwenden. Linus Torvalds begann anschließend damit, mehrere freie Versionskontrollsysteme zu testen, zum Beispiel Monotone, Arch [4] und Darcs. Sie alle schieden aber aus, weil sie entweder zu langsam oder sehr schwierig zu bedienen waren. Ansätze wie den von Subversion zog er wegen ihrer zentralistischen Architektur gar nicht in Erwägung.

Neuentwicklung

Nach dreijähriger Nutzung eines privaten Werkzeugs für die Versionskontrolle, nach dem Entzug der Lizenz und nach einer Lawine von “Ich hab’s dir ja gesagt”-Nachrichten stand plötzlich kein Programm mehr zur Verfügung, um die Kronjuwelen der freien Software zu verwalten, den Quellcode des Linux-Kernels. Die Antwort von Linus kam Anfang April 2005: Git. Einige wenige Dateien in C, in denen ein paar Wochen Arbeit steckten, bildeten die Grundlage für die künftige Lösung.

Git indexiert Verzeichnisbäume über ihre Inhalte und speichert den sich im Lauf der Zeit verändernden Zustand des Entwicklungsbaums in speziellen Dateiobjekten. Tatsächlich funktioniert Git ähnlich wie ein Dateisystem, dessen Inhalte nicht veränderbar sind und das Dateien über die SHA-1-Summe des Inhalts statt durch ihre Namen identifiziert. Von hier ist es zwar noch ein weiter Weg zu einer vollständigen Versionsverwaltung, aber es bildet die Grundlage, um recht einfach eins zu entwickeln, das schnell, effizient und verteilt funktioniert.

Schnell ans Ziel

Die Entwickler auf der Liste [5] begannen also damit, an Git zu arbeiten, und kurze Zeit später hatte es bereits die Form eines echten Versionskontrollsystems angenommen. Als Linus sich versichert hatte, dass das Projekt nach seinen Vorstellungen lief, delegierte er dessen Betreuung an Junio Hamano, einen der aktivsten Git-Entwickler. Die erste Version des Linux-Kernels, die komplett von diesem neuen Programm verwaltet wurde, war 2.6.12.

Nach zwei Monaten Entwicklungszeit stand eine Git-Version zur Verfügung, die angemessen die hohen Ansprüche eines Projekts von der Größe des Linux-Kernels befriedigen konnte. Wer die Git-Entwicklung weiterhin verfolgen will, kann sich in die Mailingliste eintragen, indem er eine Mail mit dem Text »subscribe git« an die Adresse [majordomo@vger.kernel.org] schickt.

Zweischichtiger Aufbau

Wegen seiner Schlüsselrolle in der Kernelentwicklung ist das Programm trotz seines jungendlichen Alters bereits Teil der wichtigsten Distributionen. Zum Installieren unter Debian verwendet man »apt-get«, unter Fedora »yum install git-core«. Ubuntu-Benutzer sollten nicht das Paket »git« installieren, das sind nämlich die GNU Interactive Tools. Git selbst kompilieren wirft normalerweise auch keine Probleme auf.

Git kennt zwei Arten von Kommandos: Highlevel-Tools greifen auf einfacher gestrickte Lowlevel-Befehle zurück, die die interne Datenbank manipulieren. Im Git-Jargon heißt diese Gruppe von Programmen “Klempnerei” (Plumbing), die Highlevel-Programme dagegen “Porzellan” (Porcelain). Aus Gründen der Portabilität handelt es sich um Bash-Skripte.

Um die wesentlichen Eigenschaften von Git zu demonstrieren, soll ein eigenes Repository als Beispiel dienen. In Git ist jedes Verzeichnis ein komplettes Repository. Im Gegensatz zu anderen Versionskontrollsystemen ist keine zentralisierte Datenbank erforderlich, um die verschiedenen Repositories und Projekte zu speichern. Deshalb legt man als Erstes das Verzeichnis und Repository an:

mkdir repository
cd repository
git-init-db

Das Kommando »git-init-db« erzeugt in »repository« ein verstecktes Verzeichnis mit dem Namen ».git«. Hier finden sich Dateien und Verzeichnisse, die den Zustand des Repository speichern. Bei einer der Dateien handelt es sich um den symbolischen Link »HEAD«, der zu Beginn kaputt ist, weil die Datei, auf die er zeigt, nicht existiert: »refs/heads/master«. Im Verzeichnis »refs/heads« speichert Git nach und nach die verschiedenen Codezweige (Branches), die im eigenen Projekt entstehen. Der Link »HEAD« zeigt stets auf den Zweig, der als der aktuelle gilt.

Git speichert die Objekte des Repository in einem speziellen Format im Verzeichnis »objects«. Diese Objekte sind unveränderlich, ihr Name entspricht dem SHA-1-Hash des Inhalts. Wenn Git also ein Objekt speichern will, das eine Datei aus dem Projekt repräsentiert, wendet es zuerst den SHA-1-Algorithmus auf die Originaldatei an. Daraus entsteht eine 40 Bytes lange Sequenz, die eine Hexadezimalzahl mit 160 Bit repräsentiert.

Ordnung durch SHA-1-Summen

Da diese Objekte nicht modifizierbar sind, lassen sich mehrere Versionen des gleichen Dokuments abspeichern. Git fügt einfach für das modifizierte Dokument ein anderes Objekt hinzu (unter der Annahme, dass bei verändertem Inhalt auch der SHA-1-Wert ein anderer ist). Andererseits ermöglicht das Referenzieren einer Datei anhand des Inhalts und nicht des Namens, dass sich zwei Dateien mit unterschiedlichen Namen, aber der gleichen Information, ein mit SHA-1 errechnetes Objekt teilen – das ist eine sehr elegante Methode, um mit kopierten und umbenannten Dateien umzugehen.

Versionskontrolle für Apache

Nach dem Erzeugen des Git-Repository kann der Anwender Dateien zum Projekt hinzufügen. Das folgende Beispiel verwendet zum Testen eine Kopie der Apache-Konfigurationsdatei im Verzeichnis »repository«. Das Highlevel-Kommando »git add httpd.conf« indexiert sie vor dem Editieren. Das Skript »git« dient hier nur als Wrapper für andere Kommandos vom Typ »git-XXX« oder »git-XXX-script«. So ruft die obige Anweisung hinter den Kulissen »git-add-script« auf, das wiederum das Binärprogramm »git-update-cache« benutzt. Dieses aktualisiert dann den Index der Dateien im Repository und fügt mit dem Parameter »add« eine neue Datei hinzu.

An diesem Punkt muss man aufpassen: Den Datei-Cache aktualisieren ist nicht das Gleiche wie den Zustand des eigenen aktuellen Arbeitsbaums speichern, es ist der Schritt vorher. Git kann nur Zustände speichern, die es zuvor indexiert hat. Den ersten Zustand erzeugt folgendes Kommando:

git commit -m "Erster Commit"

Git liest damit alle bis zu diesem Moment indexierten Objekte und erzeugt ein neues internes Objekt, das den aktuellen Baum repräsentiert. Außerdem erzeugt es ein spezielles Commit-Objekt, das den Lebenslauf dieses Baums speichert. Alle erzeugten Objekte finden sich im Verzeichnis ».git/objects«:

$ ls .git/objects/*/*
.git/objects/48/1cc65cc01a4e0a28e00e819873dU 8154032ad56
.git/objects/db/5821359f207eeb04444262b9fafU aeb720c99d6
.git/objects/f1/82806a63bac810b7154644812b8U 1110b788b78

Es gibt also drei Objekte: eins gehört zur Datei des Beispielprojekts, ein anderes zum Baum, der anzeigt, welche Objekte in einem bestimmten Moment existieren, und schließlich ein drittes, das erklärt, wie man zu diesem Baum kommt – entweder von einem vorherigen Baum ausgehend oder, wie in diesem Fall, vom Anfangszustand.

Das Lowlevel-Kommando »git-cat-file« zeigt mit dem Parameter »-t« den jeweiligen Objekttyp an:

$ git-cat-file -t 481cc
blob
$ git-cat-file -t f1828
tree
$ git-cat-file -t db582
commit

Als Parameter akzeptiert »git-cat-file« die ersten Zeichen des SHA-1-Werts – Vorsicht: Der zwei Zeichen lange Verzeichnisname ist Teil dieses Werts. Das Objekt, dessen SHA-1 mit »481cc« beginnt, ist also vom Typ Blob, der gewöhnliche Dateien repräsentiert. Die Tree-Objekte gehören natürlich zu den Bäumen, die Commits zu den Objekten, die den Lebenslauf der Änderungen und der assoziierten Kommentare speichern.

Viele Bäume

Wenn ein Entwickler die Konfigurationsdatei »httpd.conf« so verändert, dass Apache auf Port 8080 statt auf 80 hört, ergibt sich eine Modifikation am Beispielprojekt. Den Unterschied zwischen dem Original und der neuen Datei gibt der Befehl »git diff« als so genanntes Diff aus, das ein Patch-Kommando unverändert verarbeiten kann (Listing 1). Es gibt weitere Git-Kommandos, die Unterschiede zwischen den indexierten Dateien und dem eigenen Arbeitsverzeichnis, zwischen zwei Bäumen oder sogar zwischen einem Baum und einer indexierten Datei sichtbar machen. Eine Übersicht gibt »git diff -h«.

Listing 1: »git diff«

01 $ git diff
02 diff --git a/httpd.conf b/httpd.conf
03 --- a/httpd.conf
04 +++ b/httpd.conf
05 @@ -130,7 +130,7 @@ MaxRequestsPerChild  0
06  # prevent Apache from glomming onto all bound IP addresses (0.0.0.0)
07  #
08  #Listen 12.34.56.78:80
09 -Listen 80
10 +Listen 8080
11 
12  #
13  # Dynamic Shared Object (DSO) Support

Um die Änderungen ins Repository zu übernehmen, ist es nötig, zuerst die Indexe zu aktualisieren und dann »commit« auszuführen:

git-update-cache httpd.conf
git commit -m "Hört auf Port 8080"

Der Befehl erzeugt zwei weitere Objekte im versteckten Verzeichnis ».git«. Eine Variante des Git-Diff-Befehls zeigt die Unterschiede zwischen dem gerade aktualisierten Baum und der vorherigen Version:

git diff -p HEAD^ HEAD

Wie bereits erwähnt zeigt »HEAD« auf den aktuellen Baum. Steht dahinter das Zeichen »^«, bezieht es sich auf den Elternbaum. Das obige Kommando bedeutet also: Den Unterschied zwischen dem Vater des aktuellen Baums und dem aktuellen Baum anzeigen. Wer zum vorherigen Baum zurückkehren und die Änderungen spurlos rückgängig machen will, verwendet folgende Kommandos:

git reset HEAD^
git checkout -f
git prune

Die erste Zeile gibt an, dass »HEAD« auf seinen Vater zeigen soll. Das nächste Kommando stellt den Inhalt des genannten Baums wieder her, indem es den aktuellen überschreibt. Die »httpd.conf« im Repository wird also durch die vorherige im aktuellen Arbeitsverzeichnis ersetzt. Um im System keine Spuren von den Tree- und Commit-Objekten aus den vorherigen Operationen zu hinterlassen, löscht der Befehl »git prune« alle unbenutzten Objekte.

Etwas abzweigen

Git kann Entwicklungslinien parallel zur Hauptlinie erzeugen. Entwickler verwenden sie normalerweise, um neue, tief greifende Änderungen auszuprobieren, die sie später – wenn sie erfolgreich waren – in den Hauptzweig integrieren. Das folgende Kommando erzeugt solche Branches:

git checkout -b Server1

Dieser Befehl benennt den Entwicklungszweig für die Apache-Konfiguration mit »Server1«. Veränderungen übernimmt das Repository, wenn man erneut indexiert und »commit« ausführt. Die Rückkehr in den Hauptentwicklungszweig ist jederzeit möglich:

git checkout master

Folgende Befehle führen die Veränderungen der unterschiedlichen Zweige zusammen:

git resolve HEAD Server1 -m "Die Veränderungen in den Master einschließen"

Mit »resolve« versucht Git zu erschließen, wie die Vereinigung der beiden angegebenen Zweige aussehen könnte. Wenn das nicht funktioniert, sind sie von Hand zu modifizieren und mit einem »commit« hinzuzufügen.

Git im Web

Git kann Repositories im Internet lokal klonen. Wie beschrieben ist jedes Verzeichnis ein komplettes und gültiges Repository, sodass beim Kopieren auf einen anderen Rechner sowohl der aktuelle Inhalt als auch der Änderungsverlauf dort ankommen. Git kann verschiedene Mechanismen wie HTTP, SSH und Rsync nutzen, um diese Kopie transparent zu erstellen. Damit ist es möglich, das Git-Projekt direkt vom Maintainer herunterzuladen und im lokalen Verzeichnis »git« zu speichern:

git clone rsync://rsync.kernel.org/pub/scm/git/git.git/ git

Um danach an die Quelldateien zu kommen, ist noch ein »git checkout« nötig. Den regelmäßigen Abgleich mit dem Repository führt »git pull« durch.

Unter [7] findet sich eine mit dem Perl-Skript Gitweb [8] angefertigte Liste der Kernelprojekte unter Git-Obhut. Mit Gitweb kann jeder ohne großen Aufwand Repositories ins Web stellen. Darüber lassen sich verschiedene Daten abfragen, zum Beispiel, wer zuletzt ein Commit durchgeführt hat. Genauso zeigt Gitweb die letzte Veränderung oder die Unterschiede zwischen mehreren Versionen einer Datei an (Abbildung 1).

Abbildung 1: Beispiel für die Verwendung von Gitweb: Die Webseite zeigt eins der vielen Kernelpatches.

Abbildung 1: Beispiel für die Verwendung von Gitweb: Die Webseite zeigt eins der vielen Kernelpatches.

Außerdem besteht die Möglichkeit, diese Zusammenfassungen im RSS-Format zu exportieren, die sich mit Nachrichtenaggregatoren lesen lassen. Ein Protokoll der Änderungen zeigt »git log«. Es listet die Nachrichten auf, die zu jedem Commit gehören.

Mit Gitk steht ein Werkzeug bereit, das Änderungen, Kommentare und die Merges-Zeilen grafisch darstellt:

gitk --all

Wer das vorherige Programm auf das lokale Git-Projektverzeichnis anwendet, sieht etwas Ähnliches wie in Abbildung 2. Jeder Kreis auf den Linien auf der linken Seite entspricht einem am Projekt durchgeführten Commit. Man kann sich auf jeden dieser Punkte begeben, um sowohl das eingefügte Patch als auch den zugehörigen Kommentar zu sehen. Die gleiche Information ist auch über die Kommandozeile zu erhalten:

git-whatchanged -p -root

Git stellt in seinem verteilten Aufbau eine weitere Kommandofamilie zur Verfügung, die Änderungen am eigenen Projekt in ein öffentliches Repository befördert (Push). Wer keine Lust mehr auf das Projekt hat, kann mit der Anweisung »git repack« viele der Repository-Objekte einpacken, damit sie weniger Platz auf der Festplatte verbrauchen.

Abbildung 2: Die letzten Änderungen am Git-Code, dargestellt durch das grafische Frontend Gitk.

Abbildung 2: Die letzten Änderungen am Git-Code, dargestellt durch das grafische Frontend Gitk.

Schöneres Porzellan

Die besprochenen Git-Kommandos sind für den Gelegenheitsnutzer etwas umständlich anzuwenden. Einfacher geht es mit Cogito [9], einer Sammlung von Perl-Skripten, die auf beide Schichten von Git zurückgreifen, um die Bedienung noch mehr zu vereinfachen. Mit diesen neuen Kommandos muss niemand vor dem Commit alle modifizierten Dateien wieder indizieren, zudem vereinfachen sie den Zugriff auf Internet-Repositories. Eine ausführliche Anleitung zur Bedienung von Git speziell für Kernelprogrammierer führt [10]. (ofr)

Infos

[1] Concurrent Version System: [http://www.nongnu.org/cvs]

[2] Subversion: [http://subversion.tigris.org]

[3] Bitkeeper: [http://www.bitkeeper.com]

[4] Nico Schottelius, “Arch und Monotone”: Linux-Magazin 02/05, S. 104

[5] Git-Mailingliste: [git@vger.kernel.org]

[6] Git: [http://www.kernel.org/pub/software/scm/git/]

[7] Liste der Repositories: [http://www.kernel.org/git]

[8] Gitweb: [http://www.kernel.org/pub/software/scm/gitweb/]

[9] Cogito: [http://www.kernel.org/pub/software/scm/cogito/]

[10] Kernel Hackers’ Guide to Git: [http://linux.yyz.us/git-howto.html]

LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Nach oben