Git
Chapters ▾ 1st Edition

.1 Narzędzia Gita - Wskazywanie rewizji

Wskazywanie rewizji

Git umożliwia wskazanie konkretnej zmiany lub zakresu zmian na kilka sposobów. Nie koniecznie są one oczywiste, ale na pewno są warte uwagi.

Pojedyncze rewizje

Jak wiesz, możesz odwoływać się do pojedynczej zmiany poprzez skrót SHA-1, istnieją jednak bardziej przyjazne sposoby. Ta sekcja opisuje kilka z nich.

Krótki SHA

Git jest na tyle inteligentny, że potrafi domyśleć się o którą zmianę Ci chodziło po dodaniu zaledwie kilku znaków, o ile ta część sumy SHA-1 ma przynajmniej 4 znaki i jest unikalna, co oznacza, że istnieje tylko jeden obiekt w repozytorium, który od nich się zaczyna.

Dla przykładu, aby zobaczyć konkretną zmianę, uruchamiasz komendę git log i wybierasz zmianę w której dodałeś jakąś funkcjonalność:

$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

W tej sytuacji, wybierasz 1c002dd.... Jeżeli chcesz wykonać na nim git show, każda z poniższych komend da identyczny efekt (zakładając, że krótsze wersje są jednoznaczne):

$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d

Git może sam odnaleźć unikalne występowania wartości SHA-1. Jeżeli przekażesz parametr --abbrev-commit do komendy git log, jej wynik pokaże krótsze wartości SHA-1, przy zachowaniu ich unikalności; domyślnie stosuje długość 7 znaków, ale może ją zwiększyć, aby zachować unikalność sum kontrolnych:

$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit

Generalnie, 8 do 10 znaków to wystarczająca ilość, aby mieć unikalne wartości w projekcie. Jeden z największych projektów korzystających z Gita, jądro systemu linux, zaczyna używać 12 znaków z dostępnych 40.

KRÓTKA UWAGA NA TEMAT SHA-1

Duża ilość osób zaniepokoiła się, gdy ze względu na jakiś szczęśliwy przypadek, mieli w swoim repozytorium dwa różne obiekty posiadające tą samą wartość SHA-1.

Jeżeli zdarzy Ci się zapisać obiekt który ma sumę kontrolną SHA-1 taką samą jak inny obiekt będący już w repozytorium, Git zauważy, że obiekt taki już istnieje i założy, że został on już zapisany. Jeżeli spróbujesz pobrać jego zawartość, zawsze otrzymasz dane pierwszego obiektu.

Powinieneś wiedzieć jednak, że taki scenariusz jest strasznie rzadki. Skrót SHA-1 ma długość 20 bajtów lub 160 bitów. Ilość losowych obiektów potrzebnych do zapewnienia 50% prawdopodobieństwa kolizji to około 2^80 (wzór na obliczenie prawdopodobieństwa kolizji to p = (n(n-1)/2) * (1/2^160)). 2^80 to 1.2 x 10^24 lub 1 milion miliardów miliardów. Jest to około 1200 razy ilość ziarenek piasku na kuli ziemskiej.

Weźmy przykład, aby zaprezentować Ci jak trudne jest wygenerowanie kolizji SHA-1. Jeżeli wszyscy z 6,5 miliarda osób na ziemi byłaby programistami i w każdej sekundzie, każdy z nich tworzyłby kod wielkości całego jądra Linuksa (1 milion obiektów Gita) i wgrywał go do ogromnego repozytorium Gita, zajęłoby około 5 lat, zanim w repozytorium byłoby tyle obiektów, aby mieć pewność 50% wystąpienia kolizji. Istnieje większe prawdopodobieństwo, że każdy z członków Twojego zespołu programistycznego zostanie zaatakowany i zabity przez wilki, w nie związanych ze sobą zdarzeniach, w ciągu tej samej nocy.

Odniesienie do gałęzi

Najprostszym sposobem na wskazanie konkretnej zmiany, jest stworzenie odniesienia do gałęzi wskazującej na nią. Następnie, będziesz mógł używać nazwy gałęzi we wszystkich komendach Gita które przyjmują jako parametr obiekt lub wartość SHA-1. Na przykład, jeżeli chcesz pokazać ostatni zmieniony obiekt w gałęzi, podane niżej komendy są identyczne, przy założeniu, że topic1 wskazuje na ca82a6d:

$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1

Jeżeli chciałbyś zobaczyć, na jaką sumę SHA-1 wskazuje dana gałąź, lub jeżeli chcesz zobaczyć na jaką sumę SHA-1 każdy z tych przykładów się rozwiązuje, możesz użyć komendy rev-parse. Możesz zobaczyć również rozdział 9, aby dowiedzieć się o tym narzędziu więcej; ale, rev-parse wykonuje operacje niskopoziomowo i nie jest stworzony do codziennej pracy. Jednakże potrafi być czasami przydatny, jeżeli musisz zobaczyć co tak naprawdę się dzieje. Możesz teraz wywołać rev-parse na swojej gałęzi.

