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 [](https://goreportcard.com/report/github.com/jesseduffield/lazygit)
+# lazygit [](https://circleci.com/gh/jesseduffield/lazygit) [](https://codecov.io/gh/jesseduffield/lazygit) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](http://godoc.org/github.com/jesseduffield/lazygit) []()
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:
+
+
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)
+}