Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – III

Wir machen weiter mit unserer Einführung von Git – genauer EGit – unter Eclipse. Im ersten Post dieser Serie
Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – I
hatten ich ein einfaches Szenario vorgegeben und zugehörige Aufgaben für die Arbeit mit Git unter Eclipse definiert. Im zweiten Post
Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – II
hatte ich gezeigt, wie man unter Eclipse ein vorhandenes Projekt mit einem Git-Repository versieht.

Ich fasse nochmal die dort geleisteten Schritte unter EGit für Eclipse zusammen:

  • Vorhandenes Projekt unter Eclipse vorbereiten und von SVN-Resten befreien.
  • Target-Verzeichnis für das Git-Repository anlegen.
  • Git-Repository im Target-Verzeichnis initialisieren und EGit den Transfer aller Projekt-Dateien in den “Working Tree” im Target-Verzeichnis vornehmen lassen. Dazu ein dortiges Verzeichnis angeben.
  • Alle Projekt-Verzeichnisse/-Dateien für eine Indizierung/Behandlung in der Git-Versionsverwaltung vormerken. Dabei eclipse- und projektspezifische Dateien/Verzeichnisse gezielt außen vor lassen.
  • Alle Projekt-Verzeichnisse/-Dateien erstmalig “committen”. Im Staging View den Commit mit Informationen zum Autor und Committer sowie mit hinreichenden Kommentaren versehen.

Das führte zu folgendem Bild im Eclipse-View “Git-Repositories”:

Die Anzeige dieses Views erhält man übrigens über den Menüpunkt “Window=> Show View => Other => GIT => Git Repositories”. Es lohnt sich übrigens gleich auch noch prophylaktisch den View “Git Staging” zu laden.

Wir wenden uns nun der Aufgabe 4 unseres Aufgabenkataloges zu:

  • Aufgabe 4: Durchführung von Änderungen im Verzeichnisbaum des Projekts und Testen nachfolgender “Commits”. Blick auf die Git-Versionshistorie auf dem PC. Identifizierung des HEAD-Commmits bzw. -Knotens.

Wer das EGit-Interface nach diesem Artikel genauer studieren will, nutze bitte das Handbuch unter: http://wiki.eclipse.org/EGit/User_Guide

Testverzeichnis und Testdatei

Um die Inhalte des Working Tree nicht zu zerstören, lege ich ein Testverzeichnis “gittest” mit zwei Dateien an : “test.php5” und “basic.css”. Deren Inhalt ist sehr simpel; s.u. Nach der Anlage sieht das im PHP-Explorer so aus:

Man beachte die kleinen symbolischen Fragezeichen; sie deuten an, dass der Git-Status noch undefiniert ist.

Zu diesem Zeitpunkt sind die neuen Dateien noch nicht ohne ein “Refresh” (F5) im Working Tree des Views “Git-Repositories” zu sehen:

obwohl sie dort natürlich vorhanden
sind. Also F5 drücken.

Um das neue Verzeichnis samt Dateien der Versionsverwaltung zu unterwerfen, müssen wir das neue Verzeichnis in PHP-Explorer auswählen und zunächst in den Index des Repositories aufnehmen. Hierzu benutzen wir, wie im letzten Artikel beschrieben, den Button mit dem grünen “+”-Zeichen in der Git-Bedienleiste unter Eclipse:

Im “Git Staging View” erhalten wir dann automatisch die Möglichkeit, einen Kommentar einzugeben:

Danach drücken wir rechts unten auf den “Commit”-Button. Anschließend sind die Fragezeichen-Symbole verschwunden:

Der Master-Branch hat sich entsprechend verändert:

Man beachte die Anzeige des verkürzten Hashes für den Commit, der zur HEAD-Version des aktuellen Branches (im Moment der Master-Branch) führte!

Nun können wir uns mal die sog. “Historie” für “test.php5” ansehen. Dazu rufen wir im Kontext-Menü dieser Datei den Menüpunkt “Team=>Show in History” auf. Im sich öffnenden Historie-View erhalten wir dann:

Es wird der Originaltext der committeten Datei angezeigt. Im Zusammenhang mit dieser Datei gibt es bislang nur einen Knoten, der dem letzten Knoten des Masterbranches entspricht (Man vergleiche die Hashnummern!). Siehe auch, dass im unteren Bereich des Views der aktuelle Stand der Datei “test.php5” mit “/dev/null” verglichen wird!

Dateiänderungen, Commits und Anzeige der Dateiinhalte für verschiedene Knoten

Wir ändern nun den Inhalt der PHP-Datei; wir setzen einen Variablen-Wert um (aus var $a=100; wird var $a=200;) und speichern dann die Datei:

Eclipse zeigt uns (ähnlich wie bei SVN auch) an, dass die Datei zwar geändert wurde, aber die Änderung noch nicht “committed” worden ist. Man erkennt das z.B. am “>”-Symbol an der Datei im Datei- bzw. PHP-Explorer. Um die Änderung im Repository zu erfassen, müssen wir den “Commit”-Button in der Git-Bedienleiste (gelber Zylinder mit rotem Pfeil nach rechts) betätigen. Wie erwartet öffnet sich wieder der “Staging View”, in dem wir die Änderung kommentieren können. Danach drücken wir wieder den Button “Commit”. Ergebnis im Repository-View:

Interessanter ist nun ein Blick in den
View “History”; dort wird die Historie auch in Form eines Graphen angezeigt; die Knoten sind dabei streng gemäß der zeitlichen Historie angeordnet:

Ggf. nochmal F5 drücken, falls im unteren Bereich die Differenzen nicht angezeigt werden sollten. Wir erkennen nun zwei Knoten (mit unterschiedlichen Hashes), die die Historie dieser Datei betreffen. Netterweise wird uns auch die Änderung zur Vorgängerversion angezeigt. Das ist komfortabel!

Ein Doppelklick auf einen Knoten oder aber auf einen der mit einem Knoten verbundenen Dateinamen rechts unten im History-View öffnet übrigens die durch die Versionsänderung betroffene Datei im jeweiligen Zustand des Knotens im Editorbereich – dort wird hinter dem Namen dann auch der Hash des Commits angezeigt. Sind mehrere Dateien betroffen, wird die erste ausgewählte geöffnet.

Klickt man allerdings in der Icon-Leiste, die sich im History View rechts oben befindet, das Icon für den “Compare Mode” (links neben dem Branch-Icon), so öffnet sich die Datei im “Compare-Modus”; die Inhalte der Version des früheren Knotens werden mit der aktuell vorhandenen Version des momentan gewählten Branches verglichen.

Ein Markieren zweier Knoten des Graphen und ein “rechter Mausklick” öffnet ein Kontextmenü; auch hierüber erhalten wir die Option (Menüpunkt “Compare with each other”), ein Vergleichsfenster für diese Versionen im Editorbereich von Eclipse zu öffnen:

Das ist in unserem Fall trivial, da bislang ja nur zwei Knoten existieren. Die genannten Möglichkeiten des History-Views entfalten ihren vollen Nutzen natürlich aber vor allem bei längeren und komplexeren Historien.

Eine längere Änderungshistorie

EGit liefert noch sehr viel Möglichkeiten und Views mehr als oben beschrieben. Der Leser kann ja nun selbst mal eine Kette von Änderungen an verschiedenen Testdateien vornehmen und jede dieser Änderung committen. Natürlich ist das nicht die normale Situation in einem ernsthaften Entwicklungsprozess; wir spielen im Moment ja aber nur, um uns an EGit zu gewöhnen.

Die nachfolgende Darstellung zeigt dann das Ergebnis nach 5 bzw. 4 weiteren Änderungen an unseren beiden Testdateien “test.php” und “basic.css” im View “History“. Den History View erreicht man übrigens auch durch einen “rechte-Maus”-Klick auf den Branch im View “Git Repositories”; dort ist “Show In => History” zu wählen:

Wir bekommen nun eine etwas umfangreichere Darstellung der erstellten Knoten in unserem Master-Branch. Ein “Rechte-Maus-Klick” auf einen Knoten führt übrigens zu einem Kontextmenü, über dessen Punkt “Open in Commit Viewer” man auch eine Ansicht der zugehörigen Commit-Einträge (Kommentare etc.)
öffnen kann.

Unser lineare Graph ist im Moment allerdings immer noch etwas langweilig, da wir bislang kein Branching vorgenommen haben.

Testweises Branching

Branching gehört zwar nicht zum Umfang dieser Serie von Posts zu Git. Damit die Graphen jedoch nicht ganz so langweilig aussehen, müssen wir aber unseren Master-Branch durch weitere Entwicklungszweige ergänzen. Dort sind neue Knoten zu erzeugen und auch ein Zusammenführen von Entwicklungszweigen (Mergen) ist natürlich interessant. Ich gehe hier allerdings nur kurz auf diese Thematik ein. Das gewählte Beispiel ist entspricht dabei nur einer wenig sinnvollen, praxisfernen Spielerei; wer einen schnellen und realitätsnahen Überblick darüber gewinnen will, wie man Branching für verschiedene Situationen eines Entwicklungsprozesses effektiv nutzt, möge sich z.B. das Buch “Git” von Rene Preißel und Bjørn Stachmann (dpunkt.verlag) zu Gemüte führen.

