以编程方式检查Git分支是否可以合并的正确方法是什么?

5

我想测试一个分支是否可以合并到另一个分支中。例如,假设我想测试发布分支是否可以合并到主分支中,而不实际合并到主分支中。我想这样做:

#!/usr/env/bin bash
git checkout master
git checkout -b master_temp
git merge my_release_branch

如果退出代码为0,则我认为合并已成功,否则合并失败。

这里有一个大问题:假设合并成功,但它打开了交互式部分。我如何避免那个交互式部分?有时在合并过程中,nanovim会打开,我们必须做一些事情(不确定是什么)。有没有办法关闭它呢?

这个问题基本上是相同的,但问题并没有规定必须由其他方式而非人工完成。在我的情况下,需要自动化/程序化完成。


我在这里找到了关于 git merge-tree 的参考资料 链接,但我不确定如何在这种情况下使用它。您是否可以接受修改工作树或索引,还是希望它完全没有副作用,或者其他什么? - Daniel H
@DanielH,你知道在Github上他们如何告诉我们两个分支是否可以合并(而不实际合并它们)吗?那就是我想要做的。我宁愿没有任何副作用,我认为如果我只是检出一些临时分支,这是可能的。 - Alexander Mills
在 GitHub 上,他们实际上是在一个侧支中执行合并的;您可以获取 refs/pull/$prnumber/merge(尽管我认为这种行为不再是 API 的一部分,他们可能会更改它)。检出另一个分支会起作用,但会搞乱 HEAD,如果您没有裸仓库,则会搞乱工作树。您可以使用 git worktree 来解决这个问题。 - Daniel H
3个回答

7
晚来一步,但您可以使用以下方法实现:

晚到派对,但是您可以通过以下方式完成:

git merge-tree $(git merge-base feature_branch master) feature_branch master

点赞:这是一个有效的解决方案,特别是在 Git 2.38 (Q3 2022) 中。 - VonC
最好现在使用git merge-tree --write-tree feature_branch master,如@VonC在这里提到的https://dev59.com/8azla4cB1Zd3GeqPD-Xh#73091924 - Eyal Gerber

3

lbergnehr答案中提到的git merge-tree,随着Git 2.38(2022年第3季度)的到来变得更加有效,因为“git merge-tree手册学会了一种新模式,在这种模式下它需要两个提交,并计算出如果合并这两个提交的历史记录,则将生成一个合并提交的树。


简介; git merge-tree 在 Git 2.38 (2022年第三季度) 刚刚变得非常棒

  • 您可以快速测试两个分支是否可以合并(不进行实际合并,也就是说不修改分支、索引或工作树!):

    NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)  
    test $? -eq 0 || die "存在冲突..."
    
  • 即使这些分支没有关联,您也可以这样做:

    git merge-tree --write-tree --allow-unrelated-histories $BRANCH1 $BRANCH2
    
  • 您可以列出有冲突的文件以及它们为什么会发生冲突:

    git merge-tree --write-tree --no-messages branch1 branch2
    
  • 您可以列出有冲突的文件(只列出文件名):

    git merge-tree --write-tree --no-messages --name-only branch1 branch2
    

就像我之前所说的,非常令人印象深刻...


请查看提交 7260e87, 提交 7976721, 提交 7c48b27, 提交 de90581, 提交 cb26077, 提交 b520bc6, 提交 7fa3338, 提交 a4040cf, 提交 fae26ce, 提交 a1a7811, 提交 a34edae, 提交 1f0c3a2, 提交 6ec755a, 提交 55e48f6, 提交 70176b7 (2022年6月18日) 由Elijah Newren (newren)提交。
请查看提交 2715e8a, 提交 6debb75 (2022年6月18日) 由Johannes Schindelin (dscho)提交。
(由Junio C Hamano -- gitster --合并于提交 be733e1, 2022年7月14日)

merge-tree: 实现真正的合并

签名作者:Elijah Newren

这增加了执行真正的合并能力,而不仅仅是微不足道的合并(意味着处理三方内容合并、递归祖先合并、重命名、正确的目录/文件冲突处理等)。
然而,与git merge(man)不同,工作树和索引保持不变,没有分支被更新。
唯一的输出是:
- 标准输出打印的顶层结果树 - 退出状态为0(干净)、1(存在冲突)或其他任何值(无法执行合并;未知是否干净或有冲突)
此输出旨在由某些更高级别的脚本使用,例如以下步骤序列:
NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2) git update-ref $BRANCH1 $NEWCOMMIT
请注意,更高级别的脚本可能还想访问通常在合并期间输出的冲突/警告消息,或者快速访问具有冲突的文件列表。
这也将合并树的传统微不足道的合并标记为已弃用。微不足道的合并不仅适用性有限,输出格式也难以处理(其格式未经记录),并且通常比真正的合并性能差。

git merge-tree现在在其man page中包含以下内容:

git-merge-tree - 执行合并操作而不影响索引或工作树

git merge-tree现在在其man page中包含以下内容:

'git merge-tree' [--write-tree] 'git merge-tree' [--trivial-merge] (已弃用)

git merge-tree现在在其man page中包含以下内容:

该命令有现代的--write-tree模式和已弃用的--trivial-merge模式。除了末尾的<<DEPMERGE,DEPRECATED DESCRIPTION>>部分外,本文档的其余部分描述了现代的--write-tree模式。
执行合并操作,但不会创建任何新提交,并且不会读取或写入工作树或索引。
执行的合并将使用与“真实”git merge相同的功能,包括:
- 单个文件的三方内容合并 - 重命名检测 - 正确的目录/文件冲突处理 - 递归祖先合并(即当存在多个合并基时,通过合并合并基来创建虚拟合并基) - 等等
合并完成后,将创建一个新的顶级树对象。有关详细信息,请参见下面的OUTPUT
对于成功或有冲突的合并,git-merge-tree的输出只有一行:<OID of toplevel tree>。打印的树对象对应于在git merge结束时将在工作树中检出的内容,因此可能包含带有冲突标记的文件。
对于成功的非冲突合并,退出状态为0。当合并存在冲突时,退出状态为1。如果由于某种错误而无法完成(或开始)合并,则退出状态为0或1以外的其他值(输出未指定)。
此命令旨在作为低级别的管道工具使用,类似于git hash-objectgit mktreegit commit-treegit write-treegit update-refgit mktag。因此,它可以作为一系列步骤的一部分使用,例如:
NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
test $? -eq 0 || die "There were conflicts..."
NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
git update-ref $BRANCH1 $NEWCOMMIT

根据<<NEWMERGE,DESCRIPTION>>,与本文档的其余部分不同,此部分描述了已弃用的--trivial-merge模式。
除了可选的--trivial-merge之外,此模式不接受任何选项。
此模式读取三个树状物,并以半差异格式将平凡合并结果和冲突阶段输出到标准输出。由于这是为更高级别的脚本消耗和将结果合并回索引而设计的,因此它省略了与<branch1>匹配的条目。
这种形式不仅适用性有限(平凡合并无法处理单个文件的内容合并、重命名检测、正确的目录/文件冲突处理等),输出格式也难以处理,并且即使在成功合并时(尤其是在大型存储库中工作时),它的性能通常也会比第一种形式差。

您可以避免最后的信息提示:

merge-tree: 支持在输出中包含合并消息

签名:Elijah Newren

运行 git merge-tree --write-tree(man) 时,我们以前只返回反映合并干净程度的退出状态,并打印出结果合并的顶层树。
合并还有信息性消息,例如:
  • "Auto-merging <PATH>"
  • "CONFLICT (content): ..."
  • "CONFLICT (file/directory)"
  • 等等。
事实上,当发生非内容冲突(例如 file/directorymodify/deleteadd/add 具有不同模式、rename/rename (1to2) 等)时,这些信息性消息可能是用户唯一得到的通知,因为这些冲突在文件内容中无法表示。 添加一个 --[no-]messages 选项,以便调用者可以请求在输出末尾包含这些消息。
默认情况下,在存在冲突时包含此类消息,在合并干净时默认情况下省略它们。

git merge-tree现在在其手册页中包括以下内容:

选项

--[no-]messages

