Golang Gotcha of the Day

栏目: IT技术 · 发布时间: 5年前

内容简介:This one has bitten me in the ass probably three times in the last month. The most recent bite occured just last night as I was debugging the rebase logic in lazygit. I had noticed that after selecting the 'abort' option in my rebase menu the rebase was no

Golang Gotcha of the Day

This one has bitten me in the ass probably three times in the last month. The most recent bite occured just last night as I was debugging the rebase logic in lazygit. I had noticed that after selecting the 'abort' option in my rebase menu the rebase was not aborting, but instead moved along a couple of commits and then got stuck at some more conflicts.

Were not all aborts created equal? Maybe sometimes in order to abort you need to go forward first, and git was hitting conflicts along the way? I wasn't quite ready to give up my understanding of how rebasing works, so I took a look at the code

func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error {
    options := []string{"continue", "abort"}

    if gui.State.WorkingTreeState == "rebasing" {
        options = append(options, "skip")
    }

    menuItems := make([]*menuItem, len(options))
    for i, option := range options {
        menuItems[i] = &menuItem{
            displayString: option,
            onPress: func() error {
                return gui.genericMergeCommand(option)
            },
        }
    }
    ...

Can you spot the bug?

The logic was simple: create a slice of strings representing the different options available, and then create menu items to show in the GUI, where if you pressed say 'abort', lazygit would run git rebase --abort .

I checked for an off-by-one error on the index: perhaps the menu items started counting from 1 instead of zero? Nope.

Turns out lazygit actually ran 'git rebase --skip', which explained why it would continue the rebase and then stop again. And then it struck me: skip was the last string in the slice. It just so happens that when a function inside a loop closes over a loop variable, it ends up referring to the final value of that variable, because on each iteration of the loop the loop variable's value is reassigned. In this case, the last value was the last item in the options slice, i.e. 'skip'.

So I added a variable which was scoped to the inside of the loop, to assign the value of the loop variable for that iteration. Unlike closures which work on references, direct assignments just take the value of the variable at the time of assignment, meaning its value wasn't going to change no matter what happened in future iterations.

func (gui *Gui) handleCreateRebaseOptionsMenu(g *gocui.Gui, v *gocui.View) error {
    options := []string{"continue", "abort"}

    if gui.State.WorkingTreeState == "rebasing" {
        options = append(options, "skip")
    }

    menuItems := make([]*menuItem, len(options))
    for i, option := range options {
        // note to self. Never, EVER, close over loop variables in a function
        innerOption := option
        menuItems[i] = &menuItem{
            displayString: innerOption,
            onPress: func() error {
                return gui.genericMergeCommand(innerOption)
            },
        }
    }

And voilà! The bug was fixed, and lazygit's questionable reputation as a reliable git GUI was preserved for another day.

Lesson learnt: Never, EVER, close over loop variables in a function definition!


以上所述就是小编给大家介绍的《Golang Gotcha of the Day》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

电子邮件营销密码

电子邮件营销密码

[美] Jeanniey Mullen、David Daniesl / 薛剑韬 / 人民邮电出版社 / 2009-9 / 39.00元

在当今互联网蓬勃发展的形势下,电子邮件是互联网应用最广的服务之一。那么如何利用其作为有效的营销工具呢?本书系统地讲解了美国电子邮件营销的预算统筹、营销策略、管理模式、执行机制、涉及的技术、营销实施的细节等,其方法有很强的可循性,并可预见将获得的成果。阅读本书之后,读者会深刻感受到电子邮件营销的博大精深,它既是一门扎实严谨的科学,又是一项充满创造力的艺术。. 本书适合企业管理人员及市场营销人员......一起来看看 《电子邮件营销密码》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

URL 编码/解码
URL 编码/解码

URL 编码/解码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具