Einen neuen Branch legt man im View “Git Repositories” z.B. über das Kontextmenü des Zweiges “Branches => Local” an.

Danach gibt man in einem Popup-Formular den Namen des neuen Branches an; die Option “Checkout” zum Wechseln in den Branch ist bereits vorbelegt:

Man erkennt danach im View “Git Repositories”, dass der neue erzeugte Branch “Consolidation” auch der aktuelle (Arbeits-) Branch geworden ist (kleiner schwarzer Haken). Alle kommenden Änderungen finden auf den vorhandenen Dateiständen dieses Branches statt, bis man ggf. den Branch des Repositorys wieder gezielt wechselt.

Man erkennt an der Abbildung, dass ich auf diese Weise bereits insgesamt 4 Branches zu meinem Repository angelegt habe – neben dem “master” die Brances “bugfix”, “consolidation” und “php-extensions”. Nun kann man in diesen Branches testweise die gleiche Datei (test.php5) in unterschiedlicher, aber konflikterzeugender Weise editieren und dann im jeweiligen Branch durch Commit einen neuen Knoten erzeugen. Die jeweiligen Dateiversionen in den jeweiligen Branches sollen danach einander widersprechende Inhalte besitzen.

Ich ändere beispielsweise im Branch “bugfix” den Wert von $a auf “$a=10” ab. Dazu muss ich zuerst in den Branch “bugfix” wechseln. Dies geht wieder im Kontextmenü von “Local”:

Ist die Datei “test.php5” schon im Editor-Bereich geöffnet, ändert sich der Editor-Inhalt automatisch auf den des aktuellen Branches. Nach dem Branch-Wechsel führen wir die Änderung “$a=10;” durch und committen die neue Version. Dann machen wir eine entsprechende unabhängige Änderung “$a=20” in der gleichen Datei des Branches “php-extensions” und committen auch dort. Abschließend nehmen wir eine Änderung “$a=30” im Branch “master” vor. Schließlich wechseln wir in den Branch “consolidation”. Dort setzen wir “$a=40” und committen.

Wir “mergen” nun in einem weiteren Schritt den Branch “bugfix” in den Branch “consolidation”. Wir lösen den Merge-
Vorgang über das Kontextmenü des Branches “consolidation” aus:

In unserem speziellen Fall muss das zu einem Konflikt führen. Grund ist, dass in den zu mergenden Dateien unterschiedliche Vorgaben in demselben betroffenen Zeilenbereich stehen und die logische Historie nicht eindeutig ist. Git kann nicht wissen oder erschließen, welche Änderung in welchem Branch die gewünschte sein soll. Beide Änderungen sind ja nach dem Zeitpunkt der Verzweigung in gleichen Codebereichen erfolgt. (In manchen Git-Tools kann man übrigens einstellen, ob man rein der zeitlichen Abfolge vertrauen soll. Ich nutze ein solche Option aber aus guten Gründen nie).

Der Konflikt wird auch an anderer Stelle deutlich markiert; im geöffneten Editor einer betroffenen Datei u.a. durch gezielt vorgenommene Einschübe:

Man beachte die Markierung der verschiedenen Varianten im Editor durch “>>>” und “<<<” Zeilen. Im PHP Explorer View “Git Staging” ist der Konflikt dagegen durch eine deutliche rote Markierung am jeweiligen Dateieintrag hervorgehoben.

Rechts sieht man übrigens das sog. “merge tool”, dass man etwa über das Kontext Menü der Datei im unteren Bereich “Unstaged Changes” öffnen kann. Es geht aber immer auch über Unterfunktionen des Punktes “Team” im Kontext-Menü der Datei im PHP-Explorer.

Wir lösen den Konflikt im Editor auf; d.h. wir entscheiden uns dabei für eine der angedeuteten Varianten, z.B. die Variante vom Branch “bugfix” (a=10). Die nicht gewünschten Zeilen und die Markierungszeilen aus dem Merge-Prozess löschen wir.

Wir speichern die geänderte Datei dann ab; diese ist dadurch aber noch nicht in den Index des Branches aufgenommen. Aus Sicht des Views “Git Staging” ist der Konflikt somit noch nicht behoben; eine Aktualisierung F5 hilft nichts und auch ein “Commit” ist nicht möglich. Hier muss man vielmehr eine explizite Entscheidung zur Aufnahme der eben als künftiges Soll gespeicherten Version in den Index wählen; dies geschieht im Kontextmenü des Git-Staging-Views durch Auswahl des Punktes “Add to index“:

Danach ist ein Commit unserer neuen Dateiversion möglich:

Hinweis:
Alternativ zu einer direkten Arbeit im Editor ist auch das Arbeiten über das “Merge Tool”, das im wesentlichen einer Diff-Azeige mit Zusatzfunktionen entspricht, möglich. Das mag jeder mal selber ausprobieren.

Wir committen und mergen danach in ähnlicher Weise den Branch “php-extension” in den Branch “consolidation”. Dabei entscheiden wir uns für den Wert von “$a=20” aus “php-extensions”.

Zwischenzeitlich können wir immer mal den Zustand des jeweils aktiven Branches im
View “History” begutachten (zur Sicherheit dort F5 drücken):

Bei mir sieht das so kompliziert aus, weil ich in den anderen Branches bereits früher Änderungen durchgeführt hatte. Der “consolidation”-Branch wurde ja aus dem Master-Branch im Zustand “a54a2b7” erzeugt; seine “Vergangenheit” davor ist mit der des Master-Branches identisch.

Durch Klicken des rechten Branch-Symbols oben rechts in der Icon-Leiste dieses Views erhält man übrigens eine graphische Ansicht aller vorhandenen Branches. Es gibt in dieser Icon-Leiste noch weitere interessante Möglichkeiten, deren Studieren sich lohnt. Interessant ist ferner das Markieren zweier Versionen aus dieser Ansicht, die man dann per Kontext-Menü direkt in einer “Compare”-Ansicht öffnen und vergleichen kann.

Schließlich mergen wir noch den “consolidation”-Branch in den Master-Branch und übernehmen dabei den Wert des “consolidation”-Branches. Wir erhalten am Ende eine recht komplex wirkende Branch-Historie; im oberen Bereich sind die hier angesprochenen Änderungen mit den Hash-Nummern “fc53e0e, d3995ff, a262600, b999a78, df23e87, fb258ff, f48c829” zu sehen:

Hinweis zu sog. “Fast Forward Branches”:

In unserem Beispiel zu Branching haben wir gezielt Konflikte provoziert, die bei den verschiedenen Merges manuell beseitigt werden mussten. In vielen Fällen erfolgen aber Code-Änderungen z.B. durch Hinzufügen von Code-Fragmenten oder durch Code-Änderungen, die nicht in Konflikt mit der letzten gemeinsamen Version von zwei Branches stehen, wenn im Zielbranch an dem betroffenen Codesegment seit der Verzweigung nichts geändert wurde. Entsprechende Änderungen haben eine eindeutige logische Historie und können in Form sog. einfacher “Fast Forward Merges” [FFM] abgewickelt werden: EGit übernimmt die Änderungen dann einfach automatisch in die Target-Datei; ein manuelles Eingreifen ist nicht nötig. Ein “FFM” wird in einem Zwischendialog nach dem Mergeversuch angekündigt; das Merge-Popup (s.o.) erlaubt spezielle Einstellungen für FFMs.
Da Git aber nicht die Semantik der Änderungen überprüfen kann, muss man auf der Basis vorhergehender Codevergleiche natürlich auch im Fall von FFMs sicher sein, dass man mit der direkten Übernahme der Änderungen keinen Unsinn anrichtet. Das Denken nimmt einem Git nicht ab.

Andere Tools

Später werden wir zur Überwachung und ggf. Manipulation zentraler (!) Repositories auf den jeweiligen Servern (grafische) Tools installieren müssen, die unabhängig von Eclipse und EGit funktionieren. In Frage kommen aus dem Opensource Umfeld etwa:

GitGui (wird mit dem Git-Paket ausgeliefert; wirkt wegen Tcl/Tk etwas hausbacken, Graphen; umfangreicher Satz von Werkzeugen zur Repository-Pflege), Git Cola (Graphen; nützliche Funktionen zur Manipulation des Repositories), QGit (Graphen und einige einfache Manipulationsbefehle), GitG (web-ähnliche Oberfläche) sowie Giggle (Graph und Klon-Funktion). Die zugehörigen Pakete lassen sich für die jeweilige Linux-Distribution relativ einfach finden und installieren; ich gehe hier nicht darauf ein.

