简体中文 ▾ 主题 ▾ 最新版本 ▾ git-bisect 上次更新于 2.52.0

名称

git-bisect - 使用二分查找找到引入错误的提交

概要

git bisect start [--term-(bad|new)=<term-new> --term-(good|old)=<term-old>]
		   [--no-checkout] [--first-parent] [<bad> [<good>…​]] [--] [<pathspec>…​]
git bisect (bad|new|<term-new>) [<rev>]
git bisect (good|old|<term-old>) [<rev>…​]
git bisect terms [--term-(good|old) | --term-(bad|new)]
git bisect skip [(<rev>|<range>)…​]
git bisect next
git bisect reset [<commit>]
git bisect (visualize|view)
git bisect replay <logfile>
git bisect log
git bisect run <cmd> [<arg>…​]
git bisect help

描述

此命令使用二分查找算法来查找项目中引入错误的提交。您首先需要告知它一个已知包含错误的“坏”提交,以及一个已知在此错误引入之前存在的“好”提交。然后 git bisect 会选择这两个端点之间的某个提交,并询问您该提交是“好”还是“坏”。它会继续缩小范围,直到找到引入更改的确切提交。

实际上,git bisect 可以用来查找更改了您项目中任何属性的提交;例如,修复了错误的提交,或者导致基准测试性能提高的提交。为了支持这种更通用的用法,可以使用“旧”和“新”这两个术语来代替“好”和“坏”,或者您可以选择自己的术语。有关更多信息,请参见下面的“替代术语”部分。

基本 bisect 命令:start、bad、good

例如,假设您正在尝试查找在您的项目版本 v2.6.13-rc2 中已知可以正常工作的某个功能被破坏的提交。您将如下启动一个 bisect 会话:

$ git bisect start
$ git bisect bad                 # Current version is bad
$ git bisect good v2.6.13-rc2    # v2.6.13-rc2 is known to be good

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

Bisecting: 675 revisions left to test after this (roughly 10 steps)

您现在应该编译已检出的版本并进行测试。如果该版本能正常工作,请键入:

$ git bisect good

如果该版本有问题,请键入:

$ git bisect bad

然后 git bisect 会响应类似以下内容:

Bisecting: 337 revisions left to test after this (roughly 9 steps)

请继续重复这个过程:编译代码树,测试它,然后根据它是好是坏,运行 git bisect goodgit bisect bad 来请求需要测试的下一个提交。

最终将没有更多修订版本需要检查,命令将打印出第一个坏提交的描述。引用 refs/bisect/bad 将会指向该提交。

Bisect reset

在 bisect 会话之后,要清理 bisection 状态并返回到原始 HEAD,请发出以下命令:

$ git bisect reset

默认情况下,这将把您的代码树恢复到 git bisect start 之前检出的提交。(一个新的 git bisect start 也会这样做,因为它会清理旧的 bisection 状态)。

使用可选参数,您可以返回到不同的提交:

$ git bisect reset <commit>

例如,git bisect reset bisect/bad 将检出第一个坏修订版本,而 git bisect reset HEAD 将让您停留在当前的 bisection 提交上,并且完全避免切换提交。

替代术语

有时您寻找的不是引入了错误的提交,而是导致了某个“旧”状态与“新”状态之间发生变化的提交。例如,您可能正在寻找引入特定修复的提交。或者您可能正在寻找源文件名最终都转换为公司命名标准的第一个提交。或者其他任何情况。

在这些情况下,使用“好”和“坏”来指代“变化之前”和“变化之后”的状态可能会非常令人困惑。因此,您可以分别使用“旧”和“新”这两个术语来代替“好”和“坏”。(但请注意,您不能在一个会话中混合使用“好”和“坏”与“旧”和“新”)。

在这种更通用的用法中,您向 git bisect 提供一个具有某种属性的“新”提交和一个不具有该属性的“旧”提交。每次 git bisect 检出一个提交时,您都会测试该提交是否具有该属性。如果具有,则将该提交标记为“新”;否则,将其标记为“旧”。当 bisection 完成后,git bisect 将报告哪个提交引入了该属性。

要使用“旧”和“新”而不是“好”和“坏”,您必须在不带参数的情况下运行 git bisect start,然后运行以下命令来添加提交:

git bisect old [<rev>]

表示该提交在所寻找的更改之前,或者

git bisect new [<rev>...]

表示在之后。

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

git bisect terms

您可以使用 git bisect terms --term-oldgit bisect terms --term-good 仅获取旧的术语;git bisect terms --term-newgit bisect terms --term-bad 可用于了解如何称呼比所寻找的更改更新的提交。

如果您想使用自己的术语而不是“坏”/“好”或“新”/“旧”,您可以选择任何喜欢的名称(除了现有的 bisect 子命令,如 resetstart 等) ,方法是使用以下命令启动 bisection:

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

例如,如果您正在寻找一个引入性能回归的提交,您可能会使用:

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 goodgit bisect bad 来标记提交。

Bisect visualize/view

要在 bisection 过程中使用 gitk 查看当前剩余的可疑提交,请发出以下命令 (子命令 view 可用作 visualize 的替代):

$ git bisect visualize

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

如果以上所有环境变量均未设置,则使用 git log。您还可以提供命令行选项,如 -p--stat

$ git bisect visualize --stat

Bisect log and bisect replay

在标记了修订版本为好或坏之后,请发出以下命令以显示到目前为止已执行的操作:

$ git bisect log

如果您发现自己在指定修订版本状态时犯了错误,可以将其输出保存到文件,编辑该文件删除不正确的条目,然后发出以下命令返回到已更正的状态:

$ git bisect reset
$ git bisect replay that-file

避免测试某个提交

如果在 bisect 会话期间,您知道建议的修订版本不是一个好的测试对象(例如,它无法构建,而您知道此失败与您正在跟踪的错误无关),您可以手动选择一个附近的提交并测试该提交。

例如

$ git bisect good/bad			# previous round was good or bad.
Bisecting: 337 revisions left to test after this (roughly 9 steps)
$ git bisect visualize			# oops, that is uninteresting.
$ git reset --hard HEAD~3		# try 3 revisions before what
					# was suggested

然后编译并测试选定的修订版本,之后以通常的方式将其标记为好或坏。

Bisect skip

您可以要求 Git 代替您手动选择一个附近的提交,方法是发出命令:

$ git bisect skip                 # Current version cannot be tested

但是,如果您跳过了一个紧邻您要查找的提交的提交,Git 将无法准确地判断哪个提交是第一个坏提交。

您还可以使用范围表示法跳过一系列提交,而不是只跳过一个提交。例如:

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

这告诉 bisect 过程,在 v2.5 之后,直到包含 v2.6 的所有提交,都不应该被测试。

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

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

这告诉 bisect 过程,v2.5v2.6 之间的提交(包括这两个提交)将被跳过。

Bisect next

通常,在标记一个修订版本为好或坏之后,Git 会自动计算并检出下一个要测试的修订版本。但是,如果您需要显式请求下一个 bisection 步骤,可以使用:

$ git bisect next

您可以使用此命令来恢复被中断的 bisection 过程,中断方式是检出另一个修订版本。

通过向 bisect start 提供更多参数来缩减 bisection 的范围

如果您知道要跟踪的问题涉及代码树的哪个部分,您可以通过在发出 bisect start 命令时指定路径名参数来进一步减少试错次数:

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

如果您预先知道多个好提交,可以在发出 bisect start 命令时,在坏提交之后立即指定所有好提交,从而缩小 bisect 的空间:

$ git bisect start v2.6.20-rc6 v2.6.20-rc4 v2.6.20-rc1 --
                   # v2.6.20-rc6 is bad
                   # v2.6.20-rc4 and v2.6.20-rc1 are good

Bisect run

如果您有一个可以判断当前源代码是好是坏的脚本,您可以通过发出命令来执行 bisect:

$ git bisect run my_script arguments

请注意,脚本(上面示例中的 my_script)在当前源代码为好/旧时应退出码 0,在当前源代码为坏/新时应退出码 1 到 127(包括 125)之间的值。

任何其他退出码都将中止 bisect 过程。应注意的是,通过 exit(-1) 终止的程序将退出码 $? = 255,(参见 exit(3) 手册页),因为该值会按 & 0377 进行截断。

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

