diff options
Diffstat (limited to 'pkg/gui')
-rw-r--r-- | pkg/gui/branches_panel.go | 30 | ||||
-rw-r--r-- | pkg/gui/commits_panel.go | 92 | ||||
-rw-r--r-- | pkg/gui/context/base_context.go | 67 | ||||
-rw-r--r-- | pkg/gui/context/view_trait.go | 2 | ||||
-rw-r--r-- | pkg/gui/files_panel.go | 165 | ||||
-rw-r--r-- | pkg/gui/merge_panel.go | 21 | ||||
-rw-r--r-- | pkg/gui/reflog_panel.go | 46 | ||||
-rw-r--r-- | pkg/gui/refresh.go | 577 | ||||
-rw-r--r-- | pkg/gui/remotes_panel.go | 24 | ||||
-rw-r--r-- | pkg/gui/simple_context.go (renamed from pkg/gui/basic_context.go) | 0 | ||||
-rw-r--r-- | pkg/gui/stash_panel.go | 7 | ||||
-rw-r--r-- | pkg/gui/status_panel.go | 28 | ||||
-rw-r--r-- | pkg/gui/submodules_panel.go | 11 | ||||
-rw-r--r-- | pkg/gui/tags_panel.go | 11 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 135 |
15 files changed, 608 insertions, 608 deletions
diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 1efde5c28..59160bc0f 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -47,36 +47,6 @@ func (gui *Gui) branchesRenderToMain() error { }) } -// gui.refreshStatus is called at the end of this because that's when we can -// be sure there is a state.Branches array to pick the current branch from -func (gui *Gui) refreshBranches() { - reflogCommits := gui.State.FilteredReflogCommits - if gui.State.Modes.Filtering.Active() { - // in filter mode we filter our reflog commits to just those containing the path - // however we need all the reflog entries to populate the recencies of our branches - // which allows us to order them correctly. So if we're filtering we'll just - // manually load all the reflog commits here - var err error - reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "") - if err != nil { - gui.c.Log.Error(err) - } - } - - branches, err := gui.git.Loaders.Branches.Load(reflogCommits) - if err != nil { - _ = gui.c.Error(err) - } - - gui.State.Branches = branches - - if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Branches); err != nil { - gui.c.Log.Error(err) - } - - gui.refreshStatus() -} - // specific functions func (gui *Gui) handleBranchPress() error { diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 26b3fac09..b2233f377 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -1,11 +1,7 @@ package gui import ( - "sync" - - "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -58,81 +54,6 @@ func (gui *Gui) branchCommitsRenderToMain() error { }) } -// 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 don't get any reflog commits, but we asynchronously get them -// and refresh the branches after that -func (gui *Gui) refreshReflogCommitsConsideringStartup() { - switch gui.State.StartupStage { - case INITIAL: - go utils.Safe(func() { - _ = gui.refreshReflogCommits() - gui.refreshBranches() - gui.State.StartupStage = COMPLETE - }) - - case COMPLETE: - _ = gui.refreshReflogCommits() - } -} - -// 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. -func (gui *Gui) refreshCommits() { - wg := sync.WaitGroup{} - wg.Add(2) - - go utils.Safe(func() { - gui.refreshReflogCommitsConsideringStartup() - - gui.refreshBranches() - wg.Done() - }) - - go utils.Safe(func() { - _ = gui.refreshCommitsWithLimit() - ctx, ok := gui.State.Contexts.CommitFiles.GetParentContext() - if ok && ctx.GetKey() == context.BRANCH_COMMITS_CONTEXT_KEY { - // This makes sense when we've e.g. just amended a commit, meaning we get a new commit SHA at the same position. - // However if we've just added a brand new commit, it pushes the list down by one and so we would end up - // showing the contents of a different commit than the one we initially entered. - // Ideally we would know when to refresh the commit files context and when not to, - // or perhaps we could just pop that context off the stack whenever cycling windows. - // For now the awkwardness remains. - commit := gui.getSelectedLocalCommit() - if commit != nil { - gui.State.Panels.CommitFiles.refName = commit.RefName() - _ = gui.refreshCommitFilesView() - } - } - wg.Done() - }) - - wg.Wait() -} - -func (gui *Gui) refreshCommitsWithLimit() error { - gui.Mutexes.BranchCommitsMutex.Lock() - defer gui.Mutexes.BranchCommitsMutex.Unlock() - - commits, err := gui.git.Loaders.Commits.GetCommits( - loaders.GetCommitsOptions{ - Limit: gui.State.Panels.Commits.LimitCommits, - FilterPath: gui.State.Modes.Filtering.GetPath(), - IncludeRebaseCommits: true, - RefName: gui.refForLog(), - All: gui.ShowWholeGitGraph, - }, - ) - if err != nil { - return err - } - gui.State.Commits = commits - - return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) -} - func (gui *Gui) refForLog() string { bisectInfo := gui.git.Bisect.GetInfo() gui.State.BisectInfo = bisectInfo @@ -148,16 +69,3 @@ func (gui *Gui) refForLog() string { return bisectInfo.GetStartSha() } - -func (gui *Gui) refreshRebaseCommits() error { - gui.Mutexes.BranchCommitsMutex.Lock() - defer gui.Mutexes.BranchCommitsMutex.Unlock() - - updatedCommits, err := gui.git.Loaders.Commits.MergeRebasingCommits(gui.State.Commits) - if err != nil { - return err - } - gui.State.Commits = updatedCommits - - return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) -} diff --git a/pkg/gui/context/base_context.go b/pkg/gui/context/base_context.go index e95b298a4..f6007fe5f 100644 --- a/pkg/gui/context/base_context.go +++ b/pkg/gui/context/base_context.go @@ -3,35 +3,48 @@ package context import "github.com/jesseduffield/lazygit/pkg/gui/types" type BaseContext struct { - Kind types.ContextKind - Key types.ContextKey + kind types.ContextKind + key types.ContextKey ViewName string - WindowName string - OnGetOptionsMap func() map[string]string + windowName string + onGetOptionsMap func() map[string]string *ParentContextMgr } +type NewBaseContextOpts struct { + Kind types.ContextKind + Key types.ContextKey + ViewName string + WindowName string + + OnGetOptionsMap func() map[string]string +} + +func NewBaseContext(opts NewBaseContextOpts) *BaseContext { + return &BaseContext{ + kind: opts.Kind, + key: opts.Key, + ViewName: opts.ViewName, + windowName: opts.WindowName, + onGetOptionsMap: opts.OnGetOptionsMap, + ParentContextMgr: &ParentContextMgr{}, + } +} + func (self *BaseContext) GetOptionsMap() map[string]string { - if self.OnGetOptionsMap != nil { - return self.OnGetOptionsMap() + if self.onGetOptionsMap != nil { + return self.onGetOptionsMap() } return nil } func (self *BaseContext) SetWindowName(windowName string) { - self.WindowName = windowName + self.windowName = windowName } func (self *BaseContext) GetWindowName() string { - windowName := self.WindowName - - if windowName != "" { - return windowName - } - - // TODO: actually set this for everything so we don't default to the view name - return self.ViewName + return self.windowName } func (self *BaseContext) GetViewName() string { @@ -39,29 +52,9 @@ func (self *BaseContext) GetViewName() string { } func (self *BaseContext) GetKind() types.ContextKind { - return self.Kind + return self.kind } func (self *BaseContext) GetKey() types.ContextKey { - return self.Key -} - -type NewBaseContextOpts struct { - Kind types.ContextKind - Key types.ContextKey - ViewName string - WindowName string - - OnGetOptionsMap func() map[string]string -} - -func NewBaseContext(opts NewBaseContextOpts) *BaseContext { - return &BaseContext{ - Kind: opts.Kind, - Key: opts.Key, - ViewName: opts.ViewName, - WindowName: opts.WindowName, - OnGetOptionsMap: opts.OnGetOptionsMap, - ParentContextMgr: &ParentContextMgr{}, - } + return self.key } diff --git a/pkg/gui/context/view_trait.go b/pkg/gui/context/view_trait.go index 4c02a4990..1409ed561 100644 --- a/pkg/gui/context/view_trait.go +++ b/pkg/gui/context/view_trait.go @@ -36,7 +36,7 @@ func (self *ViewTrait) SetFooter(value string) { } func (self *ViewTrait) SetOriginX(value int) { - self.getView().SetOriginX(value) + _ = self.getView().SetOriginX(value) } // tells us the bounds of line indexes shown in the view currently diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 04d05df6f..db4bd3a54 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -2,12 +2,8 @@ package gui import ( "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/commands/loaders" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/types/enums" - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/filetree" - "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" "github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -87,167 +83,6 @@ func (gui *Gui) filesRenderToMain() error { return gui.refreshMainViews(refreshOpts) } -func (gui *Gui) refreshFilesAndSubmodules() error { - gui.Mutexes.RefreshingFilesMutex.Lock() - gui.State.IsRefreshingFiles = true - defer func() { - gui.State.IsRefreshingFiles = false - gui.Mutexes.RefreshingFilesMutex.Unlock() - }() - - prevSelectedPath := gui.getSelectedPath() - - if err := gui.refreshStateSubmoduleConfigs(); err != nil { - return err - } - - if err := gui.refreshMergeState(); err != nil { - return err - } - - if err := gui.refreshStateFiles(); err != nil { - return err - } - - gui.OnUIThread(func() error { - if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil { - gui.c.Log.Error(err) - } - - if types.ContextKey(gui.Views.Files.Context) == context.FILES_CONTEXT_KEY { - // doing this a little custom (as opposed to using gui.c.PostRefreshUpdate) because we handle selecting the file explicitly below - if err := gui.State.Contexts.Files.HandleRender(); err != nil { - return err - } - } - - if gui.currentContext().GetKey() == context.FILES_CONTEXT_KEY { - currentSelectedPath := gui.getSelectedPath() - alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath - if !alreadySelected { - gui.takeOverMergeConflictScrolling() - } - - gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) - return gui.filesRenderToMain() - } - - return nil - }) - - return nil -} - -func (gui *Gui) refreshStateFiles() error { - state := gui.State - - // keep track of where the cursor is currently and the current file names - // when we refresh, go looking for a matching name - // move the cursor to there. - - selectedNode := gui.getSelectedFileNode() - - prevNodes := gui.State.FileTreeViewModel.GetAllItems() - prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx - - // If git thinks any of our files have inline merge conflicts, but they actually don't, - // we stage them. - // Note that if files with merge conflicts have both arisen and have been resolved - // between refreshes, we won't stage them here. This is super unlikely though, - // and this approach spares us from having to call `git status` twice in a row. - // Although this also means that at startup we won't be staging anything until - // we call git status again. - pathsToStage := []string{} - prevConflictFileCount := 0 - for _, file := range state.FileTreeViewModel.GetAllFiles() { - if file.HasMergeConflicts { - prevConflictFileCount++ - } - if file.HasInlineMergeConflicts { - hasConflicts, err := mergeconflicts.FileHasConflictMarkers(file.Name) - if err != nil { - gui.Log.Error(err) - } else if !hasConflicts { - pathsToStage = append(pathsToStage, file.Name) - } - } - } - - if len(pathsToStage) > 0 { - gui.c.LogAction(gui.Tr.Actions.StageResolvedFiles) - if err := gui.git.WorkingTree.StageFiles(pathsToStage); err != nil { - return gui.c.Error(err) - } - } - - files := gui.git.Loaders.Files. - GetStatusFiles(loaders.GetStatusFileOptions{}) - - conflictFileCount := 0 - for _, file := range files { - if file.HasMergeConflicts { - conflictFileCount++ - } - } - - if gui.git.Status.WorkingTreeState() != enums.REBASE_MODE_NONE && conflictFileCount == 0 && prevConflictFileCount > 0 { - gui.OnUIThread(func() error { return gui.promptToContinueRebase() }) - } - - // for when you stage the old file of a rename and the new file is in a collapsed dir - state.FileTreeViewModel.RWMutex.Lock() - for _, file := range files { - if selectedNode != nil && selectedNode.Path != "" && file.PreviousName == selectedNode.Path { - state.FileTreeViewModel.ExpandToPath(file.Name) - } - } - - // only taking over the filter if it hasn't already been set by the user. - // Though this does make it impossible for the user to actually say they want to display all if - // conflicts are currently being shown. Hmm. Worth it I reckon. If we need to add some - // extra state here to see if the user's set the filter themselves we can do that, but - // I'd prefer to maintain as little state as possible. - if conflictFileCount > 0 { - if state.FileTreeViewModel.GetFilter() == filetree.DisplayAll { - state.FileTreeViewModel.SetFilter(filetree.DisplayConflicted) - } - } else if state.FileTreeViewModel.GetFilter() == filetree.DisplayConflicted { - state.FileTreeViewModel.SetFilter(filetree.DisplayAll) - } - - state.FileTreeViewModel.SetFiles(files) - state.FileTreeViewModel.RWMutex.Unlock() - - if err := gui.fileWatcher.addFilesToFileWatcher(files); err != nil { - return err - } - - if selectedNode != nil { - newIdx := gui.findNewSelectedIdx(prevNodes[prevSelectedLineIdx:], state.FileTreeViewModel.GetAllItems()) - if newIdx != -1 && newIdx != prevSelectedLineIdx { - newNode := state.FileTreeViewModel.GetItemAtIndex(newIdx) - // when not in tree mode, we show merge conflict files at the top, so you - // can work through them one by one without having to sift through a large - // set of files. If you have just fixed the merge conflicts of a file, we - // actually don't want to jump to that file's new position, because that - // file will now be ages away amidst the other files without merge - // conflicts: the user in this case would rather work on the next file - // with merge conflicts, which will have moved up to fill the gap left by - // the last file, meaning the cursor doesn't need to move at all. - leaveCursor := !state.FileTreeViewModel.InTreeMode() && newNode != nil && - selectedNode.File != nil && selectedNode.File.HasMergeConflicts && - newNode.File != nil && !newNode.File.HasMergeConflicts - - if !leaveCursor { - state.Panels.Files.SelectedLineIdx = newIdx - } - } - } - - gui.refreshSelectedLine(state.Panels.Files, state.FileTreeViewModel.GetItemsLength()) - return nil -} - // promptToContinueRebase asks the user if they want to continue the rebase/merge that's in progress func (gui *Gui) promptToContinueRebase() error { gui.takeOverMergeConflictScrolling() diff --git a/pkg/gui/merge_panel.go b/pkg/gui/merge_panel.go index 865eb4ffd..54783e986 100644 --- a/pkg/gui/merge_panel.go +++ b/pkg/gui/merge_panel.go @@ -8,7 +8,6 @@ import ( "math" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" "github.com/jesseduffield/lazygit/pkg/gui/types" ) @@ -284,26 +283,6 @@ func (gui *Gui) setConflictsAndRenderWithLock(path string, hasFocus bool) (bool, return gui.setConflictsAndRender(path, hasFocus) } -func (gui *Gui) refreshMergeState() error { - gui.State.Panels.Merging.Lock() - defer gui.State.Panels.Merging.Unlock() - - if gui.currentContext().GetKey() != context.MAIN_MERGING_CONTEXT_KEY { - return nil - } - - hasConflicts, err := gui.setConflictsAndRender(gui.State.Panels.Merging.GetPath(), true) - if err != nil { - return gui.c.Error(err) - } - - if !hasConflicts { - return gui.escapeMerge() - } - - return nil -} - func (gui *Gui) switchToMerge(path string) error { gui.takeOverMergeConflictScrolling() diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index 74254fa7b..7f46a2748 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -37,52 +37,6 @@ func (gui *Gui) reflogCommitsRenderToMain() error { }) } -// the reflogs panel is the only panel where we cache data, in that we only -// load entries that have been created since we last ran the call. This means -// we need to be more careful with how we use this, and to ensure we're emptying -// the reflogs array when changing contexts. -// This method also manages two things: ReflogCommits and FilteredReflogCommits. -// FilteredReflogCommits are rendered in the reflogs panel, and ReflogCommits -// are used by the branches panel to obtain recency values for sorting. -func (gui *Gui) refreshReflogCommits() error { - // pulling state into its own variable incase it gets swapped out for another state - // and we get an out of bounds exception - state := gui.State - var lastReflogCommit *models.Commit - if len(state.ReflogCommits) > 0 { - lastReflogCommit = state.ReflogCommits[0] - } - - refresh := func(stateCommits *[]*models.Commit, filterPath string) error { - commits, onlyObtainedNewReflogCommits, err := gui.git.Loaders.ReflogCommits. - GetReflogCommits(lastReflogCommit, filterPath) - if err != nil { - return gui.c.Error(err) - } - - if onlyObtainedNewReflogCommits { - *stateCommits = append(commits, *stateCommits...) - } else { - *stateCommits = commits - } - return nil - } - - if err := refresh(&state.ReflogCommits, ""); err != nil { - return err - } - - if gui.State.Modes.Filtering.Active() { - if err := refresh(&state.FilteredReflogCommits, state.Modes.Filtering.GetPath()); err != nil { - return err - } - } else { - state.FilteredReflogCommits = state.ReflogCommits - } - - return gui.c.PostRefreshUpdate(gui.State.Contexts.ReflogCommits) -} - func (gui *Gui) CheckoutReflogCommit() error { commit := gui.getSelectedReflogCommit() if commit == nil { diff --git a/pkg/gui/refresh.go b/pkg/gui/refresh.go new file mode 100644 index 000000000..bf22b5ff6 --- /dev/null +++ b/pkg/gui/refresh.go @@ -0,0 +1,577 @@ +package gui + +import ( + "fmt" + "strings" + "sync" + + "github.com/jesseduffield/lazygit/pkg/commands/loaders" + "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/types/enums" + "github.com/jesseduffield/lazygit/pkg/gui/context" + "github.com/jesseduffield/lazygit/pkg/gui/filetree" + "github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts" + "github.com/jesseduffield/lazygit/pkg/gui/presentation" + "github.com/jesseduffield/lazygit/pkg/gui/style" + "github.com/jesseduffield/lazygit/pkg/gui/types" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +func getScopeNames(scopes []types.RefreshableView) []string { + scopeNameMap := map[types.RefreshableView]string{ + types.COMMITS: "commits", + types.BRANCHES: "branches", + types.FILES: "files", + types.SUBMODULES: "submodules", + types.STASH: "stash", + types.REFLOG: "reflog", + types.TAGS: "tags", + types.REMOTES: "remotes", + types.STATUS: "status", + types.BISECT_INFO: "bisect", + } + + scopeNames := make([]string, len(scopes)) + for i, scope := range scopes { + scopeNames[i] = scopeNameMap[scope] + } + + return scopeNames +} + +func getModeName(mode types.RefreshMode) string { + switch mode { + case types.SYNC: + return "sync" + case types.ASYNC: + return "async" + case types.BLOCK_UI: + return "block-ui" + default: + return "unknown mode" + } +} + +func arrToMap(arr []types.RefreshableView) map[types.RefreshableView]bool { + output := map[types.RefreshableView]bool{} + for _, el := range arr { + output[el] = true + } + return output +} + +func (gui *Gui) Refresh(options types.RefreshOptions) error { + if options.Scope == nil { + gui.c.Log.Infof( + "refreshing all scopes in %s mode", + getModeName(options.Mode), + ) + } else { + gui.c.Log.Infof( + "refreshing the following scopes in %s mode: %s", + getModeName(options.Mode), + strings.Join(getScopeNames(options.Scope), ","), + ) + } + + wg := sync.WaitGroup{} + + f := func() { + var scopeMap map[types.RefreshableView]bool + if len(options.Scope) == 0 { + scopeMap = arrToMap([]types.RefreshableView{ + types.COMMITS, + types.BRANCHES, + types.FILES, + types.STASH, + types.REFLOG, + types.TAGS, + types.REMOTES, + types.STATUS, + types.BISECT_INFO, + }) + } else { + scopeMap = arrToMap(options.Scope) + } + + refresh := func(f func()) { + wg.Add(1) + func() { + if options.Mode == types.ASYNC { + go utils.Safe(f) + } else { + f() + } + wg.Done() + }() + } + + if scopeMap[types.COMMITS] || scopeMap[types.BRANCHES] || scopeMap[types.REFLOG] || scopeMap[types.BISECT_INFO] { + refresh(gui.refreshCommits) + } else if scopeMap[types.REBASE_COMMITS] { + // the above block handles rebase commits so we only need to call this one + // if we've asked specifically for rebase commits and not those other things + refresh(func() { _ = gui.refreshRebaseCommits() }) + } + + if scopeMap[types.FILES] || scopeMap[types.SUBMODULES] { + refresh(func() { _ = gui.refreshFilesAndSubmodules() }) + } + + if scopeMap[types.STASH] { + refresh(func() { _ = gui.refreshStashEntries() }) + } + + if scopeMap[types.TAGS] { + refresh(func() { _ = gui.refreshTags() }) + } + + if scopeMap[types.REMOTES] { + refresh(func() { _ = gui.refreshRemotes() }) + } + + wg.Wait() + + gui.refreshStatus() + + if options.Then != nil { + options.Then() + } + } + + if options.Mode == types.BLOCK_UI { + gui.OnUIThread(func() error { + f() + return nil + }) + } else { + f() + } + + 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 don't get any reflog commits, but we asynchronously get them +// and refresh the branches after that +func (gui *Gui) refreshReflogCommitsConsideringStartup() { + switch gui.State.StartupStage { + case INITIAL: + go utils.Safe(func() { + _ = gui.refreshReflogCommits() + gui.refreshBranches() + gui.State.StartupStage = COMPLETE + }) + + case COMPLETE: + _ = gui.refreshReflogCommits() + } +} + +// 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. +func (gui *Gui) refreshCommits() { + wg := sync.WaitGroup{} + wg.Add(2) + + go utils.Safe(func() { + gui.refreshReflogCommitsConsideringStartup() + + gui.refreshBranches() + wg.Done() + }) + + go utils.Safe(func() { + _ = gui.refreshCommitsWithLimit() + ctx, ok := gui.State.Contexts.CommitFiles.GetParentContext() + if ok && ctx.GetKey() == context.BRANCH_COMMITS_CONTEXT_KEY { + // This makes sense when we've e.g. just amended a commit, meaning we get a new commit SHA at the same position. + // However if we've just added a brand new commit, it pushes the list down by one and so we would end up + // showing the contents of a different commit than the one we initially entered. + // Ideally we would know when to refresh the commit files context and when not to, + // or perhaps we could just pop that context off the stack whenever cycling windows. + // For now the awkwardness remains. + commit := gui.getSelectedLocalCommit() + if commit != nil { + gui.State.Panels.CommitFiles.refName = commit.RefName() + _ = gui.refreshCommitFilesView() + } + } + wg.Done() + }) + + wg.Wait() +} + +func (gui *Gui) refreshCommitsWithLimit() error { + gui.Mutexes.BranchCommitsMutex.Lock() + defer gui.Mutexes.BranchCommitsMutex.Unlock() + + commits, err := gui.git.Loaders.Commits.GetCommits( + loaders.GetCommitsOptions{ + Limit: gui.State.Panels.Commits.LimitCommits, + FilterPath: gui.State.Modes.Filtering.GetPath(), + IncludeRebaseCommits: true, + RefName: gui.refForLog(), + All: gui.ShowWholeGitGraph, + }, + ) + if err != nil { + return err + } + gui.State.Commits = commits + + return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) +} + +func (gui *Gui) refreshRebaseCommits() error { + gui.Mutexes.BranchCommitsMutex.Lock() + defer gui.Mutexes.BranchCommitsMutex.Unlock() + + updatedCommits, err := gui.git.Loaders.Commits.MergeRebasingCommits(gui.State.Commits) + if err != nil { + return err + } + gui.State.Commits = updatedCommits + + return gui.c.PostRefreshUpdate(gui.State.Contexts.BranchCommits) +} + +func (self *Gui) refreshTags() error { + tags, err := self.git.Loaders.Tags.GetTags() + if err != nil { + return self.c.Error(err) + } + + self.State.Tags = tags + + return self.postRefreshUpdate(self.State.Contexts.Tags) +} + +func (gui *Gui) refreshStateSubmoduleConfigs() error { + configs, err := gui.git.Submodule.GetConfigs() + if err != nil { + return err + } + + gui.State.Submodules = configs + + return nil +} + +// gui.refreshStatus is called at the end of this because that's when we can +// be sure there is a state.Branches array to pick the current branch from +func (gui *Gui) refreshBranches() { + reflogCommits := gui.State.FilteredReflogCommits + if gui.State.Modes.Filtering.Active() { + // in filter mode we filter our reflog commits to just those containing the path + // however we need all the reflog entries to populate the recencies of our branches + // which allows us to order them correctly. So if we're filtering we'll just + // manually load all the reflog commits here + var err error + reflogCommits, _, err = gui.git.Loaders.ReflogCommits.GetReflogCommits(nil, "") + if err != nil { + gui.c.Log.Error(err) + } + } + + branches, err := gui.git.Loaders.Branches.Load(reflogCommits) + if err != nil { + _ = gui.c.Error(err) + } + + gui.State.Branches = branches + + if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Branches); err != nil { + gui.c.Log.Error(err) + } + + gui.refreshStatus() +} + +func (gui *Gui) refreshFilesAndSubmodules() error { + gui.Mutexes.RefreshingFilesMutex.Lock() + gui.State.IsRefreshingFiles = true + defer func() { + gui.State.IsRefreshingFiles = false + gui.Mutexes.RefreshingFilesMutex.Unlock() + }() + + prevSelectedPath := gui.getSelectedPath() + + if err := gui.refreshStateSubmoduleConfigs(); err != nil { + return err + } + + if err := gui.refreshMergeState(); err != nil { + return err + } + + if err := gui.refreshStateFiles(); err != nil { + return err + } + + gui.OnUIThread(func() error { + if err := gui.c.PostRefreshUpdate(gui.State.Contexts.Submodules); err != nil { + gui.c.Log.Error(err) + } + + if types.ContextKey(gui.Views.Files.Context) == context.FILES_CONTEXT_KEY { + // doing this a little custom (as opposed to using gui.c.PostRefreshUpdate) because we handle selecting the file explicitly below + if err := gui.State.Contexts.Files.HandleRender(); err != nil { + return err + } + } + + if gui.currentContext().GetKey() == context.FILES_CONTEXT_KEY { + currentSelectedPath := gui.getSelectedPath() + alreadySelected := prevSelectedPath != "" && currentSelectedPath == prevSelectedPath + if !alreadySelected { + gui.takeOverMergeConflictScrolling() + } + + gui.Views.Files.FocusPoint(0, gui.State.Panels.Files.SelectedLineIdx) + return gui.filesRenderToMain() + } + + return nil + }) + + return nil +} + +func (gui *Gui) refreshMergeState() error { |