Alle genannten Tools erlauben eine Anzeige der vorhandenen Branches, ihrer Knoten und zumindest von Versions-Differenzen eines ausgewählten Knotens zur Vorgängerversion. Der Umfang an Funktionalitäten zur Pflege/Manipulation des Repositories ist unterschiedlich; hervorzuheben sind hier “Git
GUI” und “Cola Git”. Für eine einfache Graphenanzeige genügen GitG, QGit und Giggle.
Ich zeige abschließend den oberen Teil der Graphen in GitGui (gitk-Ansicht), QGit, Giggle, Cola-Git (DAG). Das ist alles recht ähnlich; die DAG-Ansicht finde ich eher verwirrend – hier wird die Logik zu stark komprimiert. Cola-Git bietet alternativ aber auch die gitk-Ansichten von GitGui.

GitGUI

QGit

Giggle

Cola-Git (DAG-Ansicht)

In GitG, GitGui, Cola Git (gitk) kann man zwischen einer rein chronologischen und einer topologisch-logischen Graphendarstellung wählen. Eine rein chronologische Darstellung hilft in einen komplizierten Situationen die Historie zu rekonstruieren.

Fazit

Das EGit-Plugin für Eclipse bietet einige hübsche Features an, um die Historie von Änderungen zu verfolgen. Wir haben in diesem Artikel allerdings nur an der Oberfläche gekratzt. Der Leser ist aufgerufen, weitere Möglichkeiten zu erkunden. Zumindest für ein lokales Repository werden dabei kaum Wünsche offen gelassen. Auch die Graphendarstellung für einzelne Branches im History-View braucht sich nicht zu verstecken. Die Knoten sind chronologisch geordnet – und neben jedem Knoten wird in einer separaten Spalte der zugehörige verkürzte Hash angezeigt. Das bietet komischerweise keines der anderen Tools an.

