Nach einem “Vorfall” in einem befreundeten Projekt wurde ich gefragt,
ob wir in unserem Buch denn auch vor “push
mit -f
” warnen.
Für einen kurzen Augenblick stieg der Puls:
Hatten wir das tatsächlich übersehen?
Ein kurzer Blick ins Inhaltsverzeichnis beruhigte mein Gewissen.
Auf Seite 79 (ganz unten) wird ordnungsgemäß vor -f
gewarnt.
Für all jene, die Seite 79 vielleicht nicht mit der vollen Aufmerksamkeit
gelesen haben, gibt’s hier ein paar Tipps, was man tun kann, falls
man sich mal “verpushed”.
push --f
: Was genau ist passiert?Wie viele andere Befehle auch hat der push
-Befehl in Git eine Option -f
bzw. --force
. Es ist nur selten eine gute Idee, diese zu nutzen.
Vorher
--O---A---F1---F2 origin/master \ M1---M2 master
Push!
$ push -f # Hoppla, das war nicht gewollt!
Nachher
--O---A---F1---F2 "Abgeschnitten!" \ M1---M2 master, origin/master
Etwas unerfreuliches ist passiert:
Alles, was in den Commits und F1
und F2
implementiert wurde, ist plötzlich verschwunden.
Auf Überraschungen dürfen sich jetzt jene Entwickler gefasst machen,
die diese Features gebaut haben. Mit etwas Pech dürfen sie beim nächsten
pull
Commits mergen, die sie schon längst abgeliefert haben.
Vollends verwirrt aber werden jene Entwickler sein,
die auf Basis von F1
oder F2
weitere andere Features entwickelt haben.
Ihnen kann es passieren, dass sie beim pull
einen Merge-Konflikt erhalten,
bei dem Änderungen aus F1
oder F2
als eigene Änderungen angezeigt werden,
obwohl sie von jemand anderem implementiert wurden.
Git wird mit Handtuch ausgeliefert.
Es ist unwahrscheinlich, dass wirklich etwas verloren gegangen ist.
Zwar sind einige Commits im Hauptrepository nicht mehr sichtbar,
wenn man git log
ausführt, aber
sie sind sehr wahrscheinlich immer noch im da und bleiben es erstmal auch. Der Garbage-Collector gewährt normalerweise eine Frist von mindestens zwei Wochen, bevor er abräumt.
Git ist dezentral. Wahrscheinlich haben auch andere Entwickler Kopien der Commits auf ihren Rechnern.
wenn das Repository richtig konfiguriert ist, führt Git Buch über alle Änderungen an Branches und Tags (genannt Reflog), so dass man Verlorenes leicht wieder finden kann.
Die Meldung nach dem push --force
zeigt mir, was ich übergebügelt habe.
$ git push -f # Hoppla, das war nicht gewollt!
Counting objects: 4, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 284 bytes, done.
Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
To /Users/stachi/tmp/blubber/kapitel26.git/
+ 2450384...24ffa63 master -> master (forced update)
Jetzt sehe also, dass master
vorher auf 2450384
gezeigt hat
und jetzt auf 24ffa63
verweist.
Das Problem ist nur, dass genau dieses Commit 2450384
in meinem lokalen
Repository nicht vorhanden ist. Mit einem fetch
(oder pull
)
kann ich es nicht holen, weil kein Branch mehr darauf zeigt.
Deshalb klone ich das ganze Repository:
$ git clone ich@woauchimmer.de:repo
$ cd repo
Falls mehrere Branches betroffen sind (siehe unten), muss man die folgenden Schritte jeden Branch einmal durchführen.
Im Beispiel ist das Commit 2450384
betroffen. Wir versuchen, die
Änderungen zusammenzuführen:
$ git checkout master
$ git merge 2450384
Falls Git dabei Already up-to-date.
meldet, hat man Glück gehabt,
und muss es bleibt nichts weiter zu tun.
Anderenfalls muss den Konflikt ganz normal lösen.
Pech hat man, wenn Git
fatal: '2450384' does not point to a commit
meldet, dann muss man
sich auf die Suche nach einem Repository begeben, in dem das Commit
noch vorhanden ist. Nach dem Merge kann man die Änderungen hochladen.
$ git push origin master
Falls die neuen Änderungen (bis 24ffa63
) verworfen werden sollen,
kann man auch den vorigen Stand wieder herstellen.
$ git reset --hard 2450384
$ git push --force origin master
Aber Achtung! --force
hat seine Tücken ;-)
Wenn ein andere Entwickler in der Zwischenzeit Änderungen
auf diesem Branch hochgeladen haben, dann goto 1
.
Man erkennt das, wenn die Push-Meldung zeigt, dass ein
anderes Commit ersetzt wurde, als das was man ersetzen wollte.
Falls ein anderer Entwickler kurz vor dem Unglück “gepulled” hat, kann man dessen Stand nutzen:
$ git push --force
Aber Vorsicht!: Das kann auch vom Regen in die Traufe führen.
Das Default-Verhalten, wenn man ein push
ohne weitere Parameter
ausführt, ist wie folgt. Git führt ein “Matching” durch,
es führt das push
für jeden lokalen Branch aus,
dem ein gleich benannter Branch im entfernten Repository gegenüber steht.
Es können also viele Branches betroffen sein.
Wer das doof findet kann zweierlei tun:
git config push.default upstream
push
beteiligen.