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". Как я могу справиться с этим?

-Geoffrey Lee

Ответ 1

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

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.

Ответ 2

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

По-видимому, справочник Git Reference содержит первый совет:

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

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

Ответ 3

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

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

Ответ 4

Развернувшись sun answer, я пришел к команде, которая может создать серию патчей, похожих на то, что 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

Ответ 5

Работая с решением Philippe De Muyter, я сделал версию, которая форматирует патчи так же, как git -формат-патч (насколько я могу судить). Просто установите 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, вам нужно будет исключить любые пустые транзакции слияния или вы получите сообщение об ошибке "Патч пуст. Разбито ли оно неправильно?".