diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 5ec4bb70a..799f26326 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -1116,13 +1116,24 @@ func (c *GitCommand) FetchRemote(remoteName string) error { return c.OSCommand.RunCommand("git fetch %s", remoteName) } -// GetNewReflogCommits only returns the new reflog commits since the given lastReflogCommit +type GetReflogCommitsOptions struct { + Limit int + Recycle bool +} + +// GetReflogCommits only returns the new reflog commits since the given lastReflogCommit // if none is passed (i.e. it's value is nil) then we get all the reflog commits -func (c *GitCommand) GetNewReflogCommits(lastReflogCommit *Commit) ([]*Commit, bool, error) { +func (c *GitCommand) GetReflogCommits(lastReflogCommit *Commit, options GetReflogCommitsOptions) ([]*Commit, bool, error) { commits := make([]*Commit, 0) re := regexp.MustCompile(`(\w+).*HEAD@\{([^\}]+)\}: (.*)`) - cmd := c.OSCommand.ExecutableFromString("git reflog --abbrev=20 --date=unix") - foundLastReflogCommit := false + + limitArg := "" + if options.Limit > 0 { + limitArg = fmt.Sprintf("-%d", options.Limit) + } + + cmd := c.OSCommand.ExecutableFromString(fmt.Sprintf("git reflog --abbrev=20 --date=unix %s", limitArg)) + onlyObtainedNewReflogCommits := false err := RunLineOutputCmd(cmd, func(line string) (bool, error) { match := re.FindStringSubmatch(line) if len(match) <= 1 { @@ -1138,8 +1149,8 @@ func (c *GitCommand) GetNewReflogCommits(lastReflogCommit *Commit) ([]*Commit, b Status: "reflog", } - if lastReflogCommit != nil && commit.Sha == lastReflogCommit.Sha && commit.UnixTimestamp == lastReflogCommit.UnixTimestamp { - foundLastReflogCommit = true + if options.Recycle && lastReflogCommit != nil && commit.Sha == lastReflogCommit.Sha && commit.UnixTimestamp == lastReflogCommit.UnixTimestamp { + onlyObtainedNewReflogCommits = true // after this point we already have these reflogs loaded so we'll simply return the new ones return true, nil } @@ -1151,7 +1162,7 @@ func (c *GitCommand) GetNewReflogCommits(lastReflogCommit *Commit) ([]*Commit, b return nil, false, err } - return commits, foundLastReflogCommit, nil + return commits, onlyObtainedNewReflogCommits, nil } func (c *GitCommand) ConfiguredPager() string { diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 330329fd0..45b86a546 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -64,6 +64,8 @@ func (gui *Gui) refreshBranches() { if gui.getBranchesView().Context == "local-branches" { gui.renderLocalBranchesWithSelection() } + + gui.refreshStatus() } func (gui *Gui) renderLocalBranchesWithSelection() error { diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 01e006829..9ea551451 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -71,21 +71,40 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { return nil } +// during startup, the bottleneck is fetching the reflog entries. We need these +// on startup to sort the branches by recency. So we have two phases: INITIAL, and COMPLETE. +// In the initial phase we get a small set of reflog entries so that we can ensure +// the first couple of branches are correctly positioned. Then we asynchronously +// refresh the reflog again, without a limit, and refresh the branches again when that's done. +// When we're complete, we can begin recycling reflog entries because we know we've got +// everything down to the oldest entry. +func (gui *Gui) refreshReflogCommitsConsideringStartup() { + switch gui.State.StartupStage { + case INITIAL: + gui.refreshReflogCommits(refreshReflogOptions{Limit: 100, Recycle: false}) + + go func() { + gui.refreshReflogCommits(refreshReflogOptions{Recycle: false}) + gui.refreshBranches() + gui.State.StartupStage = COMPLETE + }() + + case COMPLETE: + gui.refreshReflogCommits(refreshReflogOptions{Recycle: true}) + } +} + // whenever we change commits, we should update branches because the upstream/downstream // counts can change. Whenever we change branches we should probably also change commits -// e.g. in the case of switching branches. We also need the status to be refreshed whenever -// the working tree status changes or the branch upstream/downstream value changes. -// Given how fast the refreshStatus method is, we should really just call it every time -// we refresh, but I'm not sure how to do that asynchronously that prevents a race condition -// other than a mutex. +// e.g. in the case of switching branches. func (gui *Gui) refreshCommits() error { wg := sync.WaitGroup{} wg.Add(2) go func() { - gui.refreshReflogCommits() + gui.refreshReflogCommitsConsideringStartup() + gui.refreshBranches() - gui.refreshStatus() wg.Done() }() diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index bb8121a2d..7f7e040f0 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -178,6 +178,12 @@ type searchingState struct { searchString string } +// startup stages so we don't need to load everything at once +const ( + INITIAL = iota + COMPLETE +) + type guiState struct { Files []*commands.File Branches []*commands.Branch @@ -208,6 +214,7 @@ type guiState struct { PrevMainWidth int PrevMainHeight int OldInformation string + StartupStage int // one of INITIAL and COMPLETE. Allows us to not load everything at once } // for now the split view will always be on diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index d40467b28..3d9ecf01c 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -46,18 +46,23 @@ func (gui *Gui) handleReflogCommitSelect(g *gocui.Gui, v *gocui.View) error { return nil } -func (gui *Gui) refreshReflogCommits() error { +type refreshReflogOptions struct { + Limit int + Recycle bool +} + +func (gui *Gui) refreshReflogCommits(options refreshReflogOptions) error { var lastReflogCommit *commands.Commit if len(gui.State.ReflogCommits) > 0 { lastReflogCommit = gui.State.ReflogCommits[0] } - commits, foundLastReflogCommit, err := gui.GitCommand.GetNewReflogCommits(lastReflogCommit) + commits, onlyObtainedNewReflogCommits, err := gui.GitCommand.GetReflogCommits(lastReflogCommit, commands.GetReflogCommitsOptions(options)) if err != nil { return gui.createErrorPanel(gui.g, err.Error()) } - if foundLastReflogCommit { + if onlyObtainedNewReflogCommits { gui.State.ReflogCommits = append(commits, gui.State.ReflogCommits...) } else { // if we haven't found it we're probably in a new repo so we don't want to