简体中文 ▾ 主题 ▾ 最新版本 ▾ git-bisect 最后更新于 2.55.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

描述

此命令使用二分查找算法来找出项目历史中哪个提交引入了错误。使用方法是:首先告诉它一个已知包含该错误的“坏”(bad)提交,以及一个已知在错误引入之前的“好”(good)提交。然后 git bisect 会在这两个端点之间选择一个提交,并询问你该选定的提交是“好”还是“坏”。它会持续缩小范围,直到找到引入该变更的确切提交。

实际上,git bisect 可以用来查找改变了项目中任何属性的提交;例如,修复了错误的提交,或者导致基准测试性能提升的提交。为了支持这种更通用的用法,可以使用“旧”(old)和“新”(new)这两个术语代替“好”和“坏”,或者你也可以选择自己的术语。有关详细信息,请参阅下文的“替代术语”一节。

基本二分查找命令:start, bad, good

举个例子,假设你正在寻找破坏了某个功能的提交,而该功能在项目的 v2.6.13-rc2 版本中是正常的。你可以按照以下方式启动二分查找会话:

$ 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 会选择历史范围中间的一个提交,将其检出(checkout),并输出类似于以下的内容:

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)

在二分查找会话结束后,要清理二分状态并返回到原始的 HEAD,请执行以下命令:

git bisect reset

默认情况下,这会将你的代码树返回到执行 git bisect start 之前检出的提交。(一个新的 git bisect start 也会执行此操作,因为它会清理旧的二分查找状态。)

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

git bisect reset <commit>

例如,git bisect reset bisect/bad 将检出第一个坏修订版本,而 git bisect reset HEAD 将让你留在当前的二分查找提交上,并且完全不进行提交切换。

替代术语

有时你寻找的不是引入故障的提交,而是导致某种“旧”状态与“新”状态之间发生变化的提交。例如,你可能在寻找引入了特定修复的提交。或者你可能在寻找源代码文件名最终全部转换为公司命名标准后的第一个提交。诸如此类。

在这种情况下,使用“好”和“坏”来指代“变更前的状态”和“变更后的状态”会非常令人困惑。因此,你可以分别使用“旧”(old)和“新”(new)来代替“好”和“坏”。(但请注意,在同一个会话中不能混合使用“好/坏”与“旧/新”。)

在这种更通用的用法中,你为 git bisect 提供一个具有某种属性的“新”提交,以及一个不具备该属性的“旧”提交。每次 git bisect 检出一个提交时,你测试该提交是否具有该属性。如果具备,标记为“新”;否则,标记为“旧”。当二分查找完成后,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 来了解如何称呼比所寻找的变更更新的提交。

如果你想使用自己的术语而不是“坏/好”或“新/旧”,你可以通过在启动二分查找时使用以下命令来选择任何你喜欢的名称(不能使用现有的二分查找子命令,如 reset, start 等):

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)

要在 gitk 中查看当前剩余的可疑提交,请在二分查找过程中执行以下命令(子命令 view 可以作为 visualize 的替代品):

$ git bisect visualize

Git 通过各种环境变量检测图形环境:

DISPLAY

在 Unix 系统的 X Window 环境中设置。

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

避免测试某个提交

如果处于二分查找会话中,你发现建议的修订版本不适合测试(例如它无法编译,且你知道该故障与你要排查的错误无关),你可以手动选择附近的提交进行测试。

例如

$ 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

这告诉二分查找过程,不应测试 v2.5 之后到并包含 v2.6 的任何提交。

请注意,如果你也想跳过该范围的第一个提交,可以执行命令:

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

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

下一个二分查找 (Bisect next)

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

$ git bisect next

你可以在中断二分查找过程(通过检出不同的修订版本)后,使用此命令恢复二分查找。

通过为 bisect start 提供更多参数来缩短二分查找

如果你知道代码树的哪一部分涉及你正在追踪的问题,可以在执行 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 is bad
                   # v2.6.20-rc4 and v2.6.20-rc1 are good

运行二分查找 (Bisect run)

如果你有一个可以判断当前源代码是好是坏的脚本,可以通过执行以下命令进行二分查找:

git bisect run <cmd> [<arg>…​]

请注意,使用 <arg> 运行的 <cmd> 如果当前源代码是好/旧,应退出并返回代码 0;如果当前源代码是坏/新,应退出并返回 1 到 127 之间的代码(包含 1 和 127,但 125 除外)。

任何其他退出代码都会中止二分查找过程。需要注意的是,通过 exit(-1) 终止的程序会留下 $? = 255(参见 exit(3) 手册页),因为该值被 & 0377 截断了。

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

你可能会经常发现,在二分查找会话期间,你希望对正在测试的修订版本进行临时修改(例如,在头文件中 s/#define DEBUG 0/#define DEBUG 1/,或者“没有此提交的修订版本需要应用此补丁以绕过本次二分查找不关心的另一个问题”)。

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

选项

--no-checkout

不要在二分查找过程的每次迭代中检出新的工作树。而是只需更新名为 BISECT_HEAD 的引用,使其指向需要测试的提交。

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

如果是裸仓库(bare repository),则默认假设使用 --no-checkout

--first-parent

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

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

当合并的分支包含损坏或不可构建的提交,但合并本身没问题时,此选项在避免误报方面特别有用。

示例

  • 自动二分查找 v1.2 和 HEAD 之间损坏的构建

    $ 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 之间的测试失败

    $ 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
  • 自动二分查找损坏的测试用例

    $ 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 都放在仓库之外更安全,以防止二分查找、make 和测试进程与脚本之间发生干扰。

  • 自动二分查找带有临时修改(热修复)的情况

    $ 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

    这会在每次测试运行前应用来自热修复分支的修改,例如在你的构建或测试环境发生变化,导致旧修订版本需要新版本已经具备的修复的情况下。(确保热修复分支基于所有正在进行二分查找的修订版本中都包含的提交,这样合并就不会引入太多内容,或者使用 git cherry-pick 代替 git merge。)

  • 自动二分查找损坏的测试用例

    $ 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

    这表明如果你将测试写在一行上,就可以无需运行脚本。

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

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