Efektywne polecenia Git

Autor
Damian
Terlecki
38 minut

Istnieją przynajmniej trzy bardzo ważne rzeczy, o których każdy szanujący się programista powinien mieć dobre pojęcie. Jest to: dobra znajomość swojego edytora kodu, jeszcze lepsze zrozumienie systemu kontroli wersji oraz podstawowa znajomość dowolnego języka skryptowego. Obecnie nasze IDE staje się często takim scyzorykiem szwajcarskim, który wykorzystujemy (jeśli znamy go dosyć dobrze) praktycznie wszędzie. Czasami jednak okazuje się, że niekoniecznie zapewnia ono wszystko, czego moglibyśmy potrzebować. Często to właśnie te podstawowe narzędzia, do których dostęp (poprzez pewną nakładkę-interfejs) zapewnia nam IDE, pozwalają nam na znacznie większą kontrolę w realizacji postawionych celów.

Tak więc, dobra znajomość systemu kontroli wersji oraz jego standardowego klienta pozwoli Ci na zwiększenie produktywności w wielu przypadkach. Dodatkowym plusem jest to, że wiedzę tą wykorzystasz również po zmianie swojego ulubionego IDE. W przypadku innego środowiska interfejs niekoniecznie musi być taki sam, a znajomość podstawowego klienta systemu kontroli wersji zapewni Ci zachowanie dotychczasowej produktywności.

Dzisiaj pokażę Ci najbardziej użyteczne polecenia gitowe, których sam używam w codziennej pracy. Pominę w tym miejscu podstawowe komendy, dlatego jeśli nie masz jeszcze podstawowej wiedzy o Gicie, polecam krótki przewodnik Rogera Dudlera. Wracając do tematu, każde polecenie można wpisać na stronie explainshell (niestety jedynie w języku angielskim), aby otrzymać dokładniejszy opis poszczególnych elementów komendy (bez kontekstu). Dokumentację Gita (również w języku angielskim) można znaleźć tutaj.

PolecenieOpisUwaga
git log --pretty=format:"%h | A: %aN/%ad | C: %cN/%cd | %s" --date=format:"%y-%m-%d %H:%M:%S" Wyświetla sformatowane logi commitów:
  • hash commita;
  • autor i jego data;
  • committer (osoba zatwierdzająca commit) i jego data;
  • wiadomość commita;
  • dodaj --author="t3rmian" w celu przefiltrowania po autorze;
  • dodaj --all w celu wypisania commitów ze wszystkich gałęzi;
  • poprzedź poleceniem git fetch --all aby uzyskać najbardziej aktualne informacje.
git log --all --graph --decorate --oneline Wyświetla ładnie sformatowaną historię commitów w jednej linii wraz z grafem w formie tekstowej.
git log [a]..[b] Pokazuje commity pomiędzy dwoma tagami/commitami/gałęziami:
  • przydatne do sporządzania informacji o wydaniu (kolejnej wersji);
  • można dodać formatowanie;
  • a jest traktowane wyłącznie;
  • b jest traktowane włącznie;
  • dołączenie ~ oznacza odniesienie do poprzedniego commita.
git reflog Wyświetla logi odniesienia lokalnego repozytorium oraz aktualizacje końcówki gałęzi (HEAD). Przydatne w celu:
  • sprawdzenia jakie polecenia były wywoływane ostatnio;
  • przywracanie zmian po twardych resetach i innych nieodwracalnych sytuacjach.
git diff [a]..[b] --name-only Wyświetla nazwy plików, które zostały zmienione pomiędzy a i b. Przydatne do:
  • poglądowego porównywania zmian pomiędzy wersjami;
  • sprawdzania, czy cofnięte zmiany zostały zaaplikowane dla całych funkcjonalności.
git pull --rebase origin master Zaciąga mastera z repozytorium zdalnego (w domyśle origin) i rebase'uje zmiany względem niego.
  • Zmienia historię.
  • Może skutkować konfliktami.
git merge -X [theirs|ours] feature Merguje gałąź z funkcjonalnością (feature) z obecną gałęzią przy wykorzystaniu standardowej strategii rekursywnej i w przypadku konfliktów, aplikuje nasze/ich (feature) zmiany.Konflikty są pomocne w prawidłowym utrzymywaniu i mergowaniu przecinających się funkcji.
git checkout feature && git merge -s ours master && git checkout master && git merge feature Nadpisuje mastera branchem feature bez zmiany historii:
  • przydatne do nadpisywania starych chronionych gałęzi;
  • gdy git push --force nie jest możliwe.
git commit --date=relative.1.day.ago Zacommitowanie z datą autora ustawioną wstecz o jeden dzień.
git commit --date "$(date -d 24hours)" Zacommitowanie z datą autora ustawioną w przód o jeden dzień:
  • wykorzystuje polecenie Linuxowe date
git commit --amend Dodaj zmiany poleceniem (git add) przed wykonaniem tego polecania w celu ponownego użycia ostatniego commita (np. gdy zapomniałeś/aś czegoś dodać bądź usunąć):
  • dodaj --no-edit w celu pozostawienia tej samej wiadomości.
Zmienia historię.
git rebase --committer-date-is-author-date HEAD~1 Wywołuje nieinteraktywny rebase w celu zmiany daty zatwierdzenia na datę autora ostatniego commita.Zmienia historię.
git reset --hard [commit] Resetuje indeks i drzewo robocze do określonego punktu. Użyj:
  • HEAD@{2} w celu resetowania z wykorzystaniem git reflog;
  • HEAD~1 w celu resetowania z wykorzystaniem git log;
