mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 04:15:48 +02:00
support bare repositories
This commit is contained in:
parent
f9f7f74efb
commit
97af7e677b
4 changed files with 102 additions and 57 deletions
32
main.go
32
main.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/go-errors/errors"
|
"github.com/go-errors/errors"
|
||||||
|
@ -22,8 +23,8 @@ var (
|
||||||
func main() {
|
func main() {
|
||||||
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
flaggy.DefaultParser.ShowVersionWithVersionFlag = false
|
||||||
|
|
||||||
repoPath := "."
|
repoPath := ""
|
||||||
flaggy.String(&repoPath, "p", "path", "Path of git repo")
|
flaggy.String(&repoPath, "p", "path", "Path of git repo. (Deprecated: use --git-dir for git directory and --work-tree for work tree directory)")
|
||||||
|
|
||||||
filterPath := ""
|
filterPath := ""
|
||||||
flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
|
flaggy.String(&filterPath, "f", "filter", "Path to filter on in `git log -- <path>`. When in filter mode, the commits, reflog, and stash are filtered based on the given path, and some operations are restricted")
|
||||||
|
@ -44,8 +45,31 @@ func main() {
|
||||||
configFlag := false
|
configFlag := false
|
||||||
flaggy.Bool(&configFlag, "c", "config", "Print the default config")
|
flaggy.Bool(&configFlag, "c", "config", "Print the default config")
|
||||||
|
|
||||||
|
workTree := ""
|
||||||
|
flaggy.String(&workTree, "w", "work-tree", "equivalent of the --work-tree git argument")
|
||||||
|
|
||||||
|
gitDir := ""
|
||||||
|
flaggy.String(&gitDir, "g", "git-dir", "equivalent of the --git-dir git argument")
|
||||||
|
|
||||||
flaggy.Parse()
|
flaggy.Parse()
|
||||||
|
|
||||||
|
if repoPath != "" {
|
||||||
|
if workTree != "" || gitDir != "" {
|
||||||
|
log.Fatal("--path option is incompatible with the --work-tree and --git-dir options")
|
||||||
|
}
|
||||||
|
|
||||||
|
workTree = repoPath
|
||||||
|
gitDir = filepath.Join(repoPath, ".git")
|
||||||
|
}
|
||||||
|
|
||||||
|
if workTree != "" {
|
||||||
|
os.Setenv("GIT_WORK_TREE", workTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gitDir != "" {
|
||||||
|
os.Setenv("GIT_DIR", gitDir)
|
||||||
|
}
|
||||||
|
|
||||||
if versionFlag {
|
if versionFlag {
|
||||||
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", commit, date, buildSource, version, runtime.GOOS, runtime.GOARCH)
|
fmt.Printf("commit=%s, build date=%s, build source=%s, version=%s, os=%s, arch=%s\n", commit, date, buildSource, version, runtime.GOOS, runtime.GOARCH)
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
|
@ -61,8 +85,8 @@ func main() {
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
if repoPath != "." {
|
if workTree != "" {
|
||||||
if err := os.Chdir(repoPath); err != nil {
|
if err := os.Chdir(workTree); err != nil {
|
||||||
log.Fatal(err.Error())
|
log.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return app, err
|
return app, err
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater, filterPath, showRecentRepos)
|
app.Gui, err = gui.NewGui(app.Log, app.GitCommand, app.OSCommand, app.Tr, config, app.Updater, filterPath, showRecentRepos)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return app, err
|
return app, err
|
||||||
|
@ -169,6 +170,11 @@ func (app *App) setupRepo() (bool, error) {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if os.Getenv("GIT_DIR") != "" {
|
||||||
|
// we've been given the git dir directly. We'll verify this dir when initializing our GitCommand object
|
||||||
|
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 := app.OSCommand.RunCommand("git status"); err != nil {
|
if err := app.OSCommand.RunCommand("git status"); err != nil {
|
||||||
cwd, err := os.Getwd()
|
cwd, err := os.Getwd()
|
||||||
|
@ -219,6 +225,14 @@ func (app *App) Run() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func gitDir() string {
|
||||||
|
dir := os.Getenv("GIT_DIR")
|
||||||
|
if dir == "" {
|
||||||
|
return ".git"
|
||||||
|
}
|
||||||
|
return dir
|
||||||
|
}
|
||||||
|
|
||||||
// Rebase contains logic for when we've been run in demon mode, meaning we've
|
// Rebase contains logic for when we've been run in demon mode, meaning we've
|
||||||
// given lazygit as a command for git to call e.g. to edit a file
|
// given lazygit as a command for git to call e.g. to edit a file
|
||||||
func (app *App) Rebase() error {
|
func (app *App) Rebase() error {
|
||||||
|
@ -230,7 +244,7 @@ func (app *App) Rebase() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
} else if strings.HasSuffix(os.Args[1], ".git/COMMIT_EDITMSG") {
|
} else if strings.HasSuffix(os.Args[1], filepath.Join(gitDir(), "COMMIT_EDITMSG")) { // TODO: test
|
||||||
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
// if we are rebasing and squashing, we'll see a COMMIT_EDITMSG
|
||||||
// but in this case we don't need to edit it, so we'll just return
|
// but in this case we don't need to edit it, so we'll just return
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -35,6 +35,16 @@ func verifyInGitRepo(runCmd func(string, ...interface{}) error) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
|
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
|
||||||
|
if os.Getenv("GIT_DIR") != "" {
|
||||||
|
// we've been given the git directory explicitly so no need to navigate to it
|
||||||
|
_, err := stat(os.Getenv("GIT_DIR"))
|
||||||
|
if err != nil {
|
||||||
|
return WrapError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
_, err := stat(".git")
|
_, err := stat(".git")
|
||||||
|
|
||||||
|
@ -52,31 +62,29 @@ func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir f
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupRepositoryAndWorktree(openGitRepository func(string) (*gogit.Repository, error), sLocalize func(string) string) (repository *gogit.Repository, worktree *gogit.Worktree, err error) {
|
func setupRepository(openGitRepository func(string) (*gogit.Repository, error), sLocalize func(string) string) (*gogit.Repository, error) {
|
||||||
repository, err = openGitRepository(".")
|
path := os.Getenv("GIT_DIR")
|
||||||
|
if path == "" {
|
||||||
|
path = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
repository, err := openGitRepository(path)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
|
if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) {
|
||||||
return nil, nil, errors.New(sLocalize("GitconfigParseErr"))
|
return nil, errors.New(sLocalize("GitconfigParseErr"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
worktree, err = repository.Worktree()
|
return repository, err
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitCommand is our main git interface
|
// GitCommand is our main git interface
|
||||||
type GitCommand struct {
|
type GitCommand struct {
|
||||||
Log *logrus.Entry
|
Log *logrus.Entry
|
||||||
OSCommand *OSCommand
|
OSCommand *OSCommand
|
||||||
Worktree *gogit.Worktree
|
|
||||||
Repo *gogit.Repository
|
Repo *gogit.Repository
|
||||||
Tr *i18n.Localizer
|
Tr *i18n.Localizer
|
||||||
Config config.AppConfigurer
|
Config config.AppConfigurer
|
||||||
|
@ -93,7 +101,7 @@ type GitCommand struct {
|
||||||
|
|
||||||
// NewGitCommand it runs git commands
|
// NewGitCommand it runs git commands
|
||||||
func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer, config config.AppConfigurer) (*GitCommand, error) {
|
func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer, config config.AppConfigurer) (*GitCommand, error) {
|
||||||
var worktree *gogit.Worktree
|
// var worktree *gogit.Worktree
|
||||||
var repo *gogit.Repository
|
var repo *gogit.Repository
|
||||||
|
|
||||||
// see what our default push behaviour is
|
// see what our default push behaviour is
|
||||||
|
@ -105,24 +113,16 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer,
|
||||||
pushToCurrent = strings.TrimSpace(output) == "current"
|
pushToCurrent = strings.TrimSpace(output) == "current"
|
||||||
}
|
}
|
||||||
|
|
||||||
fs := []func() error{
|
if err := verifyInGitRepo(osCommand.RunCommand); err != nil {
|
||||||
func() error {
|
return nil, err
|
||||||
return verifyInGitRepo(osCommand.RunCommand)
|
|
||||||
},
|
|
||||||
func() error {
|
|
||||||
return navigateToRepoRootDirectory(os.Stat, os.Chdir)
|
|
||||||
},
|
|
||||||
func() error {
|
|
||||||
var err error
|
|
||||||
repo, worktree, err = setupRepositoryAndWorktree(gogit.PlainOpen, tr.SLocalize)
|
|
||||||
return err
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, f := range fs {
|
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
|
||||||
if err := f(); err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
if repo, err = setupRepository(gogit.PlainOpen, tr.SLocalize); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
|
dotGitDir, err := findDotGitDir(os.Stat, ioutil.ReadFile)
|
||||||
|
@ -134,7 +134,6 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer,
|
||||||
Log: log,
|
Log: log,
|
||||||
OSCommand: osCommand,
|
OSCommand: osCommand,
|
||||||
Tr: tr,
|
Tr: tr,
|
||||||
Worktree: worktree,
|
|
||||||
Repo: repo,
|
Repo: repo,
|
||||||
Config: config,
|
Config: config,
|
||||||
getGlobalGitConfig: gitconfig.Global,
|
getGlobalGitConfig: gitconfig.Global,
|
||||||
|
@ -150,6 +149,10 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer,
|
||||||
}
|
}
|
||||||
|
|
||||||
func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filename string) ([]byte, error)) (string, error) {
|
func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filename string) ([]byte, error)) (string, error) {
|
||||||
|
if os.Getenv("GIT_DIR") != "" {
|
||||||
|
return os.Getenv("GIT_DIR"), nil
|
||||||
|
}
|
||||||
|
|
||||||
f, err := stat(".git")
|
f, err := stat(".git")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -236,8 +239,22 @@ type GetStatusFileOptions struct {
|
||||||
NoRenames bool
|
NoRenames bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *GitCommand) GetConfigValue(key string) string {
|
||||||
|
output, _ := c.OSCommand.RunCommandWithOutput("git config --get %s", key)
|
||||||
|
// looks like this returns an error if there is no matching value which we're okay with
|
||||||
|
return strings.TrimSpace(output)
|
||||||
|
}
|
||||||
|
|
||||||
func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*File {
|
func (c *GitCommand) GetStatusFiles(opts GetStatusFileOptions) []*File {
|
||||||
statusOutput, err := c.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames})
|
// check if config wants us ignoring untracked files
|
||||||
|
untrackedFilesSetting := c.GetConfigValue("status.showUntrackedFiles")
|
||||||
|
|
||||||
|
if untrackedFilesSetting == "" {
|
||||||
|
untrackedFilesSetting = "all"
|
||||||
|
}
|
||||||
|
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)
|
||||||
|
|
||||||
|
statusOutput, err := c.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Log.Error(err)
|
c.Log.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -582,7 +599,8 @@ func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
|
||||||
|
|
||||||
// GitStatus returns the plaintext short status of the repo
|
// GitStatus returns the plaintext short status of the repo
|
||||||
type GitStatusOptions struct {
|
type GitStatusOptions struct {
|
||||||
NoRenames bool
|
NoRenames bool
|
||||||
|
UntrackedFilesArg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) GitStatus(opts GitStatusOptions) (string, error) {
|
func (c *GitCommand) GitStatus(opts GitStatusOptions) (string, error) {
|
||||||
|
@ -590,7 +608,8 @@ func (c *GitCommand) GitStatus(opts GitStatusOptions) (string, error) {
|
||||||
if opts.NoRenames {
|
if opts.NoRenames {
|
||||||
noRenamesFlag = "--no-renames"
|
noRenamesFlag = "--no-renames"
|
||||||
}
|
}
|
||||||
return c.OSCommand.RunCommandWithOutput("git status --untracked-files=all --porcelain %s", noRenamesFlag)
|
|
||||||
|
return c.OSCommand.RunCommandWithOutput("git status %s --porcelain %s", opts.UntrackedFilesArg, noRenamesFlag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsInMergeState states whether we are still mid-merge
|
// IsInMergeState states whether we are still mid-merge
|
||||||
|
|
|
@ -150,13 +150,13 @@ func TestNavigateToRepoRootDirectory(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestSetupRepositoryAndWorktree is a function.
|
// TestSetupRepository is a function.
|
||||||
func TestSetupRepositoryAndWorktree(t *testing.T) {
|
func TestSetupRepository(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
testName string
|
testName string
|
||||||
openGitRepository func(string) (*gogit.Repository, error)
|
openGitRepository func(string) (*gogit.Repository, error)
|
||||||
sLocalize func(string) string
|
sLocalize func(string) string
|
||||||
test func(*gogit.Repository, *gogit.Worktree, error)
|
test func(*gogit.Repository, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
|
@ -168,7 +168,7 @@ func TestSetupRepositoryAndWorktree(t *testing.T) {
|
||||||
func(string) string {
|
func(string) string {
|
||||||
return "error translated"
|
return "error translated"
|
||||||
},
|
},
|
||||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
func(r *gogit.Repository, err error) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.EqualError(t, err, "error translated")
|
assert.EqualError(t, err, "error translated")
|
||||||
},
|
},
|
||||||
|
@ -179,22 +179,11 @@ func TestSetupRepositoryAndWorktree(t *testing.T) {
|
||||||
return nil, fmt.Errorf("Error from inside gogit")
|
return nil, fmt.Errorf("Error from inside gogit")
|
||||||
},
|
},
|
||||||
func(string) string { return "" },
|
func(string) string { return "" },
|
||||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
func(r *gogit.Repository, err error) {
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.EqualError(t, err, "Error from inside gogit")
|
assert.EqualError(t, err, "Error from inside gogit")
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"An error occurred cause git repository is a bare repository",
|
|
||||||
func(string) (*gogit.Repository, error) {
|
|
||||||
return &gogit.Repository{}, nil
|
|
||||||
},
|
|
||||||
func(string) string { return "" },
|
|
||||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Equal(t, gogit.ErrIsBareRepository, err)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"Setup done properly",
|
"Setup done properly",
|
||||||
func(string) (*gogit.Repository, error) {
|
func(string) (*gogit.Repository, error) {
|
||||||
|
@ -204,9 +193,8 @@ func TestSetupRepositoryAndWorktree(t *testing.T) {
|
||||||
return r, nil
|
return r, nil
|
||||||
},
|
},
|
||||||
func(string) string { return "" },
|
func(string) string { return "" },
|
||||||
func(r *gogit.Repository, w *gogit.Worktree, err error) {
|
func(r *gogit.Repository, err error) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, w)
|
|
||||||
assert.NotNil(t, r)
|
assert.NotNil(t, r)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -214,7 +202,7 @@ func TestSetupRepositoryAndWorktree(t *testing.T) {
|
||||||
|
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
s.test(setupRepositoryAndWorktree(s.openGitRepository, s.sLocalize))
|
s.test(setupRepository(s.openGitRepository, s.sLocalize))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue