mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-03-19 21:28:28 +02:00
This changes GetRepoPaths() to pull information from `git rev-parse` instead of effectively reimplementing git's logic for pathfinding. This change fixes issues with bare repos, esp. versioned homedir use cases, by aligning lazygit's path handling to what git itself does. This change also enables lazygit to run from arbitrary subdirectories of a repository, including correct handling of symlinks, including "deep" symlinks into a repo, worktree, a repo's submodules, etc. Integration tests are now resilient against unintended side effects from the host's environment variables. Of necessity, $PATH and $TERM are the only env vars allowed through now.
262 lines
7.5 KiB
Go
262 lines
7.5 KiB
Go
package git_commands
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/spf13/afero"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestGetWorktrees(t *testing.T) {
|
|
type scenario struct {
|
|
testName string
|
|
repoPaths *RepoPaths
|
|
before func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn)
|
|
expectedWorktrees []*models.Worktree
|
|
expectedErr string
|
|
}
|
|
|
|
scenarios := []scenario{
|
|
{
|
|
testName: "Single worktree (main)",
|
|
repoPaths: &RepoPaths{
|
|
repoPath: "/path/to/repo",
|
|
worktreePath: "/path/to/repo",
|
|
},
|
|
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
|
|
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
|
|
`worktree /path/to/repo
|
|
HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d
|
|
branch refs/heads/mybranch
|
|
`,
|
|
nil)
|
|
|
|
gitArgsMainWorktree := append(append([]string{"-C", "/path/to/repo"}, getRevParseArgs()...), "--absolute-git-dir")
|
|
runner.ExpectGitArgs(gitArgsMainWorktree, "/path/to/repo/.git", nil)
|
|
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
|
|
},
|
|
expectedWorktrees: []*models.Worktree{
|
|
{
|
|
IsMain: true,
|
|
IsCurrent: true,
|
|
Path: "/path/to/repo",
|
|
IsPathMissing: false,
|
|
GitDir: "/path/to/repo/.git",
|
|
Branch: "mybranch",
|
|
Name: "repo",
|
|
},
|
|
},
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
testName: "Multiple worktrees (main + linked)",
|
|
repoPaths: &RepoPaths{
|
|
repoPath: "/path/to/repo",
|
|
worktreePath: "/path/to/repo",
|
|
},
|
|
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
|
|
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
|
|
`worktree /path/to/repo
|
|
HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d
|
|
branch refs/heads/mybranch
|
|
|
|
worktree /path/to/repo-worktree
|
|
HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de
|
|
branch refs/heads/mybranch-worktree
|
|
`,
|
|
nil)
|
|
gitArgsMainWorktree := append(append([]string{"-C", "/path/to/repo"}, getRevParseArgs()...), "--absolute-git-dir")
|
|
runner.ExpectGitArgs(gitArgsMainWorktree, "/path/to/repo/.git", nil)
|
|
gitArgsLinkedWorktree := append(append([]string{"-C", "/path/to/repo-worktree"}, getRevParseArgs()...), "--absolute-git-dir")
|
|
runner.ExpectGitArgs(gitArgsLinkedWorktree, "/path/to/repo/.git/worktrees/repo-worktree", nil)
|
|
|
|
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
|
|
_ = fs.MkdirAll("/path/to/repo-worktree", 0o755)
|
|
_ = fs.MkdirAll("/path/to/repo/.git/worktrees/repo-worktree", 0o755)
|
|
_ = afero.WriteFile(fs, "/path/to/repo-worktree/.git", []byte("gitdir: /path/to/repo/.git/worktrees/repo-worktree"), 0o755)
|
|
},
|
|
expectedWorktrees: []*models.Worktree{
|
|
{
|
|
IsMain: true,
|
|
IsCurrent: true,
|
|
Path: "/path/to/repo",
|
|
IsPathMissing: false,
|
|
GitDir: "/path/to/repo/.git",
|
|
Branch: "mybranch",
|
|
Name: "repo",
|
|
},
|
|
{
|
|
IsMain: false,
|
|
IsCurrent: false,
|
|
Path: "/path/to/repo-worktree",
|
|
IsPathMissing: false,
|
|
GitDir: "/path/to/repo/.git/worktrees/repo-worktree",
|
|
Branch: "mybranch-worktree",
|
|
Name: "repo-worktree",
|
|
},
|
|
},
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
testName: "Worktree missing path",
|
|
repoPaths: &RepoPaths{
|
|
repoPath: "/path/to/repo",
|
|
worktreePath: "/path/to/repo",
|
|
},
|
|
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
|
|
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
|
|
`worktree /path/to/worktree
|
|
HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de
|
|
branch refs/heads/missingbranch
|
|
`,
|
|
nil)
|
|
|
|
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
|
|
},
|
|
expectedWorktrees: []*models.Worktree{
|
|
{
|
|
IsMain: false,
|
|
IsCurrent: false,
|
|
Path: "/path/to/worktree",
|
|
IsPathMissing: true,
|
|
GitDir: "",
|
|
Branch: "missingbranch",
|
|
Name: "worktree",
|
|
},
|
|
},
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
testName: "In linked worktree",
|
|
repoPaths: &RepoPaths{
|
|
repoPath: "/path/to/repo",
|
|
worktreePath: "/path/to/repo-worktree",
|
|
},
|
|
before: func(runner *oscommands.FakeCmdObjRunner, fs afero.Fs, getRevParseArgs argFn) {
|
|
runner.ExpectGitArgs([]string{"worktree", "list", "--porcelain"},
|
|
`worktree /path/to/repo
|
|
HEAD d85cc9d281fa6ae1665c68365fc70e75e82a042d
|
|
branch refs/heads/mybranch
|
|
|
|
worktree /path/to/repo-worktree
|
|
HEAD 775955775e79b8f5b4c4b56f82fbf657e2d5e4de
|
|
branch refs/heads/mybranch-worktree
|
|
`,
|
|
nil)
|
|
gitArgsMainWorktree := append(append([]string{"-C", "/path/to/repo"}, getRevParseArgs()...), "--absolute-git-dir")
|
|
runner.ExpectGitArgs(gitArgsMainWorktree, "/path/to/repo/.git", nil)
|
|
gitArgsLinkedWorktree := append(append([]string{"-C", "/path/to/repo-worktree"}, getRevParseArgs()...), "--absolute-git-dir")
|
|
runner.ExpectGitArgs(gitArgsLinkedWorktree, "/path/to/repo/.git/worktrees/repo-worktree", nil)
|
|
|
|
_ = fs.MkdirAll("/path/to/repo/.git", 0o755)
|
|
_ = fs.MkdirAll("/path/to/repo-worktree", 0o755)
|
|
_ = fs.MkdirAll("/path/to/repo/.git/worktrees/repo-worktree", 0o755)
|
|
_ = afero.WriteFile(fs, "/path/to/repo-worktree/.git", []byte("gitdir: /path/to/repo/.git/worktrees/repo-worktree"), 0o755)
|
|
},
|
|
expectedWorktrees: []*models.Worktree{
|
|
{
|
|
IsMain: false,
|
|
IsCurrent: true,
|
|
Path: "/path/to/repo-worktree",
|
|
IsPathMissing: false,
|
|
GitDir: "/path/to/repo/.git/worktrees/repo-worktree",
|
|
Branch: "mybranch-worktree",
|
|
Name: "repo-worktree",
|
|
},
|
|
{
|
|
IsMain: true,
|
|
IsCurrent: false,
|
|
Path: "/path/to/repo",
|
|
IsPathMissing: false,
|
|
GitDir: "/path/to/repo/.git",
|
|
Branch: "mybranch",
|
|
Name: "repo",
|
|
},
|
|
},
|
|
expectedErr: "",
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
runner := oscommands.NewFakeRunner(t)
|
|
fs := afero.NewMemMapFs()
|
|
version, err := GetGitVersion(oscommands.NewDummyOSCommand())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
getRevParseArgs := func() []string {
|
|
args := []string{"rev-parse"}
|
|
if version.IsAtLeast(2, 31, 0) {
|
|
args = append(args, "--path-format=absolute")
|
|
}
|
|
return args
|
|
}
|
|
|
|
s.before(runner, fs, getRevParseArgs)
|
|
|
|
loader := &WorktreeLoader{
|
|
GitCommon: buildGitCommon(commonDeps{runner: runner, fs: fs, repoPaths: s.repoPaths, gitVersion: version}),
|
|
}
|
|
|
|
worktrees, err := loader.GetWorktrees()
|
|
if s.expectedErr != "" {
|
|
assert.EqualError(t, errors.New(s.expectedErr), err.Error())
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.EqualValues(t, s.expectedWorktrees, worktrees)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetUniqueNamesFromPaths(t *testing.T) {
|
|
for _, scenario := range []struct {
|
|
input []string
|
|
expected []string
|
|
}{
|
|
{
|
|
input: []string{},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
input: []string{
|
|
"/my/path/feature/one",
|
|
},
|
|
expected: []string{
|
|
"one",
|
|
},
|
|
},
|
|
{
|
|
input: []string{
|
|
"/my/path/feature/one/",
|
|
},
|
|
expected: []string{
|
|
"one",
|
|
},
|
|
},
|
|
{
|
|
input: []string{
|
|
"/a/b/c/d",
|
|
"/a/b/c/e",
|
|
"/a/b/f/d",
|
|
"/a/e/c/d",
|
|
},
|
|
expected: []string{
|
|
"b/c/d",
|
|
"e",
|
|
"f/d",
|
|
"e/c/d",
|
|
},
|
|
},
|
|
} {
|
|
actual := getUniqueNamesFromPaths(scenario.input)
|
|
assert.EqualValues(t, scenario.expected, actual)
|
|
}
|
|
}
|