$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949

Skróty do RefLog

Jedną z rzeczy które Git robi w tle w czasie Twojej pracy, jest utrzymywanie reflog-a - zapisanych informacji o tym, jak wyglądały odwołania HEAD-a i innych gałęzi w ciągu ostatnich miesięcy.

Możesz zobaczyć reflog-a za pomocą komendy git reflog:

$ git reflog
734713b... HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970... HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd... HEAD@{2}: commit: added some blame and merge stuff
1c36188... HEAD@{3}: rebase -i (squash): updating HEAD
95df984... HEAD@{4}: commit: # This is a combination of two commits.
1c36188... HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5... HEAD@{6}: rebase -i (pick): updating HEAD

Za każdym razem, gdy Twoja gałąź się przesuwa, Git przechowuje tą informację w tej tymczasowej historii. Za jej pomocą, możesz wskazać również starsze zmiany. Jeżeli chcesz zobaczyć zawartość HEAD-a sprzed 5 zmian, możesz użyć odwołania @{n}, które widać w wyniku komendy reflog:

$ git show HEAD@{5}

Możesz również użyć tej składni, aby dowiedzieć się, jak wyglądała dana gałąź jakiś czas temu. Na przykład, aby zobaczyć gdzie była gałąź master wczoraj, możesz wywołać

$ git show master@{yesterday}

Co pokaże Ci, na jakim etapie znajdowała się ta gałąź wczoraj. Ta technika zadziała tylko dla danych które są jeszcze w Twoim reflog-u, nie możesz więc jej użyć do sprawdzenia zmian starszych niż kilka miesięcy.

Aby zobaczyć wynik reflog-a w formacie podobnym do wyniku git log, możesz uruchomić git log -g:

$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <schacon@gmail.com>
Date:   Fri Jan 2 18:32:33 2009 -0800

    fixed refs handling, added gc auto, updated tests

commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

Należy zaznaczyć, że informacje z reflog-a są wyłącznie lokalne - jest to zapis zmian które wprowadzałeś w swoim repozytorium. Referencje nie będą takie same na kopii repozytorium u kogoś innego; a od razu po pierwszym sklonowaniu repozytorium, będziesz miał pusty reflog, ze względu na to, że żadna aktywność nie została wykonana. Uruchomienie git show HEAD{2.months.ago} zadziała tylko wówczas, gdy sklonowałeś swoje repozytorium przynajmniej dwa miesiące temu - jeżeli sklonowałeś je pięć minut temu, otrzymasz pusty wynik.

Referencje przodków

Innym często używanym sposobem na wskazanie konkretnego commit-a jest wskazanie przodka. Jeżeli umieścisz znak ^ na końcu referencji, Git rozwinie to do rodzica tego commit-a. Załóżmy, że spojrzałeś na historię zmian w swoim projekcie:

$ git log --pretty=format:'%h %s' --graph
* 734713b fixed refs handling, added gc auto, updated tests
*   d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/
* 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list

Następne, możesz zobaczyć poprzednią zmianę, poprzez użycie HEAD^, co oznacza "rodzic HEAD-a":

$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 15:08:43 2008 -0800

    Merge commit 'phedders/rdocs'

Możesz również określić liczbę po ^ - na przykład, d921970^2 oznacza "drugi rodzic d921970". Taka składnia jest użyteczna podczas łączenia zmian, które mają więcej niż jednego rodzica. Pierwszym rodzicem jest gałąź na której byłeś podczas łączenia zmian, a drugim jest zmiana w gałęzi którą łączyłeś:

$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date:   Thu Dec 11 14:58:32 2008 -0800

    added some blame and merge stuff

$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date:   Wed Dec 10 22:22:03 2008 +0000

    Some rdoc changes

Kolejnym wskaźnikiem przodka jest ~. On również wskazuje na pierwszego rodzica, więc HEAD~ i HEAD^ są równoznaczne. Różnica zaczyna być widoczna po sprecyzowaniu liczby. HEAD~2 oznacza "pierwszy rodzic pierwszego rodzica", lub inaczej "dziadek" - przemierza to pierwszych rodziców ilość razy którą wskażesz. Na przykład, w historii pokazanej wcześniej, HEAD~3 będzie:

$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Może to być również zapisane jako HEAD^^^, co znowu daje pierwszego rodzica, pierwszego rodzica, pierwszego rodzica:

$ git show HEAD^^^
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date:   Fri Nov 7 13:47:59 2008 -0500

    ignore *.gem

Możesz również łączyć obie składnie - możesz dostać drugiego rodzica poprzedniej referencji (zakładając że było to łączenie zmian) przy użyciu HEAD~3^2, i tak dalej.

