Repository: jesseduffield/lazygit (language: go)

Issue:
Crash on deleting a branch in combination with filtering
**Describe the bug**
Lazygit crashed with "invalid memory address or nil pointer dereference error" after deleting a branch and applying filtering.
It seems this was previously fixed in #3058, but it appeared again (I tested on commit `36918560`, and it worked fine there).

**To Reproduce**
Steps to reproduce the behavior:

1. Create a branch named "dummy"
2. Focus branch list view
3. Press `/`
4. Type: dummy
5. Press `Enter`, `d`, `Enter`


**Expected behavior**
I expected lazygit to continue and either redo the filter operation, or show all branches.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Version info:**
lazygit: commit=v0.45.0, build date=2025-01-11T10:02:55Z, build source=binaryRelease, version=0.45.0, os=linux, arch=amd64, git version=2.48.1
git version 2.48.1

**Additional context**
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x8 pc=0x583685225cd2]

goroutine 325 [running]:
github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers.(*RefreshHelper).refreshBranches.func3(0x5836851883c5?)
        github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers/refresh_helper.go:495 +0x12
github.com/samber/lo.FindIndexOf[...]({0xc001a920c0?, 0x1, 0xc001a90000}, 0xc001613e50?)
        github.com/samber/lo@v1.31.0/find.go:54 +0x57
github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers.(*RefreshHelper).refreshBranches(0xc00074a680, 0x0, 0x0, 0x1)
        github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers/refresh_helper.go:494 +0x707
github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers.(*RefreshHelper).refreshReflogAndBranches(0xc00074a680, 0x0, 0x0)
        github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers/refresh_helper.go:273 +0x74
github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers.(*RefreshHelper).Refresh.func2.2()
        github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers/refresh_helper.go:130 +0x1f
github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers.(*RefreshHelper).Refresh.func2.1.1({0x0?, 0x0?})
        github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers/refresh_helper.go:107 +0x13
github.com/jesseduffield/gocui.(*Gui).onWorkerAux(0xc0003661c0, 0x0?, {0x5836856e1638?, 0xc00007b260?})
        github.com/jesseduffield/gocui@v0.3.1-0.20250107151125-716b1eb82fb4/gui.go:701 +0x65
github.com/jesseduffield/gocui.(*Gui).OnWorker.func1()
        github.com/jesseduffield/gocui@v0.3.1-0.20250107151125-716b1eb82fb4/gui.go:688 +0x2b
created by github.com/jesseduffield/gocui.(*Gui).OnWorker in goroutine 225
        github.com/jesseduffield/gocui@v0.3.1-0.20250107151125-716b1eb82fb4/gui.go:687 +0x92
```

***Note: please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs.***
I tried compiling from master. The issue persists


Discussion / hints:
I tested this with your reproduction steps, and it didn't crash for me. The filtered list of branches was empty for me after deleting the branch.

Does this happen for you every time, or only sometimes? Maybe it's somehow timing sensitive.
Hey @stefanhaller ,
This happens every time. I also tried pressing the buttons slower, with several seconds in between (if that is what you mean), but it crashed anyway.

I ended up firing a debugger and I think I can see why it happens/how to reproduce: in the unfiltered view, take note of the branch that is last in the list. Now filter the list, select the branch and delete it.

Let me know if it reproduces with this approach. It's easier to reproduce if you only have 2 branches in the repo.

You don't necessarily need to delete the last branch from the unfiltered view. You just have to ensure the last branch is present in the filtered view.
Cause if the last branch was in the filtered view, this will cause the call to [`ValuesAtIndices`](https://github.com/jesseduffield/lazygit/blob/40d6800fd35ad2d5c6d96d6f08ffa42d4c764ad3/pkg/utils/slice.go#L119C1-L128C2) to contain in index outside of the slice - `slice` will contain the new list of branches but `indices` will be based on the old list of branches, which causes `result` to be something like `[..., nil]`.
Yes, I can easily reproduce it by filtering down to just the last branch in the list and trying to delete that.

I'll see if I can come up with a fix tomorrow.

Fix patch:
diff --git a/pkg/gui/controllers/helpers/refresh_helper.go b/pkg/gui/controllers/helpers/refresh_helper.go
index b9a90af606a..a5be655a1b1 100644
--- a/pkg/gui/controllers/helpers/refresh_helper.go
+++ b/pkg/gui/controllers/helpers/refresh_helper.go
@@ -490,6 +490,8 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
 		self.refreshView(self.c.Contexts().Worktrees)
 	}
 
+	self.refreshView(self.c.Contexts().Branches)
+
 	if !keepBranchSelectionIndex && prevSelectedBranch != nil {
 		_, idx, found := lo.FindIndexOf(self.c.Contexts().Branches.GetItems(),
 			func(b *models.Branch) bool { return b.Name == prevSelectedBranch.Name })
@@ -498,8 +500,6 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
 		}
 	}
 
-	self.refreshView(self.c.Contexts().Branches)
-
 	// Need to re-render the commits view because the visualization of local
 	// branch heads might have changed
 	self.c.Mutexes().LocalCommitsMutex.Lock()


Test patch:
diff --git a/pkg/integration/tests/branch/delete_while_filtering.go b/pkg/integration/tests/branch/delete_while_filtering.go
new file mode 100644
index 00000000000..d967ff40fd2
--- /dev/null
+++ b/pkg/integration/tests/branch/delete_while_filtering.go
@@ -0,0 +1,49 @@
+package branch
+
+import (
+	"github.com/jesseduffield/lazygit/pkg/config"
+	. "github.com/jesseduffield/lazygit/pkg/integration/components"
+)
+
+// Regression test for deleting the last branch in the unfiltered list while
+// filtering is on. This used to cause a segfault.
+var DeleteWhileFiltering = NewIntegrationTest(NewIntegrationTestArgs{
+	Description:  "Delete a local branch while there's a filter in the branches panel",
+	ExtraCmdArgs: []string{},
+	Skip:         false,
+	SetupConfig: func(config *config.AppConfig) {
+		config.GetAppState().LocalBranchSortOrder = "alphabetic"
+	},
+	SetupRepo: func(shell *Shell) {
+		shell.EmptyCommit("one")
+		shell.NewBranch("branch1")
+		shell.NewBranch("branch2")
+		shell.Checkout("master")
+	},
+	Run: func(t *TestDriver, keys config.KeybindingConfig) {
+		t.Views().Branches().
+			Focus().
+			Lines(
+				Contains("master").IsSelected(),
+				Contains("branch1"),
+				Contains("branch2"),
+			).
+			FilterOrSearch("branch").
+			Lines(
+				Contains("branch1").IsSelected(),
+				Contains("branch2"),
+			).
+			SelectNextItem().
+			Press(keys.Universal.Remove).
+			Tap(func() {
+				t.ExpectPopup().
+					Menu().
+					Title(Equals("Delete branch 'branch2'?")).
+					Select(Contains("Delete local branch")).
+					Confirm()
+			}).
+			Lines(
+				Contains("branch1").IsSelected(),
+			)
+	},
+})
diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go
index 4557b8afdbe..b9b480e910c 100644
--- a/pkg/integration/tests/test_list.go
+++ b/pkg/integration/tests/test_list.go
@@ -45,6 +45,7 @@ var tests = []*components.IntegrationTest{
 	branch.DeleteMultiple,
 	branch.DeleteRemoteBranchWithCredentialPrompt,
 	branch.DeleteRemoteBranchWithDifferentName,
+	branch.DeleteWhileFiltering,
 	branch.DetachedHead,
 	branch.NewBranchAutostash,
 	branch.NewBranchFromRemoteTrackingDifferentName,