Dlaczego git nie scala sąsiednich linii bez konfliktu?

25

Niedawno dowiedziałem się, że podczas łączenia dwóch gałęzi w git, jeśli są zmiany w dwóch sąsiednich liniach, git ogłasza ten konflikt. Na przykład jeśli plik test.txtma tę treść:

Line 1: A
Line 2: B
Line 3: C
Line 4: D

w oddziale masterzmieniamy to na

Line 1: A
Line 2: B1
Line 3: C
Line 4: D

podczas gdy w oddziale testingzmieniamy to na

Line 1: A
Line 2: B
Line 3: C1
Line 4: D

a następnie spróbuj połączyć testingsię master, git ogłasza konflikt scalenia. Moim naiwnym oczekiwaniem było, że połączenie nastąpi bez konfliktu i przyniesie to:

Line 1: A
Line 2: B1
Line 3: C1
Line 4: D

Jestem pewien, że istnieje dobry powód, dla którego git nie łączy się w ten sposób. Czy ktoś może wyjaśnić ten powód?

Rlandster
źródło
Hej, właśnie zauważyłem ten ostatni tydzień. Może robiliśmy ten sam samouczek.
detly
5
Zdolności łączenia git są w rzeczywistości dość słabe, IMO
James
@James próbowałeś użyć algorytmu cierpliwości? Uważam, że osiągam z tym lepsze wyniki, zwłaszcza gdy mam do czynienia z dzieleniem kawałków (np. Chwytaniem jednego ciała funkcji zamiast dwóch). Jeśli nie lubisz gitów, możesz także użyć własnego ( na przykład blog.wuwon.id.au/2010/09/ ...).
deterb
1
Główną przyczyną jest to, że git próbuje wykonać scalenie, zamiast rozdzielić je na wyspecjalizowane narzędzie. Zupełnie nie filozofia uniksowa. W przypadku plików źródłowych można właściwie użyć gramatyki języka, aby wiarygodnie określić różnice.
MSalters
Jedynym wspólnym kontekstem jest A i D, więc dlaczego A / C1 / B1 / D nie jest poprawnym połączeniem?
Izkata,

Odpowiedzi:

13

Zakładając, że ten fragment kodu

x=0
x+=1 if foo
x+=1 if bar
return x

został zmieniony w jednym oddziale na to

x=0
x+=1 if foo && xyzzy
x+=1 if bar
return x

i w innej gałęzi

x=0
x+=1 if foo
x+=1 if bar && xyzzy
return x

wtedy nie chciałbym, żeby git połączył to z tym

x=0
x+=1 if foo && xyzzy
x+=1 if bar && xyzzy
return x

nie alarmując mnie.

Aby uniknąć takich problemów, git zwykle odmawia automatycznego scalania zmian dotykających pobliskich linii. Daje to szansę sprawdzenia, czy logika programu byłaby zepsuta, czy nie.

Ten przykład jest trywialny, ale podczas łączenia ogromnych gałęzi ryzyko podobnych „logicznych” konfliktów jest znacznie większe. Czasami chciałbym nawet, aby kontekst był jeszcze większy niż obecnie.

Arsen7
źródło
5
Nie ma to jednak nic wspólnego, po prostu dodaj niezmienną linię między tymi dwoma i nagle git połączy je bez problemu.
Darkhogg,
Tak, nie dostaję tej odpowiedzi. Możesz użyć tej samej logiki, aby uniknąć wszystkich automatycznych połączeń.
Mehrdad
Musi istnieć granica między bezpieczeństwem a użytecznością. Automatyczne scalanie niesie ryzyko uszkodzenia przepływu programu - jak próbowałem pokazać w odpowiedzi - ale jeśli git po prostu odmówi scalenia czegokolwiek, stanie się bezużyteczny. Twórcy git właśnie zdecydowali się na jakiś kontekst, który uchwyci większość „niebezpiecznych” przypadków. Dodanie niezmienionej linii (jak dowiedział się Darkhogg), głupcy zaczynają wierzyć, że połączenie może być bezpieczne.
Arsen7
11

Czy to zachowanie dotyczy tylko gitów?

Po dyskusji z kolegą właśnie próbowałem, a SVN radzi sobie z tym bez problemu: modyfikujesz 2 linie.

Możliwości scalania kilku VCS są testowane tutaj dla bazaru, darcs, git i mercurial : https://github.com/mndrix/merge-this

Wydaje się, że tylko darcy skutecznie łączą przypadek „linii sąsiednich”.

Zastosowanie sąsiednich zmian do plików nie jest trudnym problemem. Naprawdę uważam, że to zachowanie zostało wybrane celowo.

Dlaczego ktoś miałby decydować, że modyfikacja sąsiednich linii powoduje konflikt?

Myślę, że to zmusza cię do spojrzenia na to .

int max = MAX_ITEMS;
for(unsigned int i = 0; i < max; i++)
    do_stuff(i);

Modif numer 1, na module głównym:

int max = MAX_ITEMS/2; // Do stuff only on the first half
for(unsigned int i = 0; i < max; i++)
    do_stuff(i);

Zmodyfikowany numer 2, scalony z oddziału:

int max = MAX_ITEMS;
for(unsigned int i = 0; i < max/2; i++) // max/2: only on 1st half
    do_stuff(i);

Po scaleniu nie chcesz tego:

int max = MAX_ITEMS/2; // Do stuff only on the first half
for(unsigned int i = 0; i < max/2; i++) // max/2: only on 1st half
    do_stuff(i);

Widząc to zachowanie jako funkcję

Możesz zmienić zachowanie łączenia git na korzyść. Jeśli musisz zachować spójność 2 linii, ale nie możesz ich wykryć (w czasie kompilacji, na początku testów lub w innym przypadku), możesz spróbować dołączyć do nich.

Przepisz to ...:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i);
    // Need to do something else
    do_something_else(r);

...do tego:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i);
    do_something_else(r); // Need to do something else

Więc kiedy scalisz Mod 1 ...:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i)/2; // we need only the half
    do_something_else(r); // Need to do something else

... z Modif 2 ...:

for(unsigned int i = 0; i < max; i++)
    r = do_stuff(i);
    if(r < 0) // do_stuff can return an error
        handle_error(r);
    do_something_else(r/2); // Need to do something else

..., git wywoła konflikt, a ty zmusisz cię do obejrzenia go.

ofaurax
źródło
2
Po prostu zamierzam powiedzieć, że uważam, że twoja odpowiedź jest całkowicie rozsądna, ale w zależności od skomplikowanej, zdefiniowanej implementacji interakcji między twoim kodem a kontrolą źródła dla sprawdzania poprawności jest szybka ścieżka do thedailywtf.com. Ślepe scalanie kodu bez parsera językowego jest ZAWSZE najlepszym wysiłkiem, a ja miałem kilka przypadków, w których git pomógł zautomatyzować coś, czego nie powinien mieć, i wytworzył kod, który nawet by się nie skompilował.
Wug
5

Głównie zgaduję, ale myślę, że ma to związek z tym, że linia 2 jest używana jako kontekst dla zmiany linii 3.

Git nie może po prostu powiedzieć, że „Linia z C stała się linią z C1”, ponieważ może istnieć inna linia z „C”, więc mówi „Linia z C, to jest zaraz po rozpoczęciu pliku, linia z A, a linia z B jest teraz C1 ”

Jeśli „linii z B” już nie ma, część kontekstu zostanie utracona, a git może jedynie z grubsza określić, gdzie ma iść nowa linia.

Mike Gossmann
źródło
5
Jest również bardzo prawdopodobne, że C zależy od B, tak naiwny seryjnej może być kłopotliwe, nawet jeśli git „wie jak” to zrobić
Lucyny
Uwierz mi Git tylko „myśli, że wie”. Git to cmentarzysko złych pojęć, próbujące zostać wyprostowane!
user3833732
2

Pozostałe odpowiedzi tutaj są trafne, ale dla mnie zawsze wydawało się to niepotrzebnym ograniczeniem.

Jak powiedzieli inni, w tych przypadkach zdecydowanie nie chcesz, aby Git scalił linie bez ostrzeżenia.

Ale po otrzymaniu ostrzeżenia nadal chciałem zrobić to automatycznie. Napisałem więc niestandardowy sterownik scalania git, który może scalać konflikty na sąsiednich (lub pojedynczych) liniach interaktywnie:

wprowadź opis zdjęcia tutaj

Oszczędza mi to dużo czasu, ponieważ zarządzam projektem, w którym ludzie często pracują nad tymi samymi plikami i refaktoryzują dużo kodu.

Skrypt jest dostępny na GitHub na licencji GPLv3 +. Może uznasz to za przydatne:

https://github.com/paulaltin/git-subline-merge

deltacrux
źródło
4
Czy ktoś mógłby wyjaśnić, dlaczego zostało to odrzucone? Jestem tu dość nowy, więc jeśli zrobiłem coś złego, chciałbym wiedzieć, co to było, abym mógł tego uniknąć w przyszłości. Zdaję sobie sprawę, że mój post nie odpowiada dokładnie na zadane pytanie, ale jest nadal aktualny i myślę, że większość ludzi, którzy tu przychodzą, chciałaby wiedzieć nie tylko, dlaczego git to robi, ale także, co mogą z tym zrobić (tak jak wtedy, gdy po raz pierwszy dotarło do tego pytania z wyszukiwarki Google).
deltacrux
Jeszcze tego nie próbowałem, ale szukałem sposobu na zautomatyzowanie tego i cieszyłem się, że go tu znalazłem. Dzięki :) jesteś niesamowity.
Mehrdad
1
Nie ma problemu! Mam nadzieję, że okaże się przydatny. Jeśli napotkasz problemy lub masz jakieś sugestie dotyczące ulepszeń, prosimy o przesłanie opinii na temat Github. Dzięki!
deltacrux