Czy żądania ściągania przez squash łamią algorytm scalania gita?

17

Obecnie pracuję dla firmy, która używa VSTS do zarządzania kodem git. „Zalecanym” sposobem scalania oddziału przez Microsoft jest „scalenie squasha”, co oznacza, że ​​wszystkie zatwierdzenia dla tego oddziału zostają zgniecione w jednym nowym zatwierdzeniu zawierającym wszystkie zmiany.

Problem polega na tym, że jeśli zrobię jakieś zmiany w jednej gałęzi dla jednego elementu zaległości, to od razu chcę zacząć robić zmiany w innej gałęzi dla innego elementu zaległości, a zmiany te zależą od zestawu zmian pierwszego oddziału?

Mogę utworzyć gałąź dla tego elementu zaległości i oprzeć ją na pierwszej gałęzi. Jak na razie dobrze. Jednak, gdy nadchodzi czas, aby utworzyć żądanie ściągnięcia dla mnie drugiej gałęzi, pierwsza gałąź została już scalona w master, a ponieważ została wykonana jako scalenie squasha, git oznacza kilka konfliktów. Dzieje się tak, ponieważ git nie widzi oryginalnych zatwierdzeń, na podstawie których powstała druga gałąź, po prostu widzi jedno duże scalenie squasha, więc aby połączyć drugą gałąź w celu opanowania, próbuje odtworzyć wszystkie zatwierdzenia pierwszej gałęzi w top squash scala, powodując wiele konfliktów.

Więc moje pytanie brzmi: czy jest jakiś sposób na obejście tego (oprócz tego, że nigdy nie opieram jednej gałęzi funkcji na innej, co ogranicza mój przepływ pracy), czy też łączenie squash'a po prostu łamie algorytm scalania gita?

Jez
źródło

Odpowiedzi:

15

Z Gitem popełnia

  • są niezmienne,
  • i tworzą ukierunkowany wykres acykliczny.

Squashing nie łączy zatwierdzeń. Zamiast tego rejestruje nowe zatwierdzenie ze zmianami z wielu innych zatwierdzeń. Rebasing jest podobny, ale nie łączy zatwierdzeń. Nagrywanie nowego zatwierdzenia z takimi samymi zmianami, jak istniejące zatwierdzanie, nazywa się przepisywaniem historii . Ale ponieważ istniejące zatwierdzenia są niezmienne, należy to rozumieć jako „pisanie alternatywnej historii”.

Scalanie próbuje połączyć zmiany dwóch historii zatwierdzeń (gałęzi), zaczynając od wspólnego zatwierdzenia przodka.

Spójrzmy więc na twoją historię:

                                 F  feature2
                                /
               1---2---3---4---5    feature1 (old)
              /
-o---o---o---A---o---o---S          master

A jest wspólnym przodkiem, 1–5 oryginalna gałąź cech, F nowa gałąź cech, a S zgnieciony zatwierdzenie, które zawiera te same zmiany co 1–5. Jak widać, wspólnym przodkiem F i S jest A. Jeśli chodzi o git, nie ma związku między S i 1-5. Tak więc połączenie master z S z jednej strony i feature2 z 1–5 z drugiej spowoduje konflikt. Rozwiązanie tych konfliktów nie jest trudne, ale jest niepotrzebną, żmudną pracą.

Z powodu tych ograniczeń istnieją dwa podejścia do scalania / zgniatania:

  • Albo użyjesz przepisywania historii, w którym to przypadku otrzymasz wiele zatwierdzeń, które reprezentują te same zmiany. Można by wtedy rebase Drugą cechą oddziału na zgniecione popełnić:

                                     F  feature2 (old)
                                    /
                   1---2---3---4---5    feature1 (old)
                  /
    -o---o---o---A---o---o---S          master
                              \
                               F'       feature2
    
  • Lub nie używasz przepisywania historii, w którym to przypadku możesz uzyskać dodatkowe zatwierdzenia scalania:

                                     F  feature2
                                    /
                   1---2---3---4---5    feature1 (old)
                  /                 \
    -o---o---o---A---o---o-----------M  master
    

    Kiedy Feature2 i Master zostaną połączone, wspólny przodek zostanie zatwierdzony 5.

W obu przypadkach będziesz musiał połączyć się. Wysiłek ten nie zależy w dużej mierze od tego, którą z powyższych dwóch strategii wybierzesz. Ale upewnij się, że

  • gałęzie są krótkotrwałe, aby ograniczyć, jak daleko mogą dryfować od gałęzi głównej i tak dalej
  • regularnie scalasz wzorzec z gałęzią elementów lub zmieniasz gałąź elementu na wzorzec, aby synchronizować gałęzie.

Podczas pracy w zespole pomocne jest koordynowanie, kto obecnie nad czym pracuje. Pomaga to utrzymać niewielką liczbę opracowywanych funkcji i może zmniejszyć liczbę konfliktów scalania.

amon
źródło
2
Twoja odpowiedź nie wydaje się dotyczyć tego, co się stanie, jeśli najpierw połączysz squash z feature1mistrzem, a następnie zechcesz połączyć feature2później. W takim przypadku, czy pierwsze podejście nie doprowadziłoby do konfliktów, ponieważ git próbuje ponownie zastosować feature1zatwierdzenia na zgniecionym zatwierdzeniu, podczas gdy drugie pozwoliłoby gitowi ustalić, że te zatwierdzenia nie muszą być scalane?
Jez
@Jez Dokładnie tak się dzieje, gdy zgniatasz PR. Niedawno musiałem ręcznie przepisać PR w projekcie OSS (poprzez git clonerepozytorium i skopiowanie moich zmienionych plików!), Ponieważ rozgałęziłem się z gałęzi, a następnie opiekun zgniótł pierwszą gałąź. W mojej pracy zajmują się także łączeniem squasha. Oznacza to, że nie mogę pracować nad funkcją bzależną od funkcji, adopóki funkcja nie azostanie scalona.
Konto „wyrzuć”
1
I czy nie jest to naprawdę denerwujące zerwanie czegoś, co w innym przypadku działałoby, tak jak zaprojektowano git? Widzę, widzę, że różne organizacje, takie jak Microsoft i Github, faktycznie zalecają takie połączenia squasha i wydają mi się one głupie.
Jez
1
@Jez W oryginalnym scenariuszu tak, podczas łączenia Feature2 z Master wystąpią konflikty, ponieważ scalenie commits 1–5 spowoduje konflikt z tymi samymi zmianami w S. Rozwiązaniem jest zmiana bazy Feature2 (rozwiązanie 1) lub niestosowanie squash / rebasing w wszystko (rozwiązanie 2).
amon
To, czy scalenia squasha są dla Ciebie odpowiednie, zależy od tego, co chcesz zapisać w historii kontroli wersji. Jeśli gałęzie cech mają wiele zatwierdzeń WIP, wówczas squashing powoduje umieszczenie pojedynczego dużego zatwierdzenia z pełną funkcją w gałęzi master. Jeśli wolisz zachować wszystkie pośrednie zatwierdzenia gałęzi funkcji, skorzystaj ze zmiany lub scalenia.
amon
11

Scalanie squasha przerywa algorytm scalania dla wszystkich gałęzi, które zawierają zatwierdzenia usunięte przez squasha. Innymi słowy, bazy są wirusowe. Jeśli dokonujesz zmiany podstawy jednej gałęzi, musisz zmienić podstawę wszystkich innych gałęzi zależnych od tej gałęzi. Jeśli użyjesz rerere , wszelkie konflikty scalania rozwiązane ręcznie w lokalnym repozytorium nie będą musiały być rozwiązywane ręcznie, ale to nie pomaga w przypadku konfliktów rozwiązanych przez inne osoby.

Właśnie dlatego naszą niepisaną zasadą jest, że można zgnieść, dopóki nikt inny nie będzie zależał od twojej gałęzi funkcji, co może mieć miejsce w 90% przypadków. W przeciwnym razie proste scalenie pomaga wszystkim uniknąć problemów.

Karl Bielefeldt
źródło
Jednym ze sposobów, aby zarówno zgnieciony zatwierdzenie w historii głównej, jak i gałąź funkcji pozostały nienaruszone, aby utworzyć osobną gałąź tylko do squasha. Załóżmy, że masz feature-xyzoddział. Możesz utworzyć feature-xyz-squashedgałąź rozpoczynającą się od tego samego zatwierdzenia co feature-xyzgałąź, git cherry-pickzatwierdzenia od feature-xyzdo feature-xyz-squashed, zgnieść je tam i scalić feature-xyz-squashedz master. Nie powinieneś feature-xyzwtedy się łączyć . Czasami powyższe ma sens (np. Nie chcesz dołączać zatwierdzeń z hasłem), ale jest to obejście, które nie jest najlepszą praktyką.
9000