Git
简体中文 ▾ Topics ▾ Latest version ▾ git-bisect last updated in 2.44.0

名称

git-bisect - 使用二进制搜索查找引入错误的提交

概述

git bisect <子命令> <选项>

描述

该命令采取不同的子命令,并根据子命令有不同的选项:

git bisect start [--term-{new,bad}=<term> --term-{old,good}=<term>]
	  [--no-checkout] [--first-parent] [<坏的> [<好的>...]] [--] [<路径规范>...]
git bisect (bad|new|<新 term>) [<修订>]
git bisect (good|old|<旧 term>) [<修订>...]
git bisect terms [--term-good | --term-bad]
git bisect skip [(<修订>|<范围>)...]
git bisect reset [<提交>]
git bisect (visualize|view)
git bisect replay <日志文件>
git bisect log
git bisect run <命令>...
git bisect help

这个命令使用二分搜索算法来查找项目历史中哪个提交引入了一个错误。使用该命令时,首先告诉它一个已知包含错误的 "坏" 提交,以及一个已知在错误出现之前的 "好" 提交。然后 git bisect 在这两个端点之间挑选一个提交,并询问你所选的提交是 "好" 还是 "坏"。它继续缩小范围,直到找到引入该修改的确切提交。

事实上,git bisect 可以用来查找改变项目 任何 属性的提交;例如,修复了一个 bug 的提交,或者使一个基准的性能得到改善的提交。为了支持这种更普遍的用法,可以用 "old" 和 "new" 来代替 "good" 和 "bad",或者您可以选择自己的术语。更多信息见下文 "替代术语 "一节。

基本的二分命令:start, bad, good

举个例子,假设你想找到破坏了一个已知在你的项目的 v2.6.13-rc2 版本中工作的特性的提交。你启动了一个 bisect 会话,如下所示:

$ git bisect start
$ git bisect bad                 # 当前版本是坏的
$ git bisect good v2.6.13-rc2    # v2.6.13-rc2 已知为好

一旦你指定了至少一个坏的和一个好的提交,git bisect 就会在这个历史范围的中间选择一个提交,检查它,并输出类似于以下的内容:

Bisecting: 675 revisions left to test after this (roughly 10 steps) | 在这之后,还有675次修订需要测试(大约10步)

你现在应该编译检出的版本并进行测试。如果该版本工作正常,请输入

$ git bisect good

如果该版本被破坏,请输入

$ git bisect bad

然后,`git bisect`会做出如下反应

Bisecting: 337 revisions left to test after this (roughly 9 steps) | 在这之后还有337个修订要测试(大约9个步骤)。

不断重复这个过程:编译树,测试它,根据它是好是坏,运行 git bisect goodgit bisect bad 来要求下一个需要测试的提交。

最终,将没有更多的修订可以检查,该命令将打印出第一个坏提交的描述。引用 refs/bisect/bad 将被留在该提交处。

重置二分查找

在一个二分查找会话之后,要清理二分查找状态并返回到原来的 HEAD,请发出以下命令:

$ git bisect reset

默认情况下,这将使你的树返回到 git bisect start 之前检查出来的提交。 (新的 git bisect start 也会这样做,因为它清理了旧的分界状态)

通过一个可选参数,你可以返回到一个不同的提交,而不是:

$ git bisect reset <提交>

例如,git bisect reset bisect/bad 会检查出第一个坏的修订,而 git bisect reset HEAD 会让你停留在当前的分界提交上,完全避免切换提交。

替代条款

有时,您要找的不是引入断裂的提交,而是在其他 "旧" 状态和 "新" 状态之间引起变化的提交。例如,你可能要找的是引入某项修复的提交。或者你想找的是第一次提交,在这次提交中,源代码文件名最终都被转换为你公司的命名标准。或者其他什么。

在这种情况下,用 “好” 和 “坏” 来指代 “变化前的状态” 和 “变化后的状态” 会非常混乱。因此,你可以用 “旧” 和 “新” 来代替 “好” 和 “坏”。(但要注意,你不能在一个会话中把 “好” 和 “坏” 与 “旧” 和 “新” 混在一起。)

在这种更普遍的用法中,你向 git bisect 提供了一个具有某种属性的 "新" 提交和一个不具有该属性的 "旧" 提交。每次 git bisect 检查一个提交时,都要测试该提交是否具有该属性。如果有,则将该提交标记为 "新";否则,标记为 "旧"。分割完成后,git bisect 会报告哪个提交引入了该属性。

要使用 "旧" 和 "新",而不是 "好" 和 "坏",你必须运行 git bisect start,而不以提交为参数,然后运行以下命令来添加提交:

git bisect old [<修订>]

表明提交时间在所寻求的变更之前,或

git bisect new [<修订>...]

以表明它是在之后。

要获得当前使用的术语的提醒,请使用

git bisect terms

你可以用 git bisect terms --term-oldgit bisect terms --term-good 只得到旧的(分别是新的)术语。

