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 г.)
Коммит 8e0601f
docs/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, то вам нужно будет исключить любые пустые коммиты слияния, иначе вы получите сообщение об ошибке "Патч пуст. Был ли он разбит неправильно?".