您经常会发现,在 bisect 会话期间,您可能需要对正在测试的修订版本应用临时修改(例如,将 s/#define DEBUG 0/#define DEBUG 1/ 写入头文件,或者“不包含此提交的修订版本需要应用此补丁来规避另一个 bisect 不关心的其他问题”)。

为了应对这种情况,在内部 git bisect 找到下一个要测试的修订版本后,脚本可以在编译前应用补丁,运行实际测试,然后决定该修订版本(可能带有所需的补丁)是否通过了测试,最后再将代码树恢复到原始状态。最后,脚本应以实际测试的状态退出,以便 git bisect run 命令循环确定 bisect 会话的最终结果。

选项

--no-checkout

在 bisection 过程的每次迭代中,不检出新的工作树。而是仅更新名为 BISECT_HEAD 的引用,使其指向应被测试的提交。

当您在每一步执行的测试不需要检出的代码树时,此选项可能很有用。

如果仓库是裸仓库,则假定使用 --no-checkout

--first-parent

遇到合并提交时,仅跟随第一个父提交。

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

此选项在合并的分支包含损坏或无法构建的提交,但合并本身是正常的情况下,避免了误报,特别有用。

示例

  • 在 v1.2 和 HEAD 之间自动 bisect 一个损坏的构建

    $ git bisect start HEAD v1.2 --      # HEAD is bad, v1.2 is good
    $ git bisect run make                # "make" builds the app
    $ git bisect reset                   # quit the bisect session
  • 在 origin 和 HEAD 之间自动 bisect 一个测试失败

    $ git bisect start HEAD origin --    # HEAD is bad, origin is good
    $ git bisect run make test           # "make test" builds and tests
    $ git bisect reset                   # quit the bisect session
  • 自动 bisect 一个损坏的测试用例

    $ cat ~/test.sh
    #!/bin/sh
    make || exit 125                     # this skips broken builds
    ~/check_test_case.sh                 # does the test case pass?
    $ git bisect start HEAD HEAD~10 --   # culprit is among the last 10
    $ git bisect run ~/test.sh
    $ git bisect reset                   # quit the bisect session

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

    最好将 test.shcheck_test_case.sh 放在仓库之外,以防止 bisect、make 和 test 进程与脚本之间发生交互。

  • 自动 bisect 并应用临时修改(热修复)

    $ cat ~/test.sh
    #!/bin/sh
    
    # tweak the working tree by merging the hot-fix branch
    # and then attempt a build
    if	git merge --no-commit --no-ff hot-fix &&
    	make
    then
    	# run project specific test and report its status
    	~/check_test_case.sh
    	status=$?
    else
    	# tell the caller this is untestable
    	status=125
    fi
    
    # undo the tweak to allow clean flipping to the next commit
    git reset --hard
    
    # return control
    exit $status

    这会在每次测试运行前应用来自热修复分支的修改,例如,在您的构建或测试环境发生变化的情况下,旧版本可能需要一个新版本已经拥有的修复。(确保热修复分支基于一个包含在您正在 bisect 的所有修订版本中的提交,这样合并就不会引入太多内容,或者使用 git cherry-pick 而不是 git merge)。

  • 自动 bisect 一个损坏的测试用例

    $ git bisect start HEAD HEAD~10 --   # culprit is among the last 10
    $ git bisect run sh -c "make || exit 125; ~/check_test_case.sh"
    $ git bisect reset                   # quit the bisect session

    这表明,如果您将测试写在一行上,也可以不使用 run 脚本。

  • 在损坏的仓库中定位对象图的一个良好区域

    $ git bisect start HEAD <known-good-commit> [ <boundary-commit> ... ] --no-checkout
    $ git bisect run sh -c '
    	GOOD=$(git for-each-ref "--format=%(objectname)" 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                   # quit the bisect session

    在这种情况下,当 git bisect run 完成时,bisect/bad 将指向一个至少有一个父提交的对象图,该父提交的可达图是完全可遍历的,其方式符合 git pack objects 的要求。

  • 查找代码中的修复,而不是回归

    $ git bisect start
    $ git bisect new HEAD    # current commit is marked as new
    $ git bisect old HEAD~10 # the tenth commit from now is marked as old

    $ 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] 套件的一部分