uporządkowana, liniowa Historia Gita

jedną z rzeczy, która jest pomijana w wielu projektach opartych na Gita, jest wartość liniowej historii zmian. W rzeczywistości wiele narzędzi i wytycznych zniechęca przepływy pracy Gita, które mają na celu linearną historię. Uważam to za smutne, ponieważ uporządkowana historia jest bardzo cenna i istnieje prosty przepływ pracy, który zapewnia linearną historię.

Historia liniowa vs nieliniowa

historia liniowa to po prostu historia Git, w której wszystkie commity następują po sobie. Tj. nie znajdziesz żadnych połączeń gałęzi z niezależnymi historiami zmian.

1 - nonlinear-vs-linear

po co ci historia liniowa?

poza tym, że jest uporządkowana i logiczna, liniowa historia przydaje się, gdy:

  • patrząc na historię. Historia nieliniowa może być bardzo trudna do naśladowania-czasami do tego stopnia, że historia jest po prostu niezrozumiała.
  • cofanie zmian. Na przykład: “czy funkcja A została wprowadzona przed czy po poprawieniu B?”.
  • śledzenie błędów. Git posiada bardzo ciekawą funkcję o nazwie Git bisect, która może być użyta do szybkiego znalezienia, który commit wprowadził błąd lub regresję. Jednak z nieliniową historią, Git bisect staje się trudny lub wręcz niemożliwy do użycia.
  • Przywracanie zmian. Załóżmy, że znalazłeś commit, który spowodował regresję lub chcesz usunąć funkcję, która nie miała wyjść w określonym wydaniu. Przy odrobinie szczęścia (jeśli twój kod nie zmienił się zbytnio) możesz po prostu przywrócić niechciane commity za pomocą Git revert. Jeśli jednak masz nieliniową historię, być może z dużą ilością połączeń międzyzakładowych, będzie to znacznie trudniejsze.

prawdopodobnie istnieje jeszcze kilka sytuacji, w których liniowa historia jest bardzo cenna, w zależności od tego, jak używasz Gita.

chodzi o to, że im mniej liniowa jest Twoja historia, tym mniej wartościowa.

przyczyny nieliniowej historii

w skrócie, każdy commit merge jest potencjalnym źródłem nieliniowej historii. Istnieją jednak różne rodzaje commitów scalających.

Scalanie z gałęzi tematycznej do master

gdy skończysz z gałęzią tematyczną i chcesz ją zintegrować z master, powszechną metodą jest scalanie gałęzi tematycznej do master. Może coś w tym stylu:

git checkout mastergit pullgit merge --no-ff my-topic-branch

miłą właściwością tej metody jest to, że zachowujesz informacje o tym, które commity były częścią twojej gałęzi tematycznej (alternatywą byłoby pominięcie “–no-FF”, co pozwoliłoby Gitowi na szybkie przewijanie zamiast scalania, w takim przypadku może nie być tak jasne, które commity faktycznie należały do Twojej gałęzi tematycznej).

problem z połączeniem do master pojawia się, gdy gałąź tematyczna jest oparta na Starym Masterze, a nie na najnowszym tip of master. W tym przypadku nieuchronnie otrzymasz nieliniową historię.

2 - merging-old-branch

to, czy będzie to częsty problem, czy nie, w dużej mierze zależy od tego, jak aktywne jest repozytorium Git, ilu programistów pracuje jednocześnie, itp.

Scal z master do gałęzi tematycznej

czasami chcesz zaktualizować swoją gałąź, aby pasowała do najnowszego master (np. istnieje kilka nowych funkcji na master, które chcesz dostać się do gałęzi tematycznej, lub okazuje się, że nie można scalić gałąź tematyczną w master, ponieważ istnieją konflikty).

powszechną metodą, która jest nawet zalecana przez niektórych, jest połączenie końcówki mistrza z gałęzią tematyczną. Jest to główne źródło nieliniowej historii!

3 - scalanie-master-into-topic

rozwiązanie: Rebase!

Git rebase jest bardzo przydatną funkcją, której powinieneś użyć, jeśli chcesz mieć liniową historię. Niektórzy uważają, że koncepcja rebasingu jest niezręczna, ale jest to naprawdę dość proste: powtórz zmiany (commity) w gałęzi na nowym commicie.

na przykład, możesz użyć Git rebase, aby zmienić główny katalog gałęzi tematu ze starego Mastera na końcówkę najnowszego Mastera. Zakładając, że masz sprawdzoną gałąź tematyczną, możesz zrobić:

git fetch origingit rebase origin/master

4 - zmiana ustawień

zwróć uwagę, że odpowiednią operacją scalania byłoby scalenie końcówki master z gałęzią tematyczną (jak pokazano na poprzednim rysunku). Wynikowa zawartość plików w gałęzi tematycznej będzie taka sama, niezależnie od tego, czy wykonasz rebase, czy Scal. Jednak historia jest inna (linearne vs nieliniowe!).

to wszystko brzmi dobrze i dobrze. Istnieje jednak kilka zastrzeżeń dotyczących rebase, o których powinieneś być świadomy.

Zastrzeżenie 1: Rebase tworzy nowe commity

zmiana konfiguracji gałęzi spowoduje utworzenie nowych commitów. Nowe commity będą miały inne SHA: S niż stare commity. Zazwyczaj nie stanowi to problemu, ale napotkasz problemy, jeśli zmienisz bazę gałęzi, która istnieje poza lokalnym repozytorium (np. jeśli twoja gałąź już istnieje w origin).

gdybyś wypchnął odświeżoną gałąź do zdalnego repozytorium, które zawiera już tę samą gałąź (ale ze starą historią), mógłbyś:

  1. git push --force-with-lease), ponieważ Git nie pozwoli Ci w inny sposób wypchnąć nowej historii do istniejącej gałęzi. To skutecznie zastępuje historię dla danej zdalnej gałęzi nową historią.
  2. prawdopodobnie unieszczęśliwiają kogoś innego, ponieważ ich lokalna wersja danej gałęzi nie pasuje już do gałęzi na zdalnym, co może prowadzić do różnego rodzaju kłopotów.

ogólnie rzecz biorąc, unikaj nadpisywania historii gałęzi na pilocie (jedynym wyjątkiem jest przepisanie historii gałęzi, która jest w trakcie sprawdzania kodu – w zależności od tego, jak działa system sprawdzania kodu – ale to jest inna dyskusja).

jeśli chcesz zmienić base gałąź, która jest współdzielona z innymi na zdalnym, prostym przepływem pracy jest utworzenie nowej gałęzi, którą zmieniasz base, zamiast zmienić base oryginalną gałąź. Zakładając, że masz my-topic-branch sprawdzone, możesz zrobić:

git checkout -b my-topic-branch-2git fetch origingit rebase origin/mastergit push -u origin my-topic-branch-2

…a następnie powiedz ludziom pracującym nad my-topic-branch, aby zamiast tego kontynuowali pracę nad my-topic-branch-2. Stara gałąź jest wtedy przestarzała i nie powinna być łączona z powrotem do master.

Zastrzeżenie 2: rozwiązywanie konfliktów w rebase może być więcej pracy niż w merge

Jeśli pojawi się konflikt w operacji merge, wszystkie konflikty rozwiążesz jako część tego commita merge.

jednak w operacji rebase możesz potencjalnie uzyskać konflikt dla każdego commita w gałęzi, którą zmieniasz.

w zasadzie wiele razy przekonasz się, że jeśli pojawi się konflikt w commicie, napotkasz powiązane (bardzo podobne) konflikty w kolejnych commitach w Twojej gałęzi, po prostu dlatego, że commity w gałęzi tematycznej są zwykle powiązane (np. modyfikując te same części kodu).

najlepszym sposobem na zminimalizowanie konfliktów jest śledzenie tego, co dzieje się w master i unikanie zbyt długiego uruchamiania gałęzi tematu bez zmiany bazy danych. Radzenie sobie z małymi konfliktami od czasu do czasu jest łatwiejsze niż radzenie sobie z nimi wszystkimi w jednym wielkim, szczęśliwym konflikcie na końcu.

niektóre ostrzeżenia dla użytkowników GitHub

GitHub jest świetny w wielu rzeczach. Jest fantastyczny dla hostingu Git i ma wspaniały interfejs internetowy z przeglądaniem kodu, ładną funkcjonalnością Markdown, Gist itp.Z drugiej strony

Pull requests ma kilka funkcji, które aktywnie udaremniają liniową historię Git. Byłoby bardzo mile widziane, gdyby GitHub faktycznie naprawił te problemy, ale do tego czasu powinieneś zdawać sobie sprawę z niedociągnięć:

“Merge pull request” pozwala na nieliniowe scalanie do master

kuszące może być naciśnięcie Zielonego, przyjaznego przycisku “Merge pull request”, aby scalić gałąź tematu w master. Zwłaszcza, że brzmi ” ta gałąź jest na bieżąco z gałęzią bazową. Scalanie może być wykonywane automatycznie”.

GitHub merge pull request

GitHub tak naprawdę mówi, że gałąź może być scalona do master bez konfliktów. Nie sprawdza, czy gałąź pull request jest oparta na najnowszym wzorcu.

innymi słowy, jeśli chcesz mieć liniową historię, musisz upewnić się, że gałąź pull request jest przeskalowana na najnowszy master. O ile mi wiadomo, żadna taka informacja nie jest dostępna przez interfejs WWW GitHub (chyba, że używasz “chronionych gałęzi” – patrz poniżej), więc musisz to zrobić z lokalnego klienta Git.

nawet jeśli żądanie pull request zostanie poprawnie zmienione, nie ma gwarancji, że operacja scalania w interfejsie WWW GitHub będzie atomowa (tj. ktoś może wypchnąć zmiany do master, zanim przejdzie operacja scalania-a GitHub nie będzie narzekał).

tak naprawdę, jedynym sposobem na upewnienie się, że Twoje gałęzie są prawidłowo zmieniane na najnowszym wzorcu, jest wykonanie operacji scalania lokalnie i ręczne wypchnięcie wynikowego wzorca. Something along the lines:

git checkout mastergit pullgit checkout my-pullrequest-branchgit rebase mastergit checkout mastergit merge --no-ff my-pullrequest-branchgit push origin master

jeśli masz pecha i komuś uda się wprowadzić zmiany do master pomiędzy operacjami pull i push, operacja push zostanie odrzucona. Jest to jednak dobra rzecz, ponieważ gwarantuje, że Twoja operacja jest atomowa. Po prostu git reset --hard origin/master i powtarzaj powyższe kroki, aż przejdzie.

Uwaga: przestrzegaj wytycznych dotyczących projektu w.R.T. przeglądanie i testowanie kodu. Np. jeśli uruchamiasz testy automatyczne (Kompilacje, analizy statyczne, testy jednostkowe, …) jako część pull request, prawdopodobnie powinieneś ponownie przesłać swoją rebasowaną gałąź (używając git push-f lub otwierając nowy PR) zamiast ręcznie aktualizować gałąź master.

funkcja protected branches zachęca do łączenia z master

jeśli używasz chronionych gałęzi i sprawdzania stanu w swoim projekcie GitHub, w rzeczywistości otrzymujesz ochronę przed scaleniem gałęzi pull request Do master, chyba że jest ona oparta na najnowszym master (myślę, że uzasadnieniem jest to, że kontrole stanu wykonywane na gałęzi PR powinny nadal być ważne po scaleniu do master).

jednak … jeśli gałąź pull request nie jest oparta na najnowszym wzorcu, pojawi się przyjazny przycisk o nazwie “Update branch”, z tekstem ” ta gałąź jest nieaktualna z gałęzią podstawową. Scal najnowsze zmiany z master do tej gałęzi”.

github-update-branch

w tym momencie najlepszą opcją jest zmiana położenia gałęzi lokalnie i wymuszenie jej przesunięcia do pull request. Zakładając, że masz my-pullrequest-branch sprawdzone, zrób:

git fetch origingit rebase origin/mastergit push -f

niestety, żądania pull Requestów GitHub nie działają dobrze z wymuszonymi pchnięciami, więc niektóre informacje o przeglądzie kodu mogą zostać utracone w tym procesie. Jeśli nie jest to akceptowalne, rozważ utworzenie nowego żądania pull request (np. wypchnij swoją rebasowaną gałąź do nowej zdalnej gałęzi i Utwórz żądanie pull request z nowej gałęzi).

wnioski

jeśli zależy ci na liniowej historii:

  • Zmień Base swojej gałęzi tematu na najnowszy master przed scaleniem go do master.
  • Nie Scalaj Mastera z gałęzią tematyczną. Zamiast tego Rebase.
  • dzieląc gałąź tematyczną z innymi, Utwórz nową gałąź, gdy chcesz ją zmienić (my-topic-branch-2, my-topic-branch-3, …).
  • jeśli używasz pull requestów GitHub, pamiętaj:
    • przycisk “Merge” nie gwarantuje, że gałąź PR jest oparta na najnowszym wzorcu. Rebase ręcznie, gdy jest to konieczne.
    • jeśli używasz chronionych gałęzi z sprawdzaniem stanu, nigdy nie naciskaj przycisku” Aktualizuj gałąź”. Rebase ręcznie zamiast.

jeśli nie zależy Ci zbytnio na liniowej historii Gita-happy merging!

lista życzeń dla GitHub

do ciebie wspaniali ludzie pracujący w GitHub: dodaj obsługę przepływu pracy rebase w pull requests (mogą to być Opcje repozytorium opt-in).

  • Dodaj możliwość wyłączenia przycisku/operacji scalania w pull requestach, jeśli gałąź nie jest oparta na najnowszym wzorcu (nie powinno to wymagać sprawdzania stanu).
  • Dodaj przycisk” Rebase on latest master”. W wielu przypadkach powinna to być operacja bezkonfliktowa, którą można łatwo wykonać za pośrednictwem interfejsu internetowego.
  • Zachowaj historię żądań pull (commity, komentarze,…) po rebase / force push.

Leave a Reply