From c61bfbdd4cd46243ef6663533e0c9db05dd1f7bd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 12 May 2019 17:04:32 +1000 Subject: Support opening lazygit in a submodule --- pkg/commands/git.go | 43 ++++++++++++++++++----- pkg/commands/git_test.go | 76 ++++++++++++++++++++++++++++++++++++++++ pkg/commands/testdata/a_dir/file | 0 pkg/commands/testdata/a_file | 0 4 files changed, 110 insertions(+), 9 deletions(-) create mode 100644 pkg/commands/testdata/a_dir/file create mode 100644 pkg/commands/testdata/a_file (limited to 'pkg/commands') diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 159c88cca..2a6f57659 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -25,13 +25,10 @@ func verifyInGitRepo(runCmd func(string) error) error { func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error { for { - f, err := stat(".git") + _, err := stat(".git") if err == nil { - if f.IsDir() { - return nil - } - return errors.New("expected .git to be a directory") + return nil } if !os.IsNotExist(err) { @@ -75,6 +72,7 @@ type GitCommand struct { getGlobalGitConfig func(string) (string, error) getLocalGitConfig func(string) (string, error) removeFile func(string) error + DotGitDir string } // NewGitCommand it runs git commands @@ -102,6 +100,11 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer, } } + dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile) + if err != nil { + return nil, err + } + return &GitCommand{ Log: log, OSCommand: osCommand, @@ -112,9 +115,31 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer, getGlobalGitConfig: gitconfig.Global, getLocalGitConfig: gitconfig.Local, removeFile: os.RemoveAll, + DotGitDir: dotGitDir, }, nil } +func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filename string) ([]byte, error)) (string, error) { + f, err := stat(".git") + if err != nil { + return "", err + } + + if f.IsDir() { + return ".git", nil + } + + fileBytes, err := readFile(".git") + if err != nil { + return "", err + } + fileContent := string(fileBytes) + if !strings.HasPrefix(fileContent, "gitdir: ") { + return "", errors.New(".git is a file which suggests we are in a submodule but the file's contents do not contain a gitdir pointing to the actual .git directory") + } + return strings.TrimSpace(strings.TrimPrefix(fileContent, "gitdir: ")), nil +} + // GetStashEntries stash entryies func (c *GitCommand) GetStashEntries() []*StashEntry { rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'") @@ -426,14 +451,14 @@ func (c *GitCommand) IsInMergeState() (bool, error) { // RebaseMode returns "" for non-rebase mode, "normal" for normal rebase // and "interactive" for interactive rebase func (c *GitCommand) RebaseMode() (string, error) { - exists, err := c.OSCommand.FileExists(".git/rebase-apply") + exists, err := c.OSCommand.FileExists(fmt.Sprintf("%s/rebase-apply", c.DotGitDir)) if err != nil { return "", err } if exists { return "normal", nil } - exists, err = c.OSCommand.FileExists(".git/rebase-merge") + exists, err = c.OSCommand.FileExists(fmt.Sprintf("%s/rebase-merge", c.DotGitDir)) if exists { return "interactive", err } else { @@ -743,7 +768,7 @@ func (c *GitCommand) AmendTo(sha string) error { // EditRebaseTodo sets the action at a given index in the git-rebase-todo file func (c *GitCommand) EditRebaseTodo(index int, action string) error { - fileName := ".git/rebase-merge/git-rebase-todo" + fileName := fmt.Sprintf("%s/rebase-merge/git-rebase-todo", c.DotGitDir) bytes, err := ioutil.ReadFile(fileName) if err != nil { return err @@ -775,7 +800,7 @@ func (c *GitCommand) getTodoCommitCount(content []string) int { // MoveTodoDown moves a rebase todo item down by one position func (c *GitCommand) MoveTodoDown(index int) error { - fileName := ".git/rebase-merge/git-rebase-todo" + fileName := fmt.Sprintf("%s/rebase-merge/git-rebase-todo", c.DotGitDir) bytes, err := ioutil.ReadFile(fileName) if err != nil { return err diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 0d59c30b7..be9f8c883 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/go-errors/errors" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/test" "github.com/stretchr/testify/assert" @@ -2122,3 +2123,78 @@ func TestGitCommandCreateFixupCommit(t *testing.T) { }) } } + +func TestFindDotGitDir(t *testing.T) { + type scenario struct { + testName string + stat func(string) (os.FileInfo, error) + readFile func(filename string) ([]byte, error) + test func(string, error) + } + + scenarios := []scenario{ + { + ".git is a directory", + func(dotGit string) (os.FileInfo, error) { + assert.Equal(t, ".git", dotGit) + return os.Stat("testdata/a_dir") + }, + func(dotGit string) ([]byte, error) { + assert.Fail(t, "readFile should not be called if .git is a directory") + return nil, nil + }, + func(gitDir string, err error) { + assert.NoError(t, err) + assert.Equal(t, ".git", gitDir) + }, + }, + { + ".git is a file", + func(dotGit string) (os.FileInfo, error) { + assert.Equal(t, ".git", dotGit) + return os.Stat("testdata/a_file") + }, + func(dotGit string) ([]byte, error) { + assert.Equal(t, ".git", dotGit) + return []byte("gitdir: blah\n"), nil + }, + func(gitDir string, err error) { + assert.NoError(t, err) + assert.Equal(t, "blah", gitDir) + }, + }, + { + "os.Stat returns an error", + func(dotGit string) (os.FileInfo, error) { + assert.Equal(t, ".git", dotGit) + return nil, errors.New("error") + }, + func(dotGit string) ([]byte, error) { + assert.Fail(t, "readFile should not be called os.Stat returns an error") + return nil, nil + }, + func(gitDir string, err error) { + assert.Error(t, err) + }, + }, + { + "readFile returns an error", + func(dotGit string) (os.FileInfo, error) { + assert.Equal(t, ".git", dotGit) + return os.Stat("testdata/a_file") + }, + func(dotGit string) ([]byte, error) { + return nil, errors.New("error") + }, + func(gitDir string, err error) { + assert.Error(t, err) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + s.test(findDotGitDir(s.stat, s.readFile)) + }) + } +} diff --git a/pkg/commands/testdata/a_dir/file b/pkg/commands/testdata/a_dir/file new file mode 100644 index 000000000..e69de29bb diff --git a/pkg/commands/testdata/a_file b/pkg/commands/testdata/a_file new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3