Die Funktionalitäten von EGit kann man übrigens auch in einer von Eclipse unabhängigen Form genießen. Hierzu lädt man sich die entsprechende Anwendung “GitEye” der Fa. CollabNet (s. https://www.collab.net/downloads/giteye) für Linux herunter. Das Archiv einfach expandieren und GitEye starten.

Im Vorgriff auf ein Monitoring und eine direkte Verfolgung von Änderungen eines zentralen Git-Repositories auf einem entfernten Host haben wir einen kurzen Blick auf andere Tools geworfen, die sich ebenfalls leicht installieren lassen und wie EGit eine grafische Aufbereitung bieten. Auch hier gilt es, sich mit den Möglichkeiten und Grenzen der Tools vertraut zu machen.

Im nächsten Artikel dieser Serie
Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – IV
verfolgen wir allerdings die im ersten Artikel gesetzten Ziele weiter. U.a. werden wir ein zentrales Repository auf einem Host im LAN anlegen und auch dieses Repository mit Push-Befehlen auf einen aktuellen Stand bringen. Parallel werden wir uns die durchgeführten Änderungen auf dem zentralen Tool über QGit ansehen. Das entspricht den Aufgaben 5 -7 des ersten Artikels.

Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – II

Im letzten Blog-Post

Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – I

hatte ich ein einfaches Szenario beschrieben, das regelmäßige Wechsel zwischen mobiler und ortsgebundener Entwicklungsarbeit beinhaltet und gerade im Leben von Freiberuflern immer wieder auftritt. Versionskontrolle erfordert im Zuge solcher Wechsel das Arbeiten mit lokalen und zentralen Repositories sowie systematische Abgleichoperationen zwischen solchen Repositories. Git ist für derartige Szenarien gut geeignet. Um entsprechende Experimente durchführen zu können, haben wir im letzten Post unser einfaches Wunschszenario in durchzuführende logische Schritten untergliedert.

Unser Ziel ist nun eine Abbildung der identifizierten Schritte in eine Git-Versionsverwaltung unter der IDE Eclipse. Daraus ergaben sich konkrete Aufgabenstellungen für Tests. In diesem Post befassen wir uns mit den ersten drei der identifizierten Aufgaben:

  • Aufgabe 1: Erstellen eines lokalen Git-Repositories auf dem PC.
  • Aufgabe 2: Klärung: Wo liegt der “Working Tree”? Welchen Verzeichnisbaum zeigt Eclipse?
  • Aufgabe 3: Einbringen des Inhalts des Projektverzeichnisbaums in den Master-Branch des lokalen Repositories. Initiales Commit.

Ich setze für das Verständnis der nachfolgenden Ausführungen einige Begrifflichkeiten voraus, die ich im letzten Blog-Post dieser Serie erläutert habe. Da es sich hier um einen Linux-Blog handelt beschreibe ich alle Schritte zur Lösung der Aufgaben für eine Linux/Eclipse-Umgebung, Linux-Verzeichnisbäume und, soweit erforderlich, Linux-Shell-Kommandos. Vermutlich lässt sich das Meiste aber auch direkt auf eine MS-Umgebung übertragen.

Abgrenzung “Eclipse Workspace” versus “Git Workspace”

In deutschsprachigen Büchern zu Git ist oft die Rede von einem (Git-) “Workspace”. Andererseits gibt es aber auch unter Eclipse “Workspaces”. Diese sollten wir im Folgenden begrifflich auseinanderhalten:

  • Eclipse Workspace: Ein Eclipse Workspace verwaltet eine Ansammlung von mehreren Projekten und zugehörige Dateien in projektspezifischen Verzeichnissen. Für den gesamten Eclipse Workspace können verschiedene grundlegende Eclipse-Eigenschaften (Settings) projektübergeordnet eingestellt werden. Ich kürze einen Eclipse-Workspace nachfolgend mit EWS ab. Ein EWS ist regelmäßig mit einem zugehörigen Verzeichnis verbunden. Das schließt aber definierte Links in weitere zugeordnete Verzeichnisse nicht aus. Im Besonderen müssen die Projektdateien nicht zwingend in einem Verzeichnis unterhalb des Workspace-Verzeichnisses beheimatet sein. Die EWS-spezifischen Eclipse-Einstellungen werden jedoch in versteckten Dateien/Unterverzeichnissen des EWS-Verzeichnisses hinterlegt. Solche speziellen Dateien/Verzeichnisse eines EWS wird man später nicht unbedingt in einem GIT-Repository erfassen wollen, da sie typischerweise Anweisungen beinhalten, die für eine Eclipse-Version spezifisch sind; ein abgleichender Transfer auf ein anderes System mit einer älteren Eclipse-Version könnte dann zu Problenmen auf der Eclipse-Ebene führen.
  • Git-Workspace: Ein Git-Workspace bezieht sich auf Dateien einer Verzeichnisstruktur, deren Änderungshistorie in einem Git-Repository im Sinne einer Versionierung aufgezeichnet und verwaltet wird. Ich kürze nachfolgend einen Git-Workspace mit GWS ab. Ein GWS besteht typischerweise aus einem reinen Repository-Bereich unterhalb eines speziellen Verzeichnisses “.gitund einem sog. “Working
    Tree
    “, dessen Dateien eine definierte Code-Version auf einem Entwicklungs-“Branch” (-Zweig) beinhalten. Auf der Eclipse-Ebene entspricht der Working-Tree typischerweise der Verzeichnis-Struktur eines Projektes mit den dazu gehörigen aktuellen Code-Dateien.

Vorbereitende Schritte

Bevor wir uns an die Lösung unserer Aufgaben machen, müssen wir je nach Ausgangssituation ein paar vorbereitende Schritte treffen. Für diese und auch für nachfolgende Aktionen gilt:

Wir legen uns vorab immer eine Sicherheitskopie des gesamten EWS und ggf. auf Datei- oder Ordnerebene verlinkter anderer Eclipse Workspaces an. Ferner sichern wir vor Experimenten mit Dateien eines realen Projekts evtl. existierende SVN-Repositories, die mit diesem Projekt verbunden sein mögen.

Vorbereitender Schritt 0 – Git und das Eclipse EGit-Plugin installieren

Auf unserem Linux-PC installieren wir zunächst das für die jeweilige Distribution verfügbare Git-Paket. Wir erhalten damit u.a. auch Zugriff auf CLI-Kommandos, die im Zuge der Repository-Verwaltung eingesetzt werden können. Die Kenntnis solcher Kommandos ist übrigens nützlich, auch wenn man – wie ich – grafisch Bedien-Elemente einer IDE verwenden will. (Man kann unter Linux eine Git-basierte Versionsverwaltung von Projekten auch ausschließlich auf der Kommandozeile betreiben.) Wer Lust hat, kann neben Git selbst gleich auch noch die Pakete “gitg”, “Qgit”, “Giggle” und ggf. auch “git-cola” oder “cola-git” installieren. Jedes dieser Pakete bietet eine eigene unabhängig von Eclipse nutzbare GUI für Git mit mehr oder weniger Komfort und Funktionalität an.

Für Eclipse selbst gibt es ein Git-Plugin namens EGit (welches wiederum JGit benötigt). EGit sollte im Haupt- oder Update-Repository der jeweiligen Eclipse-Installation (hier Neon 3) in einer passenden Version verfügbar sein. Für den aktuellsten Stand greift man auf folgendes Eclipse-Repository zu:

http://download.eclipse.org/egit/updates.

Von dort können wir EGit genauso wie andere Eclipse Plugins auch installieren. Je nachdem, ob man vor hat, später auch externe GitHub-Dienste zu nutzen und/oder Mylyn einzusetzen, kann die Installation weiterer Plugins von Interesse sein. Das folgende Bild zeigt, welche Git-bezogenen Pakete ich selbst unter einem aktuellen Eclipse Neon 3 installiert habe:

Ich gehe auf die relativ trivialen Installationsschritte unter Linux/Eclipse nicht näher ein. Siehe bei Bedarf aber:
http://www.vogella.com/ tutorials/ EclipseGit /article.html #installation-of-git-support-into-eclipse
https://www.eclipse.org/ forums/ index.php/ t/273443/

Vorbereitender Schritt 1 – Lösen eines PHP-Projektes von seiner SVN-Anbindung

Das nachfolgende Bild zeigt die relativ simple Verzeichnis und Projektstruktur eines EWS namens “ecl_alienx”; das zugehörige Verzeichnis auf dem Linux-PC ist “/projekte/ecl_alienx”.

Das dortige Test-Projekt “alien1” und seine Verzeichnisse/Dateien sind unterhalb des Ordners “/projekte/ecl_alienx/alien1” beheimatet. Eclipse-Puristen würden diese Ortswahl für die Programmdateien mit einiger Berechtigung kritisieren. Das ist uns hier aber egal; es wird sich sowieso gleich ändern.

Den Bildinformationen entnehmen wir: Das Projekt ist offenbar noch mit einem Subversion-SVN-Repository verbunden. Wir lösen diese SVN-Verbindung nun über den Disconnect-Befehl, den wir unter dem Kontexmenüs des Projektes finden: => “Team => Disconnect”.

Dabei löschen wir auch die SVN-Metainformation in den “.svn”-Dateien jedes Projekt-Verzeichnisses. Das ist zwar nicht zwingend erforderlich; ich möchte bei den nachfolgenden Git-Experimenten aber keinen SVN-Ballast mit mir rumschleppen. (Das SVN-Repository selbst wird bei dieser Aktion übrigens nicht gelöscht).

Unser Projekt ist jetzt unbelastet von jeglicher Versionsverwaltung.

Für die weiteren Schritte lohnt es sich, den ViewGit Repositories” (z.B. im unteren Bereich des Eclipse Desktops) permanent geöffnet zu halten. Dieser View ist anfänglich natürlich noch leer; bei späteren Git-Aktionen ist ein kontrollierender Blick in diesen View immer informativ und hilfreich. Öffnen kann man den View über den Eclipse Menüpunkt “Window => Show View => Others => Git => Git Repositories”.

Lösung der Aufgabe 1 – Erstellen eines lokalen Repositories

Als erstes erstellen wir mittels einer Shell oder einem Dateimanager außerhalb des EWS ein Verzeichnis für unseren künftigen GWS inkl. eines lokalen GIT-Repositories. In meinem Testfall etwa unter “/projekte/GIT/alien1“. Dann öffnen wir das Kontext-Menü unseres Projektes erneut und wählen den Punkt “Team => Share Project“:

Im nächsten Dialogfenster klicken wir auf den “Create“-Button und geben im nachfolgenden Subdialog den vorgesehenen Zielort des Repositories an:

Achtung:
Das Bild deutet bereits an, dass EGit deutlich mehr vor hat, als nur ein Git-Repository anzulegen. Vielmehr sollen alle Unterverzeichnisse unseres Projektes in eine Verzeichnisstruktur im Targetverzeichnis “/projekte/GIT/alien1” umgezogen werden. Wir werden gleich sehen, dass dadurch der sog. “Working Tree” unter dem Directory “/projekte/GIT/alien1/alien1/” im Workspace angelegt wird; der Working Tree besteht initial also aus den ursprünglichen Projekt-Verzeichnissen und -Dateien des EWS.

Im Targetverzeichnis “/projekte/GIT/alien1” wird parallel auch das eigentliche Git-Repository als Teil des GWS angelegt. Die erforderlichen Repository-Strukturen (Unter-Verzeichnisse für verschiedene Informationen, Object-Datenbanken mit Blobs, Indices, …) findet man anschließend in einem Verzeichnis “/projekte/GIT/alien1/.git“.

Hinweis: Ich würde an dieser Struktur des GWS nichts ändern!

  • Git erlaubt zwar grundsätzlich eine Trennung des Dachverzeichnisses
    für das “.git”-Repository-Verzeichnis vom Dachverzeichnisses für den “Working Tree”. Man kann dies etwa über CLI-Kommandos erzwingen. Nach meiner Erfahrung bringt eine solche separate Lagerung des Repositories und des Working-Trees an unterschiedlichen Orten EGit aber bei weiteren Aktionen außer Tritt. Also den Umzug der Verzeichnisstruktur an die angezeigten Position bitte zulassen!
  • Bitte achtet auch darauf, dass der Pfad zum künftigen “.git”-Verzeichnisses in eurem Dialog wirklich so ähnlich aussieht wie dargestellt. Man gerät durch Unachtsamkeit relativ schnell in eine Situation, in der man “.git” nachher unterhalb des Hauptverzeichnisses des Working Trees wiederfindet. Auch das ist technisch zwar zulässig, hätte später aber mehrere unangenehme Seiteneffekte, auf die ich an dieser Stelle nicht eingehen will. Also: Bitte darauf achten, dass das “.git”-Verzeichnis auf derselben Ebene der Verzeichnisstruktur platziert wird wie der zu erzeugende Working Tree. !

Jetzt klicken wir endlich auf den Button “Finish”; je nach Größe des bereits vorhandenen Projektverzeichnisses dauert das Verlagern der Dateien ggf. ein wenig. Schließlich sind aber sowohl das Repository unter dem Directory “.git” wie auch der Working Tree am vorgesehenen Bestimmungsort vorhanden.

>Lösung der Aufgabe 2 – Working Tree und Verzeichnisstruktur

Das Ergebnis sollte sich im PHP-Explorer bzw. im View “Git Reposiories” wie folgt darstellen:

Im unteren Teil sehen wir einen Ausschnitt des “Git Repository”-Views. Bereits hier erkennen wir deutlich den Aufbau unseres neuen GWS. Der Working Tree ist dort als solcher bezeichnet und sein Pfad ist angegeben. Ein Dateimanager bestätigt die gewünschte Verzeichnishierarchie:

Glückwunsch! Wir haben unser erstes Git-Repository samt Working Tree erstellt. Durch die Parametrierung bei der Erstellung des Repositorys und durch die beiden obigen Abbildungen haben wir neben Aufgabe 1 auch schon Aufgabe 2 gelöst.

Das Repository ist in diesem Zustand aber erst vorbereitet. Noch sind dort keine Inhalte oder Verionsobjekte angelegt ….

Git-Smart-Icon-Leiste aktivieren

Um nachfolgend etwas einfacher mit dem Repository und dem Commit neuer Dateien arbeiten zu können, beschaffen wir uns eine Smart-Icon-Leiste für Git-Operationen im Kopfbereich von Eclipse. Das erreichen wir in zwei Schritten:

  • Klick auf den Eclipse-Menüpunkt “Window => Perspective => Customize Perspective … “. Dort unter dem Reiter “Action Set Availability” die Punkte “Git” und “Git Navigation Actions” aktivieren.
     
  • Danach aktivieren wir unter dem Tab “Menu Visibility” den Punkt “Git”:
     

Als Ergebnis erhalten wir folgende Leiste:

>Lösung der Aufgabe 3 – Master-Branch und initiales Commit

Der weiter oben dargestellte View “Git-Repositories” weist uns neben dem Repository-Symbol im Moment explizit darauf hin, dass noch keine HEAD-Version existiert. Wie auch? Im “Branches”-Bereich ist unter dem Punkt “Local” ja noch nicht mal ein (Master-) Branch zu finden (s. den vorhergehenden Blog-Post). Es gibt im Moment noch überhaupt keinen Branch!

Unter Git muss man Verzeichnisse und Dateien explizit für die Berücksichtigung in der Versionsverwaltung markieren. Die kleinen Fragezeichen in den Verzeichnis-Icons im PHP-Explorer deuten an, dass dies bislang noch für keines der vier Hauptverzeichnisse unseres Test-Projektes geschehen ist. Ein “gt;”-Symbol nach den Verzeichnis- bzw. auch nach einem Datei-Namen deutet ferner an, dass es eine Änderung gibt, die noch nicht per Commit im Repository erfasst wurde: Der aktuelle Inhalt jeder Datei stellt aus Sicht von Git offenbar eine Art erste (initiale) Änderung dar. Wir würden das Symbol bei jeder Datei unterhalb der Verzeichnisse sehen.

Später werden wir das “>”-Symbol natürlich genau an denjenigen Dateien/Verzeichnissen entdecken, die jemand seit dem letzten Commit modifiziert hat. Das ist beim Einsatz von Subversion ganz genauso. Einen kleinen Unterschied zu SVN gibt es allerdings doch, und der macht sich dadurch bemerkbar, dass das Verzeichnis “uploads” kein “>”-Symbol aufweist: Der Grund dafür ist, dass das Verzeichnis leer ist. Das ist ein Hinweis darauf, dass Git keine separate Versionsverwaltung für Verzeichnisse als Repository-Objekte vornimmt. Verzeichnisse sind lediglich ein Art Attribut der nach Versionen verwalteten Datei-Objekte!

Den aktuellen Repository-Zustand könnte man im Git-Sprachgebrauch also etwa so zusammenfassen: Es ist bislang weder ein “Commit” zu den vorhandenen Dateien erfolgt, noch wurden die in Verzeichnissen organisierten Dateien überhaupt für eine Indizierung und Verwaltung in einem Branch und damit auch der Git-Objektdatenbank vorgesehen. Wir ändern das nun in zwei separaten Schritten:

Schritt 1 – Einbeziehen der Verzeichnisse und ihrer Dateien in die Versionsverwaltung: Wir markieren unsere 4 Projektverzeichnisse des Beispiels “alien”, “includes”, “interpreters”, “uploads” im PHP- oder Projekt-Explorer. Dann klicken wir in der Git-Icon-Leiste auf das grüne Kreuz. Danach müssen warten, bis sich das Dialogfenster zu “Operation in Progress …” wieder schließt.

Die nächste Abbildung zeigt, dass sich die Mikro-Symbole an den Verzeichnissen nun geändert haben:

Der weiße Stern auf schwarzem Grund weist allerdings darauf hin, dass die Dateien in den Verzeichnissen immer noch keinen “Commit” erfahren haben.

Schritt 2 – Initialer Commit für alle erfassten Dateien:
Um für alle Dateien einen “Commit” einzuleiten, markieren wir unsere Verzeichnisse erneut und klicken dann auf das orange Repository-Symbol mit dem Pfeil von links nach rechts in der Git-Icon-Leiste; dieses Symbol befindet sich neben dem grünen Kreuz und symbolisiert einen Commit-Vorgang – unseren ersten im erstellten Repository.

Nach wenigen Augenblicken bietet sich uns dann folgendes Bild im sogenannten “Git Staging View“. Dieser View öffnet sich automatisch und listet Dateien/Verzeichnisse auf, die modifiziert wurden und für die endgültige Ausführung des Commits “vorgemerkt” sind. Man nennt das auch “Staging“.

Im linken Bereich sehen wir im Bereich “Unstaged Changes” Dateien (nur 2 von 5 sind sichtbar), die bislang nicht für Commits vorgemerkt wurden. Es handelt sich in unserem Fall um Eclipse-Konfigurationsdateien für das spezielle Projekt. Darunter sehen wir im Bereich “Staged Changes” allerdings die für das Commit vorgemerkten Dateien. In unserem Fall alle Projektdateien. (Es handelt sich offenbar um eine größeres Projekt, zu dem über 3000 einzelne Dateien beitragen.)

Im rechten Bereich des “Staging View” können (besser müssen) wir unseren Commit noch mit einem Kommentar verzieren. Unterhalb sollten auch Author und Committer angegeben werden; dabei sind bestimmte Formatanforderungen zu erfüllen, die wir nach einem Klick in das jeweilige Feld gefolgt von einem “Ctrl-Space” angezeigt bekommen. Die entsprechenden Werte lassen sich auch in den Eclipse-Preferences für Git hinterlegen. Man findet Git-Preferenzen wie üblich unter dem Menüpunkt “Window => Preferences => Team => Git”.

Wir klicken nun auf den Button “Commit“. Es wird jetzt eine erste Komplettversion im sog. “master”-Branch unseres Repositories erzeugt. Genauer: Zuerst wird der Master-Branch generiert; dann wird n einer ersten Projektversion auf diesem Branch der aktuelle Inhalt aller ausgewählten Projektdateien erfasst. Das Erzeugen der zugehörigen initialen Objekte des Repositories und deren Komprimierung dauert in meinem Fall wegen der großen Menge der Dateien ein paar Augenblicke.

Jeder Commit führt zu einem neuen Versionsstatus des gesamten Branches (sozusagen über alle modifizierten Dateien hinweg). Auf der Branch-Ebene entspricht ein Commit in seiner Gesamtheit somit einem eindeutigen Knoten (s. hierzu den letzten Post). Die Identität des Knotens wird durch einen eindeutigen Hash gekennzeichnet, dessen erste Buchstaben wir im Git-Repository-View auch angezeigt bekommen. Diesem Hash sind wiederum Hashes für die einzelnen erfassten Objekte (Dateien bzw. komprimierte Blobs zu deren Änderungen) zugeordnet. Auch die Änderungen selbst (bzw. zugehörige Objekte in einem Binärformat) werden also über Hashes identifiziert.

Im Git-Repository-View ergibt sich nach der Durchführung des Commits folgendes Bild :

Nun existiert offenbar der ersehnte lokale Master-Branch. Daneben erkennen wir die ersten alphanumerischen Zeichen seines Hashes (hier: 3659950) und die Anfänge unseres eben erstellten Kommentars.

Unsere Verzeichnis-Symbole im PHP-Explorer weisen nun zudem das kleine orangefarbene zylinderartige Repository-Symbol auf – damit wird angezeigt, dass die Dateien in den betroffenen Verzeichnissen ordnungsgemäß versioniert wurden. Das Fehlen von “>”-Symbolen an den Verzeichnissen, die Dateien enthalten, zeigt an, dass im Repository auch alles auf dem aktuellsten Stand ist.

Neben dem Verzeichnissymbol zum Haupt-Directory “alien1” unseres Projekts wird freundlicherweise dargestellt, welchem Branch der Inhalt des “Working Trees” gerade zugeordnet ist. (In realen Projekten wird es ja ggf. mehrere Branches geben). Der geneigte Leser wird nun sicher auch selbst beantworten können, warum das “>”-Symbol neben dem Hauptverzeichnis “alien1” nicht verschwunden ist.

Der interessierte Leser mag in einem eigenen Beispiel zudem mal einen Blick in das Verzeichnis “/…./.git/objects” werfen; man wird feststellen, dass auch dieses Verzeichnis nach dem initialen Commit mit vielen Dateien in einem Binärformat gefüllt wurde.
Nach unserem initialen Commit ist die komplette Information über den Inhalt der Projekt-Dateien also redundant vorhanden – einmal im “Working Tree” und auch in der Objektdatenbank des Repositories.

Nach weiteren Commits enthält das Repository aber deutlich mehr Informationen als der Working Tree: Der Working Tree spiegelt dann nur den Zustand wider, der zum geöffneten letzten Knoten des aktiven Branches gehört – plus ggf. zwischenzeitlich vorgenommene Änderungen, die noch nicht committed wurden. Das Repository hingegen enthält dann die gesamte bisherige Änderungshistorie. Dank Komprimierungstechnologie und der Speicherung inkrementeller Änderungen ist der Platzbedarf des Workings Trees über lange Zeit hinweg dennoch meist deutlich geringer als der Platzbedarf des Working Trees.

Zusammenfassung und Ausblick

Wir haben im Zuge dieses Artikels zu einem vorhandenen Projekt eine voll funktionsfähige lokale Versionsverwaltung unter Git eingerichtet, mit der wir nun weitere Experimente durchführen können. Wir haben dabei gesehen, dass ein Repository nach seiner Anlage auch gefüllt werden muss. Dazu sind Dateien für die Erfassung und Verfolgung in der Versionsverwaltung zu markieren. Ein Commit besteht im Grunde aus drei Phasen :

  • Phase 1: Auswahl und Staging der geänderten Dateien für die Durchführung des Commits.
  • Phase 2: Eingeben eines Kommentars zum Commit. Benennung des Autors und des Committers.
  • Phase 3: Technische Durchführung des Commits.

Ein Commit erzeugt einen eindeutig identifizierbaren Knoten in einem Branch. In einem initialen Commit werden der Master-Branch des Repositories und dessen erster Knoten erzeugt. Der Commit bzw. der korrespondierende Knoten im Branch sind durch einen eindeutigen Hash gekennzeichnet. Einem Commit sind ferner bestimmte (neu) angelegte Objekte im Verzeichnis “.git/objects” zugeordnet.

Im nächsten Blog-Post führen wir testweise einige Änderungen und zugehörige Commits durch. Wir betrachten dabei auch die Darstellung der Historie unter Eclipse. Zudem werfen wir einen ersten vergleichenden Blick auf die GUIs “GitG” bzw. “QGit”.

Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – I

Seit Jahren arbeite ich unter Eclipse fast gewohnheitsmäßig mit SVN (Subversive Plugin und Polarion Connectoren) als Versionsverwaltungstool. Eine Frage, die mich dabei seit längerem verfolgt, ist:

Ist für Freiberufler wie mich, und damit in vielen Fällen für mobile Einzelkämpfer, trotz aller SVN-Affinität ein Wechsel auf Git sinnvoll? Woran kann man den Bedarf dingfest machen?

Steht ein Entwicklungsprojekt in einem überregional verteilten Team an, so setzen ja bereits viele andere Entwickler auch Git ein. Dann ist der Einsatz natürlich auch für mich unumgänglich. In der Situation befand ich mich aber noch nicht – deswegen habe ich eine ernsthafte Auseinandersetzung bislang auch immer wieder verschoben. Nun belasten mich ein paar Defizite von SVN aber zumindest für Entwicklungsaufgaben so, das ich das Thema wieder aufgreife.

Betrachten wir also mal eine sehr viel einfachere Situation als eine Entwicklung in einem verteilten Team. Nehmen wir an, das ich alleine etwas für einen Kunden entwickeln muss, aber den Kunden regelmäßig an einem anderen Ort (u.U. im Ausland) aufsuchen muss. Oder nehmen wir an, dass während der Entwicklungsarbeiten auch aus anderen gründen längere Abwesenheiten vom Arbeitsplatz-PC erforderlich sind, und in der Zwischenzeit am Laptop weitergearbeitet werden muss. Und gehen wir weiter von einer Situation aus, in der im Vor- und Nachfeld von Reisen der Status veränderter Projekt-Dateien regelmäßig versioniert und auf einfache Weise zwischen Arbeitsplatz-PC daheim und einem Laptop abgeglichen werden muss:

  1. Bewege ich mich zum Kunden, muss ich Dateien, die sich auf aktuell laufende Entwicklungsprojekte beziehen und an denen ich zuvor an meinem Arbeitsplatz gearbeitet habe, von meinem PC auf einfache automatisierte Weise auf einen Laptop transferieren. Dabei möchte ich aus entwicklungstechnischen Gründen (und in Beratungsprojekten z.B. zu Belegzwecken) immer die gesamte bisherige Versionshistorie der Dateien mit mir führen.
  2. Natürlich möchte ich answchließend zur Kontrolle weiterer Änderungen, die während der Reise anfallen, auf dem Laptop gerne ein lokales Versionsmanagement betreiben. Das gilt vor allem bei längerer Abwesenheit vom Heim/Firmenstandort. Und die während der Reise erreichten Zwischenstände möchte ich schon aus Backup-Gründen mit einem Repository im Internet abgleichen.
  3. Und natürlich sollen die auf dem Laptop erarbeiteten Ergebnisse nach dem Ende der Reise wieder in ein dortiges zentrales Repository des Haus-/Firmennetzes eingespielt werden. Von da dann wenn möglich auch weiter in ein lokales Repository auf meinem Arbeitsplatz-PC ..

Hier besteht also schon für einen Einzelkämpfer der Bedarf nach einer lokalen und einer zentraler Versionsverfolgung. Das bedingt zwangsläufig den Abgleich zwischen lokalen und zentralen Repositories zu sinnvoll gewählten Zeitpunkten. Will man solche Szenarien mit SVN abbilden steht man vor einem kleinen Dilemma, das erhebliche Klimmzüge erfordert.

SVN ist dafür gedacht, dass man sich mit einem (!) zentralen Server abgleicht. Der muss deshalb auch kontinuierlich erreichbar sein. Nun habe ich (gerade bei etwas längeren Auslandsaufenthalten) aber nicht immer eine Internetverbindung zur Verfügung. In einer solchen Situation bräuchte ich dann zwingend eine lokale Versionsverwaltung auf meinem Laptop, dessen Repository später mit Repositories mehrerer Server abgeglichen werden soll und muss. Das ist unter SVN wirklich ein Rezept für ein gediegenes Kuddelmuddel.

Unter Git ist das nach ersten Literaturstudien dagegen gut zu beherrschen. Git erlaubt das sichere Arbeiten mit einem lokalen und zentralen Repositories. In dieser Miniserie von Blog-Posts möchte ich deshalb für Interessierte
und Einsteiger, die Git unter Eclipse betreiben wollen, erste einfache Schritte beschreiben,

  • wie man lokale Repositorys mit Eclipse-Mitteln auf einem PC und auf einem Laptop aufsetzt,
  • wie man Repositories “cloned” und Clones zum Einrichten von Server-Repositories verwendet,
  • wie man ein lokales Repository dann mit einem ein zentralen Repository im Haus- oder Firmennetz verbindet
  • und gleichzeitig ein weiteres Repository im Internet als Backup- und Refernz-Repository benutzen kann.

Ich lasse dabei ein komplexes lokales und zentrales Feature- oder Release-Branching völlig außen vor. Um Release- und Feature-Branching kümmere ich mich evtl. in späteren Blog-Artikeln. Hier geht es nur um die Unterstützung eines quasi-sequentiellen Arbeitens an verschiedenen Geräten in folgendem Sinne:

Schritte eines Wunschszenarios für mobile Einzelkämpfer

  1. Entwicklung-/Dateiänderungen am Arbeitsplatz-PC. Versionierung mit Hilfe eines lokalen Repositorys auf dem PC
  2. => regelmäßiger Abgleich des Entwicklungsstandes auf dem PC mit einem zentralen Server-Repository im Hausnetz und einem zentralen Repository im Internet
  3. => Vorbereitung einer Reise: Abgleich des zentralen Server-Repositories mit dem lokalen Repositories eines Laptops
  4. => mobiles Arbeiten am Laptop und Versionsverwaltung mit einem dortigen lokalen Repository
  5. =>Abgleich neuer Versionsstände auf dem Laptop mit einem zentralen Repository im Internet
  6. => Rückkehr ins Firmennetz und Abgleich des Laptop-Repositories mit dem zentralen Git-Repository auf einem zuständigen Server des LANs
  7. => Abgleich zwischen Server-Repository und Arbeitsplatz-Repository
  8. => Arbeiten mit dem lokalen Repository am Arbeitsplatz und erneut Abgleich mit beiden Server-Repositories
  9. => Abgleich mit Laptop in Vorbereitung einer neuen Reise.
  10. Das ist ein denkbar einfaches Szenario, dass sich mit Hilfe einer IDE wie Eclipse nachbilden lassen sollte. Zur Vorbereitung benötigen wir jedoch einige generell Git-Begrifflichkeiten. Daher greife ich nachfolgend die Arbeitsschritte nochmal aus Eclipse-Perspektive auf und führe dabei gleichzeitig ein paar zentrale Begriffe aus der Git-Welt ein, an die man sich sowieso gewöhnen muss, wenn man ein Git-Plugin unter Eclipse einsetzen will.

    Wir leiten aus der Neubeschreibung der obigen Arbeitsschritte unseres Wunschszenarios nebenbei Aufgaben ab, deren Lösung später zu einer systematischen Abbildung der gewünschten Prozesse unter Eclipse führen wird.

    Das Wunschszenario in Git-Begriffen

    Zur Vertiefung betrachten wir folgende Skizze:

    Bin ich im firmeneigenen Netz arbeite ich normalerweise auf einer Linux-Workstation. Dort ist etwa “Eclipse Neon 3” mit dem Git-Plugin (Egit) installiert. Die Entwicklungsarbeit ist in Form von (Eclipse-) “Projekten” organisiert, zu denen jeweils ein Dateibaum gehört. In meinem Fall tummeln sich dort typischerweise PHP-, HTML,-, CSS-, Javascript- und auch mal Python-Dateien.

    Für unser Beispielszenario ziehen wir später die Dateien eines konkreten Eclipse-Projektes namens “alien1” heran. Die zugehörigen Dateien liegen dabei unter 4 Haupt-Verzeichnissen: includes, admin, interpreters,
    uploads. Das Projekt wurde in einem Eclipse-“Workspace” namens “ecl_alienx” platziert. Die genannten 4 Verzeichnisse sind im Beispiel deshalb unter einem Verzeichnis “/projekte/ecl_alienx/alien1” zu finden.

    Man könnte nun darüber streiten, ob die Aufbewahrung der Programm-Dateien innerhalb des Eclipse-Workspaces sinnvoll ist. Wir unterlassen diese Diskussion aber, da wir die Verzeichnislokation im Rahmen des Git-Einsatzes sowieso ändern werden.


    Was wollen wir nun im Detail erreichen?
    Zur Versionsverwaltung möchte ich in unserem Wunsch-Szenario zunächst ein lokales Git-Repository auf dem Arebitsplatz-PC benutzen, bis ich der Meinung bin, dass erreichte Zwischenergebnisse auch auf einem zentralen (Git-) Repository eines Servers im Haus-/Firmen-Netz zur Verfügung gestellt werden sollten. Ggf. für den Zugriff durch andere Nutzer/Tester, aber auch für zentrale Backups.

    Lokal checke ich eine erreichte neue (Zwischen-) Version zunächst als sogenannten “Commit” in das Git-Repository meiner Workstation ein. Manchmal muss man in der SW-Entwicklung von einer bestimmten Version aus unterschiedliche, und für eine Weile divergierende Wege zu neuen Versionen gehen. Man erhält dann “Verzweigungen” der Versionsstände; wie in SVN auch bezeichnet man diese Zweige in Git als “Branches“. Branches können zum Beispiel als Feature- oder Release-Branches angelegt werden zur experimentellen Entwicklung von Features in einem separaten Zweig bzw. zur Konsolidierung eingefrorener Versionsstände angelegt werden. Andee Branches mögen etwa auch einer Continuous Integration dienen. Zu und zwischen Branches – also definierten Abzweigungen in der Versionshistorie – möchte man ggf. wechseln. Unter Git spricht man hierbei (abweichend von anderen Versionsverwaltungssystemen) von sog. Checkouts. Warum wird später klarer werden. Würde man z.B. vom Master-Branch zu einem anderen Branch namens “Branch2″ wechseln, so würde Branch2” zum sog. “aktiven Branch” des Repositories werden.

    Jeder Commit entspricht grafisch gesehen einem “Knoten” eines Branches (s. die Skizze weiter unten); ein Knoten symbolisiert einen bestimmte Versionsstand des Branches (also eine eindeutigen Kombination aus definierten Versionsständen aller für die Versionsverwaltung erfassten Dateien). Ein Knoten in einem Branch entspricht also genau besehen nicht nur dem punktuellen Commit, der zu seiner Erzeugung führte, sondern im Kern der ganzen Historie aller vorangegangenen Commits auf den erfassten Dateien – ausgehend von einem initialen Commit. Die Rückkehr zu einem älteren Versionsstand (Knoten) in einem Branch nennt man unter Git einen sog. “Reset“.

    Für Aktionen wie Commits, eine Historienverfolgung, Branching, Checkouts, Resets etc. sollte im Grunde gar kein Server erforderlich sein; ich will mit meiner lokalen Versionsverwaltung und dem zugehörigen Repository wenn nötig auch einen ganze Weile völlig autonom arbeiten können ohne irgendwelche Kollegen zu belästigen.

    So weit so gut. Nun eine Einschränkung:

    In unserem Szenario konzentrieren wir uns weitgehend auf sukzessive voranschreitende Versionen – oder “Knoten” – in genau einem Branch jedes eingesetzten Repositories. Der ursprünglich erzeugte, erste und native Branch eines jeden Repositories ist dessen sog. Master-Branch. Wir beschäftigen uns in dieser Blog-Post-Serie also primär mit dem Abgleich zwischen den “Master”-Branches verschiedener Repositories. Wir lassen dabei für unser Wunschszenario die gewollte Erzeugung von Branches (Entwicklungszweigen) und das Wechseln zu einem anderen als dem “Master”-Branch weitgehend außen vor.

    Die Verzeichnisstruktur, in der die Dateien eines Eclipse-Projektes organisiert werden, muss sich in einem geeigneten Äquivalent im Rahmen der
    Repository-Verwaltung widerspiegeln. Ein solches Äquivalent ist der sog. “Working Tree” eines Repositories. Wir werden sehen, dass auch der Working Tree eine Verzeichnisstruktur darstellt; diese entspricht ferner einer Momentaufnahme eines definierten Knotens auf dem “aktuellen Branch”; in unserem Beispiel also auf dem Master-Branch. Achtung: Checkouts und Resets führen zu Veränderungen der Inhalte von Dateien im “Working Tree”.

    Typischerweise kann es sich ein Versionsverwaltungssystem aber nicht leisten, unterschiedliche und vollständige Varianten aller Dateiinhalte in normaler Dateiform aufzubewahren, um die Historie zu erfassen. Git arbeitet hier (wie andere System auch) inkrementell und verwahrt Differenzen zwischen Dateizuständen; zudem wird komprimiert: Im Ergebnis entstehen so Blobs einer Art Objekt-Datenbank. Die Objekte wiederum lassen sich eindeutig identifizieren und werden definierten Zustandsknoten von Branches (und damit auch Commits) zugeordnet. Eine eindeutige Identifizierung sowohl von Objekten als auch Commits (Knoten) wird in Git über Hashes ermöglicht, zwischen denen dann Relationen hergestellt werden. Weitere Details der Objektorganisation sind in unserem Kontext unwichtig.

    Der Working Tree stellt eine Momentaufnahme eines ausgewählten Knotens dar – in der Regel des letzten, also des jüngsten Knotens einer Branch-Historie. Dieser Knoten wird auch als HEAD des Branches bezeichnet.

    Die ersten Ziele, die wir unter Eclipse erreichen müssen, sind demnach folgende:

    • Aufgabe 1: Erstellen eines lokalen Repositories auf dem PC.
    • Aufgabe 2: Klärung: Wo liegt der “Working Tree”? Welchen Verzeichnisbaum zeigt Eclipse?
    • Aufgabe 3: Einbringen des Inhalts des Projektverzeichnisbaums in den Master-Branch des lokalen Repositories. Initiales Commit.
    • Aufgabe 4: Durchführung von Änderungen im Verzeichnisbaum des Projekts und Testen nachfolgender “Commits”. Blick auf die Git-Versionshistorie auf dem PC. Identifizierung des HEAD-Commmits bzw. -Knotens.

    Zu geeigneten Zeitpunkten möchte ich den aktuellen Stand meiner Dateien auch auf einem zentralen Git-Server (z.B. im Firmen-LAN) bereitstellen. Entsprechende Operation werden als “Push“-Operationen bezeichnet. Ziel (“Target”) eines Pushes ist also ein Branch aus einem anderen (Target-) Repository – hier also im Repository des Servers. Ein Pushvorgang erzeugt dort einen neuen Knoten.

    Andere bzw. auch ich selbst können später vom Server-Repository den dort festgehaltenen Entwicklungsstand auf andere Geräte (z.B. einen Laptop) und dortige lokale Repositories überführen. Dazu führen sie dann sog. Pull- oder Fetch-Operationen durch, mit denen der Versionsstand eines definierten Branches auf dem Server abgefragt und in das lokale Repository überführt und dort integriert wird. Auf den Unterschied zwischen Pull und Fetch gehe ich an passender Stelle in einem späteren Post dieser Serie kurz ein.

    In unserem Szenario “pullen” wir typischerweise den letzten Stand vom Master-Branch des Server-Repositories. Das Target eines Pulls ist also der aktuelle Branch in dem lokalen Repository, von dem aus ich auf ein anderes Repository als Quelle von Veränderungen zugreife. Der Pull erzeugt einen neuen Knoten in meinem lokalen Branch. In unserem Szenario finden Pulls normalerweise auf dem Laptop und später auch auf dem Arbeitsplatz-PC statt. Server-Repositories protokollieren in unserem Szenarion dagegen neue Versionsstände als Ergebnis von Push-Vorgängen zum einem dortigen Branch – bei uns dem Master-Branch des jeweiligen Server-Repositories.

    Sowohl bei einer Push- bzw. Pull-Operationen finden (automatisch) Merge-Prozesse mit dem Stand des betroffenen Branches auf dem Target-Repository statt. In unserem Szenario wird ein erster Push-Prozess also z.B. den Stand einer Datei (oder mehrerer Dateien) des Master-Branches auf einem lokalen PC-Repository mit dem Stand der entsprechenden Datei(en) (bzw. ihrer Objekte) im Master-Branch des Server-Repositorys zusammenführen. Das PC-Repository verändert sich dabei nicht; der neue Knoten entsteht auf dem Server. [Hat man Push/Pull-Vorgänge zwischen definierten Master-Branches unterschiedlicher Repositories verstanden, so sind spätere, komplexere Operationen zwischen anderen lokalen und zentralen Repository-Branches (als den Master-Branches) relativ einfach zu beherrschen.]

    Beim Zusammenführen unterschiedlicher Dateiänderungen in einem Merge können ggf. Konflikte auftreten; diese müssen dann ggf. manuell aufgelöst werden. Das erfordert ggf. detaillierte Vergleiche der in den unterschiedlichen Branches/Repositories vor dem Merge durchgeführten Änderungen. Wir werden aber sehen:

    Arbeitet man als Einzelperson phasenweise entweder ausschließlich auf dem Arbeitsplatz oder in einer anderen Phase ausschließlich auf dem Laptop und gleicht bei einem Übergang zwischen diesen Arbeitsphasen die jeweiligen lokalen Repositories mit einem zentralen Server-Repository in der oben angegebenen Schrittfolge ab, so treten Konflikte in der Regel nicht auf. In diesen Fällen kann Git die notwendigen Merges automatisch und auf einfache Weise erledigen. Man spricht dann auch von Fast-Forward-Merges (FF-Merges).

    Bei FF-Merges sind eigentlich nur neue Knoten in einem Branch zu erzeugen und Referenzen zu bereits bekannten Objekten aufzubauen. Letztere müssen möglicherweise zwischen Repositories kopiert werden; aber echte neue Objekte zu neuen Datenstrukturen sind nicht zu erzeugen. Es liegt dann kein Merge-Commit wie im Falle echter Zusammenführungen mehrer unterschiedlicher Änderungen in den Quell- und Target-Branches vor.

    Die Situation wird in folgender Skizze dargestellt. Wir sehen sukzessiv erstellte Knoten in den Master-Branches dreier Repositories. Echte Commits, bei denen Datenveränderungen vorgenommen wurden, sind als solche bezeichnet und durchnummeriert. Zu jedem Knoten findet man die ersten alphanumerischen Zeichen eines zugehörigen eindeutigen Hashes. In jedem Branch ist der führende Head-Knoten durch eine rote Füllung angedeutet.

    Push und Pull-Aktionen führen in der rein sequentiellen Aktionsabfolge unseres einfachen Szenarios zu unkomplizierten und konfliktfreien FF-Merges, in die wir nicht eingreifen müssen.

    Komplizierter wäre das Mergen allerdings dann, wenn die zentralen Versionsstände zu Dateien, an denen man selbst in seinem lokalen Repository arbeitet, während einer Phase auch durch andere von anderen Stellen aus manipuliert werden würden. Das schließen wir für diese Artikelserie aber mal aus. Etwas schwieriger ist auch ein zwischenzeitlicher lokaler “Reset” auf alte Versionen beim Abgleich zwischen den verschiedenen Arbeitsphasen an unterschiedlichen Geräten zu bewältigen. Auch auch das lassen wir hier zunächst außen vor. Ich komme darauf aber in einem späteren Post der Serie zurück.

    Der eine oder andere wird sich noch die Frage stellen, wie man denn überhaupt zu einem Repository kommt. Unter Git (und damit auch mit entsprechenden Tools unter Eclipse) ist das sowohl über das gezielte Erzeugen und Initialisieren eines Repositories in einem Projekt-Verzeichnis möglich als auch über das Klonen (also Kopieren) von Repositories. Clones mit bestimmten Eigenschaften
    können auch auf Server verschoben und anschließend mit lokalen Repositories verbunden werden. Wir werden uns den Aufbau und das Klonen von Repositories mit Eclipse Tools später genauer ansehen.

    Wir können an dieser Stelle jedoch schon mehrere neue Aufgaben festhalten, die wir unter Eclipse lösen müssen:

    • Aufgabe 5: Erstellen eines zentralen Repositories auf einem Server im LAN. Hierzu werden wir unser lokales PC-Repository aus Aufgabe 1 in einer bestimmten Weise “klonen”. Der Clone wird dann auf dem Server in zugänglicher Weise platziert.
    • Aufgabe 6: Anbinden des zentralen Repositories an das lokale PC-Repository. (Verkopplung der Master-Branches). Klärung der Frage, ob man ein lokales Commit auf dem PC mit einem gleichzeitigen Push zum Server verbinden kann.
    • Aufgabe 7: testweises Überführen von neuen Änderungen, die wir auf dem PC am Projekt vornehmen werden, in den Master-Branch des Git-Server-Repositories.

    Wenden wir uns nun unserem Laptop zu. Im Wunschszenario ist beim Wechsel von einer Arbeitsphase am PC auf eine mobile Phase unter alleinigem Einsatz des Laptops zunächst also eine Push-Operation vom PC auf den Server und danach eine Pull-Operation auf dem Laptop erforderlich. Es ergeben sich also folgende weitere Aufgaben:

    • Aufgabe 8: Erzeugen eines passenden Projektes in einem geeigneten Eclipse-Workspace des Laptops. Klonen des Server-Repositorys und Bereitstellen auf dem Laptop. Verbinden mit dem Projekt. Hier stellt sich u.a. die Frage: Kann man diese Schritte unter Eclipse nicht irgendwie miteinander verbinden?
    • Aufgabe 9: Transfer von neuen Commits, die wir auf dem PC vornehmen, über das zwischengeschaltete Server-Repository in das lokale Repository des Laptops. Also: Commit auf dem PC, Push zum Server und Pull-Operation auf dem Laptop.

    Danach arbeitet man ausschließlich auf dem Laptop weiter:

    • Aufgabe 10: Durchführung von Änderungen und entsprechenden Commits auf dem Laptop. Blick auf die Versionshistorie des Laptops.

    Hinweis: Unter Git wäre prinzipiell auch ein direkter Abgleich zwischen den lokalen Repositories des Laptops und des PCs möglich gewesen. Aus verschiedenen Gründen halten wir aber in einem LAN den Weg über ein zentrales Repository für eine deutlich bessere Idee (Stern-Architektur). Ein Grund besteht etwa in Sicherheitsargumenten und einer Zugriffskontrolle: Nicht jedes System soll auf Verzeichnisstrukturen jedes andere im Netz zugreifen können. Die Zugriffsberechtigung auf bestimmte Repository-Daten lässt sich an zentraler Stelle gleich für mehrere Mitarbeiter steuern. Ein weiterer Grund ist die zentrale Zugriffsprotokollierung. Zudem stehen die Arbeitsergebnisse an zentraler Stelle auch für Backups und andere Mitarbeiter zur Verfügung.


    Betrachten wir die Situation die sich bei der Rückkehr von einer Reise ergibt. Will man dann ausschließlich auf dem Arbeitsplatz-PC weiterarbeiten, so gilt: Erforderlich ist zuerst eine Push-Operation vom Laptop in Richtung Server-Repository des Firmen-LANs und anschließend eine Pull-Operation am PC, bei der man den aktuellen Stand vom Server-Repository in das lokale Repository des PCs überführt.

    Man kann die gesamte Kette “PC => Server => Laptop => Server => PC”, die im Laufe einer Reise abgearbeitet wird, auch an der oben dargestellten Skizze nachvollziehen. Der Weg vom Laptop zum PC ist daher die Umkehrung von Aufgabe 9:

    • Aufgabe 11: Transfer von Änderungen auf dem Laptop über den zentralen LAN-Server auf den Arbeitsplatz-PC.

    Repository im Internet
    Was soll nun in unserem 1-Mann-Szenario der in der Skizze angedeutete weitere Server im Internet ?

    Ein Repository kann man auch als spezielle Backup-Datenbank für erzielte Arbeitsergebnisse betrachten. Zentrale Repositories schützen in diesem Sinne gegen lokale Verluste. Tatsächlich ist es mir schon mal passiert, dass eine Laptop-Platte auf einer Reise ihren Geist aufgegeben hat. Manchmal kann man sich dann sogar eine neue Platte beschaffen – und Linux wie Eclipse sind auch schnell installiert. Aber woher bekommt man in einem solchen Fall ein halbwegs aktuelles Repository? Hier hilft ein Server im Internet.

    Umgekehrt gilt: Auch ein Totalverlust des Laptops (z.B. durch Diebstahl) ist in der Regel weniger schlimm als der Verlust der auf diesem Gerät erzielten Arbeitsergebnisse. D.h., wir werden das Internet-Repository auch während einer Reise relativ regelmäßig mit den auf dem Laptop erzielten Versionsständen per Push-Abgleich versorgen. Soweit wir eben Zugang zu einer halbwegs vernünftigen Internet-Anbindung haben. Da Git inkrementell und komprimiert sichert, ist die Menge der zu transferierenden Daten dann oft gar nicht so groß, wie man meinen möchte. Natürlich gilt, dass man ein zentrales Repository im Internet auch für andere Zwecke wie die Arbeit in einem verteilten Team nutzen könnte.

    Wir benutzen also ein zentrales Git-Repository im Internet (oder mehrere solcher Repositories) als Backup-Instanz. Damit das Repository des Internet-Servers bereits zu Beginn einer Reise ordentlich gefüllt ist, werden wir vom PC aus rechtzeitig entsprechende Push-Aktionen durchführen. Im Sinne einer Backup-Übung sogar relativ regelmäßig. Selbstverständlich müssen unsere Administratoren dafür Sorge tragen, dass der Server und der Kommunikationsweg zum Repository hinreichend gegen Zugriffe anderer geschützt ist. Hier gelten die gleichen Maßnahmen, die man bei jedem anderen Cloud-Service auch treffen würde.

    Es ergeben sich folgende abschließende Aufgaben:

    • Aufgabe 12: Klonen des LAN-Server-Repositories und Verschieben des Clones auf einen Server im Internet. Ankopplung an die Git-Versionsverwaltung unter Eclipse auf dem PC.
    • Aufgabe 13: Testen von gezielten Push-Vorgängen vom PC und vom Laptop zum Repository auf dem Internet-Server. Klärung der Frage, ob wir unter Eclipse mit einem Commit zwei parallel Pushvorgänge zu den verschiedenen Server-Repositories im LAN und im Internet anstoßen können.
    • Aufgabe 14: Testweiser Pull eines per Laptop erzeugten Versionsstandes vom Internet-Repository auf den PC. Anschließend Push vom Laptop zum Repository auf dem LAN-Server (!) und nachfolgender erneuter Pull desselben Versionsstandes durch den PC auch vom LAN-Server. Wird die Identität der Versionsstände erkannt?

    Damit genug der Theorie. Wir haben unser Wunschszenario in Git-Begriffen ausgedeutet und uns dabei ein ordentliches Paket an Aufgaben zusammengestellt, das wir soweit möglich und sinnvoll unter Eclipse abarbeiten wollen. Im nächsten Post dieser Serie
    Erste Schritte mit Git für lokale und zentrale Repositories unter Eclipse – II
    bewegen wir uns dann in die Git-Praxis unter Eclipse und legen dort erstmal ein lokales Repository für unser Beispielprojekt “alien1” an.