将任何信息性消息(如“自动合并<路径>”或冲突通知)写入标准输出的末尾。

如果未指定,则默认情况下,如果存在合并冲突,则包括这些消息,否则将省略它们。

对于成功的合并,git-merge-tree的输出只是一行:

<顶层树的OID>

而对于有冲突的合并,默认情况下输出形式为:

git merge-tree现在在其手册页中包括以上内容:

顶级树的 OID
这是一个树对象,代表在 `git merge` 结束时在工作树中将被检出的内容。 如果存在冲突,则此树中的文件可能会包含嵌入式冲突标记。
信息性消息
此部分始终以空行开头,与前一部分分隔开,并包含有关合并的自由形式消息,例如:
- "正在自动合并 <file>" - "CONFLICT (rename/delete): <oldfile> 重命名...但在删除时已删除..." - "无法合并子模块 <submodule> (<reason>)" - "警告:无法合并二进制文件:<filename>"

如果你想知道哪些文件有冲突以及原因,使用之前的--no-messages选项!

git merge-tree --write-tree --no-messages branch1 branch2
                            ^^^^^^^^^^^^^

merge-tree:提供有冲突的文件列表

签名作者:Elijah Newren

git merge-tree(man) 的调用者 --write-tree 通常想知道哪些文件存在冲突。
虽然他们可能尝试解析打印的 CONFLICT 通知,但这些消息并不适合机器阅读。
在自由格式消息之前提供一个更简单的机制,只需以与 git ls-files(man) 相同的格式(带引号,但仅限于未合并的文件)在输出中打印文件即可。

git merge-tree现在在其手册页面中包含了:

Conflicted file list


