Подтвердить что ты не робот

Как вы обнаруживаете злое слияние в git?

Я создал простой репозиторий git, чтобы проиллюстрировать мой вопрос, доступный на GitHub здесь: https://github.com/smileyborg/EvilMerge

Здесь показана история репо:

master          A---B---D---E-----G-----I
                 \     /     \         /
another_branch    ----C       \       /
                               \     /
another_branch2                 F---H

(В реальном репо на GitHub D есть 4a48c9, а I - 48349d.)

D - это "простое" объединение зла, где слияние "правильно" разрешает конфликт слияния, но также делает несвязанное "зло" изменение, которого не было ни в одном из родителей. Можно обнаружить "злую" часть этого слияния, используя git show -c для этого фиксации, поскольку на выходе есть ++ и -- (в отличие от одиночных + и -), чтобы указать изменения которые не существовали ни в одном из родителей (см. этот ответ для контекста).

I - это другой вид слияния зла, где слияние "правильно" разрешает конфликт слияния (вызванный изменениями от F до file.txt, которые конфликтуют с изменениями из G), но также "злобно" отбрасывает изменения, внесенные в совершенно другой файл file2.txt (эффективно отменяя изменения от H).

Как вы знаете, что I - это злое слияние? Другими словами, какие команды вы можете использовать для обнаружения того, что I не только вручную разрешает конфликт, но также не может слить изменения, которые он должен иметь?

Изменить/обновить: что такое слияние зла?

Как указано René Link ниже, трудно (возможно, невозможно) определить общий набор критериев для определения "злого слияния". Однако, как Судья Верховного суда Стюарт сказал о порнографии, дурные слияния являются чем-то вы знаете, когда вы видите.

Поэтому, возможно, лучше задать вопрос: какие команды git вы можете использовать при компиляции слияния, чтобы получить diff-выход всех новых изменений, введенных исключительно в самом делете слияния. Этот diff должен включать:

  • все слияния разрешений конфликтов (по крайней мере, если разрешение включало в себя что-то более сложное, чем выбор одного родителя, изменяющегося по сравнению с другим)
  • все добавления или удаления, которые не существовали ни в одном из родителей (как показано в D)
  • все изменения, которые существовали у одного из родителей, но сброс слияния (как показано в I)

Цель состоит в том, чтобы иметь возможность взглянуть на этот результат на человека и знать, было ли слияние успешным или (случайно или злонамеренно) "злым" без, чтобы пересмотреть все ранее (например, F и H), которые интегрируются в слияние.

4b9b3361

Ответ 1

Я расширил ответ от Джозефа К. Штрауса, чтобы создать два полных сценария оболочки, которые могут быть легко использованы для получения значимого результата diff для данного слияния совершить.

Сценарии доступны в этом GitHub Gist: https://gist.github.com/smileyborg/913fe3221edfad996f06

Первый script, detect_evil_merge.sh использует стратегию автоматического повторного слияния слияния без разрешения конфликтов, а затем отличается от фактического слияния.

Второй script, detect_evil_merge2.sh использует стратегию автоматического повторного слияния слияния дважды, после разрешения конфликтов с первой родительской версией и второго разрешения конфликтов с использованием второй родительской версии, а затем разграничения каждой из для фактического слияния.

Либо script выполнит эту работу, это просто личное предпочтение, и вам будет легче понять, как были разрешены конфликты.

Ответ 2

Проще всего сделать, чтобы сравнить результаты вашего разрешения конфликта с слиянием, которое автоматически разрешает конфликты без вмешательства человека. Любые автоматические разрешения будут игнорироваться, так как они будут разрешены точно так же.

Я вижу два способа визуализации возможных "злых" резолюций. Если вы делаете это в script добавьте &> /dev/null в конец всех строк, которые вам не нравятся.

1) Используйте два отдельных отличия, один из которых соответствует первому родительскому элементу, а второй - второму родительскому.

MERGE_COMMIT=<Merge Commit>
git checkout $MERGE_COMMIT~
git merge --no-ff --no-edit -s recursive -Xours $MERGE_COMMIT^2
echo "Favor ours"
git diff HEAD..$MERGE_COMMIT
git checkout $MERGE_COMMIT~
git merge --no-ff --no-edit -s recursive -Xtheirs $MERGE_COMMIT^2
echo "Favor theirs"
git diff HEAD..$MERGE_COMMIT

