mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-28 10:15:32 -05:00
Commitd94f2f05dropped the GIT_OPTIONAL_LOCKS=0 env var that we used to set on every git command, and re-added lock suppression only as a --no-optional-locks flag on the background files refresh. The intent was sound — a foreground `git status` should persist git's refreshed stat-cache — but the change was too broad: it stopped suppressing optional locks for every other command too. The one that bites is the main-view diff. When a folder containing submodules is selected, we render `git diff --submodule -- <dir>`, and `--submodule` makes git run `git status` inside each submodule to describe its "modified" state. That status now grabs the submodule's index.lock. It runs as a PTY task on its own goroutine, so it races any submodule-mutating action the user triggers — e.g. resetting a submodule runs `git -C <submodule> stash`, which then fails with "index.lock: File exists". This is what made submodule/reset_folder flaky. `git status` is in fact the only command that takes the optional lock, but the env var also covered its use inside `git diff --submodule`, inside PTY-run commands, and inside git's own submodule child processes — none of which a per-command flag reaches cleanly. Invert the polarity to match how it worked befored94f2f05: the git command builder disables optional locks on every command by default, and the single command that benefits from taking the lock — the foreground files refresh — opts back in. This restores the original contention avoidance (including against the user's terminal git) while keeping d94f2f05's stat-cache-persistence win for the foreground refresh. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
49 lines
2.1 KiB
Go
49 lines
2.1 KiB
Go
package commands
|
|
|
|
import (
|
|
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// all we're doing here is wrapping the default command object builder with
|
|
// some git-specific stuff: e.g. adding a git-specific env var
|
|
|
|
type gitCmdObjBuilder struct {
|
|
innerBuilder *oscommands.CmdObjBuilder
|
|
}
|
|
|
|
var _ oscommands.ICmdObjBuilder = &gitCmdObjBuilder{}
|
|
|
|
// We disable git's optional locks on every command by default so that our git
|
|
// invocations never contend for index.lock. See git_commands.OptionalLocksEnvVar
|
|
// for the full rationale. Individual commands that do want the lock (currently
|
|
// only the foreground files refresh) opt back in via CmdObj.RemoveEnvVar.
|
|
var defaultEnvVar = git_commands.OptionalLocksEnvVar + "=0"
|
|
|
|
func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuilder) *gitCmdObjBuilder {
|
|
// the price of having a convenient interface where we can say .New(...).Run() is that our builder now depends on our runner, so when we want to wrap the default builder/runner in new functionality we need to jump through some hoops. We could avoid the use of a decorator function here by just exporting the runner field on the default builder but that would be misleading because we don't want anybody using that to run commands (i.e. we want there to be a single API used across the codebase)
|
|
updatedBuilder := innerBuilder.CloneWithNewRunner(func(runner oscommands.ICmdObjRunner) oscommands.ICmdObjRunner {
|
|
return &gitCmdObjRunner{
|
|
log: log,
|
|
innerRunner: runner,
|
|
}
|
|
})
|
|
|
|
return &gitCmdObjBuilder{
|
|
innerBuilder: updatedBuilder,
|
|
}
|
|
}
|
|
|
|
func (self *gitCmdObjBuilder) New(args []string) *oscommands.CmdObj {
|
|
return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar)
|
|
}
|
|
|
|
func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) *oscommands.CmdObj {
|
|
return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(defaultEnvVar)
|
|
}
|
|
|
|
func (self *gitCmdObjBuilder) Quote(str string) string {
|
|
return self.innerBuilder.Quote(str)
|
|
}
|