Rebasing

Im Beitrag „Merging“ wurde schon eine Methode erklärt wie Änderungen eines anderen Branches übernommen werden können.

Beim „Rebasing“ geht es prinzipiell auch um das anwenden von Änderungen eines „anderen Branches“, jedoch ist dieser „andere Branch“ ein besonderer – nämlich der ursprüngliche Branch, von dem der neue Branch erstellt wurde.

Gehen wir von folgendem Stand aus:

D.h. am master Branch wurden 2 Commits getätigt, danach wurde von diesem Stand ein Feature-Branch erstellt und danach wurden 2 weitere Commits am master Branch durchgeführt.

Der Zeitpunkt, wo der Feature-Branch erstellt wurde (bzw. genauer gesagt der Commit) wird hier als „Base“ bezeichnet von dem der Branch gestartet wurde.

Wenn jedoch nach dem erstellen des Branches Änderungen an der „Base“ durchgeführt wurden (hier der master Branch) werden diese natürlich nicht am Feature-Branch mit übernommen.

Jedoch kann es leicht sein, dass der feature-Branch noch nicht fertig zu mergen auf den master Branch ist. Um jedoch trotzdem die Änderungen des master-Branches auf dem feature-Branch zu erhalten kann ein „rebase“ durchgeführt werden:

git rebase master

Aktueller ausgecheckter Branch ist der Feature-Branch. Damit führen wir im Endeffekt folgende „Verschiebung“ durch:

GIT-Workflow

Nachdem nun das prinzipielle Verständnis für GIT mit Branches, merging, rebasing und remote Repositories geschaffen wurde geht es nun um die „Kategorisierung“ von Branches.

Am Beispiel von Bitbucket können beim erstellen eines Branches über die Web-Oberfläche folgende „Typen“ an Branches ausgewählt werden:

Feature

Neue Features sollten immer in einem eigenen Feature-Branch entwickelt werden.
Wird beim erstellen automatisch mit feature/<branch-name> prefixed.

Release

Dieser Branch ist der Zwischenstand zwischen schon fertig erstellten Features, welche aber noch nicht „global“ mit anderen neuen Features getesteten und/oder dokumentierten wurden, und dem master-Branch, der als „öffentlich“ (und vielleicht auch als Produktionsumgebung) angesehen werden kann.

Release-Branches dienen hier einen gewissen Release zu verfeinern und zu testen. Währenddessen kann das Entwickler-Team weiter an neuen Features oder anderen Bugfixes arbeiten.

Wird beim erstellen automatisch mit release/<branch-name> prefixed.

Bugfix

Wird typischerweise für das fixen von Problemen von einem Release verwendet.
Wird beim erstellen automatisch mit bugfix/<branch-name> prefixed.

Hotfix

Wird für „schnelle“ Fixes von schon live gegangen Änderungen auf einem Production-Branch hergenommen.
Wird beim erstellen automatisch mit hotfix/<branch-name> prefixed.

Other

Hier können weitere Branches erstellt werden, die in keine der oben beschriebenen Branch-Typen passen.
Hier wird beim erstellen des Branches kein Präfix hinzugefügt, kann daher wie gewünscht benannt werden.

Merging

Source: https://www.atlassian.com/cs/git/tutorials/using-branches/git-merge

Merging ist das anwenden der Änderungen von einem Branch auf einen anderen. In der oben gezeigten Visualisierung ist zu sehen, dass die 2 unterschiedlichen Abzweigungen des Repositories am Ende wieder zusammenführen.

Befehl

git merge <branch-name>

D.h. der aktuelle Branch ist der „empfangene“ Branch der die Änderungen vom angegeben Branch übernehmen soll.

Am dem beschriebenen Beispiel von „Branches“ kann folgender Befehl durchgeführt werden um die Änderungen vom Branch „small-feature“ auf den master Branch anzuwenden. (Vorausgesetzt es ist der master Branch aktuell ausgecheckt)

git merge small-feature

Folgender Output wird angezeigt:

