mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
Merge branch 'master' into hotfix/238-xdg-open-2
This commit is contained in:
4
main.go
4
main.go
@ -43,11 +43,11 @@ func main() {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := app.NewApp(appConfig)
|
app, err := app.Setup(appConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
app.Log.Error(err.Error())
|
app.Log.Error(err.Error())
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
app.GitCommand.SetupGit()
|
|
||||||
app.Gui.RunWithSubprocesses()
|
app.Gui.RunWithSubprocesses()
|
||||||
}
|
}
|
||||||
|
@ -65,8 +65,8 @@ func newLogger(config config.AppConfigurer) *logrus.Entry {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApp retruns a new applications
|
// Setup bootstrap a new application
|
||||||
func NewApp(config config.AppConfigurer) (*App, error) {
|
func Setup(config config.AppConfigurer) (*App, error) {
|
||||||
app := &App{
|
app := &App{
|
||||||
closers: []io.Closer{},
|
closers: []io.Closer{},
|
||||||
Config: config,
|
Config: config,
|
||||||
|
@ -15,6 +15,48 @@ import (
|
|||||||
gogit "gopkg.in/src-d/go-git.v4"
|
gogit "gopkg.in/src-d/go-git.v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func verifyInGitRepo(runCmd func(string) error) error {
|
||||||
|
return runCmd("git status")
|
||||||
|
}
|
||||||
|
|
||||||
|
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
|
||||||
|
for {
|
||||||
|
f, err := stat(".git")
|
||||||
|
|
||||||
|
if err == nil && f.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !os.IsNotExist(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = chdir(".."); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
worktree, err = repository.Worktree()
|
||||||
|
|
||||||
|
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
|
||||||
@ -26,22 +68,36 @@ type GitCommand struct {
|
|||||||
|
|
||||||
// NewGitCommand it runs git commands
|
// NewGitCommand it runs git commands
|
||||||
func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) (*GitCommand, error) {
|
func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) (*GitCommand, error) {
|
||||||
gitCommand := &GitCommand{
|
var worktree *gogit.Worktree
|
||||||
|
var repo *gogit.Repository
|
||||||
|
|
||||||
|
fs := []func() error{
|
||||||
|
func() error {
|
||||||
|
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 := f(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &GitCommand{
|
||||||
Log: log,
|
Log: log,
|
||||||
OSCommand: osCommand,
|
OSCommand: osCommand,
|
||||||
Tr: tr,
|
Tr: tr,
|
||||||
}
|
Worktree: worktree,
|
||||||
return gitCommand, nil
|
Repo: repo,
|
||||||
}
|
}, nil
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStashEntries stash entryies
|
// GetStashEntries stash entryies
|
||||||
@ -145,46 +201,11 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File {
|
|||||||
return append(headResults, tailResults...)
|
return append(headResults, tailResults...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) verifyInGitRepo() {
|
|
||||||
if output, err := c.OSCommand.RunCommandWithOutput("git status"); err != nil {
|
|
||||||
fmt.Println(output)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetBranchName branch name
|
// GetBranchName branch name
|
||||||
func (c *GitCommand) GetBranchName() (string, error) {
|
func (c *GitCommand) GetBranchName() (string, error) {
|
||||||
return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *GitCommand) navigateToRepoRootDirectory() {
|
|
||||||
_, err := os.Stat(".git")
|
|
||||||
for os.IsNotExist(err) {
|
|
||||||
c.Log.Debug("going up a directory to find the root")
|
|
||||||
os.Chdir("..")
|
|
||||||
_, err = os.Stat(".git")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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`
|
// ResetHard does the equivalent of `git reset --hard HEAD`
|
||||||
func (c *GitCommand) ResetHard() error {
|
func (c *GitCommand) ResetHard() error {
|
||||||
return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset})
|
return c.Worktree.Reset(&gogit.ResetOptions{Mode: gogit.HardReset})
|
||||||
@ -434,15 +455,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)
|
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 {
|
func includesString(list []string, a string) bool {
|
||||||
for _, b := range list {
|
for _, b := range list {
|
||||||
if b == a {
|
if b == a {
|
||||||
|
@ -1,15 +1,53 @@
|
|||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||||
"github.com/jesseduffield/lazygit/pkg/test"
|
"github.com/jesseduffield/lazygit/pkg/test"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
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 {
|
func newDummyLog() *logrus.Entry {
|
||||||
log := logrus.New()
|
log := logrus.New()
|
||||||
log.Out = ioutil.Discard
|
log.Out = ioutil.Discard
|
||||||
@ -20,6 +58,201 @@ func newDummyGitCommand() *GitCommand {
|
|||||||
return &GitCommand{
|
return &GitCommand{
|
||||||
Log: newDummyLog(),
|
Log: newDummyLog(),
|
||||||
OSCommand: newDummyOSCommand(),
|
OSCommand: newDummyOSCommand(),
|
||||||
|
Tr: i18n.NewLocalizer(newDummyLog()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVerifyInGitRepo(t *testing.T) {
|
||||||
|
type scenario struct {
|
||||||
|
runCmd func(string) error
|
||||||
|
test func(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
scenarios := []scenario{
|
||||||
|
{
|
||||||
|
func(string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func(err error) {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range scenarios {
|
||||||
|
s.test(verifyInGitRepo(s.runCmd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) (os.FileInfo, error) {
|
||||||
|
return fileInfoMock{isDir: true}, nil
|
||||||
|
},
|
||||||
|
func(string) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
func(err error) {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
func(string) (os.FileInfo, error) {
|
||||||
|
return nil, fmt.Errorf("An error occurred")
|
||||||
|
},
|
||||||
|
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) (*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)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 {
|
||||||
|
s.test(setupRepositoryAndWorktree(s.openGitRepository, s.sLocalize))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Regexp(t, "fatal: .ot a git repository \\(or any of the parent directories\\): \\.git", err.Error())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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())))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"rmdir unexisting-folder",
|
"rmdir unexisting-folder",
|
||||||
func(output string, err error) {
|
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",
|
"rmdir unexisting-folder",
|
||||||
func(err error) {
|
func(err error) {
|
||||||
assert.Regexp(t, "rmdir: .* 'unexisting-folder': .*", err.Error())
|
assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user