如何使用git平分?

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

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

  1. 如何使用它?
  2. 就像svn blame一样吗?
134140 次浏览

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

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

您知道您的程序在current修订时不能正常工作,而在0修订时可以正常工作。因此,回归很可能是在12345current中的一个提交中引入的。

你可以试着检查每个提交,构建它,检查回归是否存在。如果有大量的提交,这可能会花费很长时间。这是线性搜索。我们可以用二分法搜索。这就是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 bisect bad,如果没有则使用git bisect good

让我们假设在提交4中引入了回归。然后回归在这个修订中不存在,我们告诉它git

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

然后它将检出另一个提交。45(因为只有两次提交)。假设它选择了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个版本(345)而不是4个版本(1234)。这是一个小小的胜利,但这是因为我们的历史是如此之小。如果搜索范围是N个提交,我们应该期望使用git bisect测试1 + log2n个提交,而不是使用线性搜索测试大约N / 2个提交。

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

$ git bisect ..基本上就是Git调试工具。'Git Bisect'调试通过通过之前的提交自从你的最后(已知)工作提交。它使用二分搜索遍历所有提交,找到引入回归/错误的提交。

$ git bisect start #开始等分

$ git bisect bad #表示当前提交(v1.5)有回归/设置“坏”点

$ git bisect good v1.0 #提到它是最后一个工作良好的提交(没有回归)

提到“坏”和“好”点将帮助git平分(二进制搜索)选择中间元素(提交v1.3)。如果回归是在提交1.3版本,你将把它设置为新的“坏”点,即(Good -> v1.0 Bad -> v1.3)。

$ git bisect bad

或者类似地,如果提交v1.3没有bug,你将把它设置为新的“Good point”,即(*Good -> v1.3和Bad -> v1.6)。

$ git bisect good

git bisect run自动平分

如果你有一个自动的./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跟踪,它不会在对分期间的某个早期提交中消失。

我经常发现,你可以通过从树中复制树内脚本,并可能使用# eyz0 -类变量,然后从那里运行它来逃避。

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

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

更多的建议

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

git bisect reset HEAD

start +初始badgood一气呵成:

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

看看到目前为止已经测试了什么(手动goodbadrun):

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# EYZ0会导致SIGABRT并以状态134退出,非常烦人

  • 125是魔法,使运行被跳过与git bisect skip

    这样做的目的是帮助跳过由于不相关的原因而损坏的构建。< / p >

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

再补充一点:

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

同样,如果很难判断一个特定的提交是好是坏,你会 可以运行git bisect skip,它将忽略它。考虑到有足够多的其他 提交时,git将使用另一个bisect来缩小搜索范围

博士TL;

开始:

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

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

# EYZ0

重复一遍:

问题还存在吗?

  • 是的:# EYZ0
  • 没有:# EYZ0

结果:

<abcdef> is the first bad commit

当完成:

git bisect reset

注意:术语goodbad并不是唯一可以用来标记带有或不带有特定属性的提交的术语。

Git 2.7(2015年Q4)引入了新的git bisect选项。

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

文档添加:

有时,您不是在寻找引入破坏的提交,而是在寻找提交,导致一些其他的“旧的”;State和“new”;状态

例如,您可能正在查找引入特定修复的提交。
或者您可能正在寻找源代码文件名最终全部转换为公司命名标准的第一次提交。等等。< / p >

在这种情况下,使用“好”这样的术语会让人很困惑。和“;糟糕的;指“变化前的状态”;以及“变革后的国家”。

所以,你可以用“old"和"new"分别代替"good"和“;# EYZ3"。
(但请注意,你不能把"good"和“# EYZ1"用“# EYZ2"和“# EYZ3"在单个会话中)

在这种更一般的用法中,为git bisect提供"new"commit有一些属性和"old"没有这个属性的Commit。

每次git bisect检出一个提交时,您测试该提交是否具有
属性 如果是,将提交标记为&;new";否则,标记为&;# eyz1 &;

平分完成后,git bisect将报告哪个提交引入了该属性。


参见提交06 e6a74提交21 b55e3提交fe67687(2015年6月29日)由马修·梅(moy)
参见安东尼·德莱特(CanardChouChinois) 提交21 e5cfd(2015年6月29日)。
(由滨野朱尼奥——gitster——提交22 dd6eb合并,2015年10月5日)


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

参见提交e9011b6提交464年ce0a提交58786 d7(2022年11月10日)by Đoàn trn Công Danh (sgn)
(由滨野朱尼奥——gitster——提交e3d40fb中合并,2022年11月23日)

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

< p > # EYZ0
# EYZ0
署名:Taylor Blau

目前,我们正在使用OPT_CMDMODE,解析子命令,即使找到了该命令,它也将继续解析更多选项。

当我们运行“# eyz6 & (man)”命令时,需要一个“--log”或“--no-log”参数,或其中一个“# eyz3 & # eyz3”参数;参数,bisect--helper可能会错误地认为这些选项是bisect--helper的选项。

我们可以通过传递“--"当从git-bisect.sh调用时,跳过& --"在# EYZ3。
但是,它可能会干扰用户的“--"

让我们用OPT_SUBCOMMAND来解析子命令,因为该API是为这个特定的用例而诞生的。

Git对退出状态进行等分

西罗Santilli的2014 回答将其称为“神奇的退出状态”;

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

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

参见提交48 af1fd提交ba5bb81提交8 efa2ac提交80年c2e96(2022年1月18日),René沙夫(rscharfe)
(由滨野朱尼奥——gitster——提交e828747中合并,2022年3月6日)

bisect--helper:在出口代码126和127上再次检查运行命令

署名:René Scharfe

当运行命令无法执行或无法找到时,shell将分别返回退出码126或127。
有效的运行命令也允许返回这些代码,以指示由于历史原因导致的错误修订

这意味着输入错误可能会导致假的等分运算,它会遍历整个距离并最终报告无效的结果。

最好的解决方案是保留退出代码126和127,如71年b0251 (Bisect run:, 2007-10-26, Git v1.5.4-rc0—合并) (Bisect run: "skip"如果脚本退出码为125,则当前提交。我们一拿到他们就放弃bisect run
不过,对于那些依赖于说明126和127可以用于糟糕修订的文档的人来说,这可能不方便 这个补丁使用的解决方案是在已知的良好修订上运行命令,如果我们仍然得到相同的错误代码,则中止。
这为使用退出码126和127的脚本增加了一个步骤,但仍然支持它们,只有一个例外:它将不适用于不能识别(手动标记的)已知良好修订的命令

使用低退出码运行的命令不受影响。
在执行两次缺少的命令和三次签出(第一步,已知的良好修订并返回到第一步的修订)后报告错字

看到# EYZ0。