Aktualisiere 4bcd766..4d7056f
Fast-forward
 source.txt | 1 +
 1 file changed, 1 insertion(+)

Damit wird die 1 Zeile, die im small-feature Branch hinzugefügt wurde, auch am master Branch in diese Datei hinzugefügt.

Merge Conflict

Wenn möglich versucht GIT so gut wie es möglich ist die unterschiedlichen Source-Code Zustände beim mergen zu vereinigen.

Jedoch kann es vorkommen, dass GIT nicht weiß, wie der Code zusammengefasst werden soll. Dies ist dann ein sogenannter „Merge Conflict“.

Wann entsteht ein Merge Conflict?

Ein Merge Conflict tritt auf, wenn auf 2 unterschiedlichen Branches in der selben Datei in der selben Zeile etwas geändert bzw. gelöscht wurde.

Wie sieht ein Merge Conflict aus?

Bleiben wir beim Beispiel von „Branches“ nur mit folgenden zusätzlichen Anpassungen:

  • Am master-Branch wurde der Inhalt von source.txt von „master“ auf „master-old“ geändert.
  • Am small-feature-Branch wurde der Inhalt von source.txt von „master“ auf „master-new“ geändert.

Nun führen wir ein git merge small-feature am master-Branch durch und erhalten folgenden Output am Terminal:

automatischer Merge von source.txt
KONFLIKT (Inhalt): Merge-Konflikt in source.txt
Automatischer Merge fehlgeschlagen; beheben Sie die Konflikte und committen Sie dann das Ergebnis.

Der Inhalt des source.txt sieht nun wie folgt aus:

<<<<<<< HEAD
master-old
=======
master-new
small-feature
>>>>>>> small-feature

Wie zu sehen ist trennt GIT die Stelle mit folgenden Zeichen ab:

  • <<<<<<<
    • Kennzeichnet den Start des Merge-Conflicts
  • =======
    • Kennzeichnet die Unterschiede zwischen den 2 Branches
  • >>>>>>>
    • Kennzeichnet das Ende des Merge-Conflicts

Wie löst man einen merge conflict auf?

Im Prinzip muss nun händisch der Source-Code „aufgeräumt“ werden und jegliche von GIT automatisch hinzugefügten Elemente wieder entfernt werden.

Wir wollen hier die Änderungen vom master-Branch beibehalten aber die „neue“ Zeile vom small-feature Branch auch behalten, d.h. der neue Inhalt der source.txt sieht wie folgt aus:

master-old
small-feature

Bei betrachten des git status sieht man nun folgenden Output:

Auf Branch master
Sie haben nicht zusammengeführte Pfade.
 (beheben Sie die Konflikte und führen Sie "git commit" aus)
  (benutzen Sie "git merge --abort", um den Merge abzubrechen)

Nicht zusammengeführte Pfade:
  (benutzen Sie "git add/rm <Datei>...", um die Auflösung zu markieren)

	von beiden geändert:    source.txt

keine Änderungen zum Commit vorgemerkt (benutzen Sie "git add" und/oder "git commit -a")

D.h. da wir nun die Datei wieder geändert haben müssen wir die „gemergte“ Datei neu zum Repository hinzufügen und commit.

git add source.txt
git commit -m "merged"

D.h. final sieht unsere git log wie folgt aus:

commit f9997203d5f7887ca30b4e69752291706d81755d (HEAD -> master)
Merge: dc50e32 2d79e70
Author: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Date:   Sat Jun 22 21:09:31 2019 +0200

    merge

commit dc50e3242da6cc17d822e3adf04ae4ca9edd62b1
Author: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Date:   Sat Jun 22 20:53:35 2019 +0200

    changed master to master-old

commit 2d79e708b4f7aa2dd4898a990bfd464b8a6080f4 (small-feature)
Author: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Date:   Sat Jun 22 20:53:14 2019 +0200

    changes master to master-new

