Git: Как создать патчи для слияния?

Когда я использую git format-patch, кажется, не включает слияния. Как я могу выполнить слияние, а затем отправить его по электронной почте в виде набора исправлений?

Например, предположим, что я объединяю две ветви и выполняю еще один коммит поверх объединения:

git init

echo "initial file" > test.txt
git add test.txt
git commit -m "Commit A"

git checkout -b foo master
echo "foo" > test.txt
git commit -a -m "Commit B"

git checkout -b bar master
echo "bar" > test.txt
git commit -a -m "Commit C"

git merge foo
echo "foobar" > test.txt
git commit -a -m "Commit M"

echo "2nd line" >> test.txt
git commit -a -m "Commit D"

Это создает следующее дерево:

    B
  /   \
A       M - D 
  \   /
    C

Теперь я пытаюсь оформить первоначальный коммит и воспроизвести вышеуказанные изменения:

git checkout -b replay master
git format-patch --stdout master..bar | git am -3

Это создает конфликт слияния. В этом сценарии git format-patch master..bar производит только 3 патча, исключая "Commit M". Как мне с этим бороться?

Джеффри Ли

6 ответов

Решение

Если вы изучите содержимое первых двух патчей, вы увидите проблему:

diff --git a/test.txt b/test.txt
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-initial file
+foo

diff --git a/test.txt b/test.txt
index 7c21ad4..5716ca5 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1 @@
-initial file
+bar

с точки зрения ветви, над которой вы работали в то время (foo и bar), оба этих коммита удалили строку "исходный файл" и заменили ее чем-то совершенно другим. AFAIK, нет способа избежать такого рода конфликтов, когда вы генерируете патч нелинейной прогрессии с перекрывающимися изменениями (ваша ветвь фиксирует B и C в этом случае).

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

Похоже, не существует решения, которое могло бы создать отдельные коммиты. git format-patch Но, FWIW, вы можете отформатировать патч, содержащий эффективный коммит слияния, подходящий / совместимый с git am:

Судя по всему, справочное руководство Git дает первый совет:

git log -p Показать патч, введенный при каждом коммите

[...] Это означает, что для любого коммита вы можете получить патч, который фиксируется в проекте. Вы можете сделать это, запустив git show [SHA] с определенным коммитом SHA, или вы можете запустить git log -p, который говорит Git ставить патч после каждого коммита. [...]

Теперь страница руководства git-log дает второй намек:

git log -p -m --first-parent

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

Что в свою очередь означает конкретные шаги:

# Perform the merge:
git checkout master
git merge feature
... resolve conflicts or whatever ...
git commit

# Format a patch:
git log -p --reverse --pretty=email --stat -m --first-parent origin/master..HEAD > feature.patch

И это можно применять по назначению:

git am feature.patch

Опять же, это не будет содержать отдельные коммиты, но это производит git am совместимый патч из коммита слияния.


Конечно, если вам не нужен git am совместимый патч в первую очередь, потом проще:

git diff origin/master > feature.patch

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

Обратите внимание, что голый git log -p не будет показывать содержимое патча для коммита слияния "M", но используя git log -p -c вытаскивает это. Тем не мение, git format-patch не принимает никаких аргументов, аналогичных -c (или же --combined, -cc) принят git log,

Я тоже остаюсь в тупике.

Расширяющийся sunответ, я пришел к команде, которая может произвести серию патчей, аналогичных тому, что git format-patch будет производить, если это возможно, и что вы можете накормить git am создать историю с отдельными коммитами:

git log -p --pretty=email --stat -m --first-parent --reverse origin/master..HEAD | \
csplit -b %04d.patch - '/^From [a-z0-9]\{40\} .*$/' '{*}'
rm xx0000.patch

Патчи будут названы xx0001.patch в xxLAST.patch

В Git 2.32 (второй квартал 2021 г.) документация "" стала более ясной: слияния пропускаются.

См. (01 мая 2021 г.) Джеффа Кинга ( peff) .
(Слияние Junio ​​C Hamano - gitster- в коммите 270f8bf, 11 мая 2021 г.)

Коммит 8e0601fdocs/format-patch: упомянуть обработку слияний

Подписано: Джефф Кинг

Format-patch не имеет способа форматировать слияния таким образом, который может быть применен git-am(или любой другой инструмент), поэтому он их просто опускает.
Однако это может быть неожиданным следствием для пользователей, которые плохо разбираются в том, как работает инструмент.
Давайте добавим примечание к документации, чтобы прояснить это.

теперь включает в свою справочную страницу :

Подготовьте каждый коммит без слияния с его «патчем» в

git format-patchтеперь включает в свою справочную страницу :

Пещеры

Обратите внимание, что format-patchбудет опускать коммиты слияния из вывода, даже если они являются частью запрошенного диапазона.
Простой «патч» не включает достаточно информации для принимающей стороны, чтобы воспроизвести ту же самую фиксацию слияния.

Работая с решением Филиппа Де Мюйтера, я сделал версию, которая форматирует патчи так же, как git-format-patch (насколько я могу судить). Просто установите RANGE на желаемый диапазон коммитов (например, origin..HEAD) и перейдите:

LIST=$(git log --oneline --first-parent --reverse ${RANGE});
I=0;
IFS=$'\n';
for ITEM in ${LIST}; do
    NNNN=$(printf "%04d\n" $I);
    COMMIT=$(echo "${ITEM}" | sed 's|^\([^ ]*\) \(.*\)|\1|');
    TITLE=$(echo "${ITEM}" | sed 's|^\([^ ]*\) \(.*\)|\2|' | sed 's|[ -/~]|-|g' | sed 's|--*|-|g' | sed 's|^\(.\{52\}\).*|\1|');
    FILENAME="${NNNN}-${TITLE}.patch";
    echo "${FILENAME}";
    git log -p --pretty=email --stat -m --first-parent ${COMMIT}~1..${COMMIT} > ${FILENAME};
    I=$(($I+1));
done

Обратите внимание, что если вы используете это с git-quiltimport, то вам нужно будет исключить любые пустые коммиты слияния, иначе вы получите сообщение об ошибке "Патч пуст. Был ли он разбит неправильно?".

Другие вопросы по тегам