如何使用git bisect?

637

我读了一些文章,说git bisect很棒。 然而,我不明白它为什么很棒。

请有经验的人给出一些代码示例来演示:

  1. 如何使用它?
  2. 它只是像svn blame吗?

2
@01:正如Git书所说:“通过对项目历史记录进行暴力搜索”。 - eckes
25
不那么“野蛮”:它使用二分查找。 - cojocar
2
"git blame"类似于"svn blame"。而"git bisect"则是完全不同的东西。 - William Pursell
1
说句实话,在《Pro Git》中有一个很好的 bisect 描述, Sylvain 的回答也很不错。如果你看了这些还是不明白,我建议你提出更具体的问题。泛泛而谈的问题得到的是笼统的答案。 - Cascabel
6
更新的书籍链接为:http://git-scm.com/book/zh/v2/Git-工具-使用-Git-进行调试#二分查找。 - gdelfino
显示剩余2条评论
7个回答

860

git bisect 的思想是在历史记录中执行二分查找,以查找特定的回归。想象一下您有以下的开发历史:

... --- 0 --- 1 --- 2 --- 3 --- 4* --- 5 --- current

您知道在当前版本中,您的程序无法正常工作,而在版本0中却能够正常工作。因此,回归很可能是在提交1、2、3、4、5或者current之一引入的。

您可以尝试检出每个提交,构建它们,并检查回归是否存在。如果有大量提交,这可能需要很长时间。这是一次线性搜索。我们可以通过进行二分搜索来做得更好。这就是git bisect命令所做的事情。每一步它都试图将可能是坏的修订版本数量减少一半。

您可以像这样使用该命令:

$ git stash save
$ git bisect start
$ git bisect bad
$ git bisect good 0
Bisecting: 2 revisions left to test after this (roughly 2 steps)
[< ... sha ... >] 3

执行这个命令后,git会检出一个提交记录。在我们的情况下,它将是提交记录3。你需要构建程序,并检查回归是否存在。你还需要用git bisect bad告诉git此修订版的状态,如果回归存在,或用git bisect good说明不存在。

假设回归是在提交记录4引入的。那么,在此修订版中回归不存在,我们要通知git

$ make
$ make test
... ... ...
$ git bisect good
Bisecting: 0 revisions left to test after this (roughly 1 step)
[< ... sha ... >] 5

然后它将检出另一个提交。 要么是4,要么是5(因为只有两个提交)。 假设它选择了5。 构建后,我们测试程序并发现回归存在。 然后我们告诉git

$ make
$ make test
... ... ...
$ git bisect bad
Bisecting: 0 revisions left to test after this (roughly 0 steps)
[< ... sha ... >] 4

我们测试了最新的版本4,因为它是引入回归的版本,所以我们告诉git

$ make
$ make test
... ... ...
$ git bisect bad
< ... sha ... > is the first bad commit
< ... commit message ... >
在这种简单的情况下,我们只需要测试3个版本 (3, 4, 5),而不是4个版本 (1, 2, 3, 4)。虽然这只是一个小优势,但这是因为我们的历史记录很少。如果搜索范围是N个提交,则使用git bisect应该预期测试1 + log2 N个提交,而不是使用线性搜索测试大约N / 2个提交。
一旦找到引入回归的提交,您可以研究它以找到问题所在。完成后,您可以使用git bisect reset命令将一切恢复到使用git bisect命令之前的原始状态。

14
我要在这里提出异议,虽然这是bisect的很好的解释,但它并没有帮助我使用它。特别是因为我已经找到了一个好的提交,现在我就在那个分支上。从这个位置来看,这个解释一点也不有用。我该怎么指定坏的分支而又不用检出它呢?例如。 - PandaWood
7
你可以使用 git bisect bad <rev> [<rev>...] 命令将特定的提交标记为有问题的(或者使用 git bisect good <rev> [<rev>...] 将其标记为好的)。其中,rev 可以是任意的提交标识符,如分支名称、标签、提交哈希值(或其唯一前缀),等等。 - Sylvain Defresne
60
完成后,您需键入 git bisect reset 将所有内容还原到最近的提交状态。 - peetonn
23
在Stack上最牛的答案之一,阐述得非常清晰。我多年来一直手动执行这个过程,只是在一个好的和坏的提交之间选择一个任意的中间点,然后再根据它本身是否好/坏来选择再次位于好/坏之间的另一个点。这一直是一个极大的烦恼,我甚至从未听说过这个git子命令,直到今天...哈哈哈 - Chev
它对PHP项目有什么用处?如果有的话? - Nemoden
3
@Nemoden,是的,基本上它可以用于任何类型的项目。您只需要将“make test”步骤替换为“部署网站并重现问题”。 - alex.b

245

git bisect run 自动二分

如果您有一个自动化的./test脚本,当测试通过时退出状态为0,您可以使用bisect run自动查找错误:

git checkout KNOWN_BAD_COMMIT
git bisect start

# Confirm that our test script is correct, and fails on the bad commit.
./test
# Should output != 0.
echo $?
# Tell Git that the current commit is bad.
git bisect bad

# Same for a known good commit in the past.
git checkout KNOWN_GOOD_COMMIT
./test
# Should output 0.
echo $?
# After this, git automatically checks out to the commit
# in the middle of KNOWN_BAD_COMMIT and KNOWN_GOOD_COMMIT.
git bisect good

# Bisect automatically all the way to the first bad or last good rev.
git bisect run ./test

# End the bisect operation and checkout to master again.
git bisect reset

当然,这假设测试脚本./test是git跟踪的,并且在二分期间不会在某个早期提交中消失。

我发现很多时候你可以通过将树内脚本复制到树外,并可能玩弄类似于PATH的变量,从而逃避这种情况,并从那里运行它。

当然,如果test依赖的测试基础设施在旧提交上出现问题,则没有解决方案,您将不得不手动处理,逐个决定如何测试提交。

然而,我发现使用这种自动化通常有效,并且对于慢速测试位于任务后备中的情况可以节省大量时间,您可以让它在夜间运行,并可能在第二天早上确定您的错误,这值得一试。

更多提示

在二分后保留在第一个失败的提交上,而不是返回到master

git bisect reset HEAD

start + 初始的一次完成:

git bisect start KNOWN_BAD_COMMIT KNOWN_GOOD_COMMIT~

是等同于:

git checkout KNOWN_BAD_COMMIT
git bisect start
git bisect bad
git bisect good KNOWN_GOOD_COMMIT

查看目前已经测试过的内容(通过手动goodbad或者run):

git bisect log

示例输出:

git bisect log
git bisect start
# bad: [00b9fcdbe7e7d2579f212b51342f4d605e53253d] 9
git bisect bad 00b9fcdbe7e7d2579f212b51342f4d605e53253d
# good: [db7ec3d602db2d994fe981c0da55b7b85ca62566] 0
git bisect good db7ec3d602db2d994fe981c0da55b7b85ca62566
# good: [2461cd8ce8d3d1367ddb036c8f715c7b896397a5] 4
git bisect good 2461cd8ce8d3d1367ddb036c8f715c7b896397a5
# good: [8fbab5a3b44fd469a2da3830dac5c4c1358a87a0] 6
git bisect good 8fbab5a3b44fd469a2da3830dac5c4c1358a87a0
# bad: [dd2c05e71c246f9bcbd2fbe81deabf826c54be23] 8
git bisect bad dd2c05e71c246f9bcbd2fbe81deabf826c54be23
# bad: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05] 7
git bisect bad c536b1b7242d5fcf92cd87e9a534bedb1c0c9c05
# first bad commit: [c536b1b7242d5fcf92cd87e9a534bedb1c0c9c0

在 Git 日志中显示好的和坏的引用,以更好地了解时间概念:

git log --decorate --pretty=fuller --simplify-by-decoration master

这只显示具有相应引用的提交,从而大大减少了噪音,但包括以下类型的自动生成的引用:

refs/bisect/good*
refs/bisect/bad*

告诉我们哪些提交被标记为好或坏。

如果您想尝试该命令,请考虑 此测试存储库

失败很快,成功很慢

有时候:

  • 失败很快,例如最初的某个测试失败了
  • 成功需要一段时间,例如修复的测试通过,以及所有其他我们不关心的测试都通过了

对于这些情况,例如假设失败总是在5秒内发生,并且如果我们懒得使测试更具体,因为我们真的应该这样做,我们可以使用 timeout,如下所示:

#!/usr/bin/env bash
timeout 5 test-command
if [ $? -eq 1 ]; then
  exit 1
fi

这个代码有效是因为timeout退出124,而test-command失败退出1

神奇的退出状态

git bisect run对退出状态有点挑剔:

  • 任何大于127的值都会导致二分法失败,出现以下类似的错误信息:

    git bisect run failed:
    exit code 134 from '../test -aa' is < 0 or >= 128
    

    特别地,C语言中的assert(0)会导致SIGABRT并以状态码134退出,非常令人烦恼。

  • 125是一个神奇的数字,可以通过git bisect skip跳过运行。

    这样做的目的是帮助跳过由于不相关原因而导致的构建失败。

有关详细信息,请参阅man git-bisect

因此,您可能想使用类似以下的内容:

#!/usr/bin/env bash
set -eu
./build
status=0
./actual-test-command || status=$?
if [ "$status" -eq 125 ] || [ "$status" -gt 127 ]; then
  status=1
fi
exit "$status"

已在git 2.16.1上进行测试。


7
当回退/二分回到之前的错误版本(没有您新编写的测试)时,git如何知道保留您的新测试? - thebjorn
8
@thebjorn你说得有道理:据我所知,要么测试必须在PATH中的外部可执行文件中,要么在存储库中未跟踪的文件中。 在许多情况下,这是可能的:将测试放在单独的文件中,使用精心制作的“test_script”+模块化测试套件包含必要的测试样板,并在二分时从单独的文件运行它。 当您进行修复时,请将测试合并到主测试套件中。 - Ciro Santilli OurBigBook.com
2
使用“git bisect run”有很多出错的可能性,例如看到一个好的提交被错误合并所撤销。它进入、退出、再次进入和退出,只有最后一次“退出”是错误的。但是,您始终可以手动执行“git bisect”。因为这是一个二分查找,所以只需要几个步骤,例如在10步中查找1024个提交。 - combinatorist
1
@combinatorist,你说得对,它可能会失败。我发现当测试需要很长时间才能完成,并且我非常确定测试系统不会崩溃时,bisect run特别有用。这样,我就可以让它在后台运行,或者如果它占用太多资源而需要过夜时,我也不会失去任何大脑上下文切换的时间。 - Ciro Santilli OurBigBook.com
1
@jcarlosweb 通常情况下,你应该先进行“重置”,然后再进行修复,因为如果你在旧版本上进行修复,可能会与主分支产生合并冲突。 - Ciro Santilli OurBigBook.com
显示剩余2条评论

178

简述

开始:

$ git bisect start
$ git bisect bad
$ git bisect good <goodcommit>

或者

$ git bisect start
$ git bisect good
$ git bisect bad <badcommit>

二分:测试此后还剩下X个修订版本要测试(大约需要Y个步骤)

重复:

问题仍然存在吗?

  • 是:$ git bisect bad
  • 否:$ git bisect good

结果:

<abcdef> is the first bad commit

完成后:

git bisect reset

9
确保你在 git 仓库的根目录下运行命令,否则你会得到一个奇怪的错误提示:"You need to run this command from the toplevel of the working tree."。请注意不要改变原意。 - Paul Danelli
1
我在我的HEAD上犯了git错误,但在第一次提交时做得很好,当错误不存在时。那么接下来该怎么办?当bug不存在时,使用git bisect good移动到下一个提交吗? - Gobliins
@Gobliins 当 bug 不存在,且正确时,使用 git bisect good 命令进入下一个提交。 - Geoffrey Hale

49

补充一点:

在使用 git bisect start 命令时,我们可以指定文件名或路径,以防我们知道错误来自特定的文件。 例如, 假设我们知道导致回归的更改在 com/workingDir 目录中,那么我们就可以运行 git bisect start com/workingDir。这意味着只有更改了此目录内容的提交将被检查,这使得事情变得更快。

另外,如果很难确定某个提交是好还是坏,您可以运行 git bisect skip,这将忽略它。鉴于有足够的其他提交,git bisect 将使用其他提交来缩小搜索范围。


我真的很喜欢这个。往往我已经知道哪个文件有 bug,所以我只需要找出 bug 是在哪个提交中引入的。 - Aquarelle

16

$ git bisect .. 基本上是一个用于调试的Git工具。'Git Bisect'通过查找自上次(已知的)工作提交以来的先前提交来进行调试。它使用二分搜索来遍历所有这些提交,以找到引入回归/错误的那一个。

$ git bisect start # 开始二分查找

$ git bisect bad # 标记当前提交(v1.5)出现了回归/错误,将其设置为“坏点”

$ git bisect good v1.0 # 指定上次良好的工作提交(没有回归/错误)

这种标记“坏点”和“好点”的方法可以帮助git bisect(二分查找)选择中间元素(提交v1.3)。如果回归在提交v1.3中存在,则将其设置为新的“坏点”,即(Good -> v1.0 and Bad -> v1.3

$ git bisect bad

或者类似地,如果提交版本v1.3没有错误,您将将其设置为新的“好点”,即(*Good -> v1.3 and Bad -> v1.6)。

$ git bisect good

8
注意:术语“好”和“坏”不是您在标记提交时使用或不使用某个属性的唯一选项。
Git 2.7(2015年第四季度)引入了新的git bisect选项。
 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
                  [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]

在添加文档时:

有时候你不是在寻找引入破坏的提交,而是在寻找在某个其他“旧”状态和“新”状态之间引起变化的提交。

例如,你可能正在寻找引入特定修复的提交。
或者你可能正在寻找源代码文件名最终全部转换为公司命名标准的第一个提交。或者其他什么。

在这种情况下,使用“好”的和“坏”的术语来指代“变化之前的状态”和“变化之后的状态”可能会非常混乱。

因此,你可以分别使用“old”和“new”来代替“good”和“bad”。
(但请注意,在单个会话中不能混合使用“good”和“bad”与“old”和“new”)

在这种更一般的用法中,你向git bisect提供具有某些属性的“new”提交和没有该属性的“old”提交。

每次git bisect检出提交时,你测试该提交是否具有该属性:
如果有,将该提交标记为“new”;否则,将其标记为“old”。

当二分查找完成时,git bisect将报告引入该属性的提交。


请参考 提交 06e6a74提交 21b55e3提交 fe67687(由 Matthieu Moy (moy) 在 2015 年 6 月 29 日提交)以及 提交 21e5cfd(由 Antoine Delaite (CanardChouChinois) 在 2015 年 6 月 29 日提交)。
(由 Junio C Hamano -- gitster -- 在 2015 年 10 月 5 日的 提交 22dd6eb 合并)
请确保使用 Git 2.39(2022 年第四季度发布)进行 git bisect run:它包括修复了在 bisect-helper 中的回归错误,该错误错误地将给定给 'git bisect run' 命令的参数作为辅助程序的参数。 man 请参见 提交 e9011b6提交 464ce0a提交 58786d7(2022年11月10日)由Đoàn Trần Công Danh (sgn)提交。
(于2022年11月23日在提交提交e3d40fb中合并,由Junio C Hamano -- gitster --进行合并)

bisect--helper:使用OPT_SUBCOMMAND解析子命令

报告人:Lukáš Doktor
签名作者:Đoàn Trần Công Danh
签名作者:Taylor Blau

目前,我们正在使用OPT_CMDMODE解析子命令,即使找到命令,它仍将继续解析更多选项。当我们运行期望--log--no-log参数或其中一个"--bisect-..."参数的命令时,例如"git bisect run"(man)bisect--helper可能会错误地认为这些选项是bisect--helper的选项。在从git-bisect.sh调用时通过传递"--"并在bisect--helper中跳过该"--"可以解决这些问题。但是,它可能会干扰用户的"--"。让我们使用OPT_SUBCOMMAND解析子命令,因为该API是为此特定用例而生的。

0

git bisect 退出状态

Ciro Santilli在他2014年的回答中将它们称为“魔法退出状态”。

它们将在Git 2.36(2022年第二季度)中略有不同的使用方式:一个不太常见的错误是编写一个脚本来提供给 "git bisect"(man) 运行,但没有使其可执行,在这种情况下,即使是标记为好的修订版本也会出现126或127错误代码。

尝试识别这种情况并尽早停止迭代。

请查看提交 48af1fd, 提交 ba5bb81, 提交 8efa2ac, 提交 80c2e96 (2022年1月18日) 由René Scharfe (rscharfe)提交。
(由Junio C Hamano -- gitster --合并于提交 e828747, 2022年3月6日)

bisect--helper:在退出代码126和127上双重检查运行命令

签名作者:René Scharfe

当运行命令无法执行或找到时,shell 分别返回退出代码 126 或 127。
出于历史原因,有效的运行命令也允许返回这些代码以指示错误的修订版本。
这意味着拼写错误可能会导致虚假的二分查找运行,跨越整个距离并最终报告无效结果。
最好的解决方案是保留退出代码 126 和 127,就像 71b0251(Bisect run:,2007-10-26,Git v1.5.4-rc0 - merge)(Bisect run:“skip”当前提交,如果脚本退出代码为 125。, 2007-10-26)对于 125 所做的那样,并在获取它们时中止 bisect run
尽管如此,对于那些依赖于文档说明可以使用 126 和 127 来表示错误修订版本的人来说,这可能会不方便。
此补丁使用的解决方法是在已知良好的修订版本上运行命令,并在仍然获得相同错误代码时中止。
这增加了一步运行脚本的步骤,但仍然支持它们,只有一个例外:它不能与无法识别(手动标记的)已知良好修订版本的命令一起使用。
不受影响的是使用低退出代码的运行命令。
拼写错误在执行缺失命令两次和三个检出之后报告(第一步,已知良好的修订版本和回到第一步的修订版本)。

请参见示例


请参阅 https://github.com/git/git/commit/70d3dbfea9e9f59772d358796413725dac158599 (Git 2.40,2023年第一季度)。 - VonC

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