Compare commits

...
Author SHA1 Message Date
Jesse Duffield b726fd44e7 Use universal resource names to identify items
Refactoring the code to allow for any list item, not just branches/tags, to have an
inline status.
2023-10-02 11:44:51 +11:00
Stefan Haller 830c48f768 Avoid rendering branches view twice when refreshing
refreshWorktrees re-renders the branches view, because the branches view shows
worktrees against branches. This means that when both BRANCHES and WORKTREES are
requested to be refreshed, the branches view would be rendered twice in short
succession. This causes an ugly visual glitch when force-pushing a branch,
because when pushing is done, we would see the ↑4↓9 status come back from under
the Pushing status for a brief moment, to be replaced with a green checkmark a
moment later.

Fix this by including the worktree refresh in the branches refresh when both are
requested. This means that the two are no longer running in parallel for an
async refresh, but hopefully that's not so bad.
2023-09-26 17:25:45 +02:00
Stefan Haller 944b3994d6 Remove sync mutex
I'm pretty convinced we don't need it. Git itself does a good job of making sure
that concurrent operations don't corrupt anything.
2023-09-25 22:03:50 +02:00
Stefan Haller b4e212f5e3 Disallow pulling/pushing a branch while the branch is pushed or pulled 2023-09-25 22:03:50 +02:00
Stefan Haller 741345e51a Disallow checking out another branch while the current one is being pulled 2023-09-25 22:03:50 +02:00
Stefan Haller 2592bc6939 Add inline status for pushing tags and deleting remote tags 2023-09-25 21:39:49 +02:00
Stefan Haller 3ac5ef2d41 Show sync status in branches list
When pulling/pushing/fast-forwarding a branch, show this state in the branches
list for that branch for as long as the operation takes, to make it easier to
see when it's done (without having to stare at the status bar in the lower
left).

This will hopefully help with making these operations feel more predictable, now
that we no longer show a loader panel for them.
2023-09-25 21:39:48 +02:00
23 changed files with 277 additions and 69 deletions
+1 -5
View File
@@ -7,7 +7,6 @@ import (
"strings"
"github.com/go-errors/errors"
"github.com/sasha-s/go-deadlock"
"github.com/spf13/afero"
gogit "github.com/jesseduffield/go-git/v5"
@@ -63,7 +62,6 @@ func NewGitCommand(
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
syncMutex *deadlock.Mutex,
) (*GitCommand, error) {
currentPath, err := os.Getwd()
if err != nil {
@@ -118,7 +116,6 @@ func NewGitCommand(
gitConfig,
repoPaths,
repository,
syncMutex,
), nil
}
@@ -129,7 +126,6 @@ func NewGitCommandAux(
gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths,
repo *gogit.Repository,
syncMutex *deadlock.Mutex,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -140,7 +136,7 @@ func NewGitCommandAux(
// common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, syncMutex)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
statusCommands := git_commands.NewStatusCommands(gitCommon)
-5
View File
@@ -4,7 +4,6 @@ import (
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/sasha-s/go-deadlock"
)
type GitCommon struct {
@@ -15,8 +14,6 @@ type GitCommon struct {
repoPaths *RepoPaths
repo *gogit.Repository
config *ConfigCommands
// mutex for doing things like push/pull/fetch
syncMutex *deadlock.Mutex
}
func NewGitCommon(
@@ -27,7 +24,6 @@ func NewGitCommon(
repoPaths *RepoPaths,
repo *gogit.Repository,
config *ConfigCommands,
syncMutex *deadlock.Mutex,
) *GitCommon {
return &GitCommon{
Common: cmn,
@@ -37,6 +33,5 @@ func NewGitCommon(
repoPaths: repoPaths,
repo: repo,
config: config,
syncMutex: syncMutex,
}
}
+2 -2
View File
@@ -53,7 +53,7 @@ func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName strin
Arg(remoteName, "--delete", branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string, tagName string) error {
@@ -61,7 +61,7 @@ func (self *RemoteCommands) DeleteRemoteTag(task gocui.Task, remoteName string,
Arg(remoteName, "--delete", tagName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
// CheckRemoteBranchExists Returns remote branch
+4 -5
View File
@@ -36,7 +36,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
ToArgv()
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
return cmdObj, nil
}
@@ -70,7 +70,6 @@ func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
cmdObj.WithMutex(self.syncMutex)
return cmdObj
}
@@ -96,7 +95,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured.
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).Run()
}
func (self *SyncCommands) FastForward(
@@ -110,7 +109,7 @@ func (self *SyncCommands) FastForward(
Arg(remoteBranchName + ":" + branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
@@ -118,5 +117,5 @@ func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error
Arg(remoteName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
+1 -1
View File
@@ -52,5 +52,5 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
+4
View File
@@ -65,6 +65,10 @@ func (b *Branch) ID() string {
return b.RefName()
}
func (b *Branch) URN() string {
return "branch-" + b.ID()
}
func (b *Branch) Description() string {
return b.RefName()
}
+4
View File
@@ -24,6 +24,10 @@ func (t *Tag) ID() string {
return t.RefName()
}
func (t *Tag) URN() string {
return "tag-" + t.ID()
}
func (t *Tag) Description() string {
return t.Message
}
+1
View File
@@ -27,6 +27,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetBranchListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
c.Modes().Diffing.Ref,
c.Tr,
+4 -1
View File
@@ -27,7 +27,10 @@ func NewTagsContext(
)
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetTagListDisplayStrings(viewModel.GetItems(), c.Modes().Diffing.Ref)
return presentation.GetTagListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.Modes().Diffing.Ref, c.Tr)
}
return &TagsContext{
+19 -3
View File
@@ -34,9 +34,10 @@ func NewBranchesController(
func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
return []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.press),
Description: self.c.Tr.Checkout,
Key: opts.GetKey(opts.Config.Universal.Select),
Handler: self.checkSelected(self.press),
GetDisabledReason: self.getDisabledReasonForPress,
Description: self.c.Tr.Checkout,
},
{
Key: opts.GetKey(opts.Config.Universal.New),
@@ -299,6 +300,18 @@ func (self *BranchesController) press(selectedBranch *models.Branch) error {
return self.c.Helpers().Refs.CheckoutRef(selectedBranch.Name, types.CheckoutRefOptions{})
}
func (self *BranchesController) getDisabledReasonForPress() string {
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
if currentBranch != nil {
op := self.c.State().GetItemOperation(currentBranch)
if op == types.ItemOperationFastForwarding || op == types.ItemOperationPulling {
return self.c.Tr.CantCheckoutBranchWhilePulling
}
}
return ""
}
func (self *BranchesController) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
}
@@ -571,6 +584,9 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
)
return self.c.WithWaitingStatus(message, func(task gocui.Task) error {
self.c.State().SetItemOperation(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY)
defer func() { self.c.State().ClearItemOperation(branch, context.LOCAL_BRANCHES_CONTEXT_KEY) }()
worktree, ok := self.worktreeForBranch(branch)
if ok {
self.c.LogAction(action)
+22 -9
View File
@@ -115,12 +115,15 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
}
}
includeWorktreesWithBranches := false
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
// whenever we change commits, we should update branches because the upstream/downstream
// counts can change. Whenever we change branches we should also change commits
// e.g. in the case of switching branches.
refresh("commits and commit files", self.refreshCommitsAndCommitFiles)
refresh("reflog and branches", self.refreshReflogAndBranches)
includeWorktreesWithBranches = scopeSet.Includes(types.WORKTREES)
refresh("reflog and branches", func() { self.refreshReflogAndBranches(includeWorktreesWithBranches) })
} else if scopeSet.Includes(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
@@ -157,7 +160,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
refresh("remotes", func() { _ = self.refreshRemotes() })
}
if scopeSet.Includes(types.WORKTREES) {
if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches {
refresh("worktrees", func() { _ = self.refreshWorktrees() })
}
@@ -242,7 +245,7 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
case types.INITIAL:
self.c.OnWorker(func(_ gocui.Task) {
_ = self.refreshReflogCommits()
self.refreshBranches()
self.refreshBranches(false)
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
})
@@ -251,10 +254,10 @@ func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
}
}
func (self *RefreshHelper) refreshReflogAndBranches() {
func (self *RefreshHelper) refreshReflogAndBranches(refreshWorktrees bool) {
self.refreshReflogCommitsConsideringStartup()
self.refreshBranches()
self.refreshBranches(refreshWorktrees)
}
func (self *RefreshHelper) refreshCommitsAndCommitFiles() {
@@ -419,7 +422,7 @@ func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
// self.refreshStatus is called at the end of this because that's when we can
// be sure there is a State.Model.Branches array to pick the current branch from
func (self *RefreshHelper) refreshBranches() {
func (self *RefreshHelper) refreshBranches(refreshWorktrees bool) {
self.c.Mutexes().RefreshingBranchesMutex.Lock()
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
@@ -443,6 +446,13 @@ func (self *RefreshHelper) refreshBranches() {
self.c.Model().Branches = branches
if refreshWorktrees {
self.loadWorktrees()
if err := self.c.PostRefreshUpdate(self.c.Contexts().Worktrees); err != nil {
self.c.Log.Error(err)
}
}
if err := self.c.PostRefreshUpdate(self.c.Contexts().Branches); err != nil {
self.c.Log.Error(err)
}
@@ -634,15 +644,18 @@ func (self *RefreshHelper) refreshRemotes() error {
return nil
}
func (self *RefreshHelper) refreshWorktrees() error {
func (self *RefreshHelper) loadWorktrees() {
worktrees, err := self.c.Git().Loaders.Worktrees.GetWorktrees()
if err != nil {
self.c.Log.Error(err)
self.c.Model().Worktrees = []*models.Worktree{}
return nil
}
self.c.Model().Worktrees = worktrees
}
func (self *RefreshHelper) refreshWorktrees() error {
self.loadWorktrees()
// need to refresh branches because the branches view shows worktrees against
// branches
@@ -676,7 +689,7 @@ func (self *RefreshHelper) refreshStatus() {
repoName := self.c.Git().RepoPaths.RepoName()
status := presentation.FormatStatus(repoName, currentBranch, linkedWorktreeName, workingTreeState, self.c.Tr)
status := presentation.FormatStatus(repoName, currentBranch, types.ItemOperationNone, linkedWorktreeName, workingTreeState, self.c.Tr)
self.c.SetViewContent(self.c.Views().Status, status)
}
@@ -173,11 +173,6 @@ func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey
return err
}
// these two mutexes are used by our background goroutines (triggered via `self.goEvery`. We don't want to
// switch to a repo while one of these goroutines is in the process of updating something
self.c.Mutexes().SyncMutex.Lock()
defer self.c.Mutexes().SyncMutex.Unlock()
self.c.Mutexes().RefreshingFilesMutex.Lock()
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
+1 -1
View File
@@ -106,7 +106,7 @@ func (self *StatusController) onClick() error {
}
cx, _ := self.c.Views().Status.Cursor()
upstreamStatus := presentation.BranchStatus(currentBranch, self.c.Tr)
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr)
repoName := self.c.Git().RepoPaths.RepoName()
workingTreeState := self.c.Git().Status.WorkingTreeState()
switch workingTreeState {
+42 -17
View File
@@ -7,6 +7,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -30,14 +31,16 @@ func NewSyncController(
func (self *SyncController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
bindings := []*types.Binding{
{
Key: opts.GetKey(opts.Config.Universal.Push),
Handler: opts.Guards.NoPopupPanel(self.HandlePush),
Description: self.c.Tr.Push,
Key: opts.GetKey(opts.Config.Universal.Push),
Handler: opts.Guards.NoPopupPanel(self.HandlePush),
GetDisabledReason: self.getDisabledReasonForPushOrPull,
Description: self.c.Tr.Push,
},
{
Key: opts.GetKey(opts.Config.Universal.Pull),
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
Description: self.c.Tr.Pull,
Key: opts.GetKey(opts.Config.Universal.Pull),
Handler: opts.Guards.NoPopupPanel(self.HandlePull),
GetDisabledReason: self.getDisabledReasonForPushOrPull,
Description: self.c.Tr.Pull,
},
}
@@ -56,6 +59,18 @@ func (self *SyncController) HandlePull() error {
return self.branchCheckedOut(self.pull)()
}
func (self *SyncController) getDisabledReasonForPushOrPull() string {
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
if currentBranch != nil {
op := self.c.State().GetItemOperation(currentBranch)
if op != types.ItemOperationNone {
return self.c.Tr.CantPullOrPushSameBranchTwice
}
}
return ""
}
func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func() error {
return func() error {
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
@@ -73,13 +88,13 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
if currentBranch.IsTrackingRemote() {
opts := pushOpts{}
if currentBranch.HasCommitsToPull() {
return self.requestToForcePush(opts)
return self.requestToForcePush(currentBranch, opts)
} else {
return self.pushAux(opts)
return self.pushAux(currentBranch, opts)
}
} else {
if self.c.Git().Config.GetPushToCurrent() {
return self.pushAux(pushOpts{setUpstream: true})
return self.pushAux(currentBranch, pushOpts{setUpstream: true})
} else {
return self.c.Helpers().Upstream.PromptForUpstreamWithInitialContent(currentBranch, func(upstream string) error {
upstreamRemote, upstreamBranch, err := self.c.Helpers().Upstream.ParseUpstream(upstream)
@@ -87,7 +102,7 @@ func (self *SyncController) push(currentBranch *models.Branch) error {
return self.c.Error(err)
}
return self.pushAux(pushOpts{
return self.pushAux(currentBranch, pushOpts{
setUpstream: true,
upstreamRemote: upstreamRemote,
upstreamBranch: upstreamBranch,
@@ -107,11 +122,11 @@ func (self *SyncController) pull(currentBranch *models.Branch) error {
return self.c.Error(err)
}
return self.PullAux(PullFilesOptions{Action: action})
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
})
}
return self.PullAux(PullFilesOptions{Action: action})
return self.PullAux(currentBranch, PullFilesOptions{Action: action})
}
func (self *SyncController) setCurrentBranchUpstream(upstream string) error {
@@ -139,8 +154,13 @@ type PullFilesOptions struct {
Action string
}
func (self *SyncController) PullAux(opts PullFilesOptions) error {
func (self *SyncController) PullAux(currentBranch *models.Branch, opts PullFilesOptions) error {
return self.c.WithWaitingStatus(self.c.Tr.PullingStatus, func(task gocui.Task) error {
self.c.State().SetItemOperation(currentBranch, types.ItemOperationPulling, context.LOCAL_BRANCHES_CONTEXT_KEY)
defer func() {
self.c.State().ClearItemOperation(currentBranch, context.LOCAL_BRANCHES_CONTEXT_KEY)
}()
return self.pullWithLock(task, opts)
})
}
@@ -167,8 +187,13 @@ type pushOpts struct {
setUpstream bool
}
func (self *SyncController) pushAux(opts pushOpts) error {
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
return self.c.WithWaitingStatus(self.c.Tr.PushingStatus, func(task gocui.Task) error {
self.c.State().SetItemOperation(currentBranch, types.ItemOperationPushing, context.LOCAL_BRANCHES_CONTEXT_KEY)
defer func() {
self.c.State().ClearItemOperation(currentBranch, context.LOCAL_BRANCHES_CONTEXT_KEY)
}()
self.c.LogAction(self.c.Tr.Actions.Push)
err := self.c.Git().Sync.Push(
task,
@@ -192,7 +217,7 @@ func (self *SyncController) pushAux(opts pushOpts) error {
newOpts := opts
newOpts.force = true
return self.pushAux(newOpts)
return self.pushAux(currentBranch, newOpts)
},
})
return nil
@@ -203,7 +228,7 @@ func (self *SyncController) pushAux(opts pushOpts) error {
})
}
func (self *SyncController) requestToForcePush(opts pushOpts) error {
func (self *SyncController) requestToForcePush(currentBranch *models.Branch, opts pushOpts) error {
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
if forcePushDisabled {
return self.c.ErrorMsg(self.c.Tr.ForcePushDisabled)
@@ -214,7 +239,7 @@ func (self *SyncController) requestToForcePush(opts pushOpts) error {
Prompt: self.forcePushPrompt(),
HandleConfirm: func() error {
opts.force = true
return self.pushAux(opts)
return self.pushAux(currentBranch, opts)
},
})
}
+13
View File
@@ -131,6 +131,9 @@ func (self *TagsController) remoteDelete(tag *models.Tag) error {
Prompt: confirmPrompt,
HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(t gocui.Task) error {
self.c.State().SetItemOperation(tag, types.ItemOperationDeleting, context.TAGS_CONTEXT_KEY)
defer func() { self.c.State().ClearItemOperation(tag, context.TAGS_CONTEXT_KEY) }()
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteTag)
if err := self.c.Git().Remote.DeleteRemoteTag(t, upstream, tag.Name); err != nil {
return self.c.Error(err)
@@ -190,6 +193,16 @@ func (self *TagsController) push(tag *models.Tag) error {
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
HandleConfirm: func(response string) error {
return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func(task gocui.Task) error {
self.c.State().SetItemOperation(tag, types.ItemOperationPushing, context.TAGS_CONTEXT_KEY)
defer func() {
self.c.State().ClearItemOperation(tag, context.TAGS_CONTEXT_KEY)
// Render again to remove the inline status:
self.c.OnUIThread(func() error {
_ = self.c.Contexts().Tags.HandleRender()
return nil
})
}()
self.c.LogAction(self.c.Tr.Actions.PushTag)
err := self.c.Git().Tag.Push(task, response, tag.Name)
if err != nil {
+31 -2
View File
@@ -110,6 +110,10 @@ type Gui struct {
// lazygit was opened in, or if we'll retain the one we're currently in.
RetainOriginalDir bool
itemOperations map[string]types.ItemOperation
itemOperationsMutex *deadlock.Mutex
contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo
PrevLayout PrevLayout
// this is the initial dir we are in upon opening lazygit. We hold onto this
@@ -180,6 +184,29 @@ func (self *StateAccessor) SetRetainOriginalDir(value bool) {
self.gui.RetainOriginalDir = value
}
func (self *StateAccessor) GetItemOperation(item types.HasUrn) types.ItemOperation {
self.gui.itemOperationsMutex.Lock()
defer self.gui.itemOperationsMutex.Unlock()
return self.gui.itemOperations[item.URN()]
}
func (self *StateAccessor) SetItemOperation(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey) {
self.gui.itemOperationsMutex.Lock()
defer self.gui.itemOperationsMutex.Unlock()
self.gui.itemOperations[item.URN()] = operation
self.gui.startRenderingInlineStatus(contextKey)
}
func (self *StateAccessor) ClearItemOperation(item types.HasUrn, contextKey types.ContextKey) {
self.gui.itemOperationsMutex.Lock()
defer self.gui.itemOperationsMutex.Unlock()
self.gui.stopRenderingInlineStatus(contextKey)
delete(self.gui.itemOperations, item.URN())
}
// we keep track of some stuff from one render to the next to see if certain
// things have changed
type PrevLayout struct {
@@ -273,7 +300,6 @@ func (gui *Gui) onNewRepo(startArgs appTypes.StartArgs, contextKey types.Context
gui.gitVersion,
gui.os,
git_config.NewStdCachedGitConfig(gui.Log),
gui.Mutexes.SyncMutex,
)
if err != nil {
return err
@@ -463,7 +489,6 @@ func NewGui(
RefreshingFilesMutex: &deadlock.Mutex{},
RefreshingBranchesMutex: &deadlock.Mutex{},
RefreshingStatusMutex: &deadlock.Mutex{},
SyncMutex: &deadlock.Mutex{},
LocalCommitsMutex: &deadlock.Mutex{},
SubCommitsMutex: &deadlock.Mutex{},
AuthorsMutex: &deadlock.Mutex{},
@@ -473,6 +498,10 @@ func NewGui(
},
InitialDir: initialDir,
afterLayoutFuncs: make(chan func() error, 1000),
itemOperations: make(map[string]types.ItemOperation),
itemOperationsMutex: &deadlock.Mutex{},
contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo),
}
gui.PopupHandler = popup.NewPopupHandler(
+16 -6
View File
@@ -9,6 +9,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
@@ -19,6 +20,7 @@ var branchPrefixColorCache = make(map[string]style.TextStyle)
func GetBranchListDisplayStrings(
branches []*models.Branch,
getItemOperation func(item types.HasUrn) types.ItemOperation,
fullDescription bool,
diffName string,
tr *i18n.TranslationSet,
@@ -27,13 +29,14 @@ func GetBranchListDisplayStrings(
) [][]string {
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
diffed := branch.Name == diffName
return getBranchDisplayStrings(branch, fullDescription, diffed, tr, userConfig, worktrees)
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, tr, userConfig, worktrees)
})
}
// getBranchDisplayStrings returns the display string of branch
func getBranchDisplayStrings(
b *models.Branch,
refOperation types.ItemOperation,
fullDescription bool,
diffed bool,
tr *i18n.TranslationSet,
@@ -51,7 +54,7 @@ func getBranchDisplayStrings(
}
coloredName := nameTextStyle.Sprint(displayName)
branchStatus := utils.WithPadding(ColoredBranchStatus(b, tr), 2, utils.AlignLeft)
branchStatus := utils.WithPadding(ColoredBranchStatus(b, refOperation, tr), 2, utils.AlignLeft)
if git_commands.CheckedOutByOtherWorktree(b, worktrees) {
worktreeIcon := lo.Ternary(icons.IsIconEnabled(), icons.LINKED_WORKTREE_ICON, fmt.Sprintf("(%s)", tr.LcWorktree))
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
@@ -109,9 +112,11 @@ func GetBranchTextStyle(name string) style.TextStyle {
}
}
func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
func ColoredBranchStatus(branch *models.Branch, refOperation types.ItemOperation, tr *i18n.TranslationSet) string {
colour := style.FgYellow
if branch.UpstreamGone {
if refOperation != types.ItemOperationNone {
colour = style.FgCyan
} else if branch.UpstreamGone {
colour = style.FgRed
} else if branch.MatchesUpstream() {
colour = style.FgGreen
@@ -119,10 +124,15 @@ func ColoredBranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string
colour = style.FgMagenta
}
return colour.Sprint(BranchStatus(branch, tr))
return colour.Sprint(BranchStatus(branch, refOperation, tr))
}
func BranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
func BranchStatus(branch *models.Branch, refOperation types.ItemOperation, tr *i18n.TranslationSet) string {
refOperationStr := refOperationToString(refOperation, tr)
if refOperationStr != "" {
return refOperationStr + " " + utils.Loader()
}
if !branch.IsTrackingRemote() {
return ""
}
+23
View File
@@ -0,0 +1,23 @@
package presentation
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
)
func refOperationToString(refOperation types.ItemOperation, tr *i18n.TranslationSet) string {
switch refOperation {
case types.ItemOperationNone:
return ""
case types.ItemOperationPushing:
return tr.PushingStatus
case types.ItemOperationPulling:
return tr.PullingStatus
case types.ItemOperationFastForwarding:
return tr.FastForwardingOperation
case types.ItemOperationDeleting:
return tr.DeletingStatus
}
return ""
}
+3 -2
View File
@@ -7,14 +7,15 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
)
func FormatStatus(repoName string, currentBranch *models.Branch, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
func FormatStatus(repoName string, currentBranch *models.Branch, refOperation types.ItemOperation, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
status := ""
if currentBranch.IsRealBranch() {
status += ColoredBranchStatus(currentBranch, tr) + " "
status += ColoredBranchStatus(currentBranch, refOperation, tr) + " "
}
if workingTreeState != enums.REBASE_MODE_NONE {
+17 -4
View File
@@ -4,19 +4,27 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/theme"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
func GetTagListDisplayStrings(tags []*models.Tag, diffName string) [][]string {
func GetTagListDisplayStrings(
tags []*models.Tag,
getItemOperation func(item types.HasUrn) types.ItemOperation,
diffName string,
tr *i18n.TranslationSet,
) [][]string {
return lo.Map(tags, func(tag *models.Tag, _ int) []string {
diffed := tag.Name == diffName
return getTagDisplayStrings(tag, diffed)
return getTagDisplayStrings(tag, getItemOperation(tag), diffed, tr)
})
}
// getTagDisplayStrings returns the display string of branch
func getTagDisplayStrings(t *models.Tag, diffed bool) []string {
func getTagDisplayStrings(t *models.Tag, refOperation types.ItemOperation, diffed bool, tr *i18n.TranslationSet) []string {
textStyle := theme.DefaultTextColor
if diffed {
textStyle = theme.DiffTerminalColor
@@ -26,6 +34,11 @@ func getTagDisplayStrings(t *models.Tag, diffed bool) []string {
res = append(res, textStyle.Sprint(icons.IconForTag(t)))
}
descriptionColor := style.FgYellow
res = append(res, textStyle.Sprint(t.Name), descriptionColor.Sprint(t.Description()))
descriptionStr := descriptionColor.Sprint(t.Description())
refOperationStr := refOperationToString(refOperation, tr)
if refOperationStr != "" {
descriptionStr = style.FgCyan.Sprint(refOperationStr+" "+utils.Loader()) + " " + descriptionStr
}
res = append(res, textStyle.Sprint(t.Name), descriptionStr)
return res
}
+21 -1
View File
@@ -256,7 +256,6 @@ type Mutexes struct {
RefreshingFilesMutex *deadlock.Mutex
RefreshingBranchesMutex *deadlock.Mutex
RefreshingStatusMutex *deadlock.Mutex
SyncMutex *deadlock.Mutex
LocalCommitsMutex *deadlock.Mutex
SubCommitsMutex *deadlock.Mutex
AuthorsMutex *deadlock.Mutex
@@ -265,6 +264,24 @@ type Mutexes struct {
PtyMutex *deadlock.Mutex
}
// A long-running operation associated with an item. For example, we'll show
// that a branch is being pushed from so that there's visual feedback about
// what's happening and so that you can see multiple branches' concurrent
// operations
type ItemOperation int
const (
ItemOperationNone ItemOperation = iota
ItemOperationPushing
ItemOperationPulling
ItemOperationFastForwarding
ItemOperationDeleting
)
type HasUrn interface {
URN() string
}
type IStateAccessor interface {
GetRepoPathStack() *utils.StringStack
GetRepoState() IRepoStateAccessor
@@ -277,6 +294,9 @@ type IStateAccessor interface {
SetShowExtrasWindow(bool)
GetRetainOriginalDir() bool
SetRetainOriginalDir(bool)
GetItemOperation(item HasUrn) ItemOperation
SetItemOperation(item HasUrn, operation ItemOperation, contextKey ContextKey)
ClearItemOperation(item HasUrn, contextKey ContextKey)
}
type IRepoStateAccessor interface {
+42
View File
@@ -148,3 +148,45 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error {
return nil
}
type inlineStatusInfo struct {
refCount int
stop chan struct{}
}
func (gui *Gui) startRenderingInlineStatus(contextKey types.ContextKey) {
info := gui.contextsWithInlineStatus[contextKey]
if info == nil {
info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})}
gui.contextsWithInlineStatus[contextKey] = info
go utils.Safe(func() {
ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval)
defer ticker.Stop()
outer:
for {
select {
case <-ticker.C:
gui.c.OnUIThread(func() error {
_ = gui.c.ContextForKey(contextKey).HandleRender()
return nil
})
case <-info.stop:
break outer
}
}
})
}
info.refCount++
}
func (gui *Gui) stopRenderingInlineStatus(contextKey types.ContextKey) {
if info := gui.contextsWithInlineStatus[contextKey]; info != nil {
info.refCount--
if info.refCount <= 0 {
info.stop <- struct{}{}
delete(gui.contextsWithInlineStatus, contextKey)
}
}
}
+6
View File
@@ -57,6 +57,8 @@ type TranslationSet struct {
ResetFilter string
MergeConflictsTitle string
Checkout string
CantCheckoutBranchWhilePulling string
CantPullOrPushSameBranchTwice string
NoChangedFiles string
SoftReset string
AlreadyCheckedOutBranch string
@@ -185,6 +187,7 @@ type TranslationSet struct {
ReturnToFilesPanel string
FastForward string
FastForwarding string
FastForwardingOperation string
FoundConflictsTitle string
ViewConflictsMenuItem string
AbortMenuItem string
@@ -846,6 +849,8 @@ func EnglishTranslationSet() TranslationSet {
Scroll: "Scroll",
MergeConflictsTitle: "Merge conflicts",
Checkout: "Checkout",
CantCheckoutBranchWhilePulling: "You cannot checkout another branch while pulling the current branch",
CantPullOrPushSameBranchTwice: "You cannot push or pull a branch while it is already being pushed or pulled",
FileFilter: "Filter files by status",
FilterStagedFiles: "Show only staged files",
FilterUnstagedFiles: "Show only unstaged files",
@@ -981,6 +986,7 @@ func EnglishTranslationSet() TranslationSet {
ReturnToFilesPanel: `Return to files panel`,
FastForward: `Fast-forward this branch from its upstream`,
FastForwarding: "Fast-forwarding {{.branch}}",
FastForwardingOperation: "Fast-forwarding",
FoundConflictsTitle: "Conflicts!",
ViewConflictsMenuItem: "View conflicts",
AbortMenuItem: "Abort the %s",