Zrozumienie git rev-list

Oct 16 2020

Szukając przykładów git hooków, trafiłem na następujący post: https://github.com/Movidone/git-hooks/blob/master/pre-receive i chciałem zrozumieć następujące polecenie:

git rev-list $new_list --not --all 

gdzie new_list jest uzyskiwany z:

NULL_SHA1="0000000000000000000000000000000000000000" # 40 0's
new_list=
any_deleted=false
while read oldsha newsha refname; do
    case $oldsha,$newsha in *,$NULL_SHA1) # it's a delete
            any_deleted=true;;
        $NULL_SHA1,*) # it's a create new_list="$new_list $newsha";; *,*) # it's an update new_list="$new_list $newsha";;
    esac
done

Pomyślałem, że lista obrotów pokazuje zmiany w odwrotnej kolejności chronologicznej.

Ale czy ktoś może podzielić się większą wiedzą na temat tego, do czego -noti -allopcji są przeznaczone?

Zgodnie z dokumentacją:

--not
Reverses the meaning of the ^ prefix (or lack thereof) for all following revision specifiers, up to the next --not.
--all
Pretend as if all the refs in refs/ are listed on the command line as <commit>. 

Nie jestem w stanie w pełni zrozumieć tych opcji.

[Aktualizacja] Po wykonaniu kilku zatwierdzeń testowych, doszedłem do wniosku, że jeśli nie używam opcji --noti --allwtedy, git rev-listwyświetla listę wszystkich zatwierdzeń w gałęzi, a nie tę, którą zamierzam pushować.

Jednak chciałem zrozumieć, dlaczego nie wypisuje wartości sha na terminalu po --allprzekazaniu opcji?

Odpowiedzi

4 torek Oct 17 2020 at 13:18

git rev-listKomenda jest bardzo skomplikowane, bardzo centralny komenda w Git, jak to, co robi jest chodzić wykres . Słowo graf odnosi się tutaj zarówno do samego wykresu zatwierdzeń, jak iw niektórych przypadkach do następnego poziomu niższego (obiekty Git dostępne z zatwierdzeń).

Pomyślałem, że lista obrotów pokazuje zmiany w odwrotnej kolejności chronologicznej.

Niezupełnie, ale blisko:

  • Kolejność jest zmienna. Wartością domyślną jest odwrócenie chronologii.
  • Domyślnie chodzi o niektóre zatwierdzenia, ale można rev-listwejść głębiej, aby dołączyć obiekty drzewa i obiekty blob, a nawet obiekty znaczników. Dotyczy to programów takich jak git fetchi git push(które wywołują git pack-objects) i git pack-objects. Planuję tutaj całkowicie zignorować tę możliwość, ale czuję, że powinienem przynajmniej o tym wspomnieć. 😀

Więc domyślnie lista niektórych zatwierdzeń jest w odwrotnej kolejności chronologicznej. Jest to zarówno ważne, jak i trochę trudne, aby dokładnie określić, które części wykresu będziemy musieli git rev-listprzejść: niektóre w niektórych zatwierdzeniach .

Ale czy ktoś może podzielić się większą wiedzą na temat tego, do czego --noti --allopcji są przeznaczone?

Jak zauważa VonC , efektem jest tutaj wyświetlenie zatwierdzeń, które są nowe w repozytorium odbierającym. Zależy to od faktu, że ta git rev-listkomenda działa w podpięciu przed odbiorem . Generalnie nie robi nic użytecznego poza tym konkretnym hookiem. Zatem, jak widać, środowisko wykonawcze hooka w Git jest często przynajmniej trochę wyjątkowe. (Dotyczy to czegoś więcej niż tylko podpięcia przed odbiorem: należy pomyśleć o kontekście aktywacji każdego podpięcia.)

Więcej o --not --all

Ta --allopcja robi dokładnie to, co cytowałeś z dokumentacji:

Udawaj, że wszystkie referencje refs/są wymienione w wierszu poleceń ...

Więc robi to odpowiednik a git for-each-ref refs: pętla po każdym odwołaniu. Że zawiera nazwy oddziałów ( masterlub main, develop, feature/talli tak dalej, z których wszystkie są naprawdę refs/heads/), nazwy znacznika ( v1.2który jest naprawdę refs/tags/v1.2), zdalne śledzenie nazwy ( origin/developco jest naprawdę refs/remotes/origin/develop), bibl zamienne (w refs/replace/), The Stash ( refs/stash) odniesienia do podziału, odniesienia Gerrit, jeśli używasz Gerrit, i tak dalej. Zauważ, że nie jest to pętla dla wpisów reflog.

--notPrefiks to prosta operacja logiczna. W gitrevisions składnia zobaczyć dokumentację gitrevisions -Możemy pisać takie rzeczy develop, co oznacza, że trzeba by zacząć od developi praca wstecz i obejmują te rewizje , ale także takie rzeczy ^develop, co oznacza, że trzeba by zacząć od developi praca wstecz i wykluczyć te rewizje . Więc jeśli napiszę:

git rev-list feature1 feature2 ^main

Proszę, aby Git przeszedł po zatwierdzeniach osiągalnych z zatwierdzeń określonych przez nazwy feature1i feature2, ale aby wykluczyć zatwierdzenia dostępne z zatwierdzeń zidentyfikowanych przez main. Aby uzyskać (dużo) więcej informacji na temat ogólnej koncepcji osiągalności i chodzenia po wykresach, zobacz Think Like (a) Git .

--notOperatora skutecznie odwraca ^każdego Ref:

git rev-list --not feature1 feature2 ^main

jest jakby skrótem dla:

git rev-list ^feature1 ^feature2 main

Spowoduje to wyświetlenie listy zatwierdzeń dostępnych z main, ale wyklucza te, które są osiągalne z jednego feature1lub feature2.

Zwykle wszystkie zatwierdzenia można znaleźć za pomocą--all

Jeśli używasz Gita w normalny, codzienny sposób i nie masz w tej chwili „odłączonego HEAD” - odłączony tryb HEAD nie jest całkowicie nienormalny, ale nie jest to zwykły sposób pracy - --allopcja git rev-listmówi mu, aby zawierał wszystkie zatwierdzenia , ponieważ wszystkie zatwierdzenia są osiągalne ze wszystkich odniesień. 1 Tak więc --not --allskutecznie wyklucza wszystkie zatwierdzenia. Zatem dodanie --not --alldo git rev-listczegoś, co w innym przypadku spowodowałoby wyświetlenie listy niektórych zatwierdzeń, powoduje zablokowanie listy. Wynik jest pusty: po co nam to?

Jeśli jesteś w odłączonym trybie HEAD i wykonałeś kilka nowych zatwierdzeń - może się to zdarzyć, na przykład, gdy jesteś w środku interaktywnej lub konfliktowej rebase - wtedy git rev-list HEAD --not --allwyświetli się te zatwierdzenia, które są osiągalne z, HEADale nie z nazwy gałęzi. Na przykład w tej rebase byłyby to tylko te zmiany, które do tej pory skopiowałeś.

Tak więc tryb „odłączonego HEAD” byłby kiedyś miejscem, w którym git rev-list --not --allmógłby być użyteczny z linii poleceń. Ale w przypadku sytuacji, którą badasz - haka przed odbiorem - tak naprawdę nie jesteśmy w wierszu poleceń.

Haczyki do odbioru wstępnego

Kiedy ktoś używa git pushdo wysyłania zatwierdzeń do twojego własnego Gita, twój Git:

  • ustawia obszar kwarantanny do przechowywania wszelkich nowych obiektów (nowych zatwierdzeń, obiektów blob i tak dalej); 1
  • negocjuje z nadawcą, aby zdecydować, co powinien wysłać;
  • otrzymuje te przedmioty; i
  • pobiera listę żądań aktualizacji ref . Te żądania aktualizacji mówią po prostu, aby ta nazwa zawierała ten identyfikator skrótu . 2

Przed wykonaniem którejkolwiek z żądanych aktualizacji, Twój Git:

  1. Dostarcza całą listę do haka przed odbiorem. Ten hak może powiedzieć „nie”; jeśli tak, to całe pchnięcie jako całość jest odrzucane.
  2. Jeśli to mówi „ok”, przekazuje listę, jedno żądanie na raz, do zaczepu aktualizacji. Kiedy ten hak mówi "ok", czy aktualizacja. Jeśli zaczep mówi „nie”, twój Git odrzuca jedną aktualizację, ale przechodzi do sprawdzania innych.
  3. Po zaakceptowaniu lub odrzuceniu wszystkich aktualizacji w kroku 2, przekazuje zaakceptowaną listę do zaczepu po odbiorze.

Obiekty, które są potrzebne, które zostały dodane do jakiegoś odniesienia w kroku 2, są przenoszone z kwarantanny do bazy danych Git. Te, które zostały odrzucone, nie są.

Teraz pomyśl o typowym pliku git push. Otrzymujemy kilka nowych zatwierdzeń i żądanie: utwórz nową nazwę gałęzifeature/short lub otrzymamy nowe zatwierdzenie (zatwierdzenie) i żądanie: zaktualizuj nazwę istniejącej gałęzi, developaby zawierała nowe zatwierdzenia wraz ze starymi .

W kroku 1 powyżej mamy jeden nowy identyfikator skrótu. Uruchomiliśmy pętlę, aby odczytać wszystkie nazwy referencyjne oraz ich bieżące i proponowane nowe identyfikatory skrótów, a pętla została uruchomiona tylko raz, ponieważ tylko jedna nazwa była git push-ed. Ten hash ID odnosi się do nowego zatwierdzenia lub zatwierdzeń, które zostaną dodane do istniejącej gałęzi lub będą wskazówką i innymi zatwierdzeniami, które są wyłączne dla nowej gałęzi.