commit b71b422594ecaf924909cd7477ee73b45a2c9685
Author: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Date:   Sat Jun 22 20:52:32 2019 +0200

    added small-feature

commit 3958f0b2f8f3f0473a6ef6194df4077ac4e45dc5
Author: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Date:   Sat Jun 22 20:52:21 2019 +0200

    master file created

Branches

Ein Branch ist eine parallele Entwicklungslinie vom Source-Code, der von einem gewissen Zeitpunkt an erstellt wurde und weitergeführt wurde. Typische Darstellung für solche Branches ist z.b. folgende Illustration.

Source: https://blog.seibert-media.net/blog/2015/07/31/git-mit-branches-arbeiten-git-branch/

Prinzipiell sieht man immer einen „master“ Branch. Dieser ist der default Branch, der beim erstellen eines neuen Repositories als erstes erstellt wird.

Dieser muss aber nicht immer vorhanden sein! Theoretisch kann vor dem ersten Commit der master Branch auf einen anderen Branch gewechselt werden! Dies ist aber eher unüblich, da der master Branch sehr oft als „Basis“ des Repositories hergenommen wird.

Wichtigste Befehle

Anzeige von vorhandenen Branches

git branch

Ebenso zeigt dieser Befehl den aktuell aktiven Branch an. Dies funktioniert aber nur, wenn zumindest eine Commit in diesem Branch vorhanden ist! Ein leeres Repository ohne commits hat noch keine Branches!

Erstellung eines Branches

git checkout -b <branch-name>

Als Basis des Branches wir der aktuell aktive Branch hergenommen. Wenn sonst keine anderen Branches vorhanden sind ist es der „master“ Branch.

Unterschiede zwischen 2 Branches

git diff <branch1> <branch2>

Beispiel

Gehen wir von einem neuem, leeren Ordner aus. Als erstes muss das GIT-Repository initialisiert werden.

git init

Danach erstellen wir eine Text-Datei und commiten diese Datei in das Repository und damit in den „master“ Branch.

echo "master" > source.txt
git add source.txt
git commit -m "master file created"

Dadurch haben wir einerseits unseren ersten commit getätigt, welcher im git log zu sehen ist, andererseits ist damit der Start des „master“ Branches festgelegt.

Nun können wir einen weiteren Branch erstellen und unsere schon vorhandene source.txt erweitern.

git checkout -b small-feature 
echo "small-feature" >> source.txt
git add source.txt
git commit -m "added small-feature"

Damit sieht unser git log wie folgt aus:

commit 8a77da4bbaf8a3abdc1634e702fbacefac8b4345 (HEAD -> small-feature)
Author: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Date:   Sat Jun 22 13:20:33 2019 +0200

    added small-feature

commit 8a0d423e08b93effc490cc343e5f0e08edebcc7a (master)
Author: Kevin Pfeifer <kevin.pfeifer@sunlime.at>
Date:   Sat Jun 22 13:19:43 2019 +0200

    master file created
(END)

Und der git diff zwischen dem master und dem small-feature Branch sieht wie folgt aus:

diff --git a/source.txt b/source.txt
index 1f7391f..3921d35 100644
--- a/source.txt
+++ b/source.txt
@@ -1 +1,2 @@
 master
+small-feature

Dies ist einmal die Basis was Branches sind, wie diese erstellt werden und wie man vergleiche zwischen Branches herstellen kann.

Natürlich ist die hier gezeigt Methode über das Terminal eine nicht ganz „ansprechende“ Variante. Daher haben diverse Code-Editoren zu mindest GIT-Plugins, wenn nicht sogar schon GIT integrierte Funktionalität, welche das verwalten von Branches deutlich vereinfacht. Diese sind natürlich pro Code-Editor immer an unterschiedlichen Stellen zu finden, die Basis bleibt aber immer die gleiche.

Was ist ein Repository?

Als „Software-Repository“ versteht man ein „Projektarchiv“, in dem Quellcode für eine gewünschte Software entwickelt und versioniert wird.

Für die Versionierung gibt es mehrere Software-Implementationen wie z.B.

  • Apache Subversion (SVN)
  • Mercurial
  • GIT

In allen weiterführenden Texten und Beiträgen wird von „GIT“ ausgegangen, da ich primär nur mit dieser Versionsverwaltung arbeite.

Vorteile

  • Mehrere Entwickler können gleichzeitig an einer Software bzw. an einem Bestandteil der Software arbeiten.
  • Es ist leicht möglich auf einen älteren Stand der Software zurück zu springen ohne den aktuellen Stand der entwickelten Software zu verlieren.
  • Es ist leicht möglich gewisse „Releases“ zu markieren um z.B. ein Problem von einer speziellen ausgelieferten Version besser debuggen zu können.
  • Über diverse online Versionsverwaltungs-Platformen wie z.b. Github oder Bitbucket lassen sich Repositories einfach auch über den Browser oder eine App managen. Ebenso können über diese Platformen Änderungen im Repository (Commits) diskutiert oder Probleme (Issues) eingemeldet werden.

Nachteile

  • Für Entwickler, die den „GIT-Workflow“ nicht kennen, ist es definitiv ein Mehraufwand bzw. eine Anpassung des Entwicklungsprozesses.

Pull & Push – das remote Repository

GIT bietet nicht nur Funktionalitäten für ein lokales Repository an, sondern ebenso für ein „remote Repository“.

Wie der Name schon verrät handelt es sich im Prinzip um das gleiche Repository wie es lokal vorhanden ist, nur ist es auf einem anderen (über ein Netzwerk erreichbaren) Rechner vorhanden.

Hier gibt es extra Befehle um einerseits den aktuellen Stand vom lokalen Repository in das remote Repository zu „schieben“ (push) und natürlich auch in die andere Richtung den aktuellen Stand vom remote Repository in das lokale Repository zu „ziehen“ (pull).

Hier eine schöne Illustration in welchen unterschiedlichen Zuständen sich ein Repository befinden kann und durch welche Befehle diese hervorgerufen werden.

Wichtigsten Befehle für ein lokales Repository

Init

Befehl: git init

Erstellt im aktuellen Verzeichnis ein lokales GIT-Repository.

Status

Befehl: git status

Vergleicht die im Repository schon „vorhandenen“ (=commiteten) Dateien und Ordner mit den aktuell im Dateisystem vorhandenen Dateien und Ordner.

Unterschiede werden hier wie folgt dargestellt:

  • unversionierte Dateien
    • Noch nicht im Repository vorhandenen Dateien
  • Änderungen, die nicht zum Commit vorgemerkt sind
    • Hier gibt es 2 Kategorien:
    • „geändert“: Schon vorhandene aber geänderte Dateien im Repository
    • „gelöscht“: Im Repository zwar noch vorhanden, aber nicht mehr im Dateisystem

Add

Befehl: git add <filename> <foldername> <other-things>

Fügt Dateien und/oder Ordner zu einem „Staging“ Status hinzu.

Dieser „Staging“ Status kann dann über ein „git commit“ in das Repository hinzugefügt werden.

Commit

Befehl: git commit -m „<message>“

Überträgt alle zum „Staging“ hinzugefügten Dateien und Ordner über einen neuen „Commit“ in das Repository.

Reset

Befehl: git reset <file> oder git reset <commit>

Entfernt Dateien aus dem „Staging“ Status.
Jedoch kann git reset <commit> auch dafür verwendet werden, um das Repository auf einen alten Commit wiederherzustellen.

Log

Befehl: git log

Zeigt die letzten durchgeführten Commits im Repository an.

Diff

Befehl: git diff

Zeigt die aktuellen Unterschiede zwischen den Änderungen im Workspace und dem Staging an. Kann auch mit git diff <file> ausgeführt werden um nur die Änderungen an einer Datei zu sehen.

Checkout

Befehl-Varianten

  • git checkout <commit-hash>
  • git checkout <filename> <foldername> <other-things>
  • git checkout <branch>