2) Повреждение результатов конфликтуемого слияния с конфликтами, которые еще находятся.

MERGE_COMMIT=<Merge Commit>
git checkout $MERGE_COMMIT~
git -c merge.conflictstyle=diff3 merge --no-ff $MERGE_COMMIT^2 --no-commit
git add $(git status -s | cut -c 3-)
git commit --no-edit
git diff HEAD..$MERGE_COMMIT

Ответ 3

Прежде чем мы сможем обнаружить злые слияния, мы должны определить, что такое слияние зла.

Каждое слияние с конфликтами должно быть разрешено вручную. Чтобы разрешить конфликты, мы можем

  • выполнить одно из изменений и опустить другое.
  • в конечном итоге принять оба изменения (в этом случае порядок в результате может быть важен)
  • не принимать ни одного из них и создавать новое изменение, которое является объединением обоих.
  • не брать ни одного из них и опустить оба.

Итак, что же представляет собой злое слияние?

Согласно этот блог,

слитие считается злым, если оно не верно интегрирует все изменения со всех родителей.

Итак, что такое "верная интеграция"? Я думаю, никто не может дать общий ответ, потому что это зависит от семантики кода или текста или от того, что сливается.

Другие говорят

Слияние зла - это слияние, которое вводит изменения, которые не отображаются ни в одном родителе.

С этим определением все конфликты, разрешенные

  • выполнить одно из изменений и опустить другое.
  • не принимать ни одного из них и создавать новое изменение, которое является объединением обоих.
  • не брать ни одного из них и опустить оба.

- злые слияния.

Итак, мы, наконец, дошли до вопросов.

Является ли это законным для

  • выполнить одно из изменений и опустить другое?
  • взять оба изменения?
  • не принимать ни одного из них и создавать новое изменение, которое является консолидацией обоих?
  • не брать ни одного из них и опустить оба?

И все может усложниться, если мы подумаем о слиянии осьминогов.

Мой вывод

Единственное злое слияние, которое мы можем обнаружить, - это слияние, которое было сделано без конфликтов. В этом случае мы можем повторить слияние и сравнить его с уже выполненным слиянием. Если есть различия, чем кто-то вводил больше, чем он/она, и мы можем быть уверены, что это слияние - это злое слияние.

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

Ответ 4

Отказ от ответственности: Как указано в @smileyborg, это решение не обнаружит случая, когда злое слияние полностью вернуло изменение, внесенное одним из родителей. Этот дефект возникает, потому что согласно Git Документам для параметра -c

Кроме того, в нем перечислены только файлы, которые были изменены от всех родителей.

Недавно я обнаружил гораздо более простое решение этого вопроса, чем любой из текущих ответов.

В принципе, поведение по умолчанию git show для коммандов merge должно решить вашу проблему. В тех случаях, когда модификации с обеих сторон слияния не касаются, и никаких "злых" изменений не было сделано, не будет никакого выхода diff. Раньше я думал, что git show никогда не показывает diff для коммитов. Однако, если слияние слияния связано с беспорядочным конфликтом или слиянием зла, тогда diff будет отображаться в комбинированном формате.

Чтобы просмотреть комбинированный формат при просмотре нескольких патчей фиксации с помощью log -p, просто добавьте параметр --cc.

В примере из GitHub в вопросе отображается следующее (с моими комментариями вкраплены):

$ git show 4a48c9

( D в примере)

commit 4a48c9d0bbb4da5fb30e1d24ae4e0a4934eabb8d
Merge: 0fbc6bb 086c3e8
Author: Tyler Fox <[email protected]>
Date:   Sun Dec 28 18:46:08 2014 -0800

    Merge branch 'another_branch'

    Conflicts:
        file.txt

diff --cc file.txt
index 8be441d,f700ccd..fe5c38a
--- a/file.txt
+++ b/file.txt
@@@ -1,9 -1,7 +1,9 @@@
  This is a file in a git repo used to demonstrate an 'evil merge'.

Следующие строки не являются злыми. Изменения, сделанные первым родителем, обозначаются символом +/- в самом левом столбце; изменения, внесенные вторым родителем, обозначаются символом +/- во втором столбце.

