Repo und der Umgang mit mehreren Git-Repositories

Im Rahmen meiner aktuellen Arbeit beschäftige ich mich mit Chromium OS, das von Google als Betriebssystem (unter der Marke Chrome OS) für die leichtgewichtigen Chromebooks verwendet wird. Chromium OS ist quelloffen, man kann das System also den eigenen Bedürfnissen entsprechend anpassen. Die von Google definierte Architektur erlaubt natürlich nicht jede beliebige Änderung, aber man hat auf jeden Fall Raum für eine Individualisierung des Systems.

In diesem Tutorial zeige ich, was Repo ist, wie man Repo verwendet, wo die Unterschiede zu Git sind und welche Vorteile Repo bei der Entwicklung bietet.

Für die Chromium-OS-Entwicklung wird intern das Tool repo verwendet. Repo ist ein Code-Management-Tool, das von Google in Kombination mit Gerrit für größere Projekte (u.a. auch Android) verwendet wird. Repo ist eine Ansammlung von Python-Skripten, die den Umgang mit mehreren Git-Repositories vereinfachen soll. Es ist somit ein Wrapper für Git.

Gerrit ist ein Tool für Code-Reviews, man hat damit eine zentrale Anlaufstelle zur Überprüfung vom Commits bevor sie in ein offizielles Repository einfließen. Repo ist von Gerrit abhängig, da in letzter Anweisung die Codeänderungen an einen Gerrit-Server geschickt werden müssen.

Auf Gerrit soll in diesem Artikel nicht eingegangen werden, das ist eine Sache für einen anderen Tag. Der Workflow mit repo soll hier aber kurz demonstriert werden.

Für die Demonstration baue ich mir zuerst eine kleine Beispiel-Infrastruktur in meinem Arbeitsverzeichnis auf:

Das Verzeichnis bin soll einfach nur das Programm repo aufnehmen. Weil ich mit mehreren Git-Repositories arbeiten möchte, liegen zwei Beispiel-Repositories vor (remote-1.git, remote-2.git). Das Verzeichnis manifest soll später die Konfigurationsdatei aufnehmen, die die zwei Remote-Repositories für repo zusammenführt. Ohne diese Konfigurationsdatei funktioniert repo nicht. Und im Verzeichnis both wird später gearbeitet. Dort sollen die zwei Repositories geladen werden und dort werden weitere repo-Befehle demonstriert.

Vorab wird das Haupt-Skript besorgt, das alle anderen referenzierten Python-Skripte herunterlädt und einen Arbeitsbereich mit repo initialisieren kann. Eine URL für das Skript gibt es u.a. hier. Herunterladen kann man das Skript z.B. mit cURL, um es danach mit chmod ausführbar zu machen:

Konfiguration

Notiert werden gemeinsame Repositories bzw. Projekte in einer Konfigurationsdatei, die den Namen default.xml trägt. In dem XML-Dokument wird u.a. festgehalten, wo sich die Projekte befinden (kann mehr als ein Ort sein) und welche verwendet werden sollen. Diese Konfigurationsdatei muss selbst in einem eigenen Git-Repository liegen. In der Beispiel-Infrastruktur wird also im manifest-Verzeichnis die Datei angelegt und dem eigenen Repository hinzugefügt:

Für die Beispiel-Infrastruktur reicht der folgende Inhalt in der XML-Datei:

Das Wurzel-Element ist <manifest></manifest>. Innerhalb des Wurzel-Elements gibt es mindestens ein <remote>-Element, ein optionales <default>-Element und mindestens ein <project>-Element. Der Aufbau kann auch komplexer sein (Beispiele: 1, 2), aber für eine minimale Variante reichen diese Elemente aus.

Das <remote>-Element gibt an, wo die Projekte sind. Projekte können sich an unterschiedlichen Orten befinden, es sind also mehrere <remote>-Elemente erlaubt. Intern werden sie über das Attribut name unterschieden, jedes <remote>-Element hat einen eindeutigen Namen in der Datei. Das review-Attribut gibt die Adresse des Gerrit-Servers an, an den die Code-Änderungen am Ende geschickt werden. Ohne dieses Attribut kann repo nicht vollständig verwendet werden. Und das Attribut fetch gibt die Adresse an. Traditionell findet sich hier eine HTTP-Adresse, für das Beispiel hier wird ein lokaler Pfad über das URI-Schema file:// angegeben.

