mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-31 22:22:14 +02:00
Track busy/idle state for integration tests (#2765)
This commit is contained in:
commit
2dddd906f8
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@ -68,9 +68,8 @@ jobs:
|
||||
restore-keys: |
|
||||
${{runner.os}}-go-
|
||||
- name: Test code
|
||||
# LONG_WAIT_BEFORE_FAIL means that for a given test assertion, we'll wait longer before failing
|
||||
run: |
|
||||
LONG_WAIT_BEFORE_FAIL=true go test pkg/integration/clients/*.go
|
||||
go test pkg/integration/clients/*.go
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
|
@ -1 +0,0 @@
|
||||
see new docs [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
|
@ -6,3 +6,4 @@
|
||||
* [Keybindings](./keybindings)
|
||||
* [Undo/Redo](./Undoing.md)
|
||||
* [Searching/Filtering](./Searching.md)
|
||||
* [Dev docs](./dev)
|
||||
|
78
docs/dev/Busy.md
Normal file
78
docs/dev/Busy.md
Normal file
@ -0,0 +1,78 @@
|
||||
# Knowing when Lazygit is busy/idle
|
||||
|
||||
## The use-case
|
||||
|
||||
This topic deserves its own doc because there there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process:
|
||||
1) press a key
|
||||
2) wait until Lazygit is idle
|
||||
3) run assertion / press another key
|
||||
4) repeat
|
||||
|
||||
In the past the process was:
|
||||
1) press a key
|
||||
2) run assertion
|
||||
3) if assertion fails, wait a bit and retry
|
||||
4) repeat
|
||||
|
||||
The old process was problematic because an assertion may give a false positive due to the contents of some view not yet having changed since the last key was pressed.
|
||||
|
||||
## The solution
|
||||
|
||||
First, it's important to distinguish three different types of goroutines:
|
||||
* The UI goroutine, of which there is only one, which infinitely processes a queue of events
|
||||
* Worker goroutines, which do some work and then typically enqueue an event in the UI goroutine to display the results
|
||||
* Background goroutines, which periodically spawn worker goroutines (e.g. doing a git fetch every minute)
|
||||
|
||||
The point of distinguishing worker goroutines from background goroutines is that when any worker goroutine is running, we consider Lazygit to be 'busy', whereas this is not the case with background goroutines. It would be pointless to have background goroutines be considered 'busy' because then Lazygit would be considered busy for the entire duration of the program!
|
||||
|
||||
In gocui, the underlying package we use for managing the UI and events, we keep track of how many busy goroutines there are using the `Task` type. A task represents some work being done by lazygit. The gocui Gui struct holds a map of tasks and allows creating a new task (which adds it to the map), pausing/continuing a task, and marking a task as done (which removes it from the map). Lazygit is considered to be busy so long as there is at least one busy task in the map; otherwise it's considered idle. When Lazygit goes from busy to idle, it notifies the integration test.
|
||||
|
||||
It's important that we play by the rules below to ensure that after the user does anything, all the processing that follows happens in a contiguous block of busy-ness with no gaps.
|
||||
|
||||
### Spawning a worker goroutine
|
||||
|
||||
Here's the basic implementation of `OnWorker` (using the same flow as `WaitGroup`s):
|
||||
|
||||
```go
|
||||
func (g *Gui) OnWorker(f func(*Task)) {
|
||||
task := g.NewTask()
|
||||
go func() {
|
||||
f(task)
|
||||
task.Done()
|
||||
}()
|
||||
}
|
||||
```
|
||||
|
||||
The crucial thing here is that we create the task _before_ spawning the goroutine, because it means that we'll have at least one busy task in the map until the completion of the goroutine. If we created the task within the goroutine, the current function could exit and Lazygit would be considered idle before the goroutine starts, leading to our integration test prematurely progressing.
|
||||
|
||||
You typically invoke this with `self.c.OnWorker(f)`. Note that the callback function receives the task. This allows the callback to pause/continue the task (see below).
|
||||
|
||||
### Spawning a background goroutine
|
||||
|
||||
Spawning a background goroutine is as simple as:
|
||||
|
||||
```go
|
||||
go utils.Safe(f)
|
||||
```
|
||||
|
||||
Where `utils.Safe` is a helper function that ensures we clean up the gui if the goroutine panics.
|
||||
|
||||
### Programmatically enqueing a UI event
|
||||
|
||||
This is invoked with `self.c.OnUIThread(f)`. Internally, it creates a task before enqueuing the function as an event (including the task in the event struct) and once that event is processed by the event queue (and any other pending events are processed) the task is removed from the map by calling `task.Done()`.
|
||||
|
||||
### Pressing a key
|
||||
|
||||
If the user presses a key, an event will be enqueued automatically and a task will be created before (and `Done`'d after) the event is processed.
|
||||
|
||||
## Special cases
|
||||
|
||||
There are a couple of special cases where we manually pause/continue the task directly in the client code. These are subject to change but for the sake of completeness:
|
||||
|
||||
### Writing to the main view(s)
|
||||
|
||||
If the user focuses a file in the files panel, we run a `git diff` command for that file and write the output to the main view. But we only read enough of the command's output to fill the view's viewport: further loading only happens if the user scrolls. Given that we have a background goroutine for running the command and writing more output upon scrolling, we create our own task and call `Done` on it as soon as the viewport is filled.
|
||||
|
||||
### Requesting credentials from a git command
|
||||
|
||||
Some git commands (e.g. git push) may request credentials. This is the same deal as above; we use a worker goroutine and manually pause continue its task as we go from waiting on the git command to waiting on user input. This requires passing the task through to the `Push` method so that it can be paused/continued.
|
1
docs/dev/Integration_Tests.md
Normal file
1
docs/dev/Integration_Tests.md
Normal file
@ -0,0 +1 @@
|
||||
see new docs [here](../../pkg/integration/README.md)
|
4
docs/dev/README.md
Normal file
4
docs/dev/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Dev Documentation Overview
|
||||
|
||||
* [Busy/Idle tracking](./Busy.md).
|
||||
* [Integration Tests](../../pkg/integration/README.md)
|
8
go.mod
8
go.mod
@ -18,7 +18,7 @@ require (
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230702054502-d6c452fc12ce
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
@ -67,8 +67,8 @@ require (
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8 // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.9.0 // indirect
|
||||
golang.org/x/term v0.9.0 // indirect
|
||||
golang.org/x/text v0.10.0 // indirect
|
||||
golang.org/x/sys v0.10.0 // indirect
|
||||
golang.org/x/term v0.10.0 // indirect
|
||||
golang.org/x/text v0.11.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
16
go.sum
16
go.sum
@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230702054502-d6c452fc12ce h1:Xgm21B1an/outcRxnkDfMT6wKb6SKBR05jXOyfPA8WQ=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230702054502-d6c452fc12ce/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b h1:8FmmdaYHes1m3oNyNdS+VIgkgkFpNZAWuwTnvp0tG14=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
@ -207,21 +207,21 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
|
||||
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58=
|
||||
golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
@ -1,12 +1,20 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// here we're wrapping the default command runner in some git-specific stuff e.g. retry logic if we get an error due to the presence of .git/index.lock
|
||||
|
||||
const (
|
||||
WaitTime = 50 * time.Millisecond
|
||||
RetryCount = 5
|
||||
)
|
||||
|
||||
type gitCmdObjRunner struct {
|
||||
log *logrus.Entry
|
||||
innerRunner oscommands.ICmdObjRunner
|
||||
@ -18,13 +26,44 @@ func (self *gitCmdObjRunner) Run(cmdObj oscommands.ICmdObj) error {
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, error) {
|
||||
return self.innerRunner.RunWithOutput(cmdObj)
|
||||
var output string
|
||||
var err error
|
||||
for i := 0; i < RetryCount; i++ {
|
||||
newCmdObj := cmdObj.Clone()
|
||||
output, err = self.innerRunner.RunWithOutput(newCmdObj)
|
||||
|
||||
if err == nil || !strings.Contains(output, ".git/index.lock") {
|
||||
return output, err
|
||||
}
|
||||
|
||||
// if we have an error based on the index lock, we should wait a bit and then retry
|
||||
self.log.Warn("index.lock prevented command from running. Retrying command after a small wait")
|
||||
time.Sleep(WaitTime)
|
||||
}
|
||||
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj oscommands.ICmdObj) (string, string, error) {
|
||||
return self.innerRunner.RunWithOutputs(cmdObj)
|
||||
var stdout, stderr string
|
||||
var err error
|
||||
for i := 0; i < RetryCount; i++ {
|
||||
newCmdObj := cmdObj.Clone()
|
||||
stdout, stderr, err = self.innerRunner.RunWithOutputs(newCmdObj)
|
||||
|
||||
if err == nil || !strings.Contains(stdout+stderr, ".git/index.lock") {
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
// if we have an error based on the index lock, we should wait a bit and then retry
|
||||
self.log.Warn("index.lock prevented command from running. Retrying command after a small wait")
|
||||
time.Sleep(WaitTime)
|
||||
}
|
||||
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
// Retry logic not implemented here, but these commands typically don't need to obtain a lock.
|
||||
func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj oscommands.ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
)
|
||||
|
||||
type RemoteCommands struct {
|
||||
@ -46,12 +48,12 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchName string) error {
|
||||
cmdArgs := NewGitCmd("push").
|
||||
Arg(remoteName, "--delete", branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
// CheckRemoteBranchExists Returns remote branch
|
||||
|
@ -2,6 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
|
||||
@ -23,7 +24,7 @@ type PushOpts struct {
|
||||
SetUpstream bool
|
||||
}
|
||||
|
||||
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
|
||||
func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands.ICmdObj, error) {
|
||||
if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" {
|
||||
return nil, errors.New(self.Tr.MustSpecifyOriginError)
|
||||
}
|
||||
@ -35,12 +36,12 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
|
||||
ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex)
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex)
|
||||
return cmdObj, nil
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Push(opts PushOpts) error {
|
||||
cmdObj, err := self.PushCmdObj(opts)
|
||||
func (self *SyncCommands) Push(task gocui.Task, opts PushOpts) error {
|
||||
cmdObj, err := self.PushCmdObj(task, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -48,28 +49,33 @@ func (self *SyncCommands) Push(opts PushOpts) error {
|
||||
return cmdObj.Run()
|
||||
}
|
||||
|
||||
type FetchOptions struct {
|
||||
Background bool
|
||||
}
|
||||
|
||||
// Fetch fetch git repo
|
||||
func (self *SyncCommands) FetchCmdObj(opts FetchOptions) oscommands.ICmdObj {
|
||||
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
ArgIf(self.UserConfig.Git.FetchAll, "--all").
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
if opts.Background {
|
||||
cmdObj.DontLog().FailOnCredentialRequest()
|
||||
} else {
|
||||
cmdObj.PromptOnCredentialRequest()
|
||||
}
|
||||
return cmdObj.WithMutex(self.syncMutex)
|
||||
cmdObj.PromptOnCredentialRequest(task)
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Fetch(opts FetchOptions) error {
|
||||
cmdObj := self.FetchCmdObj(opts)
|
||||
return cmdObj.Run()
|
||||
func (self *SyncCommands) Fetch(task gocui.Task) error {
|
||||
return self.FetchCmdObj(task).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
ArgIf(self.UserConfig.Git.FetchAll, "--all").
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
cmdObj.DontLog().FailOnCredentialRequest()
|
||||
cmdObj.WithMutex(self.syncMutex)
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchBackground() error {
|
||||
return self.FetchBackgroundCmdObj().Run()
|
||||
}
|
||||
|
||||
type PullOptions struct {
|
||||
@ -78,7 +84,7 @@ type PullOptions struct {
|
||||
FastForwardOnly bool
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Pull(opts PullOptions) error {
|
||||
func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
cmdArgs := NewGitCmd("pull").
|
||||
Arg("--no-edit").
|
||||
ArgIf(opts.FastForwardOnly, "--ff-only").
|
||||
@ -88,22 +94,22 @@ func (self *SyncCommands) Pull(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().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
|
||||
func (self *SyncCommands) FastForward(task gocui.Task, branchName string, remoteName string, remoteBranchName string) error {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
Arg(remoteName).
|
||||
Arg(remoteBranchName + ":" + branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchRemote(remoteName string) error {
|
||||
func (self *SyncCommands) FetchRemote(task gocui.Task, remoteName string) error {
|
||||
cmdArgs := NewGitCmd("fetch").
|
||||
Arg(remoteName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package git_commands
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -88,7 +89,8 @@ func TestSyncPush(t *testing.T) {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
s.test(instance.PushCmdObj(s.opts))
|
||||
task := gocui.NewFakeTask()
|
||||
s.test(instance.PushCmdObj(task, s.opts))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -96,7 +98,6 @@ func TestSyncPush(t *testing.T) {
|
||||
func TestSyncFetch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
opts FetchOptions
|
||||
fetchAllConfig bool
|
||||
test func(oscommands.ICmdObj)
|
||||
}
|
||||
@ -104,7 +105,6 @@ func TestSyncFetch(t *testing.T) {
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Fetch in foreground (all=false)",
|
||||
opts: FetchOptions{Background: false},
|
||||
fetchAllConfig: false,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.True(t, cmdObj.ShouldLog())
|
||||
@ -114,7 +114,6 @@ func TestSyncFetch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Fetch in foreground (all=true)",
|
||||
opts: FetchOptions{Background: false},
|
||||
fetchAllConfig: true,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.True(t, cmdObj.ShouldLog())
|
||||
@ -122,9 +121,29 @@ func TestSyncFetch(t *testing.T) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--all"})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
task := gocui.NewFakeTask()
|
||||
s.test(instance.FetchCmdObj(task))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSyncFetchBackground(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
fetchAllConfig bool
|
||||
test func(oscommands.ICmdObj)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Fetch in background (all=false)",
|
||||
opts: FetchOptions{Background: true},
|
||||
fetchAllConfig: false,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.False(t, cmdObj.ShouldLog())
|
||||
@ -134,7 +153,6 @@ func TestSyncFetch(t *testing.T) {
|
||||
},
|
||||
{
|
||||
testName: "Fetch in background (all=true)",
|
||||
opts: FetchOptions{Background: true},
|
||||
fetchAllConfig: true,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
assert.False(t, cmdObj.ShouldLog())
|
||||
@ -149,7 +167,7 @@ func TestSyncFetch(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
s.test(instance.FetchCmdObj(s.opts))
|
||||
s.test(instance.FetchBackgroundCmdObj())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import "github.com/jesseduffield/gocui"
|
||||
|
||||
type TagCommands struct {
|
||||
*GitCommon
|
||||
}
|
||||
@ -34,9 +36,9 @@ func (self *TagCommands) Delete(tagName string) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *TagCommands) Push(remoteName string, tagName string) error {
|
||||
func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string) error {
|
||||
cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).WithMutex(self.syncMutex).Run()
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package git_config
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -20,6 +21,7 @@ type CachedGitConfig struct {
|
||||
cache map[string]string
|
||||
runGitConfigCmd func(*exec.Cmd) (string, error)
|
||||
log *logrus.Entry
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewStdCachedGitConfig(log *logrus.Entry) *CachedGitConfig {
|
||||
@ -31,10 +33,14 @@ func NewCachedGitConfig(runGitConfigCmd func(*exec.Cmd) (string, error), log *lo
|
||||
cache: make(map[string]string),
|
||||
runGitConfigCmd: runGitConfigCmd,
|
||||
log: log,
|
||||
mutex: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CachedGitConfig) Get(key string) string {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if value, ok := self.cache[key]; ok {
|
||||
self.log.Debugf("using cache for key " + key)
|
||||
return value
|
||||
@ -46,6 +52,9 @@ func (self *CachedGitConfig) Get(key string) string {
|
||||
}
|
||||
|
||||
func (self *CachedGitConfig) GetGeneral(args string) string {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if value, ok := self.cache[args]; ok {
|
||||
self.log.Debugf("using cache for args " + args)
|
||||
return value
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
@ -56,13 +57,16 @@ type ICmdObj interface {
|
||||
// returns true if IgnoreEmptyError() was called
|
||||
ShouldIgnoreEmptyError() bool
|
||||
|
||||
PromptOnCredentialRequest() ICmdObj
|
||||
PromptOnCredentialRequest(task gocui.Task) ICmdObj
|
||||
FailOnCredentialRequest() ICmdObj
|
||||
|
||||
WithMutex(mutex *deadlock.Mutex) ICmdObj
|
||||
Mutex() *deadlock.Mutex
|
||||
|
||||
GetCredentialStrategy() CredentialStrategy
|
||||
GetTask() gocui.Task
|
||||
|
||||
Clone() ICmdObj
|
||||
}
|
||||
|
||||
type CmdObj struct {
|
||||
@ -85,6 +89,7 @@ type CmdObj struct {
|
||||
|
||||
// if set to true, it means we might be asked to enter a username/password by this command.
|
||||
credentialStrategy CredentialStrategy
|
||||
task gocui.Task
|
||||
|
||||
// can be set so that we don't run certain commands simultaneously
|
||||
mutex *deadlock.Mutex
|
||||
@ -192,8 +197,9 @@ func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) e
|
||||
return self.runner.RunAndProcessLines(self, onLine)
|
||||
}
|
||||
|
||||
func (self *CmdObj) PromptOnCredentialRequest() ICmdObj {
|
||||
func (self *CmdObj) PromptOnCredentialRequest(task gocui.Task) ICmdObj {
|
||||
self.credentialStrategy = PROMPT
|
||||
self.task = task
|
||||
|
||||
return self
|
||||
}
|
||||
@ -207,3 +213,21 @@ func (self *CmdObj) FailOnCredentialRequest() ICmdObj {
|
||||
func (self *CmdObj) GetCredentialStrategy() CredentialStrategy {
|
||||
return self.credentialStrategy
|
||||
}
|
||||
|
||||
func (self *CmdObj) GetTask() gocui.Task {
|
||||
return self.task
|
||||
}
|
||||
|
||||
func (self *CmdObj) Clone() ICmdObj {
|
||||
clone := &CmdObj{}
|
||||
*clone = *self
|
||||
clone.cmd = cloneCmd(self.cmd)
|
||||
return clone
|
||||
}
|
||||
|
||||
func cloneCmd(cmd *exec.Cmd) *exec.Cmd {
|
||||
clone := &exec.Cmd{}
|
||||
*clone = *cmd
|
||||
|
||||
return clone
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
@ -19,15 +20,6 @@ type ICmdObjRunner interface {
|
||||
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
|
||||
}
|
||||
|
||||
type CredentialType int
|
||||
|
||||
const (
|
||||
Password CredentialType = iota
|
||||
Username
|
||||
Passphrase
|
||||
PIN
|
||||
)
|
||||
|
||||
type cmdObjRunner struct {
|
||||
log *logrus.Entry
|
||||
guiIO *guiIO
|
||||
@ -182,26 +174,6 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
|
||||
return nil
|
||||
}
|
||||
|
||||
// Whenever we're asked for a password we just enter a newline, which will
|
||||
// eventually cause the command to fail.
|
||||
var failPromptFn = func(CredentialType) string { return "\n" }
|
||||
|
||||
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
var promptFn func(CredentialType) string
|
||||
|
||||
switch cmdObj.GetCredentialStrategy() {
|
||||
case PROMPT:
|
||||
promptFn = self.guiIO.promptForCredentialFn
|
||||
case FAIL:
|
||||
promptFn = failPromptFn
|
||||
case NONE:
|
||||
// we should never land here
|
||||
return errors.New("runWithCredentialHandling called but cmdObj does not have a credential strategy")
|
||||
}
|
||||
|
||||
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) {
|
||||
self.guiIO.logCommandFn(cmdObj.ToString(), true)
|
||||
}
|
||||
@ -233,25 +205,6 @@ func (self *cmdObjRunner) runAndStream(cmdObj ICmdObj) error {
|
||||
})
|
||||
}
|
||||
|
||||
// runAndDetectCredentialRequest detect a username / password / passphrase question in a command
|
||||
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
|
||||
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
|
||||
func (self *cmdObjRunner) runAndDetectCredentialRequest(
|
||||
cmdObj ICmdObj,
|
||||
promptUserForCredential func(CredentialType) string,
|
||||
) error {
|
||||
// setting the output to english so we can parse it for a username/password request
|
||||
cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||
|
||||
return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) {
|
||||
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
|
||||
|
||||
go utils.Safe(func() {
|
||||
self.processOutput(tr, handler.stdinPipe, promptUserForCredential)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) runAndStreamAux(
|
||||
cmdObj ICmdObj,
|
||||
onRun func(*cmdHandler, io.Writer),
|
||||
@ -296,13 +249,79 @@ func (self *cmdObjRunner) runAndStreamAux(
|
||||
if cmdObj.ShouldIgnoreEmptyError() {
|
||||
return nil
|
||||
}
|
||||
return errors.New(stdout.String())
|
||||
stdoutStr := stdout.String()
|
||||
if stdoutStr != "" {
|
||||
return errors.New(stdoutStr)
|
||||
}
|
||||
return errors.New("Command exited with non-zero exit code, but no output")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, promptUserForCredential func(CredentialType) string) {
|
||||
type CredentialType int
|
||||
|
||||
const (
|
||||
Password CredentialType = iota
|
||||
Username
|
||||
Passphrase
|
||||
PIN
|
||||
)
|
||||
|
||||
// Whenever we're asked for a password we just enter a newline, which will
|
||||
// eventually cause the command to fail.
|
||||
var failPromptFn = func(CredentialType) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
ch <- "\n"
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
promptFn, err := self.getCredentialPromptFn(cmdObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(CredentialType) <-chan string, error) {
|
||||
switch cmdObj.GetCredentialStrategy() {
|
||||
case PROMPT:
|
||||
return self.guiIO.promptForCredentialFn, nil
|
||||
case FAIL:
|
||||
return failPromptFn, nil
|
||||
default:
|
||||
// we should never land here
|
||||
return nil, errors.New("runWithCredentialHandling called but cmdObj does not have a credential strategy")
|
||||
}
|
||||
}
|
||||
|
||||
// runAndDetectCredentialRequest detect a username / password / passphrase question in a command
|
||||
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
|
||||
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
|
||||
func (self *cmdObjRunner) runAndDetectCredentialRequest(
|
||||
cmdObj ICmdObj,
|
||||
promptUserForCredential func(CredentialType) <-chan string,
|
||||
) error {
|
||||
// setting the output to english so we can parse it for a username/password request
|
||||
cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||
|
||||
return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) {
|
||||
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
|
||||
|
||||
self.processOutput(tr, handler.stdinPipe, promptUserForCredential, cmdObj.GetTask())
|
||||
})
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) processOutput(
|
||||
reader io.Reader,
|
||||
writer io.Writer,
|
||||
promptUserForCredential func(CredentialType) <-chan string,
|
||||
task gocui.Task,
|
||||
) {
|
||||
checkForCredentialRequest := self.getCheckForCredentialRequestFunc()
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
@ -311,7 +330,10 @@ func (self *cmdObjRunner) processOutput(reader io.Reader, writer io.Writer, prom
|
||||
newBytes := scanner.Bytes()
|
||||
askFor, ok := checkForCredentialRequest(newBytes)
|
||||
if ok {
|
||||
toInput := promptUserForCredential(askFor)
|
||||
responseChan := promptUserForCredential(askFor)
|
||||
task.Pause()
|
||||
toInput := <-responseChan
|
||||
task.Continue()
|
||||
// If the return data is empty we don't write anything to stdin
|
||||
if toInput != "" {
|
||||
_, _ = writer.Write([]byte(toInput))
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
@ -15,6 +16,18 @@ func getRunner() *cmdObjRunner {
|
||||
}
|
||||
}
|
||||
|
||||
func toChanFn(f func(ct CredentialType) string) func(CredentialType) <-chan string {
|
||||
return func(ct CredentialType) <-chan string {
|
||||
ch := make(chan string)
|
||||
|
||||
go func() {
|
||||
ch <- f(ct)
|
||||
}()
|
||||
|
||||
return ch
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessOutput(t *testing.T) {
|
||||
defaultPromptUserForCredential := func(ct CredentialType) string {
|
||||
switch ct {
|
||||
@ -99,7 +112,8 @@ func TestProcessOutput(t *testing.T) {
|
||||
reader := strings.NewReader(scenario.output)
|
||||
writer := &strings.Builder{}
|
||||
|
||||
runner.processOutput(reader, writer, scenario.promptUserForCredential)
|
||||
task := gocui.NewFakeTask()
|
||||
runner.processOutput(reader, writer, toChanFn(scenario.promptUserForCredential), task)
|
||||
|
||||
if writer.String() != scenario.expectedToWrite {
|
||||
t.Errorf("expected to write '%s' but got '%s'", scenario.expectedToWrite, writer.String())
|
||||
|
@ -26,10 +26,15 @@ type guiIO struct {
|
||||
// this allows us to request info from the user like username/password, in the event
|
||||
// that a command requests it.
|
||||
// the 'credential' arg is something like 'username' or 'password'
|
||||
promptForCredentialFn func(credential CredentialType) string
|
||||
promptForCredentialFn func(credential CredentialType) <-chan string
|
||||
}
|
||||
|
||||
func NewGuiIO(log *logrus.Entry, logCommandFn func(string, bool), newCmdWriterFn func() io.Writer, promptForCredentialFn func(CredentialType) string) *guiIO {
|
||||
func NewGuiIO(
|
||||
log *logrus.Entry,
|
||||
logCommandFn func(string, bool),
|
||||
newCmdWriterFn func() io.Writer,
|
||||
promptForCredentialFn func(CredentialType) <-chan string,
|
||||
) *guiIO {
|
||||
return &guiIO{
|
||||
log: log,
|
||||
logCommandFn: logCommandFn,
|
||||
|
@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@ -15,11 +15,11 @@ type BackgroundRoutineMgr struct {
|
||||
// if we've suspended the gui (e.g. because we've switched to a subprocess)
|
||||
// we typically want to pause some things that are running like background
|
||||
// file refreshes
|
||||
pauseBackgroundThreads bool
|
||||
pauseBackgroundRefreshes bool
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) PauseBackgroundThreads(pause bool) {
|
||||
self.pauseBackgroundThreads = pause
|
||||
func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) {
|
||||
self.pauseBackgroundRefreshes = pause
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
@ -39,9 +39,7 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
if userConfig.Git.AutoRefresh {
|
||||
refreshInterval := userConfig.Refresher.RefreshInterval
|
||||
if refreshInterval > 0 {
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
go utils.Safe(func() { self.startBackgroundFilesRefresh(refreshInterval) })
|
||||
} else {
|
||||
self.gui.c.Log.Errorf(
|
||||
"Value of config option 'refresher.refreshInterval' (%d) is invalid, disabling auto-refresh",
|
||||
@ -52,6 +50,7 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig
|
||||
if !isNew {
|
||||
@ -69,17 +68,31 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval int) {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
|
||||
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan struct{}, function func() error) {
|
||||
done := make(chan struct{})
|
||||
go utils.Safe(func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if self.pauseBackgroundThreads {
|
||||
if self.pauseBackgroundRefreshes {
|
||||
continue
|
||||
}
|
||||
_ = function()
|
||||
self.gui.c.OnWorker(func(gocui.Task) {
|
||||
_ = function()
|
||||
done <- struct{}{}
|
||||
})
|
||||
// waiting so that we don't bunch up refreshes if the refresh takes longer than the interval
|
||||
<-done
|
||||
case <-stop:
|
||||
return
|
||||
}
|
||||
@ -88,7 +101,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
err = self.gui.git.Sync.Fetch(git_commands.FetchOptions{Background: true})
|
||||
err = self.gui.git.Sync.FetchBackground()
|
||||
|
||||
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
|
||||
|
@ -30,7 +30,7 @@ func NewSuggestionsContext(
|
||||
c *ContextCommon,
|
||||
) *SuggestionsContext {
|
||||
state := &SuggestionsContextState{
|
||||
AsyncHandler: tasks.NewAsyncHandler(),
|
||||
AsyncHandler: tasks.NewAsyncHandler(c.OnWorker),
|
||||
}
|
||||
getModel := func() []*types.Suggestion {
|
||||
return state.Suggestions
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
@ -363,11 +364,12 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.WithLoaderPanel(message, func() error {
|
||||
return self.c.WithLoaderPanel(message, func(task gocui.Task) error {
|
||||
if branch == self.c.Helpers().Refs.GetCheckedOutRef() {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.Pull(
|
||||
task,
|
||||
git_commands.PullOptions{
|
||||
RemoteName: branch.UpstreamRemote,
|
||||
BranchName: branch.UpstreamBranch,
|
||||
@ -381,7 +383,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
err := self.c.Git().Sync.FastForward(branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
err := self.c.Git().Sync.FastForward(task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ func (self *CommitFilesController) discard(node *filetree.CommitFileNode) error
|
||||
Title: self.c.Tr.DiscardFileChangesTitle,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DiscardOldFileChange)
|
||||
if err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), node.GetPath()); err != nil {
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
@ -205,7 +205,7 @@ func (self *CommitFilesController) edit(node *filetree.CommitFileNode) error {
|
||||
|
||||
func (self *CommitFilesController) toggleForPatch(node *filetree.CommitFileNode) error {
|
||||
toggle := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func(gocui.Task) error {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
|
@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@ -116,7 +117,7 @@ func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
||||
@ -133,7 +134,7 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() erro
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit)
|
||||
err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx())
|
||||
@ -151,7 +152,7 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error
|
||||
}
|
||||
|
||||
pull := func(stash bool) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
|
||||
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, stash)
|
||||
@ -181,7 +182,7 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex)
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"strings"
|
||||
|
||||
"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/filetree"
|
||||
@ -801,17 +800,17 @@ func (self *FilesController) onClickSecondary(opts gocui.ViewMouseBindingOpts) e
|
||||
}
|
||||
|
||||
func (self *FilesController) fetch() error {
|
||||
return self.c.WithLoaderPanel(self.c.Tr.FetchWait, func() error {
|
||||
if err := self.fetchAux(); err != nil {
|
||||
return self.c.WithLoaderPanel(self.c.Tr.FetchWait, func(task gocui.Task) error {
|
||||
if err := self.fetchAux(task); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *FilesController) fetchAux() (err error) {
|
||||
func (self *FilesController) fetchAux(task gocui.Task) (err error) {
|
||||
self.c.LogAction("Fetch")
|
||||
err = self.c.Git().Sync.Fetch(git_commands.FetchOptions{})
|
||||
err = self.c.Git().Sync.Fetch(task)
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") {
|
||||
_ = self.c.ErrorMsg(self.c.Tr.PassUnameWrong)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
@ -145,7 +146,7 @@ func (self *FilesRemoveController) remove(node *filetree.FileNode) error {
|
||||
}
|
||||
|
||||
func (self *FilesRemoveController) ResetSubmodule(submodule *models.SubmoduleConfig) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
|
||||
|
||||
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
|
||||
|
@ -3,8 +3,8 @@ package helpers
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/status"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type AppStatusHelper struct {
|
||||
@ -27,12 +27,12 @@ func (self *AppStatusHelper) Toast(message string) {
|
||||
}
|
||||
|
||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func() error) {
|
||||
go utils.Safe(func() {
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task) error) {
|
||||
self.c.OnWorker(func(task gocui.Task) {
|
||||
self.statusMgr().WithWaitingStatus(message, func() {
|
||||
self.renderAppStatus()
|
||||
|
||||
if err := f(); err != nil {
|
||||
if err := f(task); err != nil {
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.c.Error(err)
|
||||
})
|
||||
@ -50,7 +50,7 @@ func (self *AppStatusHelper) GetStatusString() string {
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) renderAppStatus() {
|
||||
go utils.Safe(func() {
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
ticker := time.NewTicker(time.Millisecond * 50)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@ -75,7 +76,7 @@ func (self *CherryPickHelper) Paste() error {
|
||||
Title: self.c.Tr.CherryPick,
|
||||
Prompt: self.c.Tr.SureCherryPick,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CherryPick)
|
||||
err := self.c.Git().Rebase.CherryPickCommits(self.getData().CherryPickedCommits)
|
||||
return self.rebaseHelper.CheckMergeOrRebase(err)
|
||||
|
@ -1,8 +1,6 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@ -20,11 +18,11 @@ func NewCredentialsHelper(
|
||||
}
|
||||
|
||||
// promptUserForCredential wait for a username, password or passphrase input from the credentials popup
|
||||
func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.CredentialType) string {
|
||||
waitGroup := sync.WaitGroup{}
|
||||
waitGroup.Add(1)
|
||||
|
||||
userInput := ""
|
||||
// We return a channel rather than returning the string directly so that the calling function knows
|
||||
// when the prompt has been created (before the user has entered anything) so that it can
|
||||
// note that we're now waiting on user input and lazygit isn't processing anything.
|
||||
func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.CredentialType) <-chan string {
|
||||
ch := make(chan string)
|
||||
|
||||
self.c.OnUIThread(func() error {
|
||||
title, mask := self.getTitleAndMask(passOrUname)
|
||||
@ -33,24 +31,19 @@ func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.Cr
|
||||
Title: title,
|
||||
Mask: mask,
|
||||
HandleConfirm: func(input string) error {
|
||||
userInput = input
|
||||
|
||||
waitGroup.Done()
|
||||
ch <- input + "\n"
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
HandleClose: func() error {
|
||||
waitGroup.Done()
|
||||
ch <- "\n"
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
// wait for username/passwords/passphrase input
|
||||
waitGroup.Wait()
|
||||
|
||||
return userInput + "\n"
|
||||
return ch
|
||||
}
|
||||
|
||||
func (self *CredentialsHelper) getTitleAndMask(passOrUname oscommands.CredentialType) (string, bool) {
|
||||
|
@ -3,6 +3,7 @@ package helpers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@ -41,7 +42,7 @@ func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus
|
||||
}
|
||||
|
||||
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
|
||||
return self.c.WithWaitingStatus(waitingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
|
||||
if err := cmdObj.StreamOutput().Run(); err != nil {
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return self.c.Error(
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
@ -63,8 +64,6 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
)
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
|
||||
f := func() {
|
||||
var scopeSet *set.Set[types.RefreshableView]
|
||||
if len(options.Scope) == 0 {
|
||||
@ -87,15 +86,13 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
}
|
||||
|
||||
refresh := func(f func()) {
|
||||
wg.Add(1)
|
||||
func() {
|
||||
if options.Mode == types.ASYNC {
|
||||
go utils.Safe(f)
|
||||
} else {
|
||||
if options.Mode == types.ASYNC {
|
||||
self.c.OnWorker(func(t gocui.Task) {
|
||||
f()
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
})
|
||||
} else {
|
||||
f()
|
||||
}
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
|
||||
@ -143,8 +140,6 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
refresh(func() { _ = self.mergeConflictsHelper.RefreshMergeState() })
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
self.refreshStatus()
|
||||
|
||||
if options.Then != nil {
|
||||
@ -206,7 +201,7 @@ func getModeName(mode types.RefreshMode) string {
|
||||
func (self *RefreshHelper) refreshReflogCommitsConsideringStartup() {
|
||||
switch self.c.State().GetRepoState().GetStartupStage() {
|
||||
case types.INITIAL:
|
||||
go utils.Safe(func() {
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
_ = self.refreshReflogCommits()
|
||||
self.refreshBranches()
|
||||
self.c.State().GetRepoState().SetStartupStage(types.COMPLETE)
|
||||
@ -350,6 +345,9 @@ 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() {
|
||||
self.c.Mutexes().RefreshingBranchesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingBranchesMutex.Unlock()
|
||||
|
||||
reflogCommits := self.c.Model().FilteredReflogCommits
|
||||
if self.c.Modes().Filtering.Active() {
|
||||
// in filter mode we filter our reflog commits to just those containing the path
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"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/style"
|
||||
@ -50,7 +51,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
self.c.Contexts().LocalCommits.SetLimitCommits(true)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(waitingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
|
||||
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
|
||||
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
|
||||
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"os"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@ -100,7 +101,7 @@ func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*ty
|
||||
// Notably, unlike other suggestion functions we're not showing all the options
|
||||
// if nothing has been typed because there'll be too much to display efficiently
|
||||
func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func() error {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.LoadingFileSuggestions, func(gocui.Task) error {
|
||||
trie := patricia.NewTrie()
|
||||
// load every non-gitignored file in the repo
|
||||
ignore, err := gitignore.FromGit()
|
||||
|
@ -1,6 +1,7 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/updates"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@ -37,7 +38,7 @@ func (self *UpdateHelper) CheckForUpdateInBackground() {
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) CheckForUpdateInForeground() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.CheckingForUpdates, func(gocui.Task) error {
|
||||
self.updater.CheckForNewUpdate(func(newVersion string, err error) error {
|
||||
if err != nil {
|
||||
return self.c.Error(err)
|
||||
@ -53,7 +54,7 @@ func (self *UpdateHelper) CheckForUpdateInForeground() error {
|
||||
}
|
||||
|
||||
func (self *UpdateHelper) startUpdating(newVersion string) {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func() error {
|
||||
_ = self.c.WithWaitingStatus(self.c.Tr.UpdateInProgressWaitingStatus, func(gocui.Task) error {
|
||||
self.c.State().SetUpdating(true)
|
||||
err := self.updater.Update(newVersion)
|
||||
return self.onUpdateFinish(err)
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/fsmiamoto/git-todo-parser/todo"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@ -217,7 +218,7 @@ func (self *LocalCommitsController) squashDown(commit *models.Commit) error {
|
||||
Title: self.c.Tr.Squash,
|
||||
Prompt: self.c.Tr.SureSquashThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
|
||||
return self.interactiveRebase(todo.Squash)
|
||||
})
|
||||
@ -242,7 +243,7 @@ func (self *LocalCommitsController) fixup(commit *models.Commit) error {
|
||||
Title: self.c.Tr.Fixup,
|
||||
Prompt: self.c.Tr.SureFixupThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
||||
return self.interactiveRebase(todo.Fixup)
|
||||
})
|
||||
@ -338,7 +339,7 @@ func (self *LocalCommitsController) drop(commit *models.Commit) error {
|
||||
Title: self.c.Tr.DeleteCommitTitle,
|
||||
Prompt: self.c.Tr.DeleteCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
return self.interactiveRebase(todo.Drop)
|
||||
})
|
||||
@ -355,7 +356,7 @@ func (self *LocalCommitsController) edit(commit *models.Commit) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.EditCommit)
|
||||
err := self.c.Git().Rebase.EditRebase(commit.Sha)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
@ -460,7 +461,7 @@ func (self *LocalCommitsController) moveDown(commit *models.Commit) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitDown)
|
||||
err := self.c.Git().Rebase.MoveCommitDown(self.c.Model().Commits, index)
|
||||
if err == nil {
|
||||
@ -498,7 +499,7 @@ func (self *LocalCommitsController) moveUp(commit *models.Commit) error {
|
||||
return self.c.ErrorMsg(self.c.Tr.AlreadyRebasing)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MovingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.MoveCommitUp)
|
||||
err := self.c.Git().Rebase.MoveCommitUp(self.c.Model().Commits, index)
|
||||
if err == nil {
|
||||
@ -524,7 +525,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx())
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
@ -558,7 +559,7 @@ func (self *LocalCommitsController) amendAttribute(commit *models.Commit) error
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) resetAuthor() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
|
||||
if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx()); err != nil {
|
||||
return self.c.Error(err)
|
||||
@ -573,7 +574,7 @@ func (self *LocalCommitsController) setAuthor() error {
|
||||
Title: self.c.Tr.SetAuthorPromptTitle,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
|
||||
HandleConfirm: func(value string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
|
||||
if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, self.context().GetSelectedLineIdx(), value); err != nil {
|
||||
return self.c.Error(err)
|
||||
@ -671,7 +672,7 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
|
||||
Title: self.c.Tr.SquashAboveCommits,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
|
||||
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
@ -723,7 +724,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
self.context().SetLimitCommits(false)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
|
||||
return self.c.Refresh(
|
||||
types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}},
|
||||
)
|
||||
@ -766,7 +767,7 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
onPress := func(value string) func() error {
|
||||
return func() error {
|
||||
self.c.UserConfig.Git.Log.Order = value
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
|
||||
return self.c.Refresh(
|
||||
types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
@ -816,7 +817,7 @@ func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
context := self.context()
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@ -117,9 +118,9 @@ func (self *RemoteBranchesController) delete(selectedBranch *models.RemoteBranch
|
||||
Title: self.c.Tr.DeleteRemoteBranch,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
err := self.c.Git().Remote.DeleteRemoteBranch(selectedBranch.RemoteName, selectedBranch.Name)
|
||||
err := self.c.Git().Remote.DeleteRemoteBranch(task, selectedBranch.RemoteName, selectedBranch.Name)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@ -197,8 +198,8 @@ func (self *RemotesController) edit(remote *models.Remote) error {
|
||||
}
|
||||
|
||||
func (self *RemotesController) fetch(remote *models.Remote) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FetchingRemoteStatus, func() error {
|
||||
err := self.c.Git().Sync.FetchRemote(remote.Name)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FetchingRemoteStatus, func(task gocui.Task) error {
|
||||
err := self.c.Git().Sync.FetchRemote(task, remote.Name)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type SubCommitsController struct {
|
||||
@ -60,7 +60,7 @@ func (self *SubCommitsController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
context := self.context()
|
||||
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
|
||||
context.SetLimitCommits(false)
|
||||
go utils.Safe(func() {
|
||||
self.c.OnWorker(func(_ gocui.Task) {
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUB_COMMITS}}); err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@ -130,7 +131,7 @@ func (self *SubmodulesController) add() error {
|
||||
Title: self.c.Tr.NewSubmodulePath,
|
||||
InitialContent: submoduleName,
|
||||
HandleConfirm: func(submodulePath string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AddingSubmoduleStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AddingSubmoduleStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AddSubmodule)
|
||||
err := self.c.Git().Submodule.Add(submoduleName, submodulePath, submoduleUrl)
|
||||
if err != nil {
|
||||
@ -152,7 +153,7 @@ func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) err
|
||||
Title: fmt.Sprintf(self.c.Tr.UpdateSubmoduleUrl, submodule.Name),
|
||||
InitialContent: submodule.Url,
|
||||
HandleConfirm: func(newUrl string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleUrlStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleUrlStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.UpdateSubmoduleUrl)
|
||||
err := self.c.Git().Submodule.UpdateUrl(submodule.Name, submodule.Path, newUrl)
|
||||
if err != nil {
|
||||
@ -166,7 +167,7 @@ func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) err
|
||||
}
|
||||
|
||||
func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.InitializingSubmoduleStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.InitializingSubmoduleStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.InitialiseSubmodule)
|
||||
err := self.c.Git().Submodule.Init(submodule.Path)
|
||||
if err != nil {
|
||||
@ -184,7 +185,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.BulkInitSubmodules, style.FgGreen.Sprint(self.c.Git().Submodule.BulkInitCmdObj().ToString())},
|
||||
OnPress: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BulkInitialiseSubmodules)
|
||||
err := self.c.Git().Submodule.BulkInitCmdObj().Run()
|
||||
if err != nil {
|
||||
@ -199,7 +200,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.BulkUpdateSubmodules, style.FgYellow.Sprint(self.c.Git().Submodule.BulkUpdateCmdObj().ToString())},
|
||||
OnPress: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BulkUpdateSubmodules)
|
||||
if err := self.c.Git().Submodule.BulkUpdateCmdObj().Run(); err != nil {
|
||||
return self.c.Error(err)
|
||||
@ -213,7 +214,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
{
|
||||
LabelColumns: []string{self.c.Tr.BulkDeinitSubmodules, style.FgRed.Sprint(self.c.Git().Submodule.BulkDeinitCmdObj().ToString())},
|
||||
OnPress: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RunningCommand, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.BulkDeinitialiseSubmodules)
|
||||
if err := self.c.Git().Submodule.BulkDeinitCmdObj().Run(); err != nil {
|
||||
return self.c.Error(err)
|
||||
@ -229,7 +230,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
}
|
||||
|
||||
func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleStatus, func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingSubmoduleStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.UpdateSubmodule)
|
||||
err := self.c.Git().Submodule.Update(submodule.Path)
|
||||
if err != nil {
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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/types"
|
||||
@ -138,15 +139,16 @@ type PullFilesOptions struct {
|
||||
}
|
||||
|
||||
func (self *SyncController) PullAux(opts PullFilesOptions) error {
|
||||
return self.c.WithLoaderPanel(self.c.Tr.PullWait, func() error {
|
||||
return self.pullWithLock(opts)
|
||||
return self.c.WithLoaderPanel(self.c.Tr.PullWait, func(task gocui.Task) error {
|
||||
return self.pullWithLock(task, opts)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *SyncController) pullWithLock(opts PullFilesOptions) error {
|
||||
func (self *SyncController) pullWithLock(task gocui.Task, opts PullFilesOptions) error {
|
||||
self.c.LogAction(opts.Action)
|
||||
|
||||
err := self.c.Git().Sync.Pull(
|
||||
task,
|
||||
git_commands.PullOptions{
|
||||
RemoteName: opts.UpstreamRemote,
|
||||
BranchName: opts.UpstreamBranch,
|
||||
@ -165,14 +167,16 @@ type pushOpts struct {
|
||||
}
|
||||
|
||||
func (self *SyncController) pushAux(opts pushOpts) error {
|
||||
return self.c.WithLoaderPanel(self.c.Tr.PushWait, func() error {
|
||||
return self.c.WithLoaderPanel(self.c.Tr.PushWait, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.Push)
|
||||
err := self.c.Git().Sync.Push(git_commands.PushOpts{
|
||||
Force: opts.force,
|
||||
UpstreamRemote: opts.upstreamRemote,
|
||||
UpstreamBranch: opts.upstreamBranch,
|
||||
SetUpstream: opts.setUpstream,
|
||||
})
|
||||
err := self.c.Git().Sync.Push(
|
||||
task,
|
||||
git_commands.PushOpts{
|
||||
Force: opts.force,
|
||||
UpstreamRemote: opts.upstreamRemote,
|
||||
UpstreamBranch: opts.upstreamBranch,
|
||||
SetUpstream: opts.setUpstream,
|
||||
})
|
||||
if err != nil {
|
||||
if !opts.force && strings.Contains(err.Error(), "Updates were rejected") {
|
||||
forcePushDisabled := self.c.UserConfig.Git.DisableForcePushing
|
||||
|
@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@ -121,9 +122,9 @@ 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() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.PushingTagStatus, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.PushTag)
|
||||
err := self.c.Git().Tag.Push(response, tag.Name)
|
||||
err := self.c.Git().Tag.Push(task, response, tag.Name)
|
||||
if err != nil {
|
||||
_ = self.c.Error(err)
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
@ -247,7 +248,7 @@ func (self *UndoController) hardResetWithAutoStash(commitSha string, options har
|
||||
Title: self.c.Tr.AutoStashTitle,
|
||||
Prompt: self.c.Tr.AutoStashPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(options.WaitingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {
|
||||
if err := self.c.Git().Stash.Save(self.c.Tr.StashPrefix + commitSha); err != nil {
|
||||
return self.c.Error(err)
|
||||
}
|
||||
@ -268,7 +269,7 @@ func (self *UndoController) hardResetWithAutoStash(commitSha string, options har
|
||||
})
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(options.WaitingStatus, func() error {
|
||||
return self.c.WithWaitingStatus(options.WaitingStatus, func(gocui.Task) error {
|
||||
return reset()
|
||||
})
|
||||
}
|
||||
|
@ -120,7 +120,10 @@ func (gui *Gui) WatchFilesForChanges() {
|
||||
}
|
||||
// only refresh if we're not already
|
||||
if !gui.IsRefreshingFiles {
|
||||
_ = gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
gui.c.OnUIThread(func() error {
|
||||
// TODO: find out if refresh needs to be run on the UI thread
|
||||
return gui.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
})
|
||||
}
|
||||
|
||||
// watch for errors
|
||||
|
@ -130,6 +130,8 @@ type Gui struct {
|
||||
|
||||
c *helpers.HelperCommon
|
||||
helpers *helpers.Helpers
|
||||
|
||||
integrationTest integrationTypes.IntegrationTest
|
||||
}
|
||||
|
||||
type StateAccessor struct {
|
||||
@ -446,14 +448,15 @@ func NewGui(
|
||||
// sake of backwards compatibility. We're making use of short circuiting here
|
||||
ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog,
|
||||
Mutexes: types.Mutexes{
|
||||
RefreshingFilesMutex: &deadlock.Mutex{},
|
||||
RefreshingStatusMutex: &deadlock.Mutex{},
|
||||
SyncMutex: &deadlock.Mutex{},
|
||||
LocalCommitsMutex: &deadlock.Mutex{},
|
||||
SubCommitsMutex: &deadlock.Mutex{},
|
||||
SubprocessMutex: &deadlock.Mutex{},
|
||||
PopupMutex: &deadlock.Mutex{},
|
||||
PtyMutex: &deadlock.Mutex{},
|
||||
RefreshingFilesMutex: &deadlock.Mutex{},
|
||||
RefreshingBranchesMutex: &deadlock.Mutex{},
|
||||
RefreshingStatusMutex: &deadlock.Mutex{},
|
||||
SyncMutex: &deadlock.Mutex{},
|
||||
LocalCommitsMutex: &deadlock.Mutex{},
|
||||
SubCommitsMutex: &deadlock.Mutex{},
|
||||
SubprocessMutex: &deadlock.Mutex{},
|
||||
PopupMutex: &deadlock.Mutex{},
|
||||
PtyMutex: &deadlock.Mutex{},
|
||||
},
|
||||
InitialDir: initialDir,
|
||||
}
|
||||
@ -469,9 +472,10 @@ func NewGui(
|
||||
func() error { return gui.State.ContextMgr.Pop() },
|
||||
func() types.Context { return gui.State.ContextMgr.Current() },
|
||||
gui.createMenu,
|
||||
func(message string, f func() error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
|
||||
func(message string, f func(gocui.Task) error) { gui.helpers.AppStatus.WithWaitingStatus(message, f) },
|
||||
func(message string) { gui.helpers.AppStatus.Toast(message) },
|
||||
func() string { return gui.Views.Confirmation.TextArea.GetContent() },
|
||||
func(f func(gocui.Task)) { gui.c.OnWorker(f) },
|
||||
)
|
||||
|
||||
guiCommon := &guiCommon{gui: gui, IPopupHandler: gui.PopupHandler}
|
||||
@ -620,7 +624,8 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
|
||||
gui.c.Log.Info("starting main loop")
|
||||
|
||||
gui.handleTestMode(startArgs.IntegrationTest)
|
||||
// setting here so we can use it in layout.go
|
||||
gui.integrationTest = startArgs.IntegrationTest
|
||||
|
||||
return gui.g.MainLoop()
|
||||
}
|
||||
@ -716,8 +721,8 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool,
|
||||
return false, gui.c.Error(err)
|
||||
}
|
||||
|
||||
gui.BackgroundRoutineMgr.PauseBackgroundThreads(true)
|
||||
defer gui.BackgroundRoutineMgr.PauseBackgroundThreads(false)
|
||||
gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(true)
|
||||
defer gui.BackgroundRoutineMgr.PauseBackgroundRefreshes(false)
|
||||
|
||||
cmdErr := gui.runSubprocess(subprocess)
|
||||
|
||||
@ -775,37 +780,23 @@ func (gui *Gui) loadNewRepo() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (gui *Gui) showInitialPopups(tasks []func(chan struct{}) error) {
|
||||
gui.waitForIntro.Add(len(tasks))
|
||||
done := make(chan struct{})
|
||||
func (gui *Gui) showIntroPopupMessage() {
|
||||
gui.waitForIntro.Add(1)
|
||||
|
||||
go utils.Safe(func() {
|
||||
for _, task := range tasks {
|
||||
task := task
|
||||
go utils.Safe(func() {
|
||||
if err := task(done); err != nil {
|
||||
_ = gui.c.Error(err)
|
||||
}
|
||||
})
|
||||
|
||||
<-done
|
||||
gui.c.OnUIThread(func() error {
|
||||
onConfirm := func() error {
|
||||
gui.c.GetAppState().StartupPopupVersion = StartupPopupVersion
|
||||
err := gui.c.SaveAppState()
|
||||
gui.waitForIntro.Done()
|
||||
return err
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) showIntroPopupMessage(done chan struct{}) error {
|
||||
onConfirm := func() error {
|
||||
done <- struct{}{}
|
||||
gui.c.GetAppState().StartupPopupVersion = StartupPopupVersion
|
||||
return gui.c.SaveAppState()
|
||||
}
|
||||
|
||||
return gui.c.Confirm(types.ConfirmOpts{
|
||||
Title: "",
|
||||
Prompt: gui.c.Tr.IntroPopupMessage,
|
||||
HandleConfirm: onConfirm,
|
||||
HandleClose: onConfirm,
|
||||
return gui.c.Confirm(types.ConfirmOpts{
|
||||
Title: "",
|
||||
Prompt: gui.c.Tr.IntroPopupMessage,
|
||||
HandleConfirm: onConfirm,
|
||||
HandleClose: onConfirm,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@ -828,6 +819,10 @@ func (gui *Gui) onUIThread(f func() error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onWorker(f func(gocui.Task)) {
|
||||
gui.g.OnWorker(f)
|
||||
}
|
||||
|
||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
return gui.helpers.WindowArrangement.GetWindowDimensions(informationStr, appStatus)
|
||||
}
|
||||
|
@ -136,6 +136,10 @@ func (self *guiCommon) OnUIThread(f func() error) {
|
||||
self.gui.onUIThread(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) OnWorker(f func(gocui.Task)) {
|
||||
self.gui.onWorker(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) error {
|
||||
return self.gui.refreshMainViews(opts)
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ import (
|
||||
// this gives our integration test a way of interacting with the gui for sending keypresses
|
||||
// and reading state.
|
||||
type GuiDriver struct {
|
||||
gui *Gui
|
||||
gui *Gui
|
||||
isIdleChan chan struct{}
|
||||
}
|
||||
|
||||
var _ integrationTypes.GuiDriver = &GuiDriver{}
|
||||
@ -40,6 +41,9 @@ func (self *GuiDriver) PressKey(keyStr string) {
|
||||
tcell.NewEventKey(tcellKey, r, tcell.ModNone),
|
||||
0,
|
||||
)
|
||||
|
||||
// wait until lazygit is idle (i.e. all processing is done) before continuing
|
||||
<-self.isIdleChan
|
||||
}
|
||||
|
||||
func (self *GuiDriver) Keys() config.KeybindingConfig {
|
||||
@ -71,7 +75,10 @@ func (self *GuiDriver) Fail(message string) {
|
||||
self.gui.g.Close()
|
||||
// need to give the gui time to close
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
fmt.Fprintln(os.Stderr, fullMessage)
|
||||
_, err := fmt.Fprintln(os.Stderr, fullMessage)
|
||||
if err != nil {
|
||||
panic("Test failed. Failed writing to stderr")
|
||||
}
|
||||
panic("Test failed")
|
||||
}
|
||||
|
||||
|
@ -114,6 +114,8 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
return err
|
||||
}
|
||||
|
||||
gui.handleTestMode()
|
||||
|
||||
gui.ViewsSetup = true
|
||||
}
|
||||
|
||||
@ -211,12 +213,10 @@ func (gui *Gui) onInitialViewsCreation() error {
|
||||
gui.g.Mutexes.ViewsMutex.Unlock()
|
||||
|
||||
if !gui.c.UserConfig.DisableStartupPopups {
|
||||
popupTasks := []func(chan struct{}) error{}
|
||||
storedPopupVersion := gui.c.GetAppState().StartupPopupVersion
|
||||
if storedPopupVersion < StartupPopupVersion {
|
||||
popupTasks = append(popupTasks, gui.showIntroPopupMessage)
|
||||
gui.showIntroPopupMessage()
|
||||
}
|
||||
gui.showInitialPopups(popupTasks)
|
||||
}
|
||||
|
||||
if gui.showRecentRepos {
|
||||
|
@ -1,6 +1,9 @@
|
||||
package popup
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type FakePopupHandler struct {
|
||||
OnErrorMsg func(message string) error
|
||||
@ -30,12 +33,12 @@ func (self *FakePopupHandler) Prompt(opts types.PromptOpts) error {
|
||||
return self.OnPrompt(opts)
|
||||
}
|
||||
|
||||
func (self *FakePopupHandler) WithLoaderPanel(message string, f func() error) error {
|
||||
return f()
|
||||
func (self *FakePopupHandler) WithLoaderPanel(message string, f func(gocui.Task) error) error {
|
||||
return f(gocui.NewFakeTask())
|
||||
}
|
||||
|
||||
func (self *FakePopupHandler) WithWaitingStatus(message string, f func() error) error {
|
||||
return f()
|
||||
func (self *FakePopupHandler) WithWaitingStatus(message string, f func(gocui.Task) error) error {
|
||||
return f(gocui.NewFakeTask())
|
||||
}
|
||||
|
||||
func (self *FakePopupHandler) Menu(opts types.CreateMenuOptions) error {
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
gctx "github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
@ -22,9 +21,10 @@ type PopupHandler struct {
|
||||
popContextFn func() error
|
||||
currentContextFn func() types.Context
|
||||
createMenuFn func(types.CreateMenuOptions) error
|
||||
withWaitingStatusFn func(message string, f func() error)
|
||||
withWaitingStatusFn func(message string, f func(gocui.Task) error)
|
||||
toastFn func(message string)
|
||||
getPromptInputFn func() string
|
||||
onWorker func(func(gocui.Task))
|
||||
}
|
||||
|
||||
var _ types.IPopupHandler = &PopupHandler{}
|
||||
@ -36,9 +36,10 @@ func NewPopupHandler(
|
||||
popContextFn func() error,
|
||||
currentContextFn func() types.Context,
|
||||
createMenuFn func(types.CreateMenuOptions) error,
|
||||
withWaitingStatusFn func(message string, f func() error),
|
||||
withWaitingStatusFn func(message string, f func(gocui.Task) error),
|
||||
toastFn func(message string),
|
||||
getPromptInputFn func() string,
|
||||
onWorker func(func(gocui.Task)),
|
||||
) *PopupHandler {
|
||||
return &PopupHandler{
|
||||
Common: common,
|
||||
@ -51,6 +52,7 @@ func NewPopupHandler(
|
||||
withWaitingStatusFn: withWaitingStatusFn,
|
||||
toastFn: toastFn,
|
||||
getPromptInputFn: getPromptInputFn,
|
||||
onWorker: onWorker,
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +64,7 @@ func (self *PopupHandler) Toast(message string) {
|
||||
self.toastFn(message)
|
||||
}
|
||||
|
||||
func (self *PopupHandler) WithWaitingStatus(message string, f func() error) error {
|
||||
func (self *PopupHandler) WithWaitingStatus(message string, f func(gocui.Task) error) error {
|
||||
self.withWaitingStatusFn(message, f)
|
||||
return nil
|
||||
}
|
||||
@ -122,7 +124,7 @@ func (self *PopupHandler) Prompt(opts types.PromptOpts) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *PopupHandler) WithLoaderPanel(message string, f func() error) error {
|
||||
func (self *PopupHandler) WithLoaderPanel(message string, f func(gocui.Task) error) error {
|
||||
index := 0
|
||||
self.Lock()
|
||||
self.index++
|
||||
@ -141,8 +143,8 @@ func (self *PopupHandler) WithLoaderPanel(message string, f func() error) error
|
||||
return nil
|
||||
}
|
||||
|
||||
go utils.Safe(func() {
|
||||
if err := f(); err != nil {
|
||||
self.onWorker(func(task gocui.Task) {
|
||||
if err := f(task); err != nil {
|
||||
self.Log.Error(err)
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/jesseduffield/generics/slices"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@ -264,7 +265,7 @@ func (self *HandlerCreator) finalHandler(customCommand config.CustomCommand, ses
|
||||
loadingText = self.c.Tr.RunningCustomCommandStatus
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(loadingText, func() error {
|
||||
return self.c.WithWaitingStatus(loadingText, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CustomCommand)
|
||||
|
||||
if customCommand.Stream {
|
||||
|
@ -48,7 +48,7 @@ func (gui *Gui) newStringTask(view *gocui.View, str string) error {
|
||||
func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
f := func(tasks.TaskOpts) error {
|
||||
gui.c.SetViewContent(view, str)
|
||||
return nil
|
||||
}
|
||||
@ -65,7 +65,7 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
|
||||
func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX int, originY int) error {
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
f := func(tasks.TaskOpts) error {
|
||||
gui.c.SetViewContent(view, str)
|
||||
_ = view.SetOrigin(originX, originY)
|
||||
return nil
|
||||
@ -81,7 +81,7 @@ func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX in
|
||||
func (gui *Gui) newStringTaskWithKey(view *gocui.View, str string, key string) error {
|
||||
manager := gui.getManager(view)
|
||||
|
||||
f := func(stop chan struct{}) error {
|
||||
f := func(tasks.TaskOpts) error {
|
||||
gui.c.ResetViewOrigin(view)
|
||||
gui.c.SetViewContent(view, str)
|
||||
return nil
|
||||
@ -130,6 +130,9 @@ func (gui *Gui) getManager(view *gocui.View) *tasks.ViewBufferManager {
|
||||
func() {
|
||||
_ = view.SetOrigin(0, 0)
|
||||
},
|
||||
func() gocui.Task {
|
||||
return gui.c.GocuiGui().NewTask()
|
||||
},
|
||||
)
|
||||
gui.viewBufferManagerMap[view.Name()] = manager
|
||||
}
|
||||
|
@ -7,29 +7,39 @@ import (
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type IntegrationTest interface {
|
||||
Run(guiAdapter *GuiDriver)
|
||||
Run(*GuiDriver)
|
||||
}
|
||||
|
||||
func (gui *Gui) handleTestMode(test integrationTypes.IntegrationTest) {
|
||||
func (gui *Gui) handleTestMode() {
|
||||
test := gui.integrationTest
|
||||
if os.Getenv(components.SANDBOX_ENV_VAR) == "true" {
|
||||
return
|
||||
}
|
||||
|
||||
if test != nil {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
isIdleChan := make(chan struct{})
|
||||
|
||||
test.Run(&GuiDriver{gui: gui})
|
||||
gui.c.GocuiGui().AddIdleListener(isIdleChan)
|
||||
|
||||
waitUntilIdle := func() {
|
||||
<-isIdleChan
|
||||
}
|
||||
|
||||
go func() {
|
||||
waitUntilIdle()
|
||||
|
||||
test.Run(&GuiDriver{gui: gui, isIdleChan: isIdleChan})
|
||||
|
||||
gui.g.Update(func(*gocui.Gui) error {
|
||||
return gocui.ErrQuit
|
||||
})
|
||||
|
||||
waitUntilIdle()
|
||||
|
||||
time.Sleep(time.Second * 1)
|
||||
|
||||
log.Fatal("gocui should have already exited")
|
||||
|
@ -77,6 +77,9 @@ type IGuiCommon interface {
|
||||
// Only necessary to call if you're not already on the UI thread i.e. you're inside a goroutine.
|
||||
// All controller handlers are executed on the UI thread.
|
||||
OnUIThread(f func() error)
|
||||
// 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))
|
||||
|
||||
// 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
|
||||
@ -118,8 +121,8 @@ type IPopupHandler interface {
|
||||
Confirm(opts ConfirmOpts) error
|
||||
// Shows a popup prompting the user for input.
|
||||
Prompt(opts PromptOpts) error
|
||||
WithLoaderPanel(message string, f func() error) error
|
||||
WithWaitingStatus(message string, f func() error) error
|
||||
WithLoaderPanel(message string, f func(gocui.Task) error) error
|
||||
WithWaitingStatus(message string, f func(gocui.Task) error) error
|
||||
Menu(opts CreateMenuOptions) error
|
||||
Toast(message string)
|
||||
GetPromptInput() string
|
||||
@ -214,14 +217,15 @@ type Model struct {
|
||||
// if you add a new mutex here be sure to instantiate it. We're using pointers to
|
||||
// mutexes so that we can pass the mutexes to controllers.
|
||||
type Mutexes struct {
|
||||
RefreshingFilesMutex *deadlock.Mutex
|
||||
RefreshingStatusMutex *deadlock.Mutex
|
||||
SyncMutex *deadlock.Mutex
|
||||
LocalCommitsMutex *deadlock.Mutex
|
||||
SubCommitsMutex *deadlock.Mutex
|
||||
SubprocessMutex *deadlock.Mutex
|
||||
PopupMutex *deadlock.Mutex
|
||||
PtyMutex *deadlock.Mutex
|
||||
RefreshingFilesMutex *deadlock.Mutex
|
||||
RefreshingBranchesMutex *deadlock.Mutex
|
||||
RefreshingStatusMutex *deadlock.Mutex
|
||||
SyncMutex *deadlock.Mutex
|
||||
LocalCommitsMutex *deadlock.Mutex
|
||||
SubCommitsMutex *deadlock.Mutex
|
||||
SubprocessMutex *deadlock.Mutex
|
||||
PopupMutex *deadlock.Mutex
|
||||
PtyMutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
type IStateAccessor interface {
|
||||
|
@ -48,9 +48,8 @@ func TestIntegration(t *testing.T) {
|
||||
},
|
||||
false,
|
||||
0,
|
||||
// allowing two attempts at the test. If a test fails intermittently,
|
||||
// there may be a concurrency issue that we need to resolve.
|
||||
2,
|
||||
// Only allowing one attempt per test. We'll see if we get any flakiness
|
||||
1,
|
||||
)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
@ -1,9 +1,6 @@
|
||||
package components
|
||||
|
||||
import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
integrationTypes "github.com/jesseduffield/lazygit/pkg/integration/types"
|
||||
)
|
||||
|
||||
@ -11,17 +8,6 @@ type assertionHelper struct {
|
||||
gui integrationTypes.GuiDriver
|
||||
}
|
||||
|
||||
// milliseconds we'll wait when an assertion fails.
|
||||
func retryWaitTimes() []int {
|
||||
if os.Getenv("LONG_WAIT_BEFORE_FAIL") == "true" {
|
||||
// CI has limited hardware, may be throttled, runs tests in parallel, etc, so we
|
||||
// give it more leeway compared to when we're running things locally.
|
||||
return []int{0, 1, 1, 1, 1, 1, 5, 10, 20, 40, 100, 200, 500, 1000, 2000, 4000}
|
||||
} else {
|
||||
return []int{0, 1, 1, 1, 1, 1, 5, 10, 20, 40, 100, 200}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *assertionHelper) matchString(matcher *TextMatcher, context string, getValue func() string) {
|
||||
self.assertWithRetries(func() (bool, string) {
|
||||
value := getValue()
|
||||
@ -29,19 +15,13 @@ func (self *assertionHelper) matchString(matcher *TextMatcher, context string, g
|
||||
})
|
||||
}
|
||||
|
||||
// We no longer assert with retries now that lazygit tells us when it's no longer
|
||||
// busy. But I'm keeping the function in case we want to re-introduce it later.
|
||||
func (self *assertionHelper) assertWithRetries(test func() (bool, string)) {
|
||||
var message string
|
||||
for _, waitTime := range retryWaitTimes() {
|
||||
time.Sleep(time.Duration(waitTime) * time.Millisecond)
|
||||
|
||||
var ok bool
|
||||
ok, message = test()
|
||||
if ok {
|
||||
return
|
||||
}
|
||||
ok, message := test()
|
||||
if !ok {
|
||||
self.fail(message)
|
||||
}
|
||||
|
||||
self.fail(message)
|
||||
}
|
||||
|
||||
func (self *assertionHelper) fail(message string) {
|
||||
|
@ -61,6 +61,7 @@ var Reword = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains(wipCommitMessage),
|
||||
Contains(commitMessage),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
@ -62,7 +62,7 @@ var DiffAndApplyPatch = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().Title(Equals("Diffing")).Select(Contains("Exit diff mode")).Confirm()
|
||||
|
||||
t.Views().Information().Content(DoesNotContain("Building patch"))
|
||||
t.Views().Information().Content(Contains("Building patch"))
|
||||
}).
|
||||
Press(keys.Universal.CreatePatchOptionsMenu)
|
||||
|
||||
|
@ -8,7 +8,7 @@ import (
|
||||
var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Discarding all possible permutations of changed files",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: true, // failing due to index.lock file being created
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
@ -22,8 +22,8 @@ var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
shell.RunShellCommand(`echo bothmodded > both-modded.txt && git add both-modded.txt`)
|
||||
shell.RunShellCommand(`echo haha > deleted-them.txt && git add deleted-them.txt`)
|
||||
shell.RunShellCommand(`echo haha2 > deleted-us.txt && git add deleted-us.txt`)
|
||||
shell.RunShellCommand(`echo mod > modded.txt & git add modded.txt`)
|
||||
shell.RunShellCommand(`echo mod > modded-staged.txt & git add modded-staged.txt`)
|
||||
shell.RunShellCommand(`echo mod > modded.txt && git add modded.txt`)
|
||||
shell.RunShellCommand(`echo mod > modded-staged.txt && git add modded-staged.txt`)
|
||||
shell.RunShellCommand(`echo del > deleted.txt && git add deleted.txt`)
|
||||
shell.RunShellCommand(`echo del > deleted-staged.txt && git add deleted-staged.txt`)
|
||||
shell.RunShellCommand(`echo change-delete > change-delete.txt && git add change-delete.txt`)
|
||||
|
@ -30,7 +30,7 @@ var SquashFixupsAboveFirstCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Content(Contains("Are you sure you want to create a fixup! commit for commit")).
|
||||
Confirm()
|
||||
}).
|
||||
NavigateToLine(Contains("commit 01")).
|
||||
NavigateToLine(Contains("commit 01").DoesNotContain("fixup!")).
|
||||
Press(keys.Commits.SquashAboveCommits).
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Confirmation().
|
||||
|
@ -1,7 +1,7 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
@ -18,11 +18,13 @@ type AsyncHandler struct {
|
||||
lastId int
|
||||
mutex deadlock.Mutex
|
||||
onReject func()
|
||||
onWorker func(func(gocui.Task))
|
||||
}
|
||||
|
||||
func NewAsyncHandler() *AsyncHandler {
|
||||
func NewAsyncHandler(onWorker func(func(gocui.Task))) *AsyncHandler {
|
||||
return &AsyncHandler{
|
||||
mutex: deadlock.Mutex{},
|
||||
mutex: deadlock.Mutex{},
|
||||
onWorker: onWorker,
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +34,7 @@ func (self *AsyncHandler) Do(f func() func()) {
|
||||
id := self.currentId
|
||||
self.mutex.Unlock()
|
||||
|
||||
go utils.Safe(func() {
|
||||
self.onWorker(func(gocui.Task) {
|
||||
after := f()
|
||||
self.handle(after, id)
|
||||
})
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -12,7 +13,10 @@ func TestAsyncHandler(t *testing.T) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
|
||||
handler := NewAsyncHandler()
|
||||
onWorker := func(f func(gocui.Task)) {
|
||||
go f(gocui.NewFakeTask())
|
||||
}
|
||||
handler := NewAsyncHandler(onWorker)
|
||||
handler.onReject = func() {
|
||||
wg.Done()
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
@ -48,6 +49,12 @@ type ViewBufferManager struct {
|
||||
refreshView func()
|
||||
onEndOfInput func()
|
||||
|
||||
// see docs/dev/Busy.md
|
||||
// A gocui task is not the same thing as the tasks defined in this file.
|
||||
// A gocui task simply represents the fact that lazygit is busy doing something,
|
||||
// whereas the tasks in this file are about rendering content to a view.
|
||||
newGocuiTask func() gocui.Task
|
||||
|
||||
// if the user flicks through a heap of items, with each one
|
||||
// spawning a process to render something to the main view,
|
||||
// it can slow things down quite a bit. In these situations we
|
||||
@ -76,6 +83,7 @@ func NewViewBufferManager(
|
||||
refreshView func(),
|
||||
onEndOfInput func(),
|
||||
onNewKey func(),
|
||||
newGocuiTask func() gocui.Task,
|
||||
) *ViewBufferManager {
|
||||
return &ViewBufferManager{
|
||||
Log: log,
|
||||
@ -85,6 +93,7 @@ func NewViewBufferManager(
|
||||
onEndOfInput: onEndOfInput,
|
||||
readLines: make(chan LinesToRead, 1024),
|
||||
onNewKey: onNewKey,
|
||||
newGocuiTask: newGocuiTask,
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,13 +103,22 @@ func (self *ViewBufferManager) ReadLines(n int) {
|
||||
})
|
||||
}
|
||||
|
||||
// note: onDone may be called twice
|
||||
func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead LinesToRead, onDone func()) func(chan struct{}) error {
|
||||
return func(stop chan struct{}) error {
|
||||
var once sync.Once
|
||||
var onDoneWrapper func()
|
||||
if onDone != nil {
|
||||
onDoneWrapper = func() { once.Do(onDone) }
|
||||
func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), prefix string, linesToRead LinesToRead, onDoneFn func()) func(TaskOpts) error {
|
||||
return func(opts TaskOpts) error {
|
||||
var onDoneOnce sync.Once
|
||||
var onFirstPageShownOnce sync.Once
|
||||
|
||||
onFirstPageShown := func() {
|
||||
onFirstPageShownOnce.Do(func() {
|
||||
opts.InitialContentLoaded()
|
||||
})
|
||||
}
|
||||
|
||||
onDone := func() {
|
||||
if onDoneFn != nil {
|
||||
onDoneOnce.Do(onDoneFn)
|
||||
}
|
||||
onFirstPageShown()
|
||||
}
|
||||
|
||||
if self.throttle {
|
||||
@ -109,7 +127,8 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stop:
|
||||
case <-opts.Stop:
|
||||
onDone()
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
@ -119,7 +138,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
timeToStart := time.Since(startTime)
|
||||
|
||||
go utils.Safe(func() {
|
||||
<-stop
|
||||
<-opts.Stop
|
||||
// we use the time it took to start the program as a way of checking if things
|
||||
// are running slow at the moment. This is admittedly a crude estimate, but
|
||||
// the point is that we only want to throttle when things are running slow
|
||||
@ -132,9 +151,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
}
|
||||
|
||||
// for pty's we need to call onDone here so that cmd.Wait() doesn't block forever
|
||||
if onDoneWrapper != nil {
|
||||
onDoneWrapper()
|
||||
}
|
||||
onDone()
|
||||
})
|
||||
|
||||
loadingMutex := deadlock.Mutex{}
|
||||
@ -153,7 +170,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
ticker := time.NewTicker(time.Millisecond * 200)
|
||||
defer ticker.Stop()
|
||||
select {
|
||||
case <-stop:
|
||||
case <-opts.Stop:
|
||||
return
|
||||
case <-ticker.C:
|
||||
loadingMutex.Lock()
|
||||
@ -169,8 +186,8 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
go utils.Safe(func() {
|
||||
isViewStale := true
|
||||
writeToView := func(content []byte) {
|
||||
_, _ = self.writer.Write(content)
|
||||
isViewStale = true
|
||||
_, _ = self.writer.Write(content)
|
||||
}
|
||||
refreshViewIfStale := func() {
|
||||
if isViewStale {
|
||||
@ -182,12 +199,12 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
outer:
|
||||
for {
|
||||
select {
|
||||
case <-stop:
|
||||
case <-opts.Stop:
|
||||
break outer
|
||||
case linesToRead := <-self.readLines:
|
||||
for i := 0; i < linesToRead.Total; i++ {
|
||||
select {
|
||||
case <-stop:
|
||||
case <-opts.Stop:
|
||||
break outer
|
||||
default:
|
||||
}
|
||||
@ -219,6 +236,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
}
|
||||
}
|
||||
refreshViewIfStale()
|
||||
onFirstPageShown()
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,10 +249,8 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
}
|
||||
}
|
||||
|
||||
// calling onDoneWrapper here again in case the program ended on its own accord
|
||||
if onDoneWrapper != nil {
|
||||
onDoneWrapper()
|
||||
}
|
||||
// calling this here again in case the program ended on its own accord
|
||||
onDone()
|
||||
|
||||
close(done)
|
||||
})
|
||||
@ -272,8 +288,30 @@ func (self *ViewBufferManager) Close() {
|
||||
// 1) command based, where the manager can be asked to read more lines, but the command can be killed
|
||||
// 2) string based, where the manager can also be asked to read more lines
|
||||
|
||||
func (self *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key string) error {
|
||||
type TaskOpts struct {
|
||||
// Channel that tells the task to stop, because another task wants to run.
|
||||
Stop chan struct{}
|
||||
|
||||
// Only for tasks which are long-running, where we read more lines sporadically.
|
||||
// We use this to keep track of when a user's action is complete (i.e. all views
|
||||
// have been refreshed to display the results of their action)
|
||||
InitialContentLoaded func()
|
||||
}
|
||||
|
||||
func (self *ViewBufferManager) NewTask(f func(TaskOpts) error, key string) error {
|
||||
gocuiTask := self.newGocuiTask()
|
||||
|
||||
var completeTaskOnce sync.Once
|
||||
|
||||
completeGocuiTask := func() {
|
||||
completeTaskOnce.Do(func() {
|
||||
gocuiTask.Done()
|
||||
})
|
||||
}
|
||||
|
||||
go utils.Safe(func() {
|
||||
defer completeGocuiTask()
|
||||
|
||||
self.taskIDMutex.Lock()
|
||||
self.newTaskID++
|
||||
taskID := self.newTaskID
|
||||
@ -286,11 +324,14 @@ func (self *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key str
|
||||
self.taskIDMutex.Unlock()
|
||||
|
||||
self.waitingMutex.Lock()
|
||||
defer self.waitingMutex.Unlock()
|
||||
|
||||
self.taskIDMutex.Lock()
|
||||
if taskID < self.newTaskID {
|
||||
self.waitingMutex.Unlock()
|
||||
self.taskIDMutex.Unlock()
|
||||
return
|
||||
}
|
||||
self.taskIDMutex.Unlock()
|
||||
|
||||
if self.stopCurrentTask != nil {
|
||||
self.stopCurrentTask()
|
||||
@ -307,13 +348,13 @@ func (self *ViewBufferManager) NewTask(f func(stop chan struct{}) error, key str
|
||||
|
||||
self.stopCurrentTask = func() { once.Do(onStop) }
|
||||
|
||||
go utils.Safe(func() {
|
||||
if err := f(stop); err != nil {
|
||||
self.Log.Error(err) // might need an onError callback
|
||||
}
|
||||
self.waitingMutex.Unlock()
|
||||
|
||||
close(notifyStopped)
|
||||
})
|
||||
if err := f(TaskOpts{Stop: stop, InitialContentLoaded: completeGocuiTask}); err != nil {
|
||||
self.Log.Error(err) // might need an onError callback
|
||||
}
|
||||
|
||||
close(notifyStopped)
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/secureexec"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
@ -26,6 +27,10 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
||||
onEndOfInput, getOnEndOfInputCallCount := getCounter()
|
||||
onNewKey, getOnNewKeyCallCount := getCounter()
|
||||
onDone, getOnDoneCallCount := getCounter()
|
||||
task := gocui.NewFakeTask()
|
||||
newTask := func() gocui.Task {
|
||||
return task
|
||||
}
|
||||
|
||||
manager := NewViewBufferManager(
|
||||
utils.NewDummyLog(),
|
||||
@ -34,6 +39,7 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
||||
refreshView,
|
||||
onEndOfInput,
|
||||
onNewKey,
|
||||
newTask,
|
||||
)
|
||||
|
||||
stop := make(chan struct{})
|
||||
@ -49,7 +55,7 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
||||
|
||||
fn := manager.NewCmdTask(start, "prefix\n", LinesToRead{20, -1}, onDone)
|
||||
|
||||
_ = fn(stop)
|
||||
_ = fn(TaskOpts{Stop: stop, InitialContentLoaded: func() { task.Done() }})
|
||||
|
||||
callCountExpectations := []struct {
|
||||
expected int
|
||||
@ -68,6 +74,10 @@ func TestNewCmdTaskInstantStop(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if task.Status() != gocui.TaskStatusDone {
|
||||
t.Errorf("expected task status to be 'done', got '%s'", task.FormatStatus())
|
||||
}
|
||||
|
||||
expectedContent := ""
|
||||
actualContent := writer.String()
|
||||
if actualContent != expectedContent {
|
||||
@ -82,6 +92,10 @@ func TestNewCmdTask(t *testing.T) {
|
||||
onEndOfInput, getOnEndOfInputCallCount := getCounter()
|
||||
onNewKey, getOnNewKeyCallCount := getCounter()
|
||||
onDone, getOnDoneCallCount := getCounter()
|
||||
task := gocui.NewFakeTask()
|
||||
newTask := func() gocui.Task {
|
||||
return task
|
||||
}
|
||||
|
||||
manager := NewViewBufferManager(
|
||||
utils.NewDummyLog(),
|
||||
@ -90,6 +104,7 @@ func TestNewCmdTask(t *testing.T) {
|
||||
refreshView,
|
||||
onEndOfInput,
|
||||
onNewKey,
|
||||
newTask,
|
||||
)
|
||||
|
||||
stop := make(chan struct{})
|
||||
@ -109,7 +124,7 @@ func TestNewCmdTask(t *testing.T) {
|
||||
close(stop)
|
||||
wg.Done()
|
||||
}()
|
||||
_ = fn(stop)
|
||||
_ = fn(TaskOpts{Stop: stop, InitialContentLoaded: func() { task.Done() }})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
@ -130,6 +145,10 @@ func TestNewCmdTask(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if task.Status() != gocui.TaskStatusDone {
|
||||
t.Errorf("expected task status to be 'done', got '%s'", task.FormatStatus())
|
||||
}
|
||||
|
||||
expectedContent := "prefix\ntest\n"
|
||||
actualContent := writer.String()
|
||||
if actualContent != expectedContent {
|
||||
@ -208,6 +227,11 @@ func TestNewCmdTaskRefresh(t *testing.T) {
|
||||
lineCountsOnRefresh = append(lineCountsOnRefresh, strings.Count(writer.String(), "\n"))
|
||||
}
|
||||
|
||||
task := gocui.NewFakeTask()
|
||||
newTask := func() gocui.Task {
|
||||
return task
|
||||
}
|
||||
|
||||
manager := NewViewBufferManager(
|
||||
utils.NewDummyLog(),
|
||||
writer,
|
||||
@ -215,6 +239,7 @@ func TestNewCmdTaskRefresh(t *testing.T) {
|
||||
refreshView,
|
||||
func() {},
|
||||
func() {},
|
||||
newTask,
|
||||
)
|
||||
|
||||
stop := make(chan struct{})
|
||||
@ -234,7 +259,7 @@ func TestNewCmdTaskRefresh(t *testing.T) {
|
||||
close(stop)
|
||||
wg.Done()
|
||||
}()
|
||||
_ = fn(stop)
|
||||
_ = fn(TaskOpts{Stop: stop, InitialContentLoaded: func() { task.Done() }})
|
||||
|
||||
wg.Wait()
|
||||
|
||||
|
109
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
109
vendor/github.com/jesseduffield/gocui/gui.go
generated
vendored
@ -173,6 +173,8 @@ type Gui struct {
|
||||
screen tcell.Screen
|
||||
suspendedMutex sync.Mutex
|
||||
suspended bool
|
||||
|
||||
taskManager *TaskManager
|
||||
}
|
||||
|
||||
// NewGui returns a new Gui object with a given output mode.
|
||||
@ -205,6 +207,7 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
|
||||
|
||||
g.gEvents = make(chan GocuiEvent, 20)
|
||||
g.userEvents = make(chan userEvent, 20)
|
||||
g.taskManager = newTaskManager()
|
||||
|
||||
if playRecording {
|
||||
g.ReplayedEvents = replayedEvents{
|
||||
@ -230,6 +233,17 @@ func NewGui(mode OutputMode, supportOverlaps bool, playRecording bool, headless
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g *Gui) NewTask() *TaskImpl {
|
||||
return g.taskManager.NewTask()
|
||||
}
|
||||
|
||||
// An idle listener listens for when the program is idle. This is useful for
|
||||
// integration tests which can wait for the program to be idle before taking
|
||||
// the next step in the test.
|
||||
func (g *Gui) AddIdleListener(c chan struct{}) {
|
||||
g.taskManager.addIdleListener(c)
|
||||
}
|
||||
|
||||
// Close finalizes the library. It should be called after a successful
|
||||
// initialization and when gocui is not needed anymore.
|
||||
func (g *Gui) Close() {
|
||||
@ -593,7 +607,8 @@ func getKey(key interface{}) (Key, rune, error) {
|
||||
|
||||
// userEvent represents an event triggered by the user.
|
||||
type userEvent struct {
|
||||
f func(*Gui) error
|
||||
f func(*Gui) error
|
||||
task Task
|
||||
}
|
||||
|
||||
// Update executes the passed function. This method can be called safely from a
|
||||
@ -602,14 +617,49 @@ type userEvent struct {
|
||||
// the user events queue. Given that Update spawns a goroutine, the order in
|
||||
// which the user events will be handled is not guaranteed.
|
||||
func (g *Gui) Update(f func(*Gui) error) {
|
||||
go g.UpdateAsync(f)
|
||||
task := g.NewTask()
|
||||
|
||||
go g.updateAsyncAux(f, task)
|
||||
}
|
||||
|
||||
// UpdateAsync is a version of Update that does not spawn a go routine, it can
|
||||
// be a bit more efficient in cases where Update is called many times like when
|
||||
// tailing a file. In general you should use Update()
|
||||
func (g *Gui) UpdateAsync(f func(*Gui) error) {
|
||||
g.userEvents <- userEvent{f: f}
|
||||
task := g.NewTask()
|
||||
|
||||
g.updateAsyncAux(f, task)
|
||||
}
|
||||
|
||||
func (g *Gui) updateAsyncAux(f func(*Gui) error, task Task) {
|
||||
g.userEvents <- userEvent{f: f, task: task}
|
||||
}
|
||||
|
||||
// Calls a function in a goroutine. Handles panics gracefully and tracks
|
||||
// number of background tasks.
|
||||
// Always use this when you want to spawn a goroutine and you want lazygit to
|
||||
// consider itself 'busy` as it runs the code. Don't use for long-running
|
||||
// background goroutines where you wouldn't want lazygit to be considered busy
|
||||
// (i.e. when you wouldn't want a loader to be shown to the user)
|
||||
func (g *Gui) OnWorker(f func(Task)) {
|
||||
task := g.NewTask()
|
||||
go func() {
|
||||
g.onWorkerAux(f, task)
|
||||
task.Done()
|
||||
}()
|
||||
}
|
||||
|
||||
func (g *Gui) onWorkerAux(f func(Task), task Task) {
|
||||
panicking := true
|
||||
defer func() {
|
||||
if panicking && Screen != nil {
|
||||
Screen.Fini()
|
||||
}
|
||||
}()
|
||||
|
||||
f(task)
|
||||
|
||||
panicking = false
|
||||
}
|
||||
|
||||
// A Manager is in charge of GUI's layout and can be used to build widgets.
|
||||
@ -666,27 +716,42 @@ func (g *Gui) MainLoop() error {
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case ev := <-g.gEvents:
|
||||
if err := g.handleEvent(&ev); err != nil {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
if err := ev.f(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := g.consumeevents(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.flush(); err != nil {
|
||||
err := g.processEvent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// consumeevents handles the remaining events in the events pool.
|
||||
func (g *Gui) consumeevents() error {
|
||||
func (g *Gui) processEvent() error {
|
||||
select {
|
||||
case ev := <-g.gEvents:
|
||||
task := g.NewTask()
|
||||
defer func() { task.Done() }()
|
||||
|
||||
if err := g.handleEvent(&ev); err != nil {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
defer func() { ev.task.Done() }()
|
||||
|
||||
if err := ev.f(g); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.processRemainingEvents(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := g.flush(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// processRemainingEvents handles the remaining events in the events pool.
|
||||
func (g *Gui) processRemainingEvents() error {
|
||||
for {
|
||||
select {
|
||||
case ev := <-g.gEvents:
|
||||
@ -694,7 +759,9 @@ func (g *Gui) consumeevents() error {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
if err := ev.f(g); err != nil {
|
||||
err := ev.f(g)
|
||||
ev.task.Done()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
@ -1355,7 +1422,7 @@ func (g *Gui) StartTicking(ctx context.Context) {
|
||||
|
||||
for _, view := range g.Views() {
|
||||
if view.HasLoader {
|
||||
g.userEvents <- userEvent{func(g *Gui) error { return nil }}
|
||||
g.UpdateAsync(func(g *Gui) error { return nil })
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
94
vendor/github.com/jesseduffield/gocui/task.go
generated
vendored
Normal file
94
vendor/github.com/jesseduffield/gocui/task.go
generated
vendored
Normal file
@ -0,0 +1,94 @@
|
||||
package gocui
|
||||
|
||||
// A task represents the fact that the program is busy doing something, which
|
||||
// is useful for integration tests which only want to proceed when the program
|
||||
// is idle.
|
||||
|
||||
type Task interface {
|
||||
Done()
|
||||
Pause()
|
||||
Continue()
|
||||
// not exporting because we don't need to
|
||||
isBusy() bool
|
||||
}
|
||||
|
||||
type TaskImpl struct {
|
||||
id int
|
||||
busy bool
|
||||
onDone func()
|
||||
withMutex func(func())
|
||||
}
|
||||
|
||||
func (self *TaskImpl) Done() {
|
||||
self.onDone()
|
||||
}
|
||||
|
||||
func (self *TaskImpl) Pause() {
|
||||
self.withMutex(func() {
|
||||
self.busy = false
|
||||
})
|
||||
}
|
||||
|
||||
func (self *TaskImpl) Continue() {
|
||||
self.withMutex(func() {
|
||||
self.busy = true
|
||||
})
|
||||
}
|
||||
|
||||
func (self *TaskImpl) isBusy() bool {
|
||||
return self.busy
|
||||
}
|
||||
|
||||
type TaskStatus int
|
||||
|
||||
const (
|
||||
TaskStatusBusy TaskStatus = iota
|
||||
TaskStatusPaused
|
||||
TaskStatusDone
|
||||
)
|
||||
|
||||
type FakeTask struct {
|
||||
status TaskStatus
|
||||
}
|
||||
|
||||
func NewFakeTask() *FakeTask {
|
||||
return &FakeTask{
|
||||
status: TaskStatusBusy,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FakeTask) Done() {
|
||||
self.status = TaskStatusDone
|
||||
}
|
||||
|
||||
func (self *FakeTask) Pause() {
|
||||
self.status = TaskStatusPaused
|
||||
}
|
||||
|
||||
func (self *FakeTask) Continue() {
|
||||
self.status = TaskStatusBusy
|
||||
}
|
||||
|
||||
func (self *FakeTask) isBusy() bool {
|
||||
return self.status == TaskStatusBusy
|
||||
}
|
||||
|
||||
func (self *FakeTask) Status() TaskStatus {
|
||||
return self.status
|
||||
}
|
||||
|
||||
func (self *FakeTask) FormatStatus() string {
|
||||
return formatTaskStatus(self.status)
|
||||
}
|
||||
|
||||
func formatTaskStatus(status TaskStatus) string {
|
||||
switch status {
|
||||
case TaskStatusBusy:
|
||||
return "busy"
|
||||
case TaskStatusPaused:
|
||||
return "paused"
|
||||
case TaskStatusDone:
|
||||
return "done"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
67
vendor/github.com/jesseduffield/gocui/task_manager.go
generated
vendored
Normal file
67
vendor/github.com/jesseduffield/gocui/task_manager.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
package gocui
|
||||
|
||||
import "sync"
|
||||
|
||||
// Tracks whether the program is busy (i.e. either something is happening on
|
||||
// the main goroutine or a worker goroutine). Used by integration tests
|
||||
// to wait until the program is idle before progressing.
|
||||
type TaskManager struct {
|
||||
// each of these listeners will be notified when the program goes from busy to idle
|
||||
idleListeners []chan struct{}
|
||||
tasks map[int]Task
|
||||
// auto-incrementing id for new tasks
|
||||
nextId int
|
||||
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func newTaskManager() *TaskManager {
|
||||
return &TaskManager{
|
||||
tasks: make(map[int]Task),
|
||||
idleListeners: []chan struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TaskManager) NewTask() *TaskImpl {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
self.nextId++
|
||||
taskId := self.nextId
|
||||
|
||||
onDone := func() { self.delete(taskId) }
|
||||
task := &TaskImpl{id: taskId, busy: true, onDone: onDone, withMutex: self.withMutex}
|
||||
self.tasks[taskId] = task
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
func (self *TaskManager) addIdleListener(c chan struct{}) {
|
||||
self.idleListeners = append(self.idleListeners, c)
|
||||
}
|
||||
|
||||
func (self *TaskManager) withMutex(f func()) {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
f()
|
||||
|
||||
// Check if all tasks are done
|
||||
for _, task := range self.tasks {
|
||||
if task.isBusy() {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, all tasks are done, so
|
||||
// notify listeners that the program is idle
|
||||
for _, listener := range self.idleListeners {
|
||||
listener <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TaskManager) delete(taskId int) {
|
||||
self.withMutex(func() {
|
||||
delete(self.tasks, taskId)
|
||||
})
|
||||
}
|
2
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
2
vendor/golang.org/x/sys/unix/mkerrors.sh
generated
vendored
@ -519,7 +519,7 @@ ccflags="$@"
|
||||
$2 ~ /^LOCK_(SH|EX|NB|UN)$/ ||
|
||||
$2 ~ /^LO_(KEY|NAME)_SIZE$/ ||
|
||||
$2 ~ /^LOOP_(CLR|CTL|GET|SET)_/ ||
|
||||
$2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ ||
|
||||
$2 ~ /^(AF|SOCK|SO|SOL|IPPROTO|IP|IPV6|TCP|MCAST|EVFILT|NOTE|SHUT|PROT|MAP|MREMAP|MFD|T?PACKET|MSG|SCM|MCL|DT|MADV|PR|LOCAL|TCPOPT|UDP)_/ ||
|
||||
$2 ~ /^NFC_(GENL|PROTO|COMM|RF|SE|DIRECTION|LLCP|SOCKPROTO)_/ ||
|
||||
$2 ~ /^NFC_.*_(MAX)?SIZE$/ ||
|
||||
$2 ~ /^RAW_PAYLOAD_/ ||
|
||||
|
40
vendor/golang.org/x/sys/unix/mremap.go
generated
vendored
Normal file
40
vendor/golang.org/x/sys/unix/mremap.go
generated
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build linux
|
||||
// +build linux
|
||||
|
||||
package unix
|
||||
|
||||
import "unsafe"
|
||||
|
||||
type mremapMmapper struct {
|
||||
mmapper
|
||||
mremap func(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (xaddr uintptr, err error)
|
||||
}
|
||||
|
||||
func (m *mremapMmapper) Mremap(oldData []byte, newLength int, flags int) (data []byte, err error) {
|
||||
if newLength <= 0 || len(oldData) == 0 || len(oldData) != cap(oldData) || flags&MREMAP_FIXED != 0 {
|
||||
return nil, EINVAL
|
||||
}
|
||||
|
||||
pOld := &oldData[cap(oldData)-1]
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
bOld := m.active[pOld]
|
||||
if bOld == nil || &bOld[0] != &oldData[0] {
|
||||
return nil, EINVAL
|
||||
}
|
||||
newAddr, errno := m.mremap(uintptr(unsafe.Pointer(&bOld[0])), uintptr(len(bOld)), uintptr(newLength), flags, 0)
|
||||
if errno != nil {
|
||||
return nil, errno
|
||||
}
|
||||
bNew := unsafe.Slice((*byte)(unsafe.Pointer(newAddr)), newLength)
|
||||
pNew := &bNew[cap(bNew)-1]
|
||||
if flags&MREMAP_DONTUNMAP == 0 {
|
||||
delete(m.active, pOld)
|
||||
}
|
||||
m.active[pNew] = bNew
|
||||
return bNew, nil
|
||||
}
|
17
vendor/golang.org/x/sys/unix/syscall_linux.go
generated
vendored
17
vendor/golang.org/x/sys/unix/syscall_linux.go
generated
vendored
@ -2124,11 +2124,15 @@ func writevRacedetect(iovecs []Iovec, n int) {
|
||||
|
||||
// mmap varies by architecture; see syscall_linux_*.go.
|
||||
//sys munmap(addr uintptr, length uintptr) (err error)
|
||||
//sys mremap(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (xaddr uintptr, err error)
|
||||
|
||||
var mapper = &mmapper{
|
||||
active: make(map[*byte][]byte),
|
||||
mmap: mmap,
|
||||
munmap: munmap,
|
||||
var mapper = &mremapMmapper{
|
||||
mmapper: mmapper{
|
||||
active: make(map[*byte][]byte),
|
||||
mmap: mmap,
|
||||
munmap: munmap,
|
||||
},
|
||||
mremap: mremap,
|
||||
}
|
||||
|
||||
func Mmap(fd int, offset int64, length int, prot int, flags int) (data []byte, err error) {
|
||||
@ -2139,6 +2143,10 @@ func Munmap(b []byte) (err error) {
|
||||
return mapper.Munmap(b)
|
||||
}
|
||||
|
||||
func Mremap(oldData []byte, newLength int, flags int) (data []byte, err error) {
|
||||
return mapper.Mremap(oldData, newLength, flags)
|
||||
}
|
||||
|
||||
//sys Madvise(b []byte, advice int) (err error)
|
||||
//sys Mprotect(b []byte, prot int) (err error)
|
||||
//sys Mlock(b []byte) (err error)
|
||||
@ -2487,7 +2495,6 @@ func Getresgid() (rgid, egid, sgid int) {
|
||||
// MqTimedreceive
|
||||
// MqTimedsend
|
||||
// MqUnlink
|
||||
// Mremap
|
||||
// Msgctl
|
||||
// Msgget
|
||||
// Msgrcv
|
||||
|
26
vendor/golang.org/x/sys/unix/zerrors_linux.go
generated
vendored
26
vendor/golang.org/x/sys/unix/zerrors_linux.go
generated
vendored
@ -493,6 +493,7 @@ const (
|
||||
BPF_F_TEST_RUN_ON_CPU = 0x1
|
||||
BPF_F_TEST_STATE_FREQ = 0x8
|
||||
BPF_F_TEST_XDP_LIVE_FRAMES = 0x2
|
||||
BPF_F_XDP_DEV_BOUND_ONLY = 0x40
|
||||
BPF_F_XDP_HAS_FRAGS = 0x20
|
||||
BPF_H = 0x8
|
||||
BPF_IMM = 0x0
|
||||
@ -826,9 +827,9 @@ const (
|
||||
DM_UUID_FLAG = 0x4000
|
||||
DM_UUID_LEN = 0x81
|
||||
DM_VERSION = 0xc138fd00
|
||||
DM_VERSION_EXTRA = "-ioctl (2022-07-28)"
|
||||
DM_VERSION_EXTRA = "-ioctl (2023-03-01)"
|
||||
DM_VERSION_MAJOR = 0x4
|
||||
DM_VERSION_MINOR = 0x2f
|
||||
DM_VERSION_MINOR = 0x30
|
||||
DM_VERSION_PATCHLEVEL = 0x0
|
||||
DT_BLK = 0x6
|
||||
DT_CHR = 0x2
|
||||
@ -1197,6 +1198,7 @@ const (
|
||||
FAN_EVENT_METADATA_LEN = 0x18
|
||||
FAN_EVENT_ON_CHILD = 0x8000000
|
||||
FAN_FS_ERROR = 0x8000
|
||||
FAN_INFO = 0x20
|
||||
FAN_MARK_ADD = 0x1
|
||||
FAN_MARK_DONT_FOLLOW = 0x4
|
||||
FAN_MARK_EVICTABLE = 0x200
|
||||
@ -1233,6 +1235,8 @@ const (
|
||||
FAN_REPORT_PIDFD = 0x80
|
||||
FAN_REPORT_TARGET_FID = 0x1000
|
||||
FAN_REPORT_TID = 0x100
|
||||
FAN_RESPONSE_INFO_AUDIT_RULE = 0x1
|
||||
FAN_RESPONSE_INFO_NONE = 0x0
|
||||
FAN_UNLIMITED_MARKS = 0x20
|
||||
FAN_UNLIMITED_QUEUE = 0x10
|
||||
FD_CLOEXEC = 0x1
|
||||
@ -1860,6 +1864,7 @@ const (
|
||||
MEMWRITEOOB64 = 0xc0184d15
|
||||
MFD_ALLOW_SEALING = 0x2
|
||||
MFD_CLOEXEC = 0x1
|
||||
MFD_EXEC = 0x10
|
||||
MFD_HUGETLB = 0x4
|
||||
MFD_HUGE_16GB = 0x88000000
|
||||
MFD_HUGE_16MB = 0x60000000
|
||||
@ -1875,6 +1880,7 @@ const (
|
||||
MFD_HUGE_8MB = 0x5c000000
|
||||
MFD_HUGE_MASK = 0x3f
|
||||
MFD_HUGE_SHIFT = 0x1a
|
||||
MFD_NOEXEC_SEAL = 0x8
|
||||
MINIX2_SUPER_MAGIC = 0x2468
|
||||
MINIX2_SUPER_MAGIC2 = 0x2478
|
||||
MINIX3_SUPER_MAGIC = 0x4d5a
|
||||
@ -1898,6 +1904,9 @@ const (
|
||||
MOUNT_ATTR_SIZE_VER0 = 0x20
|
||||
MOUNT_ATTR_STRICTATIME = 0x20
|
||||
MOUNT_ATTR__ATIME = 0x70
|
||||
MREMAP_DONTUNMAP = 0x4
|
||||
MREMAP_FIXED = 0x2
|
||||
MREMAP_MAYMOVE = 0x1
|
||||
MSDOS_SUPER_MAGIC = 0x4d44
|
||||
MSG_BATCH = 0x40000
|
||||
MSG_CMSG_CLOEXEC = 0x40000000
|
||||
@ -2204,6 +2213,7 @@ const (
|
||||
PACKET_USER = 0x6
|
||||
PACKET_VERSION = 0xa
|
||||
PACKET_VNET_HDR = 0xf
|
||||
PACKET_VNET_HDR_SZ = 0x18
|
||||
PARITY_CRC16_PR0 = 0x2
|
||||
PARITY_CRC16_PR0_CCITT = 0x4
|
||||
PARITY_CRC16_PR1 = 0x3
|
||||
@ -2221,6 +2231,7 @@ const (
|
||||
PERF_ATTR_SIZE_VER5 = 0x70
|
||||
PERF_ATTR_SIZE_VER6 = 0x78
|
||||
PERF_ATTR_SIZE_VER7 = 0x80
|
||||
PERF_ATTR_SIZE_VER8 = 0x88
|
||||
PERF_AUX_FLAG_COLLISION = 0x8
|
||||
PERF_AUX_FLAG_CORESIGHT_FORMAT_CORESIGHT = 0x0
|
||||
PERF_AUX_FLAG_CORESIGHT_FORMAT_RAW = 0x100
|
||||
@ -2361,6 +2372,7 @@ const (
|
||||
PR_FP_EXC_UND = 0x40000
|
||||
PR_FP_MODE_FR = 0x1
|
||||
PR_FP_MODE_FRE = 0x2
|
||||
PR_GET_AUXV = 0x41555856
|
||||
PR_GET_CHILD_SUBREAPER = 0x25
|
||||
PR_GET_DUMPABLE = 0x3
|
||||
PR_GET_ENDIAN = 0x13
|
||||
@ -2369,6 +2381,8 @@ const (
|
||||
PR_GET_FP_MODE = 0x2e
|
||||
PR_GET_IO_FLUSHER = 0x3a
|
||||
PR_GET_KEEPCAPS = 0x7
|
||||
PR_GET_MDWE = 0x42
|
||||
PR_GET_MEMORY_MERGE = 0x44
|
||||
PR_GET_NAME = 0x10
|
||||
PR_GET_NO_NEW_PRIVS = 0x27
|
||||
PR_GET_PDEATHSIG = 0x2
|
||||
@ -2389,6 +2403,7 @@ const (
|
||||
PR_MCE_KILL_GET = 0x22
|
||||
PR_MCE_KILL_LATE = 0x0
|
||||
PR_MCE_KILL_SET = 0x1
|
||||
PR_MDWE_REFUSE_EXEC_GAIN = 0x1
|
||||
PR_MPX_DISABLE_MANAGEMENT = 0x2c
|
||||
PR_MPX_ENABLE_MANAGEMENT = 0x2b
|
||||
PR_MTE_TAG_MASK = 0x7fff8
|
||||
@ -2423,6 +2438,8 @@ const (
|
||||
PR_SET_FP_MODE = 0x2d
|
||||
PR_SET_IO_FLUSHER = 0x39
|
||||
PR_SET_KEEPCAPS = 0x8
|
||||
PR_SET_MDWE = 0x41
|
||||
PR_SET_MEMORY_MERGE = 0x43
|
||||
PR_SET_MM = 0x23
|
||||
PR_SET_MM_ARG_END = 0x9
|
||||
PR_SET_MM_ARG_START = 0x8
|
||||
@ -2506,6 +2523,7 @@ const (
|
||||
PTRACE_GETSIGMASK = 0x420a
|
||||
PTRACE_GET_RSEQ_CONFIGURATION = 0x420f
|
||||
PTRACE_GET_SYSCALL_INFO = 0x420e
|
||||
PTRACE_GET_SYSCALL_USER_DISPATCH_CONFIG = 0x4211
|
||||
PTRACE_INTERRUPT = 0x4207
|
||||
PTRACE_KILL = 0x8
|
||||
PTRACE_LISTEN = 0x4208
|
||||
@ -2536,6 +2554,7 @@ const (
|
||||
PTRACE_SETREGSET = 0x4205
|
||||
PTRACE_SETSIGINFO = 0x4203
|
||||
PTRACE_SETSIGMASK = 0x420b
|
||||
PTRACE_SET_SYSCALL_USER_DISPATCH_CONFIG = 0x4210
|
||||
PTRACE_SINGLESTEP = 0x9
|
||||
PTRACE_SYSCALL = 0x18
|
||||
PTRACE_SYSCALL_INFO_ENTRY = 0x1
|
||||
@ -3072,7 +3091,7 @@ const (
|
||||
TASKSTATS_GENL_NAME = "TASKSTATS"
|
||||
TASKSTATS_GENL_VERSION = 0x1
|
||||
TASKSTATS_TYPE_MAX = 0x6
|
||||
TASKSTATS_VERSION = 0xd
|
||||
TASKSTATS_VERSION = 0xe
|
||||
TCIFLUSH = 0x0
|
||||
TCIOFF = 0x2
|
||||
TCIOFLUSH = 0x2
|
||||
@ -3238,6 +3257,7 @@ const (
|
||||
TP_STATUS_COPY = 0x2
|
||||
TP_STATUS_CSUMNOTREADY = 0x8
|
||||
TP_STATUS_CSUM_VALID = 0x80
|
||||
TP_STATUS_GSO_TCP = 0x100
|
||||
TP_STATUS_KERNEL = 0x0
|
||||
TP_STATUS_LOSING = 0x4
|
||||
TP_STATUS_SENDING = 0x2
|
||||
|
2
vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go
generated
vendored
@ -443,6 +443,7 @@ const (
|
||||
TIOCSWINSZ = 0x5414
|
||||
TIOCVHANGUP = 0x5437
|
||||
TOSTOP = 0x100
|
||||
TPIDR2_MAGIC = 0x54504902
|
||||
TUNATTACHFILTER = 0x401054d5
|
||||
TUNDETACHFILTER = 0x401054d6
|
||||
TUNGETDEVNETNS = 0x54e3
|
||||
@ -515,6 +516,7 @@ const (
|
||||
XCASE = 0x4
|
||||
XTABS = 0x1800
|
||||
ZA_MAGIC = 0x54366345
|
||||
ZT_MAGIC = 0x5a544e01
|
||||
_HIDIOCGRAWNAME = 0x80804804
|
||||
_HIDIOCGRAWPHYS = 0x80404805
|
||||
_HIDIOCGRAWUNIQ = 0x80404808
|
||||
|
11
vendor/golang.org/x/sys/unix/zsyscall_linux.go
generated
vendored
11
vendor/golang.org/x/sys/unix/zsyscall_linux.go
generated
vendored
@ -1868,6 +1868,17 @@ func munmap(addr uintptr, length uintptr) (err error) {
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func mremap(oldaddr uintptr, oldlength uintptr, newlength uintptr, flags int, newaddr uintptr) (xaddr uintptr, err error) {
|
||||
r0, _, e1 := Syscall6(SYS_MREMAP, uintptr(oldaddr), uintptr(oldlength), uintptr(newlength), uintptr(flags), uintptr(newaddr), 0)
|
||||
xaddr = uintptr(r0)
|
||||
if e1 != 0 {
|
||||
err = errnoErr(e1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// THIS FILE IS GENERATED BY THE COMMAND AT THE TOP; DO NOT EDIT
|
||||
|
||||
func Madvise(b []byte, advice int) (err error) {
|
||||
var _p0 unsafe.Pointer
|
||||
if len(b) > 0 {
|
||||
|
1
vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go
generated
vendored
1
vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go
generated
vendored
@ -372,6 +372,7 @@ const (
|
||||
SYS_LANDLOCK_CREATE_RULESET = 444
|
||||
SYS_LANDLOCK_ADD_RULE = 445
|
||||
SYS_LANDLOCK_RESTRICT_SELF = 446
|
||||
SYS_MEMFD_SECRET = 447
|
||||
SYS_PROCESS_MRELEASE = 448
|
||||
SYS_FUTEX_WAITV = 449
|
||||
SYS_SET_MEMPOLICY_HOME_NODE = 450
|
||||
|
35
vendor/golang.org/x/sys/unix/ztypes_linux.go
generated
vendored
35
vendor/golang.org/x/sys/unix/ztypes_linux.go
generated
vendored
@ -1538,6 +1538,10 @@ const (
|
||||
IFLA_GRO_MAX_SIZE = 0x3a
|
||||
IFLA_TSO_MAX_SIZE = 0x3b
|
||||
IFLA_TSO_MAX_SEGS = 0x3c
|
||||
IFLA_ALLMULTI = 0x3d
|
||||
IFLA_DEVLINK_PORT = 0x3e
|
||||
IFLA_GSO_IPV4_MAX_SIZE = 0x3f
|
||||
IFLA_GRO_IPV4_MAX_SIZE = 0x40
|
||||
IFLA_PROTO_DOWN_REASON_UNSPEC = 0x0
|
||||
IFLA_PROTO_DOWN_REASON_MASK = 0x1
|
||||
IFLA_PROTO_DOWN_REASON_VALUE = 0x2
|
||||
@ -1968,7 +1972,7 @@ const (
|
||||
NFT_MSG_GETFLOWTABLE = 0x17
|
||||
NFT_MSG_DELFLOWTABLE = 0x18
|
||||
NFT_MSG_GETRULE_RESET = 0x19
|
||||
NFT_MSG_MAX = 0x1a
|
||||
NFT_MSG_MAX = 0x21
|
||||
NFTA_LIST_UNSPEC = 0x0
|
||||
NFTA_LIST_ELEM = 0x1
|
||||
NFTA_HOOK_UNSPEC = 0x0
|
||||
@ -3651,7 +3655,7 @@ const (
|
||||
ETHTOOL_MSG_PSE_GET = 0x24
|
||||
ETHTOOL_MSG_PSE_SET = 0x25
|
||||
ETHTOOL_MSG_RSS_GET = 0x26
|
||||
ETHTOOL_MSG_USER_MAX = 0x26
|
||||
ETHTOOL_MSG_USER_MAX = 0x2b
|
||||
ETHTOOL_MSG_KERNEL_NONE = 0x0
|
||||
ETHTOOL_MSG_STRSET_GET_REPLY = 0x1
|
||||
ETHTOOL_MSG_LINKINFO_GET_REPLY = 0x2
|
||||
@ -3691,7 +3695,7 @@ const (
|
||||
ETHTOOL_MSG_MODULE_NTF = 0x24
|
||||
ETHTOOL_MSG_PSE_GET_REPLY = 0x25
|
||||
ETHTOOL_MSG_RSS_GET_REPLY = 0x26
|
||||
ETHTOOL_MSG_KERNEL_MAX = 0x26
|
||||
ETHTOOL_MSG_KERNEL_MAX = 0x2b
|
||||
ETHTOOL_A_HEADER_UNSPEC = 0x0
|
||||
ETHTOOL_A_HEADER_DEV_INDEX = 0x1
|
||||
ETHTOOL_A_HEADER_DEV_NAME = 0x2
|
||||
@ -3795,7 +3799,7 @@ const (
|
||||
ETHTOOL_A_RINGS_TCP_DATA_SPLIT = 0xb
|
||||
ETHTOOL_A_RINGS_CQE_SIZE = 0xc
|
||||
ETHTOOL_A_RINGS_TX_PUSH = 0xd
|
||||
ETHTOOL_A_RINGS_MAX = 0xd
|
||||
ETHTOOL_A_RINGS_MAX = 0x10
|
||||
ETHTOOL_A_CHANNELS_UNSPEC = 0x0
|
||||
ETHTOOL_A_CHANNELS_HEADER = 0x1
|
||||
ETHTOOL_A_CHANNELS_RX_MAX = 0x2
|
||||
@ -3833,14 +3837,14 @@ const (
|
||||
ETHTOOL_A_COALESCE_RATE_SAMPLE_INTERVAL = 0x17
|
||||
ETHTOOL_A_COALESCE_USE_CQE_MODE_TX = 0x18
|
||||
ETHTOOL_A_COALESCE_USE_CQE_MODE_RX = 0x19
|
||||
ETHTOOL_A_COALESCE_MAX = 0x19
|
||||
ETHTOOL_A_COALESCE_MAX = 0x1c
|
||||
ETHTOOL_A_PAUSE_UNSPEC = 0x0
|
||||
ETHTOOL_A_PAUSE_HEADER = 0x1
|
||||
ETHTOOL_A_PAUSE_AUTONEG = 0x2
|
||||
ETHTOOL_A_PAUSE_RX = 0x3
|
||||
ETHTOOL_A_PAUSE_TX = 0x4
|
||||
ETHTOOL_A_PAUSE_STATS = 0x5
|
||||
ETHTOOL_A_PAUSE_MAX = 0x5
|
||||
ETHTOOL_A_PAUSE_MAX = 0x6
|
||||
ETHTOOL_A_PAUSE_STAT_UNSPEC = 0x0
|
||||
ETHTOOL_A_PAUSE_STAT_PAD = 0x1
|
||||
ETHTOOL_A_PAUSE_STAT_TX_FRAMES = 0x2
|
||||
@ -4490,7 +4494,7 @@ const (
|
||||
NL80211_ATTR_MAC_HINT = 0xc8
|
||||
NL80211_ATTR_MAC_MASK = 0xd7
|
||||
NL80211_ATTR_MAX_AP_ASSOC_STA = 0xca
|
||||
NL80211_ATTR_MAX = 0x141
|
||||
NL80211_ATTR_MAX = 0x145
|
||||
NL80211_ATTR_MAX_CRIT_PROT_DURATION = 0xb4
|
||||
NL80211_ATTR_MAX_CSA_COUNTERS = 0xce
|
||||
NL80211_ATTR_MAX_MATCH_SETS = 0x85
|
||||
@ -4719,7 +4723,7 @@ const (
|
||||
NL80211_BAND_ATTR_HT_CAPA = 0x4
|
||||
NL80211_BAND_ATTR_HT_MCS_SET = 0x3
|
||||
NL80211_BAND_ATTR_IFTYPE_DATA = 0x9
|
||||
NL80211_BAND_ATTR_MAX = 0xb
|
||||
NL80211_BAND_ATTR_MAX = 0xd
|
||||
NL80211_BAND_ATTR_RATES = 0x2
|
||||
NL80211_BAND_ATTR_VHT_CAPA = 0x8
|
||||
NL80211_BAND_ATTR_VHT_MCS_SET = 0x7
|
||||
@ -4860,7 +4864,7 @@ const (
|
||||
NL80211_CMD_LEAVE_IBSS = 0x2c
|
||||
NL80211_CMD_LEAVE_MESH = 0x45
|
||||
NL80211_CMD_LEAVE_OCB = 0x6d
|
||||
NL80211_CMD_MAX = 0x98
|
||||
NL80211_CMD_MAX = 0x99
|
||||
NL80211_CMD_MICHAEL_MIC_FAILURE = 0x29
|
||||
NL80211_CMD_MODIFY_LINK_STA = 0x97
|
||||
NL80211_CMD_NAN_MATCH = 0x78
|
||||
@ -5841,6 +5845,8 @@ const (
|
||||
TUN_F_TSO6 = 0x4
|
||||
TUN_F_TSO_ECN = 0x8
|
||||
TUN_F_UFO = 0x10
|
||||
TUN_F_USO4 = 0x20
|
||||
TUN_F_USO6 = 0x40
|
||||
)
|
||||
|
||||
const (
|
||||
@ -5850,9 +5856,10 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
VIRTIO_NET_HDR_GSO_NONE = 0x0
|
||||
VIRTIO_NET_HDR_GSO_TCPV4 = 0x1
|
||||
VIRTIO_NET_HDR_GSO_UDP = 0x3
|
||||
VIRTIO_NET_HDR_GSO_TCPV6 = 0x4
|
||||
VIRTIO_NET_HDR_GSO_ECN = 0x80
|
||||
VIRTIO_NET_HDR_GSO_NONE = 0x0
|
||||
VIRTIO_NET_HDR_GSO_TCPV4 = 0x1
|
||||
VIRTIO_NET_HDR_GSO_UDP = 0x3
|
||||
VIRTIO_NET_HDR_GSO_TCPV6 = 0x4
|
||||
VIRTIO_NET_HDR_GSO_UDP_L4 = 0x5
|
||||
VIRTIO_NET_HDR_GSO_ECN = 0x80
|
||||
)
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_386.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_386.go
generated
vendored
@ -337,6 +337,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint32
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go
generated
vendored
@ -350,6 +350,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_arm.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_arm.go
generated
vendored
@ -328,6 +328,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint32
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go
generated
vendored
@ -329,6 +329,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go
generated
vendored
@ -330,6 +330,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_mips.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_mips.go
generated
vendored
@ -333,6 +333,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint32
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go
generated
vendored
@ -332,6 +332,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go
generated
vendored
@ -332,6 +332,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go
generated
vendored
@ -333,6 +333,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint32
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go
generated
vendored
@ -340,6 +340,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint32
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go
generated
vendored
@ -339,6 +339,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go
generated
vendored
@ -339,6 +339,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go
generated
vendored
@ -357,6 +357,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go
generated
vendored
@ -352,6 +352,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
2
vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go
generated
vendored
2
vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go
generated
vendored
@ -334,6 +334,8 @@ type Taskstats struct {
|
||||
Ac_exe_inode uint64
|
||||
Wpcopy_count uint64
|
||||
Wpcopy_delay_total uint64
|
||||
Irq_count uint64
|
||||
Irq_delay_total uint64
|
||||
}
|
||||
|
||||
type cpuMask uint64
|
||||
|
4
vendor/golang.org/x/sys/windows/service.go
generated
vendored
4
vendor/golang.org/x/sys/windows/service.go
generated
vendored
@ -218,6 +218,10 @@ type SERVICE_FAILURE_ACTIONS struct {
|
||||
Actions *SC_ACTION
|
||||
}
|
||||
|
||||
type SERVICE_FAILURE_ACTIONS_FLAG struct {
|
||||
FailureActionsOnNonCrashFailures int32
|
||||
}
|
||||
|
||||
type SC_ACTION struct {
|
||||
Type uint32
|
||||
Delay uint32
|
||||
|
2
vendor/golang.org/x/term/term_unix.go
generated
vendored
2
vendor/golang.org/x/term/term_unix.go
generated
vendored
@ -60,7 +60,7 @@ func restore(fd int, state *State) error {
|
||||
func getSize(fd int) (width, height int, err error) {
|
||||
ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(ws.Col), int(ws.Row), nil
|
||||
}
|
||||
|
8
vendor/modules.txt
vendored
8
vendor/modules.txt
vendored
@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20230702054502-d6c452fc12ce
|
||||
# github.com/jesseduffield/gocui v0.3.1-0.20230710004407-9bbfd873713b
|
||||
## explicit; go 1.12
|
||||
github.com/jesseduffield/gocui
|
||||
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
@ -293,17 +293,17 @@ golang.org/x/exp/slices
|
||||
golang.org/x/net/context
|
||||
golang.org/x/net/internal/socks
|
||||
golang.org/x/net/proxy
|
||||
# golang.org/x/sys v0.9.0
|
||||
# golang.org/x/sys v0.10.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/sys/cpu
|
||||
golang.org/x/sys/internal/unsafeheader
|
||||
golang.org/x/sys/plan9
|
||||
golang.org/x/sys/unix
|
||||
golang.org/x/sys/windows
|
||||
# golang.org/x/term v0.9.0
|
||||
# golang.org/x/term v0.10.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/term
|
||||
# golang.org/x/text v0.10.0
|
||||
# golang.org/x/text v0.11.0
|
||||
## explicit; go 1.17
|
||||
golang.org/x/text/encoding
|
||||
golang.org/x/text/encoding/internal/identifier
|
||||
|
Loading…
x
Reference in New Issue
Block a user