mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-19 22:33:16 +02:00
Show sync status in branches list (#3021)
This commit is contained in:
commit
013cfc77a1
@ -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)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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()
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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{
|
||||
|
@ -112,6 +112,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
Confirmation: helpers.NewConfirmationHelper(helperCommon),
|
||||
Mode: modeHelper,
|
||||
AppStatus: appStatusHelper,
|
||||
InlineStatus: helpers.NewInlineStatusHelper(helperCommon),
|
||||
WindowArrangement: helpers.NewWindowArrangementHelper(
|
||||
gui.c,
|
||||
windowHelper,
|
||||
|
@ -36,6 +36,7 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Select),
|
||||
Handler: self.checkSelected(self.press),
|
||||
GetDisabledReason: self.getDisabledReasonForPress,
|
||||
Description: self.c.Tr.Checkout,
|
||||
},
|
||||
{
|
||||
@ -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)
|
||||
}
|
||||
@ -563,14 +576,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
|
||||
action := self.c.Tr.Actions.FastForwardBranch
|
||||
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.FastForwarding,
|
||||
map[string]string{
|
||||
"branch": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.WithWaitingStatus(message, func(task gocui.Task) error {
|
||||
return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
worktree, ok := self.worktreeForBranch(branch)
|
||||
if ok {
|
||||
self.c.LogAction(action)
|
||||
@ -590,24 +596,17 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
WorktreeGitDir: worktreeGitDir,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return err
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.FastForward(
|
||||
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
|
||||
)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ type Helpers struct {
|
||||
Confirmation *ConfirmationHelper
|
||||
Mode *ModeHelper
|
||||
AppStatus *AppStatusHelper
|
||||
InlineStatus *InlineStatusHelper
|
||||
WindowArrangement *WindowArrangementHelper
|
||||
Search *SearchHelper
|
||||
Worktree *WorktreeHelper
|
||||
@ -81,6 +82,7 @@ func NewStubHelpers() *Helpers {
|
||||
Confirmation: &ConfirmationHelper{},
|
||||
Mode: &ModeHelper{},
|
||||
AppStatus: &AppStatusHelper{},
|
||||
InlineStatus: &InlineStatusHelper{},
|
||||
WindowArrangement: &WindowArrangementHelper{},
|
||||
Search: &SearchHelper{},
|
||||
Worktree: &WorktreeHelper{},
|
||||
|
129
pkg/gui/controllers/helpers/inline_status_helper.go
Normal file
129
pkg/gui/controllers/helpers/inline_status_helper.go
Normal file
@ -0,0 +1,129 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type InlineStatusHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewInlineStatusHelper(c *HelperCommon) *InlineStatusHelper {
|
||||
return &InlineStatusHelper{
|
||||
c: c,
|
||||
contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo),
|
||||
mutex: &deadlock.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
type InlineStatusOpts struct {
|
||||
Item types.HasUrn
|
||||
Operation types.ItemOperation
|
||||
ContextKey types.ContextKey
|
||||
}
|
||||
|
||||
type inlineStatusInfo struct {
|
||||
refCount int
|
||||
stop chan struct{}
|
||||
}
|
||||
|
||||
// A custom task for WithInlineStatus calls; it wraps the original one and
|
||||
// hides the status whenever the task is paused, and shows it again when
|
||||
// continued.
|
||||
type inlineStatusHelperTask struct {
|
||||
gocui.Task
|
||||
|
||||
inlineStatusHelper *InlineStatusHelper
|
||||
opts InlineStatusOpts
|
||||
}
|
||||
|
||||
// poor man's version of explicitly saying that struct X implements interface Y
|
||||
var _ gocui.Task = inlineStatusHelperTask{}
|
||||
|
||||
func (self inlineStatusHelperTask) Pause() {
|
||||
self.inlineStatusHelper.stop(self.opts)
|
||||
self.Task.Pause()
|
||||
|
||||
self.inlineStatusHelper.renderContext(self.opts.ContextKey)
|
||||
}
|
||||
|
||||
func (self inlineStatusHelperTask) Continue() {
|
||||
self.Task.Continue()
|
||||
self.inlineStatusHelper.start(self.opts)
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) WithInlineStatus(opts InlineStatusOpts, f func(gocui.Task) error) {
|
||||
self.c.OnWorker(func(task gocui.Task) {
|
||||
self.start(opts)
|
||||
|
||||
err := f(inlineStatusHelperTask{task, self, opts})
|
||||
if err != nil {
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.Error(err)
|
||||
})
|
||||
}
|
||||
|
||||
self.stop(opts)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) start(opts InlineStatusOpts) {
|
||||
self.c.State().SetItemOperation(opts.Item, opts.Operation)
|
||||
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
info := self.contextsWithInlineStatus[opts.ContextKey]
|
||||
if info == nil {
|
||||
info = &inlineStatusInfo{refCount: 0, stop: make(chan struct{})}
|
||||
self.contextsWithInlineStatus[opts.ContextKey] = info
|
||||
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(time.Millisecond * utils.LoaderAnimationInterval)
|
||||
defer ticker.Stop()
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
self.renderContext(opts.ContextKey)
|
||||
case <-info.stop:
|
||||
break outer
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
info.refCount++
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) stop(opts InlineStatusOpts) {
|
||||
self.mutex.Lock()
|
||||
|
||||
if info := self.contextsWithInlineStatus[opts.ContextKey]; info != nil {
|
||||
info.refCount--
|
||||
if info.refCount <= 0 {
|
||||
info.stop <- struct{}{}
|
||||
delete(self.contextsWithInlineStatus, opts.ContextKey)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
self.mutex.Unlock()
|
||||
|
||||
self.c.State().ClearItemOperation(opts.Item)
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) renderContext(contextKey types.ContextKey) {
|
||||
self.c.OnUIThread(func() error {
|
||||
_ = self.c.ContextForKey(contextKey).HandleRender()
|
||||
return nil
|
||||
})
|
||||
}
|
@ -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)
|
||||
}
|
||||
@ -636,15 +646,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
|
||||
@ -678,7 +691,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()
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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"
|
||||
)
|
||||
@ -32,11 +33,13 @@ func (self *SyncController) GetKeybindings(opts types.KeybindingsOpts) []*types.
|
||||
{
|
||||
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),
|
||||
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,8 @@ type PullFilesOptions struct {
|
||||
Action string
|
||||
}
|
||||
|
||||
func (self *SyncController) PullAux(opts PullFilesOptions) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PullingStatus, func(task gocui.Task) error {
|
||||
func (self *SyncController) PullAux(currentBranch *models.Branch, opts PullFilesOptions) error {
|
||||
return self.c.WithInlineStatus(currentBranch, types.ItemOperationPulling, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
return self.pullWithLock(task, opts)
|
||||
})
|
||||
}
|
||||
@ -167,8 +182,8 @@ type pushOpts struct {
|
||||
setUpstream bool
|
||||
}
|
||||
|
||||
func (self *SyncController) pushAux(opts pushOpts) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PushingStatus, func(task gocui.Task) error {
|
||||
func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts) error {
|
||||
return self.c.WithInlineStatus(currentBranch, types.ItemOperationPushing, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.Push)
|
||||
err := self.c.Git().Sync.Push(
|
||||
task,
|
||||
@ -192,18 +207,18 @@ func (self *SyncController) pushAux(opts pushOpts) error {
|
||||
newOpts := opts
|
||||
newOpts.force = true
|
||||
|
||||
return self.pushAux(newOpts)
|
||||
return self.pushAux(currentBranch, newOpts)
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
_ = self.c.Error(err)
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
})
|
||||
}
|
||||
|
||||
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 +229,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)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -92,10 +92,9 @@ func (self *TagsController) checkout(tag *models.Tag) error {
|
||||
func (self *TagsController) localDelete(tag *models.Tag) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalTag)
|
||||
if err := self.c.Git().Tag.LocalDelete(tag.Name); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
err := self.c.Git().Tag.LocalDelete(tag.Name)
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@ -130,10 +129,10 @@ func (self *TagsController) remoteDelete(tag *models.Tag) error {
|
||||
Title: confirmTitle,
|
||||
Prompt: confirmPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(t gocui.Task) error {
|
||||
return self.c.WithInlineStatus(tag, types.ItemOperationDeleting, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
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)
|
||||
if err := self.c.Git().Remote.DeleteRemoteTag(task, upstream, tag.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Toast(self.c.Tr.RemoteTagDeletedMessage)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
@ -189,15 +188,18 @@ func (self *TagsController) push(tag *models.Tag) error {
|
||||
InitialContent: "origin",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func(task gocui.Task) error {
|
||||
return self.c.WithInlineStatus(tag, types.ItemOperationPushing, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.PushTag)
|
||||
err := self.c.Git().Tag.Push(task, response, tag.Name)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
||||
// Render again to remove the inline status:
|
||||
self.c.OnUIThread(func() error {
|
||||
_ = self.c.Contexts().Tags.HandleRender()
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -110,6 +110,12 @@ type Gui struct {
|
||||
// lazygit was opened in, or if we'll retain the one we're currently in.
|
||||
RetainOriginalDir bool
|
||||
|
||||
// stores long-running operations associated with items (e.g. when a branch
|
||||
// is being pushed). At the moment the rule is to use an item operation when
|
||||
// we need to talk to the remote.
|
||||
itemOperations map[string]types.ItemOperation
|
||||
itemOperationsMutex *deadlock.Mutex
|
||||
|
||||
PrevLayout PrevLayout
|
||||
|
||||
// this is the initial dir we are in upon opening lazygit. We hold onto this
|
||||
@ -180,6 +186,27 @@ 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) {
|
||||
self.gui.itemOperationsMutex.Lock()
|
||||
defer self.gui.itemOperationsMutex.Unlock()
|
||||
|
||||
self.gui.itemOperations[item.URN()] = operation
|
||||
}
|
||||
|
||||
func (self *StateAccessor) ClearItemOperation(item types.HasUrn) {
|
||||
self.gui.itemOperationsMutex.Lock()
|
||||
defer self.gui.itemOperationsMutex.Unlock()
|
||||
|
||||
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,9 @@ func NewGui(
|
||||
},
|
||||
InitialDir: initialDir,
|
||||
afterLayoutFuncs: make(chan func() error, 1000),
|
||||
|
||||
itemOperations: make(map[string]types.ItemOperation),
|
||||
itemOperationsMutex: &deadlock.Mutex{},
|
||||
}
|
||||
|
||||
gui.PopupHandler = popup.NewPopupHandler(
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@ -199,3 +200,8 @@ func (self *guiCommon) RunningIntegrationTest() bool {
|
||||
func (self *guiCommon) InDemo() bool {
|
||||
return self.gui.integrationTest != nil && self.gui.integrationTest.IsDemo()
|
||||
}
|
||||
|
||||
func (self *guiCommon) WithInlineStatus(item types.HasUrn, operation types.ItemOperation, contextKey types.ContextKey, f func(gocui.Task) error) error {
|
||||
self.gui.helpers.InlineStatus.WithInlineStatus(helpers.InlineStatusOpts{Item: item, Operation: operation, ContextKey: contextKey}, f)
|
||||
return nil
|
||||
}
|
||||
|
@ -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,
|
||||
itemOperation 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, itemOperation, 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, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
||||
colour := style.FgYellow
|
||||
if branch.UpstreamGone {
|
||||
if itemOperation != 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, itemOperation, tr))
|
||||
}
|
||||
|
||||
func BranchStatus(branch *models.Branch, tr *i18n.TranslationSet) string {
|
||||
func BranchStatus(branch *models.Branch, itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
||||
itemOperationStr := itemOperationToString(itemOperation, tr)
|
||||
if itemOperationStr != "" {
|
||||
return itemOperationStr + " " + utils.Loader()
|
||||
}
|
||||
|
||||
if !branch.IsTrackingRemote() {
|
||||
return ""
|
||||
}
|
||||
|
23
pkg/gui/presentation/item_operations.go
Normal file
23
pkg/gui/presentation/item_operations.go
Normal file
@ -0,0 +1,23 @@
|
||||
package presentation
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
)
|
||||
|
||||
func itemOperationToString(itemOperation types.ItemOperation, tr *i18n.TranslationSet) string {
|
||||
switch itemOperation {
|
||||
case types.ItemOperationNone:
|
||||
return ""
|
||||
case types.ItemOperationPushing:
|
||||
return tr.PushingStatus
|
||||
case types.ItemOperationPulling:
|
||||
return tr.PullingStatus
|
||||
case types.ItemOperationFastForwarding:
|
||||
return tr.FastForwarding
|
||||
case types.ItemOperationDeleting:
|
||||
return tr.DeletingStatus
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
@ -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, itemOperation types.ItemOperation, linkedWorktreeName string, workingTreeState enums.RebaseMode, tr *i18n.TranslationSet) string {
|
||||
status := ""
|
||||
|
||||
if currentBranch.IsRealBranch() {
|
||||
status += ColoredBranchStatus(currentBranch, tr) + " "
|
||||
status += ColoredBranchStatus(currentBranch, itemOperation, tr) + " "
|
||||
}
|
||||
|
||||
if workingTreeState != enums.REBASE_MODE_NONE {
|
||||
|
@ -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, itemOperation 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())
|
||||
itemOperationStr := itemOperationToString(itemOperation, tr)
|
||||
if itemOperationStr != "" {
|
||||
descriptionStr = style.FgCyan.Sprint(itemOperationStr+" "+utils.Loader()) + " " + descriptionStr
|
||||
}
|
||||
res = append(res, textStyle.Sprint(t.Name), descriptionStr)
|
||||
return res
|
||||
}
|
||||
|
@ -87,6 +87,12 @@ type IGuiCommon interface {
|
||||
// resized, if in accordion mode.
|
||||
AfterLayout(f func() error)
|
||||
|
||||
// Wraps a function, attaching the given operation to the given item while
|
||||
// the function is executing, and also causes the given context to be
|
||||
// redrawn periodically. This allows the operation to be visualized with a
|
||||
// spinning loader animation (e.g. when a branch is being pushed).
|
||||
WithInlineStatus(item HasUrn, operation ItemOperation, contextKey ContextKey, f func(gocui.Task) error) error
|
||||
|
||||
// returns the gocui Gui struct. There is a good chance you don't actually want to use
|
||||
// this struct and instead want to use another method above
|
||||
GocuiGui() *gocui.Gui
|
||||
@ -256,7 +262,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 +270,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 +300,9 @@ type IStateAccessor interface {
|
||||
SetShowExtrasWindow(bool)
|
||||
GetRetainOriginalDir() bool
|
||||
SetRetainOriginalDir(bool)
|
||||
GetItemOperation(item HasUrn) ItemOperation
|
||||
SetItemOperation(item HasUrn, operation ItemOperation)
|
||||
ClearItemOperation(item HasUrn)
|
||||
}
|
||||
|
||||
type IRepoStateAccessor interface {
|
||||
|
@ -169,7 +169,7 @@ func chineseTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `切换到其他面板`,
|
||||
ReturnToFilesPanel: `返回文件面板`,
|
||||
FastForward: `从上游快进此分支`,
|
||||
FastForwarding: "抓取并快进 {{.branch}} ...",
|
||||
FastForwarding: "抓取并快进",
|
||||
FoundConflictsTitle: "自动合并失败",
|
||||
ViewMergeRebaseOptions: "查看 合并/变基 选项",
|
||||
NotMergingOrRebasing: "您目前既不进行变基也不进行合并",
|
||||
|
@ -134,7 +134,7 @@ func dutchTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `Ga naar een ander paneel`,
|
||||
ReturnToFilesPanel: `Ga terug naar het bestanden paneel`,
|
||||
FastForward: `Fast-forward deze branch vanaf zijn upstream`,
|
||||
FastForwarding: "Fast-forwarding {{.branch}} ...",
|
||||
FastForwarding: "Fast-forwarding",
|
||||
FoundConflictsTitle: "Conflicten!",
|
||||
ViewMergeRebaseOptions: "Bekijk merge/rebase opties",
|
||||
NotMergingOrRebasing: "Je bent momenteel niet aan het rebasen of mergen",
|
||||
|
@ -57,6 +57,8 @@ type TranslationSet struct {
|
||||
ResetFilter string
|
||||
MergeConflictsTitle string
|
||||
Checkout string
|
||||
CantCheckoutBranchWhilePulling string
|
||||
CantPullOrPushSameBranchTwice string
|
||||
NoChangedFiles string
|
||||
SoftReset string
|
||||
AlreadyCheckedOutBranch string
|
||||
@ -846,6 +848,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",
|
||||
@ -980,7 +984,7 @@ func EnglishTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `Switch to other panel (staged/unstaged changes)`,
|
||||
ReturnToFilesPanel: `Return to files panel`,
|
||||
FastForward: `Fast-forward this branch from its upstream`,
|
||||
FastForwarding: "Fast-forwarding {{.branch}}",
|
||||
FastForwarding: "Fast-forwarding",
|
||||
FoundConflictsTitle: "Conflicts!",
|
||||
ViewConflictsMenuItem: "View conflicts",
|
||||
AbortMenuItem: "Abort the %s",
|
||||
|
@ -170,7 +170,7 @@ func koreanTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `패널 전환`,
|
||||
ReturnToFilesPanel: `파일 목록으로 돌아가기`,
|
||||
FastForward: `Fast-forward this branch from its upstream`,
|
||||
FastForwarding: "Fast-forwarding {{.branch}} ...",
|
||||
FastForwarding: "Fast-forwarding",
|
||||
FoundConflictsTitle: "Auto-merge failed",
|
||||
ViewMergeRebaseOptions: "View merge/rebase options",
|
||||
NotMergingOrRebasing: "You are currently neither rebasing nor merging",
|
||||
|
@ -201,7 +201,7 @@ func RussianTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `Переключиться на другую панель (проиндексированные/непроиндексированные изменения)`,
|
||||
ReturnToFilesPanel: `Вернуться к панели файлов`,
|
||||
FastForward: `Перемотать эту ветку вперёд из её upstream-ветки`,
|
||||
FastForwarding: "Получить изменения и перемотать вперёд {{.branch}} ...",
|
||||
FastForwarding: "Получить изменения и перемотать вперёд",
|
||||
FoundConflictsTitle: "Конфликты!",
|
||||
ViewConflictsMenuItem: "Просмотр конфликтов",
|
||||
AbortMenuItem: "Прервать %s",
|
||||
|
@ -234,7 +234,7 @@ func traditionalChineseTranslationSet() TranslationSet {
|
||||
ToggleStagingPanel: `切換至另一個面板 (已預存/未預存更改)`,
|
||||
ReturnToFilesPanel: `返回檔案面板`,
|
||||
FastForward: `從上游快進此分支`,
|
||||
FastForwarding: "{{.branch}} 的擷取和快進中...",
|
||||
FastForwarding: "的擷取和快進中",
|
||||
FoundConflictsTitle: "自動合併失敗",
|
||||
ViewMergeRebaseOptions: "查看合併/變基選項",
|
||||
NotMergingOrRebasing: "你當前既不在變基也不在合併中",
|
||||
|
Loading…
x
Reference in New Issue
Block a user