Files
Stefan HallerandClaude Opus 5 d19af37ee7 Tell git where the repo is when it can't find it itself
git finds a repo by looking for a .git in the directory a command runs
in. Lazygit runs its commands in the work tree, so that normally works —
but not when the git dir lives somewhere else entirely, which is what
core.worktree and --work-tree are for. Lazygit chdir'd into such a work
tree and then ran commands that couldn't see any repo from there, so
opening a repo with core.worktree set panicked on startup. It only
worked with --git-dir because that leaves GIT_DIR in the environment for
every command to inherit.

Work out at startup whether git can find the repo from its work tree,
and when it can't, put GIT_DIR and GIT_WORK_TREE on every command the
repo's builder produces. As with the working directory the builder pins
(527124d0e0), these also go into the process env — subprocesses don't
come through the builder — but the commands don't read them from there,
because the process env belongs to whichever repo we have switched to
since.

Working out whether git can find the repo means asking git, rather than
reading the .git file, whose contents can spell the same directory
differently than git does. The extra query is skipped for a repo whose
git dir is simply its .git directory, which is nearly all of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 11:15:01 +02:00

198 lines
7.9 KiB
Go

package commands
import (
"os"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/env"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// GitCommand is our main git interface
type GitCommand struct {
Blame *git_commands.BlameCommands
Branch *git_commands.BranchCommands
Commit *git_commands.CommitCommands
Config *git_commands.ConfigCommands
Custom *git_commands.CustomCommands
Diff *git_commands.DiffCommands
File *git_commands.FileCommands
Flow *git_commands.FlowCommands
Patch *git_commands.PatchCommands
Rebase *git_commands.RebaseCommands
Remote *git_commands.RemoteCommands
Stash *git_commands.StashCommands
Status *git_commands.StatusCommands
Submodule *git_commands.SubmoduleCommands
Sync *git_commands.SyncCommands
Tag *git_commands.TagCommands
WorkingTree *git_commands.WorkingTreeCommands
Bisect *git_commands.BisectCommands
Worktree *git_commands.WorktreeCommands
Version *git_commands.GitVersion
RepoPaths *git_commands.RepoPaths
GitHub *git_commands.GitHubCommands
HostingService *git_commands.HostingService
Loaders Loaders
}
type Loaders struct {
BranchLoader *git_commands.BranchLoader
CommitFileLoader *git_commands.CommitFileLoader
CommitLoader *git_commands.CommitLoader
FileLoader *git_commands.FileLoader
ReflogCommitLoader *git_commands.ReflogCommitLoader
RemoteLoader *git_commands.RemoteLoader
StashLoader *git_commands.StashLoader
TagLoader *git_commands.TagLoader
Worktrees *git_commands.WorktreeLoader
}
func NewGitCommand(
cmn *common.Common,
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
diffRendererConfigManager *config.DiffRendererConfigManager,
) (*GitCommand, error) {
repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version)
if err != nil {
return nil, errors.Errorf("Error getting repo paths: %v", err)
}
// A bare repo has no worktree for us to work in. Callers that can offer the
// user something better (app.setupRepo) check for this first; getting here
// means nobody could, e.g. because --git-dir was pointed at a bare repo.
if repoPaths.IsBareRepo() {
return nil, errors.New(cmn.Tr.BareRepoNotSupported)
}
err = os.Chdir(repoPaths.WorktreePath())
if err != nil {
return nil, utils.WrapError(err)
}
// Everything we run through the command builder gets told where the repo is
// by the builder itself, but subprocesses don't go through it: user-defined
// custom commands, an editor, and the lazygit we re-enter as git's sequence
// editor during a rebase. Put it in the process env for those.
env.SetGitLocationEnvVars(repoPaths.GitLocationEnvVars())
// Pin the config reads to the repo directory like all other git commands
// (see NewGitCmdObjBuilder); the config commands run outside that builder.
gitConfig.SetDir(repoPaths.WorktreePath())
return NewGitCommandAux(
cmn,
version,
osCommand,
gitConfig,
repoPaths,
diffRendererConfigManager,
), nil
}
func NewGitCommandAux(
cmn *common.Common,
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths,
diffRendererConfigManager *config.DiffRendererConfigManager,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd, repoPaths.WorktreePath(), repoPaths.GitLocationEnvVars())
// 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,
// and allows for better namespacing when compared to having every method living
// on the one struct.
// common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, configCommands, diffRendererConfigManager)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
statusCommands := git_commands.NewStatusCommands(gitCommon)
flowCommands := git_commands.NewFlowCommands(gitCommon)
remoteCommands := git_commands.NewRemoteCommands(gitCommon)
branchCommands := git_commands.NewBranchCommands(gitCommon)
syncCommands := git_commands.NewSyncCommands(gitCommon)
tagCommands := git_commands.NewTagCommands(gitCommon)
commitCommands := git_commands.NewCommitCommands(gitCommon)
customCommands := git_commands.NewCustomCommands(gitCommon)
diffCommands := git_commands.NewDiffCommands(gitCommon)
fileCommands := git_commands.NewFileCommands(gitCommon)
submoduleCommands := git_commands.NewSubmoduleCommands(gitCommon)
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
patchBuilder := patch.NewPatchBuilder(cmn.Log,
func(from string, to string, reverse bool, filename string, previousPath string, plain bool) (string, error) {
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, previousPath, plain)
})
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
bisectCommands := git_commands.NewBisectCommands(gitCommon)
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
blameCommands := git_commands.NewBlameCommands(gitCommon)
gitHubCommands := git_commands.NewGitHubCommands(gitCommon)
hostingServiceCommands := git_commands.NewHostingServiceCommand(gitCommon)
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands)
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.WorkingTreeState, gitCommon)
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
remoteLoader := git_commands.NewRemoteLoader(cmn, cmd)
worktreeLoader := git_commands.NewWorktreeLoader(gitCommon)
stashLoader := git_commands.NewStashLoader(cmn, cmd)
tagLoader := git_commands.NewTagLoader(cmn, cmd)
return &GitCommand{
Blame: blameCommands,
Branch: branchCommands,
Commit: commitCommands,
Config: configCommands,
Custom: customCommands,
Diff: diffCommands,
File: fileCommands,
Flow: flowCommands,
Patch: patchCommands,
Rebase: rebaseCommands,
Remote: remoteCommands,
Stash: stashCommands,
Status: statusCommands,
Submodule: submoduleCommands,
Sync: syncCommands,
Tag: tagCommands,
Bisect: bisectCommands,
WorkingTree: workingTreeCommands,
Worktree: worktreeCommands,
Version: version,
GitHub: gitHubCommands,
HostingService: hostingServiceCommands,
Loaders: Loaders{
BranchLoader: branchLoader,
CommitFileLoader: commitFileLoader,
CommitLoader: commitLoader,
FileLoader: fileLoader,
ReflogCommitLoader: reflogCommitLoader,
RemoteLoader: remoteLoader,
Worktrees: worktreeLoader,
StashLoader: stashLoader,
TagLoader: tagLoader,
},
RepoPaths: repoPaths,
}
}
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run()
}