Skip to content

Commit f313b40

Browse files
committed
commits: add incremental git log limit
The existing git log logic fetched the first 300 commits of a repo and displayed them in the local and sub-commit views. Once a user selected a commit beyond a threshold of 200 commits the whole repository was loaded. This is problematic with large repos e.g. the linux kernel with currently ~138k commits as lazygit slows down substantially with such a large number of commits in memory. This commit replaces the current all or only the first 300 commits logic with an incremental fetching approach: 1. The first 400 commits of repo are loaded by default. 2. If the user selects a commit beyond a threshold (current limit-100) the git log limit is increased by 400 if there are more commits available. If there are more commits available is currently checked by comparing the previous log limit with the real commit count in the model, if the commit count is less then the limit it is assumed that we reached the end of the commit log. Ideally it would be better to call `git rev-list --count xyz` in the right places and compare with this result, but this requires more changes. Adding a "paginated implementation" by utilizing `git log --skip=x --max-count=y` and appending commits to the model instead of replacing the whole collection would be nice, but this requires deeper changes to keep everything consistent. Signed-off-by: Stefan Kerkmann <[email protected]>
1 parent d56a9cf commit f313b40

File tree

8 files changed

+82
-31
lines changed

8 files changed

+82
-31
lines changed

pkg/commands/git_commands/commit_loader.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,29 @@ func NewCommitLoader(
5555
}
5656
}
5757

58+
const (
59+
GIT_LOG_FETCH_COUNT = 400
60+
GIT_LOG_FETCH_THRESHOLD = GIT_LOG_FETCH_COUNT / 4
61+
)
62+
63+
type GitLogLimit struct {
64+
Limit int
65+
}
66+
67+
func DefaultGitLogLimit() *GitLogLimit {
68+
return &GitLogLimit{Limit: GIT_LOG_FETCH_COUNT}
69+
}
70+
71+
func (self *GitLogLimit) CanFetchMoreCommits(lineIdx, logCommitCount int) bool {
72+
return lineIdx >= logCommitCount-GIT_LOG_FETCH_THRESHOLD && logCommitCount >= self.Limit
73+
}
74+
75+
func (self *GitLogLimit) Increase(realCommitCount int) {
76+
self.Limit = realCommitCount + GIT_LOG_FETCH_COUNT
77+
}
78+
5879
type GetCommitsOptions struct {
59-
Limit bool
80+
LogLimit *GitLogLimit
6081
FilterPath string
6182
FilterAuthor string
6283
IncludeRebaseCommits bool
@@ -592,7 +613,7 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) *oscommands.CmdObj {
592613
Arg(prettyFormat).
593614
Arg("--abbrev=40").
594615
ArgIf(opts.FilterAuthor != "", "--author="+opts.FilterAuthor).
595-
ArgIf(opts.Limit, "-300").
616+
ArgIf(opts.LogLimit != nil, fmt.Sprintf("--max-count=%d", opts.LogLimit.Limit)).
596617
ArgIf(opts.FilterPath != "", "--follow", "--name-status").
597618
Arg("--no-show-signature").
598619
ArgIf(opts.RefToShowDivergenceFrom != "", "--left-right").

pkg/gui/context/local_commits_context.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"time"
88

99
"github.com/jesseduffield/gocui"
10+
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
1011
"github.com/jesseduffield/lazygit/pkg/commands/models"
1112
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
1213
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -144,7 +145,7 @@ type LocalCommitsViewModel struct {
144145

145146
// If this is true we limit the amount of commits we load, for the sake of keeping things fast.
146147
// If the user attempts to scroll past the end of the list, we will load more commits.
147-
limitCommits bool
148+
gitLogLimit *git_commands.GitLogLimit
148149

149150
// If this is true we'll use git log --all when fetching the commits.
150151
showWholeGitGraph bool
@@ -153,7 +154,7 @@ type LocalCommitsViewModel struct {
153154
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
154155
self := &LocalCommitsViewModel{
155156
ListViewModel: NewListViewModel(getModel),
156-
limitCommits: true,
157+
gitLogLimit: git_commands.DefaultGitLogLimit(),
157158
showWholeGitGraph: c.UserConfig().Git.Log.ShowWholeGraph,
158159
}
159160

@@ -226,12 +227,12 @@ func (self *LocalCommitsContext) ModelSearchResults(searchStr string, caseSensit
226227
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.ModelIndexToViewIndex, searchStr)
227228
}
228229

229-
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
230-
self.limitCommits = value
230+
func (self *LocalCommitsViewModel) SetGitLogLimit(limit *git_commands.GitLogLimit) {
231+
self.gitLogLimit = limit
231232
}
232233

233-
func (self *LocalCommitsViewModel) GetLimitCommits() bool {
234-
return self.limitCommits
234+
func (self *LocalCommitsViewModel) GetGitLogLimit() *git_commands.GitLogLimit {
235+
return self.gitLogLimit
235236
}
236237

237238
func (self *LocalCommitsViewModel) SetShowWholeGitGraph(value bool) {

pkg/gui/context/sub_commits_context.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ func NewSubCommitsContext(
3434
ListViewModel: NewListViewModel(
3535
func() []*models.Commit { return c.Model().SubCommits },
3636
),
37-
ref: nil,
38-
limitCommits: true,
37+
ref: nil,
38+
gitLogLimit: git_commands.DefaultGitLogLimit(),
3939
}
4040

4141
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
@@ -145,7 +145,7 @@ type SubCommitsViewModel struct {
145145
refToShowDivergenceFrom string
146146
*ListViewModel[*models.Commit]
147147

148-
limitCommits bool
148+
gitLogLimit *git_commands.GitLogLimit
149149
showBranchHeads bool
150150
}
151151

@@ -202,12 +202,12 @@ func (self *SubCommitsContext) GetCommits() []*models.Commit {
202202
return self.getModel()
203203
}
204204

205-
func (self *SubCommitsContext) SetLimitCommits(value bool) {
206-
self.limitCommits = value
205+
func (self *SubCommitsContext) SetGitLogLimit(limit *git_commands.GitLogLimit) {
206+
self.gitLogLimit = limit
207207
}
208208

209-
func (self *SubCommitsContext) GetLimitCommits() bool {
210-
return self.limitCommits
209+
func (self *SubCommitsContext) GetGitLogLimit() *git_commands.GitLogLimit {
210+
return self.gitLogLimit
211211
}
212212

213213
func (self *SubCommitsContext) GetDiffTerminals() []string {

pkg/gui/controllers/helpers/refresh_helper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
321321
checkedOutRef := self.determineCheckedOutRef()
322322
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
323323
git_commands.GetCommitsOptions{
324-
Limit: self.c.Contexts().LocalCommits.GetLimitCommits(),
324+
LogLimit: self.c.Contexts().LocalCommits.GetGitLogLimit(),
325325
FilterPath: self.c.Modes().Filtering.GetPath(),
326326
FilterAuthor: self.c.Modes().Filtering.GetAuthor(),
327327
IncludeRebaseCommits: true,
@@ -358,7 +358,7 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
358358

359359
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
360360
git_commands.GetCommitsOptions{
361-
Limit: self.c.Contexts().SubCommits.GetLimitCommits(),
361+
LogLimit: self.c.Contexts().SubCommits.GetGitLogLimit(),
362362
FilterPath: self.c.Modes().Filtering.GetPath(),
363363
FilterAuthor: self.c.Modes().Filtering.GetAuthor(),
364364
IncludeRebaseCommits: false,

pkg/gui/controllers/helpers/refs_helper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
4444
self.c.Contexts().ReflogCommits.SetSelection(0)
4545
self.c.Contexts().LocalCommits.SetSelection(0)
4646
// loading a heap of commits is slow so we limit them whenever doing a reset
47-
self.c.Contexts().LocalCommits.SetLimitCommits(true)
47+
self.c.Contexts().LocalCommits.SetGitLogLimit(git_commands.DefaultGitLogLimit())
4848

4949
self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
5050
}
@@ -179,7 +179,7 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
179179
self.c.Contexts().LocalCommits.SetSelection(0)
180180
self.c.Contexts().ReflogCommits.SetSelection(0)
181181
// loading a heap of commits is slow so we limit them whenever doing a reset
182-
self.c.Contexts().LocalCommits.SetLimitCommits(true)
182+
self.c.Contexts().LocalCommits.SetGitLogLimit(git_commands.DefaultGitLogLimit())
183183

184184
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}})
185185

pkg/gui/controllers/helpers/sub_commits_helper.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ type ViewSubCommitsOpts struct {
3434
func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
3535
commits, err := self.c.Git().Loaders.CommitLoader.GetCommits(
3636
git_commands.GetCommitsOptions{
37-
Limit: true,
37+
LogLimit: git_commands.DefaultGitLogLimit(),
3838
FilterPath: self.c.Modes().Filtering.GetPath(),
3939
FilterAuthor: self.c.Modes().Filtering.GetAuthor(),
4040
IncludeRebaseCommits: false,
@@ -59,7 +59,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
5959
subCommitsContext.SetTitleRef(utils.TruncateWithEllipsis(opts.TitleRef, 50))
6060
subCommitsContext.SetRef(opts.Ref)
6161
subCommitsContext.SetRefToShowDivergenceFrom(opts.RefToShowDivergenceFrom)
62-
subCommitsContext.SetLimitCommits(true)
62+
subCommitsContext.SetGitLogLimit(git_commands.DefaultGitLogLimit())
6363
subCommitsContext.SetShowBranchHeads(opts.ShowBranchHeads)
6464
subCommitsContext.ClearSearchString()
6565
subCommitsContext.GetView().ClearSearch()

pkg/gui/controllers/local_commits_controller.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ import (
1818
"github.com/stefanhaller/git-todo-parser/todo"
1919
)
2020

21-
// after selecting the 200th commit, we'll load in all the rest
22-
const COMMIT_THRESHOLD = 200
23-
2421
type (
2522
PullFilesFn func() error
2623
)
@@ -1142,8 +1139,8 @@ func (self *LocalCommitsController) createTag(commit *models.Commit) error {
11421139

11431140
func (self *LocalCommitsController) openSearch() error {
11441141
// we usually lazyload these commits but now that we're searching we need to load them now
1145-
if self.context().GetLimitCommits() {
1146-
self.context().SetLimitCommits(false)
1142+
if self.context().GetGitLogLimit() != nil {
1143+
self.context().SetGitLogLimit(nil)
11471144
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}})
11481145
}
11491146

@@ -1160,7 +1157,9 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
11601157
self.context().SetShowWholeGitGraph(!self.context().GetShowWholeGitGraph())
11611158

11621159
if self.context().GetShowWholeGitGraph() {
1163-
self.context().SetLimitCommits(false)
1160+
self.context().SetGitLogLimit(nil)
1161+
} else {
1162+
self.context().SetGitLogLimit(git_commands.DefaultGitLogLimit())
11641163
}
11651164

11661165
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
@@ -1262,8 +1261,30 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
12621261
func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) {
12631262
return func(types.OnFocusOpts) {
12641263
context := self.context()
1265-
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
1266-
context.SetLimitCommits(false)
1264+
limit := context.GetGitLogLimit()
1265+
1266+
if limit == nil {
1267+
return
1268+
}
1269+
1270+
lineIdx := context.GetSelectedLineIdx()
1271+
logCommitCount := len(self.c.Model().Commits)
1272+
1273+
if self.isRebasing() {
1274+
rebaseCommitCount := lo.CountBy(self.c.Model().Commits, func(c *models.Commit) bool {
1275+
return c.IsTODO()
1276+
})
1277+
1278+
if lineIdx < rebaseCommitCount {
1279+
lineIdx = 0
1280+
} else {
1281+
lineIdx -= rebaseCommitCount
1282+
}
1283+
logCommitCount -= rebaseCommitCount
1284+
}
1285+
1286+
if limit.CanFetchMoreCommits(lineIdx, logCommitCount) {
1287+
limit.Increase(logCommitCount)
12671288
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}})
12681289
}
12691290
}

pkg/gui/controllers/sub_commits_controller.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,16 @@ func (self *SubCommitsController) GetOnRenderToMain() func() {
6464
func (self *SubCommitsController) GetOnFocus() func(types.OnFocusOpts) {
6565
return func(types.OnFocusOpts) {
6666
context := self.context()
67-
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
68-
context.SetLimitCommits(false)
67+
limit := context.GetGitLogLimit()
68+
69+
if limit == nil {
70+
return
71+
}
72+
73+
logCommitCount := len(self.c.Model().SubCommits)
74+
75+
if limit.CanFetchMoreCommits(context.GetSelectedLineIdx(), logCommitCount) {
76+
limit.Increase(logCommitCount)
6977
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.SUB_COMMITS}})
7078
}
7179
}

0 commit comments

Comments
 (0)