1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-06-08 23:56:15 +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:
Jesse Duffield 2024-07-07 15:43:15 +10:00 committed by GitHub
commit b004b2e275
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 85 additions and 48 deletions

View File

@ -14,7 +14,6 @@ import (
"github.com/spf13/afero" "github.com/spf13/afero"
appTypes "github.com/jesseduffield/lazygit/pkg/app/types" 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/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
@ -119,7 +118,14 @@ func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest,
return app, err 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 { if err != nil {
return app, err return app, err
} }
@ -168,14 +174,16 @@ func openRecentRepo(app *App) bool {
return false return false
} }
func (app *App) setupRepo() (bool, error) { func (app *App) setupRepo(
repoPaths *git_commands.RepoPaths,
) (bool, error) {
if env.GetGitDirEnv() != "" { 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 return false, nil
} }
// if we are not in a git repo, we ask if we want to `git init` // 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() cwd, err := os.Getwd()
if err != nil { if err != nil {
return false, err return false, err
@ -221,6 +229,7 @@ func (app *App) setupRepo() (bool, error) {
if err := app.OSCommand.Cmd.New(args).Run(); err != nil { if err := app.OSCommand.Cmd.New(args).Run(); err != nil {
return false, err return false, err
} }
return false, nil 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 // 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 repoPaths.IsBareRepo() {
if err != nil {
return false, err
}
fmt.Print(app.Tr.BareRepo) fmt.Print(app.Tr.BareRepo)

View File

@ -2,6 +2,7 @@ package git_commands
import ( import (
ioFs "io/fs" ioFs "io/fs"
"os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -18,6 +19,7 @@ type RepoPaths struct {
repoPath string repoPath string
repoGitDirPath string repoGitDirPath string
repoName string repoName string
isBareRepo bool
} }
var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""} var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""}
@ -54,6 +56,10 @@ func (self *RepoPaths) RepoName() string {
return self.repoName return self.repoName
} }
func (self *RepoPaths) IsBareRepo() bool {
return self.isBareRepo
}
// Returns the repo paths for a typical repo // Returns the repo paths for a typical repo
func MockRepoPaths(currentPath string) *RepoPaths { func MockRepoPaths(currentPath string) *RepoPaths {
return &RepoPaths{ return &RepoPaths{
@ -62,6 +68,7 @@ func MockRepoPaths(currentPath string) *RepoPaths {
repoPath: currentPath, repoPath: currentPath,
repoGitDirPath: path.Join(currentPath, ".git"), repoGitDirPath: path.Join(currentPath, ".git"),
repoName: "lazygit", repoName: "lazygit",
isBareRepo: false,
} }
} }
@ -69,7 +76,19 @@ func GetRepoPaths(
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
version *GitVersion, version *GitVersion,
) (*RepoPaths, error) { ) (*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 { if err != nil {
return nil, err return nil, err
} }
@ -84,13 +103,14 @@ func GetRepoPaths(
return nil, err return nil, err
} }
} }
isBareRepo := gitDirResults[3] == "true"
// If we're in a submodule, --show-superproject-working-tree will return // 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 // 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 // normal repo or a worktree so return the parent of the git common
// dir (repoGitDirPath) // dir (repoGitDirPath)
isSubmodule := len(gitDirResults) == 4 isSubmodule := len(gitDirResults) == 5
var repoPath string var repoPath string
if isSubmodule { if isSubmodule {
@ -106,17 +126,10 @@ func GetRepoPaths(
repoPath: repoPath, repoPath: repoPath,
repoGitDirPath: repoGitDirPath, repoGitDirPath: repoGitDirPath,
repoName: repoName, repoName: repoName,
isBareRepo: isBareRepo,
}, nil }, nil
} }
func callGitRevParse(
cmd oscommands.ICmdObjBuilder,
version *GitVersion,
gitRevArgs ...string,
) (string, error) {
return callGitRevParseWithDir(cmd, version, "", gitRevArgs...)
}
func callGitRevParseWithDir( func callGitRevParseWithDir(
cmd oscommands.ICmdObjBuilder, cmd oscommands.ICmdObjBuilder,
version *GitVersion, version *GitVersion,

View File

@ -36,10 +36,12 @@ func TestGetRepoPaths(t *testing.T) {
"/path/to/repo/.git", "/path/to/repo/.git",
// --git-common-dir // --git-common-dir
"/path/to/repo/.git", "/path/to/repo/.git",
// --is-bare-repository
"false",
// --show-superproject-working-tree // --show-superproject-working-tree
} }
runner.ExpectGitArgs( 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"), strings.Join(expectedOutput, "\n"),
nil) nil)
}, },
@ -50,6 +52,38 @@ func TestGetRepoPaths(t *testing.T) {
repoPath: "/path/to/repo", repoPath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git", repoGitDirPath: "/path/to/repo/.git",
repoName: "repo", 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, Err: nil,
}, },
@ -63,11 +97,13 @@ func TestGetRepoPaths(t *testing.T) {
"/path/to/repo/.git/modules/submodule1", "/path/to/repo/.git/modules/submodule1",
// --git-common-dir // --git-common-dir
"/path/to/repo/.git/modules/submodule1", "/path/to/repo/.git/modules/submodule1",
// --is-bare-repository
"false",
// --show-superproject-working-tree // --show-superproject-working-tree
"/path/to/repo", "/path/to/repo",
} }
runner.ExpectGitArgs( 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"), strings.Join(expectedOutput, "\n"),
nil) nil)
}, },
@ -78,6 +114,7 @@ func TestGetRepoPaths(t *testing.T) {
repoPath: "/path/to/repo/submodule1", repoPath: "/path/to/repo/submodule1",
repoGitDirPath: "/path/to/repo/.git/modules/submodule1", repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoName: "submodule1", repoName: "submodule1",
isBareRepo: false,
}, },
Err: nil, Err: nil,
}, },
@ -85,7 +122,7 @@ func TestGetRepoPaths(t *testing.T) {
Name: "git rev-parse returns an error", Name: "git rev-parse returns an error",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) { BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
runner.ExpectGitArgs( 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")) 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 { Err: func(getRevParseArgs argFn) error {
args := strings.Join(getRevParseArgs(), " ") args := strings.Join(getRevParseArgs(), " ")
return errors.New( 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 // prepare the filesystem for the scenario
s.BeforeFunc(runner, getRevParseArgs) s.BeforeFunc(runner, getRevParseArgs)
repoPaths, err := GetRepoPaths(cmd, version) repoPaths, err := GetRepoPathsForDir("", cmd, version)
// check the error and the paths // check the error and the paths
if s.Err != nil { if s.Err != nil {

View File

@ -3,10 +3,8 @@ package git_commands
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
) )
@ -49,20 +47,8 @@ func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
return enums.REBASE_MODE_NONE return enums.REBASE_MODE_NONE
} }
func (self *StatusCommands) IsBareRepo() (bool, error) { func (self *StatusCommands) IsBareRepo() bool {
return IsBareRepo(self.os) return self.repoPaths.isBareRepo
}
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) IsInNormalRebase() (bool, error) { func (self *StatusCommands) IsInNormalRebase() (bool, error) {

View File

@ -17,6 +17,6 @@ func NewDummyUpdater() *updates.Updater {
// NewDummyGui creates a new dummy GUI for testing // NewDummyGui creates a new dummy GUI for testing
func NewDummyGui() *Gui { func NewDummyGui() *Gui {
newAppConfig := config.NewDummyAppConfig() 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 return dummyGui
} }

View File

@ -8,12 +8,7 @@ import (
// updateRecentRepoList registers the fact that we opened lazygit in this repo, // updateRecentRepoList registers the fact that we opened lazygit in this repo,
// so that we can open the same repo via the 'recent repos' menu // so that we can open the same repo via the 'recent repos' menu
func (gui *Gui) updateRecentRepoList() error { func (gui *Gui) updateRecentRepoList() error {
isBareRepo, err := gui.git.Status.IsBareRepo() if gui.git.Status.IsBareRepo() {
if err != nil {
return err
}
if isBareRepo {
// we could totally do this but it would require storing both the git-dir and the // 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 // worktree in our recent repos list, which is a change that would need to be
// backwards compatible // backwards compatible