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

有人能演示一些代码示例:

如何使用它? 就像责备一样吗?


当前回答

git bisect背后的思想是在历史中执行二进制搜索以找到特定的回归。假设您拥有以下开发历史:

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

您知道您的程序在当前的修订版本中不能正常工作,它在修订版本0时工作。因此,回归很可能是在当前的1、2、3、4、5次提交中引入的。

你可以试着检查每个提交,构建它,检查回归是否存在。如果有大量的提交,这可能会花费很长时间。这是线性搜索。我们可以用二分法搜索。这就是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将检出提交。在我们的例子中,它是commit 3。您需要构建您的程序,并检查回归是否存在。你还需要告诉git这个修订的状态,如果回归存在,git平分为坏,如果没有,git平分为好。

让我们假设在提交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平分测试1 + log2n个提交,而不是用线性搜索粗略地测试N / 2个提交。

一旦找到了引入回归的提交,就可以研究它以找到问题所在。完成后,使用git bisect reset将所有内容恢复到使用git bisect命令之前的原始状态。

其他回答

Git对退出状态进行等分

Ciro Santilli在2014年给出的答案将其称为“神奇的退出状态”

在Git 2.36 (Q2 2022)中,它们的使用略有不同:一个不太常见的错误是编写了一个脚本,让“Git bisect”(man)运行而不使其可执行,在这种情况下,所有测试都会以126或127个错误代码退出,即使是在标记为良好的修订中。

试着认识到这种情况并尽早停止迭代。

参见commit 48af1fd, commit ba5bb81, commit 8efa2ac, commit 80c2e96 (18 Jan 2022) by René Scharfe (rscharfe)。 (由Junio C Hamano—gitster—在提交e828747中合并,2022年3月6日)

Bisect——helper:在退出码126和127上再次检查运行命令 署名:René Scharfe

When a run command cannot be executed or found, shells return exit code 126 or 127, respectively. Valid run commands are allowed to return these codes as well to indicate bad revisions, though, for historical reasons. This means typos can cause bogus bisect runs that go over the full distance and end up reporting invalid results. The best solution would be to reserve exit codes 126 and 127, like 71b0251 (Bisect run: , 2007-10-26, Git v1.5.4-rc0 -- merge) (Bisect run: "skip" current commit if script exit code is 125., 2007-10-26) did for 125, and abort bisect run when we get them. That might be inconvenient for those who relied on the documentation stating that 126 and 127 can be used for bad revisions, though. The workaround used by this patch is to run the command on a known-good revision and abort if we still get the same error code. This adds one step to runs with scripts that use exit codes 126 and 127, but keeps them supported, with one exception: It won't work with commands that cannot recognize the (manually marked) known-good revision as such. Run commands that use low exit codes are unaffected. Typos are reported after executing the missing command twice and three checkouts (the first step, the known good revision and back to the revision of the first step).

看到的例子。

博士TL;

开始:

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

Or

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

平分:在此之后留下X个修订进行测试(大约Y步)

重复一遍:

问题还存在吗?

是的:$ git平分坏 不:$ git平分好

结果:

<abcdef> is the first bad commit

当完成:

git bisect reset

注意:术语good和bad并不是唯一可以用于标记带有或不带有某个属性的提交的术语。

Git 2.7(2015年Q4)引入了新的Git平分选项。

 git bisect start [--term-{old,good}=<term> --term-{new,bad}=<term>]
                  [--no-checkout] [<bad> [<good>...]] [--] [<paths>...]

文档添加:

Sometimes you are not looking for the commit that introduced a breakage, but rather for a commit that caused a change between some other "old" state and "new" state. For example, you might be looking for the commit that introduced a particular fix. Or you might be looking for the first commit in which the source-code filenames were finally all converted to your company's naming standard. Or whatever. In such cases it can be very confusing to use the terms "good" and "bad" to refer to "the state before the change" and "the state after the change". So instead, you can use the terms "old" and "new", respectively, in place of "good" and "bad". (But note that you cannot mix "good" and "bad" with "old" and "new" in a single session.) In this more general usage, you provide git bisect with a "new" commit has some property and an "old" commit that doesn't have that property. Each time git bisect checks out a commit, you test if that commit has the property: If it does, mark the commit as "new"; otherwise, mark it as "old". When the bisection is done, git bisect will report which commit introduced the property.


参见Matthieu Moy (Moy)的commit 06e6a74, commit 21b55e3, commit fe67687(2015年6月29日)。 参见Antoine Delaite (CanardChouChinois)提交的21e5cfd(2015年6月29日)。 (由Junio C Hamano—gitster—在commit 22dd6eb中合并,2015年10月5日)


确保使用Git 2.39 (Q4 2022)进行Git bisect run:它包括修复bisect-helper中的回归,该回归错误地将给予' Git bisect run'(man)的命令的参数作为helper的参数。

参见commit e9011b6, commit 464ce0a, commit 58786d7 (10 Nov 2022) by Đoàn trn Công Danh (sgn)。 (由Junio C Hamano—gitster—在commit e3d40fb中合并,2022年11月23日)

bisect——helper:用OPT_SUBCOMMAND解析子命令 报告:Lukáš博士 署名:Đoàn trn Công Danh 署名:泰勒·布劳

As of it is, we're parsing subcommand with OPT_CMDMODE, which will continue to parse more options even if the command has been found. When we're running "git bisect run"(man) with a command that expecting a --log or --no-log arguments, or one of those "--bisect-..." arguments, bisect--helper may mistakenly think those options are bisect--helper's option. We may fix those problems by passing "--" when calling from git-bisect.sh, and skip that "--" in bisect--helper. However, it may interfere with user's "--". Let's parse subcommand with OPT_SUBCOMMAND since that API was born for this specific use-case.

再补充一点:

我们可以为git bisect start指定一个文件名或路径,以防我们知道bug来自特定的文件。 例如, 假设我们知道导致回归的更改在com/workingDir中 然后运行git bisect start / workingdir 只有修改了此目录内容的提交才会被检查 这让事情变得更快。

此外,如果很难判断一个特定的提交是好是坏,您可以使用 可以运行git的等分跳过,这将忽略它。考虑到有足够多的其他 提交时,git bisect将使用另一个来缩小搜索范围。

Git平分运行自动平分

如果你有一个自动的。/test脚本,如果测试正常,退出状态为0,你可以用bisect run自动找到bug:

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的变量,然后从那里运行它来解决问题。

当然,如果测试所依赖的测试基础设施破坏了旧的提交,那么就没有解决方案,您将不得不手动操作,决定如何逐个测试提交。

然而,我发现使用这种自动化通常是有效的,并且可以为您的任务积压中较慢的测试节省大量时间,在那里您可以让它运行一夜,并且可能在第二天早上发现您的错误,这值得一试。

更多的建议

在平分后,保持在第一个失败的提交上,而不是回到master:

git bisect reset HEAD

开始+最初的坏和好的一气呵成:

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

看看到目前为止已经测试了什么(通过手动测试或运行):

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秒内,如果我们懒得让测试更具体,我们应该这样做,我们可以使用超时:

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

这是因为timeout退出124,而test-command失败退出1。

神奇的退出状态

Git bisect运行对退出状态有点挑剔:

任何大于127的值都将导致等分失败,如下所示: Git平分运行失败: 退出代码134从…/test -aa' < 0或>= 128 特别是,C断言(0)会导致SIGABRT并以状态134退出,非常烦人。 125是魔法,使运行被跳过与git平分跳过。 这样做的目的是帮助跳过由于不相关的原因而损坏的构建。

详见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上测试。