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