From 624d63d2fa90ad62998a62d8d17189cf0406864b Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Wed, 29 Aug 2018 21:47:48 +0200 Subject: [PATCH 01/19] pkg/git : remove panic in SetupGit method --- main.go | 8 +++++++- pkg/commands/git.go | 49 +++++++++++++++++++++++++++++++-------------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/main.go b/main.go index 4303af02f..6193f43d3 100644 --- a/main.go +++ b/main.go @@ -48,6 +48,12 @@ func main() { app.Log.Error(err.Error()) panic(err) } - app.GitCommand.SetupGit() + + if err := app.GitCommand.SetupGit(); err != nil { + app.Log.Error(err.Error()) + fmt.Println(err) + os.Exit(1) + } + app.Gui.RunWithSubprocesses() } diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 704a45c73..68571769e 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -15,6 +15,10 @@ import ( gogit "gopkg.in/src-d/go-git.v4" ) +// ErrGitRepositoryInvalid is emitted when we run a git command in a folder +// to check if we have a valid git repository and we get an error instead +var ErrGitRepositoryInvalid = fmt.Errorf("can't find a valid git repository in current directory") + // GitCommand is our main git interface type GitCommand struct { Log *logrus.Entry @@ -35,13 +39,20 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) } // SetupGit sets git repo up -func (c *GitCommand) SetupGit() { - c.verifyInGitRepo() - c.navigateToRepoRootDirectory() - if err := c.setupWorktree(); err != nil { - c.Log.Error(err) - panic(err) +func (c *GitCommand) SetupGit() error { + fs := []func() error{ + c.verifyInGitRepo, + c.navigateToRepoRootDirectory, + c.setupWorktree, } + + for _, f := range fs { + if err := f(); err != nil { + return err + } + } + + return nil } // GetStashEntries stash entryies @@ -145,11 +156,12 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { return append(headResults, tailResults...) } -func (c *GitCommand) verifyInGitRepo() { - if output, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil { - fmt.Println(output) - os.Exit(1) +func (c *GitCommand) verifyInGitRepo() error { + if _, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil { + return ErrGitRepositoryInvalid } + + return nil } // GetBranchName branch name @@ -157,12 +169,19 @@ func (c *GitCommand) GetBranchName() (string, error) { return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") } -func (c *GitCommand) navigateToRepoRootDirectory() { - _, err := os.Stat(".git") - for os.IsNotExist(err) { +func (c *GitCommand) navigateToRepoRootDirectory() error { + for { + f, err := os.Stat(".git") + + if err == nil && f.IsDir() { + return nil + } + c.Log.Debug("going up a directory to find the root") - os.Chdir("..") - _, err = os.Stat(".git") + + if err = os.Chdir(".."); err != nil { + return err + } } } From c1984528c8b8ee2d6d9ffdc50f31c1f81c7b97a3 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Wed, 29 Aug 2018 22:55:57 +0200 Subject: [PATCH 02/19] pkg/git : add tests for SetupGit --- pkg/commands/git.go | 117 ++++++++++++++++++++++----------------- pkg/commands/git_test.go | 70 +++++++++++++++++++++++ 2 files changed, 135 insertions(+), 52 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 68571769e..101a45515 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -19,21 +19,39 @@ import ( // to check if we have a valid git repository and we get an error instead var ErrGitRepositoryInvalid = fmt.Errorf("can't find a valid git repository in current directory") +func openGitRepositoryAndWorktree() (*gogit.Repository, *gogit.Worktree, error) { + r, err := gogit.PlainOpen(".") + + if err != nil { + return nil, nil, err + } + + w, err := r.Worktree() + + if err != nil { + return nil, nil, err + } + + return r, w, nil +} + // GitCommand is our main git interface type GitCommand struct { - Log *logrus.Entry - OSCommand *OSCommand - Worktree *gogit.Worktree - Repo *gogit.Repository - Tr *i18n.Localizer + Log *logrus.Entry + OSCommand *OSCommand + Worktree *gogit.Worktree + Repo *gogit.Repository + Tr *i18n.Localizer + openGitRepositoryAndWorktree func() (*gogit.Repository, *gogit.Worktree, error) } // NewGitCommand it runs git commands func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) (*GitCommand, error) { gitCommand := &GitCommand{ - Log: log, - OSCommand: osCommand, - Tr: tr, + Log: log, + OSCommand: osCommand, + Tr: tr, + openGitRepositoryAndWorktree: openGitRepositoryAndWorktree, } return gitCommand, nil } @@ -43,7 +61,7 @@ func (c *GitCommand) SetupGit() error { fs := []func() error{ c.verifyInGitRepo, c.navigateToRepoRootDirectory, - c.setupWorktree, + c.setupRepositoryAndWorktree, } for _, f := range fs { @@ -55,6 +73,44 @@ func (c *GitCommand) SetupGit() error { return nil } +func (c *GitCommand) verifyInGitRepo() error { + if _, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil { + return ErrGitRepositoryInvalid + } + + return nil +} + +func (c *GitCommand) navigateToRepoRootDirectory() error { + for { + f, err := os.Stat(".git") + + if err == nil && f.IsDir() { + return nil + } + + c.Log.Debug("going up a directory to find the root") + + if err = os.Chdir(".."); err != nil { + return err + } + } +} + +func (c *GitCommand) setupRepositoryAndWorktree() (err error) { + c.Repo, c.Worktree, err = c.openGitRepositoryAndWorktree() + + if err == nil { + return + } + + if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) { + return errors.New(c.Tr.SLocalize("GitconfigParseErr")) + } + + return +} + // GetStashEntries stash entryies func (c *GitCommand) GetStashEntries() []StashEntry { rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'") @@ -156,54 +212,11 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { return append(headResults, tailResults...) } -func (c *GitCommand) verifyInGitRepo() error { - if _, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil { - return ErrGitRepositoryInvalid - } - - return nil -} - // GetBranchName branch name func (c *GitCommand) GetBranchName() (string, error) { return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") } -func (c *GitCommand) navigateToRepoRootDirectory() error { - for { - f, err := os.Stat(".git") - - if err == nil && f.IsDir() { - return nil - } - - c.Log.Debug("going up a directory to find the root") - - if err = os.Chdir(".."); err != nil { - return err - } - } -} - -func (c *GitCommand) setupWorktree() error { - r, err := gogit.PlainOpen(".") - if err != nil { - if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) { - errorMessage := c.Tr.SLocalize("GitconfigParseErr") - return errors.New(errorMessage) - } - return err - } - c.Repo = r - - w, err := r.Worktree() - if err != nil { - return err - } - c.Worktree = w - return nil -} - // ResetHard does the equivalent of `git reset --hard HEAD` func (c *GitCommand) ResetHard() error { return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset}) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index fb3bafe6c..062e8ee3d 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1,13 +1,16 @@ package commands import ( + "fmt" "io/ioutil" "os/exec" "testing" + "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/test" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" + gogit "gopkg.in/src-d/go-git.v4" ) func newDummyLog() *logrus.Entry { @@ -20,6 +23,73 @@ func newDummyGitCommand() *GitCommand { return &GitCommand{ Log: newDummyLog(), OSCommand: newDummyOSCommand(), + Tr: i18n.NewLocalizer(newDummyLog()), + } +} + +func TestGitCommandSetupGit(t *testing.T) { + type scenario struct { + command func(string, ...string) *exec.Cmd + openGitRepositoryAndWorktree func() (*gogit.Repository, *gogit.Worktree, error) + test func(error) + } + + scenarios := []scenario{ + { + func(string, ...string) *exec.Cmd { + return exec.Command("exit", "1") + }, + func() (*gogit.Repository, *gogit.Worktree, error) { + return nil, nil, nil + }, + func(err error) { + assert.Error(t, err) + assert.Equal(t, ErrGitRepositoryInvalid, err) + }, + }, + { + func(string, ...string) *exec.Cmd { + return exec.Command("echo") + }, + func() (*gogit.Repository, *gogit.Worktree, error) { + return nil, nil, fmt.Errorf(`unquoted '\' must be followed by new line`) + }, + func(err error) { + assert.Error(t, err) + assert.Contains(t, err.Error(), "gitconfig") + }, + }, + { + func(string, ...string) *exec.Cmd { + return exec.Command("echo") + }, + func() (*gogit.Repository, *gogit.Worktree, error) { + return nil, nil, fmt.Errorf("Error from inside gogit") + }, + func(err error) { + assert.Error(t, err) + assert.EqualError(t, err, "Error from inside gogit") + }, + }, + { + func(string, ...string) *exec.Cmd { + return exec.Command("echo") + }, + func() (*gogit.Repository, *gogit.Worktree, error) { + return &gogit.Repository{}, &gogit.Worktree{}, nil + }, + func(err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + gitCmd.openGitRepositoryAndWorktree = s.openGitRepositoryAndWorktree + + s.test(gitCmd.SetupGit()) } } From 9f7775df263a83c5e0e845955a7abcd989f62d2a Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Thu, 30 Aug 2018 00:54:54 +0200 Subject: [PATCH 03/19] pkg/git : remove unused Map function --- pkg/commands/git.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 101a45515..48d51e900 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -466,15 +466,6 @@ func (c *GitCommand) GetBranchGraph(branchName string) (string, error) { return c.OSCommand.RunCommandWithOutput("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branchName) } -// Map (from https://gobyexample.com/collection-functions) -func Map(vs []string, f func(string) string) []string { - vsm := make([]string, len(vs)) - for i, v := range vs { - vsm[i] = f(v) - } - return vsm -} - func includesString(list []string, a string) bool { for _, b := range list { if b == a { From 43ad9a81c282022203e45ae3088b93763320bccc Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 2 Sep 2018 17:15:27 +0200 Subject: [PATCH 04/19] merge setup in function that create a new git command --- main.go | 6 -- pkg/commands/git.go | 127 ++++++++++++++-------------- pkg/commands/git_test.go | 177 ++++++++++++++++++++++++++++++++------- 3 files changed, 211 insertions(+), 99 deletions(-) diff --git a/main.go b/main.go index 6193f43d3..25e55d364 100644 --- a/main.go +++ b/main.go @@ -49,11 +49,5 @@ func main() { panic(err) } - if err := app.GitCommand.SetupGit(); err != nil { - app.Log.Error(err.Error()) - fmt.Println(err) - os.Exit(1) - } - app.Gui.RunWithSubprocesses() } diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 48d51e900..6ba5f8a84 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -19,98 +19,95 @@ import ( // to check if we have a valid git repository and we get an error instead var ErrGitRepositoryInvalid = fmt.Errorf("can't find a valid git repository in current directory") -func openGitRepositoryAndWorktree() (*gogit.Repository, *gogit.Worktree, error) { - r, err := gogit.PlainOpen(".") - - if err != nil { - return nil, nil, err - } - - w, err := r.Worktree() - - if err != nil { - return nil, nil, err - } - - return r, w, nil -} - -// GitCommand is our main git interface -type GitCommand struct { - Log *logrus.Entry - OSCommand *OSCommand - Worktree *gogit.Worktree - Repo *gogit.Repository - Tr *i18n.Localizer - openGitRepositoryAndWorktree func() (*gogit.Repository, *gogit.Worktree, error) -} - -// NewGitCommand it runs git commands -func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) (*GitCommand, error) { - gitCommand := &GitCommand{ - Log: log, - OSCommand: osCommand, - Tr: tr, - openGitRepositoryAndWorktree: openGitRepositoryAndWorktree, - } - return gitCommand, nil -} - -// SetupGit sets git repo up -func (c *GitCommand) SetupGit() error { - fs := []func() error{ - c.verifyInGitRepo, - c.navigateToRepoRootDirectory, - c.setupRepositoryAndWorktree, - } - - for _, f := range fs { - if err := f(); err != nil { - return err - } - } - - return nil -} - -func (c *GitCommand) verifyInGitRepo() error { - if _, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil { +func verifyInGitRepo(runCmdWithOutput func(string) (string, error)) error { + if _, err := runCmdWithOutput("git status"); err != nil { return ErrGitRepositoryInvalid } return nil } -func (c *GitCommand) navigateToRepoRootDirectory() error { +func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error { for { - f, err := os.Stat(".git") + f, err := stat(".git") if err == nil && f.IsDir() { return nil } - c.Log.Debug("going up a directory to find the root") + if !os.IsNotExist(err) { + return err + } - if err = os.Chdir(".."); err != nil { + if err = chdir(".."); err != nil { return err } } } -func (c *GitCommand) setupRepositoryAndWorktree() (err error) { - c.Repo, c.Worktree, err = c.openGitRepositoryAndWorktree() +func setupRepositoryAndWorktree(openGitRepository func(string) (*gogit.Repository, error), sLocalize func(string) string) (repository *gogit.Repository, worktree *gogit.Worktree, err error) { + repository, err = openGitRepository(".") + + if err != nil { + if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) { + return nil, nil, errors.New(sLocalize("GitconfigParseErr")) + } - if err == nil { return } - if strings.Contains(err.Error(), `unquoted '\' must be followed by new line`) { - return errors.New(c.Tr.SLocalize("GitconfigParseErr")) + worktree, err = repository.Worktree() + + if err != nil { + return } return } +// GitCommand is our main git interface +type GitCommand struct { + Log *logrus.Entry + OSCommand *OSCommand + Worktree *gogit.Worktree + Repo *gogit.Repository + Tr *i18n.Localizer +} + +// NewGitCommand it runs git commands +func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) (*GitCommand, error) { + var worktree *gogit.Worktree + var repo *gogit.Repository + + fs := []func() error{ + func() error { + return verifyInGitRepo(osCommand.RunCommandWithOutput) + }, + 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 := f(); err != nil { + return nil, err + } + } + + return &GitCommand{ + Log: log, + OSCommand: osCommand, + Tr: tr, + Worktree: worktree, + Repo: repo, + }, nil +} + // GetStashEntries stash entryies func (c *GitCommand) GetStashEntries() []StashEntry { rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'") diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 062e8ee3d..ebf4fd43d 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -3,8 +3,10 @@ package commands import ( "fmt" "io/ioutil" + "os" "os/exec" "testing" + "time" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/test" @@ -13,6 +15,39 @@ import ( gogit "gopkg.in/src-d/go-git.v4" ) +type fileInfoMock struct { + name string + size int64 + fileMode os.FileMode + fileModTime time.Time + isDir bool + sys interface{} +} + +func (f fileInfoMock) Name() string { + return f.name +} + +func (f fileInfoMock) Size() int64 { + return f.size +} + +func (f fileInfoMock) Mode() os.FileMode { + return f.fileMode +} + +func (f fileInfoMock) ModTime() time.Time { + return f.fileModTime +} + +func (f fileInfoMock) IsDir() bool { + return f.isDir +} + +func (f fileInfoMock) Sys() interface{} { + return f.sys +} + func newDummyLog() *logrus.Entry { log := logrus.New() log.Out = ioutil.Discard @@ -27,69 +62,155 @@ func newDummyGitCommand() *GitCommand { } } -func TestGitCommandSetupGit(t *testing.T) { +func TestVerifyInGitRepo(t *testing.T) { type scenario struct { - command func(string, ...string) *exec.Cmd - openGitRepositoryAndWorktree func() (*gogit.Repository, *gogit.Worktree, error) - test func(error) + runCmdWithOutput func(string) (string, error) + test func(error) } scenarios := []scenario{ { - func(string, ...string) *exec.Cmd { - return exec.Command("exit", "1") + func(string) (string, error) { + return "", nil }, - func() (*gogit.Repository, *gogit.Worktree, error) { - return nil, nil, nil + func(err error) { + assert.NoError(t, err) + }, + }, + { + func(string) (string, error) { + return "", ErrGitRepositoryInvalid }, func(err error) { assert.Error(t, err) assert.Equal(t, ErrGitRepositoryInvalid, err) }, }, + } + + for _, s := range scenarios { + s.test(verifyInGitRepo(s.runCmdWithOutput)) + } +} + +func TestNavigateToRepoRootDirectory(t *testing.T) { + type scenario struct { + stat func(string) (os.FileInfo, error) + chdir func(string) error + test func(error) + } + + scenarios := []scenario{ { - func(string, ...string) *exec.Cmd { - return exec.Command("echo") + func(string) (os.FileInfo, error) { + return fileInfoMock{isDir: true}, nil }, - func() (*gogit.Repository, *gogit.Worktree, error) { - return nil, nil, fmt.Errorf(`unquoted '\' must be followed by new line`) + func(string) error { + return nil }, func(err error) { - assert.Error(t, err) - assert.Contains(t, err.Error(), "gitconfig") + assert.NoError(t, err) }, }, { - func(string, ...string) *exec.Cmd { - return exec.Command("echo") + func(string) (os.FileInfo, error) { + return nil, fmt.Errorf("An error occurred") }, - func() (*gogit.Repository, *gogit.Worktree, error) { - return nil, nil, fmt.Errorf("Error from inside gogit") + func(string) error { + return nil }, func(err error) { + assert.Error(t, err) + assert.EqualError(t, err, "An error occurred") + }, + }, + { + func(string) (os.FileInfo, error) { + return nil, os.ErrNotExist + }, + func(string) error { + return fmt.Errorf("An error occurred") + }, + func(err error) { + assert.Error(t, err) + assert.EqualError(t, err, "An error occurred") + }, + }, + { + func(string) (os.FileInfo, error) { + return nil, os.ErrNotExist + }, + func(string) error { + return fmt.Errorf("An error occurred") + }, + func(err error) { + assert.Error(t, err) + assert.EqualError(t, err, "An error occurred") + }, + }, + } + + for _, s := range scenarios { + s.test(navigateToRepoRootDirectory(s.stat, s.chdir)) + } +} + +func TestSetupRepositoryAndWorktree(t *testing.T) { + type scenario struct { + openGitRepository func(string) (*gogit.Repository, error) + sLocalize func(string) string + test func(*gogit.Repository, *gogit.Worktree, error) + } + + scenarios := []scenario{ + { + func(string) (*gogit.Repository, error) { + return nil, fmt.Errorf(`unquoted '\' must be followed by new line`) + }, + func(string) string { + return "error translated" + }, + func(r *gogit.Repository, w *gogit.Worktree, err error) { + assert.Error(t, err) + assert.EqualError(t, err, "error translated") + }, + }, + { + func(string) (*gogit.Repository, error) { + return nil, fmt.Errorf("Error from inside gogit") + }, + func(string) string { return "" }, + func(r *gogit.Repository, w *gogit.Worktree, err error) { assert.Error(t, err) assert.EqualError(t, err, "Error from inside gogit") }, }, { - func(string, ...string) *exec.Cmd { - return exec.Command("echo") + func(string) (*gogit.Repository, error) { + return &gogit.Repository{}, nil }, - func() (*gogit.Repository, *gogit.Worktree, error) { - return &gogit.Repository{}, &gogit.Worktree{}, nil + func(string) string { return "" }, + func(r *gogit.Repository, w *gogit.Worktree, err error) { + assert.Error(t, err) + assert.Equal(t, gogit.ErrIsBareRepository, err) }, - func(err error) { + }, + { + func(string) (*gogit.Repository, error) { + assert.NoError(t, os.RemoveAll("/tmp/lazygit-test")) + r, err := gogit.PlainInit("/tmp/lazygit-test", false) + assert.NoError(t, err) + return r, nil + }, + func(string) string { return "" }, + func(r *gogit.Repository, w *gogit.Worktree, err error) { assert.NoError(t, err) }, }, } for _, s := range scenarios { - gitCmd := newDummyGitCommand() - gitCmd.OSCommand.command = s.command - gitCmd.openGitRepositoryAndWorktree = s.openGitRepositoryAndWorktree - - s.test(gitCmd.SetupGit()) + s.test(setupRepositoryAndWorktree(s.openGitRepository, s.sLocalize)) } } From 06846ef3ae9aed88ff6249cc2fd5bdbe0a55bdc3 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 2 Sep 2018 17:18:33 +0200 Subject: [PATCH 05/19] rename NewApp to Setup --- main.go | 2 +- pkg/app/app.go | 4 ++-- pkg/commands/git_test.go | 42 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 25e55d364..890b69a7f 100644 --- a/main.go +++ b/main.go @@ -43,7 +43,7 @@ func main() { panic(err) } - app, err := app.NewApp(appConfig) + app, err := app.Setup(appConfig) if err != nil { app.Log.Error(err.Error()) panic(err) diff --git a/pkg/app/app.go b/pkg/app/app.go index fa2415fc3..b03ec5b42 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -65,8 +65,8 @@ func newLogger(config config.AppConfigurer) *logrus.Entry { }) } -// NewApp retruns a new applications -func NewApp(config config.AppConfigurer) (*App, error) { +// Setup bootstrap a new application +func Setup(config config.AppConfigurer) (*App, error) { app := &App{ closers: []io.Closer{}, Config: config, diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index ebf4fd43d..64d3828ea 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -214,6 +214,48 @@ func TestSetupRepositoryAndWorktree(t *testing.T) { } } +func TestNewGitCommand(t *testing.T) { + actual, err := os.Getwd() + assert.NoError(t, err) + + defer func() { + assert.NoError(t, os.Chdir(actual)) + }() + + type scenario struct { + setup func() + test func(*GitCommand, error) + } + + scenarios := []scenario{ + { + func() { + assert.NoError(t, os.Chdir("/tmp")) + }, + func(gitCmd *GitCommand, err error) { + assert.Error(t, err) + assert.Equal(t, ErrGitRepositoryInvalid, err) + }, + }, + { + func() { + assert.NoError(t, os.RemoveAll("/tmp/lazygit-test")) + _, err := gogit.PlainInit("/tmp/lazygit-test", false) + assert.NoError(t, err) + assert.NoError(t, os.Chdir("/tmp/lazygit-test")) + }, + func(gitCmd *GitCommand, err error) { + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + s.setup() + s.test(NewGitCommand(newDummyLog(), newDummyOSCommand(), i18n.NewLocalizer(newDummyLog()))) + } +} + func TestGitCommandGetStashEntries(t *testing.T) { type scenario struct { command func(string, ...string) *exec.Cmd From 8c675780639ffb1f214c7fcfaa34c09862ccd092 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 2 Sep 2018 22:46:37 +0200 Subject: [PATCH 06/19] replace fmt with errors --- pkg/commands/git.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 6ba5f8a84..df78a2e3a 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -17,7 +17,7 @@ import ( // ErrGitRepositoryInvalid is emitted when we run a git command in a folder // to check if we have a valid git repository and we get an error instead -var ErrGitRepositoryInvalid = fmt.Errorf("can't find a valid git repository in current directory") +var ErrGitRepositoryInvalid = errors.New("can't find a valid git repository in current directory") func verifyInGitRepo(runCmdWithOutput func(string) (string, error)) error { if _, err := runCmdWithOutput("git status"); err != nil { From df3e7abd688d17982d477f67e05670cacdff4bb4 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Tue, 4 Sep 2018 06:16:19 +0200 Subject: [PATCH 07/19] use RunCommand --- pkg/commands/git.go | 14 +++----------- pkg/commands/git_test.go | 18 +++++++++--------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index df78a2e3a..61c566780 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -15,16 +15,8 @@ import ( gogit "gopkg.in/src-d/go-git.v4" ) -// ErrGitRepositoryInvalid is emitted when we run a git command in a folder -// to check if we have a valid git repository and we get an error instead -var ErrGitRepositoryInvalid = errors.New("can't find a valid git repository in current directory") - -func verifyInGitRepo(runCmdWithOutput func(string) (string, error)) error { - if _, err := runCmdWithOutput("git status"); err != nil { - return ErrGitRepositoryInvalid - } - - return nil +func verifyInGitRepo(runCmd func(string) error) error { + return runCmd("git status") } func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error { @@ -81,7 +73,7 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) fs := []func() error{ func() error { - return verifyInGitRepo(osCommand.RunCommandWithOutput) + return verifyInGitRepo(osCommand.RunCommand) }, func() error { return navigateToRepoRootDirectory(os.Stat, os.Chdir) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 64d3828ea..777eebaec 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -64,32 +64,32 @@ func newDummyGitCommand() *GitCommand { func TestVerifyInGitRepo(t *testing.T) { type scenario struct { - runCmdWithOutput func(string) (string, error) - test func(error) + runCmd func(string) error + test func(error) } scenarios := []scenario{ { - func(string) (string, error) { - return "", nil + func(string) error { + return nil }, func(err error) { assert.NoError(t, err) }, }, { - func(string) (string, error) { - return "", ErrGitRepositoryInvalid + func(string) error { + return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git") }, func(err error) { assert.Error(t, err) - assert.Equal(t, ErrGitRepositoryInvalid, err) + assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error()) }, }, } for _, s := range scenarios { - s.test(verifyInGitRepo(s.runCmdWithOutput)) + s.test(verifyInGitRepo(s.runCmd)) } } @@ -234,7 +234,7 @@ func TestNewGitCommand(t *testing.T) { }, func(gitCmd *GitCommand, err error) { assert.Error(t, err) - assert.Equal(t, ErrGitRepositoryInvalid, err) + assert.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error()) }, }, { From 172cd7c6871d47fee9fd132c3c3df0afd71bc445 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Tue, 4 Sep 2018 06:29:48 +0200 Subject: [PATCH 08/19] fix tests locally --- pkg/commands/os_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/commands/os_test.go b/pkg/commands/os_test.go index 5d1644a38..01173fb15 100644 --- a/pkg/commands/os_test.go +++ b/pkg/commands/os_test.go @@ -46,7 +46,7 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) { { "rmdir unexisting-folder", func(output string, err error) { - assert.Regexp(t, "rmdir: .* 'unexisting-folder': .*", err.Error()) + assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error()) }, }, } @@ -66,7 +66,7 @@ func TestOSCommandRunCommand(t *testing.T) { { "rmdir unexisting-folder", func(err error) { - assert.Regexp(t, "rmdir: .* 'unexisting-folder': .*", err.Error()) + assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error()) }, }, } From 3f68fe42cbad7018c680836ed1dd4192e86eba6a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 4 Sep 2018 19:18:18 +1000 Subject: [PATCH 09/19] update xdg-open command --- docs/Config.md | 2 +- pkg/config/config_linux.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Config.md b/docs/Config.md index d428603bc..eb1c65f9a 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -33,7 +33,7 @@ ``` os: - openCommand: 'bash -c \"xdg-open {{filename}} &>/dev/null &\"' + openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"' ``` ### OSX: diff --git a/pkg/config/config_linux.go b/pkg/config/config_linux.go index ef30ac7d2..90e922416 100644 --- a/pkg/config/config_linux.go +++ b/pkg/config/config_linux.go @@ -4,5 +4,5 @@ package config func GetPlatformDefaultConfig() []byte { return []byte( `os: - openCommand: 'bash -c \"xdg-open {{filename}} &>/dev/null &\"'`) + openCommand: 'sh -c "xdg-open {{filename}} >/dev/null"'`) } From 6c1d133315bf47897c128a11d6ae0416ac1ea2c0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 4 Sep 2018 19:03:35 +1000 Subject: [PATCH 10/19] dockerfile for testing on alpine linux --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..092e82a46 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# run with: +# docker build -t lazygit . +# docker run -it lazygit:latest + +FROM golang:alpine + +RUN apk add -U git xdg-utils + +ADD . /go/src/github.com/jesseduffield/lazygit + +RUN go install github.com/jesseduffield/lazygit + +WORKDIR /go/src/github.com/jesseduffield/lazygit \ No newline at end of file From 4d745fa52537761e1695d71a4952780c0268af5f Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Tue, 4 Sep 2018 22:23:17 +0200 Subject: [PATCH 11/19] update cache path in circleci --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 16bafdb58..2723c13cc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: - checkout - restore_cache: keys: - - pkg-cache-{{ checksum "Gopkg.lock" }} + - pkg-cache-{{ checksum "Gopkg.lock" }}-v1 - run: name: Run gofmt -s command: | @@ -31,9 +31,9 @@ jobs: command: | bash <(curl -s https://codecov.io/bash) - save_cache: - key: pkg-cache-{{ checksum "Gopkg.lock" }} + key: pkg-cache-{{ checksum "Gopkg.lock" }}-v1 paths: - - "/go/pkg" + - ~/.cache/go-build release: docker: From 4fc290b101504421d360720c341f0672fb7796cf Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Sep 2018 18:44:24 +1000 Subject: [PATCH 12/19] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 28fe9c399..4f40e8f8e 100644 --- a/README.md +++ b/README.md @@ -131,3 +131,7 @@ feel free to [raise an issue](https://github.com/jesseduffield/lazygit/issues)/[ If you want to see what I (Jesse) am up to in terms of development, follow me on [twitter](https://twitter.com/DuffieldJesse) or watch me program on [twitch](https://www.twitch.tv/jesseduffield). + +## Alternatives +If you find that lazygit doesn't quite satisfy your requirements, these may be a better fit: +- [tig](https://github.com/jonas/tig) From 422b263df425aceb68f446d41ce359370f188be1 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Sep 2018 19:07:46 +1000 Subject: [PATCH 13/19] fix popup panel resizing --- Gopkg.lock | 5 ++--- pkg/gui/commit_message_panel.go | 9 -------- pkg/gui/confirmation_panel.go | 15 ------------- pkg/gui/gui.go | 12 +--------- pkg/gui/view_helpers.go | 22 +++++++++++++++++++ vendor/github.com/jesseduffield/gocui/edit.go | 2 +- 6 files changed, 26 insertions(+), 39 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index e0df88b2d..73c8c3731 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,11 +189,11 @@ [[projects]] branch = "master" - digest = "1:acbcdae312c37a8019e0f573a9be26499058d5e1244243655373d2fd97714658" + digest = "1:145fe566d21b0e2579e1600f09e4e4b01da017676ba8e079de75a2e21111538b" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "5d9e836837237cb3aadca51ecb37c7cf3345bfa4" + revision = "c4051ef0fbcbe519bc1d082a579a38100c7cf044" [[projects]] digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc" @@ -611,7 +611,6 @@ analyzer-version = 1 input-imports = [ "github.com/cloudfoundry/jibber_jabber", - "github.com/davecgh/go-spew/spew", "github.com/fatih/color", "github.com/golang-collections/collections/stack", "github.com/heroku/rollrus", diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 26db703f0..36718680f 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -34,15 +34,6 @@ func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { - // resising ahead of time so that the top line doesn't get hidden to make - // room for the cursor on the second line - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer()) - if _, err := g.SetView("commitMessage", x0, y0, x1, y1+1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - } - v.EditNewLine() return nil } diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 1702b1df6..0bb633f32 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -11,7 +11,6 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" - "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) wrappedConfirmationFunction(function func(*gocui.Gui, *gocui.View) error) func(*gocui.Gui, *gocui.View) error { @@ -161,17 +160,3 @@ func (gui *Gui) createErrorPanel(g *gocui.Gui, message string) error { coloredMessage := colorFunction(strings.TrimSpace(message)) return gui.createConfirmationPanel(g, currentView, gui.Tr.SLocalize("Error"), coloredMessage, nil, nil) } - -func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { - // If the confirmation panel is already displayed, just resize the width, - // otherwise continue - content := utils.TrimTrailingNewline(v.Buffer()) - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content) - vx0, vy0, vx1, vy1 := v.Dimensions() - if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { - return nil - } - gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel")) - _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) - return err -} diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 2a68bce71..bb2ed752b 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -307,9 +307,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { } } - gui.resizePopupPanels(g) - - return nil + return gui.resizeCurrentPopupPanel(g) } func (gui *Gui) promptAnonymousReporting() error { @@ -355,14 +353,6 @@ func (gui *Gui) goEvery(g *gocui.Gui, interval time.Duration, function func(*goc }() } -func (gui *Gui) resizePopupPanels(g *gocui.Gui) error { - v := g.CurrentView() - if v.Name() == "commitMessage" || v.Name() == "confirmation" { - return gui.resizePopupPanel(g, v) - } - return nil -} - // Run setup the gui with keybindings and start the mainloop func (gui *Gui) Run() error { g, err := gocui.NewGui(gocui.OutputNormal, OverlappingEdges) diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index fd810220b..3a59945a5 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -268,3 +268,25 @@ func (gui *Gui) currentViewName(g *gocui.Gui) string { currentView := g.CurrentView() return currentView.Name() } + +func (gui *Gui) resizeCurrentPopupPanel(g *gocui.Gui) error { + v := g.CurrentView() + if v.Name() == "commitMessage" || v.Name() == "confirmation" { + return gui.resizePopupPanel(g, v) + } + return nil +} + +func (gui *Gui) resizePopupPanel(g *gocui.Gui, v *gocui.View) error { + // If the confirmation panel is already displayed, just resize the width, + // otherwise continue + content := v.Buffer() + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, content) + vx0, vy0, vx1, vy1 := v.Dimensions() + if vx0 == x0 && vy0 == y0 && vx1 == x1 && vy1 == y1 { + return nil + } + gui.Log.Info(gui.Tr.SLocalize("resizingPopupPanel")) + _, err := g.SetView(v.Name(), x0, y0, x1, y1, 0) + return err +} diff --git a/vendor/github.com/jesseduffield/gocui/edit.go b/vendor/github.com/jesseduffield/gocui/edit.go index a5e6f690b..c339d75fb 100644 --- a/vendor/github.com/jesseduffield/gocui/edit.go +++ b/vendor/github.com/jesseduffield/gocui/edit.go @@ -114,8 +114,8 @@ func (v *View) EditDelete(back bool) { func (v *View) EditNewLine() { v.breakLine(v.cx, v.cy) v.ox = 0 + v.cy = v.cy + 1 v.cx = 0 - v.MoveCursor(0, 1, true) } // MoveCursor moves the cursor taking into account the width of the line/view, From 98763e98cb4c0af033b5276423e451f1f8cd11ee Mon Sep 17 00:00:00 2001 From: Dawid Dziurla Date: Sun, 2 Sep 2018 17:08:59 +0200 Subject: [PATCH 14/19] initial commit message counter --- pkg/gui/commit_message_panel.go | 6 +--- pkg/gui/files_panel.go | 57 +++++++++++++++++++++++++++++++++ pkg/gui/gui.go | 10 ++++++ pkg/gui/keybindings.go | 1 - 4 files changed, 68 insertions(+), 6 deletions(-) diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 36718680f..88a011c76 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -30,14 +30,10 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { g.SetViewOnBottom("commitMessage") + g.SetViewOnBottom("commitMessageCount") return gui.switchFocus(g, v, gui.getFilesView(g)) } -func (gui *Gui) handleNewlineCommitMessage(g *gocui.Gui, v *gocui.View) error { - v.EditNewLine() - return nil -} - func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { message := gui.Tr.TemplateLocalize( "CloseConfirm", diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index a4c187c9c..43f7e5831 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -12,6 +12,7 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "strconv" ) func (gui *Gui) stagedFiles() []commands.File { @@ -218,6 +219,60 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { return gui.renderString(g, "main", content) } +func (gui *Gui) simpleEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + switch { + case key == gocui.KeyBackspace || key == gocui.KeyBackspace2: + v.EditDelete(true) + case key == gocui.KeyDelete: + v.EditDelete(false) + case key == gocui.KeyArrowDown: + v.MoveCursor(0, 1, false) + case key == gocui.KeyArrowUp: + v.MoveCursor(0, -1, false) + case key == gocui.KeyArrowLeft: + v.MoveCursor(-1, 0, false) + case key == gocui.KeyArrowRight: + v.MoveCursor(1, 0, false) + case key == gocui.KeyTab: + v.EditNewLine() + case key == gocui.KeySpace: + v.EditWrite(' ') + case key == gocui.KeyInsert: + v.Overwrite = !v.Overwrite + default: + v.EditWrite(ch) + } + + gui.renderCommitCount(v) +} + +func (gui *Gui) getCommitCount(view *gocui.View) int { + return strings.Count(view.Buffer(), "") - 1 +} + +func (gui *Gui) renderCommitCount(view *gocui.View) error { + num := 0 + offset := 5 + count := gui.getCommitCount(view) + _, y0, x1, _ := gui.getConfirmationPanelDimensions(gui.g, view.Buffer()) + + if count > 99 { + num = 3 + } else if count > 9 { + num = 2 + } else { + num = 1 + } + + if _, err := gui.g.SetView("commitMessageCount", x1-num-offset, y0-1, x1-offset+1, y0+1, 0); err != nil { + if err != gocui.ErrUnknownView { + return err + } + } + + return gui.renderString(gui.g, "commitMessageCount", strconv.Itoa(count)) +} + func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) @@ -225,7 +280,9 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { commitMessageView := gui.getCommitMessageView(g) g.Update(func(g *gocui.Gui) error { g.SetViewOnTop("commitMessage") + g.SetViewOnTop("commitMessageCount") gui.switchFocus(g, filesView, commitMessageView) + gui.renderCommitCount(commitMessageView) return nil }) return nil diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index bb2ed752b..6c35f9dca 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -262,6 +262,16 @@ func (gui *Gui) layout(g *gocui.Gui) error { commitMessageView.Title = gui.Tr.SLocalize("CommitMessage") commitMessageView.FgColor = gocui.ColorWhite commitMessageView.Editable = true + commitMessageView.Editor = gocui.EditorFunc(gui.simpleEditor) + } + if commitMessageCountView, err := g.SetView("commitMessageCount", 0, 0, width/2, height/2, 0); err != nil { + if err != gocui.ErrUnknownView { + return err + } + g.SetViewOnBottom("commitMessageCount") + commitMessageCountView.Frame = false + commitMessageCountView.BgColor = gocui.ColorDefault + commitMessageCountView.FgColor = gocui.ColorWhite } } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index c6decc24f..494381749 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -70,7 +70,6 @@ func (gui *Gui) keybindings(g *gocui.Gui) error { {ViewName: "stash", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleStashDrop}, {ViewName: "commitMessage", Key: gocui.KeyEnter, Modifier: gocui.ModNone, Handler: gui.handleCommitConfirm}, {ViewName: "commitMessage", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.handleCommitClose}, - {ViewName: "commitMessage", Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.handleNewlineCommitMessage}, } // Would make these keybindings global but that interferes with editing From 986774e5c7ddf5c1de1e1adb831baac84a2dae66 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Sep 2018 20:42:11 +1000 Subject: [PATCH 15/19] add commit count via gocui subtitle --- Gopkg.lock | 4 +- pkg/gui/commit_message_panel.go | 35 ++++++++++- pkg/gui/confirmation_panel.go | 17 ------ pkg/gui/files_panel.go | 58 +------------------ pkg/gui/gui.go | 9 --- vendor/github.com/jesseduffield/gocui/gui.go | 24 ++++++++ vendor/github.com/jesseduffield/gocui/view.go | 3 + 7 files changed, 64 insertions(+), 86 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 73c8c3731..2bbaa2118 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,11 +189,11 @@ [[projects]] branch = "master" - digest = "1:145fe566d21b0e2579e1600f09e4e4b01da017676ba8e079de75a2e21111538b" + digest = "1:71e6c15797951d3fabaa944d70253e36a6cee96bf54ca0bc43ca3de3b4814bbb" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "c4051ef0fbcbe519bc1d082a579a38100c7cf044" + revision = "2cb6e95bbbf850bb32cc1799e07d08ff0f144746" [[projects]] digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc" diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 88a011c76..7ac566b3f 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -1,6 +1,9 @@ package gui import ( + "strconv" + "strings" + "github.com/jesseduffield/gocui" ) @@ -30,7 +33,6 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { g.SetViewOnBottom("commitMessage") - g.SetViewOnBottom("commitMessageCount") return gui.switchFocus(g, v, gui.getFilesView(g)) } @@ -44,3 +46,34 @@ func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { ) return gui.renderString(g, "options", message) } + +func (gui *Gui) simpleEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { + switch { + case key == gocui.KeyBackspace || key == gocui.KeyBackspace2: + v.EditDelete(true) + case key == gocui.KeyDelete: + v.EditDelete(false) + case key == gocui.KeyArrowDown: + v.MoveCursor(0, 1, false) + case key == gocui.KeyArrowUp: + v.MoveCursor(0, -1, false) + case key == gocui.KeyArrowLeft: + v.MoveCursor(-1, 0, false) + case key == gocui.KeyArrowRight: + v.MoveCursor(1, 0, false) + case key == gocui.KeyTab: + v.EditNewLine() + case key == gocui.KeySpace: + v.EditWrite(' ') + case key == gocui.KeyInsert: + v.Overwrite = !v.Overwrite + default: + v.EditWrite(ch) + } + + v.Subtitle = gui.getBufferLength(v) +} + +func (gui *Gui) getBufferLength(view *gocui.View) string { + return " " + strconv.Itoa(strings.Count(view.Buffer(), "")-1) + " " +} diff --git a/pkg/gui/confirmation_panel.go b/pkg/gui/confirmation_panel.go index 0bb633f32..58577e430 100644 --- a/pkg/gui/confirmation_panel.go +++ b/pkg/gui/confirmation_panel.go @@ -115,20 +115,6 @@ func (gui *Gui) createConfirmationPanel(g *gocui.Gui, currentView *gocui.View, t return nil } -func (gui *Gui) handleNewline(g *gocui.Gui, v *gocui.View) error { - // resising ahead of time so that the top line doesn't get hidden to make - // room for the cursor on the second line - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, v.Buffer()) - if _, err := g.SetView("confirmation", x0, y0, x1, y1+1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - } - - v.EditNewLine() - return nil -} - func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*gocui.Gui, *gocui.View) error) error { actions := gui.Tr.TemplateLocalize( "CloseConfirm", @@ -143,9 +129,6 @@ func (gui *Gui) setKeyBindings(g *gocui.Gui, handleConfirm, handleClose func(*go if err := g.SetKeybinding("confirmation", gocui.KeyEnter, gocui.ModNone, gui.wrappedConfirmationFunction(handleConfirm)); err != nil { return err } - if err := g.SetKeybinding("confirmation", gocui.KeyTab, gocui.ModNone, gui.handleNewline); err != nil { - return err - } return g.SetKeybinding("confirmation", gocui.KeyEsc, gocui.ModNone, gui.wrappedConfirmationFunction(handleClose)) } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 43f7e5831..f85ff9d33 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -12,7 +12,6 @@ import ( "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" - "strconv" ) func (gui *Gui) stagedFiles() []commands.File { @@ -219,60 +218,6 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { return gui.renderString(g, "main", content) } -func (gui *Gui) simpleEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Modifier) { - switch { - case key == gocui.KeyBackspace || key == gocui.KeyBackspace2: - v.EditDelete(true) - case key == gocui.KeyDelete: - v.EditDelete(false) - case key == gocui.KeyArrowDown: - v.MoveCursor(0, 1, false) - case key == gocui.KeyArrowUp: - v.MoveCursor(0, -1, false) - case key == gocui.KeyArrowLeft: - v.MoveCursor(-1, 0, false) - case key == gocui.KeyArrowRight: - v.MoveCursor(1, 0, false) - case key == gocui.KeyTab: - v.EditNewLine() - case key == gocui.KeySpace: - v.EditWrite(' ') - case key == gocui.KeyInsert: - v.Overwrite = !v.Overwrite - default: - v.EditWrite(ch) - } - - gui.renderCommitCount(v) -} - -func (gui *Gui) getCommitCount(view *gocui.View) int { - return strings.Count(view.Buffer(), "") - 1 -} - -func (gui *Gui) renderCommitCount(view *gocui.View) error { - num := 0 - offset := 5 - count := gui.getCommitCount(view) - _, y0, x1, _ := gui.getConfirmationPanelDimensions(gui.g, view.Buffer()) - - if count > 99 { - num = 3 - } else if count > 9 { - num = 2 - } else { - num = 1 - } - - if _, err := gui.g.SetView("commitMessageCount", x1-num-offset, y0-1, x1-offset+1, y0+1, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - } - - return gui.renderString(gui.g, "commitMessageCount", strconv.Itoa(count)) -} - func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) @@ -280,9 +225,8 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { commitMessageView := gui.getCommitMessageView(g) g.Update(func(g *gocui.Gui) error { g.SetViewOnTop("commitMessage") - g.SetViewOnTop("commitMessageCount") gui.switchFocus(g, filesView, commitMessageView) - gui.renderCommitCount(commitMessageView) + commitMessageView.Subtitle = gui.getBufferLength(commitMessageView) return nil }) return nil diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 6c35f9dca..bb395f9d2 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -264,15 +264,6 @@ func (gui *Gui) layout(g *gocui.Gui) error { commitMessageView.Editable = true commitMessageView.Editor = gocui.EditorFunc(gui.simpleEditor) } - if commitMessageCountView, err := g.SetView("commitMessageCount", 0, 0, width/2, height/2, 0); err != nil { - if err != gocui.ErrUnknownView { - return err - } - g.SetViewOnBottom("commitMessageCount") - commitMessageCountView.Frame = false - commitMessageCountView.BgColor = gocui.ColorDefault - commitMessageCountView.FgColor = gocui.ColorWhite - } } if appStatusView, err := g.SetView("appStatus", -1, optionsTop, width, optionsTop+2, 0); err != nil { diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index 26ba79bd6..4393c06c4 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -476,6 +476,11 @@ func (g *Gui) flush() error { return err } } + if v.Subtitle != "" { + if err := g.drawSubtitle(v, fgColor, bgColor); err != nil { + return err + } + } } if err := g.draw(v); err != nil { return err @@ -582,6 +587,25 @@ func (g *Gui) drawTitle(v *View, fgColor, bgColor Attribute) error { return nil } +// drawSubtitle draws the subtitle of the view. +func (g *Gui) drawSubtitle(v *View, fgColor, bgColor Attribute) error { + if v.y0 < 0 || v.y0 >= g.maxY { + return nil + } + + start := v.x1 - 5 - len(v.Subtitle) + for i, ch := range v.Subtitle { + x := start + i + if x >= v.x1 { + break + } + if err := g.SetRune(x, v.y0, ch, fgColor, bgColor); err != nil { + return err + } + } + return nil +} + // draw manages the cursor and calls the draw function of a view. func (g *Gui) draw(v *View) error { if g.Cursor { diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 0dd897aa4..53ab06f8c 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -77,6 +77,9 @@ type View struct { // If Frame is true, Title allows to configure a title for the view. Title string + // If Frame is true, Subtitle allows to configure a subtitle for the view. + Subtitle string + // If Mask is true, the View will display the mask instead of the real // content Mask rune From a2d40cfbf1df9c6cd7107e9356cca6dbac031f40 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 5 Sep 2018 23:02:13 +1000 Subject: [PATCH 16/19] allow users to configure whether the commit length is shown --- docs/Config.md | 2 ++ pkg/config/app_config.go | 2 ++ pkg/gui/commit_message_panel.go | 10 +++++++++- pkg/gui/files_panel.go | 2 +- 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/Config.md b/docs/Config.md index eb1c65f9a..76cf4bcc0 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -14,6 +14,8 @@ - white optionsTextColor: - blue + commitLength: + show: true update: method: prompt # can be: prompt | background | never days: 14 # how often an update is checked for diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 3c272e520..ae713a212 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -222,6 +222,8 @@ func GetDefaultConfig() []byte { - white optionsTextColor: - blue + commitLength: + show: true update: method: prompt # can be: prompt | background | never days: 14 # how often a update is checked for diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 7ac566b3f..99d649102 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -71,9 +71,17 @@ func (gui *Gui) simpleEditor(v *gocui.View, key gocui.Key, ch rune, mod gocui.Mo v.EditWrite(ch) } - v.Subtitle = gui.getBufferLength(v) + gui.RenderCommitLength() } func (gui *Gui) getBufferLength(view *gocui.View) string { return " " + strconv.Itoa(strings.Count(view.Buffer(), "")-1) + " " } + +func (gui *Gui) RenderCommitLength() { + if !gui.Config.GetUserConfig().GetBool("gui.commitLength.show") { + return + } + v := gui.getCommitMessageView(gui.g) + v.Subtitle = gui.getBufferLength(v) +} diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index f85ff9d33..1971b3453 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -226,7 +226,7 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { g.Update(func(g *gocui.Gui) error { g.SetViewOnTop("commitMessage") gui.switchFocus(g, filesView, commitMessageView) - commitMessageView.Subtitle = gui.getBufferLength(commitMessageView) + gui.RenderCommitLength() return nil }) return nil From e9268d1828cb81021d14a55d4fdac4d0f082cd2b Mon Sep 17 00:00:00 2001 From: Dawid Dziurla Date: Wed, 5 Sep 2018 19:56:11 +0200 Subject: [PATCH 17/19] add confirmOnQuit config option --- docs/Config.md | 1 + pkg/config/app_config.go | 1 + pkg/gui/gui.go | 5 +++++ pkg/i18n/dutch.go | 3 +++ pkg/i18n/english.go | 3 +++ pkg/i18n/polish.go | 3 +++ 6 files changed, 16 insertions(+) diff --git a/docs/Config.md b/docs/Config.md index 76cf4bcc0..7f342df7f 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -20,6 +20,7 @@ method: prompt # can be: prompt | background | never days: 14 # how often an update is checked for reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined' + confirmOnQuit: false ``` ## Platform Defaults: diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index ae713a212..7cde63de1 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -228,6 +228,7 @@ update: method: prompt # can be: prompt | background | never days: 14 # how often a update is checked for reporting: 'undetermined' # one of: 'on' | 'off' | 'undetermined' +confirmOnQuit: false `) } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index bb395f9d2..413e48202 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -411,5 +411,10 @@ func (gui *Gui) quit(g *gocui.Gui, v *gocui.View) error { if gui.State.Updating { return gui.createUpdateQuitConfirmation(g, v) } + if gui.Config.GetUserConfig().GetBool("confirmOnQuit") { + return gui.createConfirmationPanel(g, v, "", gui.Tr.SLocalize("ConfirmQuit"), func(g *gocui.Gui, v *gocui.View) error { + return gocui.ErrQuit + }, nil) + } return gocui.ErrQuit } diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index ff588b264..0ce156cd5 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -295,6 +295,9 @@ func addDutch(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "MergeAborted", Other: "Merge afgebroken", + }, &i18n.Message{ + ID: "ConfirmQuit", + Other: `Are you sure you want to quit?`, }, ) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 1d9e8b41e..f9707ced6 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -342,6 +342,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "GitconfigParseErr", Other: `Gogit failed to parse your gitconfig file due to the presence of unquoted '\' characters. Removing these should fix the issue.`, + }, &i18n.Message{ + ID: "ConfirmQuit", + Other: `Are you sure you want to quit?`, }, ) } diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 5364d3d8f..1de545274 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -287,6 +287,9 @@ func addPolish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "MergeAborted", Other: "Scalanie anulowane", + }, &i18n.Message{ + ID: "ConfirmQuit", + Other: `Na pewno chcesz wyjść z programu?`, }, ) } From a8201894505078b5f5b56fe47881acfbd32974e0 Mon Sep 17 00:00:00 2001 From: Glenn Vriesman Date: Thu, 6 Sep 2018 17:31:19 +0200 Subject: [PATCH 18/19] Added entry to the contributing guide --- CONTRIBUTING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 540eebb79..e59a42044 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -15,9 +15,10 @@ welcome your pull requests: 1. Fork the repo and create your branch from `master`. 2. If you've added code that should be tested, add tests. 3. If you've added code that need documentation, update the documentation. -4. Be sure to test your modifications. -5. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). -6. Issue that pull request! +4. Make sure your code follows the [effective go](https://golang.org/doc/effective_go.html) guidelines as much as possible. +5. Be sure to test your modifications. +6. Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). +7. Issue that pull request! ## Code of conduct Please note by participating in this project, you agree to abide by the [code of conduct]. From 0d7a697e863310cdc3edc50cad5d02e54a349604 Mon Sep 17 00:00:00 2001 From: antham Date: Wed, 5 Sep 2018 21:48:37 +0200 Subject: [PATCH 19/19] add go.mod --- .circleci/config.yml | 22 ++++++++++++---- go.mod | 62 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 5 deletions(-) create mode 100644 go.mod diff --git a/.circleci/config.yml b/.circleci/config.yml index 2723c13cc..46dc0d161 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,14 +2,23 @@ version: 2 jobs: build: docker: - - image: circleci/golang:1.10 + - image: circleci/golang:1.11 working_directory: /go/src/github.com/jesseduffield/lazygit steps: - checkout - - restore_cache: - keys: - - pkg-cache-{{ checksum "Gopkg.lock" }}-v1 + - run: + name: Ensure go.mod file is up to date + command: | + export GO111MODULE=on + mv go.mod /tmp/ + go mod init + export GO111MODULE=auto + + if [ $(diff /tmp/go.mod go.mod|wc -l) -gt 0 ]; then + diff /tmp/go.mod go.mod + exit 1; + fi - run: name: Run gofmt -s command: | @@ -17,6 +26,9 @@ jobs: find . ! -path "./vendor/*" -name "*.go" -exec gofmt -s -d {} \; exit 1; fi + - restore_cache: + keys: + - pkg-cache-{{ checksum "Gopkg.lock" }}-v2 - run: name: Run tests command: | @@ -31,7 +43,7 @@ jobs: command: | bash <(curl -s https://codecov.io/bash) - save_cache: - key: pkg-cache-{{ checksum "Gopkg.lock" }}-v1 + key: pkg-cache-{{ checksum "Gopkg.lock" }}-v2 paths: - ~/.cache/go-build diff --git a/go.mod b/go.mod new file mode 100644 index 000000000..02db8a03e --- /dev/null +++ b/go.mod @@ -0,0 +1,62 @@ +module github.com/jesseduffield/lazygit + +require ( + github.com/aws/aws-sdk-go v1.15.21 + github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d + github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 + github.com/davecgh/go-spew v1.1.0 + github.com/emirpasic/gods v1.9.0 + github.com/fatih/color v1.7.0 + github.com/fsnotify/fsnotify v1.4.7 + github.com/go-ini/ini v1.38.2 + github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 + github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 + github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001 + github.com/hashicorp/go-safetemp v0.0.0-20180326211150-b1a1dbde6fdc + github.com/hashicorp/go-version v1.0.0 + github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce + github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331 + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 + github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 + github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8 + github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 + github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 + github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55 + github.com/magiconair/properties v1.8.0 + github.com/mattn/go-colorable v0.0.9 + github.com/mattn/go-isatty v0.0.3 + github.com/mattn/go-runewidth v0.0.2 + github.com/mgutz/str v1.2.0 + github.com/mitchellh/go-homedir v0.0.0-20180801233206-58046073cbff + github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 + github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 + github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80 + github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb + github.com/pelletier/go-buffruneio v0.2.0 + github.com/pelletier/go-toml v1.2.0 + github.com/pkg/errors v0.8.0 + github.com/pmezard/go-difflib v1.0.0 + github.com/sergi/go-diff v1.0.0 + github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 + github.com/sirupsen/logrus v1.0.6 + github.com/spf13/afero v1.1.1 + github.com/spf13/cast v1.2.0 + github.com/spf13/jwalterweatherman v0.0.0-20180814060501-14d3d4c51834 + github.com/spf13/pflag v1.0.2 + github.com/spf13/viper v1.1.0 + github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad + github.com/src-d/gcfg v1.3.0 + github.com/stretchr/testify v1.2.2 + github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea + github.com/tcnksm/go-gitconfig v0.1.2 + github.com/ulikunitz/xz v0.5.4 + github.com/xanzy/ssh-agent v0.2.0 + golang.org/x/crypto v0.0.0-20180808211826-de0752318171 + golang.org/x/net v0.0.0-20180811021610-c39426892332 + golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0 + golang.org/x/text v0.3.0 + gopkg.in/src-d/go-billy.v4 v4.2.0 + gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714 + gopkg.in/warnings.v0 v0.1.2 + gopkg.in/yaml.v2 v2.2.1 +)