Chcielibyśmy teraz sprawdzić te zatwierdzenia, a nie którekolwiek z istniejących zatwierdzeń, które są osiągalne z dowolnej istniejącej gałęzi. Dla uproszczenia, zamiast $new_listw mojej innej odpowiedzi, załóżmy, że mamy tylko jeden nowy identyfikator skrótu $newi stary identyfikator skrótu dla nazwy gałęzi $old: same zera, jeśli gałąź jest zupełnie nowa, lub jakieś ważne istniejące zatwierdzenie, jeśli jest istniejąca nazwa oddziału.

Jeśli nowe zatwierdzenia znajdują się w zupełnie nowej gałęzi, to:

git rev-list $new ^master ^develop ^feature/short ^feature/tall

zakryłby je na przykład, gdybyśmy wiedzieli, że jedynymi istniejącymi gałęziami były te cztery (i że nie ma żadnych tagów itp., o które należy się martwić). Ale co, jeśli są dodawane, powiedzmy develop,? Następnie chcielibyśmy wykluczyć zmiany, które są obecnie włączone develop. Moglibyśmy użyć $oldidentyfikatora skrótu, aby to zrobić:

git rev-list $new ^master ^$old ^feature/short ^feature/tall

To ponownie wyszczególniłoby tylko nowe commity, które ktokolwiek git push origin developchce dodać do naszego develop.

Ale pomyśl $old. To jest hash ID. Skąd Git to wziął? Git uzyskał ten identyfikator skrótu z nazwy develop . To jest hak przed odbiorem ; nazwa develop nie została jeszcze zaktualizowana . Więc nazwa develop jest nazwą starego identyfikatora skrótu $old. To znaczy:

git rev-list $new ^master ^develop ^feature/short ^feature/tall

będzie również wykonać zadanie.

Jeśli git rev-list $newnastępuje po nim „i nie wszystkie istniejące”, to:

git rev-list $new --not --branches

wykona robotę. To prawie to, co tutaj mamy.

Problem z samym użyciem --branchespolega na tym, że nie otrzymuje żadnych tagów ani innych referencji. Moglibyśmy użyć, --not --branches --tagsale --not --alljest krótszy i ma również wszystkie inne referencje.

A więc to skąd --not --allpochodzi: zależy to od specjalnego przypadku haka przed odbiciem. Podajemy nowe identyfikatory skrótów, zgodnie z propozycjami każdego, kto uruchamia a git push, które nasz Git przekazał nam jako listę wierszy. Przeszliśmy git rev-listprzez graf zatwierdzeń proponowanych do aktualizacji, przyglądając się nowym zatwierdzeniom w obszarze kwarantanny, ale wykluczając wszystkie zatwierdzenia, które są już w naszym repozytorium. Polecenie rev-list tworzy te hash ID, po jednym w każdym wierszu, które następnie czytamy w pętli powłoki i robimy, co chcemy, aby sprawdzić każde zatwierdzenie.


1 Obszar kwarantanny był nowy w Git 2.11. Wcześniej nowe obiekty mogły pozostawać w repozytorium przez jakiś czas, nawet jeśli wypychanie zostało odrzucone. Obszar kwarantanny nie jest tak naprawdę wielkim problemem dla większości ludzi, ale w przypadku dużych serwerów, takich jak GitHub, może zaoszczędzić im dużo miejsca na dysku.

2 Wniosek może być wymuszony lub niewymuszony, a jeśli jest wymuszony, może być przymusem z dzierżawą lub nie. Ta informacja nie jest dostępna w podpięciu przed odbiorem (ani w podpięciu do aktualizacji), co jest, hm, powiedzmy, nie tak świetne , ale są problemy ze zgodnością przy dodawaniu. Jednak przeważnie to wszystko znośne. Haczyk może stwierdzić, czy jest to utworzenie nowego odwołania, czy usunięcie istniejącego żądania ref, ponieważ jeśli tak, jeden z dwóch identyfikatorów skrótu - stary lub nowy - będzie zerowym „hashem” (który jest zarezerwowany; żaden identyfikator skrótu nie jest dozwolony być zerami).

5 VonC Oct 17 2020 at 01:59

To znaczy:

  • Lista zatwierdzeń, które są osiągalne, podążając za linkami nadrzędnymi z podanych zatwierdzeń, tutaj $new_listnowe, zmodyfikowane lub usunięte zatwierdzenia
  • ale wyklucz zatwierdzenia, które są osiągalne z jednego (ów) podanego ^przed a, tutaj " wszystkie ", to znaczy wszystkie zatwierdzenia HEADS lub oznaczone.

To ogranicza listę rewizji tylko do nowych otrzymanych zatwierdzeń, a nie do wszystkich zatwierdzeń (otrzymanych i już obecnych w repozytorium odbierającym)