如果你想使用你自己的术语,而不是 "坏" / "好 " 或 "新" / "旧",你可以选择任何你喜欢的名字(除了现有的 bisect 子命令如 resetstart,…​…​),通过使用以下命令启动 bisection

git bisect start --term-old <旧 term> --term-new <新 term>

例如,如果你要找一个引入性能回归的提交,你可以用

git bisect start --term-old fast --term-new slow

或者,如果你要找的是修复了某个错误的提交,你可以使用

git bisect start --term-new fixed --term-old broken

然后,用`git bisect <term-old>` 和 git bisect <term-new> 代替 git bisect good 和`git bisect bad` 来标记提交。

视觉化/视图的二分查找

要查看 gitk 中当前剩余的嫌疑人,在二分查找过程中发出以下命令(子命令`view`可以用来替代`visualize`):

$ git bisect visualize

Git 通过各种环境变量检测图形环境:DISPLAY,在 Unix 系统的 X 窗口系统环境中设置。 SESSIONNAME (会话名),在 Cygwin 下的交互式桌面会话中设置。 MSYSTEM,在 Msys2 和 Git for Windows 下设置。 SECURITYSESSIONID,可在 macOS 上的交互式桌面会话中设置。

如果没有设置这些环境变量,则会使用 git log。 您也可以提供命令行选项,如 -p--stat

$ git bisect visualize --stat

二分查找日志和重放二分查找

在将修订标记为好的或坏的之后,发出以下命令来显示到目前为止所做的事情:

$ git bisect log

如果你发现你在指定修订版的状态时犯了错误,你可以把这个命令的输出保存到一个文件中,编辑它以删除不正确的条目,然后发出下面的命令来返回到一个正确的状态:

$ git bisect reset
$ git bisect replay that-file

避免测试一个提交

如果在二分查找会话中,你知道建议的修订版不是一个好的测试对象(例如,它不能构建,而你知道这个失败与你正在追寻的错误无关),你可以手动选择一个附近的提交,并测试该提交。

例如:

$ git bisect good/bad			# 上一轮是好是坏。
Bisecting: 337 revisions left to test after this (roughly 9 steps)
$ git bisect visualize			# 哎呀,真没意思。
$ git reset --hard HEAD~3		# 尝试 3 次修改,然后再
					# 建议

然后编译和测试所选择的修订版,之后以通常的方式将修订版标记为好或坏。

跳过二分查找

与其自己选择附近的提交,你可以通过发布命令让 Git 为你做这件事:

$ git bisect skip                                # 当前版本不能被测试

然而,如果你跳过了与你要找的提交相邻的提交,Git 将无法准确判断出哪个提交是第一个坏提交。

您也可以使用范围符号跳过一系列的提交,而不仅仅是一个提交。比如说:

$ git bisect skip v2.5..v2.6

这告诉二分查找进程,v2.5 之后的提交,包括 v2.6,都不应该被测试。

请注意,如果你也想跳过范围内的第一个提交,你会发出这个命令:

$ git bisect skip v2.5 v2.5..v2.6

这告诉二分查找进程,应该跳过 v2.5v2.6(包括)之间的提交。

通过提供更多的参数来削减二分查找的起点

如果你知道你要追踪的问题涉及到树的哪一部分,你可以通过在发出 bisect start 命令时指定路径参数来进一步减少试验的数量:

$ git bisect start -- arch/i386 include/asm-i386

如果你事先知道有一个以上的好的提交,你可以在发出 `bisect start ` 命令时,指定坏的提交之后的所有好的提交,从而缩小二分查找的空间:

$ git bisect start v2.6.20-rc6 v2.6.20-rc4 v2.6.20-rc1 --
                   # v2.6.20-rc6 是坏的
                   # v2.6.20-rc4 和 v2.6.20-rc1 都是好的

运行二分查找

如果你有一个脚本,可以知道当前的源代码是好是坏,你可以通过发出命令来进行二分查找:

$ git bisect run my_script arguments

注意,如果当前的源代码是好的或是旧的,脚本(上面例子中的 my_script)应该以代码 0 退出,如果当前的源代码是坏的或是新的,则以代码 1到 127(包括)之间退出,但 125 除外。

任何其他的退出代码都会中止二分查找进程。应该注意的是,通过 exit(-1) 终止的程序会留下 $? = 255,(见 exit(3) 手册页),因为这个值是用 & 0377 砍掉的。

当当前的源代码不能被测试时,应该使用特殊的退出代码 125。如果脚本以这个代码退出,当前的修订版将被跳过(见上面的 git bisect skip)。125 被选为用于此目的的最高合理值,因为 126 和 127 被 POSIX shells 用来表示特定的错误状态(127 表示未找到命令,126 表示找到命令但不可执行——这些细节并不重要,因为就 bisect run 而言,它们是脚本中的正常错误)。