Mit dem <default>-Element werden Standardwerte angegeben. Der Wert des Attributs remote referenziert (über den Namen) das <remote>-Element, dessen Adresse standardmäßig für alle Projekte verwendet werden soll. Das revision-Attribut gibt an, welche Revision (Branch, Tag, Commit-ID) in einem Repository geladen werden soll. In diesem Beispiel gibt es nur den Branch master.

Über das <project>-Element werden die verschiedenen Projekte identifiziert. Der Name des Projekts wird in das Attribut name geschrieben und das path-Attribut gibt an, wie das von repo heruntergeladene Projekt lokal heißen soll. Zur Orientierung: das Git-Clone-Äquivalent zu einem Projekt in der Konfigurationsdatei wäre:

Falls mit mehreren <remote>-Elementen gearbeitet wird und ein Projekt sich nicht im Standard-Pfad befindet, fügt man dem betroffenen <project->Element das zusätzliche Attribut remote hinzu. Der Wert des Attributs ist dann der Name des passenden <remote>-Elements.

Repo verwenden

Mit der vorliegenden Konfigurationsdatei kann nun das Arbeitsverzeichnis initialisiert werden:

Für die Initialisierung muss der Pfad zum Git-Repository angegeben werden, das die Konfigurationsdatei hält. In diesem Fall wird einfach das lokale Repository verwendet, weil Git keinen Unterschied zwischen lokalen und abgelegenen Repositories macht. Während der Initialisierung werden u.a. die Konfigurationsdatei und die noch benötigten Python-Skripte heruntergeladen und in dem Verzeichnis .repo lokal abgelegt. Heruntergeladen werden alle Repositories mit dem Befehl:

Das synchronisiert das eigene, lokale Arbeitsverzeichnis mit dem Stand aus den konfigurierten Repositories. Falls noch keine Daten vorliegen, werden die Projekte heruntergeladen. Ab diesem Zeitpunkt liegen alle definierten Projekte lokal vor und man kann mit der Entwicklung beginnen. Für die Demonstration soll beiden Projekten eine zusätzliche Text-Datei hinzugefügt werden.

Alle Entwicklungsarbeiten finden in einem eigenen Branch statt, also wird über den Befehl:

für beide Projekte jeweils ein neuer lokaler Git-Branch mit dem Namen developer angelegt. Der Befehl ist so aufgebaut, dass nach dem Branch-Namen jeweils eine beliebige Menge lokaler Projekte angegeben werden kann. So können über den Branch-Namen auch Änderungen über mehrere Projekte / Repositories hinweg einer bestimmten (durch den Branch definierten) Arbeitsphase zugeordnet werden. Die projektspezifischen, lokalen Änderungen werden weiterhin über die gängigen Git-Befehle (add, commit, ...) abgewickelt. Repo kann in dieser Phase genutzt werden, um projektübergreifend Dateien zu verfolgen, die noch nicht von einem Commit erfasst worden sind. Dafür gibt es den Befehl repo status.

Wenn z.B. in Projekt 1 eine Datei 1.txt hinzugefügt, aber noch nicht im Staging-Bereich erfasst worden ist und in Projekt 2 eine Datei 2.txt schon im Staging-Bereich liegt, dann liefert die Statusabfrage das folgende Bild:

Mit -- wird eine von Git noch nicht beobachtete Datei gekennzeichnet. Mit A- wird eine von der Versionsverwaltung noch nicht erfasste Datei gekennzeichnet. Ein Commit in beiden Projekten würde dann eine leere Statusabfrage liefern. Repo liefert also einen Überblick zu allen Dateien, die noch nicht in die Versionsverwaltung eingeflossen sind. Wenn die Entwicklungsarbeiten abgeschlossen sind, können die Änderungen nun an den Gerrit-Server geschickt werden. Dafür gibt es den Befehl:

Auf dem Gerrit-Server können die Änderungen nun überprüft und ggf. abgelehnt oder akzeptiert werden.

Weiterführende Links

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *