L1: Git, Github und Clients - JirkaDellOro/Softwaredesign GitHub Wiki

Registriert euch (kostenlos) bei GitHub und installiert GitHub Desktop. Dabei wird auch gleich der "git" Kommandozeilen-Befehl mit installiert (über die App "Git Shell" oder den Menübefehl "Repository > Open in PowerShell" erreichbar). Die meisten Aktionen lassen sich in GitHub Desktop auslösen. Soll der git-Kommandozeilenbefehl verwendet werden, kann entweder die mit GitHub Desktop mitinstallierte App "Git Shell" gestartet werden, oder eine Kommandozeile über den Menübefehl ""Repository > Open in PowerShell" eine Kommandozeile geöffnet werden.

1. Bildet Zweiergruppen

2. Ein neues GitHub Projekt

Entwickler A legt ein Projekt an und Entwickler B "holt" sich das Projekt auf seinen Rechner.

TODO

  • A: legt ein neues Git-Projekt auf Github (z.B. mit git init auf der Kommandozeile oder mit GitHub Desktop (Menü "File->New repository") an.
  • A: fügt dem Projekt eine simple HTML-Datei (Mit einem Titel im Header und einer Textzeile im Body) zu.
  • A: überträgt die Änderung ins lokale Repository (mit git commit -a oder mit dem Commit Button in GitHub Desktop).
  • A: Lädt die Änderung ins Repository auf dem GitHub-Server (mit push).
  • A: Macht Person B zum Contributor des Projektes (in den Projekt-Eigenschaften der GitHub-Web-Seite des Projektes).
  • B: Holt sich erstmalig eine lokale Version des Remote-Repository mit clone.

3. Änderungen synchronisieren

Entwickler B ändert etwas und Entwickler A "holt" sich den neuesten Stand auf seinen Rechner.

TODO

  1. B: legt eine neue Bilddatei in den Ordner und fügt das Bild per <img>-Tag der HTML-Datei hinzu, und fügt einen beschreibenden Text in die HTML-Datei ein.
  2. B: überträgt die Änderung ins lokale Repository (mit git commit -a oder mit dem Commit Button in GitHub Desktop).
  3. B: Lädt die Änderung ins Repository auf dem GitHub-Server (mit push).
  4. A: Holt sich die Änderungen von B mit pull.

4. Zusammenhängende Änderungen einpflegen

Entwickler A ändert das Bild und passt den beschreibenden Text an. Beides wird gleichzeitig eingepflegt.

TODO

  1. A: Ändert den Inhalt der Bild-Datei.
  2. A: Ändert den beschreibenden Text in der HTML-Datei.
  3. A: überträgt die Änderung ins lokale Repository (mit git commit -a oder mit dem Commit Button in GitHub Desktop).
  4. A: Lädt die Änderung ins Repository auf dem GitHub-Server (mit push).
  5. B: Holt sich die Änderungen von B mit pull.

5. Historischen Verlauf betrachten und alte Zustände wiederherstellen

Sämtliche bisher vorgenommenen Änderungen sind von Git gespeichert worden. Mit Änderungen sind gemeint:

  • Änderungen an einzelnen Dateien
  • Hinzufügen von neuen Dateien
  • Löschen von Dateien
  • Umbenennen oder Verschieben von Dateien

der historische Verlauf wird in einem Git-GUI wie z.B. GitHub Desktop oder GitKraken direkt als Liste angezeigt. Damit kann sehr gut verfolgt werden, welcher Entwickler wann welche Änderungen vorgenommen hat. Hier zeigt sich auch, warum es sinnvoll ist, "gute" Kommentare beim commit anzugeben. Zudem sollte man sich angewöhnen, Commits so aufzuteilen, dass diese thematisch zueinander passen.
Einzelne Commits (Einträge in der Historie) werden in Git mit SHAs identifiziert. Einzelne SHAs können z.B. in GitHub Desktop durch rechts-Klick auf einen Hinstory-Eintrag (einen Commit) in die Zwischenablage kopiert werden. SHA-Werte sind zu lang, um sie abzutippen oder im Kopf zu behalten und daher unhandlich, wenn sie nicht über die Zwischenablage kopiert werden können. Daher gibt es die SHA-Werte jeweils auch in einer Kurzversion.

TODO

Um einen alten Zustand temporär herzustellen:

  • Betrachtet den bisherigen Verlauf der Commits in GitHub Desktop oder in der Kommandozeile (mit git reflog (Kurzversion) oder git log (Langversion)).
  • Sucht einen Stand aus, den Ihr wiederherstellen wollt und merkt/speichert dessen SHA-Wert
  • Öffnet das Repository in der Kommandozeile (falls noch nicht geschehn)
  • Stellt den gewünschten alten Zustand mit folgenden Kommando her: git checkout <SHA>
    Es erscheint eine WARNUNG
You are in 'detached HEAD' state. ...

Alle Dateien im Arbeitsverzeichnis befinden sich nun im alten Zustand. Hier sollte allerdings nichts geändert werden, der alte Zustand kann aber betrachtet werden. Um alle Dateien wieder in den aktuellen Zustand zu bringen:

  • Gebt auf der Kommandozeile ein: git checkout master
    Neben dieser Prozedur können auch einzelne commits rückgängig gemacht werden. Da das Rückgängig machen eine Änderung darstellt, wird dieser Schritt ebenfalls gleich in die History eingetragen. Hier sollte man wissen, was man tut, wenn man Commits NICHT in umgekehrter Reihenfolge rückgängig macht!

TODO

Um einen einzelnen Commit rückgängig zu machen:
In GitHub Desktop

  • Rechts-Click auf den letzten Commit
  • Revert this Commit

In der Kommandozeile

  • git revert <SHA> --no-edit
    Schließlich können alte Zustände auch dauerhaft wieder hergestellt werden, d.h. alle später hinzugekommenen commits werden gelöscht.

TODO

Um einen alten Commit dauerhaft als letzten Stand festzulegen,

  • in der Kommandozeile git reset <SHA> eingeben.

6. Gleichzeitig in einer Datei ändern

Viele Dateien in Software-Projekten sind Text-Dateien. Git (wie auch andere Source-Control-Systeme) ist in der Lage, nach einer Änderung festzustellen, welche Zeilen von Änderungen betroffen sind. Wird eine Datei von zwei Entwicklern zwar gleichzeitig geändert, befinden sich die Änderungen aber in unterschiedlichen Zeilen, führt Git die Änderungen automatisch zusammen.
Ein solches Zusammenführen von Änderungen, ob automatisch oder Manuell, nennt man Merge

TODO

  1. A ändert die Titelzeile der HTML-Datei und speichert die Datei lokal
  2. B ändert den Text im Body der HTML-Datei uns speichert die Datei lokal
  3. A Führt einen commit aus, danach einen push. Somit sind die Änderungen auf dem Server.
  4. B Führt einen pull aus -> Es wird automatisch ein Merge durchgeführt.
  5. B hat jetzt bereits den zusammengeführten Stand (eigene Änderungen und die von B)
  6. B kann jetzt den gemeinsamen Stand als neuen Stand per commit und push publizieren.
  • Betrachtet die History in Git for Windows und findet heraus, wie man einen automatisch durchgeführten Merge erkennen kann.
    Diese Reihenfolge muss so nicht zwingend eingehalten werden. B kann nicht immer wissen, dass Änderungen vorliegen

  • Findet heraus, was passiert, wenn in o.a. Sequenz Entwickler B statt Schritt 4 einfach, wie bisher, auch direkt seine Änderungen per commit und push publizieren will.

Das automatische Zusammenführen ist ein mächtiges Feature und erlaubt in großen Software-Projekten mit vielen Entwicklern und großen Source-Dateien ein reibungsloses gleichzeitiges Arbeiten.
Trotzdem ist es mit Vorsicht zu genießen. Git hat keine Ahnung über die inhaltliche Bedeutung der Dateien. Auch wenn Git in bester Absicht zwei vermeintlich unabhängige Änderungen automatisch mergt, weil sie sich in unterschiedelichen Zeilen befinden, kann es passieren, dass die dabei entstehende Datei syntaktische oder semantische Fehler enthält. So kann es in Programm-Code vorkommen, dass die Block-Klammerungen über mehrere Zeilen ({ und }) nicht mehr richtig zu einander passt. In XML-Dateien können Fehler entstehen, weil zu einem öffnenden <Tag> nicht mehr das passende schließende </Tag> vorhanden ist.
Daher sollte folgende Regel selbstverständlich sein: Wurden nach einem pull oder einem expliziten merge Änderungen zusammen geführt, muss vor dem commit der zusammen geführten Änderungen die Lauffähigkeit geprüft werden.

7. Gleichzeitig in einer Datei an der selben Stelle ändern

Wenn Git bei einem Commit feststellt, dass zwei Entwickler gleichzeitig in der selben Zeile einer Datei Änderungen vorgenommen haben, kann Git keine automatische Lösung anbieten. In diesem Fall meldet Git einen Konflikt (conflict).

TODO

Provoziert einen Konflikt

  1. A ändert die Titelzeile der HTML-Datei und speichert die Datei lokal
  2. B ändert den ebenfalls die Titelzeile HTML-Datei uns speichert die Datei lokal
  3. A Führt einen commit aus, danach einen push. Somit sind die Änderungen auf dem Server.
  4. B Führt einen pull aus -> Es wird versucht, ein Merge durchgeführt. Dieser scheitert und Git meldet einen Konflikt
    Beim Auftreten eines Konflikts speichert Git die Datei, in der der Konflikt auftrat in einem speziellen Format. Die beiden Versionen der betroffenen Zeile(n) werden mit Folgen von <<<<<<<< HEAD, ========== und >>>>>>>> (conflict marker) voneinander getrennt.

TODO

Löst den Konflikt

  1. B Öffnet die HTML-Datei im Editor, fügt die gewünschte aus beiden Änderungen resultierende Titelzeile und löscht die conflict marker.
  2. B führt einen commit des aufgelösten Konfliktes durch, gefolgt von einem push.
    Zum Lösen von Konflikten gibt es spezielle Editoren, die anhand der conflict marker und der drei unterschiedlichen Stände der Datei (vor beiden Änderungen, Änderung von A und Änderung von B) dem Benutzer das Editieren von Conflicts erleichtern. Meistens gibt es in solchen Merge Tools folgende Features:
  • Gleichzeitige Anzeige der drei oder vier unterschiedlichen Datei-Versionen (Stand vor allen Änderungen, Änderungen von A, Änderungen von B, vom Benutzer von Hand editierte Änderungen) inklusive gleichzeitigem Scrollen in allen Versionen.
  • Gezieltes Anspringen von erkannten Konfliktstellen gleichzeitig in allen Versionen
  • Annehmen von Änderung A oder Änderung B durch Knopfdruck
  • Editieren der finalen Konfliktlösung per Hand
    In Git kann das zu verwendende Merge Tool mit dem Kommando git config --global merge.tool <mergetool> festgelegt werden. Ein derart festgelegtes Tool kann dann im Falle eines Konfliktes mit git mergetool <filename> für eine bestimmte konfliktionäre Datei aufgerufen werden.
    Jedem Git-Anwender wird dringend empfohlen, sich ein brauchbares Merge Tool zu installieren. Hier sind eine Reihe derartiger Tools aufgelistet, davon ist z.B. P4Merge kostenlos.

TODO

  • Installiert ein Merge Tool.
  • Provoziert weitere Konflikte (z.B. mehrere in mehreren Dateien gleichzeitig) und löst diese mit dem Merge-Tool.

8. Branches und Clones

Mit dem bis jetzt Gelernten haben wir einen Werkzeugkasten, mit dem wir die Synchronisierung der Änderungen der an einem Projekt beteiligten Entwickler sehr feingranular steuern können.
Noch komplexer wird es, wenn in einem Projekt nicht nur ein Zustand als der aktuelle gelten soll, sondern wenn gleichzeitig mehrere Zustände des Projektes geführt werden sollen und wenn dann auch noch einzelne Änderungen gezielt zwischen diesen Zuständen ausgetauscht werden sollen. Oben ist bereits ein Beispiel genannt, bei dem so etwas sinnvoll sein kann: Von einer Software soll eine neue Version entstehen, dort sollen neue Features entwickelt werden. Gleichzeitig soll der alte Stand der Software weiter mit Updates wie z.B. Bugfixes beliefert werden. Wurde ein Bug im alten Stand der Software gefixt, sollen die dazu notwendigen Änderungen auch gleich in die neue Version eingepflegt werden.

9. Branches

Für derartige Szenarien gibt es in Git (wie in vielen anderen Source-Control-Systemen auch) so genannte Branches (Verzweigungen). Wenn man sich eine Folge von Commits als einen wachsenden Ast von Entwicklungsschritten vorstellt, kann mit Hilfe eines Branches ein Nebenast angelegt werden, der von einem gemeinsamen Startpunkt aus eine eigene, zeitlich parallele Entwicklungshistorie nehmen kann.
Grundsätzlich gilt:

  • In jedem Git-Projekt gibt es mindestens einen Haupt-Branch. Dieser heißt master.
  • In jedem Git-Projekt kann es beliebig viele weitere Branches geben. Diese können benutzerdefinierte Namen tragen.
  • Einzelne Commits eines Branches können durch den Benutzer in einen anderen Branch übernommen werden. Da oft gleichzeitig an mehreren Branches gearbeitet wird, ist oft ein manueller oder automatischer Merge (s.o.) notwendig.

TODO

  • Schafft euch pro Entwickler einen eigenen Branch auf dem ihr arbeitet. Mit GitHub for Desktop:

    • Feld "Current Branch" aufklappen. im "Filter" einen neuen Branch-Namen eingeben
    • Button "New Branch" klicken.

    Mit der Kommandozeile

    • checkout -b <Neuer Branch-Name>
  • A und B: Jeder führt eigene Änderungen in jeweils seinem eigenen Branch durch.

  • A und B: Publiziert Eure Änderungen mit commit und push
    Auf diese Weise sind die Änderungen zunächst vollkommen losgelöst voneinander. Es sind keine Merges notwendig und es gibt keine Konflikte. Allerdings werden auch die Änderungen nirgendwo in einem gemeinsamen Stand zusammen geführt.

TODO

Überzeugt Euch davon, dass es zwei unterschiedliche Branches gibt.

  • Holt euch mit pull den letzten Stand.
  • Klappt in GitHub for Desktop das Feld "Current Branch" auf.
  • Wechselt ggf. den Branch und überzeug euch, dass alle Dateien des Projektes auf den jeweiligen Stand im Branch gebracht werden.
    Änderungen können nun explizit mit merge von einem Branch in den anderen übertragen werden.

TODO

  • Klappt in GitHub for Desktop das Feld "Current Branch" auf
  • Wechselt in den Branch, der die Änderungen eines anderen Branches empfangen soll.
  • Wählt im Menü "Branch" den Befehl "Merge into current branch..." aus.
  • Wählt im Dialog dann den Branch aus, von der die zu übertragenden Änderungen enthält
  • Ggf. erfolgt ein Merge mit Konflikten. Löst die Konflikte mit dem Merge Tool.
  • Überzeugt euch, dass die Änderungen im aktuellen Branch angekommen sind.

Branching Strategien

Es gibt eine ganze Reihe von Szenarien, in denen Branches sinnvoll sein können. Für alle Szenarien gibt es best practices, wie Branches eingesetzt werden können.

TODO

  • Googelt nach "Branching Strategies" und versucht, ein paar der angezeigten Links zu verstehen.
    Folgende Szenarien kommen häufig zum Einsatz, die manchmal auch miteinander kombiniert werden.
  • Master und Develop Die aktuelle stabile Version einer Software wird im master Branch gehalten. Aktuelle Änderungen und Entwicklungen finden im develop Branch statt. Wenn dort ein stabiler Zustand erreicht ist, wird nach master gemergt.
  • Version Branches Für jede Version einer Software gibt es einen eigenen Branch. Wird vor allem dann eingesetzt, wenn an mehreren Versionen gleichzeitig entwickelt wird, z.B. bei Updates alter Versionen oder mehreren Kundenspezifischen Anpassungen. Änderungen, die in anderen Versionen benötigt werden, können gezielt per merge eingespielt werden.
  • Developer Branches Jeder Entwickler arbeitet auf einem eigenen Branch. Von Zeit zu Zeit pflegen die Entwickler ihre Änderungen in einen gemeinsamen Entwicklungsbranch ein.
  • Feature Branches Jedes neu zu entwickelnde Feature wird in einem neuen Branch angelegt und dort zu einer gewissen Reife gebracht. Erst, wenn die Neuentwicklung stabil genug ist, wird sie in einen gemeinsamen Branch eingepflegt.
    Allgemein gilt: Git hebt sich von anderen Source-Control-Systemen dadurch ab, dass Branches weder für den Anwender noch für das System aufwändig ist. Daher können Branching Strategien zum Einsatz kommen, die von vielen und häufig durchgeführten Branches und Merges abhängig sind.
    Es ist nicht ungewöhnlich, dass erfahrene Entwickler gleichzeitig mehrere eigene Branches betreiben.

Forks

In Projekten, bei denen das Entwicklerteam nicht fest definiert ist, ist es oft schwierig, verteiltes Arbeiten nur mit Hilfe von Branches zu ermöglichen. Jeder Entwickler, der in einem Projekt/Repository Branches anlegen soll, muss als Contributor eingetragen werden. Gleichzeitig kann jeder Contributor auch viel kaputt machen (z.B. den master Branch löschen...). In Git gibt es nur eine sehr eingeschränkte Benutzerkontrolle, die versehentliche oder böswillige Aktionen verhindern kann.
Bei sehr verteilten (typischerweise OpenSource) Projekten, insbesondere wenn diese auf einem Dienst wie GitHub gehalten werden, arbeiten weltweit Entwickler, die sich untereinander nicht kennen, ad-hoc an gemeinsamen Projekten. Hier wird daher oft mit Forks gearbeitet.
Dabei legt sich jeder Entwickler, der an einem Projekt mitarbeiten will, zunächst mal ein eigenes Repository als Kopie des ursprünglichen Projektes an. Hier kann er nach Belieben eigene Änderungen durchführen, committen, Branches anlegen und löschen, etc., ohne irgendetwas kaputt zu machen.
Hat ein Entwickler eine Änderung durchgeführt, die er gerne in das ursprüngliche Projekt einfließen lassen will, kann ein so genannter Pull-Request an den Eigentümer des ursprünglichen Projektes gestellt werden. Dieser kann sich die (zunächst vorgeschlagene) Änderung ansehen, probehalber in den aktuellen Stand des eigenen Projektes mergen, ausprobieren und ggf. übernehmen, überarbeiten, kommentieren oder zurückweisen.
Git / GitHub unterstützt das Auslösen und das Bearbeiten von Pull-Requests.

⚠️ **GitHub.com Fallback** ⚠️