你可能经常发现,在一个二分查找会话中,你想让临时的修改(例如,头文件中的 /#define DEBUG 0/#define DEBUG 1/,或者 “没有这个提交的版本需要应用这个补丁来解决这个查找不感兴趣的另一个问题”)应用到正在测试的版本中。

为了应对这种情况,在内部的 git bisect 找到要测试的下一个修订版后,脚本可以在编译前应用补丁,运行真正的测试,之后决定该修订版(可能有需要的补丁)是否通过测试,然后将树倒回原始状态。 最后,脚本应该以真实测试的状态退出,让 git bisect run 命令循环决定二分查找会话的最终结果。

选项

--no-checkout

在二分查找过程的每个迭代中,不要检出新的工作区。相反,只需更新一个名为 BISECT_HEAD 的引用,使其指向应被测试的提交。

当你在每个步骤中执行的测试不需要检查出来的目录树时,这个选项可能很有用。

如果版本库是裸仓库,则假定 --no-checkout

--first-parent

在看到合并提交时,只跟随第一个父提交。

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

这个选项在避免假阳性方面特别有用,当一个合并的分支包含有破损或不可构建的提交,但合并本身是正常的。

实例

  • 自动将 V1.2 和 HEAD 之间的破损构建二分查找:

    $ git bisect start HEAD v1.2 --      # HEAD 是坏的,V1.2 是好的
    $ git bisect run make                # "make" 构建应用程序
    $ git bisect reset                   # 退出二分会话
  • 在起源和HEAD之间自动二分查找失败测试:

    $ git bisect start HEAD origin --    # HEAD 是坏的,origin 是好的
    $ git bisect run make test           # "make test" 进行构建和测试
    $ git bisect reset                   # 退出二分会话
  • 自动二分查找错误的的测试用例:

    $ cat ~/test.sh
    #!/bin/sh
    make || exit 125                     # 跳过了错误构建
    ~/check_test_case.sh                 # 测试案例是否通过?
    $ git bisect start HEAD HEAD~10 --   # 罪魁祸首就在最后10个中
    $ git bisect run ~/test.sh
    $ git bisect reset                   # 退出二分查找会话

    这里我们使用一个 test.sh `自定义脚本。在这个脚本中,如果 `make 失败,我们就跳过当前的提交。 check_test_case.sh 如果测试用例通过,应该 exit 0,否则 exit 1

    如果 test.sh 和 `check_test_case.sh `都在仓库之外,以防止 bisect、make 和 test 进程与脚本之间的相互作用,这样会更安全。

  • 用临时修改的方式自动二分查找(热修复):

    $ cat ~/test.sh
    #!/bin/sh
    
    # 通过合并热修复来调整工作区
    # 然后尝试构建
    if	git merge --no-commit --no-ff hot-fix &&
    	make
    then
    	# 运行项目特定的测试并报告其状态
    	~/check_test_case.sh
    	status=$?
    else
    	# 告知发起进程这是不可测试的
    	status=125
    fi
    
    # 撤销调整,以便干净利落地翻转到下一次提交
    git reset --hard
    
    # 交回控制权
    exit $status

    这是在每次测试前应用热修复分支的修改,例如,如果你的构建或测试环境发生变化,老版本可能需要一个新版本的修复。(确保热修复分支是基于一个包含在所有修订版中的提交,这样合并时就不会拉入太多,或者使用 git cherry-pick 代替 git merge。)

  • 自动二分查找错误的的测试用例:

    $ git bisect start HEAD HEAD~10 --   # 罪魁祸首是最后 10 个
    $ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
    $ git bisect reset                   # 退出二分会话

    这表明,如果你把测试写在一行上,你可以不使用运行脚本。

  • 在受损的存储库中找到对象图的一个良好区域

    $ git bisect start HEAD <已知是好的提交> [ <边界提交> ... ] --no-checkout
    $ git bisect run sh -c '
    	GOOD=$(git for-each-ref "--format=%(对象名)" refs/bisect/good-*) &&
    	git rev-list --objects BISECT_HEAD --not $GOOD >tmp.$$ &&
    	git pack-objects --stdout >/dev/null <tmp.$$
    	rc=$?
    	rm -f tmp.$$
    	test $rc = 0'
    
    $ git bisect reset                   # 退出二分会话

    在这种情况下,当 git bisect run 完成时,bisect/bad 将指的是至少有一个父级的提交,其可达图在 git pack objects 的意义上是完全可穿越的。

  • 在代码中寻找修复而不是回归的方法

    $ git bisect start
    $ git bisect new HEAD    # 当前提交被标记为新提交
    $ git bisect old HEAD~10 # 从现在开始的第十次提交被标记为旧

    或:

$ git bisect start --term-old broken --term-new fixed
$ git bisect fixed
$ git bisect broken HEAD~10

获得帮助

使用 git bisect 获得简短的使用说明,使用 git bisect helpgit bisect -h 获得长的使用说明。

GIT

属于 git[1] 文档

scroll-to-top