- int a = 0;
- int b = 1;
+ int a = 1;
+ int b = 0;
 +int c = 2;
- a = b;
+ b = a;
  a++;

Вот злая часть: ++ изменилось на -- от обоих родителей. Обратите внимание на ведущие -- и ++, указывающие на то, что эти изменения происходят от обоих родителей, а это означает, что кто-то ввел новые изменения в этом фиксации, которые еще не были отражены в одном из родителей. Не путайте ведущий, diff-сгенерированный ++/-- с завершающим ++/--, который является частью содержимого файла.

--b++;
++b-- ;

Конец зла.

 +c++;

Чтобы быстро просмотреть все коммиты слияния, которые могут иметь проблемы:

git log --oneline --min-parents=2 --cc -p --unified=0

Все неинтересные слияния будут отображаться на одной строке, а грязные - злые или иначе - отобразятся объединенные различия.

Пояснение:

  • -p - Показать патч
  • --oneline - отображает каждый заголовок фиксации в одной строке
  • --min-parents=2 - показывать только слияния.
  • --cc - Показать комбинированный diff, но только для мест, где изменения обоих родителей перекрываются.
  • --unified=0 - отображать 0 строк контекста; Измените число, чтобы быть более агрессивным в поиске слияний.

В качестве альтернативы добавьте следующее, чтобы устранить все неинтересные коммиты:

-z --color=always | perl -pe 's/^([^\0]*\0\0)*([^\0]*\0\0)(.*)$/\n$2\n$3/'
  • -z - отображать NUL вместо новой строки в конце журналов фиксации
  • --color=always - Не отключайте цвет при подключении к perl
  • perl -pe 's/^([^\0]*\0\0)*([^\0]*\0\0) - Массируйте вывод, чтобы скрыть записи журнала с пустыми различиями

Ответ 5

Простейший, вероятно, лучше всего здесь: отбросить результаты некорректного (и неполного) автосекретаря без конфликтов, если они есть, с фактическими результатами слияния.

Обычные наши/их разрешения будут отображаться как все 3 (4 для 3diff) строки маркера конфликта удалены, а одна сторона или другая группа изменений также будет удалена, что будет легко для глазного яблока.

Любые изменения любых изменений в ветке будут отображаться как странный вид, например, любые добавленные или удаленные куски будут отображаться вне маркеров конфликта.

В примере репо после

git clone https://github.com/smileyborg/EvilMerge

git checkout master^
git merge --no-commit master^2   # --no-commit so  w/ or w/o conflict work the same

запуск предлагаемого diff получает

$ git diff -R master    # -R so anything master adds shows up as an add
diff --git b/file.txt a/file.txt
index 3835aac..9851407 100644
--- b/file.txt
+++ a/file.txt
@@ -1,12 +1,6 @@
 This is a file in a git repo used to demonstrate an 'evil merge'.

-<<<<<<< HEAD
-int a = 3;
-||||||| merged common ancestors
-int a = 1;
-=======
-int d = 1;
->>>>>>> master^2
+int d = 3;
 int b = 0;
 int c = 2;
 b = a;
diff --git b/file2.txt a/file2.txt
index d187a25..538e79f 100644
--- b/file2.txt
+++ a/file2.txt
@@ -4,6 +4,6 @@ int x = 0;
 int y = 1;
 int z = 2;
 x = y;
-x--;
-y--;
-z--;
+x++;
+y++;
+z++;

и он мгновенно очистит что-то подозрительное: в file.txt изменения на обеих ветвях были отброшены и строка из ниоткуда вставлена., в то время как в file2.txt конфликта никогда не было, а слияние просто безвозмездно меняет код. Небольшое копание показывает, что оно совершает реверсию здесь, но это несущественно, суть в том, что обычные изменения следуют легко узнаваемым паттернам, и что-то необычное легко обнаруживается и стоит проверить.

Аналогично, после

git branch -f wip 4a48
git checkout wip^
git merge --no-commit wip^2

запуск предлагаемого diff получает

$ git diff -R wip
diff --git b/file.txt a/file.txt
index 3e0e047..fe5c38a 100644
--- b/file.txt
+++ a/file.txt
@@ -1,19 +1,9 @@
 This is a file in a git repo used to demonstrate an 'evil merge'.

