如何让git bisect忽略已合并的分支?

46
我知道 git bisect 是设计为支持分支的,所以在好的提交 G 和坏的提交 B 之间如果合并了一个分支,它也需要考虑这些更改,因为错误可能包含在分支中。
在我的情况下,我有一个依赖项作为一个侧分支,并且我定期从中合并更改到我的主项目中。这个依赖项可以被视为一个库,它有一种不同的运行方式、不同的构建系统等等,但我仍然希望通过合并从它那里获得最新的更改到主分支。
问题是,在这种情况下进行二分查找时,你会在依赖项提交的提交中遇到无法编译的提交。
我真的只想在执行二分查找时将每个分支合并视为单个提交。
到目前为止,我发现的一种解决方法是使用 git log --first-parent 制作有效提交 G..B 的列表,然后在执行二分查找时,如果当前提交不在该列表中,则执行 git bisect skip。尽管这需要很多时间(每个跳过都需要检出/更改很多文件)。
所以问题是:有没有办法在 git bisect 中实现 --first-parent 或提供一份我认为有效的提交列表,以避免检出我已经知道无法编译的分支?我们如何只检查图表中标记为 o 的提交?
G---o---o---o---o---o---o---B  主项目分支
   /       /       / 
  x---x---x---x---x            依赖项
           \ /
            x'                 依赖项项目任务分支
编辑:为了清晰起见添加了图表。

在我看来,你不应该将依赖项放在分支中,而是应该使用子模块或只包含修订 ID 或版本号的普通文件。这样可以避免这个特定的问题。 - Robin Green
2
我同意,在这种情况下,它们实际上是共享很多代码的类似项目。可能有比分支更好的组织方式,但我仍然对给定问题的解决方案感兴趣。 - tddtrying
发布了我自己找到的解决方法,但希望看到更好的解决方案。 - tddtrying
2
git bisect --first-parent 将很快与 Git 2.29 一起推出。请参见我的下面的答案 - VonC
10个回答

17
有:自 Git 2.29(2020年第四季度)起,"git bisect" (man) 学会了"--first-parent"选项,以在第一个父级链中找到第一个断点。
请参见 commit ad464a4commit e8861ffcommit be5fe20commit 0fe305acommit 15a4802(由 Aaron Lipman (alipman88) 于07 Aug 2020提交)。 (由Junio C Hamano -- gitster --于2020年8月17日合并至commit 47f0f94

bisect:引入first-parent标志

签名作者:Aaron Lipman

Upon seeing a merge commit when bisecting, this option may be used to follow only the first parent.

In detecting regressions introduced through the merging of a branch, the merge commit will be identified as introduction of the bug and its ancestors will be ignored.

This option is particularly useful in avoiding false positives when a merged branch contained broken or non-buildable commits, but the merge itself was OK.

git bisect [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]

git bisect现在在其手册页中包含以下内容:

--first-parent

仅跟随合并提交的第一个父提交。

在检测通过合并分支引入的回归时,合并提交将被识别为引入错误的提交,其祖先将被忽略。

当合并的分支包含损坏或不可构建的提交,但合并本身是正确的时,此选项特别有用,可以避免误报。


这个功能已经测试过了吗?Git 2.29还没有发布,我试图在Ubuntu 16上构建47f0f94bc7(该功能分支的合并提交),结果出现“git:'bisect'不是一个git命令。” - Joel Gibson
1
@JoelGibson 是的,它有。并且您需要将 <git 的安装路径>/usr/libexec/git-core 添加到您的 $PATH 中。 - VonC
1
这是现在最好的答案。 - hraban
你需要在每个git bisect goodgit bisect bad命令中添加--first-parent,还是只需要在git bisect start命令中添加? - rjmunro
2
@rjmunro 只有 git bisect start 命令支持 --first-parent 选项。 - VonC

14

我想到了一个可能的解决方案,但仍希望找到更优雅的解决方案:

将所有合并到主分支的二级父提交标记为good

将每个合并请求的所有远程父提交标记为good,将会将它们之前的所有提交都视为good(这样就可以在二分查找中跳过它们)。该解决方案也应足够通用,以处理来自多个分支的多次合并,仅留下主分支上的提交。

git rev-list --first-parent --merges --parents GOOD..BAD \
| sed 's/^[^ ][^ ]* [^ ][^ ]* //' \
| xargs git bisect good

sed中的正则表达式会删除每行的前两个提交; 合并提交本身和第一个父提交,留下其他父提交(通常只剩第二个父提交)。

根据问题中所述的历史记录,运行这个一行代码会得到:

G---o---o---o---o---o---o---B  主项目分支
   /       /       / 
  G---x---G---x---G            依赖项
           \ /
            x'                 依赖项项目任务分支

这将使二分查找仅遍历主分支上的提交:

    o---o---o---o---o---o

如果合并的任何分支间接导致了问题,则通过bisect测试合并提交时会发现该问题,这可能是进一步在该分支上进行调查的原因。


12

我也一直在寻找类似的东西。据我所知,git rev-list --bisect --first-parent 看起来可以实现你想要的功能,而 rev-list 的文档则暗示了 --bisect 选项是 bisect 在内部使用的选项,但让 git bisect 添加这个标志到它对 rev-list 的调用中似乎不太容易:

bisect 命令由一个名为 git-bisect 的 shell 脚本实现,它又使用内置命令 bisect--helper 来实际执行有趣的部分(评论中说是“计算、显示和检出”...),显然基于 .git/. 中一堆魔法状态文件。似乎是 rev-list 命令正在重用 bisect--helper 中的代码,而不是你可能期望的反过来。

因此,我认为你需要扩展 bisect--helper 代码的提交筛选功能来做到这一点。

作为一种解决方法,可能会像这样工作:在 bisect 为您检查完毕后,使用 git rev-list --bisect --first-parent 重置到不同的检出,测试并标记为 good/bad/skip,然后从那里继续。


1
起初,这个解决方法似乎有很多手动步骤,但后来发现只需要在每次二分建议的检出之后执行相同的一行命令,所以并不是一个坏的解决方案。 - tddtrying
8
在 Git 2.4.0 版本中,“git rev-list --bisect --first-parent” 将不再起作用:https://github.com/git/git/commit/f88851c6376f0b2a4cf87c061a848e4ae4438e0a - VonC

11
如果历史记录看起来像:
A - B - C - H - I - J - K - L
         \              /
          D - E - F - G
在这个记录中,L是坏的,B是好的,你想忽略DEFG分支,那么运行以下命令似乎可以达到你的目的:
$ git bisect start
$ git bisect skip $( git rev-list G ^C )
$ git bisect bad L
$ git bisect good B
其中B、C、G和L分别是它们对应的commit shas。

2
在我们的情况下,合并只是单向的,即进入我们的主分支,并且可能会发生多次合并,可能会有许多本地任务分支在侧分支上。在这种情况下,它变得有点棘手。 - tddtrying
如果你有20个合并,你需要手动执行整个命令$ git bisect skip $(git rev-list G ^C) ,因为你必须获取第二个父提交的前导提交,并排除从分支首次出现的提交可达的任何提交。我考虑使用Python创建一个git rev-list --first-parent <good>..<bad>集合。将其称为setA。还有另一个集合git rev-list <good>..<bad>。将其称为setB。然后setB - setA = 我们要跳过的内容。 - solstice333
3
我已将此转化为脚本,并上传至GitHub:https://github.com/marczych/git-first-parent-bisect感谢您的启发! - marczych

9

您可以使用嫁接工具使git处理您的历史记录呈线性状态。要将整个第一个父节点历史记录线性化,您可以使用以下命令:

git rev-list --first-parent --merges --parents HEAD | cut -d' ' -f1,2 > .git/info/grafts

完成二分法后,只需放下嫁接文件。


你在测试时使用的是哪个版本的git?在v1.7.2.5中似乎无法正常工作。即使使用了grafts,git bisect也会抱怨合并提交,并拒绝让你跳过或标记它为坏的。 - user153275
1
@dpk我不记得当时用的是哪个版本,但我刚刚尝试了v1.7.2.5,并且它完全正常。我想知道你是如何让bisect首先抱怨合并提交的。它通常可以处理它们,我甚至运行了测试,以便最终合并提交变成坏的一方。您能提供重现问题的说明吗? - Björn Steinbrink
多尴尬啊,我现在无法复制它。 - user153275

4
Björn Steinbrink的解决方案非常好,但最近开始打印以下内容:
提示:支持/info/grafts已弃用
提示:并将在未来的Git版本中删除。
提示:
提示:请使用“git replace --convert-graft-file”
提示:将嫁接转换为替换引用。
提示:
提示:通过运行关闭此消息
提示:“git config advice.graftFileDeprecated false”
这是他的解决方案的现代化版本,使用“git replace”代替嫁接:
git rev-list --first-parent --merges --parents HEAD | \
  while read COMMIT PARENT1 PARENT2; do 
    git replace --graft $COMMIT $PARENT1; 
  done
不幸的是,对于大型仓库,速度要慢得多(对于约150k个提交,需要约3分钟); git replace似乎还没有批量模式。 您可能希望将rev-list限制为仅涉及二分查找的范围内的提交。
完成后要删除替换,请使用rm .git/refs/replace/*

2
因此,假设合并提交的第一个父分支始终是相同的分支并不总是正确的。例如,如果您在一个主题分支上进行操作,并将其与主分支合并以保持最新状态(因此对于此合并提交,第一个父分支是主题分支),然后切换到主分支并将主题分支合并回去,您会得到一个快进合并,它只是将主分支移动到具有第一个父分支作为您的主题分支的合并提交。这可能看起来牵强,但实际上是非常正常的工作流程 - 我总是将主分支合并到我的分支中,以便我的合并回到主分支将是一个微不足道的合并(即可快进)。 (抱歉詹姆斯,总是忘记重新基础)。
我发现有一种方法可以帮助确定哪个父分支是您的分支 - 合并提交注释本身。默认情况下,git组成了一个合并提交注释,说明合并了哪个分支,您可以使用此来推断您感兴趣的父分支是哪个分支,只要执行合并提交的人没有覆盖此合并提交注释。
所以我尝试了一下,对我来说似乎有效。我编写了一个Python脚本来帮助完成此操作在github上。如果您运行此脚本,它将尝试向后跟踪并跟随您的分支,并发出合并到您的分支中的分支末端的提交ID列表。有了这个列表,您可以将它们提供给“git bisect good”,bisect将从您的二分法中省略所有合并分支上的提交,从而实现所需的结果。

2
您可以通过运行以下命令来指示git-bisect仅遍历合并提交的第一个父级:
```bash git bisect start --first-parent ```
git bisect skip $(comm -23 <(git rev-list G | sort) <(git rev-list --first-parent G | sort))

这里的G是什么? - Christopher
1
G是已知为好的提交,正如原问题的图表所示。 - kazuho

1
你可能可以使用 git bisect start --no-checkout 来避免实际上将提交检出到工作树中。然后,我猜想你可以对你实际想要测试的提交执行 git checkout BISECT_HEAD(即只在主分支上进行第一个父提交)。我没有尝试过,但希望这样可以起作用。

1

我没有看到一步方法,但是根据您当前的解决方案: git bisect skip可以跳过要跳过的提交列表。 git log branchname将列出分支branchname上的提交。 因此,这应该让您指定提交列表。

如果您的依赖项和主要代码位于不同的文件系统空间中,则可以使用git bisect start指定要包含的路径。根据您的代码布局,这可能是最佳选项。(如果您有可能包含错误的文件列表,几乎肯定是最佳选项!)

man page有详细信息;那里的“另请参阅”也是有趣的阅读材料。


1
在这种情况下,文件系统空间重叠,但通常指定路径会有很大帮助。 - tddtrying

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