Zakresy zmian

Teraz gdy możesz już wskazywać pojedyncze zmiany, sprawdźmy jak wskazać ich zakres. Jest to szczególnie przydatne podczas zarządzania gałęziami - w sytuacji, gdy masz dużą ilość gałęzi, możesz użyć wskaźnika zakresu zmian, aby odpowiedzieć na pytanie, w stylu "Jakie są zmiany na obecnej gałęzi, których jeszcze nie włączyłem do gałęzi głównej?"

Podwójna kropka

Najczęściej używaną składnią wskazywania zakresu zmian jest podwójna kropka. Mówi ona Gitowi, aby rozwinął zakres zmian które są osiągalne z pierwszego commitu, ale nie są z drugiego. Na przykład, załóżmy że masz historię zmian która wygląda tak jak na rysunku 6-1.


Rysunek 6-1. Przykładowa historia dla wskazania zakresu zmian.

Chcesz zobaczyć co z tego co znajduje się w Twojej gałęzi "experiment" nie zostało jeszcze włączone do gałęzi "master". Możesz poprosić Gita, aby pokazał Ci logi z informacjami o tych zmianach przy pomocy master..experiment - co oznacza "wszystkie zmiany dostępne z experiment które nie są dostępne przez master". Dla zachowania zwięzłości i przejrzystości w tych przykładach, użyję liter ze zmian znajdujących się na wykresie zamiast pełnego wyniku komendy, w kolejności w jakiej się pokażą:

$ git log master..experiment
D
C

Jeżeli, z drugiej strony, chcesz zobaczyć odwrotne działanie - wszystkie zmiany z master których nie ma w experiment - możesz odwrócić nazwy gałęzi. experiment..master pokaże wszystko to z master, co nie jest dostępne z experiment:

$ git log experiment..master
F
E

Jest to przydatne, jeżeli zamierzasz utrzymywać gałąź experiment zaktualizowaną, oraz przeglądać co będziesz integrował. Innym bardzo często używanym przykładem użycia tej składni jest sprawdzenie, co zamierzasz wypchnąć do zdalnego repozytorium:

$ git log origin/master..HEAD

Ta komenda pokaże wszystkie zmiany z Twojej obecnej gałęzi, których nie ma w zdalnej gałęzi master w repozytorium. Jeżeli uruchomisz git push, a Twoja obecna gałąź śledzi origin/master, zmiany pokazane przez git log origin/master..HEAD to te, które będą wysłane na serwer. Możesz również pominąć jedną ze stron tej składni, aby Git założył HEAD. Dla przykładu, możesz otrzymać takie same wyniki jak w poprzednim przykładzie wywołując git log origin/master.. - Git wstawi HEAD jeżeli jednej ze stron brakuje.

Wielokrotne punkty

Składnie z dwiema kropkami jest użyteczna jako skrót; ale możesz chcieć wskazać więcej niż dwie gałęzie, jak na przykład zobaczenie które zmiany są w obojętnie której z gałęzi, ale nie są w gałęzi w której się obecnie znajdujesz. Git pozwala Ci na zrobienie tego poprzez użycie znaku ^, lub opcji --not podanej przed referencją z której nie chcesz widzieć zmian. Dlatego też, te trzy komendy są równoznaczne:

$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA

Jest to bardzo fajne, ponieważ przy użyciu tej składni możesz wskazać więcej niż dwie referencje w swoim zapytaniu, czego nie możesz osiągnąć przy pomocy składni z dwiema kropkami. Dla przykładu, jeżeli chcesz zobaczyć zmiany które są dostępne z refA lub refB, ale nie z refC, możesz użyć:

$ git log refA refB ^refC
$ git log refA refB --not refC

Tworzy to bardzo użyteczną składnię zapytań, która powinna Ci pomóc dowiedzieć się, co jest w Twoich gałęziach.

Potrójna kropka

Ostatnią z głównych składni zakresu jest składnia z trzema kropkami, która wskazuje na wszystkie zmiany które są dostępne z jednej z dwóch referencji, ale nie z obu. Spójrz ponownie na przykład z historią zmian na rysunku 6-1. Jeżeli chcesz zobaczyć co jest zmienione w master lub experiment, poza wspólnymi, możesz uruchomić

$ git log master...experiment
F
E
D
C

Ponownie, otrzymasz normalny wynik log, ale pokazujący tylko informacje o czterech zmianach, występujących w normalnej kolejności.

Często używaną opcją do komendy log jest --left-right, która pokazuje po której stronie każda zmiana występuje. Pozwala to na uzyskanie użyteczniejszych informacji:

$ git log --left-right master...experiment
< F
< E
> D
> C

Przy pomocy tych narzędzi, możesz dużo łatwiej wskazać którą zmianę lub zmiany chcesz zobaczyć.