mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-31 14:24:25 +03:00
Retry on index.lock error
I don't know why we're getting index.lock errors but they're impossile to stop anyway given that other processes can be calling git commands. So we're retrying a few times before re-raising. To do this we need to clone the command and the current implementation for that is best-effort. I do worry about the maintainability of that but we'll see how it goes. Also, I thought you'd need to clone the task (if it exists) but now I think not; as long as you don't call done twice on it you should be fine, and you shouldn't be done'ing a task as part of running a command: that should happen higher up.
This commit is contained in:
@ -1,12 +1,20 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||||
"github.com/sirupsen/logrus"
|
"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
|
// 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 {
|
type gitCmdObjRunner struct {
|
||||||
log *logrus.Entry
|
log *logrus.Entry
|
||||||
innerRunner oscommands.ICmdObjRunner
|
innerRunner oscommands.ICmdObjRunner
|
||||||
@ -18,13 +26,44 @@ func (self *gitCmdObjRunner) Run(cmdObj oscommands.ICmdObj) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, 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) {
|
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 {
|
func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj oscommands.ICmdObj, onLine func(line string) (bool, error)) error {
|
||||||
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
|
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,8 @@ type ICmdObj interface {
|
|||||||
|
|
||||||
GetCredentialStrategy() CredentialStrategy
|
GetCredentialStrategy() CredentialStrategy
|
||||||
GetTask() gocui.Task
|
GetTask() gocui.Task
|
||||||
|
|
||||||
|
Clone() ICmdObj
|
||||||
}
|
}
|
||||||
|
|
||||||
type CmdObj struct {
|
type CmdObj struct {
|
||||||
@ -215,3 +217,17 @@ func (self *CmdObj) GetCredentialStrategy() CredentialStrategy {
|
|||||||
func (self *CmdObj) GetTask() gocui.Task {
|
func (self *CmdObj) GetTask() gocui.Task {
|
||||||
return self.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,7 +8,7 @@ import (
|
|||||||
var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
var DiscardChanges = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
Description: "Discarding all possible permutations of changed files",
|
Description: "Discarding all possible permutations of changed files",
|
||||||
ExtraCmdArgs: []string{},
|
ExtraCmdArgs: []string{},
|
||||||
Skip: true, // failing due to index.lock file being created
|
Skip: false,
|
||||||
SetupConfig: func(config *config.AppConfig) {
|
SetupConfig: func(config *config.AppConfig) {
|
||||||
},
|
},
|
||||||
SetupRepo: func(shell *Shell) {
|
SetupRepo: func(shell *Shell) {
|
||||||
|
Reference in New Issue
Block a user