diff --git a/.circleci/config.yml b/.circleci/config.yml index c5f520b70..0eef1a409 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,26 +1,42 @@ -# Golang CircleCI 2.0 configuration file -# -# Check https://circleci.com/docs/2.0/language-go/ for more details version: 2 jobs: build: docker: - # specify the version - - image: circleci/golang:1.9 + - image: circleci/golang:1.10 - # Specify service dependencies here if necessary - # CircleCI maintains a library of pre-built images - # documented at https://circleci.com/docs/2.0/circleci-images/ - # - image: circleci/postgres:9.4 - - #### TEMPLATE_NOTE: go expects specific checkout path representing url - #### expecting it in the form of - #### /go/src/github.com/circleci/go-tool - #### /go/src/bitbucket.org/circleci/go-tool working_directory: /go/src/github.com/jesseduffield/lazygit steps: - checkout + - run: + name: Run tests + command: | + ./test.sh + - run: + name: Push on codecov result + command: | + bash <(curl -s https://codecov.io/bash) - # specify any bash command here prefixed with `run: ` - - run: go test -v ./... - - run: bash <(curl -s https://codecov.io/bash) + release: + docker: + - image: circleci/golang:1.10 + working_directory: /go/src/github.com/jesseduffield/lazygit + steps: + - checkout + - run: + name: Run gorelease + command: | + curl -sL https://git.io/goreleaser | bash + +workflows: + version: 2 + build: + jobs: + - build + release: + jobs: + - release: + filters: + tags: + only: /v[0-9]+(\.[0-9]+)*/ + branches: + ignore: /.*/ diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 1a96635a3..b10cb5f37 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,6 +23,7 @@ If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. Windows] - Lazygit Version [e.g. v0.1.45] + - The last commit id if you built project from sources (run : ```git-rev parse HEAD```) **Additional context** Add any other context about the problem here. diff --git a/.gitignore b/.gitignore index 304a2356f..f759e8a06 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ TODO.md # Tests test/repos/repo +coverage.txt # Binaries -lazygit \ No newline at end of file +lazygit diff --git a/Gopkg.lock b/Gopkg.lock index dc92d8e90..bf05a0041 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,14 +1,6 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. -[[projects]] - digest = "1:b2339e83ce9b5c4f79405f949429a7f68a9a904fed903c672aac1e7ceb7f5f02" - name = "github.com/Sirupsen/logrus" - packages = ["."] - pruneopts = "NUT" - revision = "3e01752db0189b9157070a0e1668a620f9a85da2" - version = "v1.0.6" - [[projects]] branch = "master" digest = "1:cd7ba2b29e93e2a8384e813dfc80ebb0f85d9214762e6ca89bb55a58092eab87" @@ -93,11 +85,11 @@ [[projects]] branch = "master" - digest = "1:c9a848b0484a72da2dae28957b4f67501fe27fa38bc73f4713e454353c0a4a60" + digest = "1:f774b11ae458cae2d10b94ef66ef00ba1c57f1971dd0e5534ac743cbe574f6d4" name = "github.com/jesseduffield/gocui" packages = ["."] pruneopts = "NUT" - revision = "432b7f6215f81ef1aaa1b2d9b69887822923cf79" + revision = "7818a0f93387d1037cbd06f69323d9f8d068af7c" [[projects]] digest = "1:8021af4dcbd531ae89433c8c3a6520e51064114aaf8eb1724c3cf911c497c9ba" @@ -216,13 +208,20 @@ version = "v1.0.0" [[projects]] - branch = "master" digest = "1:41618aee8828e62dfe62d44f579c06892d0e98907d1c6d5bcd83bfe8536ec5a3" name = "github.com/shibukawa/configdir" packages = ["."] pruneopts = "NUT" revision = "e180dbdc8da04c4fa04272e875ce64949f38bd3e" +[[projects]] + digest = "1:b2339e83ce9b5c4f79405f949429a7f68a9a904fed903c672aac1e7ceb7f5f02" + name = "github.com/sirupsen/logrus" + packages = ["."] + pruneopts = "NUT" + revision = "3e01752db0189b9157070a0e1668a620f9a85da2" + version = "v1.0.6" + [[projects]] digest = "1:330e9062b308ac597e28485699c02223bd052437a6eed32a173c9227dcb9d95a" name = "github.com/spf13/afero" @@ -266,6 +265,14 @@ revision = "907c19d40d9a6c9bb55f040ff4ae45271a4754b9" version = "v1.1.0" +[[projects]] + branch = "master" + digest = "1:0e9a5ac14bcc11f205031a671b28c7e05cb88b2ebbe06f383c1ab0b2c12c7cb5" + name = "github.com/spkg/bom" + packages = ["."] + pruneopts = "NUT" + revision = "59b7046e48ad6bac800c5e1dd5142282cbfcf154" + [[projects]] digest = "1:ccca1dcd18bc54e23b517a3c5babeff2e3924a7d8fc1932162225876cfe4bfb0" name = "github.com/src-d/gcfg" @@ -447,7 +454,6 @@ analyzer-name = "dep" analyzer-version = 1 input-imports = [ - "github.com/Sirupsen/logrus", "github.com/cloudfoundry/jibber_jabber", "github.com/davecgh/go-spew/spew", "github.com/fatih/color", @@ -456,7 +462,9 @@ "github.com/mgutz/str", "github.com/nicksnyder/go-i18n/v2/i18n", "github.com/shibukawa/configdir", + "github.com/sirupsen/logrus", "github.com/spf13/viper", + "github.com/spkg/bom", "github.com/stretchr/testify/assert", "github.com/tcnksm/go-gitconfig", "golang.org/x/text/language", diff --git a/Gopkg.toml b/Gopkg.toml index a47fe5e93..1f0b03cee 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -40,3 +40,7 @@ [[constraint]] name = "gopkg.in/src-d/go-git.v4" revision = "43d17e14b714665ab5bc2ecc220b6740779d733f" + +[[constraint]] + branch = "master" + name = "github.com/spkg/bom" diff --git a/README.md b/README.md index 6b85ffbe1..28fe9c399 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# lazygit [![Go Report Card](https://goreportcard.com/badge/github.com/jesseduffield/lazygit)](https://goreportcard.com/report/github.com/jesseduffield/lazygit) +# lazygit [![CircleCI](https://circleci.com/gh/jesseduffield/lazygit.svg?style=svg)](https://circleci.com/gh/jesseduffield/lazygit) [![codecov](https://codecov.io/gh/jesseduffield/lazygit/branch/master/graph/badge.svg)](https://codecov.io/gh/jesseduffield/lazygit) [![Go Report Card](https://goreportcard.com/badge/github.com/jesseduffield/lazygit)](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [![GolangCI](https://golangci.com/badges/github.com/jesseduffield/lazygit.svg)](https://golangci.com) [![GoDoc](https://godoc.org/github.com/jesseduffield/lazygit?status.svg)](http://godoc.org/github.com/jesseduffield/lazygit) [![GitHub tag](https://img.shields.io/github/tag/jesseduffield/lazygit.svg)]() A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui "gocui") library. diff --git a/docs/Config.md b/docs/Config.md index e6e2c3974..7e657adc4 100644 --- a/docs/Config.md +++ b/docs/Config.md @@ -37,3 +37,7 @@ The available attributes are: - bold - reverse # useful for high-contrast - underline + +## Example Coloring: + +![border example](/docs/resources/colored-border-example.png) diff --git a/docs/Keybindings.md b/docs/Keybindings.md index 2a1b1817f..37fb523a2 100644 --- a/docs/Keybindings.md +++ b/docs/Keybindings.md @@ -44,6 +44,7 @@ c: checkout by name n: new branch d: delete branch + D: force delete branch ## Commits Panel: diff --git a/docs/resources/colored-border-example.png b/docs/resources/colored-border-example.png new file mode 100644 index 000000000..06bb7bf8b Binary files /dev/null and b/docs/resources/colored-border-example.png differ diff --git a/pkg/app/app.go b/pkg/app/app.go index e1a26e13d..d4219289e 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -5,7 +5,7 @@ import ( "io/ioutil" "os" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/gui" @@ -53,10 +53,7 @@ func NewApp(config config.AppConfigurer) (*App, error) { return app, err } - app.Tr, err = i18n.NewLocalizer(app.Log) - if err != nil { - return app, err - } + app.Tr = i18n.NewLocalizer(app.Log) app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand) if err != nil { diff --git a/pkg/commands/git.go b/pkg/commands/git.go index bf9aa6646..14f3a433a 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -7,7 +7,7 @@ import ( "os/exec" "strings" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/utils" gitconfig "github.com/tcnksm/go-gitconfig" @@ -223,8 +223,14 @@ func (c *GitCommand) NewBranch(name string) error { } // DeleteBranch delete branch -func (c *GitCommand) DeleteBranch(branch string) error { - return c.OSCommand.RunCommand("git branch -d " + branch) +func (c *GitCommand) DeleteBranch(branch string, force bool) error { + var command string + if force { + command = "git branch -D " + } else { + command = "git branch -d " + } + return c.OSCommand.RunCommand(command + branch) } // ListStash list stash diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index 9c2a64330..c930f76eb 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/jesseduffield/lazygit/pkg/test" ) diff --git a/pkg/commands/os.go b/pkg/commands/os.go index 9756d619d..1eef36151 100644 --- a/pkg/commands/os.go +++ b/pkg/commands/os.go @@ -11,7 +11,7 @@ import ( "github.com/mgutz/str" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" gitconfig "github.com/tcnksm/go-gitconfig" ) @@ -175,7 +175,8 @@ func (c *OSCommand) Unquote(message string) string { return message } -func (C *OSCommand) AppendLineToFile(filename, line string) error { +// AppendLineToFile adds a new line in file +func (c *OSCommand) AppendLineToFile(filename, line string) error { f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) if err != nil { return err diff --git a/pkg/config/app_config.go b/pkg/config/app_config.go index 9f258d4a6..aa56365e3 100644 --- a/pkg/config/app_config.go +++ b/pkg/config/app_config.go @@ -121,10 +121,7 @@ func LoadUserConfigFromFile(v *viper.Viper) error { folder = configDirs.QueryFolderContainsFile("config.yml") } v.AddConfigPath(folder.Path) - if err := v.MergeInConfig(); err != nil { - return err - } - return nil + return v.MergeInConfig() } // InsertToUserConfig adds a key/value pair to the user's config and saves it @@ -139,10 +136,7 @@ func (c *AppConfig) InsertToUserConfig(key, value string) error { return err } v.Set(key, value) - if err := v.WriteConfig(); err != nil { - return err - } - return nil + return v.WriteConfig() } func getDefaultConfig() []byte { diff --git a/pkg/git/branch_list_builder.go b/pkg/git/branch_list_builder.go index 869e05c98..37421d5b6 100644 --- a/pkg/git/branch_list_builder.go +++ b/pkg/git/branch_list_builder.go @@ -7,7 +7,7 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/utils" - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" "gopkg.in/src-d/go-git.v4/plumbing" ) diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index 67e0ceb07..df4dcff78 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -62,20 +62,34 @@ func (gui *Gui) handleNewBranch(g *gocui.Gui, v *gocui.View) error { } func (gui *Gui) handleDeleteBranch(g *gocui.Gui, v *gocui.View) error { + return gui.deleteBranch(g, v, false) +} + +func (gui *Gui) handleForceDeleteBranch(g *gocui.Gui, v *gocui.View) error { + return gui.deleteBranch(g, v, true) +} + +func (gui *Gui) deleteBranch(g *gocui.Gui, v *gocui.View, force bool) error { checkedOutBranch := gui.State.Branches[0] selectedBranch := gui.getSelectedBranch(v) if checkedOutBranch.Name == selectedBranch.Name { return gui.createErrorPanel(g, gui.Tr.SLocalize("CantDeleteCheckOutBranch")) } + title := gui.Tr.SLocalize("DeleteBranch") + var messageId string + if force { + messageId = "ForceDeleteBranchMessage" + } else { + messageId = "DeleteBranchMessage" + } message := gui.Tr.TemplateLocalize( - "DeleteBranchMessage", + messageId, Teml{ "selectedBranchName": selectedBranch.Name, }, ) - title := gui.Tr.SLocalize("DeleteBranch") return gui.createConfirmationPanel(g, v, title, message, func(g *gocui.Gui, v *gocui.View) error { - if err := gui.GitCommand.DeleteBranch(selectedBranch.Name); err != nil { + if err := gui.GitCommand.DeleteBranch(selectedBranch.Name, force); err != nil { return gui.createErrorPanel(g, err.Error()) } return gui.refreshSidePanels(g) @@ -108,6 +122,7 @@ func (gui *Gui) renderBranchesOptions(g *gocui.Gui) error { "c": gui.Tr.SLocalize("checkoutByName"), "n": gui.Tr.SLocalize("newBranch"), "d": gui.Tr.SLocalize("deleteBranch"), + "D": gui.Tr.SLocalize("forceDeleteBranch"), "← → ↑ ↓": gui.Tr.SLocalize("navigate"), }) } diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index 7ddb50811..5791a9d15 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -251,14 +251,6 @@ func (gui *Gui) handleFileEdit(g *gocui.Gui, v *gocui.View) error { return gui.genericFileOpen(g, v, file.Name, gui.OSCommand.EditFile) } -func (gui *Gui) openFile(filename string) error { - err := gui.OSCommand.OpenFile(filename) - if err != nil { - return gui.createErrorPanel(gui.g, err.Error()) - } - return nil -} - func (gui *Gui) handleFileOpen(g *gocui.Gui, v *gocui.View) error { file, err := gui.getSelectedFile(g) if err != nil { diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index baed39056..c00b26b78 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -15,13 +15,13 @@ import ( // "strings" - "github.com/Sirupsen/logrus" "github.com/golang-collections/collections/stack" "github.com/jesseduffield/gocui" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" "github.com/jesseduffield/lazygit/pkg/i18n" "github.com/jesseduffield/lazygit/pkg/updates" + "github.com/sirupsen/logrus" ) // OverlappingEdges determines if panel edges overlap @@ -152,14 +152,15 @@ func (gui *Gui) setAppStatus(status string) error { func (gui *Gui) layout(g *gocui.Gui) error { g.Highlight = true width, height := g.Size() + version := gui.Config.GetVersion() leftSideWidth := width / 3 statusFilesBoundary := 2 filesBranchesBoundary := 2 * height / 5 // height - 20 commitsBranchesBoundary := 3 * height / 5 // height - 10 commitsStashBoundary := height - 5 // height - 5 + optionsVersionBoundary := width - max(len(version), 1) minimumHeight := 16 minimumWidth := 10 - version := gui.Config.GetVersion() appStatusView, _ := g.View("appStatus") appStatusOptionsBoundary := -2 @@ -244,7 +245,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { v.FgColor = gocui.ColorWhite } - if v, err := g.SetView("options", appStatusOptionsBoundary-1, optionsTop, width-len(version)-2, optionsTop+2, 0); err != nil { + if v, err := g.SetView("options", appStatusOptionsBoundary-1, optionsTop, optionsVersionBoundary-1, optionsTop+2, 0); err != nil { if err != gocui.ErrUnknownView { return err } @@ -281,7 +282,7 @@ func (gui *Gui) layout(g *gocui.Gui) error { v.Frame = false } - if v, err := g.SetView("version", width-len(version)-1, optionsTop, width, optionsTop+2, 0); err != nil { + if v, err := g.SetView("version", optionsVersionBoundary-1, optionsTop, width, optionsTop+2, 0); err != nil { if err != gocui.ErrUnknownView { return err } diff --git a/pkg/gui/keybindings.go b/pkg/gui/keybindings.go index 68cccda6b..8041d14ff 100644 --- a/pkg/gui/keybindings.go +++ b/pkg/gui/keybindings.go @@ -16,6 +16,7 @@ func (gui *Gui) keybindings(g *gocui.Gui) error { bindings := []Binding{ {ViewName: "", Key: 'q', Modifier: gocui.ModNone, Handler: gui.quit}, {ViewName: "", Key: gocui.KeyCtrlC, Modifier: gocui.ModNone, Handler: gui.quit}, + {ViewName: "", Key: gocui.KeyEsc, Modifier: gocui.ModNone, Handler: gui.quit}, {ViewName: "", Key: gocui.KeyPgup, Modifier: gocui.ModNone, Handler: gui.scrollUpMain}, {ViewName: "", Key: gocui.KeyPgdn, Modifier: gocui.ModNone, Handler: gui.scrollDownMain}, {ViewName: "", Key: gocui.KeyCtrlU, Modifier: gocui.ModNone, Handler: gui.scrollUpMain}, @@ -57,6 +58,7 @@ func (gui *Gui) keybindings(g *gocui.Gui) error { {ViewName: "branches", Key: 'F', Modifier: gocui.ModNone, Handler: gui.handleForceCheckout}, {ViewName: "branches", Key: 'n', Modifier: gocui.ModNone, Handler: gui.handleNewBranch}, {ViewName: "branches", Key: 'd', Modifier: gocui.ModNone, Handler: gui.handleDeleteBranch}, + {ViewName: "branches", Key: 'D', Modifier: gocui.ModNone, Handler: gui.handleForceDeleteBranch}, {ViewName: "branches", Key: 'm', Modifier: gocui.ModNone, Handler: gui.handleMerge}, {ViewName: "commits", Key: 's', Modifier: gocui.ModNone, Handler: gui.handleCommitSquashDown}, {ViewName: "commits", Key: 'r', Modifier: gocui.ModNone, Handler: gui.handleRenameCommit}, diff --git a/pkg/gui/view_helpers.go b/pkg/gui/view_helpers.go index cfe985867..91d81b55e 100644 --- a/pkg/gui/view_helpers.go +++ b/pkg/gui/view_helpers.go @@ -7,6 +7,8 @@ import ( "time" "github.com/jesseduffield/gocui" + "github.com/jesseduffield/lazygit/pkg/utils" + "github.com/spkg/bom" ) var cyclableViews = []string{"status", "files", "branches", "commits", "stash"} @@ -224,7 +226,9 @@ func (gui *Gui) renderString(g *gocui.Gui, viewName, s string) error { gui.Log.Info(s) } v.Clear() - fmt.Fprint(v, s) + output := string(bom.Clean([]byte(s))) + output = utils.NormalizeLinefeeds(output) + fmt.Fprint(v, output) v.Wrap = true return nil }) diff --git a/pkg/i18n/dutch.go b/pkg/i18n/dutch.go index 60133e662..68e7c82bd 100644 --- a/pkg/i18n/dutch.go +++ b/pkg/i18n/dutch.go @@ -132,7 +132,10 @@ func addDutch(i18nObject *i18n.Bundle) error { Other: "Verwijder branch", }, &i18n.Message{ ID: "DeleteBranchMessage", - Other: "Weet je zeker dat je {{.selectedBranchName}} branch wil verwijderen?", + Other: "Weet je zeker dat je branch {{.selectedBranchName}} wil verwijderen?", + }, &i18n.Message{ + ID: "ForceDeleteBranchMessage", + Other: "Weet je zeker dat je branch {{.selectedBranchName}} geforceerd wil verwijderen?", }, &i18n.Message{ ID: "CantMergeBranchIntoItself", Other: "Je kan niet een branch in zichzelf mergen", @@ -151,6 +154,9 @@ func addDutch(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "deleteBranch", Other: "verwijder branch", + }, &i18n.Message{ + ID: "forceDeleteBranch", + Other: "verwijder branch (forceer)", }, &i18n.Message{ ID: "NoBranchesThisRepo", Other: "Geen branches voor deze repo", diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index c0384136b..38fbac4cb 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -140,7 +140,10 @@ func addEnglish(i18nObject *i18n.Bundle) error { Other: "Delete Branch", }, &i18n.Message{ ID: "DeleteBranchMessage", - Other: "Are you sure you want delete the branch {{.selectedBranchName}} ?", + Other: "Are you sure you want to delete the branch {{.selectedBranchName}}?", + }, &i18n.Message{ + ID: "ForceDeleteBranchMessage", + Other: "Are you sure you want to force delete the branch {{.selectedBranchName}}?", }, &i18n.Message{ ID: "CantMergeBranchIntoItself", Other: "You cannot merge a branch into itself", @@ -159,6 +162,9 @@ func addEnglish(i18nObject *i18n.Bundle) error { }, &i18n.Message{ ID: "deleteBranch", Other: "delete branch", + }, &i18n.Message{ + ID: "forceDeleteBranch", + Other: "delete branch (force)", }, &i18n.Message{ ID: "NoBranchesThisRepo", Other: "No branches for this repo", diff --git a/pkg/i18n/i18n.go b/pkg/i18n/i18n.go index e209d55c5..898a13906 100644 --- a/pkg/i18n/i18n.go +++ b/pkg/i18n/i18n.go @@ -1,7 +1,7 @@ package i18n import ( - "github.com/Sirupsen/logrus" + "github.com/sirupsen/logrus" "github.com/cloudfoundry/jibber_jabber" "github.com/nicksnyder/go-i18n/v2/i18n" "golang.org/x/text/language" @@ -18,33 +18,12 @@ type Localizer struct { } // NewLocalizer creates a new Localizer -func NewLocalizer(log *logrus.Logger) (*Localizer, error) { +func NewLocalizer(log *logrus.Logger) *Localizer { + userLang := detectLanguage(jibber_jabber.DetectLanguage) - // detect the user's language - userLang, err := jibber_jabber.DetectLanguage() - if err != nil { - if err.Error() != "Could not detect Language" { - return nil, err - } - userLang = "C" - } log.Info("language: " + userLang) - // create a i18n bundle that can be used to add translations and other things - i18nBundle := &i18n.Bundle{DefaultLanguage: language.English} - - addBundles(log, i18nBundle) - - // return the new localizer that can be used to translate text - i18nLocalizer := i18n.NewLocalizer(i18nBundle, userLang) - - localizer := &Localizer{ - i18nLocalizer: i18nLocalizer, - language: userLang, - Log: log, - } - - return localizer, nil + return setupLocalizer(log, userLang) } // Localize handels the translations @@ -82,17 +61,42 @@ func (l *Localizer) GetLanguage() string { // add translation file(s) func addBundles(log *logrus.Logger, i18nBundle *i18n.Bundle) { - err := addPolish(i18nBundle) - if err != nil { - log.Fatal(err) - } - err = addDutch(i18nBundle) - if err != nil { - log.Fatal(err) - } - err = addEnglish(i18nBundle) - if err != nil { - log.Fatal(err) + fs := []func(*i18n.Bundle) error{ + addPolish, + addDutch, + addEnglish, } + for _, f := range fs { + if err := f(i18nBundle); err != nil { + log.Fatal(err) + + } + } +} + +// detectLanguage extracts user language from environment +func detectLanguage(langDetector func() (string, error)) string { + if userLang, err := langDetector(); err == nil { + return userLang + } + + return "C" +} + +// setupLocalizer creates a new localizer using given userLang +func setupLocalizer(log *logrus.Logger, userLang string) *Localizer { + // create a i18n bundle that can be used to add translations and other things + i18nBundle := &i18n.Bundle{DefaultLanguage: language.English} + + addBundles(log, i18nBundle) + + // return the new localizer that can be used to translate text + i18nLocalizer := i18n.NewLocalizer(i18nBundle, userLang) + + return &Localizer{ + i18nLocalizer: i18nLocalizer, + language: userLang, + Log: log, + } } diff --git a/pkg/i18n/i18n_test.go b/pkg/i18n/i18n_test.go new file mode 100644 index 000000000..5eeddb907 --- /dev/null +++ b/pkg/i18n/i18n_test.go @@ -0,0 +1,81 @@ +package i18n + +import ( + "fmt" + "testing" + + "github.com/nicksnyder/go-i18n/v2/i18n" + + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +func TestNewLocalizer(t *testing.T) { + assert.NotNil(t, NewLocalizer(logrus.New())) +} + +func TestDetectLanguage(t *testing.T) { + type scenario struct { + langDetector func() (string, error) + expected string + } + + scenarios := []scenario{ + { + func() (string, error) { + return "", fmt.Errorf("An error occurred") + }, + "C", + }, + { + func() (string, error) { + return "en", nil + }, + "en", + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, s.expected, detectLanguage(s.langDetector)) + } +} + +func TestLocalizer(t *testing.T) { + type scenario struct { + userLang string + test func(*Localizer) + } + + scenarios := []scenario{ + { + "C", + func(l *Localizer) { + assert.EqualValues(t, "C", l.GetLanguage()) + assert.Equal(t, "Diff", l.Localize(&i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "DiffTitle", + }, + })) + assert.Equal(t, "Diff", l.SLocalize("DiffTitle")) + assert.Equal(t, "Are you sure you want to delete the branch test?", l.TemplateLocalize("DeleteBranchMessage", Teml{"selectedBranchName": "test"})) + }, + }, + { + "nl", + func(l *Localizer) { + assert.EqualValues(t, "nl", l.GetLanguage()) + assert.Equal(t, "Diff", l.Localize(&i18n.LocalizeConfig{ + DefaultMessage: &i18n.Message{ + ID: "DiffTitle", + }, + })) + assert.Equal(t, "Diff", l.SLocalize("DiffTitle")) + assert.Equal(t, "Weet je zeker dat je branch test wil verwijderen?", l.TemplateLocalize("DeleteBranchMessage", Teml{"selectedBranchName": "test"})) + }, + }, + } + + for _, s := range scenarios { + s.test(setupLocalizer(logrus.New(), s.userLang)) + } +} diff --git a/pkg/test/test.go b/pkg/test/test.go index 7bdbd4c10..6346ac556 100644 --- a/pkg/test/test.go +++ b/pkg/test/test.go @@ -4,6 +4,7 @@ import ( "errors" "os" "os/exec" + "path/filepath" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -11,15 +12,20 @@ import ( // GenerateRepo generates a repo from test/repos and changes the directory to be // inside the newly made repo func GenerateRepo(filename string) error { - testPath := utils.GetProjectRoot() + "/test/repos/" + reposDir := "/test/repos/" + testPath := utils.GetProjectRoot() + reposDir + + // workaround for debian packaging + if _, err := os.Stat(testPath); os.IsNotExist(err) { + cwd, _ := os.Getwd() + testPath = filepath.Dir(filepath.Dir(cwd)) + reposDir + } if err := os.Chdir(testPath); err != nil { return err } if output, err := exec.Command("bash", filename).CombinedOutput(); err != nil { return errors.New(string(output)) } - if err := os.Chdir(testPath + "repo"); err != nil { - return err - } - return nil + + return os.Chdir(testPath + "repo") } diff --git a/pkg/updates/updates.go b/pkg/updates/updates.go index dce62bdb4..7e745635c 100644 --- a/pkg/updates/updates.go +++ b/pkg/updates/updates.go @@ -13,10 +13,10 @@ import ( "github.com/kardianos/osext" - "github.com/Sirupsen/logrus" getter "github.com/jesseduffield/go-getter" "github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/config" + "github.com/sirupsen/logrus" ) // Update checks for updates and does updates diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 0333ac45a..511de1af1 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -64,6 +64,13 @@ func TrimTrailingNewline(str string) string { return str } +// NormalizeLinefeeds - Removes all Windows and Mac style line feeds +func NormalizeLinefeeds(str string) string { + str = strings.Replace(str, "\r\n", "\n", -1) + str = strings.Replace(str, "\r", "", -1) + return str +} + // GetProjectRoot returns the path to the root of the project. Only to be used // in testing contexts, as with binaries it's unlikely this path will exist on // the machine diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 58e78cce9..46b264945 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -81,3 +81,36 @@ func TestTrimTrailingNewline(t *testing.T) { assert.EqualValues(t, s.expected, TrimTrailingNewline(s.str)) } } + +func TestNormalizeLinefeeds(t *testing.T) { + type scenario struct { + byteArray []byte + expected []byte + } + var scenarios = []scenario{ + { + // \r\n + []byte{97, 115, 100, 102, 13, 10}, + []byte{97, 115, 100, 102, 10}, + }, + { + // bash\r\nblah + []byte{97, 115, 100, 102, 13, 10, 97, 115, 100, 102}, + []byte{97, 115, 100, 102, 10, 97, 115, 100, 102}, + }, + { + // \r + []byte{97, 115, 100, 102, 13}, + []byte{97, 115, 100, 102}, + }, + { + // \n + []byte{97, 115, 100, 102, 10}, + []byte{97, 115, 100, 102, 10}, + }, + } + + for _, s := range scenarios { + assert.EqualValues(t, string(s.expected), NormalizeLinefeeds(string(s.byteArray))) + } +} diff --git a/test.sh b/test.sh new file mode 100755 index 000000000..a92243cf7 --- /dev/null +++ b/test.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -e +echo "" > coverage.txt + +for d in $( find ./* -maxdepth 10 ! -path "./vendor*" ! -path "./.git*" -type d); do + if ls $d/*.go &> /dev/null; then + go test -v -race -coverprofile=profile.out -covermode=atomic $d + if [ -f profile.out ]; then + cat profile.out >> coverage.txt + rm profile.out + fi + fi +done diff --git a/test/repos/bom.sh b/test/repos/bom.sh new file mode 100755 index 000000000..190f501a1 --- /dev/null +++ b/test/repos/bom.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -ex; rm -rf repo; mkdir repo; cd repo + +git init + +cat <> windowslf.txt +asdf +asdf +EOT + +cat <> linuxlf.txt +asdf +asdf +EOT + +cat <> bomtest.txt +A,B,C,D,E +F,G,H,I,J +K,L,M,N,O +P,Q,R,S,T +U,V,W,X,Y +Z,1,2,3,4 +EOT diff --git a/vendor/github.com/jesseduffield/gocui/gui.go b/vendor/github.com/jesseduffield/gocui/gui.go index 28a52d1cd..26ba79bd6 100644 --- a/vendor/github.com/jesseduffield/gocui/gui.go +++ b/vendor/github.com/jesseduffield/gocui/gui.go @@ -653,17 +653,31 @@ func (g *Gui) onKey(ev *termbox.Event) error { // execKeybindings executes the keybinding handlers that match the passed view // and event. The value of matched is true if there is a match and no errors. func (g *Gui) execKeybindings(v *View, ev *termbox.Event) (matched bool, err error) { - matched = false + var globalKb *keybinding for _, kb := range g.keybindings { if kb.handler == nil { continue } - if kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) && kb.matchView(v) { - if err := kb.handler(g, v); err != nil { - return false, err - } - matched = true + if !kb.matchKeypress(Key(ev.Key), ev.Ch, Modifier(ev.Mod)) { + continue + } + if kb.matchView(v) { + return g.execKeybinding(v, kb) + } + if kb.viewName == "" && (!v.Editable || kb.ch == 0) { + globalKb = kb } } - return matched, nil + if globalKb != nil { + return g.execKeybinding(v, globalKb) + } + return false, nil +} + +// execKeybinding executes a given keybinding +func (g *Gui) execKeybinding(v *View, kb *keybinding) (bool, error) { + if err := kb.handler(g, v); err != nil { + return false, err + } + return true, nil } diff --git a/vendor/github.com/jesseduffield/gocui/keybinding.go b/vendor/github.com/jesseduffield/gocui/keybinding.go index 7efdb75c6..82d1acc9f 100644 --- a/vendor/github.com/jesseduffield/gocui/keybinding.go +++ b/vendor/github.com/jesseduffield/gocui/keybinding.go @@ -38,9 +38,6 @@ func (kb *keybinding) matchView(v *View) bool { if v.Editable == true && kb.ch != 0 { return false } - if kb.viewName == "" { - return true - } return v != nil && kb.viewName == v.name } diff --git a/vendor/github.com/Sirupsen/logrus/LICENSE b/vendor/github.com/sirupsen/logrus/LICENSE similarity index 100% rename from vendor/github.com/Sirupsen/logrus/LICENSE rename to vendor/github.com/sirupsen/logrus/LICENSE diff --git a/vendor/github.com/Sirupsen/logrus/alt_exit.go b/vendor/github.com/sirupsen/logrus/alt_exit.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/alt_exit.go rename to vendor/github.com/sirupsen/logrus/alt_exit.go diff --git a/vendor/github.com/Sirupsen/logrus/doc.go b/vendor/github.com/sirupsen/logrus/doc.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/doc.go rename to vendor/github.com/sirupsen/logrus/doc.go diff --git a/vendor/github.com/Sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/entry.go rename to vendor/github.com/sirupsen/logrus/entry.go diff --git a/vendor/github.com/Sirupsen/logrus/exported.go b/vendor/github.com/sirupsen/logrus/exported.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/exported.go rename to vendor/github.com/sirupsen/logrus/exported.go diff --git a/vendor/github.com/Sirupsen/logrus/formatter.go b/vendor/github.com/sirupsen/logrus/formatter.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/formatter.go rename to vendor/github.com/sirupsen/logrus/formatter.go diff --git a/vendor/github.com/Sirupsen/logrus/hooks.go b/vendor/github.com/sirupsen/logrus/hooks.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/hooks.go rename to vendor/github.com/sirupsen/logrus/hooks.go diff --git a/vendor/github.com/Sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/json_formatter.go rename to vendor/github.com/sirupsen/logrus/json_formatter.go diff --git a/vendor/github.com/Sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/logger.go rename to vendor/github.com/sirupsen/logrus/logger.go diff --git a/vendor/github.com/Sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/logrus.go rename to vendor/github.com/sirupsen/logrus/logrus.go diff --git a/vendor/github.com/Sirupsen/logrus/terminal_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_bsd.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/terminal_bsd.go rename to vendor/github.com/sirupsen/logrus/terminal_bsd.go diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_appengine.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/terminal_check_appengine.go rename to vendor/github.com/sirupsen/logrus/terminal_check_appengine.go diff --git a/vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go b/vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/terminal_check_notappengine.go rename to vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go diff --git a/vendor/github.com/Sirupsen/logrus/terminal_linux.go b/vendor/github.com/sirupsen/logrus/terminal_linux.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/terminal_linux.go rename to vendor/github.com/sirupsen/logrus/terminal_linux.go diff --git a/vendor/github.com/Sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/text_formatter.go rename to vendor/github.com/sirupsen/logrus/text_formatter.go diff --git a/vendor/github.com/Sirupsen/logrus/writer.go b/vendor/github.com/sirupsen/logrus/writer.go similarity index 100% rename from vendor/github.com/Sirupsen/logrus/writer.go rename to vendor/github.com/sirupsen/logrus/writer.go diff --git a/vendor/github.com/spkg/bom/LICENSE.md b/vendor/github.com/spkg/bom/LICENSE.md new file mode 100644 index 000000000..931b189e4 --- /dev/null +++ b/vendor/github.com/spkg/bom/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 John Jeffery + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/spkg/bom/bom.go b/vendor/github.com/spkg/bom/bom.go new file mode 100644 index 000000000..93b811c6d --- /dev/null +++ b/vendor/github.com/spkg/bom/bom.go @@ -0,0 +1,39 @@ +// Package bom is used to clean up UTF-8 Byte Order Marks. +package bom + +import ( + "bufio" + "io" +) + +const ( + bom0 = 0xef + bom1 = 0xbb + bom2 = 0xbf +) + +// Clean returns b with the 3 byte BOM stripped off the front if it is present. +// If the BOM is not present, then b is returned. +func Clean(b []byte) []byte { + if len(b) >= 3 && + b[0] == bom0 && + b[1] == bom1 && + b[2] == bom2 { + return b[3:] + } + return b +} + +// NewReader returns an io.Reader that will skip over initial UTF-8 byte order marks. +func NewReader(r io.Reader) io.Reader { + buf := bufio.NewReader(r) + b, err := buf.Peek(3) + if err != nil { + // not enough bytes + return buf + } + if b[0] == bom0 && b[1] == bom1 && b[2] == bom2 { + discardBytes(buf, 3) + } + return buf +} diff --git a/vendor/github.com/spkg/bom/discard_go14.go b/vendor/github.com/spkg/bom/discard_go14.go new file mode 100644 index 000000000..782cd0624 --- /dev/null +++ b/vendor/github.com/spkg/bom/discard_go14.go @@ -0,0 +1,12 @@ +// +build !go1.5 + +package bom + +import "bufio" + +func discardBytes(buf *bufio.Reader, n int) { + // cannot use the buf.Discard method as it was introduced in Go 1.5 + for i := 0; i < n; i++ { + buf.ReadByte() + } +} diff --git a/vendor/github.com/spkg/bom/discard_go15.go b/vendor/github.com/spkg/bom/discard_go15.go new file mode 100644 index 000000000..2d17d5c5e --- /dev/null +++ b/vendor/github.com/spkg/bom/discard_go15.go @@ -0,0 +1,10 @@ +// +build go1.5 + +package bom + +import "bufio" + +func discardBytes(buf *bufio.Reader, n int) { + // the Discard method was introduced in Go 1.5 + buf.Discard(n) +}