Git:如何为合并创建补丁?

Posted

技术标签:

【中文标题】Git:如何为合并创建补丁?【英文标题】:Git: How to create patches for a merge? 【发布时间】:2011-01-18 03:31:13 【问题描述】:

当我使用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 个补丁,省略“提交 M”。我该如何处理?

-杰弗里·李

【问题讨论】:

【参考方案1】:

使用 Git 2.32(2021 年第二季度),“format-patch”文档更加清晰:它跳过了合并。

参见 Jeff King (peff) 的 commit 8e0601f(2021 年 5 月 1 日)。(由 Junio C Hamano -- gitster -- 合并到 commit 270f8bf,2021 年 5 月 11 日)

docs/format-patch: 提及合并处理

签字人:杰夫·金

Format-patch 无法以git-am(或任何其他工具)可以应用的方式格式化合并,因此它只是省略了它们。 但是,对于不熟悉该工具如何工作的用户来说,这可能是一个令人惊讶的暗示。 让我们在文档中添加一个注释,使这一点更加清晰。

git format-patch 现在包含在其man page 中:

准备每个非合并提交及其“补丁”

git format-patch 现在包含在其man page 中:

注意事项

请注意,format-patch 将忽略输出中的合并提交,即使 如果它们是请求范围的一部分。 一个简单的“补丁”不会 包含足够的信息让接收端重现相同的信息 合并提交。

【讨论】:

【参考方案2】:

似乎没有产生单个提交的解决方案 à la git format-patch,但 FWIW,您可以格式化包含有效合并提交的补丁,适合/兼容 git am

显然,Git Reference 指南提供了第一个提示:

git log -p 显示每次提交时引入的补丁

[...] 这意味着对于任何提交,您都可以获得提交给项目的补丁。您可以通过运行带有特定提交 SHA 的 git show [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

但我猜你已经想通了,如果你在这里登陆这个页面,你实际上是在寻找我上面描述的解决方法/解决方案。 ;)

【讨论】:

很好的答案,非常感谢!恐怕它不完整,因为您还需要 --reverse 在 git log 中,否则“git am feature.patch”将不起作用。所以,像这样: git log --reverse -p --pretty=email --stat -m --first-parent origin/master..HEAD > feature.patch 感谢@DarkoMaksimovic,相应地更新了解决方案。 --reverse 之前没有包含,因为所描述的过程只包含一个提交。但你是绝对正确的! 如果你想包含图片等,请使用--binary【参考方案3】:

根据 Philippe De Muyter 的解决方案,我制作了一个版本,它以与 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 一起使用,那么您将需要排除任何空的合并提交,否则您将收到错误“补丁为空。是否拆分错误?”。

【讨论】:

【参考方案4】:

扩展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.patchxxLAST.patch

【讨论】:

如果你用-z-f ''调用csplit,那么你不需要删除第一个补丁,它不会有“xx”前缀。”我真的希望git- format-patch 有一个集成选项来做这样的事情...... csplit: illegal option -- b 在 macOS 上【参考方案5】:

如果您检查前两个补丁的内容,您会发现问题:

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 本身那样处理合并历史。如果你想让别人看到你的合并,那么你需要在分支之间推/拉而不是回退差异/补丁。

【讨论】:

很难证明是否定的,但就像 seh 一样,我在这个问题上遇到了一些问题,我觉得你是对的。 是的,我了解补丁文件的问题。我希望有一种解决方法,因为人们会假设 Linux 或 Git 项目遇到了类似的情况,并且它们完全依赖于通过电子邮件提交补丁而不是推送/拉取。我会 ping Git 邮件列表,看看他们是否有任何其他反馈。谢谢。 如果你用 $ git merge --squash foo $ git commit -a -m"Commit M" 替换了上面的合并行,你的补丁就会干净地应用...... 是的,我本可以压缩提交,但这会破坏历史,而且通常不是处理分布式版本控制的好方法,恕我直言。幸运的是,Git 邮件列表中的某个人向我指出了“git bundle”,它允许您手动打包和传输 Git 对象。这似乎是最好的解决方案。 上述Jeff King关于git bundle的建议:thread.gmane.org/gmane.comp.version-control.git/140321/…【参考方案6】:

请注意,一个裸露的git log -p 不会显示合并提交“M”的任何补丁内容,但使用git log -p -c 会哄它出来。但是,git format-patch 不接受与git log 接受的-c(或--combined-cc)类似的任何参数。

我也很困惑。

【讨论】:

谢谢,这是我一直在寻找的关于为什么 git log -p 没有显示合并提交补丁的确切答案。

以上是关于Git:如何为合并创建补丁?的主要内容,如果未能解决你的问题,请参考以下文章

如何为整个目录创建补丁来更新它?

git commit提交rebase合并以及patch补丁的使用

git commit提交rebase合并以及patch补丁的使用

Git 补丁操作

git的简单介绍

git am 合并 patch 时冲突的解决方法