ronantakizawa/codereview-bench huggingface dataset:
Repository: jesseduffield/lazygit
File: pkg/gui/controllers/helpers/window_arrangement_helper.go (Go)

Code before:
// to arrange the windows (i.e. panels) on the screen.

type WindowArrangementHelper struct {
	c               *HelperCommon
	windowHelper    *WindowHelper
	modeHelper      *ModeHelper
	appStatusHelper *AppStatusHelper
}

func NewWindowArrangementHelper(
	c *HelperCommon,
	windowHelper *WindowHelper,
	modeHelper *ModeHelper,
	appStatusHelper *AppStatusHelper,
) *WindowArrangementHelper {
	return &WindowArrangementHelper{
		c:               c,
		windowHelper:    windowHelper,
		modeHelper:      modeHelper,
		appStatusHelper: appStatusHelper,
	}
}

const INFO_SECTION_PADDING = " "

func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string, forcePortraitMode bool) map[string]boxlayout.Dimensions {
	width, height := self.c.GocuiGui().Size()

	sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()

	sidePanelsDirection := boxlayout.COLUMN
	portraitMode := width <= 84 && height > 45
	if portraitMode || forcePortraitMode {
		sidePanelsDirection = boxlayout.ROW
	}

	mainPanelsDirection := boxlayout.ROW
	if self.splitMainPanelSideBySide() {
		mainPanelsDirection = boxlayout.COLUMN
	}

	extrasWindowSize := self.getExtrasWindowSize(height)

	self.c.Modes().Filtering.Active()

	showInfoSection := self.c.UserConfig.Gui.ShowBottomLine ||
		self.c.State().GetRepoState().InSearchPrompt() ||
		self.modeHelper.IsAnyModeActive() ||
		self.appStatusHelper.HasStatus()
	infoSectionSize := 0
	if showInfoSection {

Review comment (refactor):
The UserConfig struct is available to WindowArrangementHelper as `self.c.UserConfig`, so you don't have to pass it in.

Code after:
// to arrange the windows (i.e. panels) on the screen.

type WindowArrangementHelper struct {
	c               *HelperCommon
	windowHelper    *WindowHelper
	modeHelper      *ModeHelper
	appStatusHelper *AppStatusHelper
}

func NewWindowArrangementHelper(
	c *HelperCommon,
	windowHelper *WindowHelper,
	modeHelper *ModeHelper,
	appStatusHelper *AppStatusHelper,
) *WindowArrangementHelper {
	return &WindowArrangementHelper{
		c:               c,
		windowHelper:    windowHelper,
		modeHelper:      modeHelper,
		appStatusHelper: appStatusHelper,
	}
}

const INFO_SECTION_PADDING = " "

func (self *WindowArrangementHelper) shouldUsePortraitMode(width, height int) bool {
	switch self.c.UserConfig.Gui.PortraitMode {
	case "never":
		return false
	case "always":
		return true
	default: // "auto" or any garbage values in PortraitMode value
		return width <= 84 && height > 45
	}
}

func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
	width, height := self.c.GocuiGui().Size()

	sideSectionWeight, mainSectionWeight := self.getMidSectionWeights()

	sidePanelsDirection := boxlayout.COLUMN
	if self.shouldUsePortraitMode(width, height) {
		sidePanelsDirection = boxlayout.ROW
	}

	mainPanelsDirection := boxlayout.ROW
	if self.splitMainPanelSideBySide() {
		mainPanelsDirection = boxlayout.COLUMN
	}
Repository: jesseduffield/lazygit
File: pkg/integration/tests/misc/disabled_keybindings.go (Go)

Code before:
package misc

import (
	"github.com/jesseduffield/lazygit/pkg/config"
	. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var DisabledKeybindings = NewIntegrationTest(NewIntegrationTestArgs{
	Description:  "Confirms You can set keybindings to blank to disable them",
	ExtraCmdArgs: []string{},
	Skip:         false,
	SetupConfig: func(config *config.AppConfig) {
		config.UserConfig.Keybinding.Universal.PrevItem = "<disabled>"
		config.UserConfig.Keybinding.Universal.NextItem = "<disabled>"
		config.UserConfig.Keybinding.Universal.NextTab = "<up>"
		config.UserConfig.Keybinding.Universal.PrevTab = "<down>"
	},
	SetupRepo: func(shell *Shell) {},
	Run: func(t *TestDriver, keys config.KeybindingConfig) {
		t.Views().Files().
			IsFocused().
			Press("<up>")

		t.Views().Worktrees().IsFocused()
	},
})


Review comment (suggestion):
```suggestion
	Description:  "Confirms you can disable keybindings by setting them to <disabled>",
```

Code after:
package misc

import (
	"github.com/jesseduffield/lazygit/pkg/config"
	. "github.com/jesseduffield/lazygit/pkg/integration/components"
)

var DisabledKeybindings = NewIntegrationTest(NewIntegrationTestArgs{
	Description:  "Confirms you can disable keybindings by setting them to <disabled>",
	ExtraCmdArgs: []string{},
	Skip:         false,
	SetupConfig: func(config *config.AppConfig) {
		config.UserConfig.Keybinding.Universal.PrevItem = "<disabled>"
		config.UserConfig.Keybinding.Universal.NextItem = "<disabled>"
		config.UserConfig.Keybinding.Universal.NextTab = "<up>"
		config.UserConfig.Keybinding.Universal.PrevTab = "<down>"
	},
	SetupRepo: func(shell *Shell) {},
	Run: func(t *TestDriver, keys config.KeybindingConfig) {
		t.Views().Files().
			IsFocused().
			Press("<up>")

		t.Views().Worktrees().IsFocused()
	},
})
Repository: jesseduffield/lazygit
File: pkg/gui/controllers/branches_controller.go (Go)

Code before:
			err := self.c.Helpers().Refs.CreateGitResetMenu(upstream)
			if err != nil {
				return self.c.Error(err)
			}
			return nil
		},
		Tooltip: upstreamResetTooltip,
		Key:     'g',
	}

	upstreamRebaseItem := &types.MenuItem{
		LabelColumns: []string{upstreamRebaseOptions},
		OpensMenu:    true,
		OnPress: func() error {
			if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.ShortUpstreamRefName()); err != nil {
				return self.c.Error(err)
			}
			return nil
		},
		Tooltip: upstreamRebaseTooltip,
		Key:     'r',
	}

	if !selectedBranch.RemoteBranchStoredLocally() {
		viewDivergenceItem.DisabledReason = self.c.Tr.UpstreamNotSetError
		upstreamResetItem.DisabledReason = self.c.Tr.UpstreamNotSetError
		upstreamRebaseItem.DisabledReason = self.c.Tr.UpstreamNotSetError
	}

	options := []*types.MenuItem{
		viewDivergenceItem,
		unsetUpstreamItem,
		setUpstreamItem,
		upstreamResetItem,
		upstreamRebaseItem,
	}

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.BranchUpstreamOptionsTitle,
		Items: options,
	})
}

func (self *BranchesController) Context() types.Context {
	return self.context()
}

func (self *BranchesController) context() *context.BranchesContext {
	return self.c.Contexts().Branches
}


Review comment (suggestion):
There should still be a disabled reason if the branch doesn't have an upstream set at all (i.e. if `IsTrackingRemote` returns false). I'd be ok with using the same error text (UpstreamNotSetError) in that case, even if it's not totally accurate.

Code after:
			err := self.c.Helpers().Refs.CreateGitResetMenu(upstream)
			if err != nil {
				return self.c.Error(err)
			}
			return nil
		},
		Tooltip: upstreamResetTooltip,
		Key:     'g',
	}

	upstreamRebaseItem := &types.MenuItem{
		LabelColumns: []string{upstreamRebaseOptions},
		OpensMenu:    true,
		OnPress: func() error {
			if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.ShortUpstreamRefName()); err != nil {
				return self.c.Error(err)
			}
			return nil
		},
		Tooltip: upstreamRebaseTooltip,
		Key:     'r',
	}

	if !selectedBranch.IsTrackingRemote() {
		unsetUpstreamItem.DisabledReason = self.c.Tr.UpstreamNotSetError
	}

	if !selectedBranch.RemoteBranchStoredLocally() {
		viewDivergenceItem.DisabledReason = self.c.Tr.UpstreamNotSetError
		upstreamResetItem.DisabledReason = self.c.Tr.UpstreamNotSetError
		upstreamRebaseItem.DisabledReason = self.c.Tr.UpstreamNotSetError
	}

	options := []*types.MenuItem{
		viewDivergenceItem,
		unsetUpstreamItem,
		setUpstreamItem,
		upstreamResetItem,
		upstreamRebaseItem,
	}

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.BranchUpstreamOptionsTitle,
		Items: options,
	})
}

func (self *BranchesController) Context() types.Context {
	return self.context()
}
Repository: jesseduffield/lazygit
File: pkg/gui/controllers/files_controller.go (Go)

Code before:
	if self.context().GetSelected() == nil {
		copyNameItem.DisabledReason = copyNameNoItemSelectionErr
		copyFileDiffItem.DisabledReason = copyDiffNoItemSelectionErr
	}

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.CopyToClipboardMenu,
		Items: []*types.MenuItem{
			copyNameItem,
			copyFileDiffItem,
			{
				Label: self.c.Tr.CopyAllFilesDiff,
				OnPress: func() error {
					diff, err := self.c.Git().Diff.GetAllDiff(self.hasStagedChanges())
					if err != nil {
						return self.c.Error(err)
					}
					return self.copyToClipboard(diff)
				},
				Key: 'a',
			},
		},
	})
}

func (self *FilesController) hasStagedChanges() bool {
	files := self.context().GetAllFiles()
	for _, file := range files {
		if file.HasStagedChanges {
			return true
		}
	}
	return false
}

func (self *FilesController) hasPathStagedChanges() bool {
	node := self.context().GetSelected()
	return node.SomeFile(func(t *models.File) bool {
		return t.HasStagedChanges
	})
}

func (self *FilesController) copyToClipboard(content string) error {
	if content == "" {
		return self.c.Alert(self.c.Tr.NoContentToCopyTitle, self.c.Tr.NoContentToCopyBody)
	}

	if err := self.c.OS().CopyToClipboard(content); err != nil {
		return self.c.Error(err)
	}
	self.c.Toast(self.c.Tr.FileContentCopiedToast)

Review comment (refactor):
We already have this functionality [here](https://github.com/AzraelSec/lazygit/blob/d92687d75c3731aca2f0d3275a76437d3f48279c/pkg/gui/controllers/helpers/working_tree_helper.go#L41-L48), just use `self.c.Helpers().WorkingTree.AnyStagedFiles()` on line `808`.

Code after:
			self.c.Toast(self.c.Tr.FileDiffCopiedToast)
			return nil
		},
		Key: 's',
	}
	copyAllDiff := &types.MenuItem{
		Label:   self.c.Tr.CopyAllFilesDiff,
		Tooltip: self.c.Tr.CopyFileDiffTooltip,
		OnPress: func() error {
			hasStaged := self.c.Helpers().WorkingTree.AnyStagedFiles()
			diff, err := self.c.Git().Diff.GetAllDiff(hasStaged)
			if err != nil {
				return self.c.Error(err)
			}
			if err := self.c.OS().CopyToClipboard(diff); err != nil {
				return self.c.Error(err)
			}
			self.c.Toast(self.c.Tr.AllFilesDiffCopiedToast)
			return nil
		},
		Key: 'a',
	}

	if node == nil {
		copyNameItem.DisabledReason = self.c.Tr.NoContentToCopyError
		copyPathItem.DisabledReason = self.c.Tr.NoContentToCopyError
		copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
	}
	if node != nil && !node.GetHasStagedOrTrackedChanges() {
		copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
	}
	if !self.anyStagedOrTrackedFile() {
		copyAllDiff.DisabledReason = self.c.Tr.NoContentToCopyError
	}

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.CopyToClipboardMenu,
		Items: []*types.MenuItem{
			copyNameItem,
			copyPathItem,
			copyFileDiffItem,
			copyAllDiff,
		},
	})
}

func (self *FilesController) anyStagedOrTrackedFile() bool {
	if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
		return self.c.Helpers().WorkingTree.AnyTrackedFiles()
	}
	return true
Repository: jesseduffield/lazygit
File: pkg/gui/controllers/files_controller.go (Go)

Code before:
	}

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.CopyToClipboardMenu,
		Items: []*types.MenuItem{
			copyNameItem,
			copyFileDiffItem,
			{
				Label: self.c.Tr.CopyAllFilesDiff,
				OnPress: func() error {
					diff, err := self.c.Git().Diff.GetAllDiff(self.hasStagedChanges())
					if err != nil {
						return self.c.Error(err)
					}
					return self.copyToClipboard(diff)
				},
				Key: 'a',
			},
		},
	})
}

func (self *FilesController) hasStagedChanges() bool {
	files := self.context().GetAllFiles()
	for _, file := range files {
		if file.HasStagedChanges {
			return true
		}
	}
	return false
}

func (self *FilesController) hasPathStagedChanges() bool {
	node := self.context().GetSelected()
	return node.SomeFile(func(t *models.File) bool {
		return t.HasStagedChanges
	})
}

func (self *FilesController) copyToClipboard(content string) error {
	if content == "" {
		return self.c.Alert(self.c.Tr.NoContentToCopyTitle, self.c.Tr.NoContentToCopyBody)
	}

	if err := self.c.OS().CopyToClipboard(content); err != nil {
		return self.c.Error(err)
	}
	self.c.Toast(self.c.Tr.FileContentCopiedToast)
	return nil
}


Review comment (refactor):
@mark2185, I tried substituting this function for `self.getSelectedFile().HasStagedChanges`, but this only works for files. If you select a directory, `getSelectedFile()` returns `nil`. Are you happy to stick with this helper method?

Code after:
		Key: 's',
	}
	copyAllDiff := &types.MenuItem{
		Label:   self.c.Tr.CopyAllFilesDiff,
		Tooltip: self.c.Tr.CopyFileDiffTooltip,
		OnPress: func() error {
			hasStaged := self.c.Helpers().WorkingTree.AnyStagedFiles()
			diff, err := self.c.Git().Diff.GetAllDiff(hasStaged)
			if err != nil {
				return self.c.Error(err)
			}
			if err := self.c.OS().CopyToClipboard(diff); err != nil {
				return self.c.Error(err)
			}
			self.c.Toast(self.c.Tr.AllFilesDiffCopiedToast)
			return nil
		},
		Key: 'a',
	}

	if node == nil {
		copyNameItem.DisabledReason = self.c.Tr.NoContentToCopyError
		copyPathItem.DisabledReason = self.c.Tr.NoContentToCopyError
		copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
	}
	if node != nil && !node.GetHasStagedOrTrackedChanges() {
		copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
	}
	if !self.anyStagedOrTrackedFile() {
		copyAllDiff.DisabledReason = self.c.Tr.NoContentToCopyError
	}

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.CopyToClipboardMenu,
		Items: []*types.MenuItem{
			copyNameItem,
			copyPathItem,
			copyFileDiffItem,
			copyAllDiff,
		},
	})
}

func (self *FilesController) anyStagedOrTrackedFile() bool {
	if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
		return self.c.Helpers().WorkingTree.AnyTrackedFiles()
	}
	return true
}

func (self *FilesController) hasPathStagedChanges(node *filetree.FileNode) bool {
Repository: jesseduffield/lazygit
File: pkg/gui/controllers/files_controller.go (Go)

Code before:

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.CopyToClipboardMenu,
		Items: []*types.MenuItem{
			copyNameItem,
			copyPathItem,
			copyFileDiffItem,
			copyAllDiff,
		},
	})
}

func (self *FilesController) anyPathTrackedFile(node *filetree.FileNode) bool {
	stagedFiles := node.GetStagedFiles()
	if len(stagedFiles) == 0 {
		return node.SomeFile(func(t *models.File) bool {
			return t.Tracked
		})
	}

	for _, file := range stagedFiles {
		if file.Tracked {
			return true
		}
	}
	return false
}

func (self *FilesController) anyTrackedFile() bool {
	stagedFiles := self.c.Helpers().WorkingTree.GetStagedFiles()
	if len(stagedFiles) == 0 {
		return self.c.Helpers().WorkingTree.AnyTrackedFiles()
	}

	for _, stagedFile := range stagedFiles {
		if stagedFile.Tracked {
			return true
		}
	}
	return false
}

func (self *FilesController) hasPathStagedChanges(node *filetree.FileNode) bool {
	return node.SomeFile(func(t *models.File) bool {
		return t.HasStagedChanges
	})
}

func (self *FilesController) copyToClipboard(content string) error {
	if err := self.c.OS().CopyToClipboard(content); err != nil {
		return self.c.Error(err)

Review comment (suggestion):
This is not quite the logic we need here. For an untracked file, staging it is good enough to get a diff from it, so this block can just be replaced by `return true`. The function name is then not quite accurate any more, so maybe use `StagedOrTracked` instead of just `Tracked`, or maybe `StagedOrUnstagedChanges` (if we move it to FileNode, see above).

Also, you don't need `GetStagedFiles` any more, `GetHasStagedChanges` is enough.

Code after:
			self.c.Toast(self.c.Tr.AllFilesDiffCopiedToast)
			return nil
		},
		Key: 'a',
	}

	if node == nil {
		copyNameItem.DisabledReason = self.c.Tr.NoContentToCopyError
		copyPathItem.DisabledReason = self.c.Tr.NoContentToCopyError
		copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
	}
	if node != nil && !node.GetHasStagedOrTrackedChanges() {
		copyFileDiffItem.DisabledReason = self.c.Tr.NoContentToCopyError
	}
	if !self.anyStagedOrTrackedFile() {
		copyAllDiff.DisabledReason = self.c.Tr.NoContentToCopyError
	}

	return self.c.Menu(types.CreateMenuOptions{
		Title: self.c.Tr.CopyToClipboardMenu,
		Items: []*types.MenuItem{
			copyNameItem,
			copyPathItem,
			copyFileDiffItem,
			copyAllDiff,
		},
	})
}

func (self *FilesController) anyStagedOrTrackedFile() bool {
	if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
		return self.c.Helpers().WorkingTree.AnyTrackedFiles()
	}
	return true
}

func (self *FilesController) hasPathStagedChanges(node *filetree.FileNode) bool {
	return node.SomeFile(func(t *models.File) bool {
		return t.HasStagedChanges
	})
}

func (self *FilesController) stash() error {
	return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges)
}

func (self *FilesController) createResetToUpstreamMenu() error {
	return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}")
}

func (self *FilesController) handleToggleDirCollapsed() error {
Repository: jesseduffield/lazygit
File: pkg/commands/git_commands/diff.go (Go)

Code before:
	FromCommit string

	// The commit to diff against FromCommit. Leave empty to diff the working
	// copy against FromCommit. Leave both FromCommit and ToCommit empty to show
	// the diff of the unstaged working copy changes against the index if Staged
	// is false, or the staged changes against HEAD if Staged is true.
	ToCommit string

	// Whether to reverse the left and right sides of the diff.
	Reverse bool

	// Whether the given Filepath is a directory. We'll pass --dir-diff to
	// git-difftool in that case.
	IsDirectory bool

	// Whether to show the staged or the unstaged changes. Must be false if both
	// FromCommit and ToCommit are non-empty.
	Staged bool
}

func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands.ICmdObj {
	return self.cmd.New(NewGitCmd("difftool").
		Arg("--no-prompt").
		ArgIf(opts.IsDirectory, "--dir-diff").
		ArgIf(opts.Staged, "--cached").
		ArgIf(len(opts.FromCommit) > 0, opts.FromCommit).
		ArgIf(len(opts.ToCommit) > 0, opts.ToCommit).
		ArgIf(opts.Reverse, "-R").
		Arg("--", opts.Filepath).
		ToArgv())
}


Review comment (suggestion):
For consistency I would go `opts.FromCommit != ""` instead of doing a length check, given that comparing to "" makes it clear that we're dealing with a string

Code after:
	FromCommit string

	// The commit to diff against FromCommit. Leave empty to diff the working
	// copy against FromCommit. Leave both FromCommit and ToCommit empty to show
	// the diff of the unstaged working copy changes against the index if Staged
	// is false, or the staged changes against HEAD if Staged is true.
	ToCommit string

	// Whether to reverse the left and right sides of the diff.
	Reverse bool

	// Whether the given Filepath is a directory. We'll pass --dir-diff to
	// git-difftool in that case.
	IsDirectory bool

	// Whether to show the staged or the unstaged changes. Must be false if both
	// FromCommit and ToCommit are non-empty.
	Staged bool
}

func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands.ICmdObj {
	return self.cmd.New(NewGitCmd("difftool").
		Arg("--no-prompt").
		ArgIf(opts.IsDirectory, "--dir-diff").
		ArgIf(opts.Staged, "--cached").
		ArgIf(opts.FromCommit != "", opts.FromCommit).
		ArgIf(opts.ToCommit != "", opts.ToCommit).
		ArgIf(opts.Reverse, "-R").
		Arg("--", opts.Filepath).
		ToArgv())
}
Repository: jesseduffield/lazygit
File: pkg/commands/git_commands/stash_loader.go (Go)

Code before:
	"github.com/jesseduffield/lazygit/pkg/common"
	"github.com/jesseduffield/lazygit/pkg/utils"
	"github.com/samber/lo"
)

type StashLoader struct {
	*common.Common
	cmd oscommands.ICmdObjBuilder
}

func NewStashLoader(
	common *common.Common,
	cmd oscommands.ICmdObjBuilder,
) *StashLoader {
	return &StashLoader{
		Common: common,
		cmd:    cmd,
	}
}

func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry {
	if filterPath == "" {
		return self.getUnfilteredStashEntries()
	}

	cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--name-only", "--pretty=%gs date@{%ct}").ToArgv()
	rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
	if err != nil {
		return self.getUnfilteredStashEntries()
	}
	stashEntries := []*models.StashEntry{}
	var currentStashEntry *models.StashEntry
	lines := utils.SplitNul(rawString)
	isAStash := func(line string) bool { return strings.HasPrefix(line, "stash@{") }
	re := regexp.MustCompile(`stash@\{(\d+)\}`)

outer:
	for i := 0; i < len(lines); i++ {
		if !isAStash(lines[i]) {
			continue
		}
		match := re.FindStringSubmatch(lines[i])
		idx, err := strconv.Atoi(match[1])
		if err != nil {
			return self.getUnfilteredStashEntries()
		}
		currentStashEntry = self.stashEntryFromLine(lines[i], idx)
		for i+1 < len(lines) && !isAStash(lines[i+1]) {
			i++
			if lines[i] == filterPath {
				stashEntries = append(stashEntries, currentStashEntry)

Review comment (suggestion):
Commit history nitpick: switching to `-z` and `SplitNul` is good, but unrelated to the other changes, so it could be a separate commit.

Code after:
	"github.com/jesseduffield/lazygit/pkg/utils"
	"github.com/samber/lo"
)

type StashLoader struct {
	*common.Common
	cmd oscommands.ICmdObjBuilder
}

func NewStashLoader(
	common *common.Common,
	cmd oscommands.ICmdObjBuilder,
) *StashLoader {
	return &StashLoader{
		Common: common,
		cmd:    cmd,
	}
}

func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry {
	if filterPath == "" {
		return self.getUnfilteredStashEntries()
	}

	cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--name-only", "--pretty=%ct|%gs").ToArgv()
	rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
	if err != nil {
		return self.getUnfilteredStashEntries()
	}
	stashEntries := []*models.StashEntry{}
	var currentStashEntry *models.StashEntry
	lines := utils.SplitNul(rawString)
	isAStash := func(line string) bool { return strings.HasPrefix(line, "stash@{") }
	re := regexp.MustCompile(`stash@\{(\d+)\}`)

outer:
	for i := 0; i < len(lines); i++ {
		if !isAStash(lines[i]) {
			continue
		}
		match := re.FindStringSubmatch(lines[i])
		idx, err := strconv.Atoi(match[1])
		if err != nil {
			return self.getUnfilteredStashEntries()
		}
		currentStashEntry = self.stashEntryFromLine(lines[i], idx)
		for i+1 < len(lines) && !isAStash(lines[i+1]) {
			i++
			if lines[i] == filterPath {
				stashEntries = append(stashEntries, currentStashEntry)
				continue outer