mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-08-24 10:13:41 -05:00
Don't let our repo answer for a different one
GIT_DIR and GIT_WORK_TREE tell git where our repo is, and every command we run inherits them — including the ones we point at a submodule or another worktree. git resolves those against our repo instead, and says nothing about it: with GIT_DIR set, `git -C mysub log -1` reports the superproject's commit. So opening lazygit with --git-dir/--work-tree quietly broke resolving submodule conflicts, stashing and resetting a submodule, and detaching another worktree; the worktree list came back claiming every worktree shared our git dir. Drop the two variables from the commands that address another repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
616d75a1fa
commit
34d41b5d51
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
)
|
||||
|
||||
// OptionalLocksEnvVar is the name of the environment variable that tells git
|
||||
@@ -18,6 +19,15 @@ import (
|
||||
// that opts back in is the foreground files refresh; see FileLoader.gitStatus.
|
||||
const OptionalLocksEnvVar = "GIT_OPTIONAL_LOCKS"
|
||||
|
||||
// forOtherRepo prepares a command that operates on a repo other than the one
|
||||
// we have open — a submodule, or another worktree. GIT_DIR and GIT_WORK_TREE
|
||||
// say where our repo is, and every command we run inherits them, so a command
|
||||
// pointed at a different repo would be resolved against ours instead: `git -C
|
||||
// <submodule> log` would silently log the superproject's commits.
|
||||
func forOtherRepo(cmdObj *oscommands.CmdObj) *oscommands.CmdObj {
|
||||
return cmdObj.RemoveEnvVar(env.GitDirEnvVar).RemoveEnvVar(env.GitWorkTreeEnvVar)
|
||||
}
|
||||
|
||||
// convenience struct for building git commands. Especially useful when
|
||||
// including conditional args
|
||||
type GitCommandBuilder struct {
|
||||
|
||||
@@ -176,17 +176,40 @@ func getBareRepoPathsForDir(
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Asks git about the repo at dir. This is how we find our own repo, so it has
|
||||
// to be answered the way git itself would answer it there, GIT_DIR and
|
||||
// GIT_WORK_TREE included.
|
||||
func callGitRevParseWithDir(
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
dir string,
|
||||
gitRevArgs ...string,
|
||||
) (string, error) {
|
||||
return runGitRevParse(newGitRevParseCmd(cmd, dir, gitRevArgs...))
|
||||
}
|
||||
|
||||
// Asks git about a repo that isn't the one we have open; see forOtherRepo.
|
||||
func callGitRevParseInOtherRepo(
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
dir string,
|
||||
gitRevArgs ...string,
|
||||
) (string, error) {
|
||||
return runGitRevParse(forOtherRepo(newGitRevParseCmd(cmd, dir, gitRevArgs...)))
|
||||
}
|
||||
|
||||
func newGitRevParseCmd(
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
dir string,
|
||||
gitRevArgs ...string,
|
||||
) *oscommands.CmdObj {
|
||||
gitRevParse := NewGitCmd("rev-parse").Arg("--path-format=absolute").Arg(gitRevArgs...)
|
||||
if dir != "" {
|
||||
gitRevParse.Dir(dir)
|
||||
}
|
||||
|
||||
gitCmd := cmd.New(gitRevParse.ToArgv()).DontLog()
|
||||
return cmd.New(gitRevParse.ToArgv()).DontLog()
|
||||
}
|
||||
|
||||
func runGitRevParse(gitCmd *oscommands.CmdObj) (string, error) {
|
||||
res, err := gitCmd.RunWithOutput()
|
||||
if err != nil {
|
||||
return "", errors.Errorf("'%s' failed: %v", gitCmd.ToString(), err)
|
||||
|
||||
@@ -157,7 +157,7 @@ func (self *SubmoduleCommands) GetCommitSummary(path string, sha string) (string
|
||||
Config("log.showsignature=false").
|
||||
ToArgv()
|
||||
|
||||
summary, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
summary, err := forOtherRepo(self.cmd.New(cmdArgs)).DontLog().RunWithOutput()
|
||||
return strings.TrimSpace(summary), err
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ func (self *SubmoduleCommands) GetCommitSummary(path string, sha string) (string
|
||||
// caller then stages the submodule to record the resolution.
|
||||
func (self *SubmoduleCommands) CheckoutConflictCommit(path string, sha string) error {
|
||||
cmdArgs := NewGitCmd("checkout").Dir(path).Arg(sha).ToArgv()
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
return forOtherRepo(self.cmd.New(cmdArgs)).Run()
|
||||
}
|
||||
|
||||
// ConflictSideLog returns a oneline log, run inside the submodule, of the commits
|
||||
@@ -179,7 +179,7 @@ func (self *SubmoduleCommands) ConflictSideLog(path string, side string, otherSi
|
||||
Arg("--oneline", "--color=always", otherSide+".."+side).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
return forOtherRepo(self.cmd.New(cmdArgs)).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
|
||||
@@ -195,7 +195,7 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
|
||||
Arg("--include-untracked").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
return forOtherRepo(self.cmd.New(cmdArgs)).Run()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
|
||||
@@ -229,7 +229,7 @@ func (self *SubmoduleCommands) UpdateAll() error {
|
||||
// need not be.
|
||||
func (self *SubmoduleCommands) runInParentModule(submodule *models.SubmoduleConfig, cmdObj *oscommands.CmdObj) error {
|
||||
if submodule.ParentModule != nil {
|
||||
cmdObj.SetWd(submodule.ParentModule.FullPath())
|
||||
forOtherRepo(cmdObj.SetWd(submodule.ParentModule.FullPath()))
|
||||
}
|
||||
return cmdObj.Run()
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -80,6 +83,27 @@ func TestSubmoduleCheckoutConflictCommit(t *testing.T) {
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
// A command that runs inside a submodule mustn't inherit the GIT_DIR and
|
||||
// GIT_WORK_TREE that say where the superproject is; git would answer it from
|
||||
// there instead, and the answer would look perfectly plausible.
|
||||
func TestSubmoduleCommandDoesntUseOurGitLocation(t *testing.T) {
|
||||
t.Setenv(env.GitDirEnvVar, "/path/to/repo/.git")
|
||||
t.Setenv(env.GitWorkTreeEnvVar, "/path/to/repo")
|
||||
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectFunc("has neither GIT_DIR nor GIT_WORK_TREE", func(cmdObj *oscommands.CmdObj) bool {
|
||||
return lo.NoneBy(cmdObj.GetEnvVars(), func(envVar string) bool {
|
||||
return strings.HasPrefix(envVar, env.GitDirEnvVar+"=") ||
|
||||
strings.HasPrefix(envVar, env.GitWorkTreeEnvVar+"=")
|
||||
})
|
||||
}, "bbbbbbb the subject\n", nil)
|
||||
instance := buildSubmoduleCommands(commonDeps{runner: runner})
|
||||
|
||||
_, err := instance.GetCommitSummary("mysub", "bbbbbbb")
|
||||
assert.NoError(t, err)
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
func TestSubmoduleConflictSideLog(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"-C", "mysub", "log", "--oneline", "--color=always", "ccccccc..bbbbbbb"}, "bbbbbbb left\n", nil)
|
||||
|
||||
@@ -51,7 +51,7 @@ func (self *WorktreeCommands) Delete(worktreePath string, force bool) error {
|
||||
func (self *WorktreeCommands) Detach(worktreePath string) error {
|
||||
cmdArgs := NewGitCmd("checkout").Arg("--detach").GitDir(filepath.Join(worktreePath, ".git")).ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
return forOtherRepo(self.cmd.New(cmdArgs)).Run()
|
||||
}
|
||||
|
||||
func WorktreeForBranch(branch *models.Branch, worktrees []*models.Worktree) (*models.Worktree, bool) {
|
||||
|
||||
@@ -77,7 +77,7 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
|
||||
if worktree.IsPathMissing {
|
||||
return
|
||||
}
|
||||
gitDir, err := callGitRevParseWithDir(self.cmd, worktree.Path, "--absolute-git-dir")
|
||||
gitDir, err := callGitRevParseInOtherRepo(self.cmd, worktree.Path, "--absolute-git-dir")
|
||||
if err != nil {
|
||||
self.Log.Warnf("Could not find git dir for worktree %s: %v", worktree.Path, err)
|
||||
return
|
||||
|
||||
Vendored
+13
-6
@@ -6,23 +6,30 @@ import (
|
||||
|
||||
// This package encapsulates accessing/mutating the ENV of the program.
|
||||
|
||||
// The variables with which git can be told where a repo is, rather than having
|
||||
// it find out from the working directory.
|
||||
const (
|
||||
GitDirEnvVar = "GIT_DIR"
|
||||
GitWorkTreeEnvVar = "GIT_WORK_TREE"
|
||||
)
|
||||
|
||||
func GetGitDirEnv() string {
|
||||
return os.Getenv("GIT_DIR")
|
||||
return os.Getenv(GitDirEnvVar)
|
||||
}
|
||||
|
||||
func SetGitDirEnv(value string) {
|
||||
os.Setenv("GIT_DIR", value)
|
||||
os.Setenv(GitDirEnvVar, value)
|
||||
}
|
||||
|
||||
func GetWorkTreeEnv() string {
|
||||
return os.Getenv("GIT_WORK_TREE")
|
||||
return os.Getenv(GitWorkTreeEnvVar)
|
||||
}
|
||||
|
||||
func SetWorkTreeEnv(value string) {
|
||||
os.Setenv("GIT_WORK_TREE", value)
|
||||
os.Setenv(GitWorkTreeEnvVar, value)
|
||||
}
|
||||
|
||||
func UnsetGitLocationEnvVars() {
|
||||
_ = os.Unsetenv("GIT_DIR")
|
||||
_ = os.Unsetenv("GIT_WORK_TREE")
|
||||
_ = os.Unsetenv(GitDirEnvVar)
|
||||
_ = os.Unsetenv(GitWorkTreeEnvVar)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user