mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-10 07:36:27 -04:00
Open the repo when lazygit is started in its .git dir
Running lazygit in a .git dir got you told you were in a bare repo, which you weren't: the worktree was sitting right there, one directory up. git's own convention is that a git dir called .git belongs to the directory holding it — that's how `git worktree list` names the main worktree — so ask that directory, and if it is a worktree, open the repo we were really being asked about. The git dirs that aren't called .git keep the answer they had. A linked worktree's and a submodule's do have a worktree, but nothing we look at says where, so we would be guessing; a bare repo's has none to find. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
06b421ad0c
commit
f141fcc570
|
|
@ -56,9 +56,19 @@ func (self *RepoPaths) RepoName() string {
|
|||
return self.repoName
|
||||
}
|
||||
|
||||
// Whether the repo has no worktree, so that there is nothing for lazygit to
|
||||
// show. Note that this isn't quite git's core.bare: a repo that calls itself
|
||||
// non-bare but doesn't have a worktree either counts as bare for us.
|
||||
// Whether we found no worktree, so that there is nothing for lazygit to show.
|
||||
// Note that this isn't quite git's core.bare: a repo that calls itself non-bare
|
||||
// but whose worktree we couldn't find counts as bare for us too. Concretely,
|
||||
// this is true when we're in
|
||||
//
|
||||
// - a genuinely bare repo;
|
||||
// - the git dir of a linked worktree (.git/worktrees/x), whose worktree is
|
||||
// recorded but not somewhere we look;
|
||||
// - a repo that keeps its worktree somewhere only GIT_WORK_TREE knows, such
|
||||
// as a vcsh-style dotfiles repo that hasn't been given core.worktree.
|
||||
//
|
||||
// The .git dir of an ordinary repo is not one of them: GetRepoPathsForDir
|
||||
// notices the worktree holding it and hands back that repo instead.
|
||||
func (self *RepoPaths) IsBareRepo() bool {
|
||||
return self.isBareRepo
|
||||
}
|
||||
|
|
@ -99,6 +109,39 @@ func GetRepoPaths(
|
|||
func GetRepoPathsForDir(
|
||||
dir string,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
) (*RepoPaths, error) {
|
||||
repoPaths, err := repoPathsForDir(dir, cmd)
|
||||
if err != nil || !repoPaths.IsBareRepo() {
|
||||
return repoPaths, err
|
||||
}
|
||||
|
||||
// We're in a git dir rather than in a working tree, which usually just means
|
||||
// somebody ran lazygit in the .git of an ordinary repo. git's convention is
|
||||
// that a git dir called .git belongs to the directory holding it, so look
|
||||
// there: if that is a working tree, it is the repo we were asked about, and
|
||||
// there's no reason to make the user go up a directory and try again.
|
||||
//
|
||||
// The git dirs that aren't called .git keep the paths we have. A linked
|
||||
// worktree's (.git/worktrees/x) and a submodule's (.git/modules/x) do have a
|
||||
// working tree, but only the directory holding a .git tells us where, so we
|
||||
// would be guessing. A bare repo's has none to find.
|
||||
if filepath.Base(repoPaths.WorktreeGitDirPath()) != ".git" {
|
||||
return repoPaths, nil
|
||||
}
|
||||
|
||||
pathsFromWorkTree, err := repoPathsForDir(filepath.Dir(repoPaths.WorktreeGitDirPath()), cmd)
|
||||
if err != nil || pathsFromWorkTree.IsBareRepo() {
|
||||
return repoPaths, nil
|
||||
}
|
||||
return pathsFromWorkTree, nil
|
||||
}
|
||||
|
||||
// repoPathsForDir asks git about the repo at dir, and reports a bare repo when
|
||||
// there is no working tree there. Unlike GetRepoPathsForDir it never looks
|
||||
// anywhere but dir, which is what keeps that one from going round in circles.
|
||||
func repoPathsForDir(
|
||||
dir string,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
) (*RepoPaths, error) {
|
||||
gitDirOutput, err := callGitRevParseWithDir(cmd, dir, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree")
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -115,6 +115,48 @@ func TestGetRepoPaths(t *testing.T) {
|
|||
}),
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
// Standing in the .git dir of an ordinary repo: git refuses to name a
|
||||
// work tree, but the directory holding the .git is one, so we open the
|
||||
// repo from there.
|
||||
Name: "in a repo's .git dir",
|
||||
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
|
||||
gitDir := lo.Ternary(runtime.GOOS == "windows", `C:\path\to\repo\.git`, "/path/to/repo/.git")
|
||||
worktree := lo.Ternary(runtime.GOOS == "windows", `C:\path\to\repo`, "/path/to/repo")
|
||||
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
|
||||
"",
|
||||
errors.New("fatal: this operation must be run in a work tree"))
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--absolute-git-dir", "--git-common-dir"),
|
||||
strings.Join([]string{gitDir, gitDir}, "\n"),
|
||||
nil)
|
||||
|
||||
// asking again from the directory holding the .git
|
||||
runner.ExpectGitArgs(
|
||||
append(append([]string{"-C", worktree}, getRevParseArgs()...), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
|
||||
strings.Join([]string{worktree, gitDir, gitDir}, "\n"),
|
||||
nil)
|
||||
},
|
||||
Path: "/path/to/repo/.git",
|
||||
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
|
||||
worktreePath: `C:\path\to\repo`,
|
||||
worktreeGitDirPath: `C:\path\to\repo\.git`,
|
||||
repoPath: `C:\path\to\repo`,
|
||||
repoGitDirPath: `C:\path\to\repo\.git`,
|
||||
repoName: `repo`,
|
||||
isBareRepo: false,
|
||||
}, &RepoPaths{
|
||||
worktreePath: "/path/to/repo",
|
||||
worktreeGitDirPath: "/path/to/repo/.git",
|
||||
repoPath: "/path/to/repo",
|
||||
repoGitDirPath: "/path/to/repo/.git",
|
||||
repoName: "repo",
|
||||
isBareRepo: false,
|
||||
}),
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
// A repo whose work tree lives somewhere else entirely, as set up by
|
||||
// core.worktree or by --work-tree. We're in the main worktree, but the
|
||||
|
|
|
|||
34
pkg/integration/tests/misc/start_in_git_dir.go
Normal file
34
pkg/integration/tests/misc/start_in_git_dir.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package misc
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var StartInGitDir = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Start lazygit in a repo's .git dir, and have it open the repo",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(cfg *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("blah", "original content\n")
|
||||
shell.Commit("initial commit")
|
||||
shell.UpdateFile("blah", "updated content\n")
|
||||
|
||||
// this is where lazygit will start
|
||||
shell.Chdir(".git")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("initial commit"),
|
||||
)
|
||||
|
||||
// we're in the work tree the .git belongs to, not in the .git itself
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains(" M blah"),
|
||||
)
|
||||
},
|
||||
})
|
||||
|
|
@ -353,6 +353,7 @@ var tests = []*components.IntegrationTest{
|
|||
misc.DirenvUnloadsOnBlockedEnvrc,
|
||||
misc.InitialOpen,
|
||||
misc.RecentReposOnLaunch,
|
||||
misc.StartInGitDir,
|
||||
patch_building.Apply,
|
||||
patch_building.ApplyInReverse,
|
||||
patch_building.ApplyInReverseWithConflict,
|
||||
|
|
|
|||
Loading…
Reference in a new issue