-<<<<<<< HEAD
-int a = 0;
-int b = 1;
-int c = 2;
-a = b;
-||||||| merged common ancestors
-int a = 0;
-int b = 1;
-a = b;
-=======
 int a = 1;
 int b = 0;
+int c = 2;
 b = a;
->>>>>>> wip^2
 a++;
-b++;
+b--;
 c++;

и снова вычет нечетности: wip добавил a int c = 2 в ветвь wip^2, и он переключил b-- в b++ из ниоткуда.

Вы могли бы здесь получить симпатичный и автоматизировать некоторые предсказуемые вещи, чтобы сделать массовый опрос быстрее, но это действительно отдельный вопрос.

Ответ 6

Предварительное примечание: я использую Linus Torvalds определение "Злого слияния" здесь, которое, как Junio ​​Hamano примечания иногда могут быть полезными (например, для разрешения семантических конфликтов, а не текстовых конфликтов). Вот определение Linus:

"злое слияние" - это то, что вносит изменения, которые не происходят ни с одной стороны, ни на самом деле не разрешают конфликт [Источник: LKML]

Как @joseph-k-strauss, отмеченный в его ответе, проблема с любым злом-слиянием обнаружение, основанное исключительно на "-c" или "--cc", таково:

"Кроме того, в нем перечислены только файлы, которые были изменены от всех родителей". [Источник: man git -log]

И чтобы обнаружить I злоумышленник, нам нужно найти файлы, измененные некоторыми, но не всеми, его родителями.

Я считаю, что чистые слияния имеют симметричное свойство. Рассмотрим эту диаграмму:

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

В чистом слиянии диагонали одинаковы: b1 == m2 и b2 == m1. Наборы измененных строк перекрываются только при возникновении конфликтов, а чистые слияния не имеют конфликтов. И поэтому набор изменений в b2 должен соответствовать m1, поскольку вся точка b2 должна переименовать m1 поверх родителя2, чтобы привести parent2 в синхронизацию с parent1 (и запомнить --- конфликтов не было). И наоборот для m2 и b1.

Еще один способ мышления об этой симметрии: когда мы перезаряжаем, мы по существу выбрасываем b1 и вместо этого заменяем его квадратом.

Итак, если вы хотите обнаружить слияние зла, вы можете использовать "git show -c" для файлов, измененных обоими родителями, и в противном случае проверить, что симметрия выполняется для четырех сегментов диаграммы, используя "git diff --name только".

Если мы предположим, что слияние с диаграммой будет HEAD (например, посмотрим, является ли только что слияние, которое я только что совершил, является злом), и мы используем фантастическую "трехточечную" условную обозначение git, которая вычисляет базу слияния для вас, я думайте, что вам нужны только эти четыре строки:

git diff --name-only HEAD^2...HEAD^1 > m1
git diff --name-only HEAD^1...HEAD^2 > b1
git diff --name-only HEAD^1..HEAD    > m2
git diff --name-only HEAD^2..HEAD    > b2

Затем проанализируйте содержимое, чтобы увидеть, что m1 == b2 и b1 == m2. Если они не совпадают, тогда у вас есть зло!

Любой вывод из любого из них указывает на зло, поскольку, если мы cat b1 и m2 и сортируем их, тогда каждая строка должна встречаться дважды.

cat b1 m2 | sort | uniq -c | grep -v ' 2 '
cat b2 m1 | sort | uniq -c | grep -v ' 2 '

И для примера EvilMerge commit I выводит следующее:

cat b2 m1 | sort | uniq -c | grep -v ' 2 '
      1 file2.txt

Редактирование файла "file2.txt" происходит только один раз между диагоналями b2 и m1. Слияние не симметрично, поэтому это не чистое слияние. ЗЛО УСПЕШНО ОБНАРУЖЕНЫ!

Ответ 7

Как насчет пересоединения "виртуального" и сравнения результата? Другими словами

псевдокод:

  • начиная с I
  • получите 2 родителя: G, H
  • git checkout E
  • git merge H
  • теперь у вас есть новый-я.
  • сравнить я и new-I либо с помощью git diff, либо путем сравнения вывода git show I и git show new-I

Особенно последним шагом будет тяжелая работа, если вы хотите сделать это полностью автоматически, по крайней мере, если были конфликты в фиксации