This is a sequence of lines containing a filename on each line, quoted
as explained for the configuration variable `core.quotePath` (see
[`git config`](https://git-scm.com/docs/git-config)).

如果你想知道有冲突的文件列表(不包括原因,只有文件名),请使用新的--name-only选项!

git merge-tree --write-tree --name-only --no-messages branch1 branch2
                            ^^^^^^^^^^^

merge-tree:提供方便访问 ls-files -u 样式信息的方法

签名:Elijah Newren

Much like git merge(man) updates the index with information of the form

(mode, oid, stage, name)

provide this output for conflicted files for merge-tree as well.

Provide a --name-only option for users to exclude the mode, oid, and stage and only get the list of conflicted filenames.

git merge-tree现在在其手册页面中包含:

--name-only

在冲突文件信息部分,不再输出(模式、oid、阶段、路径)元组列表以提供有冲突的文件列表,而是仅提供具有冲突的文件名列表(如果它们具有多个冲突阶段,则不要多次列出文件名)。

git merge-tree现在在其手册页面中包含:

这是一个带有格式的行序列。
文件名将被引用为配置变量core.quotePath中所解释的方式(参见git config)。 但是,如果传递了--name-only选项,则模式、对象和阶段将被省略。

即使您的两个分支没有共同祖先(不相关的历史记录/历史记录),也可以这样做!

git merge-tree --write-tree --allow-unrelated-histories --name-only --no-messages branch1 branch2
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^

merge-tree:添加 --allow-unrelated-histories 标志

签署者:Elijah Newren

人们可能想要合并没有共同祖先的历史记录;提供一个与 git merge(man) 使用相同名称的标志来允许此操作。

git merge-tree 现在在其手册页面中包含了这一内容:

--allow-unrelated-histories

默认情况下,如果指定的两个分支没有共同的历史记录,merge-tree 将会报错。
可以使用此标志来覆盖该检查并使合并继续进行。


在 Git 2.39 (Q4 2022) 中, "git merge-tree --stdin"(man) 是一种新的请求合并系列和报告合并结果的方式。

请查看提交 ec1edbc提交 a9f5bb8(2022年10月23日),由Elijah Newren (newren)提交。
(由Taylor Blau -- ttaylorr --合并于提交 b1e3dd6,2022年10月30日)

merge-tree:支持使用--stdin进行多次批处理合并

签名作者:Elijah Newren

新增一个选项--stdinmerge-tree,它将接受每行两个需要合并的分支输入,并逐个执行所有合并操作并输出结果。此选项会隐含-z,并修改输出结果以包括合并状态,因为现在涉及到多次合并,程序的退出码不能再传达这些信息。

例如,对于Git托管提供者而言,这可能非常有用。当一个分支被更新时,可能需要检查针对该分支的所有代码审查是否仍然可以清晰地合并。如果在具有许多代码审查的存储库中避免为每个代码审查启动单独的进程,可能会节省大量开销。

git merge-tree现在在其手册页面中包含了:

如果传递了--stdin,则在开头会有一个额外的部分,在结尾处有一个NUL字符,然后所有部分都会为每个输入行重复。因此,如果第一个合并冲突而第二个干净,则输出将采用以下形式:
<合并状态> <顶层树的OID> <冲突文件信息> <信息性消息> NUL <合并状态> <顶层树的OID> NUL

合并状态

这是一个整数状态,后面跟着一个NUL字符。
整数状态为:
  • 0:合并存在冲突
  • 1:合并干净
  • <0:某些原因阻止了合并运行
    (例如,文件系统拒绝访问存储库对象)

git merge-tree现在在其手册页面中包含:

除0或1以外的其他内容(输出未指定)。

当传递--stdin时,返回状态为

  • 对于成功和冲突的合并都为0,并且
  • 如果无法完成所有请求的合并,则为除0或1以外的其他内容。

2
运行git merge只有在以下所有条件都成立时,您的首选编辑器1才会启动:
  • 合并开始成功(即索引不处于坏状态,并且git merge的参数都已成功解析)。
  • Git找到或创建了一个合并基础。较旧版本的Git将空树用作无关历史的合并基础;新版本需要使用--allow-unrelated-histories
  • 因此找到的合并基础不是当前(HEAD)提交,或者您使用了--no-ff
  • 传递给git merge的其他提交不是当前提交的祖先。(对于章鱼合并,规则有点更复杂,但差异应该足够明显:有多个“其他提交”)
  • 合并成功而没有冲突,或者通过扩展策略选项(-X ours-X theirs)解决了所有低级别冲突。(请注意,-s ours策略永远不会出现冲突,在那里这个约束是无用的。)
  • 您没有通过-m和合并消息参数(或者添加了--edit;请参见本答案下面的评论)。
  • 您没有指定--no-commit(由于合并提交被创建,因此调用编辑器!)。

因此,通过使用-m传递合并消息,您可以保证Git不会运行您的首选编辑器。另外,您可以故意不传递合并消息,但提供一个环境GIT_EDITOR设置,该设置调用其他实际编辑器以外的内容。例如:

GIT_EDITOR=true git merge --quiet $hash

如果合并本身存在冲突,或者根本没有开始,--no-commit将在不打开编辑器的情况下运行合并直至完成或失败。正如丹尼尔H在下面的评论中指出的那样。

除了这些技巧之外,也可以直接调用git merge-recursivegit stash脚本就是这么做的。但要小心使用此技巧,因为它绕过了许多通常的安全检查。


1您的首选编辑器由以下各种设置中的第一个定义:

  • 环境变量中的$GIT_EDITOR
  • 通过git config配置的core.editor
  • 编译的默认编辑器

使用git var GIT_EDITOR可以告诉您选择或默认的编辑器。


如果可能的话,它实际上会执行合并操作吗? - Daniel H
@DanielH:是的。您可以选择在分离的 HEAD 上执行操作,或在新分支上执行操作;或者您可以将其重置。您还必须小心快进,因为如果 git merge 选择将合并作为快进操作执行,则 HEAD 需要返回到 HEAD@{1}ORIG_HEAD,而不仅仅是 HEAD^1 - torek
我不知道--no-commit如何与返回代码交互,但这可能更符合所需的行为,并且似乎有--no-edit可以替代使用GIT_EDITOR=true,但这是不被推荐的。 - Daniel H
那么,考虑到 OP 的意图,使用 --no-commit 和/或 -m 应该可以防止默认编辑器打开? - Alexander Mills
@AlexanderMills:是的。根据您想要保留原始Git存储库的程度,您可能需要提供临时索引甚至临时工作树。 - torek
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接