Mit Git Änderungen verstecken

Von in javascript, jquery, linux, netzthetik, php, vim

Seit vier Stun­den schon arbei­tet Susi Sorg­los kon­zen­triert an ihrem Soft­ware­pro­jekt, als plötz­lich das Tele­fon läu­tet. Der auf­ge­reg­te Kun­de am ande­ren Ende der Lei­tung berich­tet über einen Pro­gramm­feh­ler, der sei­nen Betrieb zu einem Pro­duk­ti­ons­still­stand zwingt. Feu­er ist am Dach und Susi muss sich dem Bug sofort wid­men, sonst droht Unge­mach. Doch sie hat ein ande­res Pro­blem, das es zuerst zu lösen gilt: Der momen­ta­ne Arbeits­fort­schritt der letz­ten Stun­den ver­hin­dert ein Hot­fi­xing, denn durch die zwi­schen­zeit­li­che Wei­ter­ent­wick­lung befin­det sich die Soft­ware in einem insta­bi­len Zustand, der nicht der pro­duk­ti­ven Umge­bung über­ge­ben wer­den darf. Was also tun, fragt sich Susi Sorg­los. Die Arbeit der letz­ten Stun­den ver­wer­fen? Glück­li­cher­wei­se ist Susi ein Pro­fi und weiß mit ihrer Ver­sio­nie­rungs­soft­ware umzu­ge­hen. Sie stasht ihre Ände­run­gen ein­fach bei­sei­te, behebt den Feh­ler in der Soft­ware, beru­higt den Kun­den und mer­get ihre letz­ten Ände­run­gen schließ­lich mit dem Hot­fix. Was Susi genau getan hat, möch­te ich in die­sem Arti­kel erklä­ren.

Git bie­tet mit sei­ner Ver­si­ons­ver­wal­tung über soge­nann­te Bran­ches bereits ein mäch­ti­ges Werk­zeug. Wie obi­ges Bei­spiel zeigt, rei­chen die­se “Ver­zwei­gun­gen in der Ent­wick­lung” mit­un­ter aber nicht aus, um einen kom­ple­xen Pro­jekt­ab­lauf abzu­bil­den. Was macht man nun tat­säch­lich, wenn das Pro­gramm mit­ten in der Ent­wick­lung steckt und man von einer Minu­te auf die ande­re am ursprüng­li­chen Code Ände­run­gen vor­neh­men muss? Man sieht sich mit dem Pro­blem kon­fron­tiert, die geän­der­ten Datei­en nicht ein­fach dem Branch über­ge­ben zu kön­nen, weil man damit unwei­ger­lich unge­tes­te­ten Code ver­öf­fent­licht. Wür­de zum Bei­spiel Susi in einem Team arbei­ten bestün­de Gefahr, dass sich ihre Arbeits­kol­le­gen unbrauch­ba­ren Code vom Repo­si­to­ry pul­len. Betrifft der zu behan­deln­de Bug Code aus bereits ver­än­der­ten Datei­en, kann Susi auch nicht ein­fach in einen ande­ren Branch aus­che­cken, weil Git das nur zulässt, wenn die Ände­run­gen über­schrie­ben wür­den. In einer sol­chen Situa­ti­on wünscht man sich, die seit dem letz­ten Com­mit durch­ge­führ­ten Ände­run­gen ein­fach bei­sei­te legen und am sau­be­ren HEAD den Fix vor­neh­men zu kön­nen.

Und genau das bie­tet Git mit dem Befehl

git stash

Dabei wer­den die Ände­run­gen seit dem letz­ten Com­mit abge­spei­chert und der “sau­be­re” Head des aktu­el­len Branch zur Ver­fü­gung gestellt. Ich möch­te dies an einem Bei­spiel zei­gen. Für unse­re Demons­tra­ti­on gehen wir von einem berei­nig­ten HEAD im fik­ti­ven Branch „dev” aus.

test@test:/project$ git status
# On branch dev
nothing to commit (working directory clean)

Dann ändern wir ein File. Das erkennt Git natür­lich sofort:

test@test:/project$ git status
# On branch dev
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   test.txt
#
no changes added to commit (use "git add" and/or "git commit -a")

Git mel­det wie erwar­tet die Ände­rung an der Datei test.txt. Ange­nom­men wir sähen uns jetzt mit der Anfor­de­rung eines Hot­fi­xes kon­fron­tiert. Ver­su­chen wir ein­mal, auf den Master-​Branch aus­zu­che­cken:

test@test:/project$ git checkout master
error: Your local changes to the following files would
be overwritten by checkout:
	test.txt
Please, commit your changes or stash them before you can switch branches.
Aborting

Kei­ne Chan­ce also dem Pro­blem durch Wech­seln des Bran­ches zu ent­ge­hen. Git droht mit dem Ver­wer­fen der Ände­run­gen, was wir auf kei­nen Fall befür­wor­ten. Wir bekom­men jedoch einen äußerst inter­es­san­ten Fin­ger­zeig für eine bes­se­re Mög­lich­keit:

Please, commit your changes or stash them before you can switch branches.

Com­mit­ten wol­len wir wie bespro­chen nicht, aber ein stash wäre eine Mög­lich­keit:

test@test:/project$ git stash
Saved working directory and index state WIP on dev:
d9ba9c4 Testfile hinzugefüft
HEAD is now at d9ba9c4 Testfile hinzugefüft

Git spei­chert die Ände­run­gen ver­steckt ab (“to stash”) und bie­tet einen sau­be­ren HEAD an (der lus­ti­ge Typo „hin­zu­ge­füft” ist ein Fea­ture und WIP bedeu­tet Work In Pro­gress). Dem­zu­fol­ge dürf­te test.txt jetzt nicht mehr als modi­fi­ziert ange­zeigt wer­den:

test@test:/project$ git status
# On branch dev
nothing to commit (working directory clean)

Tat­säch­lich, test.txt scheint nicht mehr auf, ein Check­out wird nicht mehr ver­hin­dert und ein Kon­troll­blick in das ver­än­der­te File bestä­tigt, dass die Ände­run­gen nicht mehr vor­han­den sind. Jetzt kön­nen wir mit dem Pro­jekt all das anstel­len, was auch sonst mit einem clea­nen HEAD mög­lich wäre. Gehen wir davon aus, dass wir nun schnell den Feh­ler fixen und ihn com­mit­ten, damit wür­de „git sta­tus” wie­der anzei­gen, dass unser Branch „dev” clean ist. Es feh­len nur noch unse­re Ände­run­gen aus dem test.txt-File. Beach­tens­wert ist hier, dass Git einen Mer­ge vor­nimmt (!). Sehen wir uns dabei den ungüns­ti­gen Fall zuerst an:

test@test:/project$ git stash pop

Mit­tels pop ver­su­chen wir also, den Spei­cher in den Index zu schrei­ben und, wenn nötig, Inhal­te zu mer­gen. Git mel­det jedoch…

Auto-merging test.txt
CONFLICT (content): Merge conflict in test.txt

… etwas, das man bei der Arbeit mit Git am aller­we­nigs­ten lesen möch­te. Ein Mer­ge ist fehl­ge­schla­gen, weil nicht klar ersicht­lich war, wel­che Tei­le des Codes erhal­ten und wel­che geän­dert wer­den sol­len. Für den Pro­gram­mie­rer bleibt jetzt nichts ande­res übrig, als müh­sam Zei­le für Zei­le per Hand zusam­men­zu­fü­gen (oder sich ent­spre­chen­den Tools zu bedie­nen).
Konn­te der Mer­ge erfolg­reich abge­schlos­sen wer­den, mel­det Git…

test@test:/project$ git stash pop
# On branch dev
# Changes not staged for commit:
#   (use "git add <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
#	modified:   test.txt
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (d4b9c3cd3e5862097d92d673318d7edf1baeb35a)

Der Hot­fix wur­de mit den mit­tels stash “weg­ge­spei­cher­ten” Ände­rung ver­mischt. Damit zeigt uns Git wie­der ein ver­än­der­tes File an, das auf einen Com­mit war­tet. Bemer­kens­wert ist hier­bei die letz­te Zei­le “Drop­ped refs/stash@{0}”. Der Stash wur­de beim letz­ten Befehl tat­säch­lich gelöscht und steht nicht mehr zur Ver­fü­gung (das gilt aller­dings nur für den Fall, dass der Mer­ge erfolg­reich war). Wer den Stash noch wei­ter­ver­wen­den möch­te, soll­te sich den Befehl

git stash apply

sowie die Mög­lich­keit, meh­re­re Stas­hes über­ein­an­der­zu­sta­peln oder den Stash manu­ell zu löschen in den Manu­als anse­hen.

Ich möch­te schließ­lich noch eine wei­te­re Ver­wen­dung für den Stash erwäh­nen. Susi Sorg­los arbei­tet näm­lich in einem Ent­wick­ler­team. Und kurz, nach­dem sie pro­fes­sio­nell das Pro­blem mit dem Hot­fix umschifft hat, mel­det einer ihrer Kol­le­gen end­lich die heiß ersehn­te Fer­tig­stel­lung einer Funk­ti­on, die auch Susi drin­gend für die wei­te­re Ent­wick­lung benö­tigt. Die fer­ti­ge Funk­ti­on befin­det sich auch schon im Remo­te Repo­si­to­ry und muss nur noch gepullt wer­den, Susi weiß aber, dass sie unmög­lich pul­len kann, wenn sich bei ihr ver­än­der­te Daten im Pro­jekt befin­den. Auch hier wäre ein Com­mit ihrer unvoll­stän­di­gen Arbeit äußerst pro­ble­ma­tisch. Wie­der stasht Susi ihre Arbeit ein­fach bei­sei­te, pullt die Ände­run­gen vom Ser­ver und mer­get ihren WIP in den aktu­el­len Branch.

Share on Google+Share on RedditTweet about this on TwitterShare on LinkedInShare on FacebookShare on XingEmail this to someone