Git – zaawansowany diff, status & log

Trudno sobie wyobrazić średni czy duży projekt, którym dało by się zarządzać bez pomocy systemu kontroli wersji. Z różnych przyczyn (GitHub!), nieoficjalnym standardem w środowisku programistów Rails stał się Git.

Git jest potężnym narzędziem. Potężnym w podobny sposób jak potężny jest Emacs albo VIM. Git tak jak Emacs czy VIM potrafi przysporzyć kłopotu z najprostszymi czynnościami. Wiele osób korzysta z Gita nie rozumiejąc jego filozofii i znając tylko podstawowe polecenia, tak jak wiele osób korzysta z VIMa używając tylko <ESC>:wq oraz i. Komendy w gicie posiadają setki możliwych kombinacji argumentów. Część komend nazwana jest tak, żeby za nic nie dało się zgadnąć do czego służą (Emacs „auto-fill-mode” anybody?).

Wszystkie te programy po przeskoczeniu początkowych trudności, związanych z nauką nowego sposobu myślenia, powodują znaczny wzrost produktywności u użytkownika.

Jest jeszcze jedna rzecz łącząca Gita z VIMem i Emacsem. Wiele osób nie wie o istnieniu monstrualnej ilości trików pozwalających przyspieszyć i uprzyjemnić swoją pracę z tymi programami. Są to zwykle „sztuczki” rozwiązujące konkretny drobny problem. Na tyle drobny, że osobie napotykającej na niego nie opłaca się szukać bezpośredniego rozwiązania, więc zwykle problem zostaje rozwiązany „na około”.

Postanowiłem zebrać w jednym miejscu kilka takich „sztuczek”.

diff

Jedną z najprzydatniejszych komend w każdym porządnym systemie kontroli wersji jest diff.

Git pozwala porównać ze sobą pliki, commity, branche.

Nie wszyscy jednak wiedzą, że gitowy diff przyjmuje wiele opcji konfigurujących sposób w jaki różnice zostaną wyświetlone na ekranie. Jedną z nich jest –word-diff.

Typowy przypadek użycia tego przełącznika to np. próba wyświetlenia drobnych zmian w bardzo długiej linii. Problem ze zwykłym diffem jest taki, że zmiany zostaną pokazane linia do linii.

git diff

def too_big_if_to_be_true
-    some_very_long_method_name arg_firct, arg_second
+    some_very_long_method_name arg_first, arg_second
end

Patrząc na powyższy kod nie widać od razu co zostało zmienione i na co. Z pomocą przychodzi –word-diff. Po dodaniu tego przełącznika do wywołania git diff otrzymamy następujący rezultat:

git diff --word-diff

def too_big_if_to_be_true
some_very_long_method_name [-arg_firct,-]{+arg_first,+} arg_second
end

W tym wypadku sytuacja wygląda trochę lepiej. Nawias kwadratowy i znaki ‚-‚ pokazują nam zastępowany wyraz, nawias klamrowy i znaki ‚+’ pokazują wyraz zastępujący. Wciąż nie jest to jednak forma najbardziej czytelna, szczególnie jeśli trafi nam się kawałek kodu, w którym intensywanie korzystamy z tablicy ([]) albo hasha ({}). W tym wypadku sprawdza się flaga –color-words:

git diff --color-words

def too_big_if_to_be_true
some_very_long_method_name arg_firct,arg_first, arg_second
end

Jeśli odpalimy tą komendę w terminalu, który obsługuje kolory, jakiekolwiek wątpliwości co do wprowadzonych zmian zostaną natychmiast rozwiane. Wyraz zastępowany zostaje pokolorowany na czerwono, wyraz zastępujący na zielono.
Istnieje również możliwość połączenia –word-diff z kolorowaniem:

git diff --word-diff --color

def too_big_if_to_be_true
some_very_long_method_name [-arg_firct,-]{+arg_first,+} arg_second
end

Oczywiście nie ma sensu za każdym razem wpisywać ręcznie –word-diff. Od czego mamy aliasy:

git config alias.dic 'diff --color-words'

ustawia alias ‚dic’ działający w bieżącym repozytorium. Jeśli dodamy –global:

git config --global alias.dic 'diff --color-words'

zmiany zostaną zapisane w naszym pliku .gitconfig, przez co będą działały również w pozostałych repozytoriach.

status

