Silv
2019-05-09 02:03

Krótka historia małego błędu

(lub: podstawy rozwiązywania problemów w Gicie)

Więc było tak: chciałem zrobić commit w Gicie za pomocą terminala w Visual Studio Code.

Czemu za pomocą terminala, a nie panelu "Source control", który jest w VS Code? Wolę tak, by się nie odzwyczaić od poleceń Gita.

Commit miał być zmian w jednym pliku. Plik miałem już przygotowany – wszystko, co chciałem, zostało w nim zmienione.

Jak to się zepsuło

Otworzyłem więc terminal (CTRL+`, CTRL+backtick). Jako że zmian było dużo, otworzyłem również wspomniany plik w trybie podglądu zmian, żeby zmiana po zmianie przepisać do commit message wszystko, co zostało zmienione (staram się tak robić – pisać też treść, zamiast samych tytułów w commit messages).

W panelu "Source control" dodałem plik do sekcji "Staged changes" (jakoś tak z przyzwyczajenia nie użyłem tym razem CLI Gita). W terminalu wykonałem polecenie git commit i otworzył się mój domyślny terminalowy edytor tekstu – vim. Wpisałem tytuł i treść commit message.

Jednak podczas przeglądania zmian okazało się, że nie wszystko zostało zmienione. Toteż – zapomniawszy, że mam niezakończone polecenie git commit – otworzyłem tryb edycji pliku i zmieniłem potrzebne fragmenty.

Wróciwszy do terminala, zapisałem i zamknąłem plik (w vimie: :wq), czym zakończyłem wykonywanie polecenia git commit. Wynikiem była informacja Gita o poprawnym commicie. Spojrzałem następnie na panel "Source control" (zazwyczaj tak robię dla pewności po zacommitowaniu czegoś). Hm... jakby... ten plik nadal był w sekcji "Staged changes"?

Zaciekawiony (no...) tą sytuacją, spróbowałem dojść, co poszło nie tak. Spojrzałem na plik w widoku zmian... tak, pozostały w nim te zmiany, które zrobiłem podczas działania polecenia git commit. Jak teraz dodać je do utworzonego już commita?

Na wszelki wypadek obejrzałem wynik polecenia git log. Wszystko wyglądało w porządku, tytuł i treść commit message były takie, jakie zacommitowałem. Jeśli ktoś potrzebuje, zawsze też można zobaczyć treść ostatniego, albo i wcześniejszego commita, ale uznałem, że teraz nie będzie to miało wartości.

Co tu robić? Może usunę commit, wracając tym samym zmiany do "staging area", i jeszcze raz zacommituję, ale tym razem całość. Commit jest lokalny, więc trudności być nie powinno (w internecie można znaleźć informację, że nie jest dobrze zmieniać historię Gita już opublikowaną na serwerze).

Jak to można naprawić

Jak to zrobić? Zacząłem od wpisania w wyszukiwarce Bing git revert commit. Naprowadziło mnie to na ten wątek na Stack Overflow, w którym w zaakceptowanej odpowiedzi wspomniane były polecenia git reset oraz git reset --soft (pierwszy raz chyba widziałem tyle polubień pytania i tyle polubień odpowiedzi na Stack Overflow – jedno i druga około 20 tysięcy; @cerrato, u Ciebie wciąż mniej...).

Pomyślałem, że niekoniecznie powinienem sugerować się jedną informacją (Git jest jednak dość złożonym narzędziem), więc postanowiłem szukać dalej. Wpisałem w wyszukiwarce Bing git add more changes to previous commit (jak zasugerowała mnie zaakceptowana odpowiedź ze wspomnianego wątku). Naprowadziło mnie to na kolejny wątek na Stack Overflow. Nie opisywał on co prawda konkretnie mojego przypadku (to ten sam plik, a nie kolejny), ale zaciekawiło mnie: raz prostota wspomnianego w nim polecenia git commit --amend, a dwa – brak potrzeby "usuwania" czy "odwracania" czegokolwiek. Postanowiłem poszukać w tę stronę.

Wpisałem w wyszukiwarce Bing git amend. Naprowadziło mnie to na ten wpis na blogu Attlasiana. Opisuje on kilka przypadków użycia różnych poleceń Gita w przypadku chęci zmiany historii commitów. Zainteresował mnie drugi opisany przypadek ("Changing committed files"), ponieważ był niemal identyczny z moim.

Uznałem, że można spróbować wykonać wspomniane w artykule polecenie git commit --amend --no-edit. Flaga --no-edit pozwala nie musieć edytować po raz kolejny commit message (nie potrzebowałem tego, moja była napisana zgodnie ze wszystkimi zmianami).

Choć z zasady wierzę komputerowi, to również z zasady nie dowierzam swojej znajomości oprogramowania – toteż zrobiłem kopię zapasową wszystkich plików, które były pod kontrolą Gita w tym projekcie (w innym katalogu). Dlaczego? W przypadku błędu, historię Gita można zmienić, ale pliki na Linuksie mogą NIE DAĆ SIĘ odzyskać (zależnie od ustawień). Ostatecznie – historia Gita była (jest) w tym projekcie mniej ważna niż zmiany w plikach.

Na wszelki też wypadek sprawdziłem wspomniane polecenie w oficjalnej dokumentacji Gita. Wyglądało na to, że to jest właśnie to, czego się spodziewam.

Jak to się skończyło

Dodałem w VS Code wspomniany plik z niezacommitowanymi zmianami do sekcji "Staged changes", wpisałem git commit --amend --no-edit w terminalu i nacisnąłem ENTER. Nic nie wybuchło, a pojawiła się wiadomość:

Date: [tu data wykonania polecenia]
1 file changed [tu liczba dodanych i usuniętych linii]

Więc Git błędu nie zgłosił, ale czy się na pewno wszystko udało? Sprawdziłem, jakie zmiany zostały zacommitowane (na szczęście był tylko jeden plik). Wydawało się, że wszystko jest na swoim miejscu (zmian było dużo, więc wszystkich nie sprawdzałem).

Dziś pracę z Gitem zaliczam do udanych (nie zawsze tak jest).


UPDATE: Będąc w trakcie procesu naprawy, zamierzałem też skopiować commit message (ponieważ nie miałem ochoty jej sobie przypominać w przypadku błędu). Niestety, jakoś mi to umknęło. Teraz na szczęście wszystko poszło dobrze, ale następnym razem trzeba będzie to zrobić...

Afish

Wtedy nie trzeba toteż zrobiłem kopię zapasową wszystkich plików, które były pod kontrolą Gita

Silv

@Afish: poczytałem o tym, masz rację, że może być przydatne. Jednak żebyś mnie zrozumiał – zrobiłem ręcznie kopię zapasową z dwóch powodów: (1) staram się to przyjąć jako zasadę przy każdej czynności dotyczącej plików, którą robię pierwszy raz; (2) Git jest dla mnie (na razie?) zbyt złożonym narzędziem, bym mógł ufać, że dana czynność, która jest wykonywana automatycznie, będzie wykonana tak, jak tego oczekuję.

Afish

Spoko, nic Ci nie zarzucam ani Cię nie wyśmiewam, sam dawniej robiłem kopie zapasowe plików gita. Ale od kiedy poznałem reflog, to już praktycznie o nic się nie boję, więc po prostu zachęcam Cię do zapoznania się z tą komendą, bo ona naprawdę ratuje życie.

Michał Sikora

Nic tak nie cieszy, jak skopanie czegoś w repozytorium i odkrycie reflog.

superdurszlak

--amend to ja czasem wręcz nadużywam, za często o czymś zapominam i przypominam sobie dopiero po zrobieniu commita :D

@Afish: git reflog to wspaniała sprawa. Przyjaciel każdego, kto zrobił o jeden git branch -D za dużo

cerrato

tyle polubień pytania i tyle polubień odpowiedzi[...] około 20 tysięcy; @cerrato, u Ciebie wciąż mniej... - ze spokojem, na razie pracuję nad dominacją na 4P, jak tutaj wszystko będzie zaorane to się przerzucę na SO ;)

Azarien

Ręcznej kopii repo raczej nie robię, ale zwykle mam kilka repozytoriów lokalnych (powiązanych między sobą remote'ami typu file://). W drugim repo robię fetch z pierwszego i wiem że mam wszystko co jest zakomitowane w pierwszym. A po co mi kilka repo? Praca równoległa na wielu branczach.

Silv

@Afish: w porządku, na razie zostanę przy swoim. ;) @cerrato: miałem na myśli tyle polubień tutaj. :D @Azarien: ja bym się zaraz pogubił.

Azarien

ale co tu się gubić.. załóżmy że projekt nazywa się foobar. poszczególne repozytoria są w katalogach foobar, foobar2, ... foobar6. każde ma remote'a origin, oraz remote'y nazywające się np. foobar4, foobar5. lista istniejących remotów nie jest kompletna (w sensie że kiedy każde repo z każdym jest dwukierunkowo połączone) bo dodaję je po prostu w miarę potrzeby. alternatywa (jedno repo i ciągłe checkout na inną branczę) nie wchodzi w grę bo trwałoby to wieki.

Silv

@Azarien: może, jakbym miał więcej niż 1 czy 2 gałęzie... Jednak wydaje mi się, że bym się pogubił.

Azarien

To jest duży projekt nad którym pracuje kilkudziesięciu ludzi. Nie mówię że powinieneś tak robić. Tylko wyjaśniam, że kopię repo można mieć dodaną jako remote.

Silv

@Azarien: no wiesz, tak czy siak, ja o tym wcześniej nie pomyślałem, więc plus dla Ciebie za pomysł. :)