Best Practices mit Git

08:15 – Büro, Nürnberg

M. ist mittlerweile im Büro angekommen. Nach dem obligatorischen Plausch mit den Kollegen und nachdem er sich seine erste Tasse Kaffee geholt hat, fährt M. seinen Laptop zum zweiten Mal an diesem Tag hoch. Obwohl Git ein dezentrales Versionskontrollsystem ist, hat sich das Team von M. entschieden, ein zentrales Repository zu verwalten, auf das alle Entwickler einchecken und von dem aus der Continuous-Integration-Server (in M.’s Fall Jenkins) baut und die entsprechenden Artefakte ausliefert. Der Zugriff auf das zentrale Repository erfolgt ganz einfach über SSH. M. freut sich, dass er heute Morgen bereits so produktiv war und möchte am liebsten sofort in das zentrale Repository einchecken:

#Push commits to Remote Master 
git push origin master
! [rejected]        master -> master (non-fast-forward)

Was ist nun genau passiert?

git push origin master

bedeutet übersetzt: „Git, bitte übertrage die Commits auf meinem lokalen Branch, auf dem ich gerade bin, auf das Remote Repository mit dem Namen ‚origin‘, und dort bitte auf den Branch mit dem Namen master“. ‚origin‘ ist lediglich der Name des Remote Repositories. Das wird sogar noch klarer, wenn man sich die Datei config im Verzeichnis .git im lokalen git-Repository betrachtet. Hier steht u. a.:

[remote "origin"]
        url = ../central_git_repo/
        fetch = +refs/heads/*:refs/remotes/origin/*

Was aber bedeutet folgender Satz, den M. auf seinen misslungenen Push-Versuch bekommen hat?

! [rejected]        master -> master (non-fast-forward)

Git erlaubt grundsätzlich nur Fast Forward Pushes auf entfernte Repositories. Das Prinzip eines Fast Forward Pushes ist in Abb. 3 dargestellt.

Abb. 3: Fast Forward Pushes

Bei einem Fast Forward Push sind bereits alle Commits des Remote Repositories im lokalen Repository enthalten (blaue Kreise). Das macht die Arbeit für Git natürlich extrem einfach. Es müssen lediglich die neuen Commits (weiße Kreise) entgegengeommen und ein Pointer vom alten HEAD (der Commit, auf dem der Branch aktuell steht) auf den neuesten Commit gesetzt werden. Dieser wird dann zum neuen HEAD. Die ganze Arbeit (wie z. B. Merge-Konflikte auflösen) muss bereits vorher lokal beim Entwickler erfolgt sein. Somit ist sichergestellt, dass das zentrale Repository immer einen „sauberen“ Stand hat.

Kurz gesagt, Git hat den Push verweigert, weil noch nicht alle Commits aus dem Remote Repository im lokalen Repository vorhanden sind. M. bringt also zunächst sein lokales Repository auf den neuesten Stand:

#pull from remote 
git pull --rebase origin
Unpacking objects: 100% (9/9), done.
From ../central_git_repo
   9950f83..b3b6f11  master     -> origin/master
#check whats new 
git shortlog -s
     1  Markus
     [..]

Offensichtlich war auch M.s Kollege Markus fleißig und hat ebenfalls schon eine Textänderung umgesetzt. Da M. jetzt alle Commits aus dem Remote Repository lokal zur Verfügung hat, versucht er erneut zu pushen (Listing 2).

Listing 2
#push to remote
git push --all --tags origin
Counting objects: 34, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (10/10), done.
Writing objects: 100% (18/18), 1.33 KiB, done.
Total 18 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (18/18), done.
To ../central_git_repo/
 * [new branch]      bugfix-4711 -> bugfix-4711
 * [new branch]      stable-release -> stable-release
 * [new branch]      next -> next
Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.