Чому git не зливає суміжні лінії без конфлікту?


25

Нещодавно я дізнався, що при злитті двох гілок у git, якщо є зміни на двох суміжних лініях, git оголошує це конфліктом. Наприклад, якщо файл test.txtмістить цей вміст:

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

і у філії masterми змінюємо це на

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

в той час як у філії testingми змінюємо це на

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

а потім спроба злиття testingв master, git оголошує конфлікт злиття. Моє наївне сподівання полягало в тому, що злиття відбудеться без конфлікту і призведе до цього:

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

Я впевнений, що є вагома причина, чому git не зливається таким чином. Хтось може пояснити цю причину?


Гей, я щойно помітив це минулого тижня. Можливо, ми робили той самий підручник.
— detly

5
здібності злиття git насправді досить погані, IMO
— Джеймс

@James Ви спробували використовувати алгоритм терпіння? Я вважаю, що з цим я отримую кращі результати, зокрема, коли ми маємо справу з тим, де розбиваються ханки (наприклад, захоплення одного функціонального тіла замість двох). Якщо вам не подобаються git's, ви можете також скористатися власним ( для прикладу див. Blog.wuwon.id.au/2010/09/… ).
— детермінація

1
Першопричиною є те, що git намагається зробити злиття самостійно, замість того, щоб розподілити його на спеціалізований інструмент. Цілком не філософія Unix. Для вихідних файлів ви можете фактично використовувати граматику мови для надійного визначення відмінностей.
— MSalters

Єдиний загальний контекст - це A і D, тож чому A / C1 / B1 / D не є правильним злиттям?
— Ізката

Відповіді:


13

Якщо припустити, що це фрагмент коду

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

було змінено в одній галузі в цю

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

і в іншій галузі

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

то я б не хотів, щоб git зливав це в це

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

не насторожуючи мене.

Щоб уникнути подібних проблем, git зазвичай відмовляється автоматично об'єднувати зміни, що торкаються довколишніх ліній. Це дає вам можливість перевірити, чи буде порушена логіка програми чи ні.

Цей приклад є тривіальним, але при злитті величезних гілок ризик подібних "логічних" конфліктів значно більший. Іноді я навіть хотів би, щоб контекст був ще більшим, ніж зараз є.


5
Це не має нічого спільного, хоча просто додайте незмінну лінію між цими двома і раптом git з’єднує їх без проблем.
— Darkhogg

Так, я не отримую цієї відповіді. Ви можете використовувати ту саму логіку, щоб уникнути всіх автоматичних злиття.
— Мехрдад

Повинна бути проведена лінія між безпекою та корисністю. Автоматичні злиття несуть у собі ризик зіпсувати потік програми - як я намагався показати у відповіді, - але якщо git просто відмовиться об'єднати що-небудь, воно стане марним. Творці git щойно вирішили якийсь контекст, який охопить більшість "небезпечних" випадків. Додавання незмінної лінії (як з'ясував Даркггггг) дурням змушує вірити, що злиття може бути безпечним для виконання.
— Arsen7

11

Це поведінка лише для git?

Після обговорення з колегою, я просто спробував, і SVN обробляє це без проблем: ви зміните 2 рядки.

Тут випробувані можливості злиття декількох VCS для базару, darcs, git та mercurial : https://github.com/mndrix/merge-this

Здається, лише дарки успішно зливають корпус "сусідніх ліній".

Застосування суміжних змін до файлів не є складною проблемою. Я дійсно думаю, що така поведінка була обрана цілеспрямовано.

Чому хтось вирішить, що зміна сусідніх ліній породжує конфлікт?

Я думаю, що це змусить вас поглянути на це .

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

Модифікація № 1, на головний:

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

Модифікація №2, об'єднана з гілкою:

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

Після злиття вам цього не потрібно:

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);

Бачачи цю поведінку як особливість

Ви можете перетворити поведінку злиття git в перевагу. Якщо вам потрібно зберегти послідовність двох рядків, але ви не можете їх виявити (під час компіляції, на початку тестів чи інше), ви можете спробувати приєднатися до них.

Перепишіть це ...:

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

...до цього:

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

Отже, коли ви об'єднуєте Modif 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

... за допомогою 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 призведе до конфлікту, і ти змусиш тебе на це поглянути.


2
Я просто збираюся йти вперед і скажу, що вважаю, що ваша відповідь цілком розумна, але залежно від хитромудрої, реалізованої взаємодії між вашим кодом та вашим контрольним джерелом для перевірки правильності - це швидкий шлях до thedailywtf.com. Сліпо злиття коду без мовного аналізатора ЗАВЖДИ найкраще докласти зусиль, і в мене було кілька випадків, коли git корисно автоматизував щось, чого не повинно бути, і створив код, який навіть не компілювати.
— Вуг

5

Я в основному здогадуюсь, але думаю, що це стосується того, що рядок 2 використовується як контекст для зміни рядка 3.

Git не може просто сказати, що "Рядок з C став рядком з C1", тому що може бути інший рядок із "C", тому він говорить "Рядок з C, це відразу після початку файлу, рядок з А, а лінія з B - це C1 "

Якщо "рядка з B" вже немає, то частина контексту втрачається, і git може лише приблизно сказати, куди має піти новий рядок.


5
також дуже ймовірно, що C залежить від B, тому наївне злиття може бути клопітним, навіть якщо git "знає як" це зробити
— Lucina

Повірте, Гіт лише "думає, що знає". Git - кладовище неправильних понять, намагаються випрямитися!
— користувач3833732

2

Інші відповіді тут суттєві, але мені це завжди здавалося зайвим обмеженням.

Як говорили інші, у цих випадках ви точно не хочете, щоб Git об'єднав лінії без попередження.

Але я все-таки хотів, щоб зробити це автоматично, після попередження. Тому я написав спеціальний драйвер git merge, який міг інтерактивно поєднувати конфлікти на сусідніх (або окремих) лініях:

введіть тут опис зображення

Це економить мені величезну кількість часу, оскільки я керую проектом, де люди часто працюють над одними і тими ж файлами та переробляють багато коду.

Сценарій доступний на GitHub під ліцензією GPLv3 +. Можливо, вам буде це корисно:

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


4
Хтось міг би пояснити, чому це було знято? Я тут досить новий, тому, якщо я щось зробив не так, я хотів би знати, що це було, щоб уникнути цього в майбутньому. Я усвідомлюю, що моя публікація не відповідає точно на запитання, але це все ще актуально, і я думаю, що більшість людей, які приїжджають сюди, хотіли б знати не лише, чому це робить git, а й те, що вони можуть зробити з цим (як я це робив коли я вперше дійшло до цього питання з пошуку в Google).
— дельтакрукс

Я ще не пробував цього, але шукав спосіб автоматизувати це, і був радий знайти його тут. Спасибі :) ти приголомшливий.
— Мехрдад

1
Без проблем! Я сподіваюся, що Ви вважаєте це корисним, і не соромтесь залишати відгуки про Github, якщо у вас виникли проблеми або є якісь пропозиції щодо вдосконалення. Спасибі!
— дельтакрукс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.