Może zmienić historię.
git reset --soft HEAD~ Usuwa ostatniego commita i zachowuje zacommitowane pliki.Zmienia historię.
git revert [commit] Cofa konkretnego commita poprzez dodanie nowego. Użyj:
  • najstarszyHash..najnowszyHash w celu cofnięcia kolejnych commitów (dodaj ^, aby dołączyć poprzedni commit, najstarszy jest standardowo dobierany wyłącznie);
  • -m 1 aby wybrać pierwszego rodzica jako główną linię (zazwyczaj master) przy commitach-mergach.
Może spowodować konflikty.
git checkout inny-branch -- path/to/a/file Kopiuje plik z gałęzi inny-branch do obecnego drzewa roboczego.
git stash [|apply|pop|list|drop] Wygodny sposób na tymczasowe przechowanie niedokończonej (dodanej poleceniem git add) pracy podczas przełączania gałęzi.
git checkout . Cofa zmiany niedodane do przyszłego commita.
git clean -df Usuwa nieśledzone (untracked) pliki i foldery ( -x usuwa również pliki ignorowane). Może usunąć pliki nieprzeznaczone do usunięcia. Rozważ użycie:
  • git clean -dfn w celu wywołania testowego;
  • git clean -dif w celu wywołania interaktywnego.
git rebase -i [commit] Uruchamia interaktywny rebase do zdefiniowanego commita lub brancha. Co więcej:
  • dodaj ~ aby dodać również dany commit;
  • rebase oraz squash to alternatywa dla git merge --squash feature.
Zmienia historię.
git cherry-pick -x [commit] Aplikuje wybrany commit do obecnej gałęzi z dodatkowym komunikatem o źródle commita. Użyj:
  • najstarszyHash..najnowszyHash do zaaplikowania kilku kolejnych commitów (dodaj ^ w celu włączenia również najstarszego commita);
  • git rebase --onto target from to jako alternatywę dla zaaplikowania kilku commitów;
  • git rebase -i branch jako alternatywę dla interaktywnego zaaplikowania kilku commitów;
  • -m 1 aby wybrać pierwszego rodzica jako główną linię (zazwyczaj master) przy commitach-mergach.
Może skutkować konfliktami.
git filter-branch --env-filter 'export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"' Przepisuje całą gałąź resetując datę commita do daty autora.Zmienia historię.
git bisect start
git bisect bad [|bad_commit]
git bisect good [|good_commit]
git bisect reset
Metoda bisekcji pomaga znaleźć źródło błędu wykrytego podczas testów regresji, za pomocą zasady dziel i zwyciężaj:
  1. Znajdź dobrego (bez buga) commita i złego, rozpocznij bisekcję i zaznacz commity.
  2. Git zrobi checkout pośrodku. Przetestuj wersję i zaznacz commita jako dobry bądź zły git bisect good/git bisect bad.
  3. Po zakończeniu i znalezieniu commita wprowadzającego błąd, zresetuj bisekcję i cofnij/napraw kod.
gitk --follow [filename] Wyświetla listę commitów dla danej ścieżki/pliku w postaci grafu, podążając za zmianami nazw plików. Przydatne do:
  • śledzenia zmian funkcjonalności;
  • analizowania zgłoszeń błędów;
  • znajdywania nieudokumentowanych funkcjonalności poprzez identyfikator z narzędzia do śledzenia zgłoszeń.

Różnica między ^ i ~ w połączeniu z danym commitem jest niewielka, ale zauważalna:

  • commit~ to nawiązanie do pierwszego rodzica danego commita;
  • commit~2 to nawiązanie do commita który jest pierwszym rodzicem pierwszego rodzica danego commita;
  • commit^ to nawiązanie do pierwszego rodzica danego commita;
  • commit^2 to nawiązanie do drugiego rodzica danego commita.

W przypadku pojawienia się błędu podczas używania HEAD^1, twoja powłoka być może interpretuje znak ^ jako kontynuacja nowej linii. W takim przypadku użyj "HEAD^".

Oto ilustracja autorstwa Jona Loeligera. Oba commity B i C są rodzicami commita A. Commity-rodzice są uporządkowane od lewej do prawej.

G   H   I   J
 \ /     \ /
  D   E   F
   \  |  / \
    \ | /   |
     \|/    |
      B     C
       \   /
        \ /
         A
A =      = A^0
B = A^   = A^1     = A~1
C = A^2  = A^2
D = A^^  = A^1^1   = A~2
E = B^2  = A^^2
F = B^3  = A^^3
G = A^^^ = A^1^1^1 = A~3
H = D^2  = B^^2    = A^^^2  = A~2^2
I = F^   = B^3^    = A^^3^
J = F^2  = B^3^2   = A^^3^2

Istnieją również inne sposoby odwoływania się do wersji, takie jak master@{yesterday}, @{push}, /"naprawiono jakiś błąd". Możesz przeczytać więcej na ten temat w dokumentacji o git rev-parse. Ostatnią rzeczą, na którą chciałbym zwrócić uwagę, jest to, że istnieje różnica między zakresem zdefiniowanym przez podwójne kropki i potrójne kropki. Spójrz na wykresy na blogu Chucka Lu, aby zauważyć rozbieżność.