Чому 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.