From ca715c5b23fdc20ad9b3dd983814ab9225c5fdbc Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 7 Sep 2018 09:41:15 +1000 Subject: [PATCH 01/36] support switching to recent repo --- pkg/app/app.go | 4 ++-- pkg/gui/gui.go | 43 ++++++++++++++++++++++++++++++++++++++++++ pkg/gui/keybindings.go | 1 + 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index b03ec5b42..65acd2e35 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -77,11 +77,11 @@ func Setup(config config.AppConfigurer) (*App, error) { app.Tr = i18n.NewLocalizer(app.Log) - app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr) + app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr) if err != nil { return app, err } - app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr) + app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr) if err != nil { return app, err } diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 413e48202..e986ed294 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -33,6 +33,7 @@ var OverlappingEdges = false type SentinelErrors struct { ErrSubProcess error ErrNoFiles error + ErrSwitchRepo error } // GenerateSentinelErrors makes the sentinel errors for the gui. We're defining it here @@ -49,6 +50,7 @@ func (gui *Gui) GenerateSentinelErrors() { gui.Errors = SentinelErrors{ ErrSubProcess: errors.New(gui.Tr.SLocalize("RunningSubprocess")), ErrNoFiles: errors.New(gui.Tr.SLocalize("NoChangedFiles")), + ErrSwitchRepo: errors.New("switching repo"), } } @@ -292,6 +294,10 @@ func (gui *Gui) layout(g *gocui.Gui) error { // these are only called once (it's a place to put all the things you want // to happen on startup after the screen is first rendered) gui.Updater.CheckForNewUpdate(gui.onBackgroundUpdateCheckFinish, false) + if err := gui.updateRecentRepoList(); err != nil { + return err + } + gui.handleFileSelect(g, filesView) gui.refreshFiles(g) gui.refreshBranches(g) @@ -311,6 +317,41 @@ func (gui *Gui) layout(g *gocui.Gui) error { return gui.resizeCurrentPopupPanel(g) } +func newRecentReposList(recentRepos []string, currentRepo string) []string { + newRepos := []string{currentRepo} + for _, repo := range recentRepos { + if repo != currentRepo { + newRepos = append(newRepos, repo) + } + } + return newRepos +} + +// updateRecentRepoList registers the fact that we opened lazygit in this repo, +// so that we can open the same repo via a 'recent repos' menu +func (gui *Gui) updateRecentRepoList() error { + recentRepos := gui.Config.GetAppState().RecentRepos + currentRepo, err := os.Getwd() + if err != nil { + return err + } + gui.Config.GetAppState().RecentRepos = newRecentReposList(recentRepos, currentRepo) + return gui.Config.SaveAppState() +} + +func (gui *Gui) handleSwitchRepo(g *gocui.Gui, v *gocui.View) error { + newRepo := gui.Config.GetAppState().RecentRepos[1] + if err := os.Chdir(newRepo); err != nil { + return err + } + newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr) + if err != nil { + return err + } + gui.GitCommand = newGitCommand + return gui.Errors.ErrSwitchRepo +} + func (gui *Gui) promptAnonymousReporting() error { return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error { return gui.Config.WriteToUserConfig("reporting", "on") @@ -391,6 +432,8 @@ func (gui *Gui) RunWithSubprocesses() { if err := gui.Run(); err != nil { if err == gocui.ErrQuit { break + } else if err == gui.Errors.ErrSwitchRepo { + continue } else if err == gui.Errors.ErrSubProcess { gui.SubProcess.Stdin = os.Stdin gui.SubProcess.Stdout = os.Stdout diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 494381749..287619b8b 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -27,6 +27,7 @@ func (gui *Gui) keybindings(g *gocui.Gui) error { {ViewName: "status", Key: 'e', Modifier: gocui.ModNone, Handler: gui.handleEditConfig}, {ViewName: "status", Key: 'o', Modifier: gocui.ModNone, Handler: gui.handleOpenConfig}, {ViewName: "status", Key: 'u', Modifier: gocui.ModNone, Handler: gui.handleCheckForUpdate}, + {ViewName: "status", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleSwitchRepo}, {ViewName: "files", Key: 'c', Modifier: gocui.ModNone, Handler: gui.handleCommitPress}, {ViewName: "files", Key: 'C', Modifier: gocui.ModNone, Handler: gui.handleCommitEditorPress}, {ViewName: "files", Key: gocui.KeySpace, Modifier: gocui.ModNone, Handler: gui.handleFilePress}, From 52b132fe01156d8c7654180d03e40726e18692cd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 10 Sep 2018 20:17:39 +1000 Subject: [PATCH 02/36] better handling of cursor and origin positionings --- pkg/gui/files_panel.go | 25 ++++++++++++++++--------- pkg/gui/menu_panel.go | 10 ++++++++-- pkg/gui/view_helpers.go | 39 ++++++++++++++++++++++++++++----------- pkg/utils/utils.go | 8 ++++++++ 4 files changed, 60 insertions(+), 22 deletions(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index bfcc938bb..8fd2fb30c 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -275,22 +275,23 @@ func (gui *Gui) updateHasMergeConflictStatus() error { return nil } -func (gui *Gui) renderFile(file commands.File, filesView *gocui.View) { +func (gui *Gui) renderFile(file commands.File) string { // potentially inefficient to be instantiating these color // objects with each render red := color.New(color.FgRed) green := color.New(color.FgGreen) if !file.Tracked && !file.HasStagedChanges { - red.Fprintln(filesView, file.DisplayString) - return + return red.Sprint(file.DisplayString) } - green.Fprint(filesView, file.DisplayString[0:1]) - red.Fprint(filesView, file.DisplayString[1:3]) + + output := green.Sprint(file.DisplayString[0:1]) + output += red.Sprint(file.DisplayString[1:3]) if file.HasUnstagedChanges { - red.Fprintln(filesView, file.Name) + output += red.Sprint(file.Name) } else { - green.Fprintln(filesView, file.Name) + output += green.Sprint(file.Name) } + return output } func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { @@ -319,8 +320,14 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } gui.refreshStateFiles() filesView.Clear() - for _, file := range gui.State.Files { - gui.renderFile(file, filesView) + for i, file := range gui.State.Files { + str := gui.renderFile(file) + if i < len(gui.State.Files)-1 { + str += "\n" + } + if _, err := filesView.Write([]byte(str)); err != nil { + return err + } } gui.correctCursor(filesView) if filesView == g.CurrentView() { diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 68041390d..f52ceda07 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -8,6 +8,12 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) +// I need to store the handler function in state and it will take an interface and do something with it +// I need to have another function describing how to display one of the structs +// perhaps this calls for an interface where the struct is Binding and the interface has the methods Display and Execute +// but this means that for the one struct I can only have one possible display/execute function, but I want to use whatever I want. +// Would I ever need to use different handlers for different things? Maybe I should assume not given that I can cross that bridge when I come to it + func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error { lineNumber := gui.getItemPosition(v) if gui.State.Keys[lineNumber].Key == nil { @@ -106,11 +112,11 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { content := append(contentPanel, contentGlobal...) gui.State.Keys = append(bindingsPanel, bindingsGlobal...) // append newline at the end so the last line would be selectable - contentJoined := strings.Join(content, "\n") + "\n" + contentJoined := strings.Join(content, "\n") // y1-1 so there will not be an extra space at the end of panel x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined) - menuView, _ := g.SetView("menu", x0, y0, x1, y1-1, 0) + menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) menuView.FgColor = gocui.ColorWhite diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 7a5bd8874..5178bd4d9 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -160,7 +160,6 @@ func (gui *Gui) getItemPosition(v *gocui.View) int { func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error { // swallowing cursor movements in main - // TODO: pull this out if v == nil || v.Name() == "main" { return nil } @@ -179,19 +178,28 @@ func (gui *Gui) cursorUp(g *gocui.Gui, v *gocui.View) error { func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error { // swallowing cursor movements in main - // TODO: pull this out if v == nil || v.Name() == "main" { return nil } cx, cy := v.Cursor() ox, oy := v.Origin() - if cy+oy >= len(v.BufferLines())-2 { + ly := len(v.BufferLines()) - 1 + _, height := v.Size() + maxY := height - 1 + + // if we are at the end we just return + if cy+oy == ly { return nil } - if err := v.SetCursor(cx, cy+1); err != nil { - if err := v.SetOrigin(ox, oy+1); err != nil { - return err - } + + var err error + if cy < maxY { + err = v.SetCursor(cx, cy+1) + } else { + err = v.SetOrigin(ox, oy+1) + } + if err != nil { + return err } gui.newLineFocused(g, v) @@ -208,10 +216,19 @@ func (gui *Gui) resetOrigin(v *gocui.View) error { // if the cursor down past the last item, move it to the last line func (gui *Gui) correctCursor(v *gocui.View) error { cx, cy := v.Cursor() - _, oy := v.Origin() - lineCount := len(v.BufferLines()) - 2 - if cy >= lineCount-oy { - return v.SetCursor(cx, lineCount-oy) + ox, oy := v.Origin() + _, height := v.Size() + maxY := height - 1 + ly := len(v.BufferLines()) - 1 + if oy+cy <= ly { + return nil + } + newCy := utils.Min(ly, maxY) + if err := v.SetCursor(cx, newCy); err != nil { + return err + } + if err := v.SetOrigin(ox, ly-newCy); err != nil { + return err } return nil } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index e28ab1824..cb23eb75e 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -99,3 +99,11 @@ func ResolvePlaceholderString(str string, arguments map[string]string) string { } return str } + +// Min returns the minimum of two integers +func Min(x, y int) int { + if x < y { + return x + } + return y +} From f8b484f638e813537b9b968cf65d378b900fbcee Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 18:23:25 +1000 Subject: [PATCH 03/36] don't use newlines at the end of panel buffers --- pkg/gui/branches_panel.go | 4 +++- pkg/gui/commits_panel.go | 31 +++++++++++++++++++------------ pkg/gui/files_panel.go | 16 ++++++++-------- pkg/gui/stash_panel.go | 12 +++++++++--- 4 files changed, 39 insertions(+), 24 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 26c3c5618..d164003e5 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -152,9 +152,11 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { } gui.State.Branches = builder.Build() v.Clear() + displayStrings := []string{} for _, branch := range gui.State.Branches { - fmt.Fprintln(v, branch.GetDisplayString()) + displayStrings = append(displayStrings, branch.GetDisplayString()) } + fmt.Fprint(v, strings.Join(displayStrings, "\n")) gui.resetOrigin(v) return gui.refreshStatus(g) }) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 1c1662475..dd33369a5 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -2,12 +2,27 @@ package gui import ( "errors" + "fmt" + "strings" "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" ) +func (gui *Gui) renderCommit(commit commands.Commit) string { + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow) + white := color.New(color.FgWhite) + + shaColor := yellow + if commit.Pushed { + shaColor = red + } + + return shaColor.Sprint(commit.Sha) + " " + white.Sprint(commit.Name) +} + func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { gui.State.Commits = gui.GitCommand.GetCommits() @@ -16,19 +31,11 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { panic(err) } v.Clear() - red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) - white := color.New(color.FgWhite) - shaColor := white - for _, commit := range gui.State.Commits { - if commit.Pushed { - shaColor = red - } else { - shaColor = yellow - } - shaColor.Fprint(v, commit.Sha+" ") - white.Fprintln(v, commit.Name) + displayStrings := make([]string, len(gui.State.Commits)) + for i, commit := range gui.State.Commits { + displayStrings[i] = gui.renderCommit(commit) } + fmt.Fprint(v, strings.Join(displayStrings, "\n")) gui.refreshStatus(g) if g.CurrentView().Name() == "commits" { gui.handleCommitSelect(g, v) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 8fd2fb30c..6ad2aed5b 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -7,6 +7,7 @@ import ( // "strings" + "fmt" "strings" "github.com/fatih/color" @@ -319,16 +320,15 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { return err } gui.refreshStateFiles() - filesView.Clear() + + displayStrings := make([]string, len(gui.State.Files)) for i, file := range gui.State.Files { - str := gui.renderFile(file) - if i < len(gui.State.Files)-1 { - str += "\n" - } - if _, err := filesView.Write([]byte(str)); err != nil { - return err - } + displayStrings[i] = gui.renderFile(file) } + + filesView.Clear() + fmt.Fprint(filesView, strings.Join(displayStrings, "\n")) + gui.correctCursor(filesView) if filesView == g.CurrentView() { gui.handleFileSelect(g, filesView) diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 9ca07717b..3a9fac8d4 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -2,6 +2,7 @@ package gui import ( "fmt" + "strings" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" @@ -14,10 +15,15 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { panic(err) } gui.State.StashEntries = gui.GitCommand.GetStashEntries() - v.Clear() - for _, stashEntry := range gui.State.StashEntries { - fmt.Fprintln(v, stashEntry.DisplayString) + + displayStrings := make([]string, len(gui.State.StashEntries)) + for i, stashEntry := range gui.State.StashEntries { + displayStrings[i] = stashEntry.DisplayString } + + v.Clear() + fmt.Fprint(v, strings.Join(displayStrings, "\n")) + return gui.resetOrigin(v) }) return nil From 31c33dfdcb4f8e27a8b50493876b17825c25c0ec Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 18:47:37 +1000 Subject: [PATCH 04/36] remove redundant comments --- pkg/gui/menu_panel.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index f52ceda07..983543c94 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -8,12 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -// I need to store the handler function in state and it will take an interface and do something with it -// I need to have another function describing how to display one of the structs -// perhaps this calls for an interface where the struct is Binding and the interface has the methods Display and Execute -// but this means that for the one struct I can only have one possible display/execute function, but I want to use whatever I want. -// Would I ever need to use different handlers for different things? Maybe I should assume not given that I can cross that bridge when I come to it - func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error { lineNumber := gui.getItemPosition(v) if gui.State.Keys[lineNumber].Key == nil { @@ -111,10 +105,8 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { content := append(contentPanel, contentGlobal...) gui.State.Keys = append(bindingsPanel, bindingsGlobal...) - // append newline at the end so the last line would be selectable contentJoined := strings.Join(content, "\n") - // y1-1 so there will not be an extra space at the end of panel x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined) menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) From 35cae80de962ba17a489c3a432c9f0db5149158c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 18:49:09 +1000 Subject: [PATCH 05/36] more efficient building of branch displaystrings --- pkg/gui/branches_panel.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index d164003e5..659725ef2 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -152,9 +152,9 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { } gui.State.Branches = builder.Build() v.Clear() - displayStrings := []string{} - for _, branch := range gui.State.Branches { - displayStrings = append(displayStrings, branch.GetDisplayString()) + displayStrings := make([]string, len(gui.State.Branches)) + for i, branch := range gui.State.Branches { + displayStrings[i] = branch.GetDisplayString() } fmt.Fprint(v, strings.Join(displayStrings, "\n")) gui.resetOrigin(v) From 3b765e5417501a39bca5c2f0038488dbbeb6b200 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 12 Sep 2018 19:39:36 +1000 Subject: [PATCH 06/36] add test for min method --- pkg/utils/utils_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 0b2d35959..a088ed849 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -167,3 +167,33 @@ func TestResolvePlaceholderString(t *testing.T) { assert.EqualValues(t, string(s.expected), ResolvePlaceholderString(s.templateString, s.arguments)) } } + +func TestMin(t *testing.T) { + type scenario struct { + a int + b int + expected int + } + + scenarios := []scenario{ + { + 2, + 4, + 2, + }, + { + 2, + 1, + 1, + }, + { + 1, + 1, + 1, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, Min(s.a, s.b)) + } +} From 65a24d70c332b0ff8f7e10999ab73260a3336fe7 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Wed, 12 Sep 2018 20:43:03 +0200 Subject: [PATCH 07/36] commands/git : add tests on SquashPreviousTwoCommits --- pkg/commands/git.go | 5 ++-- pkg/commands/git_test.go | 63 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 5744fa6aa..c200a688b 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -327,12 +327,11 @@ func (c *GitCommand) Push(branchName string, force bool) error { // retaining the message of the higher commit func (c *GitCommand) SquashPreviousTwoCommits(message string) error { // TODO: test this - err := c.OSCommand.RunCommand("git reset --soft HEAD^") - if err != nil { + if err := c.OSCommand.RunCommand("git reset --soft HEAD^"); err != nil { return err } // TODO: if password is required, we need to return a subprocess - return c.OSCommand.RunCommand("git commit --amend -m " + c.OSCommand.Quote(message)) + return c.OSCommand.RunCommand(fmt.Sprintf("git commit --amend -m %s", c.OSCommand.Quote(message))) } // SquashFixupCommit squashes a 'FIXUP' commit into the commit beneath it, diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index bda3ea225..0bcbdb7dd 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -953,6 +953,69 @@ func TestGitCommandPush(t *testing.T) { } } +func TestGitCommandSquashPreviousTwoCommits(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(error) + } + + scenarios := []scenario{ + { + "Git reset triggers an error", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"reset", "--soft", "HEAD^"}, args) + + return exec.Command("exit", "1") + }, + func(err error) { + assert.NotNil(t, err) + }, + }, + { + "Git commit triggers an error", + func(cmd string, args ...string) *exec.Cmd { + if len(args) > 0 && args[0] == "reset" { + return exec.Command("echo") + } + + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args) + + return exec.Command("exit", "1") + }, + func(err error) { + assert.NotNil(t, err) + }, + }, + { + "Stash succeeded", + func(cmd string, args ...string) *exec.Cmd { + if len(args) > 0 && args[0] == "reset" { + return exec.Command("echo") + } + + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args) + + return exec.Command("echo") + }, + func(err error) { + assert.Nil(t, err) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.SquashPreviousTwoCommits("test")) + }) + } +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From c92510ceba4d7b0f10ebf30c43206c7498e5f264 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Wed, 12 Sep 2018 22:28:49 +0200 Subject: [PATCH 08/36] commands/git : add tests on SquashFixupCommit and refactor --- pkg/commands/git.go | 39 +++++++++++-------------- pkg/commands/git_test.go | 63 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index c200a688b..8756a0483 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -337,38 +337,33 @@ func (c *GitCommand) SquashPreviousTwoCommits(message string) error { // SquashFixupCommit squashes a 'FIXUP' commit into the commit beneath it, // retaining the commit message of the lower commit func (c *GitCommand) SquashFixupCommit(branchName string, shaValue string) error { - var err error commands := []string{ - "git checkout -q " + shaValue, - "git reset --soft " + shaValue + "^", - "git commit --amend -C " + shaValue + "^", - "git rebase --onto HEAD " + shaValue + " " + branchName, + fmt.Sprintf("git checkout -q %s", shaValue), + fmt.Sprintf("git reset --soft %s^", shaValue), + fmt.Sprintf("git commit --amend -C %s^", shaValue), + fmt.Sprintf("git rebase --onto HEAD %s %s", shaValue, branchName), } - ret := "" for _, command := range commands { c.Log.Info(command) - output, err := c.OSCommand.RunCommandWithOutput(command) - ret += output - if err != nil { + + if output, err := c.OSCommand.RunCommandWithOutput(command); err != nil { + ret := output + // We are already in an error state here so we're just going to append + // the output of these commands + output, _ := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git branch -d %s", shaValue)) + ret += output + output, _ = c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git checkout %s", branchName)) + ret += output + c.Log.Info(ret) - break + return errors.New(ret) } } - if err != nil { - // We are already in an error state here so we're just going to append - // the output of these commands - output, _ := c.OSCommand.RunCommandWithOutput("git branch -d " + shaValue) - ret += output - output, _ = c.OSCommand.RunCommandWithOutput("git checkout " + branchName) - ret += output - } - if err != nil { - return errors.New(ret) - } + return nil } -// CatFile obtain the contents of a file +// CatFile obtains the content of a file func (c *GitCommand) CatFile(fileName string) (string, error) { return c.OSCommand.RunCommandWithOutput("cat " + c.OSCommand.Quote(fileName)) } diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 0bcbdb7dd..bc0202df9 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1016,6 +1016,69 @@ func TestGitCommandSquashPreviousTwoCommits(t *testing.T) { } } +func TestGitCommandSquashFixupCommit(t *testing.T) { + type scenario struct { + testName string + command func() (func(string, ...string) *exec.Cmd, *[][]string) + test func(*[][]string, error) + } + + scenarios := []scenario{ + { + "An error occurred with one of the sub git command", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + if len(args) > 0 && args[0] == "checkout" { + return exec.Command("exit", "1") + } + + return exec.Command("echo") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.NotNil(t, err) + assert.Len(t, *cmdsCalled, 3) + assert.EqualValues(t, *cmdsCalled, [][]string{ + {"checkout", "-q", "6789abcd"}, + {"branch", "-d", "6789abcd"}, + {"checkout", "test"}, + }) + }, + }, + { + "Squash fixup succeeded", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + return exec.Command("echo") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.Nil(t, err) + assert.Len(t, *cmdsCalled, 4) + assert.EqualValues(t, *cmdsCalled, [][]string{ + {"checkout", "-q", "6789abcd"}, + {"reset", "--soft", "6789abcd^"}, + {"commit", "--amend", "-C", "6789abcd^"}, + {"rebase", "--onto", "HEAD", "6789abcd", "test"}, + }) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + var cmdsCalled *[][]string + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command, cmdsCalled = s.command() + s.test(cmdsCalled, gitCmd.SquashFixupCommit("test", "6789abcd")) + }) + } +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From 3cf84a5af10e51bfb4c890731e417728ff62973f Mon Sep 17 00:00:00 2001 From: mingrammer Date: Fri, 14 Sep 2018 00:23:11 +0900 Subject: [PATCH 09/36] main: display an error message instead of panic when setup fails --- main.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 890b69a7f..edf1e7207 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "log" "os" "path/filepath" "runtime" @@ -40,13 +41,13 @@ func main() { } appConfig, err := config.NewAppConfig("lazygit", version, commit, date, buildSource, debuggingFlag) if err != nil { - panic(err) + log.Fatal(err.Error()) } app, err := app.Setup(appConfig) if err != nil { app.Log.Error(err.Error()) - panic(err) + log.Fatal(err.Error()) } app.Gui.RunWithSubprocesses() From 91832f2c5e40b9ea00291ae82f66bd09585fa3bc Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Thu, 13 Sep 2018 21:27:35 +0200 Subject: [PATCH 10/36] commands/git : add tests, refactor a bit --- pkg/commands/git.go | 12 ++-- pkg/commands/git_test.go | 142 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 146 insertions(+), 8 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 8756a0483..0c7509b91 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -365,12 +365,12 @@ func (c *GitCommand) SquashFixupCommit(branchName string, shaValue string) error // CatFile obtains the content of a file func (c *GitCommand) CatFile(fileName string) (string, error) { - return c.OSCommand.RunCommandWithOutput("cat " + c.OSCommand.Quote(fileName)) + return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("cat %s", c.OSCommand.Quote(fileName))) } // StageFile stages a file func (c *GitCommand) StageFile(fileName string) error { - return c.OSCommand.RunCommand("git add " + c.OSCommand.Quote(fileName)) + return c.OSCommand.RunCommand(fmt.Sprintf("git add %s", c.OSCommand.Quote(fileName))) } // StageAll stages all files @@ -385,13 +385,11 @@ func (c *GitCommand) UnstageAll() error { // UnStageFile unstages a file func (c *GitCommand) UnStageFile(fileName string, tracked bool) error { - var command string + command := "git rm --cached %s" if tracked { - command = "git reset HEAD " - } else { - command = "git rm --cached " + command = "git reset HEAD %s" } - return c.OSCommand.RunCommand(command + c.OSCommand.Quote(fileName)) + return c.OSCommand.RunCommand(fmt.Sprintf(command, c.OSCommand.Quote(fileName))) } // GitStatus returns the plaintext short status of the repo diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index bc0202df9..466fa0c55 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -825,6 +825,146 @@ func TestGitCommandUsingGpg(t *testing.T) { } } +func TestGitCommandCatFile(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "cat", cmd) + assert.EqualValues(t, []string{"test.txt"}, args) + + return exec.Command("echo", "-n", "test") + } + + o, err := gitCmd.CatFile("test.txt") + assert.NoError(t, err) + assert.Equal(t, "test", o) +} + +func TestGitCommandStageFile(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"add", "test.txt"}, args) + + return exec.Command("echo") + } + + assert.NoError(t, gitCmd.StageFile("test.txt")) +} + +func TestGitCommandUnstageFile(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(error) + tracked bool + } + + scenarios := []scenario{ + { + "Remove an untracked file from staging", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"rm", "--cached", "test.txt"}, args) + + return exec.Command("echo") + }, + func(err error) { + assert.NoError(t, err) + }, + false, + }, + { + "Remove a tracked file from staging", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"reset", "HEAD", "test.txt"}, args) + + return exec.Command("echo") + }, + func(err error) { + assert.NoError(t, err) + }, + true, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.UnStageFile("test.txt", s.tracked)) + }) + } +} + +func TestGitCommandIsInMergeState(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(bool, error) + } + + scenarios := []scenario{ + { + "An error occurred when running status command", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + + return exec.Command("exit", "1") + }, + func(isInMergeState bool, err error) { + assert.Error(t, err) + assert.False(t, isInMergeState) + }, + }, + { + "Is not in merge state", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + return exec.Command("echo") + }, + func(isInMergeState bool, err error) { + assert.False(t, isInMergeState) + assert.NoError(t, err) + }, + }, + { + "Command output contains conclude merge", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + return exec.Command("echo", "'conclude merge'") + }, + func(isInMergeState bool, err error) { + assert.True(t, isInMergeState) + assert.NoError(t, err) + }, + }, + { + "Command output contains unmerged paths", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + return exec.Command("echo", "'unmerged paths'") + }, + func(isInMergeState bool, err error) { + assert.True(t, isInMergeState) + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.IsInMergeState()) + }) + } +} + func TestGitCommandCommit(t *testing.T) { type scenario struct { testName string @@ -917,7 +1057,7 @@ func TestGitCommandPush(t *testing.T) { }, }, { - "Push with force enable", + "Push with force enabled", func(cmd string, args ...string) *exec.Cmd { assert.EqualValues(t, "git", cmd) assert.EqualValues(t, []string{"push", "--force-with-lease", "-u", "origin", "test"}, args) From c1b7a216312b4ce75bdf984fda6f59d870183bd3 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Thu, 13 Sep 2018 21:44:26 +0200 Subject: [PATCH 11/36] commands/git : move tests --- pkg/commands/git_test.go | 280 +++++++++++++++++++-------------------- 1 file changed, 140 insertions(+), 140 deletions(-) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 466fa0c55..88ecaea66 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -825,146 +825,6 @@ func TestGitCommandUsingGpg(t *testing.T) { } } -func TestGitCommandCatFile(t *testing.T) { - gitCmd := newDummyGitCommand() - gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "cat", cmd) - assert.EqualValues(t, []string{"test.txt"}, args) - - return exec.Command("echo", "-n", "test") - } - - o, err := gitCmd.CatFile("test.txt") - assert.NoError(t, err) - assert.Equal(t, "test", o) -} - -func TestGitCommandStageFile(t *testing.T) { - gitCmd := newDummyGitCommand() - gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"add", "test.txt"}, args) - - return exec.Command("echo") - } - - assert.NoError(t, gitCmd.StageFile("test.txt")) -} - -func TestGitCommandUnstageFile(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(error) - tracked bool - } - - scenarios := []scenario{ - { - "Remove an untracked file from staging", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"rm", "--cached", "test.txt"}, args) - - return exec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - false, - }, - { - "Remove a tracked file from staging", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"reset", "HEAD", "test.txt"}, args) - - return exec.Command("echo") - }, - func(err error) { - assert.NoError(t, err) - }, - true, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := newDummyGitCommand() - gitCmd.OSCommand.command = s.command - s.test(gitCmd.UnStageFile("test.txt", s.tracked)) - }) - } -} - -func TestGitCommandIsInMergeState(t *testing.T) { - type scenario struct { - testName string - command func(string, ...string) *exec.Cmd - test func(bool, error) - } - - scenarios := []scenario{ - { - "An error occurred when running status command", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) - - return exec.Command("exit", "1") - }, - func(isInMergeState bool, err error) { - assert.Error(t, err) - assert.False(t, isInMergeState) - }, - }, - { - "Is not in merge state", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) - return exec.Command("echo") - }, - func(isInMergeState bool, err error) { - assert.False(t, isInMergeState) - assert.NoError(t, err) - }, - }, - { - "Command output contains conclude merge", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) - return exec.Command("echo", "'conclude merge'") - }, - func(isInMergeState bool, err error) { - assert.True(t, isInMergeState) - assert.NoError(t, err) - }, - }, - { - "Command output contains unmerged paths", - func(cmd string, args ...string) *exec.Cmd { - assert.EqualValues(t, "git", cmd) - assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) - return exec.Command("echo", "'unmerged paths'") - }, - func(isInMergeState bool, err error) { - assert.True(t, isInMergeState) - assert.NoError(t, err) - }, - }, - } - - for _, s := range scenarios { - t.Run(s.testName, func(t *testing.T) { - gitCmd := newDummyGitCommand() - gitCmd.OSCommand.command = s.command - s.test(gitCmd.IsInMergeState()) - }) - } -} - func TestGitCommandCommit(t *testing.T) { type scenario struct { testName string @@ -1219,6 +1079,146 @@ func TestGitCommandSquashFixupCommit(t *testing.T) { } } +func TestGitCommandCatFile(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "cat", cmd) + assert.EqualValues(t, []string{"test.txt"}, args) + + return exec.Command("echo", "-n", "test") + } + + o, err := gitCmd.CatFile("test.txt") + assert.NoError(t, err) + assert.Equal(t, "test", o) +} + +func TestGitCommandStageFile(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"add", "test.txt"}, args) + + return exec.Command("echo") + } + + assert.NoError(t, gitCmd.StageFile("test.txt")) +} + +func TestGitCommandUnstageFile(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(error) + tracked bool + } + + scenarios := []scenario{ + { + "Remove an untracked file from staging", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"rm", "--cached", "test.txt"}, args) + + return exec.Command("echo") + }, + func(err error) { + assert.NoError(t, err) + }, + false, + }, + { + "Remove a tracked file from staging", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"reset", "HEAD", "test.txt"}, args) + + return exec.Command("echo") + }, + func(err error) { + assert.NoError(t, err) + }, + true, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.UnStageFile("test.txt", s.tracked)) + }) + } +} + +func TestGitCommandIsInMergeState(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(bool, error) + } + + scenarios := []scenario{ + { + "An error occurred when running status command", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + + return exec.Command("exit", "1") + }, + func(isInMergeState bool, err error) { + assert.Error(t, err) + assert.False(t, isInMergeState) + }, + }, + { + "Is not in merge state", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + return exec.Command("echo") + }, + func(isInMergeState bool, err error) { + assert.False(t, isInMergeState) + assert.NoError(t, err) + }, + }, + { + "Command output contains conclude merge", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + return exec.Command("echo", "'conclude merge'") + }, + func(isInMergeState bool, err error) { + assert.True(t, isInMergeState) + assert.NoError(t, err) + }, + }, + { + "Command output contains unmerged paths", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) + return exec.Command("echo", "'unmerged paths'") + }, + func(isInMergeState bool, err error) { + assert.True(t, isInMergeState) + assert.NoError(t, err) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.IsInMergeState()) + }) + } +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From bbc88071e9a06ac8c3c376abed5d906c77931cbc Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 16 Sep 2018 20:46:25 +0200 Subject: [PATCH 12/36] gui : remove unreachable code --- pkg/gui/commits_panel.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 1c1662475..075d66c83 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -146,7 +146,6 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error { } return gui.handleCommitSelect(g, v) }) - return nil } func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error { From 67a42f49b484a22f8d593dc4e03d963e400f2cea Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 16 Sep 2018 22:03:56 +0200 Subject: [PATCH 13/36] commands/git : add test to RemoveFile, refactor --- pkg/commands/git.go | 7 +- pkg/commands/git_test.go | 220 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 215 insertions(+), 12 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 0c7509b91..7143309e4 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -65,6 +65,7 @@ type GitCommand struct { Tr *i18n.Localizer getGlobalGitConfig func(string) (string, error) getLocalGitConfig func(string) (string, error) + removeFile func(string) error } // NewGitCommand it runs git commands @@ -410,15 +411,15 @@ func (c *GitCommand) IsInMergeState() (bool, error) { func (c *GitCommand) RemoveFile(file File) error { // if the file isn't tracked, we assume you want to delete it if file.HasStagedChanges { - if err := c.OSCommand.RunCommand("git reset -- " + file.Name); err != nil { + if err := c.OSCommand.RunCommand(fmt.Sprintf("git reset -- %s", file.Name)); err != nil { return err } } if !file.Tracked { - return os.RemoveAll(file.Name) + return c.removeFile(file.Name) } // if the file is tracked, we assume you want to just check it out - return c.OSCommand.RunCommand("git checkout -- " + file.Name) + return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -- %s", file.Name)) } // Checkout checks out a branch, with --force if you set the force arg to true diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 88ecaea66..0ee0749a7 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -61,6 +61,7 @@ func newDummyGitCommand() *GitCommand { Tr: i18n.NewLocalizer(newDummyLog()), getGlobalGitConfig: func(string) (string, error) { return "", nil }, getLocalGitConfig: func(string) (string, error) { return "", nil }, + removeFile: func(string) error { return nil }, } } @@ -551,7 +552,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) { { "Can't retrieve pushable count", func(string, ...string) *exec.Cmd { - return exec.Command("exit", "1") + return exec.Command("test") }, func(pushableCount string, pullableCount string) { assert.EqualValues(t, "?", pushableCount) @@ -562,7 +563,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) { "Can't retrieve pullable count", func(cmd string, args ...string) *exec.Cmd { if args[1] == "head..@{u}" { - return exec.Command("exit", "1") + return exec.Command("test") } return exec.Command("echo") @@ -608,7 +609,7 @@ func TestGitCommandGetCommitsToPush(t *testing.T) { { "Can't retrieve pushable commits", func(string, ...string) *exec.Cmd { - return exec.Command("exit", "1") + return exec.Command("test") }, func(pushables []string) { assert.EqualValues(t, []string{}, pushables) @@ -872,7 +873,7 @@ func TestGitCommandCommit(t *testing.T) { assert.EqualValues(t, "git", cmd) assert.EqualValues(t, []string{"commit", "-m", "test"}, args) - return exec.Command("exit", "1") + return exec.Command("test") }, func(string) (string, error) { return "false", nil @@ -935,7 +936,7 @@ func TestGitCommandPush(t *testing.T) { assert.EqualValues(t, "git", cmd) assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args) - return exec.Command("exit", "1") + return exec.Command("test") }, false, func(err error) { @@ -967,7 +968,7 @@ func TestGitCommandSquashPreviousTwoCommits(t *testing.T) { assert.EqualValues(t, "git", cmd) assert.EqualValues(t, []string{"reset", "--soft", "HEAD^"}, args) - return exec.Command("exit", "1") + return exec.Command("test") }, func(err error) { assert.NotNil(t, err) @@ -983,7 +984,7 @@ func TestGitCommandSquashPreviousTwoCommits(t *testing.T) { assert.EqualValues(t, "git", cmd) assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args) - return exec.Command("exit", "1") + return exec.Command("test") }, func(err error) { assert.NotNil(t, err) @@ -1031,7 +1032,7 @@ func TestGitCommandSquashFixupCommit(t *testing.T) { return func(cmd string, args ...string) *exec.Cmd { cmdsCalled = append(cmdsCalled, args) if len(args) > 0 && args[0] == "checkout" { - return exec.Command("exit", "1") + return exec.Command("test") } return exec.Command("echo") @@ -1165,7 +1166,7 @@ func TestGitCommandIsInMergeState(t *testing.T) { assert.EqualValues(t, "git", cmd) assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args) - return exec.Command("exit", "1") + return exec.Command("test") }, func(isInMergeState bool, err error) { assert.Error(t, err) @@ -1219,6 +1220,207 @@ func TestGitCommandIsInMergeState(t *testing.T) { } } +func TestGitCommandRemoveFile(t *testing.T) { + type scenario struct { + testName string + command func() (func(string, ...string) *exec.Cmd, *[][]string) + test func(*[][]string, error) + file File + removeFile func(string) error + } + + scenarios := []scenario{ + { + "An error occurred when resetting", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + + return exec.Command("test") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.Error(t, err) + assert.Len(t, *cmdsCalled, 1) + assert.EqualValues(t, *cmdsCalled, [][]string{ + {"reset", "--", "test"}, + }) + }, + File{ + Name: "test", + HasStagedChanges: true, + }, + func(string) error { + return nil + }, + }, + { + "An error occurred when removing file", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + + return exec.Command("test") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.Error(t, err) + assert.EqualError(t, err, "an error occurred when removing file") + assert.Len(t, *cmdsCalled, 0) + }, + File{ + Name: "test", + Tracked: false, + }, + func(string) error { + return fmt.Errorf("an error occurred when removing file") + }, + }, + { + "An error occurred with checkout", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + + return exec.Command("test") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.Error(t, err) + assert.Len(t, *cmdsCalled, 1) + assert.EqualValues(t, *cmdsCalled, [][]string{ + {"checkout", "--", "test"}, + }) + }, + File{ + Name: "test", + Tracked: true, + HasStagedChanges: false, + }, + func(string) error { + return nil + }, + }, + { + "Checkout only", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + + return exec.Command("echo") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.NoError(t, err) + assert.Len(t, *cmdsCalled, 1) + assert.EqualValues(t, *cmdsCalled, [][]string{ + {"checkout", "--", "test"}, + }) + }, + File{ + Name: "test", + Tracked: true, + HasStagedChanges: false, + }, + func(string) error { + return nil + }, + }, + { + "Reset and checkout", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + + return exec.Command("echo") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.NoError(t, err) + assert.Len(t, *cmdsCalled, 2) + assert.EqualValues(t, *cmdsCalled, [][]string{ + {"reset", "--", "test"}, + {"checkout", "--", "test"}, + }) + }, + File{ + Name: "test", + Tracked: true, + HasStagedChanges: true, + }, + func(string) error { + return nil + }, + }, + { + "Reset and remove", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + + return exec.Command("echo") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.NoError(t, err) + assert.Len(t, *cmdsCalled, 1) + assert.EqualValues(t, *cmdsCalled, [][]string{ + {"reset", "--", "test"}, + }) + }, + File{ + Name: "test", + Tracked: false, + HasStagedChanges: true, + }, + func(filename string) error { + assert.Equal(t, "test", filename) + return nil + }, + }, + { + "Remove only", + func() (func(string, ...string) *exec.Cmd, *[][]string) { + cmdsCalled := [][]string{} + return func(cmd string, args ...string) *exec.Cmd { + cmdsCalled = append(cmdsCalled, args) + + return exec.Command("echo") + }, &cmdsCalled + }, + func(cmdsCalled *[][]string, err error) { + assert.NoError(t, err) + assert.Len(t, *cmdsCalled, 0) + }, + File{ + Name: "test", + Tracked: false, + HasStagedChanges: false, + }, + func(filename string) error { + assert.Equal(t, "test", filename) + return nil + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + var cmdsCalled *[][]string + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command, cmdsCalled = s.command() + gitCmd.removeFile = s.removeFile + s.test(cmdsCalled, gitCmd.RemoveFile(s.file)) + }) + } +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From b641d6bd965fb24b18ebf8475aa4f72c39988dd7 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 16 Sep 2018 22:08:23 +0200 Subject: [PATCH 14/36] commands/git : add test to Checkout, refactor --- pkg/commands/git.go | 2 +- pkg/commands/git_test.go | 46 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 7143309e4..402bee7ac 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -428,7 +428,7 @@ func (c *GitCommand) Checkout(branch string, force bool) error { if force { forceArg = "--force " } - return c.OSCommand.RunCommand("git checkout " + forceArg + branch) + return c.OSCommand.RunCommand(fmt.Sprintf("git checkout %s %s", forceArg, branch)) } // AddPatch prepares a subprocess for adding a patch by patch diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 0ee0749a7..87b0e4cee 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1421,6 +1421,52 @@ func TestGitCommandRemoveFile(t *testing.T) { } } +func TestGitCommandCheckout(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(error) + force bool + } + + scenarios := []scenario{ + { + "Checkout", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"checkout", "test"}, args) + + return exec.Command("echo") + }, + func(err error) { + assert.NoError(t, err) + }, + false, + }, + { + "Checkout forced", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"checkout", "--force", "test"}, args) + + return exec.Command("echo") + }, + func(err error) { + assert.NoError(t, err) + }, + true, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.Checkout("test", s.force)) + }) + } +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From 9713a151672d0787c24b922ab70e0177ac4607de Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 16 Sep 2018 22:12:03 +0200 Subject: [PATCH 15/36] commands/git : add test to GetBranchGraph, refactor --- pkg/commands/git.go | 2 +- pkg/commands/git_test.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 402bee7ac..1c5b31b81 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -451,7 +451,7 @@ func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd { // Currently it limits the result to 100 commits, but when we get async stuff // working we can do lazy loading 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(fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName)) } func includesString(list []string, a string) bool { diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 87b0e4cee..7a7af0991 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1467,6 +1467,19 @@ func TestGitCommandCheckout(t *testing.T) { } } +func TestGitCommandGetBranchGraph(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "-100", "test"}, args) + + return exec.Command("echo") + } + + _, err := gitCmd.GetBranchGraph("test") + assert.NoError(t, err) +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From 38036e0d20f3886cbbeba5ff8965f86a0db01a0e Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 16 Sep 2018 22:41:41 +0200 Subject: [PATCH 16/36] circle : kill old cache --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ddfd88844..9e32166db 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,7 +29,7 @@ jobs: fi - restore_cache: keys: - - pkg-cache-{{ checksum "Gopkg.lock" }}-v2 + - pkg-cache-{{ checksum "Gopkg.lock" }}-v3 - run: name: Run tests command: | @@ -44,7 +44,7 @@ jobs: command: | bash <(curl -s https://codecov.io/bash) - save_cache: - key: pkg-cache-{{ checksum "Gopkg.lock" }}-v2 + key: pkg-cache-{{ checksum "Gopkg.lock" }}-v3 paths: - ~/.cache/go-build From 9d9d775f50657a6ae528cbd6781e6317fabb1f4a Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Sun, 16 Sep 2018 22:49:17 +0200 Subject: [PATCH 17/36] circle : remove new line --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9e32166db..14c848257 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,6 @@ jobs: build: docker: - image: circleci/golang:1.11 - working_directory: /go/src/github.com/jesseduffield/lazygit steps: - checkout From c00c834b359bc0ebcd6e940e5cb5ef6f7247a6c7 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 17 Sep 2018 21:02:30 +1000 Subject: [PATCH 18/36] standardise rendering of lists in panels --- pkg/commands/branch.go | 6 ++-- pkg/commands/commit.go | 26 ++++++++++++++ pkg/commands/file.go | 36 +++++++++++++++++++ pkg/commands/git.go | 28 +++++++-------- pkg/commands/git_structs.go | 27 -------------- pkg/commands/stash_entry.go | 13 +++++++ pkg/git/branch_list_builder.go | 32 ++++++++--------- pkg/gui/branches_panel.go | 14 +++++--- pkg/gui/commits_panel.go | 32 ++++++----------- pkg/gui/files_panel.go | 46 +++++++----------------- pkg/gui/gui.go | 16 ++++----- pkg/gui/keybindings.go | 28 ++++++++++++--- pkg/gui/menu_panel.go | 51 ++++++-------------------- pkg/gui/stash_panel.go | 15 ++++---- pkg/utils/utils.go | 66 ++++++++++++++++++++++++++++++++++ 15 files changed, 256 insertions(+), 180 deletions(-) create mode 100644 pkg/commands/commit.go create mode 100644 pkg/commands/file.go create mode 100644 pkg/commands/stash_entry.go diff --git a/pkg/commands/branch.go b/pkg/commands/branch.go index 13c26e766..19553a26b 100644 --- a/pkg/commands/branch.go +++ b/pkg/commands/branch.go @@ -14,9 +14,9 @@ type Branch struct { Recency string } -// GetDisplayString returns the dispaly string of branch -func (b *Branch) GetDisplayString() string { - return utils.WithPadding(b.Recency, 4) + utils.ColoredString(b.Name, b.GetColor()) +// GetDisplayStrings returns the dispaly string of branch +func (b *Branch) GetDisplayStrings() []string { + return []string{b.Recency, utils.ColoredString(b.Name, b.GetColor())} } // GetColor branch color diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go new file mode 100644 index 000000000..37c3e9525 --- /dev/null +++ b/pkg/commands/commit.go @@ -0,0 +1,26 @@ +package commands + +import ( + "github.com/fatih/color" +) + +// Commit : A git commit +type Commit struct { + Sha string + Name string + Pushed bool + DisplayString string +} + +func (c *Commit) GetDisplayStrings() []string { + red := color.New(color.FgRed) + yellow := color.New(color.FgYellow) + white := color.New(color.FgWhite) + + shaColor := yellow + if c.Pushed { + shaColor = red + } + + return []string{shaColor.Sprint(c.Sha), white.Sprint(c.Name)} +} diff --git a/pkg/commands/file.go b/pkg/commands/file.go new file mode 100644 index 000000000..8fcd9aff9 --- /dev/null +++ b/pkg/commands/file.go @@ -0,0 +1,36 @@ +package commands + +import "github.com/fatih/color" + +// File : A file from git status +// duplicating this for now +type File struct { + Name string + HasStagedChanges bool + HasUnstagedChanges bool + Tracked bool + Deleted bool + HasMergeConflicts bool + DisplayString string + Type string // one of 'file', 'directory', and 'other' +} + +// GetDisplayStrings returns the display string of a file +func (f *File) GetDisplayStrings() []string { + // potentially inefficient to be instantiating these color + // objects with each render + red := color.New(color.FgRed) + green := color.New(color.FgGreen) + if !f.Tracked && !f.HasStagedChanges { + return []string{red.Sprint(f.DisplayString)} + } + + output := green.Sprint(f.DisplayString[0:1]) + output += red.Sprint(f.DisplayString[1:3]) + if f.HasUnstagedChanges { + output += red.Sprint(f.Name) + } else { + output += green.Sprint(f.Name) + } + return []string{output} +} diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 5744fa6aa..3fb46fff7 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -104,17 +104,17 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) } // GetStashEntries stash entryies -func (c *GitCommand) GetStashEntries() []StashEntry { +func (c *GitCommand) GetStashEntries() []*StashEntry { rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'") - stashEntries := []StashEntry{} + stashEntries := []*StashEntry{} for i, line := range utils.SplitLines(rawString) { stashEntries = append(stashEntries, stashEntryFromLine(line, i)) } return stashEntries } -func stashEntryFromLine(line string, index int) StashEntry { - return StashEntry{ +func stashEntryFromLine(line string, index int) *StashEntry { + return &StashEntry{ Name: line, Index: index, DisplayString: line, @@ -127,10 +127,10 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) { } // GetStatusFiles git status files -func (c *GitCommand) GetStatusFiles() []File { +func (c *GitCommand) GetStatusFiles() []*File { statusOutput, _ := c.GitStatus() statusStrings := utils.SplitLines(statusOutput) - files := []File{} + files := []*File{} for _, statusString := range statusStrings { change := statusString[0:2] @@ -140,7 +140,7 @@ func (c *GitCommand) GetStatusFiles() []File { _, untracked := map[string]bool{"??": true, "A ": true, "AM": true}[change] _, hasNoStagedChanges := map[string]bool{" ": true, "U": true, "?": true}[stagedChange] - file := File{ + file := &File{ Name: filename, DisplayString: statusString, HasStagedChanges: !hasNoStagedChanges, @@ -168,7 +168,7 @@ func (c *GitCommand) StashSave(message string) error { } // MergeStatusFiles merge status files -func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { +func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []*File) []*File { if len(oldFiles) == 0 { return newFiles } @@ -176,7 +176,7 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File { appendedIndexes := []int{} // retain position of files we already could see - result := []File{} + result := []*File{} for _, oldFile := range oldFiles { for newIndex, newFile := range newFiles { if oldFile.Name == newFile.Name { @@ -415,7 +415,7 @@ func (c *GitCommand) IsInMergeState() (bool, error) { } // RemoveFile directly -func (c *GitCommand) RemoveFile(file File) error { +func (c *GitCommand) RemoveFile(file *File) error { // if the file isn't tracked, we assume you want to delete it if file.HasStagedChanges { if err := c.OSCommand.RunCommand("git reset -- " + file.Name); err != nil { @@ -471,17 +471,17 @@ func includesString(list []string, a string) bool { } // GetCommits obtains the commits of the current branch -func (c *GitCommand) GetCommits() []Commit { +func (c *GitCommand) GetCommits() []*Commit { pushables := c.GetCommitsToPush() log := c.GetLog() - commits := []Commit{} + commits := []*Commit{} // now we can split it up and turn it into commits lines := utils.SplitLines(log) for _, line := range lines { splitLine := strings.Split(line, " ") sha := splitLine[0] pushed := includesString(pushables, sha) - commits = append(commits, Commit{ + commits = append(commits, &Commit{ Sha: sha, Name: strings.Join(splitLine[1:], " "), Pushed: pushed, @@ -519,7 +519,7 @@ func (c *GitCommand) Show(sha string) string { } // Diff returns the diff of a file -func (c *GitCommand) Diff(file File) string { +func (c *GitCommand) Diff(file *File) string { cachedArg := "" fileName := c.OSCommand.Quote(file.Name) if file.HasStagedChanges && !file.HasUnstagedChanges { diff --git a/pkg/commands/git_structs.go b/pkg/commands/git_structs.go index 2b3b3f2db..1590ce32e 100644 --- a/pkg/commands/git_structs.go +++ b/pkg/commands/git_structs.go @@ -1,32 +1,5 @@ package commands -// File : A staged/unstaged file -type File struct { - Name string - HasStagedChanges bool - HasUnstagedChanges bool - Tracked bool - Deleted bool - HasMergeConflicts bool - DisplayString string - Type string // one of 'file', 'directory', and 'other' -} - -// Commit : A git commit -type Commit struct { - Sha string - Name string - Pushed bool - DisplayString string -} - -// StashEntry : A git stash entry -type StashEntry struct { - Index int - Name string - DisplayString string -} - // Conflict : A git conflict with a start middle and end corresponding to line // numbers in the file where the conflict bars appear type Conflict struct { diff --git a/pkg/commands/stash_entry.go b/pkg/commands/stash_entry.go new file mode 100644 index 000000000..3886fd4c9 --- /dev/null +++ b/pkg/commands/stash_entry.go @@ -0,0 +1,13 @@ +package commands + +// StashEntry : A git stash entry +type StashEntry struct { + Index int + Name string + DisplayString string +} + +// GetDisplayStrings returns the dispaly string of branch +func (s *StashEntry) GetDisplayStrings() []string { + return []string{s.DisplayString} +} diff --git a/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go index 651b684f5..151e5b0b4 100644 --- a/pkg/git/branch_list_builder.go +++ b/pkg/git/branch_list_builder.go @@ -34,7 +34,7 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand) (* }, nil } -func (b *BranchListBuilder) obtainCurrentBranch() commands.Branch { +func (b *BranchListBuilder) obtainCurrentBranch() *commands.Branch { // I used go-git for this, but that breaks if you've just done a git init, // even though you're on 'master' branchName, err := b.GitCommand.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") @@ -44,11 +44,11 @@ func (b *BranchListBuilder) obtainCurrentBranch() commands.Branch { panic(err.Error()) } } - return commands.Branch{Name: strings.TrimSpace(branchName), Recency: " *"} + return &commands.Branch{Name: strings.TrimSpace(branchName), Recency: " *"} } -func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch { - branches := make([]commands.Branch, 0) +func (b *BranchListBuilder) obtainReflogBranches() []*commands.Branch { + branches := make([]*commands.Branch, 0) rawString, err := b.GitCommand.OSCommand.RunCommandWithOutput("git reflog -n100 --pretty='%cr|%gs' --grep-reflog='checkout: moving' HEAD") if err != nil { return branches @@ -58,14 +58,14 @@ func (b *BranchListBuilder) obtainReflogBranches() []commands.Branch { for _, line := range branchLines { timeNumber, timeUnit, branchName := branchInfoFromLine(line) timeUnit = abbreviatedTimeUnit(timeUnit) - branch := commands.Branch{Name: branchName, Recency: timeNumber + timeUnit} + branch := &commands.Branch{Name: branchName, Recency: timeNumber + timeUnit} branches = append(branches, branch) } return branches } -func (b *BranchListBuilder) obtainSafeBranches() []commands.Branch { - branches := make([]commands.Branch, 0) +func (b *BranchListBuilder) obtainSafeBranches() []*commands.Branch { + branches := make([]*commands.Branch, 0) bIter, err := b.GitCommand.Repo.Branches() if err != nil { @@ -73,14 +73,14 @@ func (b *BranchListBuilder) obtainSafeBranches() []commands.Branch { } err = bIter.ForEach(func(b *plumbing.Reference) error { name := b.Name().Short() - branches = append(branches, commands.Branch{Name: name}) + branches = append(branches, &commands.Branch{Name: name}) return nil }) return branches } -func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []commands.Branch, included bool) []commands.Branch { +func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existingBranches []*commands.Branch, included bool) []*commands.Branch { for _, newBranch := range newBranches { if included == branchIncluded(newBranch.Name, existingBranches) { finalBranches = append(finalBranches, newBranch) @@ -89,7 +89,7 @@ func (b *BranchListBuilder) appendNewBranches(finalBranches, newBranches, existi return finalBranches } -func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.Branch) string { +func sanitisedReflogName(reflogBranch *commands.Branch, safeBranches []*commands.Branch) string { for _, safeBranch := range safeBranches { if strings.ToLower(safeBranch.Name) == strings.ToLower(reflogBranch.Name) { return safeBranch.Name @@ -99,15 +99,15 @@ func sanitisedReflogName(reflogBranch commands.Branch, safeBranches []commands.B } // Build the list of branches for the current repo -func (b *BranchListBuilder) Build() []commands.Branch { - branches := make([]commands.Branch, 0) +func (b *BranchListBuilder) Build() []*commands.Branch { + branches := make([]*commands.Branch, 0) head := b.obtainCurrentBranch() safeBranches := b.obtainSafeBranches() if len(safeBranches) == 0 { return append(branches, head) } reflogBranches := b.obtainReflogBranches() - reflogBranches = uniqueByName(append([]commands.Branch{head}, reflogBranches...)) + reflogBranches = uniqueByName(append([]*commands.Branch{head}, reflogBranches...)) for i, reflogBranch := range reflogBranches { reflogBranches[i].Name = sanitisedReflogName(reflogBranch, safeBranches) } @@ -118,7 +118,7 @@ func (b *BranchListBuilder) Build() []commands.Branch { return branches } -func branchIncluded(branchName string, branches []commands.Branch) bool { +func branchIncluded(branchName string, branches []*commands.Branch) bool { for _, existingBranch := range branches { if strings.ToLower(existingBranch.Name) == strings.ToLower(branchName) { return true @@ -127,8 +127,8 @@ func branchIncluded(branchName string, branches []commands.Branch) bool { return false } -func uniqueByName(branches []commands.Branch) []commands.Branch { - finalBranches := make([]commands.Branch, 0) +func uniqueByName(branches []*commands.Branch) []*commands.Branch { + finalBranches := make([]*commands.Branch, 0) for _, branch := range branches { if branchIncluded(branch.Name, finalBranches) { continue diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 659725ef2..0f66533b1 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -7,6 +7,7 @@ import ( "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/git" + "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) handleBranchPress(g *gocui.Gui, v *gocui.View) error { @@ -109,7 +110,7 @@ func (gui *Gui) handleMerge(g *gocui.Gui, v *gocui.View) error { return nil } -func (gui *Gui) getSelectedBranch(v *gocui.View) commands.Branch { +func (gui *Gui) getSelectedBranch(v *gocui.View) *commands.Branch { lineNumber := gui.getItemPosition(v) return gui.State.Branches[lineNumber] } @@ -151,12 +152,15 @@ func (gui *Gui) refreshBranches(g *gocui.Gui) error { return err } gui.State.Branches = builder.Build() + v.Clear() - displayStrings := make([]string, len(gui.State.Branches)) - for i, branch := range gui.State.Branches { - displayStrings[i] = branch.GetDisplayString() + list, err := utils.RenderList(gui.State.Branches) + if err != nil { + return err } - fmt.Fprint(v, strings.Join(displayStrings, "\n")) + + fmt.Fprint(v, list) + gui.resetOrigin(v) return gui.refreshStatus(g) }) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index dd33369a5..f0b96586a 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -3,26 +3,12 @@ package gui import ( "errors" "fmt" - "strings" - "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) renderCommit(commit commands.Commit) string { - red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) - white := color.New(color.FgWhite) - - shaColor := yellow - if commit.Pushed { - shaColor = red - } - - return shaColor.Sprint(commit.Sha) + " " + white.Sprint(commit.Name) -} - func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { gui.State.Commits = gui.GitCommand.GetCommits() @@ -30,12 +16,14 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { if err != nil { panic(err) } + v.Clear() - displayStrings := make([]string, len(gui.State.Commits)) - for i, commit := range gui.State.Commits { - displayStrings[i] = gui.renderCommit(commit) + list, err := utils.RenderList(gui.State.Commits) + if err != nil { + return err } - fmt.Fprint(v, strings.Join(displayStrings, "\n")) + fmt.Fprint(v, list) + gui.refreshStatus(g) if g.CurrentView().Name() == "commits" { gui.handleCommitSelect(g, v) @@ -106,7 +94,7 @@ func (gui *Gui) handleCommitSquashDown(g *gocui.Gui, v *gocui.View) error { } // TODO: move to files panel -func (gui *Gui) anyUnStagedChanges(files []commands.File) bool { +func (gui *Gui) anyUnStagedChanges(files []*commands.File) bool { for _, file := range files { if file.Tracked && file.HasUnstagedChanges { return true @@ -169,13 +157,13 @@ func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error { return nil } -func (gui *Gui) getSelectedCommit(g *gocui.Gui) (commands.Commit, error) { +func (gui *Gui) getSelectedCommit(g *gocui.Gui) (*commands.Commit, error) { v, err := g.View("commits") if err != nil { panic(err) } if len(gui.State.Commits) == 0 { - return commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) + return &commands.Commit{}, errors.New(gui.Tr.SLocalize("NoCommitsThisBranch")) } lineNumber := gui.getItemPosition(v) if lineNumber > len(gui.State.Commits)-1 { diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 6ad2aed5b..6fd05552f 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -10,14 +10,14 @@ import ( "fmt" "strings" - "github.com/fatih/color" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) stagedFiles() []commands.File { +func (gui *Gui) stagedFiles() []*commands.File { files := gui.State.Files - result := make([]commands.File, 0) + result := make([]*commands.File, 0) for _, file := range files { if file.HasStagedChanges { result = append(result, file) @@ -26,9 +26,9 @@ func (gui *Gui) stagedFiles() []commands.File { return result } -func (gui *Gui) trackedFiles() []commands.File { +func (gui *Gui) trackedFiles() []*commands.File { files := gui.State.Files - result := make([]commands.File, 0) + result := make([]*commands.File, 0) for _, file := range files { if file.Tracked { result = append(result, file) @@ -117,9 +117,9 @@ func (gui *Gui) handleAddPatch(g *gocui.Gui, v *gocui.View) error { return gui.Errors.ErrSubProcess } -func (gui *Gui) getSelectedFile(g *gocui.Gui) (commands.File, error) { +func (gui *Gui) getSelectedFile(g *gocui.Gui) (*commands.File, error) { if len(gui.State.Files) == 0 { - return commands.File{}, gui.Errors.ErrNoFiles + return &commands.File{}, gui.Errors.ErrNoFiles } filesView, err := g.View("files") if err != nil { @@ -185,7 +185,7 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles")) return gui.renderfilesOptions(g, nil) } - gui.renderfilesOptions(g, &file) + gui.renderfilesOptions(g, file) var content string if file.HasMergeConflicts { return gui.refreshMergePanel(g) @@ -276,25 +276,6 @@ func (gui *Gui) updateHasMergeConflictStatus() error { return nil } -func (gui *Gui) renderFile(file commands.File) string { - // potentially inefficient to be instantiating these color - // objects with each render - red := color.New(color.FgRed) - green := color.New(color.FgGreen) - if !file.Tracked && !file.HasStagedChanges { - return red.Sprint(file.DisplayString) - } - - output := green.Sprint(file.DisplayString[0:1]) - output += red.Sprint(file.DisplayString[1:3]) - if file.HasUnstagedChanges { - output += red.Sprint(file.Name) - } else { - output += green.Sprint(file.Name) - } - return output -} - func (gui *Gui) catSelectedFile(g *gocui.Gui) (string, error) { item, err := gui.getSelectedFile(g) if err != nil { @@ -321,13 +302,12 @@ func (gui *Gui) refreshFiles(g *gocui.Gui) error { } gui.refreshStateFiles() - displayStrings := make([]string, len(gui.State.Files)) - for i, file := range gui.State.Files { - displayStrings[i] = gui.renderFile(file) - } - filesView.Clear() - fmt.Fprint(filesView, strings.Join(displayStrings, "\n")) + list, err := utils.RenderList(gui.State.Files) + if err != nil { + return err + } + fmt.Fprint(filesView, list) gui.correctCursor(filesView) if filesView == g.CurrentView() { diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index e3cc61529..f527e8c5d 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -71,10 +71,10 @@ type Gui struct { } type guiState struct { - Files []commands.File - Branches []commands.Branch - Commits []commands.Commit - StashEntries []commands.StashEntry + Files []*commands.File + Branches []*commands.Branch + Commits []*commands.Commit + StashEntries []*commands.StashEntry PreviousView string HasMergeConflicts bool ConflictIndex int @@ -83,17 +83,17 @@ type guiState struct { EditHistory *stack.Stack Platform commands.Platform Updating bool - Keys []Binding + Keys []*Binding } // NewGui builds a new gui handler func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) { initialState := guiState{ - Files: make([]commands.File, 0), + Files: make([]*commands.File, 0), PreviousView: "files", - Commits: make([]commands.Commit, 0), - StashEntries: make([]commands.StashEntry, 0), + Commits: make([]*commands.Commit, 0), + StashEntries: make([]*commands.StashEntry, 0), ConflictIndex: 0, ConflictTop: true, Conflicts: make([]commands.Conflict, 0), diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index c8ed7d3c8..e05caaec7 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1,6 +1,8 @@ package gui -import "github.com/jesseduffield/gocui" +import ( + "github.com/jesseduffield/gocui" +) // Binding - a keybinding mapping a key and modifier to a handler. The keypress // is only handled if the given view has focus, or handled globally if the view @@ -14,8 +16,26 @@ type Binding struct { Description string } -func (gui *Gui) GetKeybindings() []Binding { - bindings := []Binding{ +// GetDisplayStrings returns the display string of a file +func (b *Binding) GetDisplayStrings() []string { + return []string{b.GetKey(), b.Description} +} + +func (b *Binding) GetKey() string { + r, ok := b.Key.(rune) + key := "" + + if ok { + key = string(r) + } else if b.KeyReadable != "" { + key = b.KeyReadable + } + + return key +} + +func (gui *Gui) GetKeybindings() []*Binding { + bindings := []*Binding{ { ViewName: "", Key: 'q', @@ -360,7 +380,7 @@ func (gui *Gui) GetKeybindings() []Binding { // Would make these keybindings global but that interferes with editing // input in the confirmation panel for _, viewName := range []string{"status", "files", "branches", "commits", "stash", "menu"} { - bindings = append(bindings, []Binding{ + bindings = append(bindings, []*Binding{ {ViewName: viewName, Key: gocui.KeyTab, Modifier: gocui.ModNone, Handler: gui.nextView}, {ViewName: viewName, Key: gocui.KeyArrowLeft, Modifier: gocui.ModNone, Handler: gui.previousView}, {ViewName: viewName, Key: gocui.KeyArrowRight, Modifier: gocui.ModNone, Handler: gui.nextView}, diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 983543c94..ee0773a51 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -49,50 +49,20 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } -func (gui *Gui) GetKey(binding Binding) string { - r, ok := binding.Key.(rune) - key := "" - - if ok { - key = string(r) - } else if binding.KeyReadable != "" { - key = binding.KeyReadable - } - - return key -} - -func (gui *Gui) GetMaxKeyLength(bindings []Binding) int { - max := 0 - for _, binding := range bindings { - keyLength := len(gui.GetKey(binding)) - if keyLength > max { - max = keyLength - } - } - return max -} - func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { var ( - contentGlobal, contentPanel []string - bindingsGlobal, bindingsPanel []Binding + bindingsGlobal, bindingsPanel []*Binding ) // clear keys slice, so we don't have ghost elements gui.State.Keys = gui.State.Keys[:0] bindings := gui.GetKeybindings() - padWidth := gui.GetMaxKeyLength(bindings) for _, binding := range bindings { - key := gui.GetKey(binding) - if key != "" && binding.Description != "" { - content := fmt.Sprintf("%s %s", utils.WithPadding(key, padWidth), binding.Description) + if binding.GetKey() != "" && binding.Description != "" { switch binding.ViewName { case "": - contentGlobal = append(contentGlobal, content) bindingsGlobal = append(bindingsGlobal, binding) case v.Name(): - contentPanel = append(contentPanel, content) bindingsPanel = append(bindingsPanel, binding) } } @@ -100,24 +70,25 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { // append dummy element to have a separator between // panel and global keybindings - contentPanel = append(contentPanel, "") - bindingsPanel = append(bindingsPanel, Binding{}) - - content := append(contentPanel, contentGlobal...) + bindingsPanel = append(bindingsPanel, &Binding{}) gui.State.Keys = append(bindingsPanel, bindingsGlobal...) - contentJoined := strings.Join(content, "\n") - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, contentJoined) + list, err := utils.RenderList(gui.State.Keys) + if err != nil { + return err + } + + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, list) menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) menuView.FgColor = gocui.ColorWhite + menuView.Clear() + fmt.Fprint(menuView, list) if err := gui.renderMenuOptions(g); err != nil { return err } - fmt.Fprint(menuView, contentJoined) - g.Update(func(g *gocui.Gui) error { _, err := g.SetViewOnTop("menu") if err != nil { diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 3a9fac8d4..62b4efda7 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -2,10 +2,10 @@ package gui import ( "fmt" - "strings" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" ) func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { @@ -16,13 +16,12 @@ func (gui *Gui) refreshStashEntries(g *gocui.Gui) error { } gui.State.StashEntries = gui.GitCommand.GetStashEntries() - displayStrings := make([]string, len(gui.State.StashEntries)) - for i, stashEntry := range gui.State.StashEntries { - displayStrings[i] = stashEntry.DisplayString - } - v.Clear() - fmt.Fprint(v, strings.Join(displayStrings, "\n")) + list, err := utils.RenderList(gui.State.StashEntries) + if err != nil { + return err + } + fmt.Fprint(v, list) return gui.resetOrigin(v) }) @@ -35,7 +34,7 @@ func (gui *Gui) getSelectedStashEntry(v *gocui.View) *commands.StashEntry { } stashView, _ := gui.g.View("stash") lineNumber := gui.getItemPosition(stashView) - return &gui.State.StashEntries[lineNumber] + return gui.State.StashEntries[lineNumber] } func (gui *Gui) renderStashOptions(g *gocui.Gui) error { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index cb23eb75e..f1a56116b 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,10 +1,12 @@ package utils import ( + "errors" "fmt" "log" "os" "path/filepath" + "reflect" "strings" "time" @@ -107,3 +109,67 @@ func Min(x, y int) int { } return y } + +type Displayable interface { + GetDisplayStrings() []string +} + +// RenderList takes a slice of items, confirms they implement the Displayable +// interface, then generates a list of their displaystrings to write to a panel's +// buffer +func RenderList(slice interface{}) (string, error) { + s := reflect.ValueOf(slice) + if s.Kind() != reflect.Slice { + return "", errors.New("RenderList given a non-slice type") + } + + displayables := make([]Displayable, s.Len()) + + for i := 0; i < s.Len(); i++ { + value, ok := s.Index(i).Interface().(Displayable) + if !ok { + return "", errors.New("item does not implement the Displayable interface") + } + displayables[i] = value + } + + return renderDisplayableList(displayables) +} + +// renderDisplayableList takes a list of displayable items, obtains their display +// strings via GetDisplayStrings() and then returns a single string containing +// each item's string representation on its own line, with appropriate horizontal +// padding between the item's own strings +func renderDisplayableList(items []Displayable) (string, error) { + displayStrings := make([][]string, len(items)) + + for i, item := range items { + displayStrings[i] = item.GetDisplayStrings() + } + + if len(displayStrings) == 0 { + return "", nil + } + + // use first element to determine how many times to do this + padWidths := make([]int, len(displayStrings[0])) + for i, _ := range padWidths { + for _, strings := range displayStrings { + if len(strings) != len(padWidths) { + return "", errors.New("Each item must return the same number of strings to display") + } + if len(strings[i]) > padWidths[i] { + padWidths[i] = len(strings[i]) + } + } + } + + paddedDisplayStrings := make([]string, len(displayStrings)) + for i, strings := range displayStrings { + for j, padWidth := range padWidths { + paddedDisplayStrings[i] += WithPadding(strings[j], padWidth) + " " + } + } + + return strings.Join(paddedDisplayStrings, "\n"), nil +} From a66ac8092e30f6c3f9a72c10a6b3eed5fb2265eb Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 17 Sep 2018 21:11:47 +1000 Subject: [PATCH 19/36] minor refactor --- pkg/gui/menu_panel.go | 11 +++++--- pkg/utils/utils.go | 60 ++++++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 22 deletions(-) diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index ee0773a51..945d0020a 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -49,12 +49,11 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } -func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { +func (gui *Gui) getBindings(v *gocui.View) []*Binding { var ( bindingsGlobal, bindingsPanel []*Binding ) - // clear keys slice, so we don't have ghost elements - gui.State.Keys = gui.State.Keys[:0] + bindings := gui.GetKeybindings() for _, binding := range bindings { @@ -71,7 +70,11 @@ func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { // append dummy element to have a separator between // panel and global keybindings bindingsPanel = append(bindingsPanel, &Binding{}) - gui.State.Keys = append(bindingsPanel, bindingsGlobal...) + return append(bindingsPanel, bindingsGlobal...) +} + +func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { + gui.State.Keys = gui.getBindings(v) list, err := utils.RenderList(gui.State.Keys) if err != nil { diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index f1a56116b..3efa67a91 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -141,35 +141,59 @@ func RenderList(slice interface{}) (string, error) { // each item's string representation on its own line, with appropriate horizontal // padding between the item's own strings func renderDisplayableList(items []Displayable) (string, error) { - displayStrings := make([][]string, len(items)) - - for i, item := range items { - displayStrings[i] = item.GetDisplayStrings() - } - - if len(displayStrings) == 0 { + if len(items) == 0 { return "", nil } - // use first element to determine how many times to do this - padWidths := make([]int, len(displayStrings[0])) + stringArrays := getDisplayStringArrays(items) + + if !displayArraysAligned(stringArrays) { + return "", errors.New("Each item must return the same number of strings to display") + } + + padWidths := getPadWidths(stringArrays) + paddedDisplayStrings := getPaddedDisplayStrings(stringArrays, padWidths) + + return strings.Join(paddedDisplayStrings, "\n"), nil +} + +func getPadWidths(stringArrays [][]string) []int { + padWidths := make([]int, len(stringArrays[0])) for i, _ := range padWidths { - for _, strings := range displayStrings { - if len(strings) != len(padWidths) { - return "", errors.New("Each item must return the same number of strings to display") - } + for _, strings := range stringArrays { if len(strings[i]) > padWidths[i] { padWidths[i] = len(strings[i]) } } } + return padWidths +} - paddedDisplayStrings := make([]string, len(displayStrings)) - for i, strings := range displayStrings { +func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string { + paddedDisplayStrings := make([]string, len(stringArrays)) + for i, stringArray := range stringArrays { for j, padWidth := range padWidths { - paddedDisplayStrings[i] += WithPadding(strings[j], padWidth) + " " + paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " " } } - - return strings.Join(paddedDisplayStrings, "\n"), nil + return paddedDisplayStrings +} + +// displayArraysAligned returns true if every string array returned from our +// list of displayables has the same length +func displayArraysAligned(stringArrays [][]string) bool { + for _, strings := range stringArrays { + if len(strings) != len(stringArrays[0]) { + return false + } + } + return true +} + +func getDisplayStringArrays(displayables []Displayable) [][]string { + stringArrays := make([][]string, len(displayables)) + for i, item := range displayables { + stringArrays[i] = item.GetDisplayStrings() + } + return stringArrays } From f89bc10af1f9fb4211badd2fee51e903bbc03f6c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Mon, 17 Sep 2018 21:32:19 +1000 Subject: [PATCH 20/36] appease golangci --- pkg/gui/files_panel.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 6fd05552f..574e6bc1c 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -185,7 +185,9 @@ func (gui *Gui) handleFileSelect(g *gocui.Gui, v *gocui.View) error { gui.renderString(g, "main", gui.Tr.SLocalize("NoChangedFiles")) return gui.renderfilesOptions(g, nil) } - gui.renderfilesOptions(g, file) + if err := gui.renderfilesOptions(g, file); err != nil { + return err + } var content string if file.HasMergeConflicts { return gui.refreshMergePanel(g) From 6f0b32f95e3b14209553de7d289a71d49fa4623f Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Mon, 17 Sep 2018 21:19:17 +0200 Subject: [PATCH 21/36] commands/git : add GetCommits tests refactor * switch GetCommitsToPush scope to private * return a map instead of slice for look up * remove useless includesString function --- pkg/commands/git.go | 43 +++++++++----------- pkg/commands/git_test.go | 85 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 98 insertions(+), 30 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 1c5b31b81..cca406d9a 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -231,14 +231,19 @@ func (c *GitCommand) UpstreamDifferenceCount() (string, string) { return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) } -// GetCommitsToPush Returns the sha's of the commits that have not yet been pushed -// to the remote branch of the current branch -func (c *GitCommand) GetCommitsToPush() []string { - pushables, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit") +// getCommitsToPush Returns the sha's of the commits that have not yet been pushed +// to the remote branch of the current branch, a map is returned to ease look up +func (c *GitCommand) getCommitsToPush() map[string]bool { + pushables := map[string]bool{} + o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit") if err != nil { - return []string{} + return pushables } - return utils.SplitLines(pushables) + for _, p := range utils.SplitLines(o) { + pushables[p] = true + } + + return pushables } // RenameCommit renames the topmost commit with the given name @@ -454,26 +459,16 @@ func (c *GitCommand) GetBranchGraph(branchName string) (string, error) { return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName)) } -func includesString(list []string, a string) bool { - for _, b := range list { - if b == a { - return true - } - } - return false -} - // GetCommits obtains the commits of the current branch func (c *GitCommand) GetCommits() []Commit { - pushables := c.GetCommitsToPush() + pushables := c.getCommitsToPush() log := c.GetLog() commits := []Commit{} // now we can split it up and turn it into commits - lines := utils.SplitLines(log) - for _, line := range lines { + for _, line := range utils.SplitLines(log) { splitLine := strings.Split(line, " ") sha := splitLine[0] - pushed := includesString(pushables, sha) + _, pushed := pushables[sha] commits = append(commits, Commit{ Sha: sha, Name: strings.Join(splitLine[1:], " "), @@ -489,12 +484,12 @@ func (c *GitCommand) GetCommits() []Commit { func (c *GitCommand) GetLog() string { // currently limiting to 30 for performance reasons // TODO: add lazyloading when you scroll down - result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30") - if err != nil { - // assume if there is an error there are no commits yet for this branch - return "" + if result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30"); err == nil { + return result } - return result + + // assume if there is an error there are no commits yet for this branch + return "" } // Ignore adds a file to the gitignore for the repo diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 7a7af0991..aedf5253b 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -602,7 +602,7 @@ func TestGitCommandGetCommitsToPush(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]string) + test func(map[string]bool) } scenarios := []scenario{ @@ -611,8 +611,8 @@ func TestGitCommandGetCommitsToPush(t *testing.T) { func(string, ...string) *exec.Cmd { return exec.Command("test") }, - func(pushables []string) { - assert.EqualValues(t, []string{}, pushables) + func(pushables map[string]bool) { + assert.EqualValues(t, map[string]bool{}, pushables) }, }, { @@ -620,9 +620,9 @@ func TestGitCommandGetCommitsToPush(t *testing.T) { func(cmd string, args ...string) *exec.Cmd { return exec.Command("echo", "8a2bb0e\n78976bc") }, - func(pushables []string) { + func(pushables map[string]bool) { assert.Len(t, pushables, 2) - assert.EqualValues(t, []string{"8a2bb0e", "78976bc"}, pushables) + assert.EqualValues(t, map[string]bool{"8a2bb0e": true, "78976bc": true}, pushables) }, }, } @@ -631,7 +631,7 @@ func TestGitCommandGetCommitsToPush(t *testing.T) { t.Run(s.testName, func(t *testing.T) { gitCmd := newDummyGitCommand() gitCmd.OSCommand.command = s.command - s.test(gitCmd.GetCommitsToPush()) + s.test(gitCmd.getCommitsToPush()) }) } } @@ -1480,6 +1480,79 @@ func TestGitCommandGetBranchGraph(t *testing.T) { assert.NoError(t, err) } +func TestGitCommandGetCommits(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func([]Commit) + } + + scenarios := []scenario{ + { + "No data found", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + + switch args[0] { + case "rev-list": + assert.EqualValues(t, []string{"rev-list", "@{u}..head", "--abbrev-commit"}, args) + return exec.Command("echo") + case "log": + assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) + return exec.Command("echo") + } + + return nil + }, + func(commits []Commit) { + assert.Len(t, commits, 0) + }, + }, + { + "GetCommits returns 2 commits, 1 pushed the other not", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + + switch args[0] { + case "rev-list": + assert.EqualValues(t, []string{"rev-list", "@{u}..head", "--abbrev-commit"}, args) + return exec.Command("echo", "8a2bb0e") + case "log": + assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) + return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2") + } + + return nil + }, + func(commits []Commit) { + assert.Len(t, commits, 2) + assert.EqualValues(t, []Commit{ + { + Sha: "8a2bb0e", + Name: "commit 1", + Pushed: true, + DisplayString: "8a2bb0e commit 1", + }, + { + Sha: "78976bc", + Name: "commit 2", + Pushed: false, + DisplayString: "78976bc commit 2", + }, + }, commits) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.GetCommits()) + }) + } +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From 60cf549a3285531047009d9cf8cbb58c86a8e488 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Tue, 18 Sep 2018 09:23:41 +0200 Subject: [PATCH 22/36] commands/git : reverse the logic --- pkg/commands/git.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index cca406d9a..a106d8dce 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -484,12 +484,13 @@ func (c *GitCommand) GetCommits() []Commit { func (c *GitCommand) GetLog() string { // currently limiting to 30 for performance reasons // TODO: add lazyloading when you scroll down - if result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30"); err == nil { - return result + result, err := c.OSCommand.RunCommandWithOutput("git log --oneline -30") + if err != nil { + // assume if there is an error there are no commits yet for this branch + return "" } - // assume if there is an error there are no commits yet for this branch - return "" + return result } // Ignore adds a file to the gitignore for the repo From b384fcf6af4e5248616712414e0164887d7bbcbd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 18 Sep 2018 21:07:25 +1000 Subject: [PATCH 23/36] generalise popup menu panel --- pkg/gui/gui.go | 1 - pkg/gui/keybindings.go | 7 +--- pkg/gui/menu_panel.go | 74 ++++++++++------------------------- pkg/gui/options_menu_panel.go | 51 ++++++++++++++++++++++++ 4 files changed, 73 insertions(+), 60 deletions(-) create mode 100644 pkg/gui/options_menu_panel.go diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index f527e8c5d..8a2aaea81 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -83,7 +83,6 @@ type guiState struct { EditHistory *stack.Stack Platform commands.Platform Updating bool - Keys []*Binding } // NewGui builds a new gui handler diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index e05caaec7..72a187472 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -93,7 +93,7 @@ func (gui *Gui) GetKeybindings() []*Binding { ViewName: "", Key: 'x', Modifier: gocui.ModNone, - Handler: gui.handleMenu, + Handler: gui.handleCreateOptionsMenu, }, { ViewName: "status", Key: 'e', @@ -369,11 +369,6 @@ func (gui *Gui) GetKeybindings() []*Binding { Key: 'q', Modifier: gocui.ModNone, Handler: gui.handleMenuClose, - }, { - ViewName: "menu", - Key: gocui.KeySpace, - Modifier: gocui.ModNone, - Handler: gui.handleMenuPress, }, } diff --git a/pkg/gui/menu_panel.go b/pkg/gui/menu_panel.go index 945d0020a..753e8f84d 100644 --- a/pkg/gui/menu_panel.go +++ b/pkg/gui/menu_panel.go @@ -8,21 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/utils" ) -func (gui *Gui) handleMenuPress(g *gocui.Gui, v *gocui.View) error { - lineNumber := gui.getItemPosition(v) - if gui.State.Keys[lineNumber].Key == nil { - return nil - } - if len(gui.State.Keys) > lineNumber { - err := gui.handleMenuClose(g, v) - if err != nil { - return err - } - return gui.State.Keys[lineNumber].Handler(g, v) - } - return nil -} - func (gui *Gui) handleMenuSelect(g *gocui.Gui, v *gocui.View) error { // doing nothing for now // but it is needed for switch in newLineFocused @@ -39,9 +24,9 @@ func (gui *Gui) renderMenuOptions(g *gocui.Gui) error { } func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { - // better to delete because for example after closing update confirmation panel, - // the focus isn't set back to any of panels and one is unable to even quit - //_, err := g.SetViewOnBottom(v.Name()) + if err := g.DeleteKeybinding("menu", gocui.KeySpace, gocui.ModNone); err != nil { + return err + } err := g.DeleteView("menu") if err != nil { return err @@ -49,55 +34,38 @@ func (gui *Gui) handleMenuClose(g *gocui.Gui, v *gocui.View) error { return gui.returnFocus(g, v) } -func (gui *Gui) getBindings(v *gocui.View) []*Binding { - var ( - bindingsGlobal, bindingsPanel []*Binding - ) - - bindings := gui.GetKeybindings() - - for _, binding := range bindings { - if binding.GetKey() != "" && binding.Description != "" { - switch binding.ViewName { - case "": - bindingsGlobal = append(bindingsGlobal, binding) - case v.Name(): - bindingsPanel = append(bindingsPanel, binding) - } - } - } - - // append dummy element to have a separator between - // panel and global keybindings - bindingsPanel = append(bindingsPanel, &Binding{}) - return append(bindingsPanel, bindingsGlobal...) -} - -func (gui *Gui) handleMenu(g *gocui.Gui, v *gocui.View) error { - gui.State.Keys = gui.getBindings(v) - - list, err := utils.RenderList(gui.State.Keys) +func (gui *Gui) createMenu(items interface{}, handlePress func(int) error) error { + list, err := utils.RenderList(items) if err != nil { return err } - x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(g, list) - menuView, _ := g.SetView("menu", x0, y0, x1, y1, 0) + x0, y0, x1, y1 := gui.getConfirmationPanelDimensions(gui.g, list) + menuView, _ := gui.g.SetView("menu", x0, y0, x1, y1, 0) menuView.Title = strings.Title(gui.Tr.SLocalize("menu")) menuView.FgColor = gocui.ColorWhite menuView.Clear() fmt.Fprint(menuView, list) - if err := gui.renderMenuOptions(g); err != nil { + if err := gui.renderMenuOptions(gui.g); err != nil { return err } - g.Update(func(g *gocui.Gui) error { - _, err := g.SetViewOnTop("menu") - if err != nil { + wrappedHandlePress := func(g *gocui.Gui, v *gocui.View) error { + lineNumber := gui.getItemPosition(v) + return handlePress(lineNumber) + } + + if err := gui.g.SetKeybinding("menu", gocui.KeySpace, gocui.ModNone, wrappedHandlePress); err != nil { + return err + } + + gui.g.Update(func(g *gocui.Gui) error { + if _, err := g.SetViewOnTop("menu"); err != nil { return err } - return gui.switchFocus(g, v, menuView) + currentView := gui.g.CurrentView() + return gui.switchFocus(gui.g, currentView, menuView) }) return nil } diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go new file mode 100644 index 000000000..2da002b43 --- /dev/null +++ b/pkg/gui/options_menu_panel.go @@ -0,0 +1,51 @@ +package gui + +import ( + "errors" + + "github.com/jesseduffield/gocui" +) + +func (gui *Gui) getBindings(v *gocui.View) []*Binding { + var ( + bindingsGlobal, bindingsPanel []*Binding + ) + + bindings := gui.GetKeybindings() + + for _, binding := range bindings { + if binding.GetKey() != "" && binding.Description != "" { + switch binding.ViewName { + case "": + bindingsGlobal = append(bindingsGlobal, binding) + case v.Name(): + bindingsPanel = append(bindingsPanel, binding) + } + } + } + + // append dummy element to have a separator between + // panel and global keybindings + bindingsPanel = append(bindingsPanel, &Binding{}) + return append(bindingsPanel, bindingsGlobal...) +} + +func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error { + bindings := gui.getBindings(v) + + handleOptionsMenuPress := func(index int) error { + if bindings[index].Key == nil { + return nil + } + if index <= len(bindings) { + return errors.New("Index is greater than size of bindings") + } + err := gui.handleMenuClose(g, v) + if err != nil { + return err + } + return bindings[index].Handler(g, v) + } + + return gui.createMenu(bindings, handleOptionsMenuPress) +} From 950cfeff6f97d94b4ab5166214ea6cb5b38b77f0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 18:03:44 +1000 Subject: [PATCH 24/36] add specs for menu utils --- pkg/utils/utils.go | 11 ++- pkg/utils/utils_test.go | 181 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 174 insertions(+), 18 deletions(-) diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 3efa67a91..aef653c50 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -158,8 +158,11 @@ func renderDisplayableList(items []Displayable) (string, error) { } func getPadWidths(stringArrays [][]string) []int { - padWidths := make([]int, len(stringArrays[0])) - for i, _ := range padWidths { + if len(stringArrays[0]) <= 1 { + return []int{} + } + padWidths := make([]int, len(stringArrays[0])-1) + for i := range padWidths { for _, strings := range stringArrays { if len(strings[i]) > padWidths[i] { padWidths[i] = len(strings[i]) @@ -172,9 +175,13 @@ func getPadWidths(stringArrays [][]string) []int { func getPaddedDisplayStrings(stringArrays [][]string, padWidths []int) []string { paddedDisplayStrings := make([]string, len(stringArrays)) for i, stringArray := range stringArrays { + if len(stringArray) == 0 { + continue + } for j, padWidth := range padWidths { paddedDisplayStrings[i] += WithPadding(stringArray[j], padWidth) + " " } + paddedDisplayStrings[i] += stringArray[len(padWidths)] } return paddedDisplayStrings } diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index a088ed849..045bf9c64 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -1,6 +1,7 @@ package utils import ( + "errors" "testing" "github.com/stretchr/testify/assert" @@ -168,32 +169,180 @@ func TestResolvePlaceholderString(t *testing.T) { } } -func TestMin(t *testing.T) { +func TestDisplayArraysAligned(t *testing.T) { type scenario struct { - a int - b int - expected int + input [][]string + expected bool } scenarios := []scenario{ { - 2, - 4, - 2, + [][]string{{"", ""}, {"", ""}}, + true, }, { - 2, - 1, - 1, - }, - { - 1, - 1, - 1, + [][]string{{""}, {"", ""}}, + false, }, } for _, s := range scenarios { - assert.EqualValues(t, s.expected, Min(s.a, s.b)) + assert.EqualValues(t, s.expected, displayArraysAligned(s.input)) + } +} + +type myDisplayable struct { + strings []string +} + +type myStruct struct{} + +func (d *myDisplayable) GetDisplayStrings() []string { + return d.strings +} + +func TestGetDisplayStringArrays(t *testing.T) { + type scenario struct { + input []Displayable + expected [][]string + } + + scenarios := []scenario{ + { + []Displayable{ + Displayable(&myDisplayable{[]string{"a", "b"}}), + Displayable(&myDisplayable{[]string{"a", "b"}}), + }, + [][]string{{"a", "b"}, {"c", "d"}}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getDisplayStringArrays(s.input)) + } +} + +func TestRenderDisplayableList(t *testing.T) { + type scenario struct { + input []Displayable + expectedString string + expectedError error + } + + scenarios := []scenario{ + { + []Displayable{ + Displayable(&myDisplayable{[]string{}}), + Displayable(&myDisplayable{[]string{}}), + }, + "\n", + nil, + }, + { + []Displayable{ + Displayable(&myDisplayable{[]string{"aa", "b"}}), + Displayable(&myDisplayable{[]string{"c", "d"}}), + }, + "aa b\nc d", + nil, + }, + { + []Displayable{ + Displayable(&myDisplayable{[]string{"a"}}), + Displayable(&myDisplayable{[]string{"b", "c"}}), + }, + "", + errors.New("Each item must return the same number of strings to display"), + }, + } + + for _, s := range scenarios { + str, err := renderDisplayableList(s.input) + assert.EqualValues(t, s.expectedString, str) + assert.EqualValues(t, s.expectedError, err) + } +} + +func TestRenderList(t *testing.T) { + type scenario struct { + input interface{} + expectedString string + expectedError error + } + + scenarios := []scenario{ + { + []*myDisplayable{ + {[]string{"aa", "b"}}, + {[]string{"c", "d"}}, + }, + "aa b\nc d", + nil, + }, + { + []*myStruct{ + {}, + {}, + }, + "", + errors.New("item does not implement the Displayable interface"), + }, + { + &myStruct{}, + "", + errors.New("RenderList given a non-slice type"), + }, + } + + for _, s := range scenarios { + str, err := RenderList(s.input) + assert.EqualValues(t, s.expectedString, str) + assert.EqualValues(t, s.expectedError, err) + } +} + +func TestGetPaddedDisplayStrings(t *testing.T) { + type scenario struct { + stringArrays [][]string + padWidths []int + expected []string + } + + scenarios := []scenario{ + { + [][]string{{"a", "b"}, {"c", "d"}}, + []int{1}, + []string{"a b", "c d"}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getPaddedDisplayStrings(s.stringArrays, s.padWidths)) + } +} + +func TestGetPadWidths(t *testing.T) { + type scenario struct { + stringArrays [][]string + expected []int + } + + scenarios := []scenario{ + { + [][]string{{""}, {""}}, + []int{}, + }, + { + [][]string{{"a"}, {""}}, + []int{}, + }, + { + [][]string{{"aa", "b", "ccc"}, {"c", "d", "e"}}, + []int{2, 1}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, getPadWidths(s.stringArrays)) } } From e95b2e5f0b2aef3afcc6fa4e8b1f24d74cd1fb2a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 18:31:54 +1000 Subject: [PATCH 25/36] update specs --- pkg/commands/git_test.go | 40 ++++++++++++++++++++-------------------- pkg/utils/utils_test.go | 2 +- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 88ecaea66..d1c27cd30 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -275,7 +275,7 @@ func TestGitCommandGetStashEntries(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]StashEntry) + test func([]*StashEntry) } scenarios := []scenario{ @@ -284,7 +284,7 @@ func TestGitCommandGetStashEntries(t *testing.T) { func(string, ...string) *exec.Cmd { return exec.Command("echo") }, - func(entries []StashEntry) { + func(entries []*StashEntry) { assert.Len(t, entries, 0) }, }, @@ -293,8 +293,8 @@ func TestGitCommandGetStashEntries(t *testing.T) { func(string, ...string) *exec.Cmd { return exec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template") }, - func(entries []StashEntry) { - expected := []StashEntry{ + func(entries []*StashEntry) { + expected := []*StashEntry{ { 0, "WIP on add-pkg-commands-test: 55c6af2 increase parallel build", @@ -341,7 +341,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]File) + test func([]*File) } scenarios := []scenario{ @@ -350,7 +350,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) { func(cmd string, args ...string) *exec.Cmd { return exec.Command("echo") }, - func(files []File) { + func(files []*File) { assert.Len(t, files, 0) }, }, @@ -362,10 +362,10 @@ func TestGitCommandGetStatusFiles(t *testing.T) { "MM file1.txt\nA file3.txt\nAM file2.txt\n?? file4.txt", ) }, - func(files []File) { + func(files []*File) { assert.Len(t, files, 4) - expected := []File{ + expected := []*File{ { Name: "file1.txt", HasStagedChanges: true, @@ -463,22 +463,22 @@ func TestGitCommandCommitAmend(t *testing.T) { func TestGitCommandMergeStatusFiles(t *testing.T) { type scenario struct { testName string - oldFiles []File - newFiles []File - test func([]File) + oldFiles []*File + newFiles []*File + test func([]*File) } scenarios := []scenario{ { "Old file and new file are the same", - []File{}, - []File{ + []*File{}, + []*File{ { Name: "new_file.txt", }, }, - func(files []File) { - expected := []File{ + func(files []*File) { + expected := []*File{ { Name: "new_file.txt", }, @@ -490,7 +490,7 @@ func TestGitCommandMergeStatusFiles(t *testing.T) { }, { "Several files to merge, with some identical", - []File{ + []*File{ { Name: "new_file1.txt", }, @@ -501,7 +501,7 @@ func TestGitCommandMergeStatusFiles(t *testing.T) { Name: "new_file3.txt", }, }, - []File{ + []*File{ { Name: "new_file4.txt", }, @@ -512,8 +512,8 @@ func TestGitCommandMergeStatusFiles(t *testing.T) { Name: "new_file1.txt", }, }, - func(files []File) { - expected := []File{ + func(files []*File) { + expected := []*File{ { Name: "new_file1.txt", }, @@ -1223,7 +1223,7 @@ func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) - files := []File{ + files := []*File{ { Name: "deleted_staged", HasStagedChanges: false, diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 045bf9c64..76a9f8b95 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -211,7 +211,7 @@ func TestGetDisplayStringArrays(t *testing.T) { { []Displayable{ Displayable(&myDisplayable{[]string{"a", "b"}}), - Displayable(&myDisplayable{[]string{"a", "b"}}), + Displayable(&myDisplayable{[]string{"c", "d"}}), }, [][]string{{"a", "b"}, {"c", "d"}}, }, From e91fb21233f1c725c34e03b43ac763d8b4c61889 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:15:29 +1000 Subject: [PATCH 26/36] add recent repos menu option --- pkg/gui/gui.go | 35 ------------------ pkg/gui/keybindings.go | 2 +- pkg/gui/options_menu_panel.go | 6 +-- pkg/gui/recent_repos_panel.go | 69 +++++++++++++++++++++++++++++++++++ pkg/i18n/english.go | 2 +- 5 files changed, 74 insertions(+), 40 deletions(-) create mode 100644 pkg/gui/recent_repos_panel.go diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 818655aa6..7e7648cfe 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -317,41 +317,6 @@ func (gui *Gui) layout(g *gocui.Gui) error { return gui.resizeCurrentPopupPanel(g) } -func newRecentReposList(recentRepos []string, currentRepo string) []string { - newRepos := []string{currentRepo} - for _, repo := range recentRepos { - if repo != currentRepo { - newRepos = append(newRepos, repo) - } - } - return newRepos -} - -// updateRecentRepoList registers the fact that we opened lazygit in this repo, -// so that we can open the same repo via a 'recent repos' menu -func (gui *Gui) updateRecentRepoList() error { - recentRepos := gui.Config.GetAppState().RecentRepos - currentRepo, err := os.Getwd() - if err != nil { - return err - } - gui.Config.GetAppState().RecentRepos = newRecentReposList(recentRepos, currentRepo) - return gui.Config.SaveAppState() -} - -func (gui *Gui) handleSwitchRepo(g *gocui.Gui, v *gocui.View) error { - newRepo := gui.Config.GetAppState().RecentRepos[1] - if err := os.Chdir(newRepo); err != nil { - return err - } - newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr) - if err != nil { - return err - } - gui.GitCommand = newGitCommand - return gui.Errors.ErrSwitchRepo -} - func (gui *Gui) promptAnonymousReporting() error { return gui.createConfirmationPanel(gui.g, nil, gui.Tr.SLocalize("AnonymousReportingTitle"), gui.Tr.SLocalize("AnonymousReportingPrompt"), func(g *gocui.Gui, v *gocui.View) error { return gui.Config.WriteToUserConfig("reporting", "on") diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 73f4aabe5..1073da68a 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -116,7 +116,7 @@ func (gui *Gui) GetKeybindings() []*Binding { ViewName: "status", Key: 's', Modifier: gocui.ModNone, - Handler: gui.handleSwitchRepo, + Handler: gui.handleCreateRecentReposMenu, Description: gui.Tr.SLocalize("SwitchRepo"), }, { diff --git a/pkg/gui/options_menu_panel.go b/pkg/gui/options_menu_panel.go index 2da002b43..ac01ad03d 100644 --- a/pkg/gui/options_menu_panel.go +++ b/pkg/gui/options_menu_panel.go @@ -33,11 +33,11 @@ func (gui *Gui) getBindings(v *gocui.View) []*Binding { func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error { bindings := gui.getBindings(v) - handleOptionsMenuPress := func(index int) error { + handleMenuPress := func(index int) error { if bindings[index].Key == nil { return nil } - if index <= len(bindings) { + if index >= len(bindings) { return errors.New("Index is greater than size of bindings") } err := gui.handleMenuClose(g, v) @@ -47,5 +47,5 @@ func (gui *Gui) handleCreateOptionsMenu(g *gocui.Gui, v *gocui.View) error { return bindings[index].Handler(g, v) } - return gui.createMenu(bindings, handleOptionsMenuPress) + return gui.createMenu(bindings, handleMenuPress) } diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go new file mode 100644 index 000000000..add987b32 --- /dev/null +++ b/pkg/gui/recent_repos_panel.go @@ -0,0 +1,69 @@ +package gui + +import ( + "os" + "path/filepath" + + "github.com/fatih/color" + "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/commands" + "github.com/jesseduffield/lazygit/pkg/utils" +) + +type recentRepo struct { + path string +} + +func (r *recentRepo) GetDisplayStrings() []string { + yellow := color.New(color.FgMagenta) + base := filepath.Base(r.path) + path := yellow.Sprint(r.path) + return []string{base, path} +} + +func (gui *Gui) handleCreateRecentReposMenu(g *gocui.Gui, v *gocui.View) error { + recentRepoPaths := gui.Config.GetAppState().RecentRepos + reposCount := utils.Min(len(recentRepoPaths), 20) + // we won't show the current repo hence the -1 + recentRepos := make([]*recentRepo, reposCount-1) + for i, path := range recentRepoPaths[1:reposCount] { + recentRepos[i] = &recentRepo{path: path} + } + + handleMenuPress := func(index int) error { + repo := recentRepos[index] + if err := os.Chdir(repo.path); err != nil { + return err + } + newGitCommand, err := commands.NewGitCommand(gui.Log, gui.OSCommand, gui.Tr) + if err != nil { + return err + } + gui.GitCommand = newGitCommand + return gui.Errors.ErrSwitchRepo + } + + return gui.createMenu(recentRepos, handleMenuPress) +} + +// updateRecentRepoList registers the fact that we opened lazygit in this repo, +// so that we can open the same repo via the 'recent repos' menu +func (gui *Gui) updateRecentRepoList() error { + recentRepos := gui.Config.GetAppState().RecentRepos + currentRepo, err := os.Getwd() + if err != nil { + return err + } + gui.Config.GetAppState().RecentRepos = newRecentReposList(recentRepos, currentRepo) + return gui.Config.SaveAppState() +} + +func newRecentReposList(recentRepos []string, currentRepo string) []string { + newRepos := []string{currentRepo} + for _, repo := range recentRepos { + if repo != currentRepo { + newRepos = append(newRepos, repo) + } + } + return newRepos +} diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 5843b5760..b8be4cfc6 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -392,7 +392,7 @@ func addEnglish(i18nObject *i18n.Bundle) error { Other: `Are you sure you want to quit?`, }, &i18n.Message{ ID: "SwitchRepo", - Other: `Switch to a recent repo`, + Other: `switch to a recent repo`, }, ) } From fcaf4e339c4e76aafb9e89114a7eabe1bcbe0362 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:16:55 +1000 Subject: [PATCH 27/36] fix specs --- pkg/commands/git.go | 4 ++-- pkg/commands/git_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 2a127d407..c37ca9b47 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -231,9 +231,9 @@ func (c *GitCommand) UpstreamDifferenceCount() (string, string) { return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) } -// getCommitsToPush Returns the sha's of the commits that have not yet been pushed +// GetCommitsToPush Returns the sha's of the commits that have not yet been pushed // to the remote branch of the current branch, a map is returned to ease look up -func (c *GitCommand) getCommitsToPush() map[string]bool { +func (c *GitCommand) GetCommitsToPush() map[string]bool { pushables := map[string]bool{} o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit") if err != nil { diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 6d19a1629..6e3b81bf5 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -631,7 +631,7 @@ func TestGitCommandGetCommitsToPush(t *testing.T) { t.Run(s.testName, func(t *testing.T) { gitCmd := newDummyGitCommand() gitCmd.OSCommand.command = s.command - s.test(gitCmd.getCommitsToPush()) + s.test(gitCmd.GetCommitsToPush()) }) } } From 64f0eeb42e8c19251f0ca823e43c62468066474c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:23:31 +1000 Subject: [PATCH 28/36] fix specs --- pkg/commands/git_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 6e3b81bf5..790a34690 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1225,7 +1225,7 @@ func TestGitCommandRemoveFile(t *testing.T) { testName string command func() (func(string, ...string) *exec.Cmd, *[][]string) test func(*[][]string, error) - file File + file *File removeFile func(string) error } @@ -1247,7 +1247,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"reset", "--", "test"}, }) }, - File{ + &File{ Name: "test", HasStagedChanges: true, }, @@ -1270,7 +1270,7 @@ func TestGitCommandRemoveFile(t *testing.T) { assert.EqualError(t, err, "an error occurred when removing file") assert.Len(t, *cmdsCalled, 0) }, - File{ + &File{ Name: "test", Tracked: false, }, @@ -1295,7 +1295,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"checkout", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: true, HasStagedChanges: false, @@ -1321,7 +1321,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"checkout", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: true, HasStagedChanges: false, @@ -1348,7 +1348,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"checkout", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: true, HasStagedChanges: true, @@ -1374,7 +1374,7 @@ func TestGitCommandRemoveFile(t *testing.T) { {"reset", "--", "test"}, }) }, - File{ + &File{ Name: "test", Tracked: false, HasStagedChanges: true, @@ -1398,7 +1398,7 @@ func TestGitCommandRemoveFile(t *testing.T) { assert.NoError(t, err) assert.Len(t, *cmdsCalled, 0) }, - File{ + &File{ Name: "test", Tracked: false, HasStagedChanges: false, @@ -1484,7 +1484,7 @@ func TestGitCommandGetCommits(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]Commit) + test func([]*Commit) } scenarios := []scenario{ @@ -1504,7 +1504,7 @@ func TestGitCommandGetCommits(t *testing.T) { return nil }, - func(commits []Commit) { + func(commits []*Commit) { assert.Len(t, commits, 0) }, }, @@ -1524,9 +1524,9 @@ func TestGitCommandGetCommits(t *testing.T) { return nil }, - func(commits []Commit) { + func(commits []*Commit) { assert.Len(t, commits, 2) - assert.EqualValues(t, []Commit{ + assert.EqualValues(t, []*Commit{ { Sha: "8a2bb0e", Name: "commit 1", From 5a76b579528bb90b5aff388f8aa68594c22a03c3 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:31:29 +1000 Subject: [PATCH 29/36] one more spec to increase coverage --- pkg/utils/utils_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 76a9f8b95..918031512 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -346,3 +346,33 @@ func TestGetPadWidths(t *testing.T) { assert.EqualValues(t, s.expected, getPadWidths(s.stringArrays)) } } + +func TestMin(t *testing.T) { + type scenario struct { + a int + b int + expected int + } + + scenarios := []scenario{ + { + 1, + 1, + 1, + }, + { + 1, + 2, + 1, + }, + { + 2, + 1, + 1, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, Min(s.a, s.b)) + } +} From 0367399cf3368c6e05b9187ac8a9c994eaf2fc9c Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 19:54:58 +1000 Subject: [PATCH 30/36] bump deps to use forked termbox which doesn't crash as easily --- Gopkg.lock | 20 +++---- go.mod | 4 +- go.sum | 54 ++----------------- .../jesseduffield/gocui/attribute.go | 2 +- vendor/github.com/jesseduffield/gocui/gui.go | 2 +- .../jesseduffield/gocui/keybinding.go | 2 +- vendor/github.com/jesseduffield/gocui/view.go | 6 ++- .../{nsf => jesseduffield}/termbox-go/AUTHORS | 0 .../{nsf => jesseduffield}/termbox-go/LICENSE | 0 .../{nsf => jesseduffield}/termbox-go/api.go | 9 ++++ .../termbox-go/api_common.go | 0 .../termbox-go/api_windows.go | 0 .../termbox-go/escwait.go | 0 .../termbox-go/escwait_darwin.go | 0 .../termbox-go/syscalls.go | 0 .../termbox-go/syscalls_darwin.go | 0 .../termbox-go/syscalls_darwin_amd64.go | 0 .../termbox-go/syscalls_dragonfly.go | 0 .../termbox-go/syscalls_freebsd.go | 0 .../termbox-go/syscalls_linux.go | 0 .../termbox-go/syscalls_netbsd.go | 0 .../termbox-go/syscalls_openbsd.go | 0 .../termbox-go/syscalls_windows.go | 0 .../termbox-go/termbox.go | 0 .../termbox-go/termbox_common.go | 0 .../termbox-go/termbox_windows.go | 0 .../termbox-go/terminfo.go | 0 .../termbox-go/terminfo_builtin.go | 0 28 files changed, 34 insertions(+), 65 deletions(-) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/AUTHORS (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/LICENSE (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/api.go (98%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/api_common.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/api_windows.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/escwait.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/escwait_darwin.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_darwin.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_darwin_amd64.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_dragonfly.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_freebsd.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_linux.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_netbsd.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_openbsd.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/syscalls_windows.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/termbox.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/termbox_common.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/termbox_windows.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/terminfo.go (100%) rename vendor/github.com/{nsf => jesseduffield}/termbox-go/terminfo_builtin.go (100%) diff --git a/Gopkg.lock b/Gopkg.lock index 2bbaa2118..540b26052 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,11 +189,19 @@ [[projects]] branch = "master" - digest = "1:71e6c15797951d3fabaa944d70253e36a6cee96bf54ca0bc43ca3de3b4814bbb" + digest = "1:d6df25dee1274e63c25f84c257bc504359f975dedb791eec7a5b51992146bb76" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "2cb6e95bbbf850bb32cc1799e07d08ff0f144746" + revision = "4fca348422d8b6136e801b222858204a35ee369a" + +[[projects]] + branch = "master" + digest = "1:3ab130f65766f5b7cc944d557df31c6a007ec017151705ec1e1b8719f2689021" + name = "github.com/jesseduffield/termbox-go" + packages = ["."] + pruneopts = "NUT" + revision = "1e272ff78dcb4c448870f464fda1cdcf2bf0b3dd" [[projects]] digest = "1:ac6d01547ec4f7f673311b4663909269bfb8249952de3279799289467837c3cc" @@ -294,14 +302,6 @@ revision = "a16b91a3ba80db3a2301c70d1d302d42251c9079" version = "v2.0.0-beta.5" -[[projects]] - branch = "master" - digest = "1:34d9354c2c5d916c05864327553047df59fc10e86ff1f408e4136eba0a25a5ec" - name = "github.com/nsf/termbox-go" - packages = ["."] - pruneopts = "NUT" - revision = "5c94acc5e6eb520f1bcd183974e01171cc4c23b3" - [[projects]] digest = "1:cf254277d898b713195cc6b4a3fac8bf738b9f1121625df27843b52b267eec6c" name = "github.com/pelletier/go-buffruneio" diff --git a/go.mod b/go.mod index 02db8a03e..61692f46e 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,8 @@ require ( 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/jesseduffield/gocui v0.0.0-20180919095827-4fca348422d8 + github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb 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 @@ -31,7 +32,6 @@ require ( 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 diff --git a/go.sum b/go.sum index e1d3249fa..1506becb9 100644 --- a/go.sum +++ b/go.sum @@ -1,38 +1,22 @@ -github.com/BurntSushi/toml v0.3.0 h1:e1/Ivsx3Z0FVTV0NSOv/aVgbUWyQuzj7DDnFblkRvsY= github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/aws/aws-sdk-go v1.15.21 h1:STLvc6RrpycslC1NRtTvt/YSgDkIGCTrB9K9vE5R2oQ= github.com/aws/aws-sdk-go v1.15.21/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do= github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.9.0 h1:rUF4PuzEjMChMiNsVjdI+SyLu7rEqpQ5reNFnhC7oFo= github.com/emirpasic/gods v1.9.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gliderlabs/ssh v0.1.1 h1:j3L6gSLQalDETeEg/Jg0mGY0/y/N6zI2xX1978P0Uqw= -github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-ini/ini v1.38.2 h1:6Hl/z3p3iFkA0dlDfzYxuFuUGD+kaweypF6btsR2/Q4= github.com/go-ini/ini v1.38.2/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c h1:16eHWuMGvCjSfgRJKqIzapE78onvvTbdi1rMkU00lZw= -github.com/gopherjs/gopherjs v0.0.0-20180825215210-0210a2f0f73c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186 h1:URgjUo+bs1KwatoNbwG0uCO4dHN4r1jsp4a5AGgHRjo= github.com/hashicorp/go-cleanhttp v0.0.0-20171218145408-d5fe4b57a186/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-getter v0.0.0-20180809191950-4bda8fa99001 h1:MFPzqpPED05pFyGjNPJEC2sXM6EHTzFyvX+0s0JoZ48= @@ -45,18 +29,16 @@ github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGE github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331 h1:qio0y/sQdhbHRA3cmgczo04MaSV2zw+n46G1owvgWIk= github.com/heroku/rollrus v0.0.0-20180515183152-fc0cef2ff331/go.mod h1:BT+PgT529opmb6mcUY+Fg0IwVRRmwqFyavEMU17GnBg= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63 h1:Nrr/yUxNjXWYK0B3IqcFlYh1ICnesJDB4ogcfOVc5Ns= github.com/jesseduffield/go-getter v0.0.0-20180822080847-906e15686e63/go.mod h1:fNqjRf+4XnTo2PrGN1JRb79b/BeoHwP4lU00f39SQY0= -github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8 h1:YMYnpIu5HUJfx/yfIwnZhFrgWTg51FQWtvZi+PMzQm8= -github.com/jesseduffield/gocui v0.0.0-20180905104005-2cb6e95bbbf8/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw= +github.com/jesseduffield/gocui v0.0.0-20180919095827-4fca348422d8 h1:XxX+IqNOFDh1PnU4eZDzUomoKbuKCvwyEm5an/IxLQU= +github.com/jesseduffield/gocui v0.0.0-20180919095827-4fca348422d8/go.mod h1:2RtZznzYKt8RLRwvFiSkXjU0Ei8WwHdubgnlaYH47dw= +github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb h1:cFHYEWpQEfzFZVKiKZytCUX4UwQixKSw0kd3WhluPsY= +github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jtolds/gls v4.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= -github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro= github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kevinburke/ssh_config v0.0.0-20180317175531-9fc7bb800b55 h1:S38dC4mEwxdw/U41+97VWdbun8mTcTjwg5Ujfg8QPME= @@ -77,22 +59,16 @@ github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77 h1: github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699 h1:KXZJFdun9knAVAR8tg/aHJEr5DgtcbqyvzacK+CDCaI= github.com/mitchellh/mapstructure v0.0.0-20180715050151-f15292f7a699/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80 h1:7ory6RlsEkeK89iyV7Imz3sVz8YHeSw29w3PehpCWC0= github.com/nicksnyder/go-i18n v0.0.0-20180803040939-a16b91a3ba80/go.mod h1:e4Di5xjP9oTVrC6y3C7C0HoSYXjSbhh/dU0eUV32nB4= github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5 h1:/TjjTS4kg7vC+05gD0LE4+97f/+PRFICnK/7wJPk7kE= github.com/nicksnyder/go-i18n/v2 v2.0.0-beta.5/go.mod h1:4Opqa6/HIv0lhG3WRAkqzO0afezkRhxXI0P8EJkqeRU= -github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb h1:YahEjAGkJtCrkqgVHhX6n8ZX+CZ3hDRL9fjLYugLfSs= -github.com/nsf/termbox-go v0.0.0-20180613055208-5c94acc5e6eb/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ= -github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U= -github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/pelletier/go-buffruneio v0.2.0 h1:U4t4R6YkofJ5xHm3dJzuRpPZ0mr5MMCoAWooScCR7aA= github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -100,10 +76,6 @@ github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0 h1:Xuk8ma/ibJ1 github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0/go.mod h1:7AwjWCpdPhkSmNAgUv5C7EJ4AbmjEB3r047r3DXWu3Y= github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s= github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf h1:6V1qxN6Usn4jy8unvggSJz/NC790tefw8Zdy6OZS5co= -github.com/smartystreets/assertions v0.0.0-20180820201707-7c9eb446e3cf/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo= -github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/spf13/afero v1.1.1 h1:Lt3ihYMlE+lreX1GS4Qw4ZsNpYQLxIXKBTEOXm3nt6I= github.com/spf13/afero v1.1.1/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= @@ -118,7 +90,6 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/src-d/gcfg v1.3.0 h1:2BEDr8r0I0b8h/fOqwtxCEiq2HJu8n2JGZJQFGXWLjg= github.com/src-d/gcfg v1.3.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea h1:jysxIKov/4GJ33wI2aRvuIK7yBwB28E5almlgDLPRpM= github.com/stvp/roll v0.0.0-20170522205222-3627a5cbeaea/go.mod h1:Ffmqrj3nXIMIjeA4uW3Qjj0Ud9eDoTG0fu4JxyAr/tE= @@ -132,31 +103,16 @@ golang.org/x/crypto v0.0.0-20180808211826-de0752318171 h1:vYogbvSFj2YXcjQxFHu/rA golang.org/x/crypto v0.0.0-20180808211826-de0752318171/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/net v0.0.0-20180811021610-c39426892332 h1:efGso+ep0DjyCBJPjvoz0HI6UldX4Md2F1rZFe1ir0E= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0 h1:8H8QZJ30plJyIVj60H3lr8TZGIq2Fh3Cyrs/ZNg1foU= golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.0.0-20171214130843-f21a4dfb5e38/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/ini.v1 v1.38.2 h1:dGcbywv4RufeGeiMycPT/plKB5FtmLKLnWKwBiLhUA4= -gopkg.in/ini.v1 v1.38.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/src-d/go-billy.v4 v4.2.0 h1:VGbrP1EsYxtvVPEiHui+4//imr4E5MGEFLx66bQtusg= gopkg.in/src-d/go-billy.v4 v4.2.0/go.mod h1:ZHSF0JP+7oD97194otDUCD7Ofbk63+xFcfWP5bT6h+Q= -gopkg.in/src-d/go-git-fixtures.v3 v3.1.1 h1:XWW/s5W18RaJpmo1l0IYGqXKuJITWRFuA45iOf1dKJs= -gopkg.in/src-d/go-git-fixtures.v3 v3.1.1/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714 h1:+wM2BGgQ1znCKBexOB4OrGVSDw8mtKNUSq3wqxZhi/k= gopkg.in/src-d/go-git.v4 v4.0.0-20180807092216-43d17e14b714/go.mod h1:CzbUWqMn4pvmvndg3gnh5iZFmSsbhyhUWdI0IQ60AQo= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= diff --git a/vendor/github.com/jesseduffield/gocui/attribute.go b/vendor/github.com/jesseduffield/gocui/attribute.go index bad758a1d..5b46b1427 100644 --- a/vendor/github.com/jesseduffield/gocui/attribute.go +++ b/vendor/github.com/jesseduffield/gocui/attribute.go @@ -4,7 +4,7 @@ package gocui -import "github.com/nsf/termbox-go" +import "github.com/jesseduffield/termbox-go" // Attribute represents a terminal attribute, like color, font style, etc. They // can be combined using bitwise OR (|). Note that it is not possible to diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index 4393c06c4..cb163e06d 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -7,7 +7,7 @@ package gocui import ( "errors" - "github.com/nsf/termbox-go" + "github.com/jesseduffield/termbox-go" ) var ( diff --git a/vendor/github.com/jesseduffield/gocui/keybinding.go b/vendor/github.com/jesseduffield/gocui/keybinding.go index 82d1acc9f..65d9ec6cf 100644 --- a/vendor/github.com/jesseduffield/gocui/keybinding.go +++ b/vendor/github.com/jesseduffield/gocui/keybinding.go @@ -4,7 +4,7 @@ package gocui -import "github.com/nsf/termbox-go" +import "github.com/jesseduffield/termbox-go" // Keybidings are used to link a given key-press event with a handler. type keybinding struct { diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 53ab06f8c..46b86ec2a 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -10,8 +10,8 @@ import ( "io" "strings" + "github.com/jesseduffield/termbox-go" "github.com/mattn/go-runewidth" - "github.com/nsf/termbox-go" ) // Constants for overlapping edges @@ -447,6 +447,10 @@ func (v *View) ViewBufferLines() []string { return lines } +func (v *View) ViewLinesHeight() int { + return len(v.viewLines) +} + // ViewBuffer returns a string with the contents of the view's buffer that is // shown to the user. func (v *View) ViewBuffer() string { diff --git a/vendor/github.com/nsf/termbox-go/AUTHORS b/vendor/github.com/jesseduffield/termbox-go/AUTHORS similarity index 100% rename from vendor/github.com/nsf/termbox-go/AUTHORS rename to vendor/github.com/jesseduffield/termbox-go/AUTHORS diff --git a/vendor/github.com/nsf/termbox-go/LICENSE b/vendor/github.com/jesseduffield/termbox-go/LICENSE similarity index 100% rename from vendor/github.com/nsf/termbox-go/LICENSE rename to vendor/github.com/jesseduffield/termbox-go/LICENSE diff --git a/vendor/github.com/nsf/termbox-go/api.go b/vendor/github.com/jesseduffield/termbox-go/api.go similarity index 98% rename from vendor/github.com/nsf/termbox-go/api.go rename to vendor/github.com/jesseduffield/termbox-go/api.go index d530ab5c5..4c828e482 100644 --- a/vendor/github.com/nsf/termbox-go/api.go +++ b/vendor/github.com/jesseduffield/termbox-go/api.go @@ -317,6 +317,9 @@ func PollEvent() Event { event.Type = EventKey status := extract_event(inbuf, &event, true) if event.N != 0 { + if event.N > len(inbuf) { + event.N = len(inbuf) + } copy(inbuf, inbuf[event.N:]) inbuf = inbuf[:len(inbuf)-event.N] } @@ -345,6 +348,9 @@ func PollEvent() Event { input_comm <- ev status := extract_event(inbuf, &event, true) if event.N != 0 { + if event.N > len(inbuf) { + event.N = len(inbuf) + } copy(inbuf, inbuf[event.N:]) inbuf = inbuf[:len(inbuf)-event.N] } @@ -359,6 +365,9 @@ func PollEvent() Event { status := extract_event(inbuf, &event, false) if event.N != 0 { + if event.N > len(inbuf) { + event.N = len(inbuf) + } copy(inbuf, inbuf[event.N:]) inbuf = inbuf[:len(inbuf)-event.N] } diff --git a/vendor/github.com/nsf/termbox-go/api_common.go b/vendor/github.com/jesseduffield/termbox-go/api_common.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/api_common.go rename to vendor/github.com/jesseduffield/termbox-go/api_common.go diff --git a/vendor/github.com/nsf/termbox-go/api_windows.go b/vendor/github.com/jesseduffield/termbox-go/api_windows.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/api_windows.go rename to vendor/github.com/jesseduffield/termbox-go/api_windows.go diff --git a/vendor/github.com/nsf/termbox-go/escwait.go b/vendor/github.com/jesseduffield/termbox-go/escwait.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/escwait.go rename to vendor/github.com/jesseduffield/termbox-go/escwait.go diff --git a/vendor/github.com/nsf/termbox-go/escwait_darwin.go b/vendor/github.com/jesseduffield/termbox-go/escwait_darwin.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/escwait_darwin.go rename to vendor/github.com/jesseduffield/termbox-go/escwait_darwin.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls.go b/vendor/github.com/jesseduffield/termbox-go/syscalls.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_darwin.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_darwin.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_darwin.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_darwin.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_darwin_amd64.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_darwin_amd64.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_darwin_amd64.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_dragonfly.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_dragonfly.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_dragonfly.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_freebsd.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_freebsd.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_freebsd.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_freebsd.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_linux.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_linux.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_linux.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_linux.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_netbsd.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_netbsd.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_netbsd.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_netbsd.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_openbsd.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_openbsd.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_openbsd.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_openbsd.go diff --git a/vendor/github.com/nsf/termbox-go/syscalls_windows.go b/vendor/github.com/jesseduffield/termbox-go/syscalls_windows.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/syscalls_windows.go rename to vendor/github.com/jesseduffield/termbox-go/syscalls_windows.go diff --git a/vendor/github.com/nsf/termbox-go/termbox.go b/vendor/github.com/jesseduffield/termbox-go/termbox.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/termbox.go rename to vendor/github.com/jesseduffield/termbox-go/termbox.go diff --git a/vendor/github.com/nsf/termbox-go/termbox_common.go b/vendor/github.com/jesseduffield/termbox-go/termbox_common.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/termbox_common.go rename to vendor/github.com/jesseduffield/termbox-go/termbox_common.go diff --git a/vendor/github.com/nsf/termbox-go/termbox_windows.go b/vendor/github.com/jesseduffield/termbox-go/termbox_windows.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/termbox_windows.go rename to vendor/github.com/jesseduffield/termbox-go/termbox_windows.go diff --git a/vendor/github.com/nsf/termbox-go/terminfo.go b/vendor/github.com/jesseduffield/termbox-go/terminfo.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/terminfo.go rename to vendor/github.com/jesseduffield/termbox-go/terminfo.go diff --git a/vendor/github.com/nsf/termbox-go/terminfo_builtin.go b/vendor/github.com/jesseduffield/termbox-go/terminfo_builtin.go similarity index 100% rename from vendor/github.com/nsf/termbox-go/terminfo_builtin.go rename to vendor/github.com/jesseduffield/termbox-go/terminfo_builtin.go From b9708c9f8850352d18ef4d10c285698be882f838 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 20:36:40 +1000 Subject: [PATCH 31/36] fix issues with commit message panel losing focus --- pkg/gui/commit_message_panel.go | 4 ++++ pkg/gui/view_helpers.go | 9 ++++++++- pkg/utils/utils.go | 10 ++++++++++ pkg/utils/utils_test.go | 35 +++++++++++++++++++++++++++++++++ 4 files changed, 57 insertions(+), 1 deletion(-) diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index e23c47da0..74e02be90 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -37,6 +37,10 @@ func (gui *Gui) handleCommitClose(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleCommitFocused(g *gocui.Gui, v *gocui.View) error { + if _, err := g.SetViewOnTop("commitMessage"); err != nil { + return err + } + message := gui.Tr.TemplateLocalize( "CloseConfirm", Teml{ diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 5178bd4d9..96a77f3d6 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -133,8 +133,15 @@ func (gui *Gui) switchFocus(g *gocui.Gui, oldView, newView *gocui.View) error { }, ) gui.Log.Info(message) - gui.State.PreviousView = oldView.Name() + + // second class panels should never have focus restored to them because + // once they lose focus they are effectively 'destroyed' + secondClassPanels := []string{"confirmation", "menu"} + if !utils.IncludesString(secondClassPanels, oldView.Name()) { + gui.State.PreviousView = oldView.Name() + } } + newView.Highlight = true message := gui.Tr.TemplateLocalize( "newFocusedViewIs", diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index aef653c50..8e481b3a4 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -204,3 +204,13 @@ func getDisplayStringArrays(displayables []Displayable) [][]string { } return stringArrays } + +// IncludesString if the list contains the string +func IncludesString(list []string, a string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 918031512..21e1d5031 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -376,3 +376,38 @@ func TestMin(t *testing.T) { assert.EqualValues(t, s.expected, Min(s.a, s.b)) } } + +func TestIncludesString(t *testing.T) { + type scenario struct { + list []string + element string + expected bool + } + + scenarios := []scenario{ + { + []string{"a", "b"}, + "a", + true, + }, + { + []string{"a", "b"}, + "c", + false, + }, + { + []string{"a", "b"}, + "", + false, + }, + { + []string{""}, + "", + true, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, IncludesString(s.list, s.element)) + } +} From 70ee4faf15a15ebb8ae28db70fa52dd2e33f6c1e Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 20 Sep 2018 09:48:56 +1000 Subject: [PATCH 32/36] add removeAll to git --- pkg/commands/git.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index c37ca9b47..4163b46e9 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -101,6 +101,7 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer) Repo: repo, getGlobalGitConfig: gitconfig.Global, getLocalGitConfig: gitconfig.Local, + removeFile: os.RemoveAll, }, nil } From 227067fdd11669bd4f991af5d139659df7fc5921 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 22 Sep 2018 13:44:48 +1000 Subject: [PATCH 33/36] use lineheight rather than buffer length --- Gopkg.lock | 4 ++-- go.mod | 2 +- vendor/github.com/jesseduffield/gocui/view.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 540b26052..4b84d6537 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,11 +189,11 @@ [[projects]] branch = "master" - digest = "1:d6df25dee1274e63c25f84c257bc504359f975dedb791eec7a5b51992146bb76" + digest = "1:66bb9b4a5abb704642fccba52a84a7f7feef2d9623f87b700e52a6695044723f" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "4fca348422d8b6136e801b222858204a35ee369a" + revision = "03e26ff3f1de2c1bc2205113c3aba661312eee00" [[projects]] branch = "master" diff --git a/go.mod b/go.mod index 61692f46e..bfa4f7cf4 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( 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-20180919095827-4fca348422d8 + github.com/jesseduffield/gocui v0.0.0-20180921065632-03e26ff3f1de github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 46b86ec2a..939d1bdfa 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -447,8 +447,8 @@ func (v *View) ViewBufferLines() []string { return lines } -func (v *View) ViewLinesHeight() int { - return len(v.viewLines) +func (v *View) LinesHeight() int { + return len(v.lines) } // ViewBuffer returns a string with the contents of the view's buffer that is From 619c28ce56c50b6472d75158211f4bc6141b1bfd Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 22 Sep 2018 13:44:48 +1000 Subject: [PATCH 34/36] use lineheight rather than buffer length --- Gopkg.lock | 4 ++-- go.mod | 2 +- pkg/gui/view_helpers.go | 4 ++-- vendor/github.com/jesseduffield/gocui/view.go | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 540b26052..4b84d6537 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -189,11 +189,11 @@ [[projects]] branch = "master" - digest = "1:d6df25dee1274e63c25f84c257bc504359f975dedb791eec7a5b51992146bb76" + digest = "1:66bb9b4a5abb704642fccba52a84a7f7feef2d9623f87b700e52a6695044723f" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "4fca348422d8b6136e801b222858204a35ee369a" + revision = "03e26ff3f1de2c1bc2205113c3aba661312eee00" [[projects]] branch = "master" diff --git a/go.mod b/go.mod index 61692f46e..bfa4f7cf4 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( 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-20180919095827-4fca348422d8 + github.com/jesseduffield/gocui v0.0.0-20180921065632-03e26ff3f1de github.com/jesseduffield/termbox-go v0.0.0-20180919093808-1e272ff78dcb github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index 5178bd4d9..e9362c41f 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -183,7 +183,7 @@ func (gui *Gui) cursorDown(g *gocui.Gui, v *gocui.View) error { } cx, cy := v.Cursor() ox, oy := v.Origin() - ly := len(v.BufferLines()) - 1 + ly := v.LinesHeight() - 1 _, height := v.Size() maxY := height - 1 @@ -219,7 +219,7 @@ func (gui *Gui) correctCursor(v *gocui.View) error { ox, oy := v.Origin() _, height := v.Size() maxY := height - 1 - ly := len(v.BufferLines()) - 1 + ly := v.LinesHeight() - 1 if oy+cy <= ly { return nil } diff --git a/vendor/github.com/jesseduffield/gocui/view.go b/vendor/github.com/jesseduffield/gocui/view.go index 46b86ec2a..939d1bdfa 100644 --- a/vendor/github.com/jesseduffield/gocui/view.go +++ b/vendor/github.com/jesseduffield/gocui/view.go @@ -447,8 +447,8 @@ func (v *View) ViewBufferLines() []string { return lines } -func (v *View) ViewLinesHeight() int { - return len(v.viewLines) +func (v *View) LinesHeight() int { + return len(v.lines) } // ViewBuffer returns a string with the contents of the view's buffer that is From 4e0d0f7d75642472abc10d500989716bbd974f41 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 23 Sep 2018 13:09:54 +1000 Subject: [PATCH 35/36] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 4f40e8f8e..e20de5900 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,11 @@ For contributor discussion about things not better discussed here in the repo, j [![Slack](/docs/resources/slack_rgb.png)](https://join.slack.com/t/lazygit/shared_invite/enQtNDE3MjIwNTYyMDA0LTM3Yjk3NzdiYzhhNTA1YjM4Y2M4MWNmNDBkOTI0YTE4YjQ1ZmI2YWRhZTgwNjg2YzhhYjg3NDBlMmQyMTI5N2M) +## Donate +If you would like to support the development of lazygit, please donate + +[![Donate](https://d1iczxrky3cnb2.cloudfront.net/button-medium-blue.png)](https://donorbox.org/lazygit) + ## Work in progress This is still a work in progress so there's still bugs to iron out and as this is my first project in Go the code could no doubt use an increase in quality, From 3d751c03fe34ac8547dfd0e39d8964129ec4c095 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sun, 23 Sep 2018 14:13:10 +1000 Subject: [PATCH 36/36] add donation link to status panel --- pkg/gui/status_panel.go | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pkg/gui/status_panel.go b/pkg/gui/status_panel.go index 6948e9678..89b3e997e 100644 --- a/pkg/gui/status_panel.go +++ b/pkg/gui/status_panel.go @@ -2,6 +2,7 @@ package gui import ( "fmt" + "strings" "github.com/fatih/color" "github.com/jesseduffield/gocui" @@ -51,14 +52,16 @@ func (gui *Gui) handleCheckForUpdate(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleStatusSelect(g *gocui.Gui, v *gocui.View) error { - dashboardString := fmt.Sprintf( - "%s\n\n%s\n\n%s\n\n%s\n\n%s", - lazygitTitle(), - "Keybindings: https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md", - "Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md", - "Tutorial: https://www.youtube.com/watch?v=VDXvbHZYeKY", - "Raise an Issue: https://github.com/jesseduffield/lazygit/issues", - ) + dashboardString := strings.Join( + []string{ + lazygitTitle(), + "Copyright (c) 2018 Jesse Duffield", + "Keybindings: https://github.com/jesseduffield/lazygit/blob/master/docs/Keybindings.md", + "Config Options: https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md", + "Tutorial: https://www.youtube.com/watch?v=VDXvbHZYeKY", + "Raise an Issue: https://github.com/jesseduffield/lazygit/issues", + "Buy Jesse a coffee: https://donorbox.org/lazygit", + }, "\n\n") if err := gui.renderString(g, "main", dashboardString); err != nil { return err