mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-31 23:19:40 +02:00
Change RepoPaths to be acquired via RepoPathCache (#3284)
### **PR Description** In order to optimize the number of git calls made this change implements a RepoPathCache as the API by which RepoPaths instances are acquired. This primarily affects app startup and worktree enumeration. This introduces a new dependency on [go-memoize](https://github.com/kofalt/go-memoize), which is a lightweight wrapper around go-cache and singleflight, in order to ensure that the cache is concurrency safe. (As compared to a simple map, e.g.) See the go-memoize README for details. Fixes #3227. ### **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
This commit is contained in:
commit
b004b2e275
@ -14,7 +14,6 @@ import (
|
||||
"github.com/spf13/afero"
|
||||
|
||||
appTypes "github.com/jesseduffield/lazygit/pkg/app/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
@ -119,7 +118,14 @@ func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest,
|
||||
return app, err
|
||||
}
|
||||
|
||||
showRecentRepos, err := app.setupRepo()
|
||||
// If we're not in a repo, repoPaths will be nil. The error is moot for us
|
||||
// at this stage, since we'll try to init a new repo in setupRepo(), below
|
||||
repoPaths, err := git_commands.GetRepoPaths(app.OSCommand.Cmd, gitVersion)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
|
||||
showRecentRepos, err := app.setupRepo(repoPaths)
|
||||
if err != nil {
|
||||
return app, err
|
||||
}
|
||||
@ -168,14 +174,16 @@ func openRecentRepo(app *App) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (app *App) setupRepo() (bool, error) {
|
||||
func (app *App) setupRepo(
|
||||
repoPaths *git_commands.RepoPaths,
|
||||
) (bool, error) {
|
||||
if env.GetGitDirEnv() != "" {
|
||||
// we've been given the git dir directly. We'll verify this dir when initializing our Git object
|
||||
// we've been given the git dir directly. Skip setup
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// if we are not in a git repo, we ask if we want to `git init`
|
||||
if err := commands.VerifyInGitRepo(app.OSCommand); err != nil {
|
||||
if repoPaths == nil {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return false, err
|
||||
@ -221,6 +229,7 @@ func (app *App) setupRepo() (bool, error) {
|
||||
if err := app.OSCommand.Cmd.New(args).Run(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@ -238,10 +247,7 @@ func (app *App) setupRepo() (bool, error) {
|
||||
}
|
||||
|
||||
// Run this afterward so that the previous repo creation steps can run without this interfering
|
||||
if isBare, err := git_commands.IsBareRepo(app.OSCommand); isBare {
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if repoPaths.IsBareRepo() {
|
||||
|
||||
fmt.Print(app.Tr.BareRepo)
|
||||
|
||||
|
@ -2,6 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
ioFs "io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -18,6 +19,7 @@ type RepoPaths struct {
|
||||
repoPath string
|
||||
repoGitDirPath string
|
||||
repoName string
|
||||
isBareRepo bool
|
||||
}
|
||||
|
||||
var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""}
|
||||
@ -54,6 +56,10 @@ func (self *RepoPaths) RepoName() string {
|
||||
return self.repoName
|
||||
}
|
||||
|
||||
func (self *RepoPaths) IsBareRepo() bool {
|
||||
return self.isBareRepo
|
||||
}
|
||||
|
||||
// Returns the repo paths for a typical repo
|
||||
func MockRepoPaths(currentPath string) *RepoPaths {
|
||||
return &RepoPaths{
|
||||
@ -62,6 +68,7 @@ func MockRepoPaths(currentPath string) *RepoPaths {
|
||||
repoPath: currentPath,
|
||||
repoGitDirPath: path.Join(currentPath, ".git"),
|
||||
repoName: "lazygit",
|
||||
isBareRepo: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +76,19 @@ func GetRepoPaths(
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
version *GitVersion,
|
||||
) (*RepoPaths, error) {
|
||||
gitDirOutput, err := callGitRevParse(cmd, version, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree")
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return GetRepoPathsForDir(cwd, cmd, version)
|
||||
}
|
||||
|
||||
func GetRepoPathsForDir(
|
||||
dir string,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
version *GitVersion,
|
||||
) (*RepoPaths, error) {
|
||||
gitDirOutput, err := callGitRevParseWithDir(cmd, version, dir, "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -84,13 +103,14 @@ func GetRepoPaths(
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
isBareRepo := gitDirResults[3] == "true"
|
||||
|
||||
// If we're in a submodule, --show-superproject-working-tree will return
|
||||
// a value, meaning gitDirResults will be length 4. In that case
|
||||
// a value, meaning gitDirResults will be length 5. 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
|
||||
isSubmodule := len(gitDirResults) == 5
|
||||
|
||||
var repoPath string
|
||||
if isSubmodule {
|
||||
@ -106,17 +126,10 @@ func GetRepoPaths(
|
||||
repoPath: repoPath,
|
||||
repoGitDirPath: repoGitDirPath,
|
||||
repoName: repoName,
|
||||
isBareRepo: isBareRepo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func callGitRevParse(
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
version *GitVersion,
|
||||
gitRevArgs ...string,
|
||||
) (string, error) {
|
||||
return callGitRevParseWithDir(cmd, version, "", gitRevArgs...)
|
||||
}
|
||||
|
||||
func callGitRevParseWithDir(
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
version *GitVersion,
|
||||
|
@ -36,10 +36,12 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
"/path/to/repo/.git",
|
||||
// --git-common-dir
|
||||
"/path/to/repo/.git",
|
||||
// --is-bare-repository
|
||||
"false",
|
||||
// --show-superproject-working-tree
|
||||
}
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
|
||||
strings.Join(expectedOutput, "\n"),
|
||||
nil)
|
||||
},
|
||||
@ -50,6 +52,38 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
repoPath: "/path/to/repo",
|
||||
repoGitDirPath: "/path/to/repo/.git",
|
||||
repoName: "repo",
|
||||
isBareRepo: false,
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
Name: "bare repo",
|
||||
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
|
||||
// setup for main worktree
|
||||
expectedOutput := []string{
|
||||
// --show-toplevel
|
||||
"/path/to/repo",
|
||||
// --git-dir
|
||||
"/path/to/bare_repo/bare.git",
|
||||
// --git-common-dir
|
||||
"/path/to/bare_repo/bare.git",
|
||||
// --is-bare-repository
|
||||
"true",
|
||||
// --show-superproject-working-tree
|
||||
}
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
|
||||
strings.Join(expectedOutput, "\n"),
|
||||
nil)
|
||||
},
|
||||
Path: "/path/to/repo",
|
||||
Expected: &RepoPaths{
|
||||
worktreePath: "/path/to/repo",
|
||||
worktreeGitDirPath: "/path/to/bare_repo/bare.git",
|
||||
repoPath: "/path/to/bare_repo",
|
||||
repoGitDirPath: "/path/to/bare_repo/bare.git",
|
||||
repoName: "bare_repo",
|
||||
isBareRepo: true,
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
@ -63,11 +97,13 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
"/path/to/repo/.git/modules/submodule1",
|
||||
// --git-common-dir
|
||||
"/path/to/repo/.git/modules/submodule1",
|
||||
// --is-bare-repository
|
||||
"false",
|
||||
// --show-superproject-working-tree
|
||||
"/path/to/repo",
|
||||
}
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-superproject-working-tree"),
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
|
||||
strings.Join(expectedOutput, "\n"),
|
||||
nil)
|
||||
},
|
||||
@ -78,6 +114,7 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
repoPath: "/path/to/repo/submodule1",
|
||||
repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
|
||||
repoName: "submodule1",
|
||||
isBareRepo: false,
|
||||
},
|
||||
Err: nil,
|
||||
},
|
||||
@ -85,7 +122,7 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
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"),
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
|
||||
"",
|
||||
errors.New("fatal: invalid gitfile format: /path/to/repo/worktree2/.git"))
|
||||
},
|
||||
@ -94,7 +131,7 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
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),
|
||||
fmt.Sprintf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args),
|
||||
)
|
||||
},
|
||||
},
|
||||
@ -120,7 +157,7 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
// prepare the filesystem for the scenario
|
||||
s.BeforeFunc(runner, getRevParseArgs)
|
||||
|
||||
repoPaths, err := GetRepoPaths(cmd, version)
|
||||
repoPaths, err := GetRepoPathsForDir("", cmd, version)
|
||||
|
||||
// check the error and the paths
|
||||
if s.Err != nil {
|
||||
|
@ -3,10 +3,8 @@ package git_commands
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
)
|
||||
|
||||
@ -49,20 +47,8 @@ func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
|
||||
return enums.REBASE_MODE_NONE
|
||||
}
|
||||
|
||||
func (self *StatusCommands) IsBareRepo() (bool, error) {
|
||||
return IsBareRepo(self.os)
|
||||
}
|
||||
|
||||
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
|
||||
res, err := osCommand.Cmd.New(
|
||||
NewGitCmd("rev-parse").Arg("--is-bare-repository").ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// The command returns output with a newline, so we need to strip
|
||||
return strconv.ParseBool(strings.TrimSpace(res))
|
||||
func (self *StatusCommands) IsBareRepo() bool {
|
||||
return self.repoPaths.isBareRepo
|
||||
}
|
||||
|
||||
func (self *StatusCommands) IsInNormalRebase() (bool, error) {
|
||||
|
@ -17,6 +17,6 @@ func NewDummyUpdater() *updates.Updater {
|
||||
// NewDummyGui creates a new dummy GUI for testing
|
||||
func NewDummyGui() *Gui {
|
||||
newAppConfig := config.NewDummyAppConfig()
|
||||
dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{}, NewDummyUpdater(), false, "", nil)
|
||||
dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, &git_commands.GitVersion{Major: 2, Minor: 0, Patch: 0}, NewDummyUpdater(), false, "", nil)
|
||||
return dummyGui
|
||||
}
|
||||
|
@ -8,12 +8,7 @@ import (
|
||||
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
|
||||
// so that we can open the same repo via the 'recent repos' menu
|
||||
func (gui *Gui) updateRecentRepoList() error {
|
||||
isBareRepo, err := gui.git.Status.IsBareRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if isBareRepo {
|
||||
if gui.git.Status.IsBareRepo() {
|
||||
// we could totally do this but it would require storing both the git-dir and the
|
||||
// worktree in our recent repos list, which is a change that would need to be
|
||||
// backwards compatible
|
||||
|
Loading…
x
Reference in New Issue
Block a user