Dużą pomocą dla początkujących użytkowników gita jest wiadomość pokazująca się każdorazowo podczas odpalenia komendy status. Poniżej widać przykładowe wywołanie git status w repozytorium, w którym został zmieniony tylko jeden plik.

git status

# On branch master
# 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:   app/controllers/application_controller.rb
#
no changes added to commit (use "git add" and/or "git commit -a")

Jak widać więcej niż same zmiany w repozytorium zajmuje tekst objaśniający co z tymi zmianami można zrobić, jak je cofnąć, jak wykonać commit. Ten widok, uszyty specjalnie dla początkujących, nie jest jedynym możliwym sposobem wyświetlenia statusu zmian.

Z pomocą przychodzi opcja –short (w skrócie -s). Jej użycie powoduje, że zamiast wielkiego tekstowego bloba pojawia się zgrabne:

git status -s

M app/controllers/application_controller.rb

Jeśli nie mamy ustawionego wyświetlania bieżącego brancha w shellu warto jeszcze dodać przełącznik –branch. Dzięki niemu na górze wyświetlą się informacje na temat bieżącego brancha.

git status -sb
## master
M app/controllers/application_controller.rb

Można ułatwić sobie życie i dodać powyższą komendę jako alias.

git config --global alias.st 'status -sb'

log

Jedną z częściej używanych, a przy tym mocno niedocenianych komend, jest w gicie log. Większość z nas w normalnej pracy ogranicza się do odpalenia git log bez parametrów, ew. z nazwą pliku, którego log chcemy zobaczyć. git-log to idealny przykład komendy, która w 90% przypadków robi dokładnie to czego od niej oczekujemy bez żadnej pomocy z naszej strony. Na pozostałe 10% przypadków twórcy gita przygotowali mnóstwo opcji pozwalających nam zdecydować co właściwie chcemy zobaczyć, w jakim formacie i w jakiej kolejności.

git-log domyślnie wypisuje historię zmian dokonanych na danym branchu w formacie HASH, AUTOR, DATA, COMMIT_MESSAGE.

Tak naprawdę najbardziej interesujące w logu są treści commitów. Nie zawsze jednak same teksty wystarczają do znalezienia tego czego szukamy (kto nigdy nie wpisał bzdurnego COMMIT_MESSAGE niech pierwszy rzuci kamień!).

Bardzo przydana jest możliwość wypisania nie tylko samych commitów, ale także pokazania ich zawartości w formie patchy. Służy do tego polecenie:

git log -p


commit 69dc58e59f11436b4a153537f63d7db3baa8552e
Author: Maciej Gajewski <email>
Date:   Fri Sep 16 16:24:34 2011 +0200

Message 1
diff --git a/abc b/abc
index 11921d5..d8a2f87 100644
--- a/abc
+++ b/abc
@@ -1,3 +1,7 @@
Master

Master end
+
+Master 1
+
+Master 2


commit aa1d275d9c9cba634297c864301238c6903c5e08
Author: Maciej Gajewski <email>
Date:   Fri Sep 16 16:23:08 2011 +0200

added to master

diff --git a/abc b/abc
index e69de29..11921d5 100644
--- a/abc
+++ b/abc
@@ -0,0 +1,3 @@
+Master
+
+Master end

Odpalone bez parametrów pokaże nam logi bieżącego brancha łatka po łatce, co kiedy zostało dodane i przez kogo. Jeśli wpiszemy po -p nazwę pliku będziemy mogli zobaczyć zmiany w podanym pliku – poprawka po poprawce.

Zdarzają się sytuacje, że log -p zwraca za dużo informacji. Czasami chcemy po prostu zobaczyć jakie pliki były zmieniane, a nie koniecznie każdą jedną zmianę w nich. W tym celu musimy odpalić git-log z parametrem –stat


commit 78dbfbe86609759c33f9c59362f6a2813d2c3e13
Author: Maciej Gajewski <mail>
Date:   Fri Sep 16 16:28:53 2011 +0200

Added config and index files

config.xml |    9 +++++++++
index.html |   17 +++++++++++++++++
2 files changed, 26 insertions(+), 0 deletions(-)


commit fa0950503af962cccdccd1bcb9b9380501faed28
Author: Maciej Gajewski <mail>
Date:   Fri Sep 16 16:27:36 2011 +0200


Added main ruby file


