1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-07-30 03:23:08 +03:00

Merge branch 'master' into feature/auto-updates

This commit is contained in:
Jesse Duffield
2018-08-25 11:02:46 +10:00
52 changed files with 441 additions and 121 deletions

View File

@ -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 {

View File

@ -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

View File

@ -5,7 +5,7 @@ import (
"strings"
"testing"
"github.com/Sirupsen/logrus"
"github.com/sirupsen/logrus"
"github.com/jesseduffield/lazygit/pkg/test"
)

View File

@ -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

View File

@ -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 {

View File

@ -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"
)

View File

@ -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"),
})
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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},

View File

@ -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
})

View File

@ -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",

View File

@ -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",

View File

@ -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,
}
}

81
pkg/i18n/i18n_test.go Normal file
View File

@ -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))
}
}

View File

@ -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")
}

View File

@ -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

View File

@ -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

View File

@ -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)))
}
}