Skip to content

Add pending task capability for opening pull requests #4370

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pkg/gui/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ func (gui *Gui) resetHelpersAndControllers() {

syncController := controllers.NewSyncController(
common,
&gui.branchesBeingPushed,
)

submodulesController := controllers.NewSubmodulesController(common)
Expand Down Expand Up @@ -182,7 +183,7 @@ func (gui *Gui) resetHelpersAndControllers() {
renameSimilarityThresholdController := controllers.NewRenameSimilarityThresholdController(common)
verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common, &gui.viewBufferManagerMap)

branchesController := controllers.NewBranchesController(common)
branchesController := controllers.NewBranchesController(common, &gui.branchesBeingPushed)
gitFlowController := controllers.NewGitFlowController(common)
stashController := controllers.NewStashController(common)
commitFilesController := controllers.NewCommitFilesController(common)
Expand Down
60 changes: 56 additions & 4 deletions pkg/gui/controllers/branches_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package controllers
import (
"errors"
"fmt"
"sync"

"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
Expand All @@ -12,18 +13,21 @@ import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
)

type BranchesController struct {
baseController
*ListControllerTrait[*models.Branch]
c *ControllerCommon
c *ControllerCommon
branchesBeingPushed *sync.Map
}

var _ types.IController = &BranchesController{}

func NewBranchesController(
c *ControllerCommon,
branchesBeingPushed *sync.Map,
) *BranchesController {
return &BranchesController{
baseController: baseController{},
Expand All @@ -34,6 +38,7 @@ func NewBranchesController(
c.Contexts().Branches.GetSelected,
c.Contexts().Branches.GetSelectedItems,
),
branchesBeingPushed: branchesBeingPushed,
}
}

Expand Down Expand Up @@ -421,11 +426,58 @@ func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktr
return nil
}

func (self *BranchesController) blockForBranchFinishPush(branch *models.Branch) *models.Branch {
if val, ok := self.branchesBeingPushed.Load(branch.Name); ok {
// We only store waitgroups in here, so there isn't much thought into the false case
if wg, ok := val.(*deadlock.WaitGroup); ok {
wg.Wait()
// When we refresh after a branch push, the entire branch list gets replaced, so we must re-retrieve the
// branch pointer. The currently selected item might have changed since we initially requested the pull request,
// but the commit hash should continue to be the same.
updatedBranch, found := lo.Find(self.context().ListViewModel.GetItems(), func(b *models.Branch) bool {
return b.CommitHash == branch.CommitHash
})
// If it _isn't_ found, something wack is going on, so lets stick with the original
if found {
branch = updatedBranch
}
}
}
return branch
}

func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
if !selectedBranch.IsTrackingRemote() {
return errors.New(self.c.Tr.PullRequestNoUpstream)
message := utils.ResolvePlaceholderString(
self.c.Tr.PendingCreatePullRequest, map[string]string{
"branchName": selectedBranch.Name,
})
err := self.c.WithPendingMessage(message,
func(_ gocui.Task) error {
_ = self.blockForBranchFinishPush(selectedBranch)
return nil
},
func(_ gocui.Task) error {
// When we refresh after a branch push, the entire branch list gets replaced, so we must re-retrieve the
// branch pointer. The currently selected item might have changed since we initially requested the pull request,
// but the commit hash should continue to be the same.
updatedBranch, found := lo.Find(self.context().ListViewModel.GetItems(), func(b *models.Branch) bool {
return b.CommitHash == selectedBranch.CommitHash
})
// If it _isn't_ found, something wack is going on, so lets stick with the original
if found {
selectedBranch = updatedBranch
}

if !selectedBranch.IsTrackingRemote() {
return errors.New(self.c.Tr.PullRequestNoUpstream)
}

return self.createPullRequest(selectedBranch.UpstreamBranch, "")
})
if err != nil {
return err
}
return self.createPullRequest(selectedBranch.UpstreamBranch, "")
return nil
}

func (self *BranchesController) handleCreatePullRequestMenu(selectedBranch *models.Branch) error {
Expand Down
13 changes: 13 additions & 0 deletions pkg/gui/controllers/helpers/app_status_helper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package helpers

import (
"context"
"time"

"github.com/jesseduffield/gocui"
Expand Down Expand Up @@ -64,6 +65,18 @@ func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task
})
}

// WithPendingMessage displays message and begins executing pending.
// After pending finishes, via completion or cancellation, the message will be removed.
// If pending finishes with success, main will begin executing.
func (self *AppStatusHelper) WithPendingMessage(
message string,
pending func(gocui.Task) error,
main func(gocui.Task) error,
) {
pendingTask := self.c.OnWorkerPending(context.Background(), pending, main)
self.WithWaitingStatus(message, func(_ gocui.Task) error { <-pendingTask.Ctx.Done(); return nil })
}

func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.Task) error, task gocui.Task) error {
return self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) error {
return f(appStatusHelperTask{task, waitingStatusHandle})
Expand Down
25 changes: 16 additions & 9 deletions pkg/gui/controllers/helpers/refresh_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,6 @@ func NewRefreshHelper(
}

func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
if options.Mode == types.ASYNC && options.Then != nil {
panic("RefreshOptions.Then doesn't work with mode ASYNC")
}

t := time.Now()
defer func() {
self.c.Log.Infof("Refresh took %s", time.Since(t))
Expand Down Expand Up @@ -103,7 +99,9 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
// everything happens fast and it's better to have everything update
// in the one frame
if !self.c.InDemo() && options.Mode == types.ASYNC {
wg.Add(1)
self.c.OnWorker(func(t gocui.Task) error {
defer wg.Done()
f()
return nil
})
Expand Down Expand Up @@ -189,11 +187,20 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {

self.refreshStatus()

wg.Wait()

if options.Then != nil {
if err := options.Then(); err != nil {
return err
if options.Mode == types.ASYNC {
self.c.OnWorker(func(t gocui.Task) error {
wg.Wait()
if options.Then != nil {
return options.Then()
}
return nil
})
} else {
wg.Wait()
if options.Then != nil {
if err := options.Then(); err != nil {
return err
}
}
}

Expand Down
7 changes: 7 additions & 0 deletions pkg/gui/controllers/quit_actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ func (self *QuitActions) confirmQuitDuringUpdate() error {
}

func (self *QuitActions) Escape() error {
pendingTasks := self.c.State().GetPendingTasks()
if len(pendingTasks) > 0 {
head := pendingTasks[0]
head.CancelFunc()
return nil
}

currentContext := self.c.Context().Current()

if listContext, ok := currentContext.(types.IListContext); ok {
Expand Down
22 changes: 18 additions & 4 deletions pkg/gui/controllers/sync_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,33 @@ import (
"errors"
"fmt"
"strings"
"sync"

"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"
"github.com/sasha-s/go-deadlock"
)

type SyncController struct {
baseController
c *ControllerCommon
c *ControllerCommon
branchesBeingPushed *sync.Map
}

var _ types.IController = &SyncController{}

func NewSyncController(
common *ControllerCommon,
branchesBeingPushed *sync.Map,
) *SyncController {
return &SyncController{
baseController: baseController{},
c: common,
baseController: baseController{},
c: common,
branchesBeingPushed: branchesBeingPushed,
}
}

Expand Down Expand Up @@ -194,6 +199,12 @@ type pushOpts struct {

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 {
val, _ := self.branchesBeingPushed.LoadOrStore(currentBranch.Name, &deadlock.WaitGroup{})
wg, ok := val.(*deadlock.WaitGroup)
wg.Add(1)
if !ok {
self.c.Log.Fatalf("Somehow the WaitGroup we put in, didn't come back out as a WaitGroup! Received %s instead of type %t", val, val)
}
self.c.LogAction(self.c.Tr.Actions.Push)
err := self.c.Git().Sync.Push(
task,
Expand Down Expand Up @@ -229,7 +240,10 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
}
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Then: func() error {
wg.Done()
return nil
}})
})
}

Expand Down
13 changes: 13 additions & 0 deletions pkg/gui/gui.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ type Gui struct {
// so that you can return to the superproject
RepoPathStack *utils.StringStack

branchesBeingPushed sync.Map // keys: branchName string, value: deadlock.WaitGroup

// this tells us whether our views have been initially set up
ViewsSetup bool

Expand Down Expand Up @@ -156,6 +158,10 @@ func (self *StateAccessor) GetRepoPathStack() *utils.StringStack {
return self.gui.RepoPathStack
}

func (self *StateAccessor) GetPendingTasks() []*gocui.PendingTask {
return self.gui.g.PendingTasks()
}

func (self *StateAccessor) GetUpdating() bool {
return self.gui.Updating
}
Expand Down Expand Up @@ -690,6 +696,9 @@ func NewGui(
func() types.Context { return gui.State.ContextMgr.Current() },
gui.createMenu,
func(message string, f func(gocui.Task) error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
func(message string, pending func(gocui.Task) error, f func(gocui.Task) error) {
gui.helpers.AppStatus.WithPendingMessage(message, pending, f)
},
func(message string, f func() error) error {
return gui.helpers.AppStatus.WithWaitingStatusSync(message, f)
},
Expand Down Expand Up @@ -1118,6 +1127,10 @@ func (gui *Gui) onWorker(f func(gocui.Task) error) {
gui.g.OnWorker(f)
}

func (gui *Gui) onWorkerPending(ctx goContext.Context, pending func(gocui.Task) error, main func(gocui.Task) error) *gocui.PendingTask {
return gui.g.OnWorkerPending(ctx, pending, main)
}

func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
return gui.helpers.WindowArrangement.GetWindowDimensions(informationStr, appStatus)
}
Expand Down
6 changes: 6 additions & 0 deletions pkg/gui/gui_common.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package gui

import (
"context"

"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
Expand Down Expand Up @@ -115,6 +117,10 @@ func (self *guiCommon) OnWorker(f func(gocui.Task) error) {
self.gui.onWorker(f)
}

func (self *guiCommon) OnWorkerPending(ctx context.Context, pending func(gocui.Task) error, main func(gocui.Task) error) *gocui.PendingTask {
return self.gui.onWorkerPending(ctx, pending, main)
}

func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) {
self.gui.refreshMainViews(opts)
}
Expand Down
8 changes: 8 additions & 0 deletions pkg/gui/popup/popup_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type PopupHandler struct {
currentContextFn func() types.Context
createMenuFn func(types.CreateMenuOptions) error
withWaitingStatusFn func(message string, f func(gocui.Task) error)
withPendingMessageFn func(message string, pending func(gocui.Task) error, f func(gocui.Task) error)
withWaitingStatusSyncFn func(message string, f func() error) error
toastFn func(message string, kind types.ToastKind)
getPromptInputFn func() string
Expand All @@ -34,6 +35,7 @@ func NewPopupHandler(
currentContextFn func() types.Context,
createMenuFn func(types.CreateMenuOptions) error,
withWaitingStatusFn func(message string, f func(gocui.Task) error),
withPendingMessageFn func(message string, pending func(gocui.Task) error, f func(gocui.Task) error),
withWaitingStatusSyncFn func(message string, f func() error) error,
toastFn func(message string, kind types.ToastKind),
getPromptInputFn func() string,
Expand All @@ -47,6 +49,7 @@ func NewPopupHandler(
currentContextFn: currentContextFn,
createMenuFn: createMenuFn,
withWaitingStatusFn: withWaitingStatusFn,
withPendingMessageFn: withPendingMessageFn,
withWaitingStatusSyncFn: withWaitingStatusSyncFn,
toastFn: toastFn,
getPromptInputFn: getPromptInputFn,
Expand Down Expand Up @@ -75,6 +78,11 @@ func (self *PopupHandler) WithWaitingStatus(message string, f func(gocui.Task) e
return nil
}

func (self *PopupHandler) WithPendingMessage(message string, pending func(gocui.Task) error, f func(gocui.Task) error) error {
self.withPendingMessageFn(message, pending, f)
return nil
}

func (self *PopupHandler) WithWaitingStatusSync(message string, f func() error) error {
return self.withWaitingStatusSyncFn(message, f)
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/gui/types/common.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package types

import (
"context"

"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
Expand Down Expand Up @@ -68,6 +70,7 @@ type IGuiCommon interface {
// Runs a function in a goroutine. Use this whenever you want to run a goroutine and keep track of the fact
// that lazygit is still busy. See docs/dev/Busy.md
OnWorker(f func(gocui.Task) error)
OnWorkerPending(ctx context.Context, pending func(gocui.Task) error, main func(gocui.Task) error) *gocui.PendingTask
// Function to call at the end of our 'layout' function which renders views
// For example, you may want a view's line to be focused only after that view is
// resized, if in accordion mode.
Expand Down Expand Up @@ -126,6 +129,7 @@ type IPopupHandler interface {
// Shows a popup prompting the user for input.
Prompt(opts PromptOpts)
WithWaitingStatus(message string, f func(gocui.Task) error) error
WithPendingMessage(message string, pending func(gocui.Task) error, f func(gocui.Task) error) error
WithWaitingStatusSync(message string, f func() error) error
Menu(opts CreateMenuOptions) error
Toast(message string)
Expand Down Expand Up @@ -353,6 +357,7 @@ type IStateAccessor interface {
GetItemOperation(item HasUrn) ItemOperation
SetItemOperation(item HasUrn, operation ItemOperation)
ClearItemOperation(item HasUrn)
GetPendingTasks() []*gocui.PendingTask
}

type IRepoStateAccessor interface {
Expand Down
Loading
Loading