main.rb |    3 +++
1 files changed, 3 insertions(+), 0 deletions(-)

Git-Log pozwala nam na na wyświetlenie listy zmian na wszystkich branchach, nie tylko na bieżącym. Co więcej, możemy wyświetlić zmiany tylko na niektórych branchach.

Na przykład:

git log b1 b2

wyświetli wszystkie commity, które występują na branchu b1 i te które występują na branchu b2.
Niezwykle przydaje się możliwość wyświetlenia commitów, które znajdują się na jednym branchu, ale nie ma ich jeszcze na drugim.
Pisząc:

git log master..b1

możemy zobaczyć które commit są na branchu b1, ale nie zostały jeszcze zmergowane do mastera.

Git potrafi filtrować historię po dacie.


git log --since=2.hours.ago --until=10.minutes.ago

Co robi powyższy kod jest w miarę jasne. Wyświetla wszystkie commity powstałe najwyżej dwie godziny temu i nie później niż dziesięć minut temu. Zamiast hours i minutes można wstawić m.in. days, years, months, weeks itd.

Na koniec zostawiłem coś o co chyba najczęściej pojawiają się pytania w kontekście polecenia „log”.

git log -S "def login"

wyświetli wszystkie commity, które zawierają „def login”. Zawierają nie w COMMIT_MESSAGE tylko w plikach dodanych w konkretnym commicie.

Szukania ciąg dalszy

Załóżmy, że mamy jakąś zmianę ale nie wiemy na ilu gałęziach ona się znajduje. Ten problem rozwiązuje git-brach –contains.

git branch --contains=8d38c3ec11ee6247ff929f264815b3f508fc47b2
b1
b2
* master

Polecenie to wypisuje nazwy branchy na których znajduje się commit o podanym SHA1.

Wszystkie powyższe argumenty można łączyć, a więc możliwe jest znalezienie np. definicji funkcji dodanej w zeszłym tygodniu przez autora A, która nie została jeszcze z mergowana z masterem. Nie jest ważne, żeby nauczyć się koniecznie tych poleceń na pamięć (chociaż warto!). Najważniejsze jest żeby wiedzieć, że git posiada takie możliwości. Po detale można sięgać w razie potrzeby 😉

Dla wzrokowców

Na koniec tej sekcji bonus. Jeśli nie znasz jeszcze – wpisz do konsoli w swoim największym projekcie 😉

git log --oneline --graph

Aha! Istnieje jeszcze jeden „hack” pasujący do kategorii „log” chociaż nie mający z git-log wiele wspólnego.

git show :/regexp

znajduje i wyświetla ostatni commit które COMMIT_MESSAGE pasuje go wzorca regexp.

Takich trików jest naprawdę mnóstwo i naprawdę warto się zabrać za solidną naukę obsługi gita. To się opłaca.



Komentarze

  1. Sebastian 26.01.2012

    Comment Arrow

    Bardzo miły do czytania tekst. Ja lubię używać git log –oneline –decorate –all -10 : ostatnie 10 commitów z jednolinijkowym opisem SHA, commit message do tego –decorate dokłada mi branche i tagi. Lubię sobie też czasami zajrzeć w git blame jak mam pytanie odnoście metody to zaraz wiem kogo pytać.


  2. Sebastian 29.01.2012

    Comment Arrow

    Dobry tekst! Nie tylko merytorycznie ale i też przyjemnie się czyta 🙂


  3. gaza 29.11.2012

    Comment Arrow

    u mnie nie działa git status -sb
    error: unknown switch `b’

    problem wersji ?
    git version 1.7.0.4


  4. Maciej Gajewski 3.12.2012

    Comment Arrow

    Hej, tak bym obstawiał – wersja 1.7.0.4 to jest wersja bodaj sprzed 3 lat.

    Ξ www git:(master) ▶ git --version
    git version 1.7.9.6 (Apple Git-31.1)

    ↑129 www git:(master) ▶ git status -sb
    ## master
    (ciach)




O autorze

Maciej Gajewski

Pisze we wszystkim co mu wpadnie w ręce. W tej chwili Rails i Javascript, ale czasami lubi pobrudzić sobie ręce pisząc w C, bądź pobawić się czymś dziwnym (Rust, Haskell?). Kolekcjonuje edytory tekstu. Lubi pływać, ostatnio schudł 5 kilo.