Git bisect与合并的提交记录

53

我的历史记录看起来像这样:

* 3830e61 Add data escaping.              (Bad)
* 0f5e148 Improve function for getting page template.
*   aaf8dc5 Merge branch 'navigation'
|\
| * 3e667f8 Add icons.
| * 43a07b1 Add menu styles.              (Breaks)
| * 107ca95 Add Responsive Nav.           (Good)
* | ea3d736 Add ‘Admin’ notice.
* | 17ca0bb Update placeholder text.
|/
* f52cc34 Add featured image.
* 2abd954 Style placeholders.

我正在试图学习更多关于git bisect,但在处理这个历史记录时遇到了问题。我知道107ca95是好的,而3830e61是坏的。当我运行git bisect时,提交范围107ca95..3e667f8被忽略了。我碰巧知道43a07b1是引入回归的提交,但它从未被评估。

以下大致是我的做法:

git checkout master
git bisect start
git bisect bad
git bisect good 107ca95
git bisect bad (multiple times)
无论我做什么,“107ca95..3e667f8”从来没有被检出来测试。
在二分查找期间,有没有办法将历史记录“展平”,以便测试这些提交?我知道我可以使用交互式的“rebase”来展平历史记录,但我不想这样做。

1
@BalogPal - 我看到了一个类似的建议,但它似乎会将分支中的所有内容都标记为好的,而实际上它包含了一个坏的提交。对我来说奇怪的是,我甚至无法让二分法解决合并提交。令人奇怪的是,它解决到了一个甚至不在提交范围内的提交。 - tollmanz
1
没问题,你可以进行两次二分查找。如果第一次找到了一个合并点,那么你就可以用它来继续查找这个合并点所包含的提交记录。但是如果你有更好的想法,可以使用技术来实现,关键是你可以使用脚本来预标记某些提交记录。 - Balog Pal
1
@BalogPal - 感谢您的帮助!您能否更详细地阐述您的答案作为这个问题的解决方案?我想我明白了您的意思,但实际示例会非常有帮助。 - tollmanz
可能是为什么'git bisect'不知道分支?的重复问题。 - Basilevs
非常好的问题(并且有插图)!看起来git的二分行为已经改变,现在确实搜索合并分支中的提交。对于那些希望执行相反操作(仅测试主分支中的合并提交的筛选二分),我发现这篇文章提供了清晰的解释:https://blog.smart.ly/2015/02/03/git-bisect-debugging-with-feature-branches/ - waldyrious
显示剩余2条评论
4个回答

23

这是一个很久以前但未解决的问题。我决定进行调查,并发现我可以证明Git的行为与问题所述不同。一种解释是Git改进了bisect算法,或者提问者在标记提交时犯了错误。

我正在尝试学习更多关于git bisect,但在这个历史记录中遇到麻烦。我知道107ca95是好的,而3830e61是坏的。当我运行一个git bisect时,提交107ca95..3e667f8将被忽略。我碰巧知道43a07b1是引入回归的提交,但它从未被评估过

我编写了一些代码来检查它是否被评估。我的测试表明它被评估了。运行下面的代码并验证是否出现消息为Add menu styles.的提交。

进一步的评论:

  • "提交107ca95..3e667f8将被忽略":请注意,你标记为“好”的提交不会被评估,因为Git已经知道它是好的。
  • 请阅读Christian Couder的这篇文章中的“二分算法”部分。还有“检查合并基础”部分可能与此相关。
  • 如上所述,问题肯定使用不同于我使用的版本(问题来自2013年,Git 2.11来自2016年)。

Bisect运行输出

  • 请注意,首先检查了“Add Admin notice”(第4行),因为它提供了最多的信息。(从上面提到的文章中阅读“检查合并基础”部分。)
  • 然后,它像预期的那样分割线性历史记录。

# bad: [d7761d6f146eaca1d886f793ced4315539326866] Add data escaping. (Bad)
# good: [f555d9063a25a20a6ec7c3b0c0504ffe0a997e98] Add Responsive Nav. (Good)
git bisect start 'd7761d6f146eaca1d886f793ced4315539326866' 'f555d9063a25a20a6ec7c3b0c0504ffe0a997e98'
# good: [1b3b7f4952732fec0c68a37d5f313d6f4219e4ae] Add ‘Admin’ notice. (Good)
git bisect good 1b3b7f4952732fec0c68a37d5f313d6f4219e4ae
# bad: [f9a65fe9e6cde4358e5b8ef7569332abfb07675e] Add icons. (Bad)
git bisect bad f9a65fe9e6cde4358e5b8ef7569332abfb07675e
# bad: [165b8a6e5137c40ce8b90911e59d7ec8eec30f46] Add menu styles. (Bad)
git bisect bad 165b8a6e5137c40ce8b90911e59d7ec8eec30f46
# first bad commit: [165b8a6e5137c40ce8b90911e59d7ec8eec30f46] Add menu styles. (Bad)

代码

使用Python 3运行,带有Git 2.11.0。 运行命令:python3 script.py

""" The following code creates a git repository in '/tmp/git-repo' and populates
it with the following commit graph. Each commit has a test.sh which can be used
as input to a git-bisect-run.

The code then tries to find the breaking change automatically.
And prints out the git bisect log.

Written in response to https://dev59.com/xWQm5IYBdhLWcg3w4CIN
to test the claim that '107ca95..3e667f8 are never checked out'.

Needs Python 3!
"""


from itertools import chain
import os.path
import os
import sh

repo = {
0x3830e61:  {'message': "Add data escaping.", 'parents': [    0x0f5e148    ], 'test': False} , # Last:    (Bad)
0x0f5e148: {'message': "Improve function for getting page template.", 'parents': [ 0xaaf8dc5], 'test': False},
0xaaf8dc5: {'message': "Merge branch 'navigation'", 'parents': [ 0x3e667f8, 0xea3d736], 'test': False},
    0x3e667f8: {'message': "Add icons.", 'parents': [  0x43a07b1], 'test': False},
    0x43a07b1: {'message': "Add menu styles.", 'parents': [    0x107ca95], 'test': False}  , # First:       (Breaks)
    0x107ca95: {'message': "Add Responsive Nav.", 'parents': [   0xf52cc34], 'test': True}, # First:        (Good)
  0xea3d736: {'message': "Add ‘Admin’ notice.", 'parents': [ 0x17ca0bb], 'test': True},
  0x17ca0bb: {'message': "Update placeholder text.", 'parents': [  0xf52cc34], 'test': True},
0xf52cc34: {'message': "Add featured image.", 'parents': [  0x2abd954], 'test': True},
0x2abd954: {'message': "Style placeholders.", 'parents': [], 'test': True},
}

bad = 0x3830e61
good = 0x107ca95


def generate_queue(_dag, parents):
    for prev in parents:
        yield prev
        yield from generate_queue(_dag, _dag[prev]['parents'])

def make_queue(_dag, inits):
    """ Converts repo (a DAG) into a queue """
    q = list(generate_queue(_dag, inits))
    q.reverse()
    seen = set()
    r = [x for x in q if not (x in seen or seen.add(x))]
    return r

if __name__ == '__main__':
    pwd = '/tmp/git-repo'
    sh.rm('-r', pwd)
    sh.mkdir('-p', pwd)
    g = sh.git.bake(_cwd=pwd)
    g.init()

    parents = set(chain.from_iterable((repo[c]['parents'] for c in repo)))

    commits = set(repo)
    inits = list(commits - parents)
    queue = make_queue(repo, inits)

    assert len(queue) == len(repo), "queue {} vs repo {}".format(len(queue), len(repo))

    commit_ids = {}
    # Create commits
    for c in queue:
        # Set up repo
        parents = repo[c]['parents']
        if len(parents) > 0:
            g.checkout(commit_ids[parents[0]])
        if len(parents) > 1:
            if len(parents) > 2: raise NotImplementedError('Octopus merges not support yet.')
            g.merge('--no-commit', '-s', 'ours', commit_ids[parents[1]])  # just force to use 'ours' strategy.

        # Make changes
        with open(os.path.join(pwd, 'test.sh'), 'w') as f:
            f.write('exit {:d}\n'.format(0 if repo[c]['test'] else 1))
        os.chmod(os.path.join(pwd, 'test.sh'), 0o0755)
        with open(os.path.join(pwd, 'message'), 'w') as f:
            f.write(repo[c]['message'])
        g.add('test.sh', 'message')
        g.commit('-m', '{msg} ({test})'.format(msg=repo[c]['message'], test='Good' if repo[c]['test'] else 'Bad'))
        commit_ids[c] = g('rev-parse', 'HEAD').strip()

    # Run git-bisect
    g.bisect('start', commit_ids[bad], commit_ids[good])
    g.bisect('run', './test.sh')
    print(g.bisect('log'))

有趣的调查。+1。不要忘记OP在2013年写下了这个问题。git bisect可能已经随着更近期(2016年第四季度)的git 2.11发生了变化。 - VonC
1
@VonC,谢谢!我已经写了那个,但现在强调一下。 - Unapiedra

11

这个问题 已经有人回答过了

基本思路 - 找出哪个来自功能分支的提交导致主分支出错,你需要将其在 ea3d736 - 相关主分支HEAD 上重新应用。

以下是一个示例(来自git文档)可以为您执行此操作的测试脚本:

$ 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 ea3d736 &&
    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 bisect start 3830e61 f52cc34 
git bisect good ea3d736 17ca0bb #If you want to test feature branch only
git bisect run ~/test.sh

1

警告:关于“使用临时修改自动二分”的git bisect部分已经在Git 2.25(2020年第一季度)中更新。

(而git bisect --first-parent在Git 2.29+(2020年第四季度)中可用

这涉及到您在相关的master提交上重新应用您正在测试的提交的步骤(在OP的情况下是ea3d736

如果您不想移动HEAD,则“git merge --no-commit”需要“--no-ff”,这已在“git bisect”手册页面中得到了更正。

请查看提交 8dd327b(2019年10月28日)由Mihail Atanassov (matana)完成。
(由Junio C Hamano -- gitster --合并于提交 fac9ab1,2019年12月1日)

Documentation/git-bisect.txt: 将 --no-ff 添加到合并命令中

Signed-off-by: Mihail Atanassov
Reviewed-by: Jonathan Nieder

hotfix 应用程序示例使用 git merge --no-commit 在 bisect 操作期间对工作树应用临时更改。

在某些情况下,这可能是一个快进操作,而 merge 将应用 hotfix 分支的提交,而不管 --no-commit(如 git merge manual 中所述)。

在极端情况下,这将使得 [git bisect](https://git-scm.com/docs/git-bisect) 运行调用循环无限期地在第一个 bisect 步骤和快进后的 post-merge HEAD 之间。

--no-ff 添加到合并命令中以避免此问题。

git merge提到:

请注意,快进式更新不会创建合并提交,因此无法使用--no-commit停止这些合并。

因此,如果您想确保merge命令不更改或更新您的分支,请使用--no-ff--no-commit


-7
您可以使用"git start"命令选择提交范围。该命令的概要如下:
git bisect start <bad> <good>

根据您的特定情况,我认为正确的命令应该是:

git bisect start 3830e61 107ca95

2
这不就等同于楼主的方法吗? - Basilevs

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接