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:
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 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)
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
merged
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
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.
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.
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.
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.
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.
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.
Hier ist aber wichtig anzumerken, dass sowohl git push als auch git pull nur für den aktuell aktiven Branch wirksam ist, nicht für alle lokal vorhandenen Branches!
D.h. man sieht vielleicht beim git pull, dass sich etwas in anderen Branches geändert hat, diese Änderungen werden aber nicht in die anderen Branches automatisch gemerged sondern nur über git fetch heruntgeladen.
Ein git pull führt also im Hintergrund nur ein git fetch gefolgt von einem git merge aus.
Das git fetch verursacht, dass die „Remote“ Repositories mit den Local Repositories synchronisiert werden aber hier werden noch keinen „wirklichen“ Datei Änderungen durchgeführt.
Erst mit dem git merge werden die nun neuen Änderungen aus dem Local Repository in die Dateien übernommen.
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.