mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-28 10:15:32 -05:00
The model<->view index conversions were derived from arrays that only renderLines populated. That made them depend on the list having been rendered (so a conversion before the first render ignored the non-model items), and it made them go stale whenever the model changed after a render: converting an index then returned a wrong result, and once the model had grown past the last rendered length the conversion indexed a too-short array and panicked (seen in cherry_pick under -race). The conversion is a pure function of the current list length and the current non-model items, and needs none of the rendered display strings. Compute it directly and drop the cached arrays, so the result is always consistent with the current model and no longer depends on rendering. searchModelCommits converts every commit's index, and building the non-model items can be O(len) mid-rebase, so it would now be quadratic; snapshot the non-model items once via modelToViewIndexConverter instead of rebuilding them per index. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
245 lines
6.5 KiB
Go
245 lines
6.5 KiB
Go
package context
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/gocui"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
|
"github.com/samber/lo"
|
|
)
|
|
|
|
type SubCommitsContext struct {
|
|
c *ContextCommon
|
|
|
|
*SubCommitsViewModel
|
|
*ListContextTrait
|
|
*DynamicTitleBuilder
|
|
*SearchTrait
|
|
}
|
|
|
|
var (
|
|
_ types.IListContext = (*SubCommitsContext)(nil)
|
|
_ types.DiffableContext = (*SubCommitsContext)(nil)
|
|
_ types.ISearchableContext = (*SubCommitsContext)(nil)
|
|
)
|
|
|
|
func NewSubCommitsContext(
|
|
c *ContextCommon,
|
|
) *SubCommitsContext {
|
|
viewModel := &SubCommitsViewModel{
|
|
ListViewModel: NewListViewModel(
|
|
func() []*models.Commit { return c.Model().SubCommits },
|
|
),
|
|
ref: nil,
|
|
limitCommits: true,
|
|
}
|
|
|
|
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
|
|
// This can happen if a sub-commits view is asked to be rerendered while
|
|
// it is invisible; for example when switching screen modes, which
|
|
// rerenders all views.
|
|
if viewModel.GetRef() == nil {
|
|
return [][]string{}
|
|
}
|
|
|
|
var selectedCommitHashPtr *string
|
|
if c.Context().Current().GetKey() == SUB_COMMITS_CONTEXT_KEY {
|
|
selectedCommit := viewModel.GetSelected()
|
|
if selectedCommit != nil {
|
|
selectedCommitHashPtr = selectedCommit.HashPtr()
|
|
}
|
|
}
|
|
branches := []*models.Branch{}
|
|
if viewModel.GetShowBranchHeads() {
|
|
branches = c.Model().Branches
|
|
}
|
|
hasRebaseUpdateRefsConfig := c.Git().Config.GetRebaseUpdateRefs()
|
|
return presentation.GetCommitListDisplayStrings(
|
|
c.Common,
|
|
c.Model().SubCommits,
|
|
branches,
|
|
viewModel.GetRef().RefName(),
|
|
hasRebaseUpdateRefsConfig,
|
|
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
|
c.Modes().CherryPicking.SelectedHashSet(),
|
|
c.Modes().Diffing.Ref,
|
|
"",
|
|
c.UserConfig().Gui.TimeFormat,
|
|
c.UserConfig().Gui.ShortTimeFormat,
|
|
time.Now(),
|
|
c.UserConfig().Git.ParseEmoji,
|
|
selectedCommitHashPtr,
|
|
startIdx,
|
|
endIdx,
|
|
shouldShowGraph(c),
|
|
git_commands.NewNullBisectInfo(),
|
|
)
|
|
}
|
|
|
|
getNonModelItems := func() []*NonModelItem {
|
|
result := []*NonModelItem{}
|
|
if viewModel.GetRefToShowDivergenceFrom() != "" {
|
|
_, upstreamIdx, found := lo.FindIndexOf(
|
|
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceRight })
|
|
if !found {
|
|
upstreamIdx = 0
|
|
}
|
|
result = append(result, &NonModelItem{
|
|
Index: upstreamIdx,
|
|
Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderRemote),
|
|
})
|
|
|
|
_, localIdx, found := lo.FindIndexOf(
|
|
c.Model().SubCommits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft })
|
|
if !found {
|
|
localIdx = len(c.Model().SubCommits)
|
|
}
|
|
result = append(result, &NonModelItem{
|
|
Index: localIdx,
|
|
Content: fmt.Sprintf("--- %s ---", c.Tr.DivergenceSectionHeaderLocal),
|
|
})
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
ctx := &SubCommitsContext{
|
|
c: c,
|
|
SubCommitsViewModel: viewModel,
|
|
SearchTrait: NewSearchTrait(c),
|
|
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.SubCommitsDynamicTitle),
|
|
ListContextTrait: &ListContextTrait{
|
|
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
|
View: c.Views().SubCommits,
|
|
WindowName: "branches",
|
|
Key: SUB_COMMITS_CONTEXT_KEY,
|
|
Kind: types.SIDE_CONTEXT,
|
|
Focusable: true,
|
|
Transient: true,
|
|
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_SCREEN_MODE_CHANGES,
|
|
NeedsRerenderOnHeightChange: true,
|
|
})),
|
|
ListRenderer: ListRenderer{
|
|
list: viewModel,
|
|
getDisplayStrings: getDisplayStrings,
|
|
getNonModelItems: getNonModelItems,
|
|
},
|
|
c: c,
|
|
refreshViewportOnChange: true,
|
|
renderOnlyVisibleLines: true,
|
|
},
|
|
}
|
|
|
|
return ctx
|
|
}
|
|
|
|
type SubCommitsViewModel struct {
|
|
// name of the ref that the sub-commits are shown for
|
|
ref models.Ref
|
|
refToShowDivergenceFrom string
|
|
*ListViewModel[*models.Commit]
|
|
|
|
limitCommits bool
|
|
showBranchHeads bool
|
|
}
|
|
|
|
func (self *SubCommitsViewModel) SetRef(ref models.Ref) {
|
|
self.ref = ref
|
|
}
|
|
|
|
func (self *SubCommitsViewModel) GetRef() models.Ref {
|
|
return self.ref
|
|
}
|
|
|
|
func (self *SubCommitsViewModel) SetRefToShowDivergenceFrom(ref string) {
|
|
self.refToShowDivergenceFrom = ref
|
|
}
|
|
|
|
func (self *SubCommitsViewModel) GetRefToShowDivergenceFrom() string {
|
|
return self.refToShowDivergenceFrom
|
|
}
|
|
|
|
func (self *SubCommitsViewModel) SetShowBranchHeads(value bool) {
|
|
self.showBranchHeads = value
|
|
}
|
|
|
|
func (self *SubCommitsViewModel) GetShowBranchHeads() bool {
|
|
return self.showBranchHeads
|
|
}
|
|
|
|
func (self *SubCommitsContext) CanRebase() bool {
|
|
return false
|
|
}
|
|
|
|
func (self *SubCommitsContext) GetSelectedRef() models.Ref {
|
|
commit := self.GetSelected()
|
|
if commit == nil {
|
|
return nil
|
|
}
|
|
return commit
|
|
}
|
|
|
|
func (self *SubCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
|
|
commits, startIdx, endIdx := self.GetSelectedItems()
|
|
if commits == nil || startIdx == endIdx {
|
|
return nil
|
|
}
|
|
from := commits[len(commits)-1]
|
|
to := commits[0]
|
|
if from.Divergence != to.Divergence {
|
|
return nil
|
|
}
|
|
return &types.RefRange{From: from, To: to}
|
|
}
|
|
|
|
func (self *SubCommitsContext) GetCommits() []*models.Commit {
|
|
return self.getModel()
|
|
}
|
|
|
|
func (self *SubCommitsContext) SetLimitCommits(value bool) {
|
|
self.limitCommits = value
|
|
}
|
|
|
|
func (self *SubCommitsContext) GetLimitCommits() bool {
|
|
return self.limitCommits
|
|
}
|
|
|
|
func (self *SubCommitsContext) GetDiffTerminals() []string {
|
|
itemId := self.GetSelectedItemId()
|
|
|
|
return []string{itemId}
|
|
}
|
|
|
|
func (self *SubCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
|
commits, _, _ := self.GetSelectedItems()
|
|
if commits == nil {
|
|
return ""
|
|
}
|
|
return commits[0].Hash()
|
|
}
|
|
|
|
func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
|
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.modelToViewIndexConverter(), searchStr)
|
|
}
|
|
|
|
func (self *SubCommitsContext) IndexForGotoBottom() int {
|
|
commits := self.GetCommits()
|
|
selectedIdx := self.GetSelectedLineIdx()
|
|
if selectedIdx >= 0 && selectedIdx < len(commits)-1 {
|
|
if commits[selectedIdx+1].Status != models.StatusMerged {
|
|
_, idx, found := lo.FindIndexOf(commits, func(c *models.Commit) bool {
|
|
return c.Status == models.StatusMerged
|
|
})
|
|
if found {
|
|
return idx - 1
|
|
}
|
|
}
|
|
}
|
|
|
|
return self.list.Len() - 1
|
|
}
|