diff options
author | Stefan Haller <stefan@haller-berlin.de> | 2024-01-24 08:47:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-24 08:47:49 +0100 |
commit | b7d4db2446c1914fb9093b7f0513136cf48fc134 (patch) | |
tree | 8cd1102b24e36b52c28199e564f7e3f65a056c56 | |
parent | 74d937881edf7e3271976fa7484286c82650fb2e (diff) | |
parent | 3d9f1e02e5063e1ce24f4b9122963eaef64b7262 (diff) |
Use git rev-parse to obtain repository and worktree paths (#3183)
- **PR Description**
This changes GetRepoPaths() to pull information from `git rev-parse`
instead of partially 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. I believe
it also paves the way for lazygit to run from any subdirectory of a
working tree with relatively minor changes.
Addresses #1294 and #3175.
- **Please check if the PR fulfills these requirements**
* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [x] Docs (specifically `docs/Config.md`) have been updated if
necessary
* [x] You've read through your own file changes for silly mistakes etc
<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
25 files changed, 586 insertions, 438 deletions
diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..62f9670c8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true + +[*.go] +indent_style = tab diff --git a/pkg/commands/git.go b/pkg/commands/git.go index b1b04a72f..b43c8c4e5 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -2,12 +2,9 @@ package commands import ( "os" - "path" - "path/filepath" "strings" "github.com/go-errors/errors" - "github.com/spf13/afero" gogit "github.com/jesseduffield/go-git/v5" "github.com/jesseduffield/lazygit/pkg/commands/git_commands" @@ -15,7 +12,6 @@ import ( "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/env" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -64,39 +60,14 @@ func NewGitCommand( osCommand *oscommands.OSCommand, gitConfig git_config.IGitConfig, ) (*GitCommand, error) { - currentPath, err := os.Getwd() + repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version) if err != nil { - return nil, utils.WrapError(err) - } - - // converting to forward slashes for the sake of windows (which uses backwards slashes). We want everything - // to have forward slashes internally - currentPath = filepath.ToSlash(currentPath) - - gitDir := env.GetGitDirEnv() - if gitDir != "" { - // we've been given the git directory explicitly so no need to navigate to it - _, err := cmn.Fs.Stat(gitDir) - if err != nil { - return nil, utils.WrapError(err) - } - } else { - // we haven't been given the git dir explicitly so we assume it's in the current working directory as `.git/` (or an ancestor directory) - - rootDirectory, err := findWorktreeRoot(cmn.Fs, currentPath) - if err != nil { - return nil, utils.WrapError(err) - } - currentPath = rootDirectory - err = os.Chdir(rootDirectory) - if err != nil { - return nil, utils.WrapError(err) - } + return nil, errors.Errorf("Error getting repo paths: %v", err) } - repoPaths, err := git_commands.GetRepoPaths(cmn.Fs, currentPath) + err = os.Chdir(repoPaths.WorktreePath()) if err != nil { - return nil, errors.Errorf("Error getting repo paths: %v", err) + return nil, utils.WrapError(err) } repository, err := gogit.PlainOpenWithOptions( @@ -208,32 +179,6 @@ func NewGitCommandAux( } } -// this returns the root of the current worktree. So if you start lazygit from within -// a subdirectory of the worktree, it will start in the context of the root of that worktree -func findWorktreeRoot(fs afero.Fs, currentPath string) (string, error) { - for { - // we don't care if .git is a directory or a file: either is okay. - _, err := fs.Stat(path.Join(currentPath, ".git")) - - if err == nil { - return currentPath, nil - } - - if !os.IsNotExist(err) { - return "", utils.WrapError(err) - } - - currentPath = path.Dir(currentPath) - - atRoot := currentPath == path.Dir(currentPath) - if atRoot { - // we should never really land here: the code that creates GitCommand should - // verify we're in a git directory - return "", errors.New("Must open lazygit in a git repository") - } - } -} - func VerifyInGitRepo(osCommand *oscommands.OSCommand) error { return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run() } diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go index e0b5b8a9a..dfb0b4085 100644 --- a/pkg/commands/git_commands/commit.go +++ b/pkg/commands/git_commands/commit.go @@ -250,6 +250,7 @@ func (self *CommitCommands) ShowCmdObj(sha string, filterPath string) oscommands Arg(sha). ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space"). ArgIf(filterPath != "", "--", filterPath). + Dir(self.repoPaths.worktreePath). ToArgv() return self.cmd.New(cmdArgs).DontLog() diff --git a/pkg/commands/git_commands/commit_test.go b/pkg/commands/git_commands/commit_test.go index 0ade25a8d..a2b674eeb 100644 --- a/pkg/commands/git_commands/commit_test.go +++ b/pkg/commands/git_commands/commit_test.go @@ -197,7 +197,7 @@ func TestCommitShowCmdObj(t *testing.T) { contextSize: 3, ignoreWhitespace: false, extDiffCmd: "", - expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"}, + expected: []string{"-C", "/path/to/worktree", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"}, }, { testName: "Default case with filter path", @@ -205,7 +205,7 @@ func TestCommitShowCmdObj(t *testing.T) { contextSize: 3, ignoreWhitespace: false, extDiffCmd: "", - expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"}, + expected: []string{"-C", "/path/to/worktree", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--", "file.txt"}, }, { testName: "Show diff with custom context size", @@ -213,7 +213,7 @@ func TestCommitShowCmdObj(t *testing.T) { contextSize: 77, ignoreWhitespace: false, extDiffCmd: "", - expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"}, + expected: []string{"-C", "/path/to/worktree", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890"}, }, { testName: "Show diff, ignoring whitespace", @@ -221,7 +221,7 @@ func TestCommitShowCmdObj(t *testing.T) { contextSize: 77, ignoreWhitespace: true, extDiffCmd: "", - expected: []string{"show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"}, + expected: []string{"-C", "/path/to/worktree", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space"}, }, { testName: "Show diff with external diff command", @@ -229,7 +229,7 @@ func TestCommitShowCmdObj(t *testing.T) { contextSize: 3, ignoreWhitespace: false, extDiffCmd: "difft --color=always", - expected: []string{"-c", "diff.external=difft --color=always", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"}, + expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890"}, }, } @@ -243,7 +243,10 @@ func TestCommitShowCmdObj(t *testing.T) { appState.DiffContextSize = s.contextSize runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil) - instance := buildCommitCommands(commonDeps{userConfig: userConfig, appState: appState, runner: runner}) + repoPaths := RepoPaths{ + worktreePath: "/path/to/worktree", + } + instance := buildCommitCommands(commonDeps{userConfig: userConfig, appState: appState, runner: runner, repoPaths: &repoPaths}) assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath).Run()) runner.CheckForMissingCalls() diff --git a/pkg/commands/git_commands/diff.go b/pkg/commands/git_commands/diff.go index 372939024..73b30bc48 100644 --- a/pkg/commands/git_commands/diff.go +++ b/pkg/commands/git_commands/diff.go @@ -14,14 +14,19 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands { func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj { return self.cmd.New( - NewGitCmd("diff").Arg("--submodule", "--no-ext-diff", "--color").Arg(diffArgs...).ToArgv(), + NewGitCmd("diff"). + Arg("--submodule", "--no-ext-diff", "--color"). + Arg(diffArgs...). + Dir(self.repoPaths.worktreePath). + ToArgv(), ) } func (self *DiffCommands) internalDiffCmdObj(diffArgs ...string) *GitCommandBuilder { return NewGitCmd("diff"). Arg("--no-ext-diff", "--no-color"). - Arg(diffArgs...) + Arg(diffArgs...). + Dir(self.repoPaths.worktreePath) } func (self *DiffCommands) GetPathDiff(path string, staged bool) (string, error) { diff --git a/pkg/commands/git_commands/repo_paths.go b/pkg/commands/git_commands/repo_paths.go index 13cda86a4..b0e1970db 100644 --- a/pkg/commands/git_commands/repo_paths.go +++ b/pkg/commands/git_commands/repo_paths.go @@ -1,21 +1,18 @@ package git_commands import ( - "fmt" ioFs "io/fs" - "os" "path" "path/filepath" "strings" "github.com/go-errors/errors" - "github.com/jesseduffield/lazygit/pkg/env" - "github.com/samber/lo" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" + "github.com/jesseduffield/lazygit/pkg/utils" "github.com/spf13/afero" ) type RepoPaths struct { - currentPath string worktreePath string worktreeGitDirPath string repoPath string @@ -23,12 +20,7 @@ type RepoPaths struct { repoName string } -// Current working directory of the program. Currently, this will always -// be the same as WorktreePath(), but in future we may support running -// lazygit from inside a subdirectory of the worktree. -func (self *RepoPaths) CurrentPath() string { - return self.currentPath -} +var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""} // Path to the current worktree. If we're in the main worktree, this will // be the same as RepoPath() @@ -65,7 +57,6 @@ func (self *RepoPaths) RepoName() string { // Returns the repo paths for a typical repo func MockRepoPaths(currentPath string) *RepoPaths { return &RepoPaths{ - currentPath: currentPath, worktreePath: currentPath, worktreeGitDirPath: path.Join(currentPath, ".git"), repoPath: currentPath, @@ -75,44 +66,41 @@ func MockRepoPaths(currentPath string) *RepoPaths { } func GetRepoPaths( - fs afero.Fs, - currentPath string, -) (*RepoPaths, error) { - return getRepoPathsAux(afero.NewOsFs(), resolveSymlink, currentPath) -} - -func getRepoPathsAux( - fs afero.Fs, - resolveSymlinkFn func(string) (string, error), - currentPath string, + cmd oscommands.ICmdObjBuilder, + version *GitVersion, ) (*RepoPaths, error) { - worktreePath := currentPath - repoGitDirPath, repoPath, err := getCurrentRepoGitDirPath(fs, resolveSymlinkFn, currentPath) + gitDirOutput, err := callGitRevParse(cmd, version, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree") if err != nil { - return nil, errors.Errorf("failed to get repo git dir path: %v", err) + return nil, err } - var worktreeGitDirPath string - if env.GetWorkTreeEnv() != "" { - // This env is set when you pass --work-tree to lazygit. In that case, - // we're not dealing with a linked work-tree, we're dealing with a 'specified' - // worktree (for lack of a better term). In this case, the worktree has no - // .git file and it just contains a bunch of files: it has no idea it's - // pointed to by a bare repo. As such it does not have its own git dir within - // the bare repo's git dir. Instead, we just use the bare repo's git dir. - worktreeGitDirPath = repoGitDirPath - } else { - var err error - worktreeGitDirPath, err = getWorktreeGitDirPath(fs, currentPath) + gitDirResults := strings.Split(utils.NormalizeLinefeeds(gitDirOutput), "\n") + worktreePath := gitDirResults[0] + worktreeGitDirPath := gitDirResults[1] + repoGitDirPath := gitDirResults[2] + if version.IsOlderThanVersion(&gitPathFormatVersion) { + repoGitDirPath, err = filepath.Abs(repoGitDirPath) if err != nil { - return nil, errors.Errorf("failed to get worktree git dir path: %v", err) + return nil, err } } + // If we're in a submodule, --show-superproject-working-tree will return + // a value, meaning gitDirResults will be length 4. In that case + // return the worktree path as the repoPath. Otherwise we're in a + // normal repo or a worktree so return the parent of the git common + // dir (repoGitDirPath) + isSubmodule := len(gitDirResults) == 4 + + var repoPath string + if isSubmodule { + repoPath = worktreePath + } else { + repoPath = path.Dir(repoGitDirPath) + } repoName := path.Base(repoPath) return &RepoPaths{ - currentPath: currentPath, worktreePath: worktreePath, worktreeGitDirPath: worktreeGitDirPath, repoPath: repoPath, @@ -121,124 +109,31 @@ func getRepoPathsAux( }, nil } -// Returns the path of the git-dir for the worktree. For linked worktrees, the worktree has -// a .git file that points to the git-dir (which itself lives in the git-dir -// of the repo) -func getWorktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) { - // if .git is a file, we're in a linked worktree, otherwise we're in - // the main worktree - dotGitPath := path.Join(worktreePath, ".git") - gitFileInfo, err := fs.Stat(dotGitPath) - if err != nil { - return "", err - } - - if gitFileInfo.IsDir() { - return dotGitPath, nil - } - - return linkedWorktreeGitDirPath(fs, worktreePath) +func callGitRevParse( + cmd oscommands.ICmdObjBuilder, + version *GitVersion, + gitRevArgs ...string, +) (string, error) { + return callGitRevParseWithDir(cmd, version, "", gitRevArgs...) } -func linkedWorktreeGitDirPath(fs afero.Fs, worktreePath string) (string, error) { - dotGitPath := path.Join(worktreePath, ".git") - gitFileContents, err := afero.ReadFile(fs, dotGitPath) - if err != nil { - return "", err - } - - // The file will have `gitdir: /path/to/.git/worktrees/<worktree-name>` - gitDirLine := lo.Filter(strings.Split(string(gitFileContents), "\n"), func(line string, _ int) bool { - return strings.HasPrefix(line, "gitdir: ") - }) - - if len(gitDirLine) == 0 { - return "", errors.New(fmt.Sprintf("%s is a file which suggests we are in a submodule or a worktree but the file's contents do not contain a gitdir pointing to the actual .git directory", dotGitPath)) - } - - gitDir := strings.TrimPrefix(gitDirLine[0], "gitdir: ") - - gitDir = filepath.Clean(gitDir) - // For windows support - gitDir = filepath.ToSlash(gitDir) - - return gitDir, nil -} - -func getCurrentRepoGitDirPath( - fs afero.Fs, - resolveSymlinkFn func(string) (string, error), - currentPath string, -) (string, string, error) { - var unresolvedGitPath string - if env.GetGitDirEnv() != "" { - unresolvedGitPath = env.GetGitDirEnv() - } else { - unresolvedGitPath = path.Join(currentPath, ".git") +func callGitRevParseWithDir( + cmd oscommands.ICmdObjBuilder, + version *GitVersion, + dir string, + gitRevArgs ...string, +) (string, error) { + gitRevParse := NewGitCmd("rev-parse").ArgIf(version.IsAtLeastVersion(&gitPathFormatVersion), "--path-format=absolute").Arg(gitRevArgs...) + if dir != "" { + gitRevParse.Dir(dir) } - gitPath, err := resolveSymlinkFn(unresolvedGitPath) + gitCmd := cmd.New(gitRevParse.ToArgv()).DontLog() + res, err := gitCmd.RunWithOutput() if err != nil { - return "", "", err + return "", errors.Errorf("'%s' failed: %v", gitCmd.ToString(), err) } - - // check if .git is a file or a directory - gitFileInfo, err := fs.Stat(gitPath) - if err != nil { - return "", "", err - } - - if gitFileInfo.IsDir() { - // must be in the main worktree - return gitPath, path.Dir(gitPath), nil - } - - // either in a submodule, or worktree - worktreeGitPath, err := linkedWorktreeGitDirPath(fs, currentPath) - if err != nil { - return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err) - } - - _, err = fs.Stat(worktreeGitPath) - if err != nil { - if os.IsNotExist(err) { - // hardcoding error to get around windows-specific error message - return "", "", errors.Errorf("could not find git dir for %s. %s does not exist", currentPath, worktreeGitPath) - } - return "", "", errors.Errorf("could not find git dir for %s: %v", currentPath, err) - } - - // confirm whether the next directory up is the worktrees directory - parent := path.Dir(worktreeGitPath) - if path.Base(parent) == "worktrees" { - gitDirPath := path.Dir(parent) - return gitDirPath, path.Dir(gitDirPath), nil - } - - // Unlike worktrees, submodules can be nested arbitrarily deep, so we check - // if the `modules` directory is anywhere up the chain. - if strings.Contains(worktreeGitPath, "/modules/") { - // For submodules, we just return the path directly - return worktreeGitPath, currentPath, nil - } - - // If this error causes issues, we could relax the constraint and just always - // return the path - return "", "", errors.Errorf("could not find git dir for %s: the path '%s' is not under `worktrees` or `modules` directories", currentPath, worktreeGitPath) -} - -// takes a path containing a symlink and returns the true path -func resolveSymlink(path string) (string, error) { - l, err := os.Lstat(path) - if err != nil { - return "", err - } - - if l.Mode()&os.ModeSymlink == 0 { - return path, nil - } - - return filepath.EvalSymlinks(path) + return strings.TrimSpace(res), nil } // Returns the paths of linked worktrees diff --git a/pkg/commands/git_commands/repo_paths_test.go b/pkg/commands/git_commands/repo_paths_test.go index 5e6275522..ae4526737 100644 --- a/pkg/commands/git_commands/repo_paths_test.go +++ b/pkg/commands/git_commands/repo_paths_test.go @@ -1,34 +1,50 @@ package git_commands import ( + "fmt" + "strings" "testing" "github.com/go-errors/errors" - "github.com/spf13/afero" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/stretchr/testify/assert" ) -func mockResolveSymlinkFn(p string) (string, error) { return p, nil } +type ( + argFn func() []string + errFn func(getRevParseArgs argFn) error +) type Scenario struct { Name string - BeforeFunc func(fs afero.Fs) + BeforeFunc func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) Path string Expected *RepoPaths - Err error + Err errFn } -func TestGetRepoPathsAux(t *testing.T) { +func TestGetRepoPaths(t *testing.T) { scenarios := []Scenario{ { Name: "typical case", - BeforeFunc: func(fs afero.Fs) { + BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { // setup for main worktree - _ = fs.MkdirAll("/path/to/repo/.git", 0o755) + expectedOutput := []string{ + // --show-toplevel + "/path/to/repo", + // --git-dir + "/path/to/repo/.git", + // --git-common-dir + "/path/to/repo/.git", + // --show-superproject-working-tree + } + runner.ExpectGitArgs( + append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"), + strings.Join(expectedOutput, "\n"), + nil) }, Path: "/path/to/repo", Expected: &RepoPaths{ - currentPath: "/path/to/repo", worktreePath: "/path/to/repo", worktreeGitDirPath: "/path/to/repo/.git", repoPath: "/path/to/repo", @@ -38,70 +54,25 @@ func TestGetRepoPathsAux(t *testing.T) { Err: nil, }, { - Name: "linked worktree", - BeforeFunc: func(fs afero.Fs) { - // setup for linked worktree - _ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree1", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo/worktree1/.git", []byte("gitdir: /path/to/repo/.git/worktrees/worktree1"), 0o644) - }, - Path: "/path/to/repo/worktree1", - Expected: &RepoPaths{ - currentPath: "/path/to/repo/worktree1", - worktreePath: "/path/to/repo/worktree1", - worktreeGitDirPath: "/path/to/repo/.git/worktrees/worktree1", - repoPath: "/path/to/repo", - repoGitDirPath: "/path/to/repo/.git", - repoName: "repo", - }, - Err: nil, - }, - { - Name: "worktree with trailing separator in path", - BeforeFunc: func(fs afero.Fs) { - // setup for linked worktree - _ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree1", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo/worktree1/.git", []byte("gitdir: /path/to/repo/.git/worktrees/worktree1/"), 0o644) - }, - Path: "/path/to/repo/worktree1", - Expected: &RepoPaths{ - currentPath: "/path/to/repo/worktree1", - worktreePath: "/path/to/repo/worktree1", - worktreeGitDirPath: "/path/to/repo/.git/worktrees/worktree1", - repoPath: "/path/to/repo", - repoGitDirPath: "/path/to/repo/.git", - repoName: "repo", - }, - Err: nil, - }, - { - Name: "worktree .git file missing gitdir directive", - BeforeFunc: func(fs afero.Fs) { - _ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree2", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo/worktree2/.git", []byte("blah"), 0o644) - }, - Path: "/path/to/repo/worktree2", - Expected: nil, - Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2: /path/to/repo/worktree2/.git is a file which suggests we are in a submodule or a worktree but the file's contents do not contain a gitdir pointing to the actual .git directory"), - }, - { - Name: "worktree .git file gitdir directive points to a non-existing directory", - BeforeFunc: func(fs afero.Fs) { - _ = fs.MkdirAll("/path/to/repo/.git/worktrees/worktree2", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo/worktree2/.git", []byte("gitdir: /nonexistant"), 0o644) - }, - Path: "/path/to/repo/worktree2", - Expected: nil, - Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/worktree2. /nonexistant does not exist"), - }, - { Name: "submodule", - BeforeFunc: func(fs afero.Fs) { - _ = fs.MkdirAll("/path/to/repo/.git/modules/submodule1", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo/submodule1/.git", []byte("gitdir: /path/to/repo/.git/modules/submodule1"), 0o644) + BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { + expectedOutput := []string{ + // --show-toplevel + "/path/to/repo/submodule1", + // --git-dir + "/path/to/repo/.git/modules/submodule1", + // --git-common-dir + "/path/to/repo/.git/modules/submodule1", + // --show-superproject-working-tree + "/path/to/repo", + } + runner.ExpectGitArgs( + append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"), + strings.Join(expectedOutput, "\n"), + nil) }, Path: "/path/to/repo/submodule1", Expected: &RepoPaths{ - currentPath: "/path/to/repo/submodule1", worktreePath: "/path/to/repo/submodule1", worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1", repoPath: "/path/to/repo/submodule1", @@ -111,49 +82,52 @@ func TestGetRepoPathsAux(t *testing.T) { Err: nil, }, { - Name: "submodule in nested directory", - BeforeFunc: func(fs afero.Fs) { - _ = fs.MkdirAll("/path/to/repo/.git/modules/my/submodule1", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /path/to/repo/.git/modules/my/submodule1"), 0o644) - }, - Path: "/path/to/repo/my/submodule1", - Expected: &RepoPaths{ - currentPath: "/path/to/repo/my/submodule1", - worktreePath: "/path/to/repo/my/submodule1", - worktreeGitDirPath: "/path/to/repo/.git/modules/my/submodule1", - repoPath: "/path/to/repo/my/submodule1", - repoGitDirPath: "/path/to/repo/.git/modules/my/submodule1", - repoName: "submodule1", - }, - Err: nil, - }, - { - Name: "submodule git dir not under .git/modules", - BeforeFunc: func(fs afero.Fs) { - _ = fs.MkdirAll("/random/submodule1", 0o755) - _ = afero.WriteFile(fs, "/path/to/repo/my/submodule1/.git", []byte("gitdir: /random/submodule1"), 0o644) + Name: "git rev-parse returns an error", + BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { + runner.ExpectGitArgs( + append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"), + "", + errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git")) }, - Path: "/path/to/repo/my/submodule1", + Path: "/path/to/repo/worktree2", Expected: nil, - Err: errors.New("failed to get repo git dir path: could not find git dir for /path/to/repo/my/submodule1: the path '/random/submodule1' is not under `worktrees` or `modules` directories"), + Err: func(getRevParseArgs argFn) error { + args := strings.Join(getRevParseArgs(), " ") + return errors.New( + fmt.Sprintf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args), + ) + }, }, } for _, s := range scenarios { s := s t.Run(s.Name, func(t *testing.T) { - fs := afero.NewMemMapFs() + runner := oscommands.NewFakeRunner(t) + cmd := oscommands.NewDummyCmdObjBuilder(runner) + 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 + } // prepare the filesystem for the scenario |