Rebase
Das obenstehende Szenario ist kein seltenes. Während man selbst an einem Feature arbeitet, hat ein Kollege an anderer Stelle seine eigenen Features erstellt. Und gelegentlich kommt es dabei zu Kollisionen.
Mit einem "Rebase" können Änderungen einer Branch in eine andere eingespielt werden. Aber im Gegenteil zum Merge, der versucht die Änderungen der zu mergenden Branch in die aktuelle einzupflegen, wird beim Rebase versucht, die in der aktuell aktiven Branch gemachten Änderungen auf die in der Rebase-Branch geschehenen Änderungen anzuwenden. Deshalb wird, so wie häufig nach master gemerged wird, auf master rerebased. Und auch dabei kann es zu manueller Merge-Konfliktbeseitigung kommen, wenn Git diese nicht automatisch erledigen kann.
Wir beginnen unser Rebase, in dem wir den aktuellen Merge abbrechen:
$ git merge --abort
Anschließend wechseln wir in unsere Entwicklungsbranch und bereinigen dort die Fehler.
$ git checkout feature/personalized-greeting$ git rebase masterFirst, rewinding head to replay your work on top of it...Applying: Add function that asks for a name via user inputUsing index info to reconstruct a base tree...M Main.javaFalling back to patching base and 3-way merge...Auto-merging Main.javaApplying: Use new function in greetingUsing index info to reconstruct a base tree...M Main.javaFalling back to patching base and 3-way merge...Auto-merging Main.javaCONFLICT (content): Merge conflict in Main.javaerror: Failed to merge in the changes.Patch failed at 0002 Use new function in greetinghint: Use 'git am --show-current-patch' to see the failed patchResolve all conflicts manually, mark them as resolved with"git add/rm <conflicted_files>", then run "git rebase --continue".You can instead skip this commit: run "git rebase --skip".To abort and get back to the state before "git rebase", run "git rebase --abort".
Auch ein Rebase ließe sich (via Git uns auch hilfreich mitteilt) via --abort abbrechen. In diesem Fall wollen wir das aber nicht, sondern überschreiben (da seine und unsere Änderungen sich direkt widersprechen) einfach die Änderungen des Kollegen.
Vorher:
<<<<<<< HEADSystem.out.println("Hello, Humanity!");=======String name = askName();System.out.println(String.format("Hello, %s!", name));>>>>>>> Use new function in greeting
Nachher:
String name = askName();System.out.println(String.format("Hello, %s!", name));
Nun die Datei wie gewohnt in ihrem jetzigen Zustand stagen und committen:
$ git add Main.java
Laut Git gibt es nun keine Probleme mehr. Wir lassen das Rebasing fortsetzen:
$ git statusrebase in progress; onto e1b1f59You are currently rebasing branch 'feature/personalized-greeting' on 'e1b1f59'.(all conflicts fixed: run "git rebase --continue")Changes to be committed:(use "git restore --staged <file>..." to unstage)modified: Main.java$ git rebase --continue
Bei genauerem Nachdenken ließe sich die Idee des Kollegen aber doch als Standard-Wert verwenden. Das passt zwar thematisch zu unserem Feature, ist aber nicht Teil des Auftrages, an welchem wir gerade arbeiten. Dennoch können wir uns die Zeit nehmen, das eben zu implementieren:
Die neue main-Methode.:
public static void main(String[] args) {String name = askName();if ("".equals(name)) {name = "Human";}System.out.println(String.format("Hello, %s!", name));}
Diese Änderung soll nun aber nicht in diese Branch committed werden. Wir könnten die Datei einfach herum liegen lassen (ohne Staging wird sie ja nicht in Commits eingefügt), aber es gibt einen besseren Weg. Git lässt uns Änderungen in einem so genannten "Stash" für später aufbewahren.
Ein Stash lässt sich als eine Art temporärer, außerhalb der History befindlicher Commit verstehen. Es können beliebig viele solche Stashes erstellt, und auch mit Kommentaren versehen werden (via git stash push -m 'Message'). In unserem Fall reicht uns aber der autogenerierte Name, um den Stash wieder zu finden:
$ git statusgit statusOn branch feature/personalized-greetingChanges not staged for commit:(use "git add <file>..." to update what will be committed)(use "git restore <file>..." to discard changes in working directory)modified: Main.javano changes added to commit (use "git add" and/or "git commit -a")$ git stashSaved working directory and index state WIP on rebase-demonstration: fc29161 Use new function in greeting$ git statusOn branch feature/personalized-greetingnothing to commit, working tree clean$ git stash liststash@{0}: WIP on rebase-demonstration: fc29161 Use new function in greeting
Der Teil stash@{0} ist eine Durchnummerierung aller Stashes, den wir zum wiederherstellen des Stashes verwenden können. Der zuletzt erstellte Stash ist @{0}, der davor @{1} usw.
Nun, da wir unser Rebase und anschließende Aufräumarbeiten abgeschlossen haben, können wir konfliktfrei nach master mergen. Da damit das Feature auch abgeschlossen ist, kann die Branch im Anschluss entfernt werden. Ein push stellt die Änderungen dann anderen Entwicklern zur Verfügung.
$ git checkout masterSwitched to branch 'master'Your branch is up to date with 'origin/master'.$ git merge --no-ff feature/personalized-greetingMerge made by the 'recursive' strategy.Main.java | 14 +++++++++++++-1 file changed, 13 insertions(+), 1 deletion(-)$ git branch -d feature/personalized-greetingDeleted branch feature/personalized-greeting (was 7eafc37).$ git pushEnumerating objects: 9, done.Counting objects: 100% (9/9), done.Delta compression using up to 8 threadsCompressing objects: 100% (7/7), done.Writing objects: 100% (7/7), 1015 bytes | 1015.00 KiB/s, done.Total 7 (delta 3), reused 0 (delta 0)To git.honico.com:personal/sone/git-schulung.gite1b1f59..870ed70 master -> master
Theoretisch kann natürlich das push erneut fehlschlagen, da während unserer Konfliktbereinigung weitere Änderungen auf den Server gepusht wurden. In diesem Fall schlägt unser push fehl, und wir wiederholen den Bereinigungsvorgang.
Auch diese Änderungen werden nun deployed.
$ git checkout productionSwitched to branch 'production'Your branch is up to date with 'origin/production'.$ git pullAlready up to date.$ git merge --no-ff masterMerge made by the 'recursive' strategy.Main.java | 14 +++++++++++++-1 file changed, 13 insertions(+), 1 deletion(-)$ git tag -a prd/2019-11-29-personalized-greeting$ git push --tagsEnumerating objects: 2, done.Counting objects: 100% (2/2), done.Delta compression using up to 8 threadsCompressing objects: 100% (2/2), done.Writing objects: 100% (2/2), 406 bytes | 406.00 KiB/s, done.Total 2 (delta 0), reused 0 (delta 0)To git.honico.com:personal/sone/git-schulung.git* [new tag] prd/2019-11-29-personalized-greeting -> prd/2019-11-29-personalized-greeting$ git checkout masterSwitched to branch 'master'Your branch is up to date with 'origin/master'.
Auf dem Deployment-Repo könnte nun nach einem pull dieser Tag via checkout aktiviert werden.