diff --git a/pkg/commands/git.go b/pkg/commands/git.go index d9add2790..7ba2dba66 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -90,7 +90,7 @@ func NewGitCommandAux( repoPaths *git_commands.RepoPaths, pagerConfig *config.PagerConfig, ) *GitCommand { - cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd) + cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd, repoPaths.WorktreePath()) // here we're doing a bunch of dependency injection for each of our commands structs. // This is admittedly messy, but allows us to test each command struct in isolation, diff --git a/pkg/commands/git_cmd_obj_builder.go b/pkg/commands/git_cmd_obj_builder.go index 20d31c11c..9c3cd50d4 100644 --- a/pkg/commands/git_cmd_obj_builder.go +++ b/pkg/commands/git_cmd_obj_builder.go @@ -11,6 +11,15 @@ import ( type gitCmdObjBuilder struct { innerBuilder *oscommands.CmdObjBuilder + + // The directory of the repo (or worktree) this builder was created for; + // every command we produce runs there, regardless of the process's current + // working directory. The two are the same until the user switches to + // another repo: lazygit chdirs on a switch, but work still in flight for + // the previous repo (e.g. a background refresh spawning commands through + // the old builder) must keep running its commands against the repo it + // started in, not whichever one the process has since moved to. + repoDir string } var _ oscommands.ICmdObjBuilder = &gitCmdObjBuilder{} @@ -21,7 +30,7 @@ var _ oscommands.ICmdObjBuilder = &gitCmdObjBuilder{} // 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 { +func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuilder, repoDir string) *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{ @@ -33,15 +42,16 @@ func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuild return &gitCmdObjBuilder{ innerBuilder: updatedBuilder, + repoDir: repoDir, } } func (self *gitCmdObjBuilder) New(args []string) *oscommands.CmdObj { - return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar) + return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar).SetWd(self.repoDir) } func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) *oscommands.CmdObj { - return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(defaultEnvVar) + return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(defaultEnvVar).SetWd(self.repoDir) } func (self *gitCmdObjBuilder) Quote(str string) string { diff --git a/pkg/commands/git_cmd_obj_builder_test.go b/pkg/commands/git_cmd_obj_builder_test.go index ca7e54cfe..28e21501c 100644 --- a/pkg/commands/git_cmd_obj_builder_test.go +++ b/pkg/commands/git_cmd_obj_builder_test.go @@ -17,8 +17,25 @@ func TestGitCmdObjBuilderDisablesOptionalLocksByDefault(t *testing.T) { builder := NewGitCmdObjBuilder( utils.NewDummyLog(), oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)), + "/path/to/repo", ) assert.Contains(t, builder.New([]string{"git", "status"}).GetEnvVars(), git_commands.OptionalLocksEnvVar+"=0") assert.Contains(t, builder.NewShell("git status", "").GetEnvVars(), git_commands.OptionalLocksEnvVar+"=0") } + +// Every command the builder produces runs in the directory of the repo the +// builder was created for, not in the process's current directory: lazygit +// chdirs when switching repos, and commands built for the previous repo after +// that (e.g. by a background refresh still in flight) must keep addressing the +// repo they were built for. +func TestGitCmdObjBuilderPinsCommandsToRepoDir(t *testing.T) { + builder := NewGitCmdObjBuilder( + utils.NewDummyLog(), + oscommands.NewDummyCmdObjBuilder(oscommands.NewFakeRunner(t)), + "/path/to/repo", + ) + + assert.Equal(t, "/path/to/repo", builder.New([]string{"git", "status"}).GetCmd().Dir) + assert.Equal(t, "/path/to/repo", builder.NewShell("git status", "").GetCmd().Dir) +}