From ca715c5b23fdc20ad9b3dd983814ab9225c5fdbc Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 7 Sep 2018 09:41:15 +1000 Subject: [PATCH 01/47] 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/47] 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/47] 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/47] 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/47] 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/47] 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 61f0801bd366e959437676ae72b2d9fe98c4b8af Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Wed, 12 Sep 2018 15:20:35 +0200 Subject: [PATCH 07/47] Add ammend commit action. --- pkg/commands/git.go | 8 ++++++-- pkg/gui/commit_message_panel.go | 3 ++- pkg/gui/files_panel.go | 27 +++++++++++++++++++++++++++ pkg/gui/keybindings.go | 7 +++++++ pkg/i18n/dutch.go | 3 +++ pkg/i18n/english.go | 3 +++ pkg/i18n/polish.go | 3 +++ 7 files changed, 51 insertions(+), 3 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 5744fa6aa..a6f9e2f9c 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -299,8 +299,12 @@ func (c *GitCommand) usingGpg() bool { } // Commit commits to git -func (c *GitCommand) Commit(message string) (*exec.Cmd, error) { - command := fmt.Sprintf("git commit -m %s", c.OSCommand.Quote(message)) +func (c *GitCommand) Commit(message string, amend bool) (*exec.Cmd, error) { + amendParam := "" + if amend { + amendParam = "--amend " + } + command := fmt.Sprintf("git commit %s-m %s", amendParam, c.OSCommand.Quote(message)) if c.usingGpg() { return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil } diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index e23c47da0..2c3d086c7 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -12,7 +12,8 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { if message == "" { return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr")) } - sub, err := gui.GitCommand.Commit(message) + amendCommit := v.Title == gui.Tr.SLocalize("AmendLastCommit") + sub, err := gui.GitCommand.Commit(message, amendCommit) if err != nil { // TODO need to find a way to send through this error if err != gui.Errors.ErrSubProcess { diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index bfcc938bb..235cb12d8 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -198,7 +198,14 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) } + commitMessageView := gui.getCommitMessageView(g) + if commitMessageView.Title == gui.Tr.SLocalize("AmendLastCommit") { + commitMessageView.Clear() + commitMessageView.SetCursor(0, 0) + } + + commitMessageView.Title = gui.Tr.SLocalize("CommitMessage") g.Update(func(g *gocui.Gui) error { g.SetViewOnTop("commitMessage") gui.switchFocus(g, filesView, commitMessageView) @@ -208,6 +215,26 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { return nil } +func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) error { + if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) + } + commitMessageView := gui.getCommitMessageView(g) + commitMessageView.Clear() + commitMessageView.Title = gui.Tr.SLocalize("AmendLastCommit") + + g.Update(func(g *gocui.Gui) error { + g.SetViewOnTop("commitMessage") + gui.switchFocus(g, filesView, commitMessageView) + lastCommitName := gui.State.Commits[0].Name + commitMessageView.Write([]byte(lastCommitName)) + commitMessageView.SetCursor(len(lastCommitName), 0) + gui.RenderCommitLength() + return nil + }) + return nil +} + // handleCommitEditorPress - handle when the user wants to commit changes via // their editor rather than via the popup panel func (gui *Gui) handleCommitEditorPress(g *gocui.Gui, filesView *gocui.View) error { diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index c8ed7d3c8..3eb8785d2 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -1,6 +1,7 @@ package gui import "github.com/jesseduffield/gocui" +import "strings" // 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 @@ -98,6 +99,12 @@ func (gui *Gui) GetKeybindings() []Binding { Modifier: gocui.ModNone, Handler: gui.handleCommitPress, Description: gui.Tr.SLocalize("CommitChanges"), + }, { + ViewName: "files", + Key: 'M', + Modifier: gocui.ModNone, + Handler: gui.handleAmendCommitPress, + Description: strings.ToLower(gui.Tr.SLocalize("AmendLastCommit")), }, { ViewName: "files", Key: 'C', diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index cd1282de2..ef10625d5 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -34,6 +34,9 @@ func addDutch(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "CommitChanges", Other: "Commit Veranderingen", + }, &i18n.Message{ + ID: "AmendLastCommit", + Other: "Wijzig laatste commit", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commit Veranderingen met de git editor", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 4f009c075..796e6db62 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -42,6 +42,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "CommitChanges", Other: "commit changes", + }, &i18n.Message{ + ID: "AmendLastCommit", + Other: "Amend last commit", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commit changes using git editor", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index c64ab87b8..d4f204a3f 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -32,6 +32,9 @@ func addPolish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "CommitChanges", Other: "commituj zmiany", + }, &i18n.Message{ + ID: "AmendLastCommit", + Other: "Zmień ostatnie zatwierdzenie", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commituj zmiany używając edytora z gita", From 91832f2c5e40b9ea00291ae82f66bd09585fa3bc Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Thu, 13 Sep 2018 21:27:35 +0200 Subject: [PATCH 08/47] 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 09/47] 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 10/47] 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 11/47] 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 12/47] 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 13/47] 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 14/47] 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 15/47] 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 16/47] 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 17/47] 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 18/47] 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 19/47] 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 20/47] 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 21/47] 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 c789bba673da883af4d812a85819d6309c915f34 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 18 Sep 2018 21:32:24 +1000 Subject: [PATCH 22/47] color merged and unmerged commits differently --- pkg/commands/git.go | 35 ++++++++++++++++++++++++++++++----- pkg/commands/git_structs.go | 1 + pkg/gui/commits_panel.go | 7 +++++-- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 0c7509b91..cbf7aa2b8 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -462,23 +462,48 @@ func includesString(list []string, a string) bool { return false } +func (c *GitCommand) getMergeBase() string { + output, err := c.OSCommand.RunCommandWithOutput("git merge-base HEAD master") // TODO: support develop as well + if err != nil { + c.Log.Error("Could not get merge base") + return "" + } + return output +} + // GetCommits obtains the commits of the current branch func (c *GitCommand) GetCommits() []Commit { 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 { + commits := make([]Commit, len(lines)) + for i, line := range lines { splitLine := strings.Split(line, " ") sha := splitLine[0] - pushed := includesString(pushables, sha) - commits = append(commits, Commit{ + pushed := !includesString(pushables, sha) + commits[i] = Commit{ Sha: sha, Name: strings.Join(splitLine[1:], " "), Pushed: pushed, DisplayString: strings.Join(splitLine, " "), - }) + } + } + commits = c.setCommitMergedStatuses(commits) + return commits +} + +func (c *GitCommand) setCommitMergedStatuses(commits []Commit) []Commit { + ancestor := c.getMergeBase() + if ancestor == "" { + return commits + } + passedAncestor := false + for i, commit := range commits { + if strings.HasPrefix(ancestor, commit.Sha) { + passedAncestor = true + } + commits[i].Merged = passedAncestor } return commits } diff --git a/pkg/commands/git_structs.go b/pkg/commands/git_structs.go index 2b3b3f2db..cb2d125fa 100644 --- a/pkg/commands/git_structs.go +++ b/pkg/commands/git_structs.go @@ -17,6 +17,7 @@ type Commit struct { Sha string Name string Pushed bool + Merged bool DisplayString string } diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 1c1662475..3d9b10058 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -17,12 +17,15 @@ func (gui *Gui) refreshCommits(g *gocui.Gui) error { } v.Clear() red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) + yellow := color.New(color.FgGreen) + green := color.New(color.FgYellow) white := color.New(color.FgWhite) shaColor := white for _, commit := range gui.State.Commits { - if commit.Pushed { + if !commit.Pushed { shaColor = red + } else if !commit.Merged { + shaColor = green } else { shaColor = yellow } From 950cfeff6f97d94b4ab5166214ea6cb5b38b77f0 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Wed, 19 Sep 2018 18:03:44 +1000 Subject: [PATCH 23/47] 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 24/47] 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 25/47] 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 26/47] 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 27/47] 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 28/47] 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 29/47] 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 30/47] 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 a2b3cd08234e933ed572f3d95d473e19bd6adc99 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 20 Sep 2018 09:48:56 +1000 Subject: [PATCH 31/47] 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 5bc33b2f5..b394753ca 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 9481920101c7f8e29875e6db2642fe2b317564f9 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Tue, 18 Sep 2018 20:47:40 +0200 Subject: [PATCH 32/47] commands/git : add test to GetLog --- pkg/commands/git_test.go | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 790a34690..ba2696e5d 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1553,6 +1553,48 @@ func TestGitCommandGetCommits(t *testing.T) { } } +func TestGitCommandGetLog(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(string) + } + + scenarios := []scenario{ + { + "Retrieves logs", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) + + return exec.Command("echo", "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line") + }, + func(output string) { + assert.EqualValues(t, "6f0b32f commands/git : add GetCommits tests refactor\n9d9d775 circle : remove new line\n", output) + }, + }, + { + "An error occurred when retrieving logs", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) + return exec.Command("test") + }, + func(output string) { + assert.Empty(t, output) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.GetLog()) + }) + } +} + func TestGitCommandDiff(t *testing.T) { gitCommand := newDummyGitCommand() assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) From bdeb78c9a04963847ffd90c2277b3a274e3ba309 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Tue, 18 Sep 2018 20:53:32 +0200 Subject: [PATCH 33/47] commands/git : returns an error instead of panicing --- pkg/commands/git.go | 8 ++------ pkg/commands/git_test.go | 13 +++++++++++++ pkg/gui/commits_panel.go | 5 ++++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index c37ca9b47..24dcc6894 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -499,12 +499,8 @@ func (c *GitCommand) Ignore(filename string) error { } // Show shows the diff of a commit -func (c *GitCommand) Show(sha string) string { - result, err := c.OSCommand.RunCommandWithOutput("git show --color " + sha) - if err != nil { - panic(err) - } - return result +func (c *GitCommand) Show(sha string) (string, error) { + return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git show --color %s", sha)) } // Diff returns the diff of a file diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index ba2696e5d..eb17103fc 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1421,6 +1421,19 @@ func TestGitCommandRemoveFile(t *testing.T) { } } +func TestGitCommandShow(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"show", "--color", "456abcde"}, args) + + return exec.Command("echo") + } + + _, err := gitCmd.Show("456abcde") + assert.NoError(t, err) +} + func TestGitCommandCheckout(t *testing.T) { type scenario struct { testName string diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 7c09559ff..91bb334ff 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -68,7 +68,10 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error { } return gui.renderString(g, "main", gui.Tr.SLocalize("NoCommitsThisBranch")) } - commitText := gui.GitCommand.Show(commit.Sha) + commitText, err := gui.GitCommand.Show(commit.Sha) + if err != nil { + return err + } return gui.renderString(g, "main", commitText) } From 360b7c1def1f5945f30d89e393bdd4233f0c3179 Mon Sep 17 00:00:00 2001 From: Anthony HAMON Date: Tue, 18 Sep 2018 21:09:51 +0200 Subject: [PATCH 34/47] commands/git : refactor test to Diff, refactor function --- pkg/commands/git.go | 4 +- pkg/commands/git_test.go | 130 +++++++++++++----------------------- test/repos/lots_of_diffs.sh | 35 ---------- 3 files changed, 50 insertions(+), 119 deletions(-) delete mode 100755 test/repos/lots_of_diffs.sh diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 24dcc6894..d9effbef5 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -506,15 +506,15 @@ func (c *GitCommand) Show(sha string) (string, error) { // Diff returns the diff of a file func (c *GitCommand) Diff(file *File) string { cachedArg := "" + trackedArg := "--" fileName := c.OSCommand.Quote(file.Name) if file.HasStagedChanges && !file.HasUnstagedChanges { cachedArg = "--cached" } - trackedArg := "--" if !file.Tracked && !file.HasStagedChanges { trackedArg = "--no-index /dev/null" } - command := fmt.Sprintf("%s %s %s %s", "git diff --color ", cachedArg, trackedArg, fileName) + command := fmt.Sprintf("git diff --color %s %s %s", cachedArg, trackedArg, fileName) // for now we assume an error means the file was deleted s, _ := c.OSCommand.RunCommandWithOutput(command) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index eb17103fc..e5fd539b9 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/jesseduffield/lazygit/pkg/i18n" - "github.com/jesseduffield/lazygit/pkg/test" "github.com/sirupsen/logrus" "github.com/stretchr/testify/assert" gogit "gopkg.in/src-d/go-git.v4" @@ -1609,96 +1608,63 @@ func TestGitCommandGetLog(t *testing.T) { } func TestGitCommandDiff(t *testing.T) { - gitCommand := newDummyGitCommand() - assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh")) + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + file *File + } - files := []*File{ + scenarios := []scenario{ { - Name: "deleted_staged", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: true, - Deleted: true, - HasMergeConflicts: false, - DisplayString: " D deleted_staged", + "Default case", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"diff", "--color", "--", "test.txt"}, args) + + return exec.Command("echo") + }, + &File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: true, + }, }, { - Name: "file with space staged", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: false, - Deleted: false, - HasMergeConflicts: false, - DisplayString: "A \"file with space staged\"", + "All changes staged", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"diff", "--color", "--cached", "--", "test.txt"}, args) + + return exec.Command("echo") + }, + &File{ + Name: "test.txt", + HasStagedChanges: true, + HasUnstagedChanges: false, + Tracked: true, + }, }, { - Name: "file with space unstaged", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: false, - Deleted: false, - HasMergeConflicts: false, - DisplayString: "?? file with space unstaged", - }, - { - Name: "modified_unstaged", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: true, - Deleted: false, - HasMergeConflicts: false, - DisplayString: "M modified_unstaged", - }, - { - Name: "modified_staged", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: true, - Deleted: false, - HasMergeConflicts: false, - DisplayString: " M modified_staged", - }, - { - Name: "renamed_before -> renamed_after", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: true, - Deleted: false, - HasMergeConflicts: false, - DisplayString: "R renamed_before -> renamed_after", - }, - { - Name: "untracked_unstaged", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: false, - Deleted: false, - HasMergeConflicts: false, - DisplayString: "?? untracked_unstaged", - }, - { - Name: "untracked_staged", - HasStagedChanges: true, - HasUnstagedChanges: false, - Tracked: false, - Deleted: false, - HasMergeConflicts: false, - DisplayString: "A untracked_staged", - }, - { - Name: "master", - HasStagedChanges: false, - HasUnstagedChanges: true, - Tracked: false, - Deleted: false, - HasMergeConflicts: false, - DisplayString: "?? master", + "File not tracked and file has no staged changes", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"diff", "--color", "--no-index", "/dev/null", "test.txt"}, args) + + return exec.Command("echo") + }, + &File{ + Name: "test.txt", + HasStagedChanges: false, + Tracked: false, + }, }, } - for _, file := range files { - t.Run(file.Name, func(t *testing.T) { - assert.NotContains(t, gitCommand.Diff(file), "error") + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + gitCmd.Diff(s.file) }) } } diff --git a/test/repos/lots_of_diffs.sh b/test/repos/lots_of_diffs.sh deleted file mode 100755 index 08c743f35..000000000 --- a/test/repos/lots_of_diffs.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash -set -ex; rm -rf repo; mkdir repo; cd repo - -git init -git config user.email "test@example.com" -git config user.name "Lazygit Tester" - - -echo "deleted" > deleted_staged -echo "deleted_unstaged" > deleted_unstaged -echo "modified_staged" > modified_staged -echo "modified_unstaged" > modified_unstaged -echo "renamed" > renamed_before - -git add . -git commit -m "files to delete" -rm deleted_staged -rm deleted_unstaged - -rm renamed_before -echo "renamed" > renamed_after -echo "more" >> modified_staged -echo "more" >> modified_unstaged -echo "untracked_staged" > untracked_staged -echo "untracked_unstaged" > untracked_unstaged -echo "blah" > "file with space staged" -echo "blah" > "file with space unstaged" -echo "same name as branch" > master - -git add deleted_staged -git add modified_staged -git add untracked_staged -git add "file with space staged" -git add renamed_before -git add renamed_after \ No newline at end of file From 70ee4faf15a15ebb8ae28db70fa52dd2e33f6c1e Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Thu, 20 Sep 2018 09:48:56 +1000 Subject: [PATCH 35/47] 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 36/47] 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 37/47] 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 38/47] 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 39/47] 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 From f3fc98a3d03756ec94bcad955eda268fddb3613a Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 25 Sep 2018 20:11:33 +1000 Subject: [PATCH 40/47] support git flow when colouring commits --- pkg/commands/git.go | 40 ++++++++++++++++++++++++++++------------ pkg/commands/git_test.go | 22 +++++++++++++++++++--- pkg/gui/commits_panel.go | 9 +++++++-- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index b394753ca..0bfbc36a7 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -267,6 +267,10 @@ func (c *GitCommand) NewBranch(name string) error { return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -b %s", name)) } +func (c *GitCommand) CurrentBranchName() (string, error) { + return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") +} + // DeleteBranch delete branch func (c *GitCommand) DeleteBranch(branch string, force bool) error { command := "git branch -d" @@ -460,17 +464,27 @@ 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 (c *GitCommand) getMergeBase() string { - output, err := c.OSCommand.RunCommandWithOutput("git merge-base HEAD master") // TODO: support develop as well +func (c *GitCommand) getMergeBase() (string, error) { + currentBranch, err := c.CurrentBranchName() if err != nil { - c.Log.Error("Could not get merge base") - return "" + return "", err } - return output + + baseBranch := "master" + if strings.HasPrefix(currentBranch, "feature/") { + baseBranch = "develop" + } + + output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch)) + if err != nil { + // swallowing error because it's not a big deal; probably because there are no commits yet + c.Log.Error(err) + } + return output, nil } // GetCommits obtains the commits of the current branch -func (c *GitCommand) GetCommits() []*Commit { +func (c *GitCommand) GetCommits() ([]*Commit, error) { pushables := c.GetCommitsToPush() log := c.GetLog() @@ -488,14 +502,16 @@ func (c *GitCommand) GetCommits() []*Commit { DisplayString: strings.Join(splitLine, " "), } } - commits = c.setCommitMergedStatuses(commits) - return commits + return c.setCommitMergedStatuses(commits) } -func (c *GitCommand) setCommitMergedStatuses(commits []*Commit) []*Commit { - ancestor := c.getMergeBase() +func (c *GitCommand) setCommitMergedStatuses(commits []*Commit) ([]*Commit, error) { + ancestor, err := c.getMergeBase() + if err != nil { + return nil, err + } if ancestor == "" { - return commits + return commits, nil } passedAncestor := false for i, commit := range commits { @@ -504,7 +520,7 @@ func (c *GitCommand) setCommitMergedStatuses(commits []*Commit) []*Commit { } commits[i].Merged = passedAncestor } - return commits + return commits, nil } // GetLog gets the git log (currently limited to 30 commits for performance diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 790a34690..e5faa707b 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -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, error) } scenarios := []scenario{ @@ -1500,11 +1500,18 @@ func TestGitCommandGetCommits(t *testing.T) { case "log": assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) return exec.Command("echo") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("test") + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") } return nil }, - func(commits []*Commit) { + func(commits []*Commit, err error) { + assert.NoError(t, err) assert.Len(t, commits, 0) }, }, @@ -1520,23 +1527,32 @@ func TestGitCommandGetCommits(t *testing.T) { case "log": assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("echo", "78976bc") + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") } return nil }, - func(commits []*Commit) { + func(commits []*Commit, err error) { + assert.NoError(t, err) assert.Len(t, commits, 2) assert.EqualValues(t, []*Commit{ { Sha: "8a2bb0e", Name: "commit 1", Pushed: true, + Merged: false, DisplayString: "8a2bb0e commit 1", }, { Sha: "78976bc", Name: "commit 2", Pushed: false, + Merged: true, DisplayString: "78976bc commit 2", }, }, commits) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index e3f4c84ac..a86401ebf 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -11,10 +11,15 @@ import ( func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { - gui.State.Commits = gui.GitCommand.GetCommits() + commits, err := gui.GitCommand.GetCommits() + if err != nil { + return err + } + + gui.State.Commits = commits v, err := g.View("commits") if err != nil { - panic(err) + return err } v.Clear() From fb9596a3fffa5c707b27fb0eee1768d76aefd206 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 25 Sep 2018 20:25:04 +1000 Subject: [PATCH 41/47] add test for getMergeBase --- pkg/commands/git_test.go | 79 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 5a331ea18..d5b87a1f1 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1684,3 +1684,82 @@ func TestGitCommandDiff(t *testing.T) { }) } } + +func TestGitCommandGetMergeBase(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(string, error) + } + + scenarios := []scenario{ + { + "swallows an error if the call to merge-base returns an error", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + + switch args[0] { + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("test") + } + return nil + }, + func(output string, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "", output) + }, + }, + { + "returns the commit when master", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + + switch args[0] { + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("echo", "blah") + } + return nil + }, + func(output string, err error) { + assert.NoError(t, err) + assert.Equal(t, "blah\n", output) + }, + }, + { + "checks against develop when a feature branch", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + + switch args[0] { + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "feature/test") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "develop"}, args) + return exec.Command("echo", "blah") + } + return nil + }, + func(output string, err error) { + assert.NoError(t, err) + assert.Equal(t, "blah\n", output) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.getMergeBase()) + }) + } +} From eb69d98f99f77669ffbb8463f6f6d84441c638d4 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Tue, 25 Sep 2018 20:31:19 +1000 Subject: [PATCH 42/47] add test for CurrentBranchName --- pkg/commands/git.go | 6 +++++- pkg/commands/git_test.go | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 4d132237f..402ec3378 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -268,7 +268,11 @@ func (c *GitCommand) NewBranch(name string) error { } func (c *GitCommand) CurrentBranchName() (string, error) { - return c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") + output, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") + if err != nil { + return "", err + } + return utils.TrimTrailingNewline(output), nil } // DeleteBranch delete branch diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index d5b87a1f1..a2b2b9524 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1763,3 +1763,15 @@ func TestGitCommandGetMergeBase(t *testing.T) { }) } } + +func TestGitCommandCurrentBranchName(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { + assert.Equal(t, "git", cmd) + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") + } + output, err := gitCmd.CurrentBranchName() + assert.Equal(t, "master", output) + assert.NoError(t, err) +} From 28fe3d6cf989e9fe40292a5b40ed9bc2a8cdeaa6 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Tue, 25 Sep 2018 22:11:51 +0200 Subject: [PATCH 43/47] Use confirmation popup for amending last commit. --- pkg/gui/commit_message_panel.go | 3 +-- pkg/gui/files_panel.go | 30 ++++++++++-------------------- pkg/gui/keybindings.go | 3 +-- pkg/i18n/dutch.go | 5 ++++- pkg/i18n/english.go | 5 ++++- pkg/i18n/polish.go | 5 ++++- 6 files changed, 24 insertions(+), 27 deletions(-) diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index 93a3e1503..c26b5573a 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -12,8 +12,7 @@ func (gui *Gui) handleCommitConfirm(g *gocui.Gui, v *gocui.View) error { if message == "" { return gui.createErrorPanel(g, gui.Tr.SLocalize("CommitWithoutMessageErr")) } - amendCommit := v.Title == gui.Tr.SLocalize("AmendLastCommit") - sub, err := gui.GitCommand.Commit(message, amendCommit) + sub, err := gui.GitCommand.Commit(message, false) if err != nil { // TODO need to find a way to send through this error if err != gui.Errors.ErrSubProcess { diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index c0d01136a..d76ba5791 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -201,14 +201,7 @@ func (gui *Gui) handleCommitPress(g *gocui.Gui, filesView *gocui.View) error { if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) } - commitMessageView := gui.getCommitMessageView(g) - if commitMessageView.Title == gui.Tr.SLocalize("AmendLastCommit") { - commitMessageView.Clear() - commitMessageView.SetCursor(0, 0) - } - - commitMessageView.Title = gui.Tr.SLocalize("CommitMessage") g.Update(func(g *gocui.Gui) error { g.SetViewOnTop("commitMessage") gui.switchFocus(g, filesView, commitMessageView) @@ -222,20 +215,17 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro if len(gui.stagedFiles()) == 0 && !gui.State.HasMergeConflicts { return gui.createErrorPanel(g, gui.Tr.SLocalize("NoStagedFilesToCommit")) } - commitMessageView := gui.getCommitMessageView(g) - commitMessageView.Clear() - commitMessageView.Title = gui.Tr.SLocalize("AmendLastCommit") + title := strings.Title(gui.Tr.SLocalize("AmendLastCommit")) + question := gui.Tr.SLocalize("SureToAmend") + return gui.createConfirmationPanel(g, filesView, title, question, func(g *gocui.Gui, v *gocui.View) error { + lastCommitMsg := gui.State.Commits[0].Name + _, err := gui.GitCommand.Commit(lastCommitMsg, true) + if err != nil { + gui.createErrorPanel(g, err.Error()) + } - g.Update(func(g *gocui.Gui) error { - g.SetViewOnTop("commitMessage") - gui.switchFocus(g, filesView, commitMessageView) - lastCommitName := gui.State.Commits[0].Name - commitMessageView.Write([]byte(lastCommitName)) - commitMessageView.SetCursor(len(lastCommitName), 0) - gui.RenderCommitLength() - return nil - }) - return nil + return gui.refreshFiles(g) + }, nil) } // handleCommitEditorPress - handle when the user wants to commit changes via diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 44df7b686..b242028c6 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -2,7 +2,6 @@ package gui import ( "github.com/jesseduffield/gocui" - "strings" ) // Binding - a keybinding mapping a key and modifier to a handler. The keypress @@ -131,7 +130,7 @@ func (gui *Gui) GetKeybindings() []*Binding { Key: 'M', Modifier: gocui.ModNone, Handler: gui.handleAmendCommitPress, - Description: strings.ToLower(gui.Tr.SLocalize("AmendLastCommit")), + Description: gui.Tr.SLocalize("AmendLastCommit"), }, { ViewName: "files", Key: 'C', diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index ef10625d5..56e93fdc6 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -36,7 +36,10 @@ func addDutch(i18nObject *i18n.Bundle) error { Other: "Commit Veranderingen", }, &i18n.Message{ ID: "AmendLastCommit", - Other: "Wijzig laatste commit", + Other: "wijzig laatste commit", + }, &i18n.Message{ + ID: "SureToAmend", + Other: "Weet je zeker dat je de laatste commit wilt wijzigen? U kunt het commit-bericht wijzigen vanuit het commits-paneel.", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commit Veranderingen met de git editor", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index bfa1de83e..41ce71bd2 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -44,7 +44,10 @@ func addEnglish(i18nObject *i18n.Bundle) error { Other: "commit changes", }, &i18n.Message{ ID: "AmendLastCommit", - Other: "Amend last commit", + Other: "amend last commit", + }, &i18n.Message{ + ID: "SureToAmend", + Other: "Are you sure you want to amend last commit? You can change commit message from commits panel.", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commit changes using git editor", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index d4f204a3f..74b4106a5 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -34,7 +34,10 @@ func addPolish(i18nObject *i18n.Bundle) error { Other: "commituj zmiany", }, &i18n.Message{ ID: "AmendLastCommit", - Other: "Zmień ostatnie zatwierdzenie", + Other: "zmień ostatnie zatwierdzenie", + }, &i18n.Message{ + ID: "SureToAmend", + Other: "Czy na pewno chcesz zmienić ostatnie zatwierdzenie? Możesz zmienić komunikat zatwierdzenia z panelu zatwierdzeń.", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commituj zmiany używając edytora z gita", From 2be613679e2062fbb6916bf1a2ac97aebbcf9630 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Fri, 5 Oct 2018 09:11:19 +1000 Subject: [PATCH 44/47] more test coverage --- pkg/commands/git_test.go | 85 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 77 insertions(+), 8 deletions(-) diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index a2b2b9524..25820558a 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1570,6 +1570,34 @@ func TestGitCommandGetCommits(t *testing.T) { }, commits) }, }, + { + "GetCommits bubbles up an error from setCommitMergedStatuses", + 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") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("echo", "78976bc") + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + // here's where we are returning the error + return exec.Command("test") + } + + return nil + }, + func(commits []*Commit, err error) { + assert.Error(t, err) + assert.Len(t, commits, 0) + }, + }, } for _, s := range scenarios { @@ -1753,6 +1781,16 @@ func TestGitCommandGetMergeBase(t *testing.T) { assert.Equal(t, "blah\n", output) }, }, + { + "bubbles up error if there is one", + func(cmd string, args ...string) *exec.Cmd { + return exec.Command("test") + }, + func(output string, err error) { + assert.Error(t, err) + assert.Equal(t, "", output) + }, + }, } for _, s := range scenarios { @@ -1765,13 +1803,44 @@ func TestGitCommandGetMergeBase(t *testing.T) { } func TestGitCommandCurrentBranchName(t *testing.T) { - gitCmd := newDummyGitCommand() - gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd { - assert.Equal(t, "git", cmd) - assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) - return exec.Command("echo", "master") + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(string, error) + } + + scenarios := []scenario{ + { + "says we are on the master branch if we are", + func(cmd string, args ...string) *exec.Cmd { + assert.Equal(t, "git", cmd) + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") + }, + func(output string, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "master", output) + }, + }, + { + "bubbles up error if there is one", + func(cmd string, args ...string) *exec.Cmd { + assert.Equal(t, "git", cmd) + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("test") + }, + func(output string, err error) { + assert.Error(t, err) + assert.EqualValues(t, "", output) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.CurrentBranchName()) + }) } - output, err := gitCmd.CurrentBranchName() - assert.Equal(t, "master", output) - assert.NoError(t, err) } From 5f7ac97a39cf473823e290acfe59a01dfa031625 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Sat, 6 Oct 2018 07:40:12 +0200 Subject: [PATCH 45/47] Refresh side panels and use uppercase HEAD in all git commands that requires it. --- pkg/commands/git.go | 6 +++--- pkg/gui/files_panel.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 4539e4431..87ee69c8c 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -221,11 +221,11 @@ func (c *GitCommand) ResetHard() error { // UpstreamDifferenceCount checks how many pushables/pullables there are for the // current branch func (c *GitCommand) UpstreamDifferenceCount() (string, string) { - pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --count") + pushableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --count") if err != nil { return "?", "?" } - pullableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list head..@{u} --count") + pullableCount, err := c.OSCommand.RunCommandWithOutput("git rev-list HEAD..@{u} --count") if err != nil { return "?", "?" } @@ -236,7 +236,7 @@ func (c *GitCommand) UpstreamDifferenceCount() (string, string) { // 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") + o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..HEAD --abbrev-commit") if err != nil { return pushables } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index d76ba5791..b16492dfd 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -224,7 +224,7 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro gui.createErrorPanel(g, err.Error()) } - return gui.refreshFiles(g) + return gui.refreshSidePanels(g) }, nil) } From 190309e5c1c939b4de5c366b31dea265e78cb4f3 Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Mon, 8 Oct 2018 21:19:45 +0200 Subject: [PATCH 46/47] Check if there is any commit to amend and use 'A' instead of 'M' as shortcut. --- pkg/commands/git.go | 4 ++-- pkg/gui/files_panel.go | 5 +++++ pkg/gui/keybindings.go | 4 ++-- pkg/i18n/dutch.go | 3 +++ pkg/i18n/english.go | 3 +++ pkg/i18n/polish.go | 3 +++ 6 files changed, 18 insertions(+), 4 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index c7de19b3a..ffaf9fce6 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -317,9 +317,9 @@ func (c *GitCommand) usingGpg() bool { func (c *GitCommand) Commit(message string, amend bool) (*exec.Cmd, error) { amendParam := "" if amend { - amendParam = "--amend " + amendParam = "--amend" } - command := fmt.Sprintf("git commit %s-m %s", amendParam, c.OSCommand.Quote(message)) + command := fmt.Sprintf("git commit %s -m %s", amendParam, c.OSCommand.Quote(message)) if c.usingGpg() { return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index b16492dfd..19d7b9342 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -217,6 +217,11 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro } title := strings.Title(gui.Tr.SLocalize("AmendLastCommit")) question := gui.Tr.SLocalize("SureToAmend") + + if len(gui.State.Commits) == 0 { + return gui.createErrorPanel(g, gui.Tr.SLocalize("NoCommitToAmend")) + } + return gui.createConfirmationPanel(g, filesView, title, question, func(g *gocui.Gui, v *gocui.View) error { lastCommitMsg := gui.State.Commits[0].Name _, err := gui.GitCommand.Commit(lastCommitMsg, true) diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index b242028c6..78271a3f7 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -127,7 +127,7 @@ func (gui *Gui) GetKeybindings() []*Binding { Description: gui.Tr.SLocalize("CommitChanges"), }, { ViewName: "files", - Key: 'M', + Key: 'A', Modifier: gocui.ModNone, Handler: gui.handleAmendCommitPress, Description: gui.Tr.SLocalize("AmendLastCommit"), @@ -188,7 +188,7 @@ func (gui *Gui) GetKeybindings() []*Binding { Description: gui.Tr.SLocalize("stashFiles"), }, { ViewName: "files", - Key: 'A', + Key: 'M', Modifier: gocui.ModNone, Handler: gui.handleAbortMerge, Description: gui.Tr.SLocalize("abortMerge"), diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 56e93fdc6..6b24a5174 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -40,6 +40,9 @@ func addDutch(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "SureToAmend", Other: "Weet je zeker dat je de laatste commit wilt wijzigen? U kunt het commit-bericht wijzigen vanuit het commits-paneel.", + }, &i18n.Message{ + ID: "NoCommitToAmend", + Other: "Er is geen verplichting om te wijzigen.", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commit Veranderingen met de git editor", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 41ce71bd2..9ead5a54e 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -48,6 +48,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "SureToAmend", Other: "Are you sure you want to amend last commit? You can change commit message from commits panel.", + }, &i18n.Message{ + ID: "NoCommitToAmend", + Other: "There's no commit to amend.", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commit changes using git editor", diff --git a/pkg/i18n/polish.go b/pkg/i18n/polish.go index 74b4106a5..c37bfe972 100644 --- a/pkg/i18n/polish.go +++ b/pkg/i18n/polish.go @@ -38,6 +38,9 @@ func addPolish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "SureToAmend", Other: "Czy na pewno chcesz zmienić ostatnie zatwierdzenie? Możesz zmienić komunikat zatwierdzenia z panelu zatwierdzeń.", + }, &i18n.Message{ + ID: "NoCommitToAmend", + Other: "Nie ma zobowiązania do zmiany.", }, &i18n.Message{ ID: "CommitChangesWithEditor", Other: "commituj zmiany używając edytora z gita", From 4287f8ae905237abf3de181461255406c6d0b51d Mon Sep 17 00:00:00 2001 From: Kristijan Husak Date: Mon, 8 Oct 2018 22:19:42 +0200 Subject: [PATCH 47/47] Fix tests and add test scenarios for amend. --- pkg/commands/git.go | 4 +- pkg/commands/git_test.go | 81 +++++++++++++++++++++++++++++++++++++--- pkg/gui/files_panel.go | 2 +- 3 files changed, 78 insertions(+), 9 deletions(-) diff --git a/pkg/commands/git.go b/pkg/commands/git.go index ffaf9fce6..0e974b567 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -317,9 +317,9 @@ func (c *GitCommand) usingGpg() bool { func (c *GitCommand) Commit(message string, amend bool) (*exec.Cmd, error) { amendParam := "" if amend { - amendParam = "--amend" + amendParam = " --amend" } - command := fmt.Sprintf("git commit %s -m %s", amendParam, c.OSCommand.Quote(message)) + command := fmt.Sprintf("git commit%s -m %s", amendParam, c.OSCommand.Quote(message)) if c.usingGpg() { return c.OSCommand.PrepareSubProcess(c.OSCommand.Platform.shell, c.OSCommand.Platform.shellArg, command), nil } diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 25820558a..ebb9de136 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -561,7 +561,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) { { "Can't retrieve pullable count", func(cmd string, args ...string) *exec.Cmd { - if args[1] == "head..@{u}" { + if args[1] == "HEAD..@{u}" { return exec.Command("test") } @@ -575,7 +575,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) { { "Retrieve pullable and pushable count", func(cmd string, args ...string) *exec.Cmd { - if args[1] == "head..@{u}" { + if args[1] == "HEAD..@{u}" { return exec.Command("echo", "10") } @@ -889,7 +889,76 @@ func TestGitCommandCommit(t *testing.T) { gitCmd := newDummyGitCommand() gitCmd.getGlobalGitConfig = s.getGlobalGitConfig gitCmd.OSCommand.command = s.command - s.test(gitCmd.Commit("test")) + s.test(gitCmd.Commit("test", false)) + }) + } +} + +func TestGitCommandCommitAmendFromFiles(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + getGlobalGitConfig func(string) (string, error) + test func(*exec.Cmd, error) + } + + scenarios := []scenario{ + { + "Amend commit using gpg", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "bash", cmd) + assert.EqualValues(t, []string{"-c", `git commit --amend -m 'test'`}, args) + + return exec.Command("echo") + }, + func(string) (string, error) { + return "true", nil + }, + func(cmd *exec.Cmd, err error) { + assert.NotNil(t, cmd) + assert.Nil(t, err) + }, + }, + { + "Amend commit without using gpg", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args) + + return exec.Command("echo") + }, + func(string) (string, error) { + return "false", nil + }, + func(cmd *exec.Cmd, err error) { + assert.Nil(t, cmd) + assert.Nil(t, err) + }, + }, + { + "Amend commit without using gpg with an error", + func(cmd string, args ...string) *exec.Cmd { + assert.EqualValues(t, "git", cmd) + assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args) + + return exec.Command("test") + }, + func(string) (string, error) { + return "false", nil + }, + func(cmd *exec.Cmd, err error) { + assert.Nil(t, cmd) + assert.Error(t, err) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.getGlobalGitConfig = s.getGlobalGitConfig + gitCmd.OSCommand.command = s.command + s.test(gitCmd.Commit("test", true)) }) } } @@ -1507,7 +1576,7 @@ func TestGitCommandGetCommits(t *testing.T) { switch args[0] { case "rev-list": - assert.EqualValues(t, []string{"rev-list", "@{u}..head", "--abbrev-commit"}, args) + 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) @@ -1534,7 +1603,7 @@ func TestGitCommandGetCommits(t *testing.T) { switch args[0] { case "rev-list": - assert.EqualValues(t, []string{"rev-list", "@{u}..head", "--abbrev-commit"}, args) + 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) @@ -1577,7 +1646,7 @@ func TestGitCommandGetCommits(t *testing.T) { switch args[0] { case "rev-list": - assert.EqualValues(t, []string{"rev-list", "@{u}..head", "--abbrev-commit"}, args) + 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) diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 19d7b9342..a00bd2843 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -226,7 +226,7 @@ func (gui *Gui) handleAmendCommitPress(g *gocui.Gui, filesView *gocui.View) erro lastCommitMsg := gui.State.Commits[0].Name _, err := gui.GitCommand.Commit(lastCommitMsg, true) if err != nil { - gui.createErrorPanel(g, err.Error()) + return gui.createErrorPanel(g, err.Error